diff --git a/Documentation/ABI/testing/sysfs-devices-faux-tdx-host b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host new file mode 100644 index 000000000000..c9cb273abf32 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host @@ -0,0 +1,26 @@ +What: /sys/devices/faux/tdx_host/version +Contact: linux-coco@lists.linux.dev +Description: (RO) Report the version of the loaded TDX module. + Formatted as "major.minor.update". Used by TDX module + update tooling. Example: "1.2.03". + +What: /sys/devices/faux/tdx_host/seamldr_version +Contact: linux-coco@lists.linux.dev +Description: (RO) Report the version of the loaded P-SEAMLDR. + Formatted as a TDX module version. Used by TDX module + update tooling. + +What: /sys/devices/faux/tdx_host/num_remaining_updates +Contact: linux-coco@lists.linux.dev +Description: (RO) Report the number of remaining updates. TDX maintains a + log about each TDX module that has been loaded. This log has + a finite size, which limits the number of TDX module updates + that can be performed. + + After each successful update, the number reduces by one. Once it + reaches zero, further updates will fail until next reboot. The + number is always zero if the P-SEAMLDR doesn't support updates. + + See Intel Trust Domain Extensions - SEAM Loader (SEAMLDR) + Interface Specification, Chapter "SEAMLDR_INFO" and Chapter + "SEAMLDR.INSTALL" for more information. diff --git a/Documentation/arch/x86/tdx.rst b/Documentation/arch/x86/tdx.rst index ff6b110291bc..3303499ad4c6 100644 --- a/Documentation/arch/x86/tdx.rst +++ b/Documentation/arch/x86/tdx.rst @@ -73,6 +73,133 @@ initialize:: [..] virt/tdx: TDX-Module initialization failed ... +TDX module Runtime Update +------------------------- + +Similar to microcode, the BIOS generally has a copy of the TDX module +in flash. It loads this module image in to RAM at boot. However, just +like microcode, the BIOS-loaded TDX module might be out of date either +because the BIOS is old or the system has been up a long time. The +kernel can replace the BIOS version in RAM and load a different TDX +module. Kernel-loaded TDX modules do not affect the BIOS flash and do +not survive reboots. + +The TDX module is normally the only piece of software running in SEAM +mode with which the kernel interacts. But there is a second piece of +software which is used to load or update the TDX module: a persistent +SEAM loader (P-SEAMLDR). It runs in SEAM mode separately from the TDX +module. The kernel communicates with the P-SEAMLDR to perform TDX +module runtime updates. + +How to update the TDX module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Updating the TDX module is a complex process. Much of the logic and +policy is left to userspace. End users should use existing update +infrastructure provided by their distro. The Intel TDX Module Binaries +repository has a reference implementation of this logic: + + https://github.com/intel/confidential-computing.tdx.tdx-module.binaries/blob/main/version_select_and_load.py + +This section will now lay out roughly what is needed to implement a +userspace-driven TDX module update. Detailed documentation on the +tdx_host ABIs is available here:: + + Documentation/ABI/testing/sysfs-devices-faux-tdx-host + +and is not duplicated in this document. + +1. Check whether runtime update is supported at all + + Verify that the TDX module firmware upload interface is available:: + + /sys/class/firmware/tdx_module + + Note that this is the generic kernel firmware update ABI. It is + separate from the "tdx_host" device ABI itself. + +2. Check whether additional updates are possible. Verify that:: + + /sys/devices/faux/tdx_host/num_remaining_updates + + has a value greater than 0. If it is 0, the TDX update log might be + full. Reboot to reset this to a nonzero value. + +3. Choose a compatible TDX module image + + Choosing a compatible TDX module image is not trivial. There are both + hard compatibility requirements and policy choices to make. + + Hard compatibility requirements: + + - The update must be compatible with the kernel. + + The update must not change any TDX ABIs in any non-backward-compatible + way. It can introduce new features but must not require that the kernel + use new ABIs for existing features. It must ensure that the rest of the + system is not affected in any way. Software on the system must never + notice any behavioral changes. Attestation results should be identical + except for version changes. + + - The update must be compatible with the CPU. + + The set of supported CPU FMS values (family, model, stepping) is + encoded in the module image itself. In practice, module version series + are platform-specific. For example, the 1.5.x series runs on Sapphire + Rapids but not Granite Rapids, which needs 2.0.x. + + - The update must be compatible with the P-SEAMLDR. + + This information is provided in a metadata file, typically + mapping_file.json, released with the module image. Each module image + specifies the minimum required P-SEAMLDR version, and the update is + compatible only if the running P-SEAMLDR meets that requirement. + + The current version of the P-SEAMLDR can be read here:: + + /sys/devices/faux/tdx_host/seamldr_version + + - The update must be compatible with the running TDX module. + + Like P-SEAMLDR, each module image also specifies a minimum required + TDX module version. The running module must satisfy that requirement. + + The update software can read the current TDX module version here:: + + /sys/devices/faux/tdx_host/version + + Policy choices: + + - The update software chooses how to optimize its update. For instance, + it can optimize for fewer updates or for smaller version steps, + for example, 1.2.3 => 1.2.5 versus 1.2.3 => 1.2.4 => 1.2.5. + +4. Perform the update + + Run:: + + echo 1 > /sys/class/firmware/tdx_module/loading + cat > /sys/class/firmware/tdx_module/data + echo 0 > /sys/class/firmware/tdx_module/loading + + The files /sys/class/firmware/tdx_module/status and + /sys/class/firmware/tdx_module/error report update progress and error + information. + + After the update completes, the new module version is visible in + /sys/devices/faux/tdx_host/version. + +Impact on running TDs +~~~~~~~~~~~~~~~~~~~~~ + +TDX module runtime updates must have virtually no visible impact on running +TDs. Any TD visible impact is a TDX module bug. + +The main exception is the TEE_TCB_SVN_2 field in TD quotes, which +reflects the TCB of the currently running TDX module and therefore +changes after an update. By contrast, TEE_TCB_SVN reflects the TCB at TD +launch time and is not affected. + TDX Interaction to Other Kernel Components ------------------------------------------ @@ -138,13 +265,6 @@ If the platform has such erratum, the kernel prints additional message in machine check handler to tell user the machine check may be caused by kernel bug on TDX private memory. -Kexec -~~~~~~~ - -Currently kexec doesn't work on the TDX platforms with the aforementioned -erratum. It fails when loading the kexec kernel image. Otherwise it -works normally. - Interaction vs S3 and deeper states ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml b/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml index 23410aeca300..451538df63f7 100644 --- a/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml +++ b/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml @@ -45,6 +45,18 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 description: Number of daisy-chained shift registers + lines-initial-states: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Bitmask that specifies the initial state of each output line, written + by the driver before the gpiochip is registered. Bit N corresponds to + GPIO line N, following the convention already documented for + nxp,pcf8575. Because the 74HC595/74LVC594 family is push-pull output + only, a bit set to zero drives the line low and a bit set to one + drives it high. The bitmask covers up to 32 lines (four cascaded + registers); outputs beyond that come up zeroed. When the property is + absent all outputs come up low, preserving the previous behaviour. + enable-gpios: description: GPIO connected to the OE (Output Enable) pin. maxItems: 1 @@ -79,6 +91,7 @@ examples: gpio-controller; #gpio-cells = <2>; registers-number = <4>; + lines-initial-states = <0xffff0000>; spi-max-frequency = <100000>; }; }; diff --git a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml index 5e2496379a3c..de24bb361e9f 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml @@ -12,10 +12,11 @@ maintainers: properties: compatible: enum: + - xlnx,eio-gpio-1.0 + - xlnx,pmc-gpio-1.0 + - xlnx,versal-gpio-1.0 - xlnx,zynq-gpio-1.0 - xlnx,zynqmp-gpio-1.0 - - xlnx,versal-gpio-1.0 - - xlnx,pmc-gpio-1.0 reg: maxItems: 1 @@ -30,7 +31,7 @@ properties: gpio-line-names: description: strings describing the names of each gpio line - minItems: 58 + minItems: 52 maxItems: 174 interrupt-controller: true @@ -89,6 +90,16 @@ allOf: minItems: 116 maxItems: 116 + - if: + properties: + compatible: + enum: + - xlnx,eio-gpio-1.0 + then: + properties: + gpio-line-names: + maxItems: 52 + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml b/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml index 17748dd1015d..adeb3b3a2902 100644 --- a/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml @@ -85,6 +85,8 @@ properties: - nvidia,tegra194-gpio-aon - nvidia,tegra234-gpio - nvidia,tegra234-gpio-aon + - nvidia,tegra238-gpio + - nvidia,tegra238-gpio-aon - nvidia,tegra256-gpio - nvidia,tegra264-gpio - nvidia,tegra264-gpio-uphy @@ -163,6 +165,7 @@ allOf: - nvidia,tegra186-gpio - nvidia,tegra194-gpio - nvidia,tegra234-gpio + - nvidia,tegra238-gpio - nvidia,tegra256-gpio - nvidia,tegra264-gpio - nvidia,tegra264-gpio-uphy @@ -180,6 +183,7 @@ allOf: - nvidia,tegra186-gpio-aon - nvidia,tegra194-gpio-aon - nvidia,tegra234-gpio-aon + - nvidia,tegra238-gpio-aon - nvidia,tegra264-gpio-aon then: properties: @@ -192,6 +196,8 @@ allOf: compatible: contains: enum: + - nvidia,tegra238-gpio + - nvidia,tegra238-gpio-aon - nvidia,tegra264-gpio - nvidia,tegra264-gpio-uphy - nvidia,tegra264-gpio-aon diff --git a/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml b/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml index bba6f5b6606f..55069533f6d9 100644 --- a/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml @@ -95,6 +95,12 @@ patternProperties: '#interrupt-cells': const: 2 + patternProperties: + "^.+-hog(-[0-9]+)?$": + type: object + required: + - gpio-hog + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/gpio/waveshare,dsi-touch-gpio.yaml b/Documentation/devicetree/bindings/gpio/waveshare,dsi-touch-gpio.yaml new file mode 100644 index 000000000000..091e1fffcd47 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/waveshare,dsi-touch-gpio.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/waveshare,dsi-touch-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Waveshare GPIO controller on DSI TOUCH panels + +maintainers: + - Dmitry Baryshkov + +description: + Waveshare DSI TOUCH panel kits contain separate GPIO controller for toggling + power supplies and panel / touchscreen resets. + +properties: + compatible: + const: waveshare,dsi-touch-gpio + + reg: + maxItems: 1 + + gpio-controller: true + + '#gpio-cells': + const: 2 + +required: + - compatible + - reg + - gpio-controller + - "#gpio-cells" + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + wsgpio: gpio@45 { + compatible = "waveshare,dsi-touch-gpio"; + reg = <0x45>; + gpio-controller; + #gpio-cells = <2>; + }; + }; + + panel_avdd: regulator-panel-avdd { + compatible = "regulator-fixed"; + regulator-name = "panel-avdd"; + gpios = <&wsgpio 0 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + panel_iovcc: regulator-panel-iovcc { + compatible = "regulator-fixed"; + regulator-name = "panel-iovcc"; + gpios = <&wsgpio 4 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + panel_vcc: regulator-panel-vcc { + compatible = "regulator-fixed"; + regulator-name = "panel-vcc"; + gpios = <&wsgpio 8 GPIO_ACTIVE_HIGH>; + enable-active-high; + regulator-always-on; + }; +... diff --git a/Documentation/devicetree/bindings/i2c/i2c-davinci.txt b/Documentation/devicetree/bindings/i2c/i2c-davinci.txt deleted file mode 100644 index 6590501c53d4..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-davinci.txt +++ /dev/null @@ -1,43 +0,0 @@ -* Texas Instruments Davinci/Keystone I2C - -This file provides information, what the device node for the -davinci/keystone i2c interface contains. - -Required properties: -- compatible: "ti,davinci-i2c" or "ti,keystone-i2c"; -- reg : Offset and length of the register set for the device -- clocks: I2C functional clock phandle. - For 66AK2G this property should be set per binding, - Documentation/devicetree/bindings/clock/ti,sci-clk.yaml - -SoC-specific Required Properties: - -The following are mandatory properties for Keystone 2 66AK2G SoCs only: - -- power-domains: Should contain a phandle to a PM domain provider node - and an args specifier containing the I2C device id - value. This property is as per the binding, - Documentation/devicetree/bindings/soc/ti/sci-pm-domain.yaml - -Recommended properties : -- interrupts : standard interrupt property. -- clock-frequency : desired I2C bus clock frequency in Hz. -- ti,has-pfunc: boolean; if defined, it indicates that SoC supports PFUNC - registers. PFUNC registers allow to switch I2C pins to function as - GPIOs, so they can be toggled manually. - -Example (enbw_cmc board): - i2c@1c22000 { - compatible = "ti,davinci-i2c"; - reg = <0x22000 0x1000>; - clock-frequency = <100000>; - interrupts = <15>; - interrupt-parent = <&intc>; - #address-cells = <1>; - #size-cells = <0>; - - dtt@48 { - compatible = "national,lm75"; - reg = <0x48>; - }; - }; diff --git a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml index 67882ec6e06a..ee09c6d9c5f0 100644 --- a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml @@ -16,6 +16,7 @@ properties: compatible: enum: - loongson,ls2k-i2c + - loongson,ls2k0300-i2c - loongson,ls7a-i2c reg: @@ -24,6 +25,9 @@ properties: interrupts: maxItems: 1 + clocks: + maxItems: 1 + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml index 7c497a358e1d..1e8def25ec0e 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml @@ -26,6 +26,7 @@ properties: - items: - enum: + - qcom,glymur-cci - qcom,kaanapali-cci - qcom,milos-cci - qcom,qcm2290-cci @@ -35,6 +36,7 @@ properties: - qcom,sc8280xp-cci - qcom,sdm670-cci - qcom,sdm845-cci + - qcom,shikra-cci - qcom,sm6150-cci - qcom,sm6350-cci - qcom,sm8250-cci @@ -134,9 +136,11 @@ allOf: compatible: contains: enum: + - qcom,glymur-cci - qcom,kaanapali-cci - qcom,qcm2290-cci - qcom,qcs8300-cci + - qcom,shikra-cci - qcom,sm8750-cci then: properties: diff --git a/Documentation/devicetree/bindings/i2c/ti,davinci-i2c.yaml b/Documentation/devicetree/bindings/i2c/ti,davinci-i2c.yaml new file mode 100644 index 000000000000..b94ac06dce36 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/ti,davinci-i2c.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/ti,davinci-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DaVinci/Keystone I2C + +maintainers: + - Chaitanya Sabnis + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + - if: + properties: + compatible: + contains: + const: ti,keystone-i2c + then: + required: + - power-domains + +properties: + compatible: + enum: + - ti,davinci-i2c + - ti,keystone-i2c + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + power-domains: + maxItems: 1 + + ti,has-pfunc: + description: + Indicates that the SoC supports PFUNC registers, allowing I2C pins + to function as GPIOs for manual toggling. + type: boolean + +required: + - compatible + - reg + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + i2c@1c22000 { + compatible = "ti,davinci-i2c"; + reg = <0x01c22000 0x1000>; + clocks = <&i2c_clk>; + clock-frequency = <100000>; + interrupts = <15>; + #address-cells = <1>; + #size-cells = <0>; + + sensor@48 { + compatible = "national,lm75"; + reg = <0x48>; + }; + }; diff --git a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml index 8df327e52810..f1422a401b6b 100644 --- a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml @@ -15,7 +15,9 @@ allOf: properties: compatible: contains: - const: spacemit,k1-pwm + enum: + - spacemit,k1-pwm + - spacemit,k3-pwm then: properties: "#pwm-cells": @@ -26,6 +28,26 @@ allOf: const: 1 description: | Used for specifying the period length in nanoseconds. + - if: + properties: + compatible: + contains: + enum: + - spacemit,k3-pwm + then: + required: + - clock-names + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + else: + properties: + clocks: + maxItems: 1 + clock-names: + maxItems: 1 properties: compatible: @@ -36,7 +58,9 @@ properties: - marvell,pxa168-pwm - marvell,pxa910-pwm - items: - - const: spacemit,k1-pwm + - enum: + - spacemit,k1-pwm + - spacemit,k3-pwm - const: marvell,pxa910-pwm reg: @@ -47,7 +71,18 @@ properties: description: Number of cells in a pwm specifier. clocks: - maxItems: 1 + minItems: 1 + items: + - description: The function clock + - description: An optional bus clock + + clock-names: + minItems: 1 + maxItems: 2 + oneOf: + - items: + - const: func + - const: bus resets: maxItems: 1 diff --git a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt deleted file mode 100644 index f401316e0248..000000000000 --- a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt +++ /dev/null @@ -1,18 +0,0 @@ -== ST STMPE PWM controller == - -This is a PWM block embedded in the ST Microelectronics STMPE -(ST Multi-Purpose Expander) chips. The PWM is registered as a -subdevices of the STMPE MFD device. - -Required properties: -- compatible: should be: - - "st,stmpe-pwm" -- #pwm-cells: should be 2. See pwm.yaml in this directory for a description of - the cells format. - -Example: - -pwm0: pwm { - compatible = "st,stmpe-pwm"; - #pwm-cells = <2>; -}; diff --git a/MAINTAINERS b/MAINTAINERS index 1c2efe5b8f21..ea8c4eea19ee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15077,6 +15077,7 @@ M: Binbin Zhou L: linux-i2c@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml +F: drivers/i2c/busses/i2c-ls2x-v2.c F: drivers/i2c/busses/i2c-ls2x.c LOONGSON PWM DRIVER @@ -26718,7 +26719,7 @@ M: Bartosz Golaszewski L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git -F: Documentation/devicetree/bindings/i2c/i2c-davinci.txt +F: Documentation/devicetree/bindings/i2c/ti,davinci-i2c.yaml F: arch/arm/boot/dts/ti/davinci/ F: arch/arm/mach-davinci/ F: drivers/i2c/busses/i2c-davinci.c @@ -28985,8 +28986,8 @@ F: arch/x86/kernel/unwind_*.c X86 TRUST DOMAIN EXTENSIONS (TDX) M: Kiryl Shutsemau +M: Rick Edgecombe R: Dave Hansen -R: Rick Edgecombe L: x86@kernel.org L: linux-coco@lists.linux.dev L: kvm@vger.kernel.org diff --git a/arch/arm/mach-omap1/board-nokia770.c b/arch/arm/mach-omap1/board-nokia770.c index a5bf5554800f..72d9e92a1071 100644 --- a/arch/arm/mach-omap1/board-nokia770.c +++ b/arch/arm/mach-omap1/board-nokia770.c @@ -36,27 +36,6 @@ #include "clock.h" #include "mmc.h" -static const struct software_node nokia770_mpuio_gpiochip_node = { - .name = "mpuio", -}; - -static const struct software_node nokia770_gpiochip1_node = { - .name = "gpio-0-15", -}; - -static const struct software_node nokia770_gpiochip2_node = { - .name = "gpio-16-31", -}; - -static const struct software_node *nokia770_gpiochip_nodes[] = { - &nokia770_mpuio_gpiochip_node, - &nokia770_gpiochip1_node, - &nokia770_gpiochip2_node, - NULL -}; - -#define ADS7846_PENDOWN_GPIO 15 - static const unsigned int nokia770_keymap[] = { KEY(1, 0, GROUP_0 | KEY_UP), KEY(2, 0, GROUP_1 | KEY_F5), @@ -112,7 +91,7 @@ static const struct omap_lcd_config nokia770_lcd_config __initconst = { }; static const struct property_entry nokia770_mipid_props[] = { - PROPERTY_ENTRY_GPIO("reset-gpios", &nokia770_gpiochip1_node, + PROPERTY_ENTRY_GPIO("reset-gpios", &omap16xx_gpio1_swnode, 13, GPIO_ACTIVE_LOW), { } }; @@ -138,8 +117,7 @@ static const struct property_entry nokia770_ads7846_props[] = { PROPERTY_ENTRY_U16("ti,x-plate-ohms", 180), PROPERTY_ENTRY_U16("ti,debounce-tol", 3), PROPERTY_ENTRY_U16("ti,debounce-rep", 1), - PROPERTY_ENTRY_GPIO("pendown-gpios", &nokia770_gpiochip1_node, - ADS7846_PENDOWN_GPIO, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("pendown-gpios", &omap16xx_gpio1_swnode, 15, GPIO_ACTIVE_LOW), { } }; @@ -225,9 +203,9 @@ static inline void nokia770_mmc_init(void) #if IS_ENABLED(CONFIG_I2C_CBUS_GPIO) static const struct software_node_ref_args nokia770_cbus_gpio_refs[] = { - SOFTWARE_NODE_REFERENCE(&nokia770_mpuio_gpiochip_node, 9, 0), - SOFTWARE_NODE_REFERENCE(&nokia770_mpuio_gpiochip_node, 10, 0), - SOFTWARE_NODE_REFERENCE(&nokia770_mpuio_gpiochip_node, 11, 0), + SOFTWARE_NODE_REFERENCE(&omap16xx_mpu_gpio_swnode, 9, 0), + SOFTWARE_NODE_REFERENCE(&omap16xx_mpu_gpio_swnode, 10, 0), + SOFTWARE_NODE_REFERENCE(&omap16xx_mpu_gpio_swnode, 11, 0), }; static const struct property_entry nokia770_cbus_props[] = { @@ -318,7 +296,6 @@ static void __init omap_nokia770_init(void) /* Unmask SleepX signal */ omap_writew((omap_readw(0xfffb5004) & ~2), 0xfffb5004); - software_node_register_node_group(nokia770_gpiochip_nodes); platform_add_devices(nokia770_devices, ARRAY_SIZE(nokia770_devices)); gpiod_add_lookup_table(&nokia770_irq_gpio_table); diff --git a/arch/arm/mach-omap1/common.h b/arch/arm/mach-omap1/common.h index 7a7c3d9eb84a..c0f6e231fdb4 100644 --- a/arch/arm/mach-omap1/common.h +++ b/arch/arm/mach-omap1/common.h @@ -35,6 +35,9 @@ #include "soc.h" #include "i2c.h" +extern const struct software_node omap16xx_mpu_gpio_swnode; +extern const struct software_node omap16xx_gpio1_swnode; + #ifdef CONFIG_OMAP_SERIAL_WAKE int omap_serial_wakeup_init(void); #else diff --git a/arch/arm/mach-omap1/gpio16xx.c b/arch/arm/mach-omap1/gpio16xx.c index 55acec22fef4..0f97972fd248 100644 --- a/arch/arm/mach-omap1/gpio16xx.c +++ b/arch/arm/mach-omap1/gpio16xx.c @@ -9,6 +9,7 @@ */ #include +#include #include #include "hardware.h" @@ -55,14 +56,16 @@ static struct omap_gpio_platform_data omap16xx_mpu_gpio_config = { .regs = &omap16xx_mpuio_regs, }; -static struct platform_device omap16xx_mpu_gpio = { +const struct software_node omap16xx_mpu_gpio_swnode = { }; + +static const struct platform_device_info omap16xx_mpu_gpio = { .name = "omap_gpio", .id = 0, - .dev = { - .platform_data = &omap16xx_mpu_gpio_config, - }, - .num_resources = ARRAY_SIZE(omap16xx_mpu_gpio_resources), - .resource = omap16xx_mpu_gpio_resources, + .data = &omap16xx_mpu_gpio_config, + .size_data = sizeof(omap16xx_mpu_gpio_config), + .num_res = ARRAY_SIZE(omap16xx_mpu_gpio_resources), + .res = omap16xx_mpu_gpio_resources, + .swnode = &omap16xx_mpu_gpio_swnode, }; /* gpio1 */ @@ -99,14 +102,16 @@ static struct omap_gpio_platform_data omap16xx_gpio1_config = { .regs = &omap16xx_gpio_regs, }; -static struct platform_device omap16xx_gpio1 = { +const struct software_node omap16xx_gpio1_swnode = { }; + +static const struct platform_device_info omap16xx_gpio1 = { .name = "omap_gpio", .id = 1, - .dev = { - .platform_data = &omap16xx_gpio1_config, - }, - .num_resources = ARRAY_SIZE(omap16xx_gpio1_resources), - .resource = omap16xx_gpio1_resources, + .data = &omap16xx_gpio1_config, + .size_data = sizeof(omap16xx_gpio1_config), + .num_res = ARRAY_SIZE(omap16xx_gpio1_resources), + .res = omap16xx_gpio1_resources, + .swnode = &omap16xx_gpio1_swnode, }; /* gpio2 */ @@ -127,14 +132,13 @@ static struct omap_gpio_platform_data omap16xx_gpio2_config = { .regs = &omap16xx_gpio_regs, }; -static struct platform_device omap16xx_gpio2 = { +static const struct platform_device_info omap16xx_gpio2 = { .name = "omap_gpio", .id = 2, - .dev = { - .platform_data = &omap16xx_gpio2_config, - }, - .num_resources = ARRAY_SIZE(omap16xx_gpio2_resources), - .resource = omap16xx_gpio2_resources, + .data = &omap16xx_gpio2_config, + .size_data = sizeof(omap16xx_gpio2_config), + .num_res = ARRAY_SIZE(omap16xx_gpio2_resources), + .res = omap16xx_gpio2_resources, }; /* gpio3 */ @@ -155,14 +159,13 @@ static struct omap_gpio_platform_data omap16xx_gpio3_config = { .regs = &omap16xx_gpio_regs, }; -static struct platform_device omap16xx_gpio3 = { +static const struct platform_device_info omap16xx_gpio3 = { .name = "omap_gpio", .id = 3, - .dev = { - .platform_data = &omap16xx_gpio3_config, - }, - .num_resources = ARRAY_SIZE(omap16xx_gpio3_resources), - .resource = omap16xx_gpio3_resources, + .data = &omap16xx_gpio3_config, + .size_data = sizeof(omap16xx_gpio3_config), + .num_res = ARRAY_SIZE(omap16xx_gpio3_resources), + .res = omap16xx_gpio3_resources, }; /* gpio4 */ @@ -183,17 +186,16 @@ static struct omap_gpio_platform_data omap16xx_gpio4_config = { .regs = &omap16xx_gpio_regs, }; -static struct platform_device omap16xx_gpio4 = { +static const struct platform_device_info omap16xx_gpio4 = { .name = "omap_gpio", .id = 4, - .dev = { - .platform_data = &omap16xx_gpio4_config, - }, - .num_resources = ARRAY_SIZE(omap16xx_gpio4_resources), - .resource = omap16xx_gpio4_resources, + .data = &omap16xx_gpio4_config, + .size_data = sizeof(omap16xx_gpio4_config), + .num_res = ARRAY_SIZE(omap16xx_gpio4_resources), + .res = omap16xx_gpio4_resources, }; -static struct platform_device *omap16xx_gpio_dev[] __initdata = { +static const struct platform_device_info *omap16xx_gpio_dev[] __initconst = { &omap16xx_mpu_gpio, &omap16xx_gpio1, &omap16xx_gpio2, @@ -210,9 +212,8 @@ static int __init omap16xx_gpio_init(void) { int i; void __iomem *base; - struct resource *res; - struct platform_device *pdev; - struct omap_gpio_platform_data *pdata; + const struct resource *res; + const struct platform_device_info *pdevinfo; if (!cpu_is_omap16xx()) return -EINVAL; @@ -225,25 +226,24 @@ static int __init omap16xx_gpio_init(void) ULPD_CAM_CLK_CTRL); for (i = 0; i < ARRAY_SIZE(omap16xx_gpio_dev); i++) { - pdev = omap16xx_gpio_dev[i]; - pdata = pdev->dev.platform_data; + pdevinfo = omap16xx_gpio_dev[i]; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res = &pdevinfo->res[0]; if (unlikely(!res)) { - dev_err(&pdev->dev, "Invalid mem resource.\n"); + pr_err("%s.%d: Invalid mem resource.\n", pdevinfo->name, pdevinfo->id); return -ENODEV; } base = ioremap(res->start, resource_size(res)); if (unlikely(!base)) { - dev_err(&pdev->dev, "ioremap failed.\n"); + pr_err("%s.%d: ioremap failed.\n", pdevinfo->name, pdevinfo->id); return -ENOMEM; } __raw_writel(SYSCONFIG_WORD, base + OMAP1610_GPIO_SYSCONFIG); iounmap(base); - platform_device_register(omap16xx_gpio_dev[i]); + platform_device_register_full(omap16xx_gpio_dev[i]); } return 0; diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c index 7ed3da998489..ecd77d3217f3 100644 --- a/arch/x86/coco/sev/core.c +++ b/arch/x86/coco/sev/core.c @@ -367,17 +367,13 @@ static unsigned long __set_pages_state(struct snp_psc_desc *data, unsigned long local_irq_save(flags); - if (sev_cfg.ghcbs_initialized) - ghcb = __sev_get_ghcb(&state); - else - ghcb = boot_ghcb; + ghcb = __sev_get_ghcb(&state); /* Invoke the hypervisor to perform the page state changes */ if (!ghcb || vmgexit_psc(ghcb, data)) sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PSC); - if (sev_cfg.ghcbs_initialized) - __sev_put_ghcb(&state); + __sev_put_ghcb(&state); local_irq_restore(flags); diff --git a/arch/x86/coco/sev/internal.h b/arch/x86/coco/sev/internal.h index b1d0c66a651a..b9632c0fc391 100644 --- a/arch/x86/coco/sev/internal.h +++ b/arch/x86/coco/sev/internal.h @@ -70,6 +70,9 @@ void svsm_pval_pages(struct snp_psc_desc *desc); int svsm_perform_call_protocol(struct svsm_call *call); bool snp_svsm_vtpm_probe(void); +noinstr void kernel_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code); +noinstr void user_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code); + static inline u64 sev_es_rd_ghcb_msr(void) { return native_rdmsrq(MSR_AMD64_SEV_ES_GHCB); diff --git a/arch/x86/coco/sev/noinstr.c b/arch/x86/coco/sev/noinstr.c index 5afd663a1c21..e1e03f12fc7b 100644 --- a/arch/x86/coco/sev/noinstr.c +++ b/arch/x86/coco/sev/noinstr.c @@ -121,8 +121,10 @@ noinstr struct ghcb *__sev_get_ghcb(struct ghcb_state *state) WARN_ON(!irqs_disabled()); - if (!sev_cfg.ghcbs_initialized) + if (!sev_cfg.ghcbs_initialized) { + state->ghcb = NULL; return boot_ghcb; + } data = this_cpu_read(runtime_data); ghcb = &data->ghcb_page; diff --git a/arch/x86/coco/sev/svsm.c b/arch/x86/coco/sev/svsm.c index 2acf4a76afe7..916d62cd17dc 100644 --- a/arch/x86/coco/sev/svsm.c +++ b/arch/x86/coco/sev/svsm.c @@ -74,20 +74,14 @@ int svsm_perform_call_protocol(struct svsm_call *call) flags = native_local_irq_save(); - if (sev_cfg.ghcbs_initialized) - ghcb = __sev_get_ghcb(&state); - else if (boot_ghcb) - ghcb = boot_ghcb; - else - ghcb = NULL; + ghcb = __sev_get_ghcb(&state); do { ret = ghcb ? svsm_perform_ghcb_protocol(ghcb, call) : __pi_svsm_perform_msr_protocol(call); } while (ret == -EAGAIN); - if (sev_cfg.ghcbs_initialized) - __sev_put_ghcb(&state); + __sev_put_ghcb(&state); native_local_irq_restore(flags); diff --git a/arch/x86/coco/sev/vc-handle.c b/arch/x86/coco/sev/vc-handle.c index d98b5c08ef00..96b62b49b2b5 100644 --- a/arch/x86/coco/sev/vc-handle.c +++ b/arch/x86/coco/sev/vc-handle.c @@ -954,7 +954,7 @@ static __always_inline bool vc_is_db(unsigned long error_code) * Runtime #VC exception handler when raised from kernel mode. Runs in NMI mode * and will panic when an error happens. */ -DEFINE_IDTENTRY_VC_KERNEL(exc_vmm_communication) +noinstr void kernel_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code) { irqentry_state_t irq_state; @@ -1006,7 +1006,7 @@ DEFINE_IDTENTRY_VC_KERNEL(exc_vmm_communication) * Runtime #VC exception handler when raised from user mode. Runs in IRQ mode * and will kill the current task with SIGBUS when an error happens. */ -DEFINE_IDTENTRY_VC_USER(exc_vmm_communication) +noinstr void user_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code) { /* * Handle #DB before calling into !noinstr code to avoid recursive #DB. @@ -1032,6 +1032,14 @@ DEFINE_IDTENTRY_VC_USER(exc_vmm_communication) irqentry_exit_to_user_mode(regs); } +DEFINE_IDTENTRY_RAW_ERRORCODE(exc_vmm_communication) +{ + if (user_mode(regs)) + return user_exc_vmm_communication(regs, error_code); + else + return kernel_exc_vmm_communication(regs, error_code); +} + bool __init handle_vc_boot_ghcb(struct pt_regs *regs) { unsigned long exit_code = regs->orig_ax; diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S index 42447b1e1dff..c6d996593f32 100644 --- a/arch/x86/entry/entry_64.S +++ b/arch/x86/entry/entry_64.S @@ -492,7 +492,7 @@ SYM_CODE_START(\asmsym) movq %rsp, %rdi /* pt_regs pointer */ - call kernel_\cfunc + call \cfunc /* * No need to switch back to the IST stack. The current stack is either @@ -503,7 +503,7 @@ SYM_CODE_START(\asmsym) /* Switch to the regular task stack */ .Lfrom_usermode_switch_stack_\@: - idtentry_body user_\cfunc, has_error_code=1 + idtentry_body \cfunc, has_error_code=1 _ASM_NOKPROBE(\asmsym) SYM_CODE_END(\asmsym) diff --git a/arch/x86/entry/entry_fred.c b/arch/x86/entry/entry_fred.c index fbe2d10dd737..fb3594ddf731 100644 --- a/arch/x86/entry/entry_fred.c +++ b/arch/x86/entry/entry_fred.c @@ -177,16 +177,6 @@ static noinstr void fred_extint(struct pt_regs *regs) } } -#ifdef CONFIG_AMD_MEM_ENCRYPT -noinstr void exc_vmm_communication(struct pt_regs *regs, unsigned long error_code) -{ - if (user_mode(regs)) - return user_exc_vmm_communication(regs, error_code); - else - return kernel_exc_vmm_communication(regs, error_code); -} -#endif - static noinstr void fred_hwexc(struct pt_regs *regs, unsigned long error_code) { /* Optimize for #PF. That's the only exception which matters performance wise */ diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index 80c1696d8d59..23ab05438269 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -153,7 +153,7 @@ struct arch_va_list { struct sysv_va_list args; }; extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs); -#endif /* __ASSEMBLER__ */ +static __always_inline __printf(1, 2) void __WARN_validate_printf(const char *fmt, ...) { } #define __WARN_bug_entry(flags, format) ({ \ struct bug_entry *bug; \ @@ -172,6 +172,7 @@ extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs); #define __WARN_print_arg(flags, format, arg...) \ do { \ int __flags = (flags) | BUGFLAG_WARNING | BUGFLAG_ARGS ; \ + __WARN_validate_printf(format, ## arg); \ static_call_mod(WARN_trap)(__WARN_bug_entry(__flags, format), ## arg); \ asm (""); /* inhibit tail-call optimization */ \ } while (0) @@ -187,6 +188,7 @@ do { \ } \ __ret_warn_on; \ }) +#endif /* __ASSEMBLER__ */ #endif /* HAVE_ARCH_BUG_FORMAT_ARGS */ diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index 1d506e5d6f46..7b572bc24265 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -573,4 +573,5 @@ #define X86_BUG_ITS_NATIVE_ONLY X86_BUG( 1*32+ 8) /* "its_native_only" CPU is affected by ITS, VMX is not affected */ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ +#define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ #endif /* _ASM_X86_CPUFEATURES_H */ diff --git a/arch/x86/include/asm/device.h b/arch/x86/include/asm/device.h deleted file mode 100644 index 7c0a52ca2f4d..000000000000 --- a/arch/x86/include/asm/device.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_X86_DEVICE_H -#define _ASM_X86_DEVICE_H - -struct dev_archdata { -}; - -struct pdev_archdata { -}; - -#endif /* _ASM_X86_DEVICE_H */ diff --git a/arch/x86/include/asm/idtentry.h b/arch/x86/include/asm/idtentry.h index 42bf6a58ec36..20f548702404 100644 --- a/arch/x86/include/asm/idtentry.h +++ b/arch/x86/include/asm/idtentry.h @@ -340,17 +340,14 @@ static __always_inline void __##func(struct pt_regs *regs) __visible void noist_##func(struct pt_regs *regs) /** - * DECLARE_IDTENTRY_VC - Declare functions for the VC entry point + * DECLARE_IDTENTRY_VC - Declare a function for the VC entry point * @vector: Vector number (ignored for C) * @func: Function name of the entry point * - * Maps to DECLARE_IDTENTRY_RAW_ERRORCODE, but declares also the - * safe_stack C handler. + * Maps to DECLARE_IDTENTRY_RAW_ERRORCODE. */ #define DECLARE_IDTENTRY_VC(vector, func) \ - DECLARE_IDTENTRY_RAW_ERRORCODE(vector, func); \ - __visible noinstr void kernel_##func(struct pt_regs *regs, unsigned long error_code); \ - __visible noinstr void user_##func(struct pt_regs *regs, unsigned long error_code) + DECLARE_IDTENTRY_RAW_ERRORCODE(vector, func); /** * DEFINE_IDTENTRY_IST - Emit code for IST entry points @@ -391,26 +388,6 @@ static __always_inline void __##func(struct pt_regs *regs) #define DEFINE_IDTENTRY_DF(func) \ DEFINE_IDTENTRY_RAW_ERRORCODE(func) -/** - * DEFINE_IDTENTRY_VC_KERNEL - Emit code for VMM communication handler - * when raised from kernel mode - * @func: Function name of the entry point - * - * Maps to DEFINE_IDTENTRY_RAW_ERRORCODE - */ -#define DEFINE_IDTENTRY_VC_KERNEL(func) \ - DEFINE_IDTENTRY_RAW_ERRORCODE(kernel_##func) - -/** - * DEFINE_IDTENTRY_VC_USER - Emit code for VMM communication handler - * when raised from user mode - * @func: Function name of the entry point - * - * Maps to DEFINE_IDTENTRY_RAW_ERRORCODE - */ -#define DEFINE_IDTENTRY_VC_USER(func) \ - DEFINE_IDTENTRY_RAW_ERRORCODE(user_##func) - #else /* CONFIG_X86_64 */ /** diff --git a/arch/x86/include/asm/seamldr.h b/arch/x86/include/asm/seamldr.h new file mode 100644 index 000000000000..cfc6a1b1a440 --- /dev/null +++ b/arch/x86/include/asm/seamldr.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_SEAMLDR_H +#define _ASM_X86_SEAMLDR_H + +#include + +/* + * This is the "SEAMLDR_INFO" data structure defined in the + * "SEAM Loader (SEAMLDR) Interface Specification". + * + * Must be aligned to a 256-byte boundary. + */ +struct seamldr_info { + u32 version; + u32 attributes; + u32 vendor_id; + u32 build_date; + u16 build_num; + u16 minor_version; + u16 major_version; + u16 update_version; + u32 acm_x2apicid; + u32 num_remaining_updates; + u8 seam_info[128]; + u8 seam_ready; + u8 seam_debug; + u8 p_seam_ready; + u8 reserved[93]; +} __packed __aligned(256); + +static_assert(sizeof(struct seamldr_info) == 256); + +int seamldr_get_info(struct seamldr_info *seamldr_info); +int seamldr_install_module(const u8 *data, u32 data_len); +void seamldr_lock_module_update(void); +void seamldr_unlock_module_update(void); + +#endif /* _ASM_X86_SEAMLDR_H */ diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h index 09e605c85de4..594cfa19cbd4 100644 --- a/arch/x86/include/asm/sev.h +++ b/arch/x86/include/asm/sev.h @@ -661,7 +661,7 @@ static inline void snp_leak_pages(u64 pfn, unsigned int pages) { __snp_leak_pages(pfn, pages, true); } -void snp_prepare(void); +int snp_prepare(void); void snp_shutdown(void); #else static inline bool snp_probe_rmptable_info(void) { return false; } @@ -679,7 +679,7 @@ static inline void __snp_leak_pages(u64 pfn, unsigned int npages, bool dump_rmp) static inline void snp_leak_pages(u64 pfn, unsigned int npages) {} static inline void kdump_sev_callback(void) { } static inline void snp_fixup_e820_tables(void) {} -static inline void snp_prepare(void) {} +static inline int snp_prepare(void) { return -ENODEV; } static inline void snp_shutdown(void) {} #endif diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h index 049638e3da74..f20e91d7ac35 100644 --- a/arch/x86/include/asm/shared/tdx.h +++ b/arch/x86/include/asm/shared/tdx.h @@ -4,6 +4,7 @@ #include #include +#include #define TDX_HYPERCALL_STANDARD 0 diff --git a/arch/x86/kvm/vmx/tdx_errno.h b/arch/x86/include/asm/shared/tdx_errno.h similarity index 90% rename from arch/x86/kvm/vmx/tdx_errno.h rename to arch/x86/include/asm/shared/tdx_errno.h index 6ff4672c4181..ee411b360e20 100644 --- a/arch/x86/kvm/vmx/tdx_errno.h +++ b/arch/x86/include/asm/shared/tdx_errno.h @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* architectural status code for SEAMCALL */ - -#ifndef __KVM_X86_TDX_ERRNO_H -#define __KVM_X86_TDX_ERRNO_H +#ifndef _ASM_X86_SHARED_TDX_ERRNO_H +#define _ASM_X86_SHARED_TDX_ERRNO_H #define TDX_SEAMCALL_STATUS_MASK 0xFFFFFFFF00000000ULL @@ -14,6 +13,7 @@ #define TDX_NON_RECOVERABLE_TD_NON_ACCESSIBLE 0x6000000500000000ULL #define TDX_NON_RECOVERABLE_TD_WRONG_APIC_MODE 0x6000000700000000ULL #define TDX_INTERRUPTED_RESUMABLE 0x8000000300000000ULL +#define TDX_SYS_BUSY 0x8000020200000000ULL #define TDX_OPERAND_INVALID 0xC000010000000000ULL #define TDX_OPERAND_BUSY 0x8000020000000000ULL #define TDX_PREVIOUS_TLB_EPOCH_BUSY 0x8000020100000000ULL @@ -37,4 +37,4 @@ #define TDX_OPERAND_ID_SEPT 0x92 #define TDX_OPERAND_ID_TD_EPOCH 0xa9 -#endif /* __KVM_X86_TDX_ERRNO_H */ +#endif /* _ASM_X86_SHARED_TDX_ERRNO_H */ diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index a149740b24e8..e5a9cf656c07 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -32,12 +32,22 @@ #define TDX_SUCCESS 0ULL #define TDX_RND_NO_ENTROPY 0x8000020300000000ULL +/* Bit definitions of TDX_FEATURES0 metadata field */ +#define TDX_FEATURES0_TD_PRESERVING BIT_ULL(1) +#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18) + #ifndef __ASSEMBLER__ #include #include #include +/* + * TDX module and P-SEAMLDR version convention: "major.minor.update" + * (e.g., "1.5.08") with zero-padded two-digit update field. + */ +#define TDX_VERSION_FMT "%u.%u.%02u" + /* * Used by the #VE exception handler to gather the #VE exception * info from the TDX module. This is a software only structure @@ -97,57 +107,16 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1, #endif /* CONFIG_INTEL_TDX_GUEST && CONFIG_KVM_GUEST */ #ifdef CONFIG_INTEL_TDX_HOST -u64 __seamcall(u64 fn, struct tdx_module_args *args); -u64 __seamcall_ret(u64 fn, struct tdx_module_args *args); -u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args); void tdx_init(void); - -#include -#include -#include - -typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args); - -static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn, - struct tdx_module_args *args) -{ - lockdep_assert_preemption_disabled(); - - /* - * SEAMCALLs are made to the TDX module and can generate dirty - * cachelines of TDX private memory. Mark cache state incoherent - * so that the cache can be flushed during kexec. - * - * This needs to be done before actually making the SEAMCALL, - * because kexec-ing CPU could send NMI to stop remote CPUs, - * in which case even disabling IRQ won't help here. - */ - this_cpu_write(cache_state_incoherent, true); - - return func(fn, args); -} - -static __always_inline u64 sc_retry(sc_func_t func, u64 fn, - struct tdx_module_args *args) -{ - int retry = RDRAND_RETRY_LOOPS; - u64 ret; - - do { - preempt_disable(); - ret = __seamcall_dirty_cache(func, fn, args); - preempt_enable(); - } while (ret == TDX_RND_NO_ENTROPY && --retry); - - return ret; -} - -#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args)) -#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args)) -#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args)) +int tdx_cpu_enable(void); const char *tdx_dump_mce_info(struct mce *m); const struct tdx_sys_info *tdx_get_sysinfo(void); +static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinfo) +{ + return sysinfo->features.tdx_features0 & TDX_FEATURES0_TD_PRESERVING; +} + int tdx_guest_keyid_alloc(void); u32 tdx_get_nr_guest_keyids(void); void tdx_guest_keyid_free(unsigned int keyid); @@ -193,6 +162,8 @@ static inline int pg_level_to_tdx_sept_level(enum pg_level level) return level - 1; } +void tdx_sys_disable(void); + u64 tdh_vp_enter(struct tdx_vp *vp, struct tdx_module_args *args); u64 tdh_mng_addcx(struct tdx_td *td, struct page *tdcs_page); u64 tdh_mem_page_add(struct tdx_td *td, u64 gpa, struct page *page, struct page *source, u64 *ext_err1, u64 *ext_err2); @@ -224,13 +195,8 @@ static inline void tdx_init(void) { } static inline u32 tdx_get_nr_guest_keyids(void) { return 0; } static inline const char *tdx_dump_mce_info(struct mce *m) { return NULL; } static inline const struct tdx_sys_info *tdx_get_sysinfo(void) { return NULL; } +static inline void tdx_sys_disable(void) { } #endif /* CONFIG_INTEL_TDX_HOST */ -#ifdef CONFIG_KEXEC_CORE -void tdx_cpu_flush_cache_for_kexec(void); -#else -static inline void tdx_cpu_flush_cache_for_kexec(void) { } -#endif - #endif /* !__ASSEMBLER__ */ #endif /* _ASM_X86_TDX_H */ diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h index 40689c8dc67e..41150d546589 100644 --- a/arch/x86/include/asm/tdx_global_metadata.h +++ b/arch/x86/include/asm/tdx_global_metadata.h @@ -40,6 +40,10 @@ struct tdx_sys_info_td_conf { u64 cpuid_config_values[128][2]; }; +struct tdx_sys_info_handoff { + u16 module_hv; +}; + struct tdx_sys_info { struct tdx_sys_info_version version; struct tdx_sys_info_features features; diff --git a/arch/x86/include/asm/vmx.h b/arch/x86/include/asm/vmx.h index 37080382df54..49d8551d285d 100644 --- a/arch/x86/include/asm/vmx.h +++ b/arch/x86/include/asm/vmx.h @@ -147,6 +147,7 @@ struct vmcs { #define VMX_BASIC_INOUT BIT_ULL(54) #define VMX_BASIC_TRUE_CTLS BIT_ULL(55) #define VMX_BASIC_NO_HW_ERROR_CODE_CC BIT_ULL(56) +#define VMX_BASIC_NO_SEAMRET_INVD_VMCS BIT_ULL(60) static inline u32 vmx_basic_vmcs_revision_id(u64 vmx_basic) { diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c index cd796818d94d..623d4474631a 100644 --- a/arch/x86/kernel/crash.c +++ b/arch/x86/kernel/crash.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,7 @@ void native_machine_crash_shutdown(struct pt_regs *regs) crash_smp_send_stop(); + tdx_sys_disable(); x86_virt_emergency_disable_virtualization_cpu(); /* diff --git a/arch/x86/kernel/machine_kexec_64.c b/arch/x86/kernel/machine_kexec_64.c index 0590d399d4f1..c3f4a389992d 100644 --- a/arch/x86/kernel/machine_kexec_64.c +++ b/arch/x86/kernel/machine_kexec_64.c @@ -347,22 +347,6 @@ int machine_kexec_prepare(struct kimage *image) unsigned long reloc_end = (unsigned long)__relocate_kernel_end; int result; - /* - * Some early TDX-capable platforms have an erratum. A kernel - * partial write (a write transaction of less than cacheline - * lands at memory controller) to TDX private memory poisons that - * memory, and a subsequent read triggers a machine check. - * - * On those platforms the old kernel must reset TDX private - * memory before jumping to the new kernel otherwise the new - * kernel may see unexpected machine check. For simplicity - * just fail kexec/kdump on those platforms. - */ - if (boot_cpu_has_bug(X86_BUG_TDX_PW_MCE)) { - pr_info_once("Not allowed on platform with tdx_pw_mce bug\n"); - return -EOPNOTSUPP; - } - /* Setup the identity mapped 64bit page table */ result = init_pgtable(image, __pa(control_page)); if (result) diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index cb50e23c39ca..67d3c5c8aca0 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -440,16 +440,6 @@ void tdx_disable_virtualization_cpu(void) tdx_flush_vp(&arg); } local_irq_restore(flags); - - /* - * Flush cache now if kexec is possible: this is necessary to avoid - * having dirty private memory cachelines when the new kernel boots, - * but WBINVD is a relatively expensive operation and doing it during - * kexec can exacerbate races in native_stop_other_cpus(). Do it - * now, since this is a safe moment and there is going to be no more - * TDX activity on this CPU from this point on. - */ - tdx_cpu_flush_cache_for_kexec(); } #define TDX_SEAMCALL_RETRIES 10000 diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h index b5cd2ffb303e..ac8323a68b16 100644 --- a/arch/x86/kvm/vmx/tdx.h +++ b/arch/x86/kvm/vmx/tdx.h @@ -3,7 +3,6 @@ #define __KVM_X86_VMX_TDX_H #include "tdx_arch.h" -#include "tdx_errno.h" #ifdef CONFIG_KVM_INTEL_TDX #include "common.h" diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c index 63de8e8684f2..250942ef60bb 100644 --- a/arch/x86/mm/fault.c +++ b/arch/x86/mm/fault.c @@ -169,6 +169,11 @@ is_prefetch(struct pt_regs *regs, unsigned long error_code, unsigned long addr) } DEFINE_SPINLOCK(pgd_lock); +/* + * List of all process' PGD pages (excluding init_mm and some other special + * system-wide PGDs). Mainly used to keep the kernel portion of the address + * space in sync across mms. + */ LIST_HEAD(pgd_list); #ifdef CONFIG_X86_32 diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c index df2261fa4f98..7e20b22d658b 100644 --- a/arch/x86/mm/init_64.c +++ b/arch/x86/mm/init_64.c @@ -1014,7 +1014,7 @@ static void __meminit free_pagetable(struct page *page, int order) #ifdef CONFIG_HAVE_BOOTMEM_INFO_NODE enum bootmem_type type = bootmem_type(page); - if (type == SECTION_INFO || type == MIX_SECTION_INFO) { + if (type == MIX_SECTION_INFO) { while (nr_pages--) put_page_bootmem(page++); } else { @@ -1028,13 +1028,24 @@ static void __meminit free_pagetable(struct page *page, int order) } } -static void __meminit free_hugepage_table(struct page *page, +static void __meminit free_vmemmap_pages(struct page *page, unsigned int order, struct vmem_altmap *altmap) { - if (altmap) - vmem_altmap_free(altmap, PMD_SIZE / PAGE_SIZE); - else - free_pagetable(page, get_order(PMD_SIZE)); + unsigned long nr_pages = 1u << order; + + if (altmap) { + vmem_altmap_free(altmap, nr_pages); + } else if (PageReserved(page)) { + if (IS_ENABLED(CONFIG_HAVE_BOOTMEM_INFO_NODE) && + bootmem_type(page) == SECTION_INFO) { + while (nr_pages--) + put_page_bootmem(page++); + } else { + free_reserved_pages(page, nr_pages); + } + } else { + __free_pages(page, order); + } } static void __meminit free_pte_table(pte_t *pte_start, pmd_t *pmd) @@ -1118,7 +1129,8 @@ remove_pte_table(pte_t *pte_start, unsigned long addr, unsigned long end, return; if (!direct) - free_pagetable(pte_page(*pte), 0); + /* We never populate base pages from the altmap. */ + free_vmemmap_pages(pte_page(*pte), 0, NULL); spin_lock(&init_mm.page_table_lock); pte_clear(&init_mm, addr, pte); @@ -1153,19 +1165,19 @@ remove_pmd_table(pmd_t *pmd_start, unsigned long addr, unsigned long end, if (IS_ALIGNED(addr, PMD_SIZE) && IS_ALIGNED(next, PMD_SIZE)) { if (!direct) - free_hugepage_table(pmd_page(*pmd), - altmap); + free_vmemmap_pages(pmd_page(*pmd), + PMD_ORDER, altmap); spin_lock(&init_mm.page_table_lock); pmd_clear(pmd); spin_unlock(&init_mm.page_table_lock); pages++; } else if (vmemmap_pmd_is_unused(addr, next)) { - free_hugepage_table(pmd_page(*pmd), - altmap); - spin_lock(&init_mm.page_table_lock); - pmd_clear(pmd); - spin_unlock(&init_mm.page_table_lock); + free_vmemmap_pages(pmd_page(*pmd), PMD_ORDER, + altmap); + spin_lock(&init_mm.page_table_lock); + pmd_clear(pmd); + spin_unlock(&init_mm.page_table_lock); } continue; } diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c index da7f0a03cf90..f32facdb3035 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c @@ -99,17 +99,6 @@ static void pgd_dtor(pgd_t *pgd) spin_unlock(&pgd_lock); } -/* - * List of all pgd's needed for non-PAE so it can invalidate entries - * in both cached and uncached pgd's; not needed for PAE since the - * kernel pmd is shared. If PAE were not to share the pmd a similar - * tactic would be needed. This is essentially codepath-based locking - * against pageattr.c; it is the unique case in which a valid change - * of kernel pagetables can't be lazily synchronized by vmalloc faults. - * vmalloc faults work because attached pagetables are never freed. - * -- nyc - */ - #ifdef CONFIG_X86_PAE /* * In PAE mode, we need to do a cr3 reload (=tlb flush) when diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c index 41f76f15caa1..8bcdce98f6dc 100644 --- a/arch/x86/virt/svm/sev.c +++ b/arch/x86/virt/svm/sev.c @@ -511,8 +511,9 @@ static void clear_hsave_pa(void *arg) wrmsrq(MSR_VM_HSAVE_PA, 0); } -void snp_prepare(void) +int snp_prepare(void) { + int ret; u64 val; /* @@ -521,12 +522,20 @@ void snp_prepare(void) */ rdmsrq(MSR_AMD64_SYSCFG, val); if (val & MSR_AMD64_SYSCFG_SNP_EN) - return; + return 0; clear_rmp(); cpus_read_lock(); + if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) { + ret = -EOPNOTSUPP; + pr_warn("SNP init failed: not all CPUs online. (%*pbl online <-> %*pbl present masks).\n", + cpumask_pr_args(cpu_online_mask), + cpumask_pr_args(cpu_present_mask)); + goto unlock; + } + /* * MtrrFixDramModEn is not shared between threads on a core, * therefore it must be set on all CPUs prior to enabling SNP. @@ -537,7 +546,12 @@ void snp_prepare(void) /* SNP_INIT requires MSR_VM_HSAVE_PA to be cleared on all CPUs. */ on_each_cpu(clear_hsave_pa, NULL, 1); + ret = 0; + +unlock: cpus_read_unlock(); + + return ret; } EXPORT_SYMBOL_FOR_MODULES(snp_prepare, "ccp"); diff --git a/arch/x86/virt/vmx/tdx/Makefile b/arch/x86/virt/vmx/tdx/Makefile index 90da47eb85ee..d1dbc5cc5697 100644 --- a/arch/x86/virt/vmx/tdx/Makefile +++ b/arch/x86/virt/vmx/tdx/Makefile @@ -1,2 +1,2 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-y += seamcall.o tdx.o +obj-y += seamcall.o seamldr.o tdx.o diff --git a/arch/x86/virt/vmx/tdx/seamcall_internal.h b/arch/x86/virt/vmx/tdx/seamcall_internal.h new file mode 100644 index 000000000000..be5f446467df --- /dev/null +++ b/arch/x86/virt/vmx/tdx/seamcall_internal.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SEAMCALL utilities for TDX host-side operations. + * + * Provides convenient wrappers around SEAMCALL assembly with retry logic, + * error reporting and cache coherency tracking. + * + * Copyright (C) 2021-2023 Intel Corporation + */ + +#ifndef _X86_VIRT_SEAMCALL_INTERNAL_H +#define _X86_VIRT_SEAMCALL_INTERNAL_H + +#include +#include +#include +#include +#include + +u64 __seamcall(u64 fn, struct tdx_module_args *args); +u64 __seamcall_ret(u64 fn, struct tdx_module_args *args); +u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args); + +typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args); + +static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn, + struct tdx_module_args *args) +{ + lockdep_assert_preemption_disabled(); + + /* + * SEAMCALLs are made to the TDX module and can generate dirty + * cachelines of TDX private memory. Mark cache state incoherent + * so that the cache can be flushed during kexec. + * + * This needs to be done before actually making the SEAMCALL, + * because kexec-ing CPU could send NMI to stop remote CPUs, + * in which case even disabling IRQ won't help here. + */ + this_cpu_write(cache_state_incoherent, true); + + return func(fn, args); +} + +static __always_inline u64 sc_retry(sc_func_t func, u64 fn, + struct tdx_module_args *args) +{ + int retry = RDRAND_RETRY_LOOPS; + u64 ret; + + do { + preempt_disable(); + ret = __seamcall_dirty_cache(func, fn, args); + preempt_enable(); + } while (ret == TDX_RND_NO_ENTROPY && --retry); + + return ret; +} + +#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args)) +#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args)) +#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args)) + +typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args); + +static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args) +{ + pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err); +} + +static inline void seamcall_err_ret(u64 fn, u64 err, + struct tdx_module_args *args) +{ + seamcall_err(fn, err, args); + pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n", + args->rcx, args->rdx, args->r8); + pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n", + args->r9, args->r10, args->r11); +} + +static __always_inline int sc_retry_prerr(sc_func_t func, + sc_err_func_t err_func, + u64 fn, struct tdx_module_args *args) +{ + u64 sret = sc_retry(func, fn, args); + + if (sret == TDX_SUCCESS) + return 0; + + if (sret == TDX_SEAMCALL_VMFAILINVALID) + return -ENODEV; + + if (sret == TDX_SEAMCALL_GP) + return -EOPNOTSUPP; + + if (sret == TDX_SEAMCALL_UD) + return -EACCES; + + err_func(fn, sret, args); + return -EIO; +} + +#define seamcall_prerr(__fn, __args) \ + sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args)) + +#define seamcall_prerr_ret(__fn, __args) \ + sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args)) + +#endif /* _X86_VIRT_SEAMCALL_INTERNAL_H */ diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c new file mode 100644 index 000000000000..b1137ca6150d --- /dev/null +++ b/arch/x86/virt/vmx/tdx/seamldr.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * P-SEAMLDR support for TDX module management features like runtime updates + * + * Copyright (C) 2025 Intel Corporation + */ +#define pr_fmt(fmt) "seamldr: " fmt + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "seamcall_internal.h" +#include "tdx.h" + +/* P-SEAMLDR SEAMCALL leaf function */ +#define P_SEAMLDR_INFO 0x8000000000000000 +#define P_SEAMLDR_INSTALL 0x8000000000000001 + +#define SEAMLDR_MAX_NR_MODULE_PAGES 496 +#define SEAMLDR_MAX_NR_SIG_PAGES 1 + +/* + * The seamldr_params "scenario" field specifies the operation mode: + * 0: Install TDX module from scratch (not used by kernel) + * 1: Update existing TDX module to a compatible version + */ +#define SEAMLDR_SCENARIO_UPDATE 1 + +/* + * This is the "SEAMLDR_PARAMS" data structure defined in the + * "SEAM Loader (SEAMLDR) Interface Specification". + * + * It is the in-memory ABI that the kernel passes to the P-SEAMLDR + * to update the TDX module. It breaks the TDX module image up in + * page-size pieces. + */ +struct seamldr_params { + u32 version; + u32 scenario; + u64 sigstruct_pages_pa_list[SEAMLDR_MAX_NR_SIG_PAGES]; + u8 reserved[104]; + u64 module_nr_pages; + u64 module_pages_pa_list[SEAMLDR_MAX_NR_MODULE_PAGES]; +} __packed; + +static_assert(sizeof(struct seamldr_params) == 4096); + +/* + * Serialize P-SEAMLDR calls since the hardware only allows a single CPU to + * interact with P-SEAMLDR simultaneously. Use raw version as the calls can + * be made with interrupts disabled, where plain spinlocks are prohibited in + * PREEMPT_RT kernels as they become sleeping locks. + */ +static DEFINE_RAW_SPINLOCK(seamldr_lock); + +static int seamldr_call(u64 fn, struct tdx_module_args *args) +{ + /* + * With this bug, P-SEAMLDR calls corrupt the VMCS + * pointer and must be avoided. This path should be + * unreachable since sysfs hides the ABIs. + */ + if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS)) { + WARN_ON(1); + return -EINVAL; + } + + guard(raw_spinlock)(&seamldr_lock); + return seamcall_prerr(fn, args); +} + +int seamldr_get_info(struct seamldr_info *seamldr_info) +{ + struct tdx_module_args args = {}; + + /* + * Use slow_virt_to_phys() since @seamldr_info may be allocated on + * the stack. + */ + args.rcx = slow_virt_to_phys(seamldr_info); + return seamldr_call(P_SEAMLDR_INFO, &args); +} +EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host"); + +/* Call into P-SEAMLDR to install a TDX module update */ +static int seamldr_install(const struct seamldr_params *params) +{ + struct tdx_module_args args = {}; + + args.rcx = __pa(params); + return seamldr_call(P_SEAMLDR_INSTALL, &args); +} + +#define TDX_IMAGE_VERSION_2 0x200 + +/* First page of the on-disk module update image: */ +struct tdx_image_header { + u16 version; + u16 checksum; + u8 signature[8]; + u32 sigstruct_nr_pages; + u32 module_nr_pages; + u8 reserved[4076]; +} __packed; + +#define TDX_IMAGE_HEADER_SIZE sizeof(struct tdx_image_header) +static_assert(TDX_IMAGE_HEADER_SIZE == 4096); + +/* + * Intel TDX module update ABI structure. aka. "TDX module blob". + * This is the on-disk format that fw_upload lands in a kernel + * buffer. + * + * @payload contains sigstruct pages followed by module pages. + */ +struct tdx_image { + struct tdx_image_header header; + u8 payload[]; +}; + +/* + * Given a vmalloc() allocation, write all of the backing physical + * addresses to pa_list[]. Caller guarantees that the array is big + * enough. + */ +static void populate_pa_list(u64 *pa_list, const u8 *vmalloc_addr, u32 vmalloc_len_pages) +{ + int i; + + for (i = 0; i < vmalloc_len_pages; i++) { + unsigned long offset = i * PAGE_SIZE; + unsigned long pfn = vmalloc_to_pfn(&vmalloc_addr[offset]); + + pa_list[i] = pfn << PAGE_SHIFT; + } +} + +static void populate_seamldr_params(struct seamldr_params *params, + const u8 *sig, u32 sig_nr_pages, + const u8 *mod, u32 mod_nr_pages) +{ + params->version = 0; + params->scenario = SEAMLDR_SCENARIO_UPDATE; + params->module_nr_pages = mod_nr_pages; + + populate_pa_list(params->sigstruct_pages_pa_list, sig, sig_nr_pages); + populate_pa_list(params->module_pages_pa_list, mod, mod_nr_pages); +} + +/* + * @image points to a vmalloc()'d 'struct tdx_image'. Transform + * it into @params which is the P-SEAMLDR ABI format. + */ +static int init_seamldr_params(struct seamldr_params *params, + const struct tdx_image *image, + u32 image_len) +{ + const struct tdx_image_header *header = &image->header; + + u32 sigstruct_len = header->sigstruct_nr_pages * PAGE_SIZE; + u32 module_len = header->module_nr_pages * PAGE_SIZE; + + u8 *header_start = (u8 *)header; + u8 *header_end = header_start + TDX_IMAGE_HEADER_SIZE; + + u8 *sigstruct_start = header_end; + u8 *sigstruct_end = sigstruct_start + sigstruct_len; + + u8 *module_start = sigstruct_end; + + /* Check the calculated payload size against the image size. */ + if (TDX_IMAGE_HEADER_SIZE + sigstruct_len + module_len != image_len) + return -EINVAL; + + /* Reject unsupported tdx_image ABI versions. */ + if (header->version != TDX_IMAGE_VERSION_2) + return -EINVAL; + + if (header->sigstruct_nr_pages > SEAMLDR_MAX_NR_SIG_PAGES || + header->module_nr_pages > SEAMLDR_MAX_NR_MODULE_PAGES) + return -EINVAL; + + if (memcmp(header->signature, "TDX-BLOB", sizeof(header->signature))) + return -EINVAL; + + if (memchr_inv(header->reserved, 0, sizeof(header->reserved))) + return -EINVAL; + + populate_seamldr_params(params, sigstruct_start, header->sigstruct_nr_pages, + module_start, header->module_nr_pages); + return 0; +} + +/* + * During a TDX module update, all CPUs start from MODULE_UPDATE_START and + * progress to MODULE_UPDATE_DONE. Each state is associated with certain + * work. For some states, just one CPU needs to perform the work, while + * other CPUs just wait during those states. + */ +enum module_update_state { + MODULE_UPDATE_START, + MODULE_UPDATE_SHUTDOWN, + MODULE_UPDATE_CPU_INSTALL, + MODULE_UPDATE_CPU_INIT, + MODULE_UPDATE_RUN_UPDATE, + MODULE_UPDATE_DONE, +}; + +static struct update_ctrl { + enum module_update_state state; + int num_ack; + int num_failed; + /* + * Protect update_ctrl. Raw spinlock as it will be acquired from + * interrupt-disabled contexts. + */ + raw_spinlock_t lock; +} update_ctrl; + +/* Called with ctrl->lock held or during initialization. */ +static void __set_target_state(struct update_ctrl *ctrl, + enum module_update_state newstate) +{ + /* Reset ack counter. */ + ctrl->num_ack = 0; + ctrl->state = newstate; +} + +/* Last one to ack a state moves to the next state. */ +static void ack_state(struct update_ctrl *ctrl, int result) +{ + raw_spin_lock(&ctrl->lock); + + ctrl->num_failed += !!result; + ctrl->num_ack++; + if (ctrl->num_ack == num_online_cpus() && !ctrl->num_failed) + __set_target_state(ctrl, ctrl->state + 1); + + raw_spin_unlock(&ctrl->lock); +} + +static void init_state(struct update_ctrl *ctrl) +{ + raw_spin_lock_init(&ctrl->lock); + __set_target_state(ctrl, MODULE_UPDATE_START + 1); + ctrl->num_failed = 0; +} + +/* + * See multi_cpu_stop() from where this multi-cpu state-machine was + * adopted. + */ +static int do_seamldr_install_module(void *seamldr_params) +{ + enum module_update_state curstate = MODULE_UPDATE_START; + enum module_update_state newstate; + bool is_lead_cpu = false; + int ret = 0; + + /* + * Some steps must be run on exactly one CPU. Pick a "lead" CPU to + * execute those steps. Use CPU 0 because it is always online. + */ + if (smp_processor_id() == 0) + is_lead_cpu = true; + + do { + newstate = READ_ONCE(update_ctrl.state); + + if (curstate == newstate) { + cpu_relax(); + continue; + } + + curstate = newstate; + switch (curstate) { + case MODULE_UPDATE_SHUTDOWN: + if (is_lead_cpu) + ret = tdx_module_shutdown(); + break; + case MODULE_UPDATE_CPU_INSTALL: + ret = seamldr_install(seamldr_params); + break; + case MODULE_UPDATE_CPU_INIT: + ret = tdx_cpu_enable(); + break; + case MODULE_UPDATE_RUN_UPDATE: + if (is_lead_cpu) + ret = tdx_module_run_update(); + break; + default: + break; + } + + ack_state(&update_ctrl, ret); + } while (curstate != MODULE_UPDATE_DONE && + !READ_ONCE(update_ctrl.num_failed)); + + return ret; +} + +/** + * seamldr_install_module - Install a new TDX module. + * @data: Pointer to the TDX module image. + * @data_len: Size of the TDX module image. + * + * Returns 0 on success, negative error code on failure. + */ +int seamldr_install_module(const u8 *data, u32 data_len) +{ + struct seamldr_params *params; + const struct tdx_image *image; + int ret; + + /* + * init_seamldr_params() reads the header early. + * Ensure there is enough data to do at least that. + */ + if (data_len < TDX_IMAGE_HEADER_SIZE) + return -EINVAL; + + image = (const struct tdx_image *)data; + + params = kzalloc_obj(*params); + if (!params) + return -ENOMEM; + + /* Populate 'params' from 'image'. */ + ret = init_seamldr_params(params, image, data_len); + if (ret) + goto out; + + /* Ensure a stable set of online CPUs for the update process. */ + cpus_read_lock(); + init_state(&update_ctrl); + ret = stop_machine_cpuslocked(do_seamldr_install_module, params, + cpu_online_mask); + cpus_read_unlock(); + +out: + kfree(params); + return ret; +} +EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host"); + +/* + * stop_machine() does not interrupt preemption-disabled regions. + * Simply disabling preempt prevents updates. + */ +void seamldr_lock_module_update(void) +{ + preempt_disable(); +} +EXPORT_SYMBOL_FOR_MODULES(seamldr_lock_module_update, "tdx-host"); + +void seamldr_unlock_module_update(void) +{ + preempt_enable(); +} +EXPORT_SYMBOL_FOR_MODULES(seamldr_unlock_module_update, "tdx-host"); diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index cb9b3210ab71..b15269b5941d 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -37,12 +37,23 @@ #include #include #include +#include #include #include #include #include +#include + +#include "seamcall_internal.h" #include "tdx.h" +struct tdx_module_state { + bool initialized; + bool sysinit_done; + int sysinit_ret; +}; + +static struct tdx_module_state tdx_module_state; static u32 tdx_global_keyid __ro_after_init; static u32 tdx_guest_keyid_start __ro_after_init; static u32 tdx_nr_guest_keyids __ro_after_init; @@ -56,53 +67,9 @@ static struct tdmr_info_list tdx_tdmr_list; /* All TDX-usable memory regions. Protected by mem_hotplug_lock. */ static LIST_HEAD(tdx_memlist); -static struct tdx_sys_info tdx_sysinfo __ro_after_init; -static bool tdx_module_initialized __ro_after_init; +static struct tdx_sys_info tdx_sysinfo; -typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args); - -static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args) -{ - pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err); -} - -static inline void seamcall_err_ret(u64 fn, u64 err, - struct tdx_module_args *args) -{ - seamcall_err(fn, err, args); - pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n", - args->rcx, args->rdx, args->r8); - pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n", - args->r9, args->r10, args->r11); -} - -static __always_inline int sc_retry_prerr(sc_func_t func, - sc_err_func_t err_func, - u64 fn, struct tdx_module_args *args) -{ - u64 sret = sc_retry(func, fn, args); - - if (sret == TDX_SUCCESS) - return 0; - - if (sret == TDX_SEAMCALL_VMFAILINVALID) - return -ENODEV; - - if (sret == TDX_SEAMCALL_GP) - return -EOPNOTSUPP; - - if (sret == TDX_SEAMCALL_UD) - return -EACCES; - - err_func(fn, sret, args); - return -EIO; -} - -#define seamcall_prerr(__fn, __args) \ - sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args)) - -#define seamcall_prerr_ret(__fn, __args) \ - sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args)) +static DEFINE_RAW_SPINLOCK(sysinit_lock); /* * Do the module global initialization once and return its result. @@ -111,31 +78,34 @@ static __always_inline int sc_retry_prerr(sc_func_t func, static int try_init_module_global(void) { struct tdx_module_args args = {}; - static DEFINE_RAW_SPINLOCK(sysinit_lock); - static bool sysinit_done; - static int sysinit_ret; + int ret; raw_spin_lock(&sysinit_lock); - if (sysinit_done) + /* Return the "cached" return code. */ + if (tdx_module_state.sysinit_done) { + ret = tdx_module_state.sysinit_ret; goto out; + } /* RCX is module attributes and all bits are reserved */ args.rcx = 0; - sysinit_ret = seamcall_prerr(TDH_SYS_INIT, &args); + ret = seamcall_prerr(TDH_SYS_INIT, &args); /* * The first SEAMCALL also detects the TDX module, thus * it can fail due to the TDX module is not loaded. * Dump message to let the user know. */ - if (sysinit_ret == -ENODEV) + if (ret == -ENODEV) pr_err("module not loaded\n"); - sysinit_done = true; + /* Save the return code for later callers. */ + tdx_module_state.sysinit_done = true; + tdx_module_state.sysinit_ret = ret; out: raw_spin_unlock(&sysinit_lock); - return sysinit_ret; + return ret; } /** @@ -143,7 +113,7 @@ out: * (and TDX module global initialization SEAMCALL if not done) on local cpu to * make this cpu be ready to run any other SEAMCALLs. */ -static int tdx_cpu_enable(void) +int tdx_cpu_enable(void) { struct tdx_module_args args = {}; int ret; @@ -184,6 +154,17 @@ static int tdx_online_cpu(unsigned int cpu) return ret; } +static void tdx_cpu_flush_cache(void) +{ + lockdep_assert_preemption_disabled(); + + if (!this_cpu_read(cache_state_incoherent)) + return; + + wbinvd(); + this_cpu_write(cache_state_incoherent, false); +} + static int tdx_offline_cpu(unsigned int cpu) { int i; @@ -220,17 +201,34 @@ static int tdx_offline_cpu(unsigned int cpu) return -EBUSY; done: + /* + * Flush cache on the CPU going offline to ensure no dirty + * cachelines of TDX private memory remain. This may be + * redundant with WBINVD done elsewhere during CPU offline + * (e.g. hlt_play_dead()), but do it explicitly for safety. + */ + tdx_cpu_flush_cache(); x86_virt_put_ref(X86_FEATURE_VMX); return 0; } static void tdx_shutdown_cpu(void *ign) { + /* + * Flush cache in preparation for kexec - this is necessary to avoid + * having dirty private memory cachelines when the new kernel boots, + * but WBINVD is a relatively expensive operation and doing it during + * kexec can exacerbate races in native_stop_other_cpus(). Do it + * now, since this is a safe moment and there is going to be no more + * TDX activity on this CPU from this point on. + */ + tdx_cpu_flush_cache(); x86_virt_put_ref(X86_FEATURE_VMX); } static void tdx_shutdown(void *ign) { + tdx_sys_disable(); on_each_cpu(tdx_shutdown_cpu, NULL, 1); } @@ -330,7 +328,7 @@ err: return ret; } -static __init int read_sys_metadata_field(u64 field_id, u64 *data) +static int read_sys_metadata_field(u64 field_id, u64 *data) { struct tdx_module_args args = {}; int ret; @@ -1270,12 +1268,70 @@ static __init int tdx_enable(void) register_syscore(&tdx_syscore); - tdx_module_initialized = true; + tdx_module_state.initialized = true; pr_info("TDX-Module initialized\n"); return 0; } subsys_initcall(tdx_enable); +int tdx_module_shutdown(void) +{ + struct tdx_sys_info_handoff handoff = {}; + struct tdx_module_args args = {}; + int ret; + int cpu; + + ret = get_tdx_sys_info_handoff(&handoff); + /* + * Handoff information is required for proper + * shutdown. Refuse to shut down without it. + */ + if (ret) + return ret; + + /* + * Use the module's handoff version as it is the highest the + * module can produce and most likely supported by newer modules. + */ + args.rcx = handoff.module_hv; + + ret = seamcall_prerr(TDH_SYS_SHUTDOWN, &args); + if (ret) + return ret; + + /* + * Clear global and per-CPU initialization flags so the new module + * can be fully re-initialized after a successful update. + * + * No locks needed as no concurrent accesses can occur here. + */ + memset(&tdx_module_state, 0, sizeof(tdx_module_state)); + for_each_possible_cpu(cpu) + per_cpu(tdx_lp_initialized, cpu) = false; + + return 0; +} + +int tdx_module_run_update(void) +{ + struct tdx_module_args args = {}; + int ret; + + ret = seamcall_prerr(TDH_SYS_UPDATE, &args); + if (ret) + return ret; + + ret = get_tdx_sys_info_version(&tdx_sysinfo.version); + /* + * Only fails if there is something unexpected + * and severely wrong with the module. + */ + WARN_ON_ONCE(ret); + + tdx_module_state.initialized = true; + return 0; +} + static bool is_pamt_page(unsigned long phys) { struct tdmr_info_list *tdmr_list = &tdx_tdmr_list; @@ -1453,6 +1509,8 @@ static struct notifier_block tdx_memory_nb = { static void __init check_tdx_erratum(void) { + u64 basic_msr; + /* * These CPUs have an erratum. A partial write from non-TD * software (e.g. via MOVNTI variants or UC/WC mapping) to TDX @@ -1464,6 +1522,14 @@ static void __init check_tdx_erratum(void) case INTEL_EMERALDRAPIDS_X: setup_force_cpu_bug(X86_BUG_TDX_PW_MCE); } + + /* + * Some TDX-capable CPUs have an erratum where the current VMCS is + * cleared after calling into P-SEAMLDR. + */ + rdmsrq(MSR_IA32_VMX_BASIC, basic_msr); + if (!(basic_msr & VMX_BASIC_NO_SEAMRET_INVD_VMCS)) + setup_force_cpu_bug(X86_BUG_SEAMRET_INVD_VMCS); } void __init tdx_init(void) @@ -1525,12 +1591,12 @@ void __init tdx_init(void) const struct tdx_sys_info *tdx_get_sysinfo(void) { - if (!tdx_module_initialized) + if (!tdx_module_state.initialized) return NULL; return (const struct tdx_sys_info *)&tdx_sysinfo; } -EXPORT_SYMBOL_FOR_KVM(tdx_get_sysinfo); +EXPORT_SYMBOL_FOR_MODULES(tdx_get_sysinfo, "kvm-intel,tdx-host"); u32 tdx_get_nr_guest_keyids(void) { @@ -1921,21 +1987,32 @@ u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page) } EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid); -#ifdef CONFIG_KEXEC_CORE -void tdx_cpu_flush_cache_for_kexec(void) +void tdx_sys_disable(void) { - lockdep_assert_preemption_disabled(); - - if (!this_cpu_read(cache_state_incoherent)) - return; + struct tdx_module_args args = {}; + u64 ret; /* - * Private memory cachelines need to be clean at the time of - * kexec. Write them back now, as the caller promises that - * there should be no more SEAMCALLs on this CPU. + * Don't loop forever. + * + * - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between + * calls. + * + * - TDX_SYS_BUSY could be returned due to contention with other + * TDH.SYS.* SEAMCALLs, but will lock out *new* TDH.SYS.* SEAMCALLs, + * so that SYS.DISABLE can eventually make progress. + * + * This is a 'destructive' SEAMCALL, in that no other SEAMCALL can be + * run after this until a full reinitialization is done. */ - wbinvd(); - this_cpu_write(cache_state_incoherent, false); + do { + ret = seamcall(TDH_SYS_DISABLE, &args); + } while (ret == TDX_INTERRUPTED_RESUMABLE || ret == TDX_SYS_BUSY); + + /* + * Print SEAMCALL failures, but not SW-defined error codes + * (SEAMCALL faulted with #GP/#UD, TDX not supported). + */ + if (ret && (ret & TDX_SW_ERROR) != TDX_SW_ERROR) + pr_err("TDH.SYS.DISABLE failed: 0x%016llx\n", ret); } -EXPORT_SYMBOL_FOR_KVM(tdx_cpu_flush_cache_for_kexec); -#endif diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index dde219c823b4..bdfd0e1e337a 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -46,6 +46,9 @@ #define TDH_PHYMEM_PAGE_WBINVD 41 #define TDH_VP_WR 43 #define TDH_SYS_CONFIG 45 +#define TDH_SYS_SHUTDOWN 52 +#define TDH_SYS_UPDATE 53 +#define TDH_SYS_DISABLE 69 /* * SEAMCALL leaf: @@ -84,9 +87,6 @@ struct tdmr_info { DECLARE_FLEX_ARRAY(struct tdmr_reserved_area, reserved_areas); } __packed __aligned(TDMR_INFO_ALIGNMENT); -/* Bit definitions of TDX_FEATURES0 metadata field */ -#define TDX_FEATURES0_NO_RBP_MOD BIT(18) - /* * Do not put any hardware-defined TDX structure representations below * this comment! @@ -110,4 +110,7 @@ struct tdmr_info_list { int max_tdmrs; /* How many 'tdmr_info's are allocated */ }; +int tdx_module_shutdown(void); +int tdx_module_run_update(void); + #endif diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c index c7db393a9cfb..e49c300f23d4 100644 --- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c +++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c @@ -7,7 +7,7 @@ * Include this file to other C file instead. */ -static __init int get_tdx_sys_info_version(struct tdx_sys_info_version *sysinfo_version) +static int get_tdx_sys_info_version(struct tdx_sys_info_version *sysinfo_version) { int ret = 0; u64 val; @@ -100,13 +100,26 @@ static __init int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_ return ret; } +static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff) +{ + int ret; + u64 val; + + ret = read_sys_metadata_field(0x8900000100000000, &val); + if (ret) + return ret; + + sysinfo_handoff->module_hv = val; + return 0; +} + static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo) { int ret = 0; ret = ret ?: get_tdx_sys_info_version(&sysinfo->version); - pr_info("Module version: %u.%u.%02u\n", + pr_info("Module version: " TDX_VERSION_FMT "\n", sysinfo->version.major_version, sysinfo->version.minor_version, sysinfo->version.update_version); diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index bed0791c17fc..86678ee22a04 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -181,6 +181,12 @@ static const struct apd_device_desc hip08_spi_desc = { .setup = acpi_apd_setup, .fixed_clk_rate = 250000000, }; + +static const struct apd_device_desc leca_i2c_desc = { + .setup = acpi_apd_setup, + .fixed_clk_rate = 250000000, +}; + #endif /* CONFIG_ARM64 */ #endif @@ -251,6 +257,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = { { "HISI02A2", APD_ADDR(hip08_i2c_desc) }, { "HISI02A3", APD_ADDR(hip08_lite_i2c_desc) }, { "HISI0173", APD_ADDR(hip08_spi_desc) }, + { "LECA0003", APD_ADDR(leca_i2c_desc) }, { "NXP0001", APD_ADDR(nxp_i2c_desc) }, #endif { } diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 3181d8aa32a3..e4b1db809187 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -216,7 +216,7 @@ config TI_SYSC config TS_NBUS tristate "Technologic Systems NBUS Driver" depends on SOC_IMX28 - depends on OF_GPIO && PWM + depends on PWM help Driver for the Technologic Systems NBUS which is used to interface with the peripherals in the FPGA of the TS-4600 SoM. diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index d1e9e0ac63b6..78f98aee7a66 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -1374,7 +1374,9 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid) return -EOPNOTSUPP; } - snp_prepare(); + rc = snp_prepare(); + if (rc) + return rc; /* * Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c index c769631ea15d..83f7eedf0b3f 100644 --- a/drivers/firmware/google/coreboot_table.c +++ b/drivers/firmware/google/coreboot_table.c @@ -112,16 +112,20 @@ void coreboot_driver_unregister(struct coreboot_driver *driver) } EXPORT_SYMBOL(coreboot_driver_unregister); -static int coreboot_table_populate(struct device *dev, void *ptr) +static int coreboot_table_populate(struct device *dev, void *ptr, resource_size_t len) { int i, ret; void *ptr_entry; struct coreboot_device *device; struct coreboot_table_entry *entry; struct coreboot_table_header *header = ptr; + void *ptr_end; + ptr_end = ptr + len; ptr_entry = ptr + header->header_bytes; for (i = 0; i < header->table_entries; i++) { + if (ptr_entry + sizeof(*entry) > ptr_end) + return -EINVAL; entry = ptr_entry; if (entry->size < sizeof(*entry)) { @@ -129,6 +133,9 @@ static int coreboot_table_populate(struct device *dev, void *ptr) return -EINVAL; } + if (ptr_entry + entry->size > ptr_end) + return -EINVAL; + device = kzalloc(sizeof(device->dev) + entry->size, GFP_KERNEL); if (!device) return -ENOMEM; @@ -148,13 +155,13 @@ static int coreboot_table_populate(struct device *dev, void *ptr) break; } + ptr_entry += entry->size; + ret = device_register(&device->dev); if (ret) { + dev_warn(dev, "failed to register coreboot device: %d\n", ret); put_device(&device->dev); - return ret; } - - ptr_entry += entry->size; } return 0; @@ -194,7 +201,7 @@ static int coreboot_table_probe(struct platform_device *pdev) if (!ptr) return -ENOMEM; - ret = coreboot_table_populate(dev, ptr); + ret = coreboot_table_populate(dev, ptr, len); memunmap(ptr); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 020e51e30317..bbd0ebe2f131 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -102,6 +102,14 @@ config GPIO_CDEV_V1 This ABI version is deprecated. Please use the latest ABI for new developments. +config GPIO_KUNIT + tristate "Build GPIO Kunit test cases" + depends on KUNIT + default KUNIT_ALL_TESTS + help + Say Y here to build the module containing Kunit test cases verifying + the functionality of the GPIO subsystem. + config GPIO_GENERIC depends on HAS_IOMEM # Only for IOMEM drivers tristate @@ -156,6 +164,7 @@ config GPIO_74XX_MMIO config GPIO_ALTERA tristate "Altera GPIO" + select GPIO_GENERIC select GPIOLIB_IRQCHIP help Say Y or M here to build support for the Altera PIO device. @@ -301,7 +310,7 @@ config GPIO_EM config GPIO_EN7523 tristate "Airoha GPIO support" - depends on ARCH_AIROHA + depends on ARCH_AIROHA || COMPILE_TEST default ARCH_AIROHA select GPIO_GENERIC select GPIOLIB_IRQCHIP @@ -698,7 +707,7 @@ config GPIO_SPACEMIT_K1 config GPIO_SPEAR_SPICS bool "ST SPEAr13xx SPI Chip Select as GPIO support" - depends on PLAT_SPEAR + depends on PLAT_SPEAR || COMPILE_TEST select GENERIC_IRQ_CHIP help Say yes here to support ST SPEAr SPI Chip Select as GPIO device. @@ -805,8 +814,18 @@ config GPIO_VISCONTI help Say yes here to support GPIO on Tohisba Visconti. +config GPIO_WAVESHARE_DSI_TOUCH + tristate "Waveshare GPIO controller for DSI panels" + depends on BACKLIGHT_CLASS_DEVICE + depends on I2C + select REGMAP_I2C + help + Enable support for the GPIO and PWM controller found on Waveshare DSI + TOUCH panel kits. It provides GPIOs (used for regulator control and + resets) and backlight support. + config GPIO_WCD934X - tristate "Qualcomm Technologies Inc WCD9340/WCD9341 GPIO controller driver" + tristate "Qualcomm WCD9340/WCD9341 GPIO controller driver" depends on MFD_WCD934X help This driver is to support GPIO block found on the Qualcomm Technologies @@ -814,7 +833,7 @@ config GPIO_WCD934X config GPIO_XGENE bool "APM X-Gene GPIO controller support" - depends on ARM64 + depends on ARM64 || COMPILE_TEST help This driver is to support the GPIO block within the APM X-Gene SoC platform's generic flash controller. The GPIO pins are muxed with @@ -858,7 +877,7 @@ config GPIO_XTENSA config GPIO_ZEVIO bool "LSI ZEVIO SoC memory mapped GPIOs" - depends on ARM + depends on ARM || COMPILE_TEST help Say yes here to support the GPIO controller in LSI ZEVIO SoCs. @@ -1093,15 +1112,6 @@ config GPIO_SCH311X To compile this driver as a module, choose M here: the module will be called gpio-sch311x. -config GPIO_TS5500 - tristate "TS-5500 DIO blocks and compatibles" - depends on TS5500 || COMPILE_TEST - help - This driver supports Digital I/O exposed by pin blocks found on some - Technologic Systems platforms. It includes, but is not limited to, 3 - blocks of the TS-5500: DIO1, DIO2 and the LCD port, and the TS-5600 - LCD port. - config GPIO_WINBOND tristate "Winbond Super I/O GPIO support" select ISA_BUS_API @@ -2060,6 +2070,7 @@ config GPIO_VIRTIO config GPIO_SIM tristate "GPIO Simulator Module" + depends on SYSFS select IRQ_SIM select CONFIGFS_FS help diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index b267598b517d..8ec03c9aec20 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o gpiolib-acpi-y := gpiolib-acpi-core.o gpiolib-acpi-quirks.o obj-$(CONFIG_GPIOLIB) += gpiolib-swnode.o obj-$(CONFIG_GPIO_SHARED) += gpiolib-shared.o +obj-$(CONFIG_GPIO_KUNIT) += gpiolib-kunit.o # Device drivers. Generally keep list sorted alphabetically obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o @@ -194,7 +195,6 @@ obj-$(CONFIG_GPIO_TPS68470) += gpio-tps68470.o obj-$(CONFIG_GPIO_TQMX86) += gpio-tqmx86.o obj-$(CONFIG_GPIO_TS4800) += gpio-ts4800.o obj-$(CONFIG_GPIO_TS4900) += gpio-ts4900.o -obj-$(CONFIG_GPIO_TS5500) += gpio-ts5500.o obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o @@ -205,6 +205,7 @@ obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o obj-$(CONFIG_GPIO_VIRTIO) += gpio-virtio.o obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o +obj-$(CONFIG_GPIO_WAVESHARE_DSI_TOUCH) += gpio-waveshare-dsi.o obj-$(CONFIG_GPIO_WCD934X) += gpio-wcd934x.o obj-$(CONFIG_GPIO_WHISKEY_COVE) += gpio-wcove.o obj-$(CONFIG_GPIO_WINBOND) += gpio-winbond.o diff --git a/drivers/gpio/gpio-74x164.c b/drivers/gpio/gpio-74x164.c index c226524efeba..5ca61cf5206a 100644 --- a/drivers/gpio/gpio-74x164.c +++ b/drivers/gpio/gpio-74x164.c @@ -112,7 +112,7 @@ static int gen_74x164_probe(struct spi_device *spi) { struct device *dev = &spi->dev; struct gen_74x164_chip *chip; - u32 nregs; + u32 nregs, init_state; int ret; /* @@ -134,6 +134,21 @@ static int gen_74x164_probe(struct spi_device *spi) chip->registers = nregs; + /* + * Optionally seed the chain with a board-specified pattern so the + * outputs come up in a known state on the first SPI write. The + * property follows the nxp,pcf8575 convention where bit N maps to + * GPIO line N. On this output-only device, bit=0 drives the line + * low and bit=1 drives it high. The bitmask covers up to 32 lines; + * any further outputs come up zeroed by devm_kzalloc(). + */ + if (!device_property_read_u32(dev, "lines-initial-states", &init_state)) { + unsigned int i; + + for (i = 0; i < min(nregs, 4U); i++) + chip->buffer[nregs - 1 - i] = (init_state >> (i * 8)) & 0xff; + } + chip->gpiod_oe = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(chip->gpiod_oe)) return PTR_ERR(chip->gpiod_oe); diff --git a/drivers/gpio/gpio-adnp.c b/drivers/gpio/gpio-adnp.c index fe5bcaa90496..350feea2afa3 100644 --- a/drivers/gpio/gpio-adnp.c +++ b/drivers/gpio/gpio-adnp.c @@ -501,8 +501,8 @@ static int adnp_i2c_probe(struct i2c_client *client) } static const struct i2c_device_id adnp_i2c_id[] = { - { "gpio-adnp" }, - { }, + { .name = "gpio-adnp" }, + { } }; MODULE_DEVICE_TABLE(i2c, adnp_i2c_id); diff --git a/drivers/gpio/gpio-adp5585.c b/drivers/gpio/gpio-adp5585.c index 0fd3cc26d017..6f10fc646008 100644 --- a/drivers/gpio/gpio-adp5585.c +++ b/drivers/gpio/gpio-adp5585.c @@ -507,8 +507,8 @@ static const struct adp5585_gpio_chip adp5589_gpio_chip_info = { }; static const struct platform_device_id adp5585_gpio_id_table[] = { - { "adp5585-gpio", (kernel_ulong_t)&adp5585_gpio_chip_info }, - { "adp5589-gpio", (kernel_ulong_t)&adp5589_gpio_chip_info }, + { .name = "adp5585-gpio", .driver_data = (kernel_ulong_t)&adp5585_gpio_chip_info }, + { .name = "adp5589-gpio", .driver_data = (kernel_ulong_t)&adp5589_gpio_chip_info }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(platform, adp5585_gpio_id_table); diff --git a/drivers/gpio/gpio-altera.c b/drivers/gpio/gpio-altera.c index 9508d764cce4..fe144360a88d 100644 --- a/drivers/gpio/gpio-altera.c +++ b/drivers/gpio/gpio-altera.c @@ -17,6 +17,7 @@ #include #include +#include #define ALTERA_GPIO_MAX_NGPIO 32 #define ALTERA_GPIO_DATA 0x0 @@ -26,7 +27,7 @@ /** * struct altera_gpio_chip -* @gc : GPIO chip structure. +* @chip : Generic GPIO chip structure. * @regs : memory mapped IO address for the controller registers. * @gpio_lock : synchronization lock so that new irq/set/get requests * will be blocked until the current one completes. @@ -34,7 +35,7 @@ * (rising, falling, both, high) */ struct altera_gpio_chip { - struct gpio_chip gc; + struct gpio_generic_chip chip; void __iomem *regs; raw_spinlock_t gpio_lock; int interrupt_trigger; @@ -106,72 +107,6 @@ static unsigned int altera_gpio_irq_startup(struct irq_data *d) return 0; } -static int altera_gpio_get(struct gpio_chip *gc, unsigned offset) -{ - struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc); - - return !!(readl(altera_gc->regs + ALTERA_GPIO_DATA) & BIT(offset)); -} - -static int altera_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) -{ - struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc); - unsigned long flags; - unsigned int data_reg; - - raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags); - data_reg = readl(altera_gc->regs + ALTERA_GPIO_DATA); - if (value) - data_reg |= BIT(offset); - else - data_reg &= ~BIT(offset); - writel(data_reg, altera_gc->regs + ALTERA_GPIO_DATA); - raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags); - - return 0; -} - -static int altera_gpio_direction_input(struct gpio_chip *gc, unsigned offset) -{ - struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc); - unsigned long flags; - unsigned int gpio_ddr; - - raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags); - /* Set pin as input, assumes software controlled IP */ - gpio_ddr = readl(altera_gc->regs + ALTERA_GPIO_DIR); - gpio_ddr &= ~BIT(offset); - writel(gpio_ddr, altera_gc->regs + ALTERA_GPIO_DIR); - raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags); - - return 0; -} - -static int altera_gpio_direction_output(struct gpio_chip *gc, - unsigned offset, int value) -{ - struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc); - unsigned long flags; - unsigned int data_reg, gpio_ddr; - - raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags); - /* Sets the GPIO value */ - data_reg = readl(altera_gc->regs + ALTERA_GPIO_DATA); - if (value) - data_reg |= BIT(offset); - else - data_reg &= ~BIT(offset); - writel(data_reg, altera_gc->regs + ALTERA_GPIO_DATA); - - /* Set pin as output, assumes software controlled IP */ - gpio_ddr = readl(altera_gc->regs + ALTERA_GPIO_DIR); - gpio_ddr |= BIT(offset); - writel(gpio_ddr, altera_gc->regs + ALTERA_GPIO_DIR); - raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags); - - return 0; -} - static void altera_gpio_irq_edge_handler(struct irq_desc *desc) { struct gpio_chip *gc = irq_desc_get_handler_data(desc); @@ -231,9 +166,12 @@ static const struct irq_chip altera_gpio_irq_chip = { static int altera_gpio_probe(struct platform_device *pdev) { + struct gpio_generic_chip_config config; struct device *dev = &pdev->dev; int reg, ret; struct altera_gpio_chip *altera_gc; + struct gpio_generic_chip *chip; + struct gpio_chip *gc; struct gpio_irq_chip *girq; int mapped_irq; @@ -243,35 +181,45 @@ static int altera_gpio_probe(struct platform_device *pdev) raw_spin_lock_init(&altera_gc->gpio_lock); + altera_gc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(altera_gc->regs)) + return dev_err_probe(dev, PTR_ERR(altera_gc->regs), + "failed to ioremap memory resource\n"); + + chip = &altera_gc->chip; + + config = (struct gpio_generic_chip_config) { + .dev = dev, + .sz = 4, + .dat = altera_gc->regs + ALTERA_GPIO_DATA, + .set = altera_gc->regs + ALTERA_GPIO_DATA, + .dirout = altera_gc->regs + ALTERA_GPIO_DIR, + }; + + ret = gpio_generic_chip_init(chip, &config); + if (ret) + return dev_err_probe(dev, ret, "unable to init generic GPIO\n"); + + gc = &chip->gc; + if (device_property_read_u32(dev, "altr,ngpio", ®)) /* By default assume maximum ngpio */ - altera_gc->gc.ngpio = ALTERA_GPIO_MAX_NGPIO; + gc->ngpio = ALTERA_GPIO_MAX_NGPIO; else - altera_gc->gc.ngpio = reg; + gc->ngpio = reg; - if (altera_gc->gc.ngpio > ALTERA_GPIO_MAX_NGPIO) { + if (gc->ngpio > ALTERA_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "ngpio is greater than %d, defaulting to %d\n", ALTERA_GPIO_MAX_NGPIO, ALTERA_GPIO_MAX_NGPIO); - altera_gc->gc.ngpio = ALTERA_GPIO_MAX_NGPIO; + gc->ngpio = ALTERA_GPIO_MAX_NGPIO; } - altera_gc->gc.direction_input = altera_gpio_direction_input; - altera_gc->gc.direction_output = altera_gpio_direction_output; - altera_gc->gc.get = altera_gpio_get; - altera_gc->gc.set = altera_gpio_set; - altera_gc->gc.owner = THIS_MODULE; - altera_gc->gc.parent = &pdev->dev; - altera_gc->gc.base = -1; - - altera_gc->gc.label = devm_kasprintf(dev, GFP_KERNEL, "%pfw", dev_fwnode(dev)); - if (!altera_gc->gc.label) + gc->base = -1; + gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pfw", dev_fwnode(dev)); + if (!gc->label) return -ENOMEM; - altera_gc->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(altera_gc->regs)) - return dev_err_probe(dev, PTR_ERR(altera_gc->regs), "failed to ioremap memory resource\n"); - mapped_irq = platform_get_irq_optional(pdev, 0); if (mapped_irq < 0) goto skip_irq; @@ -283,7 +231,7 @@ static int altera_gpio_probe(struct platform_device *pdev) } altera_gc->interrupt_trigger = reg; - girq = &altera_gc->gc.irq; + girq = &gc->irq; gpio_irq_chip_set_chip(girq, &altera_gpio_irq_chip); if (altera_gc->interrupt_trigger == IRQ_TYPE_LEVEL_HIGH) @@ -300,7 +248,7 @@ static int altera_gpio_probe(struct platform_device *pdev) girq->parents[0] = mapped_irq; skip_irq: - ret = devm_gpiochip_add_data(dev, &altera_gc->gc, altera_gc); + ret = devm_gpiochip_add_data(dev, gc, altera_gc); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; diff --git a/drivers/gpio/gpio-amd8111.c b/drivers/gpio/gpio-amd8111.c index 15fd5e210d74..8078b5d7b80c 100644 --- a/drivers/gpio/gpio-amd8111.c +++ b/drivers/gpio/gpio-amd8111.c @@ -59,8 +59,8 @@ * want to register another driver on the same PCI id. */ static const struct pci_device_id pci_tbl[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS), 0 }, - { 0, }, /* terminate list */ + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS) }, + { }, /* terminate list */ }; MODULE_DEVICE_TABLE(pci, pci_tbl); diff --git a/drivers/gpio/gpio-bd72720.c b/drivers/gpio/gpio-bd72720.c index d0f936ed80af..306e23411209 100644 --- a/drivers/gpio/gpio-bd72720.c +++ b/drivers/gpio/gpio-bd72720.c @@ -263,8 +263,8 @@ static int gpo_bd72720_probe(struct platform_device *pdev) } static const struct platform_device_id bd72720_gpio_id[] = { - { "bd72720-gpio" }, - { }, + { .name = "bd72720-gpio" }, + { } }; MODULE_DEVICE_TABLE(platform, bd72720_gpio_id); diff --git a/drivers/gpio/gpio-bd9571mwv.c b/drivers/gpio/gpio-bd9571mwv.c index cc5b1746f2fe..f829fc40c02b 100644 --- a/drivers/gpio/gpio-bd9571mwv.c +++ b/drivers/gpio/gpio-bd9571mwv.c @@ -110,8 +110,8 @@ static int bd9571mwv_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id bd9571mwv_gpio_id_table[] = { - { "bd9571mwv-gpio", ROHM_CHIP_TYPE_BD9571 }, - { "bd9574mwf-gpio", ROHM_CHIP_TYPE_BD9574 }, + { .name = "bd9571mwv-gpio", .driver_data = ROHM_CHIP_TYPE_BD9571 }, + { .name = "bd9574mwf-gpio", .driver_data = ROHM_CHIP_TYPE_BD9574 }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, bd9571mwv_gpio_id_table); diff --git a/drivers/gpio/gpio-cros-ec.c b/drivers/gpio/gpio-cros-ec.c index 435483826c6e..9deda8a9d11a 100644 --- a/drivers/gpio/gpio-cros-ec.c +++ b/drivers/gpio/gpio-cros-ec.c @@ -196,8 +196,8 @@ static int cros_ec_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id cros_ec_gpio_id[] = { - { "cros-ec-gpio", 0 }, - {} + { .name = "cros-ec-gpio" }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_gpio_id); diff --git a/drivers/gpio/gpio-ds4520.c b/drivers/gpio/gpio-ds4520.c index f52ecae382a4..5add662a7ef5 100644 --- a/drivers/gpio/gpio-ds4520.c +++ b/drivers/gpio/gpio-ds4520.c @@ -54,7 +54,7 @@ static const struct of_device_id ds4520_gpio_of_match_table[] = { MODULE_DEVICE_TABLE(of, ds4520_gpio_of_match_table); static const struct i2c_device_id ds4520_gpio_id_table[] = { - { "ds4520-gpio" }, + { .name = "ds4520-gpio" }, { } }; MODULE_DEVICE_TABLE(i2c, ds4520_gpio_id_table); diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c index 15cebc8b5d66..c1f3d83a67c1 100644 --- a/drivers/gpio/gpio-dwapb.c +++ b/drivers/gpio/gpio-dwapb.c @@ -694,6 +694,7 @@ static const struct acpi_device_id dwapb_acpi_match[] = { {"APMC0D07", GPIO_REG_OFFSET_V1}, {"APMC0D81", GPIO_REG_OFFSET_V2}, {"FUJI200A", GPIO_REG_OFFSET_V1}, + {"LECA0001", GPIO_REG_OFFSET_V1}, { } }; MODULE_DEVICE_TABLE(acpi, dwapb_acpi_match); diff --git a/drivers/gpio/gpio-ep93xx.c b/drivers/gpio/gpio-ep93xx.c index 1f56e44ffc9a..8784e433e1ff 100644 --- a/drivers/gpio/gpio-ep93xx.c +++ b/drivers/gpio/gpio-ep93xx.c @@ -323,8 +323,7 @@ static int ep93xx_setup_irqs(struct platform_device *pdev, } girq->default_type = IRQ_TYPE_NONE; - /* TODO: replace with handle_bad_irq() once we are fully hierarchical */ - girq->handler = handle_simple_irq; + girq->handler = handle_bad_irq; return 0; } diff --git a/drivers/gpio/gpio-fxl6408.c b/drivers/gpio/gpio-fxl6408.c index afc1b8461dab..45b02d36e66f 100644 --- a/drivers/gpio/gpio-fxl6408.c +++ b/drivers/gpio/gpio-fxl6408.c @@ -150,7 +150,7 @@ static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = { MODULE_DEVICE_TABLE(of, fxl6408_dt_ids); static const struct i2c_device_id fxl6408_id[] = { - { "fxl6408" }, + { .name = "fxl6408" }, { } }; MODULE_DEVICE_TABLE(i2c, fxl6408_id); diff --git a/drivers/gpio/gpio-gw-pld.c b/drivers/gpio/gpio-gw-pld.c index 2e5d97b7363f..bf1f91c3c4a8 100644 --- a/drivers/gpio/gpio-gw-pld.c +++ b/drivers/gpio/gpio-gw-pld.c @@ -109,7 +109,7 @@ static int gw_pld_probe(struct i2c_client *client) } static const struct i2c_device_id gw_pld_id[] = { - { "gw-pld", }, + { .name = "gw-pld" }, { } }; MODULE_DEVICE_TABLE(i2c, gw_pld_id); diff --git a/drivers/gpio/gpio-ixp4xx.c b/drivers/gpio/gpio-ixp4xx.c index f34d87869c8b..669b139cd499 100644 --- a/drivers/gpio/gpio-ixp4xx.c +++ b/drivers/gpio/gpio-ixp4xx.c @@ -311,12 +311,7 @@ static int ixp4xx_gpio_probe(struct platform_device *pdev) } g->chip.gc.ngpio = 16; g->chip.gc.label = "IXP4XX_GPIO_CHIP"; - /* - * TODO: when we have migrated to device tree and all GPIOs - * are fetched using phandles, set this to -1 to get rid of - * the fixed gpiochip base. - */ - g->chip.gc.base = 0; + g->chip.gc.base = -1; g->chip.gc.parent = &pdev->dev; g->chip.gc.owner = THIS_MODULE; diff --git a/drivers/gpio/gpio-lp873x.c b/drivers/gpio/gpio-lp873x.c index f4413fa5a811..0d1bd9df265a 100644 --- a/drivers/gpio/gpio-lp873x.c +++ b/drivers/gpio/gpio-lp873x.c @@ -156,7 +156,7 @@ static int lp873x_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id lp873x_gpio_id_table[] = { - { "lp873x-gpio", }, + { .name = "lp873x-gpio" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, lp873x_gpio_id_table); diff --git a/drivers/gpio/gpio-lp87565.c b/drivers/gpio/gpio-lp87565.c index 0f337c1283b2..3ac78f2b0fa7 100644 --- a/drivers/gpio/gpio-lp87565.c +++ b/drivers/gpio/gpio-lp87565.c @@ -171,7 +171,7 @@ static int lp87565_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id lp87565_gpio_id_table[] = { - { "lp87565-q1-gpio", }, + { .name = "lp87565-q1-gpio" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, lp87565_gpio_id_table); diff --git a/drivers/gpio/gpio-max7300.c b/drivers/gpio/gpio-max7300.c index 621d609ece90..62f2434c0d79 100644 --- a/drivers/gpio/gpio-max7300.c +++ b/drivers/gpio/gpio-max7300.c @@ -53,7 +53,7 @@ static void max7300_remove(struct i2c_client *client) } static const struct i2c_device_id max7300_id[] = { - { "max7300" }, + { .name = "max7300" }, { } }; MODULE_DEVICE_TABLE(i2c, max7300_id); diff --git a/drivers/gpio/gpio-max732x.c b/drivers/gpio/gpio-max732x.c index 281ba1740a6a..24c67c912954 100644 --- a/drivers/gpio/gpio-max732x.c +++ b/drivers/gpio/gpio-max732x.c @@ -103,16 +103,16 @@ static uint64_t max732x_features[] = { }; static const struct i2c_device_id max732x_id[] = { - { "max7319", MAX7319 }, - { "max7320", MAX7320 }, - { "max7321", MAX7321 }, - { "max7322", MAX7322 }, - { "max7323", MAX7323 }, - { "max7324", MAX7324 }, - { "max7325", MAX7325 }, - { "max7326", MAX7326 }, - { "max7327", MAX7327 }, - { }, + { .name = "max7319", .driver_data = MAX7319 }, + { .name = "max7320", .driver_data = MAX7320 }, + { .name = "max7321", .driver_data = MAX7321 }, + { .name = "max7322", .driver_data = MAX7322 }, + { .name = "max7323", .driver_data = MAX7323 }, + { .name = "max7324", .driver_data = MAX7324 }, + { .name = "max7325", .driver_data = MAX7325 }, + { .name = "max7326", .driver_data = MAX7326 }, + { .name = "max7327", .driver_data = MAX7327 }, + { } }; MODULE_DEVICE_TABLE(i2c, max732x_id); diff --git a/drivers/gpio/gpio-max77620.c b/drivers/gpio/gpio-max77620.c index e6c85411c695..2bf3b55a61b5 100644 --- a/drivers/gpio/gpio-max77620.c +++ b/drivers/gpio/gpio-max77620.c @@ -367,7 +367,7 @@ static int max77620_gpio_probe(struct platform_device *pdev) static const struct platform_device_id max77620_gpio_devtype[] = { { .name = "max77620-gpio", }, { .name = "max20024-gpio", }, - {}, + { } }; MODULE_DEVICE_TABLE(platform, max77620_gpio_devtype); diff --git a/drivers/gpio/gpio-max77759.c b/drivers/gpio/gpio-max77759.c index 3bf9f23d1532..c6bdac7fb44a 100644 --- a/drivers/gpio/gpio-max77759.c +++ b/drivers/gpio/gpio-max77759.c @@ -502,7 +502,7 @@ static const struct of_device_id max77759_gpio_of_id[] = { MODULE_DEVICE_TABLE(of, max77759_gpio_of_id); static const struct platform_device_id max77759_gpio_platform_id[] = { - { "max77759-gpio", }, + { .name = "max77759-gpio" }, { } }; MODULE_DEVICE_TABLE(platform, max77759_gpio_platform_id); diff --git a/drivers/gpio/gpio-mt7621.c b/drivers/gpio/gpio-mt7621.c index 91230be51587..a814885ccd5d 100644 --- a/drivers/gpio/gpio-mt7621.c +++ b/drivers/gpio/gpio-mt7621.c @@ -29,8 +29,8 @@ #define GPIO_REG_EDGE 0xA0 struct mtk_gc { - struct irq_chip irq_chip; struct gpio_generic_chip chip; + struct mtk *parent_priv; int bank; u32 rising; u32 falling; @@ -41,20 +41,32 @@ struct mtk_gc { /** * struct mtk - state container for * data of the platform driver. It is 3 - * separate gpio-chip each one with its - * own irq_chip. - * @dev: device instance + * separate gpio-chip having an IRQ + * linear domain shared for all of them + * @pdev: platform device instance * @base: memory base address + * @irq_domain: IRQ linear domain shared across the three gpio chips * @gpio_irq: irq number from the device tree + * @num_gpios: total number of gpio pins on the three gpio chips * @gc_map: array of the gpio chips */ struct mtk { - struct device *dev; + struct platform_device *pdev; void __iomem *base; + struct irq_domain *irq_domain; int gpio_irq; + int num_gpios; struct mtk_gc gc_map[MTK_BANK_CNT]; }; +static inline struct mtk * +mt7621_gpio_gc_to_priv(struct gpio_chip *gc) +{ + struct mtk_gc *bank = gpiochip_get_data(gc); + + return bank->parent_priv; +} + static inline struct mtk_gc * to_mediatek_gpio(struct gpio_chip *chip) { @@ -67,7 +79,7 @@ static inline void mtk_gpio_w32(struct mtk_gc *rg, u32 offset, u32 val) { struct gpio_chip *gc = &rg->chip.gc; - struct mtk *mtk = gpiochip_get_data(gc); + struct mtk *mtk = mt7621_gpio_gc_to_priv(gc); offset = (rg->bank * GPIO_BANK_STRIDE) + offset; gpio_generic_write_reg(&rg->chip, mtk->base + offset, val); @@ -77,41 +89,62 @@ static inline u32 mtk_gpio_r32(struct mtk_gc *rg, u32 offset) { struct gpio_chip *gc = &rg->chip.gc; - struct mtk *mtk = gpiochip_get_data(gc); + struct mtk *mtk = mt7621_gpio_gc_to_priv(gc); offset = (rg->bank * GPIO_BANK_STRIDE) + offset; return gpio_generic_read_reg(&rg->chip, mtk->base + offset); } -static irqreturn_t -mediatek_gpio_irq_handler(int irq, void *data) +static void +mt7621_gpio_irq_bank_handler(struct mtk_gc *bank) { - struct gpio_chip *gc = data; - struct mtk_gc *rg = to_mediatek_gpio(gc); - irqreturn_t ret = IRQ_NONE; + struct mtk *priv = bank->parent_priv; + struct irq_domain *domain = priv->irq_domain; + int hwbase = bank->chip.gc.offset; unsigned long pending; - int bit; + unsigned int offset; - pending = mtk_gpio_r32(rg, GPIO_REG_STAT); + pending = mtk_gpio_r32(bank, GPIO_REG_STAT); + if (!pending) + return; - for_each_set_bit(bit, &pending, MTK_BANK_WIDTH) { - generic_handle_domain_irq(gc->irq.domain, bit); - mtk_gpio_w32(rg, GPIO_REG_STAT, BIT(bit)); - ret |= IRQ_HANDLED; + mtk_gpio_w32(bank, GPIO_REG_STAT, pending); + + for_each_set_bit(offset, &pending, MTK_BANK_WIDTH) + generic_handle_domain_irq(domain, hwbase + offset); +} + +static void +mt7621_gpio_irq_handler(struct irq_desc *desc) +{ + struct mtk *priv = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + int i; + + chained_irq_enter(chip, desc); + for (i = 0; i < MTK_BANK_CNT; i++) { + struct mtk_gc *bank = &priv->gc_map[i]; + + mt7621_gpio_irq_bank_handler(bank); } + chained_irq_exit(chip, desc); +} - return ret; +static int +mt7621_gpio_hwirq_to_offset(irq_hw_number_t hwirq, struct mtk_gc *bank) +{ + return hwirq - bank->chip.gc.offset; } static void mediatek_gpio_irq_unmask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct mtk_gc *rg = to_mediatek_gpio(gc); - int pin = d->hwirq; + struct mtk_gc *rg = gpiochip_get_data(gc); + u32 mask = mt7621_gpio_hwirq_to_offset(d->hwirq, rg); u32 rise, fall, high, low; - gpiochip_enable_irq(gc, d->hwirq); + gpiochip_enable_irq(gc, mask); guard(gpio_generic_lock_irqsave)(&rg->chip); @@ -119,18 +152,18 @@ mediatek_gpio_irq_unmask(struct irq_data *d) fall = mtk_gpio_r32(rg, GPIO_REG_FEDGE); high = mtk_gpio_r32(rg, GPIO_REG_HLVL); low = mtk_gpio_r32(rg, GPIO_REG_LLVL); - mtk_gpio_w32(rg, GPIO_REG_REDGE, rise | (BIT(pin) & rg->rising)); - mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall | (BIT(pin) & rg->falling)); - mtk_gpio_w32(rg, GPIO_REG_HLVL, high | (BIT(pin) & rg->hlevel)); - mtk_gpio_w32(rg, GPIO_REG_LLVL, low | (BIT(pin) & rg->llevel)); + mtk_gpio_w32(rg, GPIO_REG_REDGE, rise | (BIT(mask) & rg->rising)); + mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall | (BIT(mask) & rg->falling)); + mtk_gpio_w32(rg, GPIO_REG_HLVL, high | (BIT(mask) & rg->hlevel)); + mtk_gpio_w32(rg, GPIO_REG_LLVL, low | (BIT(mask) & rg->llevel)); } static void mediatek_gpio_irq_mask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct mtk_gc *rg = to_mediatek_gpio(gc); - int pin = d->hwirq; + struct mtk_gc *rg = gpiochip_get_data(gc); + u32 mask = mt7621_gpio_hwirq_to_offset(d->hwirq, rg); u32 rise, fall, high, low; scoped_guard(gpio_generic_lock_irqsave, &rg->chip) { @@ -138,22 +171,21 @@ mediatek_gpio_irq_mask(struct irq_data *d) fall = mtk_gpio_r32(rg, GPIO_REG_FEDGE); high = mtk_gpio_r32(rg, GPIO_REG_HLVL); low = mtk_gpio_r32(rg, GPIO_REG_LLVL); - mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall & ~BIT(pin)); - mtk_gpio_w32(rg, GPIO_REG_REDGE, rise & ~BIT(pin)); - mtk_gpio_w32(rg, GPIO_REG_HLVL, high & ~BIT(pin)); - mtk_gpio_w32(rg, GPIO_REG_LLVL, low & ~BIT(pin)); + mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall & ~BIT(mask)); + mtk_gpio_w32(rg, GPIO_REG_REDGE, rise & ~BIT(mask)); + mtk_gpio_w32(rg, GPIO_REG_HLVL, high & ~BIT(mask)); + mtk_gpio_w32(rg, GPIO_REG_LLVL, low & ~BIT(mask)); } - gpiochip_disable_irq(gc, d->hwirq); + gpiochip_disable_irq(gc, mask); } static int mediatek_gpio_irq_type(struct irq_data *d, unsigned int type) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct mtk_gc *rg = to_mediatek_gpio(gc); - int pin = d->hwirq; - u32 mask = BIT(pin); + struct mtk_gc *rg = gpiochip_get_data(gc); + u32 mask = BIT(mt7621_gpio_hwirq_to_offset(d->hwirq, rg)); if (type == IRQ_TYPE_PROBE) { if ((rg->rising | rg->falling | @@ -190,6 +222,26 @@ mediatek_gpio_irq_type(struct irq_data *d, unsigned int type) return 0; } +static int +mt7621_gpio_irq_reqres(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mtk_gc *rg = gpiochip_get_data(gc); + unsigned int irq = mt7621_gpio_hwirq_to_offset(d->hwirq, rg); + + return gpiochip_reqres_irq(gc, irq); +} + +static void +mt7621_gpio_irq_relres(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mtk_gc *rg = gpiochip_get_data(gc); + unsigned int irq = mt7621_gpio_hwirq_to_offset(d->hwirq, rg); + + gpiochip_relres_irq(gc, irq); +} + static int mediatek_gpio_xlate(struct gpio_chip *chip, const struct of_phandle_args *spec, u32 *flags) @@ -208,14 +260,123 @@ mediatek_gpio_xlate(struct gpio_chip *chip, static const struct irq_chip mt7621_irq_chip = { .name = "mt7621-gpio", + .irq_request_resources = mt7621_gpio_irq_reqres, + .irq_release_resources = mt7621_gpio_irq_relres, .irq_mask_ack = mediatek_gpio_irq_mask, .irq_mask = mediatek_gpio_irq_mask, .irq_unmask = mediatek_gpio_irq_unmask, .irq_set_type = mediatek_gpio_irq_type, .flags = IRQCHIP_IMMUTABLE, - GPIOCHIP_IRQ_RESOURCE_HELPERS, }; +static void +mt7621_gpio_remove(struct platform_device *pdev) +{ + struct mtk *priv = platform_get_drvdata(pdev); + int offset, virq; + + if (priv->gpio_irq > 0) + irq_set_chained_handler_and_data(priv->gpio_irq, NULL, NULL); + + /* Remove all IRQ mappings and delete the domain */ + if (priv->irq_domain) { + for (offset = 0; offset < priv->num_gpios; offset++) { + virq = irq_find_mapping(priv->irq_domain, offset); + irq_dispose_mapping(virq); + } + irq_domain_remove(priv->irq_domain); + } +} + +static struct mtk_gc * +mt7621_gpio_hwirq_to_bank(struct mtk *priv, irq_hw_number_t hwirq) +{ + int i; + + for (i = 0; i < MTK_BANK_CNT; i++) { + struct mtk_gc *bank = &priv->gc_map[i]; + + if (hwirq >= bank->chip.gc.offset && + hwirq < (bank->chip.gc.offset + bank->chip.gc.ngpio)) + return bank; + } + + return NULL; +} + +static int +mt7621_gpio_irq_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct mtk *priv = d->host_data; + struct mtk_gc *bank = mt7621_gpio_hwirq_to_bank(priv, hwirq); + struct platform_device *pdev = priv->pdev; + int ret; + + if (!bank) + return -EINVAL; + + dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n", + irq, (int)hwirq, bank->bank); + + ret = irq_set_chip_data(irq, &bank->chip.gc); + if (ret < 0) + return ret; + + irq_set_chip_and_handler(irq, &mt7621_irq_chip, handle_simple_irq); + irq_set_noprobe(irq); + + return 0; +} + +static void +mt7621_gpio_irq_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops mt7621_gpio_irq_domain_ops = { + .map = mt7621_gpio_irq_map, + .unmap = mt7621_gpio_irq_unmap, + .xlate = irq_domain_xlate_twocell, +}; + +static int +mt7621_gpio_irq_setup(struct platform_device *pdev, + struct mtk *priv) +{ + struct device *dev = &pdev->dev; + + priv->irq_domain = irq_domain_create_linear(dev_fwnode(dev), + priv->num_gpios, + &mt7621_gpio_irq_domain_ops, + priv); + if (!priv->irq_domain) { + dev_err(dev, "Couldn't allocate IRQ domain\n"); + return -ENXIO; + } + + irq_set_chained_handler_and_data(priv->gpio_irq, + mt7621_gpio_irq_handler, priv); + irq_set_status_flags(priv->gpio_irq, IRQ_DISABLE_UNLAZY); + + return 0; +} + +static int +mt7621_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) +{ + struct mtk *priv = mt7621_gpio_gc_to_priv(gc); + /* gc_offset is relative to this gpio_chip; want real offset */ + int hwirq = offset + gc->offset; + + if (hwirq >= priv->num_gpios) + return -ENXIO; + + return irq_create_mapping(priv->irq_domain, hwirq); +} + static int mediatek_gpio_bank_probe(struct device *dev, int bank) { @@ -228,6 +389,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank) rg = &mtk->gc_map[bank]; memset(rg, 0, sizeof(*rg)); + rg->parent_priv = mtk; rg->bank = bank; dat = mtk->base + GPIO_REG_DATA + (rg->bank * GPIO_BANK_STRIDE); @@ -253,41 +415,17 @@ mediatek_gpio_bank_probe(struct device *dev, int bank) rg->chip.gc.of_gpio_n_cells = 2; rg->chip.gc.of_xlate = mediatek_gpio_xlate; + rg->chip.gc.ngpio = MTK_BANK_WIDTH; rg->chip.gc.label = devm_kasprintf(dev, GFP_KERNEL, "%s-bank%d", dev_name(dev), bank); if (!rg->chip.gc.label) return -ENOMEM; rg->chip.gc.offset = bank * MTK_BANK_WIDTH; + if (mtk->gpio_irq > 0) + rg->chip.gc.to_irq = mt7621_gpio_to_irq; - if (mtk->gpio_irq) { - struct gpio_irq_chip *girq; - - /* - * Directly request the irq here instead of passing - * a flow-handler because the irq is shared. - */ - ret = devm_request_irq(dev, mtk->gpio_irq, - mediatek_gpio_irq_handler, IRQF_SHARED, - rg->chip.gc.label, &rg->chip.gc); - - if (ret) { - dev_err(dev, "Error requesting IRQ %d: %d\n", - mtk->gpio_irq, ret); - return ret; - } - - girq = &rg->chip.gc.irq; - gpio_irq_chip_set_chip(girq, &mt7621_irq_chip); - /* This will let us handle the parent IRQ in the driver */ - girq->parent_handler = NULL; - girq->num_parents = 0; - girq->parents = NULL; - girq->default_type = IRQ_TYPE_NONE; - girq->handler = handle_simple_irq; - } - - ret = devm_gpiochip_add_data(dev, &rg->chip.gc, mtk); + ret = devm_gpiochip_add_data(dev, &rg->chip.gc, rg); if (ret < 0) { dev_err(dev, "Could not register gpio %d, ret=%d\n", rg->chip.gc.ngpio, ret); @@ -322,7 +460,8 @@ mediatek_gpio_probe(struct platform_device *pdev) if (mtk->gpio_irq < 0) return mtk->gpio_irq; - mtk->dev = dev; + mtk->pdev = pdev; + mtk->num_gpios = MTK_BANK_WIDTH * MTK_BANK_CNT; platform_set_drvdata(pdev, mtk); for (i = 0; i < MTK_BANK_CNT; i++) { @@ -331,7 +470,17 @@ mediatek_gpio_probe(struct platform_device *pdev) return ret; } + if (mtk->gpio_irq > 0) { + ret = mt7621_gpio_irq_setup(pdev, mtk); + if (ret) + goto fail; + } + return 0; + +fail: + mt7621_gpio_remove(pdev); + return ret; } static const struct of_device_id mediatek_gpio_match[] = { @@ -342,6 +491,7 @@ MODULE_DEVICE_TABLE(of, mediatek_gpio_match); static struct platform_driver mediatek_gpio_driver = { .probe = mediatek_gpio_probe, + .remove = mt7621_gpio_remove, .driver = { .name = "mt7621_gpio", .of_match_table = mediatek_gpio_match, diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c index 12f11a6c9665..7e2690d92df6 100644 --- a/drivers/gpio/gpio-mxc.c +++ b/drivers/gpio/gpio-mxc.c @@ -330,13 +330,13 @@ static int gpio_set_wake_irq(struct irq_data *d, u32 enable) ret = enable_irq_wake(port->irq_high); else ret = enable_irq_wake(port->irq); - port->wakeup_pads |= (1 << gpio_idx); + port->wakeup_pads |= BIT(gpio_idx); } else { if (port->irq_high && (gpio_idx >= 16)) ret = disable_irq_wake(port->irq_high); else ret = disable_irq_wake(port->irq); - port->wakeup_pads &= ~(1 << gpio_idx); + port->wakeup_pads &= ~BIT(gpio_idx); } return ret; diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index b9c905a0ffa9..2ee35e855e4d 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -86,49 +86,49 @@ #define PCA_CHIP_TYPE(x) ((x) & PCA_TYPE_MASK) static const struct i2c_device_id pca953x_id[] = { - { "pca6408", 8 | PCA953X_TYPE | PCA_INT, }, - { "pca6416", 16 | PCA953X_TYPE | PCA_INT, }, - { "pca9505", 40 | PCA953X_TYPE | PCA_INT, }, - { "pca9506", 40 | PCA953X_TYPE | PCA_INT, }, - { "pca9534", 8 | PCA953X_TYPE | PCA_INT, }, - { "pca9535", 16 | PCA953X_TYPE | PCA_INT, }, - { "pca9536", 4 | PCA953X_TYPE, }, - { "pca9537", 4 | PCA953X_TYPE | PCA_INT, }, - { "pca9538", 8 | PCA953X_TYPE | PCA_INT, }, - { "pca9539", 16 | PCA953X_TYPE | PCA_INT, }, - { "pca9554", 8 | PCA953X_TYPE | PCA_INT, }, - { "pca9555", 16 | PCA953X_TYPE | PCA_INT, }, - { "pca9556", 8 | PCA953X_TYPE, }, - { "pca9557", 8 | PCA953X_TYPE, }, - { "pca9574", 8 | PCA957X_TYPE | PCA_INT, }, - { "pca9575", 16 | PCA957X_TYPE | PCA_INT, }, - { "pca9698", 40 | PCA953X_TYPE, }, + { .name = "pca6408", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "pca6416", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9505", .driver_data = 40 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9506", .driver_data = 40 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9534", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9535", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9536", .driver_data = 4 | PCA953X_TYPE }, + { .name = "pca9537", .driver_data = 4 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9538", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9539", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9554", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9555", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "pca9556", .driver_data = 8 | PCA953X_TYPE }, + { .name = "pca9557", .driver_data = 8 | PCA953X_TYPE }, + { .name = "pca9574", .driver_data = 8 | PCA957X_TYPE | PCA_INT }, + { .name = "pca9575", .driver_data = 16 | PCA957X_TYPE | PCA_INT }, + { .name = "pca9698", .driver_data = 40 | PCA953X_TYPE }, - { "pcal6408", 8 | PCA953X_TYPE | PCA_LATCH_INT, }, - { "pcal6416", 16 | PCA953X_TYPE | PCA_LATCH_INT, }, - { "pcal6524", 24 | PCA953X_TYPE | PCA_LATCH_INT, }, - { "pcal6534", 34 | PCAL653X_TYPE | PCA_LATCH_INT, }, - { "pcal9535", 16 | PCA953X_TYPE | PCA_LATCH_INT, }, - { "pcal9554b", 8 | PCA953X_TYPE | PCA_LATCH_INT, }, - { "pcal9555a", 16 | PCA953X_TYPE | PCA_LATCH_INT, }, + { .name = "pcal6408", .driver_data = 8 | PCA953X_TYPE | PCA_LATCH_INT }, + { .name = "pcal6416", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT }, + { .name = "pcal6524", .driver_data = 24 | PCA953X_TYPE | PCA_LATCH_INT }, + { .name = "pcal6534", .driver_data = 34 | PCAL653X_TYPE | PCA_LATCH_INT }, + { .name = "pcal9535", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT }, + { .name = "pcal9554b", .driver_data = 8 | PCA953X_TYPE | PCA_LATCH_INT }, + { .name = "pcal9555a", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT }, - { "max7310", 8 | PCA953X_TYPE, }, - { "max7312", 16 | PCA953X_TYPE | PCA_INT, }, - { "max7313", 16 | PCA953X_TYPE | PCA_INT, }, - { "max7315", 8 | PCA953X_TYPE | PCA_INT, }, - { "max7318", 16 | PCA953X_TYPE | PCA_INT, }, - { "pca6107", 8 | PCA953X_TYPE | PCA_INT, }, - { "tca6408", 8 | PCA953X_TYPE | PCA_INT, }, - { "tca6416", 16 | PCA953X_TYPE | PCA_INT, }, - { "tca6418", 18 | TCA6418_TYPE | PCA_INT, }, - { "tca6424", 24 | PCA953X_TYPE | PCA_INT, }, - { "tca9538", 8 | PCA953X_TYPE | PCA_INT, }, - { "tca9539", 16 | PCA953X_TYPE | PCA_INT, }, - { "tca9554", 8 | PCA953X_TYPE | PCA_INT, }, - { "xra1202", 8 | PCA953X_TYPE }, + { .name = "max7310", .driver_data = 8 | PCA953X_TYPE }, + { .name = "max7312", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "max7313", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "max7315", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "max7318", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "pca6107", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "tca6408", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "tca6416", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "tca6418", .driver_data = 18 | TCA6418_TYPE | PCA_INT }, + { .name = "tca6424", .driver_data = 24 | PCA953X_TYPE | PCA_INT }, + { .name = "tca9538", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "tca9539", .driver_data = 16 | PCA953X_TYPE | PCA_INT }, + { .name = "tca9554", .driver_data = 8 | PCA953X_TYPE | PCA_INT }, + { .name = "xra1202", .driver_data = 8 | PCA953X_TYPE }, - { "tcal6408", 8 | PCA953X_TYPE | PCA_LATCH_INT, }, - { "tcal6416", 16 | PCA953X_TYPE | PCA_LATCH_INT, }, + { .name = "tcal6408", .driver_data = 8 | PCA953X_TYPE | PCA_LATCH_INT }, + { .name = "tcal6416", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT }, { } }; MODULE_DEVICE_TABLE(i2c, pca953x_id); @@ -877,11 +877,9 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d) bitmap_or(irq_mask, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio); bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_high, gc->ngpio); bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_low, gc->ngpio); - bitmap_complement(reg_direction, reg_direction, gc->ngpio); - bitmap_and(irq_mask, irq_mask, reg_direction, gc->ngpio); /* Look for any newly setup interrupt */ - for_each_set_bit(level, irq_mask, gc->ngpio) + for_each_andnot_bit(level, irq_mask, reg_direction, gc->ngpio) pca953x_gpio_direction_input(&chip->gpio_chip, level); mutex_unlock(&chip->irq_lock); @@ -1005,8 +1003,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio); bitmap_or(pending, pending, cur_stat, gc->ngpio); - bitmap_complement(cur_stat, new_stat, gc->ngpio); - bitmap_and(cur_stat, cur_stat, reg_direction, gc->ngpio); + bitmap_andnot(cur_stat, reg_direction, new_stat, gc->ngpio); bitmap_and(old_stat, cur_stat, chip->irq_trig_level_low, gc->ngpio); bitmap_and(old_stat, old_stat, chip->irq_mask, gc->ngpio); bitmap_or(pending, pending, old_stat, gc->ngpio); diff --git a/drivers/gpio/gpio-pca9570.c b/drivers/gpio/gpio-pca9570.c index 4a368803fb03..7a47a9aa0414 100644 --- a/drivers/gpio/gpio-pca9570.c +++ b/drivers/gpio/gpio-pca9570.c @@ -163,9 +163,9 @@ static const struct pca9570_chip_data slg7xl45106_gpio = { }; static const struct i2c_device_id pca9570_id_table[] = { - { "pca9570", (kernel_ulong_t)&pca9570_gpio}, - { "pca9571", (kernel_ulong_t)&pca9571_gpio }, - { "slg7xl45106", (kernel_ulong_t)&slg7xl45106_gpio }, + { .name = "pca9570", .driver_data = (kernel_ulong_t)&pca9570_gpio }, + { .name = "pca9571", .driver_data = (kernel_ulong_t)&pca9571_gpio }, + { .name = "slg7xl45106", .driver_data = (kernel_ulong_t)&slg7xl45106_gpio }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, pca9570_id_table); diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c index 3b9de8c3d924..c942b959571b 100644 --- a/drivers/gpio/gpio-pcf857x.c +++ b/drivers/gpio/gpio-pcf857x.c @@ -20,19 +20,19 @@ #include static const struct i2c_device_id pcf857x_id[] = { - { "pcf8574", 8 }, - { "pcf8574a", 8 }, - { "pca8574", 8 }, - { "pca9670", 8 }, - { "pca9672", 8 }, - { "pca9674", 8 }, - { "pcf8575", 16 }, - { "pca8575", 16 }, - { "pca9671", 16 }, - { "pca9673", 16 }, - { "pca9675", 16 }, - { "max7328", 8 }, - { "max7329", 8 }, + { .name = "pcf8574", .driver_data = 8 }, + { .name = "pcf8574a", .driver_data = 8 }, + { .name = "pca8574", .driver_data = 8 }, + { .name = "pca9670", .driver_data = 8 }, + { .name = "pca9672", .driver_data = 8 }, + { .name = "pca9674", .driver_data = 8 }, + { .name = "pcf8575", .driver_data = 16 }, + { .name = "pca8575", .driver_data = 16 }, + { .name = "pca9671", .driver_data = 16 }, + { .name = "pca9673", .driver_data = 16 }, + { .name = "pca9675", .driver_data = 16 }, + { .name = "max7328", .driver_data = 8 }, + { .name = "max7329", .driver_data = 8 }, { } }; MODULE_DEVICE_TABLE(i2c, pcf857x_id); diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c index 664cf1eef494..5d61053e0596 100644 --- a/drivers/gpio/gpio-pxa.c +++ b/drivers/gpio/gpio-pxa.c @@ -708,15 +708,15 @@ static int pxa_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id gpio_id_table[] = { - { "pxa25x-gpio", (unsigned long)&pxa25x_id }, - { "pxa26x-gpio", (unsigned long)&pxa26x_id }, - { "pxa27x-gpio", (unsigned long)&pxa27x_id }, - { "pxa3xx-gpio", (unsigned long)&pxa3xx_id }, - { "pxa93x-gpio", (unsigned long)&pxa93x_id }, - { "mmp-gpio", (unsigned long)&mmp_id }, - { "mmp2-gpio", (unsigned long)&mmp2_id }, - { "pxa1928-gpio", (unsigned long)&pxa1928_id }, - { }, + { .name = "pxa25x-gpio", .driver_data = (unsigned long)&pxa25x_id }, + { .name = "pxa26x-gpio", .driver_data = (unsigned long)&pxa26x_id }, + { .name = "pxa27x-gpio", .driver_data = (unsigned long)&pxa27x_id }, + { .name = "pxa3xx-gpio", .driver_data = (unsigned long)&pxa3xx_id }, + { .name = "pxa93x-gpio", .driver_data = (unsigned long)&pxa93x_id }, + { .name = "mmp-gpio", .driver_data = (unsigned long)&mmp_id }, + { .name = "mmp2-gpio", .driver_data = (unsigned long)&mmp2_id }, + { .name = "pxa1928-gpio", .driver_data = (unsigned long)&pxa1928_id }, + { } }; static struct platform_driver pxa_gpio_driver = { diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c index 5e3152c2e51a..37ef56f45318 100644 --- a/drivers/gpio/gpio-realtek-otto.c +++ b/drivers/gpio/gpio-realtek-otto.c @@ -40,16 +40,18 @@ #define REALTEK_GPIO_PORTS_PER_BANK 4 /** - * realtek_gpio_ctrl - Realtek Otto GPIO driver data + * struct realtek_gpio_ctrl - Realtek Otto GPIO driver data * * @chip: Associated gpio_generic_chip instance * @base: Base address of the register block for a GPIO bank + * @cpumask_base: Base address of the per-CPU interrupt mask registers + * @cpu_irq_maskable: CPUs that can receive GPIO interrupts * @lock: Lock for accessing the IRQ registers and values * @intr_mask: Mask for interrupts lines * @intr_type: Interrupt type selection * @bank_read: Read a bank setting as a single 32-bit value * @bank_write: Write a bank setting as a single 32-bit value - * @imr_line_pos: Bit shift of an IRQ line's IMR value. + * @line_imr_pos: Bit shift of an IRQ line's IMR value. * * The DIR, DATA, and ISR registers consist of four 8-bit port values, packed * into a single 32-bit register. Use @bank_read (@bank_write) to get (assign) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index 9ae4a41a2427..51b4d69b8740 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -31,6 +31,7 @@ struct gpio_regmap { unsigned int reg_clr_base; unsigned int reg_dir_in_base; unsigned int reg_dir_out_base; + unsigned long *fixed_direction_mask; unsigned long *fixed_direction_output; #ifdef CONFIG_REGMAP_IRQ @@ -138,6 +139,20 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip, return regmap_write(gpio->regmap, reg, mask); } +static bool gpio_regmap_fixed_direction(struct gpio_regmap *gpio, + unsigned int offset) +{ + if (!gpio->fixed_direction_output) + return false; + + /* In this case only some GPIOs are fixed as input/output */ + if (gpio->fixed_direction_mask && + !test_bit(offset, gpio->fixed_direction_mask)) + return false; + + return true; +} + static int gpio_regmap_get_direction(struct gpio_chip *chip, unsigned int offset) { @@ -145,7 +160,7 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, unsigned int base, val, reg, mask; int invert, ret; - if (gpio->fixed_direction_output) { + if (gpio_regmap_fixed_direction(gpio, offset)) { if (test_bit(offset, gpio->fixed_direction_output)) return GPIO_LINE_DIRECTION_OUT; else @@ -181,6 +196,22 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, return GPIO_LINE_DIRECTION_IN; } +static int gpio_regmap_try_direction_fixed(struct gpio_regmap *gpio, + unsigned int offset, bool output) +{ + if (test_bit(offset, gpio->fixed_direction_output)) { + if (output) + return 0; + else + return -EINVAL; + } else { + if (output) + return -EINVAL; + else + return 0; + } +} + static int gpio_regmap_set_direction(struct gpio_chip *chip, unsigned int offset, bool output) { @@ -188,6 +219,13 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip, unsigned int base, val, reg, mask; int invert, ret; + /* + * If the direction is fixed, only accept the fixed + * direction in this call. + */ + if (gpio_regmap_fixed_direction(gpio, offset)) + return gpio_regmap_try_direction_fixed(gpio, offset, output); + if (gpio->reg_dir_out_base) { base = gpio_regmap_addr(gpio->reg_dir_out_base); invert = 0; @@ -219,6 +257,20 @@ static int gpio_regmap_direction_input(struct gpio_chip *chip, static int gpio_regmap_direction_output(struct gpio_chip *chip, unsigned int offset, int value) { + struct gpio_regmap *gpio = gpiochip_get_data(chip); + int ret; + + /* + * First check if this is gonna work on a fixed direction line, + * if it doesn't (i.e. this is a fixed input line), then do not + * attempt to set the output value either and just bail out. + */ + if (gpio_regmap_fixed_direction(gpio, offset)) { + ret = gpio_regmap_try_direction_fixed(gpio, offset, true); + if (ret) + return ret; + } + gpio_regmap_set(chip, offset, value); return gpio_regmap_set_direction(chip, offset, true); @@ -302,12 +354,23 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config goto err_free_gpio; } + if (config->fixed_direction_mask) { + gpio->fixed_direction_mask = bitmap_alloc(chip->ngpio, + GFP_KERNEL); + if (!gpio->fixed_direction_mask) { + ret = -ENOMEM; + goto err_free_gpio; + } + bitmap_copy(gpio->fixed_direction_mask, + config->fixed_direction_mask, chip->ngpio); + } + if (config->fixed_direction_output) { gpio->fixed_direction_output = bitmap_alloc(chip->ngpio, GFP_KERNEL); if (!gpio->fixed_direction_output) { ret = -ENOMEM; - goto err_free_gpio; + goto err_free_bitmap_dirmask; } bitmap_copy(gpio->fixed_direction_output, config->fixed_direction_output, chip->ngpio); @@ -329,7 +392,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config ret = gpiochip_add_data(chip, gpio); if (ret < 0) - goto err_free_bitmap; + goto err_free_bitmap_output; #ifdef CONFIG_REGMAP_IRQ if (config->regmap_irq_chip) { @@ -355,8 +418,10 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config err_remove_gpiochip: gpiochip_remove(chip); -err_free_bitmap: +err_free_bitmap_output: bitmap_free(gpio->fixed_direction_output); +err_free_bitmap_dirmask: + bitmap_free(gpio->fixed_direction_mask); err_free_gpio: kfree(gpio); return ERR_PTR(ret); @@ -376,6 +441,7 @@ void gpio_regmap_unregister(struct gpio_regmap *gpio) gpiochip_remove(&gpio->gpio_chip); bitmap_free(gpio->fixed_direction_output); + bitmap_free(gpio->fixed_direction_mask); kfree(gpio); } EXPORT_SYMBOL_GPL(gpio_regmap_unregister); diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index 0da2c5a45843..f0f570527cf2 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -695,9 +695,9 @@ static ssize_t gpio_sim_device_config_dev_name_show(struct config_item *item, pdev = dev->pdev; if (pdev) - return sprintf(page, "%s\n", dev_name(&pdev->dev)); + return sysfs_emit(page, "%s\n", dev_name(&pdev->dev)); - return sprintf(page, "gpio-sim.%d\n", dev->id); + return sysfs_emit(page, "gpio-sim.%d\n", dev->id); } CONFIGFS_ATTR_RO(gpio_sim_device_config_, dev_name); @@ -711,7 +711,7 @@ gpio_sim_device_config_live_show(struct config_item *item, char *page) scoped_guard(mutex, &dev->lock) live = gpio_sim_device_is_live(dev); - return sprintf(page, "%c\n", live ? '1' : '0'); + return sysfs_emit(page, "%c\n", live ? '1' : '0'); } static unsigned int gpio_sim_get_line_names_size(struct gpio_sim_bank *bank) @@ -1059,7 +1059,7 @@ static int gpio_sim_emit_chip_name(struct device *dev, void *data) return 0; if (device_match_fwnode(dev, ctx->swnode)) - return sprintf(ctx->page, "%s\n", dev_name(dev)); + return sysfs_emit(ctx->page, "%s\n", dev_name(dev)); return 0; } @@ -1077,7 +1077,7 @@ static ssize_t gpio_sim_bank_config_chip_name_show(struct config_item *item, return device_for_each_child(&dev->pdev->dev, &ctx, gpio_sim_emit_chip_name); - return sprintf(page, "none\n"); + return sysfs_emit(page, "none\n"); } CONFIGFS_ATTR_RO(gpio_sim_bank_config_, chip_name); @@ -1090,7 +1090,7 @@ gpio_sim_bank_config_label_show(struct config_item *item, char *page) guard(mutex)(&dev->lock); - return sprintf(page, "%s\n", bank->label ?: ""); + return sysfs_emit(page, "%s\n", bank->label ?: ""); } static ssize_t gpio_sim_bank_config_label_store(struct config_item *item, @@ -1125,7 +1125,7 @@ gpio_sim_bank_config_num_lines_show(struct config_item *item, char *page) guard(mutex)(&dev->lock); - return sprintf(page, "%u\n", bank->num_lines); + return sysfs_emit(page, "%u\n", bank->num_lines); } static ssize_t @@ -1171,7 +1171,7 @@ gpio_sim_line_config_name_show(struct config_item *item, char *page) guard(mutex)(&dev->lock); - return sprintf(page, "%s\n", line->name ?: ""); + return sysfs_emit(page, "%s\n", line->name ?: ""); } static ssize_t gpio_sim_line_config_name_store(struct config_item *item, @@ -1206,7 +1206,7 @@ gpio_sim_line_config_valid_show(struct config_item *item, char *page) guard(mutex)(&dev->lock); - return sprintf(page, "%c\n", line->valid ? '1' : '0'); + return sysfs_emit(page, "%c\n", line->valid ? '1' : '0'); } static ssize_t gpio_sim_line_config_valid_store(struct config_item *item, @@ -1244,7 +1244,7 @@ static ssize_t gpio_sim_hog_config_name_show(struct config_item *item, guard(mutex)(&dev->lock); - return sprintf(page, "%s\n", hog->name ?: ""); + return sysfs_emit(page, "%s\n", hog->name ?: ""); } static ssize_t gpio_sim_hog_config_name_store(struct config_item *item, @@ -1298,7 +1298,7 @@ static ssize_t gpio_sim_hog_config_direction_show(struct config_item *item, return -EINVAL; } - return sprintf(page, "%s\n", repr); + return sysfs_emit(page, "%s\n", repr); } static ssize_t @@ -1338,7 +1338,7 @@ static ssize_t gpio_sim_hog_config_active_low_show(struct config_item *item, guard(mutex)(&dev->lock); - return sprintf(page, "%c\n", hog->active_low ? '1' : '0'); + return sysfs_emit(page, "%c\n", hog->active_low ? '1' : '0'); } static ssize_t diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index aa7c3e44234f..d9a2dedf50ea 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -1239,6 +1240,67 @@ static const struct tegra_gpio_soc tegra234_aon_soc = { .has_vm_support = false, }; +#define TEGRA238_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA238_MAIN, _name, _bank, _port, _pins) + +static const struct tegra_gpio_port tegra238_main_ports[] = { + TEGRA238_MAIN_GPIO_PORT(A, 0, 0, 8), + TEGRA238_MAIN_GPIO_PORT(B, 0, 1, 5), + TEGRA238_MAIN_GPIO_PORT(C, 0, 2, 8), + TEGRA238_MAIN_GPIO_PORT(D, 0, 3, 8), + TEGRA238_MAIN_GPIO_PORT(E, 0, 4, 4), + TEGRA238_MAIN_GPIO_PORT(F, 0, 5, 8), + TEGRA238_MAIN_GPIO_PORT(G, 0, 6, 8), + TEGRA238_MAIN_GPIO_PORT(H, 0, 7, 6), + TEGRA238_MAIN_GPIO_PORT(J, 1, 0, 8), + TEGRA238_MAIN_GPIO_PORT(K, 1, 1, 4), + TEGRA238_MAIN_GPIO_PORT(L, 1, 2, 8), + TEGRA238_MAIN_GPIO_PORT(M, 1, 3, 8), + TEGRA238_MAIN_GPIO_PORT(N, 1, 4, 3), + TEGRA238_MAIN_GPIO_PORT(P, 1, 5, 8), + TEGRA238_MAIN_GPIO_PORT(Q, 1, 6, 3), + TEGRA238_MAIN_GPIO_PORT(R, 2, 0, 8), + TEGRA238_MAIN_GPIO_PORT(S, 2, 1, 8), + TEGRA238_MAIN_GPIO_PORT(T, 2, 2, 8), + TEGRA238_MAIN_GPIO_PORT(U, 2, 3, 6), + TEGRA238_MAIN_GPIO_PORT(V, 2, 4, 2), + TEGRA238_MAIN_GPIO_PORT(W, 3, 0, 8), + TEGRA238_MAIN_GPIO_PORT(X, 3, 1, 2) +}; + +static const struct tegra_gpio_soc tegra238_main_soc = { + .num_ports = ARRAY_SIZE(tegra238_main_ports), + .ports = tegra238_main_ports, + .name = "tegra238-gpio", + .instance = 0, + .num_irqs_per_bank = 8, + .has_vm_support = true, +}; + +#define TEGRA238_AON_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA238_AON, _name, _bank, _port, _pins) + +static const struct tegra_gpio_port tegra238_aon_ports[] = { + TEGRA238_AON_GPIO_PORT(AA, 0, 0, 8), + TEGRA238_AON_GPIO_PORT(BB, 0, 1, 1), + TEGRA238_AON_GPIO_PORT(CC, 0, 2, 8), + TEGRA238_AON_GPIO_PORT(DD, 0, 3, 8), + TEGRA238_AON_GPIO_PORT(EE, 0, 4, 6), + TEGRA238_AON_GPIO_PORT(FF, 0, 5, 8), + TEGRA238_AON_GPIO_PORT(GG, 0, 6, 8), + TEGRA238_AON_GPIO_PORT(HH, 0, 7, 4) +}; + +static const struct tegra_gpio_soc tegra238_aon_soc = { + .num_ports = ARRAY_SIZE(tegra238_aon_ports), + .ports = tegra238_aon_ports, + .name = "tegra238-gpio-aon", + .instance = 1, + .num_irqs_per_bank = 8, + .has_gte = true, + .has_vm_support = false, +}; + #define TEGRA241_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ TEGRA_GPIO_PORT(TEGRA241_MAIN, _name, _bank, _port, _pins) @@ -1333,6 +1395,7 @@ static const struct tegra_gpio_soc tegra264_aon_soc = { .name = "tegra264-gpio-aon", .instance = 1, .num_irqs_per_bank = 8, + .has_gte = true, .has_vm_support = true, }; @@ -1447,6 +1510,12 @@ static const struct of_device_id tegra186_gpio_of_match[] = { }, { .compatible = "nvidia,tegra234-gpio-aon", .data = &tegra234_aon_soc + }, { + .compatible = "nvidia,tegra238-gpio", + .data = &tegra238_main_soc + }, { + .compatible = "nvidia,tegra238-gpio-aon", + .data = &tegra238_aon_soc }, { .compatible = "nvidia,tegra256-gpio", .data = &tegra256_main_soc diff --git a/drivers/gpio/gpio-timberdale.c b/drivers/gpio/gpio-timberdale.c index f488939dd00a..78fe133f5d32 100644 --- a/drivers/gpio/gpio-timberdale.c +++ b/drivers/gpio/gpio-timberdale.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -225,19 +224,21 @@ static int timbgpio_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct gpio_chip *gc; struct timbgpio *tgpio; - struct timbgpio_platform_data *pdata = dev_get_platdata(&pdev->dev); int irq = platform_get_irq(pdev, 0); - if (!pdata || pdata->nr_pins > 32) { - dev_err(dev, "Invalid platform data\n"); - return -EINVAL; - } - tgpio = devm_kzalloc(dev, sizeof(*tgpio), GFP_KERNEL); if (!tgpio) return -EINVAL; - tgpio->irq_base = pdata->irq_base; + gc = &tgpio->gpio; + + err = device_property_read_u32(dev, "irq-base", &tgpio->irq_base); + if (err) + return err; + + err = device_property_read_u32(dev, "gpio-base", &gc->base); + if (err) + return err; spin_lock_init(&tgpio->lock); @@ -245,8 +246,6 @@ static int timbgpio_probe(struct platform_device *pdev) if (IS_ERR(tgpio->membase)) return PTR_ERR(tgpio->membase); - gc = &tgpio->gpio; - gc->label = dev_name(&pdev->dev); gc->owner = THIS_MODULE; gc->parent = &pdev->dev; @@ -256,21 +255,22 @@ static int timbgpio_probe(struct platform_device *pdev) gc->set = timbgpio_gpio_set; gc->to_irq = (irq >= 0 && tgpio->irq_base > 0) ? timbgpio_to_irq : NULL; gc->dbg_show = NULL; - gc->base = pdata->gpio_base; - gc->ngpio = pdata->nr_pins; gc->can_sleep = false; err = devm_gpiochip_add_data(&pdev->dev, gc, tgpio); if (err) return err; + if (gc->ngpio > 32) + return dev_err_probe(dev, -EINVAL, "Invalid number of pins\n"); + /* make sure to disable interrupts */ iowrite32(0x0, tgpio->membase + TGPIO_IER); if (irq < 0 || tgpio->irq_base <= 0) return 0; - for (i = 0; i < pdata->nr_pins; i++) { + for (i = 0; i < gc->ngpio; i++) { irq_set_chip_and_handler(tgpio->irq_base + i, &timbgpio_irqchip, handle_simple_irq); irq_set_chip_data(tgpio->irq_base + i, tgpio); diff --git a/drivers/gpio/gpio-tpic2810.c b/drivers/gpio/gpio-tpic2810.c index 866ff2d436d5..c38538653e99 100644 --- a/drivers/gpio/gpio-tpic2810.c +++ b/drivers/gpio/gpio-tpic2810.c @@ -112,7 +112,7 @@ static int tpic2810_probe(struct i2c_client *client) } static const struct i2c_device_id tpic2810_id_table[] = { - { "tpic2810", }, + { .name = "tpic2810" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, tpic2810_id_table); diff --git a/drivers/gpio/gpio-tps65086.c b/drivers/gpio/gpio-tps65086.c index df770ecf28bc..f29d8485ab33 100644 --- a/drivers/gpio/gpio-tps65086.c +++ b/drivers/gpio/gpio-tps65086.c @@ -91,7 +91,7 @@ static int tps65086_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id tps65086_gpio_id_table[] = { - { "tps65086-gpio", }, + { .name = "tps65086-gpio" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, tps65086_gpio_id_table); diff --git a/drivers/gpio/gpio-tps65218.c b/drivers/gpio/gpio-tps65218.c index 3b4c41f5ef55..bf85663349fb 100644 --- a/drivers/gpio/gpio-tps65218.c +++ b/drivers/gpio/gpio-tps65218.c @@ -201,7 +201,7 @@ static const struct of_device_id tps65218_dt_match[] = { MODULE_DEVICE_TABLE(of, tps65218_dt_match); static const struct platform_device_id tps65218_gpio_id_table[] = { - { "tps65218-gpio", }, + { .name = "tps65218-gpio" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, tps65218_gpio_id_table); diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 158f63bcf10c..457fd8a589e8 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -249,8 +249,8 @@ static int tps65219_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id tps6521x_gpio_id_table[] = { - { "tps65214-gpio", TPS65214 }, - { "tps65219-gpio", TPS65219 }, + { .name = "tps65214-gpio", .driver_data = TPS65214 }, + { .name = "tps65219-gpio", .driver_data = TPS65219 }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, tps6521x_gpio_id_table); diff --git a/drivers/gpio/gpio-tps65912.c b/drivers/gpio/gpio-tps65912.c index 7a2c5685c2fd..cf3fa49a7097 100644 --- a/drivers/gpio/gpio-tps65912.c +++ b/drivers/gpio/gpio-tps65912.c @@ -115,7 +115,7 @@ static int tps65912_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id tps65912_gpio_id_table[] = { - { "tps65912-gpio", }, + { .name = "tps65912-gpio" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, tps65912_gpio_id_table); diff --git a/drivers/gpio/gpio-ts4900.c b/drivers/gpio/gpio-ts4900.c index d9ee8fc77ccd..b46b48e56c56 100644 --- a/drivers/gpio/gpio-ts4900.c +++ b/drivers/gpio/gpio-ts4900.c @@ -175,7 +175,7 @@ static int ts4900_gpio_probe(struct i2c_client *client) } static const struct i2c_device_id ts4900_gpio_id_table[] = { - { "ts4900-gpio", }, + { .name = "ts4900-gpio" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, ts4900_gpio_id_table); diff --git a/drivers/gpio/gpio-ts5500.c b/drivers/gpio/gpio-ts5500.c deleted file mode 100644 index 3c7f2efe10fd..000000000000 --- a/drivers/gpio/gpio-ts5500.c +++ /dev/null @@ -1,446 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Digital I/O driver for Technologic Systems TS-5500 - * - * Copyright (c) 2012 Savoir-faire Linux Inc. - * Vivien Didelot - * - * Technologic Systems platforms have pin blocks, exposing several Digital - * Input/Output lines (DIO). This driver aims to support single pin blocks. - * In that sense, the support is not limited to the TS-5500 blocks. - * Actually, the following platforms have DIO support: - * - * TS-5500: - * Documentation: https://docs.embeddedts.com/TS-5500 - * Blocks: DIO1, DIO2 and LCD port. - * - * TS-5600: - * Documentation: https://docs.embeddedts.com/TS-5600 - * Blocks: LCD port (identical to TS-5500 LCD). - */ - -#include -#include -#include -#include -#include -#include - -/* List of supported Technologic Systems platforms DIO blocks */ -enum ts5500_blocks { TS5500_DIO1, TS5500_DIO2, TS5500_LCD, TS5600_LCD }; - -struct ts5500_priv { - const struct ts5500_dio *pinout; - struct gpio_chip gpio_chip; - spinlock_t lock; - bool strap; - u8 hwirq; -}; - -/* - * Hex 7D is used to control several blocks (e.g. DIO2 and LCD port). - * This flag ensures that the region has been requested by this driver. - */ -static bool hex7d_reserved; - -/* - * This structure is used to describe capabilities of DIO lines, - * such as available directions and connected interrupt (if any). - */ -struct ts5500_dio { - const u8 value_addr; - const u8 value_mask; - const u8 control_addr; - const u8 control_mask; - const bool no_input; - const bool no_output; - const u8 irq; -}; - -#define TS5500_DIO_IN_OUT(vaddr, vbit, caddr, cbit) \ - { \ - .value_addr = vaddr, \ - .value_mask = BIT(vbit), \ - .control_addr = caddr, \ - .control_mask = BIT(cbit), \ - } - -#define TS5500_DIO_IN(addr, bit) \ - { \ - .value_addr = addr, \ - .value_mask = BIT(bit), \ - .no_output = true, \ - } - -#define TS5500_DIO_IN_IRQ(addr, bit, _irq) \ - { \ - .value_addr = addr, \ - .value_mask = BIT(bit), \ - .no_output = true, \ - .irq = _irq, \ - } - -#define TS5500_DIO_OUT(addr, bit) \ - { \ - .value_addr = addr, \ - .value_mask = BIT(bit), \ - .no_input = true, \ - } - -/* - * Input/Output DIO lines are programmed in groups of 4. Their values are - * available through 4 consecutive bits in a value port, whereas the direction - * of these 4 lines is driven by only 1 bit in a control port. - */ -#define TS5500_DIO_GROUP(vaddr, vbitfrom, caddr, cbit) \ - TS5500_DIO_IN_OUT(vaddr, vbitfrom + 0, caddr, cbit), \ - TS5500_DIO_IN_OUT(vaddr, vbitfrom + 1, caddr, cbit), \ - TS5500_DIO_IN_OUT(vaddr, vbitfrom + 2, caddr, cbit), \ - TS5500_DIO_IN_OUT(vaddr, vbitfrom + 3, caddr, cbit) - -/* - * TS-5500 DIO1 block - * - * value control dir hw - * addr bit addr bit in out irq name pin offset - * - * 0x7b 0 0x7a 0 x x DIO1_0 1 0 - * 0x7b 1 0x7a 0 x x DIO1_1 3 1 - * 0x7b 2 0x7a 0 x x DIO1_2 5 2 - * 0x7b 3 0x7a 0 x x DIO1_3 7 3 - * 0x7b 4 0x7a 1 x x DIO1_4 9 4 - * 0x7b 5 0x7a 1 x x DIO1_5 11 5 - * 0x7b 6 0x7a 1 x x DIO1_6 13 6 - * 0x7b 7 0x7a 1 x x DIO1_7 15 7 - * 0x7c 0 0x7a 5 x x DIO1_8 4 8 - * 0x7c 1 0x7a 5 x x DIO1_9 6 9 - * 0x7c 2 0x7a 5 x x DIO1_10 8 10 - * 0x7c 3 0x7a 5 x x DIO1_11 10 11 - * 0x7c 4 x DIO1_12 12 12 - * 0x7c 5 x 7 DIO1_13 14 13 - */ -static const struct ts5500_dio ts5500_dio1[] = { - TS5500_DIO_GROUP(0x7b, 0, 0x7a, 0), - TS5500_DIO_GROUP(0x7b, 4, 0x7a, 1), - TS5500_DIO_GROUP(0x7c, 0, 0x7a, 5), - TS5500_DIO_IN(0x7c, 4), - TS5500_DIO_IN_IRQ(0x7c, 5, 7), -}; - -/* - * TS-5500 DIO2 block - * - * value control dir hw - * addr bit addr bit in out irq name pin offset - * - * 0x7e 0 0x7d 0 x x DIO2_0 1 0 - * 0x7e 1 0x7d 0 x x DIO2_1 3 1 - * 0x7e 2 0x7d 0 x x DIO2_2 5 2 - * 0x7e 3 0x7d 0 x x DIO2_3 7 3 - * 0x7e 4 0x7d 1 x x DIO2_4 9 4 - * 0x7e 5 0x7d 1 x x DIO2_5 11 5 - * 0x7e 6 0x7d 1 x x DIO2_6 13 6 - * 0x7e 7 0x7d 1 x x DIO2_7 15 7 - * 0x7f 0 0x7d 5 x x DIO2_8 4 8 - * 0x7f 1 0x7d 5 x x DIO2_9 6 9 - * 0x7f 2 0x7d 5 x x DIO2_10 8 10 - * 0x7f 3 0x7d 5 x x DIO2_11 10 11 - * 0x7f 4 x 6 DIO2_13 14 12 - */ -static const struct ts5500_dio ts5500_dio2[] = { - TS5500_DIO_GROUP(0x7e, 0, 0x7d, 0), - TS5500_DIO_GROUP(0x7e, 4, 0x7d, 1), - TS5500_DIO_GROUP(0x7f, 0, 0x7d, 5), - TS5500_DIO_IN_IRQ(0x7f, 4, 6), -}; - -/* - * TS-5500 LCD port used as DIO block - * TS-5600 LCD port is identical - * - * value control dir hw - * addr bit addr bit in out irq name pin offset - * - * 0x72 0 0x7d 2 x x LCD_0 8 0 - * 0x72 1 0x7d 2 x x LCD_1 7 1 - * 0x72 2 0x7d 2 x x LCD_2 10 2 - * 0x72 3 0x7d 2 x x LCD_3 9 3 - * 0x72 4 0x7d 3 x x LCD_4 12 4 - * 0x72 5 0x7d 3 x x LCD_5 11 5 - * 0x72 6 0x7d 3 x x LCD_6 14 6 - * 0x72 7 0x7d 3 x x LCD_7 13 7 - * 0x73 0 x LCD_EN 5 8 - * 0x73 6 x LCD_WR 6 9 - * 0x73 7 x 1 LCD_RS 3 10 - */ -static const struct ts5500_dio ts5500_lcd[] = { - TS5500_DIO_GROUP(0x72, 0, 0x7d, 2), - TS5500_DIO_GROUP(0x72, 4, 0x7d, 3), - TS5500_DIO_OUT(0x73, 0), - TS5500_DIO_IN(0x73, 6), - TS5500_DIO_IN_IRQ(0x73, 7, 1), -}; - -static inline void ts5500_set_mask(u8 mask, u8 addr) -{ - u8 val = inb(addr); - val |= mask; - outb(val, addr); -} - -static inline void ts5500_clear_mask(u8 mask, u8 addr) -{ - u8 val = inb(addr); - val &= ~mask; - outb(val, addr); -} - -static int ts5500_gpio_input(struct gpio_chip *chip, unsigned offset) -{ - struct ts5500_priv *priv = gpiochip_get_data(chip); - const struct ts5500_dio line = priv->pinout[offset]; - unsigned long flags; - - if (line.no_input) - return -ENXIO; - - if (line.no_output) - return 0; - - spin_lock_irqsave(&priv->lock, flags); - ts5500_clear_mask(line.control_mask, line.control_addr); - spin_unlock_irqrestore(&priv->lock, flags); - - return 0; -} - -static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - struct ts5500_priv *priv = gpiochip_get_data(chip); - const struct ts5500_dio line = priv->pinout[offset]; - - return !!(inb(line.value_addr) & line.value_mask); -} - -static int ts5500_gpio_output(struct gpio_chip *chip, unsigned offset, int val) -{ - struct ts5500_priv *priv = gpiochip_get_data(chip); - const struct ts5500_dio line = priv->pinout[offset]; - unsigned long flags; - - if (line.no_output) - return -ENXIO; - - spin_lock_irqsave(&priv->lock, flags); - if (!line.no_input) - ts5500_set_mask(line.control_mask, line.control_addr); - - if (val) - ts5500_set_mask(line.value_mask, line.value_addr); - else - ts5500_clear_mask(line.value_mask, line.value_addr); - spin_unlock_irqrestore(&priv->lock, flags); - - return 0; -} - -static int ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val) -{ - struct ts5500_priv *priv = gpiochip_get_data(chip); - const struct ts5500_dio line = priv->pinout[offset]; - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - if (val) - ts5500_set_mask(line.value_mask, line.value_addr); - else - ts5500_clear_mask(line.value_mask, line.value_addr); - spin_unlock_irqrestore(&priv->lock, flags); - - return 0; -} - -static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset) -{ - struct ts5500_priv *priv = gpiochip_get_data(chip); - const struct ts5500_dio *block = priv->pinout; - const struct ts5500_dio line = block[offset]; - - /* Only one pin is connected to an interrupt */ - if (line.irq) - return line.irq; - - /* As this pin is input-only, we may strap it to another in/out pin */ - if (priv->strap) - return priv->hwirq; - - return -ENXIO; -} - -static int ts5500_enable_irq(struct ts5500_priv *priv) -{ - int ret = 0; - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - if (priv->hwirq == 7) - ts5500_set_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */ - else if (priv->hwirq == 6) - ts5500_set_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */ - else if (priv->hwirq == 1) - ts5500_set_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */ - else - ret = -EINVAL; - spin_unlock_irqrestore(&priv->lock, flags); - - return ret; -} - -static void ts5500_disable_irq(struct ts5500_priv *priv) -{ - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - if (priv->hwirq == 7) - ts5500_clear_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */ - else if (priv->hwirq == 6) - ts5500_clear_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */ - else if (priv->hwirq == 1) - ts5500_clear_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */ - else - dev_err(priv->gpio_chip.parent, "invalid hwirq %d\n", - priv->hwirq); - spin_unlock_irqrestore(&priv->lock, flags); -} - -static int ts5500_dio_probe(struct platform_device *pdev) -{ - enum ts5500_blocks block = platform_get_device_id(pdev)->driver_data; - struct device *dev = &pdev->dev; - const char *name = dev_name(dev); - struct ts5500_priv *priv; - unsigned long flags; - int ret; - - ret = platform_get_irq(pdev, 0); - if (ret < 0) - return ret; - - priv = devm_kzalloc(dev, sizeof(struct ts5500_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - platform_set_drvdata(pdev, priv); - priv->hwirq = ret; - spin_lock_init(&priv->lock); - - priv->gpio_chip.owner = THIS_MODULE; - priv->gpio_chip.label = name; - priv->gpio_chip.parent = dev; - priv->gpio_chip.direction_input = ts5500_gpio_input; - priv->gpio_chip.direction_output = ts5500_gpio_output; - priv->gpio_chip.get = ts5500_gpio_get; - priv->gpio_chip.set = ts5500_gpio_set; - priv->gpio_chip.to_irq = ts5500_gpio_to_irq; - priv->gpio_chip.base = -1; - - switch (block) { - case TS5500_DIO1: - priv->pinout = ts5500_dio1; - priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio1); - - if (!devm_request_region(dev, 0x7a, 3, name)) { - dev_err(dev, "failed to request %s ports\n", name); - return -EBUSY; - } - break; - case TS5500_DIO2: - priv->pinout = ts5500_dio2; - priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio2); - - if (!devm_request_region(dev, 0x7e, 2, name)) { - dev_err(dev, "failed to request %s ports\n", name); - return -EBUSY; - } - - if (hex7d_reserved) - break; - - if (!devm_request_region(dev, 0x7d, 1, name)) { - dev_err(dev, "failed to request %s 7D\n", name); - return -EBUSY; - } - - hex7d_reserved = true; - break; - case TS5500_LCD: - case TS5600_LCD: - priv->pinout = ts5500_lcd; - priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_lcd); - - if (!devm_request_region(dev, 0x72, 2, name)) { - dev_err(dev, "failed to request %s ports\n", name); - return -EBUSY; - } - - if (!hex7d_reserved) { - if (!devm_request_region(dev, 0x7d, 1, name)) { - dev_err(dev, "failed to request %s 7D\n", name); - return -EBUSY; - } - - hex7d_reserved = true; - } - - /* Ensure usage of LCD port as DIO */ - spin_lock_irqsave(&priv->lock, flags); - ts5500_clear_mask(BIT(4), 0x7d); - spin_unlock_irqrestore(&priv->lock, flags); - break; - } - - ret = devm_gpiochip_add_data(dev, &priv->gpio_chip, priv); - if (ret) { - dev_err(dev, "failed to register the gpio chip\n"); - return ret; - } - - ret = ts5500_enable_irq(priv); - if (ret) { - dev_err(dev, "invalid interrupt %d\n", priv->hwirq); - return ret; - } - - return 0; -} - -static void ts5500_dio_remove(struct platform_device *pdev) -{ - struct ts5500_priv *priv = platform_get_drvdata(pdev); - - ts5500_disable_irq(priv); -} - -static const struct platform_device_id ts5500_dio_ids[] = { - { "ts5500-dio1", TS5500_DIO1 }, - { "ts5500-dio2", TS5500_DIO2 }, - { "ts5500-dio-lcd", TS5500_LCD }, - { "ts5600-dio-lcd", TS5600_LCD }, - { } -}; -MODULE_DEVICE_TABLE(platform, ts5500_dio_ids); - -static struct platform_driver ts5500_dio_driver = { - .driver = { - .name = "ts5500-dio", - }, - .probe = ts5500_dio_probe, - .remove = ts5500_dio_remove, - .id_table = ts5500_dio_ids, -}; - -module_platform_driver(ts5500_dio_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Savoir-faire Linux Inc. "); -MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver"); diff --git a/drivers/gpio/gpio-usbio.c b/drivers/gpio/gpio-usbio.c index 34d42c743d5b..489c8ac6299e 100644 --- a/drivers/gpio/gpio-usbio.c +++ b/drivers/gpio/gpio-usbio.c @@ -31,6 +31,7 @@ static const struct acpi_device_id usbio_gpio_acpi_hids[] = { { "INTC10B5" }, /* LNL */ { "INTC10D1" }, /* MTL-CVF */ { "INTC10E2" }, /* PTL */ + { "INTC1116" }, /* NVL */ { } }; diff --git a/drivers/gpio/gpio-waveshare-dsi.c b/drivers/gpio/gpio-waveshare-dsi.c new file mode 100644 index 000000000000..38f52351bb58 --- /dev/null +++ b/drivers/gpio/gpio-waveshare-dsi.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Waveshare International Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* I2C registers of the microcontroller. */ +#define REG_TP 0x94 +#define REG_LCD 0x95 +#define REG_PWM 0x96 +#define REG_SIZE 0x97 +#define REG_ID 0x98 +#define REG_VERSION 0x99 + +enum { + GPIO_AVDD = 0, + GPIO_PANEL_RESET = 1, + GPIO_BL_ENABLE = 2, + GPIO_IOVCC = 4, + GPIO_VCC = 8, + GPIO_TS_RESET = 9, +}; + +#define NUM_GPIO 16 + +struct waveshare_gpio { + struct mutex dir_lock; + struct mutex pwr_lock; + struct regmap *regmap; + u16 poweron_state; + + struct gpio_chip gc; +}; + +static const struct regmap_config waveshare_gpio_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_VERSION, +}; + +static int waveshare_gpio_get(struct waveshare_gpio *state, unsigned int offset) +{ + u16 pwr_state; + + guard(mutex)(&state->pwr_lock); + pwr_state = state->poweron_state & BIT(offset); + + return !!pwr_state; +} + +static int waveshare_gpio_set(struct waveshare_gpio *state, unsigned int offset, int value) +{ + u16 last_val; + int err; + + guard(mutex)(&state->pwr_lock); + + last_val = state->poweron_state; + if (value) + last_val |= BIT(offset); + else + last_val &= ~BIT(offset); + + state->poweron_state = last_val; + + err = regmap_write(state->regmap, REG_TP, last_val >> 8); + if (!err) + err = regmap_write(state->regmap, REG_LCD, last_val & 0xff); + + return err; +} + +static int waveshare_gpio_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int waveshare_gpio_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct waveshare_gpio *state = gpiochip_get_data(gc); + + return waveshare_gpio_get(state, offset); +} + +static int waveshare_gpio_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct waveshare_gpio *state = gpiochip_get_data(gc); + + return waveshare_gpio_set(state, offset, value); +} + +static int waveshare_gpio_update_status(struct backlight_device *bl) +{ + struct waveshare_gpio *state = bl_get_data(bl); + int brightness = backlight_get_brightness(bl); + + waveshare_gpio_set(state, GPIO_BL_ENABLE, brightness); + + return regmap_write(state->regmap, REG_PWM, brightness); +} + +static const struct backlight_ops waveshare_gpio_bl = { + .update_status = waveshare_gpio_update_status, +}; + +static int waveshare_gpio_probe(struct i2c_client *i2c) +{ + struct backlight_properties props = {}; + struct waveshare_gpio *state; + struct device *dev = &i2c->dev; + struct backlight_device *bl; + struct regmap *regmap; + unsigned int data; + int ret; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + ret = devm_mutex_init(dev, &state->dir_lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &state->pwr_lock); + if (ret) + return ret; + + regmap = devm_regmap_init_i2c(i2c, &waveshare_gpio_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to allocate register map\n"); + + state->regmap = regmap; + i2c_set_clientdata(i2c, state); + + ret = regmap_read(regmap, REG_ID, &data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read register\n"); + + dev_dbg(dev, "waveshare panel hw id = 0x%x\n", data); + + ret = regmap_read(regmap, REG_SIZE, &data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read register\n"); + + dev_dbg(dev, "waveshare panel size = %d\n", data); + + ret = regmap_read(regmap, REG_VERSION, &data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read register\n"); + + dev_dbg(dev, "waveshare panel mcu version = 0x%x\n", data); + + ret = waveshare_gpio_set(state, GPIO_TS_RESET, 1); + if (ret) + return dev_err_probe(dev, ret, "Failed to program GPIOs\n"); + + msleep(20); + + state->gc.parent = dev; + state->gc.label = i2c->name; + state->gc.owner = THIS_MODULE; + state->gc.base = -1; + state->gc.ngpio = NUM_GPIO; + + /* it is output only */ + state->gc.get = waveshare_gpio_gpio_get; + state->gc.set = waveshare_gpio_gpio_set; + state->gc.get_direction = waveshare_gpio_gpio_get_direction; + state->gc.can_sleep = true; + + ret = devm_gpiochip_add_data(dev, &state->gc, state); + if (ret) + return dev_err_probe(dev, ret, "Failed to create gpiochip\n"); + + props.type = BACKLIGHT_RAW; + props.max_brightness = 255; + props.brightness = 255; + bl = devm_backlight_device_register(dev, dev_name(dev), dev, state, + &waveshare_gpio_bl, &props); + return PTR_ERR_OR_ZERO(bl); +} + +static const struct of_device_id waveshare_gpio_dt_ids[] = { + { .compatible = "waveshare,dsi-touch-gpio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, waveshare_gpio_dt_ids); + +static struct i2c_driver waveshare_gpio_regulator_driver = { + .driver = { + .name = "waveshare-regulator", + .of_match_table = of_match_ptr(waveshare_gpio_dt_ids), + }, + .probe = waveshare_gpio_probe, +}; + +module_i2c_driver(waveshare_gpio_regulator_driver); + +MODULE_DESCRIPTION("GPIO controller driver for Waveshare DSI touch panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index be4b4d730547..532205175827 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -495,13 +495,11 @@ static void xgpio_irqhandler(struct irq_desc *desc) xgpio_read_ch_all(chip, XGPIO_DATA_OFFSET, hw); - bitmap_complement(rising, chip->last_irq_read, 64); - bitmap_and(rising, rising, hw, 64); + bitmap_andnot(rising, hw, chip->last_irq_read, 64); bitmap_and(rising, rising, chip->enable, 64); bitmap_and(rising, rising, chip->rising_edge, 64); - bitmap_complement(falling, hw, 64); - bitmap_and(falling, falling, chip->last_irq_read, 64); + bitmap_andnot(falling, chip->last_irq_read, hw, 64); bitmap_and(falling, falling, chip->enable, 64); bitmap_and(falling, falling, chip->falling_edge, 64); diff --git a/drivers/gpio/gpio-zevio.c b/drivers/gpio/gpio-zevio.c index 29375bea2289..af0158522ac5 100644 --- a/drivers/gpio/gpio-zevio.c +++ b/drivers/gpio/gpio-zevio.c @@ -64,14 +64,14 @@ static inline u32 zevio_gpio_port_get(struct zevio_gpio *c, unsigned pin, unsigned port_offset) { unsigned section_offset = ((pin >> 3) & 3)*ZEVIO_GPIO_SECTION_SIZE; - return readl(IOMEM(c->regs + section_offset + port_offset)); + return readl(c->regs + section_offset + port_offset); } static inline void zevio_gpio_port_set(struct zevio_gpio *c, unsigned pin, unsigned port_offset, u32 val) { unsigned section_offset = ((pin >> 3) & 3)*ZEVIO_GPIO_SECTION_SIZE; - writel(val, IOMEM(c->regs + section_offset + port_offset)); + writel(val, c->regs + section_offset + port_offset); } /* Functions for struct gpio_chip */ diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c index fafca91128b2..15a79d9a2e9e 100644 --- a/drivers/gpio/gpio-zynq.c +++ b/drivers/gpio/gpio-zynq.c @@ -25,6 +25,7 @@ #define VERSAL_GPIO_MAX_BANK 4 #define PMC_GPIO_MAX_BANK 5 #define VERSAL_UNUSED_BANKS 2 +#define EIO_GPIO_MAX_BANK 2 #define ZYNQ_GPIO_BANK0_NGPIO 32 #define ZYNQ_GPIO_BANK1_NGPIO 22 @@ -818,6 +819,16 @@ static const struct dev_pm_ops zynq_gpio_dev_pm_ops = { RUNTIME_PM_OPS(zynq_gpio_runtime_suspend, zynq_gpio_runtime_resume, NULL) }; +static const struct zynq_platform_data eio_gpio_def = { + .label = "eio_gpio", + .ngpio = 52, + .max_bank = EIO_GPIO_MAX_BANK, + .bank_min[0] = 0, + .bank_max[0] = 25, /* 0 to 25 are connected to MIOs (26 pins) */ + .bank_min[1] = 26, + .bank_max[1] = 51, /* Bank 1 are connected to MIOs (26 pins) */ +}; + static const struct zynq_platform_data versal_gpio_def = { .label = "versal_gpio", .quirks = GPIO_QUIRK_VERSAL, @@ -882,6 +893,7 @@ static const struct of_device_id zynq_gpio_of_match[] = { { .compatible = "xlnx,zynqmp-gpio-1.0", .data = &zynqmp_gpio_def }, { .compatible = "xlnx,versal-gpio-1.0", .data = &versal_gpio_def }, { .compatible = "xlnx,pmc-gpio-1.0", .data = &pmc_gpio_def }, + { .compatible = "xlnx,eio-gpio-1.0", .data = &eio_gpio_def }, { /* end of table */ } }; MODULE_DEVICE_TABLE(of, zynq_gpio_of_match); diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index 09f860200a05..fbd6945726bf 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -142,10 +142,6 @@ static struct gpio_desc *acpi_get_gpiod(char *path, unsigned int pin) if (!gdev) return ERR_PTR(-EPROBE_DEFER); - /* - * FIXME: keep track of the reference to the GPIO device somehow - * instead of putting it here. - */ return gpio_device_get_desc(gdev, pin); } diff --git a/drivers/gpio/gpiolib-devres.c b/drivers/gpio/gpiolib-devres.c index 72422c5db364..2ec825ffab7d 100644 --- a/drivers/gpio/gpiolib-devres.c +++ b/drivers/gpio/gpiolib-devres.c @@ -353,6 +353,13 @@ int devm_gpiochip_add_data_with_key(struct device *dev, struct gpio_chip *gc, vo { int ret; + /* + * We are passing the devres device here so if the user did not pass + * another parent, it's this one. + */ + if (!gc->parent) + gc->parent = dev; + ret = gpiochip_add_data_with_key(gc, data, lock_key, request_key); if (ret < 0) return ret; diff --git a/drivers/gpio/gpiolib-kunit.c b/drivers/gpio/gpiolib-kunit.c new file mode 100644 index 000000000000..380b68f879e5 --- /dev/null +++ b/drivers/gpio/gpiolib-kunit.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define GPIO_TEST_PROVIDER "gpio-test-provider" +#define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer" +#define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer" + +static int gpio_test_provider_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int gpio_test_provider_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + return 0; +} + +static int gpio_test_provider_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_chip *gc; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + gc->base = -1; + gc->ngpio = 4; + gc->label = "gpio-swnode-consumer-test-device"; + gc->parent = dev; + gc->owner = THIS_MODULE; + + gc->get_direction = gpio_test_provider_get_direction; + gc->set = gpio_test_provider_set; + + return devm_gpiochip_add_data(dev, gc, NULL); +} + +static struct platform_driver gpio_test_provider_driver = { + .probe = gpio_test_provider_probe, + .driver = { + .name = GPIO_TEST_PROVIDER, + }, +}; + +static const struct software_node gpio_test_provider_swnode = { + .name = "gpio-test-provider-primary", +}; + +struct gpio_swnode_consumer_pdata { + bool gpio_ok; +}; + +static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = { + .gpio_ok = false, +}; + +static int gpio_swnode_consumer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_swnode_consumer_pdata *pdata = dev_get_platdata(dev); + struct gpio_desc *desc; + + desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + pdata->gpio_ok = true; + + return 0; +} + +static struct platform_driver gpio_swnode_consumer_driver = { + .probe = gpio_swnode_consumer_probe, + .driver = { + .name = GPIO_SWNODE_TEST_CONSUMER, + }, +}; + +static void gpio_swnode_lookup_by_primary(struct kunit *test) +{ + struct gpio_swnode_consumer_pdata *pdata; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct platform_device *pdev; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_test_provider_swnode, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + properties[1] = (struct property_entry){ }; + + pdevinfo = (struct platform_device_info){ + .name = GPIO_SWNODE_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_swnode_pdata_template, + .size_data = sizeof(gpio_swnode_pdata_template), + .properties = properties, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&pdev->dev); + KUNIT_ASSERT_TRUE(test, pdata->gpio_ok); +} + +static void gpio_swnode_lookup_by_secondary(struct kunit *test) +{ + struct gpio_swnode_consumer_pdata *pdata; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct fwnode_handle *primary; + struct platform_device *pdev; + bool bound = false; + int ret; + + /* + * Can't live on the stack as it will still get referenced in cleanup + * path after this function returns. + */ + primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary); + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode_init(primary, NULL); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .fwnode = primary, + .swnode = &gpio_test_provider_swnode, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + properties[1] = (struct property_entry){ }; + + pdevinfo = (struct platform_device_info){ + .name = GPIO_SWNODE_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_swnode_pdata_template, + .size_data = sizeof(gpio_swnode_pdata_template), + .properties = properties, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&pdev->dev); + KUNIT_ASSERT_TRUE(test, pdata->gpio_ok); +} + +static struct kunit_case gpio_swnode_lookup_tests[] = { + KUNIT_CASE(gpio_swnode_lookup_by_primary), + KUNIT_CASE(gpio_swnode_lookup_by_secondary), + { } +}; + +static struct kunit_suite gpio_swnode_lookup_test_suite = { + .name = "gpio-swnode-lookup", + .test_cases = gpio_swnode_lookup_tests, +}; + +static BLOCKING_NOTIFIER_HEAD(gpio_unbind_notifier); + +struct gpio_unbind_consumer_drvdata { + struct device *dev; + struct gpio_desc *desc; + struct notifier_block nb; + int set_retval; +}; + +static int gpio_unbind_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct gpio_unbind_consumer_drvdata *drvdata = + container_of(nb, struct gpio_unbind_consumer_drvdata, nb); + struct device *dev = data; + + if (dev != drvdata->dev) + return NOTIFY_DONE; + + drvdata->set_retval = gpiod_set_value_cansleep(drvdata->desc, 0); + + return NOTIFY_OK; +} + +static void gpio_unbind_unregister_notifier(void *data) +{ + struct notifier_block *nb = data; + + blocking_notifier_chain_unregister(&gpio_unbind_notifier, nb); +} + +static int gpio_unbind_consumer_probe(struct platform_device *pdev) +{ + struct gpio_unbind_consumer_drvdata *data; + struct device *dev = &pdev->dev; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + + data->desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); + if (IS_ERR(data->desc)) + return PTR_ERR(data->desc); + + data->nb.notifier_call = gpio_unbind_notify; + ret = blocking_notifier_chain_register(&gpio_unbind_notifier, &data->nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, gpio_unbind_unregister_notifier, &data->nb); + if (ret) + return ret; + + platform_set_drvdata(pdev, data); + + return 0; +} + +static struct platform_driver gpio_unbind_consumer_driver = { + .probe = gpio_unbind_consumer_probe, + .driver = { + .name = GPIO_UNBIND_TEST_CONSUMER, + }, +}; + +static void gpio_unbind_with_consumers(struct kunit *test) +{ + struct gpio_unbind_consumer_drvdata *cons_data; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct platform_device *prvd, *cons; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_unbind_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_test_provider_swnode, + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + properties[1] = (struct property_entry){ }; + + pdevinfo = (struct platform_device_info){ + .name = GPIO_UNBIND_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + kunit_platform_device_unregister(test, prvd); + + ret = blocking_notifier_call_chain(&gpio_unbind_notifier, 0, &cons->dev); + KUNIT_ASSERT_EQ(test, ret, NOTIFY_OK); + + scoped_guard(device, &cons->dev) { + cons_data = platform_get_drvdata(cons); + ret = cons_data->set_retval; + } + + KUNIT_ASSERT_EQ(test, ret, -ENODEV); +} + +static struct kunit_case gpio_unbind_with_consumers_tests[] = { + KUNIT_CASE(gpio_unbind_with_consumers), + { } +}; + +static struct kunit_suite gpio_unbind_with_consumers_test_suite = { + .name = "gpio-unbind-with-consumers", + .test_cases = gpio_unbind_with_consumers_tests, +}; + +kunit_test_suites( + &gpio_swnode_lookup_test_suite, + &gpio_unbind_with_consumers_test_suite, +); + +MODULE_DESCRIPTION("Test module for the GPIO subsystem"); +MODULE_AUTHOR("Bartosz Golaszewski "); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 78dc79754077..edbcd9d80365 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -241,6 +241,14 @@ static void of_gpio_try_fixup_polarity(const struct device_node *np, * treats it as "active low". */ { "ti,tsc2005", "reset-gpios", false }, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_WSA881X) + /* + * WSA881 powerdown is always active low, but some device trees + * missed this when first contributed. It also has a very strange + * compatible. + */ + { "sdw10217201000", "powerdown-gpios", false }, #endif }; unsigned int i; diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index 4374067f621e..8d9591aa9304 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -114,10 +114,6 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, if (IS_ERR(gdev)) return ERR_CAST(gdev); - /* - * FIXME: The GPIO device reference is put at return but the descriptor - * is passed on. Find a proper solution. - */ desc = gpio_device_get_desc(gdev, args.args[0]); *flags = args.args[1]; /* We expect native GPIO flags */ diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index c1f9c0d367d5..8a5ff78a1149 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -55,7 +55,7 @@ /* Device and char device-related information */ static DEFINE_IDA(gpio_ida); -static dev_t gpio_devt; +static dev_t gpio_devt __ro_after_init; #define GPIO_DEV_MAX 256 /* 256 GPIO chip devices supported */ static int gpio_bus_match(struct device *dev, const struct device_driver *drv) @@ -114,7 +114,7 @@ static int gpiochip_irqchip_init_hw(struct gpio_chip *gc); static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc); static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc); -static bool gpiolib_initialized; +static bool gpiolib_initialized __ro_after_init; const char *gpiod_get_label(struct gpio_desc *desc) { @@ -143,13 +143,15 @@ static void desc_free_label(struct rcu_head *rh) static int desc_set_label(struct gpio_desc *desc, const char *label) { struct gpio_desc_label *new = NULL, *old; + size_t len; if (label) { - new = kzalloc_flex(*new, str, strlen(label) + 1); + len = strlen(label); + new = kzalloc_flex(*new, str, len + 1); if (!new) return -ENOMEM; - strcpy(new->str, label); + memcpy(new->str, label, len); } old = rcu_replace_pointer(desc->label, new, 1); @@ -491,6 +493,28 @@ int gpiod_get_direction(struct gpio_desc *desc) } EXPORT_SYMBOL_GPL(gpiod_get_direction); +/** + * gpiod_is_single_ended - check if the GPIO is configured as single-ended + * @desc: the GPIO descriptor to check + * + * Returns true if the GPIO is configured as either Open Drain or Open Source. + * In these modes, the direction of the line cannot always be reliably + * determined by reading hardware registers, as the "off" state (High-Z) + * is physically indistinguishable from an input state. + */ +bool gpiod_is_single_ended(struct gpio_desc *desc) +{ + if (!desc) + return false; + + if (test_bit(GPIOD_FLAG_OPEN_DRAIN, &desc->flags) || + test_bit(GPIOD_FLAG_OPEN_SOURCE, &desc->flags)) + return true; + + return false; +} +EXPORT_SYMBOL_GPL(gpiod_is_single_ended); + /* * Add a new chip to the global chips list, keeping the list of chips sorted * by range(means [base, base + ngpio - 1]) order. @@ -3414,20 +3438,13 @@ static int gpio_chip_get_value(struct gpio_chip *gc, const struct gpio_desc *des static int gpiod_get_raw_value_commit(const struct gpio_desc *desc) { - struct gpio_device *gdev; - struct gpio_chip *gc; int value; - /* FIXME Unable to use gpio_chip_guard due to const desc. */ - gdev = desc->gdev; - - guard(srcu)(&gdev->srcu); - - gc = srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) + CLASS(gpio_chip_guard, guard)(desc); + if (!guard.gc) return -ENODEV; - value = gpio_chip_get_value(gc, desc); + value = gpio_chip_get_value(guard.gc, desc); value = value < 0 ? value : !!value; trace_gpio_value(desc_to_gpio(desc), 1, value); return value; @@ -4134,8 +4151,6 @@ EXPORT_SYMBOL_GPL(gpiod_is_shared); */ int gpiod_to_irq(const struct gpio_desc *desc) { - struct gpio_device *gdev; - struct gpio_chip *gc; int offset; int ret; @@ -4143,16 +4158,13 @@ int gpiod_to_irq(const struct gpio_desc *desc) if (ret <= 0) return -EINVAL; - gdev = desc->gdev; - /* FIXME Cannot use gpio_chip_guard due to const desc. */ - guard(srcu)(&gdev->srcu); - gc = srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) + CLASS(gpio_chip_guard, guard)(desc); + if (!guard.gc) return -ENODEV; offset = gpiod_hwgpio(desc); - if (gc->to_irq) { - ret = gc->to_irq(gc, offset); + if (guard.gc->to_irq) { + ret = guard.gc->to_irq(guard.gc, offset); if (ret) return ret; @@ -4160,7 +4172,7 @@ int gpiod_to_irq(const struct gpio_desc *desc) return -ENXIO; } #ifdef CONFIG_GPIOLIB_IRQCHIP - if (gc->irq.chip) { + if (guard.gc->irq.chip) { /* * Avoid race condition with other code, which tries to lookup * an IRQ before the irqchip has been properly registered, @@ -5348,7 +5360,7 @@ EXPORT_SYMBOL_GPL(gpiod_put_array); * gpio_device of the GPIO chip with the firmware node and then simply * bind it to this stub driver. */ -static struct device_driver gpio_stub_drv = { +static struct device_driver gpio_stub_drv __ro_after_init = { .name = "gpio_stub_drv", .bus = &gpio_bus_type, }; @@ -5506,8 +5518,8 @@ static int gpiolib_seq_show(struct seq_file *s, void *v) if (gc->label) seq_printf(s, ", %s", gc->label); if (gc->can_sleep) - seq_printf(s, ", can sleep"); - seq_printf(s, ":\n"); + seq_puts(s, ", can sleep"); + seq_puts(s, ":\n"); if (gc->dbg_show) gc->dbg_show(s, gc); diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index dc4cb61a9318..650a702741df 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -244,7 +244,7 @@ DEFINE_CLASS(gpio_chip_guard, _guard; }), - struct gpio_desc *desc) + const struct gpio_desc *desc) int gpiod_request(struct gpio_desc *desc, const char *label); int gpiod_request_commit(struct gpio_desc *desc, const char *label); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 8c935f867a37..3123ab75600b 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -417,7 +417,7 @@ config I2C_ASPEED config I2C_AT91 tristate "Atmel AT91 I2C Two-Wire interface (TWI)" - depends on ARCH_MICROCHIP || COMPILE_TEST + depends on ARCH_MICROCHIP || MCHP_LAN966X_PCI || COMPILE_TEST help This supports the use of the I2C interface on Atmel AT91 processors. @@ -794,6 +794,7 @@ config I2C_K1 tristate "SpacemiT K1 I2C adapter" depends on ARCH_SPACEMIT || COMPILE_TEST depends on OF + default ARCH_SPACEMIT help This option enables support for the I2C interface on the SpacemiT K1 platform. @@ -850,6 +851,17 @@ config I2C_LS2X This driver can also be built as a module. If so, the module will be called i2c-ls2x. +config I2C_LS2X_V2 + tristate "Loongson-2 Fast Speed I2C adapter" + depends on LOONGARCH || COMPILE_TEST + select REGMAP_MMIO + help + If you say yes to this option, support will be included for the + I2C interface on the Loongson-2K0300 SoCs. + + This driver can also be built as a module. If so, the module + will be called i2c-ls2x-v2. + config I2C_MLXBF tristate "Mellanox BlueField I2C controller" depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 547123ab351f..3755c54b3d82 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_I2C_KEBA) += i2c-keba.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_LS2X) += i2c-ls2x.o +obj-$(CONFIG_I2C_LS2X_V2) += i2c-ls2x-v2.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-corei2c.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o diff --git a/drivers/i2c/busses/i2c-bcm-kona.c b/drivers/i2c/busses/i2c-bcm-kona.c index 9d8838bbd938..d4f287b31fd7 100644 --- a/drivers/i2c/busses/i2c-bcm-kona.c +++ b/drivers/i2c/busses/i2c-bcm-kona.c @@ -427,7 +427,7 @@ static int bcm_kona_i2c_write_fifo_single(struct bcm_kona_i2c_dev *dev, return -EREMOTEIO; } - /* Check if a timeout occured */ + /* Check if a timeout occurred */ if (!time_left) { dev_err(dev->device, "completion timed out\n"); return -EREMOTEIO; diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c index 0fb728ade92e..da8770182a18 100644 --- a/drivers/i2c/busses/i2c-cadence.c +++ b/drivers/i2c/busses/i2c-cadence.c @@ -1635,6 +1635,25 @@ static void cdns_i2c_remove(struct platform_device *pdev) reset_control_assert(id->reset); } +/** + * cdns_i2c_shutdown - Prepare I2C controller for system shutdown + * @pdev: Handle to the platform device structure + * + * Mark the adapter as suspended and reset the controller to ensure a clean + * handoff during system reboot or kexec. + */ +static void cdns_i2c_shutdown(struct platform_device *pdev) +{ + struct cdns_i2c *id = platform_get_drvdata(pdev); + + /* Mark the adapter as suspended to prevent further I2C transfers */ + i2c_mark_adapter_suspended(&id->adap); + + /* Reset the controller state if active - clocks are disabled when suspended */ + if (!pm_runtime_status_suspended(&pdev->dev)) + cdns_i2c_master_reset(&id->adap); +} + static struct platform_driver cdns_i2c_drv = { .driver = { .name = DRIVER_NAME, @@ -1643,6 +1662,7 @@ static struct platform_driver cdns_i2c_drv = { }, .probe = cdns_i2c_probe, .remove = cdns_i2c_remove, + .shutdown = cdns_i2c_shutdown, }; module_platform_driver(cdns_i2c_drv); diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 4dc57fd56170..e4dfa2ec58bb 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -633,6 +633,14 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev) abort_needed = (raw_intr_stats & DW_IC_INTR_MST_ON_HOLD) || (ic_stats & DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY); + + /* + * If we are in target mode and there is activity, we should also + * trigger an abort to clear the internal state machines. + */ + if (dev->mode == DW_IC_SLAVE && (ic_stats & DW_IC_STATUS_SLAVE_ACTIVITY)) + abort_needed = true; + if (abort_needed) { if (!(enable & DW_IC_ENABLE_ENABLE)) { regmap_write(dev->map, DW_IC_ENABLE, DW_IC_ENABLE_ENABLE); @@ -958,8 +966,8 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) * registered to the device core and immediate resume in case bus has * registered I2C slaves that do I2C transfers in their probe. */ - ACQUIRE(pm_runtime_noresume, pm)(dev->dev); - ret = ACQUIRE_ERR(pm_runtime_noresume, &pm); + PM_RUNTIME_ACQUIRE(dev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); if (ret) return ret; @@ -1028,5 +1036,29 @@ EXPORT_GPL_DEV_PM_OPS(i2c_dw_dev_pm_ops) = { RUNTIME_PM_OPS(i2c_dw_runtime_suspend, i2c_dw_runtime_resume, NULL) }; +void i2c_dw_shutdown(struct dw_i2c_dev *dev) +{ + unsigned int con; + + /* + * We only need to handle shutdown for target mode to ensure + * we NACK any incoming controller requests. Controller mode cleanup + * is handled after each transfer in i2c_dw_xfer(). + */ + if (dev->mode != DW_IC_SLAVE) + return; + + /* + * To quickly NACK the controller during shutdown, we set the target + * disable bit while the controller is still enabled. + */ + regmap_read(dev->map, DW_IC_CON, &con); + con |= DW_IC_CON_SLAVE_DISABLE; + regmap_write(dev->map, DW_IC_CON, con); + + i2c_dw_disable(dev); +} +EXPORT_SYMBOL_GPL(i2c_dw_shutdown); + MODULE_DESCRIPTION("Synopsys DesignWare I2C bus adapter core"); MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 9d8d104cc391..c71aa2dd368d 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -417,6 +417,7 @@ static inline void i2c_dw_configure(struct dw_i2c_dev *dev) int i2c_dw_probe(struct dw_i2c_dev *dev); int i2c_dw_init(struct dw_i2c_dev *dev); +void i2c_dw_shutdown(struct dw_i2c_dev *dev); void i2c_dw_set_mode(struct dw_i2c_dev *dev, int mode); #if IS_ENABLED(CONFIG_I2C_DESIGNWARE_BAYTRAIL) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index de929b91d5ea..7a301c8b604e 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -785,18 +785,25 @@ __i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num) * IC_RAW_INTR_STAT.MASTER_ON_HOLD holding SCL low. Check if * controller is still ACTIVE before disabling I2C. */ - if (i2c_dw_is_controller_active(dev)) - dev_err(dev->dev, "controller active\n"); - - /* - * We must disable the adapter before returning and signaling the end - * of the current transfer. Otherwise the hardware might continue - * generating interrupts which in turn causes a race condition with - * the following transfer. Needs some more investigation if the - * additional interrupts are a hardware bug or this driver doesn't - * handle them correctly yet. - */ - __i2c_dw_disable_nowait(dev); + if (i2c_dw_is_controller_active(dev)) { + /* + * If the controller is still active after the timeout, attempt a + * bus recovery to clear any potentially locked state. + */ + dev_err(dev->dev, "controller active after xfer, recovering\n"); + i2c_recover_bus(&dev->adapter); + i2c_dw_init(dev); + } else { + /* + * We must disable the adapter before returning and signaling the end + * of the current transfer. Otherwise the hardware might continue + * generating interrupts which in turn causes a race condition with + * the following transfer. Needs some more investigation if the + * additional interrupts are a hardware bug or this driver doesn't + * handle them correctly yet. + */ + __i2c_dw_disable_nowait(dev); + } if (dev->msg_err) return dev->msg_err; diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c index f21f9877c040..468287922363 100644 --- a/drivers/i2c/busses/i2c-designware-pcidrv.c +++ b/drivers/i2c/busses/i2c-designware-pcidrv.c @@ -308,58 +308,72 @@ static void i2c_dw_pci_remove(struct pci_dev *pdev) static const struct pci_device_id i2c_designware_pci_ids[] = { /* Medfield */ - { PCI_VDEVICE(INTEL, 0x0817), medfield }, - { PCI_VDEVICE(INTEL, 0x0818), medfield }, - { PCI_VDEVICE(INTEL, 0x0819), medfield }, - { PCI_VDEVICE(INTEL, 0x082C), medfield }, - { PCI_VDEVICE(INTEL, 0x082D), medfield }, - { PCI_VDEVICE(INTEL, 0x082E), medfield }, + { PCI_VDEVICE(INTEL, 0x0817), .driver_data = medfield }, + { PCI_VDEVICE(INTEL, 0x0818), .driver_data = medfield }, + { PCI_VDEVICE(INTEL, 0x0819), .driver_data = medfield }, + { PCI_VDEVICE(INTEL, 0x082C), .driver_data = medfield }, + { PCI_VDEVICE(INTEL, 0x082D), .driver_data = medfield }, + { PCI_VDEVICE(INTEL, 0x082E), .driver_data = medfield }, /* Merrifield */ - { PCI_VDEVICE(INTEL, 0x1195), merrifield }, - { PCI_VDEVICE(INTEL, 0x1196), merrifield }, + { PCI_VDEVICE(INTEL, 0x1195), .driver_data = merrifield }, + { PCI_VDEVICE(INTEL, 0x1196), .driver_data = merrifield }, /* Baytrail */ - { PCI_VDEVICE(INTEL, 0x0F41), baytrail }, - { PCI_VDEVICE(INTEL, 0x0F42), baytrail }, - { PCI_VDEVICE(INTEL, 0x0F43), baytrail }, - { PCI_VDEVICE(INTEL, 0x0F44), baytrail }, - { PCI_VDEVICE(INTEL, 0x0F45), baytrail }, - { PCI_VDEVICE(INTEL, 0x0F46), baytrail }, - { PCI_VDEVICE(INTEL, 0x0F47), baytrail }, + { PCI_VDEVICE(INTEL, 0x0F41), .driver_data = baytrail }, + { PCI_VDEVICE(INTEL, 0x0F42), .driver_data = baytrail }, + { PCI_VDEVICE(INTEL, 0x0F43), .driver_data = baytrail }, + { PCI_VDEVICE(INTEL, 0x0F44), .driver_data = baytrail }, + { PCI_VDEVICE(INTEL, 0x0F45), .driver_data = baytrail }, + { PCI_VDEVICE(INTEL, 0x0F46), .driver_data = baytrail }, + { PCI_VDEVICE(INTEL, 0x0F47), .driver_data = baytrail }, /* Haswell */ - { PCI_VDEVICE(INTEL, 0x9c61), haswell }, - { PCI_VDEVICE(INTEL, 0x9c62), haswell }, + { PCI_VDEVICE(INTEL, 0x9c61), .driver_data = haswell }, + { PCI_VDEVICE(INTEL, 0x9c62), .driver_data = haswell }, /* Braswell / Cherrytrail */ - { PCI_VDEVICE(INTEL, 0x22C1), cherrytrail }, - { PCI_VDEVICE(INTEL, 0x22C2), cherrytrail }, - { PCI_VDEVICE(INTEL, 0x22C3), cherrytrail }, - { PCI_VDEVICE(INTEL, 0x22C4), cherrytrail }, - { PCI_VDEVICE(INTEL, 0x22C5), cherrytrail }, - { PCI_VDEVICE(INTEL, 0x22C6), cherrytrail }, - { PCI_VDEVICE(INTEL, 0x22C7), cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C1), .driver_data = cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C2), .driver_data = cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C3), .driver_data = cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C4), .driver_data = cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C5), .driver_data = cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C6), .driver_data = cherrytrail }, + { PCI_VDEVICE(INTEL, 0x22C7), .driver_data = cherrytrail }, /* Elkhart Lake (PSE I2C) */ - { PCI_VDEVICE(INTEL, 0x4bb9), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bba), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bbb), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bbc), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bbd), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bbe), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bbf), elkhartlake }, - { PCI_VDEVICE(INTEL, 0x4bc0), elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bb9), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bba), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bbb), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bbc), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bbd), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bbe), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bbf), .driver_data = elkhartlake }, + { PCI_VDEVICE(INTEL, 0x4bc0), .driver_data = elkhartlake }, /* AMD NAVI */ - { PCI_VDEVICE(ATI, 0x7314), navi_amd }, - { PCI_VDEVICE(ATI, 0x73a4), navi_amd }, - { PCI_VDEVICE(ATI, 0x73e4), navi_amd }, - { PCI_VDEVICE(ATI, 0x73c4), navi_amd }, - { PCI_VDEVICE(ATI, 0x7444), navi_amd }, - { PCI_VDEVICE(ATI, 0x7464), navi_amd }, - {} + { PCI_VDEVICE(ATI, 0x7314), .driver_data = navi_amd }, + { PCI_VDEVICE(ATI, 0x73a4), .driver_data = navi_amd }, + { PCI_VDEVICE(ATI, 0x73e4), .driver_data = navi_amd }, + { PCI_VDEVICE(ATI, 0x73c4), .driver_data = navi_amd }, + { PCI_VDEVICE(ATI, 0x7444), .driver_data = navi_amd }, + { PCI_VDEVICE(ATI, 0x7464), .driver_data = navi_amd }, + { } }; MODULE_DEVICE_TABLE(pci, i2c_designware_pci_ids); +static void i2c_dw_pci_shutdown(struct pci_dev *pdev) +{ + struct dw_i2c_dev *i_dev; + + i_dev = pci_get_drvdata(pdev); + if (!i_dev) + return; + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + i2c_dw_shutdown(i_dev); +} + static struct pci_driver dw_i2c_driver = { .name = DRIVER_NAME, .probe = i2c_dw_pci_probe, .remove = i2c_dw_pci_remove, + .shutdown = i2c_dw_pci_shutdown, .driver = { .pm = pm_ptr(&i2c_dw_dev_pm_ops), }, diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 3351c4a9ef11..6d6e81242f74 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -279,6 +279,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "INT3432", 0 }, { "INT3433", 0 }, { "INTC10EF", 0 }, + { "LECA0003", 0 }, {} }; MODULE_DEVICE_TABLE(acpi, dw_i2c_acpi_match); @@ -289,9 +290,23 @@ static const struct platform_device_id dw_i2c_platform_ids[] = { }; MODULE_DEVICE_TABLE(platform, dw_i2c_platform_ids); +static void dw_i2c_plat_shutdown(struct platform_device *pdev) +{ + struct dw_i2c_dev *i_dev; + + i_dev = platform_get_drvdata(pdev); + if (!i_dev) + return; + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + i2c_dw_shutdown(i_dev); +} + static struct platform_driver dw_i2c_driver = { .probe = dw_i2c_plat_probe, .remove = dw_i2c_plat_remove, + .shutdown = dw_i2c_plat_shutdown, .driver = { .name = "i2c_designware", .of_match_table = dw_i2c_of_match, diff --git a/drivers/i2c/busses/i2c-eg20t.c b/drivers/i2c/busses/i2c-eg20t.c index f83238868802..8c67ab4f2aad 100644 --- a/drivers/i2c/busses/i2c-eg20t.c +++ b/drivers/i2c/busses/i2c-eg20t.c @@ -169,11 +169,11 @@ static DEFINE_MUTEX(pch_mutex); #define PCI_DEVICE_ID_ML7831_I2C 0x8817 static const struct pci_device_id pch_pcidev_id[] = { - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_PCH_I2C), 1, }, - { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7213_I2C), 2, }, - { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7223_I2C), 1, }, - { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7831_I2C), 1, }, - {0,} + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_PCH_I2C), .driver_data = 1 }, + { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7213_I2C), .driver_data = 2 }, + { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7223_I2C), .driver_data = 1 }, + { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7831_I2C), .driver_data = 1 }, + { } }; MODULE_DEVICE_TABLE(pci, pch_pcidev_id); diff --git a/drivers/i2c/busses/i2c-icy.c b/drivers/i2c/busses/i2c-icy.c index febcb6f01d4d..55496e48ccd1 100644 --- a/drivers/i2c/busses/i2c-icy.c +++ b/drivers/i2c/busses/i2c-icy.c @@ -193,8 +193,8 @@ static void icy_remove(struct zorro_dev *z) } static const struct zorro_device_id icy_zorro_tbl[] = { - { ZORRO_ID(VMC, 15, 0), }, - { 0 } + { .id = ZORRO_ID(VMC, 15, 0) }, + { } }; MODULE_DEVICE_TABLE(zorro, icy_zorro_tbl); diff --git a/drivers/i2c/busses/i2c-ls2x-v2.c b/drivers/i2c/busses/i2c-ls2x-v2.c new file mode 100644 index 000000000000..517760d70169 --- /dev/null +++ b/drivers/i2c/busses/i2c-ls2x-v2.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Loongson-2K0300 I2C controller driver + * + * Copyright (C) 2025-2026 Loongson Technology Corporation Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Loongson-2 fast I2C offset registers */ +#define LOONGSON2_I2C_CR1 0x00 /* I2C control 1 register */ +#define LOONGSON2_I2C_CR2 0x04 /* I2C control 2 register */ +#define LOONGSON2_I2C_OAR 0x08 /* I2C slave address register */ +#define LOONGSON2_I2C_DR 0x10 /* I2C data register */ +#define LOONGSON2_I2C_SR1 0x14 /* I2C status 1 register */ +#define LOONGSON2_I2C_SR2 0x18 /* I2C status 2 register */ +#define LOONGSON2_I2C_CCR 0x1c /* I2C clock control register */ +#define LOONGSON2_I2C_TRISE 0x20 /* I2C trise register */ +#define LOONGSON2_I2C_FLTR 0x24 + +/* Bitfields of I2C control 1 register */ +#define LOONGSON2_I2C_CR1_PE BIT(0) /* Peripheral enable */ +#define LOONGSON2_I2C_CR1_START BIT(8) /* Start generation */ +#define LOONGSON2_I2C_CR1_STOP BIT(9) /* Stop generation */ +#define LOONGSON2_I2C_CR1_ACK BIT(10) /* Acknowledge enable */ +#define LOONGSON2_I2C_CR1_POS BIT(11) /* Acknowledge/PEC Position (for data reception) */ + +#define LOONGSON2_I2C_CR1_OP_MASK (LOONGSON2_I2C_CR1_START | LOONGSON2_I2C_CR1_STOP) + +/* Bitfields of I2C control 2 register */ +#define LOONGSON2_I2C_CR2_FREQ GENMASK(5, 0) /* APB Clock Frequency in MHz */ +#define LOONGSON2_I2C_CR2_ITERREN BIT(8) /* Fault-Class Interrupt Enable */ +#define LOONGSON2_I2C_CR2_ITEVTEN BIT(9) /* Event-Based Interrupt Enable */ +#define LOONGSON2_I2C_CR2_ITBUFEN BIT(10) /* Cache-Class Interrupt Enable */ + +#define LOONGSON2_I2C_CR2_INT_MASK \ + (LOONGSON2_I2C_CR2_ITBUFEN | LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN) + +/* Bitfields of I2C status 1 register */ +#define LOONGSON2_I2C_SR1_SB BIT(0) /* Start bit (Master mode) */ +#define LOONGSON2_I2C_SR1_ADDR BIT(1) /* Address sent (master mode) */ +#define LOONGSON2_I2C_SR1_BTF BIT(2) /* Byte transfer finished */ +#define LOONGSON2_I2C_SR1_RXNE BIT(6) /* Data register not empty (receivers) */ +#define LOONGSON2_I2C_SR1_TXE BIT(7) /* Data register empty (transmitters) */ +#define LOONGSON2_I2C_SR1_BERR BIT(8) /* Bus error */ +#define LOONGSON2_I2C_SR1_ARLO BIT(9) /* Arbitration lost (master mode) */ +#define LOONGSON2_I2C_SR1_AF BIT(10) /* Acknowledge failure */ + +#define LOONGSON2_I2C_SR1_ITEVTEN_MASK \ + (LOONGSON2_I2C_SR1_BTF | LOONGSON2_I2C_SR1_ADDR | LOONGSON2_I2C_SR1_SB) +#define LOONGSON2_I2C_SR1_ITBUFEN_MASK (LOONGSON2_I2C_SR1_TXE | LOONGSON2_I2C_SR1_RXNE) +#define LOONGSON2_I2C_SR1_ITERREN_MASK \ + (LOONGSON2_I2C_SR1_AF | LOONGSON2_I2C_SR1_ARLO | LOONGSON2_I2C_SR1_BERR) + +/* Bitfields of I2C status 2 register */ +#define LOONGSON2_I2C_SR2_MSL BIT(0) /* Master/slave */ +#define LOONGSON2_I2C_SR2_BUSY BIT(1) /* Bus busy */ +#define LOONGSON2_I2C_SR2_TRA BIT(2) /* Transmitter/receiver */ +#define LOONGSON2_I2C_SR2_GENCALL BIT(4) /* General call address (Slave mode) */ + +/* Bitfields of I2C clock control register */ +#define LOONGSON2_I2C_CCR_CCR GENMASK(11, 0) +#define LOONGSON2_I2C_CCR_DUTY BIT(14) +#define LOONGSON2_I2C_CCR_FS BIT(15) + +/* Bitfields of I2C trise register */ +#define LOONGSON2_I2C_TRISE_SCL GENMASK(5, 0) + +#define LOONGSON2_I2C_FREE_SLEEP_US 10 +#define LOONGSON2_I2C_FREE_TIMEOUT_US (2 * USEC_PER_MSEC) + +/** + * struct loongson2_i2c_msg - client specific data + * @buf: data buffer + * @count: number of bytes to be transferred + * @result: result of the transfer + * @addr: 8-bit slave addr, including r/w bit + * @stop: last I2C msg to be sent, i.e. STOP to be generated + */ +struct loongson2_i2c_msg { + u8 *buf; + u32 count; + int result; + u8 addr; + bool stop; +}; + +/** + * struct loongson2_i2c_priv - private data of the controller + * @adapter: I2C adapter for this controller + * @complete: completion of I2C message + * @clk: hw i2c clock + * @regmap: regmap of the I2C device + * @parent_rate_MHz: I2C clock parent rate + * @msg: I2C transfer information + */ +struct loongson2_i2c_priv { + struct i2c_adapter adapter; + struct completion complete; + struct clk *clk; + struct regmap *regmap; + unsigned long parent_rate_MHz; + struct loongson2_i2c_msg msg; +}; + +static void loongson2_i2c_disable_irq(struct loongson2_i2c_priv *priv) +{ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_INT_MASK, 0); +} + +static void loongson2_i2c_read_msg(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + u32 rbuf; + + regmap_read(priv->regmap, LOONGSON2_I2C_DR, &rbuf); + *msg->buf++ = rbuf; + msg->count--; +} + +static void loongson2_i2c_write_msg(struct loongson2_i2c_priv *priv, u8 byte) +{ + regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte); +} + +static void loongson2_i2c_terminate_xfer(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + loongson2_i2c_disable_irq(priv); + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK, + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START); + complete(&priv->complete); +} + +static void loongson2_i2c_handle_write(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + if (msg->count) { + loongson2_i2c_write_msg(priv, *msg->buf++); + if (!--msg->count) + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, + LOONGSON2_I2C_CR2_ITBUFEN, 0); + } else { + loongson2_i2c_terminate_xfer(priv); + } +} + +static void loongson2_i2c_handle_rx_addr(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + switch (msg->count) { + case 0: + loongson2_i2c_terminate_xfer(priv); + break; + case 1: + /* Enable NACK and reset POS (Acknowledge position) */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, + LOONGSON2_I2C_CR1_ACK | LOONGSON2_I2C_CR1_POS, 0); + /* Set STOP or RepSTART */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK, + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START); + break; + case 2: + /* Enable NACK */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0); + /* Set POS (NACK position) */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, + LOONGSON2_I2C_CR1_POS); + break; + + default: + /* Enable ACK */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, + LOONGSON2_I2C_CR1_ACK); + /* Reset POS (ACK position) */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0); + break; + } +} + +static void loongson2_i2c_isr_error(u32 status, void *data) +{ + struct loongson2_i2c_priv *priv = data; + struct loongson2_i2c_msg *msg = &priv->msg; + + /* Arbitration lost */ + if (status & LOONGSON2_I2C_SR1_ARLO) { + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_ARLO, 0); + msg->result = -EAGAIN; + goto out; + } + + /* + * Acknowledge failure: + * In master transmitter mode a Stop must be generated by software. + */ + if (status & LOONGSON2_I2C_SR1_AF) { + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_STOP, + LOONGSON2_I2C_CR1_STOP); + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_AF, 0); + msg->result = -EIO; + goto out; + } + + /* Bus error */ + if (status & LOONGSON2_I2C_SR1_BERR) { + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_BERR, 0); + msg->result = -EIO; + goto out; + } + +out: + loongson2_i2c_disable_irq(priv); + complete(&priv->complete); +} + +static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + switch (msg->count) { + case 1: + loongson2_i2c_disable_irq(priv); + loongson2_i2c_read_msg(priv); + complete(&priv->complete); + break; + case 2: + case 3: + /* + * For 2-byte/3-byte reception and for N-byte reception with N > 3, we have to + * wait for byte transferred finished event before reading data. + * Just disable buffer interrupt in order to avoid another system preemption due + * to RX not empty event. + */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN, 0); + break; + default: + /* + * For N byte reception with N > 3 we directly read data register + * until N-2 data. + */ + loongson2_i2c_read_msg(priv); + break; + } +} + +static void loongson2_i2c_handle_rx_done(struct loongson2_i2c_priv *priv) +{ + struct loongson2_i2c_msg *msg = &priv->msg; + + switch (msg->count) { + case 2: + /* + * The STOP/START bit has to be set before reading the last two bytes. + * After that, we could read the last two bytes. + */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK, + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START); + + for (unsigned int i = msg->count; i > 0; i--) + loongson2_i2c_read_msg(priv); + + loongson2_i2c_disable_irq(priv); + + complete(&priv->complete); + break; + case 3: + /* + * In order to generate the NACK after the last received data byte, enable NACK + * before reading N-2 data. + */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0); + loongson2_i2c_read_msg(priv); + break; + default: + loongson2_i2c_read_msg(priv); + break; + } +} + +static irqreturn_t loongson2_i2c_isr_event(int irq, void *data) +{ + struct loongson2_i2c_priv *priv = data; + struct device *dev = regmap_get_device(priv->regmap); + struct loongson2_i2c_msg *msg = &priv->msg; + u32 status, ien, event, cr2, possible_status; + + regmap_read(priv->regmap, LOONGSON2_I2C_SR1, &status); + if (status & LOONGSON2_I2C_SR1_ITERREN_MASK) { + loongson2_i2c_isr_error(status, data); + return IRQ_NONE; + } + + regmap_read(priv->regmap, LOONGSON2_I2C_CR2, &cr2); + ien = cr2 & LOONGSON2_I2C_CR2_INT_MASK; + + /* Update possible_status if buffer interrupt is enabled */ + possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK; + if (ien & LOONGSON2_I2C_CR2_ITBUFEN) + possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK; + + event = status & possible_status; + if (!event) { + dev_dbg(dev, "spurious evt IRQ (status=0x%08x, ien=0x%08x)\n", status, ien); + return IRQ_NONE; + } + + /* Start condition generated */ + if (event & LOONGSON2_I2C_SR1_SB) + loongson2_i2c_write_msg(priv, msg->addr); + + /* I2C Address sent */ + if (event & LOONGSON2_I2C_SR1_ADDR) { + if (msg->addr & I2C_M_RD) + loongson2_i2c_handle_rx_addr(priv); + /* Clear ADDR flag */ + regmap_read(priv->regmap, LOONGSON2_I2C_SR2, &status); + /* Enable buffer interrupts for RX/TX not empty events */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN, + LOONGSON2_I2C_CR2_ITBUFEN); + } + + /* TX empty */ + if ((event & LOONGSON2_I2C_SR1_TXE) && !(msg->addr & I2C_M_RD)) + loongson2_i2c_handle_write(priv); + + /* RX not empty */ + if ((event & LOONGSON2_I2C_SR1_RXNE) && (msg->addr & I2C_M_RD)) + loongson2_i2c_handle_read(priv); + + /* + * The BTF (Byte Transfer finished) event occurs when: + * - in reception: a new byte is received in the shift register + * but the previous byte has not been read yet from data register + * - in transmission: a new byte should be sent but the data register + * has not been written yet + */ + if (event & LOONGSON2_I2C_SR1_BTF) { + if (msg->addr & I2C_M_RD) + loongson2_i2c_handle_rx_done(priv); + else + loongson2_i2c_handle_write(priv); + } + + return IRQ_HANDLED; +} + +static int loongson2_i2c_xfer_msg(struct loongson2_i2c_priv *priv, struct i2c_msg *msg, + bool is_stop) +{ + struct loongson2_i2c_msg *l_msg = &priv->msg; + unsigned long timeout; + + l_msg->addr = i2c_8bit_addr_from_msg(msg); + l_msg->buf = msg->buf; + l_msg->count = msg->len; + l_msg->stop = is_stop; + l_msg->result = 0; + + reinit_completion(&priv->complete); + + /* Enable events and errors interrupts */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, + LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN, + LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN); + + timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout); + if (!timeout) + return -ETIMEDOUT; + + return l_msg->result; +} + +static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num) +{ + struct loongson2_i2c_priv *priv = i2c_get_adapdata(i2c_adap); + struct device *dev = regmap_get_device(priv->regmap); + unsigned int status; + int ret; + + /* Wait I2C bus free */ + ret = regmap_read_poll_timeout(priv->regmap, LOONGSON2_I2C_SR2, status, + !(status & LOONGSON2_I2C_SR2_BUSY), + LOONGSON2_I2C_FREE_SLEEP_US, + LOONGSON2_I2C_FREE_TIMEOUT_US); + if (ret) { + dev_dbg(dev, "The I2C bus is busy now.\n"); + return ret; + } + + /* Start generation */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_START, + LOONGSON2_I2C_CR1_START); + + for (unsigned int i = 0; i < num; i++) { + ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1); + if (ret < 0) + return ret; + } + + return num; +} + +static u32 loongson2_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm loongson2_i2c_algo = { + .xfer = loongson2_i2c_xfer, + .functionality = loongson2_i2c_func, +}; + +static int loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv) +{ + struct device *dev = regmap_get_device(priv->regmap); + struct i2c_timings i2c_t; + u32 val, freq_MHz, ccr; + + i2c_parse_fw_timings(dev, &i2c_t, true); + priv->parent_rate_MHz = clk_get_rate(priv->clk); + + if (i2c_t.bus_freq_hz == I2C_MAX_STANDARD_MODE_FREQ) { + /* Select Standard mode */ + ccr = 0; + val = DIV_ROUND_UP(priv->parent_rate_MHz, i2c_t.bus_freq_hz * 2); + } else if (i2c_t.bus_freq_hz == I2C_MAX_FAST_MODE_FREQ) { + /* Select Fast mode */ + ccr = LOONGSON2_I2C_CCR_FS; + val = DIV_ROUND_UP(priv->parent_rate_MHz, i2c_t.bus_freq_hz * 3); + } else { + return dev_err_probe(dev, -EINVAL, "Unsupported speed (%uHz)\n", i2c_t.bus_freq_hz); + } + + FIELD_MODIFY(LOONGSON2_I2C_CCR_CCR, &ccr, val); + regmap_write(priv->regmap, LOONGSON2_I2C_CCR, ccr); + + freq_MHz = DIV_ROUND_UP(priv->parent_rate_MHz, HZ_PER_MHZ); + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_FREQ, + FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq_MHz)); + + regmap_update_bits(priv->regmap, LOONGSON2_I2C_TRISE, LOONGSON2_I2C_TRISE_SCL, + LOONGSON2_I2C_TRISE_SCL); + + /* Enable I2C */ + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_PE, + LOONGSON2_I2C_CR1_PE); + + return 0; +} + +static const struct regmap_config loongson2_i2c_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = LOONGSON2_I2C_TRISE, +}; + +static int loongson2_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct loongson2_i2c_priv *priv; + struct i2c_adapter *adap; + void __iomem *base; + int irq, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, &loongson2_i2c_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "Failed to init regmap.\n"); + + priv->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to enable clock.\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + adap = &priv->adapter; + adap->retries = 5; + adap->nr = pdev->id; + adap->dev.parent = dev; + adap->owner = THIS_MODULE; + adap->algo = &loongson2_i2c_algo; + adap->timeout = 2 * HZ; + device_set_node(&adap->dev, dev_fwnode(dev)); + i2c_set_adapdata(adap, priv); + strscpy(adap->name, pdev->name); + init_completion(&priv->complete); + platform_set_drvdata(pdev, priv); + + ret = loongson2_i2c_adjust_bus_speed(priv); + if (ret) + return ret; + + ret = devm_request_irq(dev, irq, loongson2_i2c_isr_event, IRQF_SHARED, pdev->name, priv); + if (ret) + return ret; + + return devm_i2c_add_adapter(dev, adap); +} + +static const struct of_device_id loongson2_i2c_id_table[] = { + { .compatible = "loongson,ls2k0300-i2c" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, loongson2_i2c_id_table); + +static struct platform_driver loongson2_i2c_driver = { + .driver = { + .name = "loongson2-i2c-v2", + .of_match_table = loongson2_i2c_id_table, + }, + .probe = loongson2_i2c_probe, +}; +module_platform_driver(loongson2_i2c_driver); + +MODULE_DESCRIPTION("Loongson-2K0300 I2C bus driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-mxs.c b/drivers/i2c/busses/i2c-mxs.c index 08c9091a1e35..4e07babea9c3 100644 --- a/drivers/i2c/busses/i2c-mxs.c +++ b/drivers/i2c/busses/i2c-mxs.c @@ -108,6 +108,14 @@ enum mxs_i2c_devtype { * @cmd_complete: completion object for transaction wait * @cmd_err: error code for last transaction * @adapter: i2c subsystem adapter node + * @timing0: I2C TIMING0 register value + * @timing1: I2C TIMING1 register value + * @timing2: I2C TIMING2 register value + * @dmach: DMA channel + * @pio_data: PIO data for DMA + * @addr_data: address data for DMA + * @sg_io: scatterlist for I/O + * @dma_read: flag indicating DMA read */ struct mxs_i2c_dev { struct device *dev; diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index a482a4c60744..d2f5055b0b10 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -110,7 +110,6 @@ struct geni_i2c_dev { struct clk *core_clk; u32 clk_freq_out; const struct geni_i2c_clk_fld *clk_fld; - int suspended; void *dma_buf; size_t xfer_len; dma_addr_t dma_addr; @@ -1143,7 +1142,6 @@ static int geni_i2c_probe(struct platform_device *pdev) if (ret) goto err_dma; - gi2c->suspended = 1; pm_runtime_set_suspended(gi2c->se.dev); pm_runtime_set_autosuspend_delay(gi2c->se.dev, I2C_AUTO_SUSPEND_DELAY); pm_runtime_use_autosuspend(gi2c->se.dev); @@ -1200,9 +1198,6 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev) if (ret) { enable_irq(gi2c->irq); return ret; - - } else { - gi2c->suspended = 1; } clk_disable_unprepare(gi2c->core_clk); @@ -1228,7 +1223,6 @@ static int __maybe_unused geni_i2c_runtime_resume(struct device *dev) goto out_clk_disable; enable_irq(gi2c->irq); - gi2c->suspended = 0; return 0; @@ -1243,21 +1237,25 @@ out_icc_disable: static int __maybe_unused geni_i2c_suspend_noirq(struct device *dev) { struct geni_i2c_dev *gi2c = dev_get_drvdata(dev); + int ret; i2c_mark_adapter_suspended(&gi2c->adap); - if (!gi2c->suspended) { - geni_i2c_runtime_suspend(dev); - pm_runtime_disable(dev); - pm_runtime_set_suspended(dev); - pm_runtime_enable(dev); - } - return 0; + ret = pm_runtime_force_suspend(dev); + if (ret) + i2c_mark_adapter_resumed(&gi2c->adap); + + return ret; } static int __maybe_unused geni_i2c_resume_noirq(struct device *dev) { struct geni_i2c_dev *gi2c = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; i2c_mark_adapter_resumed(&gi2c->adap); return 0; diff --git a/drivers/i2c/busses/i2c-sis630.c b/drivers/i2c/busses/i2c-sis630.c index a19c3d251804..3d0638c2bc51 100644 --- a/drivers/i2c/busses/i2c-sis630.c +++ b/drivers/i2c/busses/i2c-sis630.c @@ -431,24 +431,23 @@ static int sis630_setup(struct pci_dev *sis630_dev) in acpi io space and read acpi base addr */ if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, &b)) { - dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n"); - retval = -ENODEV; + retval = dev_err_probe(&sis630_dev->dev, -ENODEV, + "Error: Can't read bios ctl reg\n"); goto exit; } /* if ACPI already enabled , do nothing */ if (!(b & 0x80) && pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) { - dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n"); - retval = -ENODEV; + retval = dev_err_probe(&sis630_dev->dev, -ENODEV, + "Error: Can't enable ACPI\n"); goto exit; } /* Determine the ACPI base address */ if (pci_read_config_word(sis630_dev, SIS630_ACPI_BASE_REG, &acpi_base)) { - dev_err(&sis630_dev->dev, - "Error: Can't determine ACPI base address\n"); - retval = -ENODEV; + retval = dev_err_probe(&sis630_dev->dev, -ENODEV, + "Error: Can't determine ACPI base address\n"); goto exit; } @@ -469,11 +468,10 @@ static int sis630_setup(struct pci_dev *sis630_dev) /* Everything is happy, let's grab the memory and set things up. */ if (!request_region(smbus_base + SMB_STS, SIS630_SMB_IOREGION, sis630_driver.name)) { - dev_err(&sis630_dev->dev, - "I/O Region 0x%04x-0x%04x for SMBus already in use.\n", - smbus_base + SMB_STS, - smbus_base + SMB_STS + SIS630_SMB_IOREGION - 1); - retval = -EBUSY; + retval = dev_err_probe(&sis630_dev->dev, -EBUSY, + "I/O Region 0x%04x-0x%04x for SMBus already in use.\n", + smbus_base + SMB_STS, + smbus_base + SMB_STS + SIS630_SMB_IOREGION - 1); goto exit; } @@ -511,12 +509,9 @@ static int sis630_probe(struct pci_dev *dev, const struct pci_device_id *id) { int ret; - if (sis630_setup(dev)) { - dev_err(&dev->dev, - "SIS630 compatible bus not detected, " - "module not inserted.\n"); - return -ENODEV; - } + if (sis630_setup(dev)) + return dev_err_probe(&dev->dev, -ENODEV, + "Compatible bus not detected, module not inserted.\n"); /* set up the sysfs linkage to our parent device */ sis630_adapter.dev.parent = &dev->dev; diff --git a/drivers/i2c/busses/i2c-sis96x.c b/drivers/i2c/busses/i2c-sis96x.c index 77529dda6fcd..eee41dc9d706 100644 --- a/drivers/i2c/busses/i2c-sis96x.c +++ b/drivers/i2c/busses/i2c-sis96x.c @@ -245,23 +245,19 @@ static int sis96x_probe(struct pci_dev *dev, u16 ww = 0; int retval; - if (sis96x_smbus_base) { - dev_err(&dev->dev, "Only one device supported.\n"); - return -EBUSY; - } + if (sis96x_smbus_base) + return dev_err_probe(&dev->dev, -EBUSY, "Only one device supported.\n"); pci_read_config_word(dev, PCI_CLASS_DEVICE, &ww); - if (PCI_CLASS_SERIAL_SMBUS != ww) { - dev_err(&dev->dev, "Unsupported device class 0x%04x!\n", ww); - return -ENODEV; - } + if (ww != PCI_CLASS_SERIAL_SMBUS) + return dev_err_probe(&dev->dev, -ENODEV, + "Unsupported device class 0x%04x!\n", ww); sis96x_smbus_base = pci_resource_start(dev, SIS96x_BAR); - if (!sis96x_smbus_base) { - dev_err(&dev->dev, "SiS96x SMBus base address " - "not initialized!\n"); - return -EINVAL; - } + if (!sis96x_smbus_base) + return dev_err_probe(&dev->dev, -EINVAL, + "SiS96x SMBus base address not initialized!\n"); + dev_info(&dev->dev, "SiS96x SMBus base address: 0x%04x\n", sis96x_smbus_base); @@ -272,9 +268,9 @@ static int sis96x_probe(struct pci_dev *dev, /* Everything is happy, let's grab the memory and set things up. */ if (!request_region(sis96x_smbus_base, SMB_IOSIZE, sis96x_driver.name)) { - dev_err(&dev->dev, "SMBus registers 0x%04x-0x%04x " - "already in use!\n", sis96x_smbus_base, - sis96x_smbus_base + SMB_IOSIZE - 1); + dev_err_probe(&dev->dev, -EINVAL, + "SMBus registers 0x%04x-0x%04x already in use!\n", + sis96x_smbus_base, sis96x_smbus_base + SMB_IOSIZE - 1); sis96x_smbus_base = 0; return -EINVAL; @@ -287,7 +283,7 @@ static int sis96x_probe(struct pci_dev *dev, "SiS96x SMBus adapter at 0x%04x", sis96x_smbus_base); if ((retval = i2c_add_adapter(&sis96x_adapter))) { - dev_err(&dev->dev, "Couldn't register adapter!\n"); + dev_err_probe(&dev->dev, retval, "Couldn't register adapter!\n"); release_region(sis96x_smbus_base, SMB_IOSIZE); sis96x_smbus_base = 0; } diff --git a/drivers/i2c/busses/i2c-sprd.c b/drivers/i2c/busses/i2c-sprd.c index 1b490525d8dd..7b321a956fca 100644 --- a/drivers/i2c/busses/i2c-sprd.c +++ b/drivers/i2c/busses/i2c-sprd.c @@ -469,11 +469,10 @@ static int sprd_i2c_clk_init(struct sprd_i2c *i2c_dev) i2c_dev->adap.nr, i2c_dev->src_clk); i2c_dev->clk = devm_clk_get(i2c_dev->dev, "enable"); - if (IS_ERR(i2c_dev->clk)) { - dev_err(i2c_dev->dev, "i2c%d can't get the enable clock\n", - i2c_dev->adap.nr); - return PTR_ERR(i2c_dev->clk); - } + if (IS_ERR(i2c_dev->clk)) + return dev_err_probe(i2c_dev->dev, PTR_ERR(i2c_dev->clk), + "i2c%d can't get the enable clock\n", + i2c_dev->adap.nr); return 0; } @@ -548,13 +547,13 @@ static int sprd_i2c_probe(struct platform_device *pdev) IRQF_NO_SUSPEND | IRQF_ONESHOT, pdev->name, i2c_dev); if (ret) { - dev_err(&pdev->dev, "failed to request irq %d\n", i2c_dev->irq); + dev_err_probe(&pdev->dev, ret, "failed to request irq %d\n", i2c_dev->irq); goto err_rpm_put; } ret = i2c_add_numbered_adapter(&i2c_dev->adap); if (ret) { - dev_err(&pdev->dev, "add adapter failed\n"); + dev_err_probe(&pdev->dev, ret, "add adapter failed\n"); goto err_rpm_put; } diff --git a/drivers/i2c/busses/i2c-st.c b/drivers/i2c/busses/i2c-st.c index 751ea421caaf..3f89c2145741 100644 --- a/drivers/i2c/busses/i2c-st.c +++ b/drivers/i2c/busses/i2c-st.c @@ -775,17 +775,15 @@ static int st_i2c_of_get_deglitch(struct device_node *np, ret = of_property_read_u32(np, "st,i2c-min-scl-pulse-width-us", &i2c_dev->scl_min_width_us); - if ((ret == -ENODATA) || (ret == -EOVERFLOW)) { - dev_err(i2c_dev->dev, "st,i2c-min-scl-pulse-width-us invalid\n"); - return ret; - } + if ((ret == -ENODATA) || (ret == -EOVERFLOW)) + return dev_err_probe(i2c_dev->dev, ret, + "st,i2c-min-scl-pulse-width-us invalid\n"); ret = of_property_read_u32(np, "st,i2c-min-sda-pulse-width-us", &i2c_dev->sda_min_width_us); - if ((ret == -ENODATA) || (ret == -EOVERFLOW)) { - dev_err(i2c_dev->dev, "st,i2c-min-sda-pulse-width-us invalid\n"); - return ret; - } + if ((ret == -ENODATA) || (ret == -EOVERFLOW)) + return dev_err_probe(i2c_dev->dev, ret, + "st,i2c-min-sda-pulse-width-us invalid\n"); return 0; } @@ -808,16 +806,13 @@ static int st_i2c_probe(struct platform_device *pdev) return PTR_ERR(i2c_dev->base); i2c_dev->irq = irq_of_parse_and_map(np, 0); - if (!i2c_dev->irq) { - dev_err(&pdev->dev, "IRQ missing or invalid\n"); - return -EINVAL; - } + if (!i2c_dev->irq) + return dev_err_probe(&pdev->dev, -EINVAL, "IRQ missing or invalid\n"); i2c_dev->clk = of_clk_get_by_name(np, "ssc"); - if (IS_ERR(i2c_dev->clk)) { - dev_err(&pdev->dev, "Unable to request clock\n"); - return PTR_ERR(i2c_dev->clk); - } + if (IS_ERR(i2c_dev->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2c_dev->clk), + "Unable to request clock\n"); i2c_dev->mode = I2C_MODE_STANDARD; ret = of_property_read_u32(np, "clock-frequency", &clk_rate); @@ -829,10 +824,9 @@ static int st_i2c_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, i2c_dev->irq, NULL, st_i2c_isr_thread, IRQF_ONESHOT, pdev->name, i2c_dev); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to request irq %i\n", i2c_dev->irq); pinctrl_pm_select_default_state(i2c_dev->dev); /* In case idle state available, select it */ diff --git a/drivers/i2c/busses/i2c-stm32.c b/drivers/i2c/busses/i2c-stm32.c index becf8977979f..064e47d6c96f 100644 --- a/drivers/i2c/busses/i2c-stm32.c +++ b/drivers/i2c/busses/i2c-stm32.c @@ -39,7 +39,7 @@ struct stm32_i2c_dma *stm32_i2c_dma_request(struct device *dev, dma_sconfig.direction = DMA_MEM_TO_DEV; ret = dmaengine_slave_config(dma->chan_tx, &dma_sconfig); if (ret < 0) { - dev_err(dev, "can't configure tx channel\n"); + dev_err_probe(dev, ret, "can't configure tx channel\n"); goto fail_tx; } @@ -60,7 +60,7 @@ struct stm32_i2c_dma *stm32_i2c_dma_request(struct device *dev, dma_sconfig.direction = DMA_DEV_TO_MEM; ret = dmaengine_slave_config(dma->chan_rx, &dma_sconfig); if (ret < 0) { - dev_err(dev, "can't configure rx channel\n"); + dev_err_probe(dev, ret, "can't configure rx channel\n"); goto fail_rx; } diff --git a/drivers/i2c/busses/i2c-stm32f4.c b/drivers/i2c/busses/i2c-stm32f4.c index b3d56d0aa9d0..44e8b04962bb 100644 --- a/drivers/i2c/busses/i2c-stm32f4.c +++ b/drivers/i2c/busses/i2c-stm32f4.c @@ -163,11 +163,9 @@ static int stm32f4_i2c_set_periph_clk_freq(struct stm32f4_i2c_dev *i2c_dev) * to hardware limitation */ if (freq < STM32F4_I2C_MIN_STANDARD_FREQ || - freq > STM32F4_I2C_MAX_FREQ) { - dev_err(i2c_dev->dev, - "bad parent clk freq for standard mode\n"); - return -EINVAL; - } + freq > STM32F4_I2C_MAX_FREQ) + return dev_err_probe(i2c_dev->dev, -EINVAL, + "bad parent clk freq for standard mode\n"); } else { /* * To be as close as possible to 400 kHz, the parent clk @@ -175,11 +173,9 @@ static int stm32f4_i2c_set_periph_clk_freq(struct stm32f4_i2c_dev *i2c_dev) * maximum value of 46 MHz due to hardware limitation */ if (freq < STM32F4_I2C_MIN_FAST_FREQ || - freq > STM32F4_I2C_MAX_FREQ) { - dev_err(i2c_dev->dev, - "bad parent clk freq for fast mode\n"); - return -EINVAL; - } + freq > STM32F4_I2C_MAX_FREQ) + return dev_err_probe(i2c_dev->dev, -EINVAL, + "bad parent clk freq for fast mode\n"); } cr2 |= STM32F4_I2C_CR2_FREQ(freq); @@ -772,22 +768,19 @@ static int stm32f4_i2c_probe(struct platform_device *pdev) return PTR_ERR(i2c_dev->base); irq_event = irq_of_parse_and_map(np, 0); - if (!irq_event) { - dev_err(&pdev->dev, "IRQ event missing or invalid\n"); - return -EINVAL; - } + if (!irq_event) + return dev_err_probe(&pdev->dev, -EINVAL, + "IRQ event missing or invalid\n"); irq_error = irq_of_parse_and_map(np, 1); - if (!irq_error) { - dev_err(&pdev->dev, "IRQ error missing or invalid\n"); - return -EINVAL; - } + if (!irq_error) + return dev_err_probe(&pdev->dev, -EINVAL, + "IRQ error missing or invalid\n"); i2c_dev->clk = devm_clk_get_enabled(&pdev->dev, NULL); - if (IS_ERR(i2c_dev->clk)) { - dev_err(&pdev->dev, "Failed to enable clock\n"); - return PTR_ERR(i2c_dev->clk); - } + if (IS_ERR(i2c_dev->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2c_dev->clk), + "Failed to enable clock\n"); rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); if (IS_ERR(rst)) @@ -807,19 +800,15 @@ static int stm32f4_i2c_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq_event, stm32f4_i2c_isr_event, 0, pdev->name, i2c_dev); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq event %i\n", - irq_event); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to request irq event %i\n", irq_event); ret = devm_request_irq(&pdev->dev, irq_error, stm32f4_i2c_isr_error, 0, pdev->name, i2c_dev); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq error %i\n", - irq_error); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to request irq error %i\n", irq_error); ret = stm32f4_i2c_hw_config(i2c_dev); if (ret) diff --git a/drivers/i2c/busses/i2c-stm32f7.c b/drivers/i2c/busses/i2c-stm32f7.c index 067af255bd22..16c6e61c7e11 100644 --- a/drivers/i2c/busses/i2c-stm32f7.c +++ b/drivers/i2c/busses/i2c-stm32f7.c @@ -481,28 +481,22 @@ static int stm32f7_i2c_compute_timing(struct stm32f7_i2c_dev *i2c_dev, int ret = 0; specs = stm32f7_get_specs(setup->speed_freq); - if (specs == ERR_PTR(-EINVAL)) { - dev_err(i2c_dev->dev, "speed out of bound {%d}\n", - setup->speed_freq); - return -EINVAL; - } + if (specs == ERR_PTR(-EINVAL)) + return dev_err_probe(i2c_dev->dev, -EINVAL, "speed out of bound {%d}\n", + setup->speed_freq); if ((setup->rise_time > specs->rise_max) || - (setup->fall_time > specs->fall_max)) { - dev_err(i2c_dev->dev, - "timings out of bound Rise{%d>%d}/Fall{%d>%d}\n", - setup->rise_time, specs->rise_max, - setup->fall_time, specs->fall_max); - return -EINVAL; - } + (setup->fall_time > specs->fall_max)) + return dev_err_probe(i2c_dev->dev, -EINVAL, + "timings out of bound Rise{%d>%d}/Fall{%d>%d}\n", + setup->rise_time, specs->rise_max, + setup->fall_time, specs->fall_max); i2c_dev->dnf = DIV_ROUND_CLOSEST(i2c_dev->dnf_dt, i2cclk); - if (i2c_dev->dnf > STM32F7_I2C_DNF_MAX) { - dev_err(i2c_dev->dev, - "DNF out of bound %d/%d\n", - i2c_dev->dnf * i2cclk, STM32F7_I2C_DNF_MAX * i2cclk); - return -EINVAL; - } + if (i2c_dev->dnf > STM32F7_I2C_DNF_MAX) + return dev_err_probe(i2c_dev->dev, -EINVAL, + "DNF out of bound %d/%d\n", i2c_dev->dnf * i2cclk, + STM32F7_I2C_DNF_MAX * i2cclk); /* Analog and Digital Filters */ af_delay_min = @@ -567,8 +561,7 @@ static int stm32f7_i2c_compute_timing(struct stm32f7_i2c_dev *i2c_dev, } if (list_empty(&solutions)) { - dev_err(i2c_dev->dev, "no Prescaler solution\n"); - ret = -EPERM; + ret = dev_err_probe(i2c_dev->dev, -EPERM, "no Prescaler solution\n"); goto exit; } @@ -624,8 +617,7 @@ static int stm32f7_i2c_compute_timing(struct stm32f7_i2c_dev *i2c_dev, } if (!s) { - dev_err(i2c_dev->dev, "no solution at all\n"); - ret = -EPERM; + ret = dev_err_probe(i2c_dev->dev, -EPERM, "no solution at all\n"); goto exit; } @@ -674,11 +666,9 @@ static int stm32f7_i2c_setup_timing(struct stm32f7_i2c_dev *i2c_dev, i2c_parse_fw_timings(i2c_dev->dev, t, false); - if (t->bus_freq_hz > I2C_MAX_FAST_MODE_PLUS_FREQ) { - dev_err(i2c_dev->dev, "Invalid bus speed (%i>%i)\n", - t->bus_freq_hz, I2C_MAX_FAST_MODE_PLUS_FREQ); - return -EINVAL; - } + if (t->bus_freq_hz > I2C_MAX_FAST_MODE_PLUS_FREQ) + return dev_err_probe(i2c_dev->dev, -EINVAL, "Invalid bus speed (%i>%i)\n", + t->bus_freq_hz, I2C_MAX_FAST_MODE_PLUS_FREQ); setup->speed_freq = t->bus_freq_hz; i2c_dev->setup.rise_time = t->scl_rise_ns; @@ -686,10 +676,8 @@ static int stm32f7_i2c_setup_timing(struct stm32f7_i2c_dev *i2c_dev, i2c_dev->dnf_dt = t->digital_filter_width_ns; setup->clock_src = clk_get_rate(i2c_dev->clk); - if (!setup->clock_src) { - dev_err(i2c_dev->dev, "clock rate is 0\n"); - return -EINVAL; - } + if (!setup->clock_src) + return dev_err_probe(i2c_dev->dev, -EINVAL, "clock rate is 0\n"); if (!of_property_read_bool(i2c_dev->dev->of_node, "i2c-digital-filter")) i2c_dev->dnf_dt = STM32F7_I2C_DNF_DEFAULT; @@ -701,8 +689,8 @@ static int stm32f7_i2c_setup_timing(struct stm32f7_i2c_dev *i2c_dev, ret = stm32f7_i2c_compute_timing(i2c_dev, setup, &i2c_dev->timing); if (ret) { - dev_err(i2c_dev->dev, - "failed to compute I2C timings.\n"); + dev_err_probe(i2c_dev->dev, ret, + "failed to compute I2C timings.\n"); if (setup->speed_freq <= I2C_MAX_STANDARD_MODE_FREQ) break; setup->speed_freq = @@ -713,10 +701,8 @@ static int stm32f7_i2c_setup_timing(struct stm32f7_i2c_dev *i2c_dev, } } while (ret); - if (ret) { - dev_err(i2c_dev->dev, "Impossible to compute I2C timings.\n"); - return ret; - } + if (ret) + return dev_err_probe(i2c_dev->dev, ret, "Impossible to compute I2C timings.\n"); dev_dbg(i2c_dev->dev, "I2C Speed(%i), Clk Source(%i)\n", setup->speed_freq, setup->clock_src); @@ -2175,10 +2161,8 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) return -ENOMEM; setup = of_device_get_match_data(&pdev->dev); - if (!setup) { - dev_err(&pdev->dev, "Can't get device data\n"); - return -ENODEV; - } + if (!setup) + return dev_err_probe(&pdev->dev, -ENODEV, "Can't get device data\n"); i2c_dev->setup = *setup; i2c_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); @@ -2279,7 +2263,7 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) ret = dev_pm_set_wake_irq(i2c_dev->dev, irq_event); if (ret) { - dev_err(i2c_dev->dev, "Failed to set wake up irq\n"); + dev_err_probe(i2c_dev->dev, ret, "Failed to set wake up irq\n"); goto clr_wakeup_capable; } } @@ -2305,9 +2289,8 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) if (i2c_dev->smbus_mode) { ret = stm32f7_i2c_enable_smbus_host(i2c_dev); if (ret) { - dev_err(i2c_dev->dev, - "failed to enable SMBus Host-Notify protocol (%d)\n", - ret); + dev_err_probe(i2c_dev->dev, ret, + "failed to enable SMBus Host-Notify protocol\n"); goto i2c_adapter_remove; } } @@ -2315,9 +2298,8 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) if (of_property_read_bool(pdev->dev.of_node, "smbus-alert")) { ret = stm32f7_i2c_enable_smbus_alert(i2c_dev); if (ret) { - dev_err(i2c_dev->dev, - "failed to enable SMBus alert protocol (%d)\n", - ret); + dev_err_probe(i2c_dev->dev, ret, + "failed to enable SMBus alert protocol\n"); goto i2c_disable_smbus_host; } } diff --git a/drivers/i2c/busses/i2c-sun6i-p2wi.c b/drivers/i2c/busses/i2c-sun6i-p2wi.c index fb5280b8cf7f..dffbe776a195 100644 --- a/drivers/i2c/busses/i2c-sun6i-p2wi.c +++ b/drivers/i2c/busses/i2c-sun6i-p2wi.c @@ -194,22 +194,16 @@ static int p2wi_probe(struct platform_device *pdev) int ret; of_property_read_u32(np, "clock-frequency", &clk_freq); - if (clk_freq > P2WI_MAX_FREQ) { - dev_err(dev, - "required clock-frequency (%u Hz) is too high (max = 6MHz)", - clk_freq); - return -EINVAL; - } + if (clk_freq > P2WI_MAX_FREQ) + return dev_err_probe(dev, -EINVAL, + "required clock-frequency (%u Hz) is too high (max = 6MHz)", + clk_freq); - if (clk_freq == 0) { - dev_err(dev, "clock-frequency is set to 0 in DT\n"); - return -EINVAL; - } + if (clk_freq == 0) + return dev_err_probe(dev, -EINVAL, "clock-frequency is set to 0 in DT\n"); - if (of_get_child_count(np) > 1) { - dev_err(dev, "P2WI only supports one target device\n"); - return -EINVAL; - } + if (of_get_child_count(np) > 1) + return dev_err_probe(dev, -EINVAL, "P2WI only supports one target device\n"); p2wi = devm_kzalloc(dev, sizeof(struct p2wi), GFP_KERNEL); if (!p2wi) @@ -226,11 +220,9 @@ static int p2wi_probe(struct platform_device *pdev) childnp = of_get_next_available_child(np, NULL); if (childnp) { ret = of_property_read_u32(childnp, "reg", &target_addr); - if (ret) { - dev_err(dev, "invalid target address on node %pOF\n", - childnp); - return -EINVAL; - } + if (ret) + return dev_err_probe(dev, -EINVAL, + "invalid target address on node %pOF\n", childnp); p2wi->target_addr = target_addr; } @@ -245,26 +237,20 @@ static int p2wi_probe(struct platform_device *pdev) return irq; p2wi->clk = devm_clk_get_enabled(dev, NULL); - if (IS_ERR(p2wi->clk)) { - ret = PTR_ERR(p2wi->clk); - dev_err(dev, "failed to enable clk: %d\n", ret); - return ret; - } + if (IS_ERR(p2wi->clk)) + return dev_err_probe(dev, PTR_ERR(p2wi->clk), + "failed to enable clk\n"); parent_clk_freq = clk_get_rate(p2wi->clk); p2wi->rstc = devm_reset_control_get_exclusive(dev, NULL); - if (IS_ERR(p2wi->rstc)) { - dev_err(dev, "failed to retrieve reset controller: %pe\n", - p2wi->rstc); - return PTR_ERR(p2wi->rstc); - } + if (IS_ERR(p2wi->rstc)) + return dev_err_probe(dev, PTR_ERR(p2wi->rstc), + "failed to retrieve reset controller\n"); ret = reset_control_deassert(p2wi->rstc); - if (ret) { - dev_err(dev, "failed to deassert reset line: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to deassert reset line\n"); init_completion(&p2wi->complete); p2wi->adapter.dev.parent = dev; @@ -276,8 +262,7 @@ static int p2wi_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, p2wi_interrupt, 0, pdev->name, p2wi); if (ret) { - dev_err(dev, "can't register interrupt handler irq%d: %d\n", - irq, ret); + dev_err_probe(dev, ret, "can't register interrupt handler irq%d\n", irq); goto err_reset_assert; } diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 400c9089daf0..9c281b4905eb 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -164,6 +164,7 @@ struct tegra_i2c_regs { unsigned int master_reset_cntrl; unsigned int mst_fifo_control; unsigned int mst_fifo_status; + unsigned int fairness_arb; unsigned int sw_mutex; }; @@ -272,6 +273,7 @@ static const struct tegra_i2c_regs tegra264_i2c_regs = { .master_reset_cntrl = 0x0a8, .mst_fifo_control = 0x0b4, .mst_fifo_status = 0x0b8, + .fairness_arb = 0x0e8, .sw_mutex = 0x0ec, }; @@ -300,6 +302,7 @@ static const struct tegra_i2c_regs tegra410_i2c_regs = { .master_reset_cntrl = 0x0ac, .mst_fifo_control = 0x0b8, .mst_fifo_status = 0x0bc, + .fairness_arb = 0x0ec, .sw_mutex = 0x0f0, }; @@ -379,6 +382,7 @@ enum tegra_i2c_variant { * timing settings. * @enable_hs_mode_support: Enable support for high speed (HS) mode transfers. * @has_mutex: Has mutex register for mutual exclusion with other firmwares or VMs. + * @has_fairarb_reg: Has fairness arbitration register for SMBUS/MCTP support. * @variant: This represents the I2C controller variant. * @regs: Register offsets for the specific SoC variant. */ @@ -412,6 +416,7 @@ struct tegra_i2c_hw_feature { bool has_interface_timing_reg; bool enable_hs_mode_support; bool has_mutex; + bool has_fairarb_reg; enum tegra_i2c_variant variant; const struct tegra_i2c_regs *regs; }; @@ -436,6 +441,7 @@ struct tegra_i2c_hw_feature { * @msg_read: indicates that the transfer is a read access * @timings: i2c timings information like bus frequency * @multimaster_mode: indicates that I2C controller is in multi-master mode + * @is_mctp: indicates that the I2C controller is used as an MCTP controller * @dma_chan: DMA channel * @dma_phys: handle to DMA resources * @dma_buf: pointer to allocated DMA buffer @@ -476,6 +482,7 @@ struct tegra_i2c_dev { void *dma_buf; bool multimaster_mode; + bool is_mctp; bool atomic_mode; bool dma_mode; bool msg_read; @@ -709,15 +716,15 @@ static int tegra_i2c_init_dma(struct tegra_i2c_dev *i2c_dev) goto err_out; } - i2c_dev->dma_dev = i2c_dev->dma_chan->device->dev; + i2c_dev->dma_dev = dmaengine_get_dma_device(i2c_dev->dma_chan); i2c_dev->dma_buf_size = i2c_dev->hw->quirks->max_write_len + I2C_PACKET_HEADER_SIZE; dma_buf = dma_alloc_coherent(i2c_dev->dma_dev, i2c_dev->dma_buf_size, &dma_phys, GFP_KERNEL | __GFP_NOWARN); if (!dma_buf) { - dev_err(i2c_dev->dev, "failed to allocate DMA buffer\n"); - err = -ENOMEM; + err = dev_err_probe(i2c_dev->dev, -ENOMEM, + "failed to allocate DMA buffer\n"); goto err_out; } @@ -729,8 +736,7 @@ static int tegra_i2c_init_dma(struct tegra_i2c_dev *i2c_dev) err_out: tegra_i2c_release_dma(i2c_dev); if (err != -EPROBE_DEFER) { - dev_err(i2c_dev->dev, "cannot use DMA: %d\n", err); - dev_err(i2c_dev->dev, "falling back to PIO\n"); + dev_err(i2c_dev->dev, "cannot use DMA, falling back to PIO\n"); return 0; } @@ -911,6 +917,10 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (IS_VI(i2c_dev)) tegra_i2c_vi_init(i2c_dev); + /* Disable fairness arbitration if not an MCTP controller */ + if (i2c_dev->hw->has_fairarb_reg && !i2c_dev->is_mctp) + i2c_writel(i2c_dev, 0, i2c_dev->hw->regs->fairness_arb); + if (i2c_dev->hw->enable_hs_mode_support) max_bus_freq_hz = I2C_MAX_HIGH_SPEED_MODE_FREQ; else @@ -1778,6 +1788,7 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -1811,6 +1822,7 @@ static const struct tegra_i2c_hw_feature tegra20_dvc_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DVC, .regs = &tegra20_dvc_i2c_regs, }; @@ -1844,6 +1856,7 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -1876,6 +1889,7 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -1908,6 +1922,7 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -1940,6 +1955,7 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -1973,6 +1989,7 @@ static const struct tegra_i2c_hw_feature tegra210_vi_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_VI, .regs = &tegra210_vi_i2c_regs, }; @@ -2006,6 +2023,7 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -2040,6 +2058,7 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = false, + .has_fairarb_reg = false, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra20_i2c_regs, }; @@ -2074,6 +2093,7 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = true, + .has_fairarb_reg = true, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra264_i2c_regs, }; @@ -2108,6 +2128,7 @@ static const struct tegra_i2c_hw_feature tegra264_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = true, + .has_fairarb_reg = true, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra264_i2c_regs, }; @@ -2142,6 +2163,7 @@ static const struct tegra_i2c_hw_feature tegra410_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = true, + .has_fairarb_reg = true, .variant = TEGRA_I2C_VARIANT_DEFAULT, .regs = &tegra410_i2c_regs, }; @@ -2174,6 +2196,7 @@ static void tegra_i2c_parse_dt(struct tegra_i2c_dev *i2c_dev) multi_mode = device_property_read_bool(i2c_dev->dev, "multi-master"); i2c_dev->multimaster_mode = multi_mode; + i2c_dev->is_mctp = device_property_present(i2c_dev->dev, "mctp-controller"); } static int tegra_i2c_init_clocks(struct tegra_i2c_dev *i2c_dev) @@ -2207,7 +2230,7 @@ static int tegra_i2c_init_clocks(struct tegra_i2c_dev *i2c_dev) err = clk_enable(i2c_dev->div_clk); if (err) { - dev_err(i2c_dev->dev, "failed to enable div-clk: %d\n", err); + dev_err_probe(i2c_dev->dev, err, "failed to enable div-clk\n"); goto unprepare_clocks; } @@ -2233,7 +2256,7 @@ static int tegra_i2c_init_hardware(struct tegra_i2c_dev *i2c_dev) ret = pm_runtime_get_sync(i2c_dev->dev); if (ret < 0) - dev_err(i2c_dev->dev, "runtime resume failed: %d\n", ret); + dev_err_probe(i2c_dev->dev, ret, "runtime resume failed\n"); else ret = tegra_i2c_init(i2c_dev); diff --git a/drivers/i2c/busses/i2c-tiny-usb.c b/drivers/i2c/busses/i2c-tiny-usb.c index 88d66593d9fc..73355a56aabf 100644 --- a/drivers/i2c/busses/i2c-tiny-usb.c +++ b/drivers/i2c/busses/i2c-tiny-usb.c @@ -254,9 +254,8 @@ static int i2c_tiny_usb_probe(struct usb_interface *interface, dev->usb_dev->bus->busnum, dev->usb_dev->devnum); if (usb_write(&dev->adapter, CMD_SET_DELAY, delay, 0, NULL, 0) != 0) { - dev_err(&dev->adapter.dev, - "failure setting delay to %dus\n", delay); - retval = -EIO; + retval = dev_err_probe(&dev->adapter.dev, -EIO, + "failure setting delay to %dus\n", delay); goto error; } diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 28c0e4884a7f..e5fddacae9a4 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -84,8 +84,11 @@ static int i2c_acpi_resource_count(struct acpi_resource *ares, void *data) * i2c_acpi_client_count - Count the number of I2cSerialBus resources * @adev: ACPI device * - * Returns the number of I2cSerialBus resources in the ACPI-device's + * Return: + * The number of I2cSerialBus resources in the ACPI-device's * resource-list; or a negative error code. + * + * Specifically returns -ENOENT when no resources found. */ int i2c_acpi_client_count(struct acpi_device *adev) { @@ -97,7 +100,7 @@ int i2c_acpi_client_count(struct acpi_device *adev) return ret; acpi_dev_free_resource_list(&r); - return count; + return count ?: -ENOENT; } EXPORT_SYMBOL_GPL(i2c_acpi_client_count); diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index a2132d70fb36..3ec04787a737 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -63,6 +63,7 @@ static DEFINE_MUTEX(core_lock); static DEFINE_IDR(i2c_adapter_idr); +static void i2c_deregister_clients(struct i2c_adapter *adap); static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver); static DEFINE_STATIC_KEY_FALSE(i2c_trace_msg_key); @@ -1106,8 +1107,8 @@ EXPORT_SYMBOL(i2c_find_device_by_fwnode); static const struct i2c_device_id dummy_id[] = { - { "dummy", }, - { "smbus_host_notify", }, + { .name = "dummy" }, + { .name = "smbus_host_notify" }, { } }; @@ -1515,23 +1516,48 @@ int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr) } EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); +static int i2c_allocate_adapter_id(struct i2c_adapter *adap) +{ + int id, start, end; + + if (adap->nr == -1) { + start = __i2c_first_dynamic_bus_num; + end = 0; + } else { + start = adap->nr; + end = adap->nr + 1; + } + + mutex_lock(&core_lock); + id = idr_alloc(&i2c_adapter_idr, NULL, start, end, GFP_KERNEL); + mutex_unlock(&core_lock); + if (id < 0) { + if (adap->nr != -1 && id == -ENOSPC) + id = -EBUSY; + pr_err("adapter '%s': failed to allocate id: %d\n", adap->name, id); + return id; + } + + adap->nr = id; + + return 0; +} + static int i2c_register_adapter(struct i2c_adapter *adap) { - int res = -EINVAL; + int res; /* Can't register until after driver model init */ - if (WARN_ON(!is_registered)) { - res = -EAGAIN; - goto out_list; - } + if (WARN_ON(!is_registered)) + return -EAGAIN; /* Sanity checks */ if (WARN(!adap->name[0], "i2c adapter has no name")) - goto out_list; + return -EINVAL; if (!adap->algo) { pr_err("adapter '%s': no algo supplied!\n", adap->name); - goto out_list; + return -EINVAL; } if (!adap->lock_ops) @@ -1552,14 +1578,25 @@ static int i2c_register_adapter(struct i2c_adapter *adap) if (res) { pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n", adap->name, res); - goto out_list; + return res; } - dev_set_name(&adap->dev, "i2c-%d", adap->nr); + res = i2c_allocate_adapter_id(adap); + if (res) + goto err_remove_irq_domain; + + res = dev_set_name(&adap->dev, "i2c-%d", adap->nr); + if (res) + goto err_free_id; + adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; device_initialize(&adap->dev); + res = i2c_init_recovery(adap); + if (res == -EPROBE_DEFER) + goto err_put_adap; + /* * This adapter can be used as a parent immediately after device_add(), * setup runtime-pm (especially ignore-children) before hand. @@ -1569,22 +1606,21 @@ static int i2c_register_adapter(struct i2c_adapter *adap) pm_suspend_ignore_children(&adap->dev, true); pm_runtime_enable(&adap->dev); + adap->debugfs = debugfs_create_dir(dev_name(&adap->dev), i2c_debugfs_root); + + mutex_lock(&core_lock); + idr_replace(&i2c_adapter_idr, adap, adap->nr); + mutex_unlock(&core_lock); + res = device_add(&adap->dev); if (res) { pr_err("adapter '%s': can't register device (%d)\n", adap->name, res); - put_device(&adap->dev); - goto out_list; + goto err_replace_id; } - adap->debugfs = debugfs_create_dir(dev_name(&adap->dev), i2c_debugfs_root); - res = i2c_setup_smbus_alert(adap); if (res) - goto out_reg; - - res = i2c_init_recovery(adap); - if (res == -EPROBE_DEFER) - goto out_reg; + goto err_deregister_clients; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); @@ -1603,38 +1639,29 @@ static int i2c_register_adapter(struct i2c_adapter *adap) return 0; -out_reg: +err_deregister_clients: + i2c_deregister_clients(adap); + device_del(&adap->dev); +err_replace_id: + mutex_lock(&core_lock); + idr_replace(&i2c_adapter_idr, NULL, adap->nr); + mutex_unlock(&core_lock); debugfs_remove_recursive(adap->debugfs); + pm_runtime_disable(&adap->dev); +err_put_adap: init_completion(&adap->dev_released); - device_unregister(&adap->dev); + put_device(&adap->dev); wait_for_completion(&adap->dev_released); -out_list: +err_free_id: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); +err_remove_irq_domain: + i2c_host_notify_irq_teardown(adap); + return res; } -/** - * __i2c_add_numbered_adapter - i2c_add_numbered_adapter where nr is never -1 - * @adap: the adapter to register (with adap->nr initialized) - * Context: can sleep - * - * See i2c_add_numbered_adapter() for details. - */ -static int __i2c_add_numbered_adapter(struct i2c_adapter *adap) -{ - int id; - - mutex_lock(&core_lock); - id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL); - mutex_unlock(&core_lock); - if (WARN(id < 0, "couldn't get idr")) - return id == -ENOSPC ? -EBUSY : id; - - return i2c_register_adapter(adap); -} - /** * i2c_add_adapter - declare i2c adapter, use dynamic bus number * @adapter: the adapter to add @@ -1655,17 +1682,8 @@ int i2c_add_adapter(struct i2c_adapter *adapter) int id; id = of_alias_get_id(dev->of_node, "i2c"); - if (id >= 0) { - adapter->nr = id; - return __i2c_add_numbered_adapter(adapter); - } - - mutex_lock(&core_lock); - id = idr_alloc(&i2c_adapter_idr, adapter, - __i2c_first_dynamic_bus_num, 0, GFP_KERNEL); - mutex_unlock(&core_lock); - if (WARN(id < 0, "couldn't get idr")) - return id; + if (id < 0) + id = -1; adapter->nr = id; @@ -1701,7 +1719,7 @@ int i2c_add_numbered_adapter(struct i2c_adapter *adap) if (adap->nr == -1) /* -1 means dynamically assign bus id */ return i2c_add_adapter(adap); - return __i2c_add_numbered_adapter(adap); + return i2c_register_adapter(adap); } EXPORT_SYMBOL_GPL(i2c_add_numbered_adapter); @@ -1743,29 +1761,10 @@ static int __process_removed_adapter(struct device_driver *d, void *data) return 0; } -/** - * i2c_del_adapter - unregister I2C adapter - * @adap: the adapter being unregistered - * Context: can sleep - * - * This unregisters an I2C adapter which was previously registered - * by @i2c_add_adapter or @i2c_add_numbered_adapter. - */ -void i2c_del_adapter(struct i2c_adapter *adap) +static void i2c_deregister_clients(struct i2c_adapter *adap) { - struct i2c_adapter *found; struct i2c_client *client, *next; - /* First make sure that this adapter was ever added */ - mutex_lock(&core_lock); - found = idr_find(&i2c_adapter_idr, adap->nr); - mutex_unlock(&core_lock); - if (found != adap) { - pr_debug("attempting to delete unregistered adapter [%s]\n", adap->name); - return; - } - - i2c_acpi_remove_space_handler(adap); /* Tell drivers about this removal */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, @@ -1791,6 +1790,34 @@ void i2c_del_adapter(struct i2c_adapter *adap) * them up properly, so we give them a chance to do that first. */ device_for_each_child(&adap->dev, NULL, __unregister_client); device_for_each_child(&adap->dev, NULL, __unregister_dummy); +} + +/** + * i2c_del_adapter - unregister I2C adapter + * @adap: the adapter being unregistered + * Context: can sleep + * + * This unregisters an I2C adapter which was previously registered + * by @i2c_add_adapter or @i2c_add_numbered_adapter. + */ +void i2c_del_adapter(struct i2c_adapter *adap) +{ + struct i2c_adapter *found; + + /* First make sure that this adapter was ever added */ + mutex_lock(&core_lock); + found = idr_find(&i2c_adapter_idr, adap->nr); + if (found == adap) + idr_replace(&i2c_adapter_idr, NULL, adap->nr); + mutex_unlock(&core_lock); + if (found != adap) { + pr_debug("attempting to delete unregistered adapter [%s]\n", adap->name); + return; + } + + i2c_acpi_remove_space_handler(adap); + + i2c_deregister_clients(adap); /* device name is gone after device_unregister */ dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name); diff --git a/drivers/i2c/i2c-slave-eeprom.c b/drivers/i2c/i2c-slave-eeprom.c index 6bc2ef650a74..226d722af662 100644 --- a/drivers/i2c/i2c-slave-eeprom.c +++ b/drivers/i2c/i2c-slave-eeprom.c @@ -191,14 +191,14 @@ static void i2c_slave_eeprom_remove(struct i2c_client *client) } static const struct i2c_device_id i2c_slave_eeprom_id[] = { - { "slave-24c02", I2C_SLAVE_DEVICE_MAGIC(2048 / 8, 0) }, - { "slave-24c02ro", I2C_SLAVE_DEVICE_MAGIC(2048 / 8, I2C_SLAVE_FLAG_RO) }, - { "slave-24c32", I2C_SLAVE_DEVICE_MAGIC(32768 / 8, I2C_SLAVE_FLAG_ADDR16) }, - { "slave-24c32ro", I2C_SLAVE_DEVICE_MAGIC(32768 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) }, - { "slave-24c64", I2C_SLAVE_DEVICE_MAGIC(65536 / 8, I2C_SLAVE_FLAG_ADDR16) }, - { "slave-24c64ro", I2C_SLAVE_DEVICE_MAGIC(65536 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) }, - { "slave-24c512", I2C_SLAVE_DEVICE_MAGIC(524288 / 8, I2C_SLAVE_FLAG_ADDR16) }, - { "slave-24c512ro", I2C_SLAVE_DEVICE_MAGIC(524288 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) }, + { .name = "slave-24c02", .driver_data = I2C_SLAVE_DEVICE_MAGIC(2048 / 8, 0) }, + { .name = "slave-24c02ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(2048 / 8, I2C_SLAVE_FLAG_RO) }, + { .name = "slave-24c32", .driver_data = I2C_SLAVE_DEVICE_MAGIC(32768 / 8, I2C_SLAVE_FLAG_ADDR16) }, + { .name = "slave-24c32ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(32768 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) }, + { .name = "slave-24c64", .driver_data = I2C_SLAVE_DEVICE_MAGIC(65536 / 8, I2C_SLAVE_FLAG_ADDR16) }, + { .name = "slave-24c64ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(65536 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) }, + { .name = "slave-24c512", .driver_data = I2C_SLAVE_DEVICE_MAGIC(524288 / 8, I2C_SLAVE_FLAG_ADDR16) }, + { .name = "slave-24c512ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(524288 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) }, { } }; MODULE_DEVICE_TABLE(i2c, i2c_slave_eeprom_id); diff --git a/drivers/i2c/i2c-slave-testunit.c b/drivers/i2c/i2c-slave-testunit.c index 871c58461ebc..4a1bf1aa504b 100644 --- a/drivers/i2c/i2c-slave-testunit.c +++ b/drivers/i2c/i2c-slave-testunit.c @@ -270,7 +270,7 @@ static void i2c_slave_testunit_remove(struct i2c_client *client) } static const struct i2c_device_id i2c_slave_testunit_id[] = { - { "slave-testunit" }, + { .name = "slave-testunit" }, { } }; MODULE_DEVICE_TABLE(i2c, i2c_slave_testunit_id); diff --git a/drivers/i2c/i2c-smbus.c b/drivers/i2c/i2c-smbus.c index bc7bd55e6370..069f08f68bc1 100644 --- a/drivers/i2c/i2c-smbus.c +++ b/drivers/i2c/i2c-smbus.c @@ -220,7 +220,7 @@ static void smbalert_remove(struct i2c_client *ara) } static const struct i2c_device_id smbalert_ids[] = { - { "smbus_alert" }, + { .name = "smbus_alert" }, { /* LIST END */ } }; MODULE_DEVICE_TABLE(i2c, smbalert_ids); diff --git a/drivers/i2c/muxes/i2c-mux-ltc4306.c b/drivers/i2c/muxes/i2c-mux-ltc4306.c index 50fbc0d06e62..18228f5b80e8 100644 --- a/drivers/i2c/muxes/i2c-mux-ltc4306.c +++ b/drivers/i2c/muxes/i2c-mux-ltc4306.c @@ -191,8 +191,8 @@ static int ltc4306_deselect_mux(struct i2c_mux_core *muxc, u32 chan) } static const struct i2c_device_id ltc4306_id[] = { - { "ltc4305", ltc_4305 }, - { "ltc4306", ltc_4306 }, + { .name = "ltc4305", .driver_data = ltc_4305 }, + { .name = "ltc4306", .driver_data = ltc_4306 }, { } }; MODULE_DEVICE_TABLE(i2c, ltc4306_id); diff --git a/drivers/i2c/muxes/i2c-mux-pca9541.c b/drivers/i2c/muxes/i2c-mux-pca9541.c index 3d8002caf703..9a59129bc50f 100644 --- a/drivers/i2c/muxes/i2c-mux-pca9541.c +++ b/drivers/i2c/muxes/i2c-mux-pca9541.c @@ -74,8 +74,8 @@ struct pca9541 { }; static const struct i2c_device_id pca9541_id[] = { - { "pca9541" }, - {} + { .name = "pca9541" }, + { } }; MODULE_DEVICE_TABLE(i2c, pca9541_id); diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c index b9f370c9f018..8fca709ed279 100644 --- a/drivers/i2c/muxes/i2c-mux-pca954x.c +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c @@ -250,24 +250,24 @@ static const struct chip_desc chips[] = { }; static const struct i2c_device_id pca954x_id[] = { - { "max7356", max_7356 }, - { "max7357", max_7357 }, - { "max7358", max_7358 }, - { "max7367", max_7367 }, - { "max7368", max_7368 }, - { "max7369", max_7369 }, - { "pca9540", pca_9540 }, - { "pca9542", pca_9542 }, - { "pca9543", pca_9543 }, - { "pca9544", pca_9544 }, - { "pca9545", pca_9545 }, - { "pca9546", pca_9546 }, - { "pca9547", pca_9547 }, - { "pca9548", pca_9548 }, - { "pca9846", pca_9846 }, - { "pca9847", pca_9847 }, - { "pca9848", pca_9848 }, - { "pca9849", pca_9849 }, + { .name = "max7356", .driver_data = max_7356 }, + { .name = "max7357", .driver_data = max_7357 }, + { .name = "max7358", .driver_data = max_7358 }, + { .name = "max7367", .driver_data = max_7367 }, + { .name = "max7368", .driver_data = max_7368 }, + { .name = "max7369", .driver_data = max_7369 }, + { .name = "pca9540", .driver_data = pca_9540 }, + { .name = "pca9542", .driver_data = pca_9542 }, + { .name = "pca9543", .driver_data = pca_9543 }, + { .name = "pca9544", .driver_data = pca_9544 }, + { .name = "pca9545", .driver_data = pca_9545 }, + { .name = "pca9546", .driver_data = pca_9546 }, + { .name = "pca9547", .driver_data = pca_9547 }, + { .name = "pca9548", .driver_data = pca_9548 }, + { .name = "pca9846", .driver_data = pca_9846 }, + { .name = "pca9847", .driver_data = pca_9847 }, + { .name = "pca9848", .driver_data = pca_9848 }, + { .name = "pca9849", .driver_data = pca_9849 }, { } }; MODULE_DEVICE_TABLE(i2c, pca954x_id); diff --git a/drivers/i2c/muxes/i2c-mux-reg.c b/drivers/i2c/muxes/i2c-mux-reg.c index 1e566ea92bc9..13da757100fe 100644 --- a/drivers/i2c/muxes/i2c-mux-reg.c +++ b/drivers/i2c/muxes/i2c-mux-reg.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -75,37 +74,34 @@ static int i2c_mux_reg_deselect(struct i2c_mux_core *muxc, u32 chan) return 0; } -#ifdef CONFIG_OF -static int i2c_mux_reg_probe_dt(struct regmux *mux, - struct platform_device *pdev) +static int i2c_mux_reg_probe_fw(struct regmux *mux, struct device *dev) { - struct device_node *np = pdev->dev.of_node; - struct device_node *adapter_np, *child; + struct fwnode_handle *fwnode, *child; struct i2c_adapter *adapter; - struct resource res; unsigned *values; - int i = 0; + int ret, i = 0; - if (!np) + if (!dev_fwnode(dev)) return -ENODEV; - adapter_np = of_parse_phandle(np, "i2c-parent", 0); - if (!adapter_np) { - dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); + fwnode = fwnode_find_reference(dev_fwnode(dev), "i2c-parent", 0); + if (IS_ERR(fwnode)) { + dev_err(dev, "missing 'i2c-parent' property\n"); return -ENODEV; } - adapter = of_find_i2c_adapter_by_node(adapter_np); - of_node_put(adapter_np); + + adapter = i2c_find_adapter_by_fwnode(fwnode); + fwnode_handle_put(fwnode); if (!adapter) return -EPROBE_DEFER; mux->data.parent = i2c_adapter_id(adapter); put_device(&adapter->dev); - mux->data.n_values = of_get_child_count(np); - if (of_property_read_bool(np, "little-endian")) { + mux->data.n_values = device_get_child_node_count(dev); + if (device_property_read_bool(dev, "little-endian")) { mux->data.little_endian = true; - } else if (of_property_read_bool(np, "big-endian")) { + } else if (device_property_read_bool(dev, "big-endian")) { mux->data.little_endian = false; } else { #if defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : \ @@ -118,40 +114,35 @@ static int i2c_mux_reg_probe_dt(struct regmux *mux, #error Endianness not defined? #endif } - mux->data.write_only = of_property_read_bool(np, "write-only"); + mux->data.write_only = device_property_read_bool(dev, "write-only"); - values = devm_kcalloc(&pdev->dev, - mux->data.n_values, sizeof(*mux->data.values), + values = devm_kcalloc(dev, mux->data.n_values, sizeof(*mux->data.values), GFP_KERNEL); if (!values) return -ENOMEM; - for_each_child_of_node(np, child) { - of_property_read_u32(child, "reg", values + i); + device_for_each_child_node(dev, child) { + if (is_acpi_device_node(child)) { + ret = acpi_get_local_address(ACPI_HANDLE_FWNODE(child), + &values[i]); + if (ret) { + fwnode_handle_put(child); + return dev_err_probe(dev, ret, + "Cannot get address\n"); + } + } else { + fwnode_property_read_u32(child, "reg", &values[i]); + } + i++; } mux->data.values = values; - if (!of_property_read_u32(np, "idle-state", &mux->data.idle)) + if (!device_property_read_u32(dev, "idle-state", &mux->data.idle)) mux->data.idle_in_use = true; - /* map address from "reg" if exists */ - if (of_address_to_resource(np, 0, &res) == 0) { - mux->data.reg_size = resource_size(&res); - mux->data.reg = devm_ioremap_resource(&pdev->dev, &res); - if (IS_ERR(mux->data.reg)) - return PTR_ERR(mux->data.reg); - } - return 0; } -#else -static int i2c_mux_reg_probe_dt(struct regmux *mux, - struct platform_device *pdev) -{ - return 0; -} -#endif static int i2c_mux_reg_probe(struct platform_device *pdev) { @@ -169,34 +160,29 @@ static int i2c_mux_reg_probe(struct platform_device *pdev) memcpy(&mux->data, dev_get_platdata(&pdev->dev), sizeof(mux->data)); } else { - ret = i2c_mux_reg_probe_dt(mux, pdev); + ret = i2c_mux_reg_probe_fw(mux, &pdev->dev); if (ret < 0) return dev_err_probe(&pdev->dev, ret, - "Error parsing device tree"); + "Error parsing firmware description\n"); } - parent = i2c_get_adapter(mux->data.parent); - if (!parent) - return -EPROBE_DEFER; - if (!mux->data.reg) { - dev_info(&pdev->dev, - "Register not set, using platform resource\n"); mux->data.reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res); - if (IS_ERR(mux->data.reg)) { - ret = PTR_ERR(mux->data.reg); - goto err_put_parent; - } + if (IS_ERR(mux->data.reg)) + return PTR_ERR(mux->data.reg); mux->data.reg_size = resource_size(res); } if (mux->data.reg_size != 4 && mux->data.reg_size != 2 && mux->data.reg_size != 1) { dev_err(&pdev->dev, "Invalid register size\n"); - ret = -EINVAL; - goto err_put_parent; + return -EINVAL; } + parent = i2c_get_adapter(mux->data.parent); + if (!parent) + return -EPROBE_DEFER; + muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, 0, 0, i2c_mux_reg_select, NULL); if (!muxc) { diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index a4d9c070d481..e75e1d6851ab 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -15,8 +15,6 @@ #include #include -#include - #include #include #include @@ -37,6 +35,10 @@ #define DRIVER_NAME "timberdale" +#define GPIO_NR_PINS 16 +#define GPIO_BASE 0 +#define IRQ_BASE 200 + struct timberdale_device { resource_size_t ctl_mapbase; unsigned char __iomem *ctl_membase; @@ -174,11 +176,16 @@ static const struct resource timberdale_eth_resources[] = { }, }; -static struct timbgpio_platform_data - timberdale_gpio_platform_data = { - .gpio_base = 0, - .nr_pins = GPIO_NR_PINS, - .irq_base = 200, +static const struct property_entry timberdale_gpio_properties[] = { + PROPERTY_ENTRY_U32("ngpios", GPIO_NR_PINS), + PROPERTY_ENTRY_U32("gpio-base", GPIO_BASE), + PROPERTY_ENTRY_U32("irq-base", IRQ_BASE), + { } +}; + +static const struct software_node timberdale_gpio_swnode = { + .name = "timb-gpio", + .properties = timberdale_gpio_properties, }; static const struct resource timberdale_gpio_resources[] = { @@ -390,8 +397,7 @@ static const struct mfd_cell timberdale_cells_bar0_cfg0[] = { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, - .pdata_size = sizeof(timberdale_gpio_platform_data), + .swnode = &timberdale_gpio_swnode, }, { .name = "timb-video", @@ -452,8 +458,7 @@ static const struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, - .pdata_size = sizeof(timberdale_gpio_platform_data), + .swnode = &timberdale_gpio_swnode, }, { .name = "timb-mlogicore", @@ -514,8 +519,7 @@ static const struct mfd_cell timberdale_cells_bar0_cfg2[] = { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, - .pdata_size = sizeof(timberdale_gpio_platform_data), + .swnode = &timberdale_gpio_swnode, }, { .name = "timb-video", @@ -564,8 +568,7 @@ static const struct mfd_cell timberdale_cells_bar0_cfg3[] = { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, - .pdata_size = sizeof(timberdale_gpio_platform_data), + .swnode = &timberdale_gpio_swnode, }, { .name = "timb-video", diff --git a/drivers/mfd/timberdale.h b/drivers/mfd/timberdale.h index b01d2388e1af..db7b434f766d 100644 --- a/drivers/mfd/timberdale.h +++ b/drivers/mfd/timberdale.h @@ -113,7 +113,6 @@ #define GPIO_PIN_ASCB 8 #define GPIO_PIN_INIC_RST 14 #define GPIO_PIN_BT_RST 15 -#define GPIO_NR_PINS 16 /* DMA Channels */ #define DMA_UART_RX 0 diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 0200288d3a7a..5d5f357a1996 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -215,37 +215,37 @@ AT24_CHIP_DATA(at24_data_24c2048, 2097152 / 8, AT24_FLAG_ADDR16); AT24_CHIP_DATA(at24_data_INT3499, 8192 / 8, 0); static const struct i2c_device_id at24_ids[] = { - { "24c00", (kernel_ulong_t)&at24_data_24c00 }, - { "24c01", (kernel_ulong_t)&at24_data_24c01 }, - { "24cs01", (kernel_ulong_t)&at24_data_24cs01 }, - { "24c02", (kernel_ulong_t)&at24_data_24c02 }, - { "24cs02", (kernel_ulong_t)&at24_data_24cs02 }, - { "24mac402", (kernel_ulong_t)&at24_data_24mac402 }, - { "24mac602", (kernel_ulong_t)&at24_data_24mac602 }, - { "24aa025e48", (kernel_ulong_t)&at24_data_24aa025e48 }, - { "24aa025e64", (kernel_ulong_t)&at24_data_24aa025e64 }, - { "spd", (kernel_ulong_t)&at24_data_spd }, - { "24c02-vaio", (kernel_ulong_t)&at24_data_24c02_vaio }, - { "24c04", (kernel_ulong_t)&at24_data_24c04 }, - { "24cs04", (kernel_ulong_t)&at24_data_24cs04 }, - { "24c08", (kernel_ulong_t)&at24_data_24c08 }, - { "24cs08", (kernel_ulong_t)&at24_data_24cs08 }, - { "24c16", (kernel_ulong_t)&at24_data_24c16 }, - { "24cs16", (kernel_ulong_t)&at24_data_24cs16 }, - { "24c32", (kernel_ulong_t)&at24_data_24c32 }, - { "24c32d-wl", (kernel_ulong_t)&at24_data_24c32d_wlp }, - { "24cs32", (kernel_ulong_t)&at24_data_24cs32 }, - { "24c64", (kernel_ulong_t)&at24_data_24c64 }, - { "24c64-wl", (kernel_ulong_t)&at24_data_24c64d_wlp }, - { "24cs64", (kernel_ulong_t)&at24_data_24cs64 }, - { "24c128", (kernel_ulong_t)&at24_data_24c128 }, - { "24c256", (kernel_ulong_t)&at24_data_24c256 }, - { "24256e-wl", (kernel_ulong_t)&at24_data_24256e_wlp }, - { "24c512", (kernel_ulong_t)&at24_data_24c512 }, - { "24c1024", (kernel_ulong_t)&at24_data_24c1024 }, - { "24c1025", (kernel_ulong_t)&at24_data_24c1025 }, - { "24c2048", (kernel_ulong_t)&at24_data_24c2048 }, - { "at24", 0 }, + { .name = "24c00", .driver_data = (kernel_ulong_t)&at24_data_24c00 }, + { .name = "24c01", .driver_data = (kernel_ulong_t)&at24_data_24c01 }, + { .name = "24cs01", .driver_data = (kernel_ulong_t)&at24_data_24cs01 }, + { .name = "24c02", .driver_data = (kernel_ulong_t)&at24_data_24c02 }, + { .name = "24cs02", .driver_data = (kernel_ulong_t)&at24_data_24cs02 }, + { .name = "24mac402", .driver_data = (kernel_ulong_t)&at24_data_24mac402 }, + { .name = "24mac602", .driver_data = (kernel_ulong_t)&at24_data_24mac602 }, + { .name = "24aa025e48", .driver_data = (kernel_ulong_t)&at24_data_24aa025e48 }, + { .name = "24aa025e64", .driver_data = (kernel_ulong_t)&at24_data_24aa025e64 }, + { .name = "spd", .driver_data = (kernel_ulong_t)&at24_data_spd }, + { .name = "24c02-vaio", .driver_data = (kernel_ulong_t)&at24_data_24c02_vaio }, + { .name = "24c04", .driver_data = (kernel_ulong_t)&at24_data_24c04 }, + { .name = "24cs04", .driver_data = (kernel_ulong_t)&at24_data_24cs04 }, + { .name = "24c08", .driver_data = (kernel_ulong_t)&at24_data_24c08 }, + { .name = "24cs08", .driver_data = (kernel_ulong_t)&at24_data_24cs08 }, + { .name = "24c16", .driver_data = (kernel_ulong_t)&at24_data_24c16 }, + { .name = "24cs16", .driver_data = (kernel_ulong_t)&at24_data_24cs16 }, + { .name = "24c32", .driver_data = (kernel_ulong_t)&at24_data_24c32 }, + { .name = "24c32d-wl", .driver_data = (kernel_ulong_t)&at24_data_24c32d_wlp }, + { .name = "24cs32", .driver_data = (kernel_ulong_t)&at24_data_24cs32 }, + { .name = "24c64", .driver_data = (kernel_ulong_t)&at24_data_24c64 }, + { .name = "24c64-wl", .driver_data = (kernel_ulong_t)&at24_data_24c64d_wlp }, + { .name = "24cs64", .driver_data = (kernel_ulong_t)&at24_data_24cs64 }, + { .name = "24c128", .driver_data = (kernel_ulong_t)&at24_data_24c128 }, + { .name = "24c256", .driver_data = (kernel_ulong_t)&at24_data_24c256 }, + { .name = "24256e-wl", .driver_data = (kernel_ulong_t)&at24_data_24256e_wlp }, + { .name = "24c512", .driver_data = (kernel_ulong_t)&at24_data_24c512 }, + { .name = "24c1024", .driver_data = (kernel_ulong_t)&at24_data_24c1024 }, + { .name = "24c1025", .driver_data = (kernel_ulong_t)&at24_data_24c1025 }, + { .name = "24c2048", .driver_data = (kernel_ulong_t)&at24_data_24c2048 }, + { .name = "at24", .driver_data = 0 }, { /* END OF LIST */ } }; MODULE_DEVICE_TABLE(i2c, at24_ids); diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 2281d6dacc9b..ca2e8442026e 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -6,6 +6,7 @@ menuconfig CHROME_PLATFORMS bool "Platform support for Chrome hardware" depends on X86 || ARM || ARM64 || COMPILE_TEST + depends on !CPU_BIG_ENDIAN || COMPILE_TEST help Say Y here to get to see options for platform support for various Chromebooks and Chromeboxes. This option alone does @@ -75,7 +76,6 @@ config CHROMEOS_OF_HW_PROBER config CROS_EC tristate "ChromeOS Embedded Controller" select CROS_EC_PROTO - depends on X86 || ARM || ARM64 || COMPILE_TEST help If you say Y here you get support for the ChromeOS Embedded Controller (EC) providing keyboard, battery and power services. diff --git a/drivers/platform/chrome/chromeos_privacy_screen.c b/drivers/platform/chrome/chromeos_privacy_screen.c index abc5d189a389..407b04207de2 100644 --- a/drivers/platform/chrome/chromeos_privacy_screen.c +++ b/drivers/platform/chrome/chromeos_privacy_screen.c @@ -104,6 +104,9 @@ static const struct drm_privacy_screen_ops chromeos_privacy_screen_ops = { static int chromeos_privacy_screen_probe(struct platform_device *pdev) { + if (!ACPI_COMPANION(&pdev->dev)) + return -ENODEV; + struct drm_privacy_screen *drm_privacy_screen = drm_privacy_screen_register(&pdev->dev, &chromeos_privacy_screen_ops, diff --git a/drivers/platform/chrome/chromeos_tbmc.c b/drivers/platform/chrome/chromeos_tbmc.c index 5133806b2d95..fd756761a481 100644 --- a/drivers/platform/chrome/chromeos_tbmc.c +++ b/drivers/platform/chrome/chromeos_tbmc.c @@ -69,9 +69,13 @@ static int chromeos_tbmc_probe(struct platform_device *pdev) { struct input_dev *idev; struct device *dev = &pdev->dev; - struct acpi_device *adev = ACPI_COMPANION(dev); + struct acpi_device *adev; int ret; + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + idev = devm_input_allocate_device(dev); if (!idev) return -ENOMEM; diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c index 002be3352100..47e03014dcbe 100644 --- a/drivers/platform/chrome/cros_ec_chardev.c +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -31,14 +33,44 @@ /* Arbitrary bounded size for the event queue */ #define CROS_MAX_EVENT_LEN PAGE_SIZE -struct chardev_priv { +/* + * Platform device driver data. + */ +struct chardev_pdata { + struct miscdevice misc; + struct kref kref; + struct rw_semaphore ec_dev_sem; struct cros_ec_device *ec_dev; + u16 cmd_offset; + struct blocking_notifier_head subscribers; + struct notifier_block relay; +}; + +static void chardev_pdata_release(struct kref *kref) +{ + struct chardev_pdata *pdata = container_of(kref, typeof(*pdata), kref); + + kfree(pdata); +} + +static int cros_ec_chardev_relay_event(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_notify) +{ + struct chardev_pdata *pdata = container_of(nb, typeof(*pdata), relay); + + blocking_notifier_call_chain(&pdata->subscribers, queued_during_suspend, + _notify); + return NOTIFY_OK; +} + +struct chardev_priv { struct notifier_block notifier; wait_queue_head_t wait_event; unsigned long event_mask; struct list_head events; size_t event_len; - u16 cmd_offset; + struct chardev_pdata *pdata; }; struct ec_event { @@ -61,10 +93,10 @@ static int ec_get_version(struct chardev_priv *priv, char *str, int maxlen) if (!msg) return -ENOMEM; - msg->command = EC_CMD_GET_VERSION + priv->cmd_offset; + msg->command = EC_CMD_GET_VERSION + priv->pdata->cmd_offset; msg->insize = sizeof(*resp); - ret = cros_ec_cmd_xfer_status(priv->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(priv->pdata->ec_dev, msg); if (ret < 0) { snprintf(str, maxlen, "Unknown EC version, returned error: %d\n", @@ -92,10 +124,18 @@ static int cros_ec_chardev_mkbp_event(struct notifier_block *nb, { struct chardev_priv *priv = container_of(nb, struct chardev_priv, notifier); - struct cros_ec_device *ec_dev = priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event; - unsigned long event_bit = 1 << ec_dev->event_data.event_type; - int total_size = sizeof(*event) + ec_dev->event_size; + unsigned long event_bit; + int total_size; + + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return NOTIFY_DONE; + ec_dev = priv->pdata->ec_dev; + + event_bit = 1 << ec_dev->event_data.event_type; + total_size = sizeof(*event) + ec_dev->event_size; if (!(event_bit & priv->event_mask) || (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) @@ -157,8 +197,7 @@ out: static int cros_ec_chardev_open(struct inode *inode, struct file *filp) { struct miscdevice *mdev = filp->private_data; - struct cros_ec_dev *ec = dev_get_drvdata(mdev->parent); - struct cros_ec_device *ec_dev = ec->ec_dev; + struct chardev_pdata *pdata = container_of(mdev, typeof(*pdata), misc); struct chardev_priv *priv; int ret; @@ -166,18 +205,20 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp) if (!priv) return -ENOMEM; - priv->ec_dev = ec_dev; - priv->cmd_offset = ec->cmd_offset; + priv->pdata = pdata; + kref_get(&pdata->kref); filp->private_data = priv; INIT_LIST_HEAD(&priv->events); init_waitqueue_head(&priv->wait_event); nonseekable_open(inode, filp); priv->notifier.notifier_call = cros_ec_chardev_mkbp_event; - ret = blocking_notifier_chain_register(&ec_dev->event_notifier, + ret = blocking_notifier_chain_register(&pdata->subscribers, &priv->notifier); if (ret) { - dev_err(ec_dev->dev, "failed to register event notifier\n"); + dev_err(pdata->ec_dev->dev, + "failed to register event notifier\n"); + kref_put(&priv->pdata->kref, chardev_pdata_release); kfree(priv); } @@ -188,6 +229,10 @@ static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait) { struct chardev_priv *priv = filp->private_data; + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return EPOLLHUP; + poll_wait(filp, &priv->wait_event, wait); if (list_empty(&priv->events)) @@ -205,6 +250,10 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer, size_t count; int ret; + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return -ENODEV; + if (priv->event_mask) { /* queued MKBP event */ struct ec_event *event; @@ -251,11 +300,11 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer, static int cros_ec_chardev_release(struct inode *inode, struct file *filp) { struct chardev_priv *priv = filp->private_data; - struct cros_ec_device *ec_dev = priv->ec_dev; struct ec_event *event, *e; - blocking_notifier_chain_unregister(&ec_dev->event_notifier, + blocking_notifier_chain_unregister(&priv->pdata->subscribers, &priv->notifier); + kref_put(&priv->pdata->kref, chardev_pdata_release); list_for_each_entry_safe(event, e, &priv->events, node) { list_del(&event->node); @@ -298,8 +347,8 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_priv *priv, void __user *a goto exit; } - s_cmd->command += priv->cmd_offset; - ret = cros_ec_cmd_xfer(priv->ec_dev, s_cmd); + s_cmd->command += priv->pdata->cmd_offset; + ret = cros_ec_cmd_xfer(priv->pdata->ec_dev, s_cmd); /* Only copy data to userland if data was received. */ if (ret < 0) goto exit; @@ -313,7 +362,7 @@ exit: static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void __user *arg) { - struct cros_ec_device *ec_dev = priv->ec_dev; + struct cros_ec_device *ec_dev = priv->pdata->ec_dev; struct cros_ec_readmem s_mem = { }; long num; @@ -343,6 +392,10 @@ static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd, { struct chardev_priv *priv = filp->private_data; + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return -ENODEV; + if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) return -ENOTTY; @@ -374,28 +427,61 @@ static int cros_ec_chardev_probe(struct platform_device *pdev) { struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent); struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev); - struct miscdevice *misc; + struct chardev_pdata *pdata; + int ret; - /* Create a char device: we want to create it anew */ - misc = devm_kzalloc(&pdev->dev, sizeof(*misc), GFP_KERNEL); - if (!misc) + pdata = kzalloc_obj(*pdata); + if (!pdata) return -ENOMEM; - misc->minor = MISC_DYNAMIC_MINOR; - misc->fops = &chardev_fops; - misc->name = ec_platform->ec_name; - misc->parent = pdev->dev.parent; + platform_set_drvdata(pdev, pdata); + kref_init(&pdata->kref); + init_rwsem(&pdata->ec_dev_sem); + pdata->ec_dev = ec->ec_dev; + pdata->cmd_offset = ec->cmd_offset; + BLOCKING_INIT_NOTIFIER_HEAD(&pdata->subscribers); + pdata->relay.notifier_call = cros_ec_chardev_relay_event; + ret = blocking_notifier_chain_register(&pdata->ec_dev->event_notifier, + &pdata->relay); + if (ret) { + dev_err(&pdev->dev, "failed to register event notifier\n"); + goto err_put_pdata; + } - dev_set_drvdata(&pdev->dev, misc); + pdata->misc.minor = MISC_DYNAMIC_MINOR; + pdata->misc.fops = &chardev_fops; + pdata->misc.name = ec_platform->ec_name; + pdata->misc.parent = pdev->dev.parent; - return misc_register(misc); + ret = misc_register(&pdata->misc); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device\n"); + goto err_unregister_notifier; + } + + return 0; +err_unregister_notifier: + blocking_notifier_chain_unregister(&pdata->ec_dev->event_notifier, + &pdata->relay); +err_put_pdata: + kref_put(&pdata->kref, chardev_pdata_release); + return ret; } static void cros_ec_chardev_remove(struct platform_device *pdev) { - struct miscdevice *misc = dev_get_drvdata(&pdev->dev); + struct chardev_pdata *pdata = platform_get_drvdata(pdev); + struct cros_ec_device *ec_dev = pdata->ec_dev; - misc_deregister(misc); + /* stop new fops from being created */ + misc_deregister(&pdata->misc); + /* stop existing fops from running */ + scoped_guard(rwsem_write, &pdata->ec_dev_sem) + pdata->ec_dev = NULL; + + blocking_notifier_chain_unregister(&ec_dev->event_notifier, + &pdata->relay); + kref_put(&pdata->kref, chardev_pdata_release); } static const struct platform_device_id cros_ec_chardev_id[] = { diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrome/cros_ec_i2c.c index def1144a077e..2f46be4a2756 100644 --- a/drivers/platform/chrome/cros_ec_i2c.c +++ b/drivers/platform/chrome/cros_ec_i2c.c @@ -348,7 +348,7 @@ MODULE_DEVICE_TABLE(of, cros_ec_i2c_of_match); #endif static const struct i2c_device_id cros_ec_i2c_id[] = { - { "cros-ec-i2c" }, + { .name = "cros-ec-i2c" }, { } }; MODULE_DEVICE_TABLE(i2c, cros_ec_i2c_id); diff --git a/drivers/platform/chrome/cros_ec_sensorhub.c b/drivers/platform/chrome/cros_ec_sensorhub.c index 9bad8f72680e..f938c3fc84e4 100644 --- a/drivers/platform/chrome/cros_ec_sensorhub.c +++ b/drivers/platform/chrome/cros_ec_sensorhub.c @@ -122,8 +122,12 @@ static int cros_ec_sensorhub_register(struct device *dev, sensor_type[sensorhub->resp->info.type]++; } - if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) + if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) { ec->has_kb_wake_angle = true; + if (ec->group && sysfs_update_group(&ec->class_dev.kobj, + ec->group)) + dev_warn(dev, "Unable to update sysfs"); + } if (cros_ec_check_features(ec, EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) { diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index f22e9523da3e..9d3767ab1548 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -405,7 +405,8 @@ static int cros_ec_sysfs_probe(struct platform_device *pd) struct device *dev = &pd->dev; int ret; - ret = sysfs_create_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group); + ec_dev->group = &cros_ec_attr_group; + ret = sysfs_create_group(&ec_dev->class_dev.kobj, ec_dev->group); if (ret < 0) dev_err(dev, "failed to create attributes. err=%d\n", ret); @@ -416,7 +417,7 @@ static void cros_ec_sysfs_remove(struct platform_device *pd) { struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); - sysfs_remove_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group); + sysfs_remove_group(&ec_dev->class_dev.kobj, ec_dev->group); } static const struct platform_device_id cros_ec_sysfs_id[] = { diff --git a/drivers/platform/chrome/cros_hps_i2c.c b/drivers/platform/chrome/cros_hps_i2c.c index ac6498c593e3..3b9485627831 100644 --- a/drivers/platform/chrome/cros_hps_i2c.c +++ b/drivers/platform/chrome/cros_hps_i2c.c @@ -131,7 +131,7 @@ static int hps_resume(struct device *dev) static DEFINE_RUNTIME_DEV_PM_OPS(hps_pm_ops, hps_suspend, hps_resume, NULL); static const struct i2c_device_id hps_i2c_id[] = { - { "cros-hps" }, + { .name = "cros-hps" }, { } }; MODULE_DEVICE_TABLE(i2c, hps_i2c_id); diff --git a/drivers/platform/chrome/cros_kbd_led_backlight.c b/drivers/platform/chrome/cros_kbd_led_backlight.c index f4c2282129f5..80dc52833dc9 100644 --- a/drivers/platform/chrome/cros_kbd_led_backlight.c +++ b/drivers/platform/chrome/cros_kbd_led_backlight.c @@ -32,12 +32,11 @@ struct keyboard_led { * @brightness_set_blocking: Set LED brightness level. It can block the * caller for the time required for accessing a * LED device register - * @max_brightness: Maximum brightness. * * See struct led_classdev in include/linux/leds.h for more details. */ struct keyboard_led_drvdata { - int (*init)(struct platform_device *pdev); + int (*init)(struct platform_device *pdev, struct keyboard_led *keyboard_led); enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); @@ -45,12 +44,8 @@ struct keyboard_led_drvdata { enum led_brightness brightness); int (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness); - - enum led_brightness max_brightness; }; -#define KEYBOARD_BACKLIGHT_MAX 100 - #ifdef CONFIG_ACPI /* Keyboard LED ACPI Device must be defined in firmware */ @@ -94,7 +89,8 @@ keyboard_led_get_brightness_acpi(struct led_classdev *cdev) return brightness; } -static int keyboard_led_init_acpi(struct platform_device *pdev) +static int keyboard_led_init_acpi(struct platform_device *pdev, + struct keyboard_led *keyboard_led) { acpi_handle handle; acpi_status status; @@ -116,17 +112,15 @@ static const struct keyboard_led_drvdata keyboard_led_drvdata_acpi = { .init = keyboard_led_init_acpi, .brightness_set = keyboard_led_set_brightness_acpi, .brightness_get = keyboard_led_get_brightness_acpi, - .max_brightness = KEYBOARD_BACKLIGHT_MAX, }; #endif /* CONFIG_ACPI */ -#if IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) -static int keyboard_led_init_ec_pwm_mfd(struct platform_device *pdev) +static int keyboard_led_init_ec_pwm_mfd(struct platform_device *pdev, + struct keyboard_led *keyboard_led) { struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); struct cros_ec_device *cros_ec = ec_dev->ec_dev; - struct keyboard_led *keyboard_led = platform_get_drvdata(pdev); keyboard_led->ec = cros_ec; @@ -175,15 +169,8 @@ static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd = { .init = keyboard_led_init_ec_pwm_mfd, .brightness_set_blocking = keyboard_led_set_brightness_ec_pwm, .brightness_get = keyboard_led_get_brightness_ec_pwm, - .max_brightness = KEYBOARD_BACKLIGHT_MAX, }; -#else /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */ - -static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd = {}; - -#endif /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */ - static int keyboard_led_is_mfd_device(struct platform_device *pdev) { return IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) && mfd_get_cell(pdev); @@ -205,17 +192,16 @@ static int keyboard_led_probe(struct platform_device *pdev) keyboard_led = devm_kzalloc(&pdev->dev, sizeof(*keyboard_led), GFP_KERNEL); if (!keyboard_led) return -ENOMEM; - platform_set_drvdata(pdev, keyboard_led); if (drvdata->init) { - err = drvdata->init(pdev); + err = drvdata->init(pdev, keyboard_led); if (err) return err; } keyboard_led->cdev.name = "chromeos::kbd_backlight"; keyboard_led->cdev.flags |= LED_CORE_SUSPENDRESUME | LED_REJECT_NAME_CONFLICT; - keyboard_led->cdev.max_brightness = drvdata->max_brightness; + keyboard_led->cdev.max_brightness = 100; keyboard_led->cdev.brightness_set = drvdata->brightness_set; keyboard_led->cdev.brightness_set_blocking = drvdata->brightness_set_blocking; keyboard_led->cdev.brightness_get = drvdata->brightness_get; diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c index b6e935badc0e..1b5cb89839e0 100644 --- a/drivers/platform/chrome/wilco_ec/event.c +++ b/drivers/platform/chrome/wilco_ec/event.c @@ -452,8 +452,13 @@ static void hangup_device(struct event_device_data *dev_data) static int event_device_probe(struct platform_device *pdev) { struct event_device_data *dev_data; + struct acpi_device *adev; int error, minor; + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL); if (minor < 0) { error = minor; @@ -494,8 +499,7 @@ static int event_device_probe(struct platform_device *pdev) goto free_dev_data; /* Install an ACPI notify handler. */ - error = acpi_dev_install_notify_handler(ACPI_COMPANION(&pdev->dev), - ACPI_DEVICE_NOTIFY, + error = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, event_device_notify, &pdev->dev); if (error) goto free_cdev; diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 4dff71be11b6..89694092981f 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -965,6 +965,29 @@ int pwrseq_power_off(struct pwrseq_desc *desc) } EXPORT_SYMBOL_GPL(pwrseq_power_off); +/** + * pwrseq_to_device() - Get the pwrseq device pointer from a descriptor. + * @desc: Descriptor referencing the power sequencer. + * + * Return the 'dev' pointer of the power sequencer device associated with @desc. + * Consumer drivers can use this to query the pwrseq provider's device tree + * node, for example to check for the existence of specific properties. + * + * Since pwrseq_get() already takes a reference to the pwrseq device, this + * function does not take an additional reference. + * + * Returns: + * Pointer to the pwrseq struct device, or NULL if @desc is NULL. + */ +struct device *pwrseq_to_device(struct pwrseq_desc *desc) +{ + if (!desc) + return NULL; + + return &desc->pwrseq->dev; +} +EXPORT_SYMBOL_GPL(pwrseq_to_device); + #if IS_ENABLED(CONFIG_DEBUG_FS) struct pwrseq_debugfs_count_ctx { @@ -1043,7 +1066,7 @@ static int pwrseq_debugfs_seq_show(struct seq_file *seq, void *data) struct pwrseq_target *target; struct pwrseq_unit *unit; - seq_printf(seq, "%s:\n", dev_name(dev)); + seq_printf(seq, "%s (%s):\n", dev_name(dev), dev_name(dev->parent)); seq_puts(seq, " targets:\n"); list_for_each_entry(target, &pwrseq->targets, list) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index ef69ae268059..94c3f4b7ee36 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,13 @@ #include #include +struct pwrseq_pci_dev { + struct serdev_device *serdev; + struct of_changeset *ocs; + struct pci_dev *pdev; + struct list_head list; +}; + struct pwrseq_pcie_m2_pdata { const struct pwrseq_target_data **targets; }; @@ -32,9 +40,9 @@ struct pwrseq_pcie_m2_ctx { struct notifier_block nb; struct gpio_desc *w_disable1_gpio; struct gpio_desc *w_disable2_gpio; - struct serdev_device *serdev; - struct of_changeset *ocs; struct device *dev; + struct list_head pci_devices; + struct mutex list_lock; }; static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq) @@ -177,39 +185,57 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, return PWRSEQ_NO_MATCH; } -static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, - struct device_node *parent) +static const struct pci_device_id pwrseq_m2_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107), + .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, + { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1103), + .driver_data = (kernel_ulong_t)"qcom,wcn6855-bt" }, + { } /* Sentinel */ +}; + +static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, + struct pwrseq_pci_dev *pci_dev, + struct device_node *parent, + struct pci_dev *pdev) { + const struct pci_device_id *id; struct device *dev = ctx->dev; + const char *compatible; struct device_node *np; int ret; - ctx->ocs = kzalloc_obj(*ctx->ocs); - if (!ctx->ocs) + id = pci_match_id(pwrseq_m2_pci_ids, pdev); + if (WARN_ON_ONCE(!id)) /* Shouldn't happen */ + return -ENODEV; + + compatible = (const char *)id->driver_data; + + pci_dev->ocs = kzalloc_obj(*pci_dev->ocs); + if (!pci_dev->ocs) return -ENOMEM; - of_changeset_init(ctx->ocs); + of_changeset_init(pci_dev->ocs); - np = of_changeset_create_node(ctx->ocs, parent, "bluetooth"); + np = of_changeset_create_node(pci_dev->ocs, parent, "bluetooth"); if (!np) { dev_err(dev, "Failed to create bluetooth node\n"); ret = -ENODEV; goto err_destroy_changeset; } - ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt"); + ret = of_changeset_add_prop_string(pci_dev->ocs, np, "compatible", compatible); if (ret) { dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret); goto err_destroy_changeset; } - ret = of_changeset_apply(ctx->ocs); + ret = of_changeset_apply(pci_dev->ocs); if (ret) { dev_err(dev, "Failed to apply changeset: %d\n", ret); goto err_destroy_changeset; } - ret = device_add_of_node(&ctx->serdev->dev, np); + ret = device_add_of_node(&pci_dev->serdev->dev, np); if (ret) { dev_err(dev, "Failed to add OF node: %d\n", ret); goto err_revert_changeset; @@ -218,19 +244,21 @@ static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, return 0; err_revert_changeset: - of_changeset_revert(ctx->ocs); + of_changeset_revert(pci_dev->ocs); err_destroy_changeset: - of_changeset_destroy(ctx->ocs); - kfree(ctx->ocs); - ctx->ocs = NULL; + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + pci_dev->ocs = NULL; return ret; } -static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +static int pwrseq_pcie_m2_create_serdev_one(struct pwrseq_pcie_m2_ctx *ctx, + struct pci_dev *pdev) { struct serdev_controller *serdev_ctrl; struct device *dev = ctx->dev; + struct pwrseq_pci_dev *pci_dev; int ret; struct device_node *serdev_parent __free(device_node) = @@ -248,58 +276,100 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) return 0; } - ctx->serdev = serdev_device_alloc(serdev_ctrl); - if (!ctx->serdev) { + /* Bail out if the serdev device was already created for the PCI dev */ + scoped_guard(mutex, &ctx->list_lock) { + list_for_each_entry(pci_dev, &ctx->pci_devices, list) { + if (pci_dev->pdev == pdev) + return 0; + } + } + + pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); + if (!pci_dev) { ret = -ENOMEM; goto err_put_ctrl; } - ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent); + pci_dev->serdev = serdev_device_alloc(serdev_ctrl); + if (!pci_dev->serdev) { + ret = -ENOMEM; + goto err_free_pci_dev; + } + + ret = pwrseq_pcie_m2_create_bt_node(ctx, pci_dev, serdev_parent, pdev); if (ret) goto err_free_serdev; - ret = serdev_device_add(ctx->serdev); + ret = serdev_device_add(pci_dev->serdev); if (ret) { - dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret); + dev_err(dev, "Failed to add serdev for PCI device (%s): %d\n", + pci_name(pdev), ret); goto err_free_dt_node; } serdev_controller_put(serdev_ctrl); + pci_dev->pdev = pci_dev_get(pdev); + + mutex_lock(&ctx->list_lock); + list_add_tail(&pci_dev->list, &ctx->pci_devices); + mutex_unlock(&ctx->list_lock); + return 0; err_free_dt_node: - device_remove_of_node(&ctx->serdev->dev); - of_changeset_revert(ctx->ocs); - of_changeset_destroy(ctx->ocs); - kfree(ctx->ocs); - ctx->ocs = NULL; + device_remove_of_node(&pci_dev->serdev->dev); + of_changeset_revert(pci_dev->ocs); + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + pci_dev->ocs = NULL; err_free_serdev: - serdev_device_put(ctx->serdev); - ctx->serdev = NULL; + serdev_device_put(pci_dev->serdev); + pci_dev->serdev = NULL; +err_free_pci_dev: + kfree(pci_dev); err_put_ctrl: serdev_controller_put(serdev_ctrl); return ret; } -static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx) +static void __pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pwrseq_pci_dev *pci_dev) { - if (ctx->serdev) { - device_remove_of_node(&ctx->serdev->dev); - serdev_device_remove(ctx->serdev); - ctx->serdev = NULL; + if (pci_dev->serdev) { + device_remove_of_node(&pci_dev->serdev->dev); + serdev_device_remove(pci_dev->serdev); } - if (ctx->ocs) { - of_changeset_revert(ctx->ocs); - of_changeset_destroy(ctx->ocs); - kfree(ctx->ocs); - ctx->ocs = NULL; + if (pci_dev->ocs) { + of_changeset_revert(pci_dev->ocs); + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); } + + pci_dev_put(pci_dev->pdev); + list_del(&pci_dev->list); + kfree(pci_dev); } -static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action, +static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pci_dev *pdev) +{ + struct pwrseq_pci_dev *pci_dev, *tmp; + + mutex_lock(&ctx->list_lock); + list_for_each_entry_safe(pci_dev, tmp, &ctx->pci_devices, list) { + if (!pdev || pci_dev->pdev == pdev) { + __pwrseq_pcie_m2_remove_serdev(ctx, pci_dev); + if (pdev) + break; + } + } + mutex_unlock(&ctx->list_lock); +} + +static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action, void *data) { struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb); @@ -318,17 +388,15 @@ static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action switch (action) { case BUS_NOTIFY_ADD_DEVICE: - /* Create serdev device for WCN7850 */ - if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) { - ret = pwrseq_pcie_m2_create_serdev(ctx); + if (pci_match_id(pwrseq_m2_pci_ids, pdev)) { + ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev); if (ret) return notifier_from_errno(ret); } break; case BUS_NOTIFY_REMOVED_DEVICE: - /* Destroy serdev device for WCN7850 */ - if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) - pwrseq_pcie_m2_remove_serdev(ctx); + if (pci_match_id(pwrseq_m2_pci_ids, pdev)) + pwrseq_pcie_m2_remove_serdev(ctx, pdev); break; } @@ -353,7 +421,7 @@ static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 end * protocol device needs to be created manually with the help of the notifier * of the discoverable bus like PCIe. */ -static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev) +static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx) { int ret; @@ -361,18 +429,56 @@ static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, stru * Register a PCI notifier for Key E connector that has PCIe as Port * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface. */ - if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) { - if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) { - ctx->dev = dev; - ctx->nb.notifier_call = pwrseq_m2_pcie_notify; - ret = bus_register_notifier(&pci_bus_type, &ctx->nb); - if (ret) - return dev_err_probe(dev, ret, - "Failed to register notifier for serdev\n"); + if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") || + !pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie")) + return 0; + + ctx->nb.notifier_call = pwrseq_pcie_m2_notify; + ret = bus_register_notifier(&pci_bus_type, &ctx->nb); + if (ret) + return dev_err_probe(ctx->dev, ret, + "Failed to register notifier for serdev\n"); + return 0; +} + +static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + struct pci_dev *pdev = NULL; + int ret; + + if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") || + !pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie")) + return 0; + + struct device_node *pci_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); + if (!pci_parent) + return 0; + + /* Create serdev for existing PCI devices if required */ + for_each_pci_dev(pdev) { + if (!pdev->dev.parent || pci_parent != pdev->dev.parent->of_node) + continue; + + if (!pci_match_id(pwrseq_m2_pci_ids, pdev)) + continue; + + ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev); + if (ret) { + dev_err_probe(ctx->dev, ret, + "Failed to create serdev for PCI device (%s)\n", + pci_name(pdev)); + pci_dev_put(pdev); + goto err_remove_serdev; } } return 0; + +err_remove_serdev: + pwrseq_pcie_m2_remove_serdev(ctx, NULL); + + return ret; } static int pwrseq_pcie_m2_probe(struct platform_device *pdev) @@ -432,16 +538,29 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) goto err_free_regulators; } + mutex_init(&ctx->list_lock); + INIT_LIST_HEAD(&ctx->pci_devices); + ctx->dev = dev; + + /* Create serdev for available PCI devices (if required) */ + ret = pwrseq_pcie_m2_create_serdev(ctx); + if (ret) + goto err_destroy_mutex; + /* * Register a notifier for creating protocol devices for * non-discoverable busses like UART. */ - ret = pwrseq_pcie_m2_register_notifier(ctx, dev); + ret = pwrseq_pcie_m2_register_notifier(ctx); if (ret) - goto err_free_regulators; + goto err_remove_serdev; return 0; +err_remove_serdev: + pwrseq_pcie_m2_remove_serdev(ctx, NULL); +err_destroy_mutex: + mutex_destroy(&ctx->list_lock); err_free_regulators: regulator_bulk_free(ctx->num_vregs, ctx->regs); @@ -453,7 +572,8 @@ static void pwrseq_pcie_m2_remove(struct platform_device *pdev) struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev); bus_unregister_notifier(&pci_bus_type, &ctx->nb); - pwrseq_pcie_m2_remove_serdev(ctx); + pwrseq_pcie_m2_remove_serdev(ctx, NULL); + mutex_destroy(&ctx->list_lock); regulator_bulk_free(ctx->num_vregs, ctx->regs); } diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376..e8886a9b64d9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -347,6 +347,18 @@ config PWM_INTEL_LGM To compile this driver as a module, choose M here: the module will be called pwm-intel-lgm. +config PWM_IPQ + tristate "IPQ PWM support" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAVE_CLK && HAS_IOMEM + help + Generic PWM framework driver for IPQ PWM block which supports + 4 pwm channels. Each of the these channels can be configured + independent of each other. + + To compile this driver as a module, choose M here: the module + will be called pwm-ipq. + config PWM_IQS620A tristate "Azoteq IQS620A PWM support" depends on MFD_IQS62X || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025..5630a521a7cf 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o +obj-$(CONFIG_PWM_IPQ) += pwm-ipq.o obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index 3d30aeab507e..a765ef279b51 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -443,7 +443,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) err = clk_prepare_enable(tcbpwmc->slow_clk); if (err) - goto err_disable_clk;; + goto err_disable_clk; err = clk_rate_exclusive_get(tcbpwmc->clk); if (err) diff --git a/drivers/pwm/pwm-dwc.c b/drivers/pwm/pwm-dwc.c index 86b72db58741..8ba7ef2c27d0 100644 --- a/drivers/pwm/pwm-dwc.c +++ b/drivers/pwm/pwm-dwc.c @@ -147,8 +147,8 @@ static int dwc_pwm_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(dwc_pwm_pm_ops, dwc_pwm_suspend, dwc_pwm_resume); static const struct pci_device_id dwc_pwm_id_table[] = { - { PCI_VDEVICE(INTEL, 0x4bb7), (kernel_ulong_t)&ehl_pwm_info }, - { } /* Terminating Entry */ + { PCI_VDEVICE(INTEL, 0x4bb7), .driver_data = (kernel_ulong_t)&ehl_pwm_info }, + { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE(pci, dwc_pwm_id_table); diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c index 3d34cdc4a3a5..c8b801fcb525 100644 --- a/drivers/pwm/pwm-imx27.c +++ b/drivers/pwm/pwm-imx27.c @@ -200,7 +200,7 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - unsigned long period_cycles, duty_cycles, prescale, period_us, tmp; + unsigned long period_cycles, duty_cycles, prescale, period_us; struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); unsigned long long c; unsigned long long clkrate; @@ -208,6 +208,7 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, int val; int ret; u32 cr; + u64 tmp; clkrate = clk_get_rate(imx->clks[PWM_IMX27_PER].clk); c = clkrate * state->period; @@ -249,6 +250,11 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, val = readl(imx->mmio_base + MX3_PWMPR); val = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; cr = readl(imx->mmio_base + MX3_PWMCR); + + /* + * tmp stores period in nanoseconds. Result fits in u64 since + * val <= 0xfffe and prescaler in [1, 0x1000]. + */ tmp = NSEC_PER_SEC * (u64)(val + 2) * MX3_PWMCR_PRESCALER_GET(cr); tmp = DIV_ROUND_UP_ULL(tmp, clkrate); period_us = DIV_ROUND_UP_ULL(tmp, 1000); diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c new file mode 100644 index 000000000000..c53373948136 --- /dev/null +++ b/drivers/pwm/pwm-ipq.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved. + * + * Limitations: + * - The PWM controller has no publicly available datasheet. + * - Each of the four channels is programmed via two 32-bit registers + * (REG0 and REG1 at 8-byte stride). + * - Period and duty-cycle reconfiguration is fully atomic: new divider, + * pre-divider, and high-duration values are latched by setting the + * UPDATE bit (bit 30 in REG1). The hardware applies the new settings + * at the beginning of the next period without disabling the output, + * so the currently running period is always completed. + * - On disable (clearing the ENABLE bit 31 in REG1), the hardware + * finishes the current period before stopping the output. The pin + * is then driven to the inactive (low) level. + * - Upon disabling, the hardware resets the pre-divider (PRE_DIV) and divider + * fields (PWM_DIV) in REG0 and REG1 to 0x0000 and 0x0001 respectively. + * - Only normal polarity is supported. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The frequency range supported is 1 Hz to 100 Mhz (clock rate) */ +#define IPQ_PWM_MAX_PERIOD_NS ((u64)NSEC_PER_SEC) +#define IPQ_PWM_MIN_PERIOD_NS 10 + +/* + * Two 32-bit registers for each PWM: REG0, and REG1. + * Base offset for PWM #i is at 8 * #i. + */ +#define IPQ_PWM_REG0 0 +#define IPQ_PWM_REG0_PWM_DIV GENMASK(15, 0) +#define IPQ_PWM_REG0_HI_DURATION GENMASK(31, 16) + +#define IPQ_PWM_REG1 4 +#define IPQ_PWM_REG1_PRE_DIV GENMASK(15, 0) +/* + * Enable bit is set to enable output toggling in pwm device. + * Update bit is set to trigger the change and is unset automatically + * to reflect the changed divider and high duration values in register. + */ +#define IPQ_PWM_REG1_UPDATE BIT(30) +#define IPQ_PWM_REG1_ENABLE BIT(31) + +/* + * The max value specified for each field is based on the number of bits + * in the pwm control register for that field (16-bit) + */ +#define IPQ_PWM_MAX_DIV FIELD_MAX(IPQ_PWM_REG0_PWM_DIV) + +struct ipq_pwm_chip { + void __iomem *mem; + unsigned long clk_rate; +}; + +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip); + unsigned int off = 8 * pwm->hwpwm + reg; + + return readl(ipq_chip->mem + off); +} + +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg, + unsigned int val) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip); + unsigned int off = 8 * pwm->hwpwm + reg; + + writel(val, ipq_chip->mem + off); +} + +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip); + unsigned int pre_div, pwm_div; + u64 period_ns, duty_ns; + unsigned long val = 0; + unsigned long hi_dur; + + if (!state->enabled) { + /* clear IPQ_PWM_REG1_ENABLE */ + ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, IPQ_PWM_REG1_UPDATE); + return 0; + } + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + /* + * Check the upper and lower bounds for the period as per + * hardware limits + */ + if (state->period < IPQ_PWM_MIN_PERIOD_NS) + return -ERANGE; + period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS); + duty_ns = min(state->duty_cycle, period_ns); + + /* + * Pick the maximal value for PWM_DIV that still allows a + * 100% relative duty cycle. This allows a fine grained + * selection of duty cycles. + */ + pwm_div = IPQ_PWM_MAX_DIV - 1; + + /* + * although mul_u64_u64_div_u64 returns a u64, in practice it + * won't overflow due to above constraints. Take the max period + * of 10^9 (NSEC_PER_SEC) and the pwm_div + 1 (IPQ_PWM_MAX_DIV) + * 10^9 * 10^8 + * ------------- => which fits well into a 32-bit unsigned int. + * 10^9 * 65,535 + */ + pre_div = mul_u64_u64_div_u64(period_ns, ipq_chip->clk_rate, + (u64)NSEC_PER_SEC * (pwm_div + 1)); + + if (!pre_div) + return -ERANGE; + + pre_div -= 1; + + if (pre_div > IPQ_PWM_MAX_DIV) + pre_div = IPQ_PWM_MAX_DIV; + + /* pwm duty = HI_DUR * (PRE_DIV + 1) / clk_rate */ + hi_dur = mul_u64_u64_div_u64(duty_ns, ipq_chip->clk_rate, + (u64)NSEC_PER_SEC * (pre_div + 1)); + + val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) | + FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div); + ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val); + + val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div); + ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val); + + /* PWM enable toggle needs a separate write to REG1 */ + val |= IPQ_PWM_REG1_UPDATE | IPQ_PWM_REG1_ENABLE; + ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val); + + return 0; +} + +static int ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip); + unsigned int pre_div, pwm_div, hi_dur; + u64 effective_div, hi_div; + u32 reg0, reg1; + + reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1); + state->enabled = reg1 & IPQ_PWM_REG1_ENABLE; + + if (!state->enabled) + return 0; + + reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0); + + state->polarity = PWM_POLARITY_NORMAL; + + pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0); + hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0); + pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1); + + effective_div = (u64)(pwm_div + 1) * (pre_div + 1); + + /* + * effective_div <= 0x100000000, so the multiplication doesn't overflow. + */ + state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, + ipq_chip->clk_rate); + + hi_div = hi_dur * (pre_div + 1); + state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, + ipq_chip->clk_rate); + + /* + * ensure a valid config is passed back to PWM core in case duty_cycle + * is > period (>100%) + */ + state->duty_cycle = min(state->duty_cycle, state->period); + + return 0; +} + +static const struct pwm_ops ipq_pwm_ops = { + .apply = ipq_pwm_apply, + .get_state = ipq_pwm_get_state, +}; + +static int ipq_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ipq_pwm_chip *pwm; + struct pwm_chip *chip; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, 4, sizeof(*pwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + pwm = ipq_pwm_from_chip(chip); + + pwm->mem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pwm->mem)) + return dev_err_probe(dev, PTR_ERR(pwm->mem), + "Failed to acquire resource\n"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to lock clock rate\n"); + + pwm->clk_rate = clk_get_rate(clk); + if (!pwm->clk_rate) + return dev_err_probe(dev, -EINVAL, "Failed due to clock rate being zero\n"); + + chip->ops = &ipq_pwm_ops; + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add pwm chip\n"); + + return 0; +} + +static const struct of_device_id pwm_ipq_dt_match[] = { + { .compatible = "qcom,ipq6018-pwm", }, + {} +}; +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match); + +static struct platform_driver ipq_pwm_driver = { + .driver = { + .name = "ipq-pwm", + .of_match_table = pwm_ipq_dt_match, + }, + .probe = ipq_pwm_probe, +}; + +module_platform_driver(ipq_pwm_driver); + +MODULE_DESCRIPTION("Qualcomm IPQ PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index ae25d9321d75..3a0fd6593520 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -48,15 +48,15 @@ static void pwm_lpss_remove_pci(struct pci_dev *pdev) } static const struct pci_device_id pwm_lpss_pci_ids[] = { - { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info}, - { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, - { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, - { PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_tng_info}, - { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, - { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, - { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, - { PCI_VDEVICE(INTEL, 0x31c8), (unsigned long)&pwm_lpss_bxt_info}, - { PCI_VDEVICE(INTEL, 0x5ac8), (unsigned long)&pwm_lpss_bxt_info}, + { PCI_VDEVICE(INTEL, 0x0ac8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, + { PCI_VDEVICE(INTEL, 0x0f08), .driver_data = (unsigned long)&pwm_lpss_byt_info }, + { PCI_VDEVICE(INTEL, 0x0f09), .driver_data = (unsigned long)&pwm_lpss_byt_info }, + { PCI_VDEVICE(INTEL, 0x11a5), .driver_data = (unsigned long)&pwm_lpss_tng_info }, + { PCI_VDEVICE(INTEL, 0x1ac8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, + { PCI_VDEVICE(INTEL, 0x2288), .driver_data = (unsigned long)&pwm_lpss_bsw_info }, + { PCI_VDEVICE(INTEL, 0x2289), .driver_data = (unsigned long)&pwm_lpss_bsw_info }, + { PCI_VDEVICE(INTEL, 0x31c8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, + { PCI_VDEVICE(INTEL, 0x5ac8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, { }, }; MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids); diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 9d206303404a..992137a27750 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -23,6 +23,8 @@ /* PWM registers and bits definitions */ #define PWMCON 0x00 #define PWMCON_CLKDIV GENMASK(2, 0) +#define PWMCON_CLKSEL BIT(3) +#define PWMCON_OLD_PWM_MODE BIT(15) #define PWMHDUR 0x04 #define PWMLDUR 0x08 #define PWMGDUR 0x0c @@ -38,6 +40,7 @@ struct pwm_mediatek_of_data { unsigned int num_pwms; + bool clksel_fixup; bool pwm45_fixup; u16 pwm_ck_26m_sel_reg; unsigned int chanreg_base; @@ -337,6 +340,7 @@ static int pwm_mediatek_write_waveform(struct pwm_chip *chip, if (wfhw->enable) { u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES; + u32 con_val = PWMCON_OLD_PWM_MODE | wfhw->con; if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) { /* @@ -364,7 +368,11 @@ static int pwm_mediatek_write_waveform(struct pwm_chip *chip, if (pc->soc->pwm_ck_26m_sel_reg) writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg); - pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | wfhw->con); + /* Set BIT(3) to disable clock division */ + if (pc->soc->clksel_fixup) + con_val |= PWMCON_CLKSEL; + + pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, con_val); pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, wfhw->width); pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, wfhw->thres); } else { @@ -496,6 +504,7 @@ static int pwm_mediatek_probe(struct platform_device *pdev) static const struct pwm_mediatek_of_data mt2712_pwm_data = { .num_pwms = 8, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -503,6 +512,7 @@ static const struct pwm_mediatek_of_data mt2712_pwm_data = { static const struct pwm_mediatek_of_data mt6795_pwm_data = { .num_pwms = 7, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -510,6 +520,7 @@ static const struct pwm_mediatek_of_data mt6795_pwm_data = { static const struct pwm_mediatek_of_data mt7622_pwm_data = { .num_pwms = 6, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -518,6 +529,7 @@ static const struct pwm_mediatek_of_data mt7622_pwm_data = { static const struct pwm_mediatek_of_data mt7623_pwm_data = { .num_pwms = 5, + .clksel_fixup = false, .pwm45_fixup = true, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -525,13 +537,15 @@ static const struct pwm_mediatek_of_data mt7623_pwm_data = { static const struct pwm_mediatek_of_data mt7628_pwm_data = { .num_pwms = 4, - .pwm45_fixup = true, + .clksel_fixup = true, + .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, }; static const struct pwm_mediatek_of_data mt7629_pwm_data = { .num_pwms = 1, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -539,6 +553,7 @@ static const struct pwm_mediatek_of_data mt7629_pwm_data = { static const struct pwm_mediatek_of_data mt7981_pwm_data = { .num_pwms = 3, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x80, @@ -547,6 +562,7 @@ static const struct pwm_mediatek_of_data mt7981_pwm_data = { static const struct pwm_mediatek_of_data mt7986_pwm_data = { .num_pwms = 2, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -555,6 +571,7 @@ static const struct pwm_mediatek_of_data mt7986_pwm_data = { static const struct pwm_mediatek_of_data mt7988_pwm_data = { .num_pwms = 8, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x80, .chanreg_width = 0x40, @@ -562,6 +579,7 @@ static const struct pwm_mediatek_of_data mt7988_pwm_data = { static const struct pwm_mediatek_of_data mt8183_pwm_data = { .num_pwms = 4, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -570,6 +588,7 @@ static const struct pwm_mediatek_of_data mt8183_pwm_data = { static const struct pwm_mediatek_of_data mt8365_pwm_data = { .num_pwms = 3, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -578,6 +597,7 @@ static const struct pwm_mediatek_of_data mt8365_pwm_data = { static const struct pwm_mediatek_of_data mt8516_pwm_data = { .num_pwms = 5, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -586,6 +606,7 @@ static const struct pwm_mediatek_of_data mt8516_pwm_data = { static const struct pwm_mediatek_of_data mt6991_pwm_data = { .num_pwms = 4, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL_V3, .chanreg_base = 0x100, diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 107bebec3546..a02255a64ea8 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -538,7 +538,7 @@ static int __maybe_unused pca9685_pwm_runtime_resume(struct device *dev) } static const struct i2c_device_id pca9685_id[] = { - { "pca9685" }, + { .name = "pca9685" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, pca9685_id); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index 0f5bdb0e395e..80d2fa10919f 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -161,6 +161,7 @@ static int pwm_probe(struct platform_device *pdev) const struct platform_device_id *id = platform_get_device_id(pdev); struct pwm_chip *chip; struct pxa_pwm_chip *pc; + struct clk *bus_clk; struct device *dev = &pdev->dev; struct reset_control *rst; int ret = 0; @@ -177,7 +178,12 @@ static int pwm_probe(struct platform_device *pdev) return PTR_ERR(chip); pc = to_pxa_pwm_chip(chip); - pc->clk = devm_clk_get(dev, NULL); + bus_clk = devm_clk_get_optional_enabled(dev, "bus"); + if (IS_ERR(bus_clk)) + return dev_err_probe(dev, PTR_ERR(bus_clk), "Failed to get bus clock\n"); + + /* Get named func clk if bus clock is valid */ + pc->clk = devm_clk_get(dev, bus_clk ? "func" : NULL); if (IS_ERR(pc->clk)) return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index 935257a890b0..c708e4a7ad70 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -193,22 +193,6 @@ out: return ret; } -/* - * This should be moved to lib/math/div64.c. Currently there are some changes - * pending to mul_u64_u64_div_u64. Uwe will care for that when the dust settles. - */ -static u64 stm32_pwm_mul_u64_u64_div_u64_roundup(u64 a, u64 b, u64 c) -{ - u64 res = mul_u64_u64_div_u64(a, b, c); - /* Those multiplications might overflow but it doesn't matter */ - u64 rem = a * b - c * res; - - if (rem) - res += 1; - - return res; -} - static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, const void *_wfhw, @@ -223,16 +207,15 @@ static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, u64 ccr_ns; /* The result doesn't overflow for rate >= 15259 */ - wf->period_length_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1), - NSEC_PER_SEC, rate); + wf->period_length_ns = mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1), + NSEC_PER_SEC, rate); - ccr_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr, - NSEC_PER_SEC, rate); + ccr_ns = mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr, NSEC_PER_SEC, rate); if (wfhw->ccer & TIM_CCER_CCxP(ch + 1)) { wf->duty_length_ns = - stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr), - NSEC_PER_SEC, rate); + mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr), + NSEC_PER_SEC, rate); wf->duty_offset_ns = ccr_ns; } else { diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index 48808cd80737..3e3fa51ccef9 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -67,16 +67,10 @@ fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 { ns.saturating_mul(rate_hz) / NSEC_PER_SEC_U64 } -fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 { +fn cycles_to_ns(cycles: u32, rate_hz: u64) -> u64 { const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64; - // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundup` - // once available in Rust. - let numerator = cycles - .saturating_mul(NSEC_PER_SEC_U64) - .saturating_add(rate_hz - 1); - - numerator / rate_hz + (u64::from(cycles) * NSEC_PER_SEC_U64).div_ceil(rate_hz) } /// Hardware-specific waveform representation for TH1520. @@ -192,15 +186,15 @@ impl pwm::PwmOps for Th1520PwmDriverData { return Ok(()); } - wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz); + wf.period_length_ns = cycles_to_ns(wfhw.period_cycles, rate_hz); - let duty_cycles = u64::from(wfhw.duty_cycles); + let duty_cycles = wfhw.duty_cycles; if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 { wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz); wf.duty_offset_ns = 0; } else { - let period_cycles = u64::from(wfhw.period_cycles); + let period_cycles = wfhw.period_cycles; let original_duty_cycles = period_cycles.saturating_sub(duty_cycles); // For an inverted signal, `duty_length_ns` is the high time (period - low_time). diff --git a/drivers/staging/media/max96712/Kconfig b/drivers/staging/media/max96712/Kconfig index 117fadf81bd0..93a2d583e90d 100644 --- a/drivers/staging/media/max96712/Kconfig +++ b/drivers/staging/media/max96712/Kconfig @@ -2,7 +2,6 @@ config VIDEO_MAX96712 tristate "Maxim MAX96712 Quad GMSL2 Deserializer support" depends on I2C - depends on OF_GPIO depends on VIDEO_DEV select V4L2_FWNODE select VIDEO_V4L2_SUBDEV_API diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig index df1cfaf26c65..f7691f64fbe3 100644 --- a/drivers/virt/coco/Kconfig +++ b/drivers/virt/coco/Kconfig @@ -17,5 +17,7 @@ source "drivers/virt/coco/arm-cca-guest/Kconfig" source "drivers/virt/coco/guest/Kconfig" endif +source "drivers/virt/coco/tdx-host/Kconfig" + config TSM bool diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile index cb52021912b3..b323b0ae4f82 100644 --- a/drivers/virt/coco/Makefile +++ b/drivers/virt/coco/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_EFI_SECRET) += efi_secret/ obj-$(CONFIG_ARM_PKVM_GUEST) += pkvm-guest/ obj-$(CONFIG_SEV_GUEST) += sev-guest/ obj-$(CONFIG_INTEL_TDX_GUEST) += tdx-guest/ +obj-$(CONFIG_INTEL_TDX_HOST) += tdx-host/ obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest/ obj-$(CONFIG_TSM) += tsm-core.o obj-$(CONFIG_TSM_GUEST) += guest/ diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig new file mode 100644 index 000000000000..57d0c01a4357 --- /dev/null +++ b/drivers/virt/coco/tdx-host/Kconfig @@ -0,0 +1,6 @@ +config TDX_HOST_SERVICES + tristate + depends on INTEL_TDX_HOST + select FW_LOADER + select FW_UPLOAD + default m diff --git a/drivers/virt/coco/tdx-host/Makefile b/drivers/virt/coco/tdx-host/Makefile new file mode 100644 index 000000000000..e61e749a8dff --- /dev/null +++ b/drivers/virt/coco/tdx-host/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_TDX_HOST_SERVICES) += tdx-host.o diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c new file mode 100644 index 000000000000..d48952968e86 --- /dev/null +++ b/drivers/virt/coco/tdx-host/tdx-host.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TDX host user interface driver + * + * Copyright (C) 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static const struct x86_cpu_id tdx_host_ids[] = { + X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids); + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo(); + const struct tdx_sys_info_version *ver; + int ret; + + if (!tdx_sysinfo) + return -ENXIO; + + /* + * The version number can change during an update. + * Lock out updates while printing the version. + */ + seamldr_lock_module_update(); + + ver = &tdx_sysinfo->version; + ret = sysfs_emit(buf, TDX_VERSION_FMT "\n", ver->major_version, + ver->minor_version, + ver->update_version); + seamldr_unlock_module_update(); + + return ret; +} +static DEVICE_ATTR_RO(version); + +static struct attribute *tdx_host_attrs[] = { + &dev_attr_version.attr, + NULL, +}; + +static const struct attribute_group tdx_host_group = { + .attrs = tdx_host_attrs, +}; + +static ssize_t seamldr_version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct seamldr_info info; + int ret; + + ret = seamldr_get_info(&info); + if (ret) + return ret; + + return sysfs_emit(buf, TDX_VERSION_FMT "\n", info.major_version, + info.minor_version, + info.update_version); +} + +static ssize_t num_remaining_updates_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct seamldr_info info; + int ret; + + ret = seamldr_get_info(&info); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", info.num_remaining_updates); +} + +/* + * These attributes are intended for managing TDX module updates. Reading + * them issues a slow, serialized P-SEAMLDR query, so keep them admin-only. + */ +static DEVICE_ATTR_ADMIN_RO(seamldr_version); +static DEVICE_ATTR_ADMIN_RO(num_remaining_updates); + +static struct attribute *seamldr_attrs[] = { + &dev_attr_seamldr_version.attr, + &dev_attr_num_remaining_updates.attr, + NULL, +}; + +static bool supports_runtime_update(void) +{ + const struct tdx_sys_info *sysinfo = tdx_get_sysinfo(); + + if (!sysinfo) + return false; + + if (!tdx_supports_runtime_update(sysinfo)) + return false; + + /* + * This bug makes P-SEAMLDR calls clobber the current VMCS + * which breaks KVM. Avoid P-SEAMLDR calls by hiding all + * attributes if the CPU has this bug. + */ + if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS)) + return false; + + return true; +} + +static umode_t seamldr_group_visible(struct kobject *kobj, struct attribute *attr, int idx) +{ + if (!supports_runtime_update()) + return 0; + + return attr->mode; +} + +static const struct attribute_group seamldr_group = { + .attrs = seamldr_attrs, + .is_visible = seamldr_group_visible, +}; + +static const struct attribute_group *tdx_host_groups[] = { + &tdx_host_group, + &seamldr_group, + NULL, +}; + +static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl, + const u8 *data, u32 data_len) +{ + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data, + u32 offset, u32 data_len, u32 *written) +{ + int ret; + + ret = seamldr_install_module(data, data_len); + switch (ret) { + case 0: + *written = data_len; + return FW_UPLOAD_ERR_NONE; + default: + return FW_UPLOAD_ERR_FW_INVALID; + } +} + +static enum fw_upload_err tdx_fw_poll_complete(struct fw_upload *fwl) +{ + /* + * The upload completed during tdx_fw_write(). + * Never poll for completion. + */ + return FW_UPLOAD_ERR_NONE; +} + +static void tdx_fw_cancel(struct fw_upload *fwl) +{ + /* + * TDX module updates are not cancellable. + * Provide a no-op callback to satisfy fw_upload_ops. + */ +} + +static const struct fw_upload_ops tdx_fw_ops = { + .prepare = tdx_fw_prepare, + .write = tdx_fw_write, + .poll_complete = tdx_fw_poll_complete, + .cancel = tdx_fw_cancel, +}; + +static void seamldr_deinit(void *tdx_fwl) +{ + firmware_upload_unregister(tdx_fwl); +} + +static int seamldr_init(struct device *dev) +{ + struct fw_upload *tdx_fwl; + + if (!supports_runtime_update()) + return 0; + + tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "tdx_module", + &tdx_fw_ops, NULL); + if (IS_ERR(tdx_fwl)) + return PTR_ERR(tdx_fwl); + + return devm_add_action_or_reset(dev, seamldr_deinit, tdx_fwl); +} + +static int tdx_host_probe(struct faux_device *fdev) +{ + return seamldr_init(&fdev->dev); +} + +static const struct faux_device_ops tdx_host_ops = { + .probe = tdx_host_probe, +}; + +static struct faux_device *fdev; + +static int __init tdx_host_init(void) +{ + if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo()) + return -ENODEV; + + fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL, + &tdx_host_ops, + tdx_host_groups); + if (!fdev) + return -ENODEV; + + return 0; +} +module_init(tdx_host_init); + +static void __exit tdx_host_exit(void) +{ + faux_device_destroy(fdev); +} +module_exit(tdx_host_exit); + +MODULE_DESCRIPTION("TDX Host Services"); +MODULE_LICENSE("GPL"); diff --git a/include/dt-bindings/gpio/meson-axg-gpio.h b/include/dt-bindings/gpio/meson-axg-gpio.h index 25bb1fffa97a..a0d42bcd9bd3 100644 --- a/include/dt-bindings/gpio/meson-axg-gpio.h +++ b/include/dt-bindings/gpio/meson-axg-gpio.h @@ -23,7 +23,7 @@ #define GPIOAO_11 11 #define GPIOAO_12 12 #define GPIOAO_13 13 -#define GPIO_TEST_N 14 +#define GPIO_TEST_N 14 /* Second GPIO chip */ #define GPIOZ_0 0 @@ -52,7 +52,7 @@ #define BOOT_12 23 #define BOOT_13 24 #define BOOT_14 25 -#define GPIOA_0 26 +#define GPIOA_0 26 #define GPIOA_1 27 #define GPIOA_2 28 #define GPIOA_3 29 diff --git a/include/dt-bindings/gpio/nvidia,tegra238-gpio.h b/include/dt-bindings/gpio/nvidia,tegra238-gpio.h new file mode 100644 index 000000000000..8a616a1df54c --- /dev/null +++ b/include/dt-bindings/gpio/nvidia,tegra238-gpio.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */ +/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. */ + +/* + * This header provides constants for binding nvidia,tegra238-gpio*. + * + * The first cell in Tegra's GPIO specifier is the GPIO ID. The macros below + * provide names for this. + * + * The second cell contains standard flag values specified in gpio.h. + */ + +#ifndef _DT_BINDINGS_GPIO_TEGRA238_GPIO_H +#define _DT_BINDINGS_GPIO_TEGRA238_GPIO_H + +#include + +/* GPIOs implemented by main GPIO controller */ +#define TEGRA238_MAIN_GPIO_PORT_A 0 +#define TEGRA238_MAIN_GPIO_PORT_B 1 +#define TEGRA238_MAIN_GPIO_PORT_C 2 +#define TEGRA238_MAIN_GPIO_PORT_D 3 +#define TEGRA238_MAIN_GPIO_PORT_E 4 +#define TEGRA238_MAIN_GPIO_PORT_F 5 +#define TEGRA238_MAIN_GPIO_PORT_G 6 +#define TEGRA238_MAIN_GPIO_PORT_H 7 +#define TEGRA238_MAIN_GPIO_PORT_J 8 +#define TEGRA238_MAIN_GPIO_PORT_K 9 +#define TEGRA238_MAIN_GPIO_PORT_L 10 +#define TEGRA238_MAIN_GPIO_PORT_M 11 +#define TEGRA238_MAIN_GPIO_PORT_N 12 +#define TEGRA238_MAIN_GPIO_PORT_P 13 +#define TEGRA238_MAIN_GPIO_PORT_Q 14 +#define TEGRA238_MAIN_GPIO_PORT_R 15 +#define TEGRA238_MAIN_GPIO_PORT_S 16 +#define TEGRA238_MAIN_GPIO_PORT_T 17 +#define TEGRA238_MAIN_GPIO_PORT_U 18 +#define TEGRA238_MAIN_GPIO_PORT_V 19 +#define TEGRA238_MAIN_GPIO_PORT_W 20 +#define TEGRA238_MAIN_GPIO_PORT_X 21 + +#define TEGRA238_MAIN_GPIO(port, offset) \ + ((TEGRA238_MAIN_GPIO_PORT_##port * 8) + (offset)) + +/* GPIOs implemented by AON GPIO controller */ +#define TEGRA238_AON_GPIO_PORT_AA 0 +#define TEGRA238_AON_GPIO_PORT_BB 1 +#define TEGRA238_AON_GPIO_PORT_CC 2 +#define TEGRA238_AON_GPIO_PORT_DD 3 +#define TEGRA238_AON_GPIO_PORT_EE 4 +#define TEGRA238_AON_GPIO_PORT_FF 5 +#define TEGRA238_AON_GPIO_PORT_GG 6 +#define TEGRA238_AON_GPIO_PORT_HH 7 + +#define TEGRA238_AON_GPIO(port, offset) \ + ((TEGRA238_AON_GPIO_PORT_##port * 8) + (offset)) + +#endif diff --git a/include/kunit/platform_device.h b/include/kunit/platform_device.h index f8236a8536f7..eee565d5d1d3 100644 --- a/include/kunit/platform_device.h +++ b/include/kunit/platform_device.h @@ -6,10 +6,16 @@ struct completion; struct kunit; struct platform_device; struct platform_driver; +struct platform_device_info; struct platform_device * kunit_platform_device_alloc(struct kunit *test, const char *name, int id); int kunit_platform_device_add(struct kunit *test, struct platform_device *pdev); +struct platform_device * +kunit_platform_device_register_full(struct kunit *test, + const struct platform_device_info *pdevinfo); +void kunit_platform_device_unregister(struct kunit *test, + struct platform_device *pdev); int kunit_platform_device_prepare_wait_for_probe(struct kunit *test, struct platform_device *pdev, diff --git a/include/linux/gpio.h b/include/linux/gpio.h index 8f85ddb26429..b0d4942a65de 100644 --- a/include/linux/gpio.h +++ b/include/linux/gpio.h @@ -2,13 +2,11 @@ /* * NOTE: This header *must not* be included. * - * This is the LEGACY GPIO bulk include file, including legacy APIs. It is - * used for GPIO drivers still referencing the global GPIO numberspace, - * and should not be included in new code. - * * If you're implementing a GPIO driver, only include * If you're implementing a GPIO consumer, only include + * If you're using the legacy interfaces, include */ + #ifndef __LINUX_GPIO_H #define __LINUX_GPIO_H @@ -18,159 +16,7 @@ #endif #ifdef CONFIG_GPIOLIB_LEGACY - -struct device; - -/* make these flag values available regardless of GPIO kconfig options */ -#define GPIOF_IN ((1 << 0)) -#define GPIOF_OUT_INIT_LOW ((0 << 0) | (0 << 1)) -#define GPIOF_OUT_INIT_HIGH ((0 << 0) | (1 << 1)) - -#ifdef CONFIG_GPIOLIB -/* - * "valid" GPIO numbers are nonnegative and may be passed to - * setup routines like gpio_request(). Only some valid numbers - * can successfully be requested and used. - * - * Invalid GPIO numbers are useful for indicating no-such-GPIO in - * platform data and other tables. - */ -static inline bool gpio_is_valid(int number) -{ - /* only non-negative numbers are valid */ - return number >= 0; -} - -/* - * Platforms may implement their GPIO interface with library code, - * at a small performance cost for non-inlined operations and some - * extra memory (for code and for per-GPIO table entries). - */ - -/* Always use the library code for GPIO management calls, - * or when sleeping may be involved. - */ -int gpio_request(unsigned gpio, const char *label); -void gpio_free(unsigned gpio); - -static inline int gpio_direction_input(unsigned gpio) -{ - return gpiod_direction_input(gpio_to_desc(gpio)); -} -static inline int gpio_direction_output(unsigned gpio, int value) -{ - return gpiod_direction_output_raw(gpio_to_desc(gpio), value); -} - -static inline int gpio_get_value_cansleep(unsigned gpio) -{ - return gpiod_get_raw_value_cansleep(gpio_to_desc(gpio)); -} -static inline void gpio_set_value_cansleep(unsigned gpio, int value) -{ - gpiod_set_raw_value_cansleep(gpio_to_desc(gpio), value); -} - -static inline int gpio_get_value(unsigned gpio) -{ - return gpiod_get_raw_value(gpio_to_desc(gpio)); -} -static inline void gpio_set_value(unsigned gpio, int value) -{ - gpiod_set_raw_value(gpio_to_desc(gpio), value); -} - -static inline int gpio_to_irq(unsigned gpio) -{ - return gpiod_to_irq(gpio_to_desc(gpio)); -} - -int gpio_request_one(unsigned gpio, unsigned long flags, const char *label); - -int devm_gpio_request_one(struct device *dev, unsigned gpio, - unsigned long flags, const char *label); - -#else /* ! CONFIG_GPIOLIB */ - -#include - -#include -#include - -static inline bool gpio_is_valid(int number) -{ - return false; -} - -static inline int gpio_request(unsigned gpio, const char *label) -{ - return -ENOSYS; -} - -static inline int gpio_request_one(unsigned gpio, - unsigned long flags, const char *label) -{ - return -ENOSYS; -} - -static inline void gpio_free(unsigned gpio) -{ - might_sleep(); - - /* GPIO can never have been requested */ - WARN_ON(1); -} - -static inline int gpio_direction_input(unsigned gpio) -{ - return -ENOSYS; -} - -static inline int gpio_direction_output(unsigned gpio, int value) -{ - return -ENOSYS; -} - -static inline int gpio_get_value(unsigned gpio) -{ - /* GPIO can never have been requested or set as {in,out}put */ - WARN_ON(1); - return 0; -} - -static inline void gpio_set_value(unsigned gpio, int value) -{ - /* GPIO can never have been requested or set as output */ - WARN_ON(1); -} - -static inline int gpio_get_value_cansleep(unsigned gpio) -{ - /* GPIO can never have been requested or set as {in,out}put */ - WARN_ON(1); - return 0; -} - -static inline void gpio_set_value_cansleep(unsigned gpio, int value) -{ - /* GPIO can never have been requested or set as output */ - WARN_ON(1); -} - -static inline int gpio_to_irq(unsigned gpio) -{ - /* GPIO can never have been requested or set as input */ - WARN_ON(1); - return -EINVAL; -} - -static inline int devm_gpio_request_one(struct device *dev, unsigned gpio, - unsigned long flags, const char *label) -{ - WARN_ON(1); - return -EINVAL; -} - -#endif /* ! CONFIG_GPIOLIB */ +#include #endif /* CONFIG_GPIOLIB_LEGACY */ + #endif /* __LINUX_GPIO_H */ diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 3efb5cb1e1d1..fceeefd5f893 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -111,6 +111,7 @@ void devm_gpiod_unhinge(struct device *dev, struct gpio_desc *desc); void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs); int gpiod_get_direction(struct gpio_desc *desc); +bool gpiod_is_single_ended(struct gpio_desc *desc); int gpiod_direction_input(struct gpio_desc *desc); int gpiod_direction_output(struct gpio_desc *desc, int value); int gpiod_direction_output_raw(struct gpio_desc *desc, int value); @@ -337,6 +338,10 @@ static inline int gpiod_get_direction(const struct gpio_desc *desc) WARN_ON(desc); return -ENOSYS; } +static inline bool gpiod_is_single_ended(struct gpio_desc *desc) +{ + return false; +} static inline int gpiod_direction_input(struct gpio_desc *desc) { /* GPIO can never have been requested */ @@ -598,6 +603,15 @@ static inline int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, } #endif /* CONFIG_GPIOLIB && CONFIG_HTE */ +static inline +struct gpio_desc *fwnode_gpiod_get(struct fwnode_handle *fwnode, + const char *con_id, + enum gpiod_flags flags, + const char *label) +{ + return fwnode_gpiod_get_index(fwnode, con_id, 0, flags, label); +} + static inline struct gpio_desc *devm_fwnode_gpiod_get(struct device *dev, struct fwnode_handle *fwnode, diff --git a/include/linux/gpio/gpio-nomadik.h b/include/linux/gpio/gpio-nomadik.h index 8061b9826361..44c47645649c 100644 --- a/include/linux/gpio/gpio-nomadik.h +++ b/include/linux/gpio/gpio-nomadik.h @@ -32,9 +32,6 @@ struct fwnode_handle; #define NMK_GPIO_RWIMSC 0x50 #define NMK_GPIO_FWIMSC 0x54 #define NMK_GPIO_WKS 0x58 -/* These appear in DB8540 and later ASICs */ -#define NMK_GPIO_EDGELEVEL 0x5C -#define NMK_GPIO_LEVEL 0x60 /* Pull up/down values */ enum nmk_gpio_pull { @@ -236,19 +233,6 @@ nmk_pinctrl_db8500_init(const struct nmk_pinctrl_soc_data **soc) #endif -#ifdef CONFIG_PINCTRL_DB8540 - -void nmk_pinctrl_db8540_init(const struct nmk_pinctrl_soc_data **soc); - -#else - -static inline void -nmk_pinctrl_db8540_init(const struct nmk_pinctrl_soc_data **soc) -{ -} - -#endif - struct platform_device; #ifdef CONFIG_DEBUG_FS diff --git a/include/linux/gpio/legacy.h b/include/linux/gpio/legacy.h new file mode 100644 index 000000000000..557ef635935e --- /dev/null +++ b/include/linux/gpio/legacy.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This is the LEGACY GPIO include file, used only for legacy APIs. + * + * No new code should use this, but instead use the linux/gpio/consumer.h + * interfaces directly. + */ + +#ifndef __LINUX_GPIO_LEGACY_H +#define __LINUX_GPIO_LEGACY_H + +#include + +#ifdef CONFIG_GPIOLIB_LEGACY + +struct device; + +/* make these flag values available regardless of GPIO kconfig options */ +#define GPIOF_IN ((1 << 0)) +#define GPIOF_OUT_INIT_LOW ((0 << 0) | (0 << 1)) +#define GPIOF_OUT_INIT_HIGH ((0 << 0) | (1 << 1)) + +#ifdef CONFIG_GPIOLIB + +#include + +/* + * "valid" GPIO numbers are nonnegative and may be passed to + * setup routines like gpio_request(). Only some valid numbers + * can successfully be requested and used. + * + * Invalid GPIO numbers are useful for indicating no-such-GPIO in + * platform data and other tables. + */ +static inline bool gpio_is_valid(int number) +{ + /* only non-negative numbers are valid */ + return number >= 0; +} + +/* + * Platforms may implement their GPIO interface with library code, + * at a small performance cost for non-inlined operations and some + * extra memory (for code and for per-GPIO table entries). + */ + +/* Always use the library code for GPIO management calls, + * or when sleeping may be involved. + */ +int gpio_request(unsigned gpio, const char *label); +void gpio_free(unsigned gpio); + +static inline int gpio_direction_input(unsigned gpio) +{ + return gpiod_direction_input(gpio_to_desc(gpio)); +} +static inline int gpio_direction_output(unsigned gpio, int value) +{ + return gpiod_direction_output_raw(gpio_to_desc(gpio), value); +} + +static inline int gpio_get_value_cansleep(unsigned gpio) +{ + return gpiod_get_raw_value_cansleep(gpio_to_desc(gpio)); +} +static inline void gpio_set_value_cansleep(unsigned gpio, int value) +{ + gpiod_set_raw_value_cansleep(gpio_to_desc(gpio), value); +} + +static inline int gpio_get_value(unsigned gpio) +{ + return gpiod_get_raw_value(gpio_to_desc(gpio)); +} +static inline void gpio_set_value(unsigned gpio, int value) +{ + gpiod_set_raw_value(gpio_to_desc(gpio), value); +} + +static inline int gpio_to_irq(unsigned gpio) +{ + return gpiod_to_irq(gpio_to_desc(gpio)); +} + +int gpio_request_one(unsigned gpio, unsigned long flags, const char *label); + +int devm_gpio_request_one(struct device *dev, unsigned gpio, + unsigned long flags, const char *label); + +#else /* ! CONFIG_GPIOLIB */ + +#include + +#include +#include + +static inline bool gpio_is_valid(int number) +{ + return false; +} + +static inline int gpio_request(unsigned gpio, const char *label) +{ + return -ENOSYS; +} + +static inline int gpio_request_one(unsigned gpio, + unsigned long flags, const char *label) +{ + return -ENOSYS; +} + +static inline void gpio_free(unsigned gpio) +{ + might_sleep(); + + /* GPIO can never have been requested */ + WARN_ON(1); +} + +static inline int gpio_direction_input(unsigned gpio) +{ + return -ENOSYS; +} + +static inline int gpio_direction_output(unsigned gpio, int value) +{ + return -ENOSYS; +} + +static inline int gpio_get_value(unsigned gpio) +{ + /* GPIO can never have been requested or set as {in,out}put */ + WARN_ON(1); + return 0; +} + +static inline void gpio_set_value(unsigned gpio, int value) +{ + /* GPIO can never have been requested or set as output */ + WARN_ON(1); +} + +static inline int gpio_get_value_cansleep(unsigned gpio) +{ + /* GPIO can never have been requested or set as {in,out}put */ + WARN_ON(1); + return 0; +} + +static inline void gpio_set_value_cansleep(unsigned gpio, int value) +{ + /* GPIO can never have been requested or set as output */ + WARN_ON(1); +} + +static inline int gpio_to_irq(unsigned gpio) +{ + /* GPIO can never have been requested or set as input */ + WARN_ON(1); + return -EINVAL; +} + +static inline int devm_gpio_request_one(struct device *dev, unsigned gpio, + unsigned long flags, const char *label) +{ + WARN_ON(1); + return -EINVAL; +} + +#endif /* ! CONFIG_GPIOLIB */ +#endif /* CONFIG_GPIOLIB_LEGACY */ +#endif /* __LINUX_GPIO_LEGAGY_H */ diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h index 12d154732ca9..06255756710d 100644 --- a/include/linux/gpio/regmap.h +++ b/include/linux/gpio/regmap.h @@ -38,6 +38,12 @@ struct regmap; * offset to a register/bitmask pair. If not * given the default gpio_regmap_simple_xlate() * is used. + * @fixed_direction_mask: + * (Optional) Bitmap representing the GPIO lines that + * make use of the @fixed_direction_output list to + * enforce direction of the GPIO. If this is NULL + * and @fixed_direction_output is defined, ALL GPIOs + * are assumed to be fixed direction (out or in). * @fixed_direction_output: * (Optional) Bitmap representing the fixed direction of * the GPIO lines. Useful when there are GPIO lines with a @@ -89,6 +95,7 @@ struct gpio_regmap_config { int reg_stride; int ngpio_per_reg; struct irq_domain *irq_domain; + unsigned long *fixed_direction_mask; unsigned long *fixed_direction_output; #ifdef CONFIG_REGMAP_IRQ diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/platform_data/cros_ec_proto.h index de14923720a5..6ed1c4c5ce2e 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -228,6 +228,7 @@ struct cros_ec_platform { /** * struct cros_ec_dev - ChromeOS EC device entry point. * @class_dev: Device structure used in sysfs. + * @group: sysfs attributes groups for this EC. * @ec_dev: cros_ec_device structure to talk to the physical device. * @dev: Pointer to the platform device. * @debug_info: cros_ec_debugfs structure for debugging information. @@ -237,6 +238,7 @@ struct cros_ec_platform { */ struct cros_ec_dev { struct device class_dev; + const struct attribute_group *group; struct cros_ec_device *ec_dev; struct device *dev; struct cros_ec_debugfs *debug_info; diff --git a/include/linux/pwrseq/consumer.h b/include/linux/pwrseq/consumer.h index 7d583b4f266e..3c907c9e1885 100644 --- a/include/linux/pwrseq/consumer.h +++ b/include/linux/pwrseq/consumer.h @@ -23,6 +23,8 @@ devm_pwrseq_get(struct device *dev, const char *target); int pwrseq_power_on(struct pwrseq_desc *desc); int pwrseq_power_off(struct pwrseq_desc *desc); +struct device *pwrseq_to_device(struct pwrseq_desc *desc); + #else /* CONFIG_POWER_SEQUENCING */ static inline struct pwrseq_desc * __must_check @@ -51,6 +53,11 @@ static inline int pwrseq_power_off(struct pwrseq_desc *desc) return -ENOSYS; } +static inline struct device *pwrseq_to_device(struct pwrseq_desc *desc) +{ + return NULL; +} + #endif /* CONFIG_POWER_SEQUENCING */ #endif /* __POWER_SEQUENCING_CONSUMER_H__ */ diff --git a/include/linux/timb_gpio.h b/include/linux/timb_gpio.h deleted file mode 100644 index 74f5e73bf6db..000000000000 --- a/include/linux/timb_gpio.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * timb_gpio.h timberdale FPGA GPIO driver, platform data definition - * Copyright (c) 2009 Intel Corporation - */ - -#ifndef _LINUX_TIMB_GPIO_H -#define _LINUX_TIMB_GPIO_H - -/** - * struct timbgpio_platform_data - Platform data of the Timberdale GPIO driver - * @gpio_base: The number of the first GPIO pin, set to -1 for - * dynamic number allocation. - * @nr_pins: Number of pins that is supported by the hardware (1-32) - * @irq_base: If IRQ is supported by the hardware, this is the base - * number of IRQ:s. One IRQ per pin will be used. Set to - * -1 if IRQ:s is not supported. - */ -struct timbgpio_platform_data { - int gpio_base; - int nr_pins; - int irq_base; -}; - -#endif diff --git a/lib/kunit/platform.c b/lib/kunit/platform.c index 0b518de26065..737758d710b2 100644 --- a/lib/kunit/platform.c +++ b/lib/kunit/platform.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -130,6 +131,69 @@ int kunit_platform_device_add(struct kunit *test, struct platform_device *pdev) } EXPORT_SYMBOL_GPL(kunit_platform_device_add); +/** + * kunit_platform_device_register_full() - Register a KUnit test-managed platform + * device described by platform device info + * @test: test context + * @pdevinfo: platform device information describing the new device + * + * Register a test-managed platform device. The device is unregistered when the + * test completes. + * + * Return: New platform device on success, IS_ERR() on error. + */ +struct platform_device * +kunit_platform_device_register_full(struct kunit *test, + const struct platform_device_info *pdevinfo) +{ + struct platform_device *pdev; + int ret; + + pdev = platform_device_register_full(pdevinfo); + if (IS_ERR(pdev)) + return pdev; + + ret = kunit_add_action_or_reset(test, platform_device_unregister_wrapper, pdev); + if (ret) + return ERR_PTR(ret); + + return pdev; +} +EXPORT_SYMBOL_GPL(kunit_platform_device_register_full); + +static bool +kunit_platform_device_add_match(struct kunit *test, struct kunit_resource *res, + void *match_data) +{ + struct platform_device *pdev = match_data; + + return res->data == pdev && res->free == kunit_platform_device_add_exit; +} + +/** + * kunit_platform_device_unregister() - Unregister a KUnit-managed platform device + * @test: test context + * @pdev: platform device to unregister + * + * Unregister a test-managed platform device and cancel its release action. + */ +void kunit_platform_device_unregister(struct kunit *test, + struct platform_device *pdev) +{ + struct kunit_resource *res; + + res = kunit_find_resource(test, kunit_platform_device_add_match, pdev); + if (res) { + res->free = NULL; + kunit_put_resource(res); + } else { + kunit_remove_action(test, platform_device_unregister_wrapper, pdev); + } + + platform_device_unregister(pdev); +} +EXPORT_SYMBOL_GPL(kunit_platform_device_unregister); + struct kunit_platform_device_probe_nb { struct completion *x; struct device *dev; diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c index 2fc234adca5f..d15fda648dad 100644 --- a/sound/soc/codecs/wsa881x.c +++ b/sound/soc/codecs/wsa881x.c @@ -3,7 +3,6 @@ // Copyright (c) 2019, Linaro Limited #include -#include #include #include #include @@ -672,11 +671,6 @@ struct wsa881x_priv { struct sdw_stream_runtime *sruntime; struct sdw_port_config port_config[WSA881X_MAX_SWR_PORTS]; struct gpio_desc *sd_n; - /* - * Logical state for SD_N GPIO: high for shutdown, low for enable. - * For backwards compatibility. - */ - unsigned int sd_n_val; int active_ports; bool hw_init; bool port_prepared[WSA881X_MAX_SWR_PORTS]; @@ -1121,31 +1115,11 @@ static int wsa881x_probe(struct sdw_slave *pdev, if (!wsa881x) return -ENOMEM; - wsa881x->sd_n = devm_gpiod_get_optional(dev, "powerdown", 0); + wsa881x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_LOW); if (IS_ERR(wsa881x->sd_n)) return dev_err_probe(dev, PTR_ERR(wsa881x->sd_n), "Shutdown Control GPIO not found\n"); - /* - * Backwards compatibility work-around. - * - * The SD_N GPIO is active low, however upstream DTS used always active - * high. Changing the flag in driver and DTS will break backwards - * compatibility, so add a simple value inversion to work with both old - * and new DTS. - * - * This won't work properly with DTS using the flags properly in cases: - * 1. Old DTS with proper ACTIVE_LOW, however such case was broken - * before as the driver required the active high. - * 2. New DTS with proper ACTIVE_HIGH (intended), which is rare case - * (not existing upstream) but possible. This is the price of - * backwards compatibility, therefore this hack should be removed at - * some point. - */ - wsa881x->sd_n_val = gpiod_is_active_low(wsa881x->sd_n); - if (!wsa881x->sd_n_val) - dev_warn(dev, "Using ACTIVE_HIGH for shutdown GPIO. Your DTB might be outdated or you use unsupported configuration for the GPIO."); - dev_set_drvdata(dev, wsa881x); wsa881x->slave = pdev; wsa881x->dev = dev; @@ -1158,7 +1132,6 @@ static int wsa881x_probe(struct sdw_slave *pdev, pdev->prop.sink_dpn_prop = wsa_sink_dpn_prop; pdev->prop.scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; pdev->prop.clk_stop_mode1 = true; - gpiod_direction_output(wsa881x->sd_n, !wsa881x->sd_n_val); wsa881x->regmap = devm_regmap_init_sdw(pdev, &wsa881x_regmap_config); if (IS_ERR(wsa881x->regmap)) @@ -1181,7 +1154,7 @@ static int wsa881x_runtime_suspend(struct device *dev) struct regmap *regmap = dev_get_regmap(dev, NULL); struct wsa881x_priv *wsa881x = dev_get_drvdata(dev); - gpiod_direction_output(wsa881x->sd_n, wsa881x->sd_n_val); + gpiod_direction_output(wsa881x->sd_n, 1); regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); @@ -1196,13 +1169,13 @@ static int wsa881x_runtime_resume(struct device *dev) struct wsa881x_priv *wsa881x = dev_get_drvdata(dev); unsigned long time; - gpiod_direction_output(wsa881x->sd_n, !wsa881x->sd_n_val); + gpiod_direction_output(wsa881x->sd_n, 0); time = wait_for_completion_timeout(&slave->initialization_complete, msecs_to_jiffies(WSA881X_PROBE_TIMEOUT)); if (!time) { dev_err(dev, "Initialization not complete, timed out\n"); - gpiod_direction_output(wsa881x->sd_n, wsa881x->sd_n_val); + gpiod_direction_output(wsa881x->sd_n, 1); return -ETIMEDOUT; }