* Thu Jul 10 2025 Augusto Caringi <acaringi@redhat.com> [6.15.6-0] - Turn on MITIGATION_TSA for RHEL configs (Augusto Caringi) - Turn on TSA Mitigation for Fedora (Justin M. Forbes) - Linux v6.15.6 Resolves: Signed-off-by: Augusto Caringi <acaringi@redhat.com>
4323 lines
128 KiB
Diff
4323 lines
128 KiB
Diff
MAINTAINERS | 17 +
|
|
Makefile | 40 +
|
|
arch/arm/Kconfig | 4 +-
|
|
arch/arm64/Kconfig | 2 +-
|
|
arch/arm64/boot/dts/rockchip/rk3328-rock64.dts | 1 +
|
|
arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi | 1 +
|
|
arch/arm64/boot/dts/rockchip/rk3566-quartz64-b.dts | 1 +
|
|
arch/arm64/boot/dts/rockchip/rk3568-nanopi-r5s.dts | 19 +-
|
|
arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts | 15 +-
|
|
arch/arm64/boot/dts/rockchip/rk3568.dtsi | 8 +-
|
|
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 6 -
|
|
arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts | 1 +
|
|
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 +-
|
|
crypto/sig.c | 3 +-
|
|
crypto/testmgr.c | 2 +-
|
|
drivers/acpi/apei/hest.c | 8 +
|
|
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/firmware/efi/Makefile | 1 +
|
|
drivers/firmware/efi/efi.c | 124 ++-
|
|
drivers/firmware/efi/secureboot.c | 38 +
|
|
drivers/hid/hid-rmi.c | 66 --
|
|
drivers/hwtracing/coresight/coresight-etm4x-core.c | 19 +
|
|
drivers/input/rmi4/rmi_driver.c | 124 ++-
|
|
drivers/iommu/iommu.c | 22 +
|
|
drivers/media/i2c/Kconfig | 20 +
|
|
drivers/media/i2c/Makefile | 2 +
|
|
drivers/media/i2c/ov02c10.c | 1013 ++++++++++++++++++++
|
|
drivers/media/i2c/ov02e10.c | 969 +++++++++++++++++++
|
|
drivers/net/wireless/ath/ath12k/core.c | 23 +-
|
|
drivers/net/wireless/ath/ath12k/core.h | 4 +
|
|
drivers/net/wireless/ath/ath12k/fw.c | 9 +-
|
|
drivers/net/wireless/ath/ath12k/fw.h | 3 +-
|
|
drivers/net/wireless/ath/ath12k/pci.c | 4 +-
|
|
drivers/net/wireless/ath/ath12k/qmi.c | 6 +-
|
|
drivers/pci/quirks.c | 24 +
|
|
drivers/platform/x86/intel/int3472/Makefile | 3 +-
|
|
.../platform/x86/intel/int3472/clk_and_regulator.c | 166 ++--
|
|
drivers/platform/x86/intel/int3472/common.h | 57 +-
|
|
drivers/platform/x86/intel/int3472/discrete.c | 41 +-
|
|
.../platform/x86/intel/int3472/discrete_quirks.c | 22 +
|
|
drivers/scsi/sd.c | 10 +
|
|
drivers/usb/core/hub.c | 7 +
|
|
include/linux/efi.h | 22 +-
|
|
include/linux/lsm_hook_defs.h | 1 +
|
|
include/linux/rmi.h | 1 +
|
|
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 +
|
|
59 files changed, 2745 insertions(+), 344 deletions(-)
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index dd844ac8d910..dab2b4892122 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -18014,6 +18014,23 @@ T: git git://linuxtv.org/media.git
|
|
F: Documentation/devicetree/bindings/media/i2c/ovti,ov02a10.yaml
|
|
F: drivers/media/i2c/ov02a10.c
|
|
|
|
+OMNIVISION OV02C10 SENSOR DRIVER
|
|
+M: Hans de Goede <hansg@kernel.org>
|
|
+R: Bryan O'Donoghue <bod@kernel.org>
|
|
+L: linux-media@vger.kernel.org
|
|
+S: Maintained
|
|
+T: git git://linuxtv.org/media.git
|
|
+F: drivers/media/i2c/ov02c10.c
|
|
+
|
|
+OMNIVISION OV02E10 SENSOR DRIVER
|
|
+M: Bryan O'Donoghue <bod@kernel.org>
|
|
+M: Hans de Goede <hansg@kernel.org>
|
|
+L: linux-media@vger.kernel.org
|
|
+S: Maintained
|
|
+T: git git://linuxtv.org/media.git
|
|
+F: Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
|
|
+F: drivers/media/i2c/ov02e10.c
|
|
+
|
|
OMNIVISION OV08D10 SENSOR DRIVER
|
|
M: Jimmy Su <jimmy.su@intel.com>
|
|
L: linux-media@vger.kernel.org
|
|
diff --git a/Makefile b/Makefile
|
|
index 959b55a05d1b..60d048560446 100644
|
|
--- a/Makefile
|
|
+++ b/Makefile
|
|
@@ -22,6 +22,18 @@ $(if $(filter __%, $(MAKECMDGOALS)), \
|
|
PHONY := __all
|
|
__all:
|
|
|
|
+# Set RHEL variables
|
|
+# Note that this ifdef'ery is required to handle when building with
|
|
+# the O= mechanism (relocate the object file results) due to upstream
|
|
+# commit 67d7c302 which broke our RHEL include file
|
|
+ifneq ($(realpath source),)
|
|
+include $(realpath source)/Makefile.rhelver
|
|
+else
|
|
+ifneq ($(realpath Makefile.rhelver),)
|
|
+include Makefile.rhelver
|
|
+endif
|
|
+endif
|
|
+
|
|
# We are using a recursive build, so we need to do a little thinking
|
|
# to get the ordering right.
|
|
#
|
|
@@ -355,6 +367,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).
|
|
@@ -1924,6 +1947,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 25ed6f1a7c7a..d19fc6d6c134 100644
|
|
--- a/arch/arm/Kconfig
|
|
+++ b/arch/arm/Kconfig
|
|
@@ -1232,9 +1232,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
|
|
- 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 6527d0d5656a..4db1bdab9ab1 100644
|
|
--- a/arch/arm64/Kconfig
|
|
+++ b/arch/arm64/Kconfig
|
|
@@ -1413,7 +1413,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/boot/dts/rockchip/rk3328-rock64.dts b/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts
|
|
index e550b6eeeff3..5367e5fa9232 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts
|
|
@@ -343,6 +343,7 @@ flash@0 {
|
|
|
|
/* maximum speed for Rockchip SPI */
|
|
spi-max-frequency = <50000000>;
|
|
+ vcc-supply = <&vcc_io>;
|
|
};
|
|
};
|
|
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi b/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi
|
|
index 51c6aa26d828..a7e4adf87e7a 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3399-rockpro64.dtsi
|
|
@@ -850,6 +850,7 @@ flash@0 {
|
|
compatible = "jedec,spi-nor";
|
|
reg = <0>;
|
|
spi-max-frequency = <10000000>;
|
|
+ vcc-supply = <&vcc_3v0>;
|
|
};
|
|
};
|
|
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-b.dts b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-b.dts
|
|
index 5707321a1144..f8cf03380636 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-b.dts
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-b.dts
|
|
@@ -648,6 +648,7 @@ flash@0 {
|
|
spi-max-frequency = <24000000>;
|
|
spi-rx-bus-width = <4>;
|
|
spi-tx-bus-width = <1>;
|
|
+ vcc-supply = <&vcc_1v8>;
|
|
};
|
|
};
|
|
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3568-nanopi-r5s.dts b/arch/arm64/boot/dts/rockchip/rk3568-nanopi-r5s.dts
|
|
index b6ad8328c7eb..3b31f0dd8f3b 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3568-nanopi-r5s.dts
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3568-nanopi-r5s.dts
|
|
@@ -20,33 +20,43 @@ aliases {
|
|
gpio-leds {
|
|
compatible = "gpio-leds";
|
|
pinctrl-names = "default";
|
|
- pinctrl-0 = <&lan1_led_pin>, <&lan2_led_pin>, <&power_led_pin>, <&wan_led_pin>;
|
|
+ pinctrl-0 = <&lan1_led_pin>, <&lan2_led_pin>, <&sys_led_pin>, <&wan_led_pin>;
|
|
|
|
led-lan1 {
|
|
color = <LED_COLOR_ID_GREEN>;
|
|
+ default-state = "off";
|
|
function = LED_FUNCTION_LAN;
|
|
function-enumerator = <1>;
|
|
gpios = <&gpio3 RK_PD6 GPIO_ACTIVE_HIGH>;
|
|
+ label = "LAN-1";
|
|
+ linux,default-trigger = "netdev";
|
|
};
|
|
|
|
led-lan2 {
|
|
color = <LED_COLOR_ID_GREEN>;
|
|
+ default-state = "off";
|
|
function = LED_FUNCTION_LAN;
|
|
function-enumerator = <2>;
|
|
gpios = <&gpio3 RK_PD7 GPIO_ACTIVE_HIGH>;
|
|
+ label = "LAN-2";
|
|
+ linux,default-trigger = "netdev";
|
|
};
|
|
|
|
- power_led: led-power {
|
|
+ power_led: led-sys {
|
|
color = <LED_COLOR_ID_RED>;
|
|
function = LED_FUNCTION_POWER;
|
|
- linux,default-trigger = "heartbeat";
|
|
gpios = <&gpio4 RK_PD2 GPIO_ACTIVE_HIGH>;
|
|
+ label = "SYS";
|
|
+ linux,default-trigger = "heartbeat";
|
|
};
|
|
|
|
led-wan {
|
|
color = <LED_COLOR_ID_GREEN>;
|
|
+ default-state = "off";
|
|
function = LED_FUNCTION_WAN;
|
|
gpios = <&gpio2 RK_PC1 GPIO_ACTIVE_HIGH>;
|
|
+ label = "WAN";
|
|
+ linux,default-trigger = "netdev";
|
|
};
|
|
};
|
|
};
|
|
@@ -58,6 +68,7 @@ &gmac0 {
|
|
clock_in_out = "output";
|
|
phy-handle = <&rgmii_phy0>;
|
|
phy-mode = "rgmii";
|
|
+ phy-supply = <&vcc_3v3>;
|
|
pinctrl-names = "default";
|
|
pinctrl-0 = <&gmac0_miim
|
|
&gmac0_tx_bus2
|
|
@@ -125,7 +136,7 @@ lan2_led_pin: lan2-led-pin {
|
|
rockchip,pins = <3 RK_PD7 RK_FUNC_GPIO &pcfg_pull_none>;
|
|
};
|
|
|
|
- power_led_pin: power-led-pin {
|
|
+ sys_led_pin: sys-led-pin {
|
|
rockchip,pins = <4 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>;
|
|
};
|
|
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts b/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts
|
|
index b80d628c426b..6ae4316761c4 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts
|
|
@@ -481,9 +481,14 @@ eeprom@56 {
|
|
};
|
|
|
|
&mdio0 {
|
|
- rgmii_phy0: ethernet-phy@0 {
|
|
+ rgmii_phy0: ethernet-phy@3 {
|
|
+ /* Motorcomm YT8521 phy */
|
|
compatible = "ethernet-phy-ieee802.3-c22";
|
|
- reg = <0x0>;
|
|
+ reg = <0x3>;
|
|
+ pinctrl-0 = <ð_phy0_reset_pin>;
|
|
+ pinctrl-names = "default";
|
|
+ reset-assert-us = <10000>;
|
|
+ reset-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_LOW>;
|
|
};
|
|
};
|
|
|
|
@@ -556,6 +561,12 @@ &pcie3x2 {
|
|
};
|
|
|
|
&pinctrl {
|
|
+ gmac0 {
|
|
+ eth_phy0_reset_pin: eth-phy0-reset-pin {
|
|
+ rockchip,pins = <0 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up>;
|
|
+ };
|
|
+ };
|
|
+
|
|
keys {
|
|
copy_button_pin: copy-button-pin {
|
|
rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_up>;
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3568.dtsi b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
|
|
index 695cccbdab0f..e719a3df126c 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
|
|
@@ -152,7 +152,7 @@ pcie3x1: pcie@fe270000 {
|
|
compatible = "rockchip,rk3568-pcie";
|
|
#address-cells = <3>;
|
|
#size-cells = <2>;
|
|
- bus-range = <0x0 0xf>;
|
|
+ bus-range = <0x10 0x1f>;
|
|
clocks = <&cru ACLK_PCIE30X1_MST>, <&cru ACLK_PCIE30X1_SLV>,
|
|
<&cru ACLK_PCIE30X1_DBI>, <&cru PCLK_PCIE30X1>,
|
|
<&cru CLK_PCIE30X1_AUX_NDFT>;
|
|
@@ -175,7 +175,7 @@ pcie3x1: pcie@fe270000 {
|
|
num-ib-windows = <6>;
|
|
num-ob-windows = <2>;
|
|
max-link-speed = <3>;
|
|
- msi-map = <0x0 &gic 0x1000 0x1000>;
|
|
+ msi-map = <0x1000 &its 0x1000 0x1000>;
|
|
num-lanes = <1>;
|
|
phys = <&pcie30phy>;
|
|
phy-names = "pcie-phy";
|
|
@@ -205,7 +205,7 @@ pcie3x2: pcie@fe280000 {
|
|
compatible = "rockchip,rk3568-pcie";
|
|
#address-cells = <3>;
|
|
#size-cells = <2>;
|
|
- bus-range = <0x0 0xf>;
|
|
+ bus-range = <0x20 0x2f>;
|
|
clocks = <&cru ACLK_PCIE30X2_MST>, <&cru ACLK_PCIE30X2_SLV>,
|
|
<&cru ACLK_PCIE30X2_DBI>, <&cru PCLK_PCIE30X2>,
|
|
<&cru CLK_PCIE30X2_AUX_NDFT>;
|
|
@@ -228,7 +228,7 @@ pcie3x2: pcie@fe280000 {
|
|
num-ib-windows = <6>;
|
|
num-ob-windows = <2>;
|
|
max-link-speed = <3>;
|
|
- msi-map = <0x0 &gic 0x2000 0x1000>;
|
|
+ msi-map = <0x2000 &its 0x2000 0x1000>;
|
|
num-lanes = <2>;
|
|
phys = <&pcie30phy>;
|
|
phy-names = "pcie-phy";
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
|
|
index c52af310c706..5d8b96384856 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
|
|
@@ -95,8 +95,6 @@ cpu_l0: cpu@0 {
|
|
enable-method = "psci";
|
|
capacity-dmips-mhz = <530>;
|
|
clocks = <&scmi_clk SCMI_CLK_CPUL>;
|
|
- assigned-clocks = <&scmi_clk SCMI_CLK_CPUL>;
|
|
- assigned-clock-rates = <816000000>;
|
|
cpu-idle-states = <&CPU_SLEEP>;
|
|
i-cache-size = <32768>;
|
|
i-cache-line-size = <64>;
|
|
@@ -173,8 +171,6 @@ cpu_b0: cpu@400 {
|
|
enable-method = "psci";
|
|
capacity-dmips-mhz = <1024>;
|
|
clocks = <&scmi_clk SCMI_CLK_CPUB01>;
|
|
- assigned-clocks = <&scmi_clk SCMI_CLK_CPUB01>;
|
|
- assigned-clock-rates = <816000000>;
|
|
cpu-idle-states = <&CPU_SLEEP>;
|
|
i-cache-size = <65536>;
|
|
i-cache-line-size = <64>;
|
|
@@ -213,8 +209,6 @@ cpu_b2: cpu@600 {
|
|
enable-method = "psci";
|
|
capacity-dmips-mhz = <1024>;
|
|
clocks = <&scmi_clk SCMI_CLK_CPUB23>;
|
|
- assigned-clocks = <&scmi_clk SCMI_CLK_CPUB23>;
|
|
- assigned-clock-rates = <816000000>;
|
|
cpu-idle-states = <&CPU_SLEEP>;
|
|
i-cache-size = <65536>;
|
|
i-cache-line-size = <64>;
|
|
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
|
|
index d22068475c5d..17f4fd054cd3 100644
|
|
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
|
|
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
|
|
@@ -562,6 +562,7 @@ flash@0 {
|
|
spi-max-frequency = <104000000>;
|
|
spi-rx-bus-width = <4>;
|
|
spi-tx-bus-width = <1>;
|
|
+ vcc-supply = <&vcc_3v3_s3>;
|
|
};
|
|
};
|
|
|
|
diff --git a/arch/s390/include/asm/ipl.h b/arch/s390/include/asm/ipl.h
|
|
index b0d00032479d..afb9544fb007 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 3b9d9ccfad63..648ac8c7e1fd 100644
|
|
--- a/arch/s390/kernel/ipl.c
|
|
+++ b/arch/s390/kernel/ipl.c
|
|
@@ -2498,3 +2498,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 f244c5560e7f..464f915011b8 100644
|
|
--- a/arch/s390/kernel/setup.c
|
|
+++ b/arch/s390/kernel/setup.c
|
|
@@ -49,6 +49,7 @@
|
|
#include <linux/memory.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/start_kernel.h>
|
|
+#include <linux/security.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/kmemleak.h>
|
|
|
|
@@ -914,6 +915,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 9d2a13b37833..770aee4ae9ae 100644
|
|
--- a/arch/x86/kernel/setup.c
|
|
+++ b/arch/x86/kernel/setup.c
|
|
@@ -21,6 +21,7 @@
|
|
#include <linux/root_dev.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/tboot.h>
|
|
+#include <linux/security.h>
|
|
#include <linux/usb/xhci-dbgp.h>
|
|
#include <linux/static_call.h>
|
|
#include <linux/swiotlb.h>
|
|
@@ -964,6 +965,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();
|
|
|
|
@@ -1127,19 +1135,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/crypto/sig.c b/crypto/sig.c
|
|
index 53a3dd6fbe3f..2321847d8b45 100644
|
|
--- a/crypto/sig.c
|
|
+++ b/crypto/sig.c
|
|
@@ -111,8 +111,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 82977ea25db3..aeb84a4b5937 100644
|
|
--- a/crypto/testmgr.c
|
|
+++ b/crypto/testmgr.c
|
|
@@ -4170,7 +4170,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);
|
|
diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c
|
|
index 20d757687e3d..90a13f20f052 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/irq.c b/drivers/acpi/irq.c
|
|
index 1687483ff319..390b67f19181 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 fb1fe9f3b1a3..5a3312fc0ea0 100644
|
|
--- a/drivers/acpi/scan.c
|
|
+++ b/drivers/acpi/scan.c
|
|
@@ -1794,6 +1794,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 22afa4ff860d..78eacccaa1ae 100644
|
|
--- a/drivers/ata/libahci.c
|
|
+++ b/drivers/ata/libahci.c
|
|
@@ -730,6 +730,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 bbf7029e224b..cf7faa970dd6 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 3ba9d7e9a6c7..82300dc5fd49 100644
|
|
--- a/drivers/char/ipmi/ipmi_msghandler.c
|
|
+++ b/drivers/char/ipmi/ipmi_msghandler.c
|
|
@@ -35,6 +35,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"
|
|
@@ -5508,8 +5509,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/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
|
|
index a2d0009560d0..4f3486e6a84b 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 7309394b8fc9..d9f7cbba5769 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>
|
|
|
|
@@ -1007,40 +1008,101 @@ int efi_mem_type(unsigned long phys_addr)
|
|
return -EINVAL;
|
|
}
|
|
|
|
+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,
|
|
+ sizeof(struct efi_error_code), num,
|
|
+ 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,
|
|
+ sizeof(struct efi_error_code), num,
|
|
+ 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/secureboot.c b/drivers/firmware/efi/secureboot.c
|
|
new file mode 100644
|
|
index 000000000000..de0a3714a5d4
|
|
--- /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/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
|
|
index d4af17fdba46..154f0403cbf4 100644
|
|
--- a/drivers/hid/hid-rmi.c
|
|
+++ b/drivers/hid/hid-rmi.c
|
|
@@ -321,21 +321,12 @@ static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
|
|
{
|
|
struct rmi_data *hdata = hid_get_drvdata(hdev);
|
|
struct rmi_device *rmi_dev = hdata->xport.rmi_dev;
|
|
- unsigned long flags;
|
|
|
|
if (!(test_bit(RMI_STARTED, &hdata->flags)))
|
|
return 0;
|
|
|
|
- pm_wakeup_event(hdev->dev.parent, 0);
|
|
-
|
|
- local_irq_save(flags);
|
|
-
|
|
rmi_set_attn_data(rmi_dev, data[1], &data[2], size - 2);
|
|
|
|
- generic_handle_irq(hdata->rmi_irq);
|
|
-
|
|
- local_irq_restore(flags);
|
|
-
|
|
return 1;
|
|
}
|
|
|
|
@@ -589,56 +580,6 @@ static const struct rmi_transport_ops hid_rmi_ops = {
|
|
.reset = rmi_hid_reset,
|
|
};
|
|
|
|
-static void rmi_irq_teardown(void *data)
|
|
-{
|
|
- struct rmi_data *hdata = data;
|
|
- struct irq_domain *domain = hdata->domain;
|
|
-
|
|
- if (!domain)
|
|
- return;
|
|
-
|
|
- irq_dispose_mapping(irq_find_mapping(domain, 0));
|
|
-
|
|
- irq_domain_remove(domain);
|
|
- hdata->domain = NULL;
|
|
- hdata->rmi_irq = 0;
|
|
-}
|
|
-
|
|
-static int rmi_irq_map(struct irq_domain *h, unsigned int virq,
|
|
- irq_hw_number_t hw_irq_num)
|
|
-{
|
|
- irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static const struct irq_domain_ops rmi_irq_ops = {
|
|
- .map = rmi_irq_map,
|
|
-};
|
|
-
|
|
-static int rmi_setup_irq_domain(struct hid_device *hdev)
|
|
-{
|
|
- struct rmi_data *hdata = hid_get_drvdata(hdev);
|
|
- int ret;
|
|
-
|
|
- hdata->domain = irq_domain_create_linear(hdev->dev.fwnode, 1,
|
|
- &rmi_irq_ops, hdata);
|
|
- if (!hdata->domain)
|
|
- return -ENOMEM;
|
|
-
|
|
- ret = devm_add_action_or_reset(&hdev->dev, &rmi_irq_teardown, hdata);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
- hdata->rmi_irq = irq_create_mapping(hdata->domain, 0);
|
|
- if (hdata->rmi_irq <= 0) {
|
|
- hid_err(hdev, "Can't allocate an IRQ\n");
|
|
- return hdata->rmi_irq < 0 ? hdata->rmi_irq : -ENXIO;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|
{
|
|
struct rmi_data *data = NULL;
|
|
@@ -711,18 +652,11 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|
|
|
mutex_init(&data->page_mutex);
|
|
|
|
- ret = rmi_setup_irq_domain(hdev);
|
|
- if (ret) {
|
|
- hid_err(hdev, "failed to allocate IRQ domain\n");
|
|
- return ret;
|
|
- }
|
|
-
|
|
if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS)
|
|
rmi_hid_pdata.gpio_data.disable = true;
|
|
|
|
data->xport.dev = hdev->dev.parent;
|
|
data->xport.pdata = rmi_hid_pdata;
|
|
- data->xport.pdata.irq = data->rmi_irq;
|
|
data->xport.proto_name = "hid";
|
|
data->xport.ops = &hid_rmi_ops;
|
|
|
|
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
|
|
index 88ef381ee6dd..b6bbed9c0cad 100644
|
|
--- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
|
|
+++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
|
|
@@ -11,6 +11,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>
|
|
@@ -2383,6 +2384,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 = {
|
|
@@ -2451,6 +2462,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 */
|
|
@@ -2477,6 +2493,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/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
|
|
index 2168b6cd7167..5d7cda175a0c 100644
|
|
--- a/drivers/input/rmi4/rmi_driver.c
|
|
+++ b/drivers/input/rmi4/rmi_driver.c
|
|
@@ -182,34 +182,47 @@ void rmi_set_attn_data(struct rmi_device *rmi_dev, unsigned long irq_status,
|
|
attn_data.data = fifo_data;
|
|
|
|
kfifo_put(&drvdata->attn_fifo, attn_data);
|
|
+
|
|
+ schedule_work(&drvdata->attn_work);
|
|
}
|
|
EXPORT_SYMBOL_GPL(rmi_set_attn_data);
|
|
|
|
-static irqreturn_t rmi_irq_fn(int irq, void *dev_id)
|
|
+static void attn_callback(struct work_struct *work)
|
|
{
|
|
- struct rmi_device *rmi_dev = dev_id;
|
|
- struct rmi_driver_data *drvdata = dev_get_drvdata(&rmi_dev->dev);
|
|
+ struct rmi_driver_data *drvdata = container_of(work,
|
|
+ struct rmi_driver_data,
|
|
+ attn_work);
|
|
struct rmi4_attn_data attn_data = {0};
|
|
int ret, count;
|
|
|
|
count = kfifo_get(&drvdata->attn_fifo, &attn_data);
|
|
- if (count) {
|
|
- *(drvdata->irq_status) = attn_data.irq_status;
|
|
- drvdata->attn_data = attn_data;
|
|
- }
|
|
+ if (!count)
|
|
+ return;
|
|
|
|
- ret = rmi_process_interrupt_requests(rmi_dev);
|
|
+ *(drvdata->irq_status) = attn_data.irq_status;
|
|
+ drvdata->attn_data = attn_data;
|
|
+
|
|
+ ret = rmi_process_interrupt_requests(drvdata->rmi_dev);
|
|
if (ret)
|
|
- rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
|
|
+ rmi_dbg(RMI_DEBUG_CORE, &drvdata->rmi_dev->dev,
|
|
"Failed to process interrupt request: %d\n", ret);
|
|
|
|
- if (count) {
|
|
- kfree(attn_data.data);
|
|
- drvdata->attn_data.data = NULL;
|
|
- }
|
|
+ kfree(attn_data.data);
|
|
+ drvdata->attn_data.data = NULL;
|
|
|
|
if (!kfifo_is_empty(&drvdata->attn_fifo))
|
|
- return rmi_irq_fn(irq, dev_id);
|
|
+ schedule_work(&drvdata->attn_work);
|
|
+}
|
|
+
|
|
+static irqreturn_t rmi_irq_fn(int irq, void *dev_id)
|
|
+{
|
|
+ struct rmi_device *rmi_dev = dev_id;
|
|
+ int ret;
|
|
+
|
|
+ ret = rmi_process_interrupt_requests(rmi_dev);
|
|
+ if (ret)
|
|
+ rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
|
|
+ "Failed to process interrupt request: %d\n", ret);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
@@ -217,7 +230,6 @@ static irqreturn_t rmi_irq_fn(int irq, void *dev_id)
|
|
static int rmi_irq_init(struct rmi_device *rmi_dev)
|
|
{
|
|
struct rmi_device_platform_data *pdata = rmi_get_platform_data(rmi_dev);
|
|
- struct rmi_driver_data *data = dev_get_drvdata(&rmi_dev->dev);
|
|
int irq_flags = irq_get_trigger_type(pdata->irq);
|
|
int ret;
|
|
|
|
@@ -235,8 +247,6 @@ static int rmi_irq_init(struct rmi_device *rmi_dev)
|
|
return ret;
|
|
}
|
|
|
|
- data->enabled = true;
|
|
-
|
|
return 0;
|
|
}
|
|
|
|
@@ -886,23 +896,27 @@ void rmi_enable_irq(struct rmi_device *rmi_dev, bool clear_wake)
|
|
if (data->enabled)
|
|
goto out;
|
|
|
|
- enable_irq(irq);
|
|
- data->enabled = true;
|
|
- if (clear_wake && device_may_wakeup(rmi_dev->xport->dev)) {
|
|
- retval = disable_irq_wake(irq);
|
|
- if (retval)
|
|
- dev_warn(&rmi_dev->dev,
|
|
- "Failed to disable irq for wake: %d\n",
|
|
- retval);
|
|
- }
|
|
+ if (irq) {
|
|
+ enable_irq(irq);
|
|
+ data->enabled = true;
|
|
+ if (clear_wake && device_may_wakeup(rmi_dev->xport->dev)) {
|
|
+ retval = disable_irq_wake(irq);
|
|
+ if (retval)
|
|
+ dev_warn(&rmi_dev->dev,
|
|
+ "Failed to disable irq for wake: %d\n",
|
|
+ retval);
|
|
+ }
|
|
|
|
- /*
|
|
- * Call rmi_process_interrupt_requests() after enabling irq,
|
|
- * otherwise we may lose interrupt on edge-triggered systems.
|
|
- */
|
|
- irq_flags = irq_get_trigger_type(pdata->irq);
|
|
- if (irq_flags & IRQ_TYPE_EDGE_BOTH)
|
|
- rmi_process_interrupt_requests(rmi_dev);
|
|
+ /*
|
|
+ * Call rmi_process_interrupt_requests() after enabling irq,
|
|
+ * otherwise we may lose interrupt on edge-triggered systems.
|
|
+ */
|
|
+ irq_flags = irq_get_trigger_type(pdata->irq);
|
|
+ if (irq_flags & IRQ_TYPE_EDGE_BOTH)
|
|
+ rmi_process_interrupt_requests(rmi_dev);
|
|
+ } else {
|
|
+ data->enabled = true;
|
|
+ }
|
|
|
|
out:
|
|
mutex_unlock(&data->enabled_mutex);
|
|
@@ -922,20 +936,22 @@ void rmi_disable_irq(struct rmi_device *rmi_dev, bool enable_wake)
|
|
goto out;
|
|
|
|
data->enabled = false;
|
|
- disable_irq(irq);
|
|
- if (enable_wake && device_may_wakeup(rmi_dev->xport->dev)) {
|
|
- retval = enable_irq_wake(irq);
|
|
- if (retval)
|
|
- dev_warn(&rmi_dev->dev,
|
|
- "Failed to enable irq for wake: %d\n",
|
|
- retval);
|
|
- }
|
|
-
|
|
- /* make sure the fifo is clean */
|
|
- while (!kfifo_is_empty(&data->attn_fifo)) {
|
|
- count = kfifo_get(&data->attn_fifo, &attn_data);
|
|
- if (count)
|
|
- kfree(attn_data.data);
|
|
+ if (irq) {
|
|
+ disable_irq(irq);
|
|
+ if (enable_wake && device_may_wakeup(rmi_dev->xport->dev)) {
|
|
+ retval = enable_irq_wake(irq);
|
|
+ if (retval)
|
|
+ dev_warn(&rmi_dev->dev,
|
|
+ "Failed to enable irq for wake: %d\n",
|
|
+ retval);
|
|
+ }
|
|
+ } else {
|
|
+ /* make sure the fifo is clean */
|
|
+ while (!kfifo_is_empty(&data->attn_fifo)) {
|
|
+ count = kfifo_get(&data->attn_fifo, &attn_data);
|
|
+ if (count)
|
|
+ kfree(attn_data.data);
|
|
+ }
|
|
}
|
|
|
|
out:
|
|
@@ -978,6 +994,8 @@ static int rmi_driver_remove(struct device *dev)
|
|
|
|
rmi_disable_irq(rmi_dev, false);
|
|
|
|
+ cancel_work_sync(&data->attn_work);
|
|
+
|
|
rmi_f34_remove_sysfs(rmi_dev);
|
|
rmi_free_function_list(rmi_dev);
|
|
|
|
@@ -1223,9 +1241,15 @@ static int rmi_driver_probe(struct device *dev)
|
|
}
|
|
}
|
|
|
|
- retval = rmi_irq_init(rmi_dev);
|
|
- if (retval < 0)
|
|
- goto err_destroy_functions;
|
|
+ if (pdata->irq) {
|
|
+ retval = rmi_irq_init(rmi_dev);
|
|
+ if (retval < 0)
|
|
+ goto err_destroy_functions;
|
|
+ }
|
|
+
|
|
+ data->enabled = true;
|
|
+
|
|
+ INIT_WORK(&data->attn_work, attn_callback);
|
|
|
|
if (data->f01_container->dev.driver) {
|
|
/* Driver already bound, so enable ATTN now. */
|
|
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
|
|
index df871a500b28..692113b3c14f 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>
|
|
@@ -2968,6 +2969,27 @@ int iommu_dev_disable_feature(struct device *dev, enum iommu_dev_features feat)
|
|
}
|
|
EXPORT_SYMBOL_GPL(iommu_dev_disable_feature);
|
|
|
|
+#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 e45ba127069f..7617a6dd2e2f 100644
|
|
--- a/drivers/media/i2c/Kconfig
|
|
+++ b/drivers/media/i2c/Kconfig
|
|
@@ -356,6 +356,26 @@ config VIDEO_OV02A10
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called ov02a10.
|
|
|
|
+config VIDEO_OV02E10
|
|
+ tristate "OmniVision OV02E10 sensor support"
|
|
+ select V4L2_CCI_I2C
|
|
+ help
|
|
+ This is a Video4Linux2 sensor driver for the OmniVision
|
|
+ OV02E10 camera.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called ov02e10.
|
|
+
|
|
+config VIDEO_OV02C10
|
|
+ tristate "OmniVision OV02C10 sensor support"
|
|
+ select V4L2_CCI_I2C
|
|
+ help
|
|
+ This is a Video4Linux2 sensor driver for the OmniVision
|
|
+ OV02C10 camera.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called ov02c10.
|
|
+
|
|
config VIDEO_OV08D10
|
|
tristate "OmniVision OV08D10 sensor support"
|
|
help
|
|
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
|
|
index 6c23a4463527..1b577e5ebc73 100644
|
|
--- a/drivers/media/i2c/Makefile
|
|
+++ b/drivers/media/i2c/Makefile
|
|
@@ -83,6 +83,8 @@ obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o
|
|
obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o
|
|
obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
|
|
obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
|
|
+obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
|
|
+obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
|
|
obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
|
|
obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
|
|
obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
|
|
diff --git a/drivers/media/i2c/ov02c10.c b/drivers/media/i2c/ov02c10.c
|
|
new file mode 100644
|
|
index 000000000000..9e3d4a4e12ce
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/ov02c10.c
|
|
@@ -0,0 +1,1013 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+// Copyright (c) 2022 Intel Corporation.
|
|
+
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/version.h>
|
|
+#include <media/v4l2-cci.h>
|
|
+#include <media/v4l2-ctrls.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+
|
|
+#define OV02C10_LINK_FREQ_400MHZ 400000000ULL
|
|
+#define OV02C10_MCLK 19200000
|
|
+#define OV02C10_RGB_DEPTH 10
|
|
+
|
|
+#define OV02C10_REG_CHIP_ID CCI_REG16(0x300a)
|
|
+#define OV02C10_CHIP_ID 0x5602
|
|
+
|
|
+#define OV02C10_REG_STREAM_CONTROL CCI_REG8(0x0100)
|
|
+
|
|
+#define OV02C10_REG_HTS CCI_REG16(0x380c)
|
|
+
|
|
+/* vertical-timings from sensor */
|
|
+#define OV02C10_REG_VTS CCI_REG16(0x380e)
|
|
+#define OV02C10_VTS_MAX 0xffff
|
|
+
|
|
+/* Exposure controls from sensor */
|
|
+#define OV02C10_REG_EXPOSURE CCI_REG16(0x3501)
|
|
+#define OV02C10_EXPOSURE_MIN 4
|
|
+#define OV02C10_EXPOSURE_MAX_MARGIN 8
|
|
+#define OV02C10_EXPOSURE_STEP 1
|
|
+
|
|
+/* Analog gain controls from sensor */
|
|
+#define OV02C10_REG_ANALOG_GAIN CCI_REG16(0x3508)
|
|
+#define OV02C10_ANAL_GAIN_MIN 0x10
|
|
+#define OV02C10_ANAL_GAIN_MAX 0xf8
|
|
+#define OV02C10_ANAL_GAIN_STEP 1
|
|
+#define OV02C10_ANAL_GAIN_DEFAULT 0x10
|
|
+
|
|
+/* Digital gain controls from sensor */
|
|
+#define OV02C10_REG_DIGITAL_GAIN CCI_REG24(0x350a)
|
|
+#define OV02C10_DGTL_GAIN_MIN 0x0400
|
|
+#define OV02C10_DGTL_GAIN_MAX 0x3fff
|
|
+#define OV02C10_DGTL_GAIN_STEP 1
|
|
+#define OV02C10_DGTL_GAIN_DEFAULT 0x0400
|
|
+
|
|
+/* Rotate */
|
|
+#define OV02C10_ROTATE_CONTROL CCI_REG8(0x3820)
|
|
+#define OV02C10_ISP_X_WIN_CONTROL CCI_REG16(0x3810)
|
|
+#define OV02C10_ISP_Y_WIN_CONTROL CCI_REG16(0x3812)
|
|
+#define OV02C10_CONFIG_ROTATE 0x18
|
|
+
|
|
+/* Test Pattern Control */
|
|
+#define OV02C10_REG_TEST_PATTERN CCI_REG8(0x4503)
|
|
+#define OV02C10_TEST_PATTERN_ENABLE BIT(7)
|
|
+
|
|
+struct ov02c10_mode {
|
|
+ /* Frame width in pixels */
|
|
+ u32 width;
|
|
+
|
|
+ /* Frame height in pixels */
|
|
+ u32 height;
|
|
+
|
|
+ /* Horizontal timining size */
|
|
+ u32 hts;
|
|
+
|
|
+ /* Min vertical timining size */
|
|
+ u32 vts_min;
|
|
+
|
|
+ /* Sensor register settings for this resolution */
|
|
+ const struct reg_sequence *reg_sequence;
|
|
+ const int sequence_length;
|
|
+ /* Sensor register settings for 1 or 2 lane config */
|
|
+ const struct reg_sequence *lane_settings[2];
|
|
+ const int lane_settings_length[2];
|
|
+};
|
|
+
|
|
+static const struct reg_sequence sensor_1928x1092_30fps_setting[] = {
|
|
+ {0x0301, 0x08},
|
|
+ {0x0303, 0x06},
|
|
+ {0x0304, 0x01},
|
|
+ {0x0305, 0xe0},
|
|
+ {0x0313, 0x40},
|
|
+ {0x031c, 0x4f},
|
|
+ {0x3020, 0x97},
|
|
+ {0x3022, 0x01},
|
|
+ {0x3026, 0xb4},
|
|
+ {0x303b, 0x00},
|
|
+ {0x303c, 0x4f},
|
|
+ {0x303d, 0xe6},
|
|
+ {0x303e, 0x00},
|
|
+ {0x303f, 0x03},
|
|
+ {0x3021, 0x23},
|
|
+ {0x3501, 0x04},
|
|
+ {0x3502, 0x6c},
|
|
+ {0x3504, 0x0c},
|
|
+ {0x3507, 0x00},
|
|
+ {0x3508, 0x08},
|
|
+ {0x3509, 0x00},
|
|
+ {0x350a, 0x01},
|
|
+ {0x350b, 0x00},
|
|
+ {0x350c, 0x41},
|
|
+ {0x3600, 0x84},
|
|
+ {0x3603, 0x08},
|
|
+ {0x3610, 0x57},
|
|
+ {0x3611, 0x1b},
|
|
+ {0x3613, 0x78},
|
|
+ {0x3623, 0x00},
|
|
+ {0x3632, 0xa0},
|
|
+ {0x3642, 0xe8},
|
|
+ {0x364c, 0x70},
|
|
+ {0x365f, 0x0f},
|
|
+ {0x3708, 0x30},
|
|
+ {0x3714, 0x24},
|
|
+ {0x3725, 0x02},
|
|
+ {0x3737, 0x08},
|
|
+ {0x3739, 0x28},
|
|
+ {0x3749, 0x32},
|
|
+ {0x374a, 0x32},
|
|
+ {0x374b, 0x32},
|
|
+ {0x374c, 0x32},
|
|
+ {0x374d, 0x81},
|
|
+ {0x374e, 0x81},
|
|
+ {0x374f, 0x81},
|
|
+ {0x3752, 0x36},
|
|
+ {0x3753, 0x36},
|
|
+ {0x3754, 0x36},
|
|
+ {0x3761, 0x00},
|
|
+ {0x376c, 0x81},
|
|
+ {0x3774, 0x18},
|
|
+ {0x3776, 0x08},
|
|
+ {0x377c, 0x81},
|
|
+ {0x377d, 0x81},
|
|
+ {0x377e, 0x81},
|
|
+ {0x37a0, 0x44},
|
|
+ {0x37a6, 0x44},
|
|
+ {0x37aa, 0x0d},
|
|
+ {0x37ae, 0x00},
|
|
+ {0x37cb, 0x03},
|
|
+ {0x37cc, 0x01},
|
|
+ {0x37d8, 0x02},
|
|
+ {0x37d9, 0x10},
|
|
+ {0x37e1, 0x10},
|
|
+ {0x37e2, 0x18},
|
|
+ {0x37e3, 0x08},
|
|
+ {0x37e4, 0x08},
|
|
+ {0x37e5, 0x02},
|
|
+ {0x37e6, 0x08},
|
|
+
|
|
+ /* 1928x1092 */
|
|
+ {0x3800, 0x00},
|
|
+ {0x3801, 0x00},
|
|
+ {0x3802, 0x00},
|
|
+ {0x3803, 0x00},
|
|
+ {0x3804, 0x07},
|
|
+ {0x3805, 0x8f},
|
|
+ {0x3806, 0x04},
|
|
+ {0x3807, 0x47},
|
|
+ {0x3808, 0x07},
|
|
+ {0x3809, 0x88},
|
|
+ {0x380a, 0x04},
|
|
+ {0x380b, 0x44},
|
|
+ {0x3810, 0x00},
|
|
+ {0x3811, 0x02},
|
|
+ {0x3812, 0x00},
|
|
+ {0x3813, 0x02},
|
|
+ {0x3814, 0x01},
|
|
+ {0x3815, 0x01},
|
|
+ {0x3816, 0x01},
|
|
+ {0x3817, 0x01},
|
|
+
|
|
+ {0x3820, 0xb0},
|
|
+ {0x3821, 0x00},
|
|
+ {0x3822, 0x80},
|
|
+ {0x3823, 0x08},
|
|
+ {0x3824, 0x00},
|
|
+ {0x3825, 0x20},
|
|
+ {0x3826, 0x00},
|
|
+ {0x3827, 0x08},
|
|
+ {0x382a, 0x00},
|
|
+ {0x382b, 0x08},
|
|
+ {0x382d, 0x00},
|
|
+ {0x382e, 0x00},
|
|
+ {0x382f, 0x23},
|
|
+ {0x3834, 0x00},
|
|
+ {0x3839, 0x00},
|
|
+ {0x383a, 0xd1},
|
|
+ {0x383e, 0x03},
|
|
+ {0x393d, 0x29},
|
|
+ {0x393f, 0x6e},
|
|
+ {0x394b, 0x06},
|
|
+ {0x394c, 0x06},
|
|
+ {0x394d, 0x08},
|
|
+ {0x394f, 0x01},
|
|
+ {0x3950, 0x01},
|
|
+ {0x3951, 0x01},
|
|
+ {0x3952, 0x01},
|
|
+ {0x3953, 0x01},
|
|
+ {0x3954, 0x01},
|
|
+ {0x3955, 0x01},
|
|
+ {0x3956, 0x01},
|
|
+ {0x3957, 0x0e},
|
|
+ {0x3958, 0x08},
|
|
+ {0x3959, 0x08},
|
|
+ {0x395a, 0x08},
|
|
+ {0x395b, 0x13},
|
|
+ {0x395c, 0x09},
|
|
+ {0x395d, 0x05},
|
|
+ {0x395e, 0x02},
|
|
+ {0x395f, 0x00},
|
|
+ {0x395f, 0x00},
|
|
+ {0x3960, 0x00},
|
|
+ {0x3961, 0x00},
|
|
+ {0x3962, 0x00},
|
|
+ {0x3963, 0x00},
|
|
+ {0x3964, 0x00},
|
|
+ {0x3965, 0x00},
|
|
+ {0x3966, 0x00},
|
|
+ {0x3967, 0x00},
|
|
+ {0x3968, 0x01},
|
|
+ {0x3969, 0x01},
|
|
+ {0x396a, 0x01},
|
|
+ {0x396b, 0x01},
|
|
+ {0x396c, 0x10},
|
|
+ {0x396d, 0xf0},
|
|
+ {0x396e, 0x11},
|
|
+ {0x396f, 0x00},
|
|
+ {0x3970, 0x37},
|
|
+ {0x3971, 0x37},
|
|
+ {0x3972, 0x37},
|
|
+ {0x3973, 0x37},
|
|
+ {0x3974, 0x00},
|
|
+ {0x3975, 0x3c},
|
|
+ {0x3976, 0x3c},
|
|
+ {0x3977, 0x3c},
|
|
+ {0x3978, 0x3c},
|
|
+ {0x3c00, 0x0f},
|
|
+ {0x3c20, 0x01},
|
|
+ {0x3c21, 0x08},
|
|
+ {0x3f00, 0x8b},
|
|
+ {0x3f02, 0x0f},
|
|
+ {0x4000, 0xc3},
|
|
+ {0x4001, 0xe0},
|
|
+ {0x4002, 0x00},
|
|
+ {0x4003, 0x40},
|
|
+ {0x4008, 0x04},
|
|
+ {0x4009, 0x23},
|
|
+ {0x400a, 0x04},
|
|
+ {0x400b, 0x01},
|
|
+ {0x4077, 0x06},
|
|
+ {0x4078, 0x00},
|
|
+ {0x4079, 0x1a},
|
|
+ {0x407a, 0x7f},
|
|
+ {0x407b, 0x01},
|
|
+ {0x4080, 0x03},
|
|
+ {0x4081, 0x84},
|
|
+ {0x4308, 0x03},
|
|
+ {0x4309, 0xff},
|
|
+ {0x430d, 0x00},
|
|
+ {0x4806, 0x00},
|
|
+ {0x4813, 0x00},
|
|
+ {0x4837, 0x10},
|
|
+ {0x4857, 0x05},
|
|
+ {0x4500, 0x07},
|
|
+ {0x4501, 0x00},
|
|
+ {0x4503, 0x00},
|
|
+ {0x450a, 0x04},
|
|
+ {0x450e, 0x00},
|
|
+ {0x450f, 0x00},
|
|
+ {0x4900, 0x00},
|
|
+ {0x4901, 0x00},
|
|
+ {0x4902, 0x01},
|
|
+ {0x5001, 0x50},
|
|
+ {0x5006, 0x00},
|
|
+ {0x5080, 0x40},
|
|
+ {0x5181, 0x2b},
|
|
+ {0x5202, 0xa3},
|
|
+ {0x5206, 0x01},
|
|
+ {0x5207, 0x00},
|
|
+ {0x520a, 0x01},
|
|
+ {0x520b, 0x00},
|
|
+ {0x365d, 0x00},
|
|
+ {0x4815, 0x40},
|
|
+ {0x4816, 0x12},
|
|
+ {0x4f00, 0x01},
|
|
+};
|
|
+
|
|
+static const struct reg_sequence sensor_1928x1092_30fps_1lane_setting[] = {
|
|
+ {0x301b, 0xd2},
|
|
+ {0x3027, 0xe1},
|
|
+ {0x380c, 0x08},
|
|
+ {0x380d, 0xe8},
|
|
+ {0x380e, 0x04},
|
|
+ {0x380f, 0x8c},
|
|
+ {0x394e, 0x0b},
|
|
+ {0x4800, 0x24},
|
|
+ {0x5000, 0xf5},
|
|
+ /* plls */
|
|
+ {0x0303, 0x05},
|
|
+ {0x0305, 0x90},
|
|
+ {0x0316, 0x90},
|
|
+ {0x3016, 0x12},
|
|
+};
|
|
+
|
|
+static const struct reg_sequence sensor_1928x1092_30fps_2lane_setting[] = {
|
|
+ {0x301b, 0xf0},
|
|
+ {0x3027, 0xf1},
|
|
+ {0x380c, 0x04},
|
|
+ {0x380d, 0x74},
|
|
+ {0x380e, 0x09},
|
|
+ {0x380f, 0x18},
|
|
+ {0x394e, 0x0a},
|
|
+ {0x4041, 0x20},
|
|
+ {0x4884, 0x04},
|
|
+ {0x4800, 0x64},
|
|
+ {0x4d00, 0x03},
|
|
+ {0x4d01, 0xd8},
|
|
+ {0x4d02, 0xba},
|
|
+ {0x4d03, 0xa0},
|
|
+ {0x4d04, 0xb7},
|
|
+ {0x4d05, 0x34},
|
|
+ {0x4d0d, 0x00},
|
|
+ {0x5000, 0xfd},
|
|
+ {0x481f, 0x30},
|
|
+ /* plls */
|
|
+ {0x0303, 0x05},
|
|
+ {0x0305, 0x90},
|
|
+ {0x0316, 0x90},
|
|
+ {0x3016, 0x32},
|
|
+};
|
|
+
|
|
+static const char * const ov02c10_test_pattern_menu[] = {
|
|
+ "Disabled",
|
|
+ "Color Bar",
|
|
+ "Top-Bottom Darker Color Bar",
|
|
+ "Right-Left Darker Color Bar",
|
|
+ "Color Bar type 4",
|
|
+};
|
|
+
|
|
+static const s64 link_freq_menu_items[] = {
|
|
+ OV02C10_LINK_FREQ_400MHZ,
|
|
+};
|
|
+
|
|
+static const struct ov02c10_mode supported_modes[] = {
|
|
+ {
|
|
+ .width = 1928,
|
|
+ .height = 1092,
|
|
+ .hts = 2280,
|
|
+ .vts_min = 1164,
|
|
+ .reg_sequence = sensor_1928x1092_30fps_setting,
|
|
+ .sequence_length = ARRAY_SIZE(sensor_1928x1092_30fps_setting),
|
|
+ .lane_settings = {
|
|
+ sensor_1928x1092_30fps_1lane_setting,
|
|
+ sensor_1928x1092_30fps_2lane_setting
|
|
+ },
|
|
+ .lane_settings_length = {
|
|
+ ARRAY_SIZE(sensor_1928x1092_30fps_1lane_setting),
|
|
+ ARRAY_SIZE(sensor_1928x1092_30fps_2lane_setting),
|
|
+ },
|
|
+ },
|
|
+};
|
|
+
|
|
+static const char * const ov02c10_supply_names[] = {
|
|
+ "dovdd", /* Digital I/O power */
|
|
+ "avdd", /* Analog power */
|
|
+ "dvdd", /* Digital core power */
|
|
+};
|
|
+
|
|
+struct ov02c10 {
|
|
+ struct v4l2_subdev sd;
|
|
+ struct media_pad pad;
|
|
+ struct v4l2_ctrl_handler ctrl_handler;
|
|
+ struct regmap *regmap;
|
|
+
|
|
+ /* V4L2 Controls */
|
|
+ struct v4l2_ctrl *link_freq;
|
|
+ struct v4l2_ctrl *pixel_rate;
|
|
+ struct v4l2_ctrl *vblank;
|
|
+ struct v4l2_ctrl *hblank;
|
|
+ struct v4l2_ctrl *exposure;
|
|
+
|
|
+ struct clk *img_clk;
|
|
+ struct gpio_desc *reset;
|
|
+ struct regulator_bulk_data supplies[ARRAY_SIZE(ov02c10_supply_names)];
|
|
+
|
|
+ /* MIPI lane info */
|
|
+ u32 link_freq_index;
|
|
+ u8 mipi_lanes;
|
|
+};
|
|
+
|
|
+static inline struct ov02c10 *to_ov02c10(struct v4l2_subdev *subdev)
|
|
+{
|
|
+ return container_of(subdev, struct ov02c10, sd);
|
|
+}
|
|
+
|
|
+static int ov02c10_test_pattern(struct ov02c10 *ov02c10, int pattern)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (!pattern)
|
|
+ return cci_update_bits(ov02c10->regmap, OV02C10_REG_TEST_PATTERN,
|
|
+ BIT(7), 0, NULL);
|
|
+
|
|
+ cci_update_bits(ov02c10->regmap, OV02C10_REG_TEST_PATTERN,
|
|
+ 0x03, pattern - 1, &ret);
|
|
+ cci_update_bits(ov02c10->regmap, OV02C10_REG_TEST_PATTERN,
|
|
+ BIT(7), OV02C10_TEST_PATTERN_ENABLE, &ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ov02c10_set_ctrl(struct v4l2_ctrl *ctrl)
|
|
+{
|
|
+ struct ov02c10 *ov02c10 = container_of(ctrl->handler,
|
|
+ struct ov02c10, ctrl_handler);
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&ov02c10->sd);
|
|
+ const u32 height = supported_modes[0].height;
|
|
+ s64 exposure_max;
|
|
+ int ret = 0;
|
|
+
|
|
+ /* Propagate change of current control to all related controls */
|
|
+ if (ctrl->id == V4L2_CID_VBLANK) {
|
|
+ /* Update max exposure while meeting expected vblanking */
|
|
+ exposure_max = height + ctrl->val - OV02C10_EXPOSURE_MAX_MARGIN;
|
|
+ __v4l2_ctrl_modify_range(ov02c10->exposure,
|
|
+ ov02c10->exposure->minimum,
|
|
+ exposure_max, ov02c10->exposure->step,
|
|
+ exposure_max);
|
|
+ }
|
|
+
|
|
+ /* V4L2 controls values will be applied only when power is already up */
|
|
+ if (!pm_runtime_get_if_in_use(&client->dev))
|
|
+ return 0;
|
|
+
|
|
+ switch (ctrl->id) {
|
|
+ case V4L2_CID_ANALOGUE_GAIN:
|
|
+ cci_write(ov02c10->regmap, OV02C10_REG_ANALOG_GAIN,
|
|
+ ctrl->val << 4, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_DIGITAL_GAIN:
|
|
+ cci_write(ov02c10->regmap, OV02C10_REG_DIGITAL_GAIN,
|
|
+ ctrl->val << 6, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_EXPOSURE:
|
|
+ cci_write(ov02c10->regmap, OV02C10_REG_EXPOSURE,
|
|
+ ctrl->val, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_VBLANK:
|
|
+ cci_write(ov02c10->regmap, OV02C10_REG_VTS, height + ctrl->val,
|
|
+ &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_TEST_PATTERN:
|
|
+ ret = ov02c10_test_pattern(ov02c10, ctrl->val);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ pm_runtime_put(&client->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_ctrl_ops ov02c10_ctrl_ops = {
|
|
+ .s_ctrl = ov02c10_set_ctrl,
|
|
+};
|
|
+
|
|
+static int ov02c10_init_controls(struct ov02c10 *ov02c10)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&ov02c10->sd);
|
|
+ struct v4l2_ctrl_handler *ctrl_hdlr = &ov02c10->ctrl_handler;
|
|
+ const struct ov02c10_mode *mode = &supported_modes[0];
|
|
+ u32 vblank_min, vblank_max, vblank_default, vts_def;
|
|
+ struct v4l2_fwnode_device_properties props;
|
|
+ s64 exposure_max, h_blank, pixel_rate;
|
|
+ int ret;
|
|
+
|
|
+ v4l2_ctrl_handler_init(ctrl_hdlr, 10);
|
|
+
|
|
+ ov02c10->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
|
|
+ &ov02c10_ctrl_ops,
|
|
+ V4L2_CID_LINK_FREQ,
|
|
+ ov02c10->link_freq_index, 0,
|
|
+ link_freq_menu_items);
|
|
+ if (ov02c10->link_freq)
|
|
+ ov02c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+
|
|
+ /* MIPI lanes are DDR -> use link-freq * 2 */
|
|
+ pixel_rate = link_freq_menu_items[ov02c10->link_freq_index] * 2 *
|
|
+ ov02c10->mipi_lanes / OV02C10_RGB_DEPTH;
|
|
+
|
|
+ ov02c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov02c10_ctrl_ops,
|
|
+ V4L2_CID_PIXEL_RATE, 0,
|
|
+ pixel_rate, 1, pixel_rate);
|
|
+
|
|
+ /*
|
|
+ * For default multiple min by number of lanes to keep the default
|
|
+ * FPS the same indepenedent of the lane count.
|
|
+ */
|
|
+ vts_def = mode->vts_min * ov02c10->mipi_lanes;
|
|
+
|
|
+ vblank_min = mode->vts_min - mode->height;
|
|
+ vblank_max = OV02C10_VTS_MAX - mode->height;
|
|
+ vblank_default = vts_def - mode->height;
|
|
+ ov02c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov02c10_ctrl_ops,
|
|
+ V4L2_CID_VBLANK, vblank_min,
|
|
+ vblank_max, 1, vblank_default);
|
|
+
|
|
+ h_blank = mode->hts - mode->width;
|
|
+ ov02c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov02c10_ctrl_ops,
|
|
+ V4L2_CID_HBLANK, h_blank, h_blank,
|
|
+ 1, h_blank);
|
|
+ if (ov02c10->hblank)
|
|
+ ov02c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov02c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
|
|
+ OV02C10_ANAL_GAIN_MIN, OV02C10_ANAL_GAIN_MAX,
|
|
+ OV02C10_ANAL_GAIN_STEP, OV02C10_ANAL_GAIN_DEFAULT);
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov02c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
|
|
+ OV02C10_DGTL_GAIN_MIN, OV02C10_DGTL_GAIN_MAX,
|
|
+ OV02C10_DGTL_GAIN_STEP, OV02C10_DGTL_GAIN_DEFAULT);
|
|
+ exposure_max = vts_def - OV02C10_EXPOSURE_MAX_MARGIN;
|
|
+ ov02c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov02c10_ctrl_ops,
|
|
+ V4L2_CID_EXPOSURE,
|
|
+ OV02C10_EXPOSURE_MIN,
|
|
+ exposure_max,
|
|
+ OV02C10_EXPOSURE_STEP,
|
|
+ exposure_max);
|
|
+ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov02c10_ctrl_ops,
|
|
+ V4L2_CID_TEST_PATTERN,
|
|
+ ARRAY_SIZE(ov02c10_test_pattern_menu) - 1,
|
|
+ 0, 0, ov02c10_test_pattern_menu);
|
|
+
|
|
+ ret = v4l2_fwnode_device_parse(&client->dev, &props);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov02c10_ctrl_ops, &props);
|
|
+
|
|
+ if (ctrl_hdlr->error)
|
|
+ return ctrl_hdlr->error;
|
|
+
|
|
+ ov02c10->sd.ctrl_handler = ctrl_hdlr;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ov02c10_update_pad_format(const struct ov02c10_mode *mode,
|
|
+ struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ fmt->width = mode->width;
|
|
+ fmt->height = mode->height;
|
|
+ fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
|
|
+ fmt->field = V4L2_FIELD_NONE;
|
|
+}
|
|
+
|
|
+static int ov02c10_enable_streams(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state,
|
|
+ u32 pad, u64 streams_mask)
|
|
+{
|
|
+ const struct ov02c10_mode *mode = &supported_modes[0];
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
|
|
+ struct ov02c10 *ov02c10 = to_ov02c10(sd);
|
|
+ const struct reg_sequence *reg_sequence;
|
|
+ int ret, sequence_length;
|
|
+
|
|
+ ret = pm_runtime_resume_and_get(&client->dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ reg_sequence = mode->reg_sequence;
|
|
+ sequence_length = mode->sequence_length;
|
|
+ ret = regmap_multi_reg_write(ov02c10->regmap,
|
|
+ reg_sequence, sequence_length);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to set mode\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ reg_sequence = mode->lane_settings[ov02c10->mipi_lanes - 1];
|
|
+ sequence_length = mode->lane_settings_length[ov02c10->mipi_lanes - 1];
|
|
+ ret = regmap_multi_reg_write(ov02c10->regmap,
|
|
+ reg_sequence, sequence_length);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to write lane settings\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = __v4l2_ctrl_handler_setup(ov02c10->sd.ctrl_handler);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ ret = cci_write(ov02c10->regmap, OV02C10_REG_STREAM_CONTROL, 1, NULL);
|
|
+out:
|
|
+ if (ret)
|
|
+ pm_runtime_put(&client->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ov02c10_disable_streams(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state,
|
|
+ u32 pad, u64 streams_mask)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
|
|
+ struct ov02c10 *ov02c10 = to_ov02c10(sd);
|
|
+
|
|
+ cci_write(ov02c10->regmap, OV02C10_REG_STREAM_CONTROL, 0, NULL);
|
|
+ pm_runtime_put(&client->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* This function tries to get power control resources */
|
|
+static int ov02c10_get_pm_resources(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct ov02c10 *ov02c10 = to_ov02c10(sd);
|
|
+ int i;
|
|
+
|
|
+ ov02c10->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
|
+ if (IS_ERR(ov02c10->reset))
|
|
+ return dev_err_probe(dev, PTR_ERR(ov02c10->reset),
|
|
+ "failed to get reset gpio\n");
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ov02c10_supply_names); i++)
|
|
+ ov02c10->supplies[i].supply = ov02c10_supply_names[i];
|
|
+
|
|
+ return devm_regulator_bulk_get(dev, ARRAY_SIZE(ov02c10_supply_names),
|
|
+ ov02c10->supplies);
|
|
+}
|
|
+
|
|
+static int ov02c10_power_off(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct ov02c10 *ov02c10 = to_ov02c10(sd);
|
|
+
|
|
+ gpiod_set_value_cansleep(ov02c10->reset, 1);
|
|
+
|
|
+ regulator_bulk_disable(ARRAY_SIZE(ov02c10_supply_names),
|
|
+ ov02c10->supplies);
|
|
+
|
|
+ clk_disable_unprepare(ov02c10->img_clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02c10_power_on(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct ov02c10 *ov02c10 = to_ov02c10(sd);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_prepare_enable(ov02c10->img_clk);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable imaging clock: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = regulator_bulk_enable(ARRAY_SIZE(ov02c10_supply_names),
|
|
+ ov02c10->supplies);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable regulators: %d", ret);
|
|
+ clk_disable_unprepare(ov02c10->img_clk);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (ov02c10->reset) {
|
|
+ /* Assert reset for at least 2ms on back to back off-on */
|
|
+ usleep_range(2000, 2200);
|
|
+ gpiod_set_value_cansleep(ov02c10->reset, 0);
|
|
+ usleep_range(5000, 5100);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02c10_set_format(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ const struct ov02c10_mode *mode = &supported_modes[0];
|
|
+ struct ov02c10 *ov02c10 = to_ov02c10(sd);
|
|
+ s32 vblank_def, h_blank;
|
|
+
|
|
+ ov02c10_update_pad_format(mode, &fmt->format);
|
|
+ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
|
|
+ return 0;
|
|
+
|
|
+ /* Update limits and set FPS to default */
|
|
+ vblank_def = mode->vts_min * ov02c10->mipi_lanes - mode->height;
|
|
+ __v4l2_ctrl_modify_range(ov02c10->vblank, mode->vts_min - mode->height,
|
|
+ OV02C10_VTS_MAX - mode->height, 1, vblank_def);
|
|
+ __v4l2_ctrl_s_ctrl(ov02c10->vblank, vblank_def);
|
|
+ h_blank = mode->hts - mode->width;
|
|
+ __v4l2_ctrl_modify_range(ov02c10->hblank, h_blank, h_blank, 1, h_blank);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02c10_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ if (code->index > 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02c10_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(supported_modes))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
|
|
+ return -EINVAL;
|
|
+
|
|
+ fse->min_width = supported_modes[fse->index].width;
|
|
+ fse->max_width = fse->min_width;
|
|
+ fse->min_height = supported_modes[fse->index].height;
|
|
+ fse->max_height = fse->min_height;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02c10_init_state(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state)
|
|
+{
|
|
+ ov02c10_update_pad_format(&supported_modes[0],
|
|
+ v4l2_subdev_state_get_format(sd_state, 0));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops ov02c10_video_ops = {
|
|
+ .s_stream = v4l2_subdev_s_stream_helper,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops ov02c10_pad_ops = {
|
|
+ .set_fmt = ov02c10_set_format,
|
|
+ .get_fmt = v4l2_subdev_get_fmt,
|
|
+ .enum_mbus_code = ov02c10_enum_mbus_code,
|
|
+ .enum_frame_size = ov02c10_enum_frame_size,
|
|
+ .enable_streams = ov02c10_enable_streams,
|
|
+ .disable_streams = ov02c10_disable_streams,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops ov02c10_subdev_ops = {
|
|
+ .video = &ov02c10_video_ops,
|
|
+ .pad = &ov02c10_pad_ops,
|
|
+};
|
|
+
|
|
+static const struct media_entity_operations ov02c10_subdev_entity_ops = {
|
|
+ .link_validate = v4l2_subdev_link_validate,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_internal_ops ov02c10_internal_ops = {
|
|
+ .init_state = ov02c10_init_state,
|
|
+};
|
|
+
|
|
+static int ov02c10_identify_module(struct ov02c10 *ov02c10)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&ov02c10->sd);
|
|
+ u64 chip_id;
|
|
+ int ret;
|
|
+
|
|
+ ret = cci_read(ov02c10->regmap, OV02C10_REG_CHIP_ID, &chip_id, NULL);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (chip_id != OV02C10_CHIP_ID) {
|
|
+ dev_err(&client->dev, "chip id mismatch: %x!=%llx",
|
|
+ OV02C10_CHIP_ID, chip_id);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02c10_check_hwcfg(struct device *dev, struct ov02c10 *ov02c10)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint bus_cfg = {
|
|
+ .bus_type = V4L2_MBUS_CSI2_DPHY
|
|
+ };
|
|
+ struct fwnode_handle *ep, *fwnode = dev_fwnode(dev);
|
|
+ unsigned long link_freq_bitmap;
|
|
+ u32 mclk;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * Sometimes the fwnode graph is initialized by the bridge driver,
|
|
+ * wait for this.
|
|
+ */
|
|
+ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0);
|
|
+ if (!ep)
|
|
+ return dev_err_probe(dev, -EPROBE_DEFER,
|
|
+ "waiting for fwnode graph endpoint\n");
|
|
+
|
|
+ ov02c10->img_clk = devm_clk_get_optional(dev, NULL);
|
|
+ if (IS_ERR(ov02c10->img_clk)) {
|
|
+ fwnode_handle_put(ep);
|
|
+ return dev_err_probe(dev, PTR_ERR(ov02c10->img_clk),
|
|
+ "failed to get imaging clock\n");
|
|
+ }
|
|
+
|
|
+ if (ov02c10->img_clk) {
|
|
+ mclk = clk_get_rate(ov02c10->img_clk);
|
|
+ } else {
|
|
+ ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk);
|
|
+ if (ret) {
|
|
+ fwnode_handle_put(ep);
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "reading clock-frequency property\n");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (mclk != OV02C10_MCLK) {
|
|
+ fwnode_handle_put(ep);
|
|
+ return dev_err_probe(dev, -EINVAL,
|
|
+ "external clock %u is not supported\n",
|
|
+ mclk);
|
|
+ }
|
|
+
|
|
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
|
|
+ fwnode_handle_put(ep);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret, "parsing endpoint failed\n");
|
|
+
|
|
+ ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
|
|
+ bus_cfg.nr_of_link_frequencies,
|
|
+ link_freq_menu_items,
|
|
+ ARRAY_SIZE(link_freq_menu_items),
|
|
+ &link_freq_bitmap);
|
|
+ if (ret)
|
|
+ goto check_hwcfg_error;
|
|
+
|
|
+ /* v4l2_link_freq_to_bitmap() guarantees at least 1 bit is set */
|
|
+ ov02c10->link_freq_index = ffs(link_freq_bitmap) - 1;
|
|
+
|
|
+ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 1 &&
|
|
+ bus_cfg.bus.mipi_csi2.num_data_lanes != 2) {
|
|
+ ret = dev_err_probe(dev, -EINVAL,
|
|
+ "number of CSI2 data lanes %u is not supported\n",
|
|
+ bus_cfg.bus.mipi_csi2.num_data_lanes);
|
|
+ goto check_hwcfg_error;
|
|
+ }
|
|
+
|
|
+ ov02c10->mipi_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
|
|
+
|
|
+check_hwcfg_error:
|
|
+ v4l2_fwnode_endpoint_free(&bus_cfg);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void ov02c10_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)) {
|
|
+ ov02c10_power_off(&client->dev);
|
|
+ pm_runtime_set_suspended(&client->dev);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int ov02c10_probe(struct i2c_client *client)
|
|
+{
|
|
+ struct ov02c10 *ov02c10;
|
|
+ int ret;
|
|
+
|
|
+ ov02c10 = devm_kzalloc(&client->dev, sizeof(*ov02c10), GFP_KERNEL);
|
|
+ if (!ov02c10)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ v4l2_i2c_subdev_init(&ov02c10->sd, client, &ov02c10_subdev_ops);
|
|
+
|
|
+ /* Check HW config */
|
|
+ ret = ov02c10_check_hwcfg(&client->dev, ov02c10);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov02c10_get_pm_resources(&client->dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ov02c10->regmap = devm_cci_regmap_init_i2c(client, 16);
|
|
+ if (IS_ERR(ov02c10->regmap))
|
|
+ return PTR_ERR(ov02c10->regmap);
|
|
+
|
|
+ ret = ov02c10_power_on(&client->dev);
|
|
+ if (ret) {
|
|
+ dev_err_probe(&client->dev, ret, "failed to power on\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = ov02c10_identify_module(ov02c10);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to find sensor: %d", ret);
|
|
+ goto probe_error_power_off;
|
|
+ }
|
|
+
|
|
+ ret = ov02c10_init_controls(ov02c10);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to init controls: %d", ret);
|
|
+ goto probe_error_v4l2_ctrl_handler_free;
|
|
+ }
|
|
+
|
|
+ ov02c10->sd.internal_ops = &ov02c10_internal_ops;
|
|
+ ov02c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
+ ov02c10->sd.entity.ops = &ov02c10_subdev_entity_ops;
|
|
+ ov02c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
|
|
+ ov02c10->pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
+ ret = media_entity_pads_init(&ov02c10->sd.entity, 1, &ov02c10->pad);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to init entity pads: %d", ret);
|
|
+ goto probe_error_v4l2_ctrl_handler_free;
|
|
+ }
|
|
+
|
|
+ ov02c10->sd.state_lock = ov02c10->ctrl_handler.lock;
|
|
+ ret = v4l2_subdev_init_finalize(&ov02c10->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "failed to init subdev: %d", ret);
|
|
+ goto probe_error_media_entity_cleanup;
|
|
+ }
|
|
+
|
|
+ pm_runtime_set_active(&client->dev);
|
|
+ pm_runtime_enable(&client->dev);
|
|
+
|
|
+ ret = v4l2_async_register_subdev_sensor(&ov02c10->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "failed to register V4L2 subdev: %d",
|
|
+ ret);
|
|
+ goto probe_error_v4l2_subdev_cleanup;
|
|
+ }
|
|
+
|
|
+ pm_runtime_idle(&client->dev);
|
|
+ return 0;
|
|
+
|
|
+probe_error_v4l2_subdev_cleanup:
|
|
+ pm_runtime_disable(&client->dev);
|
|
+ pm_runtime_set_suspended(&client->dev);
|
|
+ v4l2_subdev_cleanup(&ov02c10->sd);
|
|
+
|
|
+probe_error_media_entity_cleanup:
|
|
+ media_entity_cleanup(&ov02c10->sd.entity);
|
|
+
|
|
+probe_error_v4l2_ctrl_handler_free:
|
|
+ v4l2_ctrl_handler_free(ov02c10->sd.ctrl_handler);
|
|
+
|
|
+probe_error_power_off:
|
|
+ ov02c10_power_off(&client->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static DEFINE_RUNTIME_DEV_PM_OPS(ov02c10_pm_ops, ov02c10_power_off,
|
|
+ ov02c10_power_on, NULL);
|
|
+
|
|
+#ifdef CONFIG_ACPI
|
|
+static const struct acpi_device_id ov02c10_acpi_ids[] = {
|
|
+ { "OVTI02C1" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(acpi, ov02c10_acpi_ids);
|
|
+#endif
|
|
+
|
|
+static const struct of_device_id ov02c10_of_match[] = {
|
|
+ { .compatible = "ovti,ov02c10" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, ov02c10_of_match);
|
|
+
|
|
+static struct i2c_driver ov02c10_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "ov02c10",
|
|
+ .pm = pm_sleep_ptr(&ov02c10_pm_ops),
|
|
+ .acpi_match_table = ACPI_PTR(ov02c10_acpi_ids),
|
|
+ .of_match_table = ov02c10_of_match,
|
|
+ },
|
|
+ .probe = ov02c10_probe,
|
|
+ .remove = ov02c10_remove,
|
|
+};
|
|
+
|
|
+module_i2c_driver(ov02c10_i2c_driver);
|
|
+
|
|
+MODULE_AUTHOR("Hao Yao <hao.yao@intel.com>");
|
|
+MODULE_AUTHOR("Heimir Thor Sverrisson <heimir.sverrisson@gmail.com>");
|
|
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
|
|
+MODULE_DESCRIPTION("OmniVision OV02C10 sensor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/i2c/ov02e10.c b/drivers/media/i2c/ov02e10.c
|
|
new file mode 100644
|
|
index 000000000000..d74dc62e189d
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/ov02e10.c
|
|
@@ -0,0 +1,969 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+// Copyright (c) 2023 Intel Corporation.
|
|
+
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <media/v4l2-cci.h>
|
|
+#include <media/v4l2-ctrls.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+
|
|
+#define OV02E10_LINK_FREQ_360MHZ 360000000ULL
|
|
+#define OV02E10_SCLK 36000000LL
|
|
+#define OV02E10_MCLK 19200000
|
|
+#define OV02E10_DATA_LANES 2
|
|
+#define OV02E10_RGB_DEPTH 10
|
|
+
|
|
+#define OV02E10_REG_PAGE_FLAG CCI_REG8(0xfd)
|
|
+#define OV02E10_PAGE_0 0x0
|
|
+#define OV02E10_PAGE_1 0x1
|
|
+#define OV02E10_PAGE_2 0x2
|
|
+#define OV02E10_PAGE_3 0x3
|
|
+#define OV02E10_PAGE_5 0x4
|
|
+#define OV02E10_PAGE_7 0x5
|
|
+#define OV02E10_PAGE_8 0x6
|
|
+#define OV02E10_PAGE_9 0xF
|
|
+#define OV02E10_PAGE_D 0x8
|
|
+#define OV02E10_PAGE_E 0x9
|
|
+#define OV02E10_PAGE_F 0xA
|
|
+
|
|
+#define OV02E10_REG_CHIP_ID CCI_REG32(0x00)
|
|
+#define OV02E10_CHIP_ID 0x45025610
|
|
+
|
|
+/* Horizontal and vertical flip */
|
|
+#define OV02E10_REG_ORIENTATION CCI_REG8(0x32)
|
|
+
|
|
+/* vertical-timings from sensor */
|
|
+#define OV02E10_REG_VTS CCI_REG16(0x35)
|
|
+#define OV02E10_VTS_DEF 2244
|
|
+#define OV02E10_VTS_MIN 2244
|
|
+#define OV02E10_VTS_MAX 0x7fff
|
|
+
|
|
+/* horizontal-timings from sensor */
|
|
+#define OV02E10_REG_HTS CCI_REG16(0x37)
|
|
+
|
|
+/* Exposure controls from sensor */
|
|
+#define OV02E10_REG_EXPOSURE CCI_REG16(0x03)
|
|
+#define OV02E10_EXPOSURE_MIN 1
|
|
+#define OV02E10_EXPOSURE_MAX_MARGIN 2
|
|
+#define OV02E10_EXPOSURE_STEP 1
|
|
+
|
|
+/* Analog gain controls from sensor */
|
|
+#define OV02E10_REG_ANALOG_GAIN CCI_REG8(0x24)
|
|
+#define OV02E10_ANAL_GAIN_MIN 0x10
|
|
+#define OV02E10_ANAL_GAIN_MAX 0xf8
|
|
+#define OV02E10_ANAL_GAIN_STEP 1
|
|
+
|
|
+/* Digital gain controls from sensor */
|
|
+#define OV02E10_REG_DIGITAL_GAIN CCI_REG16(0x21)
|
|
+#define OV02E10_DGTL_GAIN_MIN 256
|
|
+#define OV02E10_DGTL_GAIN_MAX 1020
|
|
+#define OV02E10_DGTL_GAIN_STEP 1
|
|
+#define OV02E10_DGTL_GAIN_DEFAULT 256
|
|
+
|
|
+/* Register update control */
|
|
+#define OV02E10_REG_COMMAND_UPDATE CCI_REG8(0xE7)
|
|
+#define OV02E10_COMMAND_UPDATE 0x00
|
|
+#define OV02E10_COMMAND_HOLD 0x01
|
|
+
|
|
+/* Test Pattern Control */
|
|
+#define OV02E10_REG_TEST_PATTERN CCI_REG8(0x12)
|
|
+#define OV02E10_TEST_PATTERN_ENABLE BIT(0)
|
|
+#define OV02E10_TEST_PATTERN_BAR_SHIFT 1
|
|
+
|
|
+struct reg_sequence_list {
|
|
+ u32 num_regs;
|
|
+ const struct reg_sequence *regs;
|
|
+};
|
|
+
|
|
+struct ov02e10_mode {
|
|
+ /* Frame width in pixels */
|
|
+ u32 width;
|
|
+
|
|
+ /* Frame height in pixels */
|
|
+ u32 height;
|
|
+
|
|
+ /* Horizontal timining size */
|
|
+ u32 hts;
|
|
+
|
|
+ /* Default vertical timing */
|
|
+ u32 vts_def;
|
|
+
|
|
+ /* Min vertical timining size */
|
|
+ u32 vts_min;
|
|
+
|
|
+ /* Sensor register settings for this resolution */
|
|
+ const struct reg_sequence_list reg_list;
|
|
+};
|
|
+
|
|
+static const struct reg_sequence mode_1928x1088_30fps_2lane[] = {
|
|
+ { 0xfd, 0x00 },
|
|
+ { 0x20, 0x00 },
|
|
+ { 0x20, 0x0b },
|
|
+ { 0x21, 0x02 },
|
|
+ { 0x10, 0x23 },
|
|
+ { 0xc5, 0x04 },
|
|
+ { 0x21, 0x00 },
|
|
+ { 0x14, 0x96 },
|
|
+ { 0x17, 0x01 },
|
|
+ { 0xfd, 0x01 },
|
|
+ { 0x03, 0x00 },
|
|
+ { 0x04, 0x04 },
|
|
+ { 0x05, 0x04 },
|
|
+ { 0x06, 0x62 },
|
|
+ { 0x07, 0x01 },
|
|
+ { 0x22, 0x80 },
|
|
+ { 0x24, 0xff },
|
|
+ { 0x40, 0xc6 },
|
|
+ { 0x41, 0x18 },
|
|
+ { 0x45, 0x3f },
|
|
+ { 0x48, 0x0c },
|
|
+ { 0x4c, 0x08 },
|
|
+ { 0x51, 0x12 },
|
|
+ { 0x52, 0x10 },
|
|
+ { 0x57, 0x98 },
|
|
+ { 0x59, 0x06 },
|
|
+ { 0x5a, 0x04 },
|
|
+ { 0x5c, 0x38 },
|
|
+ { 0x5e, 0x10 },
|
|
+ { 0x67, 0x11 },
|
|
+ { 0x7b, 0x04 },
|
|
+ { 0x81, 0x12 },
|
|
+ { 0x90, 0x51 },
|
|
+ { 0x91, 0x09 },
|
|
+ { 0x92, 0x21 },
|
|
+ { 0x93, 0x28 },
|
|
+ { 0x95, 0x54 },
|
|
+ { 0x9d, 0x20 },
|
|
+ { 0x9e, 0x04 },
|
|
+ { 0xb1, 0x9a },
|
|
+ { 0xb2, 0x86 },
|
|
+ { 0xb6, 0x3f },
|
|
+ { 0xb9, 0x30 },
|
|
+ { 0xc1, 0x01 },
|
|
+ { 0xc5, 0xa0 },
|
|
+ { 0xc6, 0x73 },
|
|
+ { 0xc7, 0x04 },
|
|
+ { 0xc8, 0x25 },
|
|
+ { 0xc9, 0x05 },
|
|
+ { 0xca, 0x28 },
|
|
+ { 0xcb, 0x00 },
|
|
+ { 0xcf, 0x16 },
|
|
+ { 0xd2, 0xd0 },
|
|
+ { 0xd7, 0x3f },
|
|
+ { 0xd8, 0x40 },
|
|
+ { 0xd9, 0x40 },
|
|
+ { 0xda, 0x44 },
|
|
+ { 0xdb, 0x3d },
|
|
+ { 0xdc, 0x3d },
|
|
+ { 0xdd, 0x3d },
|
|
+ { 0xde, 0x3d },
|
|
+ { 0xdf, 0xf0 },
|
|
+ { 0xea, 0x0f },
|
|
+ { 0xeb, 0x04 },
|
|
+ { 0xec, 0x29 },
|
|
+ { 0xee, 0x47 },
|
|
+ { 0xfd, 0x01 },
|
|
+ { 0x31, 0x01 },
|
|
+ { 0x27, 0x00 },
|
|
+ { 0x2f, 0x41 },
|
|
+ { 0xfd, 0x02 },
|
|
+ { 0xa1, 0x01 },
|
|
+ { 0xfd, 0x02 },
|
|
+ { 0x9a, 0x03 },
|
|
+ { 0xfd, 0x03 },
|
|
+ { 0x9d, 0x0f },
|
|
+ { 0xfd, 0x07 },
|
|
+ { 0x42, 0x00 },
|
|
+ { 0x43, 0xad },
|
|
+ { 0x44, 0x00 },
|
|
+ { 0x45, 0xa8 },
|
|
+ { 0x46, 0x00 },
|
|
+ { 0x47, 0xa8 },
|
|
+ { 0x48, 0x00 },
|
|
+ { 0x49, 0xad },
|
|
+ { 0xfd, 0x00 },
|
|
+ { 0xc4, 0x01 },
|
|
+ { 0xfd, 0x01 },
|
|
+ { 0x33, 0x03 },
|
|
+ { 0xfd, 0x00 },
|
|
+ { 0x20, 0x1f },
|
|
+};
|
|
+
|
|
+static const char *const ov02e10_test_pattern_menu[] = {
|
|
+ "Disabled",
|
|
+ "Color Bar",
|
|
+};
|
|
+
|
|
+static const s64 link_freq_menu_items[] = {
|
|
+ OV02E10_LINK_FREQ_360MHZ,
|
|
+};
|
|
+
|
|
+static const struct ov02e10_mode supported_modes[] = {
|
|
+ {
|
|
+ .width = 1928,
|
|
+ .height = 1088,
|
|
+ .hts = 534,
|
|
+ .vts_def = 2244,
|
|
+ .vts_min = 2244,
|
|
+ .reg_list = {
|
|
+ .num_regs = ARRAY_SIZE(mode_1928x1088_30fps_2lane),
|
|
+ .regs = mode_1928x1088_30fps_2lane,
|
|
+ },
|
|
+ },
|
|
+};
|
|
+
|
|
+static const char * const ov02e10_supply_names[] = {
|
|
+ "dovdd", /* Digital I/O power */
|
|
+ "avdd", /* Analog power */
|
|
+ "dvdd", /* Digital core power */
|
|
+};
|
|
+
|
|
+struct ov02e10 {
|
|
+ struct regmap *regmap;
|
|
+ struct v4l2_subdev sd;
|
|
+ struct media_pad pad;
|
|
+ struct v4l2_ctrl_handler ctrl_handler;
|
|
+
|
|
+ /* V4L2 Controls */
|
|
+ struct v4l2_ctrl *link_freq;
|
|
+ struct v4l2_ctrl *pixel_rate;
|
|
+ struct v4l2_ctrl *vblank;
|
|
+ struct v4l2_ctrl *hblank;
|
|
+ struct v4l2_ctrl *exposure;
|
|
+ struct v4l2_ctrl *vflip;
|
|
+ struct v4l2_ctrl *hflip;
|
|
+
|
|
+ struct clk *img_clk;
|
|
+ struct regulator_bulk_data supplies[ARRAY_SIZE(ov02e10_supply_names)];
|
|
+ struct gpio_desc *reset;
|
|
+
|
|
+ /* Current mode */
|
|
+ const struct ov02e10_mode *cur_mode;
|
|
+
|
|
+ /* MIPI lanes info */
|
|
+ u32 link_freq_index;
|
|
+ u8 mipi_lanes;
|
|
+};
|
|
+
|
|
+static inline struct ov02e10 *to_ov02e10(struct v4l2_subdev *subdev)
|
|
+{
|
|
+ return container_of(subdev, struct ov02e10, sd);
|
|
+}
|
|
+
|
|
+static u64 to_pixel_rate(u32 f_index)
|
|
+{
|
|
+ u64 pixel_rate = link_freq_menu_items[f_index] * 2 * OV02E10_DATA_LANES;
|
|
+
|
|
+ do_div(pixel_rate, OV02E10_RGB_DEPTH);
|
|
+
|
|
+ return pixel_rate;
|
|
+}
|
|
+
|
|
+static u64 to_pixels_per_line(u32 hts, u32 f_index)
|
|
+{
|
|
+ u64 ppl = hts * to_pixel_rate(f_index);
|
|
+
|
|
+ do_div(ppl, OV02E10_SCLK);
|
|
+
|
|
+ return ppl;
|
|
+}
|
|
+
|
|
+static void ov02e10_test_pattern(struct ov02e10 *ov02e10, u32 pattern, int *pret)
|
|
+{
|
|
+ if (pattern)
|
|
+ pattern = pattern << OV02E10_TEST_PATTERN_BAR_SHIFT |
|
|
+ OV02E10_TEST_PATTERN_ENABLE;
|
|
+
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_TEST_PATTERN, pattern, pret);
|
|
+}
|
|
+
|
|
+static int ov02e10_set_ctrl(struct v4l2_ctrl *ctrl)
|
|
+{
|
|
+ struct ov02e10 *ov02e10 = container_of(ctrl->handler,
|
|
+ struct ov02e10, ctrl_handler);
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&ov02e10->sd);
|
|
+ s64 exposure_max;
|
|
+ int ret;
|
|
+
|
|
+ /* Propagate change of current control to all related controls */
|
|
+ if (ctrl->id == V4L2_CID_VBLANK) {
|
|
+ /* Update max exposure while meeting expected vblanking */
|
|
+ exposure_max = ov02e10->cur_mode->height + ctrl->val -
|
|
+ OV02E10_EXPOSURE_MAX_MARGIN;
|
|
+ ret = __v4l2_ctrl_modify_range(ov02e10->exposure,
|
|
+ ov02e10->exposure->minimum,
|
|
+ exposure_max,
|
|
+ ov02e10->exposure->step,
|
|
+ exposure_max);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* V4L2 controls values will be applied only when power is already up */
|
|
+ if (!pm_runtime_get_if_in_use(&client->dev))
|
|
+ return 0;
|
|
+
|
|
+ ret = cci_write(ov02e10->regmap, OV02E10_REG_COMMAND_UPDATE,
|
|
+ OV02E10_COMMAND_HOLD, NULL);
|
|
+
|
|
+ switch (ctrl->id) {
|
|
+ case V4L2_CID_ANALOGUE_GAIN:
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_1, &ret);
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_ANALOG_GAIN,
|
|
+ ctrl->val, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_DIGITAL_GAIN:
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_1, &ret);
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_DIGITAL_GAIN,
|
|
+ ctrl->val, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_EXPOSURE:
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_1, &ret);
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_EXPOSURE,
|
|
+ ctrl->val, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_HFLIP:
|
|
+ case V4L2_CID_VFLIP:
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_1, &ret);
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_ORIENTATION,
|
|
+ ov02e10->hflip->val | ov02e10->vflip->val << 1, &ret);
|
|
+ break;
|
|
+ case V4L2_CID_VBLANK:
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_1, &ret);
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_VTS,
|
|
+ ov02e10->cur_mode->height + ctrl->val, &ret);
|
|
+ break;
|
|
+
|
|
+ case V4L2_CID_TEST_PATTERN:
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_1, &ret);
|
|
+ ov02e10_test_pattern(ov02e10, ctrl->val, &ret);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_COMMAND_UPDATE,
|
|
+ OV02E10_COMMAND_UPDATE, &ret);
|
|
+
|
|
+ pm_runtime_put(&client->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_ctrl_ops ov02e10_ctrl_ops = {
|
|
+ .s_ctrl = ov02e10_set_ctrl,
|
|
+};
|
|
+
|
|
+static int ov02e10_init_controls(struct ov02e10 *ov02e10)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&ov02e10->sd);
|
|
+ struct v4l2_ctrl_handler *ctrl_hdlr = &ov02e10->ctrl_handler;
|
|
+ const struct ov02e10_mode *mode = ov02e10->cur_mode;
|
|
+ u32 vblank_min, vblank_max, vblank_def;
|
|
+ struct v4l2_fwnode_device_properties props;
|
|
+ s64 exposure_max, h_blank, pixel_rate;
|
|
+ int ret;
|
|
+
|
|
+ v4l2_ctrl_handler_init(ctrl_hdlr, 12);
|
|
+
|
|
+ ov02e10->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
|
|
+ &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_LINK_FREQ,
|
|
+ ov02e10->link_freq_index,
|
|
+ 0, link_freq_menu_items);
|
|
+ if (ov02e10->link_freq)
|
|
+ ov02e10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+
|
|
+ pixel_rate = to_pixel_rate(ov02e10->link_freq_index);
|
|
+ ov02e10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_PIXEL_RATE, 0,
|
|
+ pixel_rate, 1, pixel_rate);
|
|
+
|
|
+ vblank_min = mode->vts_min - mode->height;
|
|
+ vblank_max = OV02E10_VTS_MAX - mode->height;
|
|
+ vblank_def = mode->vts_def - mode->height;
|
|
+ ov02e10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_VBLANK, vblank_min,
|
|
+ vblank_max, 1, vblank_def);
|
|
+
|
|
+ h_blank = mode->hts - mode->width;
|
|
+ ov02e10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_HBLANK, h_blank, h_blank,
|
|
+ 1, h_blank);
|
|
+ if (ov02e10->hblank)
|
|
+ ov02e10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
|
|
+ OV02E10_ANAL_GAIN_MIN, OV02E10_ANAL_GAIN_MAX,
|
|
+ OV02E10_ANAL_GAIN_STEP, OV02E10_ANAL_GAIN_MIN);
|
|
+
|
|
+ v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
|
|
+ OV02E10_DGTL_GAIN_MIN, OV02E10_DGTL_GAIN_MAX,
|
|
+ OV02E10_DGTL_GAIN_STEP, OV02E10_DGTL_GAIN_DEFAULT);
|
|
+
|
|
+ exposure_max = mode->vts_def - OV02E10_EXPOSURE_MAX_MARGIN;
|
|
+ ov02e10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_EXPOSURE,
|
|
+ OV02E10_EXPOSURE_MIN,
|
|
+ exposure_max,
|
|
+ OV02E10_EXPOSURE_STEP,
|
|
+ exposure_max);
|
|
+
|
|
+ ov02e10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
|
|
+ if (ov02e10->hflip)
|
|
+ ov02e10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
|
|
+
|
|
+ ov02e10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
|
|
+ if (ov02e10->vflip)
|
|
+ ov02e10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
|
|
+
|
|
+ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov02e10_ctrl_ops,
|
|
+ V4L2_CID_TEST_PATTERN,
|
|
+ ARRAY_SIZE(ov02e10_test_pattern_menu) - 1,
|
|
+ 0, 0, ov02e10_test_pattern_menu);
|
|
+
|
|
+ ret = v4l2_fwnode_device_parse(&client->dev, &props);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov02e10_ctrl_ops, &props);
|
|
+
|
|
+ if (ctrl_hdlr->error)
|
|
+ return ctrl_hdlr->error;
|
|
+
|
|
+ ov02e10->sd.ctrl_handler = ctrl_hdlr;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ov02e10_update_pad_format(const struct ov02e10_mode *mode,
|
|
+ struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ fmt->width = mode->width;
|
|
+ fmt->height = mode->height;
|
|
+ fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
|
|
+ fmt->field = V4L2_FIELD_NONE;
|
|
+}
|
|
+
|
|
+static int ov02e10_set_stream_mode(struct ov02e10 *ov02e10, u8 val)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, OV02E10_PAGE_0, &ret);
|
|
+ cci_write(ov02e10->regmap, CCI_REG8(0xa0), val, &ret);
|
|
+ cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, OV02E10_PAGE_1, &ret);
|
|
+ cci_write(ov02e10->regmap, CCI_REG8(0x01), 0x02, &ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ov02e10_enable_streams(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state,
|
|
+ u32 pad, u64 streams_mask)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+ const struct reg_sequence_list *reg_list;
|
|
+ int ret;
|
|
+
|
|
+ ret = pm_runtime_resume_and_get(&client->dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ reg_list = &ov02e10->cur_mode->reg_list;
|
|
+ ret = regmap_multi_reg_write(ov02e10->regmap, reg_list->regs,
|
|
+ reg_list->num_regs);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to set mode\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = __v4l2_ctrl_handler_setup(ov02e10->sd.ctrl_handler);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ ret = ov02e10_set_stream_mode(ov02e10, 1);
|
|
+
|
|
+out:
|
|
+ if (ret)
|
|
+ pm_runtime_put(&client->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ov02e10_disable_streams(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *state,
|
|
+ u32 pad, u64 streams_mask)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+
|
|
+ ov02e10_set_stream_mode(ov02e10, 0);
|
|
+ pm_runtime_put(&client->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02e10_get_pm_resources(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+ int i;
|
|
+
|
|
+ ov02e10->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
|
+ if (IS_ERR(ov02e10->reset))
|
|
+ return dev_err_probe(dev, PTR_ERR(ov02e10->reset),
|
|
+ "failed to get reset gpio\n");
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ov02e10_supply_names); i++)
|
|
+ ov02e10->supplies[i].supply = ov02e10_supply_names[i];
|
|
+
|
|
+ return devm_regulator_bulk_get(dev, ARRAY_SIZE(ov02e10_supply_names),
|
|
+ ov02e10->supplies);
|
|
+}
|
|
+
|
|
+static int ov02e10_power_off(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+
|
|
+ if (ov02e10->reset)
|
|
+ gpiod_set_value_cansleep(ov02e10->reset, 1);
|
|
+
|
|
+ regulator_bulk_disable(ARRAY_SIZE(ov02e10_supply_names),
|
|
+ ov02e10->supplies);
|
|
+
|
|
+ clk_disable_unprepare(ov02e10->img_clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02e10_power_on(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_prepare_enable(ov02e10->img_clk);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable imaging clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = regulator_bulk_enable(ARRAY_SIZE(ov02e10_supply_names),
|
|
+ ov02e10->supplies);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable regulators\n");
|
|
+ goto disable_clk;
|
|
+ }
|
|
+
|
|
+ if (ov02e10->reset) {
|
|
+ usleep_range(5000, 5100);
|
|
+ gpiod_set_value_cansleep(ov02e10->reset, 0);
|
|
+ usleep_range(8000, 8100);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+disable_clk:
|
|
+ clk_disable_unprepare(ov02e10->img_clk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ov02e10_set_format(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+ const struct ov02e10_mode *mode;
|
|
+ s32 vblank_def, h_blank;
|
|
+ int ret = 0;
|
|
+
|
|
+ mode = v4l2_find_nearest_size(supported_modes,
|
|
+ ARRAY_SIZE(supported_modes),
|
|
+ width, height, fmt->format.width,
|
|
+ fmt->format.height);
|
|
+
|
|
+ ov02e10_update_pad_format(mode, &fmt->format);
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
+ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
|
|
+ } else {
|
|
+ ov02e10->cur_mode = mode;
|
|
+ ret = __v4l2_ctrl_s_ctrl(ov02e10->link_freq,
|
|
+ ov02e10->link_freq_index);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = __v4l2_ctrl_s_ctrl_int64(ov02e10->pixel_rate,
|
|
+ to_pixel_rate(ov02e10->link_freq_index));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Update limits and set FPS to default */
|
|
+ vblank_def = mode->vts_def - mode->height;
|
|
+ ret = __v4l2_ctrl_modify_range(ov02e10->vblank,
|
|
+ mode->vts_min - mode->height,
|
|
+ OV02E10_VTS_MAX - mode->height,
|
|
+ 1, vblank_def);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = __v4l2_ctrl_s_ctrl(ov02e10->vblank, vblank_def);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ h_blank = to_pixels_per_line(mode->hts, ov02e10->link_freq_index);
|
|
+ h_blank -= mode->width;
|
|
+ ret = __v4l2_ctrl_modify_range(ov02e10->hblank, h_blank,
|
|
+ h_blank, 1, h_blank);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ov02e10_get_format(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct ov02e10 *ov02e10 = to_ov02e10(sd);
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
|
|
+ fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad);
|
|
+ else
|
|
+ ov02e10_update_pad_format(ov02e10->cur_mode, &fmt->format);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02e10_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ if (code->index > 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02e10_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(supported_modes))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
|
|
+ return -EINVAL;
|
|
+
|
|
+ fse->min_width = supported_modes[fse->index].width;
|
|
+ fse->max_width = fse->min_width;
|
|
+ fse->min_height = supported_modes[fse->index].height;
|
|
+ fse->max_height = fse->min_height;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02e10_init_state(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state)
|
|
+{
|
|
+ ov02e10_update_pad_format(&supported_modes[0],
|
|
+ v4l2_subdev_state_get_format(sd_state, 0));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops ov02e10_video_ops = {
|
|
+ .s_stream = v4l2_subdev_s_stream_helper,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops ov02e10_pad_ops = {
|
|
+ .set_fmt = ov02e10_set_format,
|
|
+ .get_fmt = ov02e10_get_format,
|
|
+ .enum_mbus_code = ov02e10_enum_mbus_code,
|
|
+ .enum_frame_size = ov02e10_enum_frame_size,
|
|
+ .enable_streams = ov02e10_enable_streams,
|
|
+ .disable_streams = ov02e10_disable_streams,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops ov02e10_subdev_ops = {
|
|
+ .video = &ov02e10_video_ops,
|
|
+ .pad = &ov02e10_pad_ops,
|
|
+};
|
|
+
|
|
+static const struct media_entity_operations ov02e10_subdev_entity_ops = {
|
|
+ .link_validate = v4l2_subdev_link_validate,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_internal_ops ov02e10_internal_ops = {
|
|
+ .init_state = ov02e10_init_state,
|
|
+};
|
|
+
|
|
+static int ov02e10_identify_module(struct ov02e10 *ov02e10)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&ov02e10->sd);
|
|
+ int ret;
|
|
+ u64 val;
|
|
+
|
|
+ ret = cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG,
|
|
+ OV02E10_PAGE_0, NULL);
|
|
+ cci_read(ov02e10->regmap, OV02E10_REG_CHIP_ID, &val, &ret);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (val != OV02E10_CHIP_ID) {
|
|
+ dev_err(&client->dev, "chip id mismatch: %x!=%x\n",
|
|
+ OV02E10_CHIP_ID, (u32)val);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ov02e10_check_hwcfg(struct device *dev, struct ov02e10 *ov02e10)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint bus_cfg = {
|
|
+ .bus_type = V4L2_MBUS_CSI2_DPHY
|
|
+ };
|
|
+ struct fwnode_handle *ep;
|
|
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
|
|
+ unsigned long link_freq_bitmap;
|
|
+ u32 ext_clk;
|
|
+ int ret;
|
|
+
|
|
+ ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
|
|
+ if (!ep)
|
|
+ return dev_err_probe(dev, -EPROBE_DEFER,
|
|
+ "waiting for fwnode graph endpoint\n");
|
|
+
|
|
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
|
|
+ fwnode_handle_put(ep);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret, "parsing endpoint failed\n");
|
|
+
|
|
+ ov02e10->img_clk = devm_clk_get_optional(dev, NULL);
|
|
+ if (IS_ERR(ov02e10->img_clk)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(ov02e10->img_clk),
|
|
+ "failed to get imaging clock\n");
|
|
+ goto out_err;
|
|
+ }
|
|
+
|
|
+ if (ov02e10->img_clk) {
|
|
+ ext_clk = clk_get_rate(ov02e10->img_clk);
|
|
+ } else {
|
|
+ ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
|
|
+ &ext_clk);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "can't get clock frequency\n");
|
|
+ goto out_err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (ext_clk != OV02E10_MCLK) {
|
|
+ dev_err(dev, "external clock %d is not supported\n",
|
|
+ ext_clk);
|
|
+ ret = -EINVAL;
|
|
+ goto out_err;
|
|
+ }
|
|
+
|
|
+ if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV02E10_DATA_LANES) {
|
|
+ dev_err(dev, "number of CSI2 data lanes %d is not supported\n",
|
|
+ bus_cfg.bus.mipi_csi2.num_data_lanes);
|
|
+ ret = -EINVAL;
|
|
+ goto out_err;
|
|
+ }
|
|
+
|
|
+ if (!bus_cfg.nr_of_link_frequencies) {
|
|
+ dev_err(dev, "no link frequencies defined\n");
|
|
+ ret = -EINVAL;
|
|
+ goto out_err;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
|
|
+ bus_cfg.nr_of_link_frequencies,
|
|
+ link_freq_menu_items,
|
|
+ ARRAY_SIZE(link_freq_menu_items),
|
|
+ &link_freq_bitmap);
|
|
+ if (ret)
|
|
+ goto out_err;
|
|
+
|
|
+ /* v4l2_link_freq_to_bitmap() guarantees at least 1 bit is set */
|
|
+ ov02e10->link_freq_index = ffs(link_freq_bitmap) - 1;
|
|
+ ov02e10->mipi_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
|
|
+
|
|
+out_err:
|
|
+ v4l2_fwnode_endpoint_free(&bus_cfg);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void ov02e10_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)) {
|
|
+ ov02e10_power_off(&client->dev);
|
|
+ pm_runtime_set_suspended(&client->dev);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int ov02e10_probe(struct i2c_client *client)
|
|
+{
|
|
+ struct ov02e10 *ov02e10;
|
|
+ int ret;
|
|
+
|
|
+ ov02e10 = devm_kzalloc(&client->dev, sizeof(*ov02e10), GFP_KERNEL);
|
|
+ if (!ov02e10)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ v4l2_i2c_subdev_init(&ov02e10->sd, client, &ov02e10_subdev_ops);
|
|
+
|
|
+ /* Check HW config */
|
|
+ ret = ov02e10_check_hwcfg(&client->dev, ov02e10);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Initialize subdev */
|
|
+ ov02e10->regmap = devm_cci_regmap_init_i2c(client, 8);
|
|
+ if (IS_ERR(ov02e10->regmap))
|
|
+ return PTR_ERR(ov02e10->regmap);
|
|
+
|
|
+ ret = ov02e10_get_pm_resources(&client->dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov02e10_power_on(&client->dev);
|
|
+ if (ret) {
|
|
+ dev_err_probe(&client->dev, ret, "failed to power on\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Check module identity */
|
|
+ ret = ov02e10_identify_module(ov02e10);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to find sensor: %d\n", ret);
|
|
+ goto probe_error_power_off;
|
|
+ }
|
|
+
|
|
+ ov02e10->cur_mode = &supported_modes[0];
|
|
+ ret = ov02e10_init_controls(ov02e10);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to init controls: %d\n", ret);
|
|
+ goto probe_error_v4l2_ctrl_handler_free;
|
|
+ }
|
|
+
|
|
+ /* Initialize subdev */
|
|
+ ov02e10->sd.internal_ops = &ov02e10_internal_ops;
|
|
+ ov02e10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
+ ov02e10->sd.entity.ops = &ov02e10_subdev_entity_ops;
|
|
+ ov02e10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
|
|
+
|
|
+ /* Initialize source pad */
|
|
+ ov02e10->pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
+ ret = media_entity_pads_init(&ov02e10->sd.entity, 1, &ov02e10->pad);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to init entity pads: %d", ret);
|
|
+ goto probe_error_v4l2_ctrl_handler_free;
|
|
+ }
|
|
+
|
|
+ ov02e10->sd.state_lock = ov02e10->ctrl_handler.lock;
|
|
+ ret = v4l2_subdev_init_finalize(&ov02e10->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "failed to init subdev: %d", ret);
|
|
+ goto probe_error_media_entity_cleanup;
|
|
+ }
|
|
+
|
|
+ pm_runtime_set_active(&client->dev);
|
|
+ pm_runtime_enable(&client->dev);
|
|
+
|
|
+ ret = v4l2_async_register_subdev_sensor(&ov02e10->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "failed to register V4L2 subdev: %d",
|
|
+ ret);
|
|
+ goto probe_error_v4l2_subdev_cleanup;
|
|
+ }
|
|
+
|
|
+ pm_runtime_idle(&client->dev);
|
|
+ return 0;
|
|
+
|
|
+probe_error_v4l2_subdev_cleanup:
|
|
+ pm_runtime_disable(&client->dev);
|
|
+ pm_runtime_set_suspended(&client->dev);
|
|
+ v4l2_subdev_cleanup(&ov02e10->sd);
|
|
+
|
|
+probe_error_media_entity_cleanup:
|
|
+ media_entity_cleanup(&ov02e10->sd.entity);
|
|
+
|
|
+probe_error_v4l2_ctrl_handler_free:
|
|
+ v4l2_ctrl_handler_free(ov02e10->sd.ctrl_handler);
|
|
+
|
|
+probe_error_power_off:
|
|
+ ov02e10_power_off(&client->dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static DEFINE_RUNTIME_DEV_PM_OPS(ov02e10_pm_ops, ov02e10_power_off,
|
|
+ ov02e10_power_on, NULL);
|
|
+
|
|
+static const struct acpi_device_id ov02e10_acpi_ids[] = {
|
|
+ { "OVTI02E1" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(acpi, ov02e10_acpi_ids);
|
|
+
|
|
+static const struct of_device_id ov02e10_of_match[] = {
|
|
+ { .compatible = "ovti,ov02e10" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, ov02e10_of_match);
|
|
+
|
|
+static struct i2c_driver ov02e10_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "ov02e10",
|
|
+ .pm = pm_sleep_ptr(&ov02e10_pm_ops),
|
|
+ .acpi_match_table = ov02e10_acpi_ids,
|
|
+ .of_match_table = ov02e10_of_match,
|
|
+ },
|
|
+ .probe = ov02e10_probe,
|
|
+ .remove = ov02e10_remove,
|
|
+};
|
|
+
|
|
+module_i2c_driver(ov02e10_i2c_driver);
|
|
+
|
|
+MODULE_AUTHOR("Jingjing Xiong <jingjing.xiong@intel.com>");
|
|
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
|
+MODULE_AUTHOR("Alan Stern <stern@rowland.harvard.edu>");
|
|
+MODULE_AUTHOR("Bryan O'Donoghue <bryan.odonoghue@linaro.org>");
|
|
+MODULE_DESCRIPTION("OmniVision OV02E10 sensor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
|
|
index 261f52b327e8..b768799221c4 100644
|
|
--- a/drivers/net/wireless/ath/ath12k/core.c
|
|
+++ b/drivers/net/wireless/ath/ath12k/core.c
|
|
@@ -1854,20 +1854,18 @@ void ath12k_core_hw_group_set_mlo_capable(struct ath12k_hw_group *ag)
|
|
|
|
lockdep_assert_held(&ag->mutex);
|
|
|
|
- /* If more than one devices are grouped, then inter MLO
|
|
- * functionality can work still independent of whether internally
|
|
- * each device supports single_chip_mlo or not.
|
|
- * Only when there is one device, then disable for WCN chipsets
|
|
- * till the required driver implementation is in place.
|
|
- */
|
|
if (ag->num_devices == 1) {
|
|
ab = ag->ab[0];
|
|
-
|
|
- /* WCN chipsets does not advertise in firmware features
|
|
- * hence skip checking
|
|
- */
|
|
- if (ab->hw_params->def_num_link)
|
|
+ /* QCN9274 firmware uses firmware IE for MLO advertisement */
|
|
+ if (ab->fw.fw_features_valid) {
|
|
+ ag->mlo_capable =
|
|
+ ath12k_fw_feature_supported(ab, ATH12K_FW_FEATURE_MLO);
|
|
return;
|
|
+ }
|
|
+
|
|
+ /* while WCN7850 firmware uses QMI single_chip_mlo_support bit */
|
|
+ ag->mlo_capable = ab->single_chip_mlo_support;
|
|
+ return;
|
|
}
|
|
|
|
ag->mlo_capable = true;
|
|
@@ -1880,7 +1878,7 @@ void ath12k_core_hw_group_set_mlo_capable(struct ath12k_hw_group *ag)
|
|
/* even if 1 device's firmware feature indicates MLO
|
|
* unsupported, make MLO unsupported for the whole group
|
|
*/
|
|
- if (!test_bit(ATH12K_FW_FEATURE_MLO, ab->fw.fw_features)) {
|
|
+ if (!ath12k_fw_feature_supported(ab, ATH12K_FW_FEATURE_MLO)) {
|
|
ag->mlo_capable = false;
|
|
return;
|
|
}
|
|
@@ -1989,6 +1987,7 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
|
|
ab->dev = dev;
|
|
ab->hif.bus = bus;
|
|
ab->qmi.num_radios = U8_MAX;
|
|
+ ab->single_chip_mlo_support = false;
|
|
|
|
/* Device index used to identify the devices in a group.
|
|
*
|
|
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
|
|
index f5f1ec796f7c..e0687fb113de 100644
|
|
--- a/drivers/net/wireless/ath/ath12k/core.h
|
|
+++ b/drivers/net/wireless/ath/ath12k/core.h
|
|
@@ -1061,6 +1061,7 @@ struct ath12k_base {
|
|
size_t m3_len;
|
|
|
|
DECLARE_BITMAP(fw_features, ATH12K_FW_FEATURE_COUNT);
|
|
+ bool fw_features_valid;
|
|
} fw;
|
|
|
|
const struct hal_rx_ops *hal_rx_ops;
|
|
@@ -1098,6 +1099,9 @@ struct ath12k_base {
|
|
enum ath12k_firmware_mode fw_mode;
|
|
struct ath12k_ftm_event_obj ftm_event_obj;
|
|
|
|
+ /* Denote whether MLO is possible within the device */
|
|
+ bool single_chip_mlo_support;
|
|
+
|
|
/* must be last */
|
|
u8 drv_priv[] __aligned(sizeof(void *));
|
|
};
|
|
diff --git a/drivers/net/wireless/ath/ath12k/fw.c b/drivers/net/wireless/ath/ath12k/fw.c
|
|
index 5be4b2d4a19d..5ac497f80cad 100644
|
|
--- a/drivers/net/wireless/ath/ath12k/fw.c
|
|
+++ b/drivers/net/wireless/ath/ath12k/fw.c
|
|
@@ -1,6 +1,6 @@
|
|
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
/*
|
|
- * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
+ * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include "core.h"
|
|
@@ -99,6 +99,8 @@ static int ath12k_fw_request_firmware_api_n(struct ath12k_base *ab,
|
|
__set_bit(i, ab->fw.fw_features);
|
|
}
|
|
|
|
+ ab->fw.fw_features_valid = true;
|
|
+
|
|
ath12k_dbg_dump(ab, ATH12K_DBG_BOOT, "features", "",
|
|
ab->fw.fw_features,
|
|
sizeof(ab->fw.fw_features));
|
|
@@ -169,3 +171,8 @@ void ath12k_fw_unmap(struct ath12k_base *ab)
|
|
release_firmware(ab->fw.fw);
|
|
memset(&ab->fw, 0, sizeof(ab->fw));
|
|
}
|
|
+
|
|
+bool ath12k_fw_feature_supported(struct ath12k_base *ab, enum ath12k_fw_features feat)
|
|
+{
|
|
+ return ab->fw.fw_features_valid && test_bit(feat, ab->fw.fw_features);
|
|
+}
|
|
diff --git a/drivers/net/wireless/ath/ath12k/fw.h b/drivers/net/wireless/ath/ath12k/fw.h
|
|
index 273c003eff3b..7afaefed5086 100644
|
|
--- a/drivers/net/wireless/ath/ath12k/fw.h
|
|
+++ b/drivers/net/wireless/ath/ath12k/fw.h
|
|
@@ -1,6 +1,6 @@
|
|
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
|
|
/*
|
|
- * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
+ * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#ifndef ATH12K_FW_H
|
|
@@ -32,5 +32,6 @@ enum ath12k_fw_features {
|
|
|
|
void ath12k_fw_map(struct ath12k_base *ab);
|
|
void ath12k_fw_unmap(struct ath12k_base *ab);
|
|
+bool ath12k_fw_feature_supported(struct ath12k_base *ab, enum ath12k_fw_features feat);
|
|
|
|
#endif /* ATH12K_FW_H */
|
|
diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
|
|
index 928991088721..a07e03f764ee 100644
|
|
--- a/drivers/net/wireless/ath/ath12k/pci.c
|
|
+++ b/drivers/net/wireless/ath/ath12k/pci.c
|
|
@@ -718,7 +718,7 @@ static void ath12k_pci_init_qmi_ce_config(struct ath12k_base *ab)
|
|
cfg->svc_to_ce_map_len = ab->hw_params->svc_to_ce_map_len;
|
|
ab->qmi.service_ins_id = ab->hw_params->qmi_service_ins_id;
|
|
|
|
- if (test_bit(ATH12K_FW_FEATURE_MULTI_QRTR_ID, ab->fw.fw_features)) {
|
|
+ if (ath12k_fw_feature_supported(ab, ATH12K_FW_FEATURE_MULTI_QRTR_ID)) {
|
|
ab_pci->qmi_instance =
|
|
u32_encode_bits(pci_domain_nr(bus), DOMAIN_NUMBER_MASK) |
|
|
u32_encode_bits(bus->number, BUS_NUMBER_MASK);
|
|
@@ -1472,7 +1472,7 @@ int ath12k_pci_power_up(struct ath12k_base *ab)
|
|
|
|
ath12k_pci_msi_enable(ab_pci);
|
|
|
|
- if (test_bit(ATH12K_FW_FEATURE_MULTI_QRTR_ID, ab->fw.fw_features))
|
|
+ if (ath12k_fw_feature_supported(ab, ATH12K_FW_FEATURE_MULTI_QRTR_ID))
|
|
ath12k_pci_update_qrtr_node_id(ab);
|
|
|
|
ret = ath12k_mhi_start(ab_pci);
|
|
diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c
|
|
index 348dbc81bad8..c5e31b954796 100644
|
|
--- a/drivers/net/wireless/ath/ath12k/qmi.c
|
|
+++ b/drivers/net/wireless/ath/ath12k/qmi.c
|
|
@@ -2264,6 +2264,9 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab)
|
|
goto out;
|
|
}
|
|
|
|
+ if (resp.single_chip_mlo_support_valid && resp.single_chip_mlo_support)
|
|
+ ab->single_chip_mlo_support = true;
|
|
+
|
|
if (!resp.num_phy_valid) {
|
|
ret = -ENODATA;
|
|
goto out;
|
|
@@ -2272,7 +2275,8 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab)
|
|
ab->qmi.num_radios = resp.num_phy;
|
|
|
|
ath12k_dbg(ab, ATH12K_DBG_QMI,
|
|
- "phy capability resp valid %d num_phy %d valid %d board_id %d\n",
|
|
+ "phy capability resp valid %d single_chip_mlo_support %d valid %d num_phy %d valid %d board_id %d\n",
|
|
+ resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support,
|
|
resp.num_phy_valid, resp.num_phy,
|
|
resp.board_id_valid, resp.board_id);
|
|
|
|
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
|
|
index d0f7b749b9a6..ba74f2d67495 100644
|
|
--- a/drivers/pci/quirks.c
|
|
+++ b/drivers/pci/quirks.c
|
|
@@ -4452,6 +4452,30 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9000,
|
|
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9084,
|
|
quirk_bridge_cavm_thrx2_pcie_root);
|
|
|
|
+/*
|
|
+ * 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/Makefile b/drivers/platform/x86/intel/int3472/Makefile
|
|
index a8aba07bf1dc..103661e6685d 100644
|
|
--- a/drivers/platform/x86/intel/int3472/Makefile
|
|
+++ b/drivers/platform/x86/intel/int3472/Makefile
|
|
@@ -1,7 +1,8 @@
|
|
obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \
|
|
intel_skl_int3472_tps68470.o \
|
|
intel_skl_int3472_common.o
|
|
-intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o led.o
|
|
+intel_skl_int3472_discrete-y := discrete.o discrete_quirks.o \
|
|
+ clk_and_regulator.o led.o
|
|
intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o
|
|
|
|
intel_skl_int3472_common-y += common.o
|
|
diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c
|
|
index 16e36ac0a7b4..c85cbfbc16c1 100644
|
|
--- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c
|
|
+++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c
|
|
@@ -5,7 +5,6 @@
|
|
#include <linux/clkdev.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/device.h>
|
|
-#include <linux/dmi.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/slab.h>
|
|
@@ -118,7 +117,7 @@ static const struct clk_ops skl_int3472_clock_ops = {
|
|
.recalc_rate = skl_int3472_clk_recalc_rate,
|
|
};
|
|
|
|
-int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
|
|
+static int skl_int3472_register_clock(struct int3472_discrete_device *int3472)
|
|
{
|
|
struct acpi_device *adev = int3472->adev;
|
|
struct clk_init_data init = {
|
|
@@ -127,12 +126,6 @@ int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
|
|
};
|
|
int ret;
|
|
|
|
- if (int3472->clock.cl)
|
|
- return 0; /* A GPIO controlled clk has already been registered */
|
|
-
|
|
- if (!acpi_check_dsm(adev->handle, &img_clk_guid, 0, BIT(1)))
|
|
- return 0; /* DSM clock control is not available */
|
|
-
|
|
init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(adev));
|
|
if (!init.name)
|
|
return -ENOMEM;
|
|
@@ -161,51 +154,26 @@ int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
|
|
return ret;
|
|
}
|
|
|
|
+int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
|
|
+{
|
|
+ if (int3472->clock.cl)
|
|
+ return 0; /* A GPIO controlled clk has already been registered */
|
|
+
|
|
+ if (!acpi_check_dsm(int3472->adev->handle, &img_clk_guid, 0, BIT(1)))
|
|
+ return 0; /* DSM clock control is not available */
|
|
+
|
|
+ return skl_int3472_register_clock(int3472);
|
|
+}
|
|
+
|
|
int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472,
|
|
struct gpio_desc *gpio)
|
|
{
|
|
- struct clk_init_data init = {
|
|
- .ops = &skl_int3472_clock_ops,
|
|
- .flags = CLK_GET_RATE_NOCACHE,
|
|
- };
|
|
- int ret;
|
|
-
|
|
if (int3472->clock.cl)
|
|
return -EBUSY;
|
|
|
|
int3472->clock.ena_gpio = gpio;
|
|
|
|
- init.name = kasprintf(GFP_KERNEL, "%s-clk",
|
|
- acpi_dev_name(int3472->adev));
|
|
- if (!init.name)
|
|
- return -ENOMEM;
|
|
-
|
|
- int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472);
|
|
-
|
|
- int3472->clock.clk_hw.init = &init;
|
|
- int3472->clock.clk = clk_register(&int3472->adev->dev,
|
|
- &int3472->clock.clk_hw);
|
|
- if (IS_ERR(int3472->clock.clk)) {
|
|
- ret = PTR_ERR(int3472->clock.clk);
|
|
- goto out_free_init_name;
|
|
- }
|
|
-
|
|
- int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL,
|
|
- int3472->sensor_name);
|
|
- if (!int3472->clock.cl) {
|
|
- ret = -ENOMEM;
|
|
- goto err_unregister_clk;
|
|
- }
|
|
-
|
|
- kfree(init.name);
|
|
- return 0;
|
|
-
|
|
-err_unregister_clk:
|
|
- clk_unregister(int3472->clock.clk);
|
|
-out_free_init_name:
|
|
- kfree(init.name);
|
|
-
|
|
- return ret;
|
|
+ return skl_int3472_register_clock(int3472);
|
|
}
|
|
|
|
void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472)
|
|
@@ -217,98 +185,72 @@ void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472)
|
|
clk_unregister(int3472->clock.clk);
|
|
}
|
|
|
|
-/*
|
|
- * The INT3472 device is going to be the only supplier of a regulator for
|
|
- * the sensor device. But unlike the clk framework the regulator framework
|
|
- * does not allow matching by consumer-device-name only.
|
|
- *
|
|
- * Ideally all sensor drivers would use "avdd" as supply-id. But for drivers
|
|
- * where this cannot be changed because another supply-id is already used in
|
|
- * e.g. DeviceTree files an alias for the other supply-id can be added here.
|
|
- *
|
|
- * Do not forget to update GPIO_REGULATOR_SUPPLY_MAP_COUNT when changing this.
|
|
- */
|
|
-static const char * const skl_int3472_regulator_map_supplies[] = {
|
|
- "avdd",
|
|
- "AVDD",
|
|
-};
|
|
-
|
|
-static_assert(ARRAY_SIZE(skl_int3472_regulator_map_supplies) ==
|
|
- GPIO_REGULATOR_SUPPLY_MAP_COUNT);
|
|
-
|
|
-/*
|
|
- * On some models there is a single GPIO regulator which is shared between
|
|
- * sensors and only listed in the ACPI resources of one sensor.
|
|
- * This DMI table contains the name of the second sensor. This is used to add
|
|
- * entries for the second sensor to the supply_map.
|
|
- */
|
|
-static const struct dmi_system_id skl_int3472_regulator_second_sensor[] = {
|
|
- {
|
|
- /* Lenovo Miix 510-12IKB */
|
|
- .matches = {
|
|
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
- DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 510-12IKB"),
|
|
- },
|
|
- .driver_data = "i2c-OVTI2680:00",
|
|
- },
|
|
- { }
|
|
-};
|
|
-
|
|
int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
|
|
- struct gpio_desc *gpio)
|
|
+ struct gpio_desc *gpio,
|
|
+ unsigned int enable_time,
|
|
+ const char *supply_name,
|
|
+ const char *second_sensor)
|
|
{
|
|
struct regulator_init_data init_data = { };
|
|
+ struct int3472_gpio_regulator *regulator;
|
|
struct regulator_config cfg = { };
|
|
- const char *second_sensor = NULL;
|
|
- const struct dmi_system_id *id;
|
|
int i, j;
|
|
|
|
- id = dmi_first_match(skl_int3472_regulator_second_sensor);
|
|
- if (id)
|
|
- second_sensor = id->driver_data;
|
|
+ if (int3472->n_regulator_gpios >= INT3472_MAX_REGULATORS) {
|
|
+ dev_err(int3472->dev, "Too many regulators mapped\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
|
|
- for (i = 0, j = 0; i < ARRAY_SIZE(skl_int3472_regulator_map_supplies); i++) {
|
|
- int3472->regulator.supply_map[j].supply = skl_int3472_regulator_map_supplies[i];
|
|
- int3472->regulator.supply_map[j].dev_name = int3472->sensor_name;
|
|
+ if (strlen(supply_name) >= GPIO_SUPPLY_NAME_LENGTH) {
|
|
+ dev_err(int3472->dev, "supply-name '%s' length too long\n", supply_name);
|
|
+ return -E2BIG;
|
|
+ }
|
|
+
|
|
+ regulator = &int3472->regulators[int3472->n_regulator_gpios];
|
|
+ string_upper(regulator->supply_name_upper, supply_name);
|
|
+
|
|
+ /* The below code assume that map-count is 2 (upper- and lower-case) */
|
|
+ static_assert(GPIO_REGULATOR_SUPPLY_MAP_COUNT == 2);
|
|
+
|
|
+ for (i = 0, j = 0; i < GPIO_REGULATOR_SUPPLY_MAP_COUNT; i++) {
|
|
+ const char *supply = i ? regulator->supply_name_upper : supply_name;
|
|
+
|
|
+ regulator->supply_map[j].supply = supply;
|
|
+ regulator->supply_map[j].dev_name = int3472->sensor_name;
|
|
j++;
|
|
|
|
if (second_sensor) {
|
|
- int3472->regulator.supply_map[j].supply =
|
|
- skl_int3472_regulator_map_supplies[i];
|
|
- int3472->regulator.supply_map[j].dev_name = second_sensor;
|
|
+ regulator->supply_map[j].supply = supply;
|
|
+ regulator->supply_map[j].dev_name = second_sensor;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
|
|
- init_data.consumer_supplies = int3472->regulator.supply_map;
|
|
+ init_data.consumer_supplies = regulator->supply_map;
|
|
init_data.num_consumer_supplies = j;
|
|
|
|
- snprintf(int3472->regulator.regulator_name,
|
|
- sizeof(int3472->regulator.regulator_name), "%s-regulator",
|
|
- acpi_dev_name(int3472->adev));
|
|
- snprintf(int3472->regulator.supply_name,
|
|
- GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0");
|
|
-
|
|
- int3472->regulator.rdesc = INT3472_REGULATOR(
|
|
- int3472->regulator.regulator_name,
|
|
- int3472->regulator.supply_name,
|
|
- &int3472_gpio_regulator_ops);
|
|
+ snprintf(regulator->regulator_name, sizeof(regulator->regulator_name), "%s-%s",
|
|
+ acpi_dev_name(int3472->adev), supply_name);
|
|
|
|
- int3472->regulator.gpio = gpio;
|
|
+ regulator->rdesc = INT3472_REGULATOR(regulator->regulator_name,
|
|
+ &int3472_gpio_regulator_ops,
|
|
+ enable_time, GPIO_REGULATOR_OFF_ON_DELAY);
|
|
|
|
cfg.dev = &int3472->adev->dev;
|
|
cfg.init_data = &init_data;
|
|
- cfg.ena_gpiod = int3472->regulator.gpio;
|
|
+ cfg.ena_gpiod = gpio;
|
|
|
|
- int3472->regulator.rdev = regulator_register(int3472->dev,
|
|
- &int3472->regulator.rdesc,
|
|
- &cfg);
|
|
+ regulator->rdev = regulator_register(int3472->dev, ®ulator->rdesc, &cfg);
|
|
+ if (IS_ERR(regulator->rdev))
|
|
+ return PTR_ERR(regulator->rdev);
|
|
|
|
- return PTR_ERR_OR_ZERO(int3472->regulator.rdev);
|
|
+ int3472->n_regulator_gpios++;
|
|
+ return 0;
|
|
}
|
|
|
|
void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472)
|
|
{
|
|
- regulator_unregister(int3472->regulator.rdev);
|
|
+ for (int i = 0; i < int3472->n_regulator_gpios; i++)
|
|
+ regulator_unregister(int3472->regulators[i].rdev);
|
|
}
|
|
diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h
|
|
index 145dec66df64..51b818e62a25 100644
|
|
--- a/drivers/platform/x86/intel/int3472/common.h
|
|
+++ b/drivers/platform/x86/intel/int3472/common.h
|
|
@@ -22,25 +22,39 @@
|
|
#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b
|
|
#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c
|
|
#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d
|
|
+#define INT3472_GPIO_TYPE_HANDSHAKE 0x12
|
|
|
|
#define INT3472_PDEV_MAX_NAME_LEN 23
|
|
#define INT3472_MAX_SENSOR_GPIOS 3
|
|
+#define INT3472_MAX_REGULATORS 3
|
|
|
|
-#define GPIO_REGULATOR_NAME_LENGTH 21
|
|
-#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9
|
|
+/* E.g. "avdd\0" */
|
|
+#define GPIO_SUPPLY_NAME_LENGTH 5
|
|
+/* 12 chars for acpi_dev_name() + "-", e.g. "ABCD1234:00-" */
|
|
+#define GPIO_REGULATOR_NAME_LENGTH (12 + GPIO_SUPPLY_NAME_LENGTH)
|
|
+/* lower- and upper-case mapping */
|
|
#define GPIO_REGULATOR_SUPPLY_MAP_COUNT 2
|
|
+/*
|
|
+ * Ensure the GPIO is driven low/high for at least 2 ms before changing.
|
|
+ *
|
|
+ * 2 ms has been chosen because it is the minimum time ovXXXX sensors need to
|
|
+ * have their reset line driven logical high to properly register a reset.
|
|
+ */
|
|
+#define GPIO_REGULATOR_ENABLE_TIME (2 * USEC_PER_MSEC)
|
|
+#define GPIO_REGULATOR_OFF_ON_DELAY (2 * USEC_PER_MSEC)
|
|
|
|
#define INT3472_LED_MAX_NAME_LEN 32
|
|
|
|
#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86
|
|
|
|
-#define INT3472_REGULATOR(_name, _supply, _ops) \
|
|
+#define INT3472_REGULATOR(_name, _ops, _enable_time, _off_on_delay) \
|
|
(const struct regulator_desc) { \
|
|
.name = _name, \
|
|
- .supply_name = _supply, \
|
|
.type = REGULATOR_VOLTAGE, \
|
|
.ops = _ops, \
|
|
.owner = THIS_MODULE, \
|
|
+ .enable_time = _enable_time, \
|
|
+ .off_on_delay = _off_on_delay, \
|
|
}
|
|
|
|
#define to_int3472_clk(hw) \
|
|
@@ -50,6 +64,7 @@
|
|
container_of(clk, struct int3472_discrete_device, clock)
|
|
|
|
struct acpi_device;
|
|
+struct dmi_system_id;
|
|
struct i2c_client;
|
|
struct platform_device;
|
|
|
|
@@ -70,6 +85,20 @@ struct int3472_cldb {
|
|
u8 reserved2[17];
|
|
};
|
|
|
|
+struct int3472_discrete_quirks {
|
|
+ /* For models where AVDD GPIO is shared between sensors */
|
|
+ const char *avdd_second_sensor;
|
|
+};
|
|
+
|
|
+struct int3472_gpio_regulator {
|
|
+ /* SUPPLY_MAP_COUNT * 2 to make room for second sensor mappings */
|
|
+ struct regulator_consumer_supply supply_map[GPIO_REGULATOR_SUPPLY_MAP_COUNT * 2];
|
|
+ char supply_name_upper[GPIO_SUPPLY_NAME_LENGTH];
|
|
+ char regulator_name[GPIO_REGULATOR_NAME_LENGTH];
|
|
+ struct regulator_dev *rdev;
|
|
+ struct regulator_desc rdesc;
|
|
+};
|
|
+
|
|
struct int3472_discrete_device {
|
|
struct acpi_device *adev;
|
|
struct device *dev;
|
|
@@ -78,15 +107,7 @@ struct int3472_discrete_device {
|
|
|
|
const struct int3472_sensor_config *sensor_config;
|
|
|
|
- struct int3472_gpio_regulator {
|
|
- /* SUPPLY_MAP_COUNT * 2 to make room for second sensor mappings */
|
|
- struct regulator_consumer_supply supply_map[GPIO_REGULATOR_SUPPLY_MAP_COUNT * 2];
|
|
- char regulator_name[GPIO_REGULATOR_NAME_LENGTH];
|
|
- char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH];
|
|
- struct gpio_desc *gpio;
|
|
- struct regulator_dev *rdev;
|
|
- struct regulator_desc rdesc;
|
|
- } regulator;
|
|
+ struct int3472_gpio_regulator regulators[INT3472_MAX_REGULATORS];
|
|
|
|
struct int3472_clock {
|
|
struct clk *clk;
|
|
@@ -104,11 +125,16 @@ struct int3472_discrete_device {
|
|
struct gpio_desc *gpio;
|
|
} pled;
|
|
|
|
+ struct int3472_discrete_quirks quirks;
|
|
+
|
|
unsigned int ngpios; /* how many GPIOs have we seen */
|
|
unsigned int n_sensor_gpios; /* how many have we mapped to sensor */
|
|
+ unsigned int n_regulator_gpios; /* how many have we mapped to a regulator */
|
|
struct gpiod_lookup_table gpios;
|
|
};
|
|
|
|
+extern const struct dmi_system_id skl_int3472_discrete_quirks[];
|
|
+
|
|
union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev,
|
|
char *id);
|
|
int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb);
|
|
@@ -122,7 +148,10 @@ int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472);
|
|
void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472);
|
|
|
|
int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
|
|
- struct gpio_desc *gpio);
|
|
+ struct gpio_desc *gpio,
|
|
+ unsigned int enable_time,
|
|
+ const char *supply_name,
|
|
+ const char *second_sensor);
|
|
void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472);
|
|
|
|
int skl_int3472_register_pled(struct int3472_discrete_device *int3472, struct gpio_desc *gpio);
|
|
diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c
|
|
index 30ff8f3ea1f5..394975f55d64 100644
|
|
--- a/drivers/platform/x86/intel/int3472/discrete.c
|
|
+++ b/drivers/platform/x86/intel/int3472/discrete.c
|
|
@@ -5,6 +5,7 @@
|
|
#include <linux/array_size.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/device.h>
|
|
+#include <linux/dmi.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/gpio/machine.h>
|
|
#include <linux/i2c.h>
|
|
@@ -145,9 +146,10 @@ static const struct int3472_gpio_map int3472_gpio_map[] = {
|
|
{ "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" },
|
|
};
|
|
|
|
-static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
|
|
+static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type,
|
|
const char **con_id, unsigned long *gpio_flags)
|
|
{
|
|
+ struct acpi_device *adev = int3472->sensor;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(int3472_gpio_map); i++) {
|
|
@@ -162,6 +164,9 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
|
|
if (!acpi_dev_hid_uid_match(adev, int3472_gpio_map[i].hid, NULL))
|
|
continue;
|
|
|
|
+ dev_dbg(int3472->dev, "mapping type 0x%02x pin to 0x%02x %s\n",
|
|
+ *type, int3472_gpio_map[i].type_to, int3472_gpio_map[i].con_id);
|
|
+
|
|
*type = int3472_gpio_map[i].type_to;
|
|
*gpio_flags = int3472_gpio_map[i].polarity_low ?
|
|
GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH;
|
|
@@ -187,7 +192,11 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
|
|
*gpio_flags = GPIO_ACTIVE_HIGH;
|
|
break;
|
|
case INT3472_GPIO_TYPE_POWER_ENABLE:
|
|
- *con_id = "power-enable";
|
|
+ *con_id = "avdd";
|
|
+ *gpio_flags = GPIO_ACTIVE_HIGH;
|
|
+ break;
|
|
+ case INT3472_GPIO_TYPE_HANDSHAKE:
|
|
+ *con_id = "dvdd";
|
|
*gpio_flags = GPIO_ACTIVE_HIGH;
|
|
break;
|
|
default:
|
|
@@ -262,7 +271,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
|
|
|
|
type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value);
|
|
|
|
- int3472_get_con_id_and_polarity(int3472->sensor, &type, &con_id, &gpio_flags);
|
|
+ int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags);
|
|
|
|
pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value);
|
|
/* Pin field is not really used under Windows and wraps around at 8 bits */
|
|
@@ -289,6 +298,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
|
|
case INT3472_GPIO_TYPE_CLK_ENABLE:
|
|
case INT3472_GPIO_TYPE_PRIVACY_LED:
|
|
case INT3472_GPIO_TYPE_POWER_ENABLE:
|
|
+ case INT3472_GPIO_TYPE_HANDSHAKE:
|
|
gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags);
|
|
if (IS_ERR(gpio)) {
|
|
ret = PTR_ERR(gpio);
|
|
@@ -310,9 +320,21 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
|
|
|
|
break;
|
|
case INT3472_GPIO_TYPE_POWER_ENABLE:
|
|
- ret = skl_int3472_register_regulator(int3472, gpio);
|
|
+ ret = skl_int3472_register_regulator(int3472, gpio,
|
|
+ GPIO_REGULATOR_ENABLE_TIME,
|
|
+ con_id,
|
|
+ int3472->quirks.avdd_second_sensor);
|
|
+ if (ret)
|
|
+ err_msg = "Failed to map power-enable to sensor\n";
|
|
+
|
|
+ break;
|
|
+ case INT3472_GPIO_TYPE_HANDSHAKE:
|
|
+ /* Setups using a handshake pin need 25 ms enable delay */
|
|
+ ret = skl_int3472_register_regulator(int3472, gpio,
|
|
+ 25 * USEC_PER_MSEC,
|
|
+ con_id, NULL);
|
|
if (ret)
|
|
- err_msg = "Failed to map regulator to sensor\n";
|
|
+ err_msg = "Failed to map handshake to sensor\n";
|
|
|
|
break;
|
|
default: /* Never reached */
|
|
@@ -378,13 +400,19 @@ static void skl_int3472_discrete_remove(struct platform_device *pdev)
|
|
static int skl_int3472_discrete_probe(struct platform_device *pdev)
|
|
{
|
|
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
|
|
+ const struct int3472_discrete_quirks *quirks = NULL;
|
|
struct int3472_discrete_device *int3472;
|
|
+ const struct dmi_system_id *id;
|
|
struct int3472_cldb cldb;
|
|
int ret;
|
|
|
|
if (!adev)
|
|
return -ENODEV;
|
|
|
|
+ id = dmi_first_match(skl_int3472_discrete_quirks);
|
|
+ if (id)
|
|
+ quirks = id->driver_data;
|
|
+
|
|
ret = skl_int3472_fill_cldb(adev, &cldb);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Couldn't fill CLDB structure\n");
|
|
@@ -408,6 +436,9 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev)
|
|
platform_set_drvdata(pdev, int3472);
|
|
int3472->clock.imgclk_index = cldb.clock_source;
|
|
|
|
+ if (quirks)
|
|
+ int3472->quirks = *quirks;
|
|
+
|
|
ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor,
|
|
&int3472->sensor_name);
|
|
if (ret)
|
|
diff --git a/drivers/platform/x86/intel/int3472/discrete_quirks.c b/drivers/platform/x86/intel/int3472/discrete_quirks.c
|
|
new file mode 100644
|
|
index 000000000000..bf88863803b2
|
|
--- /dev/null
|
|
+++ b/drivers/platform/x86/intel/int3472/discrete_quirks.c
|
|
@@ -0,0 +1,22 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/* Author: Hans de Goede <hansg@kernel.org> */
|
|
+
|
|
+#include <linux/dmi.h>
|
|
+
|
|
+#include "common.h"
|
|
+
|
|
+static const struct int3472_discrete_quirks lenovo_miix_510_quirks = {
|
|
+ .avdd_second_sensor = "i2c-OVTI2680:00",
|
|
+};
|
|
+
|
|
+const struct dmi_system_id skl_int3472_discrete_quirks[] = {
|
|
+ {
|
|
+ /* Lenovo Miix 510-12IKB */
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
+ DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 510-12IKB"),
|
|
+ },
|
|
+ .driver_data = (void *)&lenovo_miix_510_quirks,
|
|
+ },
|
|
+ { }
|
|
+};
|
|
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
|
|
index 89d5c4b17bc4..8cc874715933 100644
|
|
--- a/drivers/scsi/sd.c
|
|
+++ b/drivers/scsi/sd.c
|
|
@@ -121,6 +121,14 @@ static const char *sd_cache_types[] = {
|
|
"write back, no read (daft)"
|
|
};
|
|
|
|
+static const char *sd_probe_types[] = { "async", "sync" };
|
|
+
|
|
+static char sd_probe_type[6] = "async";
|
|
+module_param_string(probe, sd_probe_type, sizeof(sd_probe_type),
|
|
+ S_IRUGO|S_IWUSR);
|
|
+MODULE_PARM_DESC(probe, "async or sync. Setting to 'sync' disables asynchronous "
|
|
+ "device number assignments (sda, sdb, ...).");
|
|
+
|
|
static void sd_set_flush_flag(struct scsi_disk *sdkp,
|
|
struct queue_limits *lim)
|
|
{
|
|
@@ -4371,6 +4379,8 @@ static int __init init_sd(void)
|
|
goto err_out_class;
|
|
}
|
|
|
|
+ if (!strcmp(sd_probe_type, "sync"))
|
|
+ sd_template.gendrv.probe_type = PROBE_FORCE_SYNCHRONOUS;
|
|
err = scsi_register_driver(&sd_template.gendrv);
|
|
if (err)
|
|
goto err_out_driver;
|
|
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
|
|
index f71b807d71a7..ee8abbe5a43a 100644
|
|
--- a/drivers/usb/core/hub.c
|
|
+++ b/drivers/usb/core/hub.c
|
|
@@ -5857,6 +5857,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/include/linux/efi.h b/include/linux/efi.h
|
|
index 7d63d1d75f22..c7481fdedbdd 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 */
|
|
@@ -863,6 +865,14 @@ static inline int efi_range_is_wc(unsigned long start, unsigned long len)
|
|
#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
|
|
/*
|
|
@@ -874,6 +884,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)
|
|
@@ -895,6 +907,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;
|
|
@@ -909,6 +923,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
|
|
@@ -1124,13 +1139,6 @@ static inline bool efi_runtime_disabled(void) { return true; }
|
|
extern void efi_call_virt_check_flags(unsigned long flags, const void *caller);
|
|
extern unsigned long efi_call_virt_save_flags(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 bf3bbac4e02a..b9840b1a43fe 100644
|
|
--- a/include/linux/lsm_hook_defs.h
|
|
+++ b/include/linux/lsm_hook_defs.h
|
|
@@ -444,6 +444,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/rmi.h b/include/linux/rmi.h
|
|
index ab7eea01ab42..fff7c5f737fc 100644
|
|
--- a/include/linux/rmi.h
|
|
+++ b/include/linux/rmi.h
|
|
@@ -364,6 +364,7 @@ struct rmi_driver_data {
|
|
|
|
struct rmi4_attn_data attn_data;
|
|
DECLARE_KFIFO(attn_fifo, struct rmi4_attn_data, 16);
|
|
+ struct work_struct attn_work;
|
|
};
|
|
|
|
int rmi_register_transport_device(struct rmi_transport_dev *xport);
|
|
diff --git a/include/linux/security.h b/include/linux/security.h
|
|
index cc9b54d95d22..fce0e3457cf7 100644
|
|
--- a/include/linux/security.h
|
|
+++ b/include/linux/security.h
|
|
@@ -2390,4 +2390,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 a2ff4242e623..f0d2be1ee4f1 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 2fe73cda0bdd..f766681e1b5c 100644
|
|
--- a/scripts/Makefile.lib
|
|
+++ b/scripts/Makefile.lib
|
|
@@ -272,7 +272,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 98680e9cd7be..0880675cf517 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 d1fdd113450a..182e8090cfe8 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 e84ddf484010..d0501353a4b9 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 cf83afa1d879..aba751e7abff 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),
|
|
};
|