* Wed Aug 19 2026 Justin M. Forbes <jforbes@fedoraproject.org> [7.1.9-0] - packet: use consistent hard_header_len in non-ring send paths (Qihang Tang) - platform/x86: int3472: Increase handshake GPIO delay to 200 ms (Hans de Goede) - drm/xe: Fix DPT allocation paths. (Junjie Cao) [2488751] - Turn on CONFIG_CRYPTO_MLDSA for Fedora (Justin M. Forbes) - Linux v7.1.9 Resolves: rhbz#2488751 Signed-off-by: Justin M. Forbes <jforbes@fedoraproject.org>
7221 lines
211 KiB
Diff
7221 lines
211 KiB
Diff
MAINTAINERS | 29 +
|
|
Makefile | 30 +
|
|
arch/arm/Kconfig | 4 +-
|
|
arch/arm64/Kconfig | 2 +-
|
|
arch/arm64/kernel/setup.c | 27 +
|
|
arch/s390/include/asm/ipl.h | 1 +
|
|
arch/s390/kernel/ipl.c | 5 +
|
|
arch/s390/kernel/setup.c | 4 +
|
|
arch/x86/kernel/setup.c | 22 +-
|
|
arch/x86/kvm/mmu/mmu.c | 1 +
|
|
crypto/akcipher.c | 3 +-
|
|
crypto/dh.c | 25 +
|
|
crypto/seqiv.c | 15 +-
|
|
crypto/sig.c | 3 +-
|
|
crypto/testmgr.c | 6 +-
|
|
drivers/acpi/apei/hest.c | 8 +
|
|
drivers/acpi/battery.c | 14 +
|
|
drivers/acpi/irq.c | 17 +-
|
|
drivers/acpi/scan.c | 9 +
|
|
drivers/ata/libahci.c | 18 +
|
|
drivers/char/ipmi/ipmi_dmi.c | 15 +
|
|
drivers/char/ipmi/ipmi_msghandler.c | 16 +-
|
|
drivers/char/random.c | 126 ++-
|
|
drivers/firmware/efi/Makefile | 1 +
|
|
drivers/firmware/efi/efi.c | 124 ++-
|
|
drivers/firmware/efi/libstub/fdt.c | 5 +
|
|
drivers/firmware/efi/libstub/secureboot.c | 14 +-
|
|
drivers/firmware/efi/secureboot.c | 38 +
|
|
drivers/gpu/drm/xe/display/xe_fb_pin.c | 33 +-
|
|
drivers/hwtracing/coresight/coresight-etm4x-core.c | 19 +
|
|
drivers/iommu/iommu.c | 22 +
|
|
drivers/media/i2c/Kconfig | 10 +
|
|
drivers/media/i2c/Makefile | 1 +
|
|
drivers/media/i2c/imx471.c | 957 ++++++++++++++++++
|
|
drivers/media/pci/intel/ipu-bridge.c | 41 +
|
|
drivers/media/platform/Kconfig | 1 +
|
|
drivers/media/platform/Makefile | 1 +
|
|
drivers/media/platform/amd/Kconfig | 3 +
|
|
drivers/media/platform/amd/Makefile | 3 +
|
|
drivers/media/platform/amd/isp4/Kconfig | 17 +
|
|
drivers/media/platform/amd/isp4/Makefile | 10 +
|
|
drivers/media/platform/amd/isp4/isp4.c | 240 +++++
|
|
drivers/media/platform/amd/isp4/isp4.h | 20 +
|
|
drivers/media/platform/amd/isp4/isp4_debug.c | 271 +++++
|
|
drivers/media/platform/amd/isp4/isp4_debug.h | 41 +
|
|
drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h | 318 ++++++
|
|
drivers/media/platform/amd/isp4/isp4_hw_reg.h | 124 +++
|
|
drivers/media/platform/amd/isp4/isp4_interface.c | 832 ++++++++++++++++
|
|
drivers/media/platform/amd/isp4/isp4_interface.h | 144 +++
|
|
drivers/media/platform/amd/isp4/isp4_subdev.c | 1047 ++++++++++++++++++++
|
|
drivers/media/platform/amd/isp4/isp4_subdev.h | 127 +++
|
|
drivers/media/platform/amd/isp4/isp4_video.c | 797 +++++++++++++++
|
|
drivers/media/platform/amd/isp4/isp4_video.h | 57 ++
|
|
drivers/pci/quirks.c | 24 +
|
|
drivers/platform/x86/intel/int3472/discrete.c | 28 +-
|
|
drivers/usb/core/hub.c | 7 +
|
|
fs/smb/client/dir.c | 2 +
|
|
fs/xfs/libxfs/xfs_exchmaps.c | 10 +
|
|
include/linux/crypto.h | 2 +
|
|
include/linux/efi.h | 22 +-
|
|
include/linux/lsm_hook_defs.h | 1 +
|
|
include/linux/random.h | 10 +
|
|
include/linux/security.h | 9 +
|
|
kernel/module/signing.c | 9 +-
|
|
scripts/Makefile.lib | 3 +
|
|
scripts/tags.sh | 2 +
|
|
security/integrity/platform_certs/load_uefi.c | 6 +-
|
|
security/lockdown/Kconfig | 13 +
|
|
security/lockdown/lockdown.c | 11 +
|
|
sound/soc/codecs/rt722-sdca.c | 77 +-
|
|
sound/soc/codecs/rt722-sdca.h | 4 +
|
|
tools/testing/selftests/bpf/DENYLIST.rhel | 76 ++
|
|
tools/testing/selftests/bpf/Makefile | 2 +-
|
|
tools/testing/selftests/bpf/prog_tests/ksyms_btf.c | 31 -
|
|
74 files changed, 5914 insertions(+), 153 deletions(-)
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 624cfb6cf1d40..f9bcd152b09b4 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -1159,6 +1159,29 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git
|
|
F: drivers/iommu/amd/
|
|
F: include/linux/amd-iommu.h
|
|
|
|
+AMD ISP4 DRIVER
|
|
+M: Bin Du <bin.du@amd.com>
|
|
+M: Nirujogi Pratap <pratap.nirujogi@amd.com>
|
|
+L: linux-media@vger.kernel.org
|
|
+S: Maintained
|
|
+T: git git://linuxtv.org/media.git
|
|
+F: drivers/media/platform/amd/Kconfig
|
|
+F: drivers/media/platform/amd/Makefile
|
|
+F: drivers/media/platform/amd/isp4/Kconfig
|
|
+F: drivers/media/platform/amd/isp4/Makefile
|
|
+F: drivers/media/platform/amd/isp4/isp4.c
|
|
+F: drivers/media/platform/amd/isp4/isp4.h
|
|
+F: drivers/media/platform/amd/isp4/isp4_debug.c
|
|
+F: drivers/media/platform/amd/isp4/isp4_debug.h
|
|
+F: drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h
|
|
+F: drivers/media/platform/amd/isp4/isp4_hw_reg.h
|
|
+F: drivers/media/platform/amd/isp4/isp4_interface.c
|
|
+F: drivers/media/platform/amd/isp4/isp4_interface.h
|
|
+F: drivers/media/platform/amd/isp4/isp4_subdev.c
|
|
+F: drivers/media/platform/amd/isp4/isp4_subdev.h
|
|
+F: drivers/media/platform/amd/isp4/isp4_video.c
|
|
+F: drivers/media/platform/amd/isp4/isp4_video.h
|
|
+
|
|
AMD KFD
|
|
M: Felix Kuehling <Felix.Kuehling@amd.com>
|
|
L: amd-gfx@lists.freedesktop.org
|
|
@@ -24969,6 +24992,12 @@ T: git git://linuxtv.org/media.git
|
|
F: Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml
|
|
F: drivers/media/i2c/imx415.c
|
|
|
|
+SONY IMX471 SENSOR DRIVER
|
|
+M: Kate Hsuan <hpa@redhat.com>
|
|
+L: linux-media@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/media/i2c/imx471.c
|
|
+
|
|
SONY MEMORYSTICK SUBSYSTEM
|
|
M: Maxim Levitsky <maximlevitsky@gmail.com>
|
|
M: Alex Dubov <oakad@yahoo.com>
|
|
diff --git a/Makefile b/Makefile
|
|
index d7cca59812d85..43cf48259d3f8 100644
|
|
--- a/Makefile
|
|
+++ b/Makefile
|
|
@@ -356,6 +356,17 @@ ifneq ($(filter install,$(MAKECMDGOALS)),)
|
|
endif
|
|
endif
|
|
|
|
+# CKI/cross compilation hack
|
|
+# Do we need to rebuild scripts after cross compilation?
|
|
+# If kernel was cross-compiled, these scripts have arch of build host.
|
|
+REBUILD_SCRIPTS_FOR_CROSS:=0
|
|
+
|
|
+# Regenerating config with incomplete source tree will produce different
|
|
+# config options. Disable it.
|
|
+ifeq ($(REBUILD_SCRIPTS_FOR_CROSS),1)
|
|
+may-sync-config:=
|
|
+endif
|
|
+
|
|
ifdef mixed-build
|
|
# ===========================================================================
|
|
# We're called with mixed targets (*config and build targets).
|
|
@@ -1421,6 +1432,8 @@ uapi-asm-generic:
|
|
# Generate some files
|
|
# ---------------------------------------------------------------------------
|
|
|
|
+include $(srctree)/Makefile.rhelver
|
|
+
|
|
# KERNELRELEASE can change from a few different places, meaning version.h
|
|
# needs to be updated, so this check is forced on all builds
|
|
|
|
@@ -2086,6 +2099,23 @@ endif
|
|
|
|
ifdef CONFIG_MODULES
|
|
|
|
+scripts_build:
|
|
+ $(MAKE) $(build)=scripts/basic
|
|
+ $(MAKE) $(build)=scripts/mod
|
|
+ $(MAKE) $(build)=scripts scripts/module.lds
|
|
+ $(MAKE) $(build)=scripts scripts/unifdef
|
|
+ $(MAKE) $(build)=scripts
|
|
+
|
|
+prepare_after_cross:
|
|
+ # disable STACK_VALIDATION to avoid building objtool
|
|
+ sed -i '/^CONFIG_STACK_VALIDATION/d' ./include/config/auto.conf || true
|
|
+ # build minimum set of scripts and resolve_btfids to allow building
|
|
+ # external modules
|
|
+ $(MAKE) KBUILD_EXTMOD="" M="" scripts_build V=1
|
|
+ $(MAKE) -C tools/bpf/resolve_btfids
|
|
+
|
|
+PHONY += prepare_after_cross scripts_build
|
|
+
|
|
modules.order: $(build-dir)
|
|
@:
|
|
|
|
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
|
|
index 73e6647bea463..32b90d43664c4 100644
|
|
--- a/arch/arm/Kconfig
|
|
+++ b/arch/arm/Kconfig
|
|
@@ -1214,9 +1214,9 @@ config HIGHMEM
|
|
If unsure, say n.
|
|
|
|
config HIGHPTE
|
|
- bool "Allocate 2nd-level pagetables from highmem" if EXPERT
|
|
+ bool "Allocate 2nd-level pagetables from highmem"
|
|
depends on HIGHMEM && !PREEMPT_RT
|
|
- default y
|
|
+ default n
|
|
help
|
|
The VM uses one page of physical memory for each page table.
|
|
For systems with a lot of processes, this can use a lot of
|
|
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
|
|
index 10c69474f2761..96190d176bc86 100644
|
|
--- a/arch/arm64/Kconfig
|
|
+++ b/arch/arm64/Kconfig
|
|
@@ -1470,7 +1470,7 @@ endchoice
|
|
|
|
config ARM64_FORCE_52BIT
|
|
bool "Force 52-bit virtual addresses for userspace"
|
|
- depends on ARM64_VA_BITS_52 && EXPERT
|
|
+ depends on ARM64_VA_BITS_52
|
|
help
|
|
For systems with 52-bit userspace VAs enabled, the kernel will attempt
|
|
to maintain compatibility with older software by providing 48-bit VAs
|
|
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
|
|
index 23c05dc7a8f2a..d7b7b2f39e169 100644
|
|
--- a/arch/arm64/kernel/setup.c
|
|
+++ b/arch/arm64/kernel/setup.c
|
|
@@ -32,6 +32,8 @@
|
|
#include <linux/sched/task.h>
|
|
#include <linux/scs.h>
|
|
#include <linux/mm.h>
|
|
+#include <linux/security.h>
|
|
+#include <linux/libfdt.h>
|
|
|
|
#include <asm/acpi.h>
|
|
#include <asm/fixmap.h>
|
|
@@ -207,6 +209,24 @@ static void __init setup_machine_fdt(phys_addr_t dt_phys)
|
|
dump_stack_set_arch_desc("%s (DT)", name);
|
|
}
|
|
|
|
+static void __init init_secureboot_mode(void)
|
|
+{
|
|
+ void *fdt = initial_boot_params;
|
|
+ u64 chosen;
|
|
+ const __be32 *prop;
|
|
+ int len;
|
|
+
|
|
+ chosen = fdt_path_offset(fdt, "/chosen");
|
|
+ if (chosen < 0)
|
|
+ return;
|
|
+
|
|
+ prop = fdt_getprop(fdt, chosen, "secure-boot-mode", &len);
|
|
+ if (!prop || len != sizeof(u32))
|
|
+ return;
|
|
+
|
|
+ efi_set_secure_boot((enum efi_secureboot_mode)fdt32_to_cpu(*prop));
|
|
+}
|
|
+
|
|
static void __init request_standard_resources(void)
|
|
{
|
|
struct memblock_region *region;
|
|
@@ -327,6 +347,13 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p)
|
|
pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!");
|
|
WARN_TAINT(mmu_enabled_at_boot, TAINT_FIRMWARE_WORKAROUND,
|
|
FW_BUG "Booted with MMU enabled!");
|
|
+ } else {
|
|
+ init_secureboot_mode();
|
|
+
|
|
+#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT
|
|
+ if (efi_enabled(EFI_SECURE_BOOT))
|
|
+ security_lock_kernel_down("EFI Secure Boot mode", LOCKDOWN_INTEGRITY_MAX);
|
|
+#endif
|
|
}
|
|
|
|
arm64_memblock_init();
|
|
diff --git a/arch/s390/include/asm/ipl.h b/arch/s390/include/asm/ipl.h
|
|
index b0d00032479d6..afb9544fb0074 100644
|
|
--- a/arch/s390/include/asm/ipl.h
|
|
+++ b/arch/s390/include/asm/ipl.h
|
|
@@ -139,6 +139,7 @@ int ipl_report_add_component(struct ipl_report *report, struct kexec_buf *kbuf,
|
|
unsigned char flags, unsigned short cert);
|
|
int ipl_report_add_certificate(struct ipl_report *report, void *key,
|
|
unsigned long addr, unsigned long len);
|
|
+bool ipl_get_secureboot(void);
|
|
|
|
/*
|
|
* DIAG 308 support
|
|
diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c
|
|
index 3c346b02ceb95..30e16a0d4490c 100644
|
|
--- a/arch/s390/kernel/ipl.c
|
|
+++ b/arch/s390/kernel/ipl.c
|
|
@@ -2527,3 +2527,8 @@ int ipl_report_free(struct ipl_report *report)
|
|
}
|
|
|
|
#endif
|
|
+
|
|
+bool ipl_get_secureboot(void)
|
|
+{
|
|
+ return !!ipl_secure_flag;
|
|
+}
|
|
diff --git a/arch/s390/kernel/setup.c b/arch/s390/kernel/setup.c
|
|
index b60284328fe3d..e4d37b08ca295 100644
|
|
--- a/arch/s390/kernel/setup.c
|
|
+++ b/arch/s390/kernel/setup.c
|
|
@@ -47,6 +47,7 @@
|
|
#include <linux/crash_dump.h>
|
|
#include <linux/memory.h>
|
|
#include <linux/start_kernel.h>
|
|
+#include <linux/security.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/kmemleak.h>
|
|
|
|
@@ -917,6 +918,9 @@ void __init setup_arch(char **cmdline_p)
|
|
|
|
log_component_list();
|
|
|
|
+ if (ipl_get_secureboot())
|
|
+ security_lock_kernel_down("Secure IPL mode", LOCKDOWN_INTEGRITY_MAX);
|
|
+
|
|
/* Have one command line that is parsed and saved in /proc/cmdline */
|
|
/* boot_command_line has been already set up in early.c */
|
|
*cmdline_p = boot_command_line;
|
|
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
|
|
index 46882ce79c3a4..9c73814c31e04 100644
|
|
--- a/arch/x86/kernel/setup.c
|
|
+++ b/arch/x86/kernel/setup.c
|
|
@@ -21,6 +21,7 @@
|
|
#include <linux/pci.h>
|
|
#include <linux/random.h>
|
|
#include <linux/root_dev.h>
|
|
+#include <linux/security.h>
|
|
#include <linux/static_call.h>
|
|
#include <linux/sysfb.h>
|
|
#include <linux/swiotlb.h>
|
|
@@ -995,6 +996,13 @@ void __init setup_arch(char **cmdline_p)
|
|
if (efi_enabled(EFI_BOOT))
|
|
efi_init();
|
|
|
|
+ efi_set_secure_boot(boot_params.secure_boot);
|
|
+
|
|
+#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT
|
|
+ if (efi_enabled(EFI_SECURE_BOOT))
|
|
+ security_lock_kernel_down("EFI Secure Boot mode", LOCKDOWN_INTEGRITY_MAX);
|
|
+#endif
|
|
+
|
|
reserve_ibft_region();
|
|
x86_init.resources.dmi_setup();
|
|
|
|
@@ -1156,19 +1164,7 @@ void __init setup_arch(char **cmdline_p)
|
|
/* Allocate bigger log buffer */
|
|
setup_log_buf(1);
|
|
|
|
- if (efi_enabled(EFI_BOOT)) {
|
|
- switch (boot_params.secure_boot) {
|
|
- case efi_secureboot_mode_disabled:
|
|
- pr_info("Secure boot disabled\n");
|
|
- break;
|
|
- case efi_secureboot_mode_enabled:
|
|
- pr_info("Secure boot enabled\n");
|
|
- break;
|
|
- default:
|
|
- pr_info("Secure boot could not be determined\n");
|
|
- break;
|
|
- }
|
|
- }
|
|
+ efi_set_secure_boot(boot_params.secure_boot);
|
|
|
|
reserve_initrd();
|
|
|
|
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
|
|
index 0e918f35bfbea..a603eecc2dcbd 100644
|
|
--- a/arch/x86/kvm/mmu/mmu.c
|
|
+++ b/arch/x86/kvm/mmu/mmu.c
|
|
@@ -2638,6 +2638,7 @@ static int mmu_page_zap_pte(struct kvm *kvm, struct kvm_mmu_page *sp,
|
|
*/
|
|
if (tdp_enabled && invalid_list &&
|
|
child->role.guest_mode &&
|
|
+ !child->root_count &&
|
|
!atomic_long_read(&child->parent_ptes.val))
|
|
return kvm_mmu_prepare_zap_page(kvm, child,
|
|
invalid_list);
|
|
diff --git a/crypto/akcipher.c b/crypto/akcipher.c
|
|
index dfe87b3ce1837..cd1302ca3b368 100644
|
|
--- a/crypto/akcipher.c
|
|
+++ b/crypto/akcipher.c
|
|
@@ -140,8 +140,7 @@ int crypto_register_akcipher(struct akcipher_alg *alg)
|
|
|
|
if (!alg->encrypt)
|
|
alg->encrypt = akcipher_default_op;
|
|
- if (!alg->decrypt)
|
|
- alg->decrypt = akcipher_default_op;
|
|
+ alg->decrypt = akcipher_default_op;
|
|
if (!alg->set_priv_key)
|
|
alg->set_priv_key = akcipher_default_set_key;
|
|
|
|
diff --git a/crypto/dh.c b/crypto/dh.c
|
|
index 7ad4768716c89..01ccc24db074a 100644
|
|
--- a/crypto/dh.c
|
|
+++ b/crypto/dh.c
|
|
@@ -227,10 +227,35 @@ static int dh_compute_value(struct kpp_request *req)
|
|
|
|
/* SP800-56A rev 3 5.6.2.1.3 key check */
|
|
} else {
|
|
+ MPI val_pct;
|
|
+
|
|
if (dh_is_pubkey_valid(ctx, val)) {
|
|
ret = -EAGAIN;
|
|
goto err_free_val;
|
|
}
|
|
+
|
|
+ /*
|
|
+ * SP800-56Arev3, 5.6.2.1.4: ("Owner Assurance
|
|
+ * of Pair-wise Consistency"): recompute the
|
|
+ * public key and check if the results match.
|
|
+ */
|
|
+ val_pct = mpi_alloc(0);
|
|
+ if (!val_pct) {
|
|
+ ret = -ENOMEM;
|
|
+ goto err_free_val;
|
|
+ }
|
|
+
|
|
+ ret = _compute_val(ctx, base, val_pct);
|
|
+ if (ret) {
|
|
+ mpi_free(val_pct);
|
|
+ goto err_free_val;
|
|
+ }
|
|
+
|
|
+ if (mpi_cmp(val, val_pct) != 0) {
|
|
+ fips_fail_notify();
|
|
+ panic("dh: pair-wise consistency test failed\n");
|
|
+ }
|
|
+ mpi_free(val_pct);
|
|
}
|
|
}
|
|
|
|
diff --git a/crypto/seqiv.c b/crypto/seqiv.c
|
|
index 678bb4145d783..fcc059e7d3db5 100644
|
|
--- a/crypto/seqiv.c
|
|
+++ b/crypto/seqiv.c
|
|
@@ -123,6 +123,19 @@ static int seqiv_aead_decrypt(struct aead_request *req)
|
|
return crypto_aead_decrypt(subreq);
|
|
}
|
|
|
|
+static int aead_init_seqiv(struct crypto_aead *aead)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = aead_init_geniv(aead);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ crypto_aead_set_flags(aead, CRYPTO_TFM_FIPS_COMPLIANCE);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int seqiv_aead_create(struct crypto_template *tmpl, struct rtattr **tb)
|
|
{
|
|
struct aead_instance *inst;
|
|
@@ -140,7 +153,7 @@ static int seqiv_aead_create(struct crypto_template *tmpl, struct rtattr **tb)
|
|
inst->alg.encrypt = seqiv_aead_encrypt;
|
|
inst->alg.decrypt = seqiv_aead_decrypt;
|
|
|
|
- inst->alg.init = aead_init_geniv;
|
|
+ inst->alg.init = aead_init_seqiv;
|
|
inst->alg.exit = aead_exit_geniv;
|
|
|
|
inst->alg.base.cra_ctxsize = sizeof(struct aead_geniv_ctx);
|
|
diff --git a/crypto/sig.c b/crypto/sig.c
|
|
index beba745b64057..fd41f6d3abf9a 100644
|
|
--- a/crypto/sig.c
|
|
+++ b/crypto/sig.c
|
|
@@ -112,8 +112,7 @@ static int sig_prepare_alg(struct sig_alg *alg)
|
|
{
|
|
struct crypto_alg *base = &alg->base;
|
|
|
|
- if (!alg->sign)
|
|
- alg->sign = sig_default_sign;
|
|
+ alg->sign = sig_default_sign;
|
|
if (!alg->verify)
|
|
alg->verify = sig_default_verify;
|
|
if (!alg->set_priv_key)
|
|
diff --git a/crypto/testmgr.c b/crypto/testmgr.c
|
|
index 4d86efae65b21..31c6372c50ddd 100644
|
|
--- a/crypto/testmgr.c
|
|
+++ b/crypto/testmgr.c
|
|
@@ -3843,7 +3843,7 @@ static int test_akcipher_one(struct crypto_akcipher *tfm,
|
|
* Don't invoke decrypt test which requires a private key
|
|
* for vectors with only a public key.
|
|
*/
|
|
- if (vecs->public_key_vec) {
|
|
+ if (1 || vecs->public_key_vec) {
|
|
err = 0;
|
|
goto free_all;
|
|
}
|
|
@@ -3979,7 +3979,7 @@ static int test_sig_one(struct crypto_sig *tfm, const struct sig_testvec *vecs)
|
|
* Don't invoke sign test (which requires a private key)
|
|
* for vectors with only a public key.
|
|
*/
|
|
- if (vecs->public_key_vec)
|
|
+ if (1 || vecs->public_key_vec)
|
|
return 0;
|
|
|
|
sig_size = crypto_sig_maxsize(tfm);
|
|
@@ -4909,14 +4909,12 @@ static const struct alg_test_desc alg_test_descs[] = {
|
|
}, {
|
|
.alg = "ecdh-nist-p256",
|
|
.test = alg_test_kpp,
|
|
- .fips_allowed = 1,
|
|
.suite = {
|
|
.kpp = __VECS(ecdh_p256_tv_template)
|
|
}
|
|
}, {
|
|
.alg = "ecdh-nist-p384",
|
|
.test = alg_test_kpp,
|
|
- .fips_allowed = 1,
|
|
.suite = {
|
|
.kpp = __VECS(ecdh_p384_tv_template)
|
|
}
|
|
diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c
|
|
index 20d757687e3d9..90a13f20f052b 100644
|
|
--- a/drivers/acpi/apei/hest.c
|
|
+++ b/drivers/acpi/apei/hest.c
|
|
@@ -142,6 +142,14 @@ static int apei_hest_parse(apei_hest_func_t func, void *data)
|
|
if (hest_disable || !hest_tab)
|
|
return -EINVAL;
|
|
|
|
+#ifdef CONFIG_ARM64
|
|
+ /* Ignore broken firmware */
|
|
+ if (!strncmp(hest_tab->header.oem_id, "HPE ", 6) &&
|
|
+ !strncmp(hest_tab->header.oem_table_id, "ProLiant", 8) &&
|
|
+ MIDR_IMPLEMENTOR(read_cpuid_id()) == ARM_CPU_IMP_APM)
|
|
+ return -EINVAL;
|
|
+#endif
|
|
+
|
|
hest_hdr = (struct acpi_hest_header *)(hest_tab + 1);
|
|
for (i = 0; i < hest_tab->error_source_count; i++) {
|
|
len = hest_esrc_len(hest_hdr);
|
|
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
|
|
index b82dd67d98c9a..85ac301e1834a 100644
|
|
--- a/drivers/acpi/battery.c
|
|
+++ b/drivers/acpi/battery.c
|
|
@@ -10,6 +10,7 @@
|
|
|
|
#define pr_fmt(fmt) "ACPI: battery: " fmt
|
|
|
|
+#include <linux/ctype.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/jiffies.h>
|
|
@@ -483,6 +484,15 @@ static int acpi_battery_get_status(struct acpi_battery *battery)
|
|
return 0;
|
|
}
|
|
|
|
+static void acpi_battery_clean_unprintable_chars(char *str, size_t length)
|
|
+{
|
|
+ for (unsigned int i = 0; i < length; i++) {
|
|
+ if (!isascii(str[i]) || !isprint(str[i])) {
|
|
+ str[i] = '\0';
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+}
|
|
|
|
static int extract_battery_info(const int use_bix,
|
|
struct acpi_battery *battery,
|
|
@@ -524,6 +534,10 @@ static int extract_battery_info(const int use_bix,
|
|
battery->capacity_now > battery->full_charge_capacity)
|
|
battery->capacity_now = battery->full_charge_capacity;
|
|
|
|
+ if (!result)
|
|
+ acpi_battery_clean_unprintable_chars(battery->model_number,
|
|
+ ARRAY_SIZE(battery->model_number));
|
|
+
|
|
return result;
|
|
}
|
|
|
|
diff --git a/drivers/acpi/irq.c b/drivers/acpi/irq.c
|
|
index d1595156c86a4..9485212f1a6d6 100644
|
|
--- a/drivers/acpi/irq.c
|
|
+++ b/drivers/acpi/irq.c
|
|
@@ -143,6 +143,7 @@ struct acpi_irq_parse_one_ctx {
|
|
unsigned int index;
|
|
unsigned long *res_flags;
|
|
struct irq_fwspec *fwspec;
|
|
+ bool skip_producer_check;
|
|
};
|
|
|
|
/**
|
|
@@ -216,7 +217,8 @@ static acpi_status acpi_irq_parse_one_cb(struct acpi_resource *ares,
|
|
return AE_CTRL_TERMINATE;
|
|
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
|
|
eirq = &ares->data.extended_irq;
|
|
- if (eirq->producer_consumer == ACPI_PRODUCER)
|
|
+ if (!ctx->skip_producer_check &&
|
|
+ eirq->producer_consumer == ACPI_PRODUCER)
|
|
return AE_OK;
|
|
if (ctx->index >= eirq->interrupt_count) {
|
|
ctx->index -= eirq->interrupt_count;
|
|
@@ -252,8 +254,19 @@ static acpi_status acpi_irq_parse_one_cb(struct acpi_resource *ares,
|
|
static int acpi_irq_parse_one(acpi_handle handle, unsigned int index,
|
|
struct irq_fwspec *fwspec, unsigned long *flags)
|
|
{
|
|
- struct acpi_irq_parse_one_ctx ctx = { -EINVAL, index, flags, fwspec };
|
|
+ struct acpi_irq_parse_one_ctx ctx = { -EINVAL, index, flags, fwspec, false };
|
|
|
|
+ /*
|
|
+ * Firmware on arm64-based HPE m400 platform incorrectly marks
|
|
+ * its UART interrupt as ACPI_PRODUCER rather than ACPI_CONSUMER.
|
|
+ * Don't do the producer/consumer check for that device.
|
|
+ */
|
|
+ if (IS_ENABLED(CONFIG_ARM64)) {
|
|
+ struct acpi_device *adev = acpi_get_acpi_dev(handle);
|
|
+
|
|
+ if (adev && !strcmp(acpi_device_hid(adev), "APMC0D08"))
|
|
+ ctx.skip_producer_check = true;
|
|
+ }
|
|
acpi_walk_resources(handle, METHOD_NAME__CRS, acpi_irq_parse_one_cb, &ctx);
|
|
return ctx.rc;
|
|
}
|
|
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
|
|
index 530547cda8b28..dc046780c74dd 100644
|
|
--- a/drivers/acpi/scan.c
|
|
+++ b/drivers/acpi/scan.c
|
|
@@ -1792,6 +1792,15 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device)
|
|
if (!acpi_match_device_ids(device, ignore_serial_bus_ids))
|
|
return false;
|
|
|
|
+ /*
|
|
+ * Firmware on some arm64 X-Gene platforms will make the UART
|
|
+ * device appear as both a UART and a slave of that UART. Just
|
|
+ * bail out here for X-Gene UARTs.
|
|
+ */
|
|
+ if (IS_ENABLED(CONFIG_ARM64) &&
|
|
+ !strcmp(acpi_device_hid(device), "APMC0D08"))
|
|
+ return false;
|
|
+
|
|
INIT_LIST_HEAD(&resource_list);
|
|
acpi_dev_get_resources(device, &resource_list,
|
|
acpi_check_serial_bus_slave,
|
|
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
|
|
index c79abdfcd7a9b..e23bfb7f94c72 100644
|
|
--- a/drivers/ata/libahci.c
|
|
+++ b/drivers/ata/libahci.c
|
|
@@ -731,6 +731,24 @@ int ahci_stop_engine(struct ata_port *ap)
|
|
tmp &= ~PORT_CMD_START;
|
|
writel(tmp, port_mmio + PORT_CMD);
|
|
|
|
+#ifdef CONFIG_ARM64
|
|
+ /* Rev Ax of Cavium CN99XX needs a hack for port stop */
|
|
+ if (dev_is_pci(ap->host->dev) &&
|
|
+ to_pci_dev(ap->host->dev)->vendor == 0x14e4 &&
|
|
+ to_pci_dev(ap->host->dev)->device == 0x9027 &&
|
|
+ midr_is_cpu_model_range(read_cpuid_id(),
|
|
+ MIDR_CPU_MODEL(ARM_CPU_IMP_BRCM, BRCM_CPU_PART_VULCAN),
|
|
+ MIDR_CPU_VAR_REV(0, 0),
|
|
+ MIDR_CPU_VAR_REV(0, MIDR_REVISION_MASK))) {
|
|
+ tmp = readl(hpriv->mmio + 0x8000);
|
|
+ udelay(100);
|
|
+ writel(tmp | (1 << 26), hpriv->mmio + 0x8000);
|
|
+ udelay(100);
|
|
+ writel(tmp & ~(1 << 26), hpriv->mmio + 0x8000);
|
|
+ dev_warn(ap->host->dev, "CN99XX SATA reset workaround applied\n");
|
|
+ }
|
|
+#endif
|
|
+
|
|
/* wait for engine to stop. This could be as long as 500 msec */
|
|
tmp = ata_wait_register(ap, port_mmio + PORT_CMD,
|
|
PORT_CMD_LIST_ON, PORT_CMD_LIST_ON, 1, 500);
|
|
diff --git a/drivers/char/ipmi/ipmi_dmi.c b/drivers/char/ipmi/ipmi_dmi.c
|
|
index 505e32911c34c..26f7ee0ccf406 100644
|
|
--- a/drivers/char/ipmi/ipmi_dmi.c
|
|
+++ b/drivers/char/ipmi/ipmi_dmi.c
|
|
@@ -215,6 +215,21 @@ static int __init scan_for_dmi_ipmi(void)
|
|
{
|
|
const struct dmi_device *dev = NULL;
|
|
|
|
+#ifdef CONFIG_ARM64
|
|
+ /* RHEL-only
|
|
+ * If this is ARM-based HPE m400, return now, because that platform
|
|
+ * reports the host-side ipmi address as intel port-io space, which
|
|
+ * does not exist in the ARM architecture.
|
|
+ */
|
|
+ const char *dmistr = dmi_get_system_info(DMI_PRODUCT_NAME);
|
|
+
|
|
+ if (dmistr && (strcmp("ProLiant m400 Server", dmistr) == 0)) {
|
|
+ pr_debug("%s does not support host ipmi\n", dmistr);
|
|
+ return 0;
|
|
+ }
|
|
+ /* END RHEL-only */
|
|
+#endif
|
|
+
|
|
while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev)))
|
|
dmi_decode_ipmi((const struct dmi_header *) dev->device_data);
|
|
|
|
diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
|
|
index 3e0b7d60269d9..8317d264426af 100644
|
|
--- a/drivers/char/ipmi/ipmi_msghandler.c
|
|
+++ b/drivers/char/ipmi/ipmi_msghandler.c
|
|
@@ -34,6 +34,7 @@
|
|
#include <linux/uuid.h>
|
|
#include <linux/nospec.h>
|
|
#include <linux/vmalloc.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/delay.h>
|
|
|
|
#define IPMI_DRIVER_VERSION "39.2"
|
|
@@ -5649,8 +5650,21 @@ static int __init ipmi_init_msghandler_mod(void)
|
|
{
|
|
int rv;
|
|
|
|
- pr_info("version " IPMI_DRIVER_VERSION "\n");
|
|
+#ifdef CONFIG_ARM64
|
|
+ /* RHEL-only
|
|
+ * If this is ARM-based HPE m400, return now, because that platform
|
|
+ * reports the host-side ipmi address as intel port-io space, which
|
|
+ * does not exist in the ARM architecture.
|
|
+ */
|
|
+ const char *dmistr = dmi_get_system_info(DMI_PRODUCT_NAME);
|
|
|
|
+ if (dmistr && (strcmp("ProLiant m400 Server", dmistr) == 0)) {
|
|
+ pr_debug("%s does not support host ipmi\n", dmistr);
|
|
+ return -ENOSYS;
|
|
+ }
|
|
+ /* END RHEL-only */
|
|
+#endif
|
|
+ pr_info("version " IPMI_DRIVER_VERSION "\n");
|
|
mutex_lock(&ipmi_interfaces_mutex);
|
|
rv = ipmi_register_driver();
|
|
mutex_unlock(&ipmi_interfaces_mutex);
|
|
diff --git a/drivers/char/random.c b/drivers/char/random.c
|
|
index b4da1fb976c11..1e112e2b405b2 100644
|
|
--- a/drivers/char/random.c
|
|
+++ b/drivers/char/random.c
|
|
@@ -51,9 +51,11 @@
|
|
#include <linux/completion.h>
|
|
#include <linux/uuid.h>
|
|
#include <linux/uaccess.h>
|
|
+#include <linux/rcupdate.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/siphash.h>
|
|
#include <linux/sched/isolation.h>
|
|
+#include <linux/fips.h>
|
|
#include <crypto/chacha.h>
|
|
#include <crypto/blake2s.h>
|
|
#include <vdso/datapage.h>
|
|
@@ -320,6 +322,11 @@ static void crng_fast_key_erasure(u8 key[CHACHA_KEY_SIZE],
|
|
memzero_explicit(first_block, sizeof(first_block));
|
|
}
|
|
|
|
+/*
|
|
+ * Hook for external RNG.
|
|
+ */
|
|
+static const struct random_extrng __rcu *extrng;
|
|
+
|
|
/*
|
|
* This function returns a ChaCha state that you may use for generating
|
|
* random data. It also returns up to 32 bytes on its own of random data
|
|
@@ -732,7 +739,8 @@ static void __cold _credit_init_bits(size_t bits)
|
|
queue_work(system_dfl_wq, &set_ready);
|
|
atomic_notifier_call_chain(&random_ready_notifier, 0, NULL);
|
|
if (IS_ENABLED(CONFIG_VDSO_GETRANDOM))
|
|
- WRITE_ONCE(vdso_k_rng_data->is_ready, true);
|
|
+ if (!fips_enabled)
|
|
+ WRITE_ONCE(vdso_k_rng_data->is_ready, true);
|
|
wake_up_interruptible(&crng_init_wait);
|
|
kill_fasync(&fasync, SIGIO, POLL_IN);
|
|
pr_notice("crng init done\n");
|
|
@@ -751,6 +759,9 @@ static void __cold _credit_init_bits(size_t bits)
|
|
}
|
|
|
|
|
|
+static const struct file_operations extrng_random_fops;
|
|
+static const struct file_operations extrng_urandom_fops;
|
|
+
|
|
/**********************************************************************
|
|
*
|
|
* Entropy collection routines.
|
|
@@ -967,6 +978,19 @@ void __init add_bootloader_randomness(const void *buf, size_t len)
|
|
credit_init_bits(len * 8);
|
|
}
|
|
|
|
+void random_register_extrng(const struct random_extrng *rng)
|
|
+{
|
|
+ rcu_assign_pointer(extrng, rng);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(random_register_extrng);
|
|
+
|
|
+void random_unregister_extrng(void)
|
|
+{
|
|
+ RCU_INIT_POINTER(extrng, NULL);
|
|
+ synchronize_rcu();
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(random_unregister_extrng);
|
|
+
|
|
#if IS_ENABLED(CONFIG_VMGENID)
|
|
static BLOCKING_NOTIFIER_HEAD(vmfork_chain);
|
|
|
|
@@ -1381,6 +1405,7 @@ SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags
|
|
{
|
|
struct iov_iter iter;
|
|
int ret;
|
|
+ const struct random_extrng *rng;
|
|
|
|
if (flags & ~(GRND_NONBLOCK | GRND_RANDOM | GRND_INSECURE))
|
|
return -EINVAL;
|
|
@@ -1392,6 +1417,21 @@ SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags
|
|
if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM))
|
|
return -EINVAL;
|
|
|
|
+ rcu_read_lock();
|
|
+ rng = rcu_dereference(extrng);
|
|
+ if (rng && !try_module_get(rng->owner))
|
|
+ rng = NULL;
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ if (rng) {
|
|
+ ret = import_ubuf(ITER_DEST, ubuf, len, &iter);
|
|
+ if (unlikely(ret))
|
|
+ return ret;
|
|
+ ret = rng->extrng_read_iter(&iter, !!(flags & GRND_RANDOM));
|
|
+ module_put(rng->owner);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
if (!crng_ready() && !(flags & GRND_INSECURE)) {
|
|
if (flags & GRND_NONBLOCK)
|
|
return -EAGAIN;
|
|
@@ -1412,6 +1452,12 @@ static __poll_t random_poll(struct file *file, poll_table *wait)
|
|
return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM;
|
|
}
|
|
|
|
+static __poll_t extrng_poll(struct file *file, poll_table * wait)
|
|
+{
|
|
+ /* extrng pool is always full, always read, no writes */
|
|
+ return EPOLLIN | EPOLLRDNORM;
|
|
+}
|
|
+
|
|
static ssize_t write_pool_user(struct iov_iter *iter)
|
|
{
|
|
u8 block[BLAKE2S_BLOCK_SIZE];
|
|
@@ -1552,7 +1598,58 @@ static int random_fasync(int fd, struct file *filp, int on)
|
|
return fasync_helper(fd, filp, on, &fasync);
|
|
}
|
|
|
|
+static int random_open(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ const struct random_extrng *rng;
|
|
+
|
|
+ rcu_read_lock();
|
|
+ rng = rcu_dereference(extrng);
|
|
+ if (rng && !try_module_get(rng->owner))
|
|
+ rng = NULL;
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ if (!rng)
|
|
+ return 0;
|
|
+
|
|
+ filp->f_op = &extrng_random_fops;
|
|
+ filp->private_data = rng->owner;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int urandom_open(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ const struct random_extrng *rng;
|
|
+
|
|
+ rcu_read_lock();
|
|
+ rng = rcu_dereference(extrng);
|
|
+ if (rng && !try_module_get(rng->owner))
|
|
+ rng = NULL;
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ if (!rng)
|
|
+ return 0;
|
|
+
|
|
+ filp->f_op = &extrng_urandom_fops;
|
|
+ filp->private_data = rng->owner;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int extrng_release(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ module_put(filp->private_data);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+extrng_read_iter(struct kiocb *kiocb, struct iov_iter *iter)
|
|
+{
|
|
+ return rcu_dereference_raw(extrng)->extrng_read_iter(iter, false);
|
|
+}
|
|
+
|
|
const struct file_operations random_fops = {
|
|
+ .open = random_open,
|
|
.read_iter = random_read_iter,
|
|
.write_iter = random_write_iter,
|
|
.poll = random_poll,
|
|
@@ -1565,6 +1662,7 @@ const struct file_operations random_fops = {
|
|
};
|
|
|
|
const struct file_operations urandom_fops = {
|
|
+ .open = urandom_open,
|
|
.read_iter = urandom_read_iter,
|
|
.write_iter = random_write_iter,
|
|
.unlocked_ioctl = random_ioctl,
|
|
@@ -1575,6 +1673,32 @@ const struct file_operations urandom_fops = {
|
|
.splice_write = iter_file_splice_write,
|
|
};
|
|
|
|
+static const struct file_operations extrng_random_fops = {
|
|
+ .open = random_open,
|
|
+ .read_iter = extrng_read_iter,
|
|
+ .write_iter = random_write_iter,
|
|
+ .poll = extrng_poll,
|
|
+ .unlocked_ioctl = random_ioctl,
|
|
+ .compat_ioctl = compat_ptr_ioctl,
|
|
+ .fasync = random_fasync,
|
|
+ .llseek = noop_llseek,
|
|
+ .release = extrng_release,
|
|
+ .splice_read = copy_splice_read,
|
|
+ .splice_write = iter_file_splice_write,
|
|
+};
|
|
+
|
|
+static const struct file_operations extrng_urandom_fops = {
|
|
+ .open = urandom_open,
|
|
+ .read_iter = extrng_read_iter,
|
|
+ .write_iter = random_write_iter,
|
|
+ .unlocked_ioctl = random_ioctl,
|
|
+ .compat_ioctl = compat_ptr_ioctl,
|
|
+ .fasync = random_fasync,
|
|
+ .llseek = noop_llseek,
|
|
+ .release = extrng_release,
|
|
+ .splice_read = copy_splice_read,
|
|
+ .splice_write = iter_file_splice_write,
|
|
+};
|
|
|
|
/********************************************************************
|
|
*
|
|
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
|
|
index 8efbcf699e4ff..96d5a1ca981df 100644
|
|
--- a/drivers/firmware/efi/Makefile
|
|
+++ b/drivers/firmware/efi/Makefile
|
|
@@ -25,6 +25,7 @@ subdir-$(CONFIG_EFI_STUB) += libstub
|
|
obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o
|
|
obj-$(CONFIG_EFI_TEST) += test/
|
|
obj-$(CONFIG_EFI_DEV_PATH_PARSER) += dev-path-parser.o
|
|
+obj-$(CONFIG_EFI) += secureboot.o
|
|
obj-$(CONFIG_APPLE_PROPERTIES) += apple-properties.o
|
|
obj-$(CONFIG_EFI_RCI2_TABLE) += rci2-table.o
|
|
obj-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += embedded-firmware.o
|
|
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
|
|
index 318d1cc9a066c..eb41434d4f5e1 100644
|
|
--- a/drivers/firmware/efi/efi.c
|
|
+++ b/drivers/firmware/efi/efi.c
|
|
@@ -33,6 +33,7 @@
|
|
#include <linux/memblock.h>
|
|
#include <linux/security.h>
|
|
#include <linux/notifier.h>
|
|
+#include <linux/bsearch.h>
|
|
|
|
#include <asm/early_ioremap.h>
|
|
|
|
@@ -1018,40 +1019,101 @@ int efi_mem_type(unsigned long phys_addr)
|
|
return md.type;
|
|
}
|
|
|
|
+struct efi_error_code {
|
|
+ efi_status_t status;
|
|
+ int errno;
|
|
+ const char *description;
|
|
+};
|
|
+
|
|
+static const struct efi_error_code efi_error_codes[] = {
|
|
+ { EFI_SUCCESS, 0, "Success"},
|
|
+#if 0
|
|
+ { EFI_LOAD_ERROR, -EPICK_AN_ERRNO, "Load Error"},
|
|
+#endif
|
|
+ { EFI_INVALID_PARAMETER, -EINVAL, "Invalid Parameter"},
|
|
+ { EFI_UNSUPPORTED, -ENOSYS, "Unsupported"},
|
|
+ { EFI_BAD_BUFFER_SIZE, -ENOSPC, "Bad Buffer Size"},
|
|
+ { EFI_BUFFER_TOO_SMALL, -ENOSPC, "Buffer Too Small"},
|
|
+ { EFI_NOT_READY, -EAGAIN, "Not Ready"},
|
|
+ { EFI_DEVICE_ERROR, -EIO, "Device Error"},
|
|
+ { EFI_WRITE_PROTECTED, -EROFS, "Write Protected"},
|
|
+ { EFI_OUT_OF_RESOURCES, -ENOMEM, "Out of Resources"},
|
|
+#if 0
|
|
+ { EFI_VOLUME_CORRUPTED, -EPICK_AN_ERRNO, "Volume Corrupt"},
|
|
+ { EFI_VOLUME_FULL, -EPICK_AN_ERRNO, "Volume Full"},
|
|
+ { EFI_NO_MEDIA, -EPICK_AN_ERRNO, "No Media"},
|
|
+ { EFI_MEDIA_CHANGED, -EPICK_AN_ERRNO, "Media changed"},
|
|
+#endif
|
|
+ { EFI_NOT_FOUND, -ENOENT, "Not Found"},
|
|
+#if 0
|
|
+ { EFI_ACCESS_DENIED, -EPICK_AN_ERRNO, "Access Denied"},
|
|
+ { EFI_NO_RESPONSE, -EPICK_AN_ERRNO, "No Response"},
|
|
+ { EFI_NO_MAPPING, -EPICK_AN_ERRNO, "No mapping"},
|
|
+ { EFI_TIMEOUT, -EPICK_AN_ERRNO, "Time out"},
|
|
+ { EFI_NOT_STARTED, -EPICK_AN_ERRNO, "Not started"},
|
|
+ { EFI_ALREADY_STARTED, -EPICK_AN_ERRNO, "Already started"},
|
|
+#endif
|
|
+ { EFI_ABORTED, -EINTR, "Aborted"},
|
|
+#if 0
|
|
+ { EFI_ICMP_ERROR, -EPICK_AN_ERRNO, "ICMP Error"},
|
|
+ { EFI_TFTP_ERROR, -EPICK_AN_ERRNO, "TFTP Error"},
|
|
+ { EFI_PROTOCOL_ERROR, -EPICK_AN_ERRNO, "Protocol Error"},
|
|
+ { EFI_INCOMPATIBLE_VERSION, -EPICK_AN_ERRNO, "Incompatible Version"},
|
|
+#endif
|
|
+ { EFI_SECURITY_VIOLATION, -EACCES, "Security Policy Violation"},
|
|
+#if 0
|
|
+ { EFI_CRC_ERROR, -EPICK_AN_ERRNO, "CRC Error"},
|
|
+ { EFI_END_OF_MEDIA, -EPICK_AN_ERRNO, "End of Media"},
|
|
+ { EFI_END_OF_FILE, -EPICK_AN_ERRNO, "End of File"},
|
|
+ { EFI_INVALID_LANGUAGE, -EPICK_AN_ERRNO, "Invalid Languages"},
|
|
+ { EFI_COMPROMISED_DATA, -EPICK_AN_ERRNO, "Compromised Data"},
|
|
+
|
|
+ // warnings
|
|
+ { EFI_WARN_UNKOWN_GLYPH, -EPICK_AN_ERRNO, "Warning Unknown Glyph"},
|
|
+ { EFI_WARN_DELETE_FAILURE, -EPICK_AN_ERRNO, "Warning Delete Failure"},
|
|
+ { EFI_WARN_WRITE_FAILURE, -EPICK_AN_ERRNO, "Warning Write Failure"},
|
|
+ { EFI_WARN_BUFFER_TOO_SMALL, -EPICK_AN_ERRNO, "Warning Buffer Too Small"},
|
|
+#endif
|
|
+};
|
|
+
|
|
+static int
|
|
+efi_status_cmp_bsearch(const void *key, const void *item)
|
|
+{
|
|
+ u64 status = (u64)(uintptr_t)key;
|
|
+ struct efi_error_code *code = (struct efi_error_code *)item;
|
|
+
|
|
+ if (status < code->status)
|
|
+ return -1;
|
|
+ if (status > code->status)
|
|
+ return 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
int efi_status_to_err(efi_status_t status)
|
|
{
|
|
- int err;
|
|
-
|
|
- switch (status) {
|
|
- case EFI_SUCCESS:
|
|
- err = 0;
|
|
- break;
|
|
- case EFI_INVALID_PARAMETER:
|
|
- err = -EINVAL;
|
|
- break;
|
|
- case EFI_OUT_OF_RESOURCES:
|
|
- err = -ENOSPC;
|
|
- break;
|
|
- case EFI_DEVICE_ERROR:
|
|
- err = -EIO;
|
|
- break;
|
|
- case EFI_WRITE_PROTECTED:
|
|
- err = -EROFS;
|
|
- break;
|
|
- case EFI_SECURITY_VIOLATION:
|
|
- err = -EACCES;
|
|
- break;
|
|
- case EFI_NOT_FOUND:
|
|
- err = -ENOENT;
|
|
- break;
|
|
- case EFI_ABORTED:
|
|
- err = -EINTR;
|
|
- break;
|
|
- default:
|
|
- err = -EINVAL;
|
|
- }
|
|
+ struct efi_error_code *found;
|
|
+ size_t num = sizeof(efi_error_codes) / sizeof(struct efi_error_code);
|
|
|
|
- return err;
|
|
+ found = bsearch((void *)(uintptr_t)status, efi_error_codes,
|
|
+ num, sizeof(struct efi_error_code),
|
|
+ efi_status_cmp_bsearch);
|
|
+ if (!found)
|
|
+ return -EINVAL;
|
|
+ return found->errno;
|
|
+}
|
|
+
|
|
+const char *
|
|
+efi_status_to_str(efi_status_t status)
|
|
+{
|
|
+ struct efi_error_code *found;
|
|
+ size_t num = sizeof(efi_error_codes) / sizeof(struct efi_error_code);
|
|
+
|
|
+ found = bsearch((void *)(uintptr_t)status, efi_error_codes,
|
|
+ num, sizeof(struct efi_error_code),
|
|
+ efi_status_cmp_bsearch);
|
|
+ if (!found)
|
|
+ return "Unknown error code";
|
|
+ return found->description;
|
|
}
|
|
EXPORT_SYMBOL_GPL(efi_status_to_err);
|
|
|
|
diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c
|
|
index 23b3543d3041b..0d08a549a10b9 100644
|
|
--- a/drivers/firmware/efi/libstub/fdt.c
|
|
+++ b/drivers/firmware/efi/libstub/fdt.c
|
|
@@ -132,6 +132,11 @@ static efi_status_t update_fdt(void *orig_fdt, unsigned long orig_fdt_size,
|
|
}
|
|
}
|
|
|
|
+ fdt_val32 = cpu_to_fdt32((u32)efi_get_secureboot());
|
|
+ status = fdt_setprop_var(fdt, node, "secure-boot-mode", fdt_val32);
|
|
+ if (status)
|
|
+ goto fdt_set_fail;
|
|
+
|
|
/* Shrink the FDT back to its minimum size: */
|
|
fdt_pack(fdt);
|
|
|
|
diff --git a/drivers/firmware/efi/libstub/secureboot.c b/drivers/firmware/efi/libstub/secureboot.c
|
|
index 516f4f0069bd2..380354755108b 100644
|
|
--- a/drivers/firmware/efi/libstub/secureboot.c
|
|
+++ b/drivers/firmware/efi/libstub/secureboot.c
|
|
@@ -29,10 +29,13 @@ enum efi_secureboot_mode efi_get_secureboot(void)
|
|
{
|
|
u32 attr;
|
|
unsigned long size;
|
|
- enum efi_secureboot_mode mode;
|
|
+ static enum efi_secureboot_mode mode;
|
|
efi_status_t status;
|
|
u8 moksbstate;
|
|
|
|
+ if (mode != efi_secureboot_mode_unset)
|
|
+ return mode;
|
|
+
|
|
mode = efi_get_secureboot_mode(get_var);
|
|
if (mode == efi_secureboot_mode_unknown) {
|
|
efi_err("Could not determine UEFI Secure Boot status.\n");
|
|
@@ -53,10 +56,13 @@ enum efi_secureboot_mode efi_get_secureboot(void)
|
|
/* If it fails, we don't care why. Default to secure */
|
|
if (status != EFI_SUCCESS)
|
|
goto secure_boot_enabled;
|
|
- if (!(attr & EFI_VARIABLE_NON_VOLATILE) && moksbstate == 1)
|
|
- return efi_secureboot_mode_disabled;
|
|
+ if (!(attr & EFI_VARIABLE_NON_VOLATILE) && moksbstate == 1) {
|
|
+ mode = efi_secureboot_mode_disabled;
|
|
+ return mode;
|
|
+ }
|
|
|
|
secure_boot_enabled:
|
|
efi_info("UEFI Secure Boot is enabled.\n");
|
|
- return efi_secureboot_mode_enabled;
|
|
+ mode = efi_secureboot_mode_enabled;
|
|
+ return mode;
|
|
}
|
|
diff --git a/drivers/firmware/efi/secureboot.c b/drivers/firmware/efi/secureboot.c
|
|
new file mode 100644
|
|
index 0000000000000..de0a3714a5d44
|
|
--- /dev/null
|
|
+++ b/drivers/firmware/efi/secureboot.c
|
|
@@ -0,0 +1,38 @@
|
|
+/* Core kernel secure boot support.
|
|
+ *
|
|
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
|
|
+ * Written by David Howells (dhowells@redhat.com)
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public Licence
|
|
+ * as published by the Free Software Foundation; either version
|
|
+ * 2 of the Licence, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
+
|
|
+#include <linux/efi.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/printk.h>
|
|
+
|
|
+/*
|
|
+ * Decide what to do when UEFI secure boot mode is enabled.
|
|
+ */
|
|
+void __init efi_set_secure_boot(enum efi_secureboot_mode mode)
|
|
+{
|
|
+ if (efi_enabled(EFI_BOOT)) {
|
|
+ switch (mode) {
|
|
+ case efi_secureboot_mode_disabled:
|
|
+ pr_info("Secure boot disabled\n");
|
|
+ break;
|
|
+ case efi_secureboot_mode_enabled:
|
|
+ set_bit(EFI_SECURE_BOOT, &efi.flags);
|
|
+ pr_info("Secure boot enabled\n");
|
|
+ break;
|
|
+ default:
|
|
+ pr_warn("Secure boot could not be determined (mode %u)\n",
|
|
+ mode);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/drivers/gpu/drm/xe/display/xe_fb_pin.c b/drivers/gpu/drm/xe/display/xe_fb_pin.c
|
|
index bfbe3e44e78d5..09c6d9fd29830 100644
|
|
--- a/drivers/gpu/drm/xe/display/xe_fb_pin.c
|
|
+++ b/drivers/gpu/drm/xe/display/xe_fb_pin.c
|
|
@@ -161,31 +161,14 @@ static int __xe_pin_fb_vma_dpt(const struct intel_framebuffer *fb,
|
|
dpt_size = ALIGN(intel_rotation_info_size(&view->rotated) * 8,
|
|
XE_PAGE_SIZE);
|
|
|
|
- if (IS_DGFX(xe))
|
|
- dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
|
|
- dpt_size, ~0ull,
|
|
- ttm_bo_type_kernel,
|
|
- XE_BO_FLAG_VRAM0 |
|
|
- XE_BO_FLAG_GGTT |
|
|
- XE_BO_FLAG_PAGETABLE,
|
|
- alignment, false);
|
|
- else
|
|
- dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
|
|
- dpt_size, ~0ull,
|
|
- ttm_bo_type_kernel,
|
|
- XE_BO_FLAG_STOLEN |
|
|
- XE_BO_FLAG_GGTT |
|
|
- XE_BO_FLAG_PAGETABLE,
|
|
- alignment, false);
|
|
- if (IS_ERR(dpt))
|
|
- dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
|
|
- dpt_size, ~0ull,
|
|
- ttm_bo_type_kernel,
|
|
- XE_BO_FLAG_SYSTEM |
|
|
- XE_BO_FLAG_GGTT |
|
|
- XE_BO_FLAG_PAGETABLE |
|
|
- XE_BO_FLAG_FORCE_WC,
|
|
- alignment, false);
|
|
+ dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
|
|
+ dpt_size, ~0ull,
|
|
+ ttm_bo_type_kernel,
|
|
+ XE_BO_FLAG_VRAM_IF_DGFX(tile0) |
|
|
+ XE_BO_FLAG_GGTT |
|
|
+ XE_BO_FLAG_PAGETABLE |
|
|
+ XE_BO_FLAG_FORCE_WC,
|
|
+ alignment, false);
|
|
if (IS_ERR(dpt))
|
|
return PTR_ERR(dpt);
|
|
|
|
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
|
|
index a251375db24b1..2b5337eaeb6d1 100644
|
|
--- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
|
|
+++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
|
|
@@ -12,6 +12,7 @@
|
|
#include <linux/init.h>
|
|
#include <linux/types.h>
|
|
#include <linux/device.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/io.h>
|
|
#include <linux/err.h>
|
|
#include <linux/fs.h>
|
|
@@ -2547,6 +2548,16 @@ static const struct amba_id etm4_ids[] = {
|
|
{},
|
|
};
|
|
|
|
+static const struct dmi_system_id broken_coresight[] = {
|
|
+ {
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "HPE"),
|
|
+ DMI_MATCH(DMI_PRODUCT_NAME, "Apollo 70"),
|
|
+ },
|
|
+ },
|
|
+ { } /* terminating entry */
|
|
+};
|
|
+
|
|
MODULE_DEVICE_TABLE(amba, etm4_ids);
|
|
|
|
static struct amba_driver etm4x_amba_driver = {
|
|
@@ -2621,6 +2632,11 @@ static int __init etm4x_init(void)
|
|
{
|
|
int ret;
|
|
|
|
+ if (dmi_check_system(broken_coresight)) {
|
|
+ pr_info("ETM4 disabled due to firmware bug\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
ret = etm4_pm_setup();
|
|
|
|
/* etm4_pm_setup() does its own cleanup - exit on error */
|
|
@@ -2647,6 +2663,9 @@ static int __init etm4x_init(void)
|
|
|
|
static void __exit etm4x_exit(void)
|
|
{
|
|
+ if (dmi_check_system(broken_coresight))
|
|
+ return;
|
|
+
|
|
amba_driver_unregister(&etm4x_amba_driver);
|
|
platform_driver_unregister(&etm4_platform_driver);
|
|
etm4_pm_clear();
|
|
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
|
|
index d1a9e713d3a05..940458370730c 100644
|
|
--- a/drivers/iommu/iommu.c
|
|
+++ b/drivers/iommu/iommu.c
|
|
@@ -8,6 +8,7 @@
|
|
|
|
#include <linux/amba/bus.h>
|
|
#include <linux/device.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/bug.h>
|
|
@@ -3136,6 +3137,27 @@ int iommu_fwspec_add_ids(struct device *dev, const u32 *ids, int num_ids)
|
|
}
|
|
EXPORT_SYMBOL_GPL(iommu_fwspec_add_ids);
|
|
|
|
+#ifdef CONFIG_ARM64
|
|
+static int __init iommu_quirks(void)
|
|
+{
|
|
+ const char *vendor, *name;
|
|
+
|
|
+ vendor = dmi_get_system_info(DMI_SYS_VENDOR);
|
|
+ name = dmi_get_system_info(DMI_PRODUCT_NAME);
|
|
+
|
|
+ if (vendor &&
|
|
+ (strncmp(vendor, "GIGABYTE", 8) == 0 && name &&
|
|
+ (strncmp(name, "R120", 4) == 0 ||
|
|
+ strncmp(name, "R270", 4) == 0))) {
|
|
+ pr_warn("Gigabyte %s detected, force iommu passthrough mode", name);
|
|
+ iommu_def_domain_type = IOMMU_DOMAIN_IDENTITY;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+arch_initcall(iommu_quirks);
|
|
+#endif
|
|
+
|
|
/**
|
|
* iommu_setup_default_domain - Set the default_domain for the group
|
|
* @group: Group to change
|
|
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
|
|
index 8f2ba4121586e..e1143579ae3dc 100644
|
|
--- a/drivers/media/i2c/Kconfig
|
|
+++ b/drivers/media/i2c/Kconfig
|
|
@@ -287,6 +287,16 @@ config VIDEO_IMX415
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called imx415.
|
|
|
|
+config VIDEO_IMX471
|
|
+ tristate "Sony IMX471 sensor support"
|
|
+ select V4L2_CCI_I2C
|
|
+ help
|
|
+ This is a Video4Linux2 sensor driver for the Sony
|
|
+ IMX471 camera.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called imx471.
|
|
+
|
|
config VIDEO_MAX9271_LIB
|
|
tristate
|
|
|
|
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
|
|
index 90b276a7417af..f5c9fa93dc472 100644
|
|
--- a/drivers/media/i2c/Makefile
|
|
+++ b/drivers/media/i2c/Makefile
|
|
@@ -61,6 +61,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o
|
|
obj-$(CONFIG_VIDEO_IMX355) += imx355.o
|
|
obj-$(CONFIG_VIDEO_IMX412) += imx412.o
|
|
obj-$(CONFIG_VIDEO_IMX415) += imx415.o
|
|
+obj-$(CONFIG_VIDEO_IMX471) += imx471.o
|
|
obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o
|
|
obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o
|
|
obj-$(CONFIG_VIDEO_KS0127) += ks0127.o
|
|
diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c
|
|
new file mode 100644
|
|
index 0000000000000..6d358b11e96dd
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/imx471.c
|
|
@@ -0,0 +1,957 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * imx471.c - imx471 sensor driver
|
|
+ *
|
|
+ * Copyright (C) 2025 Intel Corporation
|
|
+ * Copyright (C) 2026 Kate Hsuan <hpa@redhat.com>
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <media/v4l2-cci.h>
|
|
+#include <media/v4l2-ctrls.h>
|
|
+#include <media/v4l2-subdev.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+
|
|
+#define IMX471_REG_MODE_SELECT CCI_REG8(0x0100)
|
|
+#define IMX471_MODE_STANDBY 0x00
|
|
+#define IMX471_MODE_STREAMING 0x01
|
|
+
|
|
+/* Chip ID */
|
|
+#define IMX471_REG_CHIP_ID CCI_REG16(0x0016)
|
|
+#define IMX471_CHIP_ID 0x0471
|
|
+
|
|
+/* V_TIMING internal */
|
|
+#define IMX471_REG_FLL CCI_REG16(0x0340)
|
|
+#define IMX471_FLL_MAX 0xffff
|
|
+
|
|
+/* Exposure control */
|
|
+#define IMX471_REG_EXPOSURE CCI_REG16(0x0202)
|
|
+#define IMX471_EXPOSURE_MIN 1
|
|
+#define IMX471_EXPOSURE_STEP 1
|
|
+#define IMX471_EXPOSURE_DEFAULT 1270
|
|
+
|
|
+/* Default exposure margin */
|
|
+#define IMX471_EXPOSURE_MARGIN 18
|
|
+
|
|
+/* Analog gain control */
|
|
+#define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204)
|
|
+#define IMX471_ANA_GAIN_MIN 0
|
|
+#define IMX471_ANA_GAIN_MAX 800
|
|
+#define IMX471_ANA_GAIN_STEP 1
|
|
+#define IMX471_ANA_GAIN_DEFAULT 0
|
|
+
|
|
+/* Digital gain control */
|
|
+#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG16(0x3ff9)
|
|
+#define IMX471_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e)
|
|
+#define IMX471_DGTL_GAIN_MIN 256
|
|
+#define IMX471_DGTL_GAIN_MAX 4095
|
|
+#define IMX471_DGTL_GAIN_STEP 1
|
|
+#define IMX471_DGTL_GAIN_DEFAULT 256
|
|
+
|
|
+/* HFLIP and VFLIP control */
|
|
+#define IMX471_REG_ORIENTATION CCI_REG8(0x0101)
|
|
+
|
|
+/* Test Pattern Control */
|
|
+#define IMX471_REG_TEST_PATTERN CCI_REG8(0x0600)
|
|
+
|
|
+/* default link frequency and external clock */
|
|
+#define IMX471_LINK_FREQ_DEFAULT 200000000LL
|
|
+#define IMX471_EXT_CLK 19200000
|
|
+
|
|
+/* PLL */
|
|
+#define IMX471_REG_VTPXCK_DIV CCI_REG8(0x0301)
|
|
+#define IMX471_REG_VTSYCK_DIV CCI_REG8(0x0303)
|
|
+#define IMX471_REG_PREPLLCK_VT_DIV CCI_REG8(0x0305)
|
|
+#define IMX471_REG_PLL_VT_MPY CCI_REG16(0x0306)
|
|
+#define IMX471_REG_OPPXCK_DIV CCI_REG8(0x0309)
|
|
+#define IMX471_REG_OPSYCK_DIV CCI_REG8(0x030b)
|
|
+#define IMX471_REG_PLL_MULT_DRIV CCI_REG8(0x0310)
|
|
+#define IMX471_PLL_SINGLE 0
|
|
+#define IMX471_PLL_DUAL 1
|
|
+
|
|
+/* IMX471 native and active pixel array size */
|
|
+#define IMX471_NATIVE_WIDTH 4672
|
|
+#define IMX471_NATIVE_HEIGHT 3512
|
|
+#define IMX471_PIXEL_ARRAY_LEFT 8
|
|
+#define IMX471_PIXEL_ARRAY_TOP 8
|
|
+#define IMX471_PIXEL_ARRAY_WIDTH 4656
|
|
+#define IMX471_PIXEL_ARRAY_HEIGHT 3496
|
|
+
|
|
+#define IMX471_REG_EXCK_FREQ CCI_REG16(0x0136)
|
|
+
|
|
+#define IMX471_REG_CSI_DATA_FORMAT CCI_REG16(0x0112)
|
|
+#define IMX471_CSI_DATA_FORMAT_RAW10 0x0a0a
|
|
+
|
|
+#define IMX471_REG_CSI_LANE_MODE CCI_REG8(0x0114)
|
|
+#define IMX471_CSI_2_LANE_MODE 1
|
|
+#define IMX471_CSI_4_LANE_MODE 3
|
|
+
|
|
+#define IMX471_REG_X_ADD_STA CCI_REG16(0x0344)
|
|
+#define IMX471_REG_Y_ADD_STA CCI_REG16(0x0346)
|
|
+#define IMX471_REG_X_ADD_END CCI_REG16(0x0348)
|
|
+#define IMX471_REG_Y_ADD_END CCI_REG16(0x034a)
|
|
+#define IMX471_REG_X_OUTPUT_SIZE CCI_REG16(0x034c)
|
|
+#define IMX471_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e)
|
|
+#define IMX471_REG_X_EVEN_INC CCI_REG8(0x0381)
|
|
+#define IMX471_REG_X_ODD_INC CCI_REG8(0x0383)
|
|
+#define IMX471_REG_Y_EVEN_INC CCI_REG8(0x0385)
|
|
+#define IMX471_REG_Y_ODD_INC CCI_REG8(0x0387)
|
|
+
|
|
+#define IMX471_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408)
|
|
+#define IMX471_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a)
|
|
+#define IMX471_REG_DIG_CROP_WIDTH CCI_REG16(0x040c)
|
|
+#define IMX471_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e)
|
|
+
|
|
+/* Binning mode */
|
|
+#define IMX471_REG_BINNING_MODE CCI_REG8(0x0900)
|
|
+#define IMX471_BINNING_NONE 0
|
|
+#define IMX471_BINNING_ENABLE 1
|
|
+#define IMX471_REG_BINNING_TYPE CCI_REG8(0x0901)
|
|
+#define IMX471_REG_BINNING_WEIGHTING CCI_REG8(0x0902)
|
|
+
|
|
+#define to_imx471(_sd) container_of_const(_sd, struct imx471, sd)
|
|
+
|
|
+static const char * const imx471_supply_name[] = {
|
|
+ "vana",
|
|
+};
|
|
+
|
|
+struct imx471_mode {
|
|
+ u32 width;
|
|
+ u32 height;
|
|
+
|
|
+ /* V-timing */
|
|
+ u32 fll_def;
|
|
+ u32 fll_min;
|
|
+
|
|
+ /* H-timing */
|
|
+ u32 llp;
|
|
+
|
|
+ const struct cci_reg_sequence *default_mode_regs;
|
|
+ unsigned int default_mode_regs_length;
|
|
+};
|
|
+
|
|
+struct imx471 {
|
|
+ struct v4l2_subdev sd;
|
|
+ struct media_pad pad;
|
|
+
|
|
+ struct v4l2_ctrl_handler ctrl_handler;
|
|
+ struct v4l2_ctrl *vblank;
|
|
+ struct v4l2_ctrl *hblank;
|
|
+ struct v4l2_ctrl *vflip;
|
|
+ struct v4l2_ctrl *hflip;
|
|
+ struct v4l2_ctrl *exposure;
|
|
+
|
|
+ struct gpio_desc *reset_gpio;
|
|
+ struct regulator_bulk_data supplies[ARRAY_SIZE(imx471_supply_name)];
|
|
+ struct clk *img_clk;
|
|
+
|
|
+ struct device *dev;
|
|
+ struct regmap *regmap;
|
|
+};
|
|
+
|
|
+static const struct cci_reg_sequence imx471_global_regs[] = {
|
|
+ { IMX471_REG_EXCK_FREQ, 0x1333 },
|
|
+ { CCI_REG8(0x3c7e), 0x08 },
|
|
+ { CCI_REG8(0x3c7f), 0x05 },
|
|
+ { CCI_REG8(0x3e35), 0x00 },
|
|
+ { CCI_REG8(0x3e36), 0x00 },
|
|
+ { CCI_REG8(0x3e37), 0x00 },
|
|
+ { CCI_REG8(0x3f7f), 0x01 },
|
|
+ { CCI_REG8(0x4431), 0x04 },
|
|
+ { CCI_REG8(0x531c), 0x01 },
|
|
+ { CCI_REG8(0x531d), 0x02 },
|
|
+ { CCI_REG8(0x531e), 0x04 },
|
|
+ { CCI_REG8(0x5928), 0x00 },
|
|
+ { CCI_REG8(0x5929), 0x2f },
|
|
+ { CCI_REG8(0x592a), 0x00 },
|
|
+ { CCI_REG8(0x592b), 0x85 },
|
|
+ { CCI_REG8(0x592c), 0x00 },
|
|
+ { CCI_REG8(0x592d), 0x32 },
|
|
+ { CCI_REG8(0x592e), 0x00 },
|
|
+ { CCI_REG8(0x592f), 0x88 },
|
|
+ { CCI_REG8(0x5930), 0x00 },
|
|
+ { CCI_REG8(0x5931), 0x3d },
|
|
+ { CCI_REG8(0x5932), 0x00 },
|
|
+ { CCI_REG8(0x5933), 0x93 },
|
|
+ { CCI_REG8(0x5938), 0x00 },
|
|
+ { CCI_REG8(0x5939), 0x24 },
|
|
+ { CCI_REG8(0x593a), 0x00 },
|
|
+ { CCI_REG8(0x593b), 0x7a },
|
|
+ { CCI_REG8(0x593c), 0x00 },
|
|
+ { CCI_REG8(0x593d), 0x24 },
|
|
+ { CCI_REG8(0x593e), 0x00 },
|
|
+ { CCI_REG8(0x593f), 0x7a },
|
|
+ { CCI_REG8(0x5940), 0x00 },
|
|
+ { CCI_REG8(0x5941), 0x2f },
|
|
+ { CCI_REG8(0x5942), 0x00 },
|
|
+ { CCI_REG8(0x5943), 0x85 },
|
|
+ { CCI_REG8(0x5f0e), 0x6e },
|
|
+ { CCI_REG8(0x5f11), 0xc6 },
|
|
+ { CCI_REG8(0x5f17), 0x5e },
|
|
+ { CCI_REG8(0x7990), 0x01 },
|
|
+ { CCI_REG8(0x7993), 0x5d },
|
|
+ { CCI_REG8(0x7994), 0x5d },
|
|
+ { CCI_REG8(0x7995), 0xa1 },
|
|
+ { CCI_REG8(0x799a), 0x01 },
|
|
+ { CCI_REG8(0x799d), 0x00 },
|
|
+ { CCI_REG8(0x8169), 0x01 },
|
|
+ { CCI_REG8(0x8359), 0x01 },
|
|
+ { CCI_REG8(0x9302), 0x1e },
|
|
+ { CCI_REG8(0x9306), 0x1f },
|
|
+ { CCI_REG8(0x930a), 0x26 },
|
|
+ { CCI_REG8(0x930e), 0x23 },
|
|
+ { CCI_REG8(0x9312), 0x23 },
|
|
+ { CCI_REG8(0x9316), 0x2c },
|
|
+ { CCI_REG8(0x9317), 0x19 },
|
|
+ { CCI_REG8(0xb046), 0x01 },
|
|
+ { CCI_REG8(0xb048), 0x01 },
|
|
+};
|
|
+
|
|
+static const struct cci_reg_sequence mode_1928x1088_regs[] = {
|
|
+ { IMX471_REG_CSI_DATA_FORMAT, IMX471_CSI_DATA_FORMAT_RAW10 },
|
|
+ { IMX471_REG_CSI_LANE_MODE, IMX471_CSI_4_LANE_MODE },
|
|
+ { IMX471_REG_X_ADD_STA, 8 },
|
|
+ { IMX471_REG_Y_ADD_STA, 408 },
|
|
+ { IMX471_REG_X_ADD_END, 4647 },
|
|
+ { IMX471_REG_Y_ADD_END, 3051 },
|
|
+ { IMX471_REG_X_EVEN_INC, 1 },
|
|
+ { IMX471_REG_X_ODD_INC, 1 },
|
|
+ { IMX471_REG_Y_EVEN_INC, 1 },
|
|
+ { IMX471_REG_Y_ODD_INC, 1 },
|
|
+ { IMX471_REG_BINNING_MODE, IMX471_BINNING_ENABLE },
|
|
+ { IMX471_REG_BINNING_TYPE, 0x22 },
|
|
+ { IMX471_REG_BINNING_WEIGHTING, 0x08 },
|
|
+ { IMX471_REG_DIG_CROP_X_OFFSET, 208 },
|
|
+ { IMX471_REG_DIG_CROP_Y_OFFSET, 108 },
|
|
+ { IMX471_REG_DIG_CROP_WIDTH, 1928 },
|
|
+ { IMX471_REG_DIG_CROP_HEIGHT, 1088 },
|
|
+ { IMX471_REG_X_OUTPUT_SIZE, 1928 },
|
|
+ { IMX471_REG_Y_OUTPUT_SIZE, 1088 },
|
|
+ { IMX471_REG_VTPXCK_DIV, 0x06 },
|
|
+ { IMX471_REG_VTSYCK_DIV, 0x02 },
|
|
+ { IMX471_REG_PREPLLCK_VT_DIV, 0x02 },
|
|
+ { IMX471_REG_PLL_VT_MPY, 0x0079 },
|
|
+ { IMX471_REG_OPSYCK_DIV, 0x01 },
|
|
+ { CCI_REG8(0x030d), 0x02 },
|
|
+ { CCI_REG8(0x030e), 0x00 },
|
|
+ { CCI_REG8(0x030f), 0x53 },
|
|
+ { IMX471_REG_PLL_MULT_DRIV, IMX471_PLL_DUAL },
|
|
+ { CCI_REG8(0x3f4c), 0x81 },
|
|
+ { CCI_REG8(0x3f4d), 0x81 },
|
|
+ { CCI_REG8(0x3f78), 0x01 },
|
|
+ { CCI_REG8(0x3f79), 0x31 },
|
|
+ { CCI_REG8(0x3ffe), 0x00 },
|
|
+ { CCI_REG8(0x3fff), 0x8a },
|
|
+ { CCI_REG8(0x5f0a), 0xb6 },
|
|
+};
|
|
+
|
|
+static const char * const imx471_test_pattern_menu[] = {
|
|
+ "Disabled",
|
|
+ "Solid Colour",
|
|
+ "Eight Vertical Colour Bars",
|
|
+ "Colour Bars With Fade to Grey",
|
|
+ "Pseudorandom Sequence (PN9)",
|
|
+};
|
|
+
|
|
+static const s64 link_freq_menu_items[] = {
|
|
+ IMX471_LINK_FREQ_DEFAULT,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * The Bayer formats for the flipping.
|
|
+ * - no flip
|
|
+ * - h flip
|
|
+ * - v flip
|
|
+ * - h and v flips
|
|
+ */
|
|
+static const u32 imx471_hv_flips_bayer_order[] = {
|
|
+ MEDIA_BUS_FMT_SRGGB10_1X10,
|
|
+ MEDIA_BUS_FMT_SGRBG10_1X10,
|
|
+ MEDIA_BUS_FMT_SGBRG10_1X10,
|
|
+ MEDIA_BUS_FMT_SBGGR10_1X10,
|
|
+};
|
|
+
|
|
+static const struct imx471_mode imx471_modes[] = {
|
|
+ {
|
|
+ .width = 1928,
|
|
+ .height = 1088,
|
|
+ .fll_def = 1308,
|
|
+ .fll_min = 1308,
|
|
+ .llp = 2328,
|
|
+ .default_mode_regs = mode_1928x1088_regs,
|
|
+ .default_mode_regs_length = ARRAY_SIZE(mode_1928x1088_regs),
|
|
+ },
|
|
+};
|
|
+
|
|
+static int imx471_get_regulators(struct device *dev, struct imx471 *sensor)
|
|
+{
|
|
+ for (unsigned int i = 0; i < ARRAY_SIZE(imx471_supply_name); i++)
|
|
+ sensor->supplies[i].supply = imx471_supply_name[i];
|
|
+
|
|
+ return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx471_supply_name),
|
|
+ sensor->supplies);
|
|
+}
|
|
+
|
|
+static int imx471_set_ctrl(struct v4l2_ctrl *ctrl)
|
|
+{
|
|
+ struct imx471 *sensor = container_of_const(ctrl->handler,
|
|
+ struct imx471,
|
|
+ ctrl_handler);
|
|
+ struct v4l2_subdev_state *state =
|
|
+ v4l2_subdev_get_locked_active_state(&sensor->sd);
|
|
+ const struct v4l2_mbus_framefmt *format =
|
|
+ v4l2_subdev_state_get_format(state, 0);
|
|
+ int ret;
|
|
+
|
|
+ if (ctrl->id == V4L2_CID_VBLANK) {
|
|
+ s64 exposure_max = format->height + ctrl->val -
|
|
+ IMX471_EXPOSURE_MARGIN;
|
|
+ ret = __v4l2_ctrl_modify_range(sensor->exposure,
|
|
+ sensor->exposure->minimum,
|
|
+ exposure_max,
|
|
+ sensor->exposure->step,
|
|
+ exposure_max);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (!pm_runtime_get_if_in_use(sensor->dev))
|
|
+ return 0;
|
|
+
|
|
+ switch (ctrl->id) {
|
|
+ case V4L2_CID_ANALOGUE_GAIN:
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_ANALOG_GAIN,
|
|
+ ctrl->val, NULL);
|
|
+ break;
|
|
+ case V4L2_CID_DIGITAL_GAIN:
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_DIG_GAIN_GLOBAL,
|
|
+ ctrl->val, NULL);
|
|
+ break;
|
|
+ case V4L2_CID_EXPOSURE:
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_EXPOSURE,
|
|
+ ctrl->val, &ret);
|
|
+ break;
|
|
+ case V4L2_CID_VBLANK:
|
|
+ /* Update FLL that meets expected vertical blanking */
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_FLL,
|
|
+ format->height + ctrl->val, &ret);
|
|
+ break;
|
|
+ case V4L2_CID_TEST_PATTERN:
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_TEST_PATTERN,
|
|
+ ctrl->val, NULL);
|
|
+ break;
|
|
+ case V4L2_CID_HFLIP:
|
|
+ case V4L2_CID_VFLIP:
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_ORIENTATION,
|
|
+ sensor->hflip->val | sensor->vflip->val << 1,
|
|
+ NULL);
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ dev_err(sensor->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n",
|
|
+ ctrl->id, ctrl->val);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ pm_runtime_put(sensor->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_ctrl_ops imx471_ctrl_ops = {
|
|
+ .s_ctrl = imx471_set_ctrl,
|
|
+};
|
|
+
|
|
+static u32 imx471_get_format_code(struct imx471 *sensor)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0);
|
|
+
|
|
+ return imx471_hv_flips_bayer_order[i];
|
|
+}
|
|
+
|
|
+static int imx471_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ struct imx471 *sensor = to_imx471(sd);
|
|
+
|
|
+ if (code->index >= (ARRAY_SIZE(imx471_hv_flips_bayer_order) / 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ code->code = imx471_get_format_code(sensor);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imx471_enum_frame_size(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_frame_size_enum *fse)
|
|
+{
|
|
+ if (fse->index >= ARRAY_SIZE(imx471_modes))
|
|
+ return -EINVAL;
|
|
+
|
|
+ fse->min_width = imx471_modes[fse->index].width;
|
|
+ fse->max_width = fse->min_width;
|
|
+ fse->min_height = imx471_modes[fse->index].height;
|
|
+ fse->max_height = fse->min_height;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void imx471_update_pad_format(struct imx471 *sensor,
|
|
+ const struct imx471_mode *mode,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ fmt->format.code = imx471_get_format_code(sensor);
|
|
+ fmt->format.width = mode->width;
|
|
+ fmt->format.height = mode->height;
|
|
+ fmt->format.field = V4L2_FIELD_NONE;
|
|
+}
|
|
+
|
|
+static int imx471_set_pad_format(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct imx471 *sensor = to_imx471(sd);
|
|
+ const struct imx471_mode *mode;
|
|
+ int h_blank, ret;
|
|
+
|
|
+ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes),
|
|
+ width, height, fmt->format.width,
|
|
+ fmt->format.height);
|
|
+
|
|
+ imx471_update_pad_format(sensor, mode, fmt);
|
|
+
|
|
+ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
|
|
+ return 0;
|
|
+
|
|
+ if (media_entity_is_streaming(&sensor->sd.entity))
|
|
+ return -EBUSY;
|
|
+
|
|
+ ret = __v4l2_ctrl_modify_range(sensor->vblank,
|
|
+ mode->fll_min - mode->height,
|
|
+ IMX471_FLL_MAX - mode->height,
|
|
+ 1,
|
|
+ mode->fll_def - mode->height);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ h_blank = mode->llp - mode->width;
|
|
+ /*
|
|
+ * Currently hblank is not changeable.
|
|
+ * So FPS control is done only by vblank.
|
|
+ */
|
|
+ return __v4l2_ctrl_modify_range(sensor->hblank, h_blank,
|
|
+ h_blank, 1, h_blank);
|
|
+}
|
|
+
|
|
+static int imx471_get_selection(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_selection *sel)
|
|
+{
|
|
+ switch (sel->target) {
|
|
+ case V4L2_SEL_TGT_CROP:
|
|
+ sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad);
|
|
+ break;
|
|
+
|
|
+ case V4L2_SEL_TGT_NATIVE_SIZE:
|
|
+ sel->r.top = 0;
|
|
+ sel->r.left = 0;
|
|
+ sel->r.width = IMX471_NATIVE_WIDTH;
|
|
+ sel->r.height = IMX471_NATIVE_HEIGHT;
|
|
+ return 0;
|
|
+
|
|
+ case V4L2_SEL_TGT_CROP_DEFAULT:
|
|
+ case V4L2_SEL_TGT_CROP_BOUNDS:
|
|
+ sel->r.top = IMX471_PIXEL_ARRAY_TOP;
|
|
+ sel->r.left = IMX471_PIXEL_ARRAY_LEFT;
|
|
+ sel->r.width = IMX471_PIXEL_ARRAY_WIDTH;
|
|
+ sel->r.height = IMX471_PIXEL_ARRAY_HEIGHT;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int imx471_init_state(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state)
|
|
+{
|
|
+ struct v4l2_subdev_format fmt = {
|
|
+ .which = V4L2_SUBDEV_FORMAT_TRY,
|
|
+ .format = {
|
|
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
|
|
+ .width = imx471_modes[0].width,
|
|
+ .height = imx471_modes[0].height,
|
|
+ },
|
|
+ };
|
|
+
|
|
+ return imx471_set_pad_format(sd, sd_state, &fmt);
|
|
+}
|
|
+
|
|
+static int imx471_identify_module(struct imx471 *sensor)
|
|
+{
|
|
+ int ret;
|
|
+ u64 val;
|
|
+
|
|
+ ret = cci_read(sensor->regmap, IMX471_REG_CHIP_ID, &val, NULL);
|
|
+ if (ret)
|
|
+ return dev_err_probe(sensor->dev, ret,
|
|
+ "failed to read chip id\n");
|
|
+
|
|
+ if (val != IMX471_CHIP_ID)
|
|
+ return dev_err_probe(sensor->dev, -EIO,
|
|
+ "chip id mismatch: %x!=%llx\n",
|
|
+ IMX471_CHIP_ID, val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imx471_power_off(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct imx471 *sensor = to_imx471(sd);
|
|
+
|
|
+ clk_disable_unprepare(sensor->img_clk);
|
|
+ gpiod_set_value_cansleep(sensor->reset_gpio, 1);
|
|
+
|
|
+ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name),
|
|
+ sensor->supplies);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imx471_power_on(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct imx471 *sensor = to_imx471(sd);
|
|
+ int ret;
|
|
+
|
|
+ ret = regulator_bulk_enable(ARRAY_SIZE(imx471_supply_name),
|
|
+ sensor->supplies);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable regulators: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(sensor->img_clk);
|
|
+ if (ret < 0) {
|
|
+ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name),
|
|
+ sensor->supplies);
|
|
+ dev_err(dev, "failed to enable imaging clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ gpiod_set_value_cansleep(sensor->reset_gpio, 0);
|
|
+
|
|
+ usleep_range(10000, 15000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imx471_enable_stream(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state,
|
|
+ u32 pad, u64 streams_mask)
|
|
+{
|
|
+ struct imx471 *sensor = to_imx471(sd);
|
|
+ const struct imx471_mode *mode;
|
|
+ struct v4l2_mbus_framefmt *fmt;
|
|
+ int ret;
|
|
+
|
|
+ ret = pm_runtime_resume_and_get(sensor->dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = imx471_identify_module(sensor);
|
|
+ if (ret)
|
|
+ goto error_powerdown;
|
|
+
|
|
+ ret = cci_multi_reg_write(sensor->regmap, imx471_global_regs,
|
|
+ ARRAY_SIZE(imx471_global_regs), NULL);
|
|
+ if (ret) {
|
|
+ dev_err(sensor->dev, "failed to set global settings: %d\n",
|
|
+ ret);
|
|
+ goto error_powerdown;
|
|
+ }
|
|
+
|
|
+ fmt = v4l2_subdev_state_get_format(state, 0);
|
|
+ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes),
|
|
+ width, height, fmt->width, fmt->height);
|
|
+
|
|
+ ret = cci_multi_reg_write(sensor->regmap, mode->default_mode_regs,
|
|
+ mode->default_mode_regs_length, NULL);
|
|
+ if (ret) {
|
|
+ dev_err(sensor->dev, "failed to set mode: %d\n", ret);
|
|
+ goto error_powerdown;
|
|
+ }
|
|
+
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_DPGA_USE_GLOBAL_GAIN, 1,
|
|
+ NULL);
|
|
+ if (ret)
|
|
+ goto error_powerdown;
|
|
+
|
|
+ ret = __v4l2_ctrl_handler_setup(&sensor->ctrl_handler);
|
|
+ if (ret)
|
|
+ goto error_powerdown;
|
|
+
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT,
|
|
+ IMX471_MODE_STREAMING, NULL);
|
|
+ if (ret)
|
|
+ goto error_powerdown;
|
|
+
|
|
+ __v4l2_ctrl_grab(sensor->vflip, true);
|
|
+ __v4l2_ctrl_grab(sensor->hflip, true);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+error_powerdown:
|
|
+ pm_runtime_put(sensor->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int imx471_disable_stream(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state,
|
|
+ u32 pad, u64 streams_mask)
|
|
+{
|
|
+ struct imx471 *sensor = to_imx471(sd);
|
|
+ int ret;
|
|
+
|
|
+ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT,
|
|
+ IMX471_MODE_STANDBY, NULL);
|
|
+ pm_runtime_put(sensor->dev);
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(sensor->dev,
|
|
+ "failed to disable stream with return value: %d\n",
|
|
+ ret);
|
|
+
|
|
+ __v4l2_ctrl_grab(sensor->vflip, false);
|
|
+ __v4l2_ctrl_grab(sensor->hflip, false);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops imx471_video_ops = {
|
|
+ .s_stream = v4l2_subdev_s_stream_helper,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops imx471_pad_ops = {
|
|
+ .enum_mbus_code = imx471_enum_mbus_code,
|
|
+ .get_fmt = v4l2_subdev_get_fmt,
|
|
+ .set_fmt = imx471_set_pad_format,
|
|
+ .get_selection = imx471_get_selection,
|
|
+ .enum_frame_size = imx471_enum_frame_size,
|
|
+ .enable_streams = imx471_enable_stream,
|
|
+ .disable_streams = imx471_disable_stream,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops imx471_subdev_ops = {
|
|
+ .video = &imx471_video_ops,
|
|
+ .pad = &imx471_pad_ops,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_internal_ops imx471_internal_ops = {
|
|
+ .init_state = imx471_init_state,
|
|
+};
|
|
+
|
|
+static int imx471_init_controls(struct imx471 *sensor)
|
|
+{
|
|
+ const struct imx471_mode *mode = &imx471_modes[0];
|
|
+ struct v4l2_fwnode_device_properties props;
|
|
+ struct v4l2_ctrl_handler *ctrl_hdlr;
|
|
+ struct v4l2_ctrl *link_freq;
|
|
+ s64 exposure_max, hblank;
|
|
+ u64 pixel_rate;
|
|
+ int ret;
|
|
+
|
|
+ ret = v4l2_fwnode_device_parse(sensor->dev, &props);
|
|
+ if (ret) {
|
|
+ dev_err(sensor->dev, "failed to parse fwnode: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ctrl_hdlr = &sensor->ctrl_handler;
|
|
+ v4l2_ctrl_handler_init(ctrl_hdlr, 12);
|
|
+
|
|
+ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx471_ctrl_ops, &props);
|
|
+
|
|
+ link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
|
|
+ &imx471_ctrl_ops,
|
|
+ V4L2_CID_LINK_FREQ,
|
|
+ ARRAY_SIZE(link_freq_menu_items) - 1,
|
|
+ 0,
|
|
+ link_freq_menu_items);
|
|
+
|
|
+ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */
|
|
+ pixel_rate = div_u64(IMX471_LINK_FREQ_DEFAULT * 2 * 4, 10);
|
|
+
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops,
|
|
+ V4L2_CID_PIXEL_RATE, pixel_rate,
|
|
+ pixel_rate, 1, pixel_rate);
|
|
+
|
|
+ sensor->vblank = v4l2_ctrl_new_std(ctrl_hdlr,
|
|
+ &imx471_ctrl_ops,
|
|
+ V4L2_CID_VBLANK,
|
|
+ mode->fll_min - mode->height,
|
|
+ IMX471_FLL_MAX - mode->height,
|
|
+ 1,
|
|
+ mode->fll_def - mode->height);
|
|
+
|
|
+ hblank = mode->llp - mode->width;
|
|
+ sensor->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops,
|
|
+ V4L2_CID_HBLANK, hblank, hblank,
|
|
+ 1, hblank);
|
|
+
|
|
+ /* fll >= exposure time + adjust parameter (default value is 18) */
|
|
+ exposure_max = mode->fll_def - IMX471_EXPOSURE_MARGIN;
|
|
+ sensor->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops,
|
|
+ V4L2_CID_EXPOSURE,
|
|
+ IMX471_EXPOSURE_MIN, exposure_max,
|
|
+ IMX471_EXPOSURE_STEP,
|
|
+ IMX471_EXPOSURE_DEFAULT);
|
|
+
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
|
|
+ IMX471_ANA_GAIN_MIN, IMX471_ANA_GAIN_MAX,
|
|
+ IMX471_ANA_GAIN_STEP, IMX471_ANA_GAIN_DEFAULT);
|
|
+
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
|
|
+ IMX471_DGTL_GAIN_MIN, IMX471_DGTL_GAIN_MAX,
|
|
+ IMX471_DGTL_GAIN_STEP, IMX471_DGTL_GAIN_DEFAULT);
|
|
+
|
|
+ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx471_ctrl_ops,
|
|
+ V4L2_CID_TEST_PATTERN,
|
|
+ ARRAY_SIZE(imx471_test_pattern_menu) - 1,
|
|
+ 0, 0, imx471_test_pattern_menu);
|
|
+
|
|
+ sensor->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops,
|
|
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
|
|
+
|
|
+ sensor->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops,
|
|
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
|
|
+
|
|
+ if (ctrl_hdlr->error) {
|
|
+ dev_err(sensor->dev, "%s control init failed: %d\n",
|
|
+ __func__, ctrl_hdlr->error);
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+ sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
|
|
+ sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
|
|
+
|
|
+ sensor->sd.ctrl_handler = ctrl_hdlr;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error:
|
|
+ v4l2_ctrl_handler_free(ctrl_hdlr);
|
|
+
|
|
+ return ctrl_hdlr->error;
|
|
+}
|
|
+
|
|
+static int imx471_check_hwcfg(struct imx471 *sensor)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint bus_cfg = {
|
|
+ .bus_type = V4L2_MBUS_CSI2_DPHY,
|
|
+ };
|
|
+ struct fwnode_handle *ep, *fwnode = dev_fwnode(sensor->dev);
|
|
+ unsigned long link_freq_bitmap;
|
|
+ struct clk *clk;
|
|
+ int ret;
|
|
+
|
|
+ clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL);
|
|
+ if (IS_ERR(clk))
|
|
+ return dev_err_probe(sensor->dev, PTR_ERR(clk),
|
|
+ "can't get clock frequency\n");
|
|
+
|
|
+ if (clk_get_rate(clk) != IMX471_EXT_CLK)
|
|
+ return dev_err_probe(sensor->dev, -EINVAL,
|
|
+ "external clock %lu is not supported\n",
|
|
+ clk_get_rate(clk));
|
|
+
|
|
+ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0);
|
|
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
|
|
+ fwnode_handle_put(ep);
|
|
+ if (ret)
|
|
+ return dev_err_probe(sensor->dev, ret,
|
|
+ "parsing endpoint failed\n");
|
|
+
|
|
+ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) {
|
|
+ ret = dev_err_probe(sensor->dev, -EINVAL,
|
|
+ "number of CSI2 data lanes %u is not supported\n",
|
|
+ bus_cfg.bus.mipi_csi2.num_data_lanes);
|
|
+ goto done_endpoint_free;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_link_freq_to_bitmap(sensor->dev, bus_cfg.link_frequencies,
|
|
+ bus_cfg.nr_of_link_frequencies,
|
|
+ link_freq_menu_items,
|
|
+ ARRAY_SIZE(link_freq_menu_items),
|
|
+ &link_freq_bitmap);
|
|
+
|
|
+done_endpoint_free:
|
|
+ v4l2_fwnode_endpoint_free(&bus_cfg);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int imx471_probe(struct i2c_client *client)
|
|
+{
|
|
+ struct imx471 *sensor;
|
|
+ int ret;
|
|
+
|
|
+ sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
|
|
+ if (!sensor)
|
|
+ return dev_err_probe(&client->dev, -ENOMEM,
|
|
+ "failed to allocate memory\n");
|
|
+
|
|
+ sensor->dev = &client->dev;
|
|
+
|
|
+ ret = imx471_check_hwcfg(sensor);
|
|
+ if (ret)
|
|
+ return dev_err_probe(sensor->dev, ret,
|
|
+ "failed to check hwcfg: %d\n", ret);
|
|
+
|
|
+ ret = imx471_get_regulators(sensor->dev, sensor);
|
|
+ if (ret)
|
|
+ return dev_err_probe(sensor->dev, ret,
|
|
+ "failed to get regulators\n");
|
|
+
|
|
+ sensor->reset_gpio = devm_gpiod_get_optional(sensor->dev, "reset",
|
|
+ GPIOD_OUT_HIGH);
|
|
+ if (IS_ERR(sensor->reset_gpio))
|
|
+ return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset_gpio),
|
|
+ "failed to get reset gpio\n");
|
|
+
|
|
+ sensor->img_clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL);
|
|
+ if (IS_ERR(sensor->img_clk))
|
|
+ return dev_err_probe(sensor->dev, PTR_ERR(sensor->img_clk),
|
|
+ "failed to get imaging clock\n");
|
|
+
|
|
+ v4l2_i2c_subdev_init(&sensor->sd, client, &imx471_subdev_ops);
|
|
+
|
|
+ sensor->regmap = devm_cci_regmap_init_i2c(client, 16);
|
|
+ if (IS_ERR(sensor->regmap))
|
|
+ return dev_err_probe(sensor->dev, PTR_ERR(sensor->regmap),
|
|
+ "failed to initialize CCI\n");
|
|
+
|
|
+ ret = imx471_power_on(sensor->dev);
|
|
+ if (ret)
|
|
+ return dev_err_probe(sensor->dev, ret,
|
|
+ "failed to power on\n");
|
|
+
|
|
+ ret = imx471_identify_module(sensor);
|
|
+ if (ret) {
|
|
+ dev_err_probe(sensor->dev, ret, "failed to find sensor: %d\n",
|
|
+ ret);
|
|
+ goto error_power_off;
|
|
+ }
|
|
+
|
|
+ ret = imx471_init_controls(sensor);
|
|
+ if (ret) {
|
|
+ dev_err_probe(sensor->dev, ret, "failed to init controls: %d\n",
|
|
+ ret);
|
|
+ goto error_power_off;
|
|
+ }
|
|
+
|
|
+ sensor->sd.internal_ops = &imx471_internal_ops;
|
|
+ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
+ sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
+ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
|
|
+
|
|
+ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
|
|
+ if (ret) {
|
|
+ dev_err_probe(sensor->dev, ret,
|
|
+ "failed to init entity pads: %d\n", ret);
|
|
+ goto error_v4l2_ctrl_handler_free;
|
|
+ }
|
|
+
|
|
+ sensor->sd.state_lock = sensor->ctrl_handler.lock;
|
|
+ ret = v4l2_subdev_init_finalize(&sensor->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err_probe(sensor->dev, ret, "failed to init subdev: %d\n",
|
|
+ ret);
|
|
+ goto error_media_entity_pm;
|
|
+ }
|
|
+
|
|
+ pm_runtime_set_active(sensor->dev);
|
|
+ pm_runtime_enable(sensor->dev);
|
|
+
|
|
+ ret = v4l2_async_register_subdev_sensor(&sensor->sd);
|
|
+ if (ret < 0)
|
|
+ goto error_v4l2_subdev_cleanup;
|
|
+
|
|
+ pm_runtime_idle(sensor->dev);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_v4l2_subdev_cleanup:
|
|
+ pm_runtime_disable(sensor->dev);
|
|
+ pm_runtime_set_suspended(sensor->dev);
|
|
+ v4l2_subdev_cleanup(&sensor->sd);
|
|
+
|
|
+error_media_entity_pm:
|
|
+ media_entity_cleanup(&sensor->sd.entity);
|
|
+
|
|
+error_v4l2_ctrl_handler_free:
|
|
+ v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
|
|
+
|
|
+error_power_off:
|
|
+ imx471_power_off(sensor->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void imx471_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
|
+
|
|
+ v4l2_async_unregister_subdev(sd);
|
|
+ v4l2_subdev_cleanup(sd);
|
|
+ media_entity_cleanup(&sd->entity);
|
|
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
|
|
+
|
|
+ pm_runtime_disable(&client->dev);
|
|
+
|
|
+ if (!pm_runtime_status_suspended(&client->dev)) {
|
|
+ imx471_power_off(&client->dev);
|
|
+ pm_runtime_set_suspended(&client->dev);
|
|
+ }
|
|
+}
|
|
+
|
|
+static DEFINE_RUNTIME_DEV_PM_OPS(imx471_pm_ops, imx471_power_off,
|
|
+ imx471_power_on, NULL);
|
|
+
|
|
+static const struct acpi_device_id imx471_acpi_ids[] __maybe_unused = {
|
|
+ { "SONY471A" },
|
|
+ { "TBE20A0" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(acpi, imx471_acpi_ids);
|
|
+
|
|
+static struct i2c_driver imx471_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "imx471",
|
|
+ .acpi_match_table = ACPI_PTR(imx471_acpi_ids),
|
|
+ .pm = pm_sleep_ptr(&imx471_pm_ops),
|
|
+ },
|
|
+ .probe = imx471_probe,
|
|
+ .remove = imx471_remove,
|
|
+};
|
|
+module_i2c_driver(imx471_i2c_driver);
|
|
+
|
|
+MODULE_AUTHOR("Jimmy Su <jimmy.su@intel.com>");
|
|
+MODULE_AUTHOR("Serin Yeh <serin.yeh@intel.com>");
|
|
+MODULE_AUTHOR("Kate Hsuan <hpa@redhat.com>");
|
|
+MODULE_DESCRIPTION("Sony imx471 sensor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c
|
|
index fc6608e33de40..8cfa3282bf080 100644
|
|
--- a/drivers/media/pci/intel/ipu-bridge.c
|
|
+++ b/drivers/media/pci/intel/ipu-bridge.c
|
|
@@ -97,6 +97,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = {
|
|
IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000),
|
|
/* Sony IMX471 */
|
|
IPU_SENSOR_CONFIG("SONY471A", 1, 200000000),
|
|
+ /* Sony IMX471 (found on Lenovo X1 Carbon G14) */
|
|
+ IPU_SENSOR_CONFIG("TBE20A0", 1, 200000000),
|
|
/* Toshiba T4KA3 */
|
|
IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000),
|
|
};
|
|
@@ -134,6 +136,45 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = {
|
|
},
|
|
.driver_data = "OVTI02C1",
|
|
},
|
|
+ /*
|
|
+ * The first four characters of DMI_BOARD_NAME identify the Lenovo
|
|
+ * machine type/model. For example, a DMI_BOARD_NAME starting with
|
|
+ * "21Q6" indicates a ThinkPad X9-15.
|
|
+ *
|
|
+ * Reference: https://psref.lenovo.com/
|
|
+ */
|
|
+ {
|
|
+ /* Lenovo X9-14 */
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "21QA"),
|
|
+ },
|
|
+ .driver_data = "SONY471A",
|
|
+ },
|
|
+ {
|
|
+ /* Lenovo X9-14 */
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "21QB"),
|
|
+ },
|
|
+ .driver_data = "SONY471A",
|
|
+ },
|
|
+ {
|
|
+ /* Lenovo X9-15 */
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "21Q6"),
|
|
+ },
|
|
+ .driver_data = "SONY471A",
|
|
+ },
|
|
+ {
|
|
+ /* Lenovo X9-15 */
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "21Q7"),
|
|
+ },
|
|
+ .driver_data = "SONY471A",
|
|
+ },
|
|
{} /* Terminating entry */
|
|
};
|
|
|
|
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
|
|
index 3f0b7bb68cc94..0b33e927bd59c 100644
|
|
--- a/drivers/media/platform/Kconfig
|
|
+++ b/drivers/media/platform/Kconfig
|
|
@@ -63,6 +63,7 @@ config VIDEO_MUX
|
|
|
|
# Platform drivers - Please keep it alphabetically sorted
|
|
source "drivers/media/platform/allegro-dvt/Kconfig"
|
|
+source "drivers/media/platform/amd/Kconfig"
|
|
source "drivers/media/platform/amlogic/Kconfig"
|
|
source "drivers/media/platform/amphion/Kconfig"
|
|
source "drivers/media/platform/arm/Kconfig"
|
|
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
|
|
index 6d5f79ddfcc37..16c185752474e 100644
|
|
--- a/drivers/media/platform/Makefile
|
|
+++ b/drivers/media/platform/Makefile
|
|
@@ -6,6 +6,7 @@
|
|
# Place here, alphabetically sorted by directory
|
|
# (e. g. LC_ALL=C sort Makefile)
|
|
obj-y += allegro-dvt/
|
|
+obj-y += amd/
|
|
obj-y += amlogic/
|
|
obj-y += amphion/
|
|
obj-y += arm/
|
|
diff --git a/drivers/media/platform/amd/Kconfig b/drivers/media/platform/amd/Kconfig
|
|
new file mode 100644
|
|
index 0000000000000..25af49f246b26
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/Kconfig
|
|
@@ -0,0 +1,3 @@
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
+
|
|
+source "drivers/media/platform/amd/isp4/Kconfig"
|
|
diff --git a/drivers/media/platform/amd/Makefile b/drivers/media/platform/amd/Makefile
|
|
new file mode 100644
|
|
index 0000000000000..8bfc1955f22e6
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/Makefile
|
|
@@ -0,0 +1,3 @@
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
+
|
|
+obj-y += isp4/
|
|
diff --git a/drivers/media/platform/amd/isp4/Kconfig b/drivers/media/platform/amd/isp4/Kconfig
|
|
new file mode 100644
|
|
index 0000000000000..9d1927af1cb8c
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/Kconfig
|
|
@@ -0,0 +1,17 @@
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
+
|
|
+config VIDEO_AMD_ISP4_CAPTURE
|
|
+ tristate "AMD ISP4 and camera driver"
|
|
+ depends on DRM_AMDGPU && DRM_AMD_ISP
|
|
+ depends on HAS_DMA
|
|
+ depends on VIDEO_DEV
|
|
+ select VIDEOBUF2_CORE
|
|
+ select VIDEOBUF2_MEMOPS
|
|
+ select VIDEOBUF2_V4L2
|
|
+ select VIDEOBUF2_VMALLOC
|
|
+ select VIDEO_V4L2_SUBDEV_API
|
|
+ help
|
|
+ This is support for AMD ISP4 and camera subsystem driver.
|
|
+ Say Y here to enable the ISP4 and camera device for video capture.
|
|
+ To compile this driver as a module, choose M here. The module will
|
|
+ be called amd_isp4_capture.
|
|
diff --git a/drivers/media/platform/amd/isp4/Makefile b/drivers/media/platform/amd/isp4/Makefile
|
|
new file mode 100644
|
|
index 0000000000000..3849062e17f3d
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/Makefile
|
|
@@ -0,0 +1,10 @@
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
+#
|
|
+# Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+
|
|
+obj-$(CONFIG_VIDEO_AMD_ISP4_CAPTURE) += amd_isp4_capture.o
|
|
+amd_isp4_capture-objs := isp4.o \
|
|
+ isp4_debug.o \
|
|
+ isp4_interface.o \
|
|
+ isp4_subdev.o \
|
|
+ isp4_video.o
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4.c b/drivers/media/platform/amd/isp4/isp4.c
|
|
new file mode 100644
|
|
index 0000000000000..bf6b8e26c2c0f
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4.c
|
|
@@ -0,0 +1,240 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#include <linux/irq.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <media/v4l2-ioctl.h>
|
|
+
|
|
+#include "isp4.h"
|
|
+#include "isp4_debug.h"
|
|
+#include "isp4_hw_reg.h"
|
|
+
|
|
+#define ISP4_DRV_NAME "amd_isp_capture"
|
|
+#define ISP4_FW_RESP_RB_IRQ_STATUS_MASK \
|
|
+ (ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK | \
|
|
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK)
|
|
+
|
|
+static const struct {
|
|
+ const char *name;
|
|
+ u32 status_mask;
|
|
+ u32 en_mask;
|
|
+ u32 ack_mask;
|
|
+ u32 rb_int_num;
|
|
+} isp4_irq[ISP4SD_MAX_FW_RESP_STREAM_NUM] = {
|
|
+ /* The IRQ order is aligned with the isp4_subdev.fw_resp_thread order */
|
|
+ {
|
|
+ .name = "isp_irq_global",
|
|
+ .status_mask =
|
|
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK,
|
|
+ .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK,
|
|
+ .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK,
|
|
+ .rb_int_num = 4, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12 */
|
|
+ },
|
|
+ {
|
|
+ .name = "isp_irq_stream1",
|
|
+ .status_mask =
|
|
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK,
|
|
+ .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK,
|
|
+ .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK,
|
|
+ .rb_int_num = 0, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9 */
|
|
+ },
|
|
+};
|
|
+
|
|
+void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable)
|
|
+{
|
|
+ u32 intr_en;
|
|
+
|
|
+ /* Synchronize ISP_SYS_INT0_EN writes with the IRQ handler's writes */
|
|
+ spin_lock_irq(&isp_subdev->irq_lock);
|
|
+ intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN);
|
|
+ if (enable)
|
|
+ intr_en |= isp4_irq[index].en_mask;
|
|
+ else
|
|
+ intr_en &= ~isp4_irq[index].en_mask;
|
|
+
|
|
+ isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en);
|
|
+ spin_unlock_irq(&isp_subdev->irq_lock);
|
|
+}
|
|
+
|
|
+static void isp4_wake_up_resp_thread(struct isp4_subdev *isp_subdev, u32 index)
|
|
+{
|
|
+ struct isp4sd_thread_handler *thread_ctx =
|
|
+ &isp_subdev->fw_resp_thread[index];
|
|
+
|
|
+ thread_ctx->resp_ready = true;
|
|
+ wake_up_interruptible(&thread_ctx->waitq);
|
|
+}
|
|
+
|
|
+static irqreturn_t isp4_irq_handler(int irq, void *arg)
|
|
+{
|
|
+ struct isp4_subdev *isp_subdev = arg;
|
|
+ u32 intr_ack = 0, intr_en = 0, intr_status;
|
|
+ int seen = 0;
|
|
+
|
|
+ /* Get the ISP_SYS interrupt status */
|
|
+ intr_status = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_STATUS);
|
|
+ intr_status &= ISP4_FW_RESP_RB_IRQ_STATUS_MASK;
|
|
+
|
|
+ /* Find which ISP_SYS interrupts fired */
|
|
+ for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) {
|
|
+ if (intr_status & isp4_irq[i].status_mask) {
|
|
+ intr_ack |= isp4_irq[i].ack_mask;
|
|
+ intr_en |= isp4_irq[i].en_mask;
|
|
+ seen |= BIT(i);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Disable the ISP_SYS interrupts that fired. Must be done before waking
|
|
+ * the response threads, since they re-enable interrupts when finished.
|
|
+ * The lock synchronizes RMW of INT0_EN with isp4_enable_interrupt().
|
|
+ */
|
|
+ spin_lock(&isp_subdev->irq_lock);
|
|
+ intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN) & ~intr_en;
|
|
+ isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en);
|
|
+ spin_unlock(&isp_subdev->irq_lock);
|
|
+
|
|
+ /*
|
|
+ * Clear the ISP_SYS interrupts. This must be done after the interrupts
|
|
+ * are disabled, so that ISP FW won't flag any new interrupts on these
|
|
+ * streams, and thus we don't need to clear interrupts again before
|
|
+ * re-enabling them in the response thread.
|
|
+ */
|
|
+ isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_ACK, intr_ack);
|
|
+
|
|
+ /*
|
|
+ * The operation `(seen >> i) << i` is logically equivalent to
|
|
+ * `seen &= ~BIT(i)`, with fewer instructions after compilation.
|
|
+ */
|
|
+ for (int i; (i = ffs(seen)); seen = (seen >> i) << i)
|
|
+ isp4_wake_up_resp_thread(isp_subdev, i - 1);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int isp4_capture_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM];
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct isp4_subdev *isp_subdev;
|
|
+ struct isp4_device *isp_dev;
|
|
+ int ret;
|
|
+
|
|
+ isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL);
|
|
+ if (!isp_dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dev->init_name = ISP4_DRV_NAME;
|
|
+
|
|
+ isp_subdev = &isp_dev->isp_subdev;
|
|
+ isp_subdev->mmio = devm_platform_ioremap_resource(pdev, 0);
|
|
+ if (IS_ERR(isp_subdev->mmio))
|
|
+ return dev_err_probe(dev, PTR_ERR(isp_subdev->mmio),
|
|
+ "isp ioremap fail\n");
|
|
+
|
|
+ for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) {
|
|
+ irq[i] = platform_get_irq(pdev, isp4_irq[i].rb_int_num);
|
|
+ if (irq[i] < 0)
|
|
+ return dev_err_probe(dev, irq[i],
|
|
+ "fail to get irq %d\n",
|
|
+ isp4_irq[i].rb_int_num);
|
|
+
|
|
+ ret = devm_request_irq(dev, irq[i], isp4_irq_handler,
|
|
+ IRQF_NO_AUTOEN, isp4_irq[i].name,
|
|
+ isp_subdev);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret, "fail to req irq %d\n",
|
|
+ irq[i]);
|
|
+ }
|
|
+
|
|
+ isp_dev->v4l2_dev.mdev = &isp_dev->mdev;
|
|
+
|
|
+ strscpy(isp_dev->mdev.model, "amd_isp41_mdev",
|
|
+ sizeof(isp_dev->mdev.model));
|
|
+ isp_dev->mdev.dev = dev;
|
|
+ media_device_init(&isp_dev->mdev);
|
|
+
|
|
+ snprintf(isp_dev->v4l2_dev.name, sizeof(isp_dev->v4l2_dev.name),
|
|
+ "AMD-V4L2-ROOT");
|
|
+ ret = v4l2_device_register(dev, &isp_dev->v4l2_dev);
|
|
+ if (ret) {
|
|
+ dev_err_probe(dev, ret, "fail register v4l2 device\n");
|
|
+ goto err_clean_media;
|
|
+ }
|
|
+
|
|
+ pm_runtime_set_suspended(dev);
|
|
+ pm_runtime_enable(dev);
|
|
+ spin_lock_init(&isp_subdev->irq_lock);
|
|
+ ret = isp4sd_init(&isp_dev->isp_subdev, &isp_dev->v4l2_dev, irq);
|
|
+ if (ret) {
|
|
+ dev_err_probe(dev, ret, "fail init isp4 sub dev\n");
|
|
+ goto err_pm_disable;
|
|
+ }
|
|
+
|
|
+ ret = media_create_pad_link(&isp_dev->isp_subdev.sdev.entity,
|
|
+ 0,
|
|
+ &isp_dev->isp_subdev.isp_vdev.vdev.entity,
|
|
+ 0,
|
|
+ MEDIA_LNK_FL_ENABLED |
|
|
+ MEDIA_LNK_FL_IMMUTABLE);
|
|
+ if (ret) {
|
|
+ dev_err_probe(dev, ret, "fail to create pad link\n");
|
|
+ goto err_isp4_deinit;
|
|
+ }
|
|
+
|
|
+ ret = media_device_register(&isp_dev->mdev);
|
|
+ if (ret) {
|
|
+ dev_err_probe(dev, ret, "fail to register media device\n");
|
|
+ goto err_isp4_deinit;
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, isp_dev);
|
|
+ isp_debugfs_create(isp_dev);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_isp4_deinit:
|
|
+ isp4sd_deinit(&isp_dev->isp_subdev);
|
|
+err_pm_disable:
|
|
+ pm_runtime_disable(dev);
|
|
+ v4l2_device_unregister(&isp_dev->v4l2_dev);
|
|
+err_clean_media:
|
|
+ media_device_cleanup(&isp_dev->mdev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void isp4_capture_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct isp4_device *isp_dev = platform_get_drvdata(pdev);
|
|
+ struct device *dev = &pdev->dev;
|
|
+
|
|
+ isp_debugfs_remove(isp_dev);
|
|
+
|
|
+ media_device_unregister(&isp_dev->mdev);
|
|
+ isp4sd_deinit(&isp_dev->isp_subdev);
|
|
+ pm_runtime_disable(dev);
|
|
+ v4l2_device_unregister(&isp_dev->v4l2_dev);
|
|
+ media_device_cleanup(&isp_dev->mdev);
|
|
+}
|
|
+
|
|
+static struct platform_driver isp4_capture_drv = {
|
|
+ .probe = isp4_capture_probe,
|
|
+ .remove = isp4_capture_remove,
|
|
+ .driver = {
|
|
+ .name = ISP4_DRV_NAME,
|
|
+ }
|
|
+};
|
|
+
|
|
+module_platform_driver(isp4_capture_drv);
|
|
+
|
|
+MODULE_ALIAS("platform:" ISP4_DRV_NAME);
|
|
+MODULE_IMPORT_NS("DMA_BUF");
|
|
+
|
|
+MODULE_DESCRIPTION("AMD ISP4 Driver");
|
|
+MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
|
|
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4.h b/drivers/media/platform/amd/isp4/isp4.h
|
|
new file mode 100644
|
|
index 0000000000000..2db6683d6d8b5
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4.h
|
|
@@ -0,0 +1,20 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_H_
|
|
+#define _ISP4_H_
|
|
+
|
|
+#include <drm/amd/isp.h>
|
|
+#include "isp4_subdev.h"
|
|
+
|
|
+struct isp4_device {
|
|
+ struct v4l2_device v4l2_dev;
|
|
+ struct isp4_subdev isp_subdev;
|
|
+ struct media_device mdev;
|
|
+};
|
|
+
|
|
+void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable);
|
|
+
|
|
+#endif /* _ISP4_H_ */
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_debug.c b/drivers/media/platform/amd/isp4/isp4_debug.c
|
|
new file mode 100644
|
|
index 0000000000000..2fc00fc9a1940
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_debug.c
|
|
@@ -0,0 +1,271 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#include "isp4.h"
|
|
+#include "isp4_debug.h"
|
|
+#include "isp4_hw_reg.h"
|
|
+#include "isp4_interface.h"
|
|
+
|
|
+#define ISP4DBG_FW_LOG_RINGBUF_SIZE (2 * 1024 * 1024)
|
|
+#define ISP4DBG_MACRO_2_STR(X) #X
|
|
+#define ISP4DBG_ONE_TIME_LOG_LEN 510
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+
|
|
+void isp_debugfs_create(struct isp4_device *isp_dev)
|
|
+{
|
|
+ isp_dev->isp_subdev.debugfs_dir = debugfs_create_dir("amd_isp4", NULL);
|
|
+ debugfs_create_bool("fw_log_enable", 0644,
|
|
+ isp_dev->isp_subdev.debugfs_dir,
|
|
+ &isp_dev->isp_subdev.enable_fw_log);
|
|
+ isp_dev->isp_subdev.fw_log_output =
|
|
+ devm_kzalloc(isp_dev->isp_subdev.dev,
|
|
+ ISP4DBG_FW_LOG_RINGBUF_SIZE + 32,
|
|
+ GFP_KERNEL);
|
|
+}
|
|
+
|
|
+void isp_debugfs_remove(struct isp4_device *isp_dev)
|
|
+{
|
|
+ debugfs_remove_recursive(isp_dev->isp_subdev.debugfs_dir);
|
|
+ isp_dev->isp_subdev.debugfs_dir = NULL;
|
|
+}
|
|
+
|
|
+static u32 isp_fw_fill_rb_log(struct isp4_subdev *isp, void *sys, u32 rb_size)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp->ispif;
|
|
+ char *buf = isp->fw_log_output;
|
|
+ struct device *dev = isp->dev;
|
|
+ u32 rd_ptr, wr_ptr;
|
|
+ u32 total_cnt = 0;
|
|
+ u32 offset = 0;
|
|
+ u32 cnt;
|
|
+
|
|
+ if (!sys || !rb_size)
|
|
+ return 0;
|
|
+
|
|
+ guard(mutex)(&ispif->isp4if_mutex);
|
|
+
|
|
+ rd_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_RPTR0);
|
|
+ wr_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_WPTR0);
|
|
+
|
|
+ do {
|
|
+ if (wr_ptr > rd_ptr)
|
|
+ cnt = wr_ptr - rd_ptr;
|
|
+ else if (wr_ptr < rd_ptr)
|
|
+ cnt = rb_size - rd_ptr;
|
|
+ else
|
|
+ goto quit;
|
|
+
|
|
+ if (cnt > rb_size) {
|
|
+ dev_err(dev, "fail bad fw log size %u\n", cnt);
|
|
+ goto quit;
|
|
+ }
|
|
+
|
|
+ memcpy(buf + offset, sys + rd_ptr, cnt);
|
|
+
|
|
+ offset += cnt;
|
|
+ total_cnt += cnt;
|
|
+ rd_ptr = (rd_ptr + cnt) % rb_size;
|
|
+ } while (rd_ptr < wr_ptr);
|
|
+
|
|
+ isp4hw_wreg(isp->mmio, ISP_LOG_RB_RPTR0, rd_ptr);
|
|
+
|
|
+quit:
|
|
+ return total_cnt;
|
|
+}
|
|
+
|
|
+void isp_fw_log_print(struct isp4_subdev *isp)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp->ispif;
|
|
+ char *fw_log_buf = isp->fw_log_output;
|
|
+ u32 cnt;
|
|
+
|
|
+ if (!isp->enable_fw_log || !fw_log_buf)
|
|
+ return;
|
|
+
|
|
+ cnt = isp_fw_fill_rb_log(isp, ispif->fw_log_buf->sys_addr,
|
|
+ ispif->fw_log_buf->mem_size);
|
|
+
|
|
+ if (cnt) {
|
|
+ char temp_ch;
|
|
+ char *str;
|
|
+ char *end;
|
|
+ /* line end */
|
|
+ char *le;
|
|
+
|
|
+ str = (char *)fw_log_buf;
|
|
+ end = ((char *)fw_log_buf + cnt);
|
|
+ fw_log_buf[cnt] = 0;
|
|
+
|
|
+ while (str < end) {
|
|
+ le = strchr(str, 0x0A);
|
|
+ if ((le && str + ISP4DBG_ONE_TIME_LOG_LEN >= le) ||
|
|
+ (!le && str + ISP4DBG_ONE_TIME_LOG_LEN >= end)) {
|
|
+ if (le)
|
|
+ *le = 0;
|
|
+
|
|
+ if (*str != '\0')
|
|
+ dev_dbg(isp->dev, "%s", str);
|
|
+
|
|
+ if (le) {
|
|
+ *le = 0x0A;
|
|
+ str = le + 1;
|
|
+ } else {
|
|
+ break;
|
|
+ }
|
|
+ } else {
|
|
+ u32 tmp_len = ISP4DBG_ONE_TIME_LOG_LEN;
|
|
+
|
|
+ temp_ch = str[tmp_len];
|
|
+ str[tmp_len] = 0;
|
|
+ dev_dbg(isp->dev, "%s", str);
|
|
+ str[tmp_len] = temp_ch;
|
|
+ str = &str[tmp_len];
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+char *isp4dbg_get_buf_src_str(u32 src)
|
|
+{
|
|
+ switch (src) {
|
|
+ case ISP4FW_BUFFER_SOURCE_STREAM:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_SOURCE_STREAM);
|
|
+ default:
|
|
+ return "Unknown buf source";
|
|
+ }
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_buf_done_str(u32 status)
|
|
+{
|
|
+ switch (status) {
|
|
+ case ISP4FW_BUFFER_STATUS_INVALID:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_INVALID);
|
|
+ case ISP4FW_BUFFER_STATUS_SKIPPED:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_SKIPPED);
|
|
+ case ISP4FW_BUFFER_STATUS_EXIST:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_EXIST);
|
|
+ case ISP4FW_BUFFER_STATUS_DONE:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DONE);
|
|
+ case ISP4FW_BUFFER_STATUS_LACK:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_LACK);
|
|
+ case ISP4FW_BUFFER_STATUS_DIRTY:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DIRTY);
|
|
+ case ISP4FW_BUFFER_STATUS_MAX:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_MAX);
|
|
+ default:
|
|
+ return "Unknown Buf Done Status";
|
|
+ }
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_img_fmt_str(int fmt /* enum isp4fw_image_format * */)
|
|
+{
|
|
+ switch (fmt) {
|
|
+ case ISP4FW_IMAGE_FORMAT_NV12:
|
|
+ return "NV12";
|
|
+ case ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED:
|
|
+ return "YUV422INTERLEAVED";
|
|
+ default:
|
|
+ return "unknown fmt";
|
|
+ }
|
|
+}
|
|
+
|
|
+void isp4dbg_show_bufmeta_info(struct device *dev, char *pre,
|
|
+ void *in, void *orig_buf)
|
|
+{
|
|
+ struct isp4fw_buffer_meta_info *p;
|
|
+ struct isp4if_img_buf_info *orig;
|
|
+
|
|
+ if (!in)
|
|
+ return;
|
|
+
|
|
+ if (!pre)
|
|
+ pre = "";
|
|
+
|
|
+ p = in;
|
|
+ orig = orig_buf;
|
|
+
|
|
+ dev_dbg(dev, "%s(%s) en:%d,stat:%s(%u),src:%s\n", pre,
|
|
+ isp4dbg_get_img_fmt_str(p->image_prop.image_format),
|
|
+ p->enabled, isp4dbg_get_buf_done_str(p->status), p->status,
|
|
+ isp4dbg_get_buf_src_str(p->source));
|
|
+
|
|
+ dev_dbg(dev, "%p,0x%llx(%u) %p,0x%llx(%u) %p,0x%llx(%u)\n",
|
|
+ orig->planes[0].sys_addr, orig->planes[0].mc_addr,
|
|
+ orig->planes[0].len, orig->planes[1].sys_addr,
|
|
+ orig->planes[1].mc_addr, orig->planes[1].len,
|
|
+ orig->planes[2].sys_addr, orig->planes[2].mc_addr,
|
|
+ orig->planes[2].len);
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_buf_type(u32 type)
|
|
+{
|
|
+ /* enum isp4fw_buffer_type */
|
|
+ switch (type) {
|
|
+ case ISP4FW_BUFFER_TYPE_PREVIEW:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_PREVIEW);
|
|
+ case ISP4FW_BUFFER_TYPE_META_INFO:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_META_INFO);
|
|
+ case ISP4FW_BUFFER_TYPE_MEM_POOL:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_MEM_POOL);
|
|
+ default:
|
|
+ return "unknown type";
|
|
+ }
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_cmd_str(u32 cmd)
|
|
+{
|
|
+ switch (cmd) {
|
|
+ case ISP4FW_CMD_ID_START_STREAM:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_START_STREAM);
|
|
+ case ISP4FW_CMD_ID_STOP_STREAM:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_STOP_STREAM);
|
|
+ case ISP4FW_CMD_ID_SEND_BUFFER:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SEND_BUFFER);
|
|
+ case ISP4FW_CMD_ID_SET_STREAM_CONFIG:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_STREAM_CONFIG);
|
|
+ case ISP4FW_CMD_ID_SET_OUT_CHAN_PROP:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_OUT_CHAN_PROP);
|
|
+ case ISP4FW_CMD_ID_ENABLE_OUT_CHAN:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_ENABLE_OUT_CHAN);
|
|
+ default:
|
|
+ return "unknown cmd";
|
|
+ }
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_resp_str(u32 cmd)
|
|
+{
|
|
+ switch (cmd) {
|
|
+ case ISP4FW_RESP_ID_CMD_DONE:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_CMD_DONE);
|
|
+ case ISP4FW_RESP_ID_NOTI_FRAME_DONE:
|
|
+ return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_NOTI_FRAME_DONE);
|
|
+ default:
|
|
+ return "unknown respid";
|
|
+ }
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_if_stream_str(u32 stream /* enum fw_cmd_resp_stream_id */)
|
|
+{
|
|
+ switch (stream) {
|
|
+ case ISP4IF_STREAM_ID_GLOBAL:
|
|
+ return "STREAM_GLOBAL";
|
|
+ case ISP4IF_STREAM_ID_1:
|
|
+ return "STREAM1";
|
|
+ default:
|
|
+ return "unknown streamID";
|
|
+ }
|
|
+}
|
|
+
|
|
+char *isp4dbg_get_out_ch_str(int ch /* enum isp4fw_pipe_out_ch */)
|
|
+{
|
|
+ switch ((enum isp4fw_pipe_out_ch)ch) {
|
|
+ case ISP4FW_ISP_PIPE_OUT_CH_PREVIEW:
|
|
+ return "prev";
|
|
+ default:
|
|
+ return "unknown channel";
|
|
+ }
|
|
+}
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_debug.h b/drivers/media/platform/amd/isp4/isp4_debug.h
|
|
new file mode 100644
|
|
index 0000000000000..d1262e03ae640
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_debug.h
|
|
@@ -0,0 +1,41 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_DEBUG_H_
|
|
+#define _ISP4_DEBUG_H_
|
|
+
|
|
+#include <linux/dev_printk.h>
|
|
+#include <linux/printk.h>
|
|
+
|
|
+#include "isp4_subdev.h"
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+struct isp4_device;
|
|
+
|
|
+void isp_debugfs_create(struct isp4_device *isp_dev);
|
|
+void isp_debugfs_remove(struct isp4_device *isp_dev);
|
|
+void isp_fw_log_print(struct isp4_subdev *isp);
|
|
+
|
|
+#else
|
|
+
|
|
+/* to avoid checkpatch warning */
|
|
+#define isp_debugfs_create(cam) ((void)(cam))
|
|
+#define isp_debugfs_remove(cam) ((void)(cam))
|
|
+#define isp_fw_log_print(isp) ((void)(isp))
|
|
+
|
|
+#endif /* CONFIG_DEBUG_FS */
|
|
+
|
|
+void isp4dbg_show_bufmeta_info(struct device *dev, char *pre, void *p,
|
|
+ void *orig_buf /* struct sys_img_buf_handle */);
|
|
+char *isp4dbg_get_img_fmt_str(int fmt /* enum _image_format_t */);
|
|
+char *isp4dbg_get_out_ch_str(int ch /* enum _isp_pipe_out_ch_t */);
|
|
+char *isp4dbg_get_cmd_str(u32 cmd);
|
|
+char *isp4dbg_get_buf_type(u32 type);/* enum _buffer_type_t */
|
|
+char *isp4dbg_get_resp_str(u32 resp);
|
|
+char *isp4dbg_get_buf_src_str(u32 src);
|
|
+char *isp4dbg_get_buf_done_str(u32 status);
|
|
+char *isp4dbg_get_if_stream_str(u32 stream);
|
|
+
|
|
+#endif /* _ISP4_DEBUG_H_ */
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h b/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h
|
|
new file mode 100644
|
|
index 0000000000000..88bacb00355c4
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h
|
|
@@ -0,0 +1,318 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_FW_CMD_RESP_H_
|
|
+#define _ISP4_FW_CMD_RESP_H_
|
|
+
|
|
+/*
|
|
+ * Two types of command/response channel.
|
|
+ * Type Global Command has one command/response channel.
|
|
+ * Type Stream Command has one command/response channel.
|
|
+ *----------- ------------
|
|
+ *| | --------------------------- | |
|
|
+ *| | ---->| Global Command |----> | |
|
|
+ *| | --------------------------- | |
|
|
+ *| | | |
|
|
+ *| | | |
|
|
+ *| | --------------------------- | |
|
|
+ *| | ---->| Stream Command |----> | |
|
|
+ *| | --------------------------- | |
|
|
+ *| | | |
|
|
+ *| | | |
|
|
+ *| | | |
|
|
+ *| HOST | | Firmware |
|
|
+ *| | | |
|
|
+ *| | | |
|
|
+ *| | -------------------------- | |
|
|
+ *| | <----| Global Response |<---- | |
|
|
+ *| | -------------------------- | |
|
|
+ *| | | |
|
|
+ *| | | |
|
|
+ *| | -------------------------- | |
|
|
+ *| | <----| Stream Response |<---- | |
|
|
+ *| | -------------------------- | |
|
|
+ *| | | |
|
|
+ *| | | |
|
|
+ *----------- ------------
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * cmd_id is in the format of following type:
|
|
+ * type: indicate command type, global/stream commands.
|
|
+ * group: indicate the command group.
|
|
+ * id: A unique command identification in one type and group.
|
|
+ * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->|
|
|
+ * | type | group | id |
|
|
+ */
|
|
+
|
|
+#define ISP4FW_CMD_TYPE_SHIFT 24
|
|
+#define ISP4FW_CMD_GROUP_SHIFT 16
|
|
+#define ISP4FW_CMD_TYPE_STREAM_CTRL (0x2U << ISP4FW_CMD_TYPE_SHIFT)
|
|
+
|
|
+#define ISP4FW_CMD_GROUP_STREAM_CTRL (0x1U << ISP4FW_CMD_GROUP_SHIFT)
|
|
+#define ISP4FW_CMD_GROUP_STREAM_BUFFER (0x4U << ISP4FW_CMD_GROUP_SHIFT)
|
|
+
|
|
+/* Stream Command */
|
|
+#define ISP4FW_CMD_ID_SET_STREAM_CONFIG (ISP4FW_CMD_TYPE_STREAM_CTRL\
|
|
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x1)
|
|
+#define ISP4FW_CMD_ID_SET_OUT_CHAN_PROP (ISP4FW_CMD_TYPE_STREAM_CTRL\
|
|
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x3)
|
|
+#define ISP4FW_CMD_ID_ENABLE_OUT_CHAN (ISP4FW_CMD_TYPE_STREAM_CTRL\
|
|
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x5)
|
|
+#define ISP4FW_CMD_ID_START_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\
|
|
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x7)
|
|
+#define ISP4FW_CMD_ID_STOP_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\
|
|
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x8)
|
|
+
|
|
+/* Stream Buffer Command */
|
|
+#define ISP4FW_CMD_ID_SEND_BUFFER (ISP4FW_CMD_TYPE_STREAM_CTRL\
|
|
+ | ISP4FW_CMD_GROUP_STREAM_BUFFER | 0x1)
|
|
+
|
|
+/*
|
|
+ * resp_id is in the format of following type:
|
|
+ * type: indicate command type, global/stream commands.
|
|
+ * group: indicate the command group.
|
|
+ * id: A unique command identification in one type and group.
|
|
+ * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->|
|
|
+ * | type | group | id |
|
|
+ */
|
|
+
|
|
+#define ISP4FW_RESP_GROUP_SHIFT 16
|
|
+
|
|
+#define ISP4FW_RESP_GROUP_GENERAL (0x1 << ISP4FW_RESP_GROUP_SHIFT)
|
|
+#define ISP4FW_RESP_GROUP_NOTIFICATION (0x3 << ISP4FW_RESP_GROUP_SHIFT)
|
|
+
|
|
+/* General Response */
|
|
+#define ISP4FW_RESP_ID_CMD_DONE (ISP4FW_RESP_GROUP_GENERAL | 0x1)
|
|
+
|
|
+/* Notification */
|
|
+#define ISP4FW_RESP_ID_NOTI_FRAME_DONE (ISP4FW_RESP_GROUP_NOTIFICATION | 0x1)
|
|
+
|
|
+#define ISP4FW_CMD_STATUS_SUCCESS 0
|
|
+#define ISP4FW_CMD_STATUS_FAIL 1
|
|
+#define ISP4FW_CMD_STATUS_SKIPPED 2
|
|
+
|
|
+#define ISP4FW_ADDR_SPACE_TYPE_GPU_VA 4
|
|
+
|
|
+#define ISP4FW_MEMORY_POOL_SIZE (100 * 1024 * 1024)
|
|
+
|
|
+/*
|
|
+ * standard ISP pipeline: mipicsi=>isp
|
|
+ */
|
|
+#define ISP4FW_MIPI0_ISP_PIPELINE_ID 0x5f91
|
|
+
|
|
+enum isp4fw_sensor_id {
|
|
+ /* Sensor id for ISP input from MIPI port 0 */
|
|
+ ISP4FW_SENSOR_ID_ON_MIPI0 = 0,
|
|
+};
|
|
+
|
|
+enum isp4fw_stream_id {
|
|
+ ISP4FW_STREAM_ID_INVALID = -1,
|
|
+ ISP4FW_STREAM_ID_1 = 0,
|
|
+ ISP4FW_STREAM_ID_2 = 1,
|
|
+ ISP4FW_STREAM_ID_3 = 2,
|
|
+ ISP4FW_STREAM_ID_MAXIMUM
|
|
+};
|
|
+
|
|
+enum isp4fw_image_format {
|
|
+ /* 4:2:0,semi-planar, 8-bit */
|
|
+ ISP4FW_IMAGE_FORMAT_NV12 = 1,
|
|
+ /* interleave, 4:2:2, 8-bit */
|
|
+ ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED = 7,
|
|
+};
|
|
+
|
|
+enum isp4fw_pipe_out_ch {
|
|
+ ISP4FW_ISP_PIPE_OUT_CH_PREVIEW = 0,
|
|
+};
|
|
+
|
|
+enum isp4fw_yuv_range {
|
|
+ ISP4FW_ISP_YUV_RANGE_FULL = 0, /* YUV value range in 0~255 */
|
|
+ ISP4FW_ISP_YUV_RANGE_NARROW = 1, /* YUV value range in 16~235 */
|
|
+ ISP4FW_ISP_YUV_RANGE_MAX
|
|
+};
|
|
+
|
|
+enum isp4fw_buffer_type {
|
|
+ ISP4FW_BUFFER_TYPE_PREVIEW = 8,
|
|
+ ISP4FW_BUFFER_TYPE_META_INFO = 10,
|
|
+ ISP4FW_BUFFER_TYPE_MEM_POOL = 15,
|
|
+};
|
|
+
|
|
+enum isp4fw_buffer_status {
|
|
+ /* The buffer is INVALID */
|
|
+ ISP4FW_BUFFER_STATUS_INVALID,
|
|
+ /* The buffer is not filled with image data */
|
|
+ ISP4FW_BUFFER_STATUS_SKIPPED,
|
|
+ /* The buffer is available and awaiting to be filled */
|
|
+ ISP4FW_BUFFER_STATUS_EXIST,
|
|
+ /* The buffer is filled with image data */
|
|
+ ISP4FW_BUFFER_STATUS_DONE,
|
|
+ /* The buffer is unavailable */
|
|
+ ISP4FW_BUFFER_STATUS_LACK,
|
|
+ /* The buffer is dirty, probably caused by LMI leakage */
|
|
+ ISP4FW_BUFFER_STATUS_DIRTY,
|
|
+ ISP4FW_BUFFER_STATUS_MAX
|
|
+};
|
|
+
|
|
+enum isp4fw_buffer_source {
|
|
+ /* The buffer is from the stream buffer queue */
|
|
+ ISP4FW_BUFFER_SOURCE_STREAM,
|
|
+};
|
|
+
|
|
+struct isp4fw_error_code {
|
|
+ u32 code1;
|
|
+ u32 code2;
|
|
+ u32 code3;
|
|
+ u32 code4;
|
|
+ u32 code5;
|
|
+};
|
|
+
|
|
+/* Command Structure for FW */
|
|
+
|
|
+struct isp4fw_cmd {
|
|
+ u32 cmd_seq_num;
|
|
+ u32 cmd_id;
|
|
+ u32 cmd_param[12];
|
|
+ u16 cmd_stream_id;
|
|
+ u8 cmd_silent_resp;
|
|
+ u8 reserved;
|
|
+ u32 cmd_check_sum;
|
|
+};
|
|
+
|
|
+struct isp4fw_resp_cmd_done {
|
|
+ /*
|
|
+ * The host2fw command seqNum.
|
|
+ * To indicate which command this response refers to.
|
|
+ */
|
|
+ u32 cmd_seq_num;
|
|
+ /* The host2fw command id for host double check. */
|
|
+ u32 cmd_id;
|
|
+ /*
|
|
+ * Indicate the command process status.
|
|
+ * 0 means success. 1 means fail. 2 means skipped
|
|
+ */
|
|
+ u16 cmd_status;
|
|
+ /*
|
|
+ * If cmd_status is 1, the command failed. The host can check
|
|
+ * isp4fw_error_code for details.
|
|
+ */
|
|
+ u16 isp4fw_error_code;
|
|
+ /* The response payload type varies by cmd. */
|
|
+ u8 payload[36];
|
|
+};
|
|
+
|
|
+struct isp4fw_resp_param_package {
|
|
+ u32 package_addr_lo; /* The low 32 bit of the pkg address. */
|
|
+ u32 package_addr_hi; /* The high 32 bit of the pkg address. */
|
|
+ u32 package_size; /* The total pkg size in bytes. */
|
|
+ u32 package_check_sum; /* The byte sum of the pkg. */
|
|
+};
|
|
+
|
|
+struct isp4fw_resp {
|
|
+ u32 resp_seq_num;
|
|
+ u32 resp_id;
|
|
+ union {
|
|
+ struct isp4fw_resp_cmd_done cmd_done;
|
|
+ struct isp4fw_resp_param_package frame_done;
|
|
+ u32 resp_param[12];
|
|
+ } param;
|
|
+ u8 reserved[4];
|
|
+ u32 resp_check_sum;
|
|
+};
|
|
+
|
|
+struct isp4fw_mipi_pipe_path_cfg {
|
|
+ u32 b_enable;
|
|
+ enum isp4fw_sensor_id isp4fw_sensor_id;
|
|
+};
|
|
+
|
|
+struct isp4fw_isp_pipe_path_cfg {
|
|
+ u32 isp_pipe_id; /* pipe ids for pipeline construction */
|
|
+};
|
|
+
|
|
+struct isp4fw_isp_stream_cfg {
|
|
+ /* Isp mipi path */
|
|
+ struct isp4fw_mipi_pipe_path_cfg mipi_pipe_path_cfg;
|
|
+ /* Isp pipe path */
|
|
+ struct isp4fw_isp_pipe_path_cfg isp_pipe_path_cfg;
|
|
+ /* enable TNR */
|
|
+ u32 b_enable_tnr;
|
|
+ /*
|
|
+ * Number of frames for RTA processing.
|
|
+ * Set to 0 to use the firmware's default value.
|
|
+ */
|
|
+ u32 rta_frames_per_proc;
|
|
+};
|
|
+
|
|
+struct isp4fw_image_prop {
|
|
+ enum isp4fw_image_format image_format;
|
|
+ u32 width;
|
|
+ u32 height;
|
|
+ u32 luma_pitch;
|
|
+ u32 chroma_pitch;
|
|
+ enum isp4fw_yuv_range yuv_range;
|
|
+};
|
|
+
|
|
+struct isp4fw_buffer {
|
|
+ /*
|
|
+ * A check num for debug usage, host can set the buf_tags
|
|
+ * to different numbers
|
|
+ */
|
|
+ u32 buf_tags;
|
|
+ union {
|
|
+ u32 value;
|
|
+ struct {
|
|
+ u32 space : 16;
|
|
+ u32 vmid : 16;
|
|
+ } bit;
|
|
+ } vmid_space;
|
|
+ u32 buf_base_a_lo; /* Low address of buffer A */
|
|
+ u32 buf_base_a_hi; /* High address of buffer A */
|
|
+ u32 buf_size_a; /* Buffer size of buffer A */
|
|
+
|
|
+ u32 buf_base_b_lo; /* Low address of buffer B */
|
|
+ u32 buf_base_b_hi; /* High address of buffer B */
|
|
+ u32 buf_size_b; /* Buffer size of buffer B */
|
|
+
|
|
+ u32 buf_base_c_lo; /* Low address of buffer C */
|
|
+ u32 buf_base_c_hi; /* High address of buffer C */
|
|
+ u32 buf_size_c; /* Buffer size of buffer C */
|
|
+};
|
|
+
|
|
+struct isp4fw_buffer_meta_info {
|
|
+ u32 enabled; /* enabled flag */
|
|
+ enum isp4fw_buffer_status status; /* BufferStatus */
|
|
+ struct isp4fw_error_code err; /* err code */
|
|
+ enum isp4fw_buffer_source source; /* BufferSource */
|
|
+ struct isp4fw_image_prop image_prop; /* image_prop */
|
|
+ struct isp4fw_buffer buffer; /* buffer info */
|
|
+};
|
|
+
|
|
+struct isp4fw_meta_info {
|
|
+ u32 poc; /* frame id */
|
|
+ u32 fc_id; /* frame ctl id */
|
|
+ u32 time_stamp_lo; /* timestamp low 32 bits */
|
|
+ u32 time_stamp_hi; /* timestamp_high 32 bits */
|
|
+ struct isp4fw_buffer_meta_info preview; /* preview BufferMetaInfo */
|
|
+};
|
|
+
|
|
+struct isp4fw_cmd_send_buffer {
|
|
+ enum isp4fw_buffer_type buffer_type;
|
|
+ struct isp4fw_buffer buffer; /* buffer info */
|
|
+};
|
|
+
|
|
+struct isp4fw_cmd_set_out_ch_prop {
|
|
+ enum isp4fw_pipe_out_ch ch; /* ISP output channel */
|
|
+ struct isp4fw_image_prop image_prop; /* image property */
|
|
+};
|
|
+
|
|
+struct isp4fw_cmd_enable_out_ch {
|
|
+ enum isp4fw_pipe_out_ch ch; /* ISP output channel */
|
|
+ u32 is_enable; /* If channel is enabled or not */
|
|
+};
|
|
+
|
|
+struct isp4fw_cmd_set_stream_cfg {
|
|
+ struct isp4fw_isp_stream_cfg stream_cfg; /* stream path config */
|
|
+};
|
|
+
|
|
+#endif /* _ISP4_FW_CMD_RESP_H_ */
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_hw_reg.h b/drivers/media/platform/amd/isp4/isp4_hw_reg.h
|
|
new file mode 100644
|
|
index 0000000000000..09c76f75c5ee9
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_hw_reg.h
|
|
@@ -0,0 +1,124 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_HW_REG_H_
|
|
+#define _ISP4_HW_REG_H_
|
|
+
|
|
+#include <linux/io.h>
|
|
+
|
|
+#define ISP_SOFT_RESET 0x62000
|
|
+#define ISP_SYS_INT0_EN 0x62010
|
|
+#define ISP_SYS_INT0_STATUS 0x62014
|
|
+#define ISP_SYS_INT0_ACK 0x62018
|
|
+#define ISP_CCPU_CNTL 0x62054
|
|
+#define ISP_STATUS 0x62058
|
|
+#define ISP_LOG_RB_BASE_LO0 0x62148
|
|
+#define ISP_LOG_RB_BASE_HI0 0x6214c
|
|
+#define ISP_LOG_RB_SIZE0 0x62150
|
|
+#define ISP_LOG_RB_RPTR0 0x62154
|
|
+#define ISP_LOG_RB_WPTR0 0x62158
|
|
+#define ISP_RB_BASE_LO1 0x62170
|
|
+#define ISP_RB_BASE_HI1 0x62174
|
|
+#define ISP_RB_SIZE1 0x62178
|
|
+#define ISP_RB_RPTR1 0x6217c
|
|
+#define ISP_RB_WPTR1 0x62180
|
|
+#define ISP_RB_BASE_LO2 0x62184
|
|
+#define ISP_RB_BASE_HI2 0x62188
|
|
+#define ISP_RB_SIZE2 0x6218c
|
|
+#define ISP_RB_RPTR2 0x62190
|
|
+#define ISP_RB_WPTR2 0x62194
|
|
+#define ISP_RB_BASE_LO3 0x62198
|
|
+#define ISP_RB_BASE_HI3 0x6219c
|
|
+#define ISP_RB_SIZE3 0x621a0
|
|
+#define ISP_RB_RPTR3 0x621a4
|
|
+#define ISP_RB_WPTR3 0x621a8
|
|
+#define ISP_RB_BASE_LO4 0x621ac
|
|
+#define ISP_RB_BASE_HI4 0x621b0
|
|
+#define ISP_RB_SIZE4 0x621b4
|
|
+#define ISP_RB_RPTR4 0x621b8
|
|
+#define ISP_RB_WPTR4 0x621bc
|
|
+#define ISP_RB_BASE_LO5 0x621c0
|
|
+#define ISP_RB_BASE_HI5 0x621c4
|
|
+#define ISP_RB_SIZE5 0x621c8
|
|
+#define ISP_RB_RPTR5 0x621cc
|
|
+#define ISP_RB_WPTR5 0x621d0
|
|
+#define ISP_RB_BASE_LO6 0x621d4
|
|
+#define ISP_RB_BASE_HI6 0x621d8
|
|
+#define ISP_RB_SIZE6 0x621dc
|
|
+#define ISP_RB_RPTR6 0x621e0
|
|
+#define ISP_RB_WPTR6 0x621e4
|
|
+#define ISP_RB_BASE_LO7 0x621e8
|
|
+#define ISP_RB_BASE_HI7 0x621ec
|
|
+#define ISP_RB_SIZE7 0x621f0
|
|
+#define ISP_RB_RPTR7 0x621f4
|
|
+#define ISP_RB_WPTR7 0x621f8
|
|
+#define ISP_RB_BASE_LO8 0x621fc
|
|
+#define ISP_RB_BASE_HI8 0x62200
|
|
+#define ISP_RB_SIZE8 0x62204
|
|
+#define ISP_RB_RPTR8 0x62208
|
|
+#define ISP_RB_WPTR8 0x6220c
|
|
+#define ISP_RB_BASE_LO9 0x62210
|
|
+#define ISP_RB_BASE_HI9 0x62214
|
|
+#define ISP_RB_SIZE9 0x62218
|
|
+#define ISP_RB_RPTR9 0x6221c
|
|
+#define ISP_RB_WPTR9 0x62220
|
|
+#define ISP_RB_BASE_LO10 0x62224
|
|
+#define ISP_RB_BASE_HI10 0x62228
|
|
+#define ISP_RB_SIZE10 0x6222c
|
|
+#define ISP_RB_RPTR10 0x62230
|
|
+#define ISP_RB_WPTR10 0x62234
|
|
+#define ISP_RB_BASE_LO11 0x62238
|
|
+#define ISP_RB_BASE_HI11 0x6223c
|
|
+#define ISP_RB_SIZE11 0x62240
|
|
+#define ISP_RB_RPTR11 0x62244
|
|
+#define ISP_RB_WPTR11 0x62248
|
|
+#define ISP_RB_BASE_LO12 0x6224c
|
|
+#define ISP_RB_BASE_HI12 0x62250
|
|
+#define ISP_RB_SIZE12 0x62254
|
|
+#define ISP_RB_RPTR12 0x62258
|
|
+#define ISP_RB_WPTR12 0x6225c
|
|
+
|
|
+#define ISP_POWER_STATUS 0x60000
|
|
+
|
|
+/* ISP_SOFT_RESET */
|
|
+#define ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK 0x00000001UL
|
|
+
|
|
+/* ISP_CCPU_CNTL */
|
|
+#define ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK 0x00040000UL
|
|
+
|
|
+/* ISP_STATUS */
|
|
+#define ISP_STATUS__CCPU_REPORT_MASK 0x000000feUL
|
|
+
|
|
+/* ISP_SYS_INT0_STATUS */
|
|
+#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK 0x00010000UL
|
|
+#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT10_INT_MASK 0x00040000UL
|
|
+#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT11_INT_MASK 0x00100000UL
|
|
+#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK 0x00400000UL
|
|
+
|
|
+/* ISP_SYS_INT0_EN */
|
|
+#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK 0x00010000UL
|
|
+#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT10_EN_MASK 0x00040000UL
|
|
+#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT11_EN_MASK 0x00100000UL
|
|
+#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK 0x00400000UL
|
|
+
|
|
+/* ISP_SYS_INT0_ACK */
|
|
+#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK 0x00010000UL
|
|
+#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT10_ACK_MASK 0x00040000UL
|
|
+#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT11_ACK_MASK 0x00100000UL
|
|
+#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK 0x00400000UL
|
|
+
|
|
+/* Helper functions for reading isp registers */
|
|
+static inline u32 isp4hw_rreg(void __iomem *base, u32 reg)
|
|
+{
|
|
+ return readl(base + reg);
|
|
+}
|
|
+
|
|
+/* Helper functions for writing isp registers */
|
|
+static inline void isp4hw_wreg(void __iomem *base, u32 reg, u32 val)
|
|
+{
|
|
+ return writel(val, base + reg);
|
|
+}
|
|
+
|
|
+#endif /* _ISP4_HW_REG_H_ */
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_interface.c b/drivers/media/platform/amd/isp4/isp4_interface.c
|
|
new file mode 100644
|
|
index 0000000000000..8d73f66bb42cb
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_interface.c
|
|
@@ -0,0 +1,832 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#include <linux/iopoll.h>
|
|
+
|
|
+#include "isp4_debug.h"
|
|
+#include "isp4_fw_cmd_resp.h"
|
|
+#include "isp4_hw_reg.h"
|
|
+#include "isp4_interface.h"
|
|
+
|
|
+#define ISP4IF_FW_RESP_RB_IRQ_EN_MASK \
|
|
+ (ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK\
|
|
+ | ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK)
|
|
+
|
|
+#define ISP4IF_FW_CMD_TIMEOUT (HZ / 2)
|
|
+
|
|
+struct isp4if_rb_config {
|
|
+ const char *name;
|
|
+ u32 index;
|
|
+ u32 reg_rptr;
|
|
+ u32 reg_wptr;
|
|
+ u32 reg_base_lo;
|
|
+ u32 reg_base_hi;
|
|
+ u32 reg_size;
|
|
+ u32 val_size;
|
|
+ u64 base_mc_addr;
|
|
+ void *base_sys_addr;
|
|
+};
|
|
+
|
|
+/* FW cmd ring buffer configuration */
|
|
+static struct isp4if_rb_config isp4if_cmd_rb_config[ISP4IF_STREAM_ID_MAX] = {
|
|
+ {
|
|
+ .name = "CMD_RB_GBL0",
|
|
+ .index = 3,
|
|
+ .reg_rptr = ISP_RB_RPTR4,
|
|
+ .reg_wptr = ISP_RB_WPTR4,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO4,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI4,
|
|
+ .reg_size = ISP_RB_SIZE4,
|
|
+ },
|
|
+ {
|
|
+ .name = "CMD_RB_STR1",
|
|
+ .index = 0,
|
|
+ .reg_rptr = ISP_RB_RPTR1,
|
|
+ .reg_wptr = ISP_RB_WPTR1,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO1,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI1,
|
|
+ .reg_size = ISP_RB_SIZE1,
|
|
+ },
|
|
+ {
|
|
+ .name = "CMD_RB_STR2",
|
|
+ .index = 1,
|
|
+ .reg_rptr = ISP_RB_RPTR2,
|
|
+ .reg_wptr = ISP_RB_WPTR2,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO2,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI2,
|
|
+ .reg_size = ISP_RB_SIZE2,
|
|
+ },
|
|
+ {
|
|
+ .name = "CMD_RB_STR3",
|
|
+ .index = 2,
|
|
+ .reg_rptr = ISP_RB_RPTR3,
|
|
+ .reg_wptr = ISP_RB_WPTR3,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO3,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI3,
|
|
+ .reg_size = ISP_RB_SIZE3,
|
|
+ },
|
|
+};
|
|
+
|
|
+/* FW resp ring buffer configuration */
|
|
+static struct isp4if_rb_config isp4if_resp_rb_config[ISP4IF_STREAM_ID_MAX] = {
|
|
+ {
|
|
+ .name = "RES_RB_GBL0",
|
|
+ .index = 3,
|
|
+ .reg_rptr = ISP_RB_RPTR12,
|
|
+ .reg_wptr = ISP_RB_WPTR12,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO12,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI12,
|
|
+ .reg_size = ISP_RB_SIZE12,
|
|
+ },
|
|
+ {
|
|
+ .name = "RES_RB_STR1",
|
|
+ .index = 0,
|
|
+ .reg_rptr = ISP_RB_RPTR9,
|
|
+ .reg_wptr = ISP_RB_WPTR9,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO9,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI9,
|
|
+ .reg_size = ISP_RB_SIZE9,
|
|
+ },
|
|
+ {
|
|
+ .name = "RES_RB_STR2",
|
|
+ .index = 1,
|
|
+ .reg_rptr = ISP_RB_RPTR10,
|
|
+ .reg_wptr = ISP_RB_WPTR10,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO10,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI10,
|
|
+ .reg_size = ISP_RB_SIZE10,
|
|
+ },
|
|
+ {
|
|
+ .name = "RES_RB_STR3",
|
|
+ .index = 2,
|
|
+ .reg_rptr = ISP_RB_RPTR11,
|
|
+ .reg_wptr = ISP_RB_WPTR11,
|
|
+ .reg_base_lo = ISP_RB_BASE_LO11,
|
|
+ .reg_base_hi = ISP_RB_BASE_HI11,
|
|
+ .reg_size = ISP_RB_SIZE11,
|
|
+ },
|
|
+};
|
|
+
|
|
+/* FW log ring buffer configuration */
|
|
+static struct isp4if_rb_config isp4if_log_rb_config = {
|
|
+ .name = "LOG_RB",
|
|
+ .index = 0,
|
|
+ .reg_rptr = ISP_LOG_RB_RPTR0,
|
|
+ .reg_wptr = ISP_LOG_RB_WPTR0,
|
|
+ .reg_base_lo = ISP_LOG_RB_BASE_LO0,
|
|
+ .reg_base_hi = ISP_LOG_RB_BASE_HI0,
|
|
+ .reg_size = ISP_LOG_RB_SIZE0,
|
|
+};
|
|
+
|
|
+static struct isp4if_gpu_mem_info *
|
|
+isp4if_gpu_mem_alloc(struct isp4_interface *ispif, u32 mem_size)
|
|
+{
|
|
+ struct isp4if_gpu_mem_info *mem_info;
|
|
+ struct device *dev = ispif->dev;
|
|
+ int ret;
|
|
+
|
|
+ mem_info = kmalloc_obj(*mem_info, GFP_KERNEL);
|
|
+ if (!mem_info)
|
|
+ return NULL;
|
|
+
|
|
+ mem_info->mem_size = mem_size;
|
|
+ ret = isp_kernel_buffer_alloc(dev, mem_info->mem_size,
|
|
+ &mem_info->mem_handle,
|
|
+ &mem_info->gpu_mc_addr,
|
|
+ &mem_info->sys_addr);
|
|
+ if (ret) {
|
|
+ kfree(mem_info);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ return mem_info;
|
|
+}
|
|
+
|
|
+static void isp4if_gpu_mem_free(struct isp4_interface *ispif,
|
|
+ struct isp4if_gpu_mem_info **mem_info_ptr)
|
|
+{
|
|
+ struct isp4if_gpu_mem_info *mem_info = *mem_info_ptr;
|
|
+ struct device *dev = ispif->dev;
|
|
+
|
|
+ if (!mem_info) {
|
|
+ dev_err(dev, "invalid mem_info\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ *mem_info_ptr = NULL;
|
|
+ isp_kernel_buffer_free(&mem_info->mem_handle, &mem_info->gpu_mc_addr,
|
|
+ &mem_info->sys_addr);
|
|
+ kfree(mem_info);
|
|
+}
|
|
+
|
|
+static void isp4if_dealloc_fw_gpumem(struct isp4_interface *ispif)
|
|
+{
|
|
+ isp4if_gpu_mem_free(ispif, &ispif->fw_mem_pool);
|
|
+ isp4if_gpu_mem_free(ispif, &ispif->fw_cmd_resp_buf);
|
|
+ isp4if_gpu_mem_free(ispif, &ispif->fw_log_buf);
|
|
+
|
|
+ for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++)
|
|
+ isp4if_gpu_mem_free(ispif, &ispif->meta_info_buf[i]);
|
|
+}
|
|
+
|
|
+static int isp4if_alloc_fw_gpumem(struct isp4_interface *ispif)
|
|
+{
|
|
+ struct device *dev = ispif->dev;
|
|
+
|
|
+ ispif->fw_mem_pool = isp4if_gpu_mem_alloc(ispif,
|
|
+ ISP4FW_MEMORY_POOL_SIZE);
|
|
+ if (!ispif->fw_mem_pool)
|
|
+ goto error_no_memory;
|
|
+
|
|
+ ispif->fw_cmd_resp_buf =
|
|
+ isp4if_gpu_mem_alloc(ispif, ISP4IF_RB_PMBMAP_MEM_SIZE);
|
|
+ if (!ispif->fw_cmd_resp_buf)
|
|
+ goto error_no_memory;
|
|
+
|
|
+ ispif->fw_log_buf =
|
|
+ isp4if_gpu_mem_alloc(ispif, ISP4IF_FW_LOG_RINGBUF_SIZE);
|
|
+ if (!ispif->fw_log_buf)
|
|
+ goto error_no_memory;
|
|
+
|
|
+ for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) {
|
|
+ ispif->meta_info_buf[i] =
|
|
+ isp4if_gpu_mem_alloc(ispif, ISP4IF_META_INFO_BUF_SIZE);
|
|
+ if (!ispif->meta_info_buf[i])
|
|
+ goto error_no_memory;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_no_memory:
|
|
+ dev_err(dev, "failed to allocate gpu memory\n");
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+static u32 isp4if_compute_check_sum(const void *buf, size_t buf_size)
|
|
+{
|
|
+ const u8 *surplus_ptr;
|
|
+ const u32 *buffer;
|
|
+ u32 checksum = 0;
|
|
+ size_t i;
|
|
+
|
|
+ buffer = (const u32 *)buf;
|
|
+ for (i = 0; i < buf_size / sizeof(u32); i++)
|
|
+ checksum += buffer[i];
|
|
+
|
|
+ surplus_ptr = (const u8 *)&buffer[i];
|
|
+ /* add surplus data crc checksum */
|
|
+ for (i = 0; i < buf_size % sizeof(u32); i++)
|
|
+ checksum += surplus_ptr[i];
|
|
+
|
|
+ return checksum;
|
|
+}
|
|
+
|
|
+void isp4if_clear_cmdq(struct isp4_interface *ispif)
|
|
+{
|
|
+ struct isp4if_cmd_element *buf_node, *tmp_node;
|
|
+ LIST_HEAD(free_list);
|
|
+
|
|
+ scoped_guard(spinlock, &ispif->cmdq_lock)
|
|
+ list_splice_init(&ispif->cmdq, &free_list);
|
|
+
|
|
+ list_for_each_entry_safe(buf_node, tmp_node, &free_list, list)
|
|
+ kfree(buf_node);
|
|
+}
|
|
+
|
|
+static bool isp4if_is_cmdq_rb_full(struct isp4_interface *ispif,
|
|
+ enum isp4if_stream_id stream)
|
|
+{
|
|
+ struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream];
|
|
+ u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
|
|
+ u32 len = rb_config->val_size;
|
|
+ u32 rd_ptr, wr_ptr;
|
|
+ u32 bytes_free;
|
|
+
|
|
+ rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
|
|
+ wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
|
|
+
|
|
+ /*
|
|
+ * Read and write pointers are equal, indicating the ring buffer
|
|
+ * is empty
|
|
+ */
|
|
+ if (wr_ptr == rd_ptr)
|
|
+ return false;
|
|
+
|
|
+ if (wr_ptr > rd_ptr)
|
|
+ bytes_free = len - (wr_ptr - rd_ptr);
|
|
+ else
|
|
+ bytes_free = rd_ptr - wr_ptr;
|
|
+
|
|
+ /*
|
|
+ * Ignore one byte from the bytes free to prevent rd_ptr from equaling
|
|
+ * wr_ptr when the ring buffer is full, because rd_ptr == wr_ptr is
|
|
+ * supposed to indicate that the ring buffer is empty.
|
|
+ */
|
|
+ return bytes_free <= sizeof(struct isp4fw_cmd);
|
|
+}
|
|
+
|
|
+struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif,
|
|
+ u32 seq_num, u32 cmd_id)
|
|
+{
|
|
+ struct isp4if_cmd_element *ele;
|
|
+
|
|
+ guard(spinlock)(&ispif->cmdq_lock);
|
|
+
|
|
+ list_for_each_entry(ele, &ispif->cmdq, list) {
|
|
+ if (ele->seq_num == seq_num && ele->cmd_id == cmd_id) {
|
|
+ list_del(&ele->list);
|
|
+ return ele;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/* Must check that isp4if_is_cmdq_rb_full() == false before calling */
|
|
+static int isp4if_insert_isp_fw_cmd(struct isp4_interface *ispif,
|
|
+ enum isp4if_stream_id stream,
|
|
+ const struct isp4fw_cmd *cmd)
|
|
+{
|
|
+ struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream];
|
|
+ u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
|
|
+ void *mem_sys = rb_config->base_sys_addr;
|
|
+ const u32 cmd_sz = sizeof(*cmd);
|
|
+ struct device *dev = ispif->dev;
|
|
+ u32 len = rb_config->val_size;
|
|
+ const void *src = cmd;
|
|
+ u32 rd_ptr, wr_ptr;
|
|
+ u32 bytes_to_end;
|
|
+
|
|
+ rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
|
|
+ wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
|
|
+ if (rd_ptr >= len || wr_ptr >= len) {
|
|
+ dev_err(dev,
|
|
+ "rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, cmd_sz=%u\n",
|
|
+ stream, isp4dbg_get_if_stream_str(stream), rd_ptr,
|
|
+ wr_ptr, len, cmd_sz);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ bytes_to_end = len - wr_ptr;
|
|
+ if (bytes_to_end >= cmd_sz) {
|
|
+ /* FW cmd is just a straight copy to the write pointer */
|
|
+ memcpy(mem_sys + wr_ptr, src, cmd_sz);
|
|
+ isp4hw_wreg(ispif->mmio, wreg, (wr_ptr + cmd_sz) % len);
|
|
+ } else {
|
|
+ /*
|
|
+ * FW cmd is split because the ring buffer needs to wrap
|
|
+ * around
|
|
+ */
|
|
+ memcpy(mem_sys + wr_ptr, src, bytes_to_end);
|
|
+ memcpy(mem_sys, src + bytes_to_end, cmd_sz - bytes_to_end);
|
|
+ isp4hw_wreg(ispif->mmio, wreg, cmd_sz - bytes_to_end);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline enum isp4if_stream_id isp4if_get_fw_stream(u32 cmd_id)
|
|
+{
|
|
+ return ISP4IF_STREAM_ID_1;
|
|
+}
|
|
+
|
|
+static int isp4if_send_fw_cmd(struct isp4_interface *ispif, u32 cmd_id,
|
|
+ const void *package,
|
|
+ u32 package_size, bool sync)
|
|
+{
|
|
+ enum isp4if_stream_id stream = isp4if_get_fw_stream(cmd_id);
|
|
+ struct isp4if_cmd_element *ele = NULL;
|
|
+ struct device *dev = ispif->dev;
|
|
+ struct isp4fw_cmd cmd;
|
|
+ u32 seq_num;
|
|
+ int ret;
|
|
+
|
|
+ if (package_size > sizeof(cmd.cmd_param)) {
|
|
+ dev_err(dev, "fail pkgsize(%u) > %zu cmd:0x%x, stream %d\n",
|
|
+ package_size, sizeof(cmd.cmd_param), cmd_id, stream);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&cmd, 0, sizeof(cmd));
|
|
+ cmd.cmd_id = cmd_id;
|
|
+ switch (stream) {
|
|
+ case ISP4IF_STREAM_ID_GLOBAL:
|
|
+ cmd.cmd_stream_id = ISP4FW_STREAM_ID_INVALID;
|
|
+ break;
|
|
+ case ISP4IF_STREAM_ID_1:
|
|
+ cmd.cmd_stream_id = ISP4FW_STREAM_ID_1;
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(dev, "fail bad stream id %d\n", stream);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Allocate the sync command object early and outside of the lock */
|
|
+ if (sync) {
|
|
+ ele = kmalloc_obj(*ele, GFP_KERNEL);
|
|
+ if (!ele)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* Get two references: one for the resp thread, one for us */
|
|
+ atomic_set(&ele->refcnt, 2);
|
|
+ init_completion(&ele->cmd_done);
|
|
+ }
|
|
+
|
|
+ if (package && package_size)
|
|
+ memcpy(cmd.cmd_param, package, package_size);
|
|
+
|
|
+ scoped_guard(mutex, &ispif->isp4if_mutex) {
|
|
+ ret = read_poll_timeout(isp4if_is_cmdq_rb_full, ret, !ret,
|
|
+ ISP4IF_RB_FULL_SLEEP_US,
|
|
+ ISP4IF_RB_FULL_TIMEOUT_US, false, ispif,
|
|
+ stream);
|
|
+ if (ret) {
|
|
+ struct isp4if_rb_config *rb_config =
|
|
+ &isp4if_resp_rb_config[stream];
|
|
+ u32 rd_ptr = isp4hw_rreg(ispif->mmio,
|
|
+ rb_config->reg_rptr);
|
|
+ u32 wr_ptr = isp4hw_rreg(ispif->mmio,
|
|
+ rb_config->reg_wptr);
|
|
+
|
|
+ dev_err(dev,
|
|
+ "failed to get free cmdq slot, stream %s(%d),rd %u, wr %u\n",
|
|
+ isp4dbg_get_if_stream_str(stream), stream,
|
|
+ rd_ptr, wr_ptr);
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto free_ele;
|
|
+ }
|
|
+
|
|
+ seq_num = ispif->host2fw_seq_num++;
|
|
+ cmd.cmd_seq_num = seq_num;
|
|
+ cmd.cmd_check_sum = isp4if_compute_check_sum(&cmd, sizeof(cmd)
|
|
+ - sizeof(u32));
|
|
+
|
|
+ /*
|
|
+ * only append the fw cmd to queue when its response needs to
|
|
+ * be waited for, currently there are only two such commands,
|
|
+ * disable channel and stop stream which are only sent after
|
|
+ * close camera
|
|
+ */
|
|
+ if (ele) {
|
|
+ ele->seq_num = seq_num;
|
|
+ ele->cmd_id = cmd_id;
|
|
+ scoped_guard(spinlock, &ispif->cmdq_lock)
|
|
+ list_add_tail(&ele->list, &ispif->cmdq);
|
|
+ }
|
|
+
|
|
+ ret = isp4if_insert_isp_fw_cmd(ispif, stream, &cmd);
|
|
+ if (ret) {
|
|
+ dev_err(dev,
|
|
+ "fail for insert_isp_fw_cmd cmd_id %s(0x%08x)\n",
|
|
+ isp4dbg_get_cmd_str(cmd_id), cmd_id);
|
|
+ goto err_dequeue_ele;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (ele) {
|
|
+ ret = wait_for_completion_timeout(&ele->cmd_done,
|
|
+ ISP4IF_FW_CMD_TIMEOUT);
|
|
+ if (!ret) {
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto err_dequeue_ele;
|
|
+ }
|
|
+
|
|
+ ret = 0;
|
|
+ goto put_ele_ref;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_dequeue_ele:
|
|
+ /*
|
|
+ * Try to remove the command from the queue. If that fails, then it
|
|
+ * means the response thread is currently using the object, and we need
|
|
+ * to use the refcount to avoid a use-after-free by either side.
|
|
+ */
|
|
+ if (ele && isp4if_rm_cmd_from_cmdq(ispif, seq_num, cmd_id))
|
|
+ goto free_ele;
|
|
+
|
|
+put_ele_ref:
|
|
+ /* Don't free the command if we didn't put the last reference */
|
|
+ if (ele && atomic_dec_return(&ele->refcnt))
|
|
+ ele = NULL;
|
|
+
|
|
+free_ele:
|
|
+ kfree(ele);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int isp4if_send_buffer(struct isp4_interface *ispif,
|
|
+ struct isp4if_img_buf_info *buf_info)
|
|
+{
|
|
+ struct isp4fw_cmd_send_buffer cmd;
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&cmd, 0, sizeof(cmd));
|
|
+ cmd.buffer_type = ISP4FW_BUFFER_TYPE_PREVIEW;
|
|
+ cmd.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA;
|
|
+ isp4if_split_addr64(buf_info->planes[0].mc_addr,
|
|
+ &cmd.buffer.buf_base_a_lo,
|
|
+ &cmd.buffer.buf_base_a_hi);
|
|
+ cmd.buffer.buf_size_a = buf_info->planes[0].len;
|
|
+
|
|
+ isp4if_split_addr64(buf_info->planes[1].mc_addr,
|
|
+ &cmd.buffer.buf_base_b_lo,
|
|
+ &cmd.buffer.buf_base_b_hi);
|
|
+ cmd.buffer.buf_size_b = buf_info->planes[1].len;
|
|
+
|
|
+ isp4if_split_addr64(buf_info->planes[2].mc_addr,
|
|
+ &cmd.buffer.buf_base_c_lo,
|
|
+ &cmd.buffer.buf_base_c_hi);
|
|
+ cmd.buffer.buf_size_c = buf_info->planes[2].len;
|
|
+
|
|
+ return isp4if_send_fw_cmd(ispif, ISP4FW_CMD_ID_SEND_BUFFER, &cmd,
|
|
+ sizeof(cmd), false);
|
|
+}
|
|
+
|
|
+static void isp4if_init_rb_config(struct isp4_interface *ispif,
|
|
+ struct isp4if_rb_config *rb_config)
|
|
+{
|
|
+ isp4hw_wreg(ispif->mmio, rb_config->reg_rptr, 0x0);
|
|
+ isp4hw_wreg(ispif->mmio, rb_config->reg_wptr, 0x0);
|
|
+ isp4hw_wreg(ispif->mmio, rb_config->reg_base_lo,
|
|
+ rb_config->base_mc_addr);
|
|
+ isp4hw_wreg(ispif->mmio, rb_config->reg_base_hi,
|
|
+ rb_config->base_mc_addr >> 32);
|
|
+ isp4hw_wreg(ispif->mmio, rb_config->reg_size, rb_config->val_size);
|
|
+}
|
|
+
|
|
+static int isp4if_fw_init(struct isp4_interface *ispif)
|
|
+{
|
|
+ u32 aligned_rb_chunk_size = ISP4IF_RB_PMBMAP_MEM_CHUNK & 0xffffffc0;
|
|
+ struct isp4if_rb_config *rb_config;
|
|
+ u32 offset;
|
|
+ unsigned int i;
|
|
+
|
|
+ /* initialize CMD_RB streams */
|
|
+ for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) {
|
|
+ rb_config = (isp4if_cmd_rb_config + i);
|
|
+ offset = aligned_rb_chunk_size * rb_config->index;
|
|
+
|
|
+ rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE;
|
|
+ rb_config->base_sys_addr =
|
|
+ ispif->fw_cmd_resp_buf->sys_addr + offset;
|
|
+ rb_config->base_mc_addr =
|
|
+ ispif->fw_cmd_resp_buf->gpu_mc_addr + offset;
|
|
+
|
|
+ isp4if_init_rb_config(ispif, rb_config);
|
|
+ }
|
|
+
|
|
+ /* initialize RESP_RB streams */
|
|
+ for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) {
|
|
+ rb_config = (isp4if_resp_rb_config + i);
|
|
+ offset = aligned_rb_chunk_size *
|
|
+ (rb_config->index + ISP4IF_RESP_CHAN_TO_RB_OFFSET - 1);
|
|
+
|
|
+ rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE;
|
|
+ rb_config->base_sys_addr =
|
|
+ ispif->fw_cmd_resp_buf->sys_addr + offset;
|
|
+ rb_config->base_mc_addr =
|
|
+ ispif->fw_cmd_resp_buf->gpu_mc_addr + offset;
|
|
+
|
|
+ isp4if_init_rb_config(ispif, rb_config);
|
|
+ }
|
|
+
|
|
+ /* initialize LOG_RB stream */
|
|
+ rb_config = &isp4if_log_rb_config;
|
|
+ rb_config->val_size = ISP4IF_FW_LOG_RINGBUF_SIZE;
|
|
+ rb_config->base_mc_addr = ispif->fw_log_buf->gpu_mc_addr;
|
|
+ rb_config->base_sys_addr = ispif->fw_log_buf->sys_addr;
|
|
+
|
|
+ isp4if_init_rb_config(ispif, rb_config);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4if_wait_fw_ready(struct isp4_interface *ispif,
|
|
+ u32 isp_status_addr)
|
|
+{
|
|
+ struct device *dev = ispif->dev;
|
|
+ u32 timeout_ms = 100;
|
|
+ u32 interval_ms = 1;
|
|
+ u32 reg_val;
|
|
+
|
|
+ /* wait for FW initialize done! */
|
|
+ if (!read_poll_timeout(isp4hw_rreg, reg_val, reg_val
|
|
+ & ISP_STATUS__CCPU_REPORT_MASK,
|
|
+ interval_ms * 1000, timeout_ms * 1000, false,
|
|
+ ispif->mmio, isp_status_addr))
|
|
+ return 0;
|
|
+
|
|
+ dev_err(dev, "ISP CCPU FW boot failed\n");
|
|
+
|
|
+ return -ETIME;
|
|
+}
|
|
+
|
|
+static void isp4if_enable_ccpu(struct isp4_interface *ispif)
|
|
+{
|
|
+ u32 reg_val;
|
|
+
|
|
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET);
|
|
+ reg_val &= (~ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK);
|
|
+ isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val);
|
|
+
|
|
+ usleep_range(100, 150);
|
|
+
|
|
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL);
|
|
+ reg_val &= (~ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK);
|
|
+ isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val);
|
|
+}
|
|
+
|
|
+static void isp4if_disable_ccpu(struct isp4_interface *ispif)
|
|
+{
|
|
+ u32 reg_val;
|
|
+
|
|
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL);
|
|
+ reg_val |= ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK;
|
|
+ isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val);
|
|
+
|
|
+ usleep_range(100, 150);
|
|
+
|
|
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET);
|
|
+ reg_val |= ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK;
|
|
+ isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val);
|
|
+}
|
|
+
|
|
+static int isp4if_fw_boot(struct isp4_interface *ispif)
|
|
+{
|
|
+ struct device *dev = ispif->dev;
|
|
+
|
|
+ if (ispif->status != ISP4IF_STATUS_PWR_ON) {
|
|
+ dev_err(dev, "invalid isp power status %d\n", ispif->status);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ isp4if_disable_ccpu(ispif);
|
|
+
|
|
+ isp4if_fw_init(ispif);
|
|
+
|
|
+ /* clear ccpu status */
|
|
+ isp4hw_wreg(ispif->mmio, ISP_STATUS, 0x0);
|
|
+
|
|
+ isp4if_enable_ccpu(ispif);
|
|
+
|
|
+ if (isp4if_wait_fw_ready(ispif, ISP_STATUS)) {
|
|
+ isp4if_disable_ccpu(ispif);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* enable interrupts */
|
|
+ isp4hw_wreg(ispif->mmio, ISP_SYS_INT0_EN,
|
|
+ ISP4IF_FW_RESP_RB_IRQ_EN_MASK);
|
|
+
|
|
+ ispif->status = ISP4IF_STATUS_FW_RUNNING;
|
|
+
|
|
+ dev_dbg(dev, "ISP CCPU FW boot success\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream,
|
|
+ struct isp4fw_resp *resp)
|
|
+{
|
|
+ struct isp4if_rb_config *rb_config = &isp4if_resp_rb_config[stream];
|
|
+ u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
|
|
+ void *mem_sys = rb_config->base_sys_addr;
|
|
+ const u32 resp_sz = sizeof(*resp);
|
|
+ struct device *dev = ispif->dev;
|
|
+ u32 len = rb_config->val_size;
|
|
+ u32 rd_ptr, wr_ptr;
|
|
+ u32 bytes_to_end;
|
|
+ void *dst = resp;
|
|
+ u32 checksum;
|
|
+
|
|
+ rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
|
|
+ wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
|
|
+ if (rd_ptr >= len || wr_ptr >= len)
|
|
+ goto err_rb_invalid;
|
|
+
|
|
+ /*
|
|
+ * Read and write pointers are equal, indicating the ring buffer is
|
|
+ * empty
|
|
+ */
|
|
+ if (rd_ptr == wr_ptr)
|
|
+ return -ENODATA;
|
|
+
|
|
+ bytes_to_end = len - rd_ptr;
|
|
+ if (bytes_to_end >= resp_sz) {
|
|
+ /* FW response is just a straight copy from the read pointer */
|
|
+ if (wr_ptr > rd_ptr && wr_ptr - rd_ptr < resp_sz)
|
|
+ goto err_rb_invalid;
|
|
+
|
|
+ memcpy(dst, mem_sys + rd_ptr, resp_sz);
|
|
+ isp4hw_wreg(ispif->mmio, rreg, (rd_ptr + resp_sz) % len);
|
|
+ } else {
|
|
+ /*
|
|
+ * FW response is split because the ring buffer wrapped
|
|
+ * around
|
|
+ */
|
|
+ if (wr_ptr > rd_ptr || wr_ptr < resp_sz - bytes_to_end)
|
|
+ goto err_rb_invalid;
|
|
+
|
|
+ memcpy(dst, mem_sys + rd_ptr, bytes_to_end);
|
|
+ memcpy(dst + bytes_to_end, mem_sys, resp_sz - bytes_to_end);
|
|
+ isp4hw_wreg(ispif->mmio, rreg, resp_sz - bytes_to_end);
|
|
+ }
|
|
+
|
|
+ checksum = isp4if_compute_check_sum(resp, resp_sz - sizeof(u32));
|
|
+ if (checksum != resp->resp_check_sum) {
|
|
+ dev_err(dev, "resp checksum 0x%x,should 0x%x,rptr %u,wptr %u\n",
|
|
+ checksum, resp->resp_check_sum, rd_ptr, wr_ptr);
|
|
+ dev_err(dev, "%s(%u), seqNo %u, resp_id %s(0x%x)\n",
|
|
+ isp4dbg_get_if_stream_str(stream), stream,
|
|
+ resp->resp_seq_num, isp4dbg_get_resp_str(resp->resp_id),
|
|
+ resp->resp_id);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_rb_invalid:
|
|
+ dev_err(dev,
|
|
+ "rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, resp_sz=%u\n",
|
|
+ stream, isp4dbg_get_if_stream_str(stream), rd_ptr, wr_ptr, len,
|
|
+ resp_sz);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id,
|
|
+ const void *package, u32 package_size)
|
|
+{
|
|
+ return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, false);
|
|
+}
|
|
+
|
|
+int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id,
|
|
+ const void *package, u32 package_size)
|
|
+{
|
|
+ return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, true);
|
|
+}
|
|
+
|
|
+void isp4if_clear_bufq(struct isp4_interface *ispif)
|
|
+{
|
|
+ struct isp4if_img_buf_node *buf_node, *tmp_node;
|
|
+ LIST_HEAD(free_list);
|
|
+
|
|
+ scoped_guard(spinlock, &ispif->bufq_lock)
|
|
+ list_splice_init(&ispif->bufq, &free_list);
|
|
+
|
|
+ list_for_each_entry_safe(buf_node, tmp_node, &free_list, node)
|
|
+ kfree(buf_node);
|
|
+}
|
|
+
|
|
+void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node)
|
|
+{
|
|
+ kfree(buf_node);
|
|
+}
|
|
+
|
|
+struct isp4if_img_buf_node *
|
|
+isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info)
|
|
+{
|
|
+ struct isp4if_img_buf_node *node;
|
|
+
|
|
+ node = kmalloc_obj(*node, GFP_KERNEL);
|
|
+ if (node)
|
|
+ node->buf_info = *buf_info;
|
|
+
|
|
+ return node;
|
|
+}
|
|
+
|
|
+struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif)
|
|
+{
|
|
+ struct isp4if_img_buf_node *buf_node;
|
|
+
|
|
+ guard(spinlock)(&ispif->bufq_lock);
|
|
+
|
|
+ buf_node = list_first_entry_or_null(&ispif->bufq, typeof(*buf_node),
|
|
+ node);
|
|
+ if (buf_node)
|
|
+ list_del(&buf_node->node);
|
|
+
|
|
+ return buf_node;
|
|
+}
|
|
+
|
|
+int isp4if_queue_buffer(struct isp4_interface *ispif,
|
|
+ struct isp4if_img_buf_node *buf_node)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = isp4if_send_buffer(ispif, &buf_node->buf_info);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ scoped_guard(spinlock, &ispif->bufq_lock)
|
|
+ list_add_tail(&buf_node->node, &ispif->bufq);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int isp4if_stop(struct isp4_interface *ispif)
|
|
+{
|
|
+ isp4if_disable_ccpu(ispif);
|
|
+
|
|
+ isp4if_dealloc_fw_gpumem(ispif);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int isp4if_start(struct isp4_interface *ispif)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = isp4if_alloc_fw_gpumem(ispif);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = isp4if_fw_boot(ispif);
|
|
+ if (ret)
|
|
+ goto failed_fw_boot;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+failed_fw_boot:
|
|
+ isp4if_dealloc_fw_gpumem(ispif);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int isp4if_deinit(struct isp4_interface *ispif)
|
|
+{
|
|
+ isp4if_clear_cmdq(ispif);
|
|
+
|
|
+ isp4if_clear_bufq(ispif);
|
|
+
|
|
+ mutex_destroy(&ispif->isp4if_mutex);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int isp4if_init(struct isp4_interface *ispif, struct device *dev,
|
|
+ void __iomem *isp_mmio)
|
|
+{
|
|
+ ispif->dev = dev;
|
|
+ ispif->mmio = isp_mmio;
|
|
+
|
|
+ spin_lock_init(&ispif->cmdq_lock); /* used for cmdq access */
|
|
+ spin_lock_init(&ispif->bufq_lock); /* used for bufq access */
|
|
+ mutex_init(&ispif->isp4if_mutex); /* used for commands sent to ispfw */
|
|
+
|
|
+ INIT_LIST_HEAD(&ispif->cmdq);
|
|
+ INIT_LIST_HEAD(&ispif->bufq);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_interface.h b/drivers/media/platform/amd/isp4/isp4_interface.h
|
|
new file mode 100644
|
|
index 0000000000000..ce3ac9b9e5cda
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_interface.h
|
|
@@ -0,0 +1,144 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_INTERFACE_H_
|
|
+#define _ISP4_INTERFACE_H_
|
|
+
|
|
+#include <drm/amd/isp.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+struct isp4fw_resp;
|
|
+
|
|
+#define ISP4IF_RB_MAX 25
|
|
+#define ISP4IF_RESP_CHAN_TO_RB_OFFSET 9
|
|
+#define ISP4IF_RB_PMBMAP_MEM_SIZE (SZ_16M - 1)
|
|
+#define ISP4IF_RB_PMBMAP_MEM_CHUNK \
|
|
+ (ISP4IF_RB_PMBMAP_MEM_SIZE / (ISP4IF_RB_MAX - 1))
|
|
+#define ISP4IF_HOST2FW_COMMAND_SIZE sizeof(struct isp4fw_cmd)
|
|
+#define ISP4IF_MAX_NUM_HOST2FW_COMMAND 40
|
|
+#define ISP4IF_FW_CMD_BUF_SIZE \
|
|
+ (ISP4IF_MAX_NUM_HOST2FW_COMMAND * ISP4IF_HOST2FW_COMMAND_SIZE)
|
|
+#define ISP4IF_RB_FULL_SLEEP_US (33 * USEC_PER_MSEC)
|
|
+#define ISP4IF_RB_FULL_TIMEOUT_US (10 * ISP4IF_RB_FULL_SLEEP_US)
|
|
+
|
|
+#define ISP4IF_META_INFO_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000)
|
|
+#define ISP4IF_MAX_STREAM_BUF_COUNT 8
|
|
+
|
|
+#define ISP4IF_FW_LOG_RINGBUF_SIZE SZ_2M
|
|
+
|
|
+enum isp4if_stream_id {
|
|
+ ISP4IF_STREAM_ID_GLOBAL = 0,
|
|
+ ISP4IF_STREAM_ID_1 = 1,
|
|
+ ISP4IF_STREAM_ID_MAX = 4
|
|
+};
|
|
+
|
|
+enum isp4if_status {
|
|
+ ISP4IF_STATUS_PWR_OFF,
|
|
+ ISP4IF_STATUS_PWR_ON,
|
|
+ ISP4IF_STATUS_FW_RUNNING,
|
|
+ ISP4IF_FSM_STATUS_MAX
|
|
+};
|
|
+
|
|
+struct isp4if_gpu_mem_info {
|
|
+ u64 mem_size;
|
|
+ u64 gpu_mc_addr;
|
|
+ void *sys_addr;
|
|
+ void *mem_handle;
|
|
+};
|
|
+
|
|
+struct isp4if_img_buf_info {
|
|
+ struct {
|
|
+ void *sys_addr;
|
|
+ u64 mc_addr;
|
|
+ u32 len;
|
|
+ } planes[3];
|
|
+};
|
|
+
|
|
+struct isp4if_img_buf_node {
|
|
+ struct list_head node;
|
|
+ struct isp4if_img_buf_info buf_info;
|
|
+};
|
|
+
|
|
+struct isp4if_cmd_element {
|
|
+ struct list_head list;
|
|
+ u32 seq_num;
|
|
+ u32 cmd_id;
|
|
+ struct completion cmd_done;
|
|
+ atomic_t refcnt;
|
|
+};
|
|
+
|
|
+struct isp4_interface {
|
|
+ struct device *dev;
|
|
+ void __iomem *mmio;
|
|
+
|
|
+ spinlock_t cmdq_lock; /* used for cmdq access */
|
|
+ spinlock_t bufq_lock; /* used for bufq access */
|
|
+ struct mutex isp4if_mutex; /* used to send fw cmd and read fw log */
|
|
+
|
|
+ struct list_head cmdq; /* commands sent to fw */
|
|
+ struct list_head bufq; /* buffers sent to fw */
|
|
+
|
|
+ enum isp4if_status status;
|
|
+ u32 host2fw_seq_num;
|
|
+
|
|
+ /* ISP fw buffers */
|
|
+ struct isp4if_gpu_mem_info *fw_log_buf;
|
|
+ struct isp4if_gpu_mem_info *fw_cmd_resp_buf;
|
|
+ struct isp4if_gpu_mem_info *fw_mem_pool;
|
|
+ struct isp4if_gpu_mem_info *meta_info_buf[ISP4IF_MAX_STREAM_BUF_COUNT];
|
|
+};
|
|
+
|
|
+static inline void isp4if_split_addr64(u64 addr, u32 *lo, u32 *hi)
|
|
+{
|
|
+ if (lo)
|
|
+ *lo = addr & 0xffffffff;
|
|
+
|
|
+ if (hi)
|
|
+ *hi = addr >> 32;
|
|
+}
|
|
+
|
|
+static inline u64 isp4if_join_addr64(u32 lo, u32 hi)
|
|
+{
|
|
+ return (((u64)hi) << 32) | (u64)lo;
|
|
+}
|
|
+
|
|
+int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream,
|
|
+ struct isp4fw_resp *resp);
|
|
+
|
|
+int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id,
|
|
+ const void *package, u32 package_size);
|
|
+
|
|
+int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id,
|
|
+ const void *package, u32 package_size);
|
|
+
|
|
+struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif,
|
|
+ u32 seq_num, u32 cmd_id);
|
|
+
|
|
+void isp4if_clear_cmdq(struct isp4_interface *ispif);
|
|
+
|
|
+void isp4if_clear_bufq(struct isp4_interface *ispif);
|
|
+
|
|
+void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node);
|
|
+
|
|
+struct isp4if_img_buf_node *
|
|
+isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info);
|
|
+
|
|
+struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif);
|
|
+
|
|
+int isp4if_queue_buffer(struct isp4_interface *ispif,
|
|
+ struct isp4if_img_buf_node *buf_node);
|
|
+
|
|
+int isp4if_stop(struct isp4_interface *ispif);
|
|
+
|
|
+int isp4if_start(struct isp4_interface *ispif);
|
|
+
|
|
+int isp4if_deinit(struct isp4_interface *ispif);
|
|
+
|
|
+int isp4if_init(struct isp4_interface *ispif, struct device *dev,
|
|
+ void __iomem *isp_mmio);
|
|
+
|
|
+#endif /* _ISP4_INTERFACE_H_ */
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/platform/amd/isp4/isp4_subdev.c
|
|
new file mode 100644
|
|
index 0000000000000..48deea79ce6c2
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_subdev.c
|
|
@@ -0,0 +1,1047 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#include <linux/pm_domain.h>
|
|
+#include <linux/units.h>
|
|
+
|
|
+#include "isp4.h"
|
|
+#include "isp4_debug.h"
|
|
+#include "isp4_fw_cmd_resp.h"
|
|
+#include "isp4_interface.h"
|
|
+
|
|
+#define ISP4SD_MIN_BUF_CNT_BEF_START_STREAM 4
|
|
+
|
|
+#define ISP4SD_PERFORMANCE_STATE_LOW 0
|
|
+#define ISP4SD_PERFORMANCE_STATE_HIGH 1
|
|
+
|
|
+/* align 32KB */
|
|
+#define ISP4SD_META_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000)
|
|
+
|
|
+#define to_isp4_subdev(sd) container_of(sd, struct isp4_subdev, sdev)
|
|
+
|
|
+static const char *isp4sd_entity_name = "amd isp4";
|
|
+
|
|
+static const char *isp4sd_thread_name[ISP4SD_MAX_FW_RESP_STREAM_NUM] = {
|
|
+ "amd_isp4_thread_global",
|
|
+ "amd_isp4_thread_stream1",
|
|
+};
|
|
+
|
|
+static void isp4sd_module_enable(struct isp4_subdev *isp_subdev, bool enable)
|
|
+{
|
|
+ if (isp_subdev->enable_gpio) {
|
|
+ gpiod_set_value(isp_subdev->enable_gpio, enable ? 1 : 0);
|
|
+ dev_dbg(isp_subdev->dev, "%s isp_subdev module\n",
|
|
+ enable ? "enable" : "disable");
|
|
+ }
|
|
+}
|
|
+
|
|
+static int isp4sd_setup_fw_mem_pool(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4fw_cmd_send_buffer buf_type;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ if (!ispif->fw_mem_pool) {
|
|
+ dev_err(dev, "fail to alloc mem pool\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&buf_type, 0, sizeof(buf_type));
|
|
+ buf_type.buffer_type = ISP4FW_BUFFER_TYPE_MEM_POOL;
|
|
+ buf_type.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA;
|
|
+ isp4if_split_addr64(ispif->fw_mem_pool->gpu_mc_addr,
|
|
+ &buf_type.buffer.buf_base_a_lo,
|
|
+ &buf_type.buffer.buf_base_a_hi);
|
|
+ buf_type.buffer.buf_size_a = ispif->fw_mem_pool->mem_size;
|
|
+
|
|
+ ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER,
|
|
+ &buf_type, sizeof(buf_type));
|
|
+ if (ret) {
|
|
+ dev_err(dev, "send fw mem pool 0x%llx(%u) fail %d\n",
|
|
+ ispif->fw_mem_pool->gpu_mc_addr,
|
|
+ buf_type.buffer.buf_size_a, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "send fw mem pool 0x%llx(%u) suc\n",
|
|
+ ispif->fw_mem_pool->gpu_mc_addr, buf_type.buffer.buf_size_a);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_set_stream_path(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4fw_cmd_set_stream_cfg cmd;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&cmd, 0, sizeof(cmd));
|
|
+ cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id =
|
|
+ ISP4FW_SENSOR_ID_ON_MIPI0;
|
|
+ cmd.stream_cfg.mipi_pipe_path_cfg.b_enable = true;
|
|
+ cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id =
|
|
+ ISP4FW_MIPI0_ISP_PIPELINE_ID;
|
|
+
|
|
+ cmd.stream_cfg.b_enable_tnr = true;
|
|
+ dev_dbg(dev, "isp4fw_sensor_id %d, pipeId 0x%x EnableTnr %u\n",
|
|
+ cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id,
|
|
+ cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id,
|
|
+ cmd.stream_cfg.b_enable_tnr);
|
|
+
|
|
+ return isp4if_send_command(ispif, ISP4FW_CMD_ID_SET_STREAM_CONFIG,
|
|
+ &cmd, sizeof(cmd));
|
|
+}
|
|
+
|
|
+static int isp4sd_send_meta_buf(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4fw_cmd_send_buffer buf_type;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&buf_type, 0, sizeof(buf_type));
|
|
+ for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) {
|
|
+ struct isp4if_gpu_mem_info *meta_info_buf =
|
|
+ isp_subdev->ispif.meta_info_buf[i];
|
|
+ int ret;
|
|
+
|
|
+ if (!meta_info_buf) {
|
|
+ dev_err(dev, "fail for no meta info buf(%u)\n", i);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ buf_type.buffer_type = ISP4FW_BUFFER_TYPE_META_INFO;
|
|
+ buf_type.buffer.vmid_space.bit.space =
|
|
+ ISP4FW_ADDR_SPACE_TYPE_GPU_VA;
|
|
+ isp4if_split_addr64(meta_info_buf->gpu_mc_addr,
|
|
+ &buf_type.buffer.buf_base_a_lo,
|
|
+ &buf_type.buffer.buf_base_a_hi);
|
|
+ buf_type.buffer.buf_size_a = meta_info_buf->mem_size;
|
|
+ ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER,
|
|
+ &buf_type, sizeof(buf_type));
|
|
+ if (ret) {
|
|
+ dev_err(dev, "send meta info(%u) fail\n", i);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "send meta info suc\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static bool isp4sd_get_str_out_prop(struct isp4_subdev *isp_subdev,
|
|
+ struct isp4fw_image_prop *out_prop,
|
|
+ struct v4l2_subdev_state *state, u32 pad)
|
|
+{
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ struct v4l2_mbus_framefmt *format;
|
|
+
|
|
+ format = v4l2_subdev_state_get_format(state, pad, 0);
|
|
+ if (!format) {
|
|
+ dev_err(dev, "fail get subdev state format\n");
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ switch (format->code) {
|
|
+ case MEDIA_BUS_FMT_YUYV8_1_5X8:
|
|
+ out_prop->image_format = ISP4FW_IMAGE_FORMAT_NV12;
|
|
+ out_prop->width = format->width;
|
|
+ out_prop->height = format->height;
|
|
+ out_prop->luma_pitch = format->width;
|
|
+ out_prop->chroma_pitch = out_prop->width;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_YUYV8_1X16:
|
|
+ out_prop->image_format = ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED;
|
|
+ out_prop->width = format->width;
|
|
+ out_prop->height = format->height;
|
|
+ out_prop->luma_pitch = format->width * 2;
|
|
+ out_prop->chroma_pitch = 0;
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(dev, "fail for bad image format:0x%x\n",
|
|
+ format->code);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!out_prop->width || !out_prop->height)
|
|
+ return false;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static int isp4sd_kickoff_stream(struct isp4_subdev *isp_subdev, u32 w, u32 h)
|
|
+{
|
|
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ if (sensor_info->status == ISP4SD_START_STATUS_STARTED)
|
|
+ return 0;
|
|
+
|
|
+ if (sensor_info->status == ISP4SD_START_STATUS_START_FAIL) {
|
|
+ dev_err(dev, "fail for previous start fail\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "w:%u,h:%u\n", w, h);
|
|
+
|
|
+ if (isp4sd_send_meta_buf(isp_subdev)) {
|
|
+ dev_err(dev, "fail to send meta buf\n");
|
|
+ sensor_info->status = ISP4SD_START_STATUS_START_FAIL;
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ sensor_info->status = ISP4SD_START_STATUS_OFF;
|
|
+
|
|
+ if (!sensor_info->start_stream_cmd_sent &&
|
|
+ sensor_info->buf_sent_cnt >= ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) {
|
|
+ int ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_START_STREAM,
|
|
+ NULL, 0);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to start stream\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ sensor_info->start_stream_cmd_sent = true;
|
|
+ } else {
|
|
+ dev_dbg(dev,
|
|
+ "no send START_STREAM, start_sent %u, buf_sent %u\n",
|
|
+ sensor_info->start_stream_cmd_sent,
|
|
+ sensor_info->buf_sent_cnt);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_setup_output(struct isp4_subdev *isp_subdev,
|
|
+ struct v4l2_subdev_state *state, u32 pad)
|
|
+{
|
|
+ struct isp4sd_output_info *output_info =
|
|
+ &isp_subdev->sensor_info.output_info;
|
|
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4fw_cmd_set_out_ch_prop cmd_ch_prop;
|
|
+ struct isp4fw_cmd_enable_out_ch cmd_ch_en;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ if (output_info->start_status == ISP4SD_START_STATUS_STARTED)
|
|
+ return 0;
|
|
+
|
|
+ if (output_info->start_status == ISP4SD_START_STATUS_START_FAIL) {
|
|
+ dev_err(dev, "fail for previous start fail\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&cmd_ch_prop, 0, sizeof(cmd_ch_prop));
|
|
+ cmd_ch_prop.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW;
|
|
+
|
|
+ if (!isp4sd_get_str_out_prop(isp_subdev,
|
|
+ &cmd_ch_prop.image_prop, state, pad)) {
|
|
+ dev_err(dev, "fail to get out prop\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "channel:%s,fmt %s,w:h=%u:%u,lp:%u,cp%u\n",
|
|
+ isp4dbg_get_out_ch_str(cmd_ch_prop.ch),
|
|
+ isp4dbg_get_img_fmt_str(cmd_ch_prop.image_prop.image_format),
|
|
+ cmd_ch_prop.image_prop.width, cmd_ch_prop.image_prop.height,
|
|
+ cmd_ch_prop.image_prop.luma_pitch,
|
|
+ cmd_ch_prop.image_prop.chroma_pitch);
|
|
+
|
|
+ ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_SET_OUT_CHAN_PROP,
|
|
+ &cmd_ch_prop, sizeof(cmd_ch_prop));
|
|
+ if (ret) {
|
|
+ output_info->start_status = ISP4SD_START_STATUS_START_FAIL;
|
|
+ dev_err(dev, "fail to set out prop\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&cmd_ch_en, 0, sizeof(cmd_ch_en));
|
|
+ cmd_ch_en.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW;
|
|
+ cmd_ch_en.is_enable = true;
|
|
+ ret = isp4if_send_command(ispif, ISP4FW_CMD_ID_ENABLE_OUT_CHAN,
|
|
+ &cmd_ch_en, sizeof(cmd_ch_en));
|
|
+ if (ret) {
|
|
+ output_info->start_status = ISP4SD_START_STATUS_START_FAIL;
|
|
+ dev_err(dev, "fail to enable channel\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "enable channel %s\n",
|
|
+ isp4dbg_get_out_ch_str(cmd_ch_en.ch));
|
|
+
|
|
+ if (!sensor_info->start_stream_cmd_sent) {
|
|
+ ret = isp4sd_kickoff_stream(isp_subdev,
|
|
+ cmd_ch_prop.image_prop.width,
|
|
+ cmd_ch_prop.image_prop.height);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "kickoff stream fail %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ /*
|
|
+ * sensor_info->start_stream_cmd_sent will be set to true
|
|
+ * 1. in isp4sd_kickoff_stream, if app first send buffer then
|
|
+ * start stream
|
|
+ * 2. in isp_set_stream_buf, if app first start stream, then
|
|
+ * send buffer because ISP FW has the requirement, host needs
|
|
+ * to send buffer before send start stream cmd
|
|
+ */
|
|
+ if (sensor_info->start_stream_cmd_sent) {
|
|
+ sensor_info->status = ISP4SD_START_STATUS_STARTED;
|
|
+ output_info->start_status = ISP4SD_START_STATUS_STARTED;
|
|
+ dev_dbg(dev, "kickoff stream suc,start cmd sent\n");
|
|
+ }
|
|
+ } else {
|
|
+ dev_dbg(dev, "stream running, no need kickoff\n");
|
|
+ output_info->start_status = ISP4SD_START_STATUS_STARTED;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "setup output suc\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_init_stream(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ ret = isp4sd_setup_fw_mem_pool(isp_subdev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to setup fw mem pool\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = isp4sd_set_stream_path(isp_subdev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to setup stream path\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void isp4sd_uninit_stream(struct isp4_subdev *isp_subdev,
|
|
+ struct v4l2_subdev_state *state, u32 pad)
|
|
+{
|
|
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
|
|
+ struct isp4sd_output_info *output_info = &sensor_info->output_info;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct v4l2_mbus_framefmt *format;
|
|
+
|
|
+ format = v4l2_subdev_state_get_format(state, pad, 0);
|
|
+ if (!format) {
|
|
+ dev_err(isp_subdev->dev, "fail to get v4l2 format\n");
|
|
+ } else {
|
|
+ memset(format, 0, sizeof(*format));
|
|
+ format->code = MEDIA_BUS_FMT_YUYV8_1_5X8;
|
|
+ }
|
|
+
|
|
+ isp4if_clear_bufq(ispif);
|
|
+ isp4if_clear_cmdq(ispif);
|
|
+
|
|
+ sensor_info->start_stream_cmd_sent = false;
|
|
+ sensor_info->buf_sent_cnt = 0;
|
|
+
|
|
+ sensor_info->status = ISP4SD_START_STATUS_OFF;
|
|
+ output_info->start_status = ISP4SD_START_STATUS_OFF;
|
|
+}
|
|
+
|
|
+static void isp4sd_fw_resp_cmd_done(struct isp4_subdev *isp_subdev,
|
|
+ enum isp4if_stream_id stream_id,
|
|
+ struct isp4fw_resp_cmd_done *para)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4if_cmd_element *ele =
|
|
+ isp4if_rm_cmd_from_cmdq(ispif, para->cmd_seq_num, para->cmd_id);
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ dev_dbg(dev, "stream %d,cmd %s(0x%08x)(%d),seq %u, ele %p\n",
|
|
+ stream_id,
|
|
+ isp4dbg_get_cmd_str(para->cmd_id),
|
|
+ para->cmd_id, para->cmd_status, para->cmd_seq_num,
|
|
+ ele);
|
|
+
|
|
+ if (ele) {
|
|
+ complete(&ele->cmd_done);
|
|
+ if (atomic_dec_and_test(&ele->refcnt))
|
|
+ kfree(ele);
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct isp4fw_meta_info *
|
|
+isp4sd_get_meta_by_mc(struct isp4_subdev *isp_subdev, u64 mc)
|
|
+{
|
|
+ for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) {
|
|
+ struct isp4if_gpu_mem_info *meta_info_buf =
|
|
+ isp_subdev->ispif.meta_info_buf[i];
|
|
+
|
|
+ if (meta_info_buf->gpu_mc_addr == mc)
|
|
+ return meta_info_buf->sys_addr;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void isp4sd_send_meta_info(struct isp4_subdev *isp_subdev,
|
|
+ u64 meta_info_mc)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4fw_cmd_send_buffer buf_type;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ if (isp_subdev->sensor_info.status != ISP4SD_START_STATUS_STARTED) {
|
|
+ dev_warn(dev, "not working status %i, meta_info 0x%llx\n",
|
|
+ isp_subdev->sensor_info.status, meta_info_mc);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to guarantee
|
|
+ * padding bits are zeroed, since this is not guaranteed on all
|
|
+ * compilers.
|
|
+ */
|
|
+ memset(&buf_type, 0, sizeof(buf_type));
|
|
+ buf_type.buffer_type = ISP4FW_BUFFER_TYPE_META_INFO;
|
|
+ buf_type.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA;
|
|
+ isp4if_split_addr64(meta_info_mc,
|
|
+ &buf_type.buffer.buf_base_a_lo,
|
|
+ &buf_type.buffer.buf_base_a_hi);
|
|
+ buf_type.buffer.buf_size_a = ISP4SD_META_BUF_SIZE;
|
|
+
|
|
+ if (isp4if_send_command(ispif, ISP4FW_CMD_ID_SEND_BUFFER,
|
|
+ &buf_type, sizeof(buf_type)))
|
|
+ dev_err(dev, "fail send meta_info 0x%llx\n",
|
|
+ meta_info_mc);
|
|
+ else
|
|
+ dev_dbg(dev, "resend meta_info 0x%llx\n", meta_info_mc);
|
|
+}
|
|
+
|
|
+static void isp4sd_fw_resp_frame_done(struct isp4_subdev *isp_subdev,
|
|
+ enum isp4if_stream_id stream_id,
|
|
+ struct isp4fw_resp_param_package *para)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ struct isp4if_img_buf_node *prev;
|
|
+ struct isp4fw_meta_info *meta;
|
|
+ u64 mc;
|
|
+
|
|
+ mc = isp4if_join_addr64(para->package_addr_lo, para->package_addr_hi);
|
|
+ meta = isp4sd_get_meta_by_mc(isp_subdev, mc);
|
|
+ if (!meta) {
|
|
+ dev_err(dev, "fail to get meta from mc %llx\n", mc);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "ts:%llu,streamId:%d,poc:%u,preview_en:%u,status:%s(%i)\n",
|
|
+ ktime_get_ns(), stream_id, meta->poc, meta->preview.enabled,
|
|
+ isp4dbg_get_buf_done_str(meta->preview.status),
|
|
+ meta->preview.status);
|
|
+
|
|
+ if (meta->preview.enabled &&
|
|
+ (meta->preview.status == ISP4FW_BUFFER_STATUS_SKIPPED ||
|
|
+ meta->preview.status == ISP4FW_BUFFER_STATUS_DONE ||
|
|
+ meta->preview.status == ISP4FW_BUFFER_STATUS_DIRTY)) {
|
|
+ prev = isp4if_dequeue_buffer(ispif);
|
|
+ if (prev) {
|
|
+ isp4dbg_show_bufmeta_info(dev, "prev", &meta->preview,
|
|
+ &prev->buf_info);
|
|
+ isp4vid_handle_frame_done(&isp_subdev->isp_vdev,
|
|
+ &prev->buf_info);
|
|
+ isp4if_dealloc_buffer_node(prev);
|
|
+ } else {
|
|
+ dev_err(dev, "fail null prev buf\n");
|
|
+ }
|
|
+ } else if (meta->preview.enabled) {
|
|
+ dev_err(dev, "fail bad preview status %u(%s)\n",
|
|
+ meta->preview.status,
|
|
+ isp4dbg_get_buf_done_str(meta->preview.status));
|
|
+ }
|
|
+
|
|
+ if (isp_subdev->sensor_info.status == ISP4SD_START_STATUS_STARTED)
|
|
+ isp4sd_send_meta_info(isp_subdev, mc);
|
|
+
|
|
+ dev_dbg(dev, "stream_id:%d, status:%d\n", stream_id,
|
|
+ isp_subdev->sensor_info.status);
|
|
+}
|
|
+
|
|
+static void isp4sd_fw_resp_func(struct isp4_subdev *isp_subdev,
|
|
+ enum isp4if_stream_id stream_id)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ struct isp4fw_resp resp;
|
|
+
|
|
+ if (stream_id == ISP4IF_STREAM_ID_1)
|
|
+ isp_fw_log_print(isp_subdev);
|
|
+
|
|
+ while (true) {
|
|
+ if (isp4if_f2h_resp(ispif, stream_id, &resp)) {
|
|
+ /* Re-enable the interrupt */
|
|
+ isp4_intr_enable(isp_subdev, stream_id, true);
|
|
+ /*
|
|
+ * Recheck to see if there is a new response.
|
|
+ * To ensure that an in-flight interrupt is not lost,
|
|
+ * enabling the interrupt must occur _before_ checking
|
|
+ * for a new response, hence a memory barrier is needed.
|
|
+ * Disable the interrupt again if there was a new
|
|
+ * response.
|
|
+ */
|
|
+ mb();
|
|
+ if (likely(isp4if_f2h_resp(ispif, stream_id, &resp)))
|
|
+ break;
|
|
+
|
|
+ isp4_intr_enable(isp_subdev, stream_id, false);
|
|
+ }
|
|
+
|
|
+ switch (resp.resp_id) {
|
|
+ case ISP4FW_RESP_ID_CMD_DONE:
|
|
+ isp4sd_fw_resp_cmd_done(isp_subdev, stream_id,
|
|
+ &resp.param.cmd_done);
|
|
+ break;
|
|
+ case ISP4FW_RESP_ID_NOTI_FRAME_DONE:
|
|
+ isp4sd_fw_resp_frame_done(isp_subdev, stream_id,
|
|
+ &resp.param.frame_done);
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(dev, "-><- fail respid %s(0x%x)\n",
|
|
+ isp4dbg_get_resp_str(resp.resp_id),
|
|
+ resp.resp_id);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static s32 isp4sd_fw_resp_thread(void *context)
|
|
+{
|
|
+ struct isp4_subdev_thread_param *para = context;
|
|
+ struct isp4_subdev *isp_subdev = para->isp_subdev;
|
|
+ struct isp4sd_thread_handler *thread_ctx =
|
|
+ &isp_subdev->fw_resp_thread[para->idx];
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ dev_dbg(dev, "[%u] fw resp thread started\n", para->idx);
|
|
+ while (true) {
|
|
+ wait_event_interruptible(thread_ctx->waitq,
|
|
+ thread_ctx->resp_ready);
|
|
+ thread_ctx->resp_ready = false;
|
|
+
|
|
+ if (kthread_should_stop()) {
|
|
+ dev_dbg(dev, "[%u] fw resp thread quit\n", para->idx);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ isp4sd_fw_resp_func(isp_subdev, para->idx);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_stop_resp_proc_threads(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) {
|
|
+ struct isp4sd_thread_handler *thread_ctx =
|
|
+ &isp_subdev->fw_resp_thread[i];
|
|
+
|
|
+ if (thread_ctx->thread) {
|
|
+ kthread_stop(thread_ctx->thread);
|
|
+ thread_ctx->thread = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_start_resp_proc_threads(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) {
|
|
+ struct isp4sd_thread_handler *thread_ctx =
|
|
+ &isp_subdev->fw_resp_thread[i];
|
|
+
|
|
+ isp_subdev->isp_resp_para[i].idx = i;
|
|
+ isp_subdev->isp_resp_para[i].isp_subdev = isp_subdev;
|
|
+ init_waitqueue_head(&thread_ctx->waitq);
|
|
+ thread_ctx->resp_ready = false;
|
|
+
|
|
+ thread_ctx->thread = kthread_run(isp4sd_fw_resp_thread,
|
|
+ &isp_subdev->isp_resp_para[i],
|
|
+ isp4sd_thread_name[i]);
|
|
+ if (IS_ERR(thread_ctx->thread)) {
|
|
+ dev_err(dev, "create thread [%d] fail\n", i);
|
|
+ thread_ctx->thread = NULL;
|
|
+ isp4sd_stop_resp_proc_threads(isp_subdev);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd)
|
|
+{
|
|
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
|
|
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
|
|
+ unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_LOW;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&isp_subdev->ops_mutex);
|
|
+ if (sensor_info->status == ISP4SD_START_STATUS_STARTED) {
|
|
+ dev_err(dev, "fail for stream still running\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ sensor_info->status = ISP4SD_START_STATUS_OFF;
|
|
+
|
|
+ if (isp_subdev->irq_enabled) {
|
|
+ for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++)
|
|
+ disable_irq(isp_subdev->irq[i]);
|
|
+ isp_subdev->irq_enabled = false;
|
|
+ }
|
|
+
|
|
+ isp4sd_stop_resp_proc_threads(isp_subdev);
|
|
+ dev_dbg(dev, "isp_subdev stop resp proc threads suc\n");
|
|
+
|
|
+ isp4if_stop(ispif);
|
|
+
|
|
+ ret = dev_pm_genpd_set_performance_state(dev, perf_state);
|
|
+ if (ret)
|
|
+ dev_err(dev,
|
|
+ "fail to set isp_subdev performance state %u,ret %d\n",
|
|
+ perf_state, ret);
|
|
+
|
|
+ /* hold ccpu reset */
|
|
+ isp4hw_wreg(isp_subdev->mmio, ISP_SOFT_RESET, 0);
|
|
+ isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0);
|
|
+ ret = pm_runtime_put_sync(dev);
|
|
+ if (ret)
|
|
+ dev_err(dev, "power off isp_subdev fail %d\n", ret);
|
|
+ else
|
|
+ dev_dbg(dev, "power off isp_subdev suc\n");
|
|
+
|
|
+ ispif->status = ISP4IF_STATUS_PWR_OFF;
|
|
+ isp4if_clear_cmdq(ispif);
|
|
+ isp4sd_module_enable(isp_subdev, false);
|
|
+
|
|
+ /*
|
|
+ * When opening the camera, isp4sd_module_enable(isp_subdev, true) is
|
|
+ * called. Hardware requires at least a 20ms delay between disabling
|
|
+ * and enabling the module, so a sleep is added to ensure ISP stability
|
|
+ * during quick reopen scenarios.
|
|
+ */
|
|
+ msleep(20);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int isp4sd_pwron_and_init(struct v4l2_subdev *sd)
|
|
+{
|
|
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&isp_subdev->ops_mutex);
|
|
+ if (ispif->status == ISP4IF_STATUS_FW_RUNNING) {
|
|
+ dev_dbg(dev, "camera already opened, do nothing\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ isp4sd_module_enable(isp_subdev, true);
|
|
+
|
|
+ if (ispif->status < ISP4IF_STATUS_PWR_ON) {
|
|
+ unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_HIGH;
|
|
+
|
|
+ ret = pm_runtime_resume_and_get(dev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to power on isp_subdev ret %d\n",
|
|
+ ret);
|
|
+ goto err_deinit;
|
|
+ }
|
|
+
|
|
+ /* ISPPG ISP Power Status */
|
|
+ isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0x7FF);
|
|
+ ret = dev_pm_genpd_set_performance_state(dev, perf_state);
|
|
+ if (ret) {
|
|
+ dev_err(dev,
|
|
+ "fail to set performance state %u, ret %d\n",
|
|
+ perf_state, ret);
|
|
+ goto err_deinit;
|
|
+ }
|
|
+
|
|
+ ispif->status = ISP4IF_STATUS_PWR_ON;
|
|
+ }
|
|
+
|
|
+ isp_subdev->sensor_info.start_stream_cmd_sent = false;
|
|
+ isp_subdev->sensor_info.buf_sent_cnt = 0;
|
|
+
|
|
+ ret = isp4if_start(ispif);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to start isp_subdev interface\n");
|
|
+ goto err_deinit;
|
|
+ }
|
|
+
|
|
+ if (isp4sd_start_resp_proc_threads(isp_subdev)) {
|
|
+ dev_err(dev, "isp_start_resp_proc_threads fail\n");
|
|
+ goto err_deinit;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "create resp threads ok\n");
|
|
+
|
|
+ for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++)
|
|
+ enable_irq(isp_subdev->irq[i]);
|
|
+ isp_subdev->irq_enabled = true;
|
|
+
|
|
+ return 0;
|
|
+err_deinit:
|
|
+ isp4sd_pwroff_and_deinit(sd);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int isp4sd_stop_stream(struct isp4_subdev *isp_subdev,
|
|
+ struct v4l2_subdev_state *state, u32 pad)
|
|
+{
|
|
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
|
|
+ struct isp4sd_output_info *output_info = &sensor_info->output_info;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+
|
|
+ guard(mutex)(&isp_subdev->ops_mutex);
|
|
+ dev_dbg(dev, "status %i\n", output_info->start_status);
|
|
+
|
|
+ if (output_info->start_status == ISP4SD_START_STATUS_STARTED) {
|
|
+ struct isp4fw_cmd_enable_out_ch cmd_ch_disable;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * The struct will be shared with ISP FW, use memset() to
|
|
+ * guarantee padding bits are zeroed, since this is not
|
|
+ * guaranteed on all compilers.
|
|
+ */
|
|
+ memset(&cmd_ch_disable, 0, sizeof(cmd_ch_disable));
|
|
+ cmd_ch_disable.ch = ISP4FW_ISP_PIPE_OUT_CH_PREVIEW;
|
|
+ /* `cmd_ch_disable.is_enable` is already false */
|
|
+ ret = isp4if_send_command_sync(ispif,
|
|
+ ISP4FW_CMD_ID_ENABLE_OUT_CHAN,
|
|
+ &cmd_ch_disable,
|
|
+ sizeof(cmd_ch_disable));
|
|
+ if (ret)
|
|
+ dev_err(dev, "fail to disable stream\n");
|
|
+ else
|
|
+ dev_dbg(dev, "wait disable stream suc\n");
|
|
+
|
|
+ ret = isp4if_send_command_sync(ispif, ISP4FW_CMD_ID_STOP_STREAM,
|
|
+ NULL, 0);
|
|
+ if (ret)
|
|
+ dev_err(dev, "fail to stop stream\n");
|
|
+ else
|
|
+ dev_dbg(dev, "wait stop stream suc\n");
|
|
+ }
|
|
+
|
|
+ isp4sd_uninit_stream(isp_subdev, state, pad);
|
|
+
|
|
+ /*
|
|
+ * Return success to ensure the stop process proceeds,
|
|
+ * and disregard any errors since they are not fatal.
|
|
+ */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_start_stream(struct isp4_subdev *isp_subdev,
|
|
+ struct v4l2_subdev_state *state, u32 pad)
|
|
+{
|
|
+ struct isp4sd_output_info *output_info =
|
|
+ &isp_subdev->sensor_info.output_info;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&isp_subdev->ops_mutex);
|
|
+
|
|
+ if (ispif->status != ISP4IF_STATUS_FW_RUNNING) {
|
|
+ dev_err(dev, "fail, bad fsm %d\n", ispif->status);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (output_info->start_status) {
|
|
+ case ISP4SD_START_STATUS_OFF:
|
|
+ break;
|
|
+ case ISP4SD_START_STATUS_STARTED:
|
|
+ dev_dbg(dev, "stream already started, do nothing\n");
|
|
+ return 0;
|
|
+ case ISP4SD_START_STATUS_START_FAIL:
|
|
+ dev_err(dev, "stream previously failed to start\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = isp4sd_init_stream(isp_subdev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to init isp_subdev stream\n");
|
|
+ goto err_stop_stream;
|
|
+ }
|
|
+
|
|
+ ret = isp4sd_setup_output(isp_subdev, state, pad);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to setup output\n");
|
|
+ goto err_stop_stream;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_stop_stream:
|
|
+ isp4sd_stop_stream(isp_subdev, state, pad);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd,
|
|
+ struct isp4if_img_buf_info *buf_info)
|
|
+{
|
|
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct isp4if_img_buf_node *buf_node;
|
|
+ struct device *dev = isp_subdev->dev;
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&isp_subdev->ops_mutex);
|
|
+
|
|
+ if (ispif->status != ISP4IF_STATUS_FW_RUNNING) {
|
|
+ dev_err(dev, "fail send img buf for bad fsm %d\n",
|
|
+ ispif->status);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ buf_node = isp4if_alloc_buffer_node(buf_info);
|
|
+ if (!buf_node) {
|
|
+ dev_err(dev, "fail alloc sys img buf info node\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ ret = isp4if_queue_buffer(ispif, buf_node);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to queue image buf, %d\n", ret);
|
|
+ goto error_release_buf_node;
|
|
+ }
|
|
+
|
|
+ if (!isp_subdev->sensor_info.start_stream_cmd_sent) {
|
|
+ isp_subdev->sensor_info.buf_sent_cnt++;
|
|
+
|
|
+ if (isp_subdev->sensor_info.buf_sent_cnt >=
|
|
+ ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) {
|
|
+ ret = isp4if_send_command(ispif,
|
|
+ ISP4FW_CMD_ID_START_STREAM,
|
|
+ NULL, 0);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to START_STREAM");
|
|
+ goto error_release_buf_node;
|
|
+ }
|
|
+ isp_subdev->sensor_info.start_stream_cmd_sent = true;
|
|
+ isp_subdev->sensor_info.output_info.start_status =
|
|
+ ISP4SD_START_STATUS_STARTED;
|
|
+ isp_subdev->sensor_info.status =
|
|
+ ISP4SD_START_STATUS_STARTED;
|
|
+ } else {
|
|
+ dev_dbg(dev,
|
|
+ "no send start, required %u, buf sent %u\n",
|
|
+ ISP4SD_MIN_BUF_CNT_BEF_START_STREAM,
|
|
+ isp_subdev->sensor_info.buf_sent_cnt);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_release_buf_node:
|
|
+ isp4if_dealloc_buffer_node(buf_node);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops isp4sd_video_ops = {
|
|
+ .s_stream = v4l2_subdev_s_stream_helper,
|
|
+};
|
|
+
|
|
+static int isp4sd_set_pad_format(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *format)
|
|
+{
|
|
+ struct isp4sd_output_info *stream_info =
|
|
+ &(to_isp4_subdev(sd)->sensor_info.output_info);
|
|
+ struct v4l2_mbus_framefmt *fmt;
|
|
+
|
|
+ fmt = v4l2_subdev_state_get_format(sd_state, format->pad);
|
|
+
|
|
+ if (!fmt) {
|
|
+ dev_err(sd->dev, "fail to get state format\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ *fmt = format->format;
|
|
+ switch (fmt->code) {
|
|
+ case MEDIA_BUS_FMT_YUYV8_1X16:
|
|
+ stream_info->image_size = fmt->width * fmt->height * 2;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_YUYV8_1_5X8:
|
|
+ default:
|
|
+ stream_info->image_size = fmt->width * fmt->height * 3 / 2;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!stream_info->image_size) {
|
|
+ dev_err(sd->dev,
|
|
+ "fail set pad format,code 0x%x,width %u, height %u\n",
|
|
+ fmt->code, fmt->width, fmt->height);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_dbg(sd->dev, "set pad format suc, code:%x w:%u h:%u size:%u\n",
|
|
+ fmt->code, fmt->width, fmt->height,
|
|
+ stream_info->image_size);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4sd_enable_streams(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state, u32 pad,
|
|
+ u64 streams_mask)
|
|
+{
|
|
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
|
|
+
|
|
+ return isp4sd_start_stream(isp_subdev, state, pad);
|
|
+}
|
|
+
|
|
+static int isp4sd_disable_streams(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state, u32 pad,
|
|
+ u64 streams_mask)
|
|
+{
|
|
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
|
|
+
|
|
+ return isp4sd_stop_stream(isp_subdev, state, pad);
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops isp4sd_pad_ops = {
|
|
+ .get_fmt = v4l2_subdev_get_fmt,
|
|
+ .set_fmt = isp4sd_set_pad_format,
|
|
+ .enable_streams = isp4sd_enable_streams,
|
|
+ .disable_streams = isp4sd_disable_streams,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops isp4sd_subdev_ops = {
|
|
+ .video = &isp4sd_video_ops,
|
|
+ .pad = &isp4sd_pad_ops,
|
|
+};
|
|
+
|
|
+int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev,
|
|
+ int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM])
|
|
+{
|
|
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+ struct device *dev = v4l2_dev->dev;
|
|
+ int ret;
|
|
+
|
|
+ isp_subdev->dev = dev;
|
|
+ v4l2_subdev_init(&isp_subdev->sdev, &isp4sd_subdev_ops);
|
|
+ isp_subdev->sdev.owner = THIS_MODULE;
|
|
+ isp_subdev->sdev.dev = dev;
|
|
+ snprintf(isp_subdev->sdev.name, sizeof(isp_subdev->sdev.name), "%s",
|
|
+ dev_name(dev));
|
|
+
|
|
+ isp_subdev->sdev.entity.name = isp4sd_entity_name;
|
|
+ isp_subdev->sdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
|
|
+ isp_subdev->sdev_pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
+ ret = media_entity_pads_init(&isp_subdev->sdev.entity, 1,
|
|
+ &isp_subdev->sdev_pad);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to init isp4 subdev entity pad %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_subdev_init_finalize(&isp_subdev->sdev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "fail to init finalize isp4 subdev %d\n",
|
|
+ ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_device_register_subdev(v4l2_dev, &isp_subdev->sdev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to register isp4 subdev to V4L2 device %d\n",
|
|
+ ret);
|
|
+ goto err_media_clean_up;
|
|
+ }
|
|
+
|
|
+ isp4if_init(ispif, dev, isp_subdev->mmio);
|
|
+
|
|
+ mutex_init(&isp_subdev->ops_mutex);
|
|
+ sensor_info->status = ISP4SD_START_STATUS_OFF;
|
|
+
|
|
+ /* create ISP enable gpio control */
|
|
+ isp_subdev->enable_gpio = devm_gpiod_get(isp_subdev->dev,
|
|
+ "enable_isp",
|
|
+ GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(isp_subdev->enable_gpio)) {
|
|
+ ret = PTR_ERR(isp_subdev->enable_gpio);
|
|
+ dev_err(dev, "fail to get gpiod %d\n", ret);
|
|
+ goto err_subdev_unreg;
|
|
+ }
|
|
+
|
|
+ for (unsigned int i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++)
|
|
+ isp_subdev->irq[i] = irq[i];
|
|
+
|
|
+ isp_subdev->host2fw_seq_num = 1;
|
|
+ ispif->status = ISP4IF_STATUS_PWR_OFF;
|
|
+
|
|
+ ret = isp4vid_dev_init(&isp_subdev->isp_vdev, &isp_subdev->sdev);
|
|
+ if (ret)
|
|
+ goto err_subdev_unreg;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_subdev_unreg:
|
|
+ v4l2_device_unregister_subdev(&isp_subdev->sdev);
|
|
+err_media_clean_up:
|
|
+ v4l2_subdev_cleanup(&isp_subdev->sdev);
|
|
+ media_entity_cleanup(&isp_subdev->sdev.entity);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void isp4sd_deinit(struct isp4_subdev *isp_subdev)
|
|
+{
|
|
+ struct isp4_interface *ispif = &isp_subdev->ispif;
|
|
+
|
|
+ isp4vid_dev_deinit(&isp_subdev->isp_vdev);
|
|
+ v4l2_device_unregister_subdev(&isp_subdev->sdev);
|
|
+ media_entity_cleanup(&isp_subdev->sdev.entity);
|
|
+ isp4if_deinit(ispif);
|
|
+ isp4sd_module_enable(isp_subdev, false);
|
|
+
|
|
+ ispif->status = ISP4IF_STATUS_PWR_OFF;
|
|
+}
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.h b/drivers/media/platform/amd/isp4/isp4_subdev.h
|
|
new file mode 100644
|
|
index 0000000000000..20ea08a830afd
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_subdev.h
|
|
@@ -0,0 +1,127 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_SUBDEV_H_
|
|
+#define _ISP4_SUBDEV_H_
|
|
+
|
|
+#include <linux/debugfs.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <media/v4l2-device.h>
|
|
+
|
|
+#include "isp4_fw_cmd_resp.h"
|
|
+#include "isp4_hw_reg.h"
|
|
+#include "isp4_interface.h"
|
|
+#include "isp4_video.h"
|
|
+
|
|
+/*
|
|
+ * One is for none sensor specific response which is not used now.
|
|
+ * Another is for sensor specific response
|
|
+ */
|
|
+#define ISP4SD_MAX_FW_RESP_STREAM_NUM 2
|
|
+
|
|
+/* Indicates the ISP status */
|
|
+enum isp4sd_status {
|
|
+ ISP4SD_STATUS_PWR_OFF,
|
|
+ ISP4SD_STATUS_PWR_ON,
|
|
+ ISP4SD_STATUS_FW_RUNNING,
|
|
+ ISP4SD_STATUS_MAX
|
|
+};
|
|
+
|
|
+/* Indicates sensor and output stream status */
|
|
+enum isp4sd_start_status {
|
|
+ ISP4SD_START_STATUS_OFF,
|
|
+ ISP4SD_START_STATUS_STARTED,
|
|
+ ISP4SD_START_STATUS_START_FAIL,
|
|
+};
|
|
+
|
|
+struct isp4sd_img_buf_node {
|
|
+ struct list_head node;
|
|
+ struct isp4if_img_buf_info buf_info;
|
|
+};
|
|
+
|
|
+/* This is ISP output after processing Bayer raw sensor input */
|
|
+struct isp4sd_output_info {
|
|
+ enum isp4sd_start_status start_status;
|
|
+ u32 image_size;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Struct for sensor info used as ISP input or source.
|
|
+ * status: sensor status.
|
|
+ * output_info: ISP output after processing the sensor input.
|
|
+ * start_stream_cmd_sent: indicates if ISP4FW_CMD_ID_START_STREAM was sent
|
|
+ * to firmware.
|
|
+ * buf_sent_cnt: number of buffers sent to receive images.
|
|
+ */
|
|
+struct isp4sd_sensor_info {
|
|
+ struct isp4sd_output_info output_info;
|
|
+ enum isp4sd_start_status status;
|
|
+ bool start_stream_cmd_sent;
|
|
+ u32 buf_sent_cnt;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * The thread is created by the driver to handle firmware responses which will
|
|
+ * be waken up when a firmware-to-driver response interrupt occurs.
|
|
+ */
|
|
+struct isp4sd_thread_handler {
|
|
+ struct task_struct *thread;
|
|
+ wait_queue_head_t waitq;
|
|
+ bool resp_ready;
|
|
+};
|
|
+
|
|
+struct isp4_subdev_thread_param {
|
|
+ u32 idx;
|
|
+ struct isp4_subdev *isp_subdev;
|
|
+};
|
|
+
|
|
+struct isp4_subdev {
|
|
+ struct v4l2_subdev sdev;
|
|
+ struct isp4_interface ispif;
|
|
+ struct isp4vid_dev isp_vdev;
|
|
+
|
|
+ struct media_pad sdev_pad;
|
|
+
|
|
+ enum isp4sd_status isp_status;
|
|
+ /* mutex used to synchronize the operation with firmware */
|
|
+ struct mutex ops_mutex;
|
|
+
|
|
+ struct isp4sd_thread_handler
|
|
+ fw_resp_thread[ISP4SD_MAX_FW_RESP_STREAM_NUM];
|
|
+
|
|
+ u32 host2fw_seq_num;
|
|
+
|
|
+ struct isp4sd_sensor_info sensor_info;
|
|
+
|
|
+ /* gpio descriptor */
|
|
+ struct gpio_desc *enable_gpio;
|
|
+ struct device *dev;
|
|
+ void __iomem *mmio;
|
|
+ struct isp4_subdev_thread_param
|
|
+ isp_resp_para[ISP4SD_MAX_FW_RESP_STREAM_NUM];
|
|
+ int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM];
|
|
+ bool irq_enabled;
|
|
+ /* spin lock to access ISP_SYS_INT0_EN exclusively */
|
|
+ spinlock_t irq_lock;
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ bool enable_fw_log;
|
|
+ struct dentry *debugfs_dir;
|
|
+ char *fw_log_output;
|
|
+#endif
|
|
+};
|
|
+
|
|
+int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev,
|
|
+ int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]);
|
|
+void isp4sd_deinit(struct isp4_subdev *isp_subdev);
|
|
+int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd,
|
|
+ struct isp4if_img_buf_info *buf_info);
|
|
+int isp4sd_pwron_and_init(struct v4l2_subdev *sd);
|
|
+int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd);
|
|
+
|
|
+#endif /* _ISP4_SUBDEV_H_ */
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_video.c b/drivers/media/platform/amd/isp4/isp4_video.c
|
|
new file mode 100644
|
|
index 0000000000000..0cebb39f98e1d
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_video.c
|
|
@@ -0,0 +1,797 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#include <media/v4l2-ioctl.h>
|
|
+#include <media/v4l2-mc.h>
|
|
+#include <media/videobuf2-vmalloc.h>
|
|
+
|
|
+#include "isp4_interface.h"
|
|
+#include "isp4_subdev.h"
|
|
+#include "isp4_video.h"
|
|
+
|
|
+#define ISP4VID_ISP_DRV_NAME "amd_isp_capture"
|
|
+#define ISP4VID_MAX_PREVIEW_FPS 30
|
|
+#define ISP4VID_DEFAULT_FMT V4L2_PIX_FMT_NV12
|
|
+
|
|
+#define ISP4VID_PAD_VIDEO_OUTPUT 0
|
|
+
|
|
+/* time perframe default */
|
|
+#define ISP4VID_ISP_TPF_DEFAULT isp4vid_tpfs[0]
|
|
+
|
|
+static const char *const isp4vid_video_dev_name = "Preview";
|
|
+
|
|
+/* Sizes must be in increasing order */
|
|
+static const struct v4l2_frmsize_discrete isp4vid_frmsize[] = {
|
|
+ {640, 360},
|
|
+ {640, 480},
|
|
+ {1280, 720},
|
|
+ {1280, 960},
|
|
+ {1920, 1080},
|
|
+ {1920, 1440},
|
|
+ {2560, 1440},
|
|
+ {2880, 1620},
|
|
+ {2880, 1624},
|
|
+ {2888, 1808},
|
|
+};
|
|
+
|
|
+static const u32 isp4vid_formats[] = {
|
|
+ V4L2_PIX_FMT_NV12,
|
|
+ V4L2_PIX_FMT_YUYV
|
|
+};
|
|
+
|
|
+/* time perframe list */
|
|
+static const struct v4l2_fract isp4vid_tpfs[] = {
|
|
+ { 1, ISP4VID_MAX_PREVIEW_FPS }
|
|
+};
|
|
+
|
|
+void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev,
|
|
+ const struct isp4if_img_buf_info *img_buf)
|
|
+{
|
|
+ struct isp4vid_capture_buffer *isp4vid_buf;
|
|
+ void *vbuf;
|
|
+
|
|
+ scoped_guard(mutex, &isp_vdev->buf_list_lock) {
|
|
+ isp4vid_buf = list_first_entry_or_null(&isp_vdev->buf_list,
|
|
+ typeof(*isp4vid_buf),
|
|
+ list);
|
|
+ if (!isp4vid_buf)
|
|
+ return;
|
|
+
|
|
+ vbuf = vb2_plane_vaddr(&isp4vid_buf->vb2.vb2_buf, 0);
|
|
+
|
|
+ if (vbuf != img_buf->planes[0].sys_addr) {
|
|
+ dev_err(isp_vdev->dev, "Invalid vbuf\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ list_del(&isp4vid_buf->list);
|
|
+ }
|
|
+
|
|
+ /* Fill the buffer */
|
|
+ isp4vid_buf->vb2.vb2_buf.timestamp = ktime_get_ns();
|
|
+ isp4vid_buf->vb2.sequence = isp_vdev->sequence++;
|
|
+ isp4vid_buf->vb2.field = V4L2_FIELD_ANY;
|
|
+
|
|
+ vb2_set_plane_payload(&isp4vid_buf->vb2.vb2_buf,
|
|
+ 0, isp_vdev->format.sizeimage);
|
|
+
|
|
+ vb2_buffer_done(&isp4vid_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE);
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "call vb2_buffer_done(size=%u)\n",
|
|
+ isp_vdev->format.sizeimage);
|
|
+}
|
|
+
|
|
+static const struct v4l2_pix_format isp4vid_fmt_default = {
|
|
+ .width = 1920,
|
|
+ .height = 1080,
|
|
+ .pixelformat = ISP4VID_DEFAULT_FMT,
|
|
+ .field = V4L2_FIELD_NONE,
|
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
|
+};
|
|
+
|
|
+static void isp4vid_capture_return_all_buffers(struct isp4vid_dev *isp_vdev,
|
|
+ enum vb2_buffer_state state)
|
|
+{
|
|
+ struct isp4vid_capture_buffer *vbuf, *node;
|
|
+
|
|
+ scoped_guard(mutex, &isp_vdev->buf_list_lock) {
|
|
+ list_for_each_entry_safe(vbuf, node, &isp_vdev->buf_list, list)
|
|
+ vb2_buffer_done(&vbuf->vb2.vb2_buf, state);
|
|
+ INIT_LIST_HEAD(&isp_vdev->buf_list);
|
|
+ }
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "call vb2_buffer_done(%d)\n", state);
|
|
+}
|
|
+
|
|
+static int isp4vid_vdev_link_validate(struct media_link *link)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct media_entity_operations isp4vid_vdev_ent_ops = {
|
|
+ .link_validate = isp4vid_vdev_link_validate,
|
|
+};
|
|
+
|
|
+static const struct v4l2_file_operations isp4vid_vdev_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = v4l2_fh_open,
|
|
+ .release = vb2_fop_release,
|
|
+ .read = vb2_fop_read,
|
|
+ .poll = vb2_fop_poll,
|
|
+ .unlocked_ioctl = video_ioctl2,
|
|
+ .mmap = vb2_fop_mmap,
|
|
+};
|
|
+
|
|
+static int isp4vid_ioctl_querycap(struct file *file, void *fh,
|
|
+ struct v4l2_capability *cap)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+
|
|
+ strscpy(cap->driver, ISP4VID_ISP_DRV_NAME, sizeof(cap->driver));
|
|
+ snprintf(cap->card, sizeof(cap->card), "%s", ISP4VID_ISP_DRV_NAME);
|
|
+ cap->capabilities |= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "%s|capabilities=0x%X\n", isp_vdev->vdev.name,
|
|
+ cap->capabilities);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4vid_g_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_format *f)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+
|
|
+ f->fmt.pix = isp_vdev->format;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4vid_fill_buffer_size(struct v4l2_pix_format *fmt)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ switch (fmt->pixelformat) {
|
|
+ case V4L2_PIX_FMT_NV12:
|
|
+ fmt->bytesperline = fmt->width;
|
|
+ fmt->sizeimage = fmt->bytesperline * fmt->height * 3 / 2;
|
|
+ break;
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ fmt->bytesperline = fmt->width * 2;
|
|
+ fmt->sizeimage = fmt->bytesperline * fmt->height;
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int isp4vid_try_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_format *f)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+ struct v4l2_pix_format *format = &f->fmt.pix;
|
|
+ const struct v4l2_frmsize_discrete *fsz;
|
|
+ size_t i;
|
|
+
|
|
+ /*
|
|
+ * Check if the hardware supports the requested format, use the default
|
|
+ * format otherwise.
|
|
+ */
|
|
+ for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++)
|
|
+ if (isp4vid_formats[i] == format->pixelformat)
|
|
+ break;
|
|
+
|
|
+ if (i == ARRAY_SIZE(isp4vid_formats))
|
|
+ format->pixelformat = ISP4VID_DEFAULT_FMT;
|
|
+
|
|
+ switch (format->pixelformat) {
|
|
+ case V4L2_PIX_FMT_NV12:
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ fsz = v4l2_find_nearest_size(isp4vid_frmsize,
|
|
+ ARRAY_SIZE(isp4vid_frmsize),
|
|
+ width, height, format->width,
|
|
+ format->height);
|
|
+ format->width = fsz->width;
|
|
+ format->height = fsz->height;
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n",
|
|
+ isp_vdev->vdev.name,
|
|
+ format->pixelformat);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * There is no need to check the return value, as failure will never
|
|
+ * happen here
|
|
+ */
|
|
+ isp4vid_fill_buffer_size(format);
|
|
+
|
|
+ if (format->field == V4L2_FIELD_ANY)
|
|
+ format->field = isp4vid_fmt_default.field;
|
|
+
|
|
+ if (format->colorspace == V4L2_COLORSPACE_DEFAULT)
|
|
+ format->colorspace = isp4vid_fmt_default.colorspace;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4vid_set_fmt_2_isp(struct v4l2_subdev *sdev,
|
|
+ struct v4l2_pix_format *pix_fmt)
|
|
+{
|
|
+ struct v4l2_subdev_format fmt = {};
|
|
+
|
|
+ switch (pix_fmt->pixelformat) {
|
|
+ case V4L2_PIX_FMT_NV12:
|
|
+ fmt.format.code = MEDIA_BUS_FMT_YUYV8_1_5X8;
|
|
+ break;
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ fmt.format.code = MEDIA_BUS_FMT_YUYV8_1X16;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
|
|
+ fmt.pad = ISP4VID_PAD_VIDEO_OUTPUT;
|
|
+ fmt.format.width = pix_fmt->width;
|
|
+ fmt.format.height = pix_fmt->height;
|
|
+ return v4l2_subdev_call(sdev, pad, set_fmt, NULL, &fmt);
|
|
+}
|
|
+
|
|
+static int isp4vid_s_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_format *f)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+ int ret;
|
|
+
|
|
+ /* Do not change the format while stream is on */
|
|
+ if (vb2_is_busy(&isp_vdev->vbq))
|
|
+ return -EBUSY;
|
|
+
|
|
+ ret = isp4vid_try_fmt_vid_cap(file, priv, f);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "%s|width height:%ux%u->%ux%u\n",
|
|
+ isp_vdev->vdev.name,
|
|
+ isp_vdev->format.width, isp_vdev->format.height,
|
|
+ f->fmt.pix.width, f->fmt.pix.height);
|
|
+ dev_dbg(isp_vdev->dev, "%s|pixelformat:0x%x-0x%x\n",
|
|
+ isp_vdev->vdev.name, isp_vdev->format.pixelformat,
|
|
+ f->fmt.pix.pixelformat);
|
|
+ dev_dbg(isp_vdev->dev, "%s|bytesperline:%u->%u\n",
|
|
+ isp_vdev->vdev.name, isp_vdev->format.bytesperline,
|
|
+ f->fmt.pix.bytesperline);
|
|
+ dev_dbg(isp_vdev->dev, "%s|sizeimage:%u->%u\n",
|
|
+ isp_vdev->vdev.name, isp_vdev->format.sizeimage,
|
|
+ f->fmt.pix.sizeimage);
|
|
+
|
|
+ isp_vdev->format = f->fmt.pix;
|
|
+ ret = isp4vid_set_fmt_2_isp(isp_vdev->isp_sdev, &isp_vdev->format);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int isp4vid_enum_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_fmtdesc *f)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+
|
|
+ switch (f->index) {
|
|
+ case 0:
|
|
+ f->pixelformat = V4L2_PIX_FMT_NV12;
|
|
+ break;
|
|
+ case 1:
|
|
+ f->pixelformat = V4L2_PIX_FMT_YUYV;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "%s|index=%d, pixelformat=0x%X\n",
|
|
+ isp_vdev->vdev.name, f->index, f->pixelformat);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4vid_enum_framesizes(struct file *file, void *fh,
|
|
+ struct v4l2_frmsizeenum *fsize)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) {
|
|
+ if (isp4vid_formats[i] == fsize->pixel_format)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (i == ARRAY_SIZE(isp4vid_formats))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (fsize->index < ARRAY_SIZE(isp4vid_frmsize)) {
|
|
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
|
+ fsize->discrete = isp4vid_frmsize[fsize->index];
|
|
+ dev_dbg(isp_vdev->dev, "%s|size[%d]=%dx%d\n",
|
|
+ isp_vdev->vdev.name, fsize->index,
|
|
+ fsize->discrete.width, fsize->discrete.height);
|
|
+ } else {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4vid_ioctl_enum_frameintervals(struct file *file, void *priv,
|
|
+ struct v4l2_frmivalenum *fival)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+ size_t i;
|
|
+
|
|
+ if (fival->index >= ARRAY_SIZE(isp4vid_tpfs))
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++)
|
|
+ if (isp4vid_formats[i] == fival->pixel_format)
|
|
+ break;
|
|
+
|
|
+ if (i == ARRAY_SIZE(isp4vid_formats))
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(isp4vid_frmsize); i++)
|
|
+ if (isp4vid_frmsize[i].width == fival->width &&
|
|
+ isp4vid_frmsize[i].height == fival->height)
|
|
+ break;
|
|
+
|
|
+ if (i == ARRAY_SIZE(isp4vid_frmsize))
|
|
+ return -EINVAL;
|
|
+
|
|
+ fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
|
|
+ fival->discrete = isp4vid_tpfs[fival->index];
|
|
+ v4l2_simplify_fraction(&fival->discrete.numerator,
|
|
+ &fival->discrete.denominator, 8, 333);
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "%s|interval[%d]=%d/%d\n",
|
|
+ isp_vdev->vdev.name, fival->index,
|
|
+ fival->discrete.numerator,
|
|
+ fival->discrete.denominator);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int isp4vid_ioctl_g_param(struct file *file, void *priv,
|
|
+ struct v4l2_streamparm *param)
|
|
+{
|
|
+ struct v4l2_captureparm *capture = ¶m->parm.capture;
|
|
+ struct isp4vid_dev *isp_vdev = video_drvdata(file);
|
|
+
|
|
+ if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
+ return -EINVAL;
|
|
+
|
|
+ capture->capability = V4L2_CAP_TIMEPERFRAME;
|
|
+ capture->timeperframe = isp_vdev->timeperframe;
|
|
+ capture->readbuffers = 0;
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "%s|timeperframe=%d/%d\n", isp_vdev->vdev.name,
|
|
+ capture->timeperframe.numerator,
|
|
+ capture->timeperframe.denominator);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_ioctl_ops isp4vid_vdev_ioctl_ops = {
|
|
+ .vidioc_querycap = isp4vid_ioctl_querycap,
|
|
+ .vidioc_enum_fmt_vid_cap = isp4vid_enum_fmt_vid_cap,
|
|
+ .vidioc_g_fmt_vid_cap = isp4vid_g_fmt_vid_cap,
|
|
+ .vidioc_s_fmt_vid_cap = isp4vid_s_fmt_vid_cap,
|
|
+ .vidioc_try_fmt_vid_cap = isp4vid_try_fmt_vid_cap,
|
|
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
|
|
+ .vidioc_querybuf = vb2_ioctl_querybuf,
|
|
+ .vidioc_qbuf = vb2_ioctl_qbuf,
|
|
+ .vidioc_expbuf = vb2_ioctl_expbuf,
|
|
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
|
|
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
|
|
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
|
|
+ .vidioc_streamon = vb2_ioctl_streamon,
|
|
+ .vidioc_streamoff = vb2_ioctl_streamoff,
|
|
+ .vidioc_g_parm = isp4vid_ioctl_g_param,
|
|
+ .vidioc_s_parm = isp4vid_ioctl_g_param,
|
|
+ .vidioc_enum_framesizes = isp4vid_enum_framesizes,
|
|
+ .vidioc_enum_frameintervals = isp4vid_ioctl_enum_frameintervals,
|
|
+};
|
|
+
|
|
+static unsigned int isp4vid_get_image_size(struct v4l2_pix_format *fmt)
|
|
+{
|
|
+ switch (fmt->pixelformat) {
|
|
+ case V4L2_PIX_FMT_NV12:
|
|
+ return fmt->width * fmt->height * 3 / 2;
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ return fmt->width * fmt->height * 2;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int isp4vid_qops_queue_setup(struct vb2_queue *vq,
|
|
+ unsigned int *nbuffers,
|
|
+ unsigned int *nplanes, unsigned int sizes[],
|
|
+ struct device *alloc_devs[])
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq);
|
|
+ unsigned int q_num_bufs = vb2_get_num_buffers(vq);
|
|
+
|
|
+ if (*nplanes > 1) {
|
|
+ dev_err(isp_vdev->dev,
|
|
+ "fail to setup queue, no mplane supported %u\n",
|
|
+ *nplanes);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (*nplanes == 1) {
|
|
+ unsigned int size;
|
|
+
|
|
+ size = isp4vid_get_image_size(&isp_vdev->format);
|
|
+ if (sizes[0] < size) {
|
|
+ dev_err(isp_vdev->dev,
|
|
+ "fail for small plane size %u, %u expected\n",
|
|
+ sizes[0], size);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (q_num_bufs + *nbuffers < ISP4IF_MAX_STREAM_BUF_COUNT)
|
|
+ *nbuffers = ISP4IF_MAX_STREAM_BUF_COUNT - q_num_bufs;
|
|
+
|
|
+ switch (isp_vdev->format.pixelformat) {
|
|
+ case V4L2_PIX_FMT_NV12:
|
|
+ case V4L2_PIX_FMT_YUYV: {
|
|
+ *nplanes = 1;
|
|
+ sizes[0] = max(sizes[0], isp_vdev->format.sizeimage);
|
|
+ isp_vdev->format.sizeimage = sizes[0];
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n",
|
|
+ isp_vdev->vdev.name, isp_vdev->format.pixelformat);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "%s|*nbuffers=%u *nplanes=%u sizes[0]=%u\n",
|
|
+ isp_vdev->vdev.name,
|
|
+ *nbuffers, *nplanes, sizes[0]);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void isp4vid_qops_buffer_queue(struct vb2_buffer *vb)
|
|
+{
|
|
+ struct isp4vid_capture_buffer *buf =
|
|
+ container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf);
|
|
+ struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue);
|
|
+ struct isp4if_img_buf_info *img_buf = &buf->img_buf;
|
|
+ void *vaddr = vb2_plane_vaddr(vb, 0);
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "queue buf, vaddr %p, gpuva 0x%llx, size %u\n",
|
|
+ vaddr, buf->gpu_addr, vb->planes[0].length);
|
|
+
|
|
+ switch (isp_vdev->format.pixelformat) {
|
|
+ case V4L2_PIX_FMT_NV12: {
|
|
+ u32 y_size = isp_vdev->format.sizeimage / 3 * 2;
|
|
+ u32 uv_size = isp_vdev->format.sizeimage / 3;
|
|
+
|
|
+ img_buf->planes[0].len = y_size;
|
|
+ img_buf->planes[0].sys_addr = vaddr;
|
|
+ img_buf->planes[0].mc_addr = buf->gpu_addr;
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n",
|
|
+ img_buf->planes[0].mc_addr,
|
|
+ img_buf->planes[0].len);
|
|
+
|
|
+ img_buf->planes[1].len = uv_size;
|
|
+ img_buf->planes[1].sys_addr = vaddr + y_size;
|
|
+ img_buf->planes[1].mc_addr = buf->gpu_addr + y_size;
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "img_buf[1]: mc=0x%llx size=%u\n",
|
|
+ img_buf->planes[1].mc_addr,
|
|
+ img_buf->planes[1].len);
|
|
+
|
|
+ img_buf->planes[2].len = 0;
|
|
+ }
|
|
+ break;
|
|
+ case V4L2_PIX_FMT_YUYV: {
|
|
+ img_buf->planes[0].len = isp_vdev->format.sizeimage;
|
|
+ img_buf->planes[0].sys_addr = vaddr;
|
|
+ img_buf->planes[0].mc_addr = buf->gpu_addr;
|
|
+
|
|
+ dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n",
|
|
+ img_buf->planes[0].mc_addr,
|
|
+ img_buf->planes[0].len);
|
|
+
|
|
+ img_buf->planes[1].len = 0;
|
|
+ img_buf->planes[2].len = 0;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n",
|
|
+ isp_vdev->vdev.name, isp_vdev->format.pixelformat);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (isp_vdev->stream_started)
|
|
+ isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev, img_buf);
|
|
+
|
|
+ scoped_guard(mutex, &isp_vdev->buf_list_lock)
|
|
+ list_add_tail(&buf->list, &isp_vdev->buf_list);
|
|
+}
|
|
+
|
|
+static int isp4vid_qops_start_streaming(struct vb2_queue *vq,
|
|
+ unsigned int count)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq);
|
|
+ struct isp4vid_capture_buffer *isp4vid_buf;
|
|
+ struct media_entity *entity;
|
|
+ struct v4l2_subdev *subdev;
|
|
+ struct media_pad *pad;
|
|
+ int ret = 0;
|
|
+
|
|
+ isp_vdev->sequence = 0;
|
|
+
|
|
+ ret = isp4sd_pwron_and_init(isp_vdev->isp_sdev);
|
|
+ if (ret) {
|
|
+ dev_err(isp_vdev->dev, "power up isp fail %d\n", ret);
|
|
+ goto release_buffers;
|
|
+ }
|
|
+
|
|
+ entity = &isp_vdev->vdev.entity;
|
|
+ while (1) {
|
|
+ pad = &entity->pads[0];
|
|
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
|
|
+ break;
|
|
+
|
|
+ pad = media_pad_remote_pad_first(pad);
|
|
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
|
|
+ break;
|
|
+
|
|
+ entity = pad->entity;
|
|
+ subdev = media_entity_to_v4l2_subdev(entity);
|
|
+
|
|
+ ret = v4l2_subdev_call(subdev, video, s_stream, 1);
|
|
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
|
|
+ dev_dbg(isp_vdev->dev, "fail start streaming: %s %d\n",
|
|
+ subdev->name, ret);
|
|
+ goto release_buffers;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ list_for_each_entry(isp4vid_buf, &isp_vdev->buf_list, list)
|
|
+ isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev,
|
|
+ &isp4vid_buf->img_buf);
|
|
+
|
|
+ isp_vdev->stream_started = true;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+release_buffers:
|
|
+ isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_QUEUED);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void isp4vid_qops_stop_streaming(struct vb2_queue *vq)
|
|
+{
|
|
+ struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq);
|
|
+ struct media_entity *entity;
|
|
+ struct v4l2_subdev *subdev;
|
|
+ struct media_pad *pad;
|
|
+ int ret;
|
|
+
|
|
+ entity = &isp_vdev->vdev.entity;
|
|
+ while (1) {
|
|
+ pad = &entity->pads[0];
|
|
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
|
|
+ break;
|
|
+
|
|
+ pad = media_pad_remote_pad_first(pad);
|
|
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
|
|
+ break;
|
|
+
|
|
+ entity = pad->entity;
|
|
+ subdev = media_entity_to_v4l2_subdev(entity);
|
|
+
|
|
+ ret = v4l2_subdev_call(subdev, video, s_stream, 0);
|
|
+
|
|
+ if (ret < 0 && ret != -ENOIOCTLCMD)
|
|
+ dev_dbg(isp_vdev->dev, "fail stop streaming: %s %d\n",
|
|
+ subdev->name, ret);
|
|
+ }
|
|
+
|
|
+ isp_vdev->stream_started = false;
|
|
+ isp4sd_pwroff_and_deinit(isp_vdev->isp_sdev);
|
|
+
|
|
+ /* Release all active buffers */
|
|
+ isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_ERROR);
|
|
+}
|
|
+
|
|
+static int isp4vid_qops_buf_init(struct vb2_buffer *vb)
|
|
+{
|
|
+ struct isp4vid_capture_buffer *buf =
|
|
+ container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf);
|
|
+ struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue);
|
|
+ void *mem_priv = vb->planes[0].mem_priv;
|
|
+ struct device *dev = isp_vdev->dev;
|
|
+ u64 gpu_addr;
|
|
+ void *bo;
|
|
+ int ret;
|
|
+
|
|
+ if (vb->planes[0].dbuf) {
|
|
+ buf->dbuf = vb->planes[0].dbuf;
|
|
+ } else {
|
|
+ /*
|
|
+ * HAS_DMA is a Kconfig dependency so CONFIG_HAS_DMA is always
|
|
+ * defined when this driver is compiled. The #else branch is
|
|
+ * kept as a safeguard in case the dependency is ever removed.
|
|
+ */
|
|
+#ifdef CONFIG_HAS_DMA
|
|
+ buf->dbuf = vb2_vmalloc_memops.get_dmabuf(vb, mem_priv, 0);
|
|
+ if (IS_ERR_OR_NULL(buf->dbuf)) {
|
|
+ dev_err(dev, "fail to get dma buf\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+#else
|
|
+ dev_err(dev, "get dmabuf fail -- CONFIG_HAS_DMA not defined\n");
|
|
+ buf->dbuf = NULL;
|
|
+ return -EINVAL;
|
|
+#endif
|
|
+ }
|
|
+
|
|
+ /* create isp user BO and obtain gpu_addr */
|
|
+ ret = isp_user_buffer_alloc(dev, buf->dbuf, &bo, &gpu_addr);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "fail to create isp user BO\n");
|
|
+ if (!vb->planes[0].dbuf) {
|
|
+ dma_buf_put(buf->dbuf);
|
|
+ buf->dbuf = NULL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ buf->bo = bo;
|
|
+ buf->gpu_addr = gpu_addr;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void isp4vid_qops_buf_cleanup(struct vb2_buffer *vb)
|
|
+{
|
|
+ struct isp4vid_capture_buffer *buf =
|
|
+ container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf);
|
|
+
|
|
+ if (buf->bo) {
|
|
+ isp_user_buffer_free(buf->bo);
|
|
+ buf->bo = NULL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Only put dmabufs we obtained ourselves via get_dmabuf, not ones
|
|
+ * provided by the framework for DMABUF import
|
|
+ */
|
|
+ if (buf->dbuf && buf->dbuf != vb->planes[0].dbuf)
|
|
+ dma_buf_put(buf->dbuf);
|
|
+
|
|
+ buf->dbuf = NULL;
|
|
+}
|
|
+
|
|
+static const struct vb2_ops isp4vid_qops = {
|
|
+ .queue_setup = isp4vid_qops_queue_setup,
|
|
+ .buf_init = isp4vid_qops_buf_init,
|
|
+ .buf_cleanup = isp4vid_qops_buf_cleanup,
|
|
+ .start_streaming = isp4vid_qops_start_streaming,
|
|
+ .stop_streaming = isp4vid_qops_stop_streaming,
|
|
+ .buf_queue = isp4vid_qops_buffer_queue,
|
|
+};
|
|
+
|
|
+int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd)
|
|
+{
|
|
+ const char *vdev_name = isp4vid_video_dev_name;
|
|
+ struct v4l2_device *v4l2_dev;
|
|
+ struct video_device *vdev;
|
|
+ struct vb2_queue *q;
|
|
+ int ret;
|
|
+
|
|
+ if (!isp_vdev || !isp_sd || !isp_sd->v4l2_dev)
|
|
+ return -EINVAL;
|
|
+
|
|
+ v4l2_dev = isp_sd->v4l2_dev;
|
|
+ vdev = &isp_vdev->vdev;
|
|
+
|
|
+ isp_vdev->isp_sdev = isp_sd;
|
|
+ isp_vdev->dev = v4l2_dev->dev;
|
|
+
|
|
+ /* Initialize the vb2_queue struct */
|
|
+ mutex_init(&isp_vdev->vbq_lock);
|
|
+ q = &isp_vdev->vbq;
|
|
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
|
|
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
|
+ q->buf_struct_size = sizeof(struct isp4vid_capture_buffer);
|
|
+ q->min_queued_buffers = 2;
|
|
+ q->ops = &isp4vid_qops;
|
|
+ q->drv_priv = isp_vdev;
|
|
+ q->mem_ops = &vb2_vmalloc_memops;
|
|
+ q->lock = &isp_vdev->vbq_lock;
|
|
+ q->dev = v4l2_dev->dev;
|
|
+ ret = vb2_queue_init(q);
|
|
+ if (ret) {
|
|
+ dev_err(v4l2_dev->dev, "vb2_queue_init error:%d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Initialize buffer list and its lock */
|
|
+ mutex_init(&isp_vdev->buf_list_lock);
|
|
+ INIT_LIST_HEAD(&isp_vdev->buf_list);
|
|
+
|
|
+ /* Set default frame format */
|
|
+ isp_vdev->format = isp4vid_fmt_default;
|
|
+ isp_vdev->timeperframe = ISP4VID_ISP_TPF_DEFAULT;
|
|
+ v4l2_simplify_fraction(&isp_vdev->timeperframe.numerator,
|
|
+ &isp_vdev->timeperframe.denominator, 8, 333);
|
|
+
|
|
+ ret = isp4vid_fill_buffer_size(&isp_vdev->format);
|
|
+ if (ret) {
|
|
+ dev_err(v4l2_dev->dev, "fail to fill buffer size: %d\n", ret);
|
|
+ goto err_release_vb2_queue;
|
|
+ }
|
|
+
|
|
+ ret = isp4vid_set_fmt_2_isp(isp_sd, &isp_vdev->format);
|
|
+ if (ret) {
|
|
+ dev_err(v4l2_dev->dev, "fail init format :%d\n", ret);
|
|
+ goto err_release_vb2_queue;
|
|
+ }
|
|
+
|
|
+ /* Initialize the video_device struct */
|
|
+ isp_vdev->vdev.entity.name = vdev_name;
|
|
+ isp_vdev->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
|
|
+ isp_vdev->vdev_pad.flags = MEDIA_PAD_FL_SINK;
|
|
+ ret = media_entity_pads_init(&isp_vdev->vdev.entity, 1,
|
|
+ &isp_vdev->vdev_pad);
|
|
+
|
|
+ if (ret) {
|
|
+ dev_err(v4l2_dev->dev, "init media entity pad fail:%d\n", ret);
|
|
+ goto err_release_vb2_queue;
|
|
+ }
|
|
+
|
|
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE |
|
|
+ V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
|
|
+ vdev->entity.ops = &isp4vid_vdev_ent_ops;
|
|
+ vdev->release = video_device_release_empty;
|
|
+ vdev->fops = &isp4vid_vdev_fops;
|
|
+ vdev->ioctl_ops = &isp4vid_vdev_ioctl_ops;
|
|
+ vdev->lock = NULL;
|
|
+ vdev->queue = q;
|
|
+ vdev->v4l2_dev = v4l2_dev;
|
|
+ vdev->vfl_dir = VFL_DIR_RX;
|
|
+ strscpy(vdev->name, vdev_name, sizeof(vdev->name));
|
|
+ video_set_drvdata(vdev, isp_vdev);
|
|
+
|
|
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
|
|
+ if (ret) {
|
|
+ dev_err(v4l2_dev->dev, "register video device fail:%d\n", ret);
|
|
+ goto err_entity_cleanup;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_entity_cleanup:
|
|
+ media_entity_cleanup(&isp_vdev->vdev.entity);
|
|
+err_release_vb2_queue:
|
|
+ vb2_queue_release(q);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev)
|
|
+{
|
|
+ vb2_video_unregister_device(&isp_vdev->vdev);
|
|
+}
|
|
diff --git a/drivers/media/platform/amd/isp4/isp4_video.h b/drivers/media/platform/amd/isp4/isp4_video.h
|
|
new file mode 100644
|
|
index 0000000000000..c66451e261664
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/amd/isp4/isp4_video.h
|
|
@@ -0,0 +1,57 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
+ */
|
|
+
|
|
+#ifndef _ISP4_VIDEO_H_
|
|
+#define _ISP4_VIDEO_H_
|
|
+
|
|
+#include <media/v4l2-dev.h>
|
|
+#include <media/videobuf2-memops.h>
|
|
+
|
|
+#include "isp4_interface.h"
|
|
+
|
|
+struct isp4vid_capture_buffer {
|
|
+ /*
|
|
+ * struct vb2_v4l2_buffer must be the first element
|
|
+ * the videobuf2 framework will allocate this struct based on
|
|
+ * buf_struct_size and use the first sizeof(struct vb2_buffer) bytes of
|
|
+ * memory as a vb2_buffer
|
|
+ */
|
|
+ struct vb2_v4l2_buffer vb2;
|
|
+ struct isp4if_img_buf_info img_buf;
|
|
+ struct list_head list;
|
|
+ struct dma_buf *dbuf;
|
|
+ void *bo;
|
|
+ u64 gpu_addr;
|
|
+};
|
|
+
|
|
+struct isp4vid_dev {
|
|
+ struct video_device vdev;
|
|
+ struct media_pad vdev_pad;
|
|
+ struct v4l2_pix_format format;
|
|
+
|
|
+ /* mutex that protects vbq */
|
|
+ struct mutex vbq_lock;
|
|
+ struct vb2_queue vbq;
|
|
+
|
|
+ /* mutex that protects buf_list */
|
|
+ struct mutex buf_list_lock;
|
|
+ struct list_head buf_list;
|
|
+
|
|
+ u32 sequence;
|
|
+ bool stream_started;
|
|
+
|
|
+ struct device *dev;
|
|
+ struct v4l2_subdev *isp_sdev;
|
|
+ struct v4l2_fract timeperframe;
|
|
+};
|
|
+
|
|
+int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd);
|
|
+
|
|
+void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev);
|
|
+
|
|
+void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev,
|
|
+ const struct isp4if_img_buf_info *img_buf);
|
|
+
|
|
+#endif /* _ISP4_VIDEO_H_ */
|
|
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
|
|
index dd0025d3914e6..e57d10dc843c7 100644
|
|
--- a/drivers/pci/quirks.c
|
|
+++ b/drivers/pci/quirks.c
|
|
@@ -4491,6 +4491,30 @@ static void quirk_aspeed_pci_bridge_no_alias(struct pci_dev *pdev)
|
|
}
|
|
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASPEED, 0x1150, quirk_aspeed_pci_bridge_no_alias);
|
|
|
|
+/*
|
|
+ * PCI BAR 5 is not setup correctly for the on-board AHCI controller
|
|
+ * on Broadcom's Vulcan processor. Added a quirk to fix BAR 5 by
|
|
+ * using BAR 4's resources which are populated correctly and NOT
|
|
+ * actually used by the AHCI controller.
|
|
+ */
|
|
+static void quirk_fix_vulcan_ahci_bars(struct pci_dev *dev)
|
|
+{
|
|
+ struct resource *r = &dev->resource[4];
|
|
+
|
|
+ if (!(r->flags & IORESOURCE_MEM) || (r->start == 0))
|
|
+ return;
|
|
+
|
|
+ /* Set BAR5 resource to BAR4 */
|
|
+ dev->resource[5] = *r;
|
|
+
|
|
+ /* Update BAR5 in pci config space */
|
|
+ pci_write_config_dword(dev, PCI_BASE_ADDRESS_5, r->start);
|
|
+
|
|
+ /* Clear BAR4's resource */
|
|
+ memset(r, 0, sizeof(*r));
|
|
+}
|
|
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9027, quirk_fix_vulcan_ahci_bars);
|
|
+
|
|
/*
|
|
* Intersil/Techwell TW686[4589]-based video capture cards have an empty (zero)
|
|
* class code. Fix it.
|
|
diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c
|
|
index 115bb37577a18..4b9b86742d6a2 100644
|
|
--- a/drivers/platform/x86/intel/int3472/discrete.c
|
|
+++ b/drivers/platform/x86/intel/int3472/discrete.c
|
|
@@ -15,6 +15,7 @@
|
|
#include <linux/platform_data/x86/int3472.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/string_choices.h>
|
|
+#include <linux/time64.h>
|
|
#include <linux/uuid.h>
|
|
|
|
/*
|
|
@@ -157,12 +158,23 @@ static const struct int3472_gpio_map int3472_gpio_map[] = {
|
|
.type_to = INT3472_GPIO_TYPE_RESET,
|
|
.con_id = "enable",
|
|
},
|
|
- { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */
|
|
- .hid = "OVTI08F4",
|
|
- .type_from = INT3472_GPIO_TYPE_HANDSHAKE,
|
|
- .type_to = INT3472_GPIO_TYPE_HANDSHAKE,
|
|
- .con_id = "dvdd",
|
|
- .enable_time_us = 45 * USEC_PER_MSEC,
|
|
+ { /* imx471 expects "vana" as con_id for power enable */
|
|
+ .hid = "SONY471A",
|
|
+ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE,
|
|
+ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE,
|
|
+ .con_id = "vana",
|
|
+ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME,
|
|
+ },
|
|
+ {
|
|
+ /*
|
|
+ * imx471 (on Lenovo ThinkPads X1 G14) expects "vana" as con_id
|
|
+ * for power enable
|
|
+ */
|
|
+ .hid = "TBE20A0",
|
|
+ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE,
|
|
+ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE,
|
|
+ .con_id = "vana",
|
|
+ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME,
|
|
},
|
|
};
|
|
|
|
@@ -234,8 +246,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3
|
|
case INT3472_GPIO_TYPE_HANDSHAKE:
|
|
*con_id = "dvdd";
|
|
*gpio_flags = GPIO_ACTIVE_HIGH;
|
|
- /* Setups using a handshake pin need 25 ms enable delay */
|
|
- *enable_time_us = 25 * USEC_PER_MSEC;
|
|
+ /* Powering up the sensor through the handshake pin takes up to 200 ms */
|
|
+ *enable_time_us = 200 * USEC_PER_MSEC;
|
|
break;
|
|
default:
|
|
*con_id = "unknown";
|
|
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
|
|
index 24960ba9caa91..32597cdb72ef9 100644
|
|
--- a/drivers/usb/core/hub.c
|
|
+++ b/drivers/usb/core/hub.c
|
|
@@ -5894,6 +5894,13 @@ static void hub_event(struct work_struct *work)
|
|
(u16) hub->change_bits[0],
|
|
(u16) hub->event_bits[0]);
|
|
|
|
+ /* Don't disconnect USB-SATA on TrimSlice */
|
|
+ if (strcmp(dev_name(hdev->bus->controller), "tegra-ehci.0") == 0) {
|
|
+ if ((hdev->state == 7) && (hub->change_bits[0] == 0) &&
|
|
+ (hub->event_bits[0] == 0x2))
|
|
+ hub->event_bits[0] = 0;
|
|
+ }
|
|
+
|
|
/* Lock the device, then check to see if we were
|
|
* disconnected while waiting for the lock to succeed. */
|
|
usb_lock_device(hdev);
|
|
diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
|
|
index e4295a5b55b34..99275c7473f91 100644
|
|
--- a/fs/smb/client/dir.c
|
|
+++ b/fs/smb/client/dir.c
|
|
@@ -1126,6 +1126,8 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir,
|
|
} while (unlikely(rc == -EEXIST) && ++retries < max_retries);
|
|
|
|
if (rc) {
|
|
+ if (rc == -ENOENT)
|
|
+ rc = -EOPNOTSUPP;
|
|
cifs_del_pending_open(&open);
|
|
goto out;
|
|
}
|
|
diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c
|
|
index 541e33f331672..7fb28c8afef87 100644
|
|
--- a/fs/xfs/libxfs/xfs_exchmaps.c
|
|
+++ b/fs/xfs/libxfs/xfs_exchmaps.c
|
|
@@ -959,6 +959,16 @@ xmi_can_exchange_reflink_flags(
|
|
{
|
|
struct xfs_mount *mp = req->ip1->i_mount;
|
|
|
|
+ /*
|
|
+ * The INO1_WRITTEN optimization can skip exchanging hole and
|
|
+ * unwritten mappings, which means we cannot guarantee that all
|
|
+ * shared extents actually moved to the other file. Clearing the
|
|
+ * reflink flag of an inode that still holds shared extents breaks
|
|
+ * the CoW write path, so refuse to exchange the flags in that case.
|
|
+ */
|
|
+ if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN)
|
|
+ return false;
|
|
+
|
|
if (hweight32(reflink_state) != 1)
|
|
return false;
|
|
if (req->startoff1 != 0 || req->startoff2 != 0)
|
|
diff --git a/include/linux/crypto.h b/include/linux/crypto.h
|
|
index a2137e19be7d8..79ccb6fabc108 100644
|
|
--- a/include/linux/crypto.h
|
|
+++ b/include/linux/crypto.h
|
|
@@ -152,6 +152,8 @@
|
|
#define CRYPTO_TFM_REQ_MAY_BACKLOG 0x00000400
|
|
#define CRYPTO_TFM_REQ_ON_STACK 0x00000800
|
|
|
|
+#define CRYPTO_TFM_FIPS_COMPLIANCE 0x80000000
|
|
+
|
|
/*
|
|
* Miscellaneous stuff.
|
|
*/
|
|
diff --git a/include/linux/efi.h b/include/linux/efi.h
|
|
index ccbc35479684a..8eb9a1276ba74 100644
|
|
--- a/include/linux/efi.h
|
|
+++ b/include/linux/efi.h
|
|
@@ -45,6 +45,8 @@ struct screen_info;
|
|
#define EFI_ABORTED (21 | (1UL << (BITS_PER_LONG-1)))
|
|
#define EFI_SECURITY_VIOLATION (26 | (1UL << (BITS_PER_LONG-1)))
|
|
|
|
+#define EFI_IS_ERROR(x) ((x) & (1UL << (BITS_PER_LONG-1)))
|
|
+
|
|
typedef unsigned long efi_status_t;
|
|
typedef u8 efi_bool_t;
|
|
typedef u16 efi_char16_t; /* UNICODE character */
|
|
@@ -847,6 +849,14 @@ extern int __init parse_efi_signature_list(
|
|
#define EFI_MEM_ATTR 9 /* Did firmware publish an EFI_MEMORY_ATTRIBUTES table? */
|
|
#define EFI_MEM_NO_SOFT_RESERVE 10 /* Is the kernel configured to ignore soft reservations? */
|
|
#define EFI_PRESERVE_BS_REGIONS 11 /* Are EFI boot-services memory segments available? */
|
|
+#define EFI_SECURE_BOOT 12 /* Are we in Secure Boot mode? */
|
|
+
|
|
+enum efi_secureboot_mode {
|
|
+ efi_secureboot_mode_unset,
|
|
+ efi_secureboot_mode_unknown,
|
|
+ efi_secureboot_mode_disabled,
|
|
+ efi_secureboot_mode_enabled,
|
|
+};
|
|
|
|
#ifdef CONFIG_EFI
|
|
/*
|
|
@@ -858,6 +868,8 @@ static inline bool efi_enabled(int feature)
|
|
}
|
|
extern void efi_reboot(enum reboot_mode reboot_mode, const char *__unused);
|
|
|
|
+extern void __init efi_set_secure_boot(enum efi_secureboot_mode mode);
|
|
+
|
|
bool __pure __efi_soft_reserve_enabled(void);
|
|
|
|
static inline bool __pure efi_soft_reserve_enabled(void)
|
|
@@ -879,6 +891,8 @@ static inline bool efi_enabled(int feature)
|
|
static inline void
|
|
efi_reboot(enum reboot_mode reboot_mode, const char *__unused) {}
|
|
|
|
+static inline void efi_set_secure_boot(enum efi_secureboot_mode mode) {}
|
|
+
|
|
static inline bool efi_soft_reserve_enabled(void)
|
|
{
|
|
return false;
|
|
@@ -893,6 +907,7 @@ static inline void efi_find_mirror(void) {}
|
|
#endif
|
|
|
|
extern int efi_status_to_err(efi_status_t status);
|
|
+extern const char *efi_status_to_str(efi_status_t status);
|
|
|
|
/*
|
|
* Variable Attributes
|
|
@@ -1110,13 +1125,6 @@ extern unsigned long efi_call_virt_save_flags(void);
|
|
|
|
void efi_runtime_assert_lock_held(void);
|
|
|
|
-enum efi_secureboot_mode {
|
|
- efi_secureboot_mode_unset,
|
|
- efi_secureboot_mode_unknown,
|
|
- efi_secureboot_mode_disabled,
|
|
- efi_secureboot_mode_enabled,
|
|
-};
|
|
-
|
|
static inline
|
|
enum efi_secureboot_mode efi_get_secureboot_mode(efi_get_variable_t *get_var)
|
|
{
|
|
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
|
|
index 2b8dfb35caed3..47274c0e79002 100644
|
|
--- a/include/linux/lsm_hook_defs.h
|
|
+++ b/include/linux/lsm_hook_defs.h
|
|
@@ -456,6 +456,7 @@ LSM_HOOK(int, 0, bpf_token_capable, const struct bpf_token *token, int cap)
|
|
|
|
LSM_HOOK(int, 0, locked_down, enum lockdown_reason what)
|
|
|
|
+
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
LSM_HOOK(int, 0, perf_event_open, int type)
|
|
LSM_HOOK(int, 0, perf_event_alloc, struct perf_event *event)
|
|
diff --git a/include/linux/random.h b/include/linux/random.h
|
|
index 8a8064dc3970b..3238fab8f7498 100644
|
|
--- a/include/linux/random.h
|
|
+++ b/include/linux/random.h
|
|
@@ -9,6 +9,13 @@
|
|
|
|
#include <uapi/linux/random.h>
|
|
|
|
+struct iov_iter;
|
|
+
|
|
+struct random_extrng {
|
|
+ ssize_t (*extrng_read_iter)(struct iov_iter *iter, bool reseed);
|
|
+ struct module *owner;
|
|
+};
|
|
+
|
|
struct notifier_block;
|
|
|
|
void add_device_randomness(const void *buf, size_t len);
|
|
@@ -135,6 +142,9 @@ int random_prepare_cpu(unsigned int cpu);
|
|
int random_online_cpu(unsigned int cpu);
|
|
#endif
|
|
|
|
+void random_register_extrng(const struct random_extrng *rng);
|
|
+void random_unregister_extrng(void);
|
|
+
|
|
#ifndef MODULE
|
|
extern const struct file_operations random_fops, urandom_fops;
|
|
#endif
|
|
diff --git a/include/linux/security.h b/include/linux/security.h
|
|
index 41d7367cf4036..ad6f69c6fcff9 100644
|
|
--- a/include/linux/security.h
|
|
+++ b/include/linux/security.h
|
|
@@ -2438,4 +2438,13 @@ static inline void security_initramfs_populated(void)
|
|
}
|
|
#endif /* CONFIG_SECURITY */
|
|
|
|
+#ifdef CONFIG_SECURITY_LOCKDOWN_LSM
|
|
+extern int security_lock_kernel_down(const char *where, enum lockdown_reason level);
|
|
+#else
|
|
+static inline int security_lock_kernel_down(const char *where, enum lockdown_reason level)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif /* CONFIG_SECURITY_LOCKDOWN_LSM */
|
|
+
|
|
#endif /* ! __LINUX_SECURITY_H */
|
|
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
|
|
index 590ba29c85aba..02153d857531d 100644
|
|
--- a/kernel/module/signing.c
|
|
+++ b/kernel/module/signing.c
|
|
@@ -61,10 +61,17 @@ int mod_verify_sig(const void *mod, struct load_info *info)
|
|
modlen -= sig_len + sizeof(ms);
|
|
info->len = modlen;
|
|
|
|
- return verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
|
|
+ ret = verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
|
|
VERIFY_USE_SECONDARY_KEYRING,
|
|
VERIFYING_MODULE_SIGNATURE,
|
|
NULL, NULL);
|
|
+ if (ret == -ENOKEY && IS_ENABLED(CONFIG_INTEGRITY_PLATFORM_KEYRING)) {
|
|
+ ret = verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
|
|
+ VERIFY_USE_PLATFORM_KEYRING,
|
|
+ VERIFYING_MODULE_SIGNATURE,
|
|
+ NULL, NULL);
|
|
+ }
|
|
+ return ret;
|
|
}
|
|
|
|
int module_sig_check(struct load_info *info, int flags)
|
|
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
|
|
index 0718e39cedda2..10e94a1b8b6e3 100644
|
|
--- a/scripts/Makefile.lib
|
|
+++ b/scripts/Makefile.lib
|
|
@@ -201,7 +201,10 @@ objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call
|
|
objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess
|
|
objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable
|
|
objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES)
|
|
+# RHEL-only: don't enforce OBJTOOL_WERROR for out of tree modules
|
|
+ifeq ($(KBUILD_EXTMOD),)
|
|
objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
|
|
+endif
|
|
|
|
objtool-args = $(objtool-args-y) \
|
|
$(if $(delay-objtool), --link) \
|
|
diff --git a/scripts/tags.sh b/scripts/tags.sh
|
|
index 243373683f98a..2affd5e58ee82 100755
|
|
--- a/scripts/tags.sh
|
|
+++ b/scripts/tags.sh
|
|
@@ -16,6 +16,8 @@ fi
|
|
ignore="$(echo "$RCS_FIND_IGNORE" | sed 's|\\||g' )"
|
|
# tags and cscope files should also ignore MODVERSION *.mod.c files
|
|
ignore="$ignore ( -name *.mod.c ) -prune -o"
|
|
+# RHEL tags and cscope should also ignore redhat/rpm
|
|
+ignore="$ignore ( -path redhat/rpm ) -prune -o"
|
|
|
|
# ignore arbitrary directories
|
|
if [ -n "${IGNORE_DIRS}" ]; then
|
|
diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c
|
|
index c0d6948446c30..bed56532e206c 100644
|
|
--- a/security/integrity/platform_certs/load_uefi.c
|
|
+++ b/security/integrity/platform_certs/load_uefi.c
|
|
@@ -74,7 +74,8 @@ static __init void *get_cert_list(efi_char16_t *name, efi_guid_t *guid,
|
|
return NULL;
|
|
|
|
if (*status != EFI_BUFFER_TOO_SMALL) {
|
|
- pr_err("Couldn't get size: 0x%lx\n", *status);
|
|
+ pr_err("Couldn't get size: %s (0x%lx)\n",
|
|
+ efi_status_to_str(*status), *status);
|
|
return NULL;
|
|
}
|
|
|
|
@@ -85,7 +86,8 @@ static __init void *get_cert_list(efi_char16_t *name, efi_guid_t *guid,
|
|
*status = efi.get_variable(name, guid, NULL, &lsize, db);
|
|
if (*status != EFI_SUCCESS) {
|
|
kfree(db);
|
|
- pr_err("Error reading db var: 0x%lx\n", *status);
|
|
+ pr_err("Error reading db var: %s (0x%lx)\n",
|
|
+ efi_status_to_str(*status), *status);
|
|
return NULL;
|
|
}
|
|
|
|
diff --git a/security/lockdown/Kconfig b/security/lockdown/Kconfig
|
|
index e84ddf4840101..d0501353a4b95 100644
|
|
--- a/security/lockdown/Kconfig
|
|
+++ b/security/lockdown/Kconfig
|
|
@@ -16,6 +16,19 @@ config SECURITY_LOCKDOWN_LSM_EARLY
|
|
subsystem is fully initialised. If enabled, lockdown will
|
|
unconditionally be called before any other LSMs.
|
|
|
|
+config LOCK_DOWN_IN_EFI_SECURE_BOOT
|
|
+ bool "Lock down the kernel in EFI Secure Boot mode"
|
|
+ default n
|
|
+ depends on EFI && SECURITY_LOCKDOWN_LSM_EARLY
|
|
+ help
|
|
+ UEFI Secure Boot provides a mechanism for ensuring that the firmware
|
|
+ will only load signed bootloaders and kernels. Secure boot mode may
|
|
+ be determined from EFI variables provided by the system firmware if
|
|
+ not indicated by the boot parameters.
|
|
+
|
|
+ Enabling this option results in kernel lockdown being triggered if
|
|
+ EFI Secure Boot is set.
|
|
+
|
|
choice
|
|
prompt "Kernel default lockdown mode"
|
|
default LOCK_DOWN_KERNEL_FORCE_NONE
|
|
diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c
|
|
index 8d46886d2cca0..14a9cdff942b9 100644
|
|
--- a/security/lockdown/lockdown.c
|
|
+++ b/security/lockdown/lockdown.c
|
|
@@ -72,6 +72,17 @@ static int lockdown_is_locked_down(enum lockdown_reason what)
|
|
return 0;
|
|
}
|
|
|
|
+/**
|
|
+ * security_lock_kernel_down() - Put the kernel into lock-down mode.
|
|
+ *
|
|
+ * @where: Where the lock-down is originating from (e.g. command line option)
|
|
+ * @level: The lock-down level (can only increase)
|
|
+ */
|
|
+int security_lock_kernel_down(const char *where, enum lockdown_reason level)
|
|
+{
|
|
+ return lock_kernel_down(where, level);
|
|
+}
|
|
+
|
|
static struct security_hook_list lockdown_hooks[] __ro_after_init = {
|
|
LSM_HOOK_INIT(locked_down, lockdown_is_locked_down),
|
|
};
|
|
diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c
|
|
index 79b8b7e70a334..23d2f63d68ef3 100644
|
|
--- a/sound/soc/codecs/rt722-sdca.c
|
|
+++ b/sound/soc/codecs/rt722-sdca.c
|
|
@@ -517,6 +517,61 @@ static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol,
|
|
return changed;
|
|
}
|
|
|
|
+static int rt722_sdca_set_fu06_playback_ctl(struct rt722_sdca_priv *rt722)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int ch_l, ch_r;
|
|
+
|
|
+ ch_l = (rt722->fu06_dapm_mute || rt722->fu06_mixer_l_mute) ? 0x01 : 0x00;
|
|
+ ch_r = (rt722->fu06_dapm_mute || rt722->fu06_mixer_r_mute) ? 0x01 : 0x00;
|
|
+
|
|
+ err = regmap_write(rt722->regmap,
|
|
+ SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
+ RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = regmap_write(rt722->regmap,
|
|
+ SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
+ RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rt722_sdca_fu06_playback_get(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ ucontrol->value.integer.value[0] = !rt722->fu06_mixer_l_mute;
|
|
+ ucontrol->value.integer.value[1] = !rt722->fu06_mixer_r_mute;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rt722_sdca_fu06_playback_put(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
|
|
+ int err, changed = 0;
|
|
+
|
|
+ if (rt722->fu06_mixer_l_mute != !ucontrol->value.integer.value[0] ||
|
|
+ rt722->fu06_mixer_r_mute != !ucontrol->value.integer.value[1])
|
|
+ changed = 1;
|
|
+
|
|
+ rt722->fu06_mixer_l_mute = !ucontrol->value.integer.value[0];
|
|
+ rt722->fu06_mixer_r_mute = !ucontrol->value.integer.value[1];
|
|
+
|
|
+ err = rt722_sdca_set_fu06_playback_ctl(rt722);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ return changed;
|
|
+}
|
|
+
|
|
static int rt722_sdca_set_fu0f_capture_ctl(struct rt722_sdca_priv *rt722)
|
|
{
|
|
int err;
|
|
@@ -718,6 +773,8 @@ static const struct snd_kcontrol_new rt722_sdca_controls[] = {
|
|
RT722_SDCA_CTL_FU_CH_GAIN, CH_R), 8, 3, 0,
|
|
rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, boost_vol_tlv),
|
|
/* AMP playback settings */
|
|
+ SOC_DOUBLE_EXT("FU06 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0,
|
|
+ rt722_sdca_fu06_playback_get, rt722_sdca_fu06_playback_put),
|
|
SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume",
|
|
SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
RT722_SDCA_CTL_FU_VOLUME, CH_L),
|
|
@@ -807,27 +864,17 @@ static int rt722_sdca_fu21_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_soc_component *component =
|
|
snd_soc_dapm_to_component(w->dapm);
|
|
struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
|
|
- unsigned char unmute = 0x0, mute = 0x1;
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
- regmap_write(rt722->regmap,
|
|
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
- RT722_SDCA_CTL_FU_MUTE, CH_L), unmute);
|
|
- regmap_write(rt722->regmap,
|
|
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
- RT722_SDCA_CTL_FU_MUTE, CH_R), unmute);
|
|
+ rt722->fu06_dapm_mute = false;
|
|
break;
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
- regmap_write(rt722->regmap,
|
|
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
- RT722_SDCA_CTL_FU_MUTE, CH_L), mute);
|
|
- regmap_write(rt722->regmap,
|
|
- SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06,
|
|
- RT722_SDCA_CTL_FU_MUTE, CH_R), mute);
|
|
+ rt722->fu06_dapm_mute = true;
|
|
break;
|
|
}
|
|
- return 0;
|
|
+
|
|
+ return rt722_sdca_set_fu06_playback_ctl(rt722);
|
|
}
|
|
|
|
static int rt722_sdca_fu113_event(struct snd_soc_dapm_widget *w,
|
|
@@ -1324,6 +1371,8 @@ int rt722_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave
|
|
rt722->first_hw_init = false;
|
|
rt722->fu1e_dapm_mute = true;
|
|
rt722->fu0f_dapm_mute = true;
|
|
+ rt722->fu06_dapm_mute = true;
|
|
+ rt722->fu06_mixer_l_mute = rt722->fu06_mixer_r_mute = false;
|
|
rt722->fu0f_mixer_l_mute = rt722->fu0f_mixer_r_mute = true;
|
|
rt722->fu1e_mixer_mute[0] = rt722->fu1e_mixer_mute[1] =
|
|
rt722->fu1e_mixer_mute[2] = rt722->fu1e_mixer_mute[3] = true;
|
|
diff --git a/sound/soc/codecs/rt722-sdca.h b/sound/soc/codecs/rt722-sdca.h
|
|
index 823abee9ab76c..fc50beff94245 100644
|
|
--- a/sound/soc/codecs/rt722-sdca.h
|
|
+++ b/sound/soc/codecs/rt722-sdca.h
|
|
@@ -36,6 +36,10 @@ struct rt722_sdca_priv {
|
|
bool fu0f_dapm_mute;
|
|
bool fu0f_mixer_l_mute;
|
|
bool fu0f_mixer_r_mute;
|
|
+ /* For AMP */
|
|
+ bool fu06_dapm_mute;
|
|
+ bool fu06_mixer_l_mute;
|
|
+ bool fu06_mixer_r_mute;
|
|
/* For DMIC */
|
|
bool fu1e_dapm_mute;
|
|
bool fu1e_mixer_mute[4];
|
|
diff --git a/tools/testing/selftests/bpf/DENYLIST.rhel b/tools/testing/selftests/bpf/DENYLIST.rhel
|
|
new file mode 100644
|
|
index 0000000000000..9424a83c6f489
|
|
--- /dev/null
|
|
+++ b/tools/testing/selftests/bpf/DENYLIST.rhel
|
|
@@ -0,0 +1,76 @@
|
|
+# Tests that need CONFIG_FUNCTION_ERROR_INJECTION
|
|
+# Typical observed errors are:
|
|
+# <...some function...> is not sleepable
|
|
+# <...some function...> is not modifiable
|
|
+cgrp_local_storage/yes_rcu_lock
|
|
+cgrp_local_storage/no_rcu_lock
|
|
+cgrp_local_storage/cgrp1_yes_rcu_lock
|
|
+cgrp_local_storage/cgrp1_no_rcu_lock
|
|
+compute_live_registers/addr_space_cast
|
|
+dynptr/test_read_write
|
|
+dynptr/test_dynptr_data
|
|
+dynptr/test_dynptr_copy
|
|
+dynptr/test_dynptr_copy_xdp
|
|
+dynptr/test_dynptr_memset_zero
|
|
+dynptr/test_dynptr_memset_notzero
|
|
+dynptr/test_dynptr_memset_zero_offset
|
|
+dynptr/test_dynptr_memset_zero_adjusted
|
|
+dynptr/test_dynptr_memset_overflow
|
|
+dynptr/test_dynptr_memset_overflow_offset
|
|
+dynptr/test_dynptr_memset_readonly
|
|
+dynptr/test_dynptr_memset_xdp_chunks
|
|
+dynptr/test_dynptr_skb_meta_data
|
|
+dynptr/test_dynptr_skb_meta_flags
|
|
+dynptr/test_ringbuf
|
|
+dynptr/test_skb_readonly
|
|
+dynptr/test_dynptr_skb_data
|
|
+dynptr/test_adjust
|
|
+dynptr/test_adjust_err
|
|
+dynptr/test_zero_size_dynptr
|
|
+dynptr/test_dynptr_is_null
|
|
+dynptr/test_dynptr_is_rdonly
|
|
+dynptr/test_dynptr_clone
|
|
+dynptr/test_dynptr_skb_no_buff
|
|
+dynptr/test_dynptr_skb_strcmp
|
|
+dynptr/test_dynptr_skb_tp_btf
|
|
+dynptr/test_probe_read_user_dynptr
|
|
+dynptr/test_probe_read_kernel_dynptr
|
|
+dynptr/test_probe_read_user_str_dynptr
|
|
+dynptr/test_probe_read_kernel_str_dynptr
|
|
+dynptr/test_copy_from_user_dynptr
|
|
+dynptr/test_copy_from_user_str_dynptr
|
|
+dynptr/test_copy_from_user_task_dynptr
|
|
+dynptr/test_copy_from_user_task_str_dynptr
|
|
+irq/irq_sleepable_helper
|
|
+irq/irq_sleepable_kfunc
|
|
+iters/iter_css_task_for_each_sleep
|
|
+iters/iter_ret_rcu_test_protected
|
|
+iters/iter_ret_rcu_test_type
|
|
+iters/iter_ret_rcu_test_protected_nostruct
|
|
+iters/iter_ret_rcu_test_type_nostruct
|
|
+iters/task
|
|
+iters/css
|
|
+iters/iter_tasks_without_lock
|
|
+iters/iter_css_without_lock
|
|
+iters/iter_tasks_lock_and_unlock
|
|
+iters/iter_css_lock_and_unlock
|
|
+map_excl/map_excl_allowed
|
|
+map_in_map/sleepable_acc_map_in_array
|
|
+map_in_map/sleepable_acc_map_in_htab
|
|
+mem_rdonly_untrusted/helper_param_not_ok
|
|
+module_attach
|
|
+preempt_lock/preempt_sleepable_helper
|
|
+preempt_lock/preempt_sleepable_kfunc
|
|
+rcu_read_lock/success
|
|
+rcu_read_lock/rcuptr_acquire
|
|
+read_vsyscall
|
|
+refcounted_kptr/rbtree_sleepable_rcu
|
|
+refcounted_kptr/rbtree_sleepable_rcu_no_explicit_rcu_lock
|
|
+refcounted_kptr_fail/rbtree_fail_sleepable_lock_across_rcu
|
|
+test_lsm/lsm_basic
|
|
+verifier_global_subprogs/arg_tag_ctx_fmod_ret
|
|
+
|
|
+kprobe_multi_test/attach_override # needs CONFIG_BPF_KPROBE_OVERRIDE
|
|
+libbpf_probe_prog_types/BPF_PROG_TYPE_LIRC_MODE2 # needs CONFIG_BPF_LIRC_MODE2
|
|
+test_bpffs # needs CONFIG_BPF_PRELOAD
|
|
+tunnel # needs CONFIG_NET_FOU
|
|
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
|
|
index 6ef6872adbc3e..bd76422a1f135 100644
|
|
--- a/tools/testing/selftests/bpf/Makefile
|
|
+++ b/tools/testing/selftests/bpf/Makefile
|
|
@@ -504,7 +504,7 @@ LSKELS := fexit_sleep.c trace_printk.c trace_vprintk.c map_ptr_kern.c \
|
|
LSKELS_SIGNED := fentry_test.c fexit_test.c atomics.c
|
|
|
|
# Generate both light skeleton and libbpf skeleton for these
|
|
-LSKELS_EXTRA := test_ksyms_module.c test_ksyms_weak.c kfunc_call_test.c \
|
|
+LSKELS_EXTRA := test_ksyms_module.c kfunc_call_test.c \
|
|
kfunc_call_test_subprog.c
|
|
SKEL_BLACKLIST += $$(LSKELS) $$(LSKELS_SIGNED)
|
|
|
|
diff --git a/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c b/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c
|
|
index 1d7a2f1e07317..b22f3a9cb8b80 100644
|
|
--- a/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c
|
|
+++ b/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c
|
|
@@ -7,7 +7,6 @@
|
|
#include "test_ksyms_btf.skel.h"
|
|
#include "test_ksyms_btf_null_check.skel.h"
|
|
#include "test_ksyms_weak.skel.h"
|
|
-#include "test_ksyms_weak.lskel.h"
|
|
#include "test_ksyms_btf_write_check.skel.h"
|
|
|
|
static int duration;
|
|
@@ -111,33 +110,6 @@ static void test_weak_syms(void)
|
|
test_ksyms_weak__destroy(skel);
|
|
}
|
|
|
|
-static void test_weak_syms_lskel(void)
|
|
-{
|
|
- struct test_ksyms_weak_lskel *skel;
|
|
- struct test_ksyms_weak_lskel__data *data;
|
|
- int err;
|
|
-
|
|
- skel = test_ksyms_weak_lskel__open_and_load();
|
|
- if (!ASSERT_OK_PTR(skel, "test_ksyms_weak_lskel__open_and_load"))
|
|
- return;
|
|
-
|
|
- err = test_ksyms_weak_lskel__attach(skel);
|
|
- if (!ASSERT_OK(err, "test_ksyms_weak_lskel__attach"))
|
|
- goto cleanup;
|
|
-
|
|
- /* trigger tracepoint */
|
|
- usleep(1);
|
|
-
|
|
- data = skel->data;
|
|
- ASSERT_EQ(data->out__existing_typed, 0, "existing typed ksym");
|
|
- ASSERT_NEQ(data->out__existing_typeless, -1, "existing typeless ksym");
|
|
- ASSERT_EQ(data->out__non_existent_typeless, 0, "nonexistent typeless ksym");
|
|
- ASSERT_EQ(data->out__non_existent_typed, 0, "nonexistent typed ksym");
|
|
-
|
|
-cleanup:
|
|
- test_ksyms_weak_lskel__destroy(skel);
|
|
-}
|
|
-
|
|
static void test_write_check(bool test_handler1)
|
|
{
|
|
struct test_ksyms_btf_write_check *skel;
|
|
@@ -180,9 +152,6 @@ void test_ksyms_btf(void)
|
|
if (test__start_subtest("weak_ksyms"))
|
|
test_weak_syms();
|
|
|
|
- if (test__start_subtest("weak_ksyms_lskel"))
|
|
- test_weak_syms_lskel();
|
|
-
|
|
if (test__start_subtest("write_check1"))
|
|
test_write_check(true);
|
|
|