diff options
172 files changed, 24344 insertions, 616 deletions
diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst index 5e89497ba314..22c33398ec02 100644 --- a/Documentation/core-api/printk-formats.rst +++ b/Documentation/core-api/printk-formats.rst @@ -625,6 +625,38 @@ Examples:: %p4cc Y10 little-endian (0x20303159) %p4cc NV12 big-endian (0xb231564e) +Generic FourCC code +------------------- + +:: + %p4c[hnbl] gP00 (0x67503030) + +Print a generic FourCC code, as both ASCII characters and its numerical +value as hexadecimal. + +The additional ``h``, ``r``, ``b``, and ``l`` specifiers are used to specify +host, reversed, big or little endian order data respectively. Host endian +order means the data is interpreted as a 32-bit integer and the most +significant byte is printed first; that is, the character code as printed +matches the byte order stored in memory on big-endian systems, and is reversed +on little-endian systems. + +Passed by reference. + +Examples for a little-endian machine, given &(u32)0x67503030:: + + %p4ch gP00 (0x67503030) + %p4cl gP00 (0x67503030) + %p4cb 00Pg (0x30305067) + %p4cr 00Pg (0x30305067) + +Examples for a big-endian machine, given &(u32)0x67503030:: + + %p4ch gP00 (0x67503030) + %p4cl 00Pg (0x30305067) + %p4cb gP00 (0x67503030) + %p4cr 00Pg (0x30305067) + Thanks ====== diff --git a/Documentation/devicetree/bindings/cpufreq/apple,soc-cpufreq.yaml b/Documentation/devicetree/bindings/cpufreq/apple,soc-cpufreq.yaml new file mode 100644 index 000000000000..f398c1bd5de5 --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/apple,soc-cpufreq.yaml @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/cpufreq/apple,soc-cpufreq.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SoC cpufreq device + +maintainers: + - Hector Martin <marcan@marcan.st> + +description: | + Apple SoCs (e.g. M1) have a per-cpu-cluster DVFS controller that is part of + the cluster management register block. This binding uses the standard + operating-points-v2 table to define the CPU performance states, with the + opp-level property specifying the hardware p-state index for that level. + +properties: + compatible: + items: + - enum: + - apple,t8103-soc-cpufreq + - apple,t6000-soc-cpufreq + - const: apple,soc-cpufreq + + reg: + minItems: 1 + maxItems: 6 + description: One register region per CPU cluster DVFS controller + + reg-names: + minItems: 1 + items: + - const: cluster0 + - const: cluster1 + - const: cluster2 + - const: cluster3 + - const: cluster4 + - const: cluster5 + + '#freq-domain-cells': + const: 1 + +required: + - compatible + - reg + - reg-names + - '#freq-domain-cells' + +additionalProperties: false + +examples: + - | + // This example shows a single CPU per domain and 2 domains, + // with two p-states per domain. + // Shipping hardware has 2-4 CPUs per domain and 2-6 domains. + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + compatible = "apple,icestorm"; + device_type = "cpu"; + reg = <0x0 0x0>; + operating-points-v2 = <&ecluster_opp>; + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu@10100 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10100>; + operating-points-v2 = <&pcluster_opp>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + }; + + ecluster_opp: opp-table-0 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <7500>; + }; + opp02 { + opp-hz = /bits/ 64 <972000000>; + opp-level = <2>; + clock-latency-ns = <22000>; + }; + }; + + pcluster_opp: opp-table-1 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <8000>; + }; + opp02 { + opp-hz = /bits/ 64 <828000000>; + opp-level = <2>; + clock-latency-ns = <19000>; + }; + }; + + soc { + #address-cells = <2>; + #size-cells = <2>; + + cpufreq_hw: cpufreq@210e20000 { + compatible = "apple,t8103-soc-cpufreq", "apple,soc-cpufreq"; + reg = <0x2 0x10e20000 0 0x1000>, + <0x2 0x11e20000 0 0x1000>; + reg-names = "cluster0", "cluster1"; + #freq-domain-cells = <1>; + }; + }; diff --git a/Documentation/devicetree/bindings/dma/apple,admac.yaml b/Documentation/devicetree/bindings/dma/apple,admac.yaml new file mode 100644 index 000000000000..34ede3b0de2c --- /dev/null +++ b/Documentation/devicetree/bindings/dma/apple,admac.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/apple,admac.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Audio DMA Controller (ADMAC) + +description: | + The Audio DMA Controller (ADMAC) is used to load and store audio + samples from/to system memory. It is present on Apple SoCs + from the "Apple Silicon" family. + +maintainers: + - Martin Povišer <povik@protonmail.com> + +allOf: + - $ref: "dma-controller.yaml#" + +properties: + compatible: + items: + - const: apple,t8103-admac + - const: apple,admac + + reg: + maxItems: 1 + + '#dma-cells': + const: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - '#dma-cells' + - dma-channels + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/apple-aic.h> + #include <dt-bindings/interrupt-controller/irq.h> + + dart_sio: iommu@235004000 { + compatible = "apple,t8103-dart", "apple,dart"; + reg = <0x2 0x35004000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 635 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + }; + + admac: dma-controller@238200000 { + compatible = "apple,t8103-admac", "apple,admac"; + reg = <0x2 0x38200000 0x0 0x34000>; + dma-channels = <12>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 626 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + iommus = <&dart_sio 2>; + };
\ No newline at end of file diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index 82ad669feef7..06af2bacbe97 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -22,7 +22,9 @@ description: |+ properties: compatible: - const: apple,t8103-dart + enum: + - apple,t8103-dart + - apple,t6000-dart reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/net/bluetooth-controller.yaml b/Documentation/devicetree/bindings/net/bluetooth-controller.yaml new file mode 100644 index 000000000000..0ea8a20e30f9 --- /dev/null +++ b/Documentation/devicetree/bindings/net/bluetooth-controller.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/bluetooth-controller.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Bluetooth Controller Generic Binding + +maintainers: + - Marcel Holtmann <marcel@holtmann.org> + - Johan Hedberg <johan.hedberg@gmail.com> + - Luiz Augusto von Dentz <luiz.dentz@gmail.com> + +properties: + $nodename: + pattern: "^bluetooth(@.*)?$" + + local-bd-address: + $ref: /schemas/types.yaml#/definitions/uint8-array + minItems: 6 + maxItems: 6 + description: + Specifies the BD address that was uniquely assigned to the Bluetooth + device. Formatted with least significant byte first (little-endian), e.g. + in order to assign the address 00:11:22:33:44:55 this property must have + the value [55 44 33 22 11 00]. + +additionalProperties: true + +... diff --git a/Documentation/devicetree/bindings/net/bluetooth.txt b/Documentation/devicetree/bindings/net/bluetooth.txt index 94797df751b8..3cb5a7b8e5ad 100644 --- a/Documentation/devicetree/bindings/net/bluetooth.txt +++ b/Documentation/devicetree/bindings/net/bluetooth.txt @@ -1,5 +1 @@ -The following properties are common to the Bluetooth controllers: - -- local-bd-address: array of 6 bytes, specifies the BD address that was - uniquely assigned to the Bluetooth device, formatted with least significant - byte first (little-endian). +This file has been moved to bluetooth-controller.yaml. diff --git a/Documentation/devicetree/bindings/net/brcm,bcm4377-bluetooth.yaml b/Documentation/devicetree/bindings/net/brcm,bcm4377-bluetooth.yaml new file mode 100644 index 000000000000..afe6ecebd939 --- /dev/null +++ b/Documentation/devicetree/bindings/net/brcm,bcm4377-bluetooth.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/brcm,bcm4377-bluetooth.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Broadcom BCM4377 family PCI Bluetooth Chips + +allOf: + - $ref: bluetooth-controller.yaml# + +maintainers: + - Sven Peter <sven@svenpeter.dev> + +description: + This binding describes Broadcom BCM4377 family PCI-attached bluetooth chips + usually found in Apple machines. The Wi-Fi part of the chip is described in + bindings/net/wireless/brcm,bcm4329-fmac.yaml. + +properties: + compatible: + enum: + - pci14e4,5fa0 # BCM4377 + - pci14e4,5f69 # BCM4378 + - pci14e4,5f71 # BCM4387 + + reg: + description: PCI device identifier. + + brcm,board-type: + $ref: /schemas/types.yaml#/definitions/string + description: Board type of the Bluetooth chip. This is used to decouple + the overall system board from the Bluetooth module and used to construct + firmware and calibration data filenames. + On Apple platforms, this should be the Apple module-instance codename + prefixed by "apple,", e.g. "apple,atlantisb". + + brcm,taurus-cal-blob: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: A per-device calibration blob for the Bluetooth radio. This + should be filled in by the bootloader from platform configuration + data, if necessary, and will be uploaded to the device. + This blob is used if the chip stepping of the Bluetooth module does not + support beamforming. + + brcm,taurus-bf-cal-blob: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: A per-device calibration blob for the Bluetooth radio. This + should be filled in by the bootloader from platform configuration + data, if necessary, and will be uploaded to the device. + This blob is used if the chip stepping of the Bluetooth module supports + beamforming. + + local-bd-address: true + +required: + - compatible + - reg + - local-bd-address + - brcm,board-type + +additionalProperties: false + +examples: + - | + pci0 { + #address-cells = <3>; + #size-cells = <2>; + + bluetooth@0,1 { + compatible = "pci14e4,5f69"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + brcm,board-type = "apple,honshu"; + /* To be filled by the bootloader */ + local-bd-address = [00 00 00 00 00 00]; + }; + }; diff --git a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml index c11f23b20c4c..8b4147c64355 100644 --- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml +++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/net/wireless/brcm,bcm4329-fmac.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Broadcom BCM4329 family fullmac wireless SDIO devices +title: Broadcom BCM4329 family fullmac wireless SDIO/PCIE devices maintainers: - Arend van Spriel <arend@broadcom.com> @@ -42,10 +42,16 @@ properties: - cypress,cyw43012-fmac - const: brcm,bcm4329-fmac - const: brcm,bcm4329-fmac + - enum: + - pci14e4,43dc # BCM4355 + - pci14e4,4464 # BCM4364 + - pci14e4,4488 # BCM4377 + - pci14e4,4425 # BCM4378 + - pci14e4,4433 # BCM4387 reg: - description: SDIO function number for the device, for most cases - this will be 1. + description: SDIO function number for the device (for most cases + this will be 1) or PCI device identifier. interrupts: maxItems: 1 @@ -75,6 +81,31 @@ properties: items: pattern: '^[A-Z][A-Z]-[A-Z][0-9A-Z]-[0-9]+$' + brcm,cal-blob: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: A per-device calibration blob for the Wi-Fi radio. This + should be filled in by the bootloader from platform configuration + data, if necessary, and will be uploaded to the device if present. + + brcm,board-type: + $ref: /schemas/types.yaml#/definitions/string + description: Overrides the board type, which is normally the compatible of + the root node. This can be used to decouple the overall system board or + device name from the board type for WiFi purposes, which is used to + construct firmware and NVRAM configuration filenames, allowing for + multiple devices that share the same module or characteristics for the + WiFi subsystem to share the same firmware/NVRAM files. On Apple platforms, + this should be the Apple module-instance codename prefixed by "apple,", + e.g. "apple,honshu". + + apple,antenna-sku: + $ref: /schemas/types.yaml#/definitions/string + description: Antenna SKU used to identify a specific antenna configuration + on Apple platforms. This is use to build firmware filenames, to allow + platforms with different antenna configs to have different firmware and/or + NVRAM. This would normally be filled in by the bootloader from platform + configuration data. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index aa38680aaaca..156f1777fc96 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -71,6 +71,27 @@ properties: power-domains: maxItems: 1 +patternProperties: + "^pci@": + $ref: /schemas/pci/pci-bus.yaml# + type: object + description: A single PCI root port + + properties: + reg: + maxItems: 1 + + pwren-gpios: + description: Optional GPIO to power on the device + maxItems: 1 + + required: + - reset-gpios + - interrupt-controller + - "#interrupt-cells" + - interrupt-map-mask + - interrupt-map + required: - compatible - reg @@ -141,7 +162,7 @@ examples: pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; - pci@0,0 { + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; reset-gpios = <&pinctrl_ap 152 0>; @@ -149,9 +170,17 @@ examples: #address-cells = <3>; #size-cells = <2>; ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port00 0 0 0 0>, + <0 0 0 2 &port00 0 0 0 1>, + <0 0 0 3 &port00 0 0 0 2>, + <0 0 0 4 &port00 0 0 0 3>; }; - pci@1,0 { + port01: pci@1,0 { device_type = "pci"; reg = <0x800 0x0 0x0 0x0 0x0>; reset-gpios = <&pinctrl_ap 153 0>; @@ -159,9 +188,17 @@ examples: #address-cells = <3>; #size-cells = <2>; ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; }; - pci@2,0 { + port02: pci@2,0 { device_type = "pci"; reg = <0x1000 0x0 0x0 0x0 0x0>; reset-gpios = <&pinctrl_ap 33 0>; @@ -169,6 +206,14 @@ examples: #address-cells = <3>; #size-cells = <2>; ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port02 0 0 0 0>, + <0 0 0 2 &port02 0 0 0 1>, + <0 0 0 3 &port02 0 0 0 2>, + <0 0 0 4 &port02 0 0 0 3>; }; }; }; diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml new file mode 100644 index 000000000000..bcbdc8943e92 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/apple,spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple ARM SoC SPI controller + +allOf: + - $ref: "spi-controller.yaml#" + +maintainers: + - Hector Martin <marcan@marcan.st> + +properties: + compatible: + items: + - enum: + - apple,t8103-spi + - apple,t6000-spi + - const: apple,spi + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - clocks + - interrupts + - '#address-cells' + - '#size-cells' + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/apple-aic.h> + #include <dt-bindings/interrupt-controller/irq.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + spi: spi@39b104000 { + compatible = "apple,t6000-spi", "apple,spi"; + reg = <0x3 0x9b104000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1107 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&clk>; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml index 5e32928c4fc3..e8e86e4e4596 100644 --- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml +++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml @@ -108,6 +108,18 @@ properties: minItems: 2 maxItems: 4 + spi-cs-setup-delay-ns: + description: + Delay, in nanoseconds, after chip select is asserted. + + spi-cs-hold-delay-ns: + description: + Delay, in nanoseconds, before chip select is de-asserted. + + spi-cs-inactive-delay-ns: + description: + Delay, in nanoseconds, after chip select is de-asserted. + # The controller specific properties go here. allOf: - $ref: cdns,qspi-nor-peripheral-props.yaml# diff --git a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml new file mode 100644 index 000000000000..fb3b3489e6b2 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/apple,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon DWC3 USB controller + +maintainers: + - Sven Peter <sven@svenpeter.dev> + +description: + On Apple Silicon SoCs such as the M1 each Type-C port has a corresponding + USB controller based on the Synopsys DesignWare USB3 controller. + + The common content of this binding is defined in snps,dwc3.yaml. + +allOf: + - $ref: snps,dwc3.yaml# + +select: + properties: + compatible: + contains: + const: apple,dwc3 + required: + - compatible + +properties: + compatible: + items: + - enum: + - apple,t8103-dwc3 + - apple,t6000-dwc3 + - const: apple,dwc3 + - const: snps,dwc3 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +examples: + - | + #include <dt-bindings/interrupt-controller/apple-aic.h> + #include <dt-bindings/interrupt-controller/irq.h> + + usb@82280000 { + compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x82280000 0x10000>; + interrupts = <AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>; + + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 64379c699903..6c67cba98046 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1833,28 +1833,36 @@ T: git https://github.com/AsahiLinux/linux.git F: Documentation/devicetree/bindings/arm/apple.yaml F: Documentation/devicetree/bindings/arm/apple/* F: Documentation/devicetree/bindings/clock/apple,nco.yaml +F: Documentation/devicetree/bindings/cpufreq/apple,soc-cpufreq.yaml F: Documentation/devicetree/bindings/i2c/apple,i2c.yaml F: Documentation/devicetree/bindings/interrupt-controller/apple,* F: Documentation/devicetree/bindings/iommu/apple,dart.yaml F: Documentation/devicetree/bindings/iommu/apple,sart.yaml F: Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml +F: Documentation/devicetree/bindings/net/brcm,bcm4377-bluetooth.yaml F: Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml F: Documentation/devicetree/bindings/nvmem/apple,efuses.yaml F: Documentation/devicetree/bindings/pci/apple,pcie.yaml F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml F: Documentation/devicetree/bindings/power/apple* +F: Documentation/devicetree/bindings/spi/apple,spi.yaml +F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml F: arch/arm64/boot/dts/apple/ +F: drivers/bluetooth/hci_bcm4377.c F: drivers/clk/clk-apple-nco.c +F: drivers/cpufreq/apple-soc-cpufreq.c F: drivers/i2c/busses/i2c-pasemi-core.c F: drivers/i2c/busses/i2c-pasemi-platform.c F: drivers/iommu/apple-dart.c +F: drivers/iommu/io-pgtable-dart.c F: drivers/irqchip/irq-apple-aic.c F: drivers/mailbox/apple-mailbox.c F: drivers/nvme/host/apple.c F: drivers/nvmem/apple-efuses.c F: drivers/pinctrl/pinctrl-apple-gpio.c F: drivers/soc/apple/* +F: drivers/spi/spi-apple.c F: drivers/watchdog/apple_wdt.c F: include/dt-bindings/interrupt-controller/apple-aic.h F: include/dt-bindings/pinctrl/apple.h diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index c0510c25ca6a..9d002bbbe98f 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -4,3 +4,11 @@ dtb-$(CONFIG_ARCH_APPLE) += t8103-j293.dtb dtb-$(CONFIG_ARCH_APPLE) += t8103-j313.dtb dtb-$(CONFIG_ARCH_APPLE) += t8103-j456.dtb dtb-$(CONFIG_ARCH_APPLE) += t8103-j457.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6000-j314s.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6001-j314c.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6000-j316s.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6001-j316c.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6001-j375c.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6002-j375d.dtb +dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb +dtb-$(CONFIG_ARCH_APPLE) += t8112-j493.dtb diff --git a/arch/arm64/boot/dts/apple/multi-die-cpp.h b/arch/arm64/boot/dts/apple/multi-die-cpp.h new file mode 100644 index 000000000000..19b565bb6e0e --- /dev/null +++ b/arch/arm64/boot/dts/apple/multi-die-cpp.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * C preprocessor macros for t600x multi die support. + */ + +#ifndef __DTS_APPLE_MULTI_DIE_CPP_H +#define __DTS_APPLE_MULTI_DIE_CPP_H + +/* copied include/linux/stringify.h */ +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +#define __concat_1(x, y...) x ## y +#define __concat(x, y...) __concat_1(x, y) + +#define DIE_NODE(a) __concat(a, DIE) +#define DIE_LABEL(a) __stringify(__concat(a, DIE)) + +#endif /* !__LINUX_STRINGIFY_H */ diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts new file mode 100644 index 000000000000..1430b91ff1b1 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (14-inch, M1 Pro, 2021) + * + * target-type: J314s + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t6000.dtsi" +#include "t600x-j314-j316.dtsi" + +/ { + compatible = "apple,j314s", "apple,t6000", "apple,arm-platform"; + model = "Apple MacBook Pro (14-inch, M1 Pro, 2021)"; +}; + +&wifi0 { + brcm,board-type = "apple,maldives"; +}; + +&bluetooth0 { + brcm,board-type = "apple,maldives"; +}; diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts new file mode 100644 index 000000000000..da0cbe7d9673 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (16-inch, M1 Pro, 2021) + * + * target-type: J316s + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t6000.dtsi" +#include "t600x-j314-j316.dtsi" + +/ { + compatible = "apple,j316s", "apple,t6000", "apple,arm-platform"; + model = "Apple MacBook Pro (16-inch, M1 Pro, 2021)"; +}; + +&wifi0 { + brcm,board-type = "apple,madagascar"; +}; + +&bluetooth0 { + brcm,board-type = "apple,madagascar"; +}; diff --git a/arch/arm64/boot/dts/apple/t6000.dtsi b/arch/arm64/boot/dts/apple/t6000.dtsi new file mode 100644 index 000000000000..89c3b211b116 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6000.dtsi @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple T6000 "M1 Pro" SoC + * + * Other names: H13J, "Jade Chop" + * + * Copyright The Asahi Linux Contributors + */ + +/* This chip is just a cut down version of t6001, so include it and disable the missing parts */ + +#include "t6001.dtsi" + +/ { + compatible = "apple,t6000", "apple,arm-platform"; +}; + +/delete-node/ &pmgr_south; diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts new file mode 100644 index 000000000000..c37097dcfdb3 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (14-inch, M1 Max, 2021) + * + * target-type: J314c + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t6001.dtsi" +#include "t600x-j314-j316.dtsi" + +/ { + compatible = "apple,j314c", "apple,t6001", "apple,arm-platform"; + model = "Apple MacBook Pro (14-inch, M1 Max, 2021)"; +}; + +&wifi0 { + brcm,board-type = "apple,maldives"; +}; + +&bluetooth0 { + brcm,board-type = "apple,maldives"; +}; diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts new file mode 100644 index 000000000000..3bc6e0c3294c --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (16-inch, M1 Max, 2021) + * + * target-type: J316c + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t6001.dtsi" +#include "t600x-j314-j316.dtsi" + +/ { + compatible = "apple,j316c", "apple,t6001", "apple,arm-platform"; + model = "Apple MacBook Pro (16-inch, M1 Max, 2021)"; +}; + +&wifi0 { + brcm,board-type = "apple,madagascar"; +}; + +&bluetooth0 { + brcm,board-type = "apple,madagascar"; +}; diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts new file mode 100644 index 000000000000..0f278170bd7a --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Mac Studio (M1 Max, 2022) + * + * target-type: J375c + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t6001.dtsi" + +/ { + compatible = "apple,j375c", "apple,t6001", "apple,arm-platform"; + model = "Apple Mac Studio (M1 Max, 2022)"; + + aliases { + serial0 = &serial0; + wifi0 = &wifi0; + bluetooth0 = &bluetooth0; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stdout-path = "serial0"; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; /* To be filled by loader */ + /* Format properties will be added by loader */ + status = "disabled"; + }; + }; + + memory@10000000000 { + device_type = "memory"; + reg = <0x100 0 0x2 0>; /* To be filled by loader */ + }; +}; + +&serial0 { + status = "okay"; +}; + +/* USB Type C */ +&i2c0 { + hpm0: usb-pd@38 { + compatible = "apple,cd321x"; + reg = <0x38>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Left"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_con_hs: endpoint { + remote-endpoint = <&typec0_usb_hs>; + }; + }; + }; + }; + }; + + hpm1: usb-pd@3f { + compatible = "apple,cd321x"; + reg = <0x3f>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Left Middle"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&typec1_usb_hs>; + }; + }; + }; + }; + }; + + hpm2: usb-pd@3b { + compatible = "apple,cd321x"; + reg = <0x3b>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Right Middle"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec2_con_hs: endpoint { + remote-endpoint = <&typec2_usb_hs>; + }; + }; + }; + }; + }; + + hpm3: usb-pd@3c { + compatible = "apple,cd321x"; + reg = <0x3c>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec3: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec3_con_hs: endpoint { + remote-endpoint = <&typec3_usb_hs>; + }; + }; + }; + }; + }; +}; + +/* Audio */ +&i2c1 { + status = "okay"; + + speaker: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + }; +}; + +/* PCIe devices */ +&port00 { + /* WLAN */ + bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + wifi0: wifi@0,0 { + compatible = "pci14e4,4433"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 10]; + apple,antenna-sku = "XX"; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f71"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + }; +}; + +&port01 { + /* SD card reader */ + bus-range = <2 2>; + pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + sdhci0: mmc@0,0 { + compatible = "pci17a0,9755"; + reg = <0x20000 0x0 0x0 0x0 0x0>; + cd-inverted; + wp-inverted; + }; +}; + +&port02 { + /* 10 Gbit Ethernet */ + bus-range = <3 3>; + ethernet0: ethernet@0,0 { + reg = <0x30000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 00]; + }; +}; + +&port03 { + /* USB xHCI */ + bus-range = <4 4>; + pwren-gpios = <&smc_gpio 20 GPIO_ACTIVE_HIGH>; +}; + +&wifi0 { + brcm,board-type = "apple,okinawa"; +}; + +&bluetooth0 { + brcm,board-type = "apple,okinawa"; +}; + +/* USB controllers */ +&dwc3_0 { + port { + typec0_usb_hs: endpoint { + remote-endpoint = <&typec0_con_hs>; + }; + }; +}; + +&dwc3_1 { + port { + typec1_usb_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; + }; +}; + +&dwc3_2 { + port { + typec2_usb_hs: endpoint { + remote-endpoint = <&typec2_con_hs>; + }; + }; +}; + +&dwc3_3 { + port { + typec3_usb_hs: endpoint { + remote-endpoint = <&typec3_con_hs>; + }; + }; +}; + +/ { + sound { + compatible = "apple,j375-macaudio", "apple,macaudio"; + model = "Mac Studio J375 integrated audio"; + + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + dai-link@0 { + link-name = "Speaker"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi new file mode 100644 index 000000000000..6b50a9fa68e7 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6001.dtsi @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple T6001 "M1 Max" SoC + * + * Other names: H13J, "Jade" + * + * Copyright The Asahi Linux Contributors + */ + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/apple-aic.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/apple.h> +#include <dt-bindings/spmi/spmi.h> + +#include "multi-die-cpp.h" + +#include "t600x-common.dtsi" + +/ { + compatible = "apple,t6001", "apple,arm-platform"; + + soc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + + ranges; + nonposted-mmio; + + // filled via templated includes at the end of the file + }; +}; + +#define DIE +#define DIE_NO 0 + +&{/soc} { + #include "t600x-die0.dtsi" + #include "t600x-dieX.dtsi" + #include "t600x-nvme.dtsi" +}; + +#include "t600x-gpio-pins.dtsi" +#include "t600x-pmgr.dtsi" + +#undef DIE +#undef DIE_NO + + +&cpufreq_hw { + reg = <0x2 0x10e20000 0 0x1000>, + <0x2 0x11e20000 0 0x1000>, + <0x2 0x12e20000 0 0x1000>; + reg-names = "cluster0", "cluster1", "cluster2"; +}; + +&aic { + affinities { + e-core-pmu-affinity { + apple,fiq-index = <AIC_CPU_PMU_E>; + cpus = <&cpu_e00 &cpu_e01>; + }; + + p-core-pmu-affinity { + apple,fiq-index = <AIC_CPU_PMU_P>; + cpus = <&cpu_p00 &cpu_p01 &cpu_p02 &cpu_p03 + &cpu_p10 &cpu_p11 &cpu_p12 &cpu_p13>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts new file mode 100644 index 000000000000..e3a554c6aa5e --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Mac Studio (M1 Ultra, 2022) + * + * target-type: J375d + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t6002.dtsi" + +/ { + compatible = "apple,j375d", "apple,t6002", "apple,arm-platform"; + model = "Apple Mac Studio (M1 Ultra, 2022)"; + + aliases { + serial0 = &serial0; + wifi0 = &wifi0; + bluetooth0 = &bluetooth0; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stdout-path = "serial0"; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; /* To be filled by loader */ + /* Format properties will be added by loader */ + status = "disabled"; + }; + }; + + memory@10000000000 { + device_type = "memory"; + reg = <0x100 0 0x2 0>; /* To be filled by loader */ + }; +}; + +&serial0 { + status = "okay"; +}; + +/* USB Type C */ +&i2c0 { + hpm0: usb-pd@38 { + compatible = "apple,cd321x"; + reg = <0x38>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Left"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_con_hs: endpoint { + remote-endpoint = <&typec0_usb_hs>; + }; + }; + }; + }; + }; + + hpm1: usb-pd@3f { + compatible = "apple,cd321x"; + reg = <0x3f>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Left Middle"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&typec1_usb_hs>; + }; + }; + }; + }; + }; + + hpm2: usb-pd@3b { + compatible = "apple,cd321x"; + reg = <0x3b>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Right Middle"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec2_con_hs: endpoint { + remote-endpoint = <&typec2_usb_hs>; + }; + }; + }; + }; + }; + + hpm3: usb-pd@3c { + compatible = "apple,cd321x"; + reg = <0x3c>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec3: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec3_con_hs: endpoint { + remote-endpoint = <&typec3_usb_hs>; + }; + }; + }; + }; + }; + + /* front-right */ + hpm4: usb-pd@39 { + compatible = "apple,cd321x"; + reg = <0x39>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec4: connector { + compatible = "usb-c-connector"; + label = "USB-C Front Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec4_con_hs: endpoint { + remote-endpoint = <&typec4_usb_hs>; + }; + }; + }; + }; + }; + + /* front-left */ + hpm5: usb-pd@3a { + compatible = "apple,cd321x"; + reg = <0x3a>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec5: connector { + compatible = "usb-c-connector"; + label = "USB-C Front Left"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec5_con_hs: endpoint { + remote-endpoint = <&typec5_usb_hs>; + }; + }; + }; + }; + }; +}; + +/* Audio */ +&i2c1 { + status = "okay"; + + speaker: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + }; +}; + +/* PCIe devices */ +&port00 { + /* WLAN */ + bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + wifi0: wifi@0,0 { + compatible = "pci14e4,4433"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 10]; + apple,antenna-sku = "XX"; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f71"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + }; +}; + +&port01 { + /* SD card reader */ + bus-range = <2 2>; + pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + sdhci0: mmc@0,0 { + compatible = "pci17a0,9755"; + reg = <0x20000 0x0 0x0 0x0 0x0>; + cd-inverted; + wp-inverted; + }; +}; + +&port02 { + /* 10 Gbit Ethernet */ + bus-range = <3 3>; + ethernet0: ethernet@0,0 { + reg = <0x30000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 00]; + }; +}; + +&port03 { + /* USB xHCI */ + bus-range = <4 4>; + pwren-gpios = <&smc_gpio 20 GPIO_ACTIVE_HIGH>; +}; + +&wifi0 { + brcm,board-type = "apple,okinawa"; +}; + +&bluetooth0 { + brcm,board-type = "apple,okinawa"; +}; + +/* USB controllers */ +&dwc3_0 { + port { + typec0_usb_hs: endpoint { + remote-endpoint = <&typec0_con_hs>; + }; + }; +}; + +&dwc3_1 { + port { + typec1_usb_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; + }; +}; + +&dwc3_2 { + port { + typec2_usb_hs: endpoint { + remote-endpoint = <&typec2_con_hs>; + }; + }; +}; + +&dwc3_3 { + port { + typec3_usb_hs: endpoint { + remote-endpoint = <&typec3_con_hs>; + }; + }; +}; + +&dwc3_0_die1 { + port { + typec4_usb_hs: endpoint { + remote-endpoint = <&typec4_con_hs>; + }; + }; +}; + +&dwc3_1_die1 { + port { + typec5_usb_hs: endpoint { + remote-endpoint = <&typec5_con_hs>; + }; + }; +}; + +/* delete unused USB nodes on die 1 */ + +/delete-node/ &dwc3_2_dart_0_die1; +/delete-node/ &dwc3_2_dart_1_die1; +/delete-node/ &dwc3_2_die1; + +/delete-node/ &dwc3_3_dart_0_die1; +/delete-node/ &dwc3_3_dart_1_die1; +/delete-node/ &dwc3_3_die1; + + +/* delete unused always-on power-domains on die 1 */ + +/delete-node/ &ps_atc2_usb_aon_die1; +/delete-node/ &ps_atc2_usb_die1; + +/delete-node/ &ps_atc3_usb_aon_die1; +/delete-node/ &ps_atc3_usb_die1; + +/delete-node/ &ps_disp0_cpu0_die1; +/delete-node/ &ps_disp0_fe_die1; + +/ { + sound { + compatible = "apple,j375-macaudio", "apple,macaudio"; + model = "Mac Studio J375 integrated audio"; + + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + dai-link@0 { + link-name = "Speaker"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi new file mode 100644 index 000000000000..31e2de7fb2ea --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6002.dtsi @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple T6002 "M1 Ultra" SoC + * + * Other names: H13J, "Jade 2C" + * + * Copyright The Asahi Linux Contributors + */ + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/apple-aic.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/apple.h> +#include <dt-bindings/spmi/spmi.h> + +#include "multi-die-cpp.h" + +#include "t600x-common.dtsi" + +/ { + compatible = "apple,t6002", "apple,arm-platform"; + + #address-cells = <2>; + #size-cells = <2>; + + cpus { + cpu-map { + cluster3 { + core0 { + cpu = <&cpu_e10>; + }; + core1 { + cpu = <&cpu_e11>; + }; + }; + + cluster4 { + core0 { + cpu = <&cpu_p20>; + }; + core1 { + cpu = <&cpu_p21>; + }; + core2 { + cpu = <&cpu_p22>; + }; + core3 { + cpu = <&cpu_p23>; + }; + }; + + cluster5 { + core0 { + cpu = <&cpu_p30>; + }; + core1 { + cpu = <&cpu_p31>; + }; + core2 { + cpu = <&cpu_p32>; + }; + core3 { + cpu = <&cpu_p33>; + }; + }; + }; + + cpu_e10: cpu@800 { + compatible = "apple,icestorm"; + device_type = "cpu"; + reg = <0x0 0x800>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&icestorm_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 3>; + }; + + cpu_e11: cpu@801 { + compatible = "apple,icestorm"; + device_type = "cpu"; + reg = <0x0 0x801>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&icestorm_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 3>; + }; + + cpu_p20: cpu@10900 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10900>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 4>; + }; + + cpu_p21: cpu@10901 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10901>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 4>; + }; + + cpu_p22: cpu@10902 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10902>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 4>; + }; + + cpu_p23: cpu@10903 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10903>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 4>; + }; + + cpu_p30: cpu@10a00 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10a00>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 5>; + }; + + cpu_p31: cpu@10a01 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10a01>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 5>; + }; + + cpu_p32: cpu@10a02 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10a02>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 5>; + }; + + cpu_p33: cpu@10a03 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10a03>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 5>; + }; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + + ranges; + nonposted-mmio; + + die0 { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + nonposted-mmio; + + // filled via templated includes at the end of the file + }; + + die1 { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + ranges = <0x2 0x0 0x22 0x0 0x4 0x0>, + <0x7 0x0 0x27 0x0 0xf 0x80000000>; + nonposted-mmio; + + // filled via templated includes at the end of the file + }; + }; +}; + +#define DIE +#define DIE_NO 0 + +&{/soc/die0} { + #include "t600x-die0.dtsi" + #include "t600x-dieX.dtsi" +}; + +#include "t600x-pmgr.dtsi" +#include "t600x-gpio-pins.dtsi" + +#undef DIE +#undef DIE_NO + +#define DIE _die1 +#define DIE_NO 1 + +&{/soc/die1} { + #include "t600x-dieX.dtsi" + #include "t600x-nvme.dtsi" +}; + +#include "t600x-pmgr.dtsi" + +#undef DIE +#undef DIE_NO + +&cpufreq_hw { + reg = <0x2 0x10e20000 0 0x1000>, + <0x2 0x11e20000 0 0x1000>, + <0x2 0x12e20000 0 0x1000>, + <0x22 0x10e20000 0 0x1000>, + <0x22 0x11e20000 0 0x1000>, + <0x22 0x12e20000 0 0x1000>; + reg-names = "cluster0", "cluster1", "cluster2", + "cluster3", "cluster4", "cluster5"; +}; + +&aic { + affinities { + e-core-pmu-affinity { + cpus = <&cpu_e00 &cpu_e01 + &cpu_e10 &cpu_e11>; + }; + + p-core-pmu-affinity { + cpus = <&cpu_p00 &cpu_p01 &cpu_p02 &cpu_p03 + &cpu_p10 &cpu_p11 &cpu_p12 &cpu_p13 + &cpu_p20 &cpu_p21 &cpu_p22 &cpu_p23 + &cpu_p30 &cpu_p31 &cpu_p32 &cpu_p33>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi new file mode 100644 index 000000000000..c5ee0c2da778 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Common Apple T6000 / T6001 / T6002 "M1 Pro/Max/Ultra" SoC + * + * Other names: H13J, "Jade Chop", "Jade", "Jade 2C" + * + * Copyright The Asahi Linux Contributors + */ + +/ { + #address-cells = <2>; + #size-cells = <2>; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu-map { + cluster0 { + core0 { + cpu = <&cpu_e00>; + }; + core1 { + cpu = <&cpu_e01>; + }; + }; + + cluster1 { + core0 { + cpu = <&cpu_p00>; + }; + core1 { + cpu = <&cpu_p01>; + }; + core2 { + cpu = <&cpu_p02>; + }; + core3 { + cpu = <&cpu_p03>; + }; + }; + + cluster2 { + core0 { + cpu = <&cpu_p10>; + }; + core1 { + cpu = <&cpu_p11>; + }; + core2 { + cpu = <&cpu_p12>; + }; + core3 { + cpu = <&cpu_p13>; + }; + }; + }; + + cpu_e00: cpu@0 { + compatible = "apple,icestorm"; + device_type = "cpu"; + reg = <0x0 0x0>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&icestorm_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu_e01: cpu@1 { + compatible = "apple,icestorm"; + device_type = "cpu"; + reg = <0x0 0x1>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&icestorm_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu_p00: cpu@10100 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10100>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p01: cpu@10101 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10101>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p02: cpu@10102 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10102>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p03: cpu@10103 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10103>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p10: cpu@10200 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10200>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 2>; + }; + + cpu_p11: cpu@10201 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10201>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 2>; + }; + + cpu_p12: cpu@10202 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10202>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 2>; + }; + + cpu_p13: cpu@10203 { + compatible = "apple,firestorm"; + device_type = "cpu"; + reg = <0x0 0x10203>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&firestorm_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 2>; + }; + }; + + icestorm_opp: opp-table-0 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <7500>; + }; + opp02 { + opp-hz = /bits/ 64 <972000000>; + opp-level = <2>; + clock-latency-ns = <23000>; + }; + opp03 { + opp-hz = /bits/ 64 <1332000000>; + opp-level = <3>; + clock-latency-ns = <29000>; + }; + opp04 { + opp-hz = /bits/ 64 <1704000000>; + opp-level = <4>; + clock-latency-ns = <40000>; + }; + opp05 { + opp-hz = /bits/ 64 <2064000000>; + opp-level = <5>; + clock-latency-ns = <50000>; + }; + }; + + firestorm_opp: opp-table-1 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <8000>; + }; + opp02 { + opp-hz = /bits/ 64 <828000000>; + opp-level = <2>; + clock-latency-ns = <18000>; + }; + opp03 { + opp-hz = /bits/ 64 <1056000000>; + opp-level = <3>; + clock-latency-ns = <19000>; + }; + opp04 { + opp-hz = /bits/ 64 <1296000000>; + opp-level = <4>; + clock-latency-ns = <23000>; + }; + opp05 { + opp-hz = /bits/ 64 <1524000000>; + opp-level = <5>; + clock-latency-ns = <24000>; + }; + opp06 { + opp-hz = /bits/ 64 <1752000000>; + opp-level = <6>; + clock-latency-ns = <28000>; + }; + opp07 { + opp-hz = /bits/ 64 <1980000000>; + opp-level = <7>; + clock-latency-ns = <31000>; + }; + opp08 { + opp-hz = /bits/ 64 <2208000000>; + opp-level = <8>; + clock-latency-ns = <45000>; + }; + opp09 { + opp-hz = /bits/ 64 <2448000000>; + opp-level = <9>; + clock-latency-ns = <49000>; + }; + opp10 { + opp-hz = /bits/ 64 <2676000000>; + opp-level = <10>; + clock-latency-ns = <53000>; + }; + opp11 { + opp-hz = /bits/ 64 <2904000000>; + opp-level = <11>; + clock-latency-ns = <56000>; + }; + opp12 { + opp-hz = /bits/ 64 <3036000000>; + opp-level = <12>; + clock-latency-ns = <56000>; + }; + /* Not available until CPU deep sleep is implemented + opp13 { + opp-hz = /bits/ 64 <3132000000>; + opp-level = <13>; + clock-latency-ns = <56000>; + turbo-mode; + }; + opp14 { + opp-hz = /bits/ 64 <3168000000>; + opp-level = <14>; + clock-latency-ns = <56000>; + turbo-mode; + }; + opp15 { + opp-hz = /bits/ 64 <3228000000>; + opp-level = <15>; + clock-latency-ns = <56000>; + turbo-mode; + }; + */ + }; + + pmu-e { + compatible = "apple,icestorm-pmu"; + interrupt-parent = <&aic>; + interrupts = <AIC_FIQ 0 AIC_CPU_PMU_E IRQ_TYPE_LEVEL_HIGH>; + }; + + pmu-p { + compatible = "apple,firestorm-pmu"; + interrupt-parent = <&aic>; + interrupts = <AIC_FIQ 0 AIC_CPU_PMU_P IRQ_TYPE_LEVEL_HIGH>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupt-parent = <&aic>; + interrupt-names = "phys", "virt", "hyp-phys", "hyp-virt"; + interrupts = <AIC_FIQ 0 AIC_TMR_GUEST_PHYS IRQ_TYPE_LEVEL_HIGH>, + <AIC_FIQ 0 AIC_TMR_GUEST_VIRT IRQ_TYPE_LEVEL_HIGH>, + <AIC_FIQ 0 AIC_TMR_HV_PHYS IRQ_TYPE_LEVEL_HIGH>, + <AIC_FIQ 0 AIC_TMR_HV_VIRT IRQ_TYPE_LEVEL_HIGH>; + }; + + clkref: clock-ref { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "clkref"; + }; + + clk_200m: clock-200m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <200000000>; + clock-output-names = "clk_200m"; + }; + +}; diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi new file mode 100644 index 000000000000..4c203778aa89 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Devices used on die 0 on the Apple T6002 "M1 Ultra" SoC and present on + * Apple T6000 / T6001 "M1 Pro" / "M1 Max". + * + * Copyright The Asahi Linux Contributors + */ + + cpufreq_hw: cpufreq@210e20000 { + compatible = "apple,t6000-soc-cpufreq", "apple,t8103-soc-cpufreq", "apple,soc-cpufreq"; + #freq-domain-cells = <1>; + }; + + aic: interrupt-controller@28e100000 { + compatible = "apple,t6000-aic", "apple,aic2"; + #interrupt-cells = <4>; + interrupt-controller; + reg = <0x2 0x8e100000 0x0 0xc000>, + <0x2 0x8e10c000 0x0 0x4>; + reg-names = "core", "event"; + power-domains = <&ps_aic>; + }; + + smc_mbox: mbox@290408000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x90408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 754 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 755 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 756 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 757 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + smc: smc@290400000 { + compatible = "apple,t6000-smc", "apple,smc"; + reg = <0x2 0x90400000 0x0 0x4000>, + <0x2 0x91e00000 0x0 0x100000>; + reg-names = "smc", "sram"; + mboxes = <&smc_mbox>; + + smc_gpio: gpio { + gpio-controller; + #gpio-cells = <2>; + }; + + smc_rtc: rtc { + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + + smc_reboot: reboot { + nvmem-cells = <&shutdown_flag>, <&boot_stage>, + <&boot_error_count>, <&panic_count>, <&pm_setting>; + nvmem-cell-names = "shutdown_flag", "boot_stage", + "boot_error_count", "panic_count", "pm_setting"; + }; + }; + + pinctrl_smc: pinctrl@290820000 { + compatible = "apple,t6000-pinctrl", "apple,pinctrl"; + reg = <0x2 0x90820000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_smc 0 0 30>; + apple,npins = <30>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 743 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 744 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 745 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 746 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 747 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 748 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 749 IRQ_TYPE_LEVEL_HIGH>; + }; + + wdt: watchdog@2922b0000 { + compatible = "apple,t6000-wdt", "apple,wdt"; + reg = <0x2 0x922b0000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 631 IRQ_TYPE_LEVEL_HIGH>; + }; + + nub_spmi0: spmi@2920a1300 { + compatible = "apple,t6000-spmi", "apple,spmi"; + reg = <0x2 0x920a1300 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + + pmu1: pmu@f { + compatible = "apple,maverick-pmu", "apple,spmi-pmu"; + reg = <0xf SPMI_USID>; + #address-cells = <1>; + #size-cells = <1>; + + rtc_nvmem@1400 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0x1400 0x20>; + #address-cells = <1>; + #size-cells = <1>; + + pm_setting: pm-setting@5 { + reg = <0x5 0x1>; + }; + + rtc_offset: rtc-offset@11 { + reg = <0x11 0x6>; + }; + }; + + legacy_nvmem@6000 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0x6000 0x20>; + #address-cells = <1>; + #size-cells = <1>; + + boot_stage: boot-stage@1 { + reg = <0x1 0x1>; + }; + + boot_error_count: boot-error-count@2 { + reg = <0x2 0x1>; + bits = <0 4>; + }; + + panic_count: panic-count@2 { + reg = <0x2 0x1>; + bits = <4 4>; + }; + + boot_error_stage: boot-error-stage@3 { + reg = <0x3 0x1>; + }; + + shutdown_flag: shutdown-flag@f { + reg = <0xf 0x1>; + bits = <3 1>; + }; + }; + + scrpad_nvmem@8000 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0x8000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + fault_shadow: fault-shadow@67b { + reg = <0x67b 0x10>; + }; + + socd: socd@b00 { + reg = <0xb00 0x400>; + }; + }; + + }; + }; + + i2c0: i2c@39b040000 { + compatible = "apple,t6000-i2c", "apple,i2c"; + reg = <0x3 0x9b040000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1119 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c0_pins>; + pinctrl-names = "default"; + power-domains = <&ps_i2c0>; + #address-cells = <0x1>; + #size-cells = <0x0>; + }; + + i2c1: i2c@39b044000 { + compatible = "apple,t6000-i2c", "apple,i2c"; + reg = <0x3 0x9b044000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1120 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c1_pins>; + pinctrl-names = "default"; + power-domains = <&ps_i2c1>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "disabled"; + }; + + i2c2: i2c@39b048000 { + compatible = "apple,t6000-i2c", "apple,i2c"; + reg = <0x3 0x9b048000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1121 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c2_pins>; + pinctrl-names = "default"; + power-domains = <&ps_i2c2>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "disabled"; + }; + + i2c3: i2c@39b04c000 { + compatible = "apple,t6000-i2c", "apple,i2c"; + reg = <0x3 0x9b04c000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1122 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c3_pins>; + pinctrl-names = "default"; + power-domains = <&ps_i2c3>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "disabled"; + }; + + i2c4: i2c@39b050000 { + compatible = "apple,t6000-i2c", "apple,i2c"; + reg = <0x3 0x9b050000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1123 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c4_pins>; + pinctrl-names = "default"; + power-domains = <&ps_i2c4>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "disabled"; + }; + + i2c5: i2c@39b054000 { + compatible = "apple,t6000-i2c", "apple,i2c"; + reg = <0x3 0x9b054000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1124 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c5_pins>; + pinctrl-names = "default"; + power-domains = <&ps_i2c5>; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "disabled"; + }; + + spi1: spi@39b104000 { + compatible = "apple,t6000-spi", "apple,spi"; + reg = <0x3 0x9b104000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1107 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&clk_200m>; + pinctrl-0 = <&spi1_pins>; + pinctrl-names = "default"; + power-domains = <&ps_spi1>; + status = "disabled"; + }; + + spi3: spi@39b10c000 { + compatible = "apple,t6000-spi", "apple,spi"; + reg = <0x3 0x9b10c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1109 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&clkref>; + pinctrl-0 = <&spi3_pins>; + pinctrl-names = "default"; + power-domains = <&ps_spi3>; + status = "disabled"; + }; + + serial0: serial@39b200000 { + compatible = "apple,s5l-uart"; + reg = <0x3 0x9b200000 0x0 0x1000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1097 IRQ_TYPE_LEVEL_HIGH>; + /* + * TODO: figure out the clocking properly, there may + * be a third selectable clock. + */ + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + power-domains = <&ps_uart0>; + status = "disabled"; + }; + + pcie0_dart_0: dart@581008000 { + compatible = "apple,t6000-dart"; + reg = <0x5 0x81008000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1271 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&ps_apcie_gp_sys>; + }; + + pcie0_dart_1: dart@582008000 { + compatible = "apple,t6000-dart"; + reg = <0x5 0x82008000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1274 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&ps_apcie_gp_sys>; + }; + + pcie0_dart_2: dart@583008000 { + compatible = "apple,t6000-dart"; + reg = <0x5 0x83008000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1277 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&ps_apcie_gp_sys>; + }; + + pcie0_dart_3: dart@584008000 { + compatible = "apple,t6000-dart"; + reg = <0x5 0x84008000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1280 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&ps_apcie_gp_sys>; + }; + + pcie0: pcie@590000000 { + compatible = "apple,t6000-pcie", "apple,pcie"; + device_type = "pci"; + + reg = <0x5 0x90000000 0x0 0x1000000>, + <0x5 0x80000000 0x0 0x100000>, + <0x5 0x81000000 0x0 0x4000>, + <0x5 0x82000000 0x0 0x4000>, + <0x5 0x83000000 0x0 0x4000>, + <0x5 0x84000000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "port1", "port2", "port3"; + + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1270 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 1273 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 1276 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 0 1279 IRQ_TYPE_LEVEL_HIGH>; + + msi-controller; + msi-parent = <&pcie0>; + msi-ranges = <&aic AIC_IRQ 0 1581 IRQ_TYPE_EDGE_RISING 32>; + + + iommu-map = <0x100 &pcie0_dart_0 1 1>, + <0x200 &pcie0_dart_1 1 1>, + <0x300 &pcie0_dart_2 1 1>, + <0x400 &pcie0_dart_3 1 1>; + iommu-map-mask = <0xff00>; + + bus-range = <0 4>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x5 0xa0000000 0x5 0xa0000000 0x0 0x20000000>, + <0x02000000 0x0 0xc0000000 0x5 0xc0000000 0x0 0x40000000>; + + power-domains = <&ps_apcie_gp_sys>; + pinctrl-0 = <&pcie_pins>; + pinctrl-names = "default"; + + port00: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + reset-gpios = <&pinctrl_ap 4 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port00 0 0 0 0>, + <0 0 0 2 &port00 0 0 0 1>, + <0 0 0 3 &port00 0 0 0 2>, + <0 0 0 4 &port00 0 0 0 3>; + }; + + port01: pci@1,0 { + device_type = "pci"; + reg = <0x800 0x0 0x0 0x0 0x0>; + reset-gpios = <&pinctrl_ap 5 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; + }; + + port02: pci@2,0 { + device_type = "pci"; + reg = <0x1000 0x0 0x0 0x0 0x0>; + reset-gpios = <&pinctrl_ap 6 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port02 0 0 0 0>, + <0 0 0 2 &port02 0 0 0 1>, + <0 0 0 3 &port02 0 0 0 2>, + <0 0 0 4 &port02 0 0 0 3>; + }; + + port03: pci@3,0 { + device_type = "pci"; + reg = <0x1800 0x0 0x0 0x0 0x0>; + reset-gpios = <&pinctrl_ap 7 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port03 0 0 0 0>, + <0 0 0 2 &port03 0 0 0 1>, + <0 0 0 3 &port03 0 0 0 2>, + <0 0 0 4 &port03 0 0 0 3>; + }; + }; + + dart_sio_0: iommu@39b004000 { + compatible = "apple,t6000-dart", "apple,dart"; + reg = <0x3 0x9b004000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1130 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_sio_cpu>; + }; + + dart_sio_1: iommu@39b008000 { + compatible = "apple,t6000-dart", "apple,dart"; + reg = <0x3 0x9b008000 0x0 0x8000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1130 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_sio_cpu>; + }; + + nco_clkref: clock-ref { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <1068000000>; + clock-output-names = "nco_ref"; + }; + + nco: nco@28e03c000 { + compatible = "apple,t6000-nco", "apple,nco"; + reg = <0x2 0x8e03c000 0x0 0x14000>; + clocks = <&nco_clkref>; + #clock-cells = <1>; + }; + + admac: dma-controller@39b400000 { + compatible = "apple,t6000-admac", "apple,admac"; + reg = <0x3 0x9b400000 0x0 0x34000>; + dma-channels = <16>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 0 1118 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + iommus = <&dart_sio_0 2>, <&dart_sio_1 2>; + power-domains = <&ps_sio_adma>; + apple,internal-irq-destination = <1>; + }; + + mca: mca@9b600000 { + compatible = "apple,t6000-mca", "apple,mca"; + reg = <0x3 0x9b600000 0x0 0x10000>, + <0x3 0x9b500000 0x0 0x20000>; + reg-names = "clusters", "switch"; + + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1112 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 1113 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 1114 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 1115 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&nco 0>, <&nco 1>, <&nco 2>, <&nco 3>; + power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>, + <&ps_mca2>, <&ps_mca3>; + dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>, + <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>, + <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>, + <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>; + dma-names = "tx0a", "rx0a", "tx0b", "rx0b", + "tx1a", "rx1a", "tx1b", "rx1b", + "tx2a", "rx2a", "tx2b", "rx2b", + "tx3a", "rx3a", "tx3b", "rx3b"; + + #sound-dai-cells = <1>; + apple,nclusters = <4>; + }; diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi new file mode 100644 index 000000000000..6ada6c7f50fa --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Devices used on both dies on the Apple T6002 "M1 Ultra" and present on + * Apple T6000/T6001 "M1 Pro/Max". + * + * Copyright The Asahi Linux Contributors + */ + + DIE_NODE(pmgr): power-management@28e080000 { + compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x8e080000 0 0x4000>; + }; + + DIE_NODE(pmgr_east): power-management@28e580000 { + compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x8e580000 0 0xc000>; + }; + + DIE_NODE(pmgr_south): power-management@28e680000 { + compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x8e680000 0 0xc000>; + }; + + DIE_NODE(pinctrl_nub): pinctrl@2921f0000 { + compatible = "apple,t6000-pinctrl", "apple,pinctrl"; + reg = <0x2 0x921f0000 0x0 0x4000>; + power-domains = <&DIE_NODE(ps_nub_gpio)>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&DIE_NODE(pinctrl_nub) 0 0 16>; + apple,npins = <16>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 623 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 624 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 625 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 626 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 627 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 628 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 629 IRQ_TYPE_LEVEL_HIGH>; + }; + + DIE_NODE(pmgr_mini): power-management@292280000 { + compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x92280000 0 0x4000>; + }; + + DIE_NODE(pinctrl_aop): pinctrl@293820000 { + compatible = "apple,t6000-pinctrl", "apple,pinctrl"; + reg = <0x2 0x93820000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&DIE_NODE(pinctrl_aop) 0 0 63>; + apple,npins = <63>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 567 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 568 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 569 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 570 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 571 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 572 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 573 IRQ_TYPE_LEVEL_HIGH>; + }; + + DIE_NODE(pinctrl_ap): pinctrl@39b028000 { + compatible = "apple,t6000-pinctrl", "apple,pinctrl"; + reg = <0x3 0x9b028000 0x0 0x4000>; + + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 427 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 428 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 429 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 430 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 431 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 432 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 433 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&clkref>; + power-domains = <&DIE_NODE(ps_gpio)>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&DIE_NODE(pinctrl_ap) 0 0 255>; + apple,npins = <255>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + DIE_NODE(dwc3_0_dart_0): iommu@702f00000 { + compatible = "apple,t6000-dart"; + reg = <0x7 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1194 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_0_dart_1): iommu@702f80000 { + compatible = "apple,t6000-dart"; + reg = <0x7 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1194 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_0): usb@702280000 { + compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x7 0x02280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1190 IRQ_TYPE_LEVEL_HIGH>; + /* dr_mode = "otg"; */ + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_0_dart_0) 0>, + <&DIE_NODE(dwc3_0_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + }; + + DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 { + compatible = "apple,t6000-dart"; + reg = <0xb 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1211 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_1_dart_1): iommu@b02f80000 { + compatible = "apple,t6000-dart"; + reg = <0xb 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1211 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_1): usb@b02280000 { + compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0xb 0x02280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1207 IRQ_TYPE_LEVEL_HIGH>; + /* dr_mode = "otg"; */ + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_1_dart_0) 0>, + <&DIE_NODE(dwc3_1_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + }; + + DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 { + compatible = "apple,t6000-dart"; + reg = <0xf 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1228 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_2_dart_1): iommu@f02f80000 { + compatible = "apple,t6000-dart"; + reg = <0xf 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1228 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_2): usb@f02280000 { + compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0xf 0x02280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1224 IRQ_TYPE_LEVEL_HIGH>; + /* dr_mode = "otg"; */ + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_2_dart_0) 0>, + <&DIE_NODE(dwc3_2_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + }; + + DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 { + compatible = "apple,t6000-dart"; + reg = <0x13 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1245 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_3_dart_1): iommu@1302f80000 { + compatible = "apple,t6000-dart"; + reg = <0x13 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1245 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_3): usb@1302280000 { + compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x13 0x02280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1241 IRQ_TYPE_LEVEL_HIGH>; + /* dr_mode = "otg"; */ + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_3_dart_0) 0>, + <&DIE_NODE(dwc3_3_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + }; diff --git a/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi b/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi new file mode 100644 index 000000000000..1a994c3c1b79 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * GPIO pin mappings for Apple T600x SoCs. + * + * Copyright The Asahi Linux Contributors + */ + +&pinctrl_ap { + i2c0_pins: i2c0-pins { + pinmux = <APPLE_PINMUX(92, 1)>, + <APPLE_PINMUX(93, 1)>; + }; + + i2c1_pins: i2c1-pins { + pinmux = <APPLE_PINMUX(94, 1)>, + <APPLE_PINMUX(95, 1)>; + }; + + i2c2_pins: i2c2-pins { + pinmux = <APPLE_PINMUX(96, 1)>, + <APPLE_PINMUX(97, 1)>; + }; + + i2c3_pins: i2c3-pins { + pinmux = <APPLE_PINMUX(98, 1)>, + <APPLE_PINMUX(99, 1)>; + }; + + i2c4_pins: i2c4-pins { + pinmux = <APPLE_PINMUX(8, 1)>, + <APPLE_PINMUX(9, 1)>; + }; + + i2c5_pins: i2c5-pins { + pinmux = <APPLE_PINMUX(100, 1)>, + <APPLE_PINMUX(101, 1)>; + }; + + spi1_pins: spi1-pins { + pinmux = <APPLE_PINMUX(10, 1)>, + <APPLE_PINMUX(11, 1)>, + <APPLE_PINMUX(32, 1)>, + <APPLE_PINMUX(33, 1)>; + }; + + spi3_pins: spi3-pins { + pinmux = <APPLE_PINMUX(52, 1)>, + <APPLE_PINMUX(53, 1)>, + <APPLE_PINMUX(54, 1)>, + <APPLE_PINMUX(55, 1)>; + }; + + pcie_pins: pcie-pins { + pinmux = <APPLE_PINMUX(0, 1)>, + <APPLE_PINMUX(1, 1)>, + <APPLE_PINMUX(2, 1)>, + <APPLE_PINMUX(3, 1)>; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi new file mode 100644 index 000000000000..bbe66ea64f09 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (14/16-inch, 2021) + * + * This file contains the parts common to J314 and J316 devices with both t6000 and t6001. + * + * target-type: J314s / J314c / J316s / J316c + * + * Copyright The Asahi Linux Contributors + */ + +/ { + aliases { + serial0 = &serial0; + wifi0 = &wifi0; + bluetooth0 = &bluetooth0; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stdout-path = "serial0"; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; /* To be filled by loader */ + /* Format properties will be added by loader */ + backlight = <&backlight>; + status = "disabled"; + }; + }; + + memory@10000000000 { + device_type = "memory"; + reg = <0x100 0 0x2 0>; /* To be filled by loader */ + }; +}; + +&serial0 { + status = "okay"; +}; + +/* USB Type C */ +&i2c0 { + hpm0: usb-pd@38 { + compatible = "apple,cd321x"; + reg = <0x38>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Rear"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_con_hs: endpoint { + remote-endpoint = <&typec0_usb_hs>; + }; + }; + }; + }; + }; + + hpm1: usb-pd@3f { + compatible = "apple,cd321x"; + reg = <0x3f>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Front"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&typec1_usb_hs>; + }; + }; + }; + }; + }; + + hpm2: usb-pd@3b { + compatible = "apple,cd321x"; + reg = <0x3b>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec2_con_hs: endpoint { + remote-endpoint = <&typec2_usb_hs>; + }; + }; + }; + }; + }; + + /* MagSafe port */ + hpm5: usb-pd@3a { + compatible = "apple,cd321x"; + reg = <0x3a>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <174 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + }; +}; + +&i2c1 { + status = "okay"; + + speaker_left_tweet: codec@3a { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3a>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Tweeter"; + }; + + speaker_left_woof1: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 1"; + }; + + speaker_left_woof2: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 2"; + }; +}; + +&i2c3 { + status = "okay"; + + speaker_right_tweet: codec@3d { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3d>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Tweeter"; + }; + + speaker_right_woof1: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 1"; + }; + + speaker_right_woof2: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 2"; + }; +}; + +&spi1 { + status = "disabled"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0x0>; + spi-max-frequency = <25000000>; + #address-cells = <1>; + #size-cells = <1>; + }; +}; + +&spi3 { + status = "okay"; + + hid-transport@0 { + compatible = "apple,spi-hid-transport"; + reg = <0>; + spi-max-frequency = <8000000>; + /* + * cs-setup and cs-hold delays are derived from Apple's ADT + * Mac OS driver meta data secify 45 us for 'cs to clock' and + * 'clock to cs' delays. + */ + spi-cs-setup-delay-ns = <20000>; + spi-cs-hold-delay-ns = <20000>; + spi-cs-inactive-delay-ns = <250000>; + spien-gpios = <&pinctrl_ap 194 0>; + interrupts-extended = <&pinctrl_nub 6 IRQ_TYPE_LEVEL_LOW>; + }; +}; + +/* PCIe devices */ +&port00 { + /* WLAN */ + bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + wifi0: wifi@0,0 { + compatible = "pci14e4,4433"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 10]; + apple,antenna-sku = "XX"; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f71"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + }; +}; + +&port01 { + /* SD card reader */ + bus-range = <2 2>; + pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + sdhci0: mmc@0,0 { + compatible = "pci17a0,9755"; + reg = <0x20000 0x0 0x0 0x0 0x0>; + cd-inverted; + wp-inverted; + }; +}; + +&pcie0_dart_2 { + status = "disabled"; +}; + +&pcie0_dart_3 { + status = "disabled"; +}; + +/delete-node/ &port02; +/delete-node/ &port03; + +/* USB controllers */ +&dwc3_0 { + port { + typec0_usb_hs: endpoint { + remote-endpoint = <&typec0_con_hs>; + }; + }; +}; + +&dwc3_1 { + port { + typec1_usb_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; + }; +}; + +&dwc3_2 { + port { + typec2_usb_hs: endpoint { + remote-endpoint = <&typec2_con_hs>; + }; + }; +}; + +/* ATC3 is used for DisplayPort -> HDMI only */ +&dwc3_3_dart_0 { + status = "disabled"; +}; + +&dwc3_3_dart_1 { + status = "disabled"; +}; + +&dwc3_3 { + status = "disabled"; +}; + +/ { + backlight: gpio-bl { + compatible = "gpio-backlight"; + gpios = <&smc_gpio 19 GPIO_ACTIVE_HIGH>; + default-on; + }; + + sound { + compatible = "apple,j314-macaudio", "apple,macaudio"; + model = "MacBook Pro J314/6 integrated audio"; + + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + dai-link@0 { + link-name = "Speakers"; + mclk-fs = <256>; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_woof1>, + <&speaker_right_woof1>, + <&speaker_left_tweet>, + <&speaker_right_tweet>, + <&speaker_left_woof2>, + <&speaker_right_woof2>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t600x-nvme.dtsi b/arch/arm64/boot/dts/apple/t600x-nvme.dtsi new file mode 100644 index 000000000000..04c89593735d --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-nvme.dtsi @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * NVMe related devices for Apple T600x SoCs. + * + * Copyright The Asahi Linux Contributors + */ + + DIE_NODE(ans_mbox): mbox@38f408000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x8f408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ DIE_NO 1069 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 1070 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 1071 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ DIE_NO 1072 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + power-domains = <&DIE_NODE(ps_ans2)>; + #mbox-cells = <0>; + }; + + DIE_NODE(sart): sart@393c50000 { + compatible = "apple,t6000-sart", "apple,sart3"; + reg = <0x3 0x93c50000 0x0 0x10000>; + power-domains = <&DIE_NODE(ps_ans2)>; + }; + + DIE_NODE(nvme): nvme@393cc0000 { + compatible = "apple,t6000-nvme-ans2", "apple,nvme-ans2"; + reg = <0x3 0x93cc0000 0x0 0x40000>, <0x3 0x8f400000 0x0 0x4000>; + reg-names = "nvme", "ans"; + interrupt-parent = <&aic>; + /* The NVME interrupt is always routed to die */ + interrupts = <AIC_IRQ 0 1613 IRQ_TYPE_LEVEL_HIGH>; + mboxes = <&DIE_NODE(ans_mbox)>; + apple,sart = <&DIE_NODE(sart)>; + power-domains = <&DIE_NODE(ps_ans2)>, + <&DIE_NODE(ps_apcie_st_sys)>, + <&DIE_NODE(ps_apcie_st1_sys)>; + power-domain-names = "ans", "apcie0", "apcie1"; + resets = <&DIE_NODE(ps_ans2)>; + }; diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi new file mode 100644 index 000000000000..5a041501da5b --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -0,0 +1,2013 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * PMGR Power domains for the Apple T6001 "M1 Max" SoC + * + * Copyright The Asahi Linux Contributors + */ + +&DIE_NODE(pmgr) { + DIE_NODE(ps_pms_bridge): power-controller@100 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pms_bridge); + apple,always-on; /* Core device */ + }; + + DIE_NODE(ps_aic): power-controller@108 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(aic); + apple,always-on; /* Core device */ + }; + + DIE_NODE(ps_dwi): power-controller@110 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dwi); + apple,always-on; /* Core device */ + }; + + DIE_NODE(ps_pms): power-controller@118 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pms); + apple,always-on; /* Core device */ + }; + + DIE_NODE(ps_gpio): power-controller@120 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(gpio); + power-domains = <&DIE_NODE(ps_pms)>, <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_soc_dpe): power-controller@128 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(soc_dpe); + apple,always-on; /* Core device */ + }; + + DIE_NODE(ps_pmgr_soc_ocla): power-controller@130 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pmgr_soc_ocla); + power-domains = <&DIE_NODE(ps_pms)>; + }; + + DIE_NODE(ps_pcie0_ref): power-controller@138 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x138 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pcie0_ref); + }; + + DIE_NODE(ps_pcie1_ref): power-controller@140 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pcie1_ref); + }; + + DIE_NODE(ps_apcie_st): power-controller@148 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(apcie_st); + power-domains = <&DIE_NODE(ps_pcie1_ref)>; + }; + + DIE_NODE(ps_apcie_gp): power-controller@150 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(apcie_gp); + power-domains = <&DIE_NODE(ps_pcie0_ref)>; + }; + + DIE_NODE(ps_devc0_ivdmc): power-controller@180 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(devc0_ivdmc); + }; + + DIE_NODE(ps_amcc0): power-controller@188 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc0); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_amcc2): power-controller@190 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc2); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_dcs_00): power-controller@198 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_00); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_01): power-controller@1a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_01); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_02): power-controller@1a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_02); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_03): power-controller@1b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_03); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_08): power-controller@1b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_08); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_09): power-controller@1c0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_09); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_10): power-controller@1c8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_10); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_11): power-controller@1d0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_11); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_afi): power-controller@1d8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afi); + apple,always-on; /* Apple Fabric, CPU inteface is here */ + }; + + DIE_NODE(ps_afc): power-controller@1e0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afc); + apple,always-on; /* Apple Fabric, CPU inteface is here */ + }; + + DIE_NODE(ps_afr): power-controller@1e8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afr); + /* Apple Fabric, media DIE_NODE(stuff): this can power down */ + }; + + DIE_NODE(ps_afnc1_ioa): power-controller@1f0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc1_ioa); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_afnc0_ioa): power-controller@1f8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc0_ioa); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_afnc1_ls): power-controller@200 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc1_ls); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc1_ioa)>; + }; + + DIE_NODE(ps_afnc0_ls): power-controller@208 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x208 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc0_ls); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc0_ioa)>; + }; + + DIE_NODE(ps_afnc1_lw0): power-controller@210 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc1_lw0); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc1_ls)>; + }; + + DIE_NODE(ps_afnc1_lw1): power-controller@218 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x218 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc1_lw1); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc1_ls)>; + }; + + DIE_NODE(ps_afnc1_lw2): power-controller@220 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x220 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc1_lw2); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc1_ls)>; + }; + + DIE_NODE(ps_afnc0_lw0): power-controller@228 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc0_lw0); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc0_ls)>; + }; + + DIE_NODE(ps_scodec): power-controller@230 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x230 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(scodec); + power-domains = <&DIE_NODE(ps_afnc1_lw0)>; + }; + + DIE_NODE(ps_atc0_common): power-controller@238 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x238 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_common); + power-domains = <&DIE_NODE(ps_afnc1_lw1)>; + }; + + DIE_NODE(ps_atc1_common): power-controller@240 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x240 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_common); + power-domains = <&DIE_NODE(ps_afnc1_lw1)>; + }; + + DIE_NODE(ps_c0_usb31drd): power-controller@248 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x248 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(c0_usb31drd); + power-domains = <&DIE_NODE(ps_usb)>; + }; + + DIE_NODE(ps_c1_usb31drd): power-controller@250 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(c1_usb31drd); + power-domains = <&DIE_NODE(ps_usb)>; + }; + + DIE_NODE(ps_dispext0_fe): power-controller@258 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext0_fe); + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_dispext1_fe): power-controller@260 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext1_fe); + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_ane_sys): power-controller@268 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ane_sys); + power-domains = <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_avd_sys): power-controller@270 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(avd_sys); + power-domains = <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_dispext0_cpu0): power-controller@280 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x280 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext0_cpu0); + power-domains = <&DIE_NODE(ps_dispext0_fe)>; + }; + + DIE_NODE(ps_dispext1_cpu0): power-controller@2a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext1_cpu0); + power-domains = <&DIE_NODE(ps_dispext1_fe)>; + }; + + DIE_NODE(ps_ane_sys_cpu): power-controller@2c8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ane_sys_cpu); + power-domains = <&DIE_NODE(ps_ane_sys)>; + }; + +#if DIE_NO == 0 + /* PMP is only present on die 0 of the M1 Ultra */ + DIE_NODE(ps_pmp): power-controller@2d8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pmp); + }; +#endif + + DIE_NODE(ps_pms_sram): power-controller@2e0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(pms_sram); + }; + + DIE_NODE(ps_apcie_st_sys): power-controller@2e8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(apcie_st_sys); + power-domains = <&DIE_NODE(ps_apcie_st)>, <&DIE_NODE(ps_ans2)>; + }; + + DIE_NODE(ps_apcie_st1_sys): power-controller@2f0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(apcie_st1_sys); + power-domains = <&DIE_NODE(ps_apcie_st)>, <&DIE_NODE(ps_ans2)>; + }; + + DIE_NODE(ps_atc2_common): power-controller@2f8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_common); + power-domains = <&DIE_NODE(ps_afnc1_lw1)>; + }; + + DIE_NODE(ps_atc3_common): power-controller@300 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x300 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_common); + power-domains = <&DIE_NODE(ps_afnc1_lw1)>; + }; + + DIE_NODE(ps_usb): power-controller@318 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x318 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(usb); + power-domains = <&DIE_NODE(ps_afnc1_lw2)>; + }; + + DIE_NODE(ps_apcie_gp_sys): power-controller@320 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x320 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(apcie_gp_sys); + power-domains = <&DIE_NODE(ps_afnc1_lw2)>, <&DIE_NODE(ps_apcie_gp)>; + apple,always-on; /* Breaks things if shut down */ + }; + + DIE_NODE(ps_atc0_cio): power-controller@328 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x328 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_cio); + power-domains = <&DIE_NODE(ps_atc0_common)>; + }; + + DIE_NODE(ps_atc0_pcie): power-controller@330 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x330 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_pcie); + power-domains = <&DIE_NODE(ps_atc0_common)>; + }; + + DIE_NODE(ps_atc1_cio): power-controller@338 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x338 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_cio); + power-domains = <&DIE_NODE(ps_atc1_common)>; + }; + + DIE_NODE(ps_atc1_pcie): power-controller@340 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_pcie); + power-domains = <&DIE_NODE(ps_atc1_common)>; + }; + + DIE_NODE(ps_atc2_cio): power-controller@348 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_cio); + power-domains = <&DIE_NODE(ps_atc2_common)>; + }; + + DIE_NODE(ps_atc2_pcie): power-controller@350 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_pcie); + power-domains = <&DIE_NODE(ps_atc2_common)>; + }; + + DIE_NODE(ps_atc3_cio): power-controller@358 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x358 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_cio); + power-domains = <&DIE_NODE(ps_atc3_common)>; + }; + + DIE_NODE(ps_atc3_pcie): power-controller@360 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x360 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_pcie); + power-domains = <&DIE_NODE(ps_atc3_common)>; + }; + + DIE_NODE(ps_c0_usbctl): power-controller@368 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x368 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(c0_usbctl); + power-domains = <&DIE_NODE(ps_usb)>; + }; + + DIE_NODE(ps_c1_usbctl): power-controller@370 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x370 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(c1_usbctl); + power-domains = <&DIE_NODE(ps_usb)>; + }; + + DIE_NODE(ps_atc0_cio_pcie): power-controller@378 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x378 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_cio_pcie); + power-domains = <&DIE_NODE(ps_atc0_cio)>; + }; + + DIE_NODE(ps_atc0_cio_usb): power-controller@380 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x380 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_cio_usb); + power-domains = <&DIE_NODE(ps_atc0_cio)>; + }; + + DIE_NODE(ps_atc1_cio_pcie): power-controller@388 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x388 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_cio_pcie); + power-domains = <&DIE_NODE(ps_atc1_cio)>; + }; + + DIE_NODE(ps_atc1_cio_usb): power-controller@390 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x390 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_cio_usb); + power-domains = <&DIE_NODE(ps_atc1_cio)>; + }; + + DIE_NODE(ps_atc2_cio_pcie): power-controller@398 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_cio_pcie); + power-domains = <&DIE_NODE(ps_atc2_cio)>; + }; + + DIE_NODE(ps_atc2_cio_usb): power-controller@3a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_cio_usb); + power-domains = <&DIE_NODE(ps_atc2_cio)>; + }; + + DIE_NODE(ps_atc3_cio_pcie): power-controller@3a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_cio_pcie); + power-domains = <&DIE_NODE(ps_atc3_cio)>; + }; + + DIE_NODE(ps_atc3_cio_usb): power-controller@3b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_cio_usb); + power-domains = <&DIE_NODE(ps_atc3_cio)>; + }; + + DIE_NODE(ps_trace_fab): power-controller@3b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(trace_fab); + }; +}; + +&DIE_NODE(pmgr_east) { + DIE_NODE(ps_clvr_spmi0): power-controller@100 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(clvr_spmi0); + apple,always-on; /* PCPU voltage regulator interface (used by SMC) */ + }; + + DIE_NODE(ps_clvr_spmi1): power-controller@108 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(clvr_spmi1); + apple,always-on; /* GPU voltage regulator interface (used by SMC) */ + }; + + DIE_NODE(ps_clvr_spmi2): power-controller@110 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(clvr_spmi2); + apple,always-on; /* ANE, fabric, AFR voltage regulator interface (used by SMC) */ + }; + + DIE_NODE(ps_clvr_spmi3): power-controller@118 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(clvr_spmi3); + apple,always-on; /* Additional voltage regulator, probably used on T6001 (SMC) */ + }; + + DIE_NODE(ps_clvr_spmi4): power-controller@120 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(clvr_spmi4); + apple,always-on; /* Additional voltage regulator, probably used on T6001 (SMC) */ + }; + + DIE_NODE(ps_ispsens0): power-controller@128 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ispsens0); + }; + + DIE_NODE(ps_ispsens1): power-controller@130 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ispsens1); + }; + + DIE_NODE(ps_ispsens2): power-controller@138 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x138 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ispsens2); + }; + + DIE_NODE(ps_ispsens3): power-controller@140 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ispsens3); + }; + + DIE_NODE(ps_afnc2_ioa): power-controller@148 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc2_ioa); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_afnc2_ls): power-controller@150 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc2_ls); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc2_ioa)>; + }; + + DIE_NODE(ps_afnc2_lw0): power-controller@158 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x158 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc2_lw0); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc2_ls)>; + }; + + DIE_NODE(ps_afnc2_lw1): power-controller@160 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x160 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc2_lw1); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc2_ls)>; + }; + + DIE_NODE(ps_afnc3_ioa): power-controller@168 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc3_ioa); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_afnc3_ls): power-controller@170 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x170 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc3_ls); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc3_ioa)>; + }; + + DIE_NODE(ps_afnc3_lw0): power-controller@178 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x178 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc3_lw0); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc3_ls)>; + }; + + DIE_NODE(ps_sio): power-controller@180 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sio); + power-domains = <&DIE_NODE(ps_afnc2_lw1)>; + }; + + DIE_NODE(ps_sio_cpu): power-controller@188 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sio_cpu); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_fpwm0): power-controller@190 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(fpwm0); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_fpwm1): power-controller@198 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(fpwm1); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_fpwm2): power-controller@1a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(fpwm2); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c0): power-controller@1a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c0); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c1): power-controller@1b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c1); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c2): power-controller@1b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c2); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c3): power-controller@1c0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c3); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c4): power-controller@1c8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c4); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c5): power-controller@1d0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c5); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c6): power-controller@1d8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c6); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_i2c7): power-controller@1e0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(i2c7); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_spi_p): power-controller@1e8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(spi_p); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_spi0): power-controller@1f0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(spi0); + power-domains = <&DIE_NODE(ps_spi_p)>; + }; + + DIE_NODE(ps_spi1): power-controller@1f8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(spi1); + power-domains = <&DIE_NODE(ps_spi_p)>; + }; + + DIE_NODE(ps_spi2): power-controller@200 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(spi2); + power-domains = <&DIE_NODE(ps_spi_p)>; + }; + + DIE_NODE(ps_spi3): power-controller@208 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x208 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(spi3); + power-domains = <&DIE_NODE(ps_spi_p)>; + }; + + DIE_NODE(ps_spi4): power-controller@210 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(spi4); + power-domains = <&DIE_NODE(ps_spi_p)>; + }; + + DIE_NODE(ps_sio_spmi0): power-controller@218 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x218 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sio_spmi0); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_sio_spmi1): power-controller@220 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x220 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sio_spmi1); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_sio_spmi2): power-controller@228 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sio_spmi2); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_uart_p): power-controller@230 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x230 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart_p); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_uart_n): power-controller@238 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x238 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart_n); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart0): power-controller@240 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x240 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart0); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart1): power-controller@248 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x248 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart1); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart2): power-controller@250 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart2); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart3): power-controller@258 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart3); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart4): power-controller@260 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart4); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart6): power-controller@268 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart6); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_uart7): power-controller@270 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(uart7); + power-domains = <&DIE_NODE(ps_uart_p)>; + }; + + DIE_NODE(ps_audio_p): power-controller@278 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(audio_p); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_sio_adma): power-controller@280 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x280 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sio_adma); + power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_mca0): power-controller@288 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(mca0); + power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + }; + + DIE_NODE(ps_mca1): power-controller@290 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(mca1); + power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + }; + + DIE_NODE(ps_mca2): power-controller@298 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(mca2); + power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + }; + + DIE_NODE(ps_mca3): power-controller@2a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(mca3); + power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + }; + + DIE_NODE(ps_dpa0): power-controller@2a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dpa0); + power-domains = <&DIE_NODE(ps_audio_p)>; + }; + + DIE_NODE(ps_dpa1): power-controller@2b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dpa1); + power-domains = <&DIE_NODE(ps_audio_p)>; + }; + + DIE_NODE(ps_dpa2): power-controller@2b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dpa2); + power-domains = <&DIE_NODE(ps_audio_p)>; + }; + + DIE_NODE(ps_dpa3): power-controller@2c0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dpa3); + power-domains = <&DIE_NODE(ps_audio_p)>; + }; + + DIE_NODE(ps_dpa4): power-controller@2c8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dpa4); + power-domains = <&DIE_NODE(ps_audio_p)>; + }; + + DIE_NODE(ps_aes): power-controller@2d0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(aes); + power-domains = <&DIE_NODE(ps_sio)>; + }; + + DIE_NODE(ps_amcc1): power-controller@2d8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc1); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_amcc3): power-controller@2e0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc3); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_dcs_04): power-controller@2e8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_04); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_05): power-controller@2f0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_05); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_06): power-controller@2f8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_06); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_07): power-controller@300 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x300 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_07); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_12): power-controller@308 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x308 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_12); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_13): power-controller@310 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x310 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_13); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_14): power-controller@318 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x318 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_14); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_15): power-controller@320 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x320 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_15); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_disp0_fe): power-controller@328 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x328 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(disp0_fe); + power-domains = <&DIE_NODE(ps_afnc2_lw0)>; + apple,always-on; /* TODO: figure out if we can enable PM here */ + }; + + DIE_NODE(ps_disp0_cpu0): power-controller@350 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(disp0_cpu0); + power-domains = <&DIE_NODE(ps_disp0_fe)>; + apple,always-on; /* TODO: figure out if we can enable PM here */ + apple,min-state = <4>; + }; + + DIE_NODE(ps_dispdfr_fe): power-controller@378 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x378 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispdfr_fe); + power-domains = <&DIE_NODE(ps_afnc2_lw1)>; + }; + + DIE_NODE(ps_dispdfr_be): power-controller@380 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x380 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispdfr_be); + power-domains = <&DIE_NODE(ps_dispdfr_fe)>; + }; + + DIE_NODE(ps_mipi_dsi): power-controller@388 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x388 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(mipi_dsi); + power-domains = <&DIE_NODE(ps_dispdfr_be)>; + }; + + DIE_NODE(ps_jpg): power-controller@390 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x390 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(jpg); + power-domains = <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_msr0): power-controller@398 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(msr0); + power-domains = <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_msr0_ase_core): power-controller@3a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(msr0_ase_core); + power-domains = <&DIE_NODE(ps_msr0)>; + }; + + DIE_NODE(ps_isp_sys): power-controller@3a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_sys); + power-domains = <&DIE_NODE(ps_afnc2_lw1)>; + }; + + DIE_NODE(ps_venc_sys): power-controller@3b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc_sys); + power-domains = <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_ans2): power-controller@3b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ans2); + /* + * The ADT makes ps_apcie_st[1]_sys depend on ps_ans2 instead, + * but we'd rather have a single power domain for the downstream + * device to depend on, so use this node as the child. + * This makes more sense anyway (since ANS2 uses APCIE_ST). + */ + power-domains = <&DIE_NODE(ps_afnc2_lw0)>; + }; + + DIE_NODE(ps_gfx): power-controller@3c0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(gfx); + power-domains = <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_sep): power-controller@c00 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(sep); + apple,always-on; /* Locked on */ + }; + + DIE_NODE(ps_venc_dma): power-controller@8000 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc_dma); + power-domains = <&DIE_NODE(ps_venc_sys)>; + }; + + DIE_NODE(ps_venc_pipe4): power-controller@8008 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc_pipe4); + power-domains = <&DIE_NODE(ps_venc_dma)>; + }; + + DIE_NODE(ps_venc_pipe5): power-controller@8010 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc_pipe5); + power-domains = <&DIE_NODE(ps_venc_dma)>; + }; + + DIE_NODE(ps_venc_me0): power-controller@8018 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc_me0); + power-domains = <&DIE_NODE(ps_venc_pipe5)>, <&DIE_NODE(ps_venc_pipe4)>; + }; + + DIE_NODE(ps_venc_me1): power-controller@8020 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc_me1); + power-domains = <&DIE_NODE(ps_venc_me0)>; + }; +}; + +&DIE_NODE(pmgr_south) { + DIE_NODE(ps_amcc4): power-controller@100 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc4); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_amcc5): power-controller@108 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc5); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_amcc6): power-controller@110 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc6); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_amcc7): power-controller@118 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(amcc7); + apple,always-on; /* Memory controller */ + }; + + DIE_NODE(ps_dcs_16): power-controller@120 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_16); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_17): power-controller@128 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_17); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_18): power-controller@130 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_18); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_19): power-controller@138 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x138 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_19); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_20): power-controller@140 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_20); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_21): power-controller@148 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_21); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_22): power-controller@150 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_22); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_23): power-controller@158 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x158 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_23); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_24): power-controller@160 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x160 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_24); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_25): power-controller@168 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_25); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_26): power-controller@170 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x170 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_26); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_27): power-controller@178 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x178 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_27); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_28): power-controller@180 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_28); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_29): power-controller@188 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_29); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_30): power-controller@190 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_30); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_dcs_31): power-controller@198 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dcs_31); + apple,always-on; /* LPDDR5 interface */ + }; + + DIE_NODE(ps_afnc4_ioa): power-controller@1a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc4_ioa); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_afnc4_ls): power-controller@1a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc4_ls); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc4_ioa)>; + }; + + DIE_NODE(ps_afnc4_lw0): power-controller@1b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc4_lw0); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc4_ls)>; + }; + + DIE_NODE(ps_afnc5_ioa): power-controller@1b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc5_ioa); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afi)>; + }; + + DIE_NODE(ps_afnc5_ls): power-controller@1c0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc5_ls); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc5_ioa)>; + }; + + DIE_NODE(ps_afnc5_lw0): power-controller@1c8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(afnc5_lw0); + apple,always-on; /* Apple Fabric */ + power-domains = <&DIE_NODE(ps_afnc5_ls)>; + }; + + DIE_NODE(ps_dispext2_fe): power-controller@1d0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext2_fe); + power-domains = <&DIE_NODE(ps_afnc4_lw0)>; + }; + + DIE_NODE(ps_dispext2_cpu0): power-controller@1e8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext2_cpu0); + power-domains = <&DIE_NODE(ps_dispext2_fe)>; + }; + + DIE_NODE(ps_dispext3_fe): power-controller@210 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext3_fe); + power-domains = <&DIE_NODE(ps_afnc4_lw0)>; + }; + + DIE_NODE(ps_dispext3_cpu0): power-controller@228 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(dispext3_cpu0); + power-domains = <&DIE_NODE(ps_dispext3_fe)>; + }; + + DIE_NODE(ps_msr1): power-controller@250 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(msr1); + power-domains = <&DIE_NODE(ps_afnc5_lw0)>, <&DIE_NODE(ps_afr)>; + }; + + DIE_NODE(ps_msr1_ase_core): power-controller@258 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(msr1_ase_core); + power-domains = <&DIE_NODE(ps_msr1)>; + }; + + DIE_NODE(ps_venc1_sys): power-controller@260 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc1_sys); + power-domains = <&DIE_NODE(ps_afnc5_lw0)>, <&DIE_NODE(ps_afr)>; + }; + + /* Seems to be disabled on shipping hardware */ +#if 0 + DIE_NODE(ps_ane1_sys): power-controller@268 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ane1_sys); + power-domains = <&DIE_NODE(ps_afnc5_lw0)>; + }; + + DIE_NODE(ps_ane1_sys_cpu): power-controller@270 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ane1_sys_cpu); + power-domains = <&DIE_NODE(ps_ane1_sys)>; + }; +#endif + + DIE_NODE(ps_venc1_dma): power-controller@8000 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc1_dma); + power-domains = <&DIE_NODE(ps_venc1_sys)>; + }; + + DIE_NODE(ps_venc1_pipe4): power-controller@8008 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc1_pipe4); + power-domains = <&DIE_NODE(ps_venc1_dma)>; + }; + + DIE_NODE(ps_venc1_pipe5): power-controller@8010 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc1_pipe5); + power-domains = <&DIE_NODE(ps_venc1_dma)>; + }; + + DIE_NODE(ps_venc1_me0): power-controller@8018 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc1_me0); + power-domains = <&DIE_NODE(ps_venc1_pipe4)>, <&DIE_NODE(ps_venc1_pipe5)>; + }; + + DIE_NODE(ps_venc1_me1): power-controller@8020 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(venc1_me1); + power-domains = <&DIE_NODE(ps_venc1_me0)>; + }; + + DIE_NODE(ps_prores): power-controller@c000 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xc000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(prores); + power-domains = <&DIE_NODE(ps_afnc4_lw0)>; + }; +}; + +&DIE_NODE(pmgr_mini) { + DIE_NODE(ps_debug): power-controller@58 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(debug); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_nub_spmi0): power-controller@60 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(nub_spmi0); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_nub_spmi1): power-controller@68 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x68 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(nub_spmi1); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_nub_aon): power-controller@70 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x70 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(nub_aon); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_msg): power-controller@78 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(msg); + }; + + DIE_NODE(ps_nub_gpio): power-controller@80 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(nub_gpio); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_atc0_usb_aon): power-controller@88 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_usb_aon); + apple,always-on; /* Needs to stay on for dwc3 to work */ + }; + + DIE_NODE(ps_atc1_usb_aon): power-controller@90 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x90 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_usb_aon); + apple,always-on; /* Needs to stay on for dwc3 to work */ + }; + + DIE_NODE(ps_atc2_usb_aon): power-controller@98 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x98 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_usb_aon); + apple,always-on; /* Needs to stay on for dwc3 to work */ + }; + + DIE_NODE(ps_atc3_usb_aon): power-controller@a0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xa0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_usb_aon); + apple,always-on; /* Needs to stay on for dwc3 to work */ + }; + + DIE_NODE(ps_gp_usb_aon): power-controller@a8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xa8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(gp_usb_aon); + }; + + DIE_NODE(ps_nub_fabric): power-controller@b0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xb0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(nub_fabric); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_nub_sram): power-controller@b8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(nub_sram); + apple,always-on; /* Core AON device */ + }; + + DIE_NODE(ps_debug_usb): power-controller@c0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xc0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(debug_usb); + apple,always-on; /* Core AON device */ + power-domains = <&DIE_NODE(ps_debug)>; + }; + + DIE_NODE(ps_debug_auth): power-controller@c8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xc8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(debug_auth); + apple,always-on; /* Core AON device */ + power-domains = <&DIE_NODE(ps_debug)>; + }; + + DIE_NODE(ps_atc0_usb): power-controller@d0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xd0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc0_usb); + power-domains = <&DIE_NODE(ps_atc0_usb_aon)>, <&DIE_NODE(ps_atc0_common)>; + }; + + DIE_NODE(ps_atc1_usb): power-controller@d8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xd8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc1_usb); + power-domains = <&DIE_NODE(ps_atc1_usb_aon)>, <&DIE_NODE(ps_atc1_common)>; + }; + + DIE_NODE(ps_atc2_usb): power-controller@e0 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xe0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc2_usb); + power-domains = <&DIE_NODE(ps_atc2_usb_aon)>, <&DIE_NODE(ps_atc2_common)>; + }; + + DIE_NODE(ps_atc3_usb): power-controller@e8 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xe8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(atc3_usb); + power-domains = <&DIE_NODE(ps_atc3_usb_aon)>, <&DIE_NODE(ps_atc3_common)>; + }; +}; + diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 2cd429efba5b..4efbb085e407 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -21,6 +21,26 @@ }; }; +&wifi0 { + brcm,board-type = "apple,atlantisb"; +}; + +&bluetooth0 { + brcm,board-type = "apple,atlantisb"; +}; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-left"; +}; + +&typec1 { + label = "USB-C Back-right"; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader @@ -40,6 +60,58 @@ }; }; +&i2c1 { + speaker_amp: codec@31 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x31>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + }; +}; + &i2c2 { status = "okay"; + + jack_codec: codec@48 { + compatible = "cirrus,cs42l83", "cirrus,cs42l42"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + sound { + compatible = "apple,j274-macaudio", "apple,macaudio"; + model = "Mac mini J274 integrated audio"; + + dai-link@0 { + link-name = "Speaker"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker_amp>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + + }; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 49cdf4b560a3..3a5ae54f0b9f 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -17,6 +17,46 @@ model = "Apple MacBook Pro (13-inch, M1, 2020)"; }; +&wifi0 { + brcm,board-type = "apple,honshu"; +}; + +&bluetooth0 { + brcm,board-type = "apple,honshu"; +}; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + +&spi3 { + status = "okay"; + + hid-transport@0 { + compatible = "apple,spi-hid-transport"; + reg = <0>; + spi-max-frequency = <8000000>; + /* + * cs-setup and cs-hold delays are derived from Apple's ADT + * Mac OS driver meta data secify 45 us for 'cs to clock' and + * 'clock to cs' delays. + */ + spi-cs-setup-delay-ns = <20000>; + spi-cs-hold-delay-ns = <20000>; + spi-cs-inactive-delay-ns = <250000>; + spien-gpios = <&pinctrl_ap 195 0>; + interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>; + }; +}; + /* * Remove unused PCIe ports and disable the associated DARTs. */ @@ -32,10 +72,107 @@ /delete-node/ &port01; /delete-node/ &port02; +&i2c1 { + speaker_left_rear: codec@31 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x31>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Rear"; + }; + + speaker_left_front: codec@32 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x32>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Front"; + }; +}; + &i2c2 { status = "okay"; + + jack_codec: codec@48 { + compatible = "cirrus,cs42l83", "cirrus,cs42l42"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + }; +}; + +&i2c3 { + speaker_right_rear: codec@34 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x34>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Rear"; + }; + + speaker_right_front: codec@35 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x35>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Front"; + }; }; &i2c4 { status = "okay"; }; + +/ { + backlight: gpio-bl { + compatible = "gpio-backlight"; + gpios = <&smc_gpio 18 GPIO_ACTIVE_HIGH>; + default-on; + }; + + sound { + compatible = "apple,j293-macaudio", "apple,macaudio"; + model = "MacBook Pro J293 integrated audio"; + + dai-link@0 { + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + link-name = "Speakers"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_front>, <&speaker_right_front>, + <&speaker_left_rear>, <&speaker_right_rear>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + +&framebuffer0 { + backlight = <&backlight>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index b0ebb45bdb6f..b51f651d2326 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -17,6 +17,46 @@ model = "Apple MacBook Air (M1, 2020)"; }; +&wifi0 { + brcm,board-type = "apple,shikoku"; +}; + +&bluetooth0 { + brcm,board-type = "apple,shikoku"; +}; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + +&spi3 { + status = "okay"; + + hid-transport@0 { + compatible = "apple,spi-hid-transport"; + reg = <0>; + spi-max-frequency = <8000000>; + /* + * cs-setup and cs-hold delays are derived from Apple's ADT + * Mac OS driver meta data secify 45 us for 'cs to clock' and + * 'clock to cs' delays. + */ + spi-cs-setup-delay-ns = <20000>; + spi-cs-hold-delay-ns = <20000>; + spi-cs-inactive-delay-ns = <250000>; + spien-gpios = <&pinctrl_ap 195 0>; + interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>; + }; +}; + /* * Remove unused PCIe ports and disable the associated DARTs. */ @@ -31,3 +71,85 @@ /delete-node/ &port01; /delete-node/ &port02; + +&i2c1 { + speaker_left: codec@31 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x31>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left"; + + }; +}; + +&i2c3 { + speaker_right: codec@34 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x34>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right"; + }; + + jack_codec: codec@48 { + compatible = "cirrus,cs42l83", "cirrus,cs42l42"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + backlight: gpio-bl { + compatible = "gpio-backlight"; + gpios = <&smc_gpio 18 GPIO_ACTIVE_HIGH>; + default-on; + }; + + sound { + compatible = "apple,j313-macaudio", "apple,macaudio"; + model = "MacBook Air J313 integrated audio"; + + dai-link@0 { + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + link-name = "Speakers"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left>, <&speaker_right>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + +&framebuffer0 { + backlight = <&backlight>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 884fddf7d363..5e098e8056e1 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -14,13 +14,21 @@ / { compatible = "apple,j456", "apple,t8103", "apple,arm-platform"; - model = "Apple iMac (24-inch, 4x USB-C, M1, 2020)"; + model = "Apple iMac (24-inch, 4x USB-C, M1, 2021)"; aliases { ethernet0 = ðernet0; }; }; +&wifi0 { + brcm,board-type = "apple,capri"; +}; + +&bluetooth0 { + brcm,board-type = "apple,capri"; +}; + &i2c0 { hpm2: usb-pd@3b { compatible = "apple,cd321x"; @@ -40,6 +48,18 @@ }; /* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-right"; +}; + +&typec1 { + label = "USB-C Back-right-middle"; +}; + +/* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader * (such as MAC addresses). @@ -57,3 +77,34 @@ local-mac-address = [00 10 18 00 00 00]; }; }; + +&i2c1 { + jack_codec: codec@48 { + compatible = "cirrus,cs42l83", "cirrus,cs42l42"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + }; +}; + +/ { + sound { + compatible = "apple,j456-macaudio", "apple,macaudio"; + model = "iMac J456 integrated audio"; + + dai-link@0 { + link-name = "Headphone Jack"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index d7c622931627..f67c0b52075a 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -14,13 +14,33 @@ / { compatible = "apple,j457", "apple,t8103", "apple,arm-platform"; - model = "Apple iMac (24-inch, 2x USB-C, M1, 2020)"; + model = "Apple iMac (24-inch, 2x USB-C, M1, 2021)"; aliases { ethernet0 = ðernet0; }; }; +&wifi0 { + brcm,board-type = "apple,santorini"; +}; + +&bluetooth0 { + brcm,board-type = "apple,santorini"; +}; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-right"; +}; + +&typec1 { + label = "USB-C Back-left"; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader @@ -45,3 +65,43 @@ }; /delete-node/ &port01; + +&i2c1 { + jack_codec: codec@48 { + compatible = "cirrus,cs42l83", "cirrus,cs42l42"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + }; +}; + +/ { + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "iMac integrated audio"; + #address-cells = <1>; + #size-cells = <0>; + + simple-audio-card,dai-link@0 { + bitclock-inversion; + frame-inversion; + reg = <0>; + format = "i2s"; + mclk-fs = <64>; + tdm-slot-width = <32>; + + link0_cpu: cpu { + sound-dai = <&mca 2>; + bitclock-master; + frame-master; + }; + + link0_codec: codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index fe2ae40fa9dd..7a814c7abe0c 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -11,6 +11,7 @@ / { aliases { + bluetooth0 = &bluetooth0; serial0 = &serial0; serial2 = &serial2; wifi0 = &wifi0; @@ -52,6 +53,23 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <106 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_con_hs: endpoint { + remote-endpoint = <&typec0_usb_hs>; + }; + }; + }; + }; }; hpm1: usb-pd@3f { @@ -60,6 +78,40 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <106 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&typec1_usb_hs>; + }; + }; + }; + }; + }; +}; + +/* USB controllers */ +&dwc3_0 { + port { + typec0_usb_hs: endpoint { + remote-endpoint = <&typec0_con_hs>; + }; + }; +}; + +&dwc3_1 { + port { + typec1_usb_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; }; }; @@ -70,9 +122,19 @@ */ &port00 { bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: network@0,0 { + compatible = "pci14e4,4425"; reg = <0x10000 0x0 0x0 0x0 0x0>; /* To be filled by the loader */ local-mac-address = [00 00 00 00 00 00]; + apple,antenna-sku = "XX"; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f69"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; }; }; diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index fc51bc872468..df7f56483ee8 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -387,6 +387,15 @@ power-domains = <&ps_sio>, <&ps_spi_p>; }; + ps_spi4: power-controller@260 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi4"; + power-domains = <&ps_sio>, <&ps_spi_p>; + }; + ps_uart_n: power-controller@268 { compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x268 4>; @@ -558,15 +567,6 @@ apple,always-on; /* Memory controller */ }; - ps_spi4: power-controller@260 { - compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; - reg = <0x260 4>; - #power-domain-cells = <0>; - #reset-cells = <0>; - label = "spi4"; - power-domains = <&ps_sio>, <&ps_spi_p>; - }; - ps_dcs0: power-controller@300 { compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x300 4>; @@ -717,6 +717,7 @@ #reset-cells = <0>; label = "apcie_gp"; power-domains = <&ps_apcie>; + apple,always-on; /* Breaks things if shut down */ }; ps_ans2: power-controller@3f0 { @@ -725,11 +726,6 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "ans2"; - /* - * The ADT makes ps_apcie_st depend on ps_ans2 instead, but this - * doesn't make much sense since ANS2 uses APCIE_ST. - */ - power-domains = <&ps_apcie_st>; }; ps_gfx: power-controller@3f8 { @@ -836,7 +832,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "apcie_st"; - power-domains = <&ps_apcie>; + power-domains = <&ps_apcie>, <&ps_ans2>; }; ps_ane_sys: power-controller@470 { @@ -1108,6 +1104,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc0_usb_aon"; + apple,always-on; /* Needs to stay on for dwc3 to work */ }; ps_atc1_usb_aon: power-controller@90 { @@ -1116,6 +1113,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc1_usb_aon"; + apple,always-on; /* Needs to stay on for dwc3 to work */ }; ps_atc0_usb: power-controller@98 { diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 9f8f4145db88..afb0688cb1c2 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -11,6 +11,7 @@ #include <dt-bindings/interrupt-controller/apple-aic.h> #include <dt-bindings/interrupt-controller/irq.h> #include <dt-bindings/pinctrl/apple.h> +#include <dt-bindings/spmi/spmi.h> / { compatible = "apple,t8103", "apple,arm-platform"; @@ -22,71 +23,245 @@ #address-cells = <2>; #size-cells = <0>; - cpu0: cpu@0 { + cpu-map { + cluster0 { + core0 { + cpu = <&cpu_e0>; + }; + core1 { + cpu = <&cpu_e1>; + }; + core2 { + cpu = <&cpu_e2>; + }; + core3 { + cpu = <&cpu_e3>; + }; + }; + + cluster1 { + core0 { + cpu = <&cpu_p0>; + }; + core1 { + cpu = <&cpu_p1>; + }; + core2 { + cpu = <&cpu_p2>; + }; + core3 { + cpu = <&cpu_p3>; + }; + }; + }; + + cpu_e0: cpu@0 { compatible = "apple,icestorm"; device_type = "cpu"; reg = <0x0 0x0>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 0>; }; - cpu1: cpu@1 { + cpu_e1: cpu@1 { compatible = "apple,icestorm"; device_type = "cpu"; reg = <0x0 0x1>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 0>; }; - cpu2: cpu@2 { + cpu_e2: cpu@2 { compatible = "apple,icestorm"; device_type = "cpu"; reg = <0x0 0x2>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 0>; }; - cpu3: cpu@3 { + cpu_e3: cpu@3 { compatible = "apple,icestorm"; device_type = "cpu"; reg = <0x0 0x3>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; + apple,freq-domain = <&cpufreq_hw 0>; }; - cpu4: cpu@10100 { + cpu_p0: cpu@10100 { compatible = "apple,firestorm"; device_type = "cpu"; reg = <0x0 0x10100>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; }; - cpu5: cpu@10101 { + cpu_p1: cpu@10101 { compatible = "apple,firestorm"; device_type = "cpu"; reg = <0x0 0x10101>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; }; - cpu6: cpu@10102 { + cpu_p2: cpu@10102 { compatible = "apple,firestorm"; device_type = "cpu"; reg = <0x0 0x10102>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; }; - cpu7: cpu@10103 { + cpu_p3: cpu@10103 { compatible = "apple,firestorm"; device_type = "cpu"; reg = <0x0 0x10103>; enable-method = "spin-table"; cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + }; + + ecluster_opp: opp-table-0 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <7500>; + }; + opp02 { + opp-hz = /bits/ 64 <972000000>; + opp-level = <2>; + clock-latency-ns = <22000>; + }; + opp03 { + opp-hz = /bits/ 64 <1332000000>; + opp-level = <3>; + clock-latency-ns = <27000>; + }; + opp04 { + opp-hz = /bits/ 64 <1704000000>; + opp-level = <4>; + clock-latency-ns = <33000>; + }; + opp05 { + opp-hz = /bits/ 64 <2064000000>; + opp-level = <5>; + clock-latency-ns = <50000>; }; }; + pcluster_opp: opp-table-1 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <8000>; + }; + opp02 { + opp-hz = /bits/ 64 <828000000>; + opp-level = <2>; + clock-latency-ns = <19000>; + }; + opp03 { + opp-hz = /bits/ 64 <1056000000>; + opp-level = <3>; + clock-latency-ns = <21000>; + }; + opp04 { + opp-hz = /bits/ 64 <1284000000>; + opp-level = <4>; + clock-latency-ns = <23000>; + }; + opp05 { + opp-hz = /bits/ 64 <1500000000>; + opp-level = <5>; + clock-latency-ns = <24000>; + }; + opp06 { + opp-hz = /bits/ 64 <1728000000>; + opp-level = <6>; + clock-latency-ns = <29000>; + }; + opp07 { + opp-hz = /bits/ 64 <1956000000>; + opp-level = <7>; + clock-latency-ns = <31000>; + }; + opp08 { + opp-hz = /bits/ 64 <2184000000>; + opp-level = <8>; + clock-latency-ns = <34000>; + }; + opp09 { + opp-hz = /bits/ 64 <2388000000>; + opp-level = <9>; + clock-latency-ns = <36000>; + }; + opp10 { + opp-hz = /bits/ 64 <2592000000>; + opp-level = <10>; + clock-latency-ns = <51000>; + }; + opp11 { + opp-hz = /bits/ 64 <2772000000>; + opp-level = <11>; + clock-latency-ns = <54000>; + }; + opp12 { + opp-hz = /bits/ 64 <2988000000>; + opp-level = <12>; + clock-latency-ns = <55000>; + }; +#if 0 + /* Not available until CPU deep sleep is implemented */ + opp13 { + opp-hz = /bits/ 64 <3096000000>; + opp-level = <13>; + clock-latency-ns = <55000>; + turbo-mode; + }; + opp14 { + opp-hz = /bits/ 64 <3144000000>; + opp-level = <14>; + clock-latency-ns = <56000>; + turbo-mode; + }; + opp15 { + opp-hz = /bits/ 64 <3204000000>; + opp-level = <15>; + clock-latency-ns = <56000>; + turbo-mode; + }; +#endif + }; + timer { compatible = "arm,armv8-timer"; interrupt-parent = <&aic>; @@ -116,6 +291,13 @@ clock-output-names = "clkref"; }; + clk_120m: clock-120m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <120000000>; + clock-output-names = "clk_120m"; + }; + soc { compatible = "simple-bus"; #address-cells = <2>; @@ -124,6 +306,15 @@ ranges; nonposted-mmio; + cpufreq_hw: cpufreq@210e20000 { + compatible = "apple,t8103-soc-cpufreq", "apple,soc-cpufreq"; + reg = <0x2 0x10e20000 0 0x1000>, + <0x2 0x11e20000 0 0x1000>; + reg-names = "cluster0", "cluster1"; + + #freq-domain-cells = <1>; + }; + i2c0: i2c@235010000 { compatible = "apple,t8103-i2c", "apple,i2c"; reg = <0x2 0x35010000 0x0 0x4000>; @@ -191,6 +382,20 @@ status = "disabled"; /* only used in J293 */ }; + spi3: spi@23510c000 { + compatible = "apple,t8103-spi", "apple,spi"; + reg = <0x2 0x3510c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 617 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_120m>; + pinctrl-0 = <&spi3_pins>; + pinctrl-names = "default"; + power-domains = <&ps_spi3>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; /* only used in J293/J313 */ + }; + serial0: serial@235200000 { compatible = "apple,s5l-uart"; reg = <0x2 0x35200000 0x0 0x1000>; @@ -229,12 +434,12 @@ affinities { e-core-pmu-affinity { apple,fiq-index = <AIC_CPU_PMU_E>; - cpus = <&cpu0 &cpu1 &cpu2 &cpu3>; + cpus = <&cpu_e0 &cpu_e1 &cpu_e2 &cpu_e3>; }; p-core-pmu-affinity { apple,fiq-index = <AIC_CPU_PMU_P>; - cpus = <&cpu4 &cpu5 &cpu6 &cpu7>; + cpus = <&cpu_p0 &cpu_p1 &cpu_p2 &cpu_p3>; }; }; }; @@ -292,6 +497,13 @@ <APPLE_PINMUX(134, 1)>; }; + spi3_pins: spi3-pins { + pinmux = <APPLE_PINMUX(46, 1)>, + <APPLE_PINMUX(47, 1)>, + <APPLE_PINMUX(48, 1)>, + <APPLE_PINMUX(49, 1)>; + }; + pcie_pins: pcie-pins { pinmux = <APPLE_PINMUX(150, 1)>, <APPLE_PINMUX(151, 1)>, @@ -299,6 +511,81 @@ }; }; + nub_spmi: spmi@23d0d9300 { + compatible = "apple,t8103-spmi", "apple,spmi"; + reg = <0x2 0x3d0d9300 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + + pmu1: pmu@f { + compatible = "apple,sera-pmu", "apple,spmi-pmu"; + reg = <0xf SPMI_USID>; + #address-cells = <1>; + #size-cells = <1>; + + rtc_nvmem@d000 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0xd000 0x300>; + #address-cells = <1>; + #size-cells = <1>; + + pm_setting: pm-setting@1 { + reg = <0x1 0x1>; + }; + + rtc_offset: rtc-offset@100 { + reg = <0x100 0x6>; + }; + }; + + legacy_nvmem@9f00 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0x9f00 0x20>; + #address-cells = <1>; + #size-cells = <1>; + + boot_stage: boot-stage@1 { + reg = <0x1 0x1>; + }; + + boot_error_count: boot-error-count@2 { + reg = <0x2 0x1>; + bits = <0 4>; + }; + + panic_count: panic-count@2 { + reg = <0x2 0x1>; + bits = <4 4>; + }; + + boot_error_stage: boot-error-stage@3 { + reg = <0x3 0x1>; + }; + + shutdown_flag: shutdown-flag@f { + reg = <0xf 0x1>; + bits = <3 1>; + }; + }; + + scrpad_nvmem@a000 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0xa000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + fault_shadow: fault-shadow@67b { + reg = <0x67b 0x10>; + }; + + socd: socd@b00 { + reg = <0xb00 0x400>; + }; + }; + + }; + }; + pinctrl_nub: pinctrl@23d1f0000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x3d1f0000 0x0 0x4000>; @@ -336,6 +623,44 @@ interrupts = <AIC_IRQ 338 IRQ_TYPE_LEVEL_HIGH>; }; + smc_mbox: mbox@23e408000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x3e408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 400 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 401 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 402 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 403 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + smc: smc@23e400000 { + compatible = "apple,t8103-smc", "apple,smc"; + reg = <0x2 0x3e400000 0x0 0x4000>, + <0x2 0x3fe00000 0x0 0x100000>; + reg-names = "smc", "sram"; + mboxes = <&smc_mbox>; + + smc_gpio: gpio { + gpio-controller; + #gpio-cells = <2>; + }; + + smc_rtc: rtc { + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + + smc_reboot: reboot { + nvmem-cells = <&shutdown_flag>, <&boot_stage>, + <&boot_error_count>, <&panic_count>, <&pm_setting>; + nvmem-cell-names = "shutdown_flag", "boot_stage", + "boot_error_count", "panic_count", "pm_setting"; + }; + }; + pinctrl_smc: pinctrl@23e820000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x3e820000 0x0 0x4000>; @@ -378,6 +703,100 @@ <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>; }; + ans_mbox: mbox@277408000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x77408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 583 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 584 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 585 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 586 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_ans2>; + }; + + sart: sart@27bc50000 { + compatible = "apple,t8103-sart", "apple,sart2"; + reg = <0x2 0x7bc50000 0x0 0x10000>; + power-domains = <&ps_ans2>; + }; + + nvme@27bcc0000 { + compatible = "apple,t8103-nvme-ans2", "apple,nvme-ans2"; + reg = <0x2 0x7bcc0000 0x0 0x40000>, + <0x2 0x77400000 0x0 0x4000>; + reg-names = "nvme", "ans"; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 590 IRQ_TYPE_LEVEL_HIGH>; + mboxes = <&ans_mbox>; + apple,sart = <&sart>; + power-domains = <&ps_ans2>, <&ps_apcie_st>; + power-domain-names = "ans", "apcie0"; + resets = <&ps_ans2>; + }; + + dwc3_0: usb@382280000 { + compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x3 0x82280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_0_dart_0: iommu@382f00000 { + compatible = "apple,t8103-dart"; + reg = <0x3 0x82f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 781 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_0_dart_1: iommu@382f80000 { + compatible = "apple,t8103-dart"; + reg = <0x3 0x82f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 781 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_1: usb@502280000 { + compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x5 0x02280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 857 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>; + power-domains = <&ps_atc1_usb>; + }; + + dwc3_1_dart_0: iommu@502f00000 { + compatible = "apple,t8103-dart"; + reg = <0x5 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 861 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + dwc3_1_dart_1: iommu@502f80000 { + compatible = "apple,t8103-dart"; + reg = <0x5 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 861 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart_0: dart@681008000 { compatible = "apple,t8103-dart"; reg = <0x6 0x81008000 0x0 0x4000>; @@ -498,6 +917,79 @@ <0 0 0 4 &port02 0 0 0 3>; }; }; + + dart_sio: iommu@235004000 { + compatible = "apple,t8103-dart", "apple,dart"; + reg = <0x2 0x35004000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 635 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_sio_cpu>; + }; + + nco_inp: clock-ref { + compatible = "fixed-factor-clock"; + clocks = <&clkref>; + #clock-cells = <0>; + clock-mult = <75>; + clock-div = <2>; // 24 MHz * (75/2) = 900 MHz + clock-output-names = "nco_inp"; + }; + + nco: nco@23b044000 { + compatible = "apple,t8103-nco", "apple,nco"; + reg = <0x2 0x3b044000 0x0 0x14000>; + clocks = <&nco_inp>; + #clock-cells = <1>; + apple,nchannels = <5>; + }; + + admac: dma-controller@238200000 { + compatible = "apple,t8103-admac", "apple,admac"; + reg = <0x2 0x38200000 0x0 0x34000>; + dma-channels = <24>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 626 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + iommus = <&dart_sio 2>; + power-domains = <&ps_sio_adma>; + apple,internal-irq-destination = <1>; + }; + + mca: mca@38400000 { + compatible = "apple,t8103-mca", "apple,mca"; + reg = <0x2 0x38400000 0x0 0x18000>, + <0x2 0x38300000 0x0 0x30000>; + reg-names = "clusters", "switch"; + + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 619 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 620 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 621 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 622 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 623 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 624 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&nco 0>, <&nco 1>, <&nco 2>, + <&nco 3>, <&nco 4>, <&nco 4>; + power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>, + <&ps_mca2>, <&ps_mca3>, <&ps_mca4>, <&ps_mca5>; + dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>, + <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>, + <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>, + <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>, + <&admac 16>, <&admac 17>, <&admac 18>, <&admac 19>, + <&admac 20>, <&admac 21>, <&admac 22>, <&admac 23>; + dma-names = "tx0a", "rx0a", "tx0b", "rx0b", + "tx1a", "rx1a", "tx1b", "rx1b", + "tx2a", "rx2a", "tx2b", "rx2b", + "tx3a", "rx3a", "tx3b", "rx3b", + "tx4a", "rx4a", "tx4b", "rx4b", + "tx5a", "rx5a", "tx5b", "rx5b"; + + #sound-dai-cells = <1>; + apple,nclusters = <6>; + }; }; }; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts new file mode 100644 index 000000000000..0d4306a39fe8 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple MacBook Air (M2, 2022) + * + * target-type: J413 + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t8112.dtsi" +#include "t8112-jxxx.dtsi" + +/ { + compatible = "apple,j413", "apple,t8112", "apple,arm-platform"; + model = "Apple MacBook Air (13-inch, M2, 2022)"; + + aliases { + wifi0 = &wifi0; + bluetooth0 = &bluetooth0; + }; +}; + +/* + * Force the bus number assignments so that we can declare some of the + * on-board devices and properties that are populated by the bootloader + * (such as MAC addresses). + */ +&port00 { + bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + wifi0: wifi@0,0 { + compatible = "pci14e4,4433"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 10]; + apple,antenna-sku = "XX"; + brcm,board-type = "apple,hokkaido"; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f71"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + brcm,board-type = "apple,hokkaido"; + }; +}; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + +/* + * Remove unused PCIe ports and disable the associated DARTs. + */ + +/delete-node/ &port01; +/delete-node/ &port02; +/delete-node/ &port03; + +&i2c0 { + /* MagSafe port */ + hpm5: usb-pd@3a { + compatible = "apple,cd321x"; + reg = <0x3a>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + }; +}; + +&i2c1 { + speaker_left_rear: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Rear"; + }; + + speaker_left_front: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Front"; + }; +}; + +&i2c2 { + status = "okay"; +}; + +&i2c3 { + speaker_right_rear: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Rear"; + }; + + speaker_right_front: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Front"; + }; +}; + +&i2c4 { + status = "okay"; +}; + +/ { + backlight: gpio-bl { + compatible = "gpio-backlight"; + gpios = <&smc_gpio 18 GPIO_ACTIVE_HIGH>; + default-on; + }; + + sound { + compatible = "apple,j413-macaudio", "apple,macaudio"; + model = "MacBook Pro J413 integrated audio"; + + dai-link@0 { + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + link-name = "Speakers"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_front>, <&speaker_right_front>, + <&speaker_left_rear>, <&speaker_right_rear>; + }; + }; +#if 0 + dai-link@1 { + link-name = "Headphone Jack"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; +#endif + }; +}; + +&framebuffer0 { + backlight = <&backlight>; +}; + +&mtp_hid { + multi-touch { + firmware-name = "apple/tpmtfw-j413.bin"; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts new file mode 100644 index 000000000000..aa8c045cd1ac --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple MacBook Pro (13-inch, M1, 2022) + * + * target-type: J493 + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t8112.dtsi" +#include "t8112-jxxx.dtsi" + +/ { + compatible = "apple,j493", "apple,t8112", "apple,arm-platform"; + model = "Apple MacBook Pro (13-inch, M2, 2022)"; + + aliases { + wifi0 = &wifi0; + bluetooth0 = &bluetooth0; + }; +}; + +/* + * Force the bus number assignments so that we can declare some of the + * on-board devices and properties that are populated by the bootloader + * (such as MAC addresses). + */ +&port00 { + bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + wifi0: network@0,0 { + compatible = "pci14e4,4425"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 00 00 00 00 00]; + apple,antenna-sku = "XX"; + brcm,board-type = "apple,kyushu"; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f69"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + brcm,board-type = "apple,kyushu"; + }; +}; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + +/* + * Remove unused PCIe ports and disable the associated DARTs. + */ + +/delete-node/ &port01; +/delete-node/ &port02; +/delete-node/ &port03; + +&i2c1 { + speaker_left_rear: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Rear"; + }; + + speaker_left_front: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Front"; + }; +}; + +&i2c2 { + status = "okay"; +}; + +&i2c3 { + speaker_right_rear: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Rear"; + }; + + speaker_right_front: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Front"; + }; +}; + +&i2c4 { + status = "okay"; +}; + +/ { + backlight: gpio-bl { + compatible = "gpio-backlight"; + gpios = <&smc_gpio 18 GPIO_ACTIVE_HIGH>; + default-on; + }; + + sound { + compatible = "apple,j493-macaudio", "apple,macaudio"; + model = "MacBook Pro J493 integrated audio"; + + dai-link@0 { + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + + link-name = "Speakers"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_front>, <&speaker_right_front>, + <&speaker_left_rear>, <&speaker_right_rear>; + }; + }; +#if 0 + dai-link@1 { + link-name = "Headphone Jack"; + mclk-fs = <64>; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; +#endif + }; +}; + +&framebuffer0 { + backlight = <&backlight>; +}; + +&mtp_hid { + multi-touch { + firmware-name = "apple/tpmtfw-j493.bin"; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi new file mode 100644 index 000000000000..a66f14f795b1 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple M2 MacBook Air/Pro (M2, 2022) + * + * This file contains parts common to all Apple M2 devices using the t8112. + * + * target-type: J493, J413 + * + * Copyright The Asahi Linux Contributors + */ + +/ { + aliases { + serial0 = &serial0; + serial2 = &serial2; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stdout-path = "serial0"; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; /* To be filled by loader */ + /* Format properties will be added by loader */ + status = "disabled"; + }; + }; + + memory@800000000 { + device_type = "memory"; + reg = <0x8 0 0x2 0>; /* To be filled by loader */ + }; +}; + +&serial0 { + status = "okay"; +}; + +&serial2 { + status = "okay"; +}; + +&i2c0 { + hpm0: usb-pd@38 { + compatible = "apple,cd321x"; + reg = <0x38>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_con_hs: endpoint { + remote-endpoint = <&typec0_usb_hs>; + }; + }; + }; + }; + }; + + hpm1: usb-pd@3f { + compatible = "apple,cd321x"; + reg = <0x3f>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&typec1_usb_hs>; + }; + }; + }; + }; + }; +}; + +/* USB controllers */ +&dwc3_0 { + port { + typec0_usb_hs: endpoint { + remote-endpoint = <&typec0_con_hs>; + }; + }; +}; + +&dwc3_1 { + port { + typec1_usb_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; + }; +}; + +&mtp { + status = "okay"; +}; +&mtp_mbox{ + status = "okay"; +}; +&mtp_dart { + status = "okay"; +}; +&mtp_dockchannel { + status = "okay"; +}; +&mtp_hid { + multi-touch { + apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>; + }; + + keyboard { + }; + + stm { + apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>; + }; + + actuator { + }; + + tp_accel { + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi new file mode 100644 index 000000000000..0f2d810921c8 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -0,0 +1,1141 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * PMGR Power domains for the Apple T8112 "M2" SoC + * + * Copyright The Asahi Linux Contributors + */ + + +&pmgr { + ps_sbr: power-controller@100 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sbr"; + apple,always-on; /* Core device */ + }; + + ps_aic: power-controller@108 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aic"; + apple,always-on; /* Core device */ + }; + + ps_dwi: power-controller@110 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dwi"; + apple,always-on; /* Core device */ + }; + + ps_soc_spmi0: power-controller@118 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "soc_spmi0"; + }; + + ps_gpio: power-controller@120 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpio"; + }; + + ps_pms_busif: power-controller@128 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_busif"; + apple,always-on; /* Core device */ + }; + + ps_pms: power-controller@130 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms"; + apple,always-on; /* Core device */ + }; + + ps_pms_c1ppt: power-controller@160 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x160 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_c1ppt"; + power-domains = <&ps_pms>; + }; + + ps_soc_dpe: power-controller@168 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "soc_dpe"; + apple,always-on; /* Core device */ + }; + + ps_pmgr_soc_ocla: power-controller@170 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x170 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmgr_soc_ocla"; + power-domains = <&ps_pms>; + }; + + ps_ispsens0: power-controller@178 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x178 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens0"; + }; + + ps_ispsens1: power-controller@180 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens1"; + }; + + ps_ispsens2: power-controller@188 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens2"; + }; + + ps_ispsens3: power-controller@190 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens3"; + }; + + ps_pcie_ref: power-controller@198 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcie_ref"; + }; + + ps_aft0: power-controller@1a0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aft0"; + }; + + ps_imx: power-controller@1a8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "imx"; + apple,always-on; /* Apple fabric, critical block */ + }; + + ps_sio_busif: power-controller@1b0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_busif"; + }; + + ps_sio: power-controller@1b8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio"; + apple,always-on; + power-domains = <&ps_sio_busif>; + }; + + ps_sio_cpu: power-controller@1c0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_cpu"; + power-domains = <&ps_sio>; + }; + + ps_fpwm0: power-controller@1c8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm0"; + power-domains = <&ps_sio>; + }; + + ps_fpwm1: power-controller@1d0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm1"; + power-domains = <&ps_sio>; + }; + + ps_fpwm2: power-controller@1d8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm2"; + power-domains = <&ps_sio>; + }; + + ps_i2c0: power-controller@1e0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c0"; + power-domains = <&ps_sio>; + }; + + ps_i2c1: power-controller@1e8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c1"; + power-domains = <&ps_sio>; + }; + + ps_i2c2: power-controller@1f0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c2"; + power-domains = <&ps_sio>; + }; + + ps_i2c3: power-controller@1f8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c3"; + power-domains = <&ps_sio>; + }; + + ps_i2c4: power-controller@200 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c4"; + power-domains = <&ps_sio>; + }; + + ps_spi_p: power-controller@208 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x208 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi_p"; + power-domains = <&ps_sio>; + }; + + ps_uart_p: power-controller@210 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_p"; + power-domains = <&ps_sio>; + }; + + ps_audio_p: power-controller@218 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x218 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "audio_p"; + power-domains = <&ps_sio>; + }; + + ps_aes: power-controller@220 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x220 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aes"; + power-domains = <&ps_sio>; + }; + + ps_spi0: power-controller@228 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi0"; + power-domains = <&ps_spi_p>; + }; + + ps_spi1: power-controller@230 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x230 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi1"; + power-domains = <&ps_spi_p>; + }; + + ps_spi2: power-controller@238 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x238 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi2"; + power-domains = <&ps_spi_p>; + }; + + ps_spi3: power-controller@240 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x240 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi3"; + power-domains = <&ps_spi_p>; + }; + + ps_spi4: power-controller@248 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x248 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi4"; + power-domains = <&ps_spi_p>; + }; + + ps_spi5: power-controller@250 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi5"; + power-domains = <&ps_spi_p>; + }; + + ps_uart_n: power-controller@258 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_n"; + power-domains = <&ps_uart_p>; + }; + + ps_uart0: power-controller@260 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart0"; + power-domains = <&ps_uart_p>; + }; + + ps_uart1: power-controller@268 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart1"; + power-domains = <&ps_uart_p>; + }; + + ps_uart2: power-controller@270 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart2"; + power-domains = <&ps_uart_p>; + }; + + ps_uart3: power-controller@278 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart3"; + power-domains = <&ps_uart_p>; + }; + + ps_uart4: power-controller@280 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x280 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart4"; + power-domains = <&ps_uart_p>; + }; + + ps_uart5: power-controller@288 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart5"; + power-domains = <&ps_uart_p>; + }; + + ps_uart6: power-controller@290 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart6"; + power-domains = <&ps_uart_p>; + }; + + ps_uart7: power-controller@298 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart7"; + power-domains = <&ps_uart_p>; + }; + + ps_uart8: power-controller@2a0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart8"; + power-domains = <&ps_uart_p>; + }; + + ps_sio_adma: power-controller@2a8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_adma"; + power-domains = <&ps_spi_p>, <&ps_audio_p>; + }; + + ps_dpa0: power-controller@2b0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa0"; + power-domains = <&ps_audio_p>; + }; + + ps_dpa1: power-controller@2b8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa1"; + power-domains = <&ps_audio_p>; + }; + + ps_mca0: power-controller@2c0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca0"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca1: power-controller@2c8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca1"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca2: power-controller@2d0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca2"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca3: power-controller@2d8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca3"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca4: power-controller@2e0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca4"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca5: power-controller@2e8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca5"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mcc: power-controller@2f0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mcc"; + apple,always-on; /* Memory controller */ + }; + + ps_dcs0: power-controller@2f8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x2f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs0"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs2: power-controller@300 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x300 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs2"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs1: power-controller@308 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x308 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs1"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs3: power-controller@310 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x310 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs3"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs4: power-controller@318 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x318 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs4"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs5: power-controller@320 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x320 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs5"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs6: power-controller@328 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x328 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs6"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_dcs7: power-controller@330 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x330 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs7"; + apple,always-on; /* LPDDR4 interface */ + }; + + ps_smx0: power-controller@338 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x338 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "smx0"; + apple,always-on; /* Apple fabric, critical block */ + }; + + ps_smx1: power-controller@340 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "smx1"; + apple,always-on; /* Apple fabric, critical block */ + }; + + ps_apcie: power-controller@348 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie"; + power-domains = <&ps_imx>, <&ps_pcie_ref>; + }; + + ps_rmx0: power-controller@350 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "rmx0"; + /* Apple Fabric, display/image stuff: this can power down */ + }; + + ps_rmx1: power-controller@358 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x358 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "rmx1"; + /* Apple Fabric, display/image stuff: this can power down */ + }; + + ps_cmx: power-controller@360 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x360 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "cmx"; + apple,always-on; /* Apple fabric, critical block */ + }; + + ps_mmx: power-controller@368 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x368 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mmx"; + /* Apple Fabric, media stuff: this can power down */ + }; + + ps_disp0_sys: power-controller@370 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x370 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp0_sys"; + power-domains = <&ps_rmx1>; + apple,always-on; /* TODO: figure out if we can enable PM here */ + }; + + ps_disp0_fe: power-controller@378 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x378 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp0_fe"; + power-domains = <&ps_disp0_sys>; + apple,always-on; /* TODO: figure out if we can enable PM here */ + }; + + ps_dispext_sys: power-controller@380 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x380 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext_sys"; + power-domains = <&ps_rmx0>; + }; + + ps_dispext_fe: power-controller@388 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x388 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext_fe"; + power-domains = <&ps_dispext_sys>; + }; + + ps_dispext_cpu0: power-controller@3c8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext_cpu0"; + power-domains = <&ps_dispext_fe>; + apple,min-state = <4>; + }; + + ps_dptx_ext_phy: power-controller@3d8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dptx_ext_phy"; + }; + + ps_dispdfr_fe: power-controller@3e0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispdfr_fe"; + power-domains = <&ps_rmx0>; + }; + + ps_dispdfr_be: power-controller@3e8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispdfr_be"; + power-domains = <&ps_dispdfr_fe>; + }; + + ps_mipi_dsi: power-controller@3f0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mipi_dsi"; + power-domains = <&ps_dispdfr_be>; + }; + + ps_jpg: power-controller@3f8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x3f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "jpg"; + power-domains = <&ps_cmx>; + }; + + ps_apcie_gp: power-controller@400 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x400 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_gp"; + power-domains = <&ps_apcie>; + apple,always-on; /* Breaks things if shut down */ + }; + + ps_msr: power-controller@408 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x408 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr"; + power-domains = <&ps_imx>; + }; + + ps_pmp: power-controller@410 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x410 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmp"; + apple,always-on; + }; + + ps_pms_sram: power-controller@418 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x418 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_sram"; + apple,always-on; + }; + + ps_msr_ase_core: power-controller@420 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x420 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr_ase_core"; + power-domains = <&ps_msr>; + }; + + ps_ans: power-controller@428 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x428 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ans"; + power-domains = <&ps_imx>; + }; + + ps_gfx: power-controller@430 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x430 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gfx"; + }; + + ps_isp_sys: power-controller@438 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x438 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_sys"; + power-domains = <&ps_rmx1>; + }; + + ps_venc_sys: power-controller@440 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x440 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_sys"; + power-domains = <&ps_rmx1>; + }; + + ps_avd_sys: power-controller@448 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x448 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "avd_sys"; + power-domains = <&ps_mmx>; + }; + + ps_apcie_st: power-controller@450 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x450 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_st"; + power-domains = <&ps_apcie>, <&ps_ans>; + }; + + ps_atc0_common: power-controller@458 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x458 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_common"; + power-domains = <&ps_imx>; + }; + + ps_atc0_pcie: power-controller@460 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x460 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_pcie"; + power-domains = <&ps_atc0_common>; + }; + + ps_atc0_cio: power-controller@468 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x468 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio"; + power-domains = <&ps_atc0_common>; + }; + + ps_atc0_cio_pcie: power-controller@470 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x470 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_pcie"; + power-domains = <&ps_atc0_cio>; + }; + + ps_atc0_cio_usb: power-controller@478 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x478 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_usb"; + power-domains = <&ps_atc0_cio>; + }; + + ps_atc1_common: power-controller@480 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x480 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_common"; + power-domains = <&ps_rmx0>; + }; + + ps_atc1_pcie: power-controller@488 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x488 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_pcie"; + power-domains = <&ps_atc1_common>; + }; + + ps_atc1_cio: power-controller@490 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x490 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio"; + power-domains = <&ps_atc1_common>; + }; + + ps_atc1_cio_pcie: power-controller@498 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x498 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_pcie"; + power-domains = <&ps_atc1_cio>; + }; + + ps_atc1_cio_usb: power-controller@4a0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_usb"; + power-domains = <&ps_atc1_cio>; + }; + + ps_ane_sys: power-controller@4a8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_sys"; + power-domains = <&ps_mmx>; + }; + + ps_scodec: power-controller@4b0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec"; + power-domains = <&ps_rmx0>; + }; + + ps_sep: power-controller@c00 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sep"; + apple,always-on; + }; + + ps_venc_dma: power-controller@8000 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_dma"; + power-domains = <&ps_venc_sys>; + }; + + ps_venc_pipe4: power-controller@8008 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_pipe4"; + power-domains = <&ps_venc_dma>; + }; + + ps_venc_pipe5: power-controller@8010 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_pipe5"; + power-domains = <&ps_venc_dma>; + }; + + ps_venc_me0: power-controller@8018 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_me0"; + power-domains = <&ps_venc_pipe5>, <&ps_venc_pipe4>; + }; + + ps_venc_me1: power-controller@8020 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x8020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_me1"; + power-domains = <&ps_venc_pipe5>, <&ps_venc_pipe4>; + }; + + ps_disp0_cpu0: power-controller@10000 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x10000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp0_cpu0"; + power-domains = <&ps_disp0_fe>; + apple,min-state = <4>; + }; +}; + +&pmgr_mini { + + ps_debug_gated: power-controller@58 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_gated"; + apple,always-on; /* Core AON device */ + }; + + ps_nub_spmi0: power-controller@60 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi0"; + apple,always-on; /* Core AON device */ + }; + + ps_nub_spmi1: power-controller@68 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x68 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi1"; + apple,always-on; /* Core AON device */ + }; + + ps_nub_aon: power-controller@70 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x70 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_aon"; + apple,always-on; /* Core AON device */ + }; + + ps_msg: power-controller@78 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msg"; + }; + + ps_nub_gpio: power-controller@80 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_gpio"; + apple,always-on; + }; + + ps_atc0_usb_aon: power-controller@88 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_usb_aon"; + apple,always-on; /* Needs to stay on for dwc3 to work */ + }; + + ps_atc1_usb_aon: power-controller@90 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x90 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_usb_aon"; + apple,always-on; /* Needs to stay on for dwc3 to work */ + }; + + ps_atc0_usb: power-controller@98 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x98 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_usb"; + power-domains = <&ps_atc0_usb_aon>, <&ps_atc0_common>; + }; + + ps_atc1_usb: power-controller@a0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xa0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_usb"; + power-domains = <&ps_atc1_usb_aon>, <&ps_atc1_common>; + }; + + ps_nub_fabric: power-controller@a8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xa8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_fabric"; + apple,always-on; /* Core AON device */ + }; + + ps_nub_sram: power-controller@b0 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xb0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_sram"; + apple,always-on; /* Core AON device */ + }; + + ps_debug_switch: power-controller@b8 { + compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_switch"; + apple,always-on; /* Core AON device */ + }; +}; + diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi new file mode 100644 index 000000000000..852937ec80ea --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -0,0 +1,1097 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple T8112 "M2" SoC + * + * Other names: H14G + * + * Copyright The Asahi Linux Contributors + */ + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/apple-aic.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/apple.h> +#include <dt-bindings/spmi/spmi.h> + +/ { + compatible = "apple,t8112", "apple,arm-platform"; + + #address-cells = <2>; + #size-cells = <2>; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu-map { + cluster0 { + core0 { + cpu = <&cpu_e0>; + }; + core1 { + cpu = <&cpu_e1>; + }; + core2 { + cpu = <&cpu_e2>; + }; + core3 { + cpu = <&cpu_e3>; + }; + }; + + cluster1 { + core0 { + cpu = <&cpu_p0>; + }; + core1 { + cpu = <&cpu_p1>; + }; + core2 { + cpu = <&cpu_p2>; + }; + core3 { + cpu = <&cpu_p3>; + }; + }; + }; + + cpu_e0: cpu@0 { + compatible = "apple,blizzard"; + device_type = "cpu"; + reg = <0x0 0x0>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; // TODO + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu_e1: cpu@1 { + compatible = "apple,blizzard"; + device_type = "cpu"; + reg = <0x0 0x1>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; // TODO + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu_e2: cpu@2 { + compatible = "apple,blizzard"; + device_type = "cpu"; + reg = <0x0 0x2>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; // TODO + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu_e3: cpu@3 { + compatible = "apple,blizzard"; + device_type = "cpu"; + reg = <0x0 0x3>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&ecluster_opp>; + capacity-dmips-mhz = <714>; // TODO + apple,freq-domain = <&cpufreq_hw 0>; + }; + + cpu_p0: cpu@10100 { + compatible = "apple,avalanche"; + device_type = "cpu"; + reg = <0x0 0x10100>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p1: cpu@10101 { + compatible = "apple,avalanche"; + device_type = "cpu"; + reg = <0x0 0x10101>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p2: cpu@10102 { + compatible = "apple,avalanche"; + device_type = "cpu"; + reg = <0x0 0x10102>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + + cpu_p3: cpu@10103 { + compatible = "apple,avalanche"; + device_type = "cpu"; + reg = <0x0 0x10103>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + operating-points-v2 = <&pcluster_opp>; + capacity-dmips-mhz = <1024>; + apple,freq-domain = <&cpufreq_hw 1>; + }; + }; + + ecluster_opp: opp-table-0 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <600000000>; + opp-level = <1>; + clock-latency-ns = <7500>; + }; + opp02 { + opp-hz = /bits/ 64 <912000000>; + opp-level = <2>; + clock-latency-ns = <20000>; + }; + opp03 { + opp-hz = /bits/ 64 <1284000000>; + opp-level = <3>; + clock-latency-ns = <22000>; + }; + opp04 { + opp-hz = /bits/ 64 <1752000000>; + opp-level = <4>; + clock-latency-ns = <30000>; + }; + opp05 { + opp-hz = /bits/ 64 <2004000000>; + opp-level = <5>; + clock-latency-ns = <35000>; + }; + opp06 { + opp-hz = /bits/ 64 <2256000000>; + opp-level = <6>; + clock-latency-ns = <39000>; + }; + opp07 { + opp-hz = /bits/ 64 <2424000000>; + opp-level = <7>; + clock-latency-ns = <53000>; + }; + }; + + pcluster_opp: opp-table-1 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <660000000>; + opp-level = <1>; + clock-latency-ns = <9000>; + }; + opp02 { + opp-hz = /bits/ 64 <924000000>; + opp-level = <2>; + clock-latency-ns = <19000>; + }; + opp03 { + opp-hz = /bits/ 64 <1188000000>; + opp-level = <3>; + clock-latency-ns = <22000>; + }; + opp04 { + opp-hz = /bits/ 64 <1452000000>; + opp-level = <4>; + clock-latency-ns = <24000>; + }; + opp05 { + opp-hz = /bits/ 64 <1704000000>; + opp-level = <5>; + clock-latency-ns = <26000>; + }; + opp06 { + opp-hz = /bits/ 64 <1968000000>; + opp-level = <6>; + clock-latency-ns = <28000>; + }; + opp07 { + opp-hz = /bits/ 64 <2208000000>; + opp-level = <7>; + clock-latency-ns = <30000>; + }; + opp08 { + opp-hz = /bits/ 64 <2400000000>; + opp-level = <8>; + clock-latency-ns = <33000>; + }; + opp09 { + opp-hz = /bits/ 64 <2568000000>; + opp-level = <9>; + clock-latency-ns = <34000>; + }; + opp10 { + opp-hz = /bits/ 64 <2724000000>; + opp-level = <10>; + clock-latency-ns = <36000>; + }; + opp11 { + opp-hz = /bits/ 64 <2868000000>; + opp-level = <11>; + clock-latency-ns = <41000>; + }; + opp12 { + opp-hz = /bits/ 64 <2988000000>; + opp-level = <12>; + clock-latency-ns = <42000>; + }; + opp13 { + opp-hz = /bits/ 64 <3096000000>; + opp-level = <13>; + clock-latency-ns = <44000>; + }; + opp14 { + opp-hz = /bits/ 64 <3204000000>; + opp-level = <14>; + clock-latency-ns = <46000>; + }; + /* Not available until CPU deep sleep is implemented */ +#if 0 + opp15 { + opp-hz = /bits/ 64 <3324000000>; + opp-level = <15>; + clock-latency-ns = <62000>; + turbo-mode; + }; + opp16 { + opp-hz = /bits/ 64 <3408000000>; + opp-level = <16>; + clock-latency-ns = <62000>; + turbo-mode; + }; + opp17 { + opp-hz = /bits/ 64 <3504000000>; + opp-level = <17>; + clock-latency-ns = <62000>; + turbo-mode; + }; +#endif + }; + + timer { + compatible = "arm,armv8-timer"; + interrupt-parent = <&aic>; + interrupt-names = "phys", "virt", "hyp-phys", "hyp-virt"; + interrupts = <AIC_FIQ AIC_TMR_GUEST_PHYS IRQ_TYPE_LEVEL_HIGH>, + <AIC_FIQ AIC_TMR_GUEST_VIRT IRQ_TYPE_LEVEL_HIGH>, + <AIC_FIQ AIC_TMR_HV_PHYS IRQ_TYPE_LEVEL_HIGH>, + <AIC_FIQ AIC_TMR_HV_VIRT IRQ_TYPE_LEVEL_HIGH>; + }; + + pmu-e { + compatible = "apple,blizzard-pmu"; + interrupt-parent = <&aic>; + interrupts = <AIC_FIQ AIC_CPU_PMU_E IRQ_TYPE_LEVEL_HIGH>; + }; + + pmu-p { + compatible = "apple,avalanche-pmu"; + interrupt-parent = <&aic>; + interrupts = <AIC_FIQ AIC_CPU_PMU_P IRQ_TYPE_LEVEL_HIGH>; + }; + + clkref: clock-ref { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "clkref"; + }; + + clk_120m: clock-120m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <120000000>; + clock-output-names = "clk_120m"; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + + ranges; + nonposted-mmio; + + cpufreq_hw: cpufreq@210e20000 { + compatible = "apple,t8112-soc-cpufreq", "apple,soc-cpufreq"; + reg = <0x2 0x10e20000 0 0x1000>, + <0x2 0x11e20000 0 0x1000>; + reg-names = "cluster0", "cluster1"; + + #freq-domain-cells = <1>; + }; + + i2c0: i2c@235010000 { + compatible = "apple,t8112-i2c", "apple,i2c"; + reg = <0x2 0x35010000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 761 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c0_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c0>; + }; + + i2c1: i2c@235014000 { + compatible = "apple,t8112-i2c", "apple,i2c"; + reg = <0x2 0x35014000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 762 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c1_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c1>; + }; + + i2c2: i2c@235018000 { + compatible = "apple,t8112-i2c", "apple,i2c"; + reg = <0x2 0x35018000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 763 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c2_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + status = "disabled"; /* not used in all devices */ + power-domains = <&ps_i2c2>; + }; + + i2c3: i2c@23501c000 { + compatible = "apple,t8112-i2c", "apple,i2c"; + reg = <0x2 0x3501c000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 764 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c3_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c3>; + }; + + i2c4: i2c@235020000 { + compatible = "apple,t8112-i2c", "apple,i2c"; + reg = <0x2 0x35020000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 765 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-0 = <&i2c4_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c4>; + status = "disabled"; + }; + + spi3: spi@23510c000 { + compatible = "apple,t8112-spi", "apple,spi"; + reg = <0x2 0x3510c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 751 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_120m>; + pinctrl-0 = <&spi3_pins>; + pinctrl-names = "default"; + power-domains = <&ps_spi3>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + serial0: serial@235200000 { + compatible = "apple,s5l-uart"; + reg = <0x2 0x35200000 0x0 0x1000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 739 IRQ_TYPE_LEVEL_HIGH>; + /* + * TODO: figure out the clocking properly, there may + * be a third selectable clock. + */ + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + power-domains = <&ps_uart0>; + status = "disabled"; + }; + + serial2: serial@235208000 { + compatible = "apple,s5l-uart"; + reg = <0x2 0x35208000 0x0 0x1000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 741 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + power-domains = <&ps_uart2>; + status = "disabled"; + }; + + aic: interrupt-controller@23b0c0000 { + compatible = "apple,t8112-aic", "apple,aic2"; + #interrupt-cells = <3>; + interrupt-controller; + reg = <0x2 0x3b0c0000 0x0 0x8000>, + <0x2 0x3b0c8000 0x0 0x4>; + reg-names = "core", "event"; + power-domains = <&ps_aic>; + + affinities { + e-core-pmu-affinity { + apple,fiq-index = <AIC_CPU_PMU_E>; + cpus = <&cpu_e0 &cpu_e1 &cpu_e2 &cpu_e3>; + }; + + p-core-pmu-affinity { + apple,fiq-index = <AIC_CPU_PMU_P>; + cpus = <&cpu_p0 &cpu_p1 &cpu_p2 &cpu_p3>; + }; + }; + }; + + pmgr: power-management@23b700000 { + compatible = "apple,t8112-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x3b700000 0 0x10000>; + }; + + pinctrl_ap: pinctrl@23c100000 { + compatible = "apple,t8112-pinctrl", "apple,pinctrl"; + reg = <0x2 0x3c100000 0x0 0x100000>; + power-domains = <&ps_gpio>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_ap 0 0 213>; + apple,npins = <213>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 199 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 200 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 201 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 202 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 203 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 204 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 205 IRQ_TYPE_LEVEL_HIGH>; + + i2c0_pins: i2c0-pins { + pinmux = <APPLE_PINMUX(111, 1)>, + <APPLE_PINMUX(110, 1)>; + }; + + i2c1_pins: i2c1-pins { + pinmux = <APPLE_PINMUX(113, 1)>, + <APPLE_PINMUX(112, 1)>; + }; + + i2c2_pins: i2c2-pins { + pinmux = <APPLE_PINMUX(87, 1)>, + <APPLE_PINMUX(86, 1)>; + }; + + i2c3_pins: i2c3-pins { + pinmux = <APPLE_PINMUX(54, 1)>, + <APPLE_PINMUX(53, 1)>; + }; + + i2c4_pins: i2c4-pins { + pinmux = <APPLE_PINMUX(131, 1)>, + <APPLE_PINMUX(130, 1)>; + }; + + spi3_pins: spi3-pins { + pinmux = <APPLE_PINMUX(46, 1)>, + <APPLE_PINMUX(47, 1)>, + <APPLE_PINMUX(48, 1)>, + <APPLE_PINMUX(49, 1)>; + }; + + pcie_pins: pcie-pins { + pinmux = <APPLE_PINMUX(162, 1)>; + // TODO: 3 more CLKREQs + }; + }; + + nub_spmi: spmi@23d0d9300 { + compatible = "apple,t8112-spmi", "apple,spmi"; + reg = <0x2 0x3d714000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 256 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 384 IRQ_TYPE_LEVEL_HIGH>; + + pmu1: pmu@e { + compatible = "apple,stowe-pmu", "apple,spmi-pmu"; + reg = <0xe SPMI_USID>; + #address-cells = <1>; + #size-cells = <1>; + + rtc_nvmem@f800 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0xf800 0x300>; + #address-cells = <1>; + #size-cells = <1>; + + pm_setting: pm-setting@1 { + reg = <0x1 0x1>; + }; + + rtc_offset: rtc-offset@100 { + reg = <0x100 0x6>; + }; + }; + + legacy_nvmem@f700 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0xf700 0x20>; + #address-cells = <1>; + #size-cells = <1>; + + boot_stage: boot-stage@1 { + reg = <0x1 0x1>; + }; + + boot_error_count: boot-error-count@2 { + reg = <0x2 0x1>; + bits = <0 4>; + }; + + panic_count: panic-count@2 { + reg = <0x2 0x1>; + bits = <4 4>; + }; + + boot_error_stage: boot-error-stage@3 { + reg = <0x3 0x1>; + }; + + shutdown_flag: shutdown-flag@f { + reg = <0xf 0x1>; + bits = <3 1>; + }; + }; + + scrpad_nvmem@8000 { + compatible = "apple,spmi-pmu-nvmem"; + reg = <0x8000 0x2800>; + #address-cells = <1>; + #size-cells = <1>; + + fault_shadow: fault-shadow@67b { + reg = <0x67b 0x10>; + }; + + socd: socd@b00 { + reg = <0xb00 0x400>; + }; + }; + + }; + }; + + pinctrl_nub: pinctrl@23d1f0000 { + compatible = "apple,t8112-pinctrl", "apple,pinctrl"; + reg = <0x2 0x3d1f0000 0x0 0x4000>; + power-domains = <&ps_nub_gpio>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_nub 0 0 24>; + apple,npins = <24>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 371 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 372 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 373 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 374 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 375 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 376 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 377 IRQ_TYPE_LEVEL_HIGH>; + }; + + pmgr_mini: power-management@23d280000 { + compatible = "apple,t8112-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x3d280000 0 0x4000>; + }; + + wdt: watchdog@23d2b0000 { + compatible = "apple,t8112-wdt", "apple,wdt"; + reg = <0x2 0x3d2b0000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 379 IRQ_TYPE_LEVEL_HIGH>; + }; + + smc_mbox: mbox@23e408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x3e408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 499 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 500 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 501 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 502 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + smc: smc@23e400000 { + compatible = "apple,t8112-smc", "apple,smc"; + reg = <0x2 0x3e400000 0x0 0x4000>, + <0x2 0x3fe00000 0x0 0x100000>; + reg-names = "smc", "sram"; + mboxes = <&smc_mbox>; + + smc_gpio: gpio { + gpio-controller; + #gpio-cells = <2>; + }; + + smc_rtc: rtc { + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + + smc_reboot: reboot { + nvmem-cells = <&shutdown_flag>, <&boot_stage>, + <&boot_error_count>, <&panic_count>, <&pm_setting>; + nvmem-cell-names = "shutdown_flag", "boot_stage", + "boot_error_count", "panic_count", "pm_setting"; + }; + }; + + pinctrl_smc: pinctrl@23e820000 { + compatible = "apple,t8112-pinctrl", "apple,pinctrl"; + reg = <0x2 0x3e820000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_smc 0 0 18>; + apple,npins = <18>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 490 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 491 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 492 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 493 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 494 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 495 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 496 IRQ_TYPE_LEVEL_HIGH>; + }; + + pinctrl_aop: pinctrl@24a820000 { + compatible = "apple,t8112-pinctrl", "apple,pinctrl"; + reg = <0x2 0x4a820000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_aop 0 0 54>; + apple,npins = <54>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 301 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 302 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 303 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 304 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 305 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 306 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 307 IRQ_TYPE_LEVEL_HIGH>; + }; + + mtp: mtp@24e400000 { + compatible = "apple,t8112-mtp", "apple,t8112-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4"; + reg = <0x2 0x4e400000 0x0 0x4000>, + <0x2 0x4ec9c000 0x0 0x8000>; + reg-names = "asc", "sram"; + mboxes = <&mtp_mbox>; + iommus = <&mtp_dart 1>; + #helper-cells = <0>; + + status = "disabled"; + }; + + mtp_mbox: mbox@24e408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x4e408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 864 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 865 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 866 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 867 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + + status = "disabled"; + }; + + mtp_dart: iommu@24e808000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x4e808000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 848 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + + status = "disabled"; + }; + + mtp_dockchannel: fifo@24eb14000 { + compatible = "apple,t8112-dockchannel", "apple,dockchannel"; + reg = <0x2 0x4eb14000 0x0 0x4000>; + reg-names = "irq"; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 850 IRQ_TYPE_LEVEL_HIGH>; + + ranges = <0 0x2 0x4eb28000 0x20000>; + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller; + #interrupt-cells = <2>; + + status = "disabled"; + + mtp_hid: input@8000 { + compatible = "apple,dockchannel-hid"; + reg = <0x8000 0x4000>, + <0xc000 0x4000>, + <0x0000 0x4000>, + <0x4000 0x4000>; + reg-names = "config", "data", + "rmt-config", "rmt-data"; + iommus = <&mtp_dart 1>; + interrupt-parent = <&mtp_dockchannel>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>, + <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "tx", "rx"; + + apple,fifo-size = <0x800>; + apple,helper-cpu = <&mtp>; + }; + + }; + + ans_mbox: mbox@277408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x77408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 717 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 718 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 719 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 720 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_ans>; + }; + + sart: sart@27bc50000 { + compatible = "apple,t8112-sart", "apple,t6000-sart"; + reg = <0x2 0x7bc50000 0x0 0x10000>; + power-domains = <&ps_ans>; + }; + + nvme@27bcc0000 { + compatible = "apple,t8112-nvme-ans2", "apple,nvme-ans2"; + reg = <0x2 0x7bcc0000 0x0 0x40000>, + <0x2 0x77400000 0x0 0x4000>; + reg-names = "nvme", "ans"; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 724 IRQ_TYPE_LEVEL_HIGH>; + mboxes = <&ans_mbox>; + apple,sart = <&sart>; + power-domains = <&ps_ans>, <&ps_apcie_st>; + power-domain-names = "ans", "apcie0"; + resets = <&ps_ans>; + }; + + dwc3_0: usb@382280000 { + compatible = "apple,t8112-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x3 0x82280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1031 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_0_dart_0: iommu@382f00000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x3 0x82f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1035 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_0_dart_1: iommu@382f80000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x3 0x82f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1035 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_1: usb@502280000 { + compatible = "apple,t8112-dwc3", "apple,dwc3", "snps,dwc3"; + reg = <0x5 0x02280000 0x0 0x100000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1112 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>; + power-domains = <&ps_atc1_usb>; + }; + + dwc3_1_dart_0: iommu@502f00000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x5 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1116 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + dwc3_1_dart_1: iommu@502f80000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x5 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 1116 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + pcie0_dart: dart@681008000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x6 0x81008000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 782 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&ps_apcie_gp>; + }; + + pcie0: pcie@690000000 { + compatible = "apple,t8112-pcie", "apple,pcie"; + device_type = "pci"; + + reg = <0x6 0x90000000 0x0 0x1000000>, + <0x6 0x80000000 0x0 0x100000>, + <0x6 0x81000000 0x0 0x4000>, + <0x6 0x82000000 0x0 0x4000>, + <0x6 0x83000000 0x0 0x4000>, + <0x6 0x84000000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "port1", "port2", "port3"; + + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 781 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 784 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 787 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 790 IRQ_TYPE_LEVEL_HIGH>; + + msi-controller; + msi-parent = <&pcie0>; + msi-ranges = <&aic AIC_IRQ 793 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x100 &pcie0_dart 0 1>, + <0x200 &pcie0_dart 1 1>, + <0x300 &pcie0_dart 2 1>, + <0x400 &pcie0_dart 3 1>; + iommu-map-mask = <0xff00>; + + bus-range = <0 4>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x6 0xa0000000 0x6 0xa0000000 0x0 0x20000000>, + <0x02000000 0x0 0xc0000000 0x6 0xc0000000 0x0 0x40000000>; + + power-domains = <&ps_apcie_gp>; + pinctrl-0 = <&pcie_pins>; + pinctrl-names = "default"; + + port00: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + reset-gpios = <&pinctrl_ap 166 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port00 0 0 0 0>, + <0 0 0 2 &port00 0 0 0 1>, + <0 0 0 3 &port00 0 0 0 2>, + <0 0 0 4 &port00 0 0 0 3>; + }; + + /* TODO: GPIOs unknown */ + port01: pci@1,0 { + device_type = "pci"; + reg = <0x800 0x0 0x0 0x0 0x0>; + //reset-gpios = <&pinctrl_ap 153 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; + }; + + port02: pci@2,0 { + device_type = "pci"; + reg = <0x1000 0x0 0x0 0x0 0x0>; + //reset-gpios = <&pinctrl_ap 33 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port02 0 0 0 0>, + <0 0 0 2 &port02 0 0 0 1>, + <0 0 0 3 &port02 0 0 0 2>, + <0 0 0 4 &port02 0 0 0 3>; + }; + + port03: pci@3,0 { + device_type = "pci"; + reg = <0x1800 0x0 0x0 0x0 0x0>; + //reset-gpios = <&pinctrl_ap 33 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port03 0 0 0 0>, + <0 0 0 2 &port03 0 0 0 1>, + <0 0 0 3 &port03 0 0 0 2>, + <0 0 0 4 &port03 0 0 0 3>; + }; + }; + + dart_sio: iommu@235004000 { + compatible = "apple,t8112-dart", "apple,t8110-dart", "apple,dart"; + reg = <0x2 0x35004000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 769 IRQ_TYPE_LEVEL_HIGH>; + #iommu-cells = <1>; + power-domains = <&ps_sio_cpu>; + }; + + nco_inp: clock-ref { + compatible = "fixed-factor-clock"; + clocks = <&clkref>; + #clock-cells = <0>; + clock-mult = <75>; + clock-div = <2>; // 24 MHz * (75/2) = 900 MHz + clock-output-names = "nco_inp"; + }; + + nco: nco@23b044000 { + compatible = "apple,t8112-nco", "apple,nco"; + reg = <0x2 0x3b044000 0x0 0x14000>; + clocks = <&nco_inp>; + #clock-cells = <1>; + apple,nchannels = <5>; + }; + + admac: dma-controller@238200000 { + compatible = "apple,t8112-admac", "apple,admac"; + reg = <0x2 0x38200000 0x0 0x34000>; + dma-channels = <24>; + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 760 IRQ_TYPE_LEVEL_HIGH>; + #dma-cells = <1>; + iommus = <&dart_sio 2>; + power-domains = <&ps_sio_adma>; + apple,internal-irq-destination = <1>; + }; + + mca: mca@38400000 { + compatible = "apple,t8112-mca", "apple,mca"; + reg = <0x2 0x38400000 0x0 0x18000>, + <0x2 0x38300000 0x0 0x30000>; + reg-names = "clusters", "switch"; + + interrupt-parent = <&aic>; + interrupts = <AIC_IRQ 753 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 754 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 755 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 756 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 757 IRQ_TYPE_LEVEL_HIGH>, + <AIC_IRQ 758 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&nco 0>, <&nco 1>, <&nco 2>, + <&nco 3>, <&nco 4>, <&nco 4>; + power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>, + <&ps_mca2>, <&ps_mca3>, <&ps_mca4>, <&ps_mca5>; + dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>, + <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>, + <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>, + <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>, + <&admac 16>, <&admac 17>, <&admac 18>, <&admac 19>, + <&admac 20>, <&admac 21>, <&admac 22>, <&admac 23>; + dma-names = "tx0a", "rx0a", "tx0b", "rx0b", + "tx1a", "rx1a", "tx1b", "rx1b", + "tx2a", "rx2a", "tx2b", "rx2b", + "tx3a", "rx3a", "tx3b", "rx3b", + "tx4a", "rx4a", "tx4b", "rx4b", + "tx5a", "rx5a", "tx5b", "rx5b"; + + #sound-dai-cells = <1>; + apple,nclusters = <6>; + }; + }; +}; + +#include "t8112-pmgr.dtsi" diff --git a/drivers/acpi/x86/apple.c b/drivers/acpi/x86/apple.c index c285c91a5e9c..71b8f103ab0f 100644 --- a/drivers/acpi/x86/apple.c +++ b/drivers/acpi/x86/apple.c @@ -70,13 +70,16 @@ void acpi_extract_apple_properties(struct acpi_device *adev) if ( key->type != ACPI_TYPE_STRING || (val->type != ACPI_TYPE_INTEGER && - val->type != ACPI_TYPE_BUFFER)) + val->type != ACPI_TYPE_BUFFER && + val->type != ACPI_TYPE_STRING)) continue; /* skip invalid properties */ __set_bit(i, valid); newsize += key->string.length + 1; if ( val->type == ACPI_TYPE_BUFFER) newsize += val->buffer.length; + else if (val->type == ACPI_TYPE_STRING) + newsize += val->string.length + 1; } numvalid = bitmap_weight(valid, numprops); @@ -118,6 +121,12 @@ void acpi_extract_apple_properties(struct acpi_device *adev) newprops[v].type = val->type; if (val->type == ACPI_TYPE_INTEGER) { newprops[v].integer.value = val->integer.value; + } else if (val->type == ACPI_TYPE_STRING) { + newprops[v].string.length = val->string.length; + newprops[v].string.pointer = free_space; + memcpy(free_space, val->string.pointer, + val->string.length); + free_space += val->string.length + 1; } else { newprops[v].buffer.length = val->buffer.length; newprops[v].buffer.pointer = free_space; diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index e30707405455..47f7baf20bb8 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -274,6 +274,18 @@ config BT_HCIBCM203X Say Y here to compile support for HCI BCM203x devices into the kernel or say M to compile it as module (bcm203x). + +config BT_HCIBCM4377 + tristate "HCI BCM4377/4378/4387 PCIe driver" + depends on PCI + select FW_LOADER + help + Support for Broadcom BCM4377/4378/4387 bluetooth chipsets attached via + PCIe. These are usually found in Apple machines. + + Say Y here to compile support for HCI BCM4377 family devices into the + kernel or say M to compile it as module (hci_bcm4377). + config BT_HCIBPA10X tristate "HCI BPA10x USB driver" depends on USB diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 3321a8aea4a0..e0b261f24fc9 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o obj-$(CONFIG_BT_HCIUART) += hci_uart.o obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o +obj-$(CONFIG_BT_HCIBCM4377) += hci_bcm4377.o obj-$(CONFIG_BT_HCIBPA10X) += bpa10x.o obj-$(CONFIG_BT_HCIBFUSB) += bfusb.o obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c new file mode 100644 index 000000000000..e5db0ac8a81b --- /dev/null +++ b/drivers/bluetooth/hci_bcm4377.c @@ -0,0 +1,2466 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Bluetooth HCI driver for Broadcom 4377/4378/4387 devices attached via PCIe + * + * Copyright (C) The Asahi Linux Contributors + */ + +#include <linux/async.h> +#include <linux/bitfield.h> +#include <linux/completion.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/msi.h> +#include <linux/of.h> +#include <linux/pci.h> +#include <linux/printk.h> + +#include <asm/unaligned.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +enum bcm4377_chip { + BCM4377 = 0, + BCM4378, + BCM4387, +}; + +#define BCM4377_DEVICE_ID 0x5fa0 +#define BCM4378_DEVICE_ID 0x5f69 +#define BCM4387_DEVICE_ID 0x5f71 + +#define BCM4377_TIMEOUT 1000 + +/* + * These devices only support DMA transactions inside a 32bit window + * (possibly to avoid 64 bit arithmetic). The window size cannot exceed + * 0xffffffff but is always aligned down to the previous 0x200 byte boundary + * which effectively limits the window to [start, start+0xfffffe00]. + * We just limit the DMA window to [0, 0xfffffe00] to make sure we don't + * run into this limitation. + */ +#define BCM4377_DMA_MASK 0xfffffe00 + +#define BCM4377_PCIECFG_BAR0_WINDOW1 0x80 +#define BCM4377_PCIECFG_BAR0_WINDOW2 0x70 +#define BCM4377_PCIECFG_BAR0_CORE2_WINDOW1 0x74 +#define BCM4377_PCIECFG_BAR0_CORE2_WINDOW2 0x78 +#define BCM4377_PCIECFG_BAR2_WINDOW 0x84 + +#define BCM4377_PCIECFG_BAR0_CORE2_WINDOW1_DEFAULT 0x18011000 +#define BCM4377_PCIECFG_BAR2_WINDOW_DEFAULT 0x19000000 + +#define BCM4377_PCIECFG_SUBSYSTEM_CTRL 0x88 + +#define BCM4377_BAR0_FW_DOORBELL 0x140 +#define BCM4377_BAR0_RTI_CONTROL 0x144 + +#define BCM4377_BAR0_SLEEP_CONTROL 0x150 +#define BCM4377_BAR0_SLEEP_CONTROL_AWAKE 2 +#define BCM4377_BAR0_SLEEP_CONTROL_QUIESCED 3 + +#define BCM4377_BAR0_DOORBELL 0x174 +#define BCM4377_BAR0_DOORBELL_VALUE GENMASK(31, 16) +#define BCM4377_BAR0_DOORBELL_IDX GENMASK(15, 8) +#define BCM4377_BAR0_DOORBELL_RING BIT(5) + +#define BCM4377_BAR0_HOST_WINDOW_LO 0x590 +#define BCM4377_BAR0_HOST_WINDOW_HI 0x594 +#define BCM4377_BAR0_HOST_WINDOW_SIZE 0x598 + +#define BCM4377_BAR2_BOOTSTAGE 0x200454 + +#define BCM4377_BAR2_FW_LO 0x200478 +#define BCM4377_BAR2_FW_HI 0x20047c +#define BCM4377_BAR2_FW_SIZE 0x200480 + +#define BCM4377_BAR2_CONTEXT_ADDR_LO 0x20048c +#define BCM4377_BAR2_CONTEXT_ADDR_HI 0x200450 + +#define BCM4377_BAR2_RTI_STATUS 0x20045c +#define BCM4377_BAR2_RTI_WINDOW_LO 0x200494 +#define BCM4377_BAR2_RTI_WINDOW_HI 0x200498 +#define BCM4377_BAR2_RTI_WINDOW_SIZE 0x20049c + +#define BCM4377_OTP_SIZE 0xe0 +#define BCM4377_OTP_SYS_VENDOR 0x15 +#define BCM4377_OTP_CIS 0x80 +#define BCM4377_OTP_VENDOR_HDR 0x00000008 +#define BCM4377_OTP_MAX_PARAM_LEN 16 + +#define BCM4377_N_TRANSFER_RINGS 9 +#define BCM4377_N_COMPLETION_RINGS 6 + +#define BCM4377_MAX_RING_SIZE 256 + +#define BCM4377_MSGID_GENERATION GENMASK(15, 8) +#define BCM4377_MSGID_ID GENMASK(7, 0) + +#define BCM4377_RING_N_ENTRIES 128 + +#define BCM4377_CONTROL_MSG_SIZE 0x34 +#define BCM4377_XFER_RING_MAX_INPLACE_PAYLOAD_SIZE (4 * 0xff) + +#define MAX_ACL_PAYLOAD_SIZE (HCI_MAX_FRAME_SIZE + HCI_ACL_HDR_SIZE) +#define MAX_SCO_PAYLOAD_SIZE (HCI_MAX_SCO_SIZE + HCI_SCO_HDR_SIZE) +#define MAX_EVENT_PAYLOAD_SIZE (HCI_MAX_EVENT_SIZE + HCI_EVENT_HDR_SIZE) + +enum bcm4377_otp_params_type { + BCM4377_OTP_BOARD_PARAMS, + BCM4377_OTP_CHIP_PARAMS +}; + +enum bcm4377_transfer_ring_id { + BCM4377_XFER_RING_CONTROL = 0, + BCM4377_XFER_RING_HCI_H2D = 1, + BCM4377_XFER_RING_HCI_D2H = 2, + BCM4377_XFER_RING_SCO_H2D = 3, + BCM4377_XFER_RING_SCO_D2H = 4, + BCM4377_XFER_RING_ACL_H2D = 5, + BCM4377_XFER_RING_ACL_D2H = 6, +}; + +enum bcm4377_completion_ring_id { + BCM4377_ACK_RING_CONTROL = 0, + BCM4377_ACK_RING_HCI_ACL = 1, + BCM4377_EVENT_RING_HCI_ACL = 2, + BCM4377_ACK_RING_SCO = 3, + BCM4377_EVENT_RING_SCO = 4, +}; + +enum bcm4377_doorbell { + BCM4377_DOORBELL_CONTROL = 0, + BCM4377_DOORBELL_HCI_H2D = 1, + BCM4377_DOORBELL_HCI_D2H = 2, + BCM4377_DOORBELL_ACL_H2D = 3, + BCM4377_DOORBELL_ACL_D2H = 4, + BCM4377_DOORBELL_SCO = 6, +}; + +/* + * Transfer ring entry + * + * flags: Flags to indicate if the payload is appended or mapped + * len: Payload length + * payload: Optional payload DMA address + * id: Message id to recognize the answer in the completion ring entry + */ +struct bcm4377_xfer_ring_entry { +#define BCM4377_XFER_RING_FLAG_PAYLOAD_MAPPED BIT(0) +#define BCM4377_XFER_RING_FLAG_PAYLOAD_IN_FOOTER BIT(1) + u8 flags; + __le16 len; + u8 _unk0; + __le64 payload; + __le16 id; + u8 _unk1[2]; +} __packed; +static_assert(sizeof(struct bcm4377_xfer_ring_entry) == 0x10); + +/* + * Completion ring entry + * + * flags: Flags to indicate if the payload is appended or mapped. If the payload + * is mapped it can be found in the buffer of the corresponding transfer + * ring message. + * ring_id: Transfer ring ID which required this message + * msg_id: Message ID specified in transfer ring entry + * len: Payload length + */ +struct bcm4377_completion_ring_entry { + u8 flags; + u8 _unk0; + __le16 ring_id; + __le16 msg_id; + __le32 len; + u8 _unk1[6]; +} __packed; +static_assert(sizeof(struct bcm4377_completion_ring_entry) == 0x10); + +enum bcm4377_control_message_type { + BCM4377_CONTROL_MSG_CREATE_XFER_RING = 1, + BCM4377_CONTROL_MSG_CREATE_COMPLETION_RING = 2, + BCM4377_CONTROL_MSG_DESTROY_XFER_RING = 3, + BCM4377_CONTROL_MSG_DESTROY_COMPLETION_RING = 4, +}; + +/* + * Control message used to create a completion ring + * + * msg_type: Must be BCM4377_CONTROL_MSG_CREATE_COMPLETION_RING + * header_size: Unknown, but probably reserved space in front of the entry + * footer_size: Number of 32 bit words reserved for payloads after the entry + * id/id_again: Completion ring index + * ring_iova: DMA address of the ring buffer + * n_elements: Number of elements inside the ring buffer + * msi: MSI index, doesn't work for all rings though and should be zero + * intmod_delay: Unknown delay + * intmod_bytes: Unknown + */ +struct bcm4377_create_completion_ring_msg { + u8 msg_type; + u8 header_size; + u8 footer_size; + u8 _unk0; + __le16 id; + __le16 id_again; + __le64 ring_iova; + __le16 n_elements; + __le32 unk; + u8 _unk1[6]; + __le16 msi; + __le16 intmod_delay; + __le32 intmod_bytes; + __le16 _unk2; + __le32 _unk3; + u8 _unk4[10]; +} __packed; +static_assert(sizeof(struct bcm4377_create_completion_ring_msg) == + BCM4377_CONTROL_MSG_SIZE); + +/* + * Control ring message used to destroy a completion ring + * + * msg_type: Must be BCM4377_CONTROL_MSG_DESTROY_COMPLETION_RING + * ring_id: Completion ring to be destroyed + */ +struct bcm4377_destroy_completion_ring_msg { + u8 msg_type; + u8 _pad0; + __le16 ring_id; + u8 _pad1[48]; +} __packed; +static_assert(sizeof(struct bcm4377_destroy_completion_ring_msg) == + BCM4377_CONTROL_MSG_SIZE); + +/* + * Control message used to create a transfer ring + * + * msg_type: Must be BCM4377_CONTROL_MSG_CREATE_XFER_RING + * header_size: Number of 32 bit words reserved for unknown content before the + * entry + * footer_size: Number of 32 bit words reserved for payloads after the entry + * ring_id/ring_id_again: Transfer ring index + * ring_iova: DMA address of the ring buffer + * n_elements: Number of elements inside the ring buffer + * completion_ring_id: Completion ring index for acknowledgements and events + * doorbell: Doorbell index used to notify device of new entries + * flags: Transfer ring flags + * - virtual: set if there is no associated shared memory and only the + * corresponding completion ring is used + * - sync: only set for the SCO rings + */ +struct bcm4377_create_transfer_ring_msg { + u8 msg_type; + u8 header_size; + u8 footer_size; + u8 _unk0; + __le16 ring_id; + __le16 ring_id_again; + __le64 ring_iova; + u8 _unk1[8]; + __le16 n_elements; + __le16 completion_ring_id; + __le16 doorbell; +#define BCM4377_XFER_RING_FLAG_VIRTUAL BIT(7) +#define BCM4377_XFER_RING_FLAG_SYNC BIT(8) + __le16 flags; + u8 _unk2[20]; +} __packed; +static_assert(sizeof(struct bcm4377_create_transfer_ring_msg) == + BCM4377_CONTROL_MSG_SIZE); + +/* + * Control ring message used to destroy a transfer ring + * + * msg_type: Must be BCM4377_CONTROL_MSG_DESTROY_XFER_RING + * ring_id: Transfer ring to be destroyed + */ +struct bcm4377_destroy_transfer_ring_msg { + u8 msg_type; + u8 _pad0; + __le16 ring_id; + u8 _pad1[48]; +} __packed; +static_assert(sizeof(struct bcm4377_destroy_transfer_ring_msg) == + BCM4377_CONTROL_MSG_SIZE); + +/* + * "Converged IPC" context struct used to make the device aware of all other + * shared memory structures. A pointer to this structure is configured inside a + * MMIO register. + * + * version: Protocol version, must be 2. + * size: Size of this structure, must be 0x68. + * enabled_caps: Enabled capabilities. Unknown bitfield but should be 2. + * peripheral_info_addr: DMA address for a 0x20 buffer to which the device will + * write unknown contents + * {completion,xfer}_ring_{tails,heads}_addr: DMA pointers to ring heads/tails + * n_completion_rings: Number of completion rings, the firmware only works if + * this is set to BCM4377_N_COMPLETION_RINGS. + * n_xfer_rings: Number of transfer rings, the firmware only works if + * this is set to BCM4377_N_TRANSFER_RINGS. + * control_completion_ring_addr: Control completion ring buffer DMA address + * control_xfer_ring_addr: Control transfer ring buffer DMA address + * control_xfer_ring_n_entries: Number of control transfer ring entries + * control_completion_ring_n_entries: Number of control completion ring entries + * control_xfer_ring_doorbell: Control transfer ring doorbell + * control_completion_ring_doorbell: Control completion ring doorbell, + * must be set to 0xffff + * control_xfer_ring_msi: Control completion ring MSI index, must be 0 + * control_completion_ring_msi: Control completion ring MSI index, must be 0. + * control_xfer_ring_header_size: Number of 32 bit words reserved in front of + * every control transfer ring entry + * control_xfer_ring_footer_size: Number of 32 bit words reserved after every + * control transfer ring entry + * control_completion_ring_header_size: Number of 32 bit words reserved in front + * of every control completion ring entry + * control_completion_ring_footer_size: Number of 32 bit words reserved after + * every control completion ring entry + * scratch_pad: Optional scratch pad DMA address + * scratch_pad_size: Scratch pad size + */ +struct bcm4377_context { + __le16 version; + __le16 size; + __le32 enabled_caps; + + __le64 peripheral_info_addr; + + /* ring heads and tails */ + __le64 completion_ring_heads_addr; + __le64 xfer_ring_tails_addr; + __le64 completion_ring_tails_addr; + __le64 xfer_ring_heads_addr; + __le16 n_completion_rings; + __le16 n_xfer_rings; + + /* control ring configuration */ + __le64 control_completion_ring_addr; + __le64 control_xfer_ring_addr; + __le16 control_xfer_ring_n_entries; + __le16 control_completion_ring_n_entries; + __le16 control_xfer_ring_doorbell; + __le16 control_completion_ring_doorbell; + __le16 control_xfer_ring_msi; + __le16 control_completion_ring_msi; + u8 control_xfer_ring_header_size; + u8 control_xfer_ring_footer_size; + u8 control_completion_ring_header_size; + u8 control_completion_ring_footer_size; + + __le16 _unk0; + __le16 _unk1; + + __le64 scratch_pad; + __le32 scratch_pad_size; + + __le32 _unk3; +} __packed; +static_assert(sizeof(struct bcm4377_context) == 0x68); + +#define BCM4378_CALIBRATION_CHUNK_SIZE 0xe6 +struct bcm4378_hci_send_calibration_cmd { + u8 unk; + __le16 blocks_left; + u8 data[BCM4378_CALIBRATION_CHUNK_SIZE]; +} __packed; + +#define BCM4378_PTB_CHUNK_SIZE 0xcf +struct bcm4378_hci_send_ptb_cmd { + __le16 blocks_left; + u8 data[BCM4378_PTB_CHUNK_SIZE]; +} __packed; + +/* + * Shared memory structure used to store the ring head and tail pointers. + */ +struct bcm4377_ring_state { + __le16 completion_ring_head[BCM4377_N_COMPLETION_RINGS]; + __le16 completion_ring_tail[BCM4377_N_COMPLETION_RINGS]; + __le16 xfer_ring_head[BCM4377_N_TRANSFER_RINGS]; + __le16 xfer_ring_tail[BCM4377_N_TRANSFER_RINGS]; +}; + +/* + * A transfer ring can be used in two configurations: + * 1) Send control or HCI messages to the device which are then acknowledged + * in the corresponding completion ring + * 2) Receiving HCI frames from the devices. In this case the transfer ring + * itself contains empty messages that are acknowledged once data is + * available from the device. If the payloads fit inside the footers + * of the completion ring the transfer ring can be configured to be + * virtual such that it has no ring buffer. + * + * ring_id: ring index hardcoded in the firmware + * doorbell: doorbell index to notify device of new entries + * payload_size: optional in-place payload size + * mapped_payload_size: optional out-of-place payload size + * completion_ring: index of corresponding completion ring + * n_entries: number of entries inside this ring + * generation: ring generation; incremented on hci_open to detect stale messages + * sync: set to true for SCO rings + * virtual: set to true if this ring has no entries and is just required to + * setup a corresponding completion ring for device->host messages + * d2h_buffers_only: set to true if this ring is only used to provide large + * buffers used by device->host messages in the completion + * ring + * allow_wait: allow to wait for messages to be acknowledged + * enabled: true once the ring has been created and can be used + * ring: ring buffer for entries (struct bcm4377_xfer_ring_entry) + * ring_dma: DMA address for ring entry buffer + * payloads: payload buffer for mapped_payload_size payloads + * payloads_dma:DMA address for payload buffer + * events: pointer to array of completions if waiting is allowed + * msgids: bitmap to keep track of used message ids + * lock: Spinlock to protect access to ring structurs used in the irq handler + */ +struct bcm4377_transfer_ring { + enum bcm4377_transfer_ring_id ring_id; + enum bcm4377_doorbell doorbell; + size_t payload_size; + size_t mapped_payload_size; + u8 completion_ring; + u16 n_entries; + u8 generation; + + bool sync; + bool virtual; + bool d2h_buffers_only; + bool allow_wait; + bool enabled; + + void *ring; + dma_addr_t ring_dma; + + void *payloads; + dma_addr_t payloads_dma; + + struct completion **events; + DECLARE_BITMAP(msgids, BCM4377_MAX_RING_SIZE); + spinlock_t lock; +}; + +/* + * A completion ring can be either used to either acknowledge messages sent in + * the corresponding transfer ring or to receive messages associated with the + * transfer ring. When used to receive messages the transfer ring either + * has no ring buffer and is only advanced ("virtual transfer ring") or it + * only contains empty DMA buffers to be used for the payloads. + * + * ring_id: completion ring id, hardcoded in firmware + * payload_size: optional payload size after each entry + * delay: unknown delay + * n_entries: number of entries in this ring + * enabled: true once the ring has been created and can be used + * ring: ring buffer for entries (struct bcm4377_completion_ring_entry) + * ring_dma: DMA address of ring buffer + * transfer_rings: bitmap of corresponding transfer ring ids + */ +struct bcm4377_completion_ring { + enum bcm4377_completion_ring_id ring_id; + u16 payload_size; + u16 delay; + u16 n_entries; + bool enabled; + + void *ring; + dma_addr_t ring_dma; + + unsigned long transfer_rings; +}; + +struct bcm4377_data; + +/* + * Chip-specific configuration struct + * + * id: Chip id (e.g. 0x4377 for BCM4377) + * otp_offset: Offset to the start of the OTP inside BAR0 + * bar0_window1: Backplane address mapped to the first window in BAR0 + * bar0_window2: Backplane address mapped to the second window in BAR0 + * bar0_core2_window2: Optional backplane address mapped to the second core's + * second window in BAR0 + * has_bar0_core2_window2: Set to true if this chip requires the second core's + * second window to be configured + * clear_pciecfg_subsystem_ctrl_bit19: Set to true if bit 19 in the + * vendor-specific subsystem control + * register has to be cleared + * disable_aspm: Set to true if ASPM must be disabled due to hardware errata + * broken_ext_scan: Set to true if the chip erroneously claims to support + * extended scanning + * fixup_le_ext_adv_report_evt_type: Set to true if the upper byte of evt_type + * field of the LE Advanced Report has to be + * cleared + * board_type: Default board type, used for non-DT platforms + * send_calibration: Optional callback to send calibration data + * send_ptb: Callback to send "PTB" regulatory/calibration data + */ +struct bcm4377_hw { + unsigned int id; + + u32 otp_offset; + + u32 bar0_window1; + u32 bar0_window2; + u32 bar0_core2_window2; + + unsigned long has_bar0_core2_window2 : 1; + unsigned long clear_pciecfg_subsystem_ctrl_bit19 : 1; + unsigned long disable_aspm : 1; + unsigned long broken_ext_scan : 1; + unsigned long fixup_le_ext_adv_report_evt_type : 1; + + const char *board_type; + + int (*send_calibration)(struct bcm4377_data *bcm4377); + int (*send_ptb)(struct bcm4377_data *bcm4377, + const struct firmware *fw); +}; + +static const struct bcm4377_hw bcm4377_hw_variants[]; + +/* + * Private struct associated with each device containing global state + * + * pdev: Pointer to associated struct pci_dev + * hdev: Pointer to associated strucy hci_dev + * bar0: iomem pointing to BAR0 + * bar1: iomem pointing to BAR2 + * bootstage: Current value of the bootstage + * rti_status: Current "RTI" status value + * hw: Pointer to chip-specific struct bcm4377_hw + * taurus_cal_blob: "Taurus" calibration blob used for some chips + * taurus_cal_size: "Taurus" calibration blob size + * taurus_beamforming_cal_blob: "Taurus" beamforming calibration blob used for + * some chips + * taurus_beamforming_cal_size: "Taurus" beamforming calibration blob size + * stepping: Chip stepping read from OTP; used for firmware selection + * vendor: Antenna vendor read from OTP; used for firmware selection + * board_type: Board type read from FDT; used for firmware selection + * event: Event for changed bootstage or rti_status; used for booting firmware + * ctx: "Converged IPC" context + * ctx_dma: "Converged IPC" context DMA address + * ring_state: Shared memory buffer containing ring head and tail indexes + * ring_state_dma: DMA address for ring_state + * {control,hci_acl,sco}_ack_ring: Completion rings used to acknowledge messages + * {hci_acl,sco}_event_ring: Completion rings used for device->host messages + * control_h2d_ring: Transfer ring used for control messages + * {hci,sco,acl}_h2d_ring: Transfer ring used to transfer HCI frames + * {hci,sco,acl}_d2h_ring: Transfer ring used to receive HCI frames in the + * corresponding completion ring + */ +struct bcm4377_data { + struct pci_dev *pdev; + struct hci_dev *hdev; + + void __iomem *bar0; + void __iomem *bar2; + + u32 bootstage; + u32 rti_status; + + const struct bcm4377_hw *hw; + + const void *taurus_cal_blob; + int taurus_cal_size; + const void *taurus_beamforming_cal_blob; + int taurus_beamforming_cal_size; + + char stepping[BCM4377_OTP_MAX_PARAM_LEN]; + char vendor[BCM4377_OTP_MAX_PARAM_LEN]; + const char *board_type; + + struct completion event; + + struct bcm4377_context *ctx; + dma_addr_t ctx_dma; + + struct bcm4377_ring_state *ring_state; + dma_addr_t ring_state_dma; + + /* + * The HCI and ACL rings have to be merged because this structure is + * hardcoded in the firmware. + */ + struct bcm4377_completion_ring control_ack_ring; + struct bcm4377_completion_ring hci_acl_ack_ring; + struct bcm4377_completion_ring hci_acl_event_ring; + struct bcm4377_completion_ring sco_ack_ring; + struct bcm4377_completion_ring sco_event_ring; + + struct bcm4377_transfer_ring control_h2d_ring; + struct bcm4377_transfer_ring hci_h2d_ring; + struct bcm4377_transfer_ring hci_d2h_ring; + struct bcm4377_transfer_ring sco_h2d_ring; + struct bcm4377_transfer_ring sco_d2h_ring; + struct bcm4377_transfer_ring acl_h2d_ring; + struct bcm4377_transfer_ring acl_d2h_ring; +}; + +static void bcm4377_ring_doorbell(struct bcm4377_data *bcm4377, u8 doorbell, + u16 val) +{ + u32 db = 0; + + db |= FIELD_PREP(BCM4377_BAR0_DOORBELL_VALUE, val); + db |= FIELD_PREP(BCM4377_BAR0_DOORBELL_IDX, doorbell); + db |= BCM4377_BAR0_DOORBELL_RING; + + dev_dbg(&bcm4377->pdev->dev, "write %d to doorbell #%d (0x%x)\n", val, + doorbell, db); + iowrite32(db, bcm4377->bar0 + BCM4377_BAR0_DOORBELL); +} + +static int bcm4377_extract_msgid(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring, + u16 raw_msgid, u8 *msgid) +{ + u8 generation = FIELD_GET(BCM4377_MSGID_GENERATION, raw_msgid); + *msgid = FIELD_GET(BCM4377_MSGID_ID, raw_msgid); + + if (generation != ring->generation) { + dev_warn( + &bcm4377->pdev->dev, + "invalid message generation %d should be %d in entry for ring %d\n", + generation, ring->generation, ring->ring_id); + return -EINVAL; + } + + if (*msgid >= ring->n_entries) { + dev_warn(&bcm4377->pdev->dev, + "invalid message id in entry for ring %d: %d > %d\n", + ring->ring_id, *msgid, ring->n_entries); + return -EINVAL; + } + + return 0; +} + +static void bcm4377_handle_event(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring, + u16 raw_msgid, u8 entry_flags, u8 type, + void *payload, size_t len) +{ + struct sk_buff *skb; + u16 head; + u8 msgid; + unsigned long flags; + + spin_lock_irqsave(&ring->lock, flags); + if (!ring->enabled) { + dev_warn(&bcm4377->pdev->dev, + "event for disabled transfer ring %d\n", + ring->ring_id); + goto out; + } + + if (ring->d2h_buffers_only && + entry_flags & BCM4377_XFER_RING_FLAG_PAYLOAD_MAPPED) { + if (bcm4377_extract_msgid(bcm4377, ring, raw_msgid, &msgid)) + goto out; + + if (len > ring->mapped_payload_size) { + dev_warn( + &bcm4377->pdev->dev, + "invalid payload len in event for ring %d: %zu > %zu\n", + ring->ring_id, len, ring->mapped_payload_size); + goto out; + } + + payload = ring->payloads + msgid * ring->mapped_payload_size; + } + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) + goto out; + + memcpy(skb_put(skb, len), payload, len); + hci_skb_pkt_type(skb) = type; + hci_recv_frame(bcm4377->hdev, skb); + +out: + head = le16_to_cpu(bcm4377->ring_state->xfer_ring_head[ring->ring_id]); + head = (head + 1) % ring->n_entries; + bcm4377->ring_state->xfer_ring_head[ring->ring_id] = cpu_to_le16(head); + + bcm4377_ring_doorbell(bcm4377, ring->doorbell, head); + + spin_unlock_irqrestore(&ring->lock, flags); +} + +static void bcm4377_handle_ack(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring, + u16 raw_msgid) +{ + unsigned long flags; + u8 msgid; + + spin_lock_irqsave(&ring->lock, flags); + + if (bcm4377_extract_msgid(bcm4377, ring, raw_msgid, &msgid)) + goto unlock; + + if (!test_bit(msgid, ring->msgids)) { + dev_warn( + &bcm4377->pdev->dev, + "invalid message id in ack for ring %d: %d is not used\n", + ring->ring_id, msgid); + goto unlock; + } + + if (ring->allow_wait && ring->events[msgid]) { + complete(ring->events[msgid]); + ring->events[msgid] = NULL; + } + + bitmap_release_region(ring->msgids, msgid, ring->n_entries); + +unlock: + spin_unlock_irqrestore(&ring->lock, flags); +} + +static void bcm4377_handle_completion(struct bcm4377_data *bcm4377, + struct bcm4377_completion_ring *ring, + u16 pos) +{ + struct bcm4377_completion_ring_entry *entry; + u16 msg_id, transfer_ring; + size_t entry_size, data_len; + void *data; + + if (pos >= ring->n_entries) { + dev_warn(&bcm4377->pdev->dev, + "invalid offset %d for completion ring %d\n", pos, + ring->ring_id); + return; + } + + entry_size = sizeof(*entry) + ring->payload_size; + entry = ring->ring + pos * entry_size; + data = ring->ring + pos * entry_size + sizeof(*entry); + data_len = le32_to_cpu(entry->len); + msg_id = le16_to_cpu(entry->msg_id); + transfer_ring = le16_to_cpu(entry->ring_id); + + if ((ring->transfer_rings & BIT(transfer_ring)) == 0) { + dev_warn( + &bcm4377->pdev->dev, + "invalid entry at offset %d for transfer ring %d in completion ring %d\n", + pos, transfer_ring, ring->ring_id); + return; + } + + dev_dbg(&bcm4377->pdev->dev, + "entry in completion ring %d for transfer ring %d with msg_id %d\n", + ring->ring_id, transfer_ring, msg_id); + + switch (transfer_ring) { + case BCM4377_XFER_RING_CONTROL: + bcm4377_handle_ack(bcm4377, &bcm4377->control_h2d_ring, msg_id); + break; + case BCM4377_XFER_RING_HCI_H2D: + bcm4377_handle_ack(bcm4377, &bcm4377->hci_h2d_ring, msg_id); + break; + case BCM4377_XFER_RING_SCO_H2D: + bcm4377_handle_ack(bcm4377, &bcm4377->sco_h2d_ring, msg_id); + break; + case BCM4377_XFER_RING_ACL_H2D: + bcm4377_handle_ack(bcm4377, &bcm4377->acl_h2d_ring, msg_id); + break; + + case BCM4377_XFER_RING_HCI_D2H: + bcm4377_handle_event(bcm4377, &bcm4377->hci_d2h_ring, msg_id, + entry->flags, HCI_EVENT_PKT, data, + data_len); + break; + case BCM4377_XFER_RING_SCO_D2H: + bcm4377_handle_event(bcm4377, &bcm4377->sco_d2h_ring, msg_id, + entry->flags, HCI_SCODATA_PKT, data, + data_len); + break; + case BCM4377_XFER_RING_ACL_D2H: + bcm4377_handle_event(bcm4377, &bcm4377->acl_d2h_ring, msg_id, + entry->flags, HCI_ACLDATA_PKT, data, + data_len); + break; + + default: + dev_warn( + &bcm4377->pdev->dev, + "entry in completion ring %d for unknown transfer ring %d with msg_id %d\n", + ring->ring_id, transfer_ring, msg_id); + } +} + +static void bcm4377_poll_completion_ring(struct bcm4377_data *bcm4377, + struct bcm4377_completion_ring *ring) +{ + u16 tail; + __le16 *heads = bcm4377->ring_state->completion_ring_head; + __le16 *tails = bcm4377->ring_state->completion_ring_tail; + + if (!ring->enabled) + return; + + tail = le16_to_cpu(tails[ring->ring_id]); + dev_dbg(&bcm4377->pdev->dev, + "completion ring #%d: head: %d, tail: %d\n", ring->ring_id, + le16_to_cpu(heads[ring->ring_id]), tail); + + while (tail != le16_to_cpu(READ_ONCE(heads[ring->ring_id]))) { + /* + * ensure the CPU doesn't speculate through the comparison. + * otherwise it might already read the (empty) queue entry + * before the updated head has been loaded and checked. + */ + dma_rmb(); + + bcm4377_handle_completion(bcm4377, ring, tail); + + tail = (tail + 1) % ring->n_entries; + tails[ring->ring_id] = cpu_to_le16(tail); + } +} + +static irqreturn_t bcm4377_irq(int irq, void *data) +{ + struct bcm4377_data *bcm4377 = data; + u32 bootstage, rti_status; + + bootstage = ioread32(bcm4377->bar2 + BCM4377_BAR2_BOOTSTAGE); + rti_status = ioread32(bcm4377->bar2 + BCM4377_BAR2_RTI_STATUS); + + if (bootstage != bcm4377->bootstage || + rti_status != bcm4377->rti_status) { + dev_dbg(&bcm4377->pdev->dev, + "bootstage = %d -> %d, rti state = %d -> %d\n", + bcm4377->bootstage, bootstage, bcm4377->rti_status, + rti_status); + complete(&bcm4377->event); + bcm4377->bootstage = bootstage; + bcm4377->rti_status = rti_status; + } + + bcm4377_poll_completion_ring(bcm4377, &bcm4377->control_ack_ring); + bcm4377_poll_completion_ring(bcm4377, &bcm4377->hci_acl_event_ring); + bcm4377_poll_completion_ring(bcm4377, &bcm4377->hci_acl_ack_ring); + bcm4377_poll_completion_ring(bcm4377, &bcm4377->sco_ack_ring); + bcm4377_poll_completion_ring(bcm4377, &bcm4377->sco_event_ring); + + return IRQ_HANDLED; +} + +static int bcm4377_enqueue(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring, void *data, + size_t len, bool wait) +{ + unsigned long flags; + struct bcm4377_xfer_ring_entry *entry; + void *payload; + size_t offset; + u16 head, tail, new_head; + u16 raw_msgid; + int ret, msgid; + DECLARE_COMPLETION_ONSTACK(event); + + if (len > ring->payload_size && len > ring->mapped_payload_size) { + dev_warn( + &bcm4377->pdev->dev, + "payload len %zu is too large for ring %d (max is %zu or %zu)\n", + len, ring->ring_id, ring->payload_size, + ring->mapped_payload_size); + return -EINVAL; + } + if (wait && !ring->allow_wait) + return -EINVAL; + if (ring->virtual) + return -EINVAL; + + spin_lock_irqsave(&ring->lock, flags); + + head = le16_to_cpu(bcm4377->ring_state->xfer_ring_head[ring->ring_id]); + tail = le16_to_cpu(bcm4377->ring_state->xfer_ring_tail[ring->ring_id]); + + new_head = (head + 1) % ring->n_entries; + + if (new_head == tail) { + dev_warn(&bcm4377->pdev->dev, + "can't send message because ring %d is full\n", + ring->ring_id); + ret = -EINVAL; + goto out; + } + + msgid = bitmap_find_free_region(ring->msgids, ring->n_entries, 0); + if (msgid < 0) { + dev_warn(&bcm4377->pdev->dev, + "can't find message id for ring %d\n", ring->ring_id); + ret = -EINVAL; + goto out; + } + + raw_msgid = FIELD_PREP(BCM4377_MSGID_GENERATION, ring->generation); + raw_msgid |= FIELD_PREP(BCM4377_MSGID_ID, msgid); + + offset = head * (sizeof(*entry) + ring->payload_size); + entry = ring->ring + offset; + + memset(entry, 0, sizeof(*entry)); + entry->id = cpu_to_le16(raw_msgid); + entry->len = cpu_to_le16(len); + + if (len <= ring->payload_size) { + entry->flags = BCM4377_XFER_RING_FLAG_PAYLOAD_IN_FOOTER; + payload = ring->ring + offset + sizeof(*entry); + } else { + entry->flags = BCM4377_XFER_RING_FLAG_PAYLOAD_MAPPED; + entry->payload = cpu_to_le64(ring->payloads_dma + + msgid * ring->mapped_payload_size); + payload = ring->payloads + msgid * ring->mapped_payload_size; + } + + memcpy(payload, data, len); + + if (wait) + ring->events[msgid] = &event; + + /* + * The 4377 chips stop responding to any commands as soon as they + * have been idle for a while. Poking the sleep control register here + * makes them come alive again. + */ + iowrite32(BCM4377_BAR0_SLEEP_CONTROL_AWAKE, + bcm4377->bar0 + BCM4377_BAR0_SLEEP_CONTROL); + + dev_dbg(&bcm4377->pdev->dev, + "updating head for transfer queue #%d to %d\n", ring->ring_id, + new_head); + bcm4377->ring_state->xfer_ring_head[ring->ring_id] = + cpu_to_le16(new_head); + + if (!ring->sync) + bcm4377_ring_doorbell(bcm4377, ring->doorbell, new_head); + ret = 0; + +out: + spin_unlock_irqrestore(&ring->lock, flags); + + if (ret == 0 && wait) { + ret = wait_for_completion_interruptible_timeout( + &event, BCM4377_TIMEOUT); + if (ret == 0) + ret = -ETIMEDOUT; + else if (ret > 0) + ret = 0; + + spin_lock_irqsave(&ring->lock, flags); + ring->events[msgid] = NULL; + spin_unlock_irqrestore(&ring->lock, flags); + } + + return ret; +} + +static int bcm4377_create_completion_ring(struct bcm4377_data *bcm4377, + struct bcm4377_completion_ring *ring) +{ + struct bcm4377_create_completion_ring_msg msg; + int ret; + + if (ring->enabled) { + dev_warn(&bcm4377->pdev->dev, + "completion ring %d already enabled\n", ring->ring_id); + return 0; + } + + memset(ring->ring, 0, + ring->n_entries * (sizeof(struct bcm4377_completion_ring_entry) + + ring->payload_size)); + memset(&msg, 0, sizeof(msg)); + msg.msg_type = BCM4377_CONTROL_MSG_CREATE_COMPLETION_RING; + msg.id = cpu_to_le16(ring->ring_id); + msg.id_again = cpu_to_le16(ring->ring_id); + msg.ring_iova = cpu_to_le64(ring->ring_dma); + msg.n_elements = cpu_to_le16(ring->n_entries); + msg.intmod_bytes = cpu_to_le32(0xffffffff); + msg.unk = cpu_to_le32(0xffffffff); + msg.intmod_delay = cpu_to_le16(ring->delay); + msg.footer_size = ring->payload_size / 4; + + ret = bcm4377_enqueue(bcm4377, &bcm4377->control_h2d_ring, &msg, + sizeof(msg), true); + if (!ret) + ring->enabled = true; + + return ret; +} + +static int bcm4377_destroy_completion_ring(struct bcm4377_data *bcm4377, + struct bcm4377_completion_ring *ring) +{ + struct bcm4377_destroy_completion_ring_msg msg; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.msg_type = BCM4377_CONTROL_MSG_DESTROY_COMPLETION_RING; + msg.ring_id = cpu_to_le16(ring->ring_id); + + ret = bcm4377_enqueue(bcm4377, &bcm4377->control_h2d_ring, &msg, + sizeof(msg), true); + if (ret) + dev_warn(&bcm4377->pdev->dev, + "failed to destroy completion ring %d\n", + ring->ring_id); + + ring->enabled = false; + return ret; +} + +static int bcm4377_create_transfer_ring(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring) +{ + struct bcm4377_create_transfer_ring_msg msg; + u16 flags = 0; + int ret, i; + unsigned long spinlock_flags; + + if (ring->virtual) + flags |= BCM4377_XFER_RING_FLAG_VIRTUAL; + if (ring->sync) + flags |= BCM4377_XFER_RING_FLAG_SYNC; + + spin_lock_irqsave(&ring->lock, spinlock_flags); + memset(&msg, 0, sizeof(msg)); + msg.msg_type = BCM4377_CONTROL_MSG_CREATE_XFER_RING; + msg.ring_id = cpu_to_le16(ring->ring_id); + msg.ring_id_again = cpu_to_le16(ring->ring_id); + msg.ring_iova = cpu_to_le64(ring->ring_dma); + msg.n_elements = cpu_to_le16(ring->n_entries); + msg.completion_ring_id = cpu_to_le16(ring->completion_ring); + msg.doorbell = cpu_to_le16(ring->doorbell); + msg.flags = cpu_to_le16(flags); + msg.footer_size = ring->payload_size / 4; + + bcm4377->ring_state->xfer_ring_head[ring->ring_id] = 0; + bcm4377->ring_state->xfer_ring_tail[ring->ring_id] = 0; + ring->generation++; + spin_unlock_irqrestore(&ring->lock, spinlock_flags); + + ret = bcm4377_enqueue(bcm4377, &bcm4377->control_h2d_ring, &msg, + sizeof(msg), true); + + spin_lock_irqsave(&ring->lock, spinlock_flags); + + if (ring->d2h_buffers_only) { + for (i = 0; i < ring->n_entries; ++i) { + struct bcm4377_xfer_ring_entry *entry = + ring->ring + i * sizeof(*entry); + u16 raw_msgid = FIELD_PREP(BCM4377_MSGID_GENERATION, + ring->generation); + raw_msgid |= FIELD_PREP(BCM4377_MSGID_ID, i); + + memset(entry, 0, sizeof(*entry)); + entry->id = cpu_to_le16(raw_msgid); + entry->len = cpu_to_le16(ring->mapped_payload_size); + entry->flags = BCM4377_XFER_RING_FLAG_PAYLOAD_MAPPED; + entry->payload = + cpu_to_le64(ring->payloads_dma + + i * ring->mapped_payload_size); + } + } + + /* + * send some messages if this is a device->host ring to allow the device + * to reply by acknowledging them in the completion ring + */ + if (ring->virtual || ring->d2h_buffers_only) { + bcm4377->ring_state->xfer_ring_head[ring->ring_id] = + cpu_to_le16(0xf); + bcm4377_ring_doorbell(bcm4377, ring->doorbell, 0xf); + } + + ring->enabled = true; + spin_unlock_irqrestore(&ring->lock, spinlock_flags); + + return ret; +} + +static int bcm4377_destroy_transfer_ring(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring) +{ + struct bcm4377_destroy_transfer_ring_msg msg; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.msg_type = BCM4377_CONTROL_MSG_DESTROY_XFER_RING; + msg.ring_id = cpu_to_le16(ring->ring_id); + + ret = bcm4377_enqueue(bcm4377, &bcm4377->control_h2d_ring, &msg, + sizeof(msg), true); + if (ret) + dev_warn(&bcm4377->pdev->dev, + "failed to destroy transfer ring %d\n", ring->ring_id); + + ring->enabled = false; + return ret; +} + +static int __bcm4378_send_calibration_chunk(struct bcm4377_data *bcm4377, + const void *data, size_t data_len, + u16 blocks_left) +{ + struct bcm4378_hci_send_calibration_cmd cmd; + struct sk_buff *skb; + + if (data_len > sizeof(cmd.data)) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.unk = 0x03; + cmd.blocks_left = cpu_to_le16(blocks_left); + memcpy(cmd.data, data, data_len); + + skb = __hci_cmd_sync(bcm4377->hdev, 0xfd97, sizeof(cmd), &cmd, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + kfree_skb(skb); + return 0; +} + +static int __bcm4378_send_calibration(struct bcm4377_data *bcm4377, + const void *data, size_t data_size) +{ + int ret; + size_t i, left, transfer_len; + size_t blocks = + DIV_ROUND_UP(data_size, (size_t)BCM4378_CALIBRATION_CHUNK_SIZE); + + if (!data) { + dev_err(&bcm4377->pdev->dev, + "no calibration data available.\n"); + return -ENOENT; + } + + for (i = 0, left = data_size; i < blocks; ++i, left -= transfer_len) { + transfer_len = + min_t(size_t, left, BCM4378_CALIBRATION_CHUNK_SIZE); + + ret = __bcm4378_send_calibration_chunk( + bcm4377, data + i * BCM4378_CALIBRATION_CHUNK_SIZE, + transfer_len, blocks - i - 1); + if (ret) { + dev_err(&bcm4377->pdev->dev, + "send calibration chunk failed with %d\n", ret); + return ret; + } + } + + return 0; +} + +static int bcm4378_send_calibration(struct bcm4377_data *bcm4377) +{ + if ((strcmp(bcm4377->stepping, "b1") == 0) || + strcmp(bcm4377->stepping, "b3") == 0) + return __bcm4378_send_calibration( + bcm4377, bcm4377->taurus_beamforming_cal_blob, + bcm4377->taurus_beamforming_cal_size); + else + return __bcm4378_send_calibration(bcm4377, + bcm4377->taurus_cal_blob, + bcm4377->taurus_cal_size); +} + +static int bcm4387_send_calibration(struct bcm4377_data *bcm4377) +{ + if (strcmp(bcm4377->stepping, "c2") == 0) + return __bcm4378_send_calibration( + bcm4377, bcm4377->taurus_beamforming_cal_blob, + bcm4377->taurus_beamforming_cal_size); + else + return __bcm4378_send_calibration(bcm4377, + bcm4377->taurus_cal_blob, + bcm4377->taurus_cal_size); +} + +static const struct firmware *bcm4377_request_blob(struct bcm4377_data *bcm4377, + const char *suffix) +{ + const struct firmware *fw; + char name0[64], name1[64]; + int ret; + + snprintf(name0, sizeof(name0), "brcm/brcmbt%04x%s-%s-%s.%s", + bcm4377->hw->id, bcm4377->stepping, bcm4377->board_type, + bcm4377->vendor, suffix); + snprintf(name1, sizeof(name1), "brcm/brcmbt%04x%s-%s.%s", + bcm4377->hw->id, bcm4377->stepping, bcm4377->board_type, + suffix); + dev_dbg(&bcm4377->pdev->dev, "Trying to load firmware: '%s' or '%s'\n", + name0, name1); + + ret = firmware_request_nowarn(&fw, name0, &bcm4377->pdev->dev); + if (!ret) + return fw; + ret = firmware_request_nowarn(&fw, name1, &bcm4377->pdev->dev); + if (!ret) + return fw; + + dev_err(&bcm4377->pdev->dev, + "Unable to load firmware; tried '%s' and '%s'\n", name0, name1); + return NULL; +} + +static int bcm4377_send_ptb(struct bcm4377_data *bcm4377, + const struct firmware *fw) +{ + struct sk_buff *skb; + int ret = 0; + + skb = __hci_cmd_sync(bcm4377->hdev, 0xfd98, fw->size, fw->data, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + ret = PTR_ERR(skb); + dev_err(&bcm4377->pdev->dev, "sending ptb failed (%d)", ret); + return ret; + } + + kfree_skb(skb); + return ret; +} + +static int bcm4378_send_ptb_chunk(struct bcm4377_data *bcm4377, + const void *data, size_t data_len, + u16 blocks_left) +{ + struct bcm4378_hci_send_ptb_cmd cmd; + struct sk_buff *skb; + + if (data_len > BCM4378_PTB_CHUNK_SIZE) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.blocks_left = cpu_to_le16(blocks_left); + memcpy(cmd.data, data, data_len); + + skb = __hci_cmd_sync(bcm4377->hdev, 0xfe0d, sizeof(cmd), &cmd, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + kfree_skb(skb); + return 0; +} + +static int bcm4378_send_ptb(struct bcm4377_data *bcm4377, + const struct firmware *fw) +{ + size_t chunks = DIV_ROUND_UP(fw->size, (size_t)BCM4378_PTB_CHUNK_SIZE); + size_t i, left, transfer_len; + int ret; + + for (i = 0, left = fw->size; i < chunks; ++i, left -= transfer_len) { + transfer_len = min_t(size_t, left, BCM4378_PTB_CHUNK_SIZE); + + dev_dbg(&bcm4377->pdev->dev, "sending ptb chunk %zu/%zu\n", + i + 1, chunks); + ret = bcm4378_send_ptb_chunk( + bcm4377, fw->data + i * BCM4378_PTB_CHUNK_SIZE, + transfer_len, chunks - i - 1); + if (ret) { + dev_err(&bcm4377->pdev->dev, + "sending ptb chunk %zu failed (%d)", i, ret); + return ret; + } + } + + return 0; +} + +static int bcm4377_hci_open(struct hci_dev *hdev) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + int ret; + + dev_dbg(&bcm4377->pdev->dev, "creating rings\n"); + + ret = bcm4377_create_completion_ring(bcm4377, + &bcm4377->hci_acl_ack_ring); + if (ret) + return ret; + ret = bcm4377_create_completion_ring(bcm4377, + &bcm4377->hci_acl_event_ring); + if (ret) + goto destroy_hci_acl_ack; + ret = bcm4377_create_completion_ring(bcm4377, &bcm4377->sco_ack_ring); + if (ret) + goto destroy_hci_acl_event; + ret = bcm4377_create_completion_ring(bcm4377, &bcm4377->sco_event_ring); + if (ret) + goto destroy_sco_ack; + dev_dbg(&bcm4377->pdev->dev, + "all completion rings successfully created!\n"); + + ret = bcm4377_create_transfer_ring(bcm4377, &bcm4377->hci_h2d_ring); + if (ret) + goto destroy_sco_event; + ret = bcm4377_create_transfer_ring(bcm4377, &bcm4377->hci_d2h_ring); + if (ret) + goto destroy_hci_h2d; + ret = bcm4377_create_transfer_ring(bcm4377, &bcm4377->sco_h2d_ring); + if (ret) + goto destroy_hci_d2h; + ret = bcm4377_create_transfer_ring(bcm4377, &bcm4377->sco_d2h_ring); + if (ret) + goto destroy_sco_h2d; + ret = bcm4377_create_transfer_ring(bcm4377, &bcm4377->acl_h2d_ring); + if (ret) + goto destroy_sco_d2h; + ret = bcm4377_create_transfer_ring(bcm4377, &bcm4377->acl_d2h_ring); + if (ret) + goto destroy_acl_h2d; + dev_dbg(&bcm4377->pdev->dev, + "all transfer rings successfully created!\n"); + + return 0; + +destroy_acl_h2d: + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_h2d_ring); +destroy_sco_d2h: + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_d2h_ring); +destroy_sco_h2d: + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_h2d_ring); +destroy_hci_d2h: + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_h2d_ring); +destroy_hci_h2d: + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_d2h_ring); +destroy_sco_event: + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_event_ring); +destroy_sco_ack: + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_ack_ring); +destroy_hci_acl_event: + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_event_ring); +destroy_hci_acl_ack: + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_ack_ring); + + dev_err(&bcm4377->pdev->dev, "Creating rings failed with %d\n", ret); + return ret; +} + +static int bcm4377_hci_close(struct hci_dev *hdev) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + + dev_dbg(&bcm4377->pdev->dev, "destroying rings in hci_close\n"); + + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_d2h_ring); + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_h2d_ring); + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_d2h_ring); + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_h2d_ring); + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_d2h_ring); + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_h2d_ring); + + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_event_ring); + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_ack_ring); + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_event_ring); + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_ack_ring); + + return 0; +} + +static bool bcm4377_is_valid_bdaddr(struct bcm4377_data *bcm4377, + bdaddr_t *addr) +{ + if (addr->b[0] != 0x93) + return true; + if (addr->b[1] != 0x76) + return true; + if (addr->b[2] != 0x00) + return true; + if (addr->b[4] != (bcm4377->hw->id & 0xff)) + return true; + if (addr->b[5] != (bcm4377->hw->id >> 8)) + return true; + return false; +} + +static int bcm4377_check_bdaddr(struct bcm4377_data *bcm4377) +{ + struct hci_rp_read_bd_addr *bda; + struct sk_buff *skb; + + skb = __hci_cmd_sync(bcm4377->hdev, HCI_OP_READ_BD_ADDR, 0, NULL, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + int err = PTR_ERR(skb); + + dev_err(&bcm4377->pdev->dev, "HCI_OP_READ_BD_ADDR failed (%d)", + err); + return err; + } + + if (skb->len != sizeof(*bda)) { + dev_err(&bcm4377->pdev->dev, + "HCI_OP_READ_BD_ADDR reply length invalid"); + kfree_skb(skb); + return -EIO; + } + + bda = (struct hci_rp_read_bd_addr *)skb->data; + if (!bcm4377_is_valid_bdaddr(bcm4377, &bda->bdaddr)) + set_bit(HCI_QUIRK_INVALID_BDADDR, &bcm4377->hdev->quirks); + + kfree_skb(skb); + return 0; +} + +static int bcm4377_hci_setup(struct hci_dev *hdev) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + const struct firmware *fw; + int ret; + + if (bcm4377->hw->send_calibration) { + ret = bcm4377->hw->send_calibration(bcm4377); + if (ret) + return ret; + } + + fw = bcm4377_request_blob(bcm4377, "ptb"); + if (!fw) { + dev_err(&bcm4377->pdev->dev, "failed to load PTB data"); + return -ENOENT; + } + + ret = bcm4377->hw->send_ptb(bcm4377, fw); + release_firmware(fw); + if (ret) + return ret; + + return bcm4377_check_bdaddr(bcm4377); +} + +static int bcm4377_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + struct bcm4377_transfer_ring *ring; + int ret; + + switch (hci_skb_pkt_type(skb)) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + ring = &bcm4377->hci_h2d_ring; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + ring = &bcm4377->acl_h2d_ring; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + ring = &bcm4377->sco_h2d_ring; + break; + + default: + return -EILSEQ; + } + + ret = bcm4377_enqueue(bcm4377, ring, skb->data, skb->len, false); + if (ret < 0) { + hdev->stat.err_tx++; + return ret; + } + + hdev->stat.byte_tx += skb->len; + kfree_skb(skb); + return ret; +} + +static int bcm4377_hci_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + struct sk_buff *skb; + int err; + + skb = __hci_cmd_sync(hdev, 0xfc01, 6, bdaddr, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + dev_err(&bcm4377->pdev->dev, + "Change address command failed (%d)", err); + return err; + } + kfree_skb(skb); + + return 0; +} + +static int bcm4377_alloc_transfer_ring(struct bcm4377_data *bcm4377, + struct bcm4377_transfer_ring *ring) +{ + size_t entry_size; + + spin_lock_init(&ring->lock); + ring->payload_size = ALIGN(ring->payload_size, 4); + ring->mapped_payload_size = ALIGN(ring->mapped_payload_size, 4); + + if (ring->payload_size > BCM4377_XFER_RING_MAX_INPLACE_PAYLOAD_SIZE) + return -EINVAL; + if (ring->n_entries > BCM4377_MAX_RING_SIZE) + return -EINVAL; + if (ring->virtual && ring->allow_wait) + return -EINVAL; + + if (ring->d2h_buffers_only) { + if (ring->virtual) + return -EINVAL; + if (ring->payload_size) + return -EINVAL; + if (!ring->mapped_payload_size) + return -EINVAL; + } + if (ring->virtual) + return 0; + + entry_size = + ring->payload_size + sizeof(struct bcm4377_xfer_ring_entry); + ring->ring = dmam_alloc_coherent(&bcm4377->pdev->dev, + ring->n_entries * entry_size, + &ring->ring_dma, GFP_KERNEL); + if (!ring->ring) + return -ENOMEM; + + if (ring->allow_wait) { + ring->events = devm_kcalloc(&bcm4377->pdev->dev, + ring->n_entries, + sizeof(*ring->events), GFP_KERNEL); + if (!ring->events) + return -ENOMEM; + } + + if (ring->mapped_payload_size) { + ring->payloads = dmam_alloc_coherent( + &bcm4377->pdev->dev, + ring->n_entries * ring->mapped_payload_size, + &ring->payloads_dma, GFP_KERNEL); + if (!ring->payloads) + return -ENOMEM; + } + + return 0; +} + +static int bcm4377_alloc_completion_ring(struct bcm4377_data *bcm4377, + struct bcm4377_completion_ring *ring) +{ + size_t entry_size; + + ring->payload_size = ALIGN(ring->payload_size, 4); + if (ring->payload_size > BCM4377_XFER_RING_MAX_INPLACE_PAYLOAD_SIZE) + return -EINVAL; + if (ring->n_entries > BCM4377_MAX_RING_SIZE) + return -EINVAL; + + entry_size = ring->payload_size + + sizeof(struct bcm4377_completion_ring_entry); + + ring->ring = dmam_alloc_coherent(&bcm4377->pdev->dev, + ring->n_entries * entry_size, + &ring->ring_dma, GFP_KERNEL); + if (!ring->ring) + return -ENOMEM; + return 0; +} + +static int bcm4377_init_context(struct bcm4377_data *bcm4377) +{ + struct device *dev = &bcm4377->pdev->dev; + dma_addr_t peripheral_info_dma; + + bcm4377->ctx = dmam_alloc_coherent(dev, sizeof(*bcm4377->ctx), + &bcm4377->ctx_dma, GFP_KERNEL); + if (!bcm4377->ctx) + return -ENOMEM; + memset(bcm4377->ctx, 0, sizeof(*bcm4377->ctx)); + + bcm4377->ring_state = + dmam_alloc_coherent(dev, sizeof(*bcm4377->ring_state), + &bcm4377->ring_state_dma, GFP_KERNEL); + if (!bcm4377->ring_state) + return -ENOMEM; + memset(bcm4377->ring_state, 0, sizeof(*bcm4377->ring_state)); + + bcm4377->ctx->version = cpu_to_le16(1); + bcm4377->ctx->size = cpu_to_le16(sizeof(*bcm4377->ctx)); + bcm4377->ctx->enabled_caps = cpu_to_le16(2); + + /* + * The BT device will write 0x20 bytes of data to this buffer but + * the exact contents are unknown. It only needs to exist for BT + * to work such that we can just allocate and then ignore it. + */ + if (!dmam_alloc_coherent(&bcm4377->pdev->dev, 0x20, + &peripheral_info_dma, GFP_KERNEL)) + return -ENOMEM; + bcm4377->ctx->peripheral_info_addr = cpu_to_le64(peripheral_info_dma); + + bcm4377->ctx->xfer_ring_heads_addr = cpu_to_le64( + bcm4377->ring_state_dma + + offsetof(struct bcm4377_ring_state, xfer_ring_head)); + bcm4377->ctx->xfer_ring_tails_addr = cpu_to_le64( + bcm4377->ring_state_dma + + offsetof(struct bcm4377_ring_state, xfer_ring_tail)); + bcm4377->ctx->completion_ring_heads_addr = cpu_to_le64( + bcm4377->ring_state_dma + + offsetof(struct bcm4377_ring_state, completion_ring_head)); + bcm4377->ctx->completion_ring_tails_addr = cpu_to_le64( + bcm4377->ring_state_dma + + offsetof(struct bcm4377_ring_state, completion_ring_tail)); + + bcm4377->ctx->n_completion_rings = + cpu_to_le16(BCM4377_N_COMPLETION_RINGS); + bcm4377->ctx->n_xfer_rings = cpu_to_le16(BCM4377_N_TRANSFER_RINGS); + + bcm4377->ctx->control_completion_ring_addr = + cpu_to_le64(bcm4377->control_ack_ring.ring_dma); + bcm4377->ctx->control_completion_ring_n_entries = + cpu_to_le16(bcm4377->control_ack_ring.n_entries); + bcm4377->ctx->control_completion_ring_doorbell = cpu_to_le16(0xffff); + bcm4377->ctx->control_completion_ring_msi = 0; + bcm4377->ctx->control_completion_ring_header_size = 0; + bcm4377->ctx->control_completion_ring_footer_size = 0; + + bcm4377->ctx->control_xfer_ring_addr = + cpu_to_le64(bcm4377->control_h2d_ring.ring_dma); + bcm4377->ctx->control_xfer_ring_n_entries = + cpu_to_le16(bcm4377->control_h2d_ring.n_entries); + bcm4377->ctx->control_xfer_ring_doorbell = + cpu_to_le16(bcm4377->control_h2d_ring.doorbell); + bcm4377->ctx->control_xfer_ring_msi = 0; + bcm4377->ctx->control_xfer_ring_header_size = 0; + bcm4377->ctx->control_xfer_ring_footer_size = + bcm4377->control_h2d_ring.payload_size / 4; + + dev_dbg(&bcm4377->pdev->dev, "context initialized at IOVA %pad", + &bcm4377->ctx_dma); + + return 0; +} + +static int bcm4377_prepare_rings(struct bcm4377_data *bcm4377) +{ + int ret; + + /* + * Even though many of these settings appear to be configurable + * when sending the "create ring" messages most of these are + * actually hardcoded in some (and quite possibly all) firmware versions + * and changing them on the host has no effect. + * Specifically, this applies to at least the doorbells, the transfer + * and completion ring ids and their mapping (e.g. both HCI and ACL + * entries will always be queued in completion rings 1 and 2 no matter + * what we configure here). + */ + bcm4377->control_ack_ring.ring_id = BCM4377_ACK_RING_CONTROL; + bcm4377->control_ack_ring.n_entries = 32; + bcm4377->control_ack_ring.transfer_rings = + BIT(BCM4377_XFER_RING_CONTROL); + + bcm4377->hci_acl_ack_ring.ring_id = BCM4377_ACK_RING_HCI_ACL; + bcm4377->hci_acl_ack_ring.n_entries = 2 * BCM4377_RING_N_ENTRIES; + bcm4377->hci_acl_ack_ring.transfer_rings = + BIT(BCM4377_XFER_RING_HCI_H2D) | BIT(BCM4377_XFER_RING_ACL_H2D); + bcm4377->hci_acl_ack_ring.delay = 1000; + + /* + * A payload size of MAX_EVENT_PAYLOAD_SIZE is enough here since large + * ACL packets will be transmitted inside buffers mapped via + * acl_d2h_ring anyway. + */ + bcm4377->hci_acl_event_ring.ring_id = BCM4377_EVENT_RING_HCI_ACL; + bcm4377->hci_acl_event_ring.payload_size = MAX_EVENT_PAYLOAD_SIZE; + bcm4377->hci_acl_event_ring.n_entries = 2 * BCM4377_RING_N_ENTRIES; + bcm4377->hci_acl_event_ring.transfer_rings = + BIT(BCM4377_XFER_RING_HCI_D2H) | BIT(BCM4377_XFER_RING_ACL_D2H); + bcm4377->hci_acl_event_ring.delay = 1000; + + bcm4377->sco_ack_ring.ring_id = BCM4377_ACK_RING_SCO; + bcm4377->sco_ack_ring.n_entries = BCM4377_RING_N_ENTRIES; + bcm4377->sco_ack_ring.transfer_rings = BIT(BCM4377_XFER_RING_SCO_H2D); + + bcm4377->sco_event_ring.ring_id = BCM4377_EVENT_RING_SCO; + bcm4377->sco_event_ring.payload_size = MAX_SCO_PAYLOAD_SIZE; + bcm4377->sco_event_ring.n_entries = BCM4377_RING_N_ENTRIES; + bcm4377->sco_event_ring.transfer_rings = BIT(BCM4377_XFER_RING_SCO_D2H); + + bcm4377->control_h2d_ring.ring_id = BCM4377_XFER_RING_CONTROL; + bcm4377->control_h2d_ring.doorbell = BCM4377_DOORBELL_CONTROL; + bcm4377->control_h2d_ring.payload_size = BCM4377_CONTROL_MSG_SIZE; + bcm4377->control_h2d_ring.completion_ring = BCM4377_ACK_RING_CONTROL; + bcm4377->control_h2d_ring.allow_wait = true; + bcm4377->control_h2d_ring.n_entries = BCM4377_RING_N_ENTRIES; + + bcm4377->hci_h2d_ring.ring_id = BCM4377_XFER_RING_HCI_H2D; + bcm4377->hci_h2d_ring.doorbell = BCM4377_DOORBELL_HCI_H2D; + bcm4377->hci_h2d_ring.payload_size = MAX_EVENT_PAYLOAD_SIZE; + bcm4377->hci_h2d_ring.completion_ring = BCM4377_ACK_RING_HCI_ACL; + bcm4377->hci_h2d_ring.n_entries = BCM4377_RING_N_ENTRIES; + + bcm4377->hci_d2h_ring.ring_id = BCM4377_XFER_RING_HCI_D2H; + bcm4377->hci_d2h_ring.doorbell = BCM4377_DOORBELL_HCI_D2H; + bcm4377->hci_d2h_ring.completion_ring = BCM4377_EVENT_RING_HCI_ACL; + bcm4377->hci_d2h_ring.virtual = true; + bcm4377->hci_d2h_ring.n_entries = BCM4377_RING_N_ENTRIES; + + bcm4377->sco_h2d_ring.ring_id = BCM4377_XFER_RING_SCO_H2D; + bcm4377->sco_h2d_ring.doorbell = BCM4377_DOORBELL_SCO; + bcm4377->sco_h2d_ring.payload_size = MAX_SCO_PAYLOAD_SIZE; + bcm4377->sco_h2d_ring.completion_ring = BCM4377_ACK_RING_SCO; + bcm4377->sco_h2d_ring.sync = true; + bcm4377->sco_h2d_ring.n_entries = BCM4377_RING_N_ENTRIES; + + bcm4377->sco_d2h_ring.ring_id = BCM4377_XFER_RING_SCO_D2H; + bcm4377->sco_d2h_ring.doorbell = BCM4377_DOORBELL_SCO; + bcm4377->sco_d2h_ring.completion_ring = BCM4377_EVENT_RING_SCO; + bcm4377->sco_d2h_ring.virtual = true; + bcm4377->sco_d2h_ring.sync = true; + bcm4377->sco_d2h_ring.n_entries = BCM4377_RING_N_ENTRIES; + + /* + * This ring has to use mapped_payload_size because the largest ACL + * packet doesn't fit inside the largest possible footer + */ + bcm4377->acl_h2d_ring.ring_id = BCM4377_XFER_RING_ACL_H2D; + bcm4377->acl_h2d_ring.doorbell = BCM4377_DOORBELL_ACL_H2D; + bcm4377->acl_h2d_ring.mapped_payload_size = MAX_ACL_PAYLOAD_SIZE; + bcm4377->acl_h2d_ring.completion_ring = BCM4377_ACK_RING_HCI_ACL; + bcm4377->acl_h2d_ring.n_entries = BCM4377_RING_N_ENTRIES; + + /* + * This ring only contains empty buffers to be used by incoming + * ACL packets that do not fit inside the footer of hci_acl_event_ring + */ + bcm4377->acl_d2h_ring.ring_id = BCM4377_XFER_RING_ACL_D2H; + bcm4377->acl_d2h_ring.doorbell = BCM4377_DOORBELL_ACL_D2H; + bcm4377->acl_d2h_ring.completion_ring = BCM4377_EVENT_RING_HCI_ACL; + bcm4377->acl_d2h_ring.d2h_buffers_only = true; + bcm4377->acl_d2h_ring.mapped_payload_size = MAX_ACL_PAYLOAD_SIZE; + bcm4377->acl_d2h_ring.n_entries = BCM4377_RING_N_ENTRIES; + + /* + * no need for any cleanup since this is only called from _probe + * and only devres-managed allocations are used + */ + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->control_h2d_ring); + if (ret) + return ret; + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->hci_h2d_ring); + if (ret) + return ret; + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->hci_d2h_ring); + if (ret) + return ret; + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->sco_h2d_ring); + if (ret) + return ret; + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->sco_d2h_ring); + if (ret) + return ret; + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->acl_h2d_ring); + if (ret) + return ret; + ret = bcm4377_alloc_transfer_ring(bcm4377, &bcm4377->acl_d2h_ring); + if (ret) + return ret; + + ret = bcm4377_alloc_completion_ring(bcm4377, + &bcm4377->control_ack_ring); + if (ret) + return ret; + ret = bcm4377_alloc_completion_ring(bcm4377, + &bcm4377->hci_acl_ack_ring); + if (ret) + return ret; + ret = bcm4377_alloc_completion_ring(bcm4377, + &bcm4377->hci_acl_event_ring); + if (ret) + return ret; + ret = bcm4377_alloc_completion_ring(bcm4377, &bcm4377->sco_ack_ring); + if (ret) + return ret; + ret = bcm4377_alloc_completion_ring(bcm4377, &bcm4377->sco_event_ring); + if (ret) + return ret; + + dev_dbg(&bcm4377->pdev->dev, "all rings allocated and prepared\n"); + + return 0; +} + +static int bcm4377_boot(struct bcm4377_data *bcm4377) +{ + const struct firmware *fw; + void *bfr; + dma_addr_t fw_dma; + int ret = 0; + u32 bootstage, rti_status; + + bootstage = ioread32(bcm4377->bar2 + BCM4377_BAR2_BOOTSTAGE); + rti_status = ioread32(bcm4377->bar2 + BCM4377_BAR2_RTI_STATUS); + + if (bootstage != 0) { + dev_err(&bcm4377->pdev->dev, "bootstage is %d and not 0\n", + bootstage); + return -EINVAL; + } + + if (rti_status != 0) { + dev_err(&bcm4377->pdev->dev, "RTI status is %d and not 0\n", + rti_status); + return -EINVAL; + } + + fw = bcm4377_request_blob(bcm4377, "bin"); + if (!fw) { + dev_err(&bcm4377->pdev->dev, "Failed to load firmware\n"); + return -ENOENT; + } + + bfr = dma_alloc_coherent(&bcm4377->pdev->dev, fw->size, &fw_dma, + GFP_KERNEL); + if (!bfr) { + ret = -ENOMEM; + goto out_release_fw; + } + + memcpy(bfr, fw->data, fw->size); + + iowrite32(0, bcm4377->bar0 + BCM4377_BAR0_HOST_WINDOW_LO); + iowrite32(0, bcm4377->bar0 + BCM4377_BAR0_HOST_WINDOW_HI); + iowrite32(BCM4377_DMA_MASK, + bcm4377->bar0 + BCM4377_BAR0_HOST_WINDOW_SIZE); + + iowrite32(lower_32_bits(fw_dma), bcm4377->bar2 + BCM4377_BAR2_FW_LO); + iowrite32(upper_32_bits(fw_dma), bcm4377->bar2 + BCM4377_BAR2_FW_HI); + iowrite32(fw->size, bcm4377->bar2 + BCM4377_BAR2_FW_SIZE); + iowrite32(0, bcm4377->bar0 + BCM4377_BAR0_FW_DOORBELL); + + dev_dbg(&bcm4377->pdev->dev, "waiting for firmware to boot\n"); + + ret = wait_for_completion_interruptible_timeout(&bcm4377->event, + BCM4377_TIMEOUT); + if (ret == 0) { + ret = -ETIMEDOUT; + goto out_dma_free; + } else if (ret < 0) { + goto out_dma_free; + } + + if (bcm4377->bootstage != 2) { + dev_err(&bcm4377->pdev->dev, "boostage %d != 2\n", + bcm4377->bootstage); + ret = -ENXIO; + goto out_dma_free; + } + + dev_dbg(&bcm4377->pdev->dev, "firmware has booted (stage = %x)\n", + bcm4377->bootstage); + ret = 0; + +out_dma_free: + dma_free_coherent(&bcm4377->pdev->dev, fw->size, bfr, fw_dma); +out_release_fw: + release_firmware(fw); + return ret; +} + +static int bcm4377_setup_rti(struct bcm4377_data *bcm4377) +{ + int ret; + + dev_dbg(&bcm4377->pdev->dev, "starting RTI\n"); + iowrite32(1, bcm4377->bar0 + BCM4377_BAR0_RTI_CONTROL); + + ret = wait_for_completion_interruptible_timeout(&bcm4377->event, + BCM4377_TIMEOUT); + if (ret == 0) { + dev_err(&bcm4377->pdev->dev, + "timed out while waiting for RTI to transition to state 1"); + return -ETIMEDOUT; + } else if (ret < 0) { + return ret; + } + + if (bcm4377->rti_status != 1) { + dev_err(&bcm4377->pdev->dev, "RTI did not ack state 1 (%d)\n", + bcm4377->rti_status); + return -ENODEV; + } + dev_dbg(&bcm4377->pdev->dev, "RTI is in state 1\n"); + + /* allow access to the entire IOVA space again */ + iowrite32(0, bcm4377->bar2 + BCM4377_BAR2_RTI_WINDOW_LO); + iowrite32(0, bcm4377->bar2 + BCM4377_BAR2_RTI_WINDOW_HI); + iowrite32(BCM4377_DMA_MASK, + bcm4377->bar2 + BCM4377_BAR2_RTI_WINDOW_SIZE); + + /* setup "Converged IPC" context */ + iowrite32(lower_32_bits(bcm4377->ctx_dma), + bcm4377->bar2 + BCM4377_BAR2_CONTEXT_ADDR_LO); + iowrite32(upper_32_bits(bcm4377->ctx_dma), + bcm4377->bar2 + BCM4377_BAR2_CONTEXT_ADDR_HI); + iowrite32(2, bcm4377->bar0 + BCM4377_BAR0_RTI_CONTROL); + + ret = wait_for_completion_interruptible_timeout(&bcm4377->event, + BCM4377_TIMEOUT); + if (ret == 0) { + dev_err(&bcm4377->pdev->dev, + "timed out while waiting for RTI to transition to state 2"); + return -ETIMEDOUT; + } else if (ret < 0) { + return ret; + } + + if (bcm4377->rti_status != 2) { + dev_err(&bcm4377->pdev->dev, "RTI did not ack state 2 (%d)\n", + bcm4377->rti_status); + return -ENODEV; + } + + dev_dbg(&bcm4377->pdev->dev, + "RTI is in state 2; control ring is ready\n"); + bcm4377->control_ack_ring.enabled = true; + + return 0; +} + +static int bcm4377_parse_otp_board_params(struct bcm4377_data *bcm4377, + char tag, const char *val, size_t len) +{ + if (tag != 'V') + return 0; + if (len >= sizeof(bcm4377->vendor)) + return -EINVAL; + + strscpy(bcm4377->vendor, val, len + 1); + return 0; +} + +static int bcm4377_parse_otp_chip_params(struct bcm4377_data *bcm4377, char tag, + const char *val, size_t len) +{ + size_t idx = 0; + + if (tag != 's') + return 0; + if (len >= sizeof(bcm4377->stepping)) + return -EINVAL; + + while (len != 0) { + bcm4377->stepping[idx] = tolower(val[idx]); + if (val[idx] == '\0') + return 0; + + idx++; + len--; + } + + bcm4377->stepping[idx] = '\0'; + return 0; +} + +static int bcm4377_parse_otp_str(struct bcm4377_data *bcm4377, const u8 *str, + enum bcm4377_otp_params_type type) +{ + const char *p; + int ret; + + p = skip_spaces(str); + while (*p) { + char tag = *p++; + const char *end; + size_t len; + + if (*p++ != '=') /* implicit NUL check */ + return -EINVAL; + + /* *p might be NUL here, if so end == p and len == 0 */ + end = strchrnul(p, ' '); + len = end - p; + + /* leave 1 byte for NUL in destination string */ + if (len > (BCM4377_OTP_MAX_PARAM_LEN - 1)) + return -EINVAL; + + switch (type) { + case BCM4377_OTP_BOARD_PARAMS: + ret = bcm4377_parse_otp_board_params(bcm4377, tag, p, + len); + break; + case BCM4377_OTP_CHIP_PARAMS: + ret = bcm4377_parse_otp_chip_params(bcm4377, tag, p, + len); + break; + default: + ret = -EINVAL; + break; + } + + if (ret) + return ret; + + /* Skip to next arg, if any */ + p = skip_spaces(end); + } + + return 0; +} + +static int bcm4377_parse_otp_sys_vendor(struct bcm4377_data *bcm4377, u8 *otp, + size_t size) +{ + int idx = 4; + const char *chip_params; + const char *board_params; + int ret; + + /* 4-byte header and two empty strings */ + if (size < 6) + return -EINVAL; + + if (get_unaligned_le32(otp) != BCM4377_OTP_VENDOR_HDR) + return -EINVAL; + + chip_params = &otp[idx]; + + /* Skip first string, including terminator */ + idx += strnlen(chip_params, size - idx) + 1; + if (idx >= size) + return -EINVAL; + + board_params = &otp[idx]; + + /* Skip to terminator of second string */ + idx += strnlen(board_params, size - idx); + if (idx >= size) + return -EINVAL; + + /* At this point both strings are guaranteed NUL-terminated */ + dev_dbg(&bcm4377->pdev->dev, + "OTP: chip_params='%s' board_params='%s'\n", chip_params, + board_params); + + ret = bcm4377_parse_otp_str(bcm4377, chip_params, + BCM4377_OTP_CHIP_PARAMS); + if (ret) + return ret; + + ret = bcm4377_parse_otp_str(bcm4377, board_params, + BCM4377_OTP_BOARD_PARAMS); + if (ret) + return ret; + + if (!bcm4377->stepping[0] || !bcm4377->vendor[0]) + return -EINVAL; + + dev_dbg(&bcm4377->pdev->dev, "OTP: stepping=%s, vendor=%s\n", + bcm4377->stepping, bcm4377->vendor); + return 0; +} + +static int bcm4377_parse_otp(struct bcm4377_data *bcm4377) +{ + u8 otp[BCM4377_OTP_SIZE]; + int i; + int ret = -ENOENT; + + for (i = 0; i < BCM4377_OTP_SIZE; ++i) + otp[i] = ioread8(bcm4377->bar0 + bcm4377->hw->otp_offset + i); + + i = 0; + while (i < (BCM4377_OTP_SIZE - 1)) { + u8 type = otp[i]; + u8 length = otp[i + 1]; + + if (type == 0) + break; + + if ((i + 2 + length) > BCM4377_OTP_SIZE) + break; + + switch (type) { + case BCM4377_OTP_SYS_VENDOR: + dev_dbg(&bcm4377->pdev->dev, + "OTP @ 0x%x (%d): SYS_VENDOR", i, length); + ret = bcm4377_parse_otp_sys_vendor(bcm4377, &otp[i + 2], + length); + break; + case BCM4377_OTP_CIS: + dev_dbg(&bcm4377->pdev->dev, "OTP @ 0x%x (%d): CIS", i, + length); + break; + default: + dev_dbg(&bcm4377->pdev->dev, "OTP @ 0x%x (%d): unknown", + i, length); + break; + } + + i += 2 + length; + } + + return ret; +} + +static int bcm4377_init_cfg(struct bcm4377_data *bcm4377) +{ + int ret; + u32 ctrl; + + ret = pci_write_config_dword(bcm4377->pdev, + BCM4377_PCIECFG_BAR0_WINDOW1, + bcm4377->hw->bar0_window1); + if (ret) + return ret; + + ret = pci_write_config_dword(bcm4377->pdev, + BCM4377_PCIECFG_BAR0_WINDOW2, + bcm4377->hw->bar0_window2); + if (ret) + return ret; + + ret = pci_write_config_dword( + bcm4377->pdev, BCM4377_PCIECFG_BAR0_CORE2_WINDOW1, + BCM4377_PCIECFG_BAR0_CORE2_WINDOW1_DEFAULT); + if (ret) + return ret; + + if (bcm4377->hw->has_bar0_core2_window2) { + ret = pci_write_config_dword(bcm4377->pdev, + BCM4377_PCIECFG_BAR0_CORE2_WINDOW2, + bcm4377->hw->bar0_core2_window2); + if (ret) + return ret; + } + + ret = pci_write_config_dword(bcm4377->pdev, BCM4377_PCIECFG_BAR2_WINDOW, + BCM4377_PCIECFG_BAR2_WINDOW_DEFAULT); + if (ret) + return ret; + + ret = pci_read_config_dword(bcm4377->pdev, + BCM4377_PCIECFG_SUBSYSTEM_CTRL, &ctrl); + if (ret) + return ret; + + if (bcm4377->hw->clear_pciecfg_subsystem_ctrl_bit19) + ctrl &= ~BIT(19); + ctrl |= BIT(16); + + return pci_write_config_dword(bcm4377->pdev, + BCM4377_PCIECFG_SUBSYSTEM_CTRL, ctrl); +} + +static int bcm4377_probe_of(struct bcm4377_data *bcm4377) +{ + struct device_node *np = bcm4377->pdev->dev.of_node; + int ret; + + if (!np) + return 0; + + ret = of_property_read_string(np, "brcm,board-type", + &bcm4377->board_type); + if (ret) { + dev_err(&bcm4377->pdev->dev, "no brcm,board-type property\n"); + return ret; + } + + bcm4377->taurus_beamforming_cal_blob = + of_get_property(np, "brcm,taurus-bf-cal-blob", + &bcm4377->taurus_beamforming_cal_size); + if (!bcm4377->taurus_beamforming_cal_blob) { + dev_err(&bcm4377->pdev->dev, + "no brcm,taurus-bf-cal-blob property\n"); + return -ENOENT; + } + bcm4377->taurus_cal_blob = of_get_property(np, "brcm,taurus-cal-blob", + &bcm4377->taurus_cal_size); + if (!bcm4377->taurus_cal_blob) { + dev_err(&bcm4377->pdev->dev, + "no brcm,taurus-cal-blob property\n"); + return -ENOENT; + } + + return 0; +} + +static void bcm4377_disable_aspm(struct bcm4377_data *bcm4377) +{ + pci_disable_link_state(bcm4377->pdev, + PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1); + + /* + * pci_disable_link_state can fail if either CONFIG_PCIEASPM is disabled + * or if the BIOS hasn't handed over control to us. We must *always* + * disable ASPM for this device due to hardware errata though. + */ + pcie_capability_clear_word(bcm4377->pdev, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPMC); +} + +static void bcm4377_pci_free_irq_vectors(void *data) +{ + pci_free_irq_vectors(data); +} + +static void bcm4377_hci_free_dev(void *data) +{ + hci_free_dev(data); +} + +static void bcm4377_hci_unregister_dev(void *data) +{ + hci_unregister_dev(data); +} + +static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct bcm4377_data *bcm4377; + struct hci_dev *hdev; + int ret, irq; + + ret = dma_set_mask_and_coherent(&pdev->dev, BCM4377_DMA_MASK); + if (ret) + return ret; + + bcm4377 = devm_kzalloc(&pdev->dev, sizeof(*bcm4377), GFP_KERNEL); + if (!bcm4377) + return -ENOMEM; + + bcm4377->pdev = pdev; + bcm4377->hw = &bcm4377_hw_variants[id->driver_data]; + init_completion(&bcm4377->event); + + ret = bcm4377_prepare_rings(bcm4377); + if (ret) + return ret; + + ret = bcm4377_init_context(bcm4377); + if (ret) + return ret; + + bcm4377->board_type = bcm4377->hw->board_type; + ret = bcm4377_probe_of(bcm4377); + if (ret) + return ret; + if (!bcm4377->board_type) { + dev_err(&pdev->dev, "unable to determine board type\n"); + return ret; + } + + ret = pci_reset_function_locked(pdev); + if (ret) + dev_warn( + &pdev->dev, + "function level reset failed with %d; trying to continue anyway\n", + ret); + + /* + * If this number is too low and we try to access any BAR too + * early the device will crash. Experiments have shown that + * approximately 50 msec is the minimum amount we have to wait. + * Let's double that to be safe. + */ + msleep(100); + + if (bcm4377->hw->disable_aspm) + bcm4377_disable_aspm(bcm4377); + + ret = pci_enable_device(pdev); + if (ret) + return ret; + pci_set_master(pdev); + + ret = bcm4377_init_cfg(bcm4377); + if (ret) + return ret; + + bcm4377->bar0 = pcim_iomap(pdev, 0, 0); + if (!bcm4377->bar0) + return -EBUSY; + bcm4377->bar2 = pcim_iomap(pdev, 2, 0); + if (!bcm4377->bar2) + return -EBUSY; + + ret = bcm4377_parse_otp(bcm4377); + if (ret) { + dev_err(&pdev->dev, "Reading OTP failed with %d\n", ret); + return ret; + } + + /* + * Legacy interrupts result in an IRQ storm because we don't know where + * the interrupt mask and status registers for these chips are. + * MSIs are acked automatically instead. + */ + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + if (ret < 0) + return -ENODEV; + ret = devm_add_action_or_reset(&pdev->dev, bcm4377_pci_free_irq_vectors, + pdev); + if (ret) + return ret; + + irq = pci_irq_vector(pdev, 0); + if (irq <= 0) + return -ENODEV; + + ret = devm_request_irq(&pdev->dev, irq, bcm4377_irq, 0, "bcm4377", + bcm4377); + if (ret) + return ret; + + hdev = hci_alloc_dev(); + if (!hdev) + return -ENOMEM; + ret = devm_add_action_or_reset(&pdev->dev, bcm4377_hci_free_dev, hdev); + if (ret) + return ret; + + bcm4377->hdev = hdev; + + hdev->bus = HCI_PCI; + hdev->dev_type = HCI_PRIMARY; + hdev->open = bcm4377_hci_open; + hdev->close = bcm4377_hci_close; + hdev->send = bcm4377_hci_send_frame; + hdev->set_bdaddr = bcm4377_hci_set_bdaddr; + hdev->setup = bcm4377_hci_setup; + + set_bit(HCI_QUIRK_USE_BDADDR_PROPERTY, &hdev->quirks); + if (bcm4377->hw->broken_ext_scan) + set_bit(HCI_QUIRK_BROKEN_EXT_SCAN, &hdev->quirks); + if (bcm4377->hw->fixup_le_ext_adv_report_evt_type) + set_bit(HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_EVT_TYPE, + &hdev->quirks); + + pci_set_drvdata(pdev, bcm4377); + hci_set_drvdata(hdev, bcm4377); + SET_HCIDEV_DEV(hdev, &pdev->dev); + + ret = bcm4377_boot(bcm4377); + if (ret) + return ret; + + ret = bcm4377_setup_rti(bcm4377); + if (ret) + return ret; + + ret = hci_register_dev(hdev); + if (ret) + return ret; + return devm_add_action_or_reset(&pdev->dev, bcm4377_hci_unregister_dev, + hdev); +} + +static int bcm4377_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct bcm4377_data *bcm4377 = pci_get_drvdata(pdev); + int ret; + + ret = hci_suspend_dev(bcm4377->hdev); + if (ret) + return ret; + + iowrite32(BCM4377_BAR0_SLEEP_CONTROL_QUIESCED, + bcm4377->bar0 + BCM4377_BAR0_SLEEP_CONTROL); + + return 0; +} + +static int bcm4377_resume(struct pci_dev *pdev) +{ + struct bcm4377_data *bcm4377 = pci_get_drvdata(pdev); + + iowrite32(BCM4377_BAR0_SLEEP_CONTROL_AWAKE, + bcm4377->bar0 + BCM4377_BAR0_SLEEP_CONTROL); + + return hci_resume_dev(bcm4377->hdev); +} + +static const struct bcm4377_hw bcm4377_hw_variants[] = { + [BCM4377] = { + .id = 0x4377, + .otp_offset = 0x4120, + .bar0_window1 = 0x1800b000, + .bar0_window2 = 0x1810c000, + .board_type = "apple,formosa", + .disable_aspm = true, + .broken_ext_scan = true, + .send_ptb = bcm4377_send_ptb, + }, + + [BCM4378] = { + .id = 0x4378, + .otp_offset = 0x4120, + .bar0_window1 = 0x18002000, + .bar0_window2 = 0x1810a000, + .bar0_core2_window2 = 0x18107000, + .has_bar0_core2_window2 = true, + .fixup_le_ext_adv_report_evt_type = true, + .send_calibration = bcm4378_send_calibration, + .send_ptb = bcm4378_send_ptb, + }, + + [BCM4387] = { + .id = 0x4387, + .otp_offset = 0x413c, + .bar0_window1 = 0x18002000, + .bar0_window2 = 0x18109000, + .bar0_core2_window2 = 0x18106000, + .has_bar0_core2_window2 = true, + .clear_pciecfg_subsystem_ctrl_bit19 = true, + .fixup_le_ext_adv_report_evt_type = true, + .send_calibration = bcm4387_send_calibration, + .send_ptb = bcm4378_send_ptb, + }, +}; + +#define BCM4377_DEVID_ENTRY(id) \ + { \ + PCI_VENDOR_ID_BROADCOM, BCM##id##_DEVICE_ID, PCI_ANY_ID, \ + PCI_ANY_ID, PCI_CLASS_NETWORK_OTHER << 8, 0xffff00, \ + BCM##id \ + } + +static const struct pci_device_id bcm4377_devid_table[] = { + BCM4377_DEVID_ENTRY(4377), + BCM4377_DEVID_ENTRY(4378), + BCM4377_DEVID_ENTRY(4387), + {}, +}; +MODULE_DEVICE_TABLE(pci, bcm4377_devid_table); + +static struct pci_driver bcm4377_pci_driver = { + .name = "hci_bcm4377", + .id_table = bcm4377_devid_table, + .probe = bcm4377_probe, + .suspend = bcm4377_suspend, + .resume = bcm4377_resume, +}; +module_pci_driver(bcm4377_pci_driver); + +MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); +MODULE_DESCRIPTION("Bluetooth support for Broadcom 4377/4378/4387 PCIe devices"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_FIRMWARE("brcm/brcmbt4377*.bin"); +MODULE_FIRMWARE("brcm/brcmbt4377*.ptb"); +MODULE_FIRMWARE("brcm/brcmbt4378*.bin"); +MODULE_FIRMWARE("brcm/brcmbt4378*.ptb"); +MODULE_FIRMWARE("brcm/brcmbt4387*.bin"); +MODULE_FIRMWARE("brcm/brcmbt4387*.ptb"); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 954749afb5fe..9a020a0bd9c4 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -41,6 +41,15 @@ config ARM_ALLWINNER_SUN50I_CPUFREQ_NVMEM To compile this driver as a module, choose M here: the module will be called sun50i-cpufreq-nvmem. +config ARM_APPLE_SOC_CPUFREQ + tristate "Apple Silicon SoC CPUFreq support" + depends on ARCH_APPLE || COMPILE_TEST + select PM_OPP + default ARCH_APPLE + help + This adds the CPUFreq driver for Apple Silicon machines + (e.g. Apple M1). + config ARM_ARMADA_37XX_CPUFREQ tristate "Armada 37xx CPUFreq support" depends on ARCH_MVEBU && CPUFREQ_DT diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 285de70af877..c0da6f2f6cd5 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_X86_AMD_FREQ_SENSITIVITY) += amd_freq_sensitivity.o ################################################################################## # ARM SoC drivers +obj-$(CONFIG_ARM_APPLE_SOC_CPUFREQ) += apple-soc-cpufreq.o obj-$(CONFIG_ARM_ARMADA_37XX_CPUFREQ) += armada-37xx-cpufreq.o obj-$(CONFIG_ARM_ARMADA_8K_CPUFREQ) += armada-8k-cpufreq.o obj-$(CONFIG_ARM_BRCMSTB_AVS_CPUFREQ) += brcmstb-avs-cpufreq.o diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c new file mode 100644 index 000000000000..a92f7e7c57b5 --- /dev/null +++ b/drivers/cpufreq/apple-soc-cpufreq.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple SoC CPU cluster performance state driver + * + * Copyright The Asahi Linux Contributors + * + * Based on scpi-cpufreq.c + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/slab.h> + +#define APPLE_DVFS_PSTATE_BITS_T8103 4 +#define APPLE_DVFS_PSTATE_BITS_T8112 5 + +#define APPLE_DVFS_CMD 0x20 +#define APPLE_DVFS_CMD_BUSY BIT(31) +#define APPLE_DVFS_CMD_SET BIT(25) +#define APPLE_DVFS_CMD_PS2 GENMASK(16, 12) +#define APPLE_DVFS_CMD_PS1 GENMASK(4, 0) + +/* Same timebase as CPU counter (24MHz) */ +#define APPLE_DVFS_LAST_CHG_TIME 0x38 + +/* + * Apple ran out of bits and had to shift this in T8112... + */ +#define APPLE_DVFS_STATUS 0x50 +#define APPLE_DVFS_STATUS_CUR_PS_T8103 GENMASK(7, 4) +#define APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8103 4 +#define APPLE_DVFS_STATUS_TGT_PS_T8103 GENMASK(3, 0) +#define APPLE_DVFS_STATUS_CUR_PS_T8112 GENMASK(9, 5) +#define APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8112 5 +#define APPLE_DVFS_STATUS_TGT_PS_T8112 GENMASK(4, 0) +/* + * Div is +1, base clock is 12MHz on existing SoCs. + * For documentation purposes. We use the OPP table to + * get the frequency. + */ +#define APPLE_DVFS_PLL_STATUS 0xc0 +#define APPLE_DVFS_PLL_FACTOR 0xc8 +#define APPLE_DVFS_PLL_FACTOR_MULT GENMASK(31, 16) +#define APPLE_DVFS_PLL_FACTOR_DIV GENMASK(15, 0) + +struct apple_soc_cpufreq_info { + u64 max_pstate; + u64 cur_pstate_mask; + u64 cur_pstate_shift; +}; + +struct apple_cpu_priv { + struct device *cpu_dev; + void __iomem *reg_base; + const struct apple_soc_cpufreq_info *info; +}; + +#define APPLE_DVFS_TRANSITION_TIMEOUT 100 + +static struct cpufreq_driver apple_soc_cpufreq_driver; + +static unsigned int apple_soc_cpufreq_get_rate(unsigned int cpu) +{ + struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); + struct apple_cpu_priv *priv = policy->driver_data; + unsigned int pstate; + unsigned int i; + + if (priv->info->cur_pstate_mask) { + u64 reg = readq_relaxed(priv->reg_base + APPLE_DVFS_STATUS); + pstate = (reg & priv->info->cur_pstate_mask) >> priv->info->cur_pstate_shift; + } else { + /* + * For the fallback case we might not know the layout of DVFS_STATUS, + * so just use the command register value (which ignores boost limitations). + */ + u64 reg = readq_relaxed(priv->reg_base + APPLE_DVFS_CMD); + pstate = FIELD_GET(APPLE_DVFS_CMD_PS1, reg); + } + + for (i = 0; policy->freq_table[i].frequency != CPUFREQ_TABLE_END; i++) + if (policy->freq_table[i].driver_data == pstate) + return policy->freq_table[i].frequency; + + dev_err(priv->cpu_dev, "could not find frequency for pstate %d\n", + pstate); + return 0; +} + +static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int index) +{ + struct apple_cpu_priv *priv = policy->driver_data; + unsigned int pstate = policy->freq_table[index].driver_data; + u64 reg; + + /* Fallback for newer SoCs */ + if (index > priv->info->max_pstate) + index = priv->info->max_pstate; + + if (readq_poll_timeout_atomic(priv->reg_base + APPLE_DVFS_CMD, reg, + !(reg & APPLE_DVFS_CMD_BUSY), 2, + APPLE_DVFS_TRANSITION_TIMEOUT)) { + return -EIO; + } + + reg &= ~(APPLE_DVFS_CMD_PS1 | APPLE_DVFS_CMD_PS2); + reg |= FIELD_PREP(APPLE_DVFS_CMD_PS1, pstate); + reg |= FIELD_PREP(APPLE_DVFS_CMD_PS2, pstate); + reg |= APPLE_DVFS_CMD_SET; + + writeq_relaxed(reg, priv->reg_base + APPLE_DVFS_CMD); + + return 0; +} + +static unsigned int apple_soc_cpufreq_fast_switch(struct cpufreq_policy *policy, + unsigned int target_freq) +{ + if (apple_soc_cpufreq_set_target(policy, policy->cached_resolved_idx) < 0) + return 0; + + return policy->freq_table[policy->cached_resolved_idx].frequency; +} + +static int apple_soc_cpufreq_find_cluster(struct cpufreq_policy *policy, + void __iomem **reg_base) +{ + struct of_phandle_args args; + struct device_node *cpu_np; + char name[32]; + int cpu, ret; + int index; + + cpu_np = of_cpu_device_node_get(policy->cpu); + if (!cpu_np) + return -EINVAL; + + ret = of_parse_phandle_with_args(cpu_np, "apple,freq-domain", + "#freq-domain-cells", 0, &args); + of_node_put(cpu_np); + if (ret) + return ret; + + index = args.args[0]; + + snprintf(name, sizeof(name), "cluster%d", index); + ret = of_property_match_string(args.np, "reg-names", name); + if (ret < 0) + return ret; + + *reg_base = of_iomap(args.np, ret); + if (IS_ERR(*reg_base)) + return PTR_ERR(*reg_base); + + for_each_possible_cpu(cpu) { + cpu_np = of_cpu_device_node_get(cpu); + if (!cpu_np) + continue; + + ret = of_parse_phandle_with_args(cpu_np, "apple,freq-domain", + "#freq-domain-cells", 0, &args); + of_node_put(cpu_np); + if (ret < 0) + continue; + + if (index == args.args[0]) + cpumask_set_cpu(cpu, policy->cpus); + } + + return 0; +} + +static struct freq_attr *apple_soc_cpufreq_hw_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, + NULL, +}; + +static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) +{ + int ret, i; + unsigned int transition_latency; + void __iomem *reg_base; + struct device *cpu_dev; + struct apple_cpu_priv *priv; + struct cpufreq_frequency_table *freq_table; + struct platform_device *pdev = cpufreq_get_driver_data(); + + cpu_dev = get_cpu_device(policy->cpu); + if (!cpu_dev) { + pr_err("failed to get cpu%d device\n", policy->cpu); + return -ENODEV; + } + + ret = dev_pm_opp_of_add_table(cpu_dev); + if (ret < 0) { + dev_err(cpu_dev, "%s: failed to add OPP table: %d\n", __func__, ret); + return ret; + } + + ret = apple_soc_cpufreq_find_cluster(policy, ®_base); + if (ret) { + dev_err(cpu_dev, "%s: failed to get cluster info: %d\n", __func__, ret); + return ret; + } + + ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); + if (ret) { + dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", __func__, ret); + goto out_iounmap; + } + + ret = dev_pm_opp_get_opp_count(cpu_dev); + if (ret <= 0) { + dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n"); + ret = -EPROBE_DEFER; + goto out_free_opp; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto out_free_opp; + } + + priv->info = of_device_get_match_data(&pdev->dev); + if (!priv->info) { + ret = -ENODEV; + goto out_free_opp; + } + + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); + if (ret) { + dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); + goto out_free_priv; + } + + /* Get OPP levels (p-state indexes) and stash them in driver_data */ + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { + unsigned long rate = freq_table[i].frequency * 1000; + struct dev_pm_opp *opp = dev_pm_opp_find_freq_floor(cpu_dev, &rate); + + if (IS_ERR(opp)) { + ret = PTR_ERR(opp); + goto out_free_cpufreq_table; + } + freq_table[i].driver_data = dev_pm_opp_get_level(opp); + dev_pm_opp_put(opp); + } + + priv->cpu_dev = cpu_dev; + priv->reg_base = reg_base; + policy->driver_data = priv; + policy->freq_table = freq_table; + + transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev); + if (!transition_latency) + transition_latency = CPUFREQ_ETERNAL; + + policy->cpuinfo.transition_latency = transition_latency; + policy->dvfs_possible_from_any_cpu = true; + policy->fast_switch_possible = true; + + if (policy_has_boost_freq(policy)) { + ret = cpufreq_enable_boost_support(); + if (ret) { + dev_warn(cpu_dev, "failed to enable boost: %d\n", ret); + } else { + apple_soc_cpufreq_hw_attr[1] = &cpufreq_freq_attr_scaling_boost_freqs; + apple_soc_cpufreq_driver.boost_enabled = true; + } + } + + return 0; + +out_free_cpufreq_table: + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); +out_free_priv: + kfree(priv); +out_free_opp: + dev_pm_opp_remove_all_dynamic(cpu_dev); +out_iounmap: + iounmap(reg_base); + return ret; +} + +static int apple_soc_cpufreq_exit(struct cpufreq_policy *policy) +{ + struct apple_cpu_priv *priv = policy->driver_data; + + dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); + dev_pm_opp_remove_all_dynamic(priv->cpu_dev); + iounmap(priv->reg_base); + kfree(priv); + + return 0; +} + +static struct cpufreq_driver apple_soc_cpufreq_driver = { + .name = "apple-cpufreq", + .flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY | + CPUFREQ_NEED_INITIAL_FREQ_CHECK | CPUFREQ_IS_COOLING_DEV, + .verify = cpufreq_generic_frequency_table_verify, + .attr = cpufreq_generic_attr, + .get = apple_soc_cpufreq_get_rate, + .init = apple_soc_cpufreq_init, + .exit = apple_soc_cpufreq_exit, + .target_index = apple_soc_cpufreq_set_target, + .fast_switch = apple_soc_cpufreq_fast_switch, + .register_em = cpufreq_register_em_with_opp, + .attr = apple_soc_cpufreq_hw_attr, +}; + +static int apple_soc_cpufreq_probe(struct platform_device *pdev) +{ + int ret; + + apple_soc_cpufreq_driver.driver_data = pdev; + + ret = cpufreq_register_driver(&apple_soc_cpufreq_driver); + if (ret) + return dev_err_probe(&pdev->dev, ret, "registering cpufreq failed\n"); + + return 0; +} + +static int apple_soc_cpufreq_remove(struct platform_device *pdev) +{ + cpufreq_unregister_driver(&apple_soc_cpufreq_driver); + + return 0; +} + +const struct apple_soc_cpufreq_info soc_t8103_info = { + .max_pstate = 15, + .cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8103, + .cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8103, +}; + +const struct apple_soc_cpufreq_info soc_t8112_info = { + .max_pstate = 31, + .cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8112, + .cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8112, +}; + +const struct apple_soc_cpufreq_info soc_default_info = { + .max_pstate = 15, + .cur_pstate_mask = 0, /* fallback */ +}; + +static const struct of_device_id apple_soc_cpufreq_of_match[] = { + { + .compatible = "apple,t8103-soc-cpufreq", + .data = &soc_t8103_info, + }, + { + .compatible = "apple,t8112-cpufreq", + .data = &soc_t8112_info, + }, + { + .compatible = "apple,soc-cpufreq", + .data = &soc_default_info, + }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_soc_cpufreq_of_match); + +static struct platform_driver apple_soc_cpufreq_plat_driver = { + .probe = apple_soc_cpufreq_probe, + .remove = apple_soc_cpufreq_remove, + .driver = { + .name = "apple-soc-cpufreq", + .of_match_table = apple_soc_cpufreq_of_match, + }, +}; +module_platform_driver(apple_soc_cpufreq_plat_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_DESCRIPTION("Apple SoC CPU cluster DVFS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 2c96de3f2d83..12b203d67779 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -103,6 +103,8 @@ static const struct of_device_id allowlist[] __initconst = { static const struct of_device_id blocklist[] __initconst = { { .compatible = "allwinner,sun50i-h6", }, + { .compatible = "apple,arm-platform", }, + { .compatible = "arm,vexpress", }, { .compatible = "calxeda,highbank", }, diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 487ed4ddc3be..bda78b8172d4 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -85,6 +85,14 @@ config AMCC_PPC440SPE_ADMA help Enable support for the AMCC PPC440SPe RAID engines. +config APPLE_ADMAC + tristate "Apple ADMAC support" + depends on ARCH_APPLE || COMPILE_TEST + select DMA_ENGINE + default ARCH_APPLE + help + Enable support for Audio DMA Controller found on Apple Silicon chips. + config AT_HDMAC tristate "Atmel AHB DMA support" depends on ARCH_AT91 diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 2f1b87ffd7ab..10f7d4241001 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_ALTERA_MSGDMA) += altera-msgdma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ obj-$(CONFIG_AMD_PTDMA) += ptdma/ +obj-$(CONFIG_APPLE_ADMAC) += apple-admac.o obj-$(CONFIG_AT_HDMAC) += at_hdmac.o obj-$(CONFIG_AT_XDMAC) += at_xdmac.o obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c new file mode 100644 index 000000000000..aa4c07bcb72a --- /dev/null +++ b/drivers/dma/apple-admac.c @@ -0,0 +1,752 @@ +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_dma.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/pm_runtime.h> + +#include "dmaengine.h" + +#define NCHANNELS_MAX 64 + +#define RING_WRITE_SLOT GENMASK(1, 0) +#define RING_READ_SLOT GENMASK(5, 4) +#define RING_FULL BIT(9) +#define RING_EMPTY BIT(8) +#define RING_ERR BIT(10) + +#define STATUS_DESC_DONE BIT(0) +#define STATUS_ERR BIT(6) + +#define FLAG_DESC_NOTIFY BIT(16) + +#define REG_TX_START 0x0000 +#define REG_TX_STOP 0x0004 +#define REG_RX_START 0x0008 +#define REG_RX_STOP 0x000c + +#define REG_CHAN_CTL(ch) (0x8000 + (ch)*0x200) +#define REG_CHAN_CTL_RST_RINGS BIT(0) + +#define REG_DESC_RING(ch) (0x8070 + (ch)*0x200) +#define REG_REPORT_RING(ch) (0x8074 + (ch)*0x200) + +#define REG_RESIDUE(ch) (0x8064 + (ch)*0x200) + +#define REG_BUS_WIDTH(ch) (0x8040 + (ch)*0x200) + +#define BUS_WIDTH_8BIT 0x00 +#define BUS_WIDTH_16BIT 0x01 +#define BUS_WIDTH_32BIT 0x02 +#define BUS_WIDTH_FRAME_2_WORDS 0x10 +#define BUS_WIDTH_FRAME_4_WORDS 0x20 + +#define REG_CHAN_BURSTSIZE(ch) (0x8054 + (ch)*0x200) + +#define REG_DESC_WRITE(ch) (0x10000 + (ch / 2) * 0x4 + (ch & 1) * 0x4000) +#define REG_REPORT_READ(ch) (0x10100 + (ch / 2) * 0x4 + (ch & 1) * 0x4000) + +#define IRQ_INDEX_MAX 3 + +#define REG_TX_INTSTATE(idx) (0x0030 + (idx) * 4) +#define REG_RX_INTSTATE(idx) (0x0040 + (idx) * 4) +#define REG_CHAN_INTSTATUS(ch,idx) (0x8010 + (ch) * 0x200 + (idx) * 4) +#define REG_CHAN_INTMASK(ch,idx) (0x8020 + (ch) * 0x200 + (idx) * 4) + +struct admac_data; +struct admac_tx; + +struct admac_chan { + int no; + struct admac_data *host; + struct dma_chan chan; + struct tasklet_struct tasklet; + + spinlock_t lock; + struct admac_tx *current_tx; + int nperiod_acks; + + struct list_head submitted; + struct list_head issued; +}; + +struct admac_data { + struct dma_device dma; + struct device *dev; + __iomem void *base; + + int irq_index; + int nchannels; + struct admac_chan channels[]; +}; + +struct admac_tx { + struct dma_async_tx_descriptor tx; + bool cyclic; + dma_addr_t buf_addr; + dma_addr_t buf_end; + size_t buf_len; + size_t period_len; + + size_t submitted_pos; + size_t reclaimed_pos; + + struct list_head node; +}; + +static void admac_poke(struct admac_data *ad, int reg, u32 val) +{ + writel_relaxed(val, ad->base + reg); +} + +static u32 admac_peek(struct admac_data *ad, int reg) +{ + return readl_relaxed(ad->base + reg); +} + +static void admac_modify(struct admac_data *ad, int reg, u32 mask, u32 val) +{ + void __iomem *addr = ad->base + reg; + u32 curr = readl_relaxed(addr); + + writel_relaxed((curr & ~mask) | (val & mask), addr); +} + +static struct admac_chan *to_admac_chan(struct dma_chan *chan) +{ + return container_of(chan, struct admac_chan, chan); +} + +static struct admac_tx *to_admac_tx(struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct admac_tx, tx); +} + +static enum dma_transfer_direction admac_chan_direction(int channo) +{ + return (channo & 1) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; +} + +static dma_cookie_t admac_tx_submit(struct dma_async_tx_descriptor *tx) +{ + struct admac_tx *adtx = to_admac_tx(tx); + struct admac_chan *adchan = to_admac_chan(tx->chan); + unsigned long flags; + dma_cookie_t cookie; + + spin_lock_irqsave(&adchan->lock, flags); + cookie = dma_cookie_assign(tx); + list_add_tail(&adtx->node, &adchan->submitted); + spin_unlock_irqrestore(&adchan->lock, flags); + + return cookie; +} + +static int admac_desc_free(struct dma_async_tx_descriptor *tx) +{ + struct admac_tx *adtx = to_admac_tx(tx); + devm_kfree(to_admac_chan(tx->chan)->host->dev, adtx); + return 0; +} + +static struct dma_async_tx_descriptor *admac_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct admac_chan *adchan = container_of(chan, struct admac_chan, chan); + struct admac_tx *adtx; + + if (direction != admac_chan_direction(adchan->no)) + return NULL; + + adtx = devm_kzalloc(adchan->host->dev, sizeof(*adtx), GFP_NOWAIT); + if (!adtx) + return NULL; + + adtx->cyclic = true; + + adtx->buf_addr = buf_addr; + adtx->buf_len = buf_len; + adtx->buf_end = buf_addr + buf_len; + adtx->period_len = period_len; + + adtx->submitted_pos = 0; + adtx->reclaimed_pos = 0; + + dma_async_tx_descriptor_init(&adtx->tx, chan); + adtx->tx.tx_submit = admac_tx_submit; + adtx->tx.desc_free = admac_desc_free; + + return &adtx->tx; +} + +/* + * Write one hardware descriptor for a dmaegine cyclic transaction. + */ +static void admac_cyclic_write_one_desc(struct admac_data *ad, int channo, + struct admac_tx *tx) +{ + dma_addr_t addr; + + if (WARN_ON(!tx->cyclic)) + return; + + addr = tx->buf_addr + (tx->submitted_pos % tx->buf_len); + WARN_ON(addr + tx->period_len > tx->buf_end); + + dev_dbg(ad->dev, "ch%d descriptor: addr=0x%llx len=0x%x flags=0x%lx\n", + channo, addr, (u32) tx->period_len, FLAG_DESC_NOTIFY); + + admac_poke(ad, REG_DESC_WRITE(channo), addr); + admac_poke(ad, REG_DESC_WRITE(channo), addr >> 32); + admac_poke(ad, REG_DESC_WRITE(channo), tx->period_len); + admac_poke(ad, REG_DESC_WRITE(channo), FLAG_DESC_NOTIFY); + + tx->submitted_pos += tx->period_len; + tx->submitted_pos %= 2 * tx->buf_len; +} + +/* + * Write all the hardware descriptors for a cyclic transaction + * there is space for. + */ +static void admac_cyclic_write_desc(struct admac_data *ad, int channo, + struct admac_tx *tx) +{ + int i; + + for (i = 0; i < 4; i++) { + if (admac_peek(ad, REG_DESC_RING(channo)) & RING_FULL) + break; + admac_cyclic_write_one_desc(ad, channo, tx); + } +} + +static int admac_alloc_chan_resources(struct dma_chan *chan) +{ + return 0; +} + +static void admac_free_chan_resources(struct dma_chan *chan) +{ + // TODO +} + +static int admac_ring_noccupied_slots(int ringval) +{ + int wrslot = FIELD_GET(RING_WRITE_SLOT, ringval); + int rdslot = FIELD_GET(RING_READ_SLOT, ringval); + + if (wrslot != rdslot) { + return (wrslot + 4 - rdslot) % 4; + } else { + WARN_ON((ringval & (RING_FULL | RING_EMPTY)) == 0); + + if (ringval & RING_FULL) + return 4; + else + return 0; + } +} + +/* + * Read from hardware the residue of a cyclic dmaengine transaction. + */ +u32 admac_cyclic_read_residue(struct admac_data *ad, int channo, struct admac_tx *adtx) +{ + u32 ring1, ring2; + u32 residue1, residue2; + int nreports; + size_t pos; + + ring1 = admac_peek(ad, REG_REPORT_RING(channo)); + residue1 = admac_peek(ad, REG_RESIDUE(channo)); + ring2 = admac_peek(ad, REG_REPORT_RING(channo)); + residue2 = admac_peek(ad, REG_RESIDUE(channo)); + + if (residue2 > residue1) { + // engine must have loaded next descriptor between the two residue reads + nreports = admac_ring_noccupied_slots(ring1) + 1; + } else { + // no descriptor load between the two reads, ring2 is safe to use + nreports = admac_ring_noccupied_slots(ring2); + } + + pos = adtx->reclaimed_pos + adtx->period_len * (nreports + 1) \ + - residue2; + + return adtx->buf_len - pos % adtx->buf_len; +} + +static enum dma_status admac_tx_status(struct dma_chan *chan, dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct admac_chan *adchan = to_admac_chan(chan); + struct admac_data *ad = adchan->host; + struct admac_tx *adtx; + + enum dma_status ret; + size_t residue; + unsigned long flags; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_COMPLETE || !txstate) + return ret; + + spin_lock_irqsave(&adchan->lock, flags); + adtx = adchan->current_tx; + + if (adtx && adtx->tx.cookie == cookie) { + ret = DMA_IN_PROGRESS; + residue = admac_cyclic_read_residue(ad, adchan->no, adtx); + } else { + ret = DMA_IN_PROGRESS; + residue = 0; + list_for_each_entry(adtx, &adchan->issued, node) { + if (adtx->tx.cookie == cookie) { + residue = adtx->buf_len; + break; + } + } + } + spin_unlock_irqrestore(&adchan->lock, flags); + + dma_set_residue(txstate, residue); + return ret; +} + +static void admac_start_chan(struct admac_chan *adchan) +{ + u32 startbit = 1 << (adchan->no / 2); + switch (admac_chan_direction(adchan->no)) { + case DMA_MEM_TO_DEV: + admac_poke(adchan->host, REG_TX_START, startbit); + break; + case DMA_DEV_TO_MEM: + admac_poke(adchan->host, REG_RX_START, startbit); + break; + default: + break; + } + dev_dbg(adchan->host->dev, "ch%d start\n", adchan->no); +} + +static void admac_stop_chan(struct admac_chan *adchan) +{ + u32 stopbit = 1 << (adchan->no / 2); + switch (admac_chan_direction(adchan->no)) { + case DMA_MEM_TO_DEV: + admac_poke(adchan->host, REG_TX_STOP, stopbit); + break; + case DMA_DEV_TO_MEM: + admac_poke(adchan->host, REG_RX_STOP, stopbit); + break; + default: + break; + } + dev_dbg(adchan->host->dev, "ch%d stop\n", adchan->no); +} + +static void admac_start_current_tx(struct admac_chan *adchan) +{ + struct admac_data *ad = adchan->host; + int ch = adchan->no; + + admac_poke(ad, REG_CHAN_INTSTATUS(ch, ad->irq_index), + STATUS_DESC_DONE | STATUS_ERR); + admac_poke(ad, REG_CHAN_CTL(ch), REG_CHAN_CTL_RST_RINGS); + admac_poke(ad, REG_CHAN_CTL(ch), 0); + + admac_cyclic_write_one_desc(ad, ch, adchan->current_tx); + admac_start_chan(adchan); + admac_cyclic_write_desc(ad, ch, adchan->current_tx); +} + +static void admac_issue_pending(struct dma_chan *chan) +{ + struct admac_chan *adchan = to_admac_chan(chan); + struct admac_tx *tx; + unsigned long flags; + + spin_lock_irqsave(&adchan->lock, flags); + list_splice_tail_init(&adchan->submitted, &adchan->issued); + if (!list_empty(&adchan->issued) && !adchan->current_tx) { + tx = list_first_entry(&adchan->issued, struct admac_tx, node); + list_del(&tx->node); + + adchan->current_tx = tx; + adchan->nperiod_acks = 0; + admac_start_current_tx(adchan); + } + spin_unlock_irqrestore(&adchan->lock, flags); +} + +static int admac_pause(struct dma_chan *chan) +{ + struct admac_chan *adchan = to_admac_chan(chan); + + admac_stop_chan(adchan); + + return 0; +} + +static int admac_resume(struct dma_chan *chan) +{ + struct admac_chan *adchan = to_admac_chan(chan); + + admac_start_chan(adchan); + + return 0; +} + +static int admac_terminate_all(struct dma_chan *chan) +{ + struct admac_chan *adchan = to_admac_chan(chan); + struct admac_tx *adtx, *_adtx; + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&adchan->lock, flags); + admac_stop_chan(adchan); + adchan->current_tx = NULL; + list_splice_tail_init(&adchan->submitted, &head); + list_splice_tail_init(&adchan->issued, &head); + spin_unlock_irqrestore(&adchan->lock, flags); + + list_for_each_entry_safe(adtx, _adtx, &head, node) { + list_del(&adtx->node); + admac_desc_free(&adtx->tx); + } + + return 0; +} + +static struct dma_chan *admac_dma_of_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct admac_data *ad = (struct admac_data*) ofdma->of_dma_data; + unsigned int index; + + if (dma_spec->args_count != 1) + return NULL; + + index = dma_spec->args[0]; + + if (index >= ad->nchannels) { + dev_err(ad->dev, "channel index %u out of bounds\n", index); + return NULL; + } + + return &ad->channels[index].chan; +} + +static int admac_drain_reports(struct admac_data *ad, int channo) +{ + int count; + + for (count = 0; count < 4; count++) { + u32 countval_hi, countval_lo, unk1, flags; + + if (admac_peek(ad, REG_REPORT_RING(channo)) & RING_EMPTY) + break; + + countval_lo = admac_peek(ad, REG_REPORT_READ(channo)); + countval_hi = admac_peek(ad, REG_REPORT_READ(channo)); + unk1 = admac_peek(ad, REG_REPORT_READ(channo)); + flags = admac_peek(ad, REG_REPORT_READ(channo)); + + dev_dbg(ad->dev, "ch%d report: countval=0x%llx unk1=0x%x flags=0x%x\n", + channo, ((u64) countval_hi) << 32 | countval_lo, unk1, flags); + } + + return count; +} + +static void admac_handle_status_err(struct admac_data *ad, int channo) +{ + bool handled = false; + + if (admac_peek(ad, REG_DESC_RING(channo) & RING_ERR)) { + admac_poke(ad, REG_DESC_RING(channo), RING_ERR); + dev_err(ad->dev, "ch%d descriptor ring error\n", channo); + handled = true; + } + + if (admac_peek(ad, REG_REPORT_RING(channo)) & RING_ERR) { + admac_poke(ad, REG_REPORT_RING(channo), RING_ERR); + dev_err(ad->dev, "ch%d report ring error\n", channo); + handled = true; + } + + if (unlikely(!handled)) { + dev_err(ad->dev, "ch%d unknown error, masking future error interrupts\n", channo); + admac_modify(ad, REG_CHAN_INTMASK(channo, ad->irq_index), STATUS_ERR, 0); + } +} + +static void admac_handle_status_desc_done(struct admac_data *ad, int channo) +{ + struct admac_chan *adchan = &ad->channels[channo]; + unsigned long flags; + int nreports; + + admac_poke(ad, REG_CHAN_INTSTATUS(channo, ad->irq_index), + STATUS_DESC_DONE); + + spin_lock_irqsave(&adchan->lock, flags); + nreports = admac_drain_reports(ad, channo); + + if (adchan->current_tx) { + struct admac_tx *tx = adchan->current_tx; + + adchan->nperiod_acks += nreports; + tx->reclaimed_pos += nreports * tx->period_len; + tx->reclaimed_pos %= 2 * tx->buf_len; + + admac_cyclic_write_desc(ad, channo, tx); + tasklet_schedule(&adchan->tasklet); + } + spin_unlock_irqrestore(&adchan->lock, flags); +} + +static void admac_handle_chan_int(struct admac_data *ad, int no) +{ + u32 cause = admac_peek(ad, REG_CHAN_INTSTATUS(no, ad->irq_index)); + + if (cause & STATUS_ERR) + admac_handle_status_err(ad, no); + + if (cause & STATUS_DESC_DONE) + admac_handle_status_desc_done(ad, no); +} + +static irqreturn_t admac_interrupt(int irq, void *devid) +{ + struct admac_data *ad = devid; + u32 rx_intstate, tx_intstate; + int i; + + rx_intstate = admac_peek(ad, REG_RX_INTSTATE(ad->irq_index)); + tx_intstate = admac_peek(ad, REG_TX_INTSTATE(ad->irq_index)); + + for (i = 0; i < ad->nchannels; i += 2) + if (tx_intstate & (1 << (i / 2))) + admac_handle_chan_int(ad, i); + + for (i = 1; i < ad->nchannels; i += 2) + if (rx_intstate & (1 << (i / 2))) + admac_handle_chan_int(ad, i); + + return (tx_intstate | rx_intstate) != 0 ? IRQ_HANDLED : IRQ_NONE; +} + +static void admac_chan_tasklet(struct tasklet_struct *t) +{ + struct admac_chan *adchan = from_tasklet(adchan, t, tasklet); + struct admac_tx *adtx; + struct dmaengine_desc_callback cb; + struct dmaengine_result tx_result; + int nacks; + + spin_lock_irq(&adchan->lock); + adtx = adchan->current_tx; + nacks = adchan->nperiod_acks; + adchan->nperiod_acks = 0; + spin_unlock_irq(&adchan->lock); + + if (!adtx || !nacks) + return; + + tx_result.result = DMA_TRANS_NOERROR; + tx_result.residue = 0; + + dmaengine_desc_get_callback(&adtx->tx, &cb); + while (nacks--) + dmaengine_desc_callback_invoke(&cb, &tx_result); +} + +static int admac_device_config(struct dma_chan *chan, + struct dma_slave_config *config) +{ + struct admac_chan *adchan = to_admac_chan(chan); + struct admac_data *ad = adchan->host; + bool is_tx = admac_chan_direction(adchan->no) == DMA_MEM_TO_DEV; + u32 bus_width = 0; + + + switch (is_tx ? config->dst_addr_width : config->src_addr_width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + bus_width |= BUS_WIDTH_8BIT; + break; + case DMA_SLAVE_BUSWIDTH_2_BYTES: + bus_width |= BUS_WIDTH_16BIT; + break; + case DMA_SLAVE_BUSWIDTH_4_BYTES: + bus_width |= BUS_WIDTH_32BIT; + break; + default: + return -EINVAL; + } + + switch (is_tx ? config->dst_port_window_size : config->src_port_window_size) { + case 0 ... 1: + break; + case 2: + bus_width |= BUS_WIDTH_FRAME_2_WORDS; + break; + case 4: + bus_width |= BUS_WIDTH_FRAME_4_WORDS; + break; + default: + return -EINVAL; + } + + admac_poke(adchan->host, REG_BUS_WIDTH(adchan->no), bus_width); + + /* TODO: burst size */ + + admac_poke(adchan->host, REG_CHAN_BURSTSIZE(adchan->no), 0xc00060); + + /* TODO: this belongs elsewhere */ + admac_poke(adchan->host, REG_CHAN_INTSTATUS(adchan->no, ad->irq_index), + STATUS_DESC_DONE | STATUS_ERR); + admac_poke(adchan->host, REG_CHAN_INTMASK(adchan->no, ad->irq_index), + STATUS_DESC_DONE | STATUS_ERR); + return 0; +} + +static int admac_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct admac_data *ad; + struct dma_device *dma; + int nchannels; + int err, irq, i; + + err = of_property_read_u32(np, "dma-channels", &nchannels); + if (err || nchannels > NCHANNELS_MAX) { + dev_err(&pdev->dev, "missing or invalid dma-channels property\n"); + return -EINVAL; + } + + ad = devm_kzalloc(&pdev->dev, struct_size(ad, channels, nchannels), GFP_KERNEL); + if (!ad) + return -ENOMEM; + + platform_set_drvdata(pdev, ad); + ad->dev = &pdev->dev; + ad->nchannels = nchannels; + + err = of_property_read_u32(np, "apple,internal-irq-destination", &ad->irq_index); + if (err || ad->irq_index > IRQ_INDEX_MAX) { + dev_err(&pdev->dev, "missing or invalid apple,internal-irq-destination property\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "unable to obtain interrupt resource\n"); + return irq; + } + + err = devm_request_irq(&pdev->dev, irq, admac_interrupt, + 0, dev_name(&pdev->dev), ad); + if (err) { + dev_err(&pdev->dev, "unable to register interrupt: %d\n", err); + return err; + } + + ad->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ad->base)) { + dev_err(&pdev->dev, "unable to obtain MMIO resource\n"); + return PTR_ERR(ad->base); + } + + dma = &ad->dma; + + dma_cap_set(DMA_PRIVATE, dma->cap_mask); + dma_cap_set(DMA_CYCLIC, dma->cap_mask); + + dma->dev = &pdev->dev; + dma->device_alloc_chan_resources = admac_alloc_chan_resources; + dma->device_free_chan_resources = admac_free_chan_resources; + dma->device_tx_status = admac_tx_status; + dma->device_issue_pending = admac_issue_pending; + dma->device_terminate_all = admac_terminate_all; + dma->device_prep_dma_cyclic = admac_prep_dma_cyclic; + dma->device_config = admac_device_config; + dma->device_pause = admac_pause; + dma->device_resume = admac_resume; + + dma->directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM); + dma->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + + INIT_LIST_HEAD(&dma->channels); + for (i = 0; i < nchannels; i++) { + struct admac_chan *adchan = &ad->channels[i]; + adchan->host = ad; + adchan->no = i; + adchan->chan.device = &ad->dma; + dma_cookie_init(&adchan->chan); + spin_lock_init(&adchan->lock); + INIT_LIST_HEAD(&adchan->submitted); + INIT_LIST_HEAD(&adchan->issued); + list_add_tail(&adchan->chan.device_node, &dma->channels); + tasklet_setup(&adchan->tasklet, admac_chan_tasklet); + } + + err = dma_async_device_register(&ad->dma); + if (err) { + dev_err(&pdev->dev, "failed to register DMA device: %d\n", err); + return err; + } + + err = of_dma_controller_register(pdev->dev.of_node, admac_dma_of_xlate, ad); + if (err) { + dev_err(&pdev->dev, "failed to register with OF: %d\n", err); + dma_async_device_unregister(&ad->dma); + return err; + } + + dev_dbg(&pdev->dev, "all good, ready to go!\n"); + + return 0; +} + +static int admac_remove(struct platform_device *pdev) +{ + struct admac_data *ad = platform_get_drvdata(pdev); + + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&ad->dma); + + return 0; +} + +static const struct of_device_id admac_of_match[] = { + { .compatible = "apple,admac", }, + { } +}; +MODULE_DEVICE_TABLE(of, admac_of_match); + +static struct platform_driver apple_admac_driver = { + .driver = { + .name = "apple-admac", + .of_match_table = admac_of_match, + }, + .probe = admac_probe, + .remove = admac_remove, +}; +module_platform_driver(apple_admac_driver); + +MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>"); +MODULE_DESCRIPTION("Driver for Audio DMA Controller (ADMAC) in Apple SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b01961999ced..fc35a7ca31dd 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1282,6 +1282,17 @@ config GPIO_LP87565 This driver can also be built as a module. If so, the module will be called gpio-lp87565. +config GPIO_MACSMC + tristate "Apple Mac SMC GPIO" + depends on APPLE_SMC + default ARCH_APPLE + help + Support for GPIOs controlled by the SMC microcontroller on Apple Mac + systems. + + This driver can also be built as a module. If so, the module will be + called gpio-macsmc. + config GPIO_MADERA tristate "Cirrus Logic Madera class codecs" depends on PINCTRL_MADERA diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 14352f6dfe8e..5f7ec8fc6cdd 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o +obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c new file mode 100644 index 000000000000..2f1f07ea3891 --- /dev/null +++ b/drivers/gpio/gpio-macsmc.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC GPIO driver + * Copyright The Asahi Linux Contributors + * + * This driver implements basic SMC PMU GPIO support that can read inputs + * and write outputs. Mode changes and IRQ config are not yet implemented. + */ + +#include <linux/bitmap.h> +#include <linux/device.h> +#include <linux/gpio/driver.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> + +#define MAX_GPIO 64 + +/* + * Commands 0-6 are, presumably, the intended API. + * Command 0xff lets you get/set the pin configuration in detail directly, + * but the bit meanings seem not to be stable between devices/PMU hardware + * versions. + * + * We're going to try to make do with the low commands for now. + * We don't implement pin mode changes at this time. + */ + +#define CMD_ACTION (0 << 24) +#define CMD_OUTPUT (1 << 24) +#define CMD_INPUT (2 << 24) +#define CMD_PINMODE (3 << 24) +#define CMD_IRQ_ENABLE (4 << 24) +#define CMD_IRQ_ACK (5 << 24) +#define CMD_IRQ_MODE (6 << 24) +#define CMD_CONFIG (0xff << 24) + +#define MODE_INPUT 0 +#define MODE_OUTPUT 1 +#define MODE_VALUE_0 0 +#define MODE_VALUE_1 2 + +#define IRQ_MODE_HIGH 0 +#define IRQ_MODE_LOW 1 +#define IRQ_MODE_RISING 2 +#define IRQ_MODE_FALLING 3 +#define IRQ_MODE_BOTH 4 + +#define CONFIG_MASK GENMASK(23, 16) +#define CONFIG_VAL GENMASK(7, 0) + +#define CONFIG_OUTMODE GENMASK(7, 6) +#define CONFIG_IRQMODE GENMASK(5, 3) +#define CONFIG_PULLDOWN BIT(2) +#define CONFIG_PULLUP BIT(1) +#define CONFIG_OUTVAL BIT(0) + +/* + * output modes seem to differ depending on the PMU in use... ? + * j274 / M1 (Sera PMU): + * 0 = input + * 1 = output + * 2 = open drain + * 3 = disable + * j314 / M1Pro (Maverick PMU): + * 0 = input + * 1 = open drain + * 2 = output + * 3 = ? + */ + +#define SMC_EV_GPIO 0x7202 + +struct macsmc_gpio { + struct device *dev; + struct apple_smc *smc; + struct gpio_chip gc; + struct irq_chip ic; + struct notifier_block nb; + + struct mutex irq_mutex; + DECLARE_BITMAP(irq_supported, MAX_GPIO); + DECLARE_BITMAP(irq_enable_shadow, MAX_GPIO); + DECLARE_BITMAP(irq_enable, MAX_GPIO); + u32 irq_mode_shadow[MAX_GPIO]; + u32 irq_mode[MAX_GPIO]; + + int first_index; +}; + +static int macsmc_gpio_nr(smc_key key) +{ + int low = hex_to_bin(key & 0xff); + int high = hex_to_bin((key >> 8) & 0xff); + + if (low < 0 || high < 0) + return -1; + + return low | (high << 4); +} + +static int macsmc_gpio_key(unsigned int offset) +{ + return _SMC_KEY("gP\0\0") | (hex_asc_hi(offset) << 8) | hex_asc_lo(offset); +} + +static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(offset); + u32 val; + int ret; + + /* First try reading the explicit pin mode register */ + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_PINMODE, &val); + if (!ret) + return (val & MODE_OUTPUT) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; + + /* + * Less common IRQ configs cause CMD_PINMODE to fail, and so does open drain mode. + * Fall back to reading IRQ mode, which will only succeed for inputs. + */ + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val); + return (!ret) ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; +} + +static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(offset); + u32 val; + int ret; + + ret = macsmc_gpio_get_direction(gc, offset); + if (ret < 0) + return ret; + + if (ret == GPIO_LINE_DIRECTION_OUT) + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_OUTPUT, &val); + else + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_INPUT, &val); + + if (ret < 0) + return ret; + + return val ? 1 : 0; +} + +static void macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(offset); + int ret; + + value |= CMD_OUTPUT; + ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value); + if (ret < 0) + dev_err(smcgp->dev, "GPIO set failed %p4ch = 0x%x\n", &key, value); +} + +static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, unsigned int ngpios) +{ + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + int count = apple_smc_get_key_count(smcgp->smc) - smcgp->first_index; + int i; + + if (count > MAX_GPIO) + count = MAX_GPIO; + + bitmap_zero(valid_mask, ngpios); + + for (i = 0; i < count; i++) { + smc_key key; + int gpio_nr; + u32 val; + int ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key); + + if (ret < 0) + return ret; + + if (key > SMC_KEY(gPff)) + break; + + gpio_nr = macsmc_gpio_nr(key); + if (gpio_nr < 0 || gpio_nr > MAX_GPIO) { + dev_err(smcgp->dev, "Bad GPIO key %p4ch\n", &key); + continue; + } + + set_bit(gpio_nr, valid_mask); + + /* Check for IRQ support */ + ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val); + if (!ret) + set_bit(gpio_nr, smcgp->irq_supported); + } + + return 0; +} + +static int macsmc_gpio_event(struct notifier_block *nb, unsigned long event, void *data) +{ + struct macsmc_gpio *smcgp = container_of(nb, struct macsmc_gpio, nb); + u16 type = event >> 16; + u8 offset = (event >> 8) & 0xff; + smc_key key = macsmc_gpio_key(offset); + unsigned long flags; + int ret; + + if (type != SMC_EV_GPIO) + return NOTIFY_DONE; + + if (offset > MAX_GPIO) { + dev_err(smcgp->dev, "GPIO event index %d out of range\n", offset); + return NOTIFY_BAD; + } + + local_irq_save(flags); + ret = handle_irq_desc(irq_resolve_mapping(smcgp->gc.irq.domain, offset)); + local_irq_restore(flags); + + if (apple_smc_write_u32(smcgp->smc, key, CMD_IRQ_ACK | 1) < 0) + dev_err(smcgp->dev, "GPIO IRQ ack failed for %p4ch\n", &key); + + return (ret == 0) ? NOTIFY_OK : NOTIFY_DONE; +} + +static void macsmc_gpio_irq_enable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + + set_bit(irqd_to_hwirq(d), smcgp->irq_enable_shadow); +} + +static void macsmc_gpio_irq_disable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + + clear_bit(irqd_to_hwirq(d), smcgp->irq_enable_shadow); +} + +static int macsmc_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + int offset = irqd_to_hwirq(d); + u32 mode; + + if (!test_bit(offset, smcgp->irq_supported)) + return -EINVAL; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_LEVEL_HIGH: + mode = IRQ_MODE_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + mode = IRQ_MODE_LOW; + break; + case IRQ_TYPE_EDGE_RISING: + mode = IRQ_MODE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + mode = IRQ_MODE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + mode = IRQ_MODE_BOTH; + break; + default: + return -EINVAL; + } + + smcgp->irq_mode_shadow[offset] = mode; + return 0; +} + +static void macsmc_gpio_irq_bus_lock(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + + mutex_lock(&smcgp->irq_mutex); +} + +static void macsmc_gpio_irq_bus_sync_unlock(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct macsmc_gpio *smcgp = gpiochip_get_data(gc); + smc_key key = macsmc_gpio_key(irqd_to_hwirq(d)); + int offset = irqd_to_hwirq(d); + bool val; + + if (smcgp->irq_mode_shadow[offset] != smcgp->irq_mode[offset]) { + u32 cmd = CMD_IRQ_MODE | smcgp->irq_mode_shadow[offset]; + if (apple_smc_write_u32(smcgp->smc, key, cmd) < 0) + dev_err(smcgp->dev, "GPIO IRQ config failed for %p4ch = 0x%x\n", &key, cmd); + else + smcgp->irq_mode_shadow[offset] = smcgp->irq_mode[offset]; + } + + val = test_bit(offset, smcgp->irq_enable_shadow); + if (test_bit(offset, smcgp->irq_enable) != val) { + if (apple_smc_write_u32(smcgp->smc, key, CMD_IRQ_ENABLE | val) < 0) + dev_err(smcgp->dev, "GPIO IRQ en/disable failed for %p4ch\n", &key); + else + change_bit(offset, smcgp->irq_enable); + } + + mutex_unlock(&smcgp->irq_mutex); +} + +static int macsmc_gpio_probe(struct platform_device *pdev) +{ + struct macsmc_gpio *smcgp; + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + smc_key key; + int ret; + + smcgp = devm_kzalloc(&pdev->dev, sizeof(*smcgp), GFP_KERNEL); + if (!smcgp) + return -ENOMEM; + + pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "gpio"); + + smcgp->dev = &pdev->dev; + smcgp->smc = smc; + smcgp->first_index = apple_smc_find_first_key_index(smc, SMC_KEY(gP00)); + + if (smcgp->first_index >= apple_smc_get_key_count(smc)) + return -ENODEV; + + ret = apple_smc_get_key_by_index(smc, smcgp->first_index, &key); + if (ret < 0) + return ret; + + if (key > macsmc_gpio_key(MAX_GPIO - 1)) + return -ENODEV; + + dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key); + + smcgp->gc.label = "macsmc-pmu-gpio"; + smcgp->gc.owner = THIS_MODULE; + smcgp->gc.get = macsmc_gpio_get; + smcgp->gc.set = macsmc_gpio_set; + smcgp->gc.get_direction = macsmc_gpio_get_direction; + smcgp->gc.init_valid_mask = macsmc_gpio_init_valid_mask; + smcgp->gc.can_sleep = true; + smcgp->gc.ngpio = MAX_GPIO; + smcgp->gc.base = -1; + smcgp->gc.parent = &pdev->dev; + + smcgp->ic.name = "macsmc-pmu-gpio"; + smcgp->ic.irq_mask = macsmc_gpio_irq_disable; + smcgp->ic.irq_unmask = macsmc_gpio_irq_enable; + smcgp->ic.irq_set_type = macsmc_gpio_irq_set_type; + smcgp->ic.irq_bus_lock = macsmc_gpio_irq_bus_lock; + smcgp->ic.irq_bus_sync_unlock = macsmc_gpio_irq_bus_sync_unlock; + smcgp->ic.irq_set_type = macsmc_gpio_irq_set_type; + smcgp->ic.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND; + + smcgp->gc.irq.chip = &smcgp->ic; + smcgp->gc.irq.parent_handler = NULL; + smcgp->gc.irq.num_parents = 0; + smcgp->gc.irq.parents = NULL; + smcgp->gc.irq.default_type = IRQ_TYPE_NONE; + smcgp->gc.irq.handler = handle_simple_irq; + + mutex_init(&smcgp->irq_mutex); + + smcgp->nb.notifier_call = macsmc_gpio_event; + apple_smc_register_notifier(smc, &smcgp->nb); + + return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp); +} + +static struct platform_driver macsmc_gpio_driver = { + .driver = { + .name = "macsmc-gpio", + }, + .probe = macsmc_gpio_probe, +}; +module_platform_driver(macsmc_gpio_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC GPIO driver"); +MODULE_ALIAS("platform:macsmc-gpio"); diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index 627d637a1e7e..20c78bb1365d 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -71,6 +71,7 @@ config DRM_SIMPLEDRM depends on DRM && MMU select DRM_GEM_SHMEM_HELPER select DRM_KMS_HELPER + select BACKLIGHT_CLASS_DEVICE help DRM driver for simple platform-provided framebuffers. diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index 5422363690e7..40de01efbbd9 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only +#include <linux/backlight.h> #include <linux/clk.h> #include <linux/of_clk.h> #include <linux/minmax.h> @@ -225,6 +226,9 @@ struct simpledrm_device { size_t nformats; struct drm_connector connector; struct drm_simple_display_pipe pipe; + + /* backlight */ + struct backlight_device *backlight; }; static struct simpledrm_device *simpledrm_device_of_dev(struct drm_device *dev) @@ -673,6 +677,9 @@ simpledrm_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe, dst += drm_fb_clip_offset(sdev->pitch, sdev->format, &dst_clip); drm_fb_blit_toio(dst, sdev->pitch, sdev->format->format, vmap, fb, &src_clip); + if (sdev->backlight) + backlight_enable(sdev->backlight); + drm_dev_exit(idx); } @@ -686,6 +693,9 @@ simpledrm_simple_display_pipe_disable(struct drm_simple_display_pipe *pipe) if (!drm_dev_enter(dev, &idx)) return; + if (sdev->backlight) + backlight_disable(sdev->backlight); + /* Clear screen to black if disabled */ memset_io(sdev->screen_base, 0, sdev->pitch * sdev->mode.vdisplay); @@ -845,6 +855,10 @@ simpledrm_device_create(struct drm_driver *drv, struct platform_device *pdev) sdev->pdev = pdev; platform_set_drvdata(pdev, sdev); + sdev->backlight = devm_of_find_backlight(&pdev->dev); + if (IS_ERR(sdev->backlight)) + sdev->backlight = NULL; + ret = simpledrm_device_init_clocks(sdev); if (ret) return ERR_PTR(ret); diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 70da5931082f..bd47caba1413 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -130,7 +130,7 @@ config HID_APPLE depends on HID depends on LEDS_CLASS depends on NEW_LEDS - default !EXPERT + default !EXPERT || SPI_HID_APPLE help Support for some Apple devices which less or more break HID specification. @@ -676,11 +676,13 @@ config LOGIWHEELS_FF config HID_MAGICMOUSE tristate "Apple Magic Mouse/Trackpad multi-touch support" depends on HID + default SPI_HID_APPLE help Support for the Apple Magic Mouse/Trackpad multi-touch. Say Y here if you want support for the multi-touch features of the - Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad. + Apple Wireless "Magic" Mouse, the Apple Wireless "Magic" Trackpad and + fource touch Trackpads in Macbooks starting from 2015. config HID_MALTRON tristate "Maltron L90 keyboard" @@ -1041,7 +1043,7 @@ config HID_SONY * Guitar Hero PS3 and PC guitar dongles config SONY_FF - bool "Sony PS2/3/4 accessories force feedback support" + bool "Sony PS2/3/4 accessories force feedback support" depends on HID_SONY select INPUT_FF_MEMLESS help @@ -1320,4 +1322,8 @@ source "drivers/hid/amd-sfh-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig" +source "drivers/hid/spi-hid/Kconfig" + +source "drivers/hid/dockchannel-hid/Kconfig" + endmenu diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index cac2cbe26d11..2c90409d515c 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -156,3 +156,7 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + +obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ + +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig new file mode 100644 index 000000000000..8a81d551a83d --- /dev/null +++ b/drivers/hid/dockchannel-hid/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +menu "DockChannel HID support" + depends on APPLE_DOCKCHANNEL + +config HID_DOCKCHANNEL + tristate "HID over DockChannel transport layer for Apple Silicon SoCs" + default ARCH_APPLE + depends on APPLE_DOCKCHANNEL && INPUT && OF && HID + help + Say Y here if you use an M2 or later Apple Silicon based laptop. + The keyboard and touchpad are HID based devices connected via the + proprietary DockChannel interface. + +endmenu diff --git a/drivers/hid/dockchannel-hid/Makefile b/drivers/hid/dockchannel-hid/Makefile new file mode 100644 index 000000000000..7dba766b047f --- /dev/null +++ b/drivers/hid/dockchannel-hid/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# +# Makefile for DockChannel HID transport drivers +# + +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid.o diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c new file mode 100644 index 000000000000..ea9d57ed9fa4 --- /dev/null +++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c @@ -0,0 +1,1121 @@ +/* + * SPDX-License-Identifier: GPL-2.0 OR MIT + * + * Apple DockChannel HID transport driver + * + * Copyright The Asahi Linux Contributors + */ +#include <asm/unaligned.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/hid.h> +#include <linux/slab.h> +#include <linux/soc/apple/dockchannel.h> +#include <linux/of.h> +#include "../hid-ids.h" + +#define COMMAND_TIMEOUT_MS 1000 +#define START_TIMEOUT_MS 2000 + +#define MAX_INTERFACES 16 + +/* Data + checksum */ +#define MAX_PKT_SIZE (0xffff + 4) + +#define DCHID_CHANNEL_CMD 0x11 +#define DCHID_CHANNEL_REPORT 0x12 + +struct dchid_hdr { + u8 hdr_len; + u8 channel; + __le16 length; + u8 seq; + u8 iface; + __le16 pad; +} __packed; + +#define IFACE_COMM 0 + +#define FLAGS_GROUP GENMASK(7, 6) +#define FLAGS_REQ GENMASK(5, 0) + +#define GROUP_INPUT 0 +#define GROUP_OUTPUT 1 +#define GROUP_CMD 2 + +#define REQ_SET_REPORT 0 +#define REQ_GET_REPORT 1 + +struct dchid_subhdr { + u8 flags; + u8 unk; + __le16 length; + __le32 retcode; +} __packed; + +#define EVENT_GPIO_CMD 0xa0 +#define EVENT_INIT 0xf0 +#define EVENT_READY 0xf1 + +struct dchid_init_hdr { + u8 type; + u8 unk1; + u8 unk2; + u8 iface; + char name[16]; +} __packed; + +#define INIT_HID_DESCRIPTOR 0 +#define INIT_GPIO_REQUEST 1 +#define INIT_TERMINATOR 2 + +#define CMD_RESET_INTERFACE 0x40 +#define CMD_SEND_FIRMWARE 0x95 +#define CMD_ENABLE_INTERFACE 0xb4 +#define CMD_ACK_GPIO_CMD 0xa1 + +struct dchid_init_block_hdr { + __le16 type; + __le16 subtype; + __le16 length; +} __packed; + +#define MAX_GPIO_NAME 32 + +struct dchid_gpio_request { + __le16 unk; + __le16 id; + char name[MAX_GPIO_NAME]; +} __packed; + +struct dchid_gpio_cmd { + u8 type; + u8 iface; + u8 gpio; + u8 unk; + u8 cmd; +} __packed; + +struct dchid_gpio_ack { + u8 type; + __le32 retcode; + u8 cmd[]; +} __packed; + +#define STM_REPORT_ID 0x10 +#define STM_REPORT_SERIAL 0x11 +#define STM_REPORT_KEYBTYPE 0x14 + +#define KEYBOARD_TYPE_ANSI 0 +#define KEYBOARD_TYPE_ISO 1 +#define KEYBOARD_TYPE_JIS 2 + +struct dchid_stm_id { + u8 unk; + __le16 vendor_id; + __le16 product_id; + __le16 version_number; + u8 unk2; + u8 unk3; + u8 keyboard_type; + u8 serial_length; + /* Serial follows, but we grab it with a different report. */ +} __packed; + +#define FW_MAGIC 0x46444948 +#define FW_VER 1 + +struct fw_header { + __le32 magic; + __le32 version; + __le32 hdr_length; + __le32 data_length; + __le32 iface_offset; +} __packed; + +struct dchid_work { + struct work_struct work; + struct dchid_iface *iface; + + struct dchid_hdr hdr; + u8 data[]; +}; + +struct dchid_iface { + struct dockchannel_hid *dchid; + struct hid_device *hid; + struct workqueue_struct *wq; + + bool creating; + struct work_struct create_work; + + int index; + const char *name; + const struct device_node *of_node; + + uint8_t tx_seq; + bool deferred; + bool starting; + bool open; + struct completion ready; + + void *hid_desc; + size_t hid_desc_len; + + struct gpio_desc *gpio; + int gpio_id; + + struct mutex out_mutex; + u32 out_flags; + int out_report; + u32 retcode; + void *resp_buf; + size_t resp_size; + struct completion out_complete; +}; + +struct dockchannel_hid { + struct device *dev; + struct dockchannel *dc; + + bool id_ready; + struct dchid_stm_id device_id; + char serial[64]; + + struct dchid_iface *comm; + struct dchid_iface *ifaces[MAX_INTERFACES]; + + u8 pkt_buf[MAX_PKT_SIZE]; + + /* Workqueue to asynchronously create HID devices */ + struct workqueue_struct *new_iface_wq; +}; + +static struct dchid_iface * +dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name) +{ + struct dchid_iface *iface; + + if (index >= MAX_INTERFACES) { + dev_err(dchid->dev, "Interface index %d out of range\n", index); + return NULL; + } + + if (dchid->ifaces[index]) + return dchid->ifaces[index]; + + iface = devm_kzalloc(dchid->dev, sizeof(struct dchid_iface), GFP_KERNEL); + if (!iface) + return NULL; + + iface->index = index; + iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL); + iface->dchid = dchid; + iface->out_report= -1; + init_completion(&iface->out_complete); + init_completion(&iface->ready); + mutex_init(&iface->out_mutex); + iface->wq = alloc_ordered_workqueue("dchid-%s", WQ_MEM_RECLAIM, iface->name); + if (!iface->wq) + return NULL; + + /* Comm is not a HID subdevice */ + if (!strcmp(name, "comm")) { + dchid->ifaces[index] = iface; + return iface; + } + + iface->of_node = of_get_child_by_name(dchid->dev->of_node, name); + if (!iface->of_node) { + dev_warn(dchid->dev, "No OF node for subdevice %s, ignoring.", name); + return NULL; + } + + dchid->ifaces[index] = iface; + return iface; +} + +static u32 dchid_checksum(void *p, size_t length) +{ + u32 sum = 0; + + while (length >= 4) { + sum += get_unaligned_le32(p); + p += 4; + length -= 4; + } + + WARN_ON_ONCE(length); + return sum; +} + +static int dchid_send(struct dchid_iface *iface, u32 flags, void *msg, size_t size) +{ + u32 checksum = 0xffffffff; + size_t wsize = round_down(size, 4); + size_t tsize = size - wsize; + int ret; + struct { + struct dchid_hdr hdr; + struct dchid_subhdr sub; + } __packed h; + + memset(&h, 0, sizeof(h)); + h.hdr.hdr_len = sizeof(h.hdr); + h.hdr.channel = DCHID_CHANNEL_CMD; + h.hdr.length = round_up(size, 4) + sizeof(h.sub); + h.hdr.seq = iface->tx_seq; + h.hdr.iface = iface->index; + h.sub.flags = flags; + h.sub.length = size; + + ret = dockchannel_send(iface->dchid->dc, &h, sizeof(h)); + if (ret < 0) + return ret; + checksum -= dchid_checksum(&h, sizeof(h)); + + ret = dockchannel_send(iface->dchid->dc, msg, wsize); + if (ret < 0) + return ret; + checksum -= dchid_checksum(msg, wsize); + + if (tsize) { + u8 tail[4] = {0, 0, 0, 0}; + + memcpy(tail, msg + wsize, tsize); + ret = dockchannel_send(iface->dchid->dc, tail, sizeof(tail)); + if (ret < 0) + return ret; + checksum -= dchid_checksum(tail, sizeof(tail)); + } + + ret = dockchannel_send(iface->dchid->dc, &checksum, sizeof(checksum)); + if (ret < 0) + return ret; + + return 0; +} + +static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req, + void *data, size_t size, void *resp_buf, size_t resp_size) +{ + int ret; + int report_id = *(u8*)data; + + mutex_lock(&iface->out_mutex); + + WARN_ON(iface->out_report != -1); + iface->out_report = report_id; + iface->out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req); + iface->resp_buf = resp_buf; + iface->resp_size = resp_size; + reinit_completion(&iface->out_complete); + + ret = dchid_send(iface, iface->out_flags, data, size); + if (ret < 0) + goto done; + + if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(COMMAND_TIMEOUT_MS))) { + dev_err(iface->dchid->dev, "output report 0x%x to iface %d (%s) timed out\n", + report_id, iface->index, iface->name); + ret = -ETIMEDOUT; + goto done; + } + + ret = iface->resp_size; + if (iface->retcode) { + dev_err(iface->dchid->dev, + "output report 0x%x to iface %d (%s) failed with err 0x%x\n", + report_id, iface->index, iface->name, iface->retcode); + ret = -EIO; + } + +done: + iface->tx_seq++; + iface->out_report = -1; + iface->out_flags = 0; + iface->resp_buf = NULL; + iface->resp_size = 0; + mutex_unlock(&iface->out_mutex); + return ret; +} + +static int dchid_comm_cmd(struct dockchannel_hid *dchid, void *cmd, size_t size) +{ + return dchid_cmd(dchid->comm, GROUP_CMD, REQ_SET_REPORT, cmd, size, NULL, 0); +} + +static int dchid_enable_interface(struct dchid_iface *iface) +{ + u8 msg[] = { CMD_ENABLE_INTERFACE, iface->index }; + + return dchid_comm_cmd(iface->dchid, msg, sizeof(msg)); +} + +static int dchid_reset_interface(struct dchid_iface *iface, int state) +{ + u8 msg[] = { CMD_RESET_INTERFACE, 1, iface->index, state }; + + return dchid_comm_cmd(iface->dchid, msg, sizeof(msg)); +} + +static int dchid_send_firmware(struct dchid_iface *iface, void *firmware, size_t size) +{ + struct { + u8 cmd; + u8 unk1; + u8 unk2; + u8 iface; + u64 addr; + u32 size; + } __packed msg = { + .cmd = CMD_SEND_FIRMWARE, + .unk1 = 2, + .unk2 = 0, + .iface = iface->index, + .size = size, + }; + dma_addr_t addr; + void *buf = dmam_alloc_coherent(iface->dchid->dev, size, &addr, GFP_KERNEL); + + if (IS_ERR_OR_NULL(buf)) + return buf ? PTR_ERR(buf) : -ENOMEM; + + msg.addr = addr; + memcpy(buf, firmware, size); + wmb(); + + return dchid_comm_cmd(iface->dchid, &msg, sizeof(msg)); +} + +static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t *size) +{ + int ret; + const char *fw_name; + const struct firmware *fw; + struct fw_header *hdr; + u8 *fw_data; + + ret = of_property_read_string(iface->of_node, "firmware-name", &fw_name); + if (ret) { + /* Firmware is only for some devices */ + *firmware = NULL; + *size = 0; + return 0; + } + + ret = request_firmware(&fw, fw_name, iface->dchid->dev); + if (ret) + return ret; + + hdr = (struct fw_header *)fw->data; + + if (hdr->magic != FW_MAGIC || hdr->version != FW_VER || + hdr->hdr_length < sizeof(*hdr) || hdr->hdr_length > fw->size || + (hdr->hdr_length + (size_t)hdr->data_length) > fw->size || + hdr->iface_offset >= hdr->data_length) { + dev_warn(iface->dchid->dev, "%s: invalid firmware header\n", + fw_name); + ret = -EINVAL; + goto done; + } + + fw_data = devm_kmemdup(iface->dchid->dev, fw->data + hdr->hdr_length, + hdr->data_length, GFP_KERNEL); + if (!fw_data) { + ret = -ENOMEM; + goto done; + } + + if (hdr->iface_offset) + fw_data[hdr->iface_offset] = iface->index; + + *firmware = fw_data; + *size = hdr->data_length; + +done: + release_firmware(fw); + return ret; +} + +static int dchid_start_interface(struct dchid_iface *iface) +{ + void *fw; + size_t size; + int ret; + + if (iface->starting) { + dev_warn(iface->dchid->dev, "Interface %s is already starting", iface->name); + return -EINPROGRESS; + } + + dev_info(iface->dchid->dev, "Starting interface %s\n", iface->name); + + iface->starting = true; + + /* Look to see if we need firmware */ + ret = dchid_get_firmware(iface, &fw, &size); + if (ret < 0) + goto err; + + /* Only multi-touch has firmware */ + if (fw && size) { + + /* Send firmware to the device */ + dev_info(iface->dchid->dev, "Sending firmware for %s\n", iface->name); + ret = dchid_send_firmware(iface, fw, size); + if (ret < 0) { + dev_err(iface->dchid->dev, "Failed to send %s firmwareS", iface->name); + goto err; + } + + /* After loading firmware, multi-touch needs a reset */ + dev_info(iface->dchid->dev, "Resetting %s\n", iface->name); + dchid_reset_interface(iface, 0); + dchid_reset_interface(iface, 2); + } + + return 0; + +err: + iface->starting = false; + return ret; +} + +static int dchid_start(struct hid_device *hdev) +{ + return 0; +}; + +static void dchid_stop(struct hid_device *hdev) +{ + /* no-op, we don't know what the shutdown commands are, if any */ +} + +static int dchid_open(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + int ret; + + if (!completion_done(&iface->ready)) { + ret = dchid_start_interface(iface); + if (ret < 0) + return ret; + + if (!wait_for_completion_timeout(&iface->ready, msecs_to_jiffies(START_TIMEOUT_MS))) { + dev_err(iface->dchid->dev, "iface %s start timed out\n", iface->name); + return -ETIMEDOUT; + } + } + + iface->open = true; + return 0; +} + +static void dchid_close(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + + iface->open = false; +} + +static int dchid_parse(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + + return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len); +} + +/* Note: buf excludes report number! For ease of fetching strings/etc. */ +static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len) +{ + int ret = dchid_cmd(iface, GROUP_CMD, REQ_GET_REPORT, &reportnum, 1, buf, len); + + return ret <= 0 ? ret : ret - 1; +} + +/* Note: buf includes report number! */ +static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len) +{ + return dchid_cmd(iface, GROUP_OUTPUT, REQ_SET_REPORT, buf, len, NULL, 0); +} + +static int dchid_raw_request(struct hid_device *hdev, + unsigned char reportnum, __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + struct dchid_iface *iface = hdev->driver_data; + + switch (reqtype) { + case HID_REQ_GET_REPORT: + buf[0] = reportnum; + return dchid_cmd(iface, GROUP_OUTPUT, REQ_GET_REPORT, &reportnum, 1, buf + 1, len - 1); + case HID_REQ_SET_REPORT: + return dchid_set_report(iface, buf, len); + default: + return -EIO; + } + + return 0; +} + +static struct hid_ll_driver dchid_ll = { + .start = &dchid_start, + .stop = &dchid_stop, + .open = &dchid_open, + .close = &dchid_close, + .parse = &dchid_parse, + .raw_request = &dchid_raw_request, +}; + +static void dchid_create_interface_work(struct work_struct *ws) +{ + struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work); + struct dockchannel_hid *dchid = iface->dchid; + struct hid_device *hid; + int ret; + + if (iface->hid) { + dev_warn(dchid->dev, "Interface %s already created!\n", + iface->name); + return; + } + + dev_info(dchid->dev, "New interface %s\n", iface->name); + + /* Start the interface. This is not the entire init process, as firmware is loaded later on device open. */ + ret = dchid_enable_interface(iface); + if (ret < 0) { + dev_warn(dchid->dev, "Failed to enable %s: %d\n", iface->name, ret); + return; + } + + iface->deferred = false; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return; + + snprintf(hid->name, sizeof(hid->name), "Apple MTP %s", iface->name); + snprintf(hid->phys, sizeof(hid->phys), "%s.%d (%s)", + dev_name(dchid->dev), iface->index, iface->name); + strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq)); + + hid->ll_driver = &dchid_ll; + hid->bus = BUS_HOST; + hid->vendor = dchid->device_id.vendor_id; + hid->product = dchid->device_id.product_id; + hid->version = dchid->device_id.version_number; + hid->type = HID_TYPE_OTHER; + if (!strcmp(iface->name, "multi-touch")) { + hid->type = HID_TYPE_SPI_MOUSE; + } else if (!strcmp(iface->name, "keyboard")) { + hid->type = HID_TYPE_SPI_KEYBOARD; + + /* + * These country codes match what earlier Apple HID keyboards did. + * Apple seems to allocate keyboard IDs in groups of 3 (for the 3 + * layout groups), hence the % 3. + */ + switch (dchid->device_id.keyboard_type % 3) { + case KEYBOARD_TYPE_ANSI: + hid->country = 33; // US-English + break; + + case KEYBOARD_TYPE_ISO: + hid->country = 13; // ISO + break; + + case KEYBOARD_TYPE_JIS: + hid->country = 15; // Japan + break; + } + } + + hid->dev.parent = iface->dchid->dev; + hid->driver_data = iface; + + iface->hid = hid; + + ret = hid_add_device(hid); + if (ret < 0) { + iface->hid = NULL; + hid_destroy_device(hid); + dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name); + } +} + +static int dchid_create_interface(struct dchid_iface *iface) +{ + if (iface->creating) + return -EBUSY; + + iface->creating = true; + INIT_WORK(&iface->create_work, dchid_create_interface_work); + return queue_work(iface->dchid->new_iface_wq, &iface->create_work); +} + +static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, size_t desc_len) +{ + if (iface->hid) { + dev_warn(iface->dchid->dev, "Tried to initialize already started interface %s!\n", + iface->name); + return; + } + + iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL); + if (!iface->hid_desc) + return; + + iface->hid_desc_len = desc_len; + + /* We need to enable STM first, since it'll give us the device IDs */ + if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) { + dchid_create_interface(iface); + } else { + iface->deferred = true; + } +} + +static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t length) +{ + struct dchid_iface *iface; + u8 *pkt = data; + u8 index; + int i, ret; + + if (length < 2) { + dev_err(dchid->dev, "Bad length for ready message: %ld\n", length); + return; + } + + index = pkt[1]; + + if (index >= MAX_INTERFACES) { + dev_err(dchid->dev, "Got ready notification for bad iface %d\n", index); + return; + } + + iface = dchid->ifaces[index]; + if (!iface) { + dev_err(dchid->dev, "Got ready notification for unknown iface %d\n", index); + return; + } + + dev_info(dchid->dev, "Inteface %s is now ready\n", iface->name); + complete_all(&iface->ready); + + /* When STM is ready, grab global device info */ + if (!strcmp(iface->name, "stm")) { + ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id, + sizeof(dchid->device_id)); + if (ret < sizeof(dchid->device_id)) { + dev_warn(iface->dchid->dev, "Failed to get device ID from STM!\n"); + /* Fake it and keep going. Things might still work... */ + memset(&dchid->device_id, 0, sizeof(dchid->device_id)); + dchid->device_id.vendor_id = HOST_VENDOR_ID_APPLE; + } + ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial, + sizeof(dchid->serial) - 1); + if (ret < 0) { + dev_warn(iface->dchid->dev, "Failed to get serial from STM!\n"); + dchid->serial[0] = 0; + } + + dchid->id_ready = true; + for (i = 0; i < MAX_INTERFACES; i++) { + if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred) + continue; + dchid_create_interface(dchid->ifaces[i]); + } + } +} + +static void dchid_request_gpio(struct dchid_iface *iface, int id, const char *name) +{ + char prop_name[MAX_GPIO_NAME + 16]; + + dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n", iface->name, id, name); + + if (iface->gpio) { + dev_err(iface->dchid->dev, "Cannot request more than one GPIO per interface!\n"); + return; + } + + snprintf(prop_name, sizeof(prop_name), "apple,%s-gpios", name); + + iface->gpio = devm_gpiod_get_from_of_node(iface->dchid->dev, + iface->of_node, prop_name, 0, + GPIOD_OUT_LOW, name); + + if (IS_ERR_OR_NULL(iface->gpio)) { + dev_err(iface->dchid->dev, "Failed to request GPIO %s\n", prop_name); + iface->gpio = NULL; + return; + } + + iface->gpio_id = id; +} + +static void dchid_handle_init(struct dockchannel_hid *dchid, void *data, size_t length) +{ + struct dchid_init_hdr *hdr = data; + struct dchid_iface *iface; + struct dchid_init_block_hdr *blk; + + if (length < sizeof(*hdr)) + return; + + iface = dchid_get_interface(dchid, hdr->iface, hdr->name); + if (!iface) + return; + + data += sizeof(*hdr); + length -= sizeof(*hdr); + + while (length > sizeof(*blk)) { + blk = data; + data += sizeof(*blk); + length -= sizeof(*blk); + + if (blk->length > length) + return; + switch (blk->type) { + case INIT_HID_DESCRIPTOR: + dchid_handle_descriptor(iface, data, blk->length); + break; + + case INIT_GPIO_REQUEST: { + struct dchid_gpio_request *req = data; + + if (sizeof(*req) > length) + return; + dchid_request_gpio(iface, req->id, req->name); + break; + } + + case INIT_TERMINATOR: + return; + } + + data += blk->length + sizeof(*blk); + length -= blk->length + sizeof(*blk); + } +} + +static void dchid_handle_gpio(struct dockchannel_hid *dchid, void *data, size_t length) +{ + struct dchid_gpio_cmd *cmd = data; + struct dchid_iface *iface; + u32 retcode = 0xe000f00d; /* Give it a random Apple-style error code */ + struct dchid_gpio_ack *ack; + + if (length < sizeof(*cmd)) + return; + + if (cmd->iface >= MAX_INTERFACES || !(iface = dchid->ifaces[cmd->iface])) { + dev_err(dchid->dev, "Got GPIO command for bad inteface %d\n", cmd->iface); + goto err; + } + + if (!iface->gpio || cmd->gpio != iface->gpio_id) { + dev_err(dchid->dev, "Got GPIO command for bad GPIO %s#%d\n", + iface->name, cmd->gpio); + goto err; + } + + dev_info(dchid->dev, "GPIO command: %s#%d: %d\n", iface->name, cmd->gpio, cmd->cmd); + + switch (cmd->cmd) { + case 3: + /* Pulse. */ + gpiod_set_value_cansleep(iface->gpio, 1); + msleep(10); /* Random guess... */ + gpiod_set_value_cansleep(iface->gpio, 0); + retcode = 0; + break; + default: + dev_err(dchid->dev, "Unknown GPIO command %d\n", cmd->cmd ); + break; + } + +err: + /* Ack it */ + ack = kzalloc(sizeof(*ack) + length, GFP_KERNEL); + if (!ack) + return; + + ack->type = CMD_ACK_GPIO_CMD; + ack->retcode = retcode; + memcpy(ack->cmd, data, length); + + if (dchid_comm_cmd(dchid, ack, sizeof(*ack) + length) < 0) + dev_err(dchid->dev, "Failed to ACK GPIO command\n"); + + kfree(ack); +} + +static void dchid_handle_event(struct dockchannel_hid *dchid, void *data, size_t length) +{ + u8 *p = data; + switch (*p) { + case EVENT_INIT: + dchid_handle_init(dchid, data, length); + break; + case EVENT_READY: + dchid_handle_ready(dchid, data, length); + break; + case EVENT_GPIO_CMD: + dchid_handle_gpio(dchid, data, length); + break; + } +} + +static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length) +{ + struct dockchannel_hid *dchid = iface->dchid; + + if (!iface->hid) { + dev_warn(dchid->dev, "Report received but %s is not initialized!\n", iface->name); + return; + } + + if (!iface->open) + return; + + hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1); +} + +static void dchid_packet_work(struct work_struct *ws) +{ + struct dchid_work *work = container_of(ws, struct dchid_work, work); + struct dchid_subhdr *shdr = (void *)work->data; + struct dockchannel_hid *dchid = work->iface->dchid; + int type = FIELD_GET(FLAGS_GROUP, shdr->flags); + u8 *payload = work->data + sizeof(*shdr); + + if (shdr->length + sizeof(*shdr) > work->hdr.length) { + dev_err(dchid->dev, "Bad sub header length (%d > %ld)\n", + shdr->length, work->hdr.length - sizeof(*shdr)); + return; + } + + switch (type) { + case GROUP_INPUT: + if (work->hdr.iface == IFACE_COMM) + dchid_handle_event(dchid, payload, shdr->length); + else + dchid_handle_report(work->iface, payload, shdr->length); + break; + default: + dev_err(dchid->dev, "Received unknown packet type %d\n", type); + break; + } + + kfree(work); +} + +static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr, void *data) +{ + struct dchid_subhdr *shdr = (void *)data; + u8 *payload = data + sizeof(*shdr); + + if (shdr->length + sizeof(*shdr) > hdr->length) { + dev_err(iface->dchid->dev, "Bad sub header length (%d > %ld)\n", + shdr->length, hdr->length - sizeof(*shdr)); + return; + } + if (shdr->flags != iface->out_flags) { + dev_err(iface->dchid->dev, + "Received unexpected flags 0x%x on ACK channel (expFected 0x%x)\n", + shdr->flags, iface->out_flags); + return; + } + + if (shdr->length < 1) { + dev_err(iface->dchid->dev, "Received length 0 output report ack\n"); + return; + } + if (iface->tx_seq != hdr->seq) { + dev_err(iface->dchid->dev, "Received ACK with bad seq (expected %d, got %d)\n", + iface->tx_seq, hdr->seq); + return; + } + if (iface->out_report != payload[0]) { + dev_err(iface->dchid->dev, "Received ACK with bad report (expected %d, got %d\n", + iface->out_report, payload[0]); + return; + } + + if (iface->resp_buf && iface->resp_size) + memcpy(iface->resp_buf, payload + 1, min((size_t)shdr->length - 1, iface->resp_size)); + + iface->resp_size = shdr->length; + iface->out_report = -1; + iface->retcode = shdr->retcode; + complete(&iface->out_complete); +} + +static void dchid_handle_packet(void *cookie, size_t avail) +{ + struct dockchannel_hid *dchid = cookie; + struct dchid_hdr hdr; + struct dchid_work *work; + struct dchid_iface *iface; + u32 checksum; + + if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) { + dev_err(dchid->dev, "Read failed (header)\n"); + return; + } + + if (hdr.hdr_len != sizeof(hdr)) { + dev_err(dchid->dev, "Bad header length %d\n", hdr.hdr_len); + goto done; + } + + if (dockchannel_recv(dchid->dc, dchid->pkt_buf, hdr.length + 4) != (hdr.length + 4)) { + dev_err(dchid->dev, "Read failed (body)\n"); + goto done; + } + + checksum = dchid_checksum(&hdr, sizeof(hdr)); + checksum += dchid_checksum(dchid->pkt_buf, hdr.length + 4); + + if (checksum != 0xffffffff) { + dev_err(dchid->dev, "Checksum mismatch (iface %d): 0x%08x != 0xffffffff\n", + hdr.iface, checksum); + goto done; + } + + + if (hdr.iface >= MAX_INTERFACES) { + dev_err(dchid->dev, "Bad iface %d\n", hdr.iface); + } + + iface = dchid->ifaces[hdr.iface]; + + if (!iface) { + dev_err(dchid->dev, "Received packet for uninitialized iface %d\n", hdr.iface); + goto done; + } + + switch (hdr.channel) { + case DCHID_CHANNEL_CMD: + dchid_handle_ack(iface, &hdr, dchid->pkt_buf); + goto done; + case DCHID_CHANNEL_REPORT: + break; + default: + dev_warn(dchid->dev, "Unknown channel 0x%x, treating as report...\n", + hdr.channel); + break; + } + + work = kzalloc(sizeof(*work) + hdr.length, GFP_KERNEL); + if (!work) + return; + + work->hdr = hdr; + work->iface = iface; + memcpy(work->data, dchid->pkt_buf, hdr.length); + INIT_WORK(&work->work, dchid_packet_work); + + queue_work(iface->wq, &work->work); + +done: + dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); +} + +static int dockchannel_hid_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel_hid *dchid; + struct device_node *child; + struct property *prop; + bool defer = false; + + /* + * First make sure all the GPIOs are available, in cased we need to defer. + * This is necessary because MTP will request them by name later, and by then + * it's too late to defer the probe. + */ + + for_each_child_of_node(dev->of_node, child) { + for_each_property_of_node(child, prop) { + size_t len = strlen(prop->name); + struct gpio_desc *gpio; + + if (len < 12 || strncmp("apple,", prop->name, 6) || + strcmp("-gpios", prop->name + len - 6)) + continue; + + gpio = gpiod_get_from_of_node(child, prop->name, 0, GPIOD_ASIS, + prop->name); + if (IS_ERR_OR_NULL(gpio)) { + if (PTR_ERR(gpio) == EPROBE_DEFER) { + defer = true; + of_node_put(child); + return -EPROBE_DEFER; + } + } else { + gpiod_put(gpio); + } + } + } + + dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL); + if (!dchid) + return -ENOMEM; + + + dchid->dev = &pdev->dev; + dchid->dc = dockchannel_init(pdev); + if (IS_ERR_OR_NULL(dchid->dc)) { + return -PTR_ERR(dchid->dc); + } + dchid->new_iface_wq = alloc_workqueue("dchid-new", WQ_MEM_RECLAIM, 0); + if (!dchid->new_iface_wq) + return -ENOMEM; + + dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm"); + if (!dchid->comm) { + dev_err(dchid->dev, "Failed to initialize comm interface"); + return -EIO; + } + + dev_info(dchid->dev, "initialized, awaiting packets\n"); + dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); + + return 0; +} + +static int dockchannel_hid_remove(struct platform_device *pdev) +{ + BUG_ON(1); + return 0; +} + +static const struct of_device_id dockchannel_hid_of_match[] = { + { .compatible = "apple,dockchannel-hid" }, + {}, +}; +MODULE_DEVICE_TABLE(of, dockchannel_hid_of_match); +MODULE_FIRMWARE("apple/tpmtfw-*.bin"); + +static struct platform_driver dockchannel_hid_driver = { + .driver = { + .name = "dockchannel-hid", + .of_match_table = dockchannel_hid_of_match, + }, + .probe = dockchannel_hid_probe, + .remove = dockchannel_hid_remove, +}; +module_platform_driver(dockchannel_hid_driver); + +MODULE_DESCRIPTION("Apple DockChannel HID transport driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 42a568902f49..7dc7b8a6aea2 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -252,6 +252,50 @@ static const struct apple_key_translation apple_fn_keys[] = { { } }; +static const struct apple_key_translation apple_fn_keys_spi[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, + { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY }, + { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY }, + { KEY_F4, KEY_SEARCH, APPLE_FLAG_FKEY }, + { KEY_F5, KEY_RECORD, APPLE_FLAG_FKEY }, + { KEY_F6, KEY_SLEEP, APPLE_FLAG_FKEY }, + { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY }, + { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY }, + { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY }, + { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY }, + { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY }, + { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + +static const struct apple_key_translation apple_fn_keys_mbp13[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { } +}; + static const struct apple_key_translation powerbook_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, @@ -400,6 +444,16 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) table = macbookair_fn_keys; + else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI) + switch (hid->product) { + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022: + table = apple_fn_keys_mbp13; + break; + default: + table = apple_fn_keys_spi; + break; + } else if (hid->product < 0x21d || hid->product >= 0x300) table = powerbook_fn_keys; else @@ -607,6 +661,8 @@ static void apple_setup_input(struct input_dev *input) /* Enable all needed keys */ apple_setup_key_translation(input, apple_fn_keys); + apple_setup_key_translation(input, apple_fn_keys_spi); + apple_setup_key_translation(input, apple_fn_keys_mbp13); apple_setup_key_translation(input, powerbook_fn_keys); apple_setup_key_translation(input, powerbook_numlock_keys); apple_setup_key_translation(input, apple_iso_keyboard); @@ -783,6 +839,10 @@ static int apple_probe(struct hid_device *hdev, struct apple_sc *asc; int ret; + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && + hdev->type != HID_TYPE_SPI_KEYBOARD) + return -ENODEV; + asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL); if (asc == NULL) { hid_err(hdev, "can't alloc apple descriptor\n"); @@ -1024,6 +1084,10 @@ static const struct hid_device_id apple_devices[] = { .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = APPLE_HAS_FN }, + { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, + HID_ANY_ID), .driver_data = APPLE_HAS_FN }, { } }; diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 00154a1cd2d8..048f13b487b3 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2219,6 +2219,12 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) case BUS_I2C: bus = "I2C"; break; + case BUS_SPI: + bus = "SPI"; + break; + case BUS_HOST: + bus = "HOST"; + break; case BUS_VIRTUAL: bus = "VIRTUAL"; break; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index d9eb676abe96..7ecfa2622d53 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -89,6 +89,8 @@ #define USB_VENDOR_ID_APPLE 0x05ac #define BT_VENDOR_ID_APPLE 0x004c +#define SPI_VENDOR_ID_APPLE 0x05ac +#define HOST_VENDOR_ID_APPLE 0x05ac #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d #define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269 @@ -185,6 +187,12 @@ #define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 0x029c #define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 0x029a #define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f +#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020 0x0281 +#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341 +#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342 +#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343 +#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022 0x0351 +#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354 #define USB_VENDOR_ID_ASUS 0x0486 #define USB_DEVICE_ID_ASUS_T91MT 0x0185 diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 664a624a363d..a951e8a8f085 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -59,8 +59,12 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define MOUSE_REPORT_ID 0x29 #define MOUSE2_REPORT_ID 0x12 #define DOUBLE_REPORT_ID 0xf7 +#define SPI_REPORT_ID 0x02 +#define MTP_REPORT_ID 0x75 #define USB_BATTERY_TIMEOUT_MS 60000 +#define MAX_CONTACTS 16 + /* These definitions are not precise, but they're close enough. (Bits * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem * to be some kind of bit mask -- 0x20 may be a near-field reading, @@ -111,6 +115,25 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define TRACKPAD2_RES_Y \ ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) +#define J314_TP_DIMENSION_X (float)13000 +#define J314_TP_MIN_X -5900 +#define J314_TP_MAX_X 6500 +#define J314_TP_RES_X \ + ((J314_TP_MAX_X - J314_TP_MIN_X) / (J314_TP_DIMENSION_X / 100)) +#define J314_TP_DIMENSION_Y (float)8100 +#define J314_TP_MIN_Y -200 +#define J314_TP_MAX_Y 7400 +#define J314_TP_RES_Y \ + ((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100)) + +#define J314_TP_MAX_FINGER_ORIENTATION 16384 + +struct magicmouse_input_ops { + int (*raw_event)(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size); + int (*setup_input)(struct input_dev *input, struct hid_device *hdev); +}; + /** * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. @@ -129,9 +152,8 @@ struct magicmouse_sc { int scroll_accel; unsigned long scroll_jiffies; + struct input_mt_pos pos[MAX_CONTACTS]; struct { - short x; - short y; short scroll_x; short scroll_y; short scroll_x_hr; @@ -139,12 +161,13 @@ struct magicmouse_sc { u8 size; bool scroll_x_active; bool scroll_y_active; - } touches[16]; - int tracking_ids[16]; + } touches[MAX_CONTACTS]; + int tracking_ids[MAX_CONTACTS]; struct hid_device *hdev; struct delayed_work work; struct timer_list battery_timer; + struct magicmouse_input_ops input_ops; }; static int magicmouse_firm_touch(struct magicmouse_sc *msc) @@ -188,7 +211,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) } else if (last_state != 0) { state = last_state; } else if ((id = magicmouse_firm_touch(msc)) >= 0) { - int x = msc->touches[id].x; + int x = msc->pos[id].x; if (x < middle_button_start) state = 1; else if (x > middle_button_stop) @@ -249,8 +272,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda /* Store tracking ID and other fields. */ msc->tracking_ids[raw_id] = id; - msc->touches[id].x = x; - msc->touches[id].y = y; + msc->pos[id].x = x; + msc->pos[id].y = y; msc->touches[id].size = size; /* If requested, emulate a scroll wheel by detecting small @@ -374,6 +397,14 @@ static int magicmouse_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + return msc->input_ops.raw_event(hdev, report, data, size); +} + +static int magicmouse_raw_event_usb(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); struct input_dev *input = msc->input; int x = 0, y = 0, ii, clicks = 0, npoints; @@ -502,6 +533,175 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 1; } +/** + * struct tp_finger - single trackpad finger structure, le16-aligned + * + * @unknown1: unknown + * @unknown2: unknown + * @abs_x: absolute x coordinate + * @abs_y: absolute y coordinate + * @rel_x: relative x coordinate + * @rel_y: relative y coordinate + * @tool_major: tool area, major axis + * @tool_minor: tool area, minor axis + * @orientation: 16384 when point, else 15 bit angle + * @touch_major: touch area, major axis + * @touch_minor: touch area, minor axis + * @unused: zeros + * @pressure: pressure on forcetouch touchpad + * @multi: one finger: varies, more fingers: constant + * @crc16: on last finger: crc over the whole message struct + * (i.e. message header + this struct) minus the last + * @crc16 field; unknown on all other fingers. + */ +struct tp_finger { + __le16 unknown1; + __le16 unknown2; + __le16 abs_x; + __le16 abs_y; + __le16 rel_x; + __le16 rel_y; + __le16 tool_major; + __le16 tool_minor; + __le16 orientation; + __le16 touch_major; + __le16 touch_minor; + __le16 unused[2]; + __le16 pressure; + __le16 multi; +} __attribute__((packed, aligned(2))); + +/** + * vendor trackpad report + * + * @num_fingers: the number of fingers being reported in @fingers + * @buttons: same as HID buttons + */ +struct tp_header { + // HID vendor part, up to 1751 bytes + u8 unknown[22]; + u8 num_fingers; + u8 buttons; + u8 unknown3[14]; +}; + +/** + * standard HID mouse report + * + * @report_id: reportid + * @buttons: HID Usage Buttons 3 1-bit reports + */ +struct tp_mouse_report { + // HID mouse report + u8 report_id; + u8 buttons; + u8 rel_x; + u8 rel_y; + u8 padding[4]; +}; + +static inline int le16_to_int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + +static void report_finger_data(struct input_dev *input, int slot, + const struct input_mt_pos *pos, + const struct tp_finger *f) +{ + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, + le16_to_int(f->touch_major) << 1); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + le16_to_int(f->touch_minor) << 1); + input_report_abs(input, ABS_MT_WIDTH_MAJOR, + le16_to_int(f->tool_major) << 1); + input_report_abs(input, ABS_MT_WIDTH_MINOR, + le16_to_int(f->tool_minor) << 1); + input_report_abs(input, ABS_MT_ORIENTATION, + J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation)); + input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure)); + input_report_abs(input, ABS_MT_POSITION_X, pos->x); + input_report_abs(input, ABS_MT_POSITION_Y, pos->y); +} + +static int magicmouse_raw_event_mtp(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + struct input_dev *input = msc->input; + struct tp_header *tp_hdr; + struct tp_finger *f; + int i, n; + u32 npoints; + const size_t hdr_sz = sizeof(struct tp_header); + const size_t touch_sz = sizeof(struct tp_finger); + u8 map_contacs[MAX_CONTACTS]; + + // hid_warn(hdev, "%s\n", __func__); + // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data, + // size, false); + + /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */ + if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0) + return 0; + + tp_hdr = (struct tp_header *)data; + + npoints = (size - hdr_sz) / touch_sz; + if (npoints < tp_hdr->num_fingers || npoints > MAX_CONTACTS) { + hid_warn(hdev, + "unexpected number of touches (%u) for " + "report\n", + npoints); + return 0; + } + + n = 0; + for (i = 0; i < tp_hdr->num_fingers; i++) { + f = (struct tp_finger *)(data + hdr_sz + i * touch_sz); + if (le16_to_int(f->touch_major) == 0) + continue; + + hid_dbg(hdev, "ev x:%04hx y:%04hx\n", le16_to_int(f->abs_x), + le16_to_int(f->abs_y)); + msc->pos[n].x = le16_to_int(f->abs_x); + msc->pos[n].y = -le16_to_int(f->abs_y); + map_contacs[n] = i; + n++; + } + + input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0); + + for (i = 0; i < n; i++) { + int idx = map_contacs[i]; + f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz); + report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f); + } + + input_mt_sync_frame(input); + input_report_key(input, BTN_MOUSE, tp_hdr->buttons & 1); + + input_sync(input); + return 1; +} + +static int magicmouse_raw_event_spi(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + const size_t hdr_sz = sizeof(struct tp_mouse_report); + + if (size < hdr_sz) + return 0; + + if (data[0] != TRACKPAD2_USB_REPORT_ID) + return 0; + + return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); +} + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { @@ -519,7 +719,17 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, return 0; } -static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) + +static int magicmouse_setup_input(struct input_dev *input, + struct hid_device *hdev) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + return msc->input_ops.setup_input(input, hdev); +} + +static int magicmouse_setup_input_usb(struct input_dev *input, + struct hid_device *hdev) { int error; int mt_flags = 0; @@ -592,7 +802,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd __set_bit(EV_ABS, input->evbit); - error = input_mt_init_slots(input, 16, mt_flags); + error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); if (error) return error; input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2, @@ -671,6 +881,79 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd return 0; } +static int magicmouse_setup_input_spi(struct input_dev *input, + struct hid_device *hdev) +{ + int error; + int mt_flags = 0; + + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + __clear_bit(BTN_0, input->keybit); + __clear_bit(BTN_RIGHT, input->keybit); + __clear_bit(BTN_MIDDLE, input->keybit); + __clear_bit(EV_REL, input->evbit); + __clear_bit(REL_X, input->relbit); + __clear_bit(REL_Y, input->relbit); + + mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK; + + /* finger touch area */ + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0); + + /* finger approach area */ + input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0); + input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0); + + /* Note: Touch Y position from the device is inverted relative + * to how pointer motion is reported (and relative to how USB + * HID recommends the coordinates work). This driver keeps + * the origin at the same position, and just uses the additive + * inverse of the reported Y. + */ + + input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0); + + /* + * This makes libinput recognize this as a PressurePad and + * stop trying to use pressure for touch size. Pressure unit + * seems to be ~grams on these touchpads. + */ + input_abs_set_res(input, ABS_MT_PRESSURE, 1); + + /* finger orientation */ + input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION, + J314_TP_MAX_FINGER_ORIENTATION, 0, 0); + + /* finger position */ + input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X, + 0, 0); + /* Y axis is inverted */ + input_set_abs_params(input, ABS_MT_POSITION_Y, -J314_TP_MAX_Y, -J314_TP_MIN_Y, + 0, 0); + + /* X/Y resolution */ + input_abs_set_res(input, ABS_MT_POSITION_X, J314_TP_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, J314_TP_RES_Y); + + input_set_events_per_packet(input, 60); + + /* touchpad button */ + input_set_capability(input, EV_KEY, BTN_MOUSE); + + /* + * hid-input may mark device as using autorepeat, but the trackpad does + * not actually want it. + */ + __clear_bit(EV_REP, input->evbit); + + error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); + if (error) + return error; + + return 0; +} + static int magicmouse_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -726,6 +1009,9 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) feature_size = sizeof(feature_mt_trackpad2_usb); feature = feature_mt_trackpad2_usb; } + } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) { + feature_size = sizeof(feature_mt_trackpad2_usb); + feature = feature_mt_trackpad2_usb; } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { feature_size = sizeof(feature_mt_mouse2); feature = feature_mt_mouse2; @@ -800,12 +1086,30 @@ static int magicmouse_probe(struct hid_device *hdev, struct hid_report *report; int ret; + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && + hdev->type != HID_TYPE_SPI_MOUSE) + return -ENODEV; + msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL); if (msc == NULL) { hid_err(hdev, "can't alloc magicmouse descriptor\n"); return -ENOMEM; } + // internal trackpad use a data format use input ops to avoid + // conflicts with the report ID. + if (id->bus == BUS_HOST) { + msc->input_ops.raw_event = magicmouse_raw_event_mtp; + msc->input_ops.setup_input = magicmouse_setup_input_spi; + } else if (id->bus == BUS_SPI) { + msc->input_ops.raw_event = magicmouse_raw_event_spi; + msc->input_ops.setup_input = magicmouse_setup_input_spi; + + } else { + msc->input_ops.raw_event = magicmouse_raw_event_usb; + msc->input_ops.setup_input = magicmouse_setup_input_usb; + } + msc->scroll_accel = SCROLL_ACCEL_DEFAULT; msc->hdev = hdev; INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work); @@ -854,6 +1158,10 @@ static int magicmouse_probe(struct hid_device *hdev, else /* USB_VENDOR_ID_APPLE */ report = hid_register_report(hdev, HID_INPUT_REPORT, TRACKPAD2_USB_REPORT_ID, 0); + } else if (id->bus == BUS_SPI) { + report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0); + } else if (id->bus == BUS_HOST) { + report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0); } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ report = hid_register_report(hdev, HID_INPUT_REPORT, TRACKPAD_REPORT_ID, 0); @@ -868,6 +1176,10 @@ static int magicmouse_probe(struct hid_device *hdev, } report->size = 6; + /* MTP devices do not need the MT enable, this is handled by the MTP driver */ + if (id->bus == BUS_HOST) + return 0; + /* * Some devices repond with 'invalid report id' when feature * report switching it into multitouch mode is sent to it. @@ -948,6 +1260,10 @@ static const struct hid_device_id magic_mice[] = { USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = 0 }, + { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, + HID_ANY_ID), .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(hid, magic_mice); diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig new file mode 100644 index 000000000000..8e37f0fec28a --- /dev/null +++ b/drivers/hid/spi-hid/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "SPI HID support" + depends on SPI + +config SPI_HID_APPLE_OF + tristate "HID over SPI transport layer for Apple Silicon SoCs" + default ARCH_APPLE + depends on SPI && INPUT && OF + help + Say Y here if you use Apple Silicon based laptop. The keyboard and + touchpad are HID based devices connected via SPI. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called spi-hid-apple-of. It will also build/depend on the + module spi-hid-apple. + +endmenu + +config SPI_HID_APPLE_CORE + tristate + default y if SPI_HID_APPLE_OF=y + default m if SPI_HID_APPLE_OF=m + select HID + select CRC16 diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile new file mode 100644 index 000000000000..f276ee12cb94 --- /dev/null +++ b/drivers/hid/spi-hid/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for SPI HID tarnsport drivers +# + +obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid-apple.o + +spi-hid-apple-objs = spi-hid-apple-core.o + +obj-$(CONFIG_SPI_HID_APPLE_OF) += spi-hid-apple-of.o diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c new file mode 100644 index 000000000000..56941430befa --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple-core.c @@ -0,0 +1,1029 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * + * Apple SPI HID transport driver + * + * Copyright (C) The Asahi Linux Contributors + * + * Based on: drivers/input/applespi.c + * + * MacBook (Pro) SPI keyboard and touchpad driver + * + * Copyright (c) 2015-2018 Federico Lorenzi + * Copyright (c) 2017-2018 Ronald Tschalär + * + */ + +//#define DEBUG 2 + +#include <asm/unaligned.h> +#include <linux/crc16.h> +#include <linux/delay.h> +#include <linux/device/driver.h> +#include <linux/hid.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/printk.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/wait.h> + +#include "spi-hid-apple.h" + +#define SPIHID_DEF_WAIT msecs_to_jiffies(100) + +#define SPIHID_MAX_INPUT_REPORT_SIZE 0x800 + +/* support only keyboard, trackpad and management dev for now */ +#define SPIHID_MAX_DEVICES 3 + +#define SPIHID_DEVICE_ID_MNGT 0x0 +#define SPIHID_DEVICE_ID_KBD 0x1 +#define SPIHID_DEVICE_ID_TP 0x2 +#define SPIHID_DEVICE_ID_INFO 0xd0 + +#define SPIHID_READ_PACKET 0x20 +#define SPIHID_WRITE_PACKET 0x40 + +#define SPIHID_DESC_MAX 512 + +#define SPIHID_SET_LEDS 0x0151 /* caps lock */ + +#define SPI_RW_CHG_DELAY_US 200 /* 'Inter Stage Us'? */ + +static const u8 spi_hid_apple_booted[4] = { 0xa0, 0x80, 0x00, 0x00 }; +static const u8 spi_hid_apple_status_ok[4] = { 0xac, 0x27, 0x68, 0xd5 }; + +struct spihid_interface { + struct hid_device *hid; + u8 *hid_desc; + u32 hid_desc_len; + u32 id; + unsigned country; + u32 max_control_report_len; + u32 max_input_report_len; + u32 max_output_report_len; + u8 name[32]; + bool ready; +}; + +struct spihid_input_report { + u8 *buf; + u32 length; + u32 offset; + u8 device; + u8 flags; +}; + +struct spihid_apple { + struct spi_device *spidev; + + struct spihid_apple_ops *ops; + + struct spihid_interface mngt; + struct spihid_interface kbd; + struct spihid_interface tp; + + wait_queue_head_t wait; + struct mutex tx_lock; //< protects against concurrent SPI writes + + struct spi_message rx_msg; + struct spi_message tx_msg; + struct spi_transfer rx_transfer; + struct spi_transfer tx_transfer; + struct spi_transfer status_transfer; + + u8 *rx_buf; + u8 *tx_buf; + u8 *status_buf; + + u8 vendor[32]; + u8 product[64]; + u8 serial[32]; + + u32 num_devices; + + u32 vendor_id; + u32 product_id; + u32 version_number; + + u8 msg_id; + + /* fragmented HID report */ + struct spihid_input_report report; + + /* state tracking flags */ + bool status_booted; +}; + +/** + * struct spihid_msg_hdr - common header of protocol messages. + * + * Each message begins with fixed header, followed by a message-type specific + * payload, and ends with a 16-bit crc. Because of the varying lengths of the + * payload, the crc is defined at the end of each payload struct, rather than + * in this struct. + * + * @unknown0: request type? output, input (0x10), feature, protocol + * @unknown1: maybe report id? + * @unknown2: mostly zero, in info request maybe device num + * @msgid: incremented on each message, rolls over after 255; there is a + * separate counter for each message type. + * @rsplen: response length (the exact nature of this field is quite + * speculative). On a request/write this is often the same as + * @length, though in some cases it has been seen to be much larger + * (e.g. 0x400); on a response/read this the same as on the + * request; for reads that are not responses it is 0. + * @length: length of the remainder of the data in the whole message + * structure (after re-assembly in case of being split over + * multiple spi-packets), minus the trailing crc. The total size + * of a message is therefore @length + 10. + */ + +struct spihid_msg_hdr { + u8 unknown0; + u8 unknown1; + u8 unknown2; + u8 id; + __le16 rsplen; + __le16 length; +}; + +/** + * struct spihid_transfer_packet - a complete spi packet; always 256 bytes. This carries + * the (parts of the) message in the data. But note that this does not + * necessarily contain a complete message, as in some cases (e.g. many + * fingers pressed) the message is split over multiple packets (see the + * @offset, @remain, and @length fields). In general the data parts in + * spihid_transfer_packet's are concatenated until @remaining is 0, and the + * result is an message. + * + * @flags: 0x40 = write (to device), 0x20 = read (from device); note that + * the response to a write still has 0x40. + * @device: 1 = keyboard, 2 = touchpad + * @offset: specifies the offset of this packet's data in the complete + * message; i.e. > 0 indicates this is a continuation packet (in + * the second packet for a message split over multiple packets + * this would then be the same as the @length in the first packet) + * @remain: number of message bytes remaining in subsequents packets (in + * the first packet of a message split over two packets this would + * then be the same as the @length in the second packet) + * @length: length of the valid data in the @data in this packet + * @data: all or part of a message + * @crc16: crc over this whole structure minus this @crc16 field. This + * covers just this packet, even on multi-packet messages (in + * contrast to the crc in the message). + */ +struct spihid_transfer_packet { + u8 flags; + u8 device; + __le16 offset; + __le16 remain; + __le16 length; + u8 data[246]; + __le16 crc16; +}; + +/* + * how HID is mapped onto the protocol is not fully clear. This are the known + * reports/request: + * + * pkt.flags pkt.dev? msg.u0 msg.u1 msg.u2 + * info 0x40 0xd0 0x20 0x01 0xd0 + * + * info mngt: 0x40 0xd0 0x20 0x10 0x00 + * info kbd: 0x40 0xd0 0x20 0x10 0x01 + * info tp: 0x40 0xd0 0x20 0x10 0x02 + * + * desc kbd: 0x40 0xd0 0x20 0x10 0x01 + * desc trackpad: 0x40 0xd0 0x20 0x10 0x02 + * + * mt mode: 0x40 0x02 0x52 0x02 0x00 set protocol? + * capslock led 0x40 0x01 0x51 0x01 0x00 output report + * + * report kbd: 0x20 0x01 0x10 0x01 0x00 input report + * report tp: 0x20 0x02 0x10 0x02 0x00 input report + * + */ + + +static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0, + u8 unk1, u8 unk2, u16 resp_len, u8 *buf, + size_t len) +{ + struct spihid_transfer_packet *pkt; + struct spihid_msg_hdr *hdr; + u16 crc; + int err; + + /* know reports are small enoug to fit in a single packet */ + if (len > sizeof(pkt->data) - sizeof(*hdr) - sizeof(__le16)) + return -EINVAL; + + err = mutex_lock_interruptible(&spihid->tx_lock); + if (err < 0) + return err; + + pkt = (struct spihid_transfer_packet *)spihid->tx_buf; + + memset(pkt, 0, sizeof(*pkt)); + pkt->flags = SPIHID_WRITE_PACKET; + pkt->device = target; + pkt->length = sizeof(*hdr) + len + sizeof(__le16); + + hdr = (struct spihid_msg_hdr *)&pkt->data[0]; + hdr->unknown0 = unk0; + hdr->unknown1 = unk1; + hdr->unknown2 = unk2; + hdr->id = spihid->msg_id++; + hdr->rsplen = cpu_to_le16(resp_len); + hdr->length = cpu_to_le16(len); + + if (len) + memcpy(pkt->data + sizeof(*hdr), buf, len); + crc = crc16(0, &pkt->data[0], sizeof(*hdr) + len); + put_unaligned_le16(crc, pkt->data + sizeof(*hdr) + len); + + pkt->crc16 = crc16(0, spihid->tx_buf, + offsetof(struct spihid_transfer_packet, crc16)); + + err = spi_sync(spihid->spidev, &spihid->tx_msg); + mutex_unlock(&spihid->tx_lock); + if (err < 0) + return err; + + return (int)len; +} + +struct spihid_apple *spihid_get_data(struct spihid_interface *idev) +{ + switch (idev->id) { + case SPIHID_DEVICE_ID_KBD: + return container_of(idev, struct spihid_apple, kbd); + case SPIHID_DEVICE_ID_TP: + return container_of(idev, struct spihid_apple, tp); + default: + return NULL; + } +} + +static int apple_ll_start(struct hid_device *hdev) +{ + /* no-op SPI transport is already setup */ + return 0; +}; + +static void apple_ll_stop(struct hid_device *hdev) +{ + /* no-op, devices will be desstroyed on driver destruction */ +} + +static int apple_ll_open(struct hid_device *hdev) +{ + struct spihid_apple *spihid; + struct spihid_interface *idev = hdev->driver_data; + + if (idev->hid_desc_len == 0) { + spihid = spihid_get_data(idev); + dev_warn(&spihid->spidev->dev, + "HID descriptor missing for dev %u", idev->id); + } else + idev->ready = true; + + return 0; +} + +static void apple_ll_close(struct hid_device *hdev) +{ + struct spihid_interface *idev = hdev->driver_data; + idev->ready = false; +} + +static int apple_ll_parse(struct hid_device *hdev) +{ + struct spihid_interface *idev = hdev->driver_data; + + return hid_parse_report(hdev, idev->hid_desc, idev->hid_desc_len); +} + +static int apple_ll_raw_request(struct hid_device *hdev, + unsigned char reportnum, __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + struct spihid_interface *idev = hdev->driver_data; + struct spihid_apple *spihid = spihid_get_data(idev); + + dev_dbg(&spihid->spidev->dev, + "apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu", + idev->id, reportnum, rtype); + + switch (reqtype) { + case HID_REQ_GET_REPORT: + return -EINVAL; // spihid_get_raw_report(); + case HID_REQ_SET_REPORT: + if (buf[0] != reportnum) + return -EINVAL; + if (reportnum != idev->id) { + dev_warn(&spihid->spidev->dev, + "device:%u reportnum:" + "%hhu mismatch", + idev->id, reportnum); + return -EINVAL; + } + return spihid_apple_request(spihid, idev->id, 0x52, reportnum, 0x00, 2, buf, len); + default: + return -EIO; + } +} + +static int apple_ll_output_report(struct hid_device *hdev, __u8 *buf, + size_t len) +{ + struct spihid_interface *idev = hdev->driver_data; + struct spihid_apple *spihid = spihid_get_data(idev); + if (!spihid) + return -1; + + dev_dbg(&spihid->spidev->dev, + "apple_ll_output_report: device:%u len:%zu:", + idev->id, len); + // second idev->id should maybe be buf[0]? + return spihid_apple_request(spihid, idev->id, 0x51, idev->id, 0x00, 0, buf, len); +} + +static struct hid_ll_driver apple_hid_ll = { + .start = &apple_ll_start, + .stop = &apple_ll_stop, + .open = &apple_ll_open, + .close = &apple_ll_close, + .parse = &apple_ll_parse, + .raw_request = &apple_ll_raw_request, + .output_report = &apple_ll_output_report, +}; + +static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid, + u32 iface) +{ + switch (iface) { + case SPIHID_DEVICE_ID_MNGT: + return &spihid->mngt; + case SPIHID_DEVICE_ID_KBD: + return &spihid->kbd; + case SPIHID_DEVICE_ID_TP: + return &spihid->tp; + default: + return NULL; + } +} + +static int spihid_verify_msg(struct spihid_apple *spihid, u8 *buf, size_t len) +{ + u16 msg_crc, crc; + struct device *dev = &spihid->spidev->dev; + + crc = crc16(0, buf, len - sizeof(__le16)); + msg_crc = get_unaligned_le16(buf + len - sizeof(__le16)); + if (crc != msg_crc) { + dev_warn_ratelimited(dev, "Read message crc mismatch\n"); + return 0; + } + return 1; +} + +static bool spihid_status_report(struct spihid_apple *spihid, u8 *pl, + size_t len) +{ + struct device *dev = &spihid->spidev->dev; + dev_dbg(dev, "%s: len: %zu", __func__, len); + if (len == 5 && pl[0] == 0xe0) + return true; + + return false; +} + +static bool spihid_process_input_report(struct spihid_apple *spihid, u32 device, + struct spihid_msg_hdr *hdr, u8 *payload, + size_t len) +{ + //dev_dbg(&spihid>spidev->dev, "input report: req:%hx iface:%u ", hdr->unknown0, device); + if (hdr->unknown0 != 0x10) + return false; + + /* HID device as well but Vendor usage only, handle it internally for now */ + if (device == 0) { + if (hdr->unknown1 == 0xe0) { + return spihid_status_report(spihid, payload, len); + } + } else if (device < SPIHID_MAX_DEVICES) { + struct spihid_interface *iface = + spihid_get_iface(spihid, device); + if (iface && iface->hid && iface->ready) { + hid_input_report(iface->hid, HID_INPUT_REPORT, payload, + len, 1); + return true; + } + } else + dev_dbg(&spihid->spidev->dev, + "unexpected iface:%u for input report", device); + + return false; +} + +struct spihid_device_info { + __le16 u0[2]; + __le16 num_devices; + __le16 vendor_id; + __le16 product_id; + __le16 version_number; + __le16 vendor_str[2]; //< offset and string length + __le16 product_str[2]; //< offset and string length + __le16 serial_str[2]; //< offset and string length +}; + +static bool spihid_process_device_info(struct spihid_apple *spihid, u32 iface, + u8 *payload, size_t len) +{ + struct device *dev = &spihid->spidev->dev; + + if (iface != SPIHID_DEVICE_ID_INFO) + return false; + + if (spihid->vendor_id == 0 && + len >= sizeof(struct spihid_device_info)) { + struct spihid_device_info *info = + (struct spihid_device_info *)payload; + u16 voff, vlen, poff, plen, soff, slen; + u32 num_devices; + + num_devices = __le16_to_cpu(info->num_devices); + + if (num_devices < SPIHID_MAX_DEVICES) { + dev_err(dev, + "Device info reports %u devices, expecting at least 3", + num_devices); + return false; + } + spihid->num_devices = num_devices; + + if (spihid->num_devices > SPIHID_MAX_DEVICES) { + dev_info( + dev, + "limiting the number of devices to mngt, kbd and mouse"); + spihid->num_devices = SPIHID_MAX_DEVICES; + } + + spihid->vendor_id = __le16_to_cpu(info->vendor_id); + spihid->product_id = __le16_to_cpu(info->product_id); + spihid->version_number = __le16_to_cpu(info->version_number); + + voff = __le16_to_cpu(info->vendor_str[0]); + vlen = __le16_to_cpu(info->vendor_str[1]); + + if (voff < len && vlen <= len - voff && + vlen < sizeof(spihid->vendor)) { + memcpy(spihid->vendor, payload + voff, vlen); + spihid->vendor[vlen] = '\0'; + } + + poff = __le16_to_cpu(info->product_str[0]); + plen = __le16_to_cpu(info->product_str[1]); + + if (poff < len && plen <= len - poff && + plen < sizeof(spihid->product)) { + memcpy(spihid->product, payload + poff, plen); + spihid->product[plen] = '\0'; + } + + soff = __le16_to_cpu(info->serial_str[0]); + slen = __le16_to_cpu(info->serial_str[1]); + + if (soff < len && slen <= len - soff && + slen < sizeof(spihid->serial)) { + memcpy(spihid->vendor, payload + soff, slen); + spihid->serial[slen] = '\0'; + } + + wake_up_interruptible(&spihid->wait); + } + return true; +} + +struct spihid_iface_info { + u8 u_0; + u8 interface_num; + u8 u_2; + u8 u_3; + u8 u_4; + u8 country_code; + __le16 max_input_report_len; + __le16 max_output_report_len; + __le16 max_control_report_len; + __le16 name_offset; + __le16 name_length; +}; + +static bool spihid_process_iface_info(struct spihid_apple *spihid, u32 num, + u8 *payload, size_t len) +{ + struct spihid_iface_info *info; + struct spihid_interface *iface = spihid_get_iface(spihid, num); + u32 name_off, name_len; + + if (!iface) + return false; + + if (!iface->max_input_report_len) { + if (len < sizeof(*info)) + return false; + + info = (struct spihid_iface_info *)payload; + + iface->max_input_report_len = + le16_to_cpu(info->max_input_report_len); + iface->max_output_report_len = + le16_to_cpu(info->max_output_report_len); + iface->max_control_report_len = + le16_to_cpu(info->max_control_report_len); + iface->country = info->country_code; + + name_off = le16_to_cpu(info->name_offset); + name_len = le16_to_cpu(info->name_length); + + if (name_off < len && name_len <= len - name_off && + name_len < sizeof(iface->name)) { + memcpy(iface->name, payload + name_off, name_len); + iface->name[name_len] = '\0'; + } + + dev_dbg(&spihid->spidev->dev, "Info for %s, country code: 0x%x", + iface->name, iface->country); + + wake_up_interruptible(&spihid->wait); + } + + return true; +} + +static int spihid_register_hid_device(struct spihid_apple *spihid, + struct spihid_interface *idev, u8 device); + +static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid, + u32 num, u8 *payload, + size_t len) +{ + struct spihid_interface *iface = spihid_get_iface(spihid, num); + + if (!iface) + return false; + + if (iface->hid_desc_len == 0) { + if (len > SPIHID_DESC_MAX) + return false; + memcpy(iface->hid_desc, payload, len); + iface->hid_desc_len = len; + + /* do not register the mngt iface as HID device */ + if (num > 0) + spihid_register_hid_device(spihid, iface, num); + + wake_up_interruptible(&spihid->wait); + } + return true; +} + +static bool spihid_process_response(struct spihid_apple *spihid, + struct spihid_msg_hdr *hdr, u8 *payload, + size_t len) +{ + if (hdr->unknown0 == 0x20) { + switch (hdr->unknown1) { + case 0x01: + return spihid_process_device_info(spihid, hdr->unknown2, + payload, len); + case 0x02: + return spihid_process_iface_info(spihid, hdr->unknown2, + payload, len); + case 0x10: + return spihid_process_iface_hid_report_desc( + spihid, hdr->unknown2, payload, len); + default: + break; + } + } + + return false; +} + +static void spihid_process_message(struct spihid_apple *spihid, u8 *data, + size_t length, u8 device, u8 flags) +{ + struct device *dev = &spihid->spidev->dev; + struct spihid_msg_hdr *hdr; + bool handled = false; + u8 *payload; + + if (!spihid_verify_msg(spihid, data, length)) + return; + + hdr = (struct spihid_msg_hdr *)data; + + if (hdr->length == 0) + return; + + payload = data + sizeof(struct spihid_msg_hdr); + + switch (flags) { + case SPIHID_READ_PACKET: + handled = spihid_process_input_report(spihid, device, hdr, + payload, hdr->length); + break; + case SPIHID_WRITE_PACKET: + handled = spihid_process_response(spihid, hdr, payload, + hdr->length); + break; + default: + break; + } + +#if defined(DEBUG) && DEBUG > 1 + { + dev_dbg(dev, + "R msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n", + hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id, + hdr->length); + print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1, + payload, hdr->length, true); + } +#else + if (!handled) { + dev_dbg(dev, + "R unhandled msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n", + hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id, + hdr->length); + print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1, + payload, hdr->length, true); + } +#endif +} + +static void spihid_assemble_meesage(struct spihid_apple *spihid, + struct spihid_transfer_packet *pkt) +{ + size_t length, offset, remain; + struct device *dev = &spihid->spidev->dev; + struct spihid_input_report *rep = &spihid->report; + + length = le16_to_cpu(pkt->length); + remain = le16_to_cpu(pkt->remain); + offset = le16_to_cpu(pkt->offset); + + if (offset + length + remain > U16_MAX) { + return; + } + + if (pkt->device != rep->device || pkt->flags != rep->flags || + pkt->offset != rep->offset) { + rep->device = 0; + rep->flags = 0; + rep->offset = 0; + rep->length = 0; + } + + if (pkt->offset == 0) { + if (rep->offset != 0) { + dev_warn(dev, "incomplete report off:%u len:%u", + rep->offset, rep->length); + } + memcpy(rep->buf, pkt->data, length); + rep->offset = length; + rep->length = length + pkt->remain; + rep->device = pkt->device; + rep->flags = pkt->flags; + } else if (pkt->offset == rep->offset) { + if (pkt->offset + length + pkt->remain != rep->length) { + dev_warn(dev, "incomplete report off:%u len:%u", + rep->offset, rep->length); + return; + } + memcpy(rep->buf + pkt->offset, pkt->data, pkt->length); + rep->offset += pkt->length; + + if (rep->offset == rep->length) { + spihid_process_message(spihid, rep->buf, rep->length, + rep->device, rep->flags); + rep->device = 0; + rep->flags = 0; + rep->offset = 0; + rep->length = 0; + } + } +} + +static void spihid_process_read(struct spihid_apple *spihid) +{ + u16 crc; + size_t length; + struct device *dev = &spihid->spidev->dev; + struct spihid_transfer_packet *pkt; + + pkt = (struct spihid_transfer_packet *)spihid->rx_buf; + + /* check transfer packet crc */ + crc = crc16(0, spihid->rx_buf, + offsetof(struct spihid_transfer_packet, crc16)); + if (crc != pkt->crc16) { + dev_warn_ratelimited(dev, "Read package crc mismatch\n"); + return; + } + + length = le16_to_cpu(pkt->length); + + if (length < sizeof(struct spihid_msg_hdr) + 2) { + if (length == sizeof(spi_hid_apple_booted) && + !memcmp(pkt->data, spi_hid_apple_booted, length)) { + if (!spihid->status_booted) { + spihid->status_booted = true; + wake_up_interruptible(&spihid->wait); + } + } else { + dev_info(dev, "R short packet: len:%zu\n", length); + print_hex_dump_debug("spihid pkt:", DUMP_PREFIX_OFFSET, 16, 1, + pkt->data, length, false); + } + return; + } + +#if defined(DEBUG) && DEBUG > 1 + dev_dbg(dev, + "R pkt: flags:%02hhx dev:%02hhx off:%hu remain:%hu, len:%zu\n", + pkt->flags, pkt->device, pkt->offset, pkt->remain, length); +#if defined(DEBUG) && DEBUG > 2 + print_hex_dump_debug("spihid pkt: ", DUMP_PREFIX_OFFSET, 16, 1, + spihid->rx_buf, + sizeof(struct spihid_transfer_packet), true); +#endif +#endif + + if (length > sizeof(pkt->data)) { + dev_warn_ratelimited(dev, "Invalid pkt len:%zu", length); + return; + } + + /* short message */ + if (pkt->offset == 0 && pkt->remain == 0) { + spihid_process_message(spihid, pkt->data, length, pkt->device, + pkt->flags); + } else { + spihid_assemble_meesage(spihid, pkt); + } +} + +static void spihid_read_packet_sync(struct spihid_apple *spihid) +{ + int err; + + err = spi_sync(spihid->spidev, &spihid->rx_msg); + if (!err) { + spihid_process_read(spihid); + } else { + dev_warn(&spihid->spidev->dev, "RX failed: %d\n", err); + } +} + +irqreturn_t spihid_apple_core_irq(int irq, void *data) +{ + struct spi_device *spi = data; + struct spihid_apple *spihid = spi_get_drvdata(spi); + + spihid_read_packet_sync(spihid); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(spihid_apple_core_irq); + +static void spihid_apple_setup_spi_msgs(struct spihid_apple *spihid) +{ + memset(&spihid->rx_transfer, 0, sizeof(spihid->rx_transfer)); + + spihid->rx_transfer.rx_buf = spihid->rx_buf; + spihid->rx_transfer.len = sizeof(struct spihid_transfer_packet); + + spi_message_init(&spihid->rx_msg); + spi_message_add_tail(&spihid->rx_transfer, &spihid->rx_msg); + + memset(&spihid->tx_transfer, 0, sizeof(spihid->rx_transfer)); + memset(&spihid->status_transfer, 0, sizeof(spihid->status_transfer)); + + spihid->tx_transfer.tx_buf = spihid->tx_buf; + spihid->tx_transfer.len = sizeof(struct spihid_transfer_packet); + spihid->tx_transfer.delay.unit = SPI_DELAY_UNIT_USECS; + spihid->tx_transfer.delay.value = SPI_RW_CHG_DELAY_US; + + spihid->status_transfer.rx_buf = spihid->status_buf; + spihid->status_transfer.len = sizeof(spi_hid_apple_status_ok); + + spi_message_init(&spihid->tx_msg); + spi_message_add_tail(&spihid->tx_transfer, &spihid->tx_msg); + spi_message_add_tail(&spihid->status_transfer, &spihid->tx_msg); +} + +static int spihid_apple_setup_spi(struct spihid_apple *spihid) +{ + spihid_apple_setup_spi_msgs(spihid); + + return spihid->ops->power_on(spihid->ops); +} + +static int spihid_register_hid_device(struct spihid_apple *spihid, + struct spihid_interface *iface, u8 device) +{ + int ret; + struct hid_device *hid; + + iface->id = device; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + strscpy(hid->name, spihid->product, sizeof(hid->name)); + snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)", + dev_name(&spihid->spidev->dev), device); + strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq)); + + hid->ll_driver = &apple_hid_ll; + hid->bus = BUS_SPI; + hid->vendor = spihid->vendor_id; + hid->product = spihid->product_id; + hid->version = spihid->version_number; + + if (device == SPIHID_DEVICE_ID_KBD) + hid->type = HID_TYPE_SPI_KEYBOARD; + else if (device == SPIHID_DEVICE_ID_TP) + hid->type = HID_TYPE_SPI_MOUSE; + + hid->country = iface->country; + hid->dev.parent = &spihid->spidev->dev; + hid->driver_data = iface; + + ret = hid_add_device(hid); + if (ret < 0) { + hid_destroy_device(hid); + dev_warn(&spihid->spidev->dev, + "Failed to register hid device %hhu", device); + return ret; + } + + iface->hid = hid; + + return 0; +} + +static void spihid_destroy_hid_device(struct spihid_interface *iface) +{ + if (iface->hid) { + hid_destroy_device(iface->hid); + iface->hid = NULL; + } + iface->ready = false; +} + +int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops) +{ + struct device *dev = &spi->dev; + struct spihid_apple *spihid; + int err, i; + + if (!ops || !ops->power_on || !ops->power_off || !ops->enable_irq || !ops->disable_irq) + return -EINVAL; + + spihid = devm_kzalloc(dev, sizeof(*spihid), GFP_KERNEL); + if (!spihid) + return -ENOMEM; + + spihid->ops = ops; + spihid->spidev = spi; + + // init spi + spi_set_drvdata(spi, spihid); + + /* allocate SPI buffers */ + spihid->rx_buf = devm_kmalloc( + &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); + spihid->tx_buf = devm_kmalloc( + &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); + spihid->status_buf = devm_kmalloc( + &spi->dev, sizeof(spi_hid_apple_status_ok), GFP_KERNEL); + + if (!spihid->rx_buf || !spihid->tx_buf || !spihid->status_buf) + return -ENOMEM; + + spihid->report.buf = + devm_kmalloc(dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL); + + spihid->kbd.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL); + spihid->tp.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL); + + if (!spihid->report.buf || !spihid->kbd.hid_desc || + !spihid->tp.hid_desc) + return -ENOMEM; + + init_waitqueue_head(&spihid->wait); + + mutex_init(&spihid->tx_lock); + + /* Init spi transfer buffers and power device on */ + err = spihid_apple_setup_spi(spihid); + if (err < 0) + goto error; + + /* enable HID irq */ + spihid->ops->enable_irq(spihid->ops); + + // wait for boot message + err = wait_event_interruptible_timeout(spihid->wait, + spihid->status_booted, + msecs_to_jiffies(1000)); + if (err == 0) + err = -ENODEV; + if (err < 0) { + dev_err(dev, "waiting for device boot failed: %d", err); + goto error; + } + + /* request device information */ + dev_dbg(dev, "request device info"); + spihid_apple_request(spihid, 0xd0, 0x20, 0x01, 0xd0, 0, NULL, 0); + err = wait_event_interruptible_timeout(spihid->wait, spihid->vendor_id, + SPIHID_DEF_WAIT); + if (err == 0) + err = -ENODEV; + if (err < 0) { + dev_err(dev, "waiting for device info failed: %d", err); + goto error; + } + + /* request interface information */ + for (i = 0; i < spihid->num_devices; i++) { + struct spihid_interface *iface = spihid_get_iface(spihid, i); + if (!iface) + continue; + dev_dbg(dev, "request interface info 0x%02x", i); + spihid_apple_request(spihid, 0xd0, 0x20, 0x02, i, + SPIHID_DESC_MAX, NULL, 0); + err = wait_event_interruptible_timeout( + spihid->wait, iface->max_input_report_len, + SPIHID_DEF_WAIT); + } + + /* request HID report descriptors */ + for (i = 1; i < spihid->num_devices; i++) { + struct spihid_interface *iface = spihid_get_iface(spihid, i); + if (!iface) + continue; + dev_dbg(dev, "request hid report desc 0x%02x", i); + spihid_apple_request(spihid, 0xd0, 0x20, 0x10, i, + SPIHID_DESC_MAX, NULL, 0); + wait_event_interruptible_timeout( + spihid->wait, iface->hid_desc_len, SPIHID_DEF_WAIT); + } + + return 0; +error: + return err; +} +EXPORT_SYMBOL_GPL(spihid_apple_core_probe); + +void spihid_apple_core_remove(struct spi_device *spi) +{ + struct spihid_apple *spihid = spi_get_drvdata(spi); + + /* destroy input devices */ + + spihid_destroy_hid_device(&spihid->tp); + spihid_destroy_hid_device(&spihid->kbd); + + /* disable irq */ + spihid->ops->disable_irq(spihid->ops); + + /* power SPI device down */ + spihid->ops->power_off(spihid->ops); +} +EXPORT_SYMBOL_GPL(spihid_apple_core_remove); + +void spihid_apple_core_shutdown(struct spi_device *spi) +{ + struct spihid_apple *spihid = spi_get_drvdata(spi); + + /* disable irq */ + spihid->ops->disable_irq(spihid->ops); + + /* power SPI device down */ + spihid->ops->power_off(spihid->ops); +} +EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown); + +MODULE_DESCRIPTION("Apple SPI HID transport driver"); +MODULE_AUTHOR("Janne Grunau <j@jannau.net>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c new file mode 100644 index 000000000000..db76774eea7e --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple-of.c @@ -0,0 +1,138 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * + * Apple SPI HID transport driver - Open Firmware + * + * Copyright (C) The Asahi Linux Contributors + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_irq.h> + +#include "spi-hid-apple.h" + + +struct spihid_apple_of { + struct spihid_apple_ops ops; + + struct gpio_desc *enable_gpio; + int irq; +}; + +int spihid_apple_of_power_on(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + /* reset the controller on boot */ + gpiod_direction_output(sh_of->enable_gpio, 1); + msleep(5); + gpiod_direction_output(sh_of->enable_gpio, 0); + msleep(5); + /* turn SPI device on */ + gpiod_direction_output(sh_of->enable_gpio, 1); + msleep(50); + + return 0; +} + +int spihid_apple_of_power_off(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + /* turn SPI device off */ + gpiod_direction_output(sh_of->enable_gpio, 0); + + return 0; +} + +int spihid_apple_of_enable_irq(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + enable_irq(sh_of->irq); + + return 0; +} + +int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + disable_irq(sh_of->irq); + + return 0; +} + +static int spihid_apple_of_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct spihid_apple_of *spihid_of; + int err; + + dev_warn(dev, "%s:%d", __func__, __LINE__); + + spihid_of = devm_kzalloc(dev, sizeof(*spihid_of), GFP_KERNEL); + if (!spihid_of) + return -ENOMEM; + + spihid_of->ops.power_on = spihid_apple_of_power_on; + spihid_of->ops.power_off = spihid_apple_of_power_off; + spihid_of->ops.enable_irq = spihid_apple_of_enable_irq; + spihid_of->ops.disable_irq = spihid_apple_of_disable_irq; + + spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0); + if (IS_ERR(spihid_of->enable_gpio)) { + err = PTR_ERR(spihid_of->enable_gpio); + dev_err(dev, "failed to get 'spien' gpio pin: %d", err); + return err; + } + + spihid_of->irq = of_irq_get(dev->of_node, 0); + if (spihid_of->irq < 0) { + err = spihid_of->irq; + dev_err(dev, "failed to get 'extended-irq': %d", err); + return err; + } + err = devm_request_threaded_irq(dev, spihid_of->irq, NULL, + spihid_apple_core_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN, + "spi-hid-apple-irq", spi); + if (err < 0) { + dev_err(dev, "failed to request extended-irq %d: %d", + spihid_of->irq, err); + return err; + } + + return spihid_apple_core_probe(spi, &spihid_of->ops); +} + +static const struct of_device_id spihid_apple_of_match[] = { + { .compatible = "apple,spi-hid-transport" }, + {}, +}; +MODULE_DEVICE_TABLE(of, spihid_apple_of_match); + +static struct spi_device_id spihid_apple_of_id[] = { + { "spi-hid-transport", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, spihid_apple_of_id); + +static struct spi_driver spihid_apple_of_driver = { + .driver = { + .name = "spi-hid-apple-of", + //.pm = &spi_hid_apple_of_pm, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(spihid_apple_of_match), + }, + + .id_table = spihid_apple_of_id, + .probe = spihid_apple_of_probe, + .remove = spihid_apple_core_remove, + .shutdown = spihid_apple_core_shutdown, +}; + +module_spi_driver(spihid_apple_of_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h new file mode 100644 index 000000000000..2d9554e8a5f8 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ + +#ifndef SPI_HID_APPLE_H +#define SPI_HID_APPLE_H + +#include <linux/interrupt.h> +#include <linux/spi/spi.h> + +/** + * struct spihid_apple_ops - Ops to control the device from the core driver. + * + * @power_on: reset and power the device on. + * @power_off: power the device off. + * @enable_irq: enable irq or ACPI gpe. + * @disable_irq: disable irq or ACPI gpe. + */ + +struct spihid_apple_ops { + int (*power_on)(struct spihid_apple_ops *ops); + int (*power_off)(struct spihid_apple_ops *ops); + int (*enable_irq)(struct spihid_apple_ops *ops); + int (*disable_irq)(struct spihid_apple_ops *ops); +}; + +irqreturn_t spihid_apple_core_irq(int irq, void *data); + +int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops); +void spihid_apple_core_remove(struct spi_device *spi); +void spihid_apple_core_shutdown(struct spi_device *spi); + +#endif /* SPI_HID_APPLE_H */ diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index a18ab7358d8f..08e681473ae8 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -902,4 +902,16 @@ config INPUT_STPMIC1_ONKEY To compile this driver as a module, choose M here: the module will be called stpmic1_onkey. +config INPUT_MACSMC_HID + tristate "Apple Mac SMC lid/buttons" + depends on APPLE_SMC + default ARCH_APPLE + help + Say Y here if you want to use the input events delivered via the + SMC controller on Apple Mac machines using the macsmc driver. + This includes lid open/close and the power button. + + To compile this driver as a module, choose M here: the + module will be called macsmc-hid. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 28dfc444f0a9..a607c6472d0a 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +obj-$(CONFIG_INPUT_MACSMC_HID) += macsmc-hid.o obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o diff --git a/drivers/input/misc/macsmc-hid.c b/drivers/input/misc/macsmc-hid.c new file mode 100644 index 000000000000..c4c7440d4ee6 --- /dev/null +++ b/drivers/input/misc/macsmc-hid.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC input event driver + * Copyright The Asahi Linux Contributors + * + * This driver exposes HID events from the SMC as an input device. + * This includes the lid open/close and power button notifications. + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/reboot.h> + +struct macsmc_hid { + struct device *dev; + struct apple_smc *smc; + struct input_dev *input; + struct notifier_block nb; + bool wakeup_mode; +}; + +#define SMC_EV_BTN 0x7201 +#define SMC_EV_LID 0x7203 + +#define BTN_POWER 0x06 +#define BTN_POWER_HELD1 0xfe +#define BTN_POWER_HELD2 0x00 + +static int macsmc_hid_event(struct notifier_block *nb, unsigned long event, void *data) +{ + struct macsmc_hid *smchid = container_of(nb, struct macsmc_hid, nb); + u16 type = event >> 16; + u8 d1 = (event >> 8) & 0xff; + u8 d2 = event & 0xff; + + switch (type) { + case SMC_EV_BTN: + switch (d1) { + case BTN_POWER: + if (smchid->wakeup_mode) { + if (d2) { + dev_info(smchid->dev, "Button wakeup\n"); + pm_wakeup_hard_event(smchid->dev); + } + } else { + input_report_key(smchid->input, KEY_POWER, d2); + input_sync(smchid->input); + } + break; + case BTN_POWER_HELD1: + /* + * TODO: is this pre-warning useful? + */ + if (d2) + dev_warn(smchid->dev, "Power button held down\n"); + break; + case BTN_POWER_HELD2: + /* + * If we get here, we have about 4 seconds before forced shutdown. + * Try to do an emergency shutdown to make sure the NVMe cache is + * flushed. macOS actually does this by panicing (!)... + */ + if (d2) { + dev_crit(smchid->dev, "Triggering forced shutdown!\n"); + if (kernel_can_power_off()) + kernel_power_off(); + else /* Missing macsmc-reboot driver? */ + kernel_restart("SMC power button triggered restart"); + } + break; + default: + dev_info(smchid->dev, "Unknown SMC button event: %02x %02x\n", d1, d2); + break; + } + return NOTIFY_OK; + case SMC_EV_LID: + if (smchid->wakeup_mode && !d1) { + dev_info(smchid->dev, "Lid wakeup\n"); + pm_wakeup_hard_event(smchid->dev); + } + input_report_switch(smchid->input, SW_LID, d1); + input_sync(smchid->input); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int macsmc_hid_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_hid *smchid; + bool have_lid, have_power; + int ret; + + have_lid = apple_smc_key_exists(smc, SMC_KEY(MSLD)); + have_power = apple_smc_key_exists(smc, SMC_KEY(bHLD)); + + if (!have_lid && !have_power) + return -ENODEV; + + smchid = devm_kzalloc(&pdev->dev, sizeof(*smchid), GFP_KERNEL); + if (!smchid) + return -ENOMEM; + + smchid->dev = &pdev->dev; + smchid->smc = smc; + platform_set_drvdata(pdev, smchid); + + smchid->input = devm_input_allocate_device(&pdev->dev); + if (!smchid->input) + return -ENOMEM; + + smchid->input->phys = "macsmc-hid (0)"; + smchid->input->name = "Apple SMC power/lid events"; + + if (have_lid) + input_set_capability(smchid->input, EV_SW, SW_LID); + if (have_power) + input_set_capability(smchid->input, EV_KEY, KEY_POWER); + + ret = input_register_device(smchid->input); + if (ret) { + dev_err(&pdev->dev, "Failed to register input device: %d\n", ret); + return ret; + } + + if (have_lid) { + u8 val; + + ret = apple_smc_read_u8(smc, SMC_KEY(MSLD), &val); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read initial lid state\n"); + } else { + input_report_switch(smchid->input, SW_LID, val); + } + } + if (have_power) { + u32 val; + + ret = apple_smc_read_u32(smc, SMC_KEY(bHLD), &val); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read initial power button state\n"); + } else { + input_report_key(smchid->input, KEY_POWER, val & 1); + } + } + + input_sync(smchid->input); + + smchid->nb.notifier_call = macsmc_hid_event; + apple_smc_register_notifier(smc, &smchid->nb); + + device_init_wakeup(&pdev->dev, 1); + + return 0; +} + +static int macsmc_hid_pm_prepare(struct device *dev) +{ + struct macsmc_hid *smchid = dev_get_drvdata(dev); + + smchid->wakeup_mode = true; + return 0; +} + +static void macsmc_hid_pm_complete(struct device *dev) +{ + struct macsmc_hid *smchid = dev_get_drvdata(dev); + + smchid->wakeup_mode = false; +} + +static const struct dev_pm_ops macsmc_hid_pm_ops = { + .prepare = macsmc_hid_pm_prepare, + .complete = macsmc_hid_pm_complete, +}; + +static struct platform_driver macsmc_hid_driver = { + .driver = { + .name = "macsmc-hid", + .owner = THIS_MODULE, + .pm = &macsmc_hid_pm_ops, + }, + .probe = macsmc_hid_probe, +}; +module_platform_driver(macsmc_hid_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC GPIO driver"); +MODULE_ALIAS("platform:macsmc-hid"); diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index c79a0df090c0..58603124dd42 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -67,6 +67,15 @@ config IOMMU_IO_PGTABLE_ARMV7S_SELFTEST If unsure, say N here. +config IOMMU_IO_PGTABLE_DART + bool "Apple DART Formats" + select IOMMU_IO_PGTABLE + depends on ARM64 || (COMPILE_TEST && !GENERIC_ATOMIC64) + help + Enable support for the Apple DART pagetable formats. These include + the t8020 and t6000/t8110 DART formats used in Apple M1/M2 family + SoCs. + endmenu config IOMMU_DEBUGFS @@ -294,7 +303,7 @@ config APPLE_DART tristate "Apple DART IOMMU Support" depends on ARCH_APPLE || (COMPILE_TEST && !GENERIC_ATOMIC64) select IOMMU_API - select IOMMU_IO_PGTABLE_LPAE + select IOMMU_IO_PGTABLE_DART default ARCH_APPLE help Support for Apple DART (Device Address Resolution Table) IOMMUs diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 44475a9b3eea..cc9f381013c3 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_IOMMU_DMA) += dma-iommu.o obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o obj-$(CONFIG_IOMMU_IO_PGTABLE_ARMV7S) += io-pgtable-arm-v7s.o obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o +obj-$(CONFIG_IOMMU_IO_PGTABLE_DART) += io-pgtable-dart.o obj-$(CONFIG_IOASID) += ioasid.o obj-$(CONFIG_IOMMU_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 8af0242a90d9..249798e26e5d 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -33,58 +33,162 @@ #include <linux/swab.h> #include <linux/types.h> -#define DART_MAX_STREAMS 16 +#define DART_MAX_STREAMS 256 #define DART_MAX_TTBR 4 #define MAX_DARTS_PER_DEVICE 2 -#define DART_STREAM_ALL 0xffff +/* Common registers */ #define DART_PARAMS1 0x00 -#define DART_PARAMS_PAGE_SHIFT GENMASK(27, 24) +#define DART_PARAMS1_PAGE_SHIFT GENMASK(27, 24) #define DART_PARAMS2 0x04 -#define DART_PARAMS_BYPASS_SUPPORT BIT(0) +#define DART_PARAMS2_BYPASS_SUPPORT BIT(0) -#define DART_STREAM_COMMAND 0x20 -#define DART_STREAM_COMMAND_BUSY BIT(2) -#define DART_STREAM_COMMAND_INVALIDATE BIT(20) +/* T8020/T6000 registers */ -#define DART_STREAM_SELECT 0x34 +#define DART_T8020_STREAM_COMMAND 0x20 +#define DART_T8020_STREAM_COMMAND_BUSY BIT(2) +#define DART_T8020_STREAM_COMMAND_INVALIDATE BIT(20) -#define DART_ERROR 0x40 -#define DART_ERROR_STREAM GENMASK(27, 24) -#define DART_ERROR_CODE GENMASK(11, 0) -#define DART_ERROR_FLAG BIT(31) +#define DART_T8020_STREAM_SELECT 0x34 -#define DART_ERROR_READ_FAULT BIT(4) -#define DART_ERROR_WRITE_FAULT BIT(3) -#define DART_ERROR_NO_PTE BIT(2) -#define DART_ERROR_NO_PMD BIT(1) -#define DART_ERROR_NO_TTBR BIT(0) +#define DART_T8020_ERROR 0x40 +#define DART_T8020_ERROR_STREAM GENMASK(27, 24) +#define DART_T8020_ERROR_CODE GENMASK(11, 0) +#define DART_T8020_ERROR_FLAG BIT(31) -#define DART_CONFIG 0x60 -#define DART_CONFIG_LOCK BIT(15) +#define DART_T8020_ERROR_READ_FAULT BIT(4) +#define DART_T8020_ERROR_WRITE_FAULT BIT(3) +#define DART_T8020_ERROR_NO_PTE BIT(2) +#define DART_T8020_ERROR_NO_PMD BIT(1) +#define DART_T8020_ERROR_NO_TTBR BIT(0) + +#define DART_T8020_CONFIG 0x60 +#define DART_T8020_CONFIG_LOCK BIT(15) #define DART_STREAM_COMMAND_BUSY_TIMEOUT 100 -#define DART_ERROR_ADDR_HI 0x54 -#define DART_ERROR_ADDR_LO 0x50 +#define DART_T8020_ERROR_ADDR_HI 0x54 +#define DART_T8020_ERROR_ADDR_LO 0x50 + +#define DART_T8020_STREAMS_ENABLE 0xfc + +#define DART_T8020_TCR 0x100 +#define DART_T8020_TCR_TRANSLATE_ENABLE BIT(7) +#define DART_T8020_TCR_BYPASS_DART BIT(8) +#define DART_T8020_TCR_BYPASS_DAPF BIT(12) + +#define DART_T8020_TTBR 0x200 +#define DART_T8020_TTBR_VALID BIT(31) +#define DART_T8020_TTBR_ADDR_OFF 0 +#define DART_T8020_TTBR_SHIFT 12 + +/* T8110 registers */ + +#define DART_T8110_PARAMS3 0x08 +#define DART_T8110_PARAMS3_PA_WIDTH GENMASK(29, 24) +#define DART_T8110_PARAMS3_VA_WIDTH GENMASK(21, 16) +#define DART_T8110_PARAMS3_VER_MAJ GENMASK(15, 8) +#define DART_T8110_PARAMS3_VER_MIN GENMASK(7, 0) + +#define DART_T8110_PARAMS4 0x0c +#define DART_T8110_PARAMS4_NUM_CLIENTS GENMASK(24, 16) +#define DART_T8110_PARAMS4_NUM_SIDS GENMASK(8, 0) + +#define DART_T8110_TLB_CMD 0x80 +#define DART_T8110_TLB_CMD_BUSY BIT(31) +#define DART_T8110_TLB_CMD_OP GENMASK(10, 8) +#define DART_T8110_TLB_CMD_OP_FLUSH_ALL 0 +#define DART_T8110_TLB_CMD_OP_FLUSH_SID 1 +#define DART_T8110_TLB_CMD_STREAM GENMASK(7, 0) + +#define DART_T8110_ERROR 0x100 +#define DART_T8110_ERROR_STREAM GENMASK(27, 20) +#define DART_T8110_ERROR_CODE GENMASK(14, 0) +#define DART_T8110_ERROR_FLAG BIT(31) + +#define DART_T8110_ERROR_MASK 0x104 + +#define DART_T8110_ERROR_READ_FAULT BIT(4) +#define DART_T8110_ERROR_WRITE_FAULT BIT(3) +#define DART_T8110_ERROR_NO_PTE BIT(3) +#define DART_T8110_ERROR_NO_PMD BIT(2) +#define DART_T8110_ERROR_NO_PGD BIT(1) +#define DART_T8110_ERROR_NO_TTBR BIT(0) + +#define DART_T8110_ERROR_ADDR_LO 0x170 +#define DART_T8110_ERROR_ADDR_HI 0x174 + +#define DART_T8110_PROTECT 0x200 +#define DART_T8110_UNPROTECT 0x204 +#define DART_T8110_PROTECT_LOCK 0x208 +#define DART_T8110_PROTECT_TTBR_TCR BIT(0) + +#define DART_T8110_ENABLE_STREAMS 0xc00 +#define DART_T8110_DISABLE_STREAMS 0xc20 + +#define DART_T8110_TCR 0x1000 +#define DART_T8110_TCR_REMAP GENMASK(11, 8) +#define DART_T8110_TCR_REMAP_EN BIT(7) +#define DART_T8110_TCR_BYPASS_DAPF BIT(2) +#define DART_T8110_TCR_BYPASS_DART BIT(1) +#define DART_T8110_TCR_TRANSLATE_ENABLE BIT(0) + +#define DART_T8110_TTBR 0x1400 +#define DART_T8110_TTBR_VALID BIT(0) +#define DART_T8110_TTBR_ADDR_OFF 2 +#define DART_T8110_TTBR_SHIFT 14 + +#define DART_TCR(dart, sid) ((dart)->hw->tcr + ((sid) << 2)) + +#define DART_TTBR(dart, sid, idx) ((dart)->hw->ttbr + \ + (((dart)->hw->ttbr_count * (sid)) << 2) + \ + ((idx) << 2)) + +struct apple_dart_stream_map; + +enum dart_type { + DART_T8020, + DART_T6000, + DART_T8110, +}; + +struct apple_dart_hw { + enum dart_type type; + irqreturn_t (*irq_handler)(int irq, void *dev); + int (*invalidate_tlb)(struct apple_dart_stream_map *stream_map); + + u32 oas; + enum io_pgtable_fmt fmt; -#define DART_STREAMS_ENABLE 0xfc + int max_sid_count; -#define DART_TCR(sid) (0x100 + 4 * (sid)) -#define DART_TCR_TRANSLATE_ENABLE BIT(7) -#define DART_TCR_BYPASS0_ENABLE BIT(8) -#define DART_TCR_BYPASS1_ENABLE BIT(12) + u64 lock; + u64 lock_bit; -#define DART_TTBR(sid, idx) (0x200 + 16 * (sid) + 4 * (idx)) -#define DART_TTBR_VALID BIT(31) -#define DART_TTBR_SHIFT 12 + u64 error; + + u64 enable_streams; + u64 disable_streams; + + u64 tcr; + u64 tcr_enabled; + u64 tcr_disabled; + u64 tcr_bypass; + + u64 ttbr; + u64 ttbr_valid; + u64 ttbr_addr_off; + u64 ttbr_shift; + int ttbr_count; +}; /* * Private structure associated with each DART device. * * @dev: device struct + * @hw: SoC-specific hardware data * @regs: mapped MMIO region * @irq: interrupt number, can be shared with other DARTs * @clks: clocks associated with this DART @@ -98,6 +202,7 @@ */ struct apple_dart { struct device *dev; + const struct apple_dart_hw *hw; void __iomem *regs; @@ -107,12 +212,17 @@ struct apple_dart { spinlock_t lock; + u32 oas; u32 pgsize; + u32 num_streams; u32 supports_bypass : 1; u32 force_bypass : 1; struct iommu_group *sid2group[DART_MAX_STREAMS]; struct iommu_device iommu; + + u32 save_tcr[DART_MAX_STREAMS]; + u32 save_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR]; }; /* @@ -132,11 +242,11 @@ struct apple_dart { */ struct apple_dart_stream_map { struct apple_dart *dart; - unsigned long sidmap; + DECLARE_BITMAP(sidmap, DART_MAX_STREAMS); }; struct apple_dart_atomic_stream_map { struct apple_dart *dart; - atomic64_t sidmap; + atomic_long_t sidmap[BITS_TO_LONGS(DART_MAX_STREAMS)]; }; /* @@ -194,50 +304,55 @@ static struct apple_dart_domain *to_dart_domain(struct iommu_domain *dom) static void apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map) { + struct apple_dart *dart = stream_map->dart; int sid; - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) - writel(DART_TCR_TRANSLATE_ENABLE, - stream_map->dart->regs + DART_TCR(sid)); + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) + writel(dart->hw->tcr_enabled, dart->regs + DART_TCR(dart, sid)); } static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map) { + struct apple_dart *dart = stream_map->dart; int sid; - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) - writel(0, stream_map->dart->regs + DART_TCR(sid)); + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) + writel(dart->hw->tcr_disabled, dart->regs + DART_TCR(dart, sid)); } static void apple_dart_hw_enable_bypass(struct apple_dart_stream_map *stream_map) { + struct apple_dart *dart = stream_map->dart; int sid; WARN_ON(!stream_map->dart->supports_bypass); - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) - writel(DART_TCR_BYPASS0_ENABLE | DART_TCR_BYPASS1_ENABLE, - stream_map->dart->regs + DART_TCR(sid)); + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) + writel(dart->hw->tcr_bypass, + dart->regs + DART_TCR(dart, sid)); } static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map, u8 idx, phys_addr_t paddr) { + struct apple_dart *dart = stream_map->dart; int sid; - WARN_ON(paddr & ((1 << DART_TTBR_SHIFT) - 1)); - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) - writel(DART_TTBR_VALID | (paddr >> DART_TTBR_SHIFT), - stream_map->dart->regs + DART_TTBR(sid, idx)); + WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1)); + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) + writel(dart->hw->ttbr_valid | + (paddr >> dart->hw->ttbr_shift) << dart->hw->ttbr_addr_off, + dart->regs + DART_TTBR(dart, sid, idx)); } static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map, u8 idx) { + struct apple_dart *dart = stream_map->dart; int sid; - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) - writel(0, stream_map->dart->regs + DART_TTBR(sid, idx)); + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) + writel(0, dart->regs + DART_TTBR(dart, sid, idx)); } static void @@ -245,12 +360,12 @@ apple_dart_hw_clear_all_ttbrs(struct apple_dart_stream_map *stream_map) { int i; - for (i = 0; i < DART_MAX_TTBR; ++i) + for (i = 0; i < stream_map->dart->hw->ttbr_count; ++i) apple_dart_hw_clear_ttbr(stream_map, i); } static int -apple_dart_hw_stream_command(struct apple_dart_stream_map *stream_map, +apple_dart_t8020_hw_stream_command(struct apple_dart_stream_map *stream_map, u32 command) { unsigned long flags; @@ -259,12 +374,12 @@ apple_dart_hw_stream_command(struct apple_dart_stream_map *stream_map, spin_lock_irqsave(&stream_map->dart->lock, flags); - writel(stream_map->sidmap, stream_map->dart->regs + DART_STREAM_SELECT); - writel(command, stream_map->dart->regs + DART_STREAM_COMMAND); + writel(stream_map->sidmap[0], stream_map->dart->regs + DART_T8020_STREAM_SELECT); + writel(command, stream_map->dart->regs + DART_T8020_STREAM_COMMAND); ret = readl_poll_timeout_atomic( - stream_map->dart->regs + DART_STREAM_COMMAND, command_reg, - !(command_reg & DART_STREAM_COMMAND_BUSY), 1, + stream_map->dart->regs + DART_T8020_STREAM_COMMAND, command_reg, + !(command_reg & DART_T8020_STREAM_COMMAND_BUSY), 1, DART_STREAM_COMMAND_BUSY_TIMEOUT); spin_unlock_irqrestore(&stream_map->dart->lock, flags); @@ -272,7 +387,7 @@ apple_dart_hw_stream_command(struct apple_dart_stream_map *stream_map, if (ret) { dev_err(stream_map->dart->dev, "busy bit did not clear after command %x for streams %lx\n", - command, stream_map->sidmap); + command, stream_map->sidmap[0]); return ret; } @@ -280,48 +395,102 @@ apple_dart_hw_stream_command(struct apple_dart_stream_map *stream_map, } static int -apple_dart_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map) +apple_dart_t8110_hw_tlb_command(struct apple_dart_stream_map *stream_map, + u32 command) { - return apple_dart_hw_stream_command(stream_map, - DART_STREAM_COMMAND_INVALIDATE); + struct apple_dart *dart = stream_map->dart; + unsigned long flags; + int ret = 0; + int sid; + + spin_lock_irqsave(&dart->lock, flags); + + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { + u32 val = FIELD_PREP(DART_T8110_TLB_CMD_OP, command) | + FIELD_PREP(DART_T8110_TLB_CMD_STREAM, sid); + writel(val, dart->regs + DART_T8110_TLB_CMD); + + ret = readl_poll_timeout_atomic( + dart->regs + DART_T8110_TLB_CMD, val, + !(val & DART_T8110_TLB_CMD_BUSY), 1, + DART_STREAM_COMMAND_BUSY_TIMEOUT); + + if (ret) + break; + + } + + spin_unlock_irqrestore(&dart->lock, flags); + + if (ret) { + dev_err(stream_map->dart->dev, + "busy bit did not clear after command %x for stream %d\n", + command, sid); + return ret; + } + + return 0; +} + +static int +apple_dart_t8020_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map) +{ + return apple_dart_t8020_hw_stream_command( + stream_map, DART_T8020_STREAM_COMMAND_INVALIDATE); +} + +static int +apple_dart_t8110_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map) +{ + return apple_dart_t8110_hw_tlb_command( + stream_map, DART_T8110_TLB_CMD_OP_FLUSH_SID); } static int apple_dart_hw_reset(struct apple_dart *dart) { u32 config; struct apple_dart_stream_map stream_map; + int i; - config = readl(dart->regs + DART_CONFIG); - if (config & DART_CONFIG_LOCK) { + config = readl(dart->regs + dart->hw->lock); + if (config & dart->hw->lock_bit) { dev_err(dart->dev, "DART is locked down until reboot: %08x\n", config); return -EINVAL; } stream_map.dart = dart; - stream_map.sidmap = DART_STREAM_ALL; + bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); + bitmap_set(stream_map.sidmap, 0, dart->num_streams); apple_dart_hw_disable_dma(&stream_map); apple_dart_hw_clear_all_ttbrs(&stream_map); /* enable all streams globally since TCR is used to control isolation */ - writel(DART_STREAM_ALL, dart->regs + DART_STREAMS_ENABLE); + for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) + writel(U32_MAX, dart->regs + dart->hw->enable_streams); /* clear any pending errors before the interrupt is unmasked */ - writel(readl(dart->regs + DART_ERROR), dart->regs + DART_ERROR); + writel(readl(dart->regs + dart->hw->error), dart->regs + dart->hw->error); + + if (dart->hw->type == DART_T8110) + writel(0, dart->regs + DART_T8110_ERROR_MASK); - return apple_dart_hw_invalidate_tlb(&stream_map); + return dart->hw->invalidate_tlb(&stream_map); } static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) { - int i; + int i, j; struct apple_dart_atomic_stream_map *domain_stream_map; struct apple_dart_stream_map stream_map; for_each_stream_map(i, domain, domain_stream_map) { stream_map.dart = domain_stream_map->dart; - stream_map.sidmap = atomic64_read(&domain_stream_map->sidmap); - apple_dart_hw_invalidate_tlb(&stream_map); + + for (j = 0; j < BITS_TO_LONGS(stream_map.dart->num_streams); j++) + stream_map.sidmap[j] = atomic64_read(&domain_stream_map->sidmap[j]); + + stream_map.dart->hw->invalidate_tlb(&stream_map); } } @@ -391,11 +560,11 @@ apple_dart_setup_translation(struct apple_dart_domain *domain, for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) apple_dart_hw_set_ttbr(stream_map, i, pgtbl_cfg->apple_dart_cfg.ttbr[i]); - for (; i < DART_MAX_TTBR; ++i) + for (; i < stream_map->dart->hw->ttbr_count; ++i) apple_dart_hw_clear_ttbr(stream_map, i); apple_dart_hw_enable_translation(stream_map); - apple_dart_hw_invalidate_tlb(stream_map); + stream_map->dart->hw->invalidate_tlb(stream_map); } static int apple_dart_finalize_domain(struct iommu_domain *domain, @@ -405,7 +574,7 @@ static int apple_dart_finalize_domain(struct iommu_domain *domain, struct apple_dart *dart = cfg->stream_maps[0].dart; struct io_pgtable_cfg pgtbl_cfg; int ret = 0; - int i; + int i, j; mutex_lock(&dart_domain->init_lock); @@ -414,20 +583,21 @@ static int apple_dart_finalize_domain(struct iommu_domain *domain, for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) { dart_domain->stream_maps[i].dart = cfg->stream_maps[i].dart; - atomic64_set(&dart_domain->stream_maps[i].sidmap, - cfg->stream_maps[i].sidmap); + for (j = 0; j < BITS_TO_LONGS(dart->num_streams); j++) + atomic64_set(&dart_domain->stream_maps[i].sidmap[j], + cfg->stream_maps[i].sidmap[j]); } pgtbl_cfg = (struct io_pgtable_cfg){ .pgsize_bitmap = dart->pgsize, .ias = 32, - .oas = 36, + .oas = dart->oas, .coherent_walk = 1, .iommu_dev = dart->dev, }; dart_domain->pgtbl_ops = - alloc_io_pgtable_ops(APPLE_DART, &pgtbl_cfg, domain); + alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg, domain); if (!dart_domain->pgtbl_ops) { ret = -ENOMEM; goto done; @@ -450,7 +620,7 @@ apple_dart_mod_streams(struct apple_dart_atomic_stream_map *domain_maps, struct apple_dart_stream_map *master_maps, bool add_streams) { - int i; + int i, j; for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) { if (domain_maps[i].dart != master_maps[i].dart) @@ -460,12 +630,14 @@ apple_dart_mod_streams(struct apple_dart_atomic_stream_map *domain_maps, for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) { if (!domain_maps[i].dart) break; - if (add_streams) - atomic64_or(master_maps[i].sidmap, - &domain_maps[i].sidmap); - else - atomic64_and(~master_maps[i].sidmap, - &domain_maps[i].sidmap); + for (j = 0; j < BITS_TO_LONGS(domain_maps[i].dart->num_streams); j++) { + if (add_streams) + atomic64_or(master_maps[i].sidmap[j], + &domain_maps[i].sidmap[j]); + else + atomic64_and(~master_maps[i].sidmap[j], + &domain_maps[i].sidmap[j]); + } } return 0; @@ -632,14 +804,14 @@ static int apple_dart_of_xlate(struct device *dev, struct of_phandle_args *args) for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) { if (cfg->stream_maps[i].dart == dart) { - cfg->stream_maps[i].sidmap |= 1 << sid; + set_bit(sid, cfg->stream_maps[i].sidmap); return 0; } } for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) { if (!cfg->stream_maps[i].dart) { cfg->stream_maps[i].dart = dart; - cfg->stream_maps[i].sidmap = 1 << sid; + set_bit(sid, cfg->stream_maps[i].sidmap); return 0; } } @@ -658,7 +830,7 @@ static void apple_dart_release_group(void *iommu_data) mutex_lock(&apple_dart_groups_lock); for_each_stream_map(i, group_master_cfg, stream_map) - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) + for_each_set_bit(sid, stream_map->sidmap, stream_map->dart->num_streams) stream_map->dart->sid2group[sid] = NULL; kfree(iommu_data); @@ -677,7 +849,7 @@ static struct iommu_group *apple_dart_device_group(struct device *dev) mutex_lock(&apple_dart_groups_lock); for_each_stream_map(i, cfg, stream_map) { - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) { + for_each_set_bit(sid, stream_map->sidmap, stream_map->dart->num_streams) { struct iommu_group *stream_group = stream_map->dart->sid2group[sid]; @@ -716,7 +888,7 @@ static struct iommu_group *apple_dart_device_group(struct device *dev) apple_dart_release_group); for_each_stream_map(i, cfg, stream_map) - for_each_set_bit(sid, &stream_map->sidmap, DART_MAX_STREAMS) + for_each_set_bit(sid, stream_map->sidmap, stream_map->dart->num_streams) stream_map->dart->sid2group[sid] = group; res = group; @@ -787,30 +959,30 @@ static const struct iommu_ops apple_dart_iommu_ops = { } }; -static irqreturn_t apple_dart_irq(int irq, void *dev) +static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) { struct apple_dart *dart = dev; const char *fault_name = NULL; - u32 error = readl(dart->regs + DART_ERROR); - u32 error_code = FIELD_GET(DART_ERROR_CODE, error); - u32 addr_lo = readl(dart->regs + DART_ERROR_ADDR_LO); - u32 addr_hi = readl(dart->regs + DART_ERROR_ADDR_HI); + u32 error = readl(dart->regs + DART_T8020_ERROR); + u32 error_code = FIELD_GET(DART_T8020_ERROR_CODE, error); + u32 addr_lo = readl(dart->regs + DART_T8020_ERROR_ADDR_LO); + u32 addr_hi = readl(dart->regs + DART_T8020_ERROR_ADDR_HI); u64 addr = addr_lo | (((u64)addr_hi) << 32); - u8 stream_idx = FIELD_GET(DART_ERROR_STREAM, error); + u8 stream_idx = FIELD_GET(DART_T8020_ERROR_STREAM, error); - if (!(error & DART_ERROR_FLAG)) + if (!(error & DART_T8020_ERROR_FLAG)) return IRQ_NONE; /* there should only be a single bit set but let's use == to be sure */ - if (error_code == DART_ERROR_READ_FAULT) + if (error_code == DART_T8020_ERROR_READ_FAULT) fault_name = "READ FAULT"; - else if (error_code == DART_ERROR_WRITE_FAULT) + else if (error_code == DART_T8020_ERROR_WRITE_FAULT) fault_name = "WRITE FAULT"; - else if (error_code == DART_ERROR_NO_PTE) + else if (error_code == DART_T8020_ERROR_NO_PTE) fault_name = "NO PTE FOR IOVA"; - else if (error_code == DART_ERROR_NO_PMD) + else if (error_code == DART_T8020_ERROR_NO_PMD) fault_name = "NO PMD FOR IOVA"; - else if (error_code == DART_ERROR_NO_TTBR) + else if (error_code == DART_T8020_ERROR_NO_TTBR) fault_name = "NO TTBR FOR IOVA"; else fault_name = "unknown"; @@ -820,7 +992,47 @@ static irqreturn_t apple_dart_irq(int irq, void *dev) "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", error, stream_idx, error_code, fault_name, addr); - writel(error, dart->regs + DART_ERROR); + writel(error, dart->regs + DART_T8020_ERROR); + return IRQ_HANDLED; +} + + +static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) +{ + struct apple_dart *dart = dev; + const char *fault_name = NULL; + u32 error = readl(dart->regs + DART_T8110_ERROR); + u32 error_code = FIELD_GET(DART_T8110_ERROR_CODE, error); + u32 addr_lo = readl(dart->regs + DART_T8110_ERROR_ADDR_LO); + u32 addr_hi = readl(dart->regs + DART_T8110_ERROR_ADDR_HI); + u64 addr = addr_lo | (((u64)addr_hi) << 32); + u8 stream_idx = FIELD_GET(DART_T8110_ERROR_STREAM, error); + + if (!(error & DART_T8110_ERROR_FLAG)) + return IRQ_NONE; + + /* there should only be a single bit set but let's use == to be sure */ + if (error_code == DART_T8110_ERROR_READ_FAULT) + fault_name = "READ FAULT"; + else if (error_code == DART_T8110_ERROR_WRITE_FAULT) + fault_name = "WRITE FAULT"; + else if (error_code == DART_T8110_ERROR_NO_PTE) + fault_name = "NO PTE FOR IOVA"; + else if (error_code == DART_T8110_ERROR_NO_PMD) + fault_name = "NO PMD FOR IOVA"; + else if (error_code == DART_T8110_ERROR_NO_PGD) + fault_name = "NO PGD FOR IOVA"; + else if (error_code == DART_T8110_ERROR_NO_TTBR) + fault_name = "NO TTBR FOR IOVA"; + else + fault_name = "unknown"; + + dev_err_ratelimited( + dart->dev, + "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", + error, stream_idx, error_code, fault_name, addr); + + writel(error, dart->regs + DART_T8110_ERROR); return IRQ_HANDLED; } @@ -848,7 +1060,7 @@ static int apple_dart_set_bus_ops(const struct iommu_ops *ops) static int apple_dart_probe(struct platform_device *pdev) { int ret; - u32 dart_params[2]; + u32 dart_params[4]; struct resource *res; struct apple_dart *dart; struct device *dev = &pdev->dev; @@ -858,6 +1070,7 @@ static int apple_dart_probe(struct platform_device *pdev) return -ENOMEM; dart->dev = dev; + dart->hw = of_device_get_match_data(dev); spin_lock_init(&dart->lock); dart->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); @@ -882,17 +1095,39 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) return ret; - ret = apple_dart_hw_reset(dart); - if (ret) - goto err_clk_disable; - dart_params[0] = readl(dart->regs + DART_PARAMS1); dart_params[1] = readl(dart->regs + DART_PARAMS2); - dart->pgsize = 1 << FIELD_GET(DART_PARAMS_PAGE_SHIFT, dart_params[0]); - dart->supports_bypass = dart_params[1] & DART_PARAMS_BYPASS_SUPPORT; + dart->pgsize = 1 << FIELD_GET(DART_PARAMS1_PAGE_SHIFT, dart_params[0]); + dart->supports_bypass = dart_params[1] & DART_PARAMS2_BYPASS_SUPPORT; + + switch (dart->hw->type) { + case DART_T8020: + case DART_T6000: + dart->oas = dart->hw->oas; + dart->num_streams = dart->hw->max_sid_count; + break; + + case DART_T8110: + dart_params[2] = readl(dart->regs + DART_T8110_PARAMS3); + dart_params[3] = readl(dart->regs + DART_T8110_PARAMS4); + dart->oas = FIELD_GET(DART_T8110_PARAMS3_PA_WIDTH, dart_params[2]); + dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, dart_params[3]); + break; + } + + if (dart->num_streams > DART_MAX_STREAMS) { + dev_err(&pdev->dev, "Too many streams (%d > %d)\n", + dart->num_streams, DART_MAX_STREAMS); + goto err_clk_disable; + } + dart->force_bypass = dart->pgsize > PAGE_SIZE; - ret = request_irq(dart->irq, apple_dart_irq, IRQF_SHARED, + ret = apple_dart_hw_reset(dart); + if (ret) + goto err_clk_disable; + + ret = request_irq(dart->irq, dart->hw->irq_handler, IRQF_SHARED, "apple-dart fault handler", dart); if (ret) goto err_clk_disable; @@ -914,8 +1149,8 @@ static int apple_dart_probe(struct platform_device *pdev) dev_info( &pdev->dev, - "DART [pagesize %x, bypass support: %d, bypass forced: %d] initialized\n", - dart->pgsize, dart->supports_bypass, dart->force_bypass); + "DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d] initialized\n", + dart->pgsize, dart->num_streams, dart->supports_bypass, dart->force_bypass); return 0; err_sysfs_remove: @@ -946,8 +1181,131 @@ static int apple_dart_remove(struct platform_device *pdev) return 0; } +static const struct apple_dart_hw apple_dart_hw_t8103 = { + .type = DART_T8020, + .irq_handler = apple_dart_t8020_irq, + .invalidate_tlb = apple_dart_t8020_hw_invalidate_tlb, + .oas = 36, + .fmt = APPLE_DART, + .max_sid_count = 16, + + .enable_streams = DART_T8020_STREAMS_ENABLE, + .lock = DART_T8020_CONFIG, + .lock_bit = DART_T8020_CONFIG_LOCK, + + .error = DART_T8020_ERROR, + + .tcr = DART_T8020_TCR, + .tcr_enabled = DART_T8020_TCR_TRANSLATE_ENABLE, + .tcr_disabled = 0, + .tcr_bypass = DART_T8020_TCR_BYPASS_DAPF | DART_T8020_TCR_BYPASS_DART, + + .ttbr = DART_T8020_TTBR, + .ttbr_valid = DART_T8020_TTBR_VALID, + .ttbr_addr_off = DART_T8020_TTBR_ADDR_OFF, + .ttbr_shift = DART_T8020_TTBR_SHIFT, + .ttbr_count = 4, +}; +static const struct apple_dart_hw apple_dart_hw_t6000 = { + .type = DART_T6000, + .irq_handler = apple_dart_t8020_irq, + .invalidate_tlb = apple_dart_t8020_hw_invalidate_tlb, + .oas = 42, + .fmt = APPLE_DART2, + .max_sid_count = 16, + + .enable_streams = DART_T8020_STREAMS_ENABLE, + .lock = DART_T8020_CONFIG, + .lock_bit = DART_T8020_CONFIG_LOCK, + + .error = DART_T8020_ERROR, + + .tcr = DART_T8020_TCR, + .tcr_enabled = DART_T8020_TCR_TRANSLATE_ENABLE, + .tcr_disabled = 0, + .tcr_bypass = DART_T8020_TCR_BYPASS_DAPF | DART_T8020_TCR_BYPASS_DART, + + .ttbr = DART_T8020_TTBR, + .ttbr_valid = DART_T8020_TTBR_VALID, + .ttbr_addr_off = DART_T8020_TTBR_ADDR_OFF, + .ttbr_shift = DART_T8020_TTBR_SHIFT, + .ttbr_count = 4, +}; + +static const struct apple_dart_hw apple_dart_hw_t8110 = { + .type = DART_T8110, + .irq_handler = apple_dart_t8110_irq, + .invalidate_tlb = apple_dart_t8110_hw_invalidate_tlb, + .fmt = APPLE_DART2, + .max_sid_count = 256, + + .enable_streams = DART_T8110_ENABLE_STREAMS, + .disable_streams = DART_T8110_DISABLE_STREAMS, + .lock = DART_T8110_PROTECT, + .lock_bit = DART_T8110_PROTECT_TTBR_TCR, + + .error = DART_T8110_ERROR, + + .tcr = DART_T8110_TCR, + .tcr_enabled = DART_T8110_TCR_TRANSLATE_ENABLE, + .tcr_disabled = 0, + .tcr_bypass = DART_T8110_TCR_BYPASS_DAPF | DART_T8110_TCR_BYPASS_DART, + + .ttbr = DART_T8110_TTBR, + .ttbr_valid = DART_T8110_TTBR_VALID, + .ttbr_addr_off = DART_T8110_TTBR_ADDR_OFF, + .ttbr_shift = DART_T8110_TTBR_SHIFT, + .ttbr_count = 1, +}; + +#ifdef CONFIG_PM_SLEEP +static int apple_dart_suspend(struct device *dev) +{ + struct apple_dart *dart = dev_get_drvdata(dev); + unsigned int sid, idx; + + for (sid = 0; sid < dart->num_streams; sid++) { + dart->save_tcr[sid] = readl_relaxed(dart->regs + DART_TCR(dart, sid)); + for (idx = 0; idx < dart->hw->ttbr_count; idx++) + dart->save_ttbr[sid][idx] = + readl_relaxed(dart->regs + DART_TTBR(dart, sid, idx)); + } + + return 0; +} + +static int apple_dart_resume(struct device *dev) +{ + struct apple_dart *dart = dev_get_drvdata(dev); + unsigned int sid, idx; + int ret; + + ret = apple_dart_hw_reset(dart); + if (ret) { + dev_err(dev, "Failed to reset DART on resume\n"); + return ret; + } + + for (sid = 0; sid < dart->num_streams; sid++) { + for (idx = 0; idx < dart->hw->ttbr_count; idx++) + writel_relaxed(dart->save_ttbr[sid][idx], + dart->regs + DART_TTBR(dart, sid, idx)); + writel_relaxed(dart->save_tcr[sid], dart->regs + DART_TCR(dart, sid)); + } + + return 0; +} + +static const struct dev_pm_ops apple_dart_pm_ops = { + .suspend = apple_dart_suspend, + .resume = apple_dart_resume, +}; +#endif + static const struct of_device_id apple_dart_of_match[] = { - { .compatible = "apple,t8103-dart", .data = NULL }, + { .compatible = "apple,t8110-dart", .data = &apple_dart_hw_t8110 }, + { .compatible = "apple,t8103-dart", .data = &apple_dart_hw_t8103 }, + { .compatible = "apple,t6000-dart", .data = &apple_dart_hw_t6000 }, {}, }; MODULE_DEVICE_TABLE(of, apple_dart_of_match); @@ -957,6 +1315,9 @@ static struct platform_driver apple_dart_driver = { .name = "apple-dart", .of_match_table = apple_dart_of_match, .suppress_bind_attrs = true, +#ifdef CONFIG_PM_SLEEP + .pm = &apple_dart_pm_ops, +#endif }, .probe = apple_dart_probe, .remove = apple_dart_remove, diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c index 94ff319ae8ac..d7f5e23da643 100644 --- a/drivers/iommu/io-pgtable-arm.c +++ b/drivers/iommu/io-pgtable-arm.c @@ -130,9 +130,6 @@ #define ARM_MALI_LPAE_MEMATTR_IMP_DEF 0x88ULL #define ARM_MALI_LPAE_MEMATTR_WRITE_ALLOC 0x8DULL -#define APPLE_DART_PTE_PROT_NO_WRITE (1<<7) -#define APPLE_DART_PTE_PROT_NO_READ (1<<8) - /* IOPTE accessors */ #define iopte_deref(pte,d) __va(iopte_to_paddr(pte, d)) @@ -406,15 +403,6 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, { arm_lpae_iopte pte; - if (data->iop.fmt == APPLE_DART) { - pte = 0; - if (!(prot & IOMMU_WRITE)) - pte |= APPLE_DART_PTE_PROT_NO_WRITE; - if (!(prot & IOMMU_READ)) - pte |= APPLE_DART_PTE_PROT_NO_READ; - return pte; - } - if (data->iop.fmt == ARM_64_LPAE_S1 || data->iop.fmt == ARM_32_LPAE_S1) { pte = ARM_LPAE_PTE_nG; @@ -1107,52 +1095,6 @@ out_free_data: return NULL; } -static struct io_pgtable * -apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) -{ - struct arm_lpae_io_pgtable *data; - int i; - - if (cfg->oas > 36) - return NULL; - - data = arm_lpae_alloc_pgtable(cfg); - if (!data) - return NULL; - - /* - * The table format itself always uses two levels, but the total VA - * space is mapped by four separate tables, making the MMIO registers - * an effective "level 1". For simplicity, though, we treat this - * equivalently to LPAE stage 2 concatenation at level 2, with the - * additional TTBRs each just pointing at consecutive pages. - */ - if (data->start_level < 1) - goto out_free_data; - if (data->start_level == 1 && data->pgd_bits > 2) - goto out_free_data; - if (data->start_level > 1) - data->pgd_bits = 0; - data->start_level = 2; - cfg->apple_dart_cfg.n_ttbrs = 1 << data->pgd_bits; - data->pgd_bits += data->bits_per_level; - - data->pgd = __arm_lpae_alloc_pages(ARM_LPAE_PGD_SIZE(data), GFP_KERNEL, - cfg); - if (!data->pgd) - goto out_free_data; - - for (i = 0; i < cfg->apple_dart_cfg.n_ttbrs; ++i) - cfg->apple_dart_cfg.ttbr[i] = - virt_to_phys(data->pgd + i * ARM_LPAE_GRANULE(data)); - - return &data->iop; - -out_free_data: - kfree(data); - return NULL; -} - struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = { .alloc = arm_64_lpae_alloc_pgtable_s1, .free = arm_lpae_free_pgtable, @@ -1178,11 +1120,6 @@ struct io_pgtable_init_fns io_pgtable_arm_mali_lpae_init_fns = { .free = arm_lpae_free_pgtable, }; -struct io_pgtable_init_fns io_pgtable_apple_dart_init_fns = { - .alloc = apple_dart_alloc_pgtable, - .free = arm_lpae_free_pgtable, -}; - #ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST static struct io_pgtable_cfg *cfg_cookie __initdata; diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c new file mode 100644 index 000000000000..9c3c2505f3dc --- /dev/null +++ b/drivers/iommu/io-pgtable-dart.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple DART page table allocator. + * + * Copyright (C) 2022 The Asahi Linux Contributors + * + * Based on io-pgtable-arm. + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon <will.deacon@arm.com> + */ + +#define pr_fmt(fmt) "dart io-pgtable: " fmt + +#include <linux/atomic.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/io-pgtable.h> +#include <linux/kernel.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <asm/barrier.h> + +#define DART_MAX_ADDR_BITS 52 +#define DART_MAX_LEVELS 3 + +/* Struct accessors */ +#define io_pgtable_to_data(x) \ + container_of((x), struct dart_io_pgtable, iop) + +#define io_pgtable_ops_to_data(x) \ + io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) + +/* + * Calculate the right shift amount to get to the portion describing level l + * in a virtual address mapped by the pagetable in d. + */ +#define DART_LVL_SHIFT(l, d) \ + (((DART_MAX_LEVELS - (l)) * (d)->bits_per_level) + \ + ilog2(sizeof(dart_iopte))) + +#define DART_GRANULE(d) \ + (sizeof(dart_iopte) << (d)->bits_per_level) +#define DART_PGD_SIZE(d) \ + (sizeof(dart_iopte) << (d)->pgd_bits) + +#define DART_PTES_PER_TABLE(d) \ + (DART_GRANULE(d) >> ilog2(sizeof(dart_iopte))) + +/* + * Calculate the index at level l used to map virtual address a using the + * pagetable in d. + */ +#define DART_PGD_IDX(l, d) \ + ((l) == (d)->start_level ? (d)->pgd_bits - (d)->bits_per_level : 0) + +#define DART_LVL_IDX(a, l, d) \ + (((u64)(a) >> DART_LVL_SHIFT(l, d)) & \ + ((1 << ((d)->bits_per_level + DART_PGD_IDX(l, d))) - 1)) + +/* Calculate the block/page mapping size at level l for pagetable in d. */ +#define DART_BLOCK_SIZE(l, d) (1ULL << DART_LVL_SHIFT(l, d)) + +#define APPLE_DART_PTE_SUBPAGE_START GENMASK_ULL(63, 52) +#define APPLE_DART_PTE_SUBPAGE_END GENMASK_ULL(51, 40) + +#define APPLE_DART1_PADDR_MASK GENMASK_ULL(35, 12) +#define APPLE_DART2_PADDR_MASK GENMASK_ULL(37, 10) +#define APPLE_DART2_PADDR_SHIFT (4) + +/* Apple DART1 protection bits */ +#define APPLE_DART1_PTE_PROT_NO_READ BIT(8) +#define APPLE_DART1_PTE_PROT_NO_WRITE BIT(7) +#define APPLE_DART1_PTE_PROT_SP_DIS BIT(1) + +/* Apple DART2 protection bits */ +#define APPLE_DART2_PTE_PROT_NO_READ BIT(3) +#define APPLE_DART2_PTE_PROT_NO_WRITE BIT(2) +#define APPLE_DART2_PTE_PROT_NO_CACHE BIT(1) + +/* marks PTE as valid */ +#define APPLE_DART_PTE_VALID BIT(0) + +/* IOPTE accessors */ +#define iopte_deref(pte, d) __va(iopte_to_paddr(pte, d)) + +struct dart_io_pgtable { + struct io_pgtable iop; + + int pgd_bits; + int start_level; + int bits_per_level; + + void *pgd; +}; + +typedef u64 dart_iopte; + +static inline bool iopte_leaf(dart_iopte pte, int lvl, + enum io_pgtable_fmt fmt) +{ + return (lvl == (DART_MAX_LEVELS - 1)) && (pte & APPLE_DART_PTE_VALID); +} + +static dart_iopte paddr_to_iopte(phys_addr_t paddr, + struct dart_io_pgtable *data) +{ + dart_iopte pte; + + if (data->iop.fmt == APPLE_DART) + return paddr & APPLE_DART1_PADDR_MASK; + + /* format is APPLE_DART2 */ + pte = paddr >> APPLE_DART2_PADDR_SHIFT; + pte &= APPLE_DART2_PADDR_MASK; + + return pte; +} + +static phys_addr_t iopte_to_paddr(dart_iopte pte, + struct dart_io_pgtable *data) +{ + u64 paddr; + + if (data->iop.fmt == APPLE_DART) + return pte & APPLE_DART1_PADDR_MASK; + + /* format is APPLE_DART2 */ + paddr = pte & APPLE_DART2_PADDR_MASK; + paddr <<= APPLE_DART2_PADDR_SHIFT; + + return paddr; +} + +static void *__dart_alloc_pages(size_t size, gfp_t gfp, + struct io_pgtable_cfg *cfg) +{ + struct device *dev = cfg->iommu_dev; + int order = get_order(size); + struct page *p; + + VM_BUG_ON((gfp & __GFP_HIGHMEM)); + p = alloc_pages_node(dev ? dev_to_node(dev) : NUMA_NO_NODE, + gfp | __GFP_ZERO, order); + if (!p) + return NULL; + + return page_address(p); +} + +static void __dart_free_pages(void *pages, size_t size) +{ + free_pages((unsigned long)pages, get_order(size)); +} + +static void __dart_init_pte(struct dart_io_pgtable *data, + phys_addr_t paddr, dart_iopte prot, + int lvl, int num_entries, dart_iopte *ptep) +{ + dart_iopte pte = prot; + size_t sz = DART_BLOCK_SIZE(lvl, data); + int i; + + if (lvl == DART_MAX_LEVELS - 1 && data->iop.fmt == APPLE_DART) + pte |= APPLE_DART1_PTE_PROT_SP_DIS; + + pte |= APPLE_DART_PTE_VALID; + + /* subpage protection: always allow access to the entire page */ + pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_START, 0); + pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_END, 0xfff); + + for (i = 0; i < num_entries; i++) + ptep[i] = pte | paddr_to_iopte(paddr + i * sz, data); +} + +static int dart_init_pte(struct dart_io_pgtable *data, + unsigned long iova, phys_addr_t paddr, + dart_iopte prot, int lvl, int num_entries, + dart_iopte *ptep) +{ + int i; + + for (i = 0; i < num_entries; i++) + if (iopte_leaf(ptep[i], lvl, data->iop.fmt)) { + /* We require an unmap first */ + WARN_ON(iopte_leaf(ptep[i], lvl, data->iop.fmt)); + return -EEXIST; + } + + __dart_init_pte(data, paddr, prot, lvl, num_entries, ptep); + return 0; +} + +static dart_iopte dart_install_table(dart_iopte *table, + dart_iopte *ptep, + dart_iopte curr, + struct dart_io_pgtable *data) +{ + dart_iopte old, new; + + new = paddr_to_iopte(__pa(table), data) | APPLE_DART_PTE_VALID; + + /* + * Ensure the table itself is visible before its PTE can be. + * Whilst we could get away with cmpxchg64_release below, this + * doesn't have any ordering semantics when !CONFIG_SMP. + */ + dma_wmb(); + + old = cmpxchg64_relaxed(ptep, curr, new); + + return old; +} + +static int __dart_map(struct dart_io_pgtable *data, unsigned long iova, + phys_addr_t paddr, size_t size, size_t pgcount, + dart_iopte prot, int lvl, dart_iopte *ptep, + gfp_t gfp, size_t *mapped) +{ + dart_iopte *cptep, pte; + size_t block_size = DART_BLOCK_SIZE(lvl, data); + size_t tblsz = DART_GRANULE(data); + struct io_pgtable_cfg *cfg = &data->iop.cfg; + int ret = 0, num_entries, max_entries, map_idx_start; + + /* Find our entry at the current level */ + map_idx_start = DART_LVL_IDX(iova, lvl, data); + ptep += map_idx_start; + + /* If we can install a leaf entry at this level, then do so */ + if (size == block_size) { + max_entries = DART_PTES_PER_TABLE(data) - map_idx_start; + num_entries = min_t(int, pgcount, max_entries); + ret = dart_init_pte(data, iova, paddr, prot, lvl, num_entries, ptep); + if (!ret && mapped) + *mapped += num_entries * size; + + return ret; + } + + /* We can't allocate tables at the final level */ + if (WARN_ON(lvl >= DART_MAX_LEVELS - 1)) + return -EINVAL; + + /* Grab a pointer to the next level */ + pte = READ_ONCE(*ptep); + if (!pte) { + cptep = __dart_alloc_pages(tblsz, gfp, cfg); + if (!cptep) + return -ENOMEM; + + pte = dart_install_table(cptep, ptep, 0, data); + if (pte) + __dart_free_pages(cptep, tblsz); + } + + if (pte && !iopte_leaf(pte, lvl, data->iop.fmt)) { + cptep = iopte_deref(pte, data); + } else if (pte) { + /* We require an unmap first */ + WARN_ON(pte); + return -EEXIST; + } + + /* Rinse, repeat */ + return __dart_map(data, iova, paddr, size, pgcount, prot, lvl + 1, + cptep, gfp, mapped); +} + +static dart_iopte dart_prot_to_pte(struct dart_io_pgtable *data, + int prot) +{ + dart_iopte pte = 0; + + if (data->iop.fmt == APPLE_DART) { + if (!(prot & IOMMU_WRITE)) + pte |= APPLE_DART1_PTE_PROT_NO_WRITE; + if (!(prot & IOMMU_READ)) + pte |= APPLE_DART1_PTE_PROT_NO_READ; + } + if (data->iop.fmt == APPLE_DART2) { + if (!(prot & IOMMU_WRITE)) + pte |= APPLE_DART2_PTE_PROT_NO_WRITE; + if (!(prot & IOMMU_READ)) + pte |= APPLE_DART2_PTE_PROT_NO_READ; + if (!(prot & IOMMU_CACHE)) + pte |= APPLE_DART2_PTE_PROT_NO_CACHE; + } + + return pte; +} + +static int dart_map_pages(struct io_pgtable_ops *ops, unsigned long iova, + phys_addr_t paddr, size_t pgsize, size_t pgcount, + int iommu_prot, gfp_t gfp, size_t *mapped) +{ + struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops); + struct io_pgtable_cfg *cfg = &data->iop.cfg; + dart_iopte *ptep = data->pgd; + int ret, lvl = data->start_level; + dart_iopte prot; + long iaext = (s64)iova >> cfg->ias; + + if (WARN_ON(!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize)) + return -EINVAL; + + if (WARN_ON(iaext || paddr >> cfg->oas)) + return -ERANGE; + + /* If no access, then nothing to do */ + if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE))) + return 0; + + prot = dart_prot_to_pte(data, iommu_prot); + ret = __dart_map(data, iova, paddr, pgsize, pgcount, prot, lvl, + ptep, gfp, mapped); + /* + * Synchronise all PTE updates for the new mapping before there's + * a chance for anything to kick off a table walk for the new iova. + */ + wmb(); + + return ret; +} + +static int dart_map(struct io_pgtable_ops *ops, unsigned long iova, + phys_addr_t paddr, size_t size, int iommu_prot, gfp_t gfp) +{ + return dart_map_pages(ops, iova, paddr, size, 1, iommu_prot, gfp, + NULL); +} + +static void __dart_free_pgtable(struct dart_io_pgtable *data, int lvl, + dart_iopte *ptep) +{ + dart_iopte *start, *end; + unsigned long table_size; + + if (lvl == data->start_level) + table_size = DART_PGD_SIZE(data); + else + table_size = DART_GRANULE(data); + + start = ptep; + + /* Only leaf entries at the last level */ + if (lvl == DART_MAX_LEVELS - 1) + end = ptep; + else + end = (void *)ptep + table_size; + + while (ptep != end) { + dart_iopte pte = *ptep++; + + if (!pte || iopte_leaf(pte, lvl, data->iop.fmt)) + continue; + + __dart_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); + } + + __dart_free_pages(start, table_size); +} + +static size_t __dart_unmap(struct dart_io_pgtable *data, + struct iommu_iotlb_gather *gather, + unsigned long iova, size_t size, size_t pgcount, + int lvl, dart_iopte *ptep) +{ + dart_iopte pte; + struct io_pgtable *iop = &data->iop; + int i = 0, num_entries, max_entries, unmap_idx_start; + + /* Something went horribly wrong and we ran out of page table */ + if (WARN_ON(lvl == DART_MAX_LEVELS)) + return 0; + + unmap_idx_start = DART_LVL_IDX(iova, lvl, data); + ptep += unmap_idx_start; + pte = READ_ONCE(*ptep); + if (WARN_ON(!pte)) + return 0; + + /* If the size matches this level, we're in the right place */ + if (size == DART_BLOCK_SIZE(lvl, data)) { + max_entries = DART_PTES_PER_TABLE(data) - unmap_idx_start; + num_entries = min_t(int, pgcount, max_entries); + + while (i < num_entries) { + pte = READ_ONCE(*ptep); + if (WARN_ON(!pte)) + break; + + /* clear pte */ + *ptep = 0; + + if (!iopte_leaf(pte, lvl, iop->fmt)) { + /* Also flush any partial walks */ + io_pgtable_tlb_flush_walk(iop, iova + i * size, size, + DART_GRANULE(data)); + __dart_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); + } else if (!iommu_iotlb_gather_queued(gather)) { + io_pgtable_tlb_add_page(iop, gather, iova + i * size, size); + } + + ptep++; + i++; + } + + return i * size; + } + + /* Keep on walkin' */ + ptep = iopte_deref(pte, data); + return __dart_unmap(data, gather, iova, size, pgcount, lvl + 1, ptep); +} + +static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova, + size_t pgsize, size_t pgcount, + struct iommu_iotlb_gather *gather) +{ + struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops); + struct io_pgtable_cfg *cfg = &data->iop.cfg; + dart_iopte *ptep = data->pgd; + long iaext = (s64)iova >> cfg->ias; + + if (WARN_ON(!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize || !pgcount)) + return 0; + + if (WARN_ON(iaext)) + return 0; + + return __dart_unmap(data, gather, iova, pgsize, pgcount, + data->start_level, ptep); +} + +static size_t dart_unmap(struct io_pgtable_ops *ops, unsigned long iova, + size_t size, struct iommu_iotlb_gather *gather) +{ + return dart_unmap_pages(ops, iova, size, 1, gather); +} + +static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops, + unsigned long iova) +{ + struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops); + dart_iopte pte, *ptep = data->pgd; + int lvl = data->start_level; + + do { + /* Valid IOPTE pointer? */ + if (!ptep) + return 0; + + /* Grab the IOPTE we're interested in */ + ptep += DART_LVL_IDX(iova, lvl, data); + pte = READ_ONCE(*ptep); + + /* Valid entry? */ + if (!pte) + return 0; + + /* Leaf entry? */ + if (iopte_leaf(pte, lvl, data->iop.fmt)) + goto found_translation; + + /* Take it to the next level */ + ptep = iopte_deref(pte, data); + } while (++lvl < DART_MAX_LEVELS); + + /* Ran out of page tables to walk */ + return 0; + +found_translation: + iova &= (DART_BLOCK_SIZE(lvl, data) - 1); + return iopte_to_paddr(pte, data) | iova; +} + +static void dart_restrict_pgsizes(struct io_pgtable_cfg *cfg) +{ + unsigned long granule, page_sizes; + unsigned int max_addr_bits = 48; + + /* + * We need to restrict the supported page sizes to match the + * translation regime for a particular granule. Aim to match + * the CPU page size if possible, otherwise prefer smaller sizes. + * While we're at it, restrict the block sizes to match the + * chosen granule. + */ + if (cfg->pgsize_bitmap & PAGE_SIZE) + granule = PAGE_SIZE; + else if (cfg->pgsize_bitmap & ~PAGE_MASK) + granule = 1UL << __fls(cfg->pgsize_bitmap & ~PAGE_MASK); + else if (cfg->pgsize_bitmap & PAGE_MASK) + granule = 1UL << __ffs(cfg->pgsize_bitmap & PAGE_MASK); + else + granule = 0; + + switch (granule) { + case SZ_4K: + page_sizes = (SZ_4K | SZ_2M | SZ_1G); + break; + case SZ_16K: + page_sizes = (SZ_16K | SZ_32M); + break; + default: + page_sizes = 0; + } + + cfg->pgsize_bitmap &= page_sizes; + cfg->ias = min(cfg->ias, max_addr_bits); + cfg->oas = min(cfg->oas, max_addr_bits); +} + +static struct dart_io_pgtable * +dart_alloc_pgtable(struct io_pgtable_cfg *cfg) +{ + struct dart_io_pgtable *data; + int bits_per_level, levels, va_bits, pg_shift; + + dart_restrict_pgsizes(cfg); + + if (!(cfg->pgsize_bitmap & (SZ_4K | SZ_16K))) + return NULL; + + if (cfg->ias > DART_MAX_ADDR_BITS) + return NULL; + + if (cfg->oas > DART_MAX_ADDR_BITS) + return NULL; + + pg_shift = __ffs(cfg->pgsize_bitmap); + bits_per_level = pg_shift - ilog2(sizeof(dart_iopte)); + + va_bits = cfg->ias - pg_shift; + levels = DIV_ROUND_UP(va_bits, bits_per_level); + if (levels > DART_MAX_LEVELS) + return NULL; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->bits_per_level = bits_per_level; + data->start_level = DART_MAX_LEVELS - levels; + + /* Calculate the actual size of our pgd (without concatenation) */ + data->pgd_bits = va_bits - (data->bits_per_level * (levels - 1)); + + data->iop.ops = (struct io_pgtable_ops) { + .map = dart_map, + .map_pages = dart_map_pages, + .unmap = dart_unmap, + .unmap_pages = dart_unmap_pages, + .iova_to_phys = dart_iova_to_phys, + }; + + return data; +} + +static struct io_pgtable * +apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) +{ + struct dart_io_pgtable *data; + int i; + + if (!cfg->coherent_walk) + return NULL; + + if (cfg->oas != 36 && cfg->oas != 42) + return NULL; + + data = dart_alloc_pgtable(cfg); + if (!data) + return NULL; + + /* + * The table format itself always uses two levels, but the total VA + * space is mapped by four separate tables, making the MMIO registers + * an effective "level 1". For simplicity, though, we treat this + * equivalently to LPAE stage 2 concatenation at level 2, with the + * additional TTBRs each just pointing at consecutive pages. + */ + if (data->start_level == 0 && data->pgd_bits > 2) + goto out_free_data; + if (data->start_level > 0) + data->pgd_bits = 0; + data->start_level = 1; + cfg->apple_dart_cfg.n_ttbrs = 1 << data->pgd_bits; + data->pgd_bits += data->bits_per_level; + + data->pgd = __dart_alloc_pages(DART_PGD_SIZE(data), GFP_KERNEL, + cfg); + if (!data->pgd) + goto out_free_data; + + for (i = 0; i < cfg->apple_dart_cfg.n_ttbrs; ++i) + cfg->apple_dart_cfg.ttbr[i] = + virt_to_phys(data->pgd + i * DART_GRANULE(data)); + + return &data->iop; + +out_free_data: + kfree(data); + return NULL; +} + +static void apple_dart_free_pgtable(struct io_pgtable *iop) +{ + struct dart_io_pgtable *data = io_pgtable_to_data(iop); + + __dart_free_pgtable(data, data->start_level, data->pgd); + kfree(data); +} + +struct io_pgtable_init_fns io_pgtable_apple_dart_init_fns = { + .alloc = apple_dart_alloc_pgtable, + .free = apple_dart_free_pgtable, +}; diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c index f4bfcef98297..49f46e1eabf7 100644 --- a/drivers/iommu/io-pgtable.c +++ b/drivers/iommu/io-pgtable.c @@ -20,7 +20,10 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = { [ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns, [ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns, [ARM_MALI_LPAE] = &io_pgtable_arm_mali_lpae_init_fns, +#endif +#ifdef CONFIG_IOMMU_IO_PGTABLE_DART [APPLE_DART] = &io_pgtable_apple_dart_init_fns, + [APPLE_DART2] = &io_pgtable_apple_dart_init_fns, #endif #ifdef CONFIG_IOMMU_IO_PGTABLE_ARMV7S [ARM_V7S] = &io_pgtable_arm_v7s_init_fns, diff --git a/drivers/mailbox/apple-mailbox.c b/drivers/mailbox/apple-mailbox.c index 496c4951ccb1..06c7ad3b1c03 100644 --- a/drivers/mailbox/apple-mailbox.c +++ b/drivers/mailbox/apple-mailbox.c @@ -17,6 +17,7 @@ */ #include <linux/apple-mailbox.h> +#include <linux/delay.h> #include <linux/device.h> #include <linux/gfp.h> #include <linux/interrupt.h> @@ -25,6 +26,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> +#include <linux/spinlock.h> #include <linux/types.h> #define APPLE_ASC_MBOX_CONTROL_FULL BIT(16) @@ -100,6 +102,9 @@ struct apple_mbox { struct device *dev; struct mbox_controller controller; + spinlock_t rx_lock; + spinlock_t tx_lock; + bool tx_pending; }; static const struct of_device_id apple_mbox_of_match[]; @@ -112,6 +117,14 @@ static bool apple_mbox_hw_can_send(struct apple_mbox *apple_mbox) return !(mbox_ctrl & apple_mbox->hw->control_full); } +static bool apple_mbox_hw_send_empty(struct apple_mbox *apple_mbox) +{ + u32 mbox_ctrl = + readl_relaxed(apple_mbox->regs + apple_mbox->hw->a2i_control); + + return mbox_ctrl & apple_mbox->hw->control_empty; +} + static int apple_mbox_hw_send(struct apple_mbox *apple_mbox, struct apple_mbox_msg *msg) { @@ -155,11 +168,15 @@ static int apple_mbox_chan_send_data(struct mbox_chan *chan, void *data) { struct apple_mbox *apple_mbox = chan->con_priv; struct apple_mbox_msg *msg = data; + unsigned long flags; int ret; + spin_lock_irqsave(&apple_mbox->tx_lock, flags); + WARN_ON(apple_mbox->tx_pending); + ret = apple_mbox_hw_send(apple_mbox, msg); if (ret) - return ret; + goto err_unlock; /* * The interrupt is level triggered and will keep firing as long as the @@ -174,9 +191,13 @@ static int apple_mbox_chan_send_data(struct mbox_chan *chan, void *data) writel_relaxed(apple_mbox->hw->irq_bit_send_empty, apple_mbox->regs + apple_mbox->hw->irq_ack); } + apple_mbox->tx_pending = true; enable_irq(apple_mbox->irq_send_empty); - return 0; +err_unlock: + spin_unlock_irqrestore(&apple_mbox->tx_lock, flags); + + return ret; } static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data) @@ -191,17 +212,27 @@ static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data) * it at the main controller again. */ disable_irq_nosync(apple_mbox->irq_send_empty); - mbox_chan_txdone(&apple_mbox->chan, 0); + spin_lock(&apple_mbox->tx_lock); + if (apple_mbox->tx_pending) { + apple_mbox->tx_pending = false; + spin_unlock(&apple_mbox->tx_lock); + mbox_chan_txdone(&apple_mbox->chan, 0); + } else { + spin_unlock(&apple_mbox->tx_lock); + } return IRQ_HANDLED; } -static irqreturn_t apple_mbox_recv_irq(int irq, void *data) +static int apple_mbox_poll(struct apple_mbox *apple_mbox) { - struct apple_mbox *apple_mbox = data; + struct apple_mbox_msg msg; + int ret = 0; - while (apple_mbox_hw_recv(apple_mbox, &msg) == 0) + while (apple_mbox_hw_recv(apple_mbox, &msg) == 0) { mbox_chan_received_data(&apple_mbox->chan, (void *)&msg); + ret++; + } /* * The interrupt will keep firing even if there are no more messages @@ -216,9 +247,57 @@ static irqreturn_t apple_mbox_recv_irq(int irq, void *data) apple_mbox->regs + apple_mbox->hw->irq_ack); } + return ret; +} + +static irqreturn_t apple_mbox_recv_irq(int irq, void *data) +{ + struct apple_mbox *apple_mbox = data; + + spin_lock(&apple_mbox->rx_lock); + apple_mbox_poll(apple_mbox); + spin_unlock(&apple_mbox->rx_lock); + return IRQ_HANDLED; } +static bool apple_mbox_chan_peek_data(struct mbox_chan *chan) +{ + struct apple_mbox *apple_mbox = chan->con_priv; + unsigned long flags; + int ret; + + spin_lock_irqsave(&apple_mbox->rx_lock, flags); + ret = apple_mbox_poll(apple_mbox); + spin_unlock_irqrestore(&apple_mbox->rx_lock, flags); + + return ret > 0; +} + +static int apple_mbox_chan_flush(struct mbox_chan *chan, unsigned long timeout) +{ + struct apple_mbox *apple_mbox = chan->con_priv; + unsigned long deadline = jiffies + msecs_to_jiffies(timeout); + unsigned long flags; + + while (time_before(jiffies, deadline)) { + if (apple_mbox_hw_send_empty(apple_mbox)) { + spin_lock_irqsave(&apple_mbox->tx_lock, flags); + if (apple_mbox->tx_pending) { + apple_mbox->tx_pending = false; + disable_irq_nosync(apple_mbox->irq_send_empty); + } + /* Mailbox subsystem will call txdone for us */ + spin_unlock_irqrestore(&apple_mbox->tx_lock, flags); + return 0; + } + + udelay(1); + } + + return -ETIME; +} + static int apple_mbox_chan_startup(struct mbox_chan *chan) { struct apple_mbox *apple_mbox = chan->con_priv; @@ -250,6 +329,8 @@ static void apple_mbox_chan_shutdown(struct mbox_chan *chan) static const struct mbox_chan_ops apple_mbox_ops = { .send_data = apple_mbox_chan_send_data, + .peek_data = apple_mbox_chan_peek_data, + .flush = apple_mbox_chan_flush, .startup = apple_mbox_chan_startup, .shutdown = apple_mbox_chan_shutdown, }; @@ -304,6 +385,8 @@ static int apple_mbox_probe(struct platform_device *pdev) mbox->controller.txdone_irq = true; mbox->controller.of_xlate = apple_mbox_of_xlate; mbox->chan.con_priv = mbox; + spin_lock_init(&mbox->rx_lock); + spin_lock_init(&mbox->tx_lock); irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-recv", dev_name(dev)); if (!irqname) @@ -311,8 +394,8 @@ static int apple_mbox_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, mbox->irq_recv_not_empty, NULL, apple_mbox_recv_irq, - IRQF_NO_AUTOEN | IRQF_ONESHOT, irqname, - mbox); + IRQF_NO_AUTOEN | IRQF_ONESHOT | IRQF_NO_SUSPEND, + irqname, mbox); if (ret) return ret; @@ -320,9 +403,8 @@ static int apple_mbox_probe(struct platform_device *pdev) if (!irqname) return -ENOMEM; - ret = devm_request_irq(dev, mbox->irq_send_empty, - apple_mbox_send_empty_irq, IRQF_NO_AUTOEN, - irqname, mbox); + ret = devm_request_irq(dev, mbox->irq_send_empty, apple_mbox_send_empty_irq, + IRQF_NO_AUTOEN | IRQF_NO_SUSPEND, irqname, mbox); if (ret) return ret; diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c index 4229b9b5da98..e27500e8cba9 100644 --- a/drivers/mailbox/mailbox.c +++ b/drivers/mailbox/mailbox.c @@ -310,7 +310,7 @@ int mbox_flush(struct mbox_chan *chan, unsigned long timeout) return -ENOTSUPP; ret = chan->mbox->ops->flush(chan, timeout); - if (ret < 0) + if (ret >= 0) tx_tick(chan, ret); return ret; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 3b59456f5545..e675aaf00bc8 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -51,6 +51,21 @@ config MFD_ACT8945A linear regulators, along with a complete ActivePath battery charger. +config MFD_APPLE_SPMI_PMU + tristate "Apple SPMI PMUs" + depends on SPMI + depends on ARCH_APPLE || COMPILE_TEST + default ARCH_APPLE + select MFD_SIMPLE_MFD_SPMI + help + Say yes here to enable support for Apple PMUs attached via the + SPMI bus. These can be found on Apple devices such as Apple + Silicon Macs. + + This driver itself only attaches to the core device, and relies + on subsystem drivers for individual device functions. You must + enable those for it to be useful. + config MFD_SUN4I_GPADC tristate "Allwinner sunxi platforms' GPADC MFD driver" select MFD_CORE @@ -1214,6 +1229,19 @@ config MFD_SIMPLE_MFD_I2C sub-devices represented by child nodes in Device Tree will be subsequently registered. +config MFD_SIMPLE_MFD_SPMI + tristate + depends on SPMI + select MFD_CORE + select REGMAP_SPMI + help + This driver creates a single register map with the intention for it + to be shared by all sub-devices. + + Once the register map has been successfully initialised, any + sub-devices represented by child nodes in Device Tree will be + subsequently registered. + config MFD_SL28CPLD tristate "Kontron sl28cpld Board Management Controller" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 858cacf659d6..8d8abcd68351 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -266,6 +266,7 @@ obj-$(CONFIG_MFD_QCOM_PM8008) += qcom-pm8008.o obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o +obj-$(CONFIG_MFD_SIMPLE_MFD_SPMI) += simple-mfd-spmi.o obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o diff --git a/drivers/mfd/simple-mfd-spmi.c b/drivers/mfd/simple-mfd-spmi.c new file mode 100644 index 000000000000..99f25751000a --- /dev/null +++ b/drivers/mfd/simple-mfd-spmi.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Simple MFD - SPMI + * + * Copyright The Asahi Linux Contributors + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spmi.h> +#include <linux/of_platform.h> + +static const struct regmap_config spmi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xffff, +}; + +static int simple_spmi_probe(struct spmi_device *sdev) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spmi_ext(sdev, &spmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return devm_of_platform_populate(&sdev->dev); +} + +static const struct of_device_id simple_spmi_id_table[] = { + { .compatible = "apple,spmi-pmu" }, + {} +}; +MODULE_DEVICE_TABLE(of, simple_spmi_id_table); + +static struct spmi_driver pmic_spmi_driver = { + .probe = simple_spmi_probe, + .driver = { + .name = "simple-mfd-spmi", + .owner = THIS_MODULE, + .of_match_table = simple_spmi_id_table, + }, +}; +module_spmi_driver(pmic_spmi_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Simple MFD - SPMI driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index ed53276f6ad9..2fd4221b24aa 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -2015,6 +2015,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( struct sdhci_host *host; int ret, bar = first_bar + slotno; size_t priv_size = chip->fixes ? chip->fixes->priv_size : 0; + u32 cd_debounce_delay_ms; if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) { dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar); @@ -2081,6 +2082,10 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( if (host->mmc->caps & MMC_CAP_CD_WAKE) device_init_wakeup(&pdev->dev, true); + if (device_property_read_u32(&pdev->dev, "cd-debounce-delay-ms", + &cd_debounce_delay_ms)) + cd_debounce_delay_ms = 200; + if (slot->cd_idx >= 0) { ret = mmc_gpiod_request_cd(host->mmc, "cd", slot->cd_idx, slot->cd_override_level, 0); @@ -2088,7 +2093,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( ret = mmc_gpiod_request_cd(host->mmc, NULL, slot->cd_idx, slot->cd_override_level, - 0); + cd_debounce_delay_ms * 1000); if (ret == -EPROBE_DEFER) goto remove; @@ -2096,6 +2101,16 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( dev_warn(&pdev->dev, "failed to setup card detect gpio\n"); slot->cd_idx = -1; } + } else if (is_of_node(pdev->dev.fwnode)) { + /* Allow all OF systems to use a CD GPIO if provided */ + + ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, + slot->cd_override_level, + cd_debounce_delay_ms * 1000); + if (ret == -EPROBE_DEFER) + goto remove; + else if (ret == 0) + slot->cd_idx = 0; } if (chip->fixes && chip->fixes->add_host) diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index ac2d400d1d6c..aeb0294385ed 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -1965,55 +1965,55 @@ static const struct driver_info at_umc2000sp_info = { static const struct usb_device_id products[] = { { /* ASIX AX88179 10/100/1000 */ - USB_DEVICE(0x0b95, 0x1790), + USB_DEVICE_AND_INTERFACE_INFO(0x0b95, 0x1790, 0xff, 0xff, 0), .driver_info = (unsigned long)&ax88179_info, }, { /* ASIX AX88178A 10/100/1000 */ - USB_DEVICE(0x0b95, 0x178a), + USB_DEVICE_AND_INTERFACE_INFO(0x0b95, 0x178a, 0xff, 0xff, 0), .driver_info = (unsigned long)&ax88178a_info, }, { /* Cypress GX3 SuperSpeed to Gigabit Ethernet Bridge Controller */ - USB_DEVICE(0x04b4, 0x3610), + USB_DEVICE_AND_INTERFACE_INFO(0x04b4, 0x3610, 0xff, 0xff, 0), .driver_info = (unsigned long)&cypress_GX3_info, }, { /* D-Link DUB-1312 USB 3.0 to Gigabit Ethernet Adapter */ - USB_DEVICE(0x2001, 0x4a00), + USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x4a00, 0xff, 0xff, 0), .driver_info = (unsigned long)&dlink_dub1312_info, }, { /* Sitecom USB 3.0 to Gigabit Adapter */ - USB_DEVICE(0x0df6, 0x0072), + USB_DEVICE_AND_INTERFACE_INFO(0x0df6, 0x0072, 0xff, 0xff, 0), .driver_info = (unsigned long)&sitecom_info, }, { /* Samsung USB Ethernet Adapter */ - USB_DEVICE(0x04e8, 0xa100), + USB_DEVICE_AND_INTERFACE_INFO(0x04e8, 0xa100, 0xff, 0xff, 0), .driver_info = (unsigned long)&samsung_info, }, { /* Lenovo OneLinkDock Gigabit LAN */ - USB_DEVICE(0x17ef, 0x304b), + USB_DEVICE_AND_INTERFACE_INFO(0x17ef, 0x304b, 0xff, 0xff, 0), .driver_info = (unsigned long)&lenovo_info, }, { /* Belkin B2B128 USB 3.0 Hub + Gigabit Ethernet Adapter */ - USB_DEVICE(0x050d, 0x0128), + USB_DEVICE_AND_INTERFACE_INFO(0x050d, 0x0128, 0xff, 0xff, 0), .driver_info = (unsigned long)&belkin_info, }, { /* Toshiba USB 3.0 GBit Ethernet Adapter */ - USB_DEVICE(0x0930, 0x0a13), + USB_DEVICE_AND_INTERFACE_INFO(0x0930, 0x0a13, 0xff, 0xff, 0), .driver_info = (unsigned long)&toshiba_info, }, { /* Magic Control Technology U3-A9003 USB 3.0 Gigabit Ethernet Adapter */ - USB_DEVICE(0x0711, 0x0179), + USB_DEVICE_AND_INTERFACE_INFO(0x0711, 0x0179, 0xff, 0xff, 0), .driver_info = (unsigned long)&mct_info, }, { /* Allied Telesis AT-UMC2000 USB 3.0/USB 3.1 Gen 1 to Gigabit Ethernet Adapter */ - USB_DEVICE(0x07c9, 0x000e), + USB_DEVICE_AND_INTERFACE_INFO(0x07c9, 0x000e, 0xff, 0xff, 0), .driver_info = (unsigned long)&at_umc2000_info, }, { /* Allied Telesis AT-UMC200 USB 3.0/USB 3.1 Gen 1 to Fast Ethernet Adapter */ - USB_DEVICE(0x07c9, 0x000f), + USB_DEVICE_AND_INTERFACE_INFO(0x07c9, 0x000f, 0xff, 0xff, 0), .driver_info = (unsigned long)&at_umc200_info, }, { /* Allied Telesis AT-UMC2000/SP USB 3.0/USB 3.1 Gen 1 to Gigabit Ethernet Adapter */ - USB_DEVICE(0x07c9, 0x0010), + USB_DEVICE_AND_INTERFACE_INFO(0x07c9, 0x0010, 0xff, 0xff, 0), .driver_info = (unsigned long)&at_umc2000sp_info, }, { }, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile index 13c13504a6e8..19009eb9db93 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile @@ -47,3 +47,5 @@ brcmfmac-$(CONFIG_OF) += \ of.o brcmfmac-$(CONFIG_DMI) += \ dmi.o +brcmfmac-$(CONFIG_ACPI) += \ + acpi.o diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/acpi.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/acpi.c new file mode 100644 index 000000000000..dec6a83d13b1 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/acpi.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright The Asahi Linux Contributors + */ + +#include <linux/acpi.h> +#include "debug.h" +#include "core.h" +#include "common.h" + +void brcmf_acpi_probe(struct device *dev, enum brcmf_bus_type bus_type, + struct brcmf_mp_device *settings) +{ + acpi_status status; + const union acpi_object *o; + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_device *adev = ACPI_COMPANION(dev); + + if (!adev) + return; + + if (!ACPI_FAILURE(acpi_dev_get_property(adev, "module-instance", + ACPI_TYPE_STRING, &o))) { + brcmf_dbg(INFO, "ACPI module-instance=%s\n", o->string.pointer); + settings->board_type = devm_kasprintf(dev, GFP_KERNEL, + "apple,%s", + o->string.pointer); + } else { + brcmf_dbg(INFO, "No ACPI module-instance\n"); + } + + status = acpi_evaluate_object(adev->handle, "RWCV", NULL, &buf); + o = buf.pointer; + if (!ACPI_FAILURE(status) && o && o->type == ACPI_TYPE_BUFFER && + o->buffer.length >= 2) { + char *antenna_sku = devm_kzalloc(dev, 3, GFP_KERNEL); + + if (!antenna_sku) { + brcmf_err("Failed to allocate antenna-sku"); + } else { + memcpy(antenna_sku, o->buffer.pointer, 2); + brcmf_dbg(INFO, "ACPI RWCV data=%*phN antenna-sku=%s\n", + (int)o->buffer.length, o->buffer.pointer, + antenna_sku); + settings->antenna_sku = antenna_sku; + } + + kfree(buf.pointer); + } else { + brcmf_dbg(INFO, "No ACPI antenna-sku\n"); + } +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h index 3f5da3bb6aa5..f4bd98da9761 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h @@ -7,6 +7,8 @@ #define BRCMFMAC_BUS_H #include "debug.h" +#include <linux/kernel.h> +#include <linux/firmware.h> /* IDs of the 6 default common rings of msgbuf protocol */ #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT 0 @@ -34,6 +36,12 @@ enum brcmf_bus_protocol_type { BRCMF_PROTO_MSGBUF }; +/* Firmware blobs that may be available */ +enum brcmf_blob_type { + BRCMF_BLOB_CLM, + BRCMF_BLOB_TXCAP, +}; + struct brcmf_mp_device; struct brcmf_bus_dcmd { @@ -60,7 +68,7 @@ struct brcmf_bus_dcmd { * @wowl_config: specify if dongle is configured for wowl when going to suspend * @get_ramsize: obtain size of device memory. * @get_memdump: obtain device memory dump in provided buffer. - * @get_fwname: obtain firmware name. + * @get_blob: obtain a firmware blob. * * This structure provides an abstract interface towards the * bus specific driver. For control messages to common driver @@ -77,8 +85,8 @@ struct brcmf_bus_ops { void (*wowl_config)(struct device *dev, bool enabled); size_t (*get_ramsize)(struct device *dev); int (*get_memdump)(struct device *dev, void *data, size_t len); - int (*get_fwname)(struct device *dev, const char *ext, - unsigned char *fw_name); + int (*get_blob)(struct device *dev, const struct firmware **fw, + enum brcmf_blob_type type); void (*debugfs_create)(struct device *dev); int (*reset)(struct device *dev); }; @@ -220,10 +228,10 @@ int brcmf_bus_get_memdump(struct brcmf_bus *bus, void *data, size_t len) } static inline -int brcmf_bus_get_fwname(struct brcmf_bus *bus, const char *ext, - unsigned char *fw_name) +int brcmf_bus_get_blob(struct brcmf_bus *bus, const struct firmware **fw, + enum brcmf_blob_type type) { - return bus->ops->get_fwname(bus->dev, ext, fw_name); + return bus->ops->get_blob(bus->dev, fw, type); } static inline diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 605206abe424..63ecbbe11af1 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -770,12 +770,50 @@ void brcmf_set_mpc(struct brcmf_if *ifp, int mpc) } } +static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, + struct brcmf_scan_params_v2_le *params_le, + struct cfg80211_scan_request *request); + +static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2_le, + struct brcmf_scan_params_le *params_le) +{ + size_t params_size; + u32 ch; + int n_channels, n_ssids; + + memcpy(¶ms_le->ssid_le, ¶ms_v2_le->ssid_le, + sizeof(params_le->ssid_le)); + memcpy(¶ms_le->bssid, ¶ms_v2_le->bssid, + sizeof(params_le->bssid)); + + params_le->bss_type = params_v2_le->bss_type; + params_le->scan_type = params_v2_le->scan_type; + params_le->nprobes = params_v2_le->nprobes; + params_le->active_time = params_v2_le->active_time; + params_le->passive_time = params_v2_le->passive_time; + params_le->home_time = params_v2_le->home_time; + params_le->channel_num = params_v2_le->channel_num; + + ch = le32_to_cpu(params_v2_le->channel_num); + n_channels = ch & BRCMF_SCAN_PARAMS_COUNT_MASK; + n_ssids = ch >> BRCMF_SCAN_PARAMS_NSSID_SHIFT; + + params_size = sizeof(u16) * n_channels; + if (n_ssids > 0) { + params_size = roundup(params_size, sizeof(u32)); + params_size += sizeof(struct brcmf_ssid_le) * n_ssids; + } + + memcpy(¶ms_le->channel_list[0], + ¶ms_v2_le->channel_list[0], params_size); +} + s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, bool aborted, bool fw_abort) { struct brcmf_pub *drvr = cfg->pub; - struct brcmf_scan_params_le params_le; + struct brcmf_scan_params_v2_le params_v2_le; struct cfg80211_scan_request *scan_request; u64 reqid; u32 bucket; @@ -794,20 +832,23 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, if (fw_abort) { /* Do a scan abort to stop the driver's scan engine */ brcmf_dbg(SCAN, "ABORT scan in firmware\n"); - memset(¶ms_le, 0, sizeof(params_le)); - eth_broadcast_addr(params_le.bssid); - params_le.bss_type = DOT11_BSSTYPE_ANY; - params_le.scan_type = 0; - params_le.channel_num = cpu_to_le32(1); - params_le.nprobes = cpu_to_le32(1); - params_le.active_time = cpu_to_le32(-1); - params_le.passive_time = cpu_to_le32(-1); - params_le.home_time = cpu_to_le32(-1); - /* Scan is aborted by setting channel_list[0] to -1 */ - params_le.channel_list[0] = cpu_to_le16(-1); + + brcmf_escan_prep(cfg, ¶ms_v2_le, NULL); + /* E-Scan (or anyother type) can be aborted by SCAN */ - err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, - ¶ms_le, sizeof(params_le)); + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, + ¶ms_v2_le, + sizeof(params_v2_le)); + } else { + struct brcmf_scan_params_le params_le; + + brcmf_scan_params_v2_to_v1(¶ms_v2_le, ¶ms_le); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, + ¶ms_le, + sizeof(params_le)); + } + if (err) bphy_err(drvr, "Scan abort failed\n"); } @@ -1027,7 +1068,7 @@ done: } static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, - struct brcmf_scan_params_le *params_le, + struct brcmf_scan_params_v2_le *params_le, struct cfg80211_scan_request *request) { u32 n_ssids; @@ -1036,9 +1077,14 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, s32 offset; u16 chanspec; char *ptr; + int length; struct brcmf_ssid_le ssid_le; eth_broadcast_addr(params_le->bssid); + + length = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; + + params_le->version = BRCMF_SCAN_PARAMS_VERSION_V2; params_le->bss_type = DOT11_BSSTYPE_ANY; params_le->scan_type = BRCMF_SCANTYPE_ACTIVE; params_le->channel_num = 0; @@ -1048,6 +1094,15 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, params_le->home_time = cpu_to_le32(-1); memset(¶ms_le->ssid_le, 0, sizeof(params_le->ssid_le)); + /* Scan abort */ + if (!request) { + length += sizeof(u16); + params_le->channel_num = cpu_to_le32(1); + params_le->channel_list[0] = cpu_to_le16(-1); + params_le->length = cpu_to_le16(length); + return; + } + n_ssids = request->n_ssids; n_channels = request->n_channels; @@ -1055,6 +1110,7 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", n_channels); if (n_channels > 0) { + length += roundup(sizeof(u16) * n_channels, sizeof(u32)); for (i = 0; i < n_channels; i++) { chanspec = channel_to_chanspec(&cfg->d11inf, request->channels[i]); @@ -1065,12 +1121,14 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, } else { brcmf_dbg(SCAN, "Scanning all channels\n"); } + /* Copy ssid array if applicable */ brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); if (n_ssids > 0) { - offset = offsetof(struct brcmf_scan_params_le, channel_list) + + offset = offsetof(struct brcmf_scan_params_v2_le, channel_list) + n_channels * sizeof(u16); offset = roundup(offset, sizeof(u32)); + length += sizeof(ssid_le) * n_ssids, ptr = (char *)params_le + offset; for (i = 0; i < n_ssids; i++) { memset(&ssid_le, 0, sizeof(ssid_le)); @@ -1090,6 +1148,7 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, brcmf_dbg(SCAN, "Performing passive scan\n"); params_le->scan_type = BRCMF_SCANTYPE_PASSIVE; } + params_le->length = cpu_to_le16(length); /* Adding mask to channel numbers */ params_le->channel_num = cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | @@ -1101,8 +1160,8 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, struct cfg80211_scan_request *request) { struct brcmf_pub *drvr = cfg->pub; - s32 params_size = BRCMF_SCAN_PARAMS_FIXED_SIZE + - offsetof(struct brcmf_escan_params_le, params_le); + s32 params_size = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE + + offsetof(struct brcmf_escan_params_le, params_v2_le); struct brcmf_escan_params_le *params; s32 err = 0; @@ -1122,8 +1181,22 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, goto exit; } BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN); - brcmf_escan_prep(cfg, ¶ms->params_le, request); - params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); + brcmf_escan_prep(cfg, ¶ms->params_v2_le, request); + + params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2); + + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { + struct brcmf_escan_params_le *params_v1; + + params_size -= BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; + params_size += BRCMF_SCAN_PARAMS_FIXED_SIZE; + params_v1 = kzalloc(params_size, GFP_KERNEL); + params_v1->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); + brcmf_scan_params_v2_to_v1(¶ms->params_v2_le, ¶ms_v1->params_le); + kfree(params); + params = params_v1; + } + params->action = cpu_to_le16(WL_ESCAN_ACTION_START); params->sync_id = cpu_to_le16(0x1234); @@ -1344,51 +1417,44 @@ static u16 brcmf_map_fw_linkdown_reason(const struct brcmf_event_msg *e) return reason; } -static int brcmf_set_pmk(struct brcmf_if *ifp, const u8 *pmk_data, u16 pmk_len) +static int brcmf_set_wsec(struct brcmf_if *ifp, const u8 *key, u16 key_len, u16 flags) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_wsec_pmk_le pmk; - int i, err; + int err; + + if (key_len > sizeof(pmk.key)) { + bphy_err(drvr, "key must be less than %zu bytes\n", + sizeof(pmk.key)); + return -EINVAL; + } + + memset(&pmk, 0, sizeof(pmk)); - /* convert to firmware key format */ - pmk.key_len = cpu_to_le16(pmk_len << 1); - pmk.flags = cpu_to_le16(BRCMF_WSEC_PASSPHRASE); - for (i = 0; i < pmk_len; i++) - snprintf(&pmk.key[2 * i], 3, "%02x", pmk_data[i]); + /* pass key material directly */ + pmk.key_len = cpu_to_le16(key_len); + pmk.flags = cpu_to_le16(flags); + memcpy(pmk.key, key, key_len); - /* store psk in firmware */ + /* store key material in firmware */ err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_WSEC_PMK, &pmk, sizeof(pmk)); if (err < 0) bphy_err(drvr, "failed to change PSK in firmware (len=%u)\n", - pmk_len); + key_len); return err; } +static int brcmf_set_pmk(struct brcmf_if *ifp, const u8 *pmk_data, u16 pmk_len) +{ + return brcmf_set_wsec(ifp, pmk_data, pmk_len, 0); +} + static int brcmf_set_sae_password(struct brcmf_if *ifp, const u8 *pwd_data, u16 pwd_len) { - struct brcmf_pub *drvr = ifp->drvr; - struct brcmf_wsec_sae_pwd_le sae_pwd; - int err; - - if (pwd_len > BRCMF_WSEC_MAX_SAE_PASSWORD_LEN) { - bphy_err(drvr, "sae_password must be less than %d\n", - BRCMF_WSEC_MAX_SAE_PASSWORD_LEN); - return -EINVAL; - } - - sae_pwd.key_len = cpu_to_le16(pwd_len); - memcpy(sae_pwd.key, pwd_data, pwd_len); - - err = brcmf_fil_iovar_data_set(ifp, "sae_password", &sae_pwd, - sizeof(sae_pwd)); - if (err < 0) - bphy_err(drvr, "failed to set SAE password in firmware (len=%u)\n", - pwd_len); - - return err; + return brcmf_set_wsec(ifp, pwd_data, pwd_len, BRCMF_WSEC_PASSPHRASE); } static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason, @@ -3978,6 +4044,37 @@ exit: return 0; } +static s32 +brcmf_pmksa_v3_op(struct brcmf_if *ifp, struct cfg80211_pmksa *pmksa, + bool alive) +{ + struct brcmf_pmk_op_v3_le *pmk_op; + int length = offsetof(struct brcmf_pmk_op_v3_le, pmk); + int ret; + + pmk_op = kzalloc(sizeof(*pmk_op), GFP_KERNEL); + pmk_op->version = cpu_to_le16(BRCMF_PMKSA_VER_3); + + if (!pmksa) { + /* Flush operation, operate on entire list */ + pmk_op->count = cpu_to_le16(0); + } else { + /* Single PMK operation */ + pmk_op->count = cpu_to_le16(1); + length += sizeof(struct brcmf_pmksa_v3); + memcpy(pmk_op->pmk[0].bssid, pmksa->bssid, ETH_ALEN); + memcpy(pmk_op->pmk[0].pmkid, pmksa->pmkid, WLAN_PMKID_LEN); + pmk_op->pmk[0].pmkid_len = WLAN_PMKID_LEN; + pmk_op->pmk[0].time_left = alive ? BRCMF_PMKSA_NO_EXPIRY : 0; + } + + pmk_op->length = cpu_to_le16(length); + + ret = brcmf_fil_iovar_data_set(ifp, "pmkid_info", pmk_op, sizeof(*pmk_op)); + kfree(pmk_op); + return ret; +} + static __used s32 brcmf_update_pmklist(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp) { @@ -4014,6 +4111,14 @@ brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, if (!check_vif_up(ifp->vif)) return -EIO; + brcmf_dbg(CONN, "set_pmksa - PMK bssid: %pM =\n", pmksa->bssid); + brcmf_dbg(CONN, "%*ph\n", WLAN_PMKID_LEN, pmksa->pmkid); + + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3)) + return brcmf_pmksa_v3_op(ifp, pmksa, true); + + /* TODO: implement PMKID_V2 */ + npmk = le32_to_cpu(cfg->pmk_list.npmk); for (i = 0; i < npmk; i++) if (!memcmp(pmksa->bssid, pmk[i].bssid, ETH_ALEN)) @@ -4030,9 +4135,6 @@ brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, return -EINVAL; } - brcmf_dbg(CONN, "set_pmksa - PMK bssid: %pM =\n", pmk[npmk].bssid); - brcmf_dbg(CONN, "%*ph\n", WLAN_PMKID_LEN, pmk[npmk].pmkid); - err = brcmf_update_pmklist(cfg, ifp); brcmf_dbg(TRACE, "Exit\n"); @@ -4056,6 +4158,11 @@ brcmf_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *ndev, brcmf_dbg(CONN, "del_pmksa - PMK bssid = %pM\n", pmksa->bssid); + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3)) + return brcmf_pmksa_v3_op(ifp, pmksa, false); + + /* TODO: implement PMKID_V2 */ + npmk = le32_to_cpu(cfg->pmk_list.npmk); for (i = 0; i < npmk; i++) if (!memcmp(pmksa->bssid, pmk[i].bssid, ETH_ALEN)) @@ -4092,6 +4199,11 @@ brcmf_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *ndev) if (!check_vif_up(ifp->vif)) return -EIO; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3)) + return brcmf_pmksa_v3_op(ifp, NULL, false); + + /* TODO: implement PMKID_V2 */ + memset(&cfg->pmk_list, 0, sizeof(cfg->pmk_list)); err = brcmf_update_pmklist(cfg, ifp); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c index 4ec7773b6906..99502de31d56 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c @@ -212,8 +212,8 @@ struct sbsocramregs { #define ARMCR4_TCBANB_MASK 0xf #define ARMCR4_TCBANB_SHIFT 0 -#define ARMCR4_BSZ_MASK 0x3f -#define ARMCR4_BSZ_MULT 8192 +#define ARMCR4_BSZ_MASK 0x7f +#define ARMCR4_BLK_1K_MASK 0x200 struct brcmf_core_priv { struct brcmf_core pub; @@ -675,7 +675,8 @@ static u32 brcmf_chip_sysmem_ramsize(struct brcmf_core_priv *sysmem) } /** Return the TCM-RAM size of the ARMCR4 core. */ -static u32 brcmf_chip_tcm_ramsize(struct brcmf_core_priv *cr4) +static u32 brcmf_chip_tcm_ramsize(struct brcmf_chip_priv *ci, + struct brcmf_core_priv *cr4) { u32 corecap; u32 memsize = 0; @@ -683,6 +684,7 @@ static u32 brcmf_chip_tcm_ramsize(struct brcmf_core_priv *cr4) u32 nbb; u32 totb; u32 bxinfo; + u32 blksize; u32 idx; corecap = brcmf_chip_core_read32(cr4, ARMCR4_CAP); @@ -694,7 +696,12 @@ static u32 brcmf_chip_tcm_ramsize(struct brcmf_core_priv *cr4) for (idx = 0; idx < totb; idx++) { brcmf_chip_core_write32(cr4, ARMCR4_BANKIDX, idx); bxinfo = brcmf_chip_core_read32(cr4, ARMCR4_BANKINFO); - memsize += ((bxinfo & ARMCR4_BSZ_MASK) + 1) * ARMCR4_BSZ_MULT; + if (bxinfo & ARMCR4_BLK_1K_MASK) + blksize = 1024; + else + blksize = 8192; + + memsize += ((bxinfo & ARMCR4_BSZ_MASK) + 1) * blksize; } return memsize; @@ -727,11 +734,17 @@ static u32 brcmf_chip_tcm_rambase(struct brcmf_chip_priv *ci) return 0x200000; case BRCM_CC_4359_CHIP_ID: return (ci->pub.chiprev < 9) ? 0x180000 : 0x160000; + case BRCM_CC_4355_CHIP_ID: case BRCM_CC_4364_CHIP_ID: case CY_CC_4373_CHIP_ID: return 0x160000; case CY_CC_43752_CHIP_ID: + case BRCM_CC_4377_CHIP_ID: return 0x170000; + case BRCM_CC_4378_CHIP_ID: + return 0x352000; + case BRCM_CC_4387_CHIP_ID: + return 0x740000; default: brcmf_err("unknown chip: %s\n", ci->pub.name); break; @@ -749,7 +762,7 @@ int brcmf_chip_get_raminfo(struct brcmf_chip *pub) mem = brcmf_chip_get_core(&ci->pub, BCMA_CORE_ARM_CR4); if (mem) { mem_core = container_of(mem, struct brcmf_core_priv, pub); - ci->pub.ramsize = brcmf_chip_tcm_ramsize(mem_core); + ci->pub.ramsize = brcmf_chip_tcm_ramsize(ci, mem_core); ci->pub.rambase = brcmf_chip_tcm_rambase(ci); if (ci->pub.rambase == INVALID_RAMBASE) { brcmf_err("RAM base not provided with ARM CR4 core\n"); @@ -1286,15 +1299,18 @@ static bool brcmf_chip_cm3_set_active(struct brcmf_chip_priv *chip) static inline void brcmf_chip_cr4_set_passive(struct brcmf_chip_priv *chip) { + int i; struct brcmf_core *core; brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CR4); - core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_80211); - brcmf_chip_resetcore(core, D11_BCMA_IOCTL_PHYRESET | - D11_BCMA_IOCTL_PHYCLOCKEN, - D11_BCMA_IOCTL_PHYCLOCKEN, - D11_BCMA_IOCTL_PHYCLOCKEN); + /* Disable the cores only and let the firmware enable them. + * Releasing reset ourselves breaks BCM4387 in weird ways. + */ + for (i = 0; (core = brcmf_chip_get_d11core(&chip->pub, i)); i++) + brcmf_chip_coredisable(core, D11_BCMA_IOCTL_PHYRESET | + D11_BCMA_IOCTL_PHYCLOCKEN, + D11_BCMA_IOCTL_PHYCLOCKEN); } static bool brcmf_chip_cr4_set_active(struct brcmf_chip_priv *chip, u32 rstvec) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c index fe01da9e620d..280ca0f1ba18 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c @@ -101,7 +101,7 @@ void brcmf_c_set_joinpref_default(struct brcmf_if *ifp) static int brcmf_c_download(struct brcmf_if *ifp, u16 flag, struct brcmf_dload_data_le *dload_buf, - u32 len) + u32 len, const char *var) { s32 err; @@ -112,18 +112,17 @@ static int brcmf_c_download(struct brcmf_if *ifp, u16 flag, dload_buf->crc = cpu_to_le32(0); len = sizeof(*dload_buf) + len - 1; - err = brcmf_fil_iovar_data_set(ifp, "clmload", dload_buf, len); + err = brcmf_fil_iovar_data_set(ifp, var, dload_buf, len); return err; } -static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) +static int brcmf_c_download_blob(struct brcmf_if *ifp, + const void *data, size_t size, + const char *loadvar, const char *statvar) { struct brcmf_pub *drvr = ifp->drvr; - struct brcmf_bus *bus = drvr->bus_if; struct brcmf_dload_data_le *chunk_buf; - const struct firmware *clm = NULL; - u8 clm_name[BRCMF_FW_NAME_LEN]; u32 chunk_len; u32 datalen; u32 cumulative_len; @@ -133,27 +132,11 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) brcmf_dbg(TRACE, "Enter\n"); - memset(clm_name, 0, sizeof(clm_name)); - err = brcmf_bus_get_fwname(bus, ".clm_blob", clm_name); - if (err) { - bphy_err(drvr, "get CLM blob file name failed (%d)\n", err); - return err; - } - - err = firmware_request_nowarn(&clm, clm_name, bus->dev); - if (err) { - brcmf_info("no clm_blob available (err=%d), device may have limited channels available\n", - err); - return 0; - } - chunk_buf = kzalloc(sizeof(*chunk_buf) + MAX_CHUNK_LEN - 1, GFP_KERNEL); - if (!chunk_buf) { - err = -ENOMEM; - goto done; - } + if (!chunk_buf) + return -ENOMEM; - datalen = clm->size; + datalen = size; cumulative_len = 0; do { if (datalen > MAX_CHUNK_LEN) { @@ -162,9 +145,10 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) chunk_len = datalen; dl_flag |= DL_END; } - memcpy(chunk_buf->data, clm->data + cumulative_len, chunk_len); + memcpy(chunk_buf->data, data + cumulative_len, chunk_len); - err = brcmf_c_download(ifp, dl_flag, chunk_buf, chunk_len); + err = brcmf_c_download(ifp, dl_flag, chunk_buf, chunk_len, + loadvar); dl_flag &= ~DL_BEGIN; @@ -173,20 +157,81 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) } while ((datalen > 0) && (err == 0)); if (err) { - bphy_err(drvr, "clmload (%zu byte file) failed (%d)\n", - clm->size, err); - /* Retrieve clmload_status and print */ - err = brcmf_fil_iovar_int_get(ifp, "clmload_status", &status); + bphy_err(drvr, "%s (%zu byte file) failed (%d)\n", + loadvar, size, err); + /* Retrieve status and print */ + err = brcmf_fil_iovar_int_get(ifp, statvar, &status); if (err) - bphy_err(drvr, "get clmload_status failed (%d)\n", err); + bphy_err(drvr, "get %s failed (%d)\n", statvar, err); else - brcmf_dbg(INFO, "clmload_status=%d\n", status); + brcmf_dbg(INFO, "%s=%d\n", statvar, status); err = -EIO; } kfree(chunk_buf); -done: - release_firmware(clm); + return err; +} + +static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_bus *bus = drvr->bus_if; + const struct firmware *fw = NULL; + s32 err; + + brcmf_dbg(TRACE, "Enter\n"); + + err = brcmf_bus_get_blob(bus, &fw, BRCMF_BLOB_CLM); + if (err || !fw) { + brcmf_info("no clm_blob available (err=%d), device may have limited channels available\n", + err); + return 0; + } + + err = brcmf_c_download_blob(ifp, fw->data, fw->size, + "clmload", "clmload_status"); + + release_firmware(fw); + return err; +} + +static int brcmf_c_process_txcap_blob(struct brcmf_if *ifp) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_bus *bus = drvr->bus_if; + const struct firmware *fw = NULL; + s32 err; + + brcmf_dbg(TRACE, "Enter\n"); + + err = brcmf_bus_get_blob(bus, &fw, BRCMF_BLOB_TXCAP); + if (err || !fw) { + brcmf_info("no txcap_blob available (err=%d)\n", err); + return 0; + } + + brcmf_info("TxCap blob found, loading\n"); + err = brcmf_c_download_blob(ifp, fw->data, fw->size, + "txcapload", "txcapload_status"); + + release_firmware(fw); + return err; +} + +static int brcmf_c_process_cal_blob(struct brcmf_if *ifp) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_mp_device *settings = drvr->settings; + s32 err; + + brcmf_dbg(TRACE, "Enter\n"); + + if (!settings->cal_blob || !settings->cal_size) + return 0; + + brcmf_info("Calibration blob provided by platform, loading\n"); + err = brcmf_c_download_blob(ifp, settings->cal_blob, settings->cal_size, + "calload", "calload_status"); return err; } @@ -267,6 +312,20 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) goto done; } + /* Do TxCap downloading, if needed */ + err = brcmf_c_process_txcap_blob(ifp); + if (err < 0) { + bphy_err(drvr, "download TxCap blob file failed, %d\n", err); + goto done; + } + + /* Download external calibration blob, if available */ + err = brcmf_c_process_cal_blob(ifp); + if (err < 0) { + bphy_err(drvr, "download calibration blob file failed, %d\n", err); + goto done; + } + /* query for 'ver' to get version info from firmware */ memset(buf, 0, sizeof(buf)); err = brcmf_fil_iovar_data_get(ifp, "ver", buf, sizeof(buf)); @@ -456,6 +515,7 @@ struct brcmf_mp_device *brcmf_get_module_param(struct device *dev, /* No platform data for this device, try OF and DMI data */ brcmf_dmi_probe(settings, chip, chiprev); brcmf_of_probe(dev, bus_type, settings); + brcmf_acpi_probe(dev, bus_type, settings); } return settings; } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h index 15accc88d5c0..39e8abcf0522 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h @@ -51,6 +51,9 @@ struct brcmf_mp_device { struct brcmfmac_pd_cc *country_codes; const char *board_type; unsigned char mac[ETH_ALEN]; + const char *antenna_sku; + const void *cal_blob; + int cal_size; union { struct brcmfmac_sdio_pd sdio; } bus; @@ -73,6 +76,15 @@ static inline void brcmf_dmi_probe(struct brcmf_mp_device *settings, u32 chip, u32 chiprev) {} #endif +#ifdef CONFIG_ACPI +void brcmf_acpi_probe(struct device *dev, enum brcmf_bus_type bus_type, + struct brcmf_mp_device *settings); +#else +static inline void brcmf_acpi_probe(struct device *dev, + enum brcmf_bus_type bus_type, + struct brcmf_mp_device *settings) {} +#endif + u8 brcmf_map_prio_to_prec(void *cfg, u8 prio); u8 brcmf_map_prio_to_aci(void *cfg, u8 prio); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index d2ac844e1e9f..ac873677343c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -126,6 +126,53 @@ static void brcmf_feat_firmware_overrides(struct brcmf_pub *drv) drv->feat_flags |= feat_flags; } +struct brcmf_feat_wlcfeat { + u16 min_ver_major; + u16 min_ver_minor; + u32 feat_flags; +}; + +static const struct brcmf_feat_wlcfeat brcmf_feat_wlcfeat_map[] = { + { 12, 0, BIT(BRCMF_FEAT_PMKID_V2) }, + { 13, 0, BIT(BRCMF_FEAT_PMKID_V3) }, +}; + +static void brcmf_feat_wlc_version_overrides(struct brcmf_pub *drv) +{ + struct brcmf_if *ifp = brcmf_get_ifp(drv, 0); + const struct brcmf_feat_wlcfeat *e; + struct brcmf_wlc_version_le ver; + u32 feat_flags = 0; + int i, err, major, minor; + + err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver, sizeof(ver)); + if (err) + return; + + major = le16_to_cpu(ver.wlc_ver_major); + minor = le16_to_cpu(ver.wlc_ver_minor); + + brcmf_dbg(INFO, "WLC version: %d.%d\n", major, minor); + + for (i = 0; i < ARRAY_SIZE(brcmf_feat_wlcfeat_map); i++) { + e = &brcmf_feat_wlcfeat_map[i]; + if (major > e->min_ver_major || + (major == e->min_ver_major && + minor >= e->min_ver_minor)) { + feat_flags |= e->feat_flags; + } + } + + if (!feat_flags) + return; + + for (i = 0; i < BRCMF_FEAT_LAST; i++) + if (feat_flags & BIT(i)) + brcmf_dbg(INFO, "enabling firmware feature: %s\n", + brcmf_feat_names[i]); + drv->feat_flags |= feat_flags; +} + /** * brcmf_feat_iovar_int_get() - determine feature through iovar query. * @@ -288,6 +335,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_RANDOM_MAC); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_FWSUP, "sup_wpa"); + brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_SCAN_V2, "scan_ver"); if (drvr->settings->feature_disable) { brcmf_dbg(INFO, "Features: 0x%02x, disable: 0x%02x\n", @@ -296,6 +344,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) ifp->drvr->feat_flags &= ~drvr->settings->feature_disable; } + brcmf_feat_wlc_version_overrides(drvr); brcmf_feat_firmware_overrides(drvr); /* set chip related quirks */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index d1f4257af696..becbcc50d57a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -29,6 +29,7 @@ * DOT11H: firmware supports 802.11h * SAE: simultaneous authentication of equals * FWAUTH: Firmware authenticator + * SCAN_V2: Version 2 scan params */ #define BRCMF_FEAT_LIST \ BRCMF_FEAT_DEF(MBSS) \ @@ -51,7 +52,10 @@ BRCMF_FEAT_DEF(MONITOR_FMT_HW_RX_HDR) \ BRCMF_FEAT_DEF(DOT11H) \ BRCMF_FEAT_DEF(SAE) \ - BRCMF_FEAT_DEF(FWAUTH) + BRCMF_FEAT_DEF(FWAUTH) \ + BRCMF_FEAT_DEF(SCAN_V2) \ + BRCMF_FEAT_DEF(PMKID_V2) \ + BRCMF_FEAT_DEF(PMKID_V3) /* * Quirks: diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c index dcbe55b56e43..fbfc9458d240 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c @@ -21,6 +21,8 @@ #define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */ #define BRCMF_FW_NVRAM_PCIEDEV_LEN 10 /* pcie/1/4/ + \0 */ #define BRCMF_FW_DEFAULT_BOARDREV "boardrev=0xff" +#define BRCMF_FW_MACADDR_FMT "macaddr=%pM" +#define BRCMF_FW_MACADDR_LEN (7 + ETH_ALEN * 3) enum nvram_parser_state { IDLE, @@ -57,6 +59,7 @@ struct nvram_parser { bool multi_dev_v1; bool multi_dev_v2; bool boardrev_found; + bool strip_mac; }; /* @@ -121,6 +124,10 @@ static enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp) nvp->multi_dev_v2 = true; if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0) nvp->boardrev_found = true; + /* strip macaddr if platform MAC overrides */ + if (nvp->strip_mac && + strncmp(&nvp->data[nvp->entry], "macaddr", 7) == 0) + st = COMMENT; } else if (!is_nvram_char(c) || c == ' ') { brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n", nvp->line, nvp->column); @@ -209,6 +216,7 @@ static int brcmf_init_nvram_parser(struct nvram_parser *nvp, size = data_len; /* Add space for properties we may add */ size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1; + size += BRCMF_FW_MACADDR_LEN + 1; /* Alloc for extra 0 byte + roundup by 4 + length field */ size += 1 + 3 + sizeof(u32); nvp->nvram = kzalloc(size, GFP_KERNEL); @@ -368,22 +376,34 @@ static void brcmf_fw_add_defaults(struct nvram_parser *nvp) nvp->nvram_len++; } +static void brcmf_fw_add_macaddr(struct nvram_parser *nvp, u8 *mac) +{ + BUG_ON(snprintf(&nvp->nvram[nvp->nvram_len], BRCMF_FW_MACADDR_LEN + 1, + BRCMF_FW_MACADDR_FMT, mac) != BRCMF_FW_MACADDR_LEN); + nvp->nvram_len += BRCMF_FW_MACADDR_LEN + 1; +} + /* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil * and ending in a NUL. Removes carriage returns, empty lines, comment lines, * and converts newlines to NULs. Shortens buffer as needed and pads with NULs. * End of buffer is completed with token identifying length of buffer. */ static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len, - u32 *new_length, u16 domain_nr, u16 bus_nr) + u32 *new_length, u16 domain_nr, u16 bus_nr, + struct device *dev) { struct nvram_parser nvp; u32 pad; u32 token; __le32 token_le; + u8 mac[ETH_ALEN]; if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0) return NULL; + if (eth_platform_get_mac_address(dev, mac) == 0) + nvp.strip_mac = true; + while (nvp.pos < data_len) { nvp.state = nv_parser_states[nvp.state](&nvp); if (nvp.state == END) @@ -404,6 +424,9 @@ static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len, brcmf_fw_add_defaults(&nvp); + if (nvp.strip_mac) + brcmf_fw_add_macaddr(&nvp, mac); + pad = nvp.nvram_len; *new_length = roundup(nvp.nvram_len + 1, 4); while (pad != *new_length) { @@ -430,6 +453,7 @@ struct brcmf_fw { struct device *dev; struct brcmf_fw_request *req; u32 curpos; + unsigned int board_index; void (*done)(struct device *dev, int err, struct brcmf_fw_request *req); }; @@ -546,7 +570,8 @@ static int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx) if (data) nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length, fwctx->req->domain_nr, - fwctx->req->bus_nr); + fwctx->req->bus_nr, + fwctx->dev); if (free_bcm47xx_nvram) bcm47xx_nvram_release_contents(data); @@ -596,39 +621,50 @@ static int brcmf_fw_complete_request(const struct firmware *fw, static char *brcm_alt_fw_path(const char *path, const char *board_type) { - char alt_path[BRCMF_FW_NAME_LEN]; - char suffix[5]; + char base[BRCMF_FW_NAME_LEN]; + const char *suffix; + char *ret; + + if (!board_type) + return NULL; - strscpy(alt_path, path, BRCMF_FW_NAME_LEN); - /* At least one character + suffix */ - if (strlen(alt_path) < 5) + suffix = strrchr(path, '.'); + if (!suffix || suffix == path) return NULL; - /* strip .txt or .bin at the end */ - strscpy(suffix, alt_path + strlen(alt_path) - 4, 5); - alt_path[strlen(alt_path) - 4] = 0; - strlcat(alt_path, ".", BRCMF_FW_NAME_LEN); - strlcat(alt_path, board_type, BRCMF_FW_NAME_LEN); - strlcat(alt_path, suffix, BRCMF_FW_NAME_LEN); + /* strip extension at the end */ + strscpy(base, path, BRCMF_FW_NAME_LEN); + base[suffix - path] = 0; - return kstrdup(alt_path, GFP_KERNEL); + ret = kasprintf(GFP_KERNEL, "%s.%s%s", base, board_type, suffix); + if (!ret) + brcmf_err("out of memory allocating firmware path for '%s'\n", + path); + + brcmf_dbg(TRACE, "FW alt path: %s\n", ret); + + return ret; } static int brcmf_fw_request_firmware(const struct firmware **fw, struct brcmf_fw *fwctx) { struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; + unsigned int i; int ret; - /* Files can be board-specific, first try a board-specific path */ - if (cur->type == BRCMF_FW_TYPE_NVRAM && fwctx->req->board_type) { + /* Files can be board-specific, first try board-specific paths */ + for (i = 0; i < ARRAY_SIZE(fwctx->req->board_types); i++) { char *alt_path; - alt_path = brcm_alt_fw_path(cur->path, fwctx->req->board_type); + if (!fwctx->req->board_types[i]) + goto fallback; + alt_path = brcm_alt_fw_path(cur->path, + fwctx->req->board_types[i]); if (!alt_path) goto fallback; - ret = request_firmware(fw, alt_path, fwctx->dev); + ret = firmware_request_nowarn(fw, alt_path, fwctx->dev); kfree(alt_path); if (ret == 0) return ret; @@ -662,15 +698,40 @@ static void brcmf_fw_request_done_alt_path(const struct firmware *fw, void *ctx) { struct brcmf_fw *fwctx = ctx; struct brcmf_fw_item *first = &fwctx->req->items[0]; + const char *board_type, *alt_path; int ret = 0; - /* Fall back to canonical path if board firmware not found */ - if (!fw) - ret = request_firmware_nowait(THIS_MODULE, true, first->path, + if (fw) { + brcmf_fw_request_done(fw, ctx); + return; + } + + /* Try next board firmware */ + if (fwctx->board_index < ARRAY_SIZE(fwctx->req->board_types)) { + board_type = fwctx->req->board_types[fwctx->board_index++]; + if (!board_type) + goto fallback; + alt_path = brcm_alt_fw_path(first->path, board_type); + if (!alt_path) + goto fallback; + + ret = request_firmware_nowait(THIS_MODULE, true, alt_path, fwctx->dev, GFP_KERNEL, fwctx, - brcmf_fw_request_done); + brcmf_fw_request_done_alt_path); + kfree(alt_path); + + if (ret < 0) + brcmf_fw_request_done(fw, ctx); + return; + } - if (fw || ret < 0) +fallback: + /* Fall back to canonical path if board firmware not found */ + ret = request_firmware_nowait(THIS_MODULE, true, first->path, + fwctx->dev, GFP_KERNEL, fwctx, + brcmf_fw_request_done); + + if (ret < 0) brcmf_fw_request_done(fw, ctx); } @@ -714,10 +775,11 @@ int brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req, fwctx->done = fw_cb; /* First try alternative board-specific path if any */ - if (fwctx->req->board_type) + if (fwctx->req->board_types[0]) alt_path = brcm_alt_fw_path(first->path, - fwctx->req->board_type); + fwctx->req->board_types[0]); if (alt_path) { + fwctx->board_index++; ret = request_firmware_nowait(THIS_MODULE, true, alt_path, fwctx->dev, GFP_KERNEL, fwctx, brcmf_fw_request_done_alt_path); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h index e290dec9c53d..1266cbaee072 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h @@ -11,6 +11,8 @@ #define BRCMF_FW_DEFAULT_PATH "brcm/" +#define BRCMF_FW_MAX_BOARD_TYPES 8 + /** * struct brcmf_firmware_mapping - Used to map chipid/revmask to firmware * filename and nvram filename. Each bus type implementation should create @@ -66,7 +68,7 @@ struct brcmf_fw_request { u16 domain_nr; u16 bus_nr; u32 n_items; - const char *board_type; + const char *board_types[BRCMF_FW_MAX_BOARD_TYPES]; struct brcmf_fw_item items[]; }; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index c87b829adb0d..1d406649eca2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -48,6 +48,10 @@ /* size of brcmf_scan_params not including variable length array */ #define BRCMF_SCAN_PARAMS_FIXED_SIZE 64 +#define BRCMF_SCAN_PARAMS_V2_FIXED_SIZE 72 + +/* version of brcmf_scan_params structure */ +#define BRCMF_SCAN_PARAMS_VERSION_V2 2 /* masks for channel and ssid count */ #define BRCMF_SCAN_PARAMS_COUNT_MASK 0x0000ffff @@ -67,6 +71,7 @@ #define BRCMF_PRIMARY_KEY (1 << 1) #define DOT11_BSSTYPE_ANY 2 #define BRCMF_ESCAN_REQ_VERSION 1 +#define BRCMF_ESCAN_REQ_VERSION_V2 2 #define BRCMF_MAXRATES_IN_SET 16 /* max # of rates in rateset */ @@ -169,6 +174,10 @@ #define BRCMF_HE_CAP_MCS_MAP_NSS_MAX 8 +#define BRCMF_PMKSA_VER_2 2 +#define BRCMF_PMKSA_VER_3 3 +#define BRCMF_PMKSA_NO_EXPIRY 0xffffffff + /* MAX_CHUNK_LEN is the maximum length for data passing to firmware in each * ioctl. It is relatively small because firmware has small maximum size input * playload restriction for ioctls. @@ -350,6 +359,12 @@ struct brcmf_ssid_le { unsigned char SSID[IEEE80211_MAX_SSID_LEN]; }; +/* Alternate SSID structure used in some places... */ +struct brcmf_ssid8_le { + u8 SSID_len; + unsigned char SSID[IEEE80211_MAX_SSID_LEN]; +}; + struct brcmf_scan_params_le { struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ u8 bssid[ETH_ALEN]; /* default: bcast */ @@ -386,6 +401,45 @@ struct brcmf_scan_params_le { __le16 channel_list[1]; /* list of chanspecs */ }; +struct brcmf_scan_params_v2_le { + __le16 version; /* structure version */ + __le16 length; /* structure length */ + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, + * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT + */ + u8 pad; + __le32 scan_type; /* flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the + * home channel between channel scans + */ + __le32 channel_num; /* count of channels and ssids that follow + * + * low half is count of channels in + * channel_list, 0 means default (use all + * available channels) + * + * high half is entries in struct brcmf_ssid + * array that follows channel_list, aligned for + * s32 (4 bytes) meaning an odd channel count + * implies a 2-byte pad between end of + * channel_list and first ssid + * + * if ssid count is zero, single ssid in the + * fixed parameter portion is assumed, otherwise + * ssid in the fixed portion is ignored + */ + __le16 channel_list[1]; /* list of chanspecs */ +}; + struct brcmf_scan_results { u32 buflen; u32 version; @@ -397,7 +451,10 @@ struct brcmf_escan_params_le { __le32 version; __le16 action; __le16 sync_id; - struct brcmf_scan_params_le params_le; + union { + struct brcmf_scan_params_le params_le; + struct brcmf_scan_params_v2_le params_v2_le; + }; }; struct brcmf_escan_result_le { @@ -517,7 +574,7 @@ struct brcmf_wsec_key_le { struct brcmf_wsec_pmk_le { __le16 key_len; __le16 flags; - u8 key[2 * BRCMF_WSEC_MAX_PSK_LEN + 1]; + u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN]; }; /** @@ -742,6 +799,31 @@ struct brcmf_rev_info_le { }; /** + * struct brcmf_wlc_version_le - firmware revision info. + * + * @version: structure version. + * @length: structure length. + * @epi_ver_major: EPI major version + * @epi_ver_minor: EPI minor version + * @epi_ver_rc: EPI rc version + * @epi_ver_incr: EPI increment version + * @wlc_ver_major: WLC major version + * @wlc_ver_minor: WLC minor version + */ +struct brcmf_wlc_version_le { + __le16 version; + __le16 length; + + __le16 epi_ver_major; + __le16 epi_ver_minor; + __le16 epi_ver_rc; + __le16 epi_ver_incr; + + __le16 wlc_ver_major; + __le16 wlc_ver_minor; +}; + +/** * struct brcmf_assoclist_le - request assoc list. * * @count: indicates number of stations. @@ -804,6 +886,51 @@ struct brcmf_pmksa { }; /** + * struct brcmf_pmksa_v2 - PMK Security Association + * + * @length: Length of the structure. + * @bssid: The AP's BSSID. + * @pmkid: The PMK ID. + * @pmk: PMK material for FILS key derivation. + * @pmk_len: Length of PMK data. + * @ssid: The AP's SSID. + * @fils_cache_id: FILS cache identifier + */ +struct brcmf_pmksa_v2 { + __le16 length; + u8 bssid[ETH_ALEN]; + u8 pmkid[WLAN_PMKID_LEN]; + u8 pmk[WLAN_PMK_LEN_SUITE_B_192]; + __le16 pmk_len; + struct brcmf_ssid8_le ssid; + u16 fils_cache_id; +}; + +/** + * struct brcmf_pmksa_v3 - PMK Security Association + * + * @bssid: The AP's BSSID. + * @pmkid: The PMK ID. + * @pmkid_len: The length of the PMK ID. + * @pmk: PMK material for FILS key derivation. + * @pmk_len: Length of PMK data. + * @fils_cache_id: FILS cache identifier + * @ssid: The AP's SSID. + * @time_left: Remaining time until expiry. 0 = expired, ~0 = no expiry. + */ +struct brcmf_pmksa_v3 { + u8 bssid[ETH_ALEN]; + u8 pmkid[WLAN_PMKID_LEN]; + u8 pmkid_len; + u8 pmk[WLAN_PMK_LEN_SUITE_B_192]; + u8 pmk_len; + __le16 fils_cache_id; + u8 pad; + struct brcmf_ssid8_le ssid; + __le32 time_left; +}; + +/** * struct brcmf_pmk_list_le - List of pmksa's. * * @npmk: Number of pmksa's. @@ -815,6 +942,34 @@ struct brcmf_pmk_list_le { }; /** + * struct brcmf_pmk_list_v2_le - List of pmksa's. + * + * @version: Request version. + * @length: Length of this structure. + * @pmk: PMK SA information. + */ +struct brcmf_pmk_list_v2_le { + __le16 version; + __le16 length; + struct brcmf_pmksa_v2 pmk[BRCMF_MAXPMKID]; +}; + +/** + * struct brcmf_pmk_op_v3_le - Operation on PMKSA list. + * + * @version: Request version. + * @length: Length of this structure. + * @pmk: PMK SA information. + */ +struct brcmf_pmk_op_v3_le { + __le16 version; + __le16 length; + __le16 count; + __le16 pad; + struct brcmf_pmksa_v3 pmk[BRCMF_MAXPMKID]; +}; + +/** * struct brcmf_pno_param_le - PNO scan configuration parameters * * @version: PNO parameters version. diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h index 2e322edbb907..6a849f4a94dd 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h @@ -8,10 +8,10 @@ #ifdef CONFIG_BRCMFMAC_PROTO_MSGBUF #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT_MAX_ITEM 64 -#define BRCMF_H2D_MSGRING_RXPOST_SUBMIT_MAX_ITEM 512 +#define BRCMF_H2D_MSGRING_RXPOST_SUBMIT_MAX_ITEM 1024 #define BRCMF_D2H_MSGRING_CONTROL_COMPLETE_MAX_ITEM 64 #define BRCMF_D2H_MSGRING_TX_COMPLETE_MAX_ITEM 1024 -#define BRCMF_D2H_MSGRING_RX_COMPLETE_MAX_ITEM 512 +#define BRCMF_D2H_MSGRING_RX_COMPLETE_MAX_ITEM 1024 #define BRCMF_H2D_TXFLOWRING_MAX_ITEM 512 #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT_ITEMSIZE 40 diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c index 083ac58f466d..60a97277d798 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c @@ -64,14 +64,31 @@ void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type, { struct brcmfmac_sdio_pd *sdio = &settings->bus.sdio; struct device_node *root, *np = dev->of_node; + const char *prop; int irq; int err; u32 irqf; u32 val; + /* Apple ARM64 platforms have their own idea of board type, passed in + * via the device tree. They also have an antenna SKU parameter + */ + if (!of_property_read_string(np, "brcm,board-type", &prop)) + settings->board_type = prop; + + if (!of_property_read_string(np, "apple,antenna-sku", &prop)) + settings->antenna_sku = prop; + + /* The WLAN calibration blob is normally stored in SROM, but Apple + * ARM64 platforms pass it via the DT instead. + */ + prop = of_get_property(np, "brcm,cal-blob", &settings->cal_size); + if (prop && settings->cal_size) + settings->cal_blob = prop; + /* Set board-type to the first string of the machine compatible prop */ root = of_find_node_by_path("/"); - if (root) { + if (root && !settings->board_type) { int i; char *board_type; const char *tmp; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 97f0f13dfe50..d26a412c9ca3 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -13,6 +13,7 @@ #include <linux/bcma/bcma.h> #include <linux/sched.h> #include <linux/io.h> +#include <linux/random.h> #include <asm/unaligned.h> #include <soc.h> @@ -49,16 +50,22 @@ enum brcmf_pcie_state { BRCMF_FW_DEF(43602, "brcmfmac43602-pcie"); BRCMF_FW_DEF(4350, "brcmfmac4350-pcie"); BRCMF_FW_DEF(4350C, "brcmfmac4350c2-pcie"); +BRCMF_FW_CLM_DEF(4355C1, "brcmfmac4355c1-pcie"); BRCMF_FW_CLM_DEF(4356, "brcmfmac4356-pcie"); BRCMF_FW_CLM_DEF(43570, "brcmfmac43570-pcie"); BRCMF_FW_DEF(4358, "brcmfmac4358-pcie"); BRCMF_FW_DEF(4359, "brcmfmac4359-pcie"); -BRCMF_FW_DEF(4364, "brcmfmac4364-pcie"); +BRCMF_FW_CLM_DEF(4364B2, "brcmfmac4364b2-pcie"); +BRCMF_FW_CLM_DEF(4364B3, "brcmfmac4364b3-pcie"); BRCMF_FW_DEF(4365B, "brcmfmac4365b-pcie"); BRCMF_FW_DEF(4365C, "brcmfmac4365c-pcie"); BRCMF_FW_DEF(4366B, "brcmfmac4366b-pcie"); BRCMF_FW_DEF(4366C, "brcmfmac4366c-pcie"); BRCMF_FW_DEF(4371, "brcmfmac4371-pcie"); +BRCMF_FW_CLM_DEF(4377B3, "brcmfmac4377b3-pcie"); +BRCMF_FW_CLM_DEF(4378B1, "brcmfmac4378b1-pcie"); +BRCMF_FW_CLM_DEF(4378B3, "brcmfmac4378b3-pcie"); +BRCMF_FW_CLM_DEF(4387C2, "brcmfmac4387c2-pcie"); /* firmware config files */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.txt"); @@ -66,6 +73,8 @@ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.txt"); /* per-board firmware binaries */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.bin"); +MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.clm_blob"); +MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.txcap_blob"); static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_43602_CHIP_ID, 0xFFFFFFFF, 43602), @@ -73,13 +82,15 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_4350_CHIP_ID, 0x000000FF, 4350C), BRCMF_FW_ENTRY(BRCM_CC_4350_CHIP_ID, 0xFFFFFF00, 4350), BRCMF_FW_ENTRY(BRCM_CC_43525_CHIP_ID, 0xFFFFFFF0, 4365C), + BRCMF_FW_ENTRY(BRCM_CC_4355_CHIP_ID, 0xFFFFFFFF, 4355C1), /* 12 */ BRCMF_FW_ENTRY(BRCM_CC_4356_CHIP_ID, 0xFFFFFFFF, 4356), BRCMF_FW_ENTRY(BRCM_CC_43567_CHIP_ID, 0xFFFFFFFF, 43570), BRCMF_FW_ENTRY(BRCM_CC_43569_CHIP_ID, 0xFFFFFFFF, 43570), BRCMF_FW_ENTRY(BRCM_CC_43570_CHIP_ID, 0xFFFFFFFF, 43570), BRCMF_FW_ENTRY(BRCM_CC_4358_CHIP_ID, 0xFFFFFFFF, 4358), BRCMF_FW_ENTRY(BRCM_CC_4359_CHIP_ID, 0xFFFFFFFF, 4359), - BRCMF_FW_ENTRY(BRCM_CC_4364_CHIP_ID, 0xFFFFFFFF, 4364), + BRCMF_FW_ENTRY(BRCM_CC_4364_CHIP_ID, 0x0000000F, 4364B2), /* 3 */ + BRCMF_FW_ENTRY(BRCM_CC_4364_CHIP_ID, 0xFFFFFFF0, 4364B3), /* 4 */ BRCMF_FW_ENTRY(BRCM_CC_4365_CHIP_ID, 0x0000000F, 4365B), BRCMF_FW_ENTRY(BRCM_CC_4365_CHIP_ID, 0xFFFFFFF0, 4365C), BRCMF_FW_ENTRY(BRCM_CC_4366_CHIP_ID, 0x0000000F, 4366B), @@ -87,6 +98,10 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_43664_CHIP_ID, 0xFFFFFFF0, 4366C), BRCMF_FW_ENTRY(BRCM_CC_43666_CHIP_ID, 0xFFFFFFF0, 4366C), BRCMF_FW_ENTRY(BRCM_CC_4371_CHIP_ID, 0xFFFFFFFF, 4371), + BRCMF_FW_ENTRY(BRCM_CC_4377_CHIP_ID, 0xFFFFFFFF, 4377B3), /* 4 */ + BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0x0000000F, 4378B1), /* 3 */ + BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0xFFFFFFE0, 4378B3), /* 5 */ + BRCMF_FW_ENTRY(BRCM_CC_4387_CHIP_ID, 0xFFFFFFFF, 4387C2), /* 7 */ }; #define BRCMF_PCIE_FW_UP_TIMEOUT 5000 /* msec */ @@ -118,6 +133,12 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0 0x140 #define BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1 0x144 +#define BRCMF_PCIE_64_PCIE2REG_INTMASK 0xC14 +#define BRCMF_PCIE_64_PCIE2REG_MAILBOXINT 0xC30 +#define BRCMF_PCIE_64_PCIE2REG_MAILBOXMASK 0xC34 +#define BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0 0xA20 +#define BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1 0xA24 + #define BRCMF_PCIE2_INTA 0x01 #define BRCMF_PCIE2_INTB 0x02 @@ -137,6 +158,8 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_MB_INT_D2H3_DB0 0x400000 #define BRCMF_PCIE_MB_INT_D2H3_DB1 0x800000 +#define BRCMF_PCIE_MB_INT_FN0 (BRCMF_PCIE_MB_INT_FN0_0 | \ + BRCMF_PCIE_MB_INT_FN0_1) #define BRCMF_PCIE_MB_INT_D2H_DB (BRCMF_PCIE_MB_INT_D2H0_DB0 | \ BRCMF_PCIE_MB_INT_D2H0_DB1 | \ BRCMF_PCIE_MB_INT_D2H1_DB0 | \ @@ -146,6 +169,40 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_PCIE_MB_INT_D2H3_DB0 | \ BRCMF_PCIE_MB_INT_D2H3_DB1) +#define BRCMF_PCIE_64_MB_INT_D2H0_DB0 0x1 +#define BRCMF_PCIE_64_MB_INT_D2H0_DB1 0x2 +#define BRCMF_PCIE_64_MB_INT_D2H1_DB0 0x4 +#define BRCMF_PCIE_64_MB_INT_D2H1_DB1 0x8 +#define BRCMF_PCIE_64_MB_INT_D2H2_DB0 0x10 +#define BRCMF_PCIE_64_MB_INT_D2H2_DB1 0x20 +#define BRCMF_PCIE_64_MB_INT_D2H3_DB0 0x40 +#define BRCMF_PCIE_64_MB_INT_D2H3_DB1 0x80 +#define BRCMF_PCIE_64_MB_INT_D2H4_DB0 0x100 +#define BRCMF_PCIE_64_MB_INT_D2H4_DB1 0x200 +#define BRCMF_PCIE_64_MB_INT_D2H5_DB0 0x400 +#define BRCMF_PCIE_64_MB_INT_D2H5_DB1 0x800 +#define BRCMF_PCIE_64_MB_INT_D2H6_DB0 0x1000 +#define BRCMF_PCIE_64_MB_INT_D2H6_DB1 0x2000 +#define BRCMF_PCIE_64_MB_INT_D2H7_DB0 0x4000 +#define BRCMF_PCIE_64_MB_INT_D2H7_DB1 0x8000 + +#define BRCMF_PCIE_64_MB_INT_D2H_DB (BRCMF_PCIE_64_MB_INT_D2H0_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H0_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H1_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H1_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H2_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H2_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H3_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H3_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H4_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H4_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H5_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H5_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H6_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H6_DB1 | \ + BRCMF_PCIE_64_MB_INT_D2H7_DB0 | \ + BRCMF_PCIE_64_MB_INT_D2H7_DB1) + #define BRCMF_PCIE_SHARED_VERSION_7 7 #define BRCMF_PCIE_MIN_SHARED_VERSION 5 #define BRCMF_PCIE_MAX_SHARED_VERSION BRCMF_PCIE_SHARED_VERSION_7 @@ -255,12 +312,26 @@ struct brcmf_pcie_core_info { u32 wrapbase; }; +#define BRCMF_OTP_MAX_PARAM_LEN 16 + +struct brcmf_otp_params { + char module[BRCMF_OTP_MAX_PARAM_LEN]; + char vendor[BRCMF_OTP_MAX_PARAM_LEN]; + char version[BRCMF_OTP_MAX_PARAM_LEN]; + bool valid; +}; + struct brcmf_pciedev_info { enum brcmf_pcie_state state; bool in_irq; struct pci_dev *pdev; char fw_name[BRCMF_FW_NAME_LEN]; char nvram_name[BRCMF_FW_NAME_LEN]; + char clm_name[BRCMF_FW_NAME_LEN]; + char txcap_name[BRCMF_FW_NAME_LEN]; + const struct firmware *clm_fw; + const struct firmware *txcap_fw; + const struct brcmf_pcie_reginfo *reginfo; void __iomem *regs; void __iomem *tcm; u32 ram_base; @@ -280,6 +351,7 @@ struct brcmf_pciedev_info { void (*write_ptr)(struct brcmf_pciedev_info *devinfo, u32 mem_offset, u16 value); struct brcmf_mp_device *settings; + struct brcmf_otp_params otp; }; struct brcmf_pcie_ringbuf { @@ -346,11 +418,49 @@ static const u32 brcmf_ring_itemsize[BRCMF_NROF_COMMON_MSGRINGS] = { BRCMF_D2H_MSGRING_RX_COMPLETE_ITEMSIZE }; +struct brcmf_pcie_reginfo { + u32 intmask; + u32 mailboxint; + u32 mailboxmask; + u32 h2d_mailbox_0; + u32 h2d_mailbox_1; + u32 int_d2h_db; + u32 int_fn0; +}; + +static const struct brcmf_pcie_reginfo brcmf_reginfo_default = { + .intmask = BRCMF_PCIE_PCIE2REG_INTMASK, + .mailboxint = BRCMF_PCIE_PCIE2REG_MAILBOXINT, + .mailboxmask = BRCMF_PCIE_PCIE2REG_MAILBOXMASK, + .h2d_mailbox_0 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, + .h2d_mailbox_1 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, + .int_d2h_db = BRCMF_PCIE_MB_INT_D2H_DB, + .int_fn0 = BRCMF_PCIE_MB_INT_FN0, +}; + +static const struct brcmf_pcie_reginfo brcmf_reginfo_64 = { + .intmask = BRCMF_PCIE_64_PCIE2REG_INTMASK, + .mailboxint = BRCMF_PCIE_64_PCIE2REG_MAILBOXINT, + .mailboxmask = BRCMF_PCIE_64_PCIE2REG_MAILBOXMASK, + .h2d_mailbox_0 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, + .h2d_mailbox_1 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, + .int_d2h_db = BRCMF_PCIE_64_MB_INT_D2H_DB, + .int_fn0 = 0, +}; + static void brcmf_pcie_setup(struct device *dev, int ret, struct brcmf_fw_request *fwreq); static struct brcmf_fw_request * brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo); +static u16 +brcmf_pcie_read_reg16(struct brcmf_pciedev_info *devinfo, u32 reg_offset) +{ + void __iomem *address = devinfo->regs + reg_offset; + + return ioread16(address); +} + static u32 brcmf_pcie_read_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset) { @@ -496,6 +606,8 @@ brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset, } +#define READCC32(devinfo, reg) brcmf_pcie_read_reg32(devinfo, \ + CHIPCREGOFFS(reg)) #define WRITECC32(devinfo, reg, value) brcmf_pcie_write_reg32(devinfo, \ CHIPCREGOFFS(reg), value) @@ -779,30 +891,29 @@ static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo, static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo) { - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXMASK, 0); + brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, 0); } static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo) { - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXMASK, - BRCMF_PCIE_MB_INT_D2H_DB | - BRCMF_PCIE_MB_INT_FN0_0 | - BRCMF_PCIE_MB_INT_FN0_1); + brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, + devinfo->reginfo->int_d2h_db | + devinfo->reginfo->int_fn0); } static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo) { if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) brcmf_pcie_write_reg32(devinfo, - BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1); + devinfo->reginfo->h2d_mailbox_1, 1); } static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg) { struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)arg; - if (brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT)) { + if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint)) { brcmf_pcie_intr_disable(devinfo); brcmf_dbg(PCIE, "Enter\n"); return IRQ_WAKE_THREAD; @@ -817,15 +928,14 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) u32 status; devinfo->in_irq = true; - status = brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT); + status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint); brcmf_dbg(PCIE, "Enter %x\n", status); if (status) { - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT, + brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, status); - if (status & (BRCMF_PCIE_MB_INT_FN0_0 | - BRCMF_PCIE_MB_INT_FN0_1)) + if (status & devinfo->reginfo->int_fn0) brcmf_pcie_handle_mb_data(devinfo); - if (status & BRCMF_PCIE_MB_INT_D2H_DB) { + if (status & devinfo->reginfo->int_d2h_db) { if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) brcmf_proto_msgbuf_rx_trigger( &devinfo->pdev->dev); @@ -884,8 +994,8 @@ static void brcmf_pcie_release_irq(struct brcmf_pciedev_info *devinfo) if (devinfo->in_irq) brcmf_err(bus, "Still in IRQ (processing) !!!\n"); - status = brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT); - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT, status); + status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint); + brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, status); devinfo->irq_allocated = false; } @@ -937,7 +1047,7 @@ static int brcmf_pcie_ring_mb_ring_bell(void *ctx) brcmf_dbg(PCIE, "RING !\n"); /* Any arbitrary value will do, lets use 1 */ - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1); + brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->h2d_mailbox_0, 1); return 0; } @@ -1382,23 +1492,29 @@ static int brcmf_pcie_get_memdump(struct device *dev, void *data, size_t len) return 0; } -static -int brcmf_pcie_get_fwname(struct device *dev, const char *ext, u8 *fw_name) +static int brcmf_pcie_get_blob(struct device *dev, const struct firmware **fw, + enum brcmf_blob_type type) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); - struct brcmf_fw_request *fwreq; - struct brcmf_fw_name fwnames[] = { - { ext, fw_name }, - }; + struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie; + struct brcmf_pciedev_info *devinfo = buspub->devinfo; - fwreq = brcmf_fw_alloc_request(bus_if->chip, bus_if->chiprev, - brcmf_pcie_fwnames, - ARRAY_SIZE(brcmf_pcie_fwnames), - fwnames, ARRAY_SIZE(fwnames)); - if (!fwreq) - return -ENOMEM; + switch (type) { + case BRCMF_BLOB_CLM: + *fw = devinfo->clm_fw; + devinfo->clm_fw = NULL; + break; + case BRCMF_BLOB_TXCAP: + *fw = devinfo->txcap_fw; + devinfo->txcap_fw = NULL; + break; + default: + return -ENOENT; + } + + if (!*fw) + return -ENOENT; - kfree(fwreq); return 0; } @@ -1445,7 +1561,7 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = { .wowl_config = brcmf_pcie_wowl_config, .get_ramsize = brcmf_pcie_get_ramsize, .get_memdump = brcmf_pcie_get_memdump, - .get_fwname = brcmf_pcie_get_fwname, + .get_blob = brcmf_pcie_get_blob, .reset = brcmf_pcie_reset, }; @@ -1527,6 +1643,13 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, return 0; } +struct brcmf_random_seed_footer { + __le32 length; + __le32 magic; +}; + +#define BRCMF_RANDOM_SEED_MAGIC 0xfeedc0de +#define BRCMF_RANDOM_SEED_LENGTH 0x100 static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, const struct firmware *fw, void *nvram, @@ -1558,11 +1681,32 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, brcmf_pcie_write_ram32(devinfo, devinfo->ci->ramsize - 4, 0); if (nvram) { + size_t rand_len = BRCMF_RANDOM_SEED_LENGTH; + struct brcmf_random_seed_footer footer = { + .length = cpu_to_le32(rand_len), + .magic = cpu_to_le32(BRCMF_RANDOM_SEED_MAGIC), + }; + void *randbuf; + brcmf_dbg(PCIE, "Download NVRAM %s\n", devinfo->nvram_name); address = devinfo->ci->rambase + devinfo->ci->ramsize - nvram_len; memcpy_toio(devinfo->tcm + address, nvram, nvram_len); brcmf_fw_nvram_free(nvram); + + /* Some Apple chips/firmwares expect a buffer of random data + * to be present before NVRAM + */ + brcmf_dbg(PCIE, "Download random seed\n"); + + address -= sizeof(footer); + memcpy_toio(devinfo->tcm + address, &footer, sizeof(footer)); + + address -= rand_len; + randbuf = kzalloc(rand_len, GFP_KERNEL); + get_random_bytes(randbuf, rand_len); + memcpy_toio(devinfo->tcm + address, randbuf, rand_len); + kfree(randbuf); } else { brcmf_dbg(PCIE, "No matching NVRAM file found %s\n", devinfo->nvram_name); @@ -1698,15 +1842,22 @@ static int brcmf_pcie_buscoreprep(void *ctx) static int brcmf_pcie_buscore_reset(void *ctx, struct brcmf_chip *chip) { struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)ctx; - u32 val; + struct brcmf_core *core; + u32 val, reg; devinfo->ci = chip; brcmf_pcie_reset_device(devinfo); - val = brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT); + /* reginfo is not ready yet */ + core = brcmf_chip_get_core(chip, BCMA_CORE_PCIE2); + if (core->rev >= 64) + reg = BRCMF_PCIE_64_PCIE2REG_MAILBOXINT; + else + reg = BRCMF_PCIE_PCIE2REG_MAILBOXINT; + + val = brcmf_pcie_read_reg32(devinfo, reg); if (val != 0xffffffff) - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_MAILBOXINT, - val); + brcmf_pcie_write_reg32(devinfo, reg, val); return 0; } @@ -1729,8 +1880,223 @@ static const struct brcmf_buscore_ops brcmf_pcie_buscore_ops = { .write32 = brcmf_pcie_buscore_write32, }; +#define BRCMF_OTP_SYS_VENDOR 0x15 +#define BRCMF_OTP_BRCM_CIS 0x80 + +#define BRCMF_OTP_VENDOR_HDR 0x00000008 + +static int +brcmf_pcie_parse_otp_sys_vendor(struct brcmf_pciedev_info *devinfo, + u8 *data, size_t size) +{ + int idx = 4; + const char *chip_params; + const char *board_params; + const char *p; + + /* 4-byte header and two empty strings */ + if (size < 6) + return -EINVAL; + + if (get_unaligned_le32(data) != BRCMF_OTP_VENDOR_HDR) + return -EINVAL; + + chip_params = &data[idx]; + + /* Skip first string, including terminator */ + idx += strnlen(chip_params, size - idx) + 1; + if (idx >= size) + return -EINVAL; + + board_params = &data[idx]; + + /* Skip to terminator of second string */ + idx += strnlen(board_params, size - idx); + if (idx >= size) + return -EINVAL; + + /* At this point both strings are guaranteed NUL-terminated */ + brcmf_dbg(PCIE, "OTP: chip_params='%s' board_params='%s'\n", + chip_params, board_params); + + p = skip_spaces(board_params); + while (*p) { + char tag = *p++; + const char *end; + size_t len; + + if (*p++ != '=') /* implicit NUL check */ + return -EINVAL; + + /* *p might be NUL here, if so end == p and len == 0 */ + end = strchrnul(p, ' '); + len = end - p; + + /* leave 1 byte for NUL in destination string */ + if (len > (BRCMF_OTP_MAX_PARAM_LEN - 1)) + return -EINVAL; + + /* Copy len characters plus a NUL terminator */ + switch (tag) { + case 'M': + strscpy(devinfo->otp.module, p, len + 1); + break; + case 'V': + strscpy(devinfo->otp.vendor, p, len + 1); + break; + case 'm': + strscpy(devinfo->otp.version, p, len + 1); + break; + } + + /* Skip to next arg, if any */ + p = skip_spaces(end); + } + + brcmf_dbg(PCIE, "OTP: module=%s vendor=%s version=%s\n", + devinfo->otp.module, devinfo->otp.vendor, + devinfo->otp.version); + + if (!devinfo->otp.module[0] || + !devinfo->otp.vendor[0] || + !devinfo->otp.version[0]) + return -EINVAL; + + devinfo->otp.valid = true; + return 0; +} + +static int +brcmf_pcie_parse_otp(struct brcmf_pciedev_info *devinfo, u8 *otp, size_t size) +{ + int p = 0; + int ret = -EINVAL; + + brcmf_dbg(PCIE, "parse_otp size=%ld\n", size); + + while (p < (size - 1)) { + u8 type = otp[p]; + u8 length = otp[p + 1]; + + if (type == 0) + break; + + if ((p + 2 + length) > size) + break; + + switch (type) { + case BRCMF_OTP_SYS_VENDOR: + brcmf_dbg(PCIE, "OTP @ 0x%x (%d): SYS_VENDOR\n", + p, length); + ret = brcmf_pcie_parse_otp_sys_vendor(devinfo, + &otp[p + 2], + length); + break; + case BRCMF_OTP_BRCM_CIS: + brcmf_dbg(PCIE, "OTP @ 0x%x (%d): BRCM_CIS\n", + p, length); + break; + default: + brcmf_dbg(PCIE, "OTP @ 0x%x (%d): Unknown type 0x%x\n", + p, length, type); + break; + } + + p += 2 + length; + } + + return ret; +} + +static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo) +{ + const struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); + u32 coreid, base, words, idx, sromctl; + u16 *otp; + struct brcmf_core *core; + int ret; + + switch (devinfo->ci->chip) { + case BRCM_CC_4355_CHIP_ID: + coreid = BCMA_CORE_CHIPCOMMON; + base = 0x8c0; + words = 0xb2; + break; + case BRCM_CC_4364_CHIP_ID: + coreid = BCMA_CORE_CHIPCOMMON; + base = 0x8c0; + words = 0x1a0; + break; + case BRCM_CC_4377_CHIP_ID: + case BRCM_CC_4378_CHIP_ID: + coreid = BCMA_CORE_GCI; + base = 0x1120; + words = 0x170; + break; + case BRCM_CC_4387_CHIP_ID: + coreid = BCMA_CORE_GCI; + base = 0x113c; + words = 0x170; + break; + default: + /* OTP not supported on this chip */ + return 0; + } + + core = brcmf_chip_get_core(devinfo->ci, coreid); + if (!core) { + brcmf_err(bus, "No OTP core\n"); + return -ENODEV; + } + + if (coreid == BCMA_CORE_CHIPCOMMON) { + /* Chips with OTP accessed via ChipCommon need additional + * handling to access the OTP + */ + brcmf_pcie_select_core(devinfo, coreid); + sromctl = READCC32(devinfo, sromcontrol); + + if (!(sromctl & BCMA_CC_SROM_CONTROL_OTP_PRESENT)) { + /* Chip lacks OTP, try without it... */ + brcmf_err(bus, + "OTP unavailable, using default firmware\n"); + return 0; + } + + /* Map OTP to shadow area */ + WRITECC32(devinfo, sromcontrol, + sromctl | BCMA_CC_SROM_CONTROL_OTPSEL); + } + + otp = kcalloc(words, sizeof(u16), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + /* Map bus window to SROM/OTP shadow area in core */ + base = brcmf_pcie_buscore_prep_addr(devinfo->pdev, base + core->base); + + brcmf_dbg(PCIE, "OTP data:\n"); + for (idx = 0; idx < words; idx++) { + otp[idx] = brcmf_pcie_read_reg16(devinfo, base + 2 * idx); + brcmf_dbg(PCIE, "[%8x] 0x%04x\n", base + 2 * idx, otp[idx]); + } + + if (coreid == BCMA_CORE_CHIPCOMMON) { + brcmf_pcie_select_core(devinfo, coreid); + WRITECC32(devinfo, sromcontrol, sromctl); + } + + ret = brcmf_pcie_parse_otp(devinfo, (u8 *)otp, 2 * words); + kfree(otp); + + return ret; +} + #define BRCMF_PCIE_FW_CODE 0 #define BRCMF_PCIE_FW_NVRAM 1 +#define BRCMF_PCIE_FW_CLM 2 +#define BRCMF_PCIE_FW_TXCAP 3 static void brcmf_pcie_setup(struct device *dev, int ret, struct brcmf_fw_request *fwreq) @@ -1755,6 +2121,8 @@ static void brcmf_pcie_setup(struct device *dev, int ret, fw = fwreq->items[BRCMF_PCIE_FW_CODE].binary; nvram = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.data; nvram_len = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.len; + devinfo->clm_fw = fwreq->items[BRCMF_PCIE_FW_CLM].binary; + devinfo->txcap_fw = fwreq->items[BRCMF_PCIE_FW_TXCAP].binary; kfree(fwreq); ret = brcmf_chip_get_raminfo(devinfo->ci); @@ -1830,6 +2198,8 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo) struct brcmf_fw_name fwnames[] = { { ".bin", devinfo->fw_name }, { ".txt", devinfo->nvram_name }, + { ".clm_blob", devinfo->clm_name }, + { ".txcap_blob", devinfo->txcap_name }, }; fwreq = brcmf_fw_alloc_request(devinfo->ci->chip, devinfo->ci->chiprev, @@ -1842,11 +2212,54 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo) fwreq->items[BRCMF_PCIE_FW_CODE].type = BRCMF_FW_TYPE_BINARY; fwreq->items[BRCMF_PCIE_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM; fwreq->items[BRCMF_PCIE_FW_NVRAM].flags = BRCMF_FW_REQF_OPTIONAL; - fwreq->board_type = devinfo->settings->board_type; + fwreq->items[BRCMF_PCIE_FW_CLM].type = BRCMF_FW_TYPE_BINARY; + fwreq->items[BRCMF_PCIE_FW_CLM].flags = BRCMF_FW_REQF_OPTIONAL; + fwreq->items[BRCMF_PCIE_FW_TXCAP].type = BRCMF_FW_TYPE_BINARY; + fwreq->items[BRCMF_PCIE_FW_TXCAP].flags = BRCMF_FW_REQF_OPTIONAL; /* NVRAM reserves PCI domain 0 for Broadcom's SDK faked bus */ fwreq->domain_nr = pci_domain_nr(devinfo->pdev->bus) + 1; fwreq->bus_nr = devinfo->pdev->bus->number; + /* Apple platforms with fancy firmware/NVRAM selection */ + if (devinfo->settings->board_type && + devinfo->settings->antenna_sku && + devinfo->otp.valid) { + const struct brcmf_otp_params *otp = &devinfo->otp; + struct device *dev = &devinfo->pdev->dev; + const char **bt = fwreq->board_types; + + brcmf_dbg(PCIE, "Apple board: %s\n", + devinfo->settings->board_type); + + /* Example: apple,shikoku-RASP-m-6.11-X3 */ + bt[0] = devm_kasprintf(dev, GFP_KERNEL, "%s-%s-%s-%s-%s", + devinfo->settings->board_type, + otp->module, otp->vendor, otp->version, + devinfo->settings->antenna_sku); + bt[1] = devm_kasprintf(dev, GFP_KERNEL, "%s-%s-%s-%s", + devinfo->settings->board_type, + otp->module, otp->vendor, otp->version); + bt[2] = devm_kasprintf(dev, GFP_KERNEL, "%s-%s-%s", + devinfo->settings->board_type, + otp->module, otp->vendor); + bt[3] = devm_kasprintf(dev, GFP_KERNEL, "%s-%s", + devinfo->settings->board_type, + otp->module); + bt[4] = devm_kasprintf(dev, GFP_KERNEL, "%s-%s", + devinfo->settings->board_type, + devinfo->settings->antenna_sku); + bt[5] = devinfo->settings->board_type; + + if (!bt[0] || !bt[1] || !bt[2] || !bt[3] || !bt[4]) { + kfree(fwreq); + return NULL; + } + + } else { + brcmf_dbg(PCIE, "Board: %s\n", devinfo->settings->board_type); + fwreq->board_types[0] = devinfo->settings->board_type; + } + return fwreq; } @@ -1858,6 +2271,7 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) struct brcmf_pciedev_info *devinfo; struct brcmf_pciedev *pcie_bus_dev; struct brcmf_bus *bus; + struct brcmf_core *core; brcmf_dbg(PCIE, "Enter %x:%x\n", pdev->vendor, pdev->device); @@ -1876,6 +2290,12 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto fail; } + core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2); + if (core->rev >= 64) + devinfo->reginfo = &brcmf_reginfo_64; + else + devinfo->reginfo = &brcmf_reginfo_default; + pcie_bus_dev = kzalloc(sizeof(*pcie_bus_dev), GFP_KERNEL); if (pcie_bus_dev == NULL) { ret = -ENOMEM; @@ -1918,6 +2338,12 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (ret) goto fail_bus; + ret = brcmf_pcie_read_otp(devinfo); + if (ret) { + brcmf_err(bus, "failed to parse OTP\n"); + goto fail_brcmf; + } + fwreq = brcmf_pcie_prepare_fw_request(devinfo); if (!fwreq) { ret = -ENOMEM; @@ -1981,6 +2407,8 @@ brcmf_pcie_remove(struct pci_dev *pdev) brcmf_pcie_release_ringbuffers(devinfo); brcmf_pcie_reset_device(devinfo); brcmf_pcie_release_resource(devinfo); + release_firmware(devinfo->clm_fw); + release_firmware(devinfo->txcap_fw); if (devinfo->ci) brcmf_chip_detach(devinfo->ci); @@ -2038,7 +2466,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev) brcmf_dbg(PCIE, "Enter, dev=%p, bus=%p\n", dev, bus); /* Check if device is still up and running, if so we are ready */ - if (brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_INTMASK) != 0) { + if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) { brcmf_dbg(PCIE, "Try to wakeup device....\n"); if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM)) goto cleanup; @@ -2086,6 +2514,7 @@ static const struct pci_device_id brcmf_pcie_devid_table[] = { BRCMF_PCIE_DEVICE(BRCM_PCIE_4350_DEVICE_ID), BRCMF_PCIE_DEVICE_SUB(0x4355, BRCM_PCIE_VENDOR_ID_BROADCOM, 0x4355), BRCMF_PCIE_DEVICE(BRCM_PCIE_4354_RAW_DEVICE_ID), + BRCMF_PCIE_DEVICE(BRCM_PCIE_4355_DEVICE_ID), BRCMF_PCIE_DEVICE(BRCM_PCIE_4356_DEVICE_ID), BRCMF_PCIE_DEVICE(BRCM_PCIE_43567_DEVICE_ID), BRCMF_PCIE_DEVICE(BRCM_PCIE_43570_DEVICE_ID), @@ -2105,6 +2534,9 @@ static const struct pci_device_id brcmf_pcie_devid_table[] = { BRCMF_PCIE_DEVICE(BRCM_PCIE_4366_2G_DEVICE_ID), BRCMF_PCIE_DEVICE(BRCM_PCIE_4366_5G_DEVICE_ID), BRCMF_PCIE_DEVICE(BRCM_PCIE_4371_DEVICE_ID), + BRCMF_PCIE_DEVICE(BRCM_PCIE_4377_DEVICE_ID), + BRCMF_PCIE_DEVICE(BRCM_PCIE_4378_DEVICE_ID), + BRCMF_PCIE_DEVICE(BRCM_PCIE_4387_DEVICE_ID), { /* end: all zeroes */ } }; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index 212fbbe1cd7e..2b71991f7d9b 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -4130,23 +4130,24 @@ brcmf_sdio_watchdog(struct timer_list *t) } } -static -int brcmf_sdio_get_fwname(struct device *dev, const char *ext, u8 *fw_name) +static int brcmf_sdio_get_blob(struct device *dev, const struct firmware **fw, + enum brcmf_blob_type type) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); - struct brcmf_fw_request *fwreq; - struct brcmf_fw_name fwnames[] = { - { ext, fw_name }, - }; + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; - fwreq = brcmf_fw_alloc_request(bus_if->chip, bus_if->chiprev, - brcmf_sdio_fwnames, - ARRAY_SIZE(brcmf_sdio_fwnames), - fwnames, ARRAY_SIZE(fwnames)); - if (!fwreq) - return -ENOMEM; + switch (type) { + case BRCMF_BLOB_CLM: + *fw = sdiodev->clm_fw; + sdiodev->clm_fw = NULL; + break; + default: + return -ENOENT; + } + + if (!*fw) + return -ENOENT; - kfree(fwreq); return 0; } @@ -4189,13 +4190,14 @@ static const struct brcmf_bus_ops brcmf_sdio_bus_ops = { .wowl_config = brcmf_sdio_wowl_config, .get_ramsize = brcmf_sdio_bus_get_ramsize, .get_memdump = brcmf_sdio_bus_get_memdump, - .get_fwname = brcmf_sdio_get_fwname, + .get_blob = brcmf_sdio_get_blob, .debugfs_create = brcmf_sdio_debugfs_create, .reset = brcmf_sdio_bus_reset }; #define BRCMF_SDIO_FW_CODE 0 #define BRCMF_SDIO_FW_NVRAM 1 +#define BRCMF_SDIO_FW_CLM 2 static void brcmf_sdio_firmware_callback(struct device *dev, int err, struct brcmf_fw_request *fwreq) @@ -4218,6 +4220,7 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, code = fwreq->items[BRCMF_SDIO_FW_CODE].binary; nvram = fwreq->items[BRCMF_SDIO_FW_NVRAM].nv_data.data; nvram_len = fwreq->items[BRCMF_SDIO_FW_NVRAM].nv_data.len; + sdiod->clm_fw = fwreq->items[BRCMF_SDIO_FW_CLM].binary; kfree(fwreq); /* try to download image and nvram to the dongle */ @@ -4416,6 +4419,7 @@ brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus) struct brcmf_fw_name fwnames[] = { { ".bin", bus->sdiodev->fw_name }, { ".txt", bus->sdiodev->nvram_name }, + { ".clm_blob", bus->sdiodev->clm_name }, }; fwreq = brcmf_fw_alloc_request(bus->ci->chip, bus->ci->chiprev, @@ -4427,7 +4431,9 @@ brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus) fwreq->items[BRCMF_SDIO_FW_CODE].type = BRCMF_FW_TYPE_BINARY; fwreq->items[BRCMF_SDIO_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM; - fwreq->board_type = bus->sdiodev->settings->board_type; + fwreq->items[BRCMF_SDIO_FW_CLM].type = BRCMF_FW_TYPE_BINARY; + fwreq->items[BRCMF_SDIO_FW_CLM].flags = BRCMF_FW_REQF_OPTIONAL; + fwreq->board_types[0] = bus->sdiodev->settings->board_type; return fwreq; } @@ -4583,6 +4589,8 @@ void brcmf_sdio_remove(struct brcmf_sdio *bus) if (bus->sdiodev->settings) brcmf_release_module_param(bus->sdiodev->settings); + release_firmware(bus->sdiodev->clm_fw); + bus->sdiodev->clm_fw = NULL; kfree(bus->rxbuf); kfree(bus->hdrbuf); kfree(bus); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h index 15d2c02fa3ec..7b74c295e4c9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h @@ -186,9 +186,11 @@ struct brcmf_sdio_dev { struct sg_table sgtable; char fw_name[BRCMF_FW_NAME_LEN]; char nvram_name[BRCMF_FW_NAME_LEN]; + char clm_name[BRCMF_FW_NAME_LEN]; bool wowl_enabled; enum brcmf_sdiod_state state; struct brcmf_sdiod_freezer *freezer; + const struct firmware *clm_fw; }; /* sdio core registers */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c index 9fb68c2dc7e3..85e18fb9c497 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c @@ -1154,24 +1154,11 @@ error: return NULL; } -static -int brcmf_usb_get_fwname(struct device *dev, const char *ext, u8 *fw_name) +static int brcmf_usb_get_blob(struct device *dev, const struct firmware **fw, + enum brcmf_blob_type type) { - struct brcmf_bus *bus = dev_get_drvdata(dev); - struct brcmf_fw_request *fwreq; - struct brcmf_fw_name fwnames[] = { - { ext, fw_name }, - }; - - fwreq = brcmf_fw_alloc_request(bus->chip, bus->chiprev, - brcmf_usb_fwnames, - ARRAY_SIZE(brcmf_usb_fwnames), - fwnames, ARRAY_SIZE(fwnames)); - if (!fwreq) - return -ENOMEM; - - kfree(fwreq); - return 0; + /* No blobs for USB devices... */ + return -ENOENT; } static const struct brcmf_bus_ops brcmf_usb_bus_ops = { @@ -1180,7 +1167,7 @@ static const struct brcmf_bus_ops brcmf_usb_bus_ops = { .txdata = brcmf_usb_tx, .txctl = brcmf_usb_tx_ctlpkt, .rxctl = brcmf_usb_rx_ctlpkt, - .get_fwname = brcmf_usb_get_fwname, + .get_blob = brcmf_usb_get_blob, }; #define BRCMF_USB_FW_CODE 0 diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h index ed0b707f0cdf..7e863cea344f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h @@ -37,6 +37,7 @@ #define BRCM_CC_4350_CHIP_ID 0x4350 #define BRCM_CC_43525_CHIP_ID 43525 #define BRCM_CC_4354_CHIP_ID 0x4354 +#define BRCM_CC_4355_CHIP_ID 0x4355 #define BRCM_CC_4356_CHIP_ID 0x4356 #define BRCM_CC_43566_CHIP_ID 43566 #define BRCM_CC_43567_CHIP_ID 43567 @@ -51,6 +52,9 @@ #define BRCM_CC_43664_CHIP_ID 43664 #define BRCM_CC_43666_CHIP_ID 43666 #define BRCM_CC_4371_CHIP_ID 0x4371 +#define BRCM_CC_4377_CHIP_ID 0x4377 +#define BRCM_CC_4378_CHIP_ID 0x4378 +#define BRCM_CC_4387_CHIP_ID 0x4387 #define CY_CC_4373_CHIP_ID 0x4373 #define CY_CC_43012_CHIP_ID 43012 #define CY_CC_43752_CHIP_ID 43752 @@ -69,6 +73,7 @@ #define BRCM_PCIE_4350_DEVICE_ID 0x43a3 #define BRCM_PCIE_4354_DEVICE_ID 0x43df #define BRCM_PCIE_4354_RAW_DEVICE_ID 0x4354 +#define BRCM_PCIE_4355_DEVICE_ID 0x43dc #define BRCM_PCIE_4356_DEVICE_ID 0x43ec #define BRCM_PCIE_43567_DEVICE_ID 0x43d3 #define BRCM_PCIE_43570_DEVICE_ID 0x43d9 @@ -87,6 +92,9 @@ #define BRCM_PCIE_4366_2G_DEVICE_ID 0x43c4 #define BRCM_PCIE_4366_5G_DEVICE_ID 0x43c5 #define BRCM_PCIE_4371_DEVICE_ID 0x440d +#define BRCM_PCIE_4377_DEVICE_ID 0x4488 +#define BRCM_PCIE_4378_DEVICE_ID 0x4425 +#define BRCM_PCIE_4387_DEVICE_ID 0x4433 /* brcmsmac IDs */ diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index d702d7d60235..67cef4dda24c 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -195,8 +195,20 @@ struct apple_nvme { int irq; spinlock_t lock; + + /* + * Delayed cache flush handling state + */ + struct nvme_ns *flush_ns; + unsigned long flush_interval; + unsigned long last_flush; + struct delayed_work flush_dwork; }; +unsigned int flush_interval = 1000; +module_param(flush_interval, uint, 0644); +MODULE_PARM_DESC(flush_interval, "Grace period in msecs between flushes"); + static_assert(sizeof(struct nvme_command) == 64); static_assert(sizeof(struct apple_nvmmu_tcb) == 128); @@ -729,6 +741,26 @@ static int apple_nvme_remove_sq(struct apple_nvme *anv) return nvme_submit_sync_cmd(anv->ctrl.admin_q, &c, NULL, 0); } +static bool apple_nvme_delayed_flush(struct apple_nvme *anv, struct nvme_ns *ns, + struct request *req) +{ + if (!anv->flush_interval || req_op(req) != REQ_OP_FLUSH) + return false; + if (delayed_work_pending(&anv->flush_dwork)) + return true; + if (time_before(jiffies, anv->last_flush + anv->flush_interval)) { + kblockd_mod_delayed_work_on(WORK_CPU_UNBOUND, &anv->flush_dwork, + anv->flush_interval); + if (WARN_ON_ONCE(anv->flush_ns && anv->flush_ns != ns)) + goto out; + anv->flush_ns = ns; + return true; + } +out: + anv->last_flush = jiffies; + return false; +} + static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { @@ -764,6 +796,12 @@ static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx, } blk_mq_start_request(req); + + if (apple_nvme_delayed_flush(anv, ns, req)) { + blk_mq_complete_request(req); + return BLK_STS_OK; + } + apple_nvme_submit_cmd(q, cmnd); return BLK_STS_OK; @@ -1366,6 +1404,28 @@ static int apple_nvme_attach_genpd(struct apple_nvme *anv) return 0; } +static void apple_nvme_flush_work(struct work_struct *work) +{ + struct nvme_command c = { }; + struct apple_nvme *anv; + struct nvme_ns *ns; + int err; + + anv = container_of(work, struct apple_nvme, flush_dwork.work); + ns = anv->flush_ns; + if (WARN_ON_ONCE(!ns)) + return; + + c.common.opcode = nvme_cmd_flush; + c.common.nsid = cpu_to_le32(anv->flush_ns->head->ns_id); + err = nvme_submit_sync_cmd(ns->queue, &c, NULL, 0); + if (err) { + dev_err(anv->dev, "Deferred flush failed: %d\n", err); + } else { + anv->last_flush = jiffies; + } +} + static int apple_nvme_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1508,12 +1568,21 @@ static int apple_nvme_probe(struct platform_device *pdev) goto put_dev; } + if (flush_interval) { + anv->flush_interval = msecs_to_jiffies(flush_interval); + anv->flush_ns = NULL; + anv->last_flush = jiffies - anv->flush_interval; + } + + INIT_DELAYED_WORK(&anv->flush_dwork, apple_nvme_flush_work); + nvme_reset_ctrl(&anv->ctrl); async_schedule(apple_nvme_async_probe, anv); return 0; put_dev: + apple_nvme_detach_genpd(anv); put_device(anv->dev); return ret; } @@ -1541,6 +1610,7 @@ static void apple_nvme_shutdown(struct platform_device *pdev) { struct apple_nvme *anv = platform_get_drvdata(pdev); + flush_delayed_work(&anv->flush_dwork); apple_nvme_disable(anv, true); if (apple_rtkit_is_running(anv->rtk)) apple_rtkit_shutdown(anv->rtk); diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 967d0084800e..577984390149 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -172,6 +172,19 @@ config NVMEM_BCM_OCOTP This driver can also be built as a module. If so, the module will be called nvmem-bcm-ocotp. +config NVMEM_SPMI_MFD + tristate "Generic SPMI MFD NVMEM" + depends on MFD_SIMPLE_MFD_SPMI || COMPILE_TEST + default ARCH_APPLE + help + Say y here to build a generic driver to expose an SPMI MFD device + as a NVMEM provider. This can be used for PMIC/PMU devices which + are used to store power and RTC-related settings on certain + platforms, such as Apple Silicon Macs. + + This driver can also be built as a module. If so, the module + will be called nvmem-spmi-mfd. + config NVMEM_STM32_ROMEM tristate "STMicroelectronics STM32 factory-programmed memory support" depends on ARCH_STM32 || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 00e136a0a123..026eac4da53d 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -38,6 +38,8 @@ nvmem-rockchip-otp-y := rockchip-otp.o obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o nvmem_stm32_romem-y := stm32-romem.o obj-$(CONFIG_NVMEM_STM32_ROMEM) += nvmem_stm32_romem.o +nvmem_spmi_mfd-y := spmi-mfd-nvmem.o +obj-$(CONFIG_NVMEM_SPMI_MFD) += nvmem_spmi_mfd.o nvmem_sunxi_sid-y := sunxi_sid.o obj-$(CONFIG_UNIPHIER_EFUSE) += nvmem-uniphier-efuse.o nvmem-uniphier-efuse-y := uniphier-efuse.o diff --git a/drivers/nvmem/spmi-mfd-nvmem.c b/drivers/nvmem/spmi-mfd-nvmem.c new file mode 100644 index 000000000000..284c93be2e18 --- /dev/null +++ b/drivers/nvmem/spmi-mfd-nvmem.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Generic SPMI MFD NVMEM driver + * + * Copyright The Asahi Linux Contributors + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +struct spmi_mfd_nvmem { + struct regmap *regmap; + unsigned int base; +}; + +static int spmi_mfd_nvmem_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct spmi_mfd_nvmem *nvmem = priv; + + return regmap_bulk_read(nvmem->regmap, nvmem->base + offset, val, bytes); +} + +static int spmi_mfd_nvmem_write(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct spmi_mfd_nvmem *nvmem = priv; + + return regmap_bulk_write(nvmem->regmap, nvmem->base + offset, val, bytes); +} + +static int spmi_mfd_nvmem_probe(struct platform_device *pdev) +{ + struct spmi_mfd_nvmem *nvmem; + const __be32 *addr; + int len; + struct nvmem_config nvmem_cfg = { + .dev = &pdev->dev, + .name = "spmi_mfd_nvmem", + .id = NVMEM_DEVID_AUTO, + .word_size = 1, + .stride = 1, + .reg_read = spmi_mfd_nvmem_read, + .reg_write = spmi_mfd_nvmem_write, + }; + + nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL); + if (!nvmem) + return -ENOMEM; + + nvmem_cfg.priv = nvmem; + + nvmem->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!nvmem->regmap) { + dev_err(&pdev->dev, "Parent regmap unavailable.\n"); + return -ENXIO; + } + + addr = of_get_property(pdev->dev.of_node, "reg", &len); + if (!addr) { + dev_err(&pdev->dev, "no reg property\n"); + return -EINVAL; + } + if (len != 2 * sizeof(u32)) { + dev_err(&pdev->dev, "invalid reg property\n"); + return -EINVAL; + } + + nvmem->base = be32_to_cpup(&addr[0]); + nvmem_cfg.size = be32_to_cpup(&addr[1]); + + return PTR_ERR_OR_ZERO(devm_nvmem_register(&pdev->dev, &nvmem_cfg)); +} + +static const struct of_device_id spmi_mfd_nvmem_id_table[] = { + { .compatible = "apple,spmi-pmu-nvmem" }, + { .compatible = "spmi-mfd-nvmem" }, + { }, +}; +MODULE_DEVICE_TABLE(of, spmi_mfd_nvmem_id_table); + +static struct platform_driver spmi_mfd_nvmem_driver = { + .probe = spmi_mfd_nvmem_probe, + .driver = { + .name = "spmi-mfd-nvmem", + .owner = THIS_MODULE, + .of_match_table = spmi_mfd_nvmem_id_table, + }, +}; + +module_platform_driver(spmi_mfd_nvmem_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_DESCRIPTION("SPMI MFD NVMEM driver"); diff --git a/drivers/of/address.c b/drivers/of/address.c index 94f017d808c4..68f54ec92496 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -538,7 +538,7 @@ static u64 __of_translate_address(struct device_node *dev, pbus = of_match_bus(parent); pbus->count_cells(dev, &pna, &pns); if (!OF_CHECK_COUNTS(pna, pns)) { - pr_err("Bad cell count for %pOF\n", dev); + pr_debug("Bad cell count for %pOF\n", dev); break; } diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index a2c3c207a04b..f26a617ea5c5 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -507,20 +507,53 @@ static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, return readl_relaxed(port->base + PORT_RID2SID(idx)); } +static int apple_pcie_probe_port(struct device_node *np) +{ + struct gpio_desc *gd; + + gd = gpiod_get_from_of_node(np, "reset-gpios", 0, + GPIOD_OUT_LOW, "PERST#"); + if (IS_ERR(gd)) { + return PTR_ERR(gd); + } + + gpiod_put(gd); + + gd = gpiod_get_from_of_node(np, "pwren-gpios", 0, + GPIOD_OUT_LOW, "PWREN"); + if (IS_ERR(gd)) { + if (PTR_ERR(gd) != -ENOENT) + return PTR_ERR(gd); + } else { + gpiod_put(gd); + } + + return 0; +} + static int apple_pcie_setup_port(struct apple_pcie *pcie, struct device_node *np) { struct platform_device *platform = to_platform_device(pcie->dev); struct apple_pcie_port *port; - struct gpio_desc *reset; + struct gpio_desc *reset, *pwren = NULL; u32 stat, idx; int ret, i; - reset = gpiod_get_from_of_node(np, "reset-gpios", 0, - GPIOD_OUT_LOW, "PERST#"); + reset = devm_gpiod_get_from_of_node(pcie->dev, np, "reset-gpios", 0, + GPIOD_OUT_LOW, "PERST#"); if (IS_ERR(reset)) return PTR_ERR(reset); + pwren = devm_gpiod_get_from_of_node(pcie->dev, np, "pwren-gpios", 0, + GPIOD_OUT_LOW, "PWREN"); + if (IS_ERR(pwren)) { + if (PTR_ERR(pwren) == -ENOENT) + pwren = NULL; + else + return PTR_ERR(pwren); + } + port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; @@ -541,18 +574,27 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); /* Assert PERST# before setting up the clock */ - gpiod_set_value(reset, 1); + gpiod_set_value_cansleep(reset, 1); + + /* Power on the device if required */ + gpiod_set_value_cansleep(pwren, 1); ret = apple_pcie_setup_refclk(pcie, port); if (ret < 0) return ret; - /* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) */ - usleep_range(100, 200); + /* + * The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) + * If powering up, the minimal Tpvperl is 100ms + */ + if (pwren) + msleep(100); + else + usleep_range(100, 200); /* Deassert PERST# */ rmw_set(PORT_PERST_OFF, port->base + PORT_PERST); - gpiod_set_value(reset, 0); + gpiod_set_value_cansleep(reset, 0); /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */ msleep(100); @@ -797,8 +839,18 @@ static int apple_pcie_init(struct pci_config_window *cfg) static int apple_pcie_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct device_node *of_port; int ret; + /* Check for probe dependencies for all ports first */ + for_each_child_of_node(dev->of_node, of_port) { + ret = apple_pcie_probe_port(of_port); + of_node_put(of_port); + if (ret) + return dev_err_probe(dev, ret, "Port %pOF probe fail\n", of_port); + } + ret = bus_register_notifier(&pci_bus_type, &apple_pcie_nb); if (ret) return ret; diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 18fc6a08569e..d683a75fc972 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -15,3 +15,5 @@ source "drivers/platform/mellanox/Kconfig" source "drivers/platform/olpc/Kconfig" source "drivers/platform/surface/Kconfig" + +source "drivers/platform/apple/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 4de08ef4ec9d..3e5d5039a28c 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_OLPC_EC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ obj-$(CONFIG_SURFACE_PLATFORMS) += surface/ +obj-$(CONFIG_APPLE_PLATFORMS) += apple/ diff --git a/drivers/platform/apple/Kconfig b/drivers/platform/apple/Kconfig new file mode 100644 index 000000000000..42525aa9fbbe --- /dev/null +++ b/drivers/platform/apple/Kconfig @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Apple Platform-Specific Drivers +# + +menuconfig APPLE_PLATFORMS + bool "Apple Mac Platform-Specific Device Drivers" + default y + help + Say Y here to get to see options for platform-specific device drivers + for Apple devices. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if APPLE_PLATFORMS + +config APPLE_SMC + tristate "Apple SMC Driver" + depends on ARCH_APPLE || COMPILE_TEST + default ARCH_APPLE + select MFD_CORE + help + Build support for the Apple System Management Controller present in + Apple Macs. This driver currently supports the SMC in Apple Silicon + Macs. For x86 Macs, see the applesmc driver (SENSORS_APPLESMC). + + Say Y here if you have an Apple Silicon Mac. + + To compile this driver as a module, choose M here: the module will + be called macsmc. + +if APPLE_SMC + +config APPLE_SMC_RTKIT + tristate "RTKit (Apple Silicon) backend" + depends on ARCH_APPLE || COMPILE_TEST + depends on APPLE_RTKIT + default ARCH_APPLE + help + Build support for SMC communications via the RTKit backend. This is + required for Apple Silicon Macs. + + Say Y here if you have an Apple Silicon Mac. + + To compile this driver as a module, choose M here: the module will + be called macsmc-rtkit. + +endif +endif diff --git a/drivers/platform/apple/Makefile b/drivers/platform/apple/Makefile new file mode 100644 index 000000000000..79fac195398b --- /dev/null +++ b/drivers/platform/apple/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/apple +# Apple Platform-Specific Drivers +# + +macsmc-y += smc_core.o +macsmc-rtkit-y += smc_rtkit.o + +obj-$(CONFIG_APPLE_SMC) += macsmc.o +obj-$(CONFIG_APPLE_SMC_RTKIT) += macsmc-rtkit.o diff --git a/drivers/platform/apple/smc.h b/drivers/platform/apple/smc.h new file mode 100644 index 000000000000..8ae51887b2c5 --- /dev/null +++ b/drivers/platform/apple/smc.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC internal core definitions + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef _SMC_H +#define _SMC_H + +#include <linux/mfd/macsmc.h> + +struct apple_smc_backend_ops { + int (*read_key)(void *cookie, smc_key key, void *buf, size_t size); + int (*write_key)(void *cookie, smc_key key, void *buf, size_t size); + int (*write_key_atomic)(void *cookie, smc_key key, void *buf, size_t size); + int (*rw_key)(void *cookie, smc_key key, void *wbuf, size_t wsize, + void *rbuf, size_t rsize); + int (*get_key_by_index)(void *cookie, int index, smc_key *key); + int (*get_key_info)(void *cookie, smc_key key, struct apple_smc_key_info *info); +}; + +struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, + void *cookie); +void *apple_smc_get_cookie(struct apple_smc *smc); +int apple_smc_remove(struct apple_smc *smc); +void apple_smc_event_received(struct apple_smc *smc, uint32_t event); + +#endif diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c new file mode 100644 index 000000000000..daf029cd072f --- /dev/null +++ b/drivers/platform/apple/smc_core.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC core framework + * Copyright The Asahi Linux Contributors + */ + +#include <linux/device.h> +#include <linux/mfd/core.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include "smc.h" + +struct apple_smc { + struct device *dev; + + void *be_cookie; + const struct apple_smc_backend_ops *be; + + struct mutex mutex; + + u32 key_count; + smc_key first_key; + smc_key last_key; + + struct blocking_notifier_head event_handlers; +}; + +static const struct mfd_cell apple_smc_devs[] = { + { + .name = "macsmc-gpio", + }, + { + .name = "macsmc-hid", + }, + { + .name = "macsmc-power", + }, + { + .name = "macsmc-reboot", + }, + { + .name = "macsmc-rtc", + }, +}; + +int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + int ret; + + mutex_lock(&smc->mutex); + ret = smc->be->read_key(smc->be_cookie, key, buf, size); + mutex_unlock(&smc->mutex); + + return ret; +} +EXPORT_SYMBOL(apple_smc_read); + +int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + int ret; + + mutex_lock(&smc->mutex); + ret = smc->be->write_key(smc->be_cookie, key, buf, size); + mutex_unlock(&smc->mutex); + + return ret; +} +EXPORT_SYMBOL(apple_smc_write); + +int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + int ret; + + /* + * Will fail if SMC is busy. This is only used by SMC reboot/poweroff + * final calls, so it doesn't really matter at that point. + */ + if (!mutex_trylock(&smc->mutex)) + return -EBUSY; + + ret = smc->be->write_key_atomic(smc->be_cookie, key, buf, size); + mutex_unlock(&smc->mutex); + + return ret; +} +EXPORT_SYMBOL(apple_smc_write_atomic); + +int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize, + void *rbuf, size_t rsize) +{ + int ret; + + mutex_lock(&smc->mutex); + ret = smc->be->rw_key(smc->be_cookie, key, wbuf, wsize, rbuf, rsize); + mutex_unlock(&smc->mutex); + + return ret; +} +EXPORT_SYMBOL(apple_smc_rw); + +int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key) +{ + int ret; + + mutex_lock(&smc->mutex); + ret = smc->be->get_key_by_index(smc->be_cookie, index, key); + mutex_unlock(&smc->mutex); + + return ret; +} +EXPORT_SYMBOL(apple_smc_get_key_by_index); + +int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info) +{ + int ret; + + mutex_lock(&smc->mutex); + ret = smc->be->get_key_info(smc->be_cookie, key, info); + mutex_unlock(&smc->mutex); + + return ret; +} +EXPORT_SYMBOL(apple_smc_get_key_info); + +int apple_smc_find_first_key_index(struct apple_smc *smc, smc_key key) +{ + int start = 0, count = smc->key_count; + int ret; + + if (key <= smc->first_key) + return 0; + if (key > smc->last_key) + return smc->key_count; + + while (count > 1) { + int pivot = start + ((count - 1) >> 1); + smc_key pkey; + + ret = apple_smc_get_key_by_index(smc, pivot, &pkey); + if (ret < 0) + return ret; + + if (pkey == key) + return pivot; + + pivot++; + + if (pkey < key) { + count -= pivot - start; + start = pivot; + } else { + count = pivot - start; + } + } + + return start; +} +EXPORT_SYMBOL(apple_smc_find_first_key_index); + +int apple_smc_get_key_count(struct apple_smc *smc) +{ + return smc->key_count; +} +EXPORT_SYMBOL(apple_smc_get_key_count); + +void apple_smc_event_received(struct apple_smc *smc, uint32_t event) +{ + dev_dbg(smc->dev, "Event: 0x%08x\n", event); + blocking_notifier_call_chain(&smc->event_handlers, event, NULL); +} +EXPORT_SYMBOL(apple_smc_event_received); + +int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n) +{ + return blocking_notifier_chain_register(&smc->event_handlers, n); +} +EXPORT_SYMBOL(apple_smc_register_notifier); + +int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n) +{ + return blocking_notifier_chain_unregister(&smc->event_handlers, n); +} +EXPORT_SYMBOL(apple_smc_unregister_notifier); + +void *apple_smc_get_cookie(struct apple_smc *smc) +{ + return smc->be_cookie; +} +EXPORT_SYMBOL(apple_smc_get_cookie); + +struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie) +{ + struct apple_smc *smc; + u32 count; + int ret; + + smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL); + if (!smc) + return ERR_PTR(-ENOMEM); + + smc->dev = dev; + smc->be_cookie = cookie; + smc->be = ops; + mutex_init(&smc->mutex); + BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers); + + ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to get key count")); + smc->key_count = be32_to_cpu(count); + + ret = apple_smc_get_key_by_index(smc, 0, &smc->first_key); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to get first key")); + + ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &smc->last_key); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to get last key")); + + /* Enable notifications */ + apple_smc_write_flag(smc, SMC_KEY(NTAP), 1); + + dev_info(dev, "Initialized (%d keys %p4ch..%p4ch)\n", + smc->key_count, &smc->first_key, &smc->last_key); + + dev_set_drvdata(dev, smc); + + ret = mfd_add_devices(dev, -1, apple_smc_devs, ARRAY_SIZE(apple_smc_devs), NULL, 0, NULL); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Subdevice initialization failed")); + + return smc; +} +EXPORT_SYMBOL(apple_smc_probe); + +int apple_smc_remove(struct apple_smc *smc) +{ + mfd_remove_devices(smc->dev); + + /* Disable notifications */ + apple_smc_write_flag(smc, SMC_KEY(NTAP), 1); + + return 0; +} +EXPORT_SYMBOL(apple_smc_remove); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC core"); diff --git a/drivers/platform/apple/smc_rtkit.c b/drivers/platform/apple/smc_rtkit.c new file mode 100644 index 000000000000..5b7c4c475bbb --- /dev/null +++ b/drivers/platform/apple/smc_rtkit.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC RTKit backend + * Copyright The Asahi Linux Contributors + */ + +#include <asm/unaligned.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/soc/apple/rtkit.h> +#include "smc.h" + +#define SMC_ENDPOINT 0x20 + +/* Guess */ +#define SMC_SHMEM_SIZE 0x1000 + +#define SMC_MSG_READ_KEY 0x10 +#define SMC_MSG_WRITE_KEY 0x11 +#define SMC_MSG_GET_KEY_BY_INDEX 0x12 +#define SMC_MSG_GET_KEY_INFO 0x13 +#define SMC_MSG_INITIALIZE 0x17 +#define SMC_MSG_NOTIFICATION 0x18 +#define SMC_MSG_RW_KEY 0x20 + +#define SMC_DATA GENMASK(63, 32) +#define SMC_WSIZE GENMASK(31, 24) +#define SMC_SIZE GENMASK(23, 16) +#define SMC_ID GENMASK(15, 12) +#define SMC_MSG GENMASK(7, 0) +#define SMC_RESULT SMC_MSG + +#define SMC_RECV_TIMEOUT 100 + +struct apple_smc_rtkit { + struct device *dev; + struct apple_smc *core; + struct apple_rtkit *rtk; + + struct completion init_done; + bool initialized; + bool alive; + + struct resource *sram; + void __iomem *sram_base; + struct apple_rtkit_shmem shmem; + + unsigned int msg_id; + + bool atomic_pending; + struct completion cmd_done; + u64 cmd_ret; +}; + +static int apple_smc_rtkit_write_key_atomic(void *cookie, smc_key key, void *buf, size_t size) +{ + struct apple_smc_rtkit *smc = cookie; + int ret; + u64 msg; + u8 result; + + if (size > SMC_SHMEM_SIZE || size == 0) + return -EINVAL; + + if (!smc->alive) + return -EIO; + + memcpy_toio(smc->shmem.iomem, buf, size); + smc->msg_id = (smc->msg_id + 1) & 0xf; + msg = (FIELD_PREP(SMC_MSG, SMC_MSG_WRITE_KEY) | + FIELD_PREP(SMC_SIZE, size) | + FIELD_PREP(SMC_ID, smc->msg_id) | + FIELD_PREP(SMC_DATA, key)); + smc->atomic_pending = true; + + ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, true); + if (ret < 0) { + dev_err(smc->dev, "Failed to send command (%d)\n", ret); + return ret; + } + + while (smc->atomic_pending) { + ret = apple_rtkit_poll(smc->rtk); + if (ret < 0) { + dev_err(smc->dev, "RTKit poll failed (%llx)", msg); + return ret; + } + udelay(100); + } + + if (FIELD_GET(SMC_ID, smc->cmd_ret) != smc->msg_id) { + dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n", + smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret)); + return -EIO; + } + + result = FIELD_GET(SMC_RESULT, smc->cmd_ret); + if (result != 0) + return -result; + + return FIELD_GET(SMC_SIZE, smc->cmd_ret); +} + +static int apple_smc_cmd(struct apple_smc_rtkit *smc, u64 cmd, u64 arg, + u64 size, u64 wsize, u32 *ret_data) +{ + int ret; + u64 msg; + u8 result; + + if (!smc->alive) + return -EIO; + + reinit_completion(&smc->cmd_done); + + smc->msg_id = (smc->msg_id + 1) & 0xf; + msg = (FIELD_PREP(SMC_MSG, cmd) | + FIELD_PREP(SMC_SIZE, size) | + FIELD_PREP(SMC_WSIZE, wsize) | + FIELD_PREP(SMC_ID, smc->msg_id) | + FIELD_PREP(SMC_DATA, arg)); + + ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, false); + if (ret < 0) { + dev_err(smc->dev, "Failed to send command\n"); + return ret; + } + + do { + if (wait_for_completion_timeout(&smc->cmd_done, + msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) { + dev_err(smc->dev, "Command timed out (%llx)", msg); + return -ETIMEDOUT; + } + if (FIELD_GET(SMC_ID, smc->cmd_ret) == smc->msg_id) + break; + dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n", + smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret)); + } while(1); + + result = FIELD_GET(SMC_RESULT, smc->cmd_ret); + if (result != 0) + return -result; + + if (ret_data) + *ret_data = FIELD_GET(SMC_DATA, smc->cmd_ret); + + return FIELD_GET(SMC_SIZE, smc->cmd_ret); +} + +static int _apple_smc_rtkit_read_key(struct apple_smc_rtkit *smc, smc_key key, + void *buf, size_t size, size_t wsize) +{ + int ret; + u32 rdata; + u64 cmd; + + if (size > SMC_SHMEM_SIZE || size == 0) + return -EINVAL; + + cmd = wsize ? SMC_MSG_RW_KEY : SMC_MSG_READ_KEY; + + ret = apple_smc_cmd(smc, cmd, key, size, wsize, &rdata); + if (ret < 0) + return ret; + + if (size <= 4) + memcpy(buf, &rdata, size); + else + memcpy_fromio(buf, smc->shmem.iomem, size); + + return ret; +} + +static int apple_smc_rtkit_read_key(void *cookie, smc_key key, void *buf, size_t size) +{ + return _apple_smc_rtkit_read_key(cookie, key, buf, size, 0); +} + +static int apple_smc_rtkit_write_key(void *cookie, smc_key key, void *buf, size_t size) +{ + struct apple_smc_rtkit *smc = cookie; + + if (size > SMC_SHMEM_SIZE || size == 0) + return -EINVAL; + + memcpy_toio(smc->shmem.iomem, buf, size); + return apple_smc_cmd(smc, SMC_MSG_WRITE_KEY, key, size, 0, NULL); +} + +static int apple_smc_rtkit_rw_key(void *cookie, smc_key key, + void *wbuf, size_t wsize, void *rbuf, size_t rsize) +{ + struct apple_smc_rtkit *smc = cookie; + + if (wsize > SMC_SHMEM_SIZE || wsize == 0) + return -EINVAL; + + memcpy_toio(smc->shmem.iomem, wbuf, wsize); + return _apple_smc_rtkit_read_key(smc, key, rbuf, rsize, wsize); +} + +static int apple_smc_rtkit_get_key_by_index(void *cookie, int index, smc_key *key) +{ + struct apple_smc_rtkit *smc = cookie; + int ret; + + ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_BY_INDEX, index, 0, 0, key); + + *key = swab32(*key); + return ret; +} + +static int apple_smc_rtkit_get_key_info(void *cookie, smc_key key, struct apple_smc_key_info *info) +{ + struct apple_smc_rtkit *smc = cookie; + u8 key_info[6]; + int ret; + + ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL); + if (ret >= 0 && info) { + info->size = key_info[0]; + info->type_code = get_unaligned_be32(&key_info[1]); + info->flags = key_info[5]; + } + return ret; +} + +static const struct apple_smc_backend_ops apple_smc_rtkit_be_ops = { + .read_key = apple_smc_rtkit_read_key, + .write_key = apple_smc_rtkit_write_key, + .write_key_atomic = apple_smc_rtkit_write_key_atomic, + .rw_key = apple_smc_rtkit_rw_key, + .get_key_by_index = apple_smc_rtkit_get_key_by_index, + .get_key_info = apple_smc_rtkit_get_key_info, +}; + +static void apple_smc_rtkit_crashed(void *cookie) +{ + struct apple_smc_rtkit *smc = cookie; + + dev_err(smc->dev, "SMC crashed! Your system will reboot in a few seconds...\n"); + smc->alive = false; +} + +static int apple_smc_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_smc_rtkit *smc = cookie; + struct resource res = { + .start = bfr->iova, + .end = bfr->iova + bfr->size - 1, + .name = "rtkit_map", + .flags = smc->sram->flags, + }; + + if (!bfr->iova) { + dev_err(smc->dev, "RTKit wants a RAM buffer\n"); + return -EIO; + } + + if (res.end < res.start || !resource_contains(smc->sram, &res)) { + dev_err(smc->dev, + "RTKit buffer request outside SRAM region: %pR", &res); + return -EFAULT; + } + + bfr->iomem = smc->sram_base + (res.start - smc->sram->start); + bfr->is_mapped = true; + + return 0; +} + +static void apple_smc_rtkit_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) +{ + // no-op +} + +static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message) +{ + struct apple_smc_rtkit *smc = cookie; + + if (endpoint != SMC_ENDPOINT) { + dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint); + return false; + } + + if (!smc->initialized) { + int ret; + + smc->shmem.iova = message; + smc->shmem.size = SMC_SHMEM_SIZE; + ret = apple_smc_rtkit_shmem_setup(smc, &smc->shmem); + if (ret < 0) + dev_err(smc->dev, "Failed to initialize shared memory\n"); + else + smc->alive = true; + smc->initialized = true; + complete(&smc->init_done); + } else if (FIELD_GET(SMC_MSG, message) == SMC_MSG_NOTIFICATION) { + /* Handle these in the RTKit worker thread */ + return false; + } else { + smc->cmd_ret = message; + if (smc->atomic_pending) { + smc->atomic_pending = false; + } else { + complete(&smc->cmd_done); + } + } + + return true; +} + +static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message) +{ + struct apple_smc_rtkit *smc = cookie; + + if (endpoint != SMC_ENDPOINT) { + dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint); + return; + } + + if (FIELD_GET(SMC_MSG, message) != SMC_MSG_NOTIFICATION) { + dev_err(smc->dev, "Received unknown message from worker: 0x%llx\n", message); + return; + } + + apple_smc_event_received(smc->core, FIELD_GET(SMC_DATA, message)); +} + +static const struct apple_rtkit_ops apple_smc_rtkit_ops = { + .crashed = apple_smc_rtkit_crashed, + .recv_message = apple_smc_rtkit_recv, + .recv_message_early = apple_smc_rtkit_recv_early, + .shmem_setup = apple_smc_rtkit_shmem_setup, + .shmem_destroy = apple_smc_rtkit_shmem_destroy, +}; + +static int apple_smc_rtkit_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_smc_rtkit *smc; + int ret; + + smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL); + if (!smc) + return -ENOMEM; + + smc->dev = dev; + + smc->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!smc->sram) + return dev_err_probe(dev, EIO, + "No SRAM region"); + + smc->sram_base = devm_ioremap_resource(dev, smc->sram); + if (IS_ERR(smc->sram_base)) + return dev_err_probe(dev, PTR_ERR(smc->sram_base), + "Failed to map SRAM region"); + + smc->rtk = + devm_apple_rtkit_init(dev, smc, NULL, 0, &apple_smc_rtkit_ops); + if (IS_ERR(smc->rtk)) + return dev_err_probe(dev, PTR_ERR(smc->rtk), + "Failed to intialize RTKit"); + + ret = apple_rtkit_wake(smc->rtk); + if (ret != 0) + return dev_err_probe(dev, ret, + "Failed to wake up SMC"); + + ret = apple_rtkit_start_ep(smc->rtk, SMC_ENDPOINT); + if (ret != 0) { + dev_err(dev, "Failed to start endpoint"); + goto cleanup; + } + + init_completion(&smc->init_done); + init_completion(&smc->cmd_done); + + ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, + FIELD_PREP(SMC_MSG, SMC_MSG_INITIALIZE), NULL, false); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to send init message"); + + if (wait_for_completion_timeout(&smc->init_done, + msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) { + ret = -ETIMEDOUT; + dev_err(dev, "Timed out initializing SMC"); + goto cleanup; + } + + if (!smc->alive) { + ret = -EIO; + goto cleanup; + } + + smc->core = apple_smc_probe(dev, &apple_smc_rtkit_be_ops, smc); + if (IS_ERR(smc->core)) { + ret = PTR_ERR(smc->core); + goto cleanup; + } + + return 0; + +cleanup: + /* Try to shut down RTKit, if it's not completely wedged */ + if (apple_rtkit_is_running(smc->rtk)) + apple_rtkit_quiesce(smc->rtk); + + return ret; +} + +static int apple_smc_rtkit_remove(struct platform_device *pdev) +{ + struct apple_smc *core = platform_get_drvdata(pdev); + struct apple_smc_rtkit *smc = apple_smc_get_cookie(core); + + apple_smc_remove(core); + + if (apple_rtkit_is_running(smc->rtk)) + apple_rtkit_quiesce(smc->rtk); + + return 0; +} + +static const struct of_device_id apple_smc_rtkit_of_match[] = { + { .compatible = "apple,smc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_smc_rtkit_of_match); + +static struct platform_driver apple_smc_rtkit_driver = { + .driver = { + .name = "macsmc-rtkit", + .owner = THIS_MODULE, + .of_match_table = apple_smc_rtkit_of_match, + }, + .probe = apple_smc_rtkit_probe, + .remove = apple_smc_rtkit_remove, +}; +module_platform_driver(apple_smc_rtkit_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC RTKit backend driver"); diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 4b563db3ab3e..94eeb910e5f9 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -117,6 +117,18 @@ config POWER_RESET_LINKSTATION Say Y here if you have a Buffalo LinkStation LS421D/E. +config POWER_RESET_MACSMC + tristate "Apple SMC reset/power-off driver" + depends on ARCH_APPLE || COMPILE_TEST + depends on APPLE_SMC + depends on OF + default ARCH_APPLE + help + This driver supports reset and power-off on Apple Mac machines + that implement this functionality via the SMC. + + Say Y here if you have an Apple Silicon Mac. + config POWER_RESET_MSM bool "Qualcomm MSM power-off driver" depends on ARCH_QCOM diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index f606a2f60539..ba49b8c0d0c0 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.o diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c new file mode 100644 index 000000000000..c33ba2a7852d --- /dev/null +++ b/drivers/power/reset/macsmc-reboot.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC Reboot/Poweroff Handler + * Copyright The Asahi Linux Contributors + */ + +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/slab.h> + +struct macsmc_reboot_nvmem { + struct nvmem_cell *shutdown_flag; + struct nvmem_cell *pm_setting; + struct nvmem_cell *boot_stage; + struct nvmem_cell *boot_error_count; + struct nvmem_cell *panic_count; +}; + +static const char *nvmem_names[] = { + "shutdown_flag", + "pm_setting", + "boot_stage", + "boot_error_count", + "panic_count", +}; + +enum boot_stage { + BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */ + BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */ + BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */ +}; + +enum pm_setting { + PM_SETTING_AC_POWER_RESTORE = 0x02, + PM_SETTING_AC_POWER_OFF = 0x03, +}; + +static const char *ac_power_modes[] = { "off", "restore" }; + +static int ac_power_mode_map[] = { + PM_SETTING_AC_POWER_OFF, + PM_SETTING_AC_POWER_RESTORE, +}; + +struct macsmc_reboot { + struct device *dev; + struct apple_smc *smc; + struct notifier_block reboot_notify; + + union { + struct macsmc_reboot_nvmem nvm; + struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)]; + }; +}; + +/* Helpers to read/write a u8 given a struct nvmem_cell */ +static int nvmem_cell_get_u8(struct nvmem_cell *cell) +{ + size_t len; + u8 val; + void *ret = nvmem_cell_read(cell, &len); + + if (IS_ERR(ret)) + return PTR_ERR(ret); + + if (len < 1) { + kfree(ret); + return -EINVAL; + } + + val = *(u8 *)ret; + kfree(ret); + return val; +} + +static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val) +{ + return nvmem_cell_write(cell, &val, sizeof(val)); +} + +static ssize_t macsmc_ac_power_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct macsmc_reboot *reboot = dev_get_drvdata(dev); + int mode; + int ret; + + mode = sysfs_match_string(ac_power_modes, buf); + if (mode < 0) + return mode; + + ret = nvmem_cell_set_u8(reboot->nvm.pm_setting, ac_power_mode_map[mode]); + if (ret < 0) + return ret; + + return n; +} + +static ssize_t macsmc_ac_power_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct macsmc_reboot *reboot = dev_get_drvdata(dev); + int len = 0; + int i; + int mode = nvmem_cell_get_u8(reboot->nvm.pm_setting); + + if (mode < 0) + return mode; + + for (i = 0; i < ARRAY_SIZE(ac_power_mode_map); i++) + if (mode == ac_power_mode_map[i]) + len += scnprintf(buf+len, PAGE_SIZE-len, + "[%s] ", ac_power_modes[i]); + else + len += scnprintf(buf+len, PAGE_SIZE-len, + "%s ", ac_power_modes[i]); + buf[len-1] = '\n'; + return len; +} +static DEVICE_ATTR(ac_power_mode, 0644, macsmc_ac_power_mode_show, + macsmc_ac_power_mode_store); + +/* + * SMC 'MBSE' key actions: + * + * 'offw' - shutdown warning + * 'slpw' - sleep warning + * 'rest' - restart warning + * 'off1' - shutdown (needs PMU bit set to stay on) + * 'susp' - suspend + * 'phra' - restart ("PE Halt Restart Action"?) + * 'panb' - panic beginning + * 'pane' - panic end + */ + +static int macsmc_power_off(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Issuing power off (off1)\n"); + + if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) { + dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n"); + } else { + mdelay(100); + WARN_ON(1); + } + + return NOTIFY_OK; +} + +static int macsmc_restart(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Issuing restart (phra)\n"); + + if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) { + dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n"); + } else { + mdelay(100); + WARN_ON(1); + } + + return NOTIFY_OK; +} + +static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data) +{ + struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify); + u32 val; + u8 shutdown_flag; + + switch (action) { + case SYS_RESTART: + val = SMC_KEY(rest); + shutdown_flag = 0; + break; + case SYS_POWER_OFF: + val = SMC_KEY(offw); + shutdown_flag = 1; + break; + default: + return NOTIFY_DONE; + } + + dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val); + + /* On the Mac Mini, this will turn off the LED for power off */ + if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0) + dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val); + + /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */ + if (reboot->nvm.boot_stage && + nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0) + dev_err(reboot->dev, "Failed to write boot_stage\n"); + + /* + * Set the PMU flag to actually reboot into the off state. + * Without this, the device will just reboot. We make it optional in case it is no longer + * necessary on newer hardware. + */ + if (reboot->nvm.shutdown_flag && + nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0) + dev_err(reboot->dev, "Failed to write shutdown_flag\n"); + + return NOTIFY_OK; +} + +static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot) +{ + int boot_error_count, panic_count; + + if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count) + return; + + boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count); + if (boot_error_count < 0) { + dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count); + return; + } + + panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count); + if (panic_count < 0) { + dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count); + return; + } + + if (!boot_error_count && !panic_count) + return; + + dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n", + boot_error_count, panic_count); + + if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0) + dev_err(reboot->dev, "Failed to reset panic_count\n"); + if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0) + dev_err(reboot->dev, "Failed to reset boot_error_count\n"); +} + +static int macsmc_reboot_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_reboot *reboot; + int ret, i; + + /* Ignore devices without this functionality */ + if (!apple_smc_key_exists(smc, SMC_KEY(MBSE))) + return -ENODEV; + + reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL); + if (!reboot) + return -ENOMEM; + + reboot->dev = &pdev->dev; + reboot->smc = smc; + + platform_set_drvdata(pdev, reboot); + + pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "reboot"); + + for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) { + struct nvmem_cell *cell; + cell = devm_nvmem_cell_get(&pdev->dev, + nvmem_names[i]); + if (IS_ERR(cell)) { + if (PTR_ERR(cell) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n", + nvmem_names[i], PTR_ERR(cell)); + /* Non fatal, we'll deal with it */ + cell = NULL; + } + reboot->nvm_cells[i] = cell; + } + + /* Set the boot_stage to indicate we're running the OS kernel */ + if (reboot->nvm.boot_stage && + nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0) + dev_err(reboot->dev, "Failed to write boot_stage\n"); + + /* Display and clear the error counts */ + macsmc_power_init_error_counts(reboot); + + reboot->reboot_notify.notifier_call = macsmc_reboot_notify; + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, + macsmc_power_off, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register power-off handler\n"); + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH, + macsmc_restart, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n"); + + ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n"); + + dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n"); + + if (device_create_file(&pdev->dev, &dev_attr_ac_power_mode)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + return 0; +} + +static int macsmc_reboot_remove(struct platform_device *pdev) +{ + device_remove_file(&pdev->dev, &dev_attr_ac_power_mode); + + return 0; +} + + +static struct platform_driver macsmc_reboot_driver = { + .driver = { + .name = "macsmc-reboot", + .owner = THIS_MODULE, + }, + .probe = macsmc_reboot_probe, + .remove = macsmc_reboot_remove, +}; +module_platform_driver(macsmc_reboot_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_ALIAS("platform:macsmc-reboot"); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 1aa8323ad9f6..780941701795 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -897,4 +897,11 @@ config BATTERY_UG3105 device is off or suspended, the functionality of this driver is limited to reporting capacity only. +config CHARGER_MACSMC + tristate "Apple SMC Charger / Battery support" + depends on APPLE_SMC + help + Say Y here to enable support for the charger and battery controls on + Apple SMC controllers, as used on Apple Silicon Macs. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 7f02f36aea55..bba0c6ad4101 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -108,3 +108,4 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o +obj-$(CONFIG_CHARGER_MACSMC) += macsmc_power.o diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c new file mode 100644 index 000000000000..dee48ee8b5a4 --- /dev/null +++ b/drivers/power/supply/macsmc_power.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC Power/Battery Management + * Copyright The Asahi Linux Contributors + */ + +#include <linux/ctype.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/power_supply.h> + +#define MAX_STRING_LENGTH 256 + +struct macsmc_power { + struct device *dev; + struct apple_smc *smc; + + struct power_supply *batt; + char model_name[MAX_STRING_LENGTH]; + char serial_number[MAX_STRING_LENGTH]; + char mfg_date[MAX_STRING_LENGTH]; + + struct power_supply *ac; + + struct notifier_block nb; +}; + +#define CHNC_BATTERY_FULL BIT(0) +#define CHNC_NO_CHARGER BIT(7) +#define CHNC_NOCHG_CH0C BIT(14) +#define CHNC_NOCHG_CH0B_CH0K BIT(15) +#define CHNC_BATTERY_FULL_2 BIT(18) +#define CHNC_BMS_BUSY BIT(23) +#define CHNC_NOAC_CH0J BIT(53) +#define CHNC_NOAC_CH0I BIT(54) + +#define CH0R_LOWER_FLAGS GENMASK(15, 0) +#define CH0R_NOAC_CH0I BIT(0) +#define CH0R_NOAC_CH0J BIT(5) +#define CH0R_BMS_BUSY BIT(8) +#define CH0R_NOAC_CH0K BIT(9) + +#define CH0X_CH0C BIT(0) +#define CH0X_CH0B BIT(1) + +static int macsmc_battery_get_status(struct macsmc_power *power) +{ + u64 nocharge_flags; + u32 nopower_flags; + u16 ac_current; + int ret; + + /* + * Note: there are fallbacks in case some of these SMC keys disappear in the future + * or are not present on some machines. We treat the absence of the CHCE/CHCC/BSFC/CHSC + * flags as an error, since they are quite fundamental and simple booleans. + */ + + /* + * If power input is inhibited, we are definitely discharging. + * However, if the only reason is the BMS is doing a balancing cycle, + * go ahead and ignore that one to avoid spooking users. + */ + ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags); + if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY)) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* If no charger is present, we are definitely discharging. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE)); + if (ret < 0) + return ret; + else if (!ret) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* If AC is not charge capable, we are definitely discharging. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC)); + if (ret < 0) + return ret; + else if (!ret) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* + * If the AC input current limit is tiny or 0, we are discharging no matter + * how much the BMS believes it can charge. + */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current); + if (!ret && ac_current < 100) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* If the battery is full, report it as such. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC)); + if (ret < 0) + return ret; + else if (ret) + return POWER_SUPPLY_STATUS_FULL; + + /* If there are reasons we aren't charging... */ + ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags); + if (!ret) { + /* Perhaps the battery is full after all */ + if (nocharge_flags & CHNC_BATTERY_FULL) + return POWER_SUPPLY_STATUS_FULL; + /* Or maybe the BMS is just busy doing something, if so call it charging anyway */ + else if (nocharge_flags == CHNC_BMS_BUSY) + return POWER_SUPPLY_STATUS_CHARGING; + /* If we have other reasons we aren't charging, say we aren't */ + else if (nocharge_flags) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + /* Else we're either charging or about to charge */ + else + return POWER_SUPPLY_STATUS_CHARGING; + } + + /* As a fallback, use the system charging flag. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC)); + if (ret < 0) + return ret; + if (!ret) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + else + return POWER_SUPPLY_STATUS_CHARGING; +} + +static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) +{ + int ret; + u8 val; + + /* CH0I returns a bitmask like the low byte of CH0R */ + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val); + if (ret) + return ret; + if (val & CH0R_NOAC_CH0I) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + + /* CH0C returns a bitmask containing CH0B/CH0C flags */ + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val); + if (ret) + return ret; + if (val & CH0X_CH0C) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + else + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; +} + +static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val) +{ + u8 ch0i, ch0c; + int ret; + + /* + * CH0I/CH0C are "hard" controls that will allow the battery to run down to 0. + * CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%; + * we don't expose these yet. + */ + + switch (val) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + ch0i = ch0c = 0; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + ch0i = 0; + ch0c = 1; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + ch0i = 1; + ch0c = 0; + break; + default: + return -EINVAL; + } + + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), ch0i); + if (ret) + return ret; + return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), ch0c); +} + +static int macsmc_battery_get_date(const char *s, int *out) +{ + if (!isdigit(s[0]) || !isdigit(s[1])) + return -ENOTSUPP; + + *out = (s[0] - '0') * 10 + s[1] - '0'; + return 0; +} + +static int macsmc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + int ret = 0; + u8 vu8; + u16 vu16; + u32 vu32; + s16 vs16; + s32 vs32; + s64 vs64; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = macsmc_battery_get_status(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + val->intval = macsmc_battery_get_charge_behaviour(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16); + val->intval = vu16 == 0xffff ? 0 : vu16 * 60; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16); + val->intval = vu16 == 0xffff ? 0 : vu16 * 60; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8); + val->intval = vu8; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32); + val->intval = vs32 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = apple_smc_read_u32(power->smc, SMC_KEY(CSIL), &vu32); + val->intval = vu32 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); + val->intval = swab16(vu16) * 1000; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16); + val->intval = vu16 - 2732; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64); + val->intval = vs64; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16); + val->intval = vu16; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD)); + val->intval = ret == 1 ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD; + ret = ret < 0 ? ret : 0; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = power->model_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = power->serial_number; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval); + val->intval += 2000 - 8; /* -8 is a fixup for a firmware bug... */ + break; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval); + break; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int macsmc_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return macsmc_battery_set_charge_behaviour(power, val->intval); + default: + return -EINVAL; + } +} + +static int macsmc_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return true; + default: + return false; + } +} + +static enum power_supply_property macsmc_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_MANUFACTURE_YEAR, + POWER_SUPPLY_PROP_MANUFACTURE_MONTH, + POWER_SUPPLY_PROP_MANUFACTURE_DAY, +}; + +static const struct power_supply_desc macsmc_battery_desc = { + .name = "macsmc-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = macsmc_battery_get_property, + .set_property = macsmc_battery_set_property, + .property_is_writeable = macsmc_battery_property_is_writeable, + .properties = macsmc_battery_props, + .num_properties = ARRAY_SIZE(macsmc_battery_props), +}; + +static int macsmc_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + int ret = 0; + u16 vu16; + u32 vu32; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32); + val->intval = !!vu32; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT: + ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32); + val->intval = vu32 * 1000; + break; + default: + return -EINVAL; + } + + return ret; +} + +static enum power_supply_property macsmc_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_POWER_LIMIT, +}; + +static const struct power_supply_desc macsmc_ac_desc = { + .name = "macsmc-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = macsmc_ac_get_property, + .properties = macsmc_ac_props, + .num_properties = ARRAY_SIZE(macsmc_ac_props), +}; + +static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data) +{ + struct macsmc_power *power = container_of(nb, struct macsmc_power, nb); + + if ((event & 0xffffff00) == 0x71010100) { + bool charging = (event & 0xff) != 0; + + dev_info(power->dev, "Charging: %d\n", charging); + power_supply_changed(power->batt); + power_supply_changed(power->ac); + + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int macsmc_power_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct macsmc_power *power; + int ret; + + power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); + if (!power) + return -ENOMEM; + + power->dev = &pdev->dev; + power->smc = smc; + dev_set_drvdata(&pdev->dev, power); + + /* Ignore devices without a charger/battery */ + if (macsmc_battery_get_status(power) <= POWER_SUPPLY_STATUS_UNKNOWN) + return -ENODEV; + + /* Fetch string properties */ + apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, sizeof(power->model_name) - 1); + apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1); + apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1); + + /* Turn off the "optimized battery charging" flags, in case macOS left them on */ + apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0); + apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0); + + psy_cfg.drv_data = power; + power->batt = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg); + if (IS_ERR(power->batt)) { + dev_err(&pdev->dev, "Failed to register battery\n"); + ret = PTR_ERR(power->batt); + return ret; + } + + power->ac = devm_power_supply_register(&pdev->dev, &macsmc_ac_desc, &psy_cfg); + if (IS_ERR(power->ac)) { + dev_err(&pdev->dev, "Failed to register AC adapter\n"); + ret = PTR_ERR(power->ac); + return ret; + } + + power->nb.notifier_call = macsmc_power_event; + apple_smc_register_notifier(power->smc, &power->nb); + + return 0; +} + +static int macsmc_power_remove(struct platform_device *pdev) +{ + struct macsmc_power *power = dev_get_drvdata(&pdev->dev); + + apple_smc_unregister_notifier(power->smc, &power->nb); + + return 0; +} + +static struct platform_driver macsmc_power_driver = { + .driver = { + .name = "macsmc-power", + .owner = THIS_MODULE, + }, + .probe = macsmc_power_probe, + .remove = macsmc_power_remove, +}; +module_platform_driver(macsmc_power_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC battery and power management driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_ALIAS("platform:macsmc-power"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index a00f901b5c1d..8717f2d9381c 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1973,4 +1973,17 @@ config RTC_DRV_MSC313 This driver can also be built as a module, if so, the module will be called "rtc-msc313". +config RTC_DRV_MACSMC + tristate "Apple Mac SMC RTC" + depends on ARCH_APPLE || COMPILE_TEST + depends on APPLE_SMC + depends on OF + default ARCH_APPLE + help + If you say yes here you get support for RTC functions + inside Apple SPMI PMUs. + + To compile this driver as a module, choose M here: the + module will be called rtc-macsmc. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index fb04467b652d..68519801a320 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o +obj-$(CONFIG_RTC_DRV_MACSMC) += rtc-macsmc.o obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o obj-$(CONFIG_RTC_DRV_MAX6916) += rtc-max6916.o diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c new file mode 100644 index 000000000000..34730c925248 --- /dev/null +++ b/drivers/rtc/rtc-macsmc.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC RTC driver + * Copyright The Asahi Linux Contributors + */ + +#include <linux/bitops.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/slab.h> + +/* 48-bit RTC */ +#define RTC_BYTES 6 +#define RTC_BITS (8 * RTC_BYTES) + +/* 32768 Hz clock */ +#define RTC_SEC_SHIFT 15 + +struct macsmc_rtc { + struct device *dev; + struct apple_smc *smc; + struct rtc_device *rtc_dev; + struct nvmem_cell *rtc_offset; +}; + +static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + u64 ctr = 0, off = 0; + time64_t now; + void *p_off; + size_t len; + int ret; + + ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES); + if (ret != RTC_BYTES) + return ret < 0 ? ret : -EIO; + + p_off = nvmem_cell_read(rtc->rtc_offset, &len); + if (IS_ERR(p_off)) + return PTR_ERR(p_off); + if (len < RTC_BYTES) { + kfree(p_off); + return -EIO; + } + + memcpy(&off, p_off, RTC_BYTES); + kfree(p_off); + + /* Sign extend from 48 to 64 bits, then arithmetic shift right 15 bits to get seconds */ + now = sign_extend64(ctr + off, RTC_BITS - 1) >> RTC_SEC_SHIFT; + rtc_time64_to_tm(now, tm); + + return ret; +} + +static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + u64 ctr = 0, off = 0; + int ret; + + ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES); + if (ret != RTC_BYTES) + return ret < 0 ? ret : -EIO; + + /* This sets the offset such that the set second begins now */ + off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr; + return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES); +} + +static const struct rtc_class_ops macsmc_rtc_ops = { + .read_time = macsmc_rtc_get_time, + .set_time = macsmc_rtc_set_time, +}; + +static int macsmc_rtc_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_rtc *rtc; + + /* Ignore devices without this functionality */ + if (!apple_smc_key_exists(smc, SMC_KEY(CLKM))) + return -ENODEV; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->dev = &pdev->dev; + rtc->smc = smc; + + pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "rtc"); + + rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset"); + if (IS_ERR(rtc->rtc_offset)) + return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset), + "Failed to get rtc_offset NVMEM cell\n"); + + rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtc->rtc_dev)) + return PTR_ERR(rtc->rtc_dev); + + rtc->rtc_dev->ops = &macsmc_rtc_ops; + rtc->rtc_dev->range_min = S64_MIN >> (RTC_SEC_SHIFT + (64 - RTC_BITS)); + rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS)); + + platform_set_drvdata(pdev, rtc); + + return devm_rtc_register_device(rtc->rtc_dev); +} + +static struct platform_driver macsmc_rtc_driver = { + .driver = { + .name = "macsmc-rtc", + .owner = THIS_MODULE, + }, + .probe = macsmc_rtc_probe, +}; +module_platform_driver(macsmc_rtc_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC RTC driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_ALIAS("platform:macsmc-rtc"); diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index a1596fefacff..694a44968f76 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -30,6 +30,20 @@ config APPLE_RTKIT Say 'y' here if you have an Apple SoC. +config APPLE_RTKIT_HELPER + tristate "Apple Generic RTKit helper co-processor" + depends on APPLE_RTKIT + depends on ARCH_APPLE || COMPILE_TEST + default ARCH_APPLE + help + Apple SoCs such as the M1 come with various co-processors running + their proprietary RTKit operating system. This option enables support + for a generic co-processor that does not implement any additional + in-band communications. It can be used for testing purposes, or for + coprocessors such as MTP that communicate over a different interface. + + Say 'y' here if you have an Apple SoC. + config APPLE_SART tristate "Apple SART DMA address filter" depends on ARCH_APPLE || COMPILE_TEST @@ -41,6 +55,16 @@ config APPLE_SART Say 'y' here if you have an Apple SoC. +config APPLE_DOCKCHANNEL + tristate "Apple DockChannel FIFO" + depends on ARCH_APPLE || COMPILE_TEST + default ARCH_APPLE + help + DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor + communications. + + Say 'y' here if you have an Apple SoC. + endmenu endif diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index e293770cf66d..07a95a883d3d 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -4,5 +4,11 @@ obj-$(CONFIG_APPLE_PMGR_PWRSTATE) += apple-pmgr-pwrstate.o obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o apple-rtkit-y = rtkit.o rtkit-crashlog.o +obj-$(CONFIG_APPLE_RTKIT_HELPER) += apple-rtkit-helper.o +apple-rtkit-helper-y = rtkit-helper.o + obj-$(CONFIG_APPLE_SART) += apple-sart.o apple-sart-y = sart.o + +obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o +apple-dockchannel-y = dockchannel.o diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c new file mode 100644 index 000000000000..c21ef7545e17 --- /dev/null +++ b/drivers/soc/apple/dockchannel.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple DockChannel FIFO driver + * Copyright The Asahi Linux Contributors + */ + +#include <asm/unaligned.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/irq.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/soc/apple/dockchannel.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +#define DOCKCHANNEL_MAX_IRQ 32 + +#define DOCKCHANNEL_TX_TIMEOUT_MS 1000 +#define DOCKCHANNEL_RX_TIMEOUT_MS 1000 + +#define IRQ_MASK 0x0 +#define IRQ_FLAG 0x4 + +#define IRQ_TX BIT(0) +#define IRQ_RX BIT(1) + +#define CONFIG_TX_THRESH 0x0 +#define CONFIG_RX_THRESH 0x4 + +#define DATA_TX8 0x4 +#define DATA_TX16 0x8 +#define DATA_TX24 0xc +#define DATA_TX32 0x10 +#define DATA_TX_FREE 0x14 +#define DATA_RX8 0x1c +#define DATA_RX16 0x20 +#define DATA_RX24 0x24 +#define DATA_RX32 0x28 +#define DATA_RX_COUNT 0x2c + +struct dockchannel { + struct device *dev; + int tx_irq; + int rx_irq; + + void __iomem *config_base; + void __iomem *data_base; + + u32 fifo_size; + bool awaiting; + struct completion tx_comp; + struct completion rx_comp; + + void *cookie; + void (*data_available)(void *cookie, size_t avail); +}; + +struct dockchannel_common { + struct device *dev; + struct irq_domain *domain; + int irq; + + void __iomem *irq_base; +}; + +/* Dockchannel FIFO functions */ + +static irqreturn_t dockchannel_tx_irq(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + disable_irq_nosync(irq); + complete(&dockchannel->tx_comp); + + return IRQ_HANDLED; +} + +static irqreturn_t dockchannel_rx_irq(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + disable_irq_nosync(irq); + + if (dockchannel->awaiting) { + return IRQ_WAKE_THREAD; + } else { + complete(&dockchannel->rx_comp); + return IRQ_HANDLED; + } +} + +static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); + + dockchannel->awaiting = false; + dockchannel->data_available(dockchannel->cookie, avail); + + return IRQ_HANDLED; +} + +int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count) +{ + size_t left = count; + const u8 *p = buf; + + while (left > 0) { + size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE); + size_t block = min(left, avail); + + if (avail == 0) { + size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left); + + writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH); + reinit_completion(&dockchannel->tx_comp); + enable_irq(dockchannel->tx_irq); + + if (!wait_for_completion_timeout(&dockchannel->tx_comp, + msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) { + disable_irq(dockchannel->tx_irq); + return -ETIMEDOUT; + } + + continue; + } + + while (block >= 4) { + writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32); + p += 4; + left -= 4; + block -= 4; + } + while (block > 0) { + writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8); + left--; + block--; + } + } + + return count; +} +EXPORT_SYMBOL(dockchannel_send); + +int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count) +{ + size_t left = count; + u8 *p = buf; + + while (left > 0) { + size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); + size_t block = min(left, avail); + + if (avail == 0) { + size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left); + + writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); + reinit_completion(&dockchannel->rx_comp); + enable_irq(dockchannel->rx_irq); + + if (!wait_for_completion_timeout(&dockchannel->rx_comp, + msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) { + disable_irq(dockchannel->rx_irq); + return -ETIMEDOUT; + } + + continue; + } + + while (block >= 4) { + put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p); + p += 4; + left -= 4; + block -= 4; + } + while (block > 0) { + *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8; + left--; + block--; + } + } + + return count; +} +EXPORT_SYMBOL(dockchannel_recv); + +int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count) +{ + size_t threshold = min((size_t)dockchannel->fifo_size, count); + + if (!count) { + dockchannel->awaiting = false; + disable_irq(dockchannel->rx_irq); + return 0; + } + + dockchannel->data_available = callback; + dockchannel->cookie = cookie; + dockchannel->awaiting = true; + writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); + enable_irq(dockchannel->rx_irq); + + return threshold; +} +EXPORT_SYMBOL(dockchannel_await); + +struct dockchannel *dockchannel_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel *dockchannel; + int ret; + + dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL); + if (!dockchannel) + return ERR_PTR(-ENOMEM); + + dockchannel->dev = dev; + dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); + if (IS_ERR(dockchannel->config_base)) + return (void *)dockchannel->config_base; + + dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data"); + if (IS_ERR(dockchannel->data_base)) + return (void *)dockchannel->data_base; + + ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property")); + + init_completion(&dockchannel->tx_comp); + init_completion(&dockchannel->rx_comp); + + dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx"); + if (dockchannel->tx_irq <= 0) { + return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq, + "Failed to get TX IRQ")); + } + + dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx"); + if (dockchannel->rx_irq <= 0) { + return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq, + "Failed to get RX IRQ")); + } + + ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN, + "apple-dockchannel-tx", dockchannel); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ")); + + ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq, + dockchannel_rx_irq_thread, IRQF_NO_AUTOEN, + "apple-dockchannel-rx", dockchannel); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ")); + + return dockchannel; +} +EXPORT_SYMBOL(dockchannel_init); + + +/* Dockchannel IRQchip */ + +static void dockchannel_irq(struct irq_desc *desc) +{ + unsigned int irq = irq_desc_get_irq(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct dockchannel_common *dcc = irq_get_handler_data(irq); + unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG); + int bit; + + chained_irq_enter(chip, desc); + + for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ) + generic_handle_domain_irq(dcc->domain, bit); + + chained_irq_exit(chip, desc); +} + +static void dockchannel_irq_ack(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + + writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG); +} + +static void dockchannel_irq_mask(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK); + + writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK); +} + +static void dockchannel_irq_unmask(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK); + + writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK); +} + +static const struct irq_chip dockchannel_irqchip = { + .name = "dockchannel-irqc", + .irq_ack = dockchannel_irq_ack, + .irq_mask = dockchannel_irq_mask, + .irq_unmask = dockchannel_irq_unmask, +}; + +static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_data(virq, d->host_data); + irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq); + + return 0; +} + +static const struct irq_domain_ops dockchannel_irq_domain_ops = { + .xlate = irq_domain_xlate_twocell, + .map = dockchannel_irq_domain_map, +}; + +static int dockchannel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel_common *dcc; + struct device_node *child; + + dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL); + if (!dcc) + return -ENOMEM; + + dcc->dev = dev; + platform_set_drvdata(pdev, dcc); + + dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq"); + if (IS_ERR(dcc->irq_base)) + return PTR_ERR(dcc->irq_base); + + writel_relaxed(0, dcc->irq_base + IRQ_MASK); + writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); + + dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ, + &dockchannel_irq_domain_ops, dcc); + if (!dcc->domain) + return -ENOMEM; + + dcc->irq = platform_get_irq(pdev, 0); + if (dcc->irq <= 0) + return dev_err_probe(dev, dcc->irq, "Failed to get IRQ"); + + irq_set_handler_data(dcc->irq, dcc); + irq_set_chained_handler(dcc->irq, dockchannel_irq); + + for_each_child_of_node(dev->of_node, child) + of_platform_device_create(child, NULL, dev); + + return 0; +} + +static int dockchannel_remove(struct platform_device *pdev) +{ + struct dockchannel_common *dcc = platform_get_drvdata(pdev); + int hwirq; + + device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy); + + irq_set_chained_handler_and_data(dcc->irq, NULL, NULL); + + for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++) + irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq)); + + irq_domain_remove(dcc->domain); + + writel_relaxed(0, dcc->irq_base + IRQ_MASK); + writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); + + return 0; +} + +static const struct of_device_id dockchannel_of_match[] = { + { .compatible = "apple,dockchannel" }, + {}, +}; +MODULE_DEVICE_TABLE(of, dockchannel_of_match); + +static struct platform_driver dockchannel_driver = { + .driver = { + .name = "dockchannel", + .of_match_table = dockchannel_of_match, + }, + .probe = dockchannel_probe, + .remove = dockchannel_remove, +}; +module_platform_driver(dockchannel_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple DockChannel driver"); diff --git a/drivers/soc/apple/rtkit-helper.c b/drivers/soc/apple/rtkit-helper.c new file mode 100644 index 000000000000..01c7f30e41e6 --- /dev/null +++ b/drivers/soc/apple/rtkit-helper.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple Generic RTKit helper coprocessor + * Copyright The Asahi Linux Contributors + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/soc/apple/rtkit.h> + +#define APPLE_ASC_CPU_CONTROL 0x44 +#define APPLE_ASC_CPU_CONTROL_RUN BIT(4) + +struct apple_rtkit_helper { + struct device *dev; + struct apple_rtkit *rtk; + + void __iomem *asc_base; + + struct resource *sram; + void __iomem *sram_base; +}; + +static int apple_rtkit_helper_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_rtkit_helper *helper = cookie; + struct resource res = { + .start = bfr->iova, + .end = bfr->iova + bfr->size - 1, + .name = "rtkit_map", + .flags = helper->sram->flags, + }; + + if (!bfr->iova) { + bfr->buffer = dma_alloc_coherent(helper->dev, bfr->size, + &bfr->iova, GFP_KERNEL); + if (!bfr->buffer) + return -ENOMEM; + return 0; + } + + if (!helper->sram) { + dev_err(helper->dev, + "RTKit buffer request with no SRAM region: %pR", &res); + return -EFAULT; + } + + if (res.end < res.start || !resource_contains(helper->sram, &res)) { + dev_err(helper->dev, + "RTKit buffer request outside SRAM region: %pR", &res); + return -EFAULT; + } + + bfr->iomem = helper->sram_base + (res.start - helper->sram->start); + bfr->is_mapped = true; + + return 0; +} + +static void apple_rtkit_helper_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) +{ + // no-op +} + +static const struct apple_rtkit_ops apple_rtkit_helper_ops = { + .shmem_setup = apple_rtkit_helper_shmem_setup, + .shmem_destroy = apple_rtkit_helper_shmem_destroy, +}; + +static int apple_rtkit_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_rtkit_helper *helper; + int ret; + + helper = devm_kzalloc(dev, sizeof(*helper), GFP_KERNEL); + if (!helper) + return -ENOMEM; + + helper->dev = dev; + platform_set_drvdata(pdev, helper); + + helper->asc_base = devm_platform_ioremap_resource_byname(pdev, "asc"); + if (IS_ERR(helper->asc_base)) + return PTR_ERR(helper->asc_base); + + helper->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (helper->sram) { + helper->sram_base = devm_ioremap_resource(dev, helper->sram); + if (IS_ERR(helper->sram_base)) + return dev_err_probe(dev, PTR_ERR(helper->sram_base), + "Failed to map SRAM region"); + } + + helper->rtk = + devm_apple_rtkit_init(dev, helper, NULL, 0, &apple_rtkit_helper_ops); + if (IS_ERR(helper->rtk)) + return dev_err_probe(dev, PTR_ERR(helper->rtk), + "Failed to intialize RTKit"); + + writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN, + helper->asc_base + APPLE_ASC_CPU_CONTROL); + + /* Works for both wake and boot */ + ret = apple_rtkit_wake(helper->rtk); + if (ret != 0) + return dev_err_probe(dev, ret, "Failed to wake up coprocessor"); + + return 0; +} + +static int apple_rtkit_helper_remove(struct platform_device *pdev) +{ + struct apple_rtkit_helper *helper = platform_get_drvdata(pdev); + + if (apple_rtkit_is_running(helper->rtk)) + apple_rtkit_quiesce(helper->rtk); + + writel_relaxed(0, helper->asc_base + APPLE_ASC_CPU_CONTROL); + + return 0; +} + +static const struct of_device_id apple_rtkit_helper_of_match[] = { + { .compatible = "apple,rtk-helper-asc4" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_rtkit_helper_of_match); + +static struct platform_driver apple_rtkit_helper_driver = { + .driver = { + .name = "rtkit-helper", + .of_match_table = apple_rtkit_helper_of_match, + }, + .probe = apple_rtkit_helper_probe, + .remove = apple_rtkit_helper_remove, +}; +module_platform_driver(apple_rtkit_helper_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple RTKit helper driver"); diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index cf1129e9f76b..0a449e95c22b 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -628,37 +628,11 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message, } EXPORT_SYMBOL_GPL(apple_rtkit_send_message); -int apple_rtkit_send_message_wait(struct apple_rtkit *rtk, u8 ep, u64 message, - unsigned long timeout, bool atomic) +int apple_rtkit_poll(struct apple_rtkit *rtk) { - DECLARE_COMPLETION_ONSTACK(completion); - int ret; - long t; - - ret = apple_rtkit_send_message(rtk, ep, message, &completion, atomic); - if (ret < 0) - return ret; - - if (atomic) { - ret = mbox_flush(rtk->mbox_chan, timeout); - if (ret < 0) - return ret; - - if (try_wait_for_completion(&completion)) - return 0; - - return -ETIME; - } else { - t = wait_for_completion_interruptible_timeout( - &completion, msecs_to_jiffies(timeout)); - if (t < 0) - return t; - else if (t == 0) - return -ETIME; - return 0; - } + return mbox_client_peek_data(rtk->mbox_chan); } -EXPORT_SYMBOL_GPL(apple_rtkit_send_message_wait); +EXPORT_SYMBOL_GPL(apple_rtkit_poll); int apple_rtkit_start_ep(struct apple_rtkit *rtk, u8 endpoint) { diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 3b1044ebc400..863e215a5127 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -79,6 +79,14 @@ config SPI_ALTERA_DFL Altera SPI master controller. The SPI master is connected to a SPI slave to Avalon bridge in a Intel MAX BMC. +config SPI_APPLE + tristate "Apple SoC SPI Controller platform driver" + depends on ARCH_APPLE || COMPILE_TEST + help + This enables support for the SPI controller present on + many Apple SoCs, including the t8103 (M1) and t600x + (M1 Pro/Max). + config SPI_AR934X tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver" depends on ATH79 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 0f44eb6083a5..f86ba8634f77 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o obj-$(CONFIG_SPI_ALTERA) += spi-altera-platform.o obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o +obj-$(CONFIG_SPI_APPLE) += spi-apple.o obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.o diff --git a/drivers/spi/spi-apple.c b/drivers/spi/spi-apple.c new file mode 100644 index 000000000000..c483ad3f69ef --- /dev/null +++ b/drivers/spi/spi-apple.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple SoC SPI device driver + * + * Copyright The Asahi Linux Contributors + * + * Based on spi-sifive.c, Copyright 2018 SiFive, Inc. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/spi/spi.h> + +#define APPLE_SPI_CTRL 0x000 +#define APPLE_SPI_CTRL_RUN BIT(0) +#define APPLE_SPI_CTRL_TX_RESET BIT(2) +#define APPLE_SPI_CTRL_RX_RESET BIT(3) + +#define APPLE_SPI_CFG 0x004 +#define APPLE_SPI_CFG_CPHA BIT(1) +#define APPLE_SPI_CFG_CPOL BIT(2) +#define APPLE_SPI_CFG_MODE GENMASK(6, 5) +#define APPLE_SPI_CFG_MODE_POLLED 0 +#define APPLE_SPI_CFG_MODE_IRQ 1 +#define APPLE_SPI_CFG_MODE_DMA 2 +#define APPLE_SPI_CFG_IE_RXCOMPLETE BIT(7) +#define APPLE_SPI_CFG_IE_TXRXTHRESH BIT(8) +#define APPLE_SPI_CFG_LSB_FIRST BIT(13) +#define APPLE_SPI_CFG_WORD_SIZE GENMASK(16, 15) +#define APPLE_SPI_CFG_WORD_SIZE_8B 0 +#define APPLE_SPI_CFG_WORD_SIZE_16B 1 +#define APPLE_SPI_CFG_WORD_SIZE_32B 2 +#define APPLE_SPI_CFG_FIFO_THRESH GENMASK(18, 17) +#define APPLE_SPI_CFG_FIFO_THRESH_8B 0 +#define APPLE_SPI_CFG_FIFO_THRESH_4B 1 +#define APPLE_SPI_CFG_FIFO_THRESH_1B 2 +#define APPLE_SPI_CFG_IE_TXCOMPLETE BIT(21) + +#define APPLE_SPI_STATUS 0x008 +#define APPLE_SPI_STATUS_RXCOMPLETE BIT(0) +#define APPLE_SPI_STATUS_TXRXTHRESH BIT(1) +#define APPLE_SPI_STATUS_TXCOMPLETE BIT(2) + +#define APPLE_SPI_PIN 0x00c +#define APPLE_SPI_PIN_KEEP_MOSI BIT(0) +#define APPLE_SPI_PIN_CS BIT(1) + +#define APPLE_SPI_TXDATA 0x010 +#define APPLE_SPI_RXDATA 0x020 +#define APPLE_SPI_CLKDIV 0x030 +#define APPLE_SPI_CLKDIV_MAX 0x7ff +#define APPLE_SPI_RXCNT 0x034 +#define APPLE_SPI_WORD_DELAY 0x038 +#define APPLE_SPI_TXCNT 0x04c + +#define APPLE_SPI_FIFOSTAT 0x10c +#define APPLE_SPI_FIFOSTAT_TXFULL BIT(4) +#define APPLE_SPI_FIFOSTAT_LEVEL_TX GENMASK(15, 8) +#define APPLE_SPI_FIFOSTAT_RXEMPTY BIT(20) +#define APPLE_SPI_FIFOSTAT_LEVEL_RX GENMASK(31, 24) + +#define APPLE_SPI_IE_XFER 0x130 +#define APPLE_SPI_IF_XFER 0x134 +#define APPLE_SPI_XFER_RXCOMPLETE BIT(0) +#define APPLE_SPI_XFER_TXCOMPLETE BIT(1) + +#define APPLE_SPI_IE_FIFO 0x138 +#define APPLE_SPI_IF_FIFO 0x13c +#define APPLE_SPI_FIFO_RXTHRESH BIT(4) +#define APPLE_SPI_FIFO_TXTHRESH BIT(5) +#define APPLE_SPI_FIFO_RXFULL BIT(8) +#define APPLE_SPI_FIFO_TXEMPTY BIT(9) +#define APPLE_SPI_FIFO_RXUNDERRUN BIT(16) +#define APPLE_SPI_FIFO_TXOVERFLOW BIT(17) + +#define APPLE_SPI_SHIFTCFG 0x150 +#define APPLE_SPI_SHIFTCFG_CLK_ENABLE BIT(0) +#define APPLE_SPI_SHIFTCFG_CS_ENABLE BIT(1) +#define APPLE_SPI_SHIFTCFG_AND_CLK_DATA BIT(8) +#define APPLE_SPI_SHIFTCFG_CS_AS_DATA BIT(9) +#define APPLE_SPI_SHIFTCFG_TX_ENABLE BIT(10) +#define APPLE_SPI_SHIFTCFG_RX_ENABLE BIT(11) +#define APPLE_SPI_SHIFTCFG_BITS GENMASK(21, 16) +#define APPLE_SPI_SHIFTCFG_OVERRIDE_CS BIT(24) + +#define APPLE_SPI_PINCFG 0x154 +#define APPLE_SPI_PINCFG_KEEP_CLK BIT(0) +#define APPLE_SPI_PINCFG_KEEP_CS BIT(1) +#define APPLE_SPI_PINCFG_KEEP_MOSI BIT(2) +#define APPLE_SPI_PINCFG_CLK_IDLE_VAL BIT(8) +#define APPLE_SPI_PINCFG_CS_IDLE_VAL BIT(9) +#define APPLE_SPI_PINCFG_MOSI_IDLE_VAL BIT(10) + +#define APPLE_SPI_DELAY_PRE 0x160 +#define APPLE_SPI_DELAY_POST 0x168 +#define APPLE_SPI_DELAY_ENABLE BIT(0) +#define APPLE_SPI_DELAY_NO_INTERBYTE BIT(1) +#define APPLE_SPI_DELAY_SET_SCK BIT(4) +#define APPLE_SPI_DELAY_SET_MOSI BIT(6) +#define APPLE_SPI_DELAY_SCK_VAL BIT(8) +#define APPLE_SPI_DELAY_MOSI_VAL BIT(12) + +#define APPLE_SPI_FIFO_DEPTH 16 + +/* + * The slowest refclock available is 24MHz, the highest divider is 0x7ff, + * the largest word size is 32 bits, the FIFO depth is 16, the maximum + * intra-word delay is 0xffff refclocks. So the maximum time a transfer + * cycle can take is: + * + * (0x7ff * 32 + 0xffff) * 16 / 24e6 Hz ~= 87ms + * + * Double it and round it up to 200ms for good measure. + */ +#define APPLE_SPI_TIMEOUT_MS 200 + +struct apple_spi { + void __iomem *regs; /* MMIO register address */ + struct clk *clk; /* bus clock */ + struct completion done; /* wake-up from interrupt */ +}; + +static inline void reg_write(struct apple_spi *spi, int offset, u32 value) +{ + writel_relaxed(value, spi->regs + offset); +} + +static inline u32 reg_read(struct apple_spi *spi, int offset) +{ + return readl_relaxed(spi->regs + offset); +} + +static inline void reg_mask(struct apple_spi *spi, int offset, u32 clear, u32 set) +{ + u32 val = reg_read(spi, offset); + + val &= ~clear; + val |= set; + reg_write(spi, offset, val); +} + +static void apple_spi_init(struct apple_spi *spi) +{ + /* Set CS high (inactive) and disable override and auto-CS */ + reg_write(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS); + reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_OVERRIDE_CS, 0); + reg_mask(spi, APPLE_SPI_PINCFG, APPLE_SPI_PINCFG_CS_IDLE_VAL, APPLE_SPI_PINCFG_KEEP_CS); + + /* Reset FIFOs */ + reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET); + + /* Configure defaults */ + reg_write(spi, APPLE_SPI_CFG, + FIELD_PREP(APPLE_SPI_CFG_FIFO_THRESH, APPLE_SPI_CFG_FIFO_THRESH_8B) | + FIELD_PREP(APPLE_SPI_CFG_MODE, APPLE_SPI_CFG_MODE_IRQ) | + FIELD_PREP(APPLE_SPI_CFG_WORD_SIZE, APPLE_SPI_CFG_WORD_SIZE_8B)); + + /* Disable IRQs */ + reg_write(spi, APPLE_SPI_IE_FIFO, 0); + reg_write(spi, APPLE_SPI_IE_XFER, 0); + + /* Disable delays */ + reg_write(spi, APPLE_SPI_DELAY_PRE, 0); + reg_write(spi, APPLE_SPI_DELAY_POST, 0); +} + +static int apple_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *msg) +{ + struct apple_spi *spi = spi_controller_get_devdata(ctlr); + struct spi_device *device = msg->spi; + + u32 cfg = ((device->mode & SPI_CPHA ? APPLE_SPI_CFG_CPHA : 0) | + (device->mode & SPI_CPOL ? APPLE_SPI_CFG_CPOL : 0) | + (device->mode & SPI_LSB_FIRST ? APPLE_SPI_CFG_LSB_FIRST : 0)); + + /* Update core config */ + reg_mask(spi, APPLE_SPI_CFG, + APPLE_SPI_CFG_CPHA | APPLE_SPI_CFG_CPOL | APPLE_SPI_CFG_LSB_FIRST, cfg); + + return 0; +} + +static void apple_spi_set_cs(struct spi_device *device, bool is_high) +{ + struct apple_spi *spi = spi_controller_get_devdata(device->controller); + + reg_mask(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS, is_high ? APPLE_SPI_PIN_CS : 0); +} + +static bool apple_spi_prep_transfer(struct apple_spi *spi, struct spi_transfer *t) +{ + u32 cr, fifo_threshold; + + /* Calculate and program the clock rate */ + cr = DIV_ROUND_UP(clk_get_rate(spi->clk), t->speed_hz); + reg_write(spi, APPLE_SPI_CLKDIV, min_t(u32, cr, APPLE_SPI_CLKDIV_MAX)); + + /* Update bits per word */ + reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_BITS, + FIELD_PREP(APPLE_SPI_SHIFTCFG_BITS, t->bits_per_word)); + + /* We will want to poll if the time we need to wait is + * less than the context switching time. + * Let's call that threshold 5us. The operation will take: + * bits_per_word * fifo_threshold / hz <= 5 * 10^-6 + * 200000 * bits_per_word * fifo_threshold <= hz + */ + fifo_threshold = APPLE_SPI_FIFO_DEPTH / 2; + return (200000 * t->bits_per_word * fifo_threshold) <= t->speed_hz; +} + +static irqreturn_t apple_spi_irq(int irq, void *dev_id) +{ + struct apple_spi *spi = dev_id; + u32 fifo = reg_read(spi, APPLE_SPI_IF_FIFO) & reg_read(spi, APPLE_SPI_IE_FIFO); + u32 xfer = reg_read(spi, APPLE_SPI_IF_XFER) & reg_read(spi, APPLE_SPI_IE_XFER); + + if (fifo || xfer) { + /* Disable interrupts until next transfer */ + reg_write(spi, APPLE_SPI_IE_XFER, 0); + reg_write(spi, APPLE_SPI_IE_FIFO, 0); + complete(&spi->done); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int apple_spi_wait(struct apple_spi *spi, u32 fifo_bit, u32 xfer_bit, int poll) +{ + int ret = 0; + + if (poll) { + u32 fifo, xfer; + unsigned long timeout = jiffies + APPLE_SPI_TIMEOUT_MS * HZ / 1000; + + do { + fifo = reg_read(spi, APPLE_SPI_IF_FIFO); + xfer = reg_read(spi, APPLE_SPI_IF_XFER); + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + break; + } + } while (!((fifo & fifo_bit) || (xfer & xfer_bit))); + } else { + reinit_completion(&spi->done); + reg_write(spi, APPLE_SPI_IE_XFER, xfer_bit); + reg_write(spi, APPLE_SPI_IE_FIFO, fifo_bit); + + if (!wait_for_completion_timeout(&spi->done, + msecs_to_jiffies(APPLE_SPI_TIMEOUT_MS))) + ret = -ETIMEDOUT; + + reg_write(spi, APPLE_SPI_IE_XFER, 0); + reg_write(spi, APPLE_SPI_IE_FIFO, 0); + } + + return ret; +} + +static void apple_spi_tx(struct apple_spi *spi, const void **tx_ptr, u32 *left, + unsigned int bytes_per_word) +{ + u32 inuse, words, wrote; + + if (!*tx_ptr) + return; + + inuse = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_TX, reg_read(spi, APPLE_SPI_FIFOSTAT)); + words = wrote = min_t(u32, *left, APPLE_SPI_FIFO_DEPTH - inuse); + + if (!words) + return; + + *left -= words; + + switch (bytes_per_word) { + case 1: { + const u8 *p = *tx_ptr; + + while (words--) + reg_write(spi, APPLE_SPI_TXDATA, *p++); + break; + } + case 2: { + const u16 *p = *tx_ptr; + + while (words--) + reg_write(spi, APPLE_SPI_TXDATA, *p++); + break; + } + case 4: { + const u32 *p = *tx_ptr; + + while (words--) + reg_write(spi, APPLE_SPI_TXDATA, *p++); + break; + } + default: + WARN_ON(1); + } + + *tx_ptr = ((u8 *)*tx_ptr) + bytes_per_word * wrote; +} + +static void apple_spi_rx(struct apple_spi *spi, void **rx_ptr, u32 *left, + unsigned int bytes_per_word) +{ + u32 words, read; + + if (!*rx_ptr) + return; + + words = read = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_RX, reg_read(spi, APPLE_SPI_FIFOSTAT)); + WARN_ON(words > *left); + + if (!words) + return; + + *left -= min_t(u32, *left, words); + + switch (bytes_per_word) { + case 1: { + u8 *p = *rx_ptr; + + while (words--) + *p++ = reg_read(spi, APPLE_SPI_RXDATA); + break; + } + case 2: { + u16 *p = *rx_ptr; + + while (words--) + *p++ = reg_read(spi, APPLE_SPI_RXDATA); + break; + } + case 4: { + u32 *p = *rx_ptr; + + while (words--) + *p++ = reg_read(spi, APPLE_SPI_RXDATA); + break; + } + default: + WARN_ON(1); + } + + *rx_ptr = ((u8 *)*rx_ptr) + bytes_per_word * read; +} + +static int apple_spi_transfer_one(struct spi_controller *ctlr, struct spi_device *device, + struct spi_transfer *t) +{ + struct apple_spi *spi = spi_controller_get_devdata(ctlr); + bool poll = apple_spi_prep_transfer(spi, t); + const void *tx_ptr = t->tx_buf; + void *rx_ptr = t->rx_buf; + unsigned int bytes_per_word; + u32 words, remaining_tx, remaining_rx; + u32 xfer_flags = 0; + u32 fifo_flags; + int retries = 100; + int ret = 0; + + if (t->bits_per_word > 16) + bytes_per_word = 4; + else if (t->bits_per_word > 8) + bytes_per_word = 2; + else + bytes_per_word = 1; + + words = t->len / bytes_per_word; + remaining_tx = tx_ptr ? words : 0; + remaining_rx = rx_ptr ? words : 0; + + /* Reset FIFOs */ + reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET); + + /* Clear IRQ flags */ + reg_write(spi, APPLE_SPI_IF_XFER, ~0); + reg_write(spi, APPLE_SPI_IF_FIFO, ~0); + + /* Determine transfer completion flags we wait for */ + if (tx_ptr) + xfer_flags |= APPLE_SPI_XFER_TXCOMPLETE; + if (rx_ptr) + xfer_flags |= APPLE_SPI_XFER_RXCOMPLETE; + + /* Set transfer length */ + reg_write(spi, APPLE_SPI_TXCNT, remaining_tx); + reg_write(spi, APPLE_SPI_RXCNT, remaining_rx); + + /* Prime transmit FIFO */ + apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word); + + /* Start transfer */ + reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RUN); + + /* TX again since a few words get popped off immediately */ + apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word); + + while (xfer_flags) { + fifo_flags = 0; + + if (remaining_tx) + fifo_flags |= APPLE_SPI_FIFO_TXTHRESH; + if (remaining_rx) + fifo_flags |= APPLE_SPI_FIFO_RXTHRESH; + + /* Wait for anything to happen */ + ret = apple_spi_wait(spi, fifo_flags, xfer_flags, poll); + if (ret) { + dev_err(&ctlr->dev, "transfer timed out (remaining %d tx, %d rx)\n", + remaining_tx, remaining_rx); + goto err; + } + + /* Stop waiting on transfer halves once they complete */ + xfer_flags &= ~reg_read(spi, APPLE_SPI_IF_XFER); + + /* Transmit and receive everything we can */ + apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word); + apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word); + } + + /* + * Sometimes the transfer completes before the last word is in the RX FIFO. + * Normally one retry is all it takes to get the last word out. + */ + while (remaining_rx && retries--) + apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word); + + if (remaining_tx) + dev_err(&ctlr->dev, "transfer completed with %d words left to transmit\n", + remaining_tx); + if (remaining_rx) + dev_err(&ctlr->dev, "transfer completed with %d words left to receive\n", + remaining_rx); + +err: + fifo_flags = reg_read(spi, APPLE_SPI_IF_FIFO); + WARN_ON(fifo_flags & APPLE_SPI_FIFO_TXOVERFLOW); + WARN_ON(fifo_flags & APPLE_SPI_FIFO_RXUNDERRUN); + + /* Stop transfer */ + reg_write(spi, APPLE_SPI_CTRL, 0); + + return ret; +} + +static void apple_spi_clk_disable_unprepare(void *data) +{ + clk_disable_unprepare(data); +} + +static int apple_spi_probe(struct platform_device *pdev) +{ + struct apple_spi *spi; + int ret, irq; + struct spi_controller *ctlr; + + ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(struct apple_spi)); + if (!ctlr) + return dev_err_probe(&pdev->dev, -ENOMEM, "out of memory\n"); + + spi = spi_controller_get_devdata(ctlr); + init_completion(&spi->done); + platform_set_drvdata(pdev, ctlr); + + spi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(spi->regs)) + return PTR_ERR(spi->regs); + + spi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(spi->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), "Unable to find bus clock\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, apple_spi_irq, 0, + dev_name(&pdev->dev), spi); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Unable to bind to interrupt\n"); + + ret = clk_prepare_enable(spi->clk); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Unable to enable bus clock\n"); + + ret = devm_add_action_or_reset(&pdev->dev, apple_spi_clk_disable_unprepare, spi->clk); + if (ret) + return ret; + + ctlr->dev.of_node = pdev->dev.of_node; + ctlr->bus_num = pdev->id; + ctlr->num_chipselect = 1; + ctlr->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST; + ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); + ctlr->flags = 0; + ctlr->prepare_message = apple_spi_prepare_message; + ctlr->set_cs = apple_spi_set_cs; + ctlr->transfer_one = apple_spi_transfer_one; + ctlr->auto_runtime_pm = true; + + pm_runtime_set_active(&pdev->dev); + devm_pm_runtime_enable(&pdev->dev); + + apple_spi_init(spi); + + ret = devm_spi_register_controller(&pdev->dev, ctlr); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "devm_spi_register_controller failed\n"); + + return 0; +} + +static const struct of_device_id apple_spi_of_match[] = { + { .compatible = "apple,spi", }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_spi_of_match); + +static struct platform_driver apple_spi_driver = { + .probe = apple_spi_probe, + .driver = { + .name = "apple-spi", + .owner = THIS_MODULE, + .of_match_table = apple_spi_of_match, + }, +}; +module_platform_driver(apple_spi_driver); + +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_DESCRIPTION("Apple SoC SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index ea09d1b42bf6..c8342b950783 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -2059,6 +2059,22 @@ void spi_flush_queue(struct spi_controller *ctlr) /*-------------------------------------------------------------------------*/ #if defined(CONFIG_OF) +static void of_spi_parse_dt_cs_delay(struct device_node *nc, + struct spi_delay *delay, const char *prop) +{ + u32 value; + + if (!of_property_read_u32(nc, prop, &value)) { + if (value > U16_MAX) { + delay->value = DIV_ROUND_UP(value, 1000); + delay->unit = SPI_DELAY_UNIT_USECS; + } else { + delay->value = value; + delay->unit = SPI_DELAY_UNIT_NSECS; + } + } +} + static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi, struct device_node *nc) { @@ -2148,6 +2164,11 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi, if (!of_property_read_u32(nc, "spi-max-frequency", &value)) spi->max_speed_hz = value; + /* Device CS delays */ + of_spi_parse_dt_cs_delay(nc, &spi->cs_setup, "spi-cs-setup-delay-ns"); + of_spi_parse_dt_cs_delay(nc, &spi->cs_hold, "spi-cs-hold-delay-ns"); + of_spi_parse_dt_cs_delay(nc, &spi->cs_inactive, "spi-cs-inactive-delay-ns"); + return 0; } diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index 737802046314..96c73c5b5720 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -45,4 +45,12 @@ config SPMI_MTK_PMIF This is required for communicating with Mediatek PMICs and other devices that have the SPMI interface. +config SPMI_APPLE + tristate "Apple SoC SPMI Controller platform driver" + depends on ARCH_APPLE || COMPILE_TEST + help + This enables basic support for the SPMI controller present on + many Apple SoCs, including the t8103 (M1) and t600x + (M1 Pro/Max). + endif diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile index 9d974424c8c1..989b84bbca60 100644 --- a/drivers/spmi/Makefile +++ b/drivers/spmi/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_SPMI) += spmi.o obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o obj-$(CONFIG_SPMI_MTK_PMIF) += spmi-mtk-pmif.o +obj-$(CONFIG_SPMI_APPLE) += spmi-apple-controller.o diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c new file mode 100644 index 000000000000..5b49d6c609f5 --- /dev/null +++ b/drivers/spmi/spmi-apple-controller.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple SoC SPMI device driver + * + * Copyright The Asahi Linux Contributors + * + * Inspired by: + * OpenBSD support Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org> + * Correllium support Copyright (C) 2021 Corellium LLC + * hisi-spmi-controller.c + * spmi-pmic-ard.c Copyright (c) 2021, The Linux Foundation. + */ + +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/spmi.h> + + +/* SPMI Controller Registers */ +#define SPMI_STATUS_REG 0 +#define SPMI_CMD_REG 0x4 +#define SPMI_RSP_REG 0x8 + +#define SPMI_RX_FIFO_EMPTY BIT(24) +#define SPMI_TX_FIFO_EMPTY BIT(8) + +/* Apple SPMI controler */ +struct apple_spmi { + void __iomem *regs; + struct spmi_controller *ctrl; +}; + +static inline u32 read_reg(struct apple_spmi *spmi, int offset) +{ + return(readl_relaxed(spmi->regs + offset)); +} + +static inline void write_reg(u32 value, struct apple_spmi *spmi, int offset) +{ + writel_relaxed(value, spmi->regs + offset); +} + +static int spmi_read_cmd(struct spmi_controller *ctrl, + u8 opc, u8 slave_id, u16 slave_addr, u8 *__buf, size_t bc) +{ + struct apple_spmi *spmi; + u32 spmi_cmd = opc|slave_id<<8|slave_addr<<16|(bc-1)|(1<<15); + u32 rsp; + volatile u32 status; + size_t len_to_read; + u8 i; + + spmi = spmi_controller_get_drvdata(ctrl); + + write_reg(spmi_cmd, spmi, SPMI_CMD_REG); + + /* Wait for Rx FIFO to have something */ + /* Quite ugly msleep, need to find a better way to do it */ + i=0; + do { + status=read_reg(spmi, SPMI_STATUS_REG); + msleep(10); + i+=1; + } while ((status & SPMI_RX_FIFO_EMPTY) && i<5); + + if(i>=5){ + dev_err(&ctrl->dev,"spmi_read_cmd:took to long to get the status"); + return -1; + } + + /* Read SPMI reply status */ + rsp=read_reg(spmi, SPMI_RSP_REG); + + len_to_read = 0; + /* Read SPMI data reply */ + while (!( status & SPMI_RX_FIFO_EMPTY ) && (len_to_read < bc )) { + rsp=read_reg(spmi, SPMI_RSP_REG); + i=0; + while ((len_to_read<bc)&&(i<4)) { + __buf[len_to_read++]=((0xff<<(8*i))&rsp)>>(8*i); + i+=1; + } + } + + return 0; +} + +static int spmi_write_cmd(struct spmi_controller *ctrl, + u8 opc, u8 slave_id, u16 slave_addr, const u8 *__buf, size_t bc) +{ + struct apple_spmi *spmi; + u32 spmi_cmd = opc|slave_id<<8|slave_addr<<16|(bc-1)|(1<<15); + volatile u32 rsp; + volatile u32 status; + size_t i=0,j; + + spmi = spmi_controller_get_drvdata(ctrl); + + write_reg(spmi_cmd, spmi, SPMI_CMD_REG); + + while (i<bc) { + j=0; + spmi_cmd=0; + while ((j<4)&(i<bc)) { + spmi_cmd |= __buf[i++]<<(j++*8); + } + write_reg(spmi_cmd, spmi, SPMI_CMD_REG); + } + + /* Wait for Rx FIFO to have something */ + /* Quite ugly msleep, need to find a better way to do it */ + i=0; + do { + status=read_reg(spmi, SPMI_STATUS_REG); + msleep(10); + i+=1; + } while ((status & SPMI_RX_FIFO_EMPTY) && i<5); + + if(i>=5){ + dev_err(&ctrl->dev,"spmi_write_cmd:took to long to get the status"); + return -1; + } + + rsp = read_reg(spmi, SPMI_RSP_REG); + + return 0; +} + +static int spmi_controller_probe(struct platform_device *pdev) +{ + struct apple_spmi *spmi; + struct spmi_controller *ctrl; + int ret; + + ctrl = spmi_controller_alloc(&pdev->dev, sizeof(struct apple_spmi)); + if (IS_ERR(ctrl)) { + dev_err_probe(&pdev->dev, PTR_ERR(ctrl), "Can't allocate spmi_controller data\n"); + return -ENOMEM; + } + + spmi = spmi_controller_get_drvdata(ctrl); + spmi->ctrl=ctrl; + platform_set_drvdata(pdev, ctrl); + + spmi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(spmi->regs)) { + dev_err_probe(&pdev->dev, PTR_ERR(spmi->regs), "Can't get ioremap regs.\n"); + return PTR_ERR(spmi->regs); + } + + ctrl->dev.of_node = of_node_get(pdev->dev.of_node); + + /* Callbacks */ + ctrl->read_cmd = spmi_read_cmd; + ctrl->write_cmd = spmi_write_cmd; + + ret = spmi_controller_add(ctrl); + if (ret) { + dev_err(&pdev->dev, "spmi_controller_add failed with error %d!\n", ret); + goto err_put_controller; + } + + /* Let's look for other nodes in device tree like the rtc */ + ret = devm_of_platform_populate(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "spmi_controller_probe: devm_of_platform_populate failed with error %d!\n", ret); + goto err_devm_of_platform_populate; + } + + return 0; + +err_put_controller: + spmi_controller_put(ctrl); +err_devm_of_platform_populate: + return ret; +} + +static int spmi_del_controller(struct platform_device *pdev) +{ + struct spmi_controller *ctrl = platform_get_drvdata(pdev); + + spmi_controller_remove(ctrl); + spmi_controller_put(ctrl); + return 0; +} + +static const struct of_device_id spmi_controller_match_table[] = { + {.compatible = "apple,spmi",}, + {} +}; +MODULE_DEVICE_TABLE(of, spmi_controller_match_table); + +static struct platform_driver spmi_controller_driver = { + .probe = spmi_controller_probe, + .remove = spmi_del_controller, + .driver = { + .name = "apple-spmi", + .owner = THIS_MODULE, + .of_match_table = spmi_controller_match_table, + }, +}; +module_platform_driver(spmi_controller_driver); + +MODULE_AUTHOR("Jean-Francois Bortolotti <jeff@borto.fr>"); +MODULE_DESCRIPTION("Apple SoC SPMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index 1afe47b62ad5..2a4ba4f3d23c 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -40,6 +40,7 @@ #include <linux/clk.h> #include <linux/cpufreq.h> #include <linux/of.h> +#include <linux/pm_runtime.h> #include <asm/irq.h> /* UART name and device definitions */ @@ -1354,30 +1355,49 @@ static int apple_s5l_serial_startup(struct uart_port *port) /* power power management control */ +static int __maybe_unused s3c24xx_serial_runtime_suspend(struct device *dev) +{ + struct uart_port *port = dev_get_drvdata(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + int timeout = 10000; + + while (--timeout && !s3c24xx_serial_txempty_nofifo(port)) + udelay(100); + + if (!IS_ERR(ourport->baudclk)) + clk_disable_unprepare(ourport->baudclk); + + clk_disable_unprepare(ourport->clk); + return 0; +}; + +static int __maybe_unused s3c24xx_serial_runtime_resume(struct device *dev) +{ + struct uart_port *port = dev_get_drvdata(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + clk_prepare_enable(ourport->clk); + + if (!IS_ERR(ourport->baudclk)) + clk_prepare_enable(ourport->baudclk); + return 0; +}; + static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level, unsigned int old) { struct s3c24xx_uart_port *ourport = to_ourport(port); - int timeout = 10000; ourport->pm_level = level; switch (level) { - case 3: - while (--timeout && !s3c24xx_serial_txempty_nofifo(port)) - udelay(100); - - if (!IS_ERR(ourport->baudclk)) - clk_disable_unprepare(ourport->baudclk); - - clk_disable_unprepare(ourport->clk); + case UART_PM_STATE_OFF: + pm_runtime_mark_last_busy(port->dev); + pm_runtime_put_sync(port->dev); break; - case 0: - clk_prepare_enable(ourport->clk); - - if (!IS_ERR(ourport->baudclk)) - clk_prepare_enable(ourport->baudclk); + case UART_PM_STATE_ON: + pm_runtime_get_sync(port->dev); break; default: dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level); @@ -2248,18 +2268,15 @@ static int s3c24xx_serial_probe(struct platform_device *pdev) } } + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + dev_dbg(&pdev->dev, "%s: adding port\n", __func__); uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); platform_set_drvdata(pdev, &ourport->port); - /* - * Deactivate the clock enabled in s3c24xx_serial_init_port here, - * so that a potential re-enablement through the pm-callback overlaps - * and keeps the clock enabled in this case. - */ - clk_disable_unprepare(ourport->clk); - if (!IS_ERR(ourport->baudclk)) - clk_disable_unprepare(ourport->baudclk); + pm_runtime_put_sync(&pdev->dev); ret = s3c24xx_serial_cpufreq_register(ourport); if (ret < 0) @@ -2273,10 +2290,21 @@ static int s3c24xx_serial_probe(struct platform_device *pdev) static int s3c24xx_serial_remove(struct platform_device *dev) { struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); if (port) { + pm_runtime_get_sync(&dev->dev); + s3c24xx_serial_cpufreq_deregister(to_ourport(port)); uart_remove_one_port(&s3c24xx_uart_drv, port); + + clk_disable_unprepare(ourport->clk); + if (!IS_ERR(ourport->baudclk)) + clk_disable_unprepare(ourport->baudclk); + + pm_runtime_disable(&dev->dev); + pm_runtime_set_suspended(&dev->dev); + pm_runtime_put_noidle(&dev->dev); } uart_unregister_driver(&s3c24xx_uart_drv); @@ -2285,8 +2313,8 @@ static int s3c24xx_serial_remove(struct platform_device *dev) } /* UART power management code */ -#ifdef CONFIG_PM_SLEEP -static int s3c24xx_serial_suspend(struct device *dev) + +static int __maybe_unused s3c24xx_serial_suspend(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); @@ -2296,7 +2324,7 @@ static int s3c24xx_serial_suspend(struct device *dev) return 0; } -static int s3c24xx_serial_resume(struct device *dev) +static int __maybe_unused s3c24xx_serial_resume(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); @@ -2316,7 +2344,7 @@ static int s3c24xx_serial_resume(struct device *dev) return 0; } -static int s3c24xx_serial_resume_noirq(struct device *dev) +static int __maybe_unused s3c24xx_serial_resume_noirq(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); @@ -2386,16 +2414,14 @@ static int s3c24xx_serial_resume_noirq(struct device *dev) } static const struct dev_pm_ops s3c24xx_serial_pm_ops = { +#ifdef CONFIG_PM_SLEEP .suspend = s3c24xx_serial_suspend, .resume = s3c24xx_serial_resume, .resume_noirq = s3c24xx_serial_resume_noirq, +#endif + SET_RUNTIME_PM_OPS(s3c24xx_serial_runtime_suspend, + s3c24xx_serial_runtime_resume, NULL) }; -#define SERIAL_SAMSUNG_PM_OPS (&s3c24xx_serial_pm_ops) - -#else /* !CONFIG_PM_SLEEP */ - -#define SERIAL_SAMSUNG_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ /* Console code */ @@ -2936,7 +2962,7 @@ static struct platform_driver samsung_serial_driver = { .id_table = s3c24xx_serial_driver_ids, .driver = { .name = "samsung-uart", - .pm = SERIAL_SAMSUNG_PM_OPS, + .pm = &s3c24xx_serial_pm_ops, .of_match_table = of_match_ptr(s3c24xx_uart_dt_match), }, }; diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 573421984948..b4c58deccabb 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -116,6 +116,9 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) dwc->current_dr_role = mode; } +static void dwc3_core_exit(struct dwc3 *dwc); +static int dwc3_core_init_for_resume(struct dwc3 *dwc); + static void __dwc3_set_mode(struct work_struct *work) { struct dwc3 *dwc = work_to_dwc(work); @@ -130,10 +133,11 @@ static void __dwc3_set_mode(struct work_struct *work) if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG) dwc3_otg_update(dwc, 0); - if (!dwc->desired_dr_role) + if (!dwc->desired_dr_role && !dwc->role_switch_reset_quirk) goto out; - if (dwc->desired_dr_role == dwc->current_dr_role) + if (dwc->desired_dr_role == dwc->current_dr_role && + !dwc->role_switch_reset_quirk) goto out; if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev) @@ -158,6 +162,34 @@ static void __dwc3_set_mode(struct work_struct *work) break; } + if (dwc->role_switch_reset_quirk) { + if (dwc->current_dr_role) { + dwc->current_dr_role = 0; + dwc3_core_exit(dwc); + } + + if (dwc->desired_dr_role) { + /* + * the first call to __dwc3_set_mode comes from + * dwc3_drd_init. In that case dwc3_core_init has been + * called but dwc->current_dr_role is zero such that + * we must not reinitialize the core again here. + */ + if (dwc->role_switch_reset_quirk_initialized) { + ret = dwc3_core_init_for_resume(dwc); + if (ret) { + dev_err(dwc->dev, + "failed to reinitialize core\n"); + goto out; + } + } + + dwc->role_switch_reset_quirk_initialized = 1; + } else { + goto out; + } + } + /* For DRD host or device mode only */ if (dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) { reg = dwc3_readl(dwc->regs, DWC3_GCTL); @@ -1764,6 +1796,9 @@ static int dwc3_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(dwc->susp_clk), "could not get suspend clock\n"); } + + if (of_device_is_compatible(dev->of_node, "apple,dwc3")) + dwc->role_switch_reset_quirk = true; } ret = reset_control_deassert(dwc->reset); @@ -1900,7 +1935,6 @@ static int dwc3_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM static int dwc3_core_init_for_resume(struct dwc3 *dwc) { int ret; @@ -1927,6 +1961,7 @@ assert_reset: return ret; } +#ifdef CONFIG_PM static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) { unsigned long flags; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 81c486b3941c..ee0225ce2234 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1103,6 +1103,9 @@ struct dwc3_scratchpad_array { * 3 - Reserved * @dis_metastability_quirk: set to disable metastability quirk. * @dis_split_quirk: set to disable split boundary. + * @role_switch_reset_quirk: set to force reinitialization after any role switch + * @role_switch_reset_quirk_initialized: set to true after the first role switch + * which is triggered from dwc3_drd_init directly * @imod_interval: set the interrupt moderation interval in 250ns * increments or 0 to disable. * @max_cfg_eps: current max number of IN eps used across all USB configs. @@ -1318,6 +1321,9 @@ struct dwc3 { unsigned dis_split_quirk:1; unsigned async_callbacks:1; + unsigned role_switch_reset_quirk:1; + unsigned role_switch_reset_quirk_initialized:1; + u16 imod_interval; int max_cfg_eps; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 039bf241769a..4579505cac1f 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -461,6 +461,9 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, break; } + if (dwc->role_switch_reset_quirk && role == USB_ROLE_NONE) + mode = 0; + dwc3_set_mode(dwc, mode); return 0; } @@ -489,6 +492,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) role = USB_ROLE_DEVICE; break; } + + if (dwc->role_switch_reset_quirk && !dwc->current_dr_role) + role = USB_ROLE_NONE; + spin_unlock_irqrestore(&dwc->lock, flags); return role; } diff --git a/include/linux/bcma/bcma_driver_chipcommon.h b/include/linux/bcma/bcma_driver_chipcommon.h index e3314f746bfa..2d94c30ed439 100644 --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -271,6 +271,7 @@ #define BCMA_CC_SROM_CONTROL_OP_WRDIS 0x40000000 #define BCMA_CC_SROM_CONTROL_OP_WREN 0x60000000 #define BCMA_CC_SROM_CONTROL_OTPSEL 0x00000010 +#define BCMA_CC_SROM_CONTROL_OTP_PRESENT 0x00000020 #define BCMA_CC_SROM_CONTROL_LOCK 0x00000008 #define BCMA_CC_SROM_CONTROL_SIZE_MASK 0x00000006 #define BCMA_CC_SROM_CONTROL_SIZE_1K 0x00000000 diff --git a/include/linux/hid.h b/include/linux/hid.h index 4363a63b9775..dacfc4c7ce01 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -569,7 +569,9 @@ struct hid_input { enum hid_type { HID_TYPE_OTHER = 0, HID_TYPE_USBMOUSE, - HID_TYPE_USBNONE + HID_TYPE_USBNONE, + HID_TYPE_SPI_KEYBOARD, + HID_TYPE_SPI_MOUSE, }; enum hid_battery_status { @@ -715,6 +717,8 @@ struct hid_descriptor { .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod) #define HID_I2C_DEVICE(ven, prod) \ .bus = BUS_I2C, .vendor = (ven), .product = (prod) +#define HID_SPI_DEVICE(ven, prod) \ + .bus = BUS_SPI, .vendor = (ven), .product = (prod) #define HID_REPORT_ID(rep) \ .report_type = (rep) diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h index 86af6f0a00a2..76b98511cbc8 100644 --- a/include/linux/io-pgtable.h +++ b/include/linux/io-pgtable.h @@ -17,6 +17,7 @@ enum io_pgtable_fmt { ARM_MALI_LPAE, AMD_IOMMU_V1, APPLE_DART, + APPLE_DART2, IO_PGTABLE_NUM_FMTS, }; diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h new file mode 100644 index 000000000000..39b4dc4ca881 --- /dev/null +++ b/include/linux/mfd/macsmc.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC core definitions + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef _LINUX_MFD_MACSMC_H +#define _LINUX_MFD_MACSMC_H + +struct apple_smc; + +typedef u32 smc_key; + +#define SMC_KEY(s) (smc_key)(_SMC_KEY(#s)) +#define _SMC_KEY(s) (((s)[0] << 24) | ((s)[1] << 16) | ((s)[2] << 8) | (s)[3]) + +#define APPLE_SMC_READABLE BIT(7) +#define APPLE_SMC_WRITABLE BIT(6) +#define APPLE_SMC_FUNCTION BIT(4) + +struct apple_smc_key_info { + u8 size; + u32 type_code; + u8 flags; +}; + +int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size); +int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size); +int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size); +int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize, + void *rbuf, size_t rsize); + +int apple_smc_get_key_count(struct apple_smc *smc); +int apple_smc_find_first_key_index(struct apple_smc *smc, smc_key key); +int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key); +int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info); + +static inline bool apple_smc_key_exists(struct apple_smc *smc, smc_key key) +{ + return apple_smc_get_key_info(smc, key, NULL) >= 0; +} + +#define APPLE_SMC_TYPE_OPS(type) \ + static inline int apple_smc_read_##type(struct apple_smc *smc, smc_key key, type *p) \ + { \ + int ret = apple_smc_read(smc, key, p, sizeof(*p)); \ + return (ret < 0) ? ret : ((ret != sizeof(*p)) ? -EINVAL : 0); \ + } \ + static inline int apple_smc_write_##type(struct apple_smc *smc, smc_key key, type p) \ + { \ + return apple_smc_write(smc, key, &p, sizeof(p)); \ + } \ + static inline int apple_smc_write_##type##_atomic(struct apple_smc *smc, smc_key key, type p) \ + { \ + return apple_smc_write_atomic(smc, key, &p, sizeof(p)); \ + } \ + static inline int apple_smc_rw_##type(struct apple_smc *smc, smc_key key, \ + type w, type *r) \ + { \ + int ret = apple_smc_rw(smc, key, &w, sizeof(w), r, sizeof(*r)); \ + return (ret < 0) ? ret : ((ret != sizeof(*r)) ? -EINVAL : 0); \ + } + +APPLE_SMC_TYPE_OPS(u64) +APPLE_SMC_TYPE_OPS(u32) +APPLE_SMC_TYPE_OPS(u16) +APPLE_SMC_TYPE_OPS(u8) +APPLE_SMC_TYPE_OPS(s64) +APPLE_SMC_TYPE_OPS(s32) +APPLE_SMC_TYPE_OPS(s16) +APPLE_SMC_TYPE_OPS(s8) + +static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key) +{ + u8 val; + int ret = apple_smc_read_u8(smc, key, &val); + if (ret < 0) + return ret; + return val ? 1 : 0; +} +#define apple_smc_write_flag apple_smc_write_u8 + +int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n); +int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n); + +#endif diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h new file mode 100644 index 000000000000..0b7093935ddf --- /dev/null +++ b/include/linux/soc/apple/dockchannel.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple Dockchannel devices + * Copyright (C) The Asahi Linux Contributors + */ +#ifndef _LINUX_APPLE_DOCKCHANNEL_H_ +#define _LINUX_APPLE_DOCKCHANNEL_H_ + +#include <linux/device.h> +#include <linux/types.h> +#include <linux/of_platform.h> + +#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL) + +struct dockchannel; + +struct dockchannel *dockchannel_init(struct platform_device *pdev); + +int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count); +int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count); +int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count); + +#endif +#endif diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h index 88eb832eac7b..2d837aa7b91f 100644 --- a/include/linux/soc/apple/rtkit.h +++ b/include/linux/soc/apple/rtkit.h @@ -135,21 +135,15 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message, struct completion *completion, bool atomic); /* - * Send a message to the given endpoint and wait until it has been submitted - * to the hardware FIFO. - * Will return zero on success and a negative error code on failure - * (e.g. -ETIME when the message couldn't be written within the given - * timeout) + * Process incoming messages in atomic context. + * This only guarantees that messages arrive as far as the recv_message_early + * callback; drivers expecting to handle incoming messages synchronously + * by calling this function must do it that way. + * Will return 1 if some data was processed, 0 if none was, or a + * negative error code on failure. * * @rtk: RTKit reference - * @ep: target endpoint - * @message: message to be sent - * @timeout: timeout in milliseconds to allow the message transmission - * to be completed - * @atomic: if set to true this function can be called from atomic - * context. */ -int apple_rtkit_send_message_wait(struct apple_rtkit *rtk, u8 ep, u64 message, - unsigned long timeout, bool atomic); +int apple_rtkit_poll(struct apple_rtkit *rtk); #endif /* _LINUX_APPLE_RTKIT_H_ */ diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index fe7935be7dc4..dd275842b9b2 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -274,6 +274,27 @@ enum { * during the hdev->setup vendor callback. */ HCI_QUIRK_BROKEN_ENHANCED_SETUP_SYNC_CONN, + + /* + * When this quirk is set, the upper 8 bits of the evt_type field of + * the LE Extended Advertising Report events are discarded. + * Some Broadcom controllers found in Apple machines put the channel + * the report was received on into these reserved bits. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_EVT_TYPE, + + /* + * When this quirk is set, the HCI_OP_LE_SET_EXT_SCAN_ENABLE command is + * disabled. This is required for some Broadcom controllers which + * erroneously claim to support extended scanning. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_BROKEN_EXT_SCAN, }; /* HCI device flags */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index c0ea2a4892b1..149b9a10f52f 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1501,7 +1501,9 @@ void hci_conn_del_sysfs(struct hci_conn *conn); /* Use ext scanning if set ext scan param and ext scan enable is supported */ #define use_ext_scan(dev) (((dev)->commands[37] & 0x20) && \ - ((dev)->commands[37] & 0x40)) + ((dev)->commands[37] & 0x40) && \ + !test_bit(HCI_QUIRK_BROKEN_EXT_SCAN, &(dev)->quirks)) + /* Use ext create connection if command is supported */ #define use_ext_conn(dev) ((dev)->commands[37] & 0x80) diff --git a/include/sound/soc.h b/include/sound/soc.h index b276dcb5d4e8..73cc83d73163 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -916,6 +916,9 @@ struct snd_soc_card { int (*late_probe)(struct snd_soc_card *card); int (*remove)(struct snd_soc_card *card); + int (*filter_controls)(struct snd_soc_card *card, + struct snd_kcontrol *kcontrol); + /* the pre and post PM functions are used to do any PM work before and * after the codec and DAI's do any PM work. */ int (*suspend_pre)(struct snd_soc_card *card); @@ -1098,6 +1101,8 @@ struct snd_soc_pcm_runtime { #define asoc_rtd_to_codec(rtd, n) (rtd)->dais[n + (rtd)->num_cpus] #define asoc_substream_to_rtd(substream) \ (struct snd_soc_pcm_runtime *)snd_pcm_substream_chip(substream) +#define asoc_pcm_to_rtd(pcm) \ + ((struct snd_soc_pcm_runtime *)pcm->private_data) #define for_each_rtd_components(rtd, i, component) \ for ((i) = 0, component = NULL; \ diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 3c1853a9d1c0..31707499f90f 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1757,27 +1757,50 @@ char *fourcc_string(char *buf, char *end, const u32 *fourcc, char output[sizeof("0123 little-endian (0x01234567)")]; char *p = output; unsigned int i; + bool pix_fmt = false; u32 orig, val; - if (fmt[1] != 'c' || fmt[2] != 'c') + if (fmt[1] != 'c') return error_string(buf, end, "(%p4?)", spec); if (check_pointer(&buf, end, fourcc, spec)) return buf; orig = get_unaligned(fourcc); - val = orig & ~BIT(31); + switch (fmt[2]) { + case 'h': + val = orig; + break; + case 'r': + val = orig = swab32(orig); + break; + case 'l': + val = orig = le32_to_cpu(orig); + break; + case 'b': + val = orig = be32_to_cpu(orig); + break; + case 'c': + /* Pixel formats are printed LSB-first */ + val = swab32(orig & ~BIT(31)); + pix_fmt = true; + break; + default: + return error_string(buf, end, "(%p4?)", spec); + } for (i = 0; i < sizeof(u32); i++) { - unsigned char c = val >> (i * 8); + unsigned char c = val >> ((3 - i) * 8); /* Print non-control ASCII characters as-is, dot otherwise */ *p++ = isascii(c) && isprint(c) ? c : '.'; } - *p++ = ' '; - strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian"); - p += strlen(p); + if (pix_fmt) { + *p++ = ' '; + strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian"); + p += strlen(p); + } *p++ = ' '; *p++ = '('; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index af17dfb20e01..0b5d70aeea93 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -6237,6 +6237,10 @@ static void hci_le_ext_adv_report_evt(struct hci_dev *hdev, void *data, break; evt_type = __le16_to_cpu(info->type); + if (test_bit(HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_EVT_TYPE, + &hdev->quirks)) + evt_type &= 0xff; + legacy_evt_type = ext_evt_type_to_legacy(hdev, evt_type); if (legacy_evt_type != LE_ADV_INVALID) { process_adv_report(hdev, legacy_evt_type, &info->bdaddr, diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 7d4747b6bab2..848fbae26c3b 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -68,6 +68,7 @@ config SND_SOC_ACPI # All the supported SoCs source "sound/soc/adi/Kconfig" source "sound/soc/amd/Kconfig" +source "sound/soc/apple/Kconfig" source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" source "sound/soc/bcm/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index d4528962ac34..9b198d4edf37 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ obj-$(CONFIG_SND_SOC) += generic/ +obj-$(CONFIG_SND_SOC) += apple/ obj-$(CONFIG_SND_SOC) += adi/ obj-$(CONFIG_SND_SOC) += amd/ obj-$(CONFIG_SND_SOC) += atmel/ diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig new file mode 100644 index 000000000000..ce6188aa659d --- /dev/null +++ b/sound/soc/apple/Kconfig @@ -0,0 +1,21 @@ + +config SND_SOC_APPLE_SILICON + tristate "ASoC machine driver for Apple Silicon Macs" + depends on ARCH_APPLE || COMPILE_TEST + select SND_SOC_APPLE_MCA + select SND_SIMPLE_CARD_UTILS + default ARCH_APPLE + help + This option enables an ASoC machine driver for Apple Silicon Macs. + +config SND_SOC_APPLE_MCA + tristate "Apple Silicon MCA driver" + depends on ARCH_APPLE || COMPILE_TEST + select SND_DMAENGINE_PCM + select CONFIG_COMMON_CLK + select APPLE_ADMAC + select APPLE_NCO + default ARCH_APPLE + help + This option enables an ASoC platform driver for MCA blocks found + on Apple Silicon SoCs. diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile new file mode 100644 index 000000000000..a45cf8213c29 --- /dev/null +++ b/sound/soc/apple/Makefile @@ -0,0 +1,6 @@ +snd-soc-apple-mca-objs := mca.o +snd-soc-apple-silicon-objs := macaudio.o + +obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o +obj-$(CONFIG_SND_SOC_APPLE_SILICON) += snd-soc-apple-silicon.o + diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c new file mode 100644 index 000000000000..d39ef638ea95 --- /dev/null +++ b/sound/soc/apple/macaudio.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ASoC machine driver for Apple Silicon Macs + * + * Copyright (C) The Asahi Linux Contributors + * + * Based on sound/soc/qcom/{sc7180.c|common.c} + * + * Copyright (c) 2018, Linaro Limited. + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/simple_card_utils.h> +#include <sound/soc.h> +#include <uapi/linux/input-event-codes.h> + +#define DRIVER_NAME "snd-soc-apple-macaudio" + +struct macaudio_snd_data { + struct snd_soc_card card; + struct snd_soc_jack_pin pin; + struct snd_soc_jack jack; + + struct macaudio_link_props { + unsigned int mclk_fs; + } *link_props; + + const struct snd_pcm_chmap_elem *speaker_chmap; + + unsigned int speaker_nchans_array[2]; + struct snd_pcm_hw_constraint_list speaker_nchans_list; + + struct list_head hidden_kcontrols; +}; + +static int macaudio_parse_of(struct macaudio_snd_data *ma, struct snd_soc_card *card) +{ + struct device_node *np; + struct device_node *codec = NULL; + struct device_node *cpu = NULL; + struct device *dev = card->dev; + struct snd_soc_dai_link *link; + struct macaudio_link_props *link_props; + int ret, num_links; + int i = 0; + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret) { + dev_err(dev, "Error parsing card name: %d\n", ret); + return ret; + } + + ret = asoc_simple_parse_routing(card, NULL); + if (ret) { + return ret; + } + + /* Populate links */ + num_links = of_get_available_child_count(dev->of_node); + + /* Allocate the DAI link array */ + card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL); + ma->link_props = devm_kcalloc(dev, num_links, sizeof(*ma->link_props), GFP_KERNEL); + if (!card->dai_link || !ma->link_props) + return -ENOMEM; + + card->num_links = num_links; + link = card->dai_link; + link_props = ma->link_props; + + for_each_available_child_of_node(dev->of_node, np) { + link->id = i++; + + /* CPU side is bit and frame clock master, I2S with both clocks inverted */ + link->dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBC_CFC | + SND_SOC_DAIFMT_GATED | + SND_SOC_DAIFMT_IB_IF; + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { + dev_err(card->dev, "Missing link name\n"); + goto err_put_np; + } + + cpu = of_get_child_by_name(np, "cpu"); + codec = of_get_child_by_name(np, "codec"); + + if (!codec || !cpu) { + dev_err(dev, "Missing DAI specifications for '%s'\n", link->name); + ret = -EINVAL; + goto err; + } + + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(card->dev, "%s: codec dai not found: %d\n", + link->name, ret); + goto err; + } + + ret = snd_soc_of_get_dai_link_cpus(dev, cpu, link); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(card->dev, "%s: cpu dai not found: %d\n", + link->name, ret); + goto err; + } + + link->num_platforms = 1; + link->platforms = devm_kzalloc(dev, sizeof(*link->platforms), + GFP_KERNEL); + if (!link->platforms) { + ret = -ENOMEM; + goto err_put_np; + } + link->platforms->of_node = link->cpus->of_node; + + of_property_read_u32(np, "mclk-fs", &link_props->mclk_fs); + + link->stream_name = link->name; + link++; + link_props++; + + of_node_put(cpu); + of_node_put(codec); + } + + /* TODO: snd_soc_of_get_dai_link_codecs cleanup */ + + return 0; +err: + of_node_put(cpu); + of_node_put(codec); +err_put_np: + of_node_put(np); + return ret; +} + +static int macaudio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->num]; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *dai; + int i, mclk; + + if (props->mclk_fs) { + mclk = params_rate(params) * props->mclk_fs; + + for_each_rtd_codec_dais(rtd, i, dai) + snd_soc_dai_set_sysclk(dai, 0, mclk, SND_SOC_CLOCK_IN); + + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT); + } + + return 0; +} + +static void macaudio_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->num]; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *dai; + int i; + + if (props->mclk_fs) { + for_each_rtd_codec_dais(rtd, i, dai) + snd_soc_dai_set_sysclk(dai, 0, 0, SND_SOC_CLOCK_IN); + + snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + } +} + + +static int macaudio_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct snd_pcm_hw_constraint_list *nchans_list = &ma->speaker_nchans_list; + unsigned int *nchans_array = ma->speaker_nchans_array; + int ret; + + if (!strcmp(rtd->dai_link->name, "Speakers")) { + if (rtd->num_codecs > 2) { + nchans_list->count = 2; + nchans_list->list = nchans_array; + nchans_array[0] = 2; + nchans_array[1] = rtd->num_codecs; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, nchans_list); + if (ret < 0) + return ret; + } else if (rtd->num_codecs == 2) { + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int macaudio_assign_tdm(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *dai, *cpu_dai; + int ret, i; + int nchans = 0, nslots = 0, slot_width = 32; + + nslots = rtd->num_codecs; + + for_each_rtd_codec_dais(rtd, i, dai) { + int codec_nchans = 1; + int mask = ((1 << codec_nchans) - 1) << nchans; + + ret = snd_soc_dai_set_tdm_slot(dai, mask, + mask, nslots, slot_width); + if (ret == -EINVAL) + /* Try without the RX mask */ + ret = snd_soc_dai_set_tdm_slot(dai, mask, + 0, nslots, slot_width); + + if (ret < 0) { + dev_err(card->dev, "DAI %s refuses TDM settings: %d", + dai->name, ret); + return ret; + } + + nchans += codec_nchans; + } + + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, (1 << nslots) - 1, + (1 << nslots) - 1, nslots, slot_width); + if (ret < 0) { + dev_err(card->dev, "CPU DAI %s refuses TDM settings: %d", + cpu_dai->name, ret); + return ret; + } + + return 0; +} + +static int macaudio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + int ret, i; + + if (rtd->num_codecs > 1) { + ret = macaudio_assign_tdm(rtd); + if (ret < 0) + return ret; + } + + for_each_rtd_components(rtd, i, component) + snd_soc_component_set_jack(component, &ma->jack, NULL); + + return 0; +} + +static void macaudio_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component; + int i; + + for_each_rtd_components(rtd, i, component) + snd_soc_component_set_jack(component, NULL, NULL); +} + +struct fixed_kctl { + char *name; + char *value; +} macaudio_fixed_kctls[] = { + {"ASI1 Sel", "Left"}, + {"Left ASI1 Sel", "Left"}, + {"Right ASI1 Sel", "Left"}, + {"Left Front ASI1 Sel", "Left"}, + {"Left Rear ASI1 Sel", "Left"}, + {"Right Front ASI1 Sel", "Left"}, + {"Right Rear ASI1 Sel", "Left"}, + {"Left Tweeter ASI1 Sel", "Left"}, + {"Left Woofer 1 ASI1 Sel", "Left"}, + {"Left Woofer 2 ASI1 Sel", "Left"}, + {"Right Tweeter ASI1 Sel", "Left"}, + {"Right Woofer 1 ASI1 Sel", "Left"}, + {"Right Woofer 2 ASI1 Sel", "Left"}, + {"Left ISENSE Switch", "Off"}, + {"Left VSENSE Switch", "Off"}, + {"Right ISENSE Switch", "Off"}, + {"Right VSENSE Switch", "Off"}, + { } +}; + +static struct fixed_kctl *find_fixed_kctl(const char *name) +{ + struct fixed_kctl *fctl; + + for (fctl = macaudio_fixed_kctls; fctl->name != NULL; fctl++) + if (!strcmp(fctl->name, name)) + return fctl; + + return NULL; +} + +static int macaudio_probe(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + int ret; + + INIT_LIST_HEAD(&ma->hidden_kcontrols); + + ma->pin.pin = "Headphones"; + ma->pin.mask = SND_JACK_HEADSET | SND_JACK_HEADPHONE; + ret = snd_soc_card_jack_new_pins(card, ma->pin.pin, + SND_JACK_HEADSET | + SND_JACK_HEADPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &ma->jack, &ma->pin, 1); + + if (ret < 0) + dev_err(card->dev, "jack creation failed: %d\n", ret); + + return ret; +} + +static int macaudio_remove(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct snd_kcontrol *kcontrol; + + list_for_each_entry(kcontrol, &ma->hidden_kcontrols, list) + snd_ctl_free_one(kcontrol); + + return 0; +} + +static void snd_soc_kcontrol_set_strval(struct snd_soc_card *card, + struct snd_kcontrol *kcontrol, const char *strvalue) +{ + struct snd_ctl_elem_value value; + struct snd_ctl_elem_info info; + int sel, i, ret; + + ret = kcontrol->info(kcontrol, &info); + if (ret < 0) { + dev_err(card->dev, "can't obtain info on control '%s': %d", + kcontrol->id.name, ret); + return; + } + + switch (info.type) { + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + for (sel = 0; sel < info.value.enumerated.items; sel++) { + info.value.enumerated.item = sel; + kcontrol->info(kcontrol, &info); + + if (!strcmp(strvalue, info.value.enumerated.name)) + break; + } + + if (sel == info.value.enumerated.items) + goto not_avail; + + for (i = 0; i < info.count; i++) + value.value.enumerated.item[i] = sel; + break; + + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + sel = !strcmp(strvalue, "On"); + + if (!sel && strcmp(strvalue, "Off")) + goto not_avail; + + for (i = 0; i < info.count; i++) /* TODO */ + value.value.integer.value[i] = sel; + break; + + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (kstrtoint(strvalue, 10, &sel)) + goto not_avail; + + for (i = 0; i < info.count; i++) + value.value.integer.value[i] = sel; + break; + + default: + dev_err(card->dev, "%s: control '%s' has unsupported type %d", + __func__, kcontrol->id.name, info.type); + return; + } + + ret = kcontrol->put(kcontrol, &value); + if (ret < 0) { + dev_err(card->dev, "can't set control '%s' to '%s': %d", + kcontrol->id.name, strvalue, ret); + return; + } + + dev_info(card->dev, "set '%s' to '%s'", + kcontrol->id.name, strvalue); + return; + +not_avail: + dev_err(card->dev, "option '%s' on control '%s' not available", + strvalue, kcontrol->id.name); + return; + +} + +static int macaudio_late_probe(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct snd_kcontrol *kcontrol; + struct snd_soc_pcm_runtime *rtd; + int ret; + + list_for_each_entry(kcontrol, &ma->hidden_kcontrols, list) { + struct fixed_kctl *fctl = find_fixed_kctl(kcontrol->id.name); + + if (fctl) + snd_soc_kcontrol_set_strval(card, kcontrol, fctl->value); + } + + for_each_card_rtds(card, rtd) { + bool speakers_link = !strcmp(rtd->dai_link->name, "Speaker") + || !strcmp(rtd->dai_link->name, "Speakers"); + + if (speakers_link && ma->speaker_chmap) { + ret = snd_pcm_add_chmap_ctls(rtd->pcm, + SNDRV_PCM_STREAM_PLAYBACK, ma->speaker_chmap, + rtd->num_codecs, 0, NULL); + if (ret < 0) + dev_err(card->dev, "failed to add channel map on '%s': %d\n", + rtd->dai_link->name, ret); + } + } + + return 0; +} + +static int macaudio_filter_controls(struct snd_soc_card *card, + struct snd_kcontrol *kcontrol) +{ + struct fixed_kctl *fctl = find_fixed_kctl(kcontrol->id.name); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + dev_info(card->dev, "visiting control %s, have match %d\n", + kcontrol->id.name, !!fctl); + + if (!fctl) + return 0; + + list_add_tail(&kcontrol->list, &ma->hidden_kcontrols); + return 1; +} + +static const struct snd_soc_ops macaudio_ops = { + .startup = macaudio_startup, + .shutdown = macaudio_shutdown, + .hw_params = macaudio_hw_params, +}; + +static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), +}; + +static const struct snd_pcm_chmap_elem macaudio_j274_chmaps[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { } +}; + +static const struct snd_pcm_chmap_elem macaudio_j293_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +static const struct snd_pcm_chmap_elem macaudio_j314_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 6, + .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR, + SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +static const struct of_device_id macaudio_snd_device_id[] = { + { .compatible = "apple,j274-macaudio", .data = macaudio_j274_chmaps }, + { .compatible = "apple,j293-macaudio", .data = macaudio_j293_chmaps }, + { .compatible = "apple,j314-macaudio", .data = macaudio_j314_chmaps }, + { .compatible = "apple,macaudio", }, + { } +}; +MODULE_DEVICE_TABLE(of, macaudio_snd_device_id); + +static int macaudio_snd_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct macaudio_snd_data *data; + struct device *dev = &pdev->dev; + struct snd_soc_dai_link *link; + const struct of_device_id *of_id; + int ret; + int i; + + of_id = of_match_device(macaudio_snd_device_id, dev); + if (!of_id) + return -EINVAL; + + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->speaker_chmap = of_id->data; + card = &data->card; + snd_soc_card_set_drvdata(card, data); + + card->owner = THIS_MODULE; + card->driver_name = DRIVER_NAME; + card->dev = dev; + card->dapm_widgets = macaudio_snd_widgets; + card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets); + card->probe = macaudio_probe; + card->late_probe = macaudio_late_probe; + card->remove = macaudio_remove; + card->filter_controls = macaudio_filter_controls; + card->remove = macaudio_remove; + + ret = macaudio_parse_of(data, card); + if (ret) + return ret; + + for_each_card_prelinks(card, i, link) { + link->ops = &macaudio_ops; + link->init = macaudio_init; + link->exit = macaudio_exit; + } + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver macaudio_snd_driver = { + .probe = macaudio_snd_platform_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = macaudio_snd_device_id, + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(macaudio_snd_driver); + +MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>"); +MODULE_DESCRIPTION("Apple Silicon Macs machine sound driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c new file mode 100644 index 000000000000..846dba0383a1 --- /dev/null +++ b/sound/soc/apple/mca.c @@ -0,0 +1,1287 @@ +#define USE_RXB_FOR_CAPTURE + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/bitfield.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_clk.h> +#include <linux/of_dma.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +/* relative to cluster base */ +#define REG_STATUS 0x0 +#define STATUS_MCLK_EN BIT(0) +#define REG_MCLK_CONF 0x4 +#define MCLK_CONF_DIV GENMASK(11, 8) + +#define REG_SYNCGEN_STATUS 0x100 +#define SYNCGEN_STATUS_EN BIT(0) +#define REG_SYNCGEN_MCLK_SEL 0x104 +#define SYNCGEN_MCLK_SEL GENMASK(3, 0) +#define REG_SYNCGEN_HI_PERIOD 0x108 +#define REG_SYNCGEN_LO_PERIOD 0x10c + +#define REG_PORT_ENABLES 0x600 +#define PORT_ENABLES_CLOCKS GENMASK(2, 1) +#define PORT_ENABLES_TX_DATA BIT(3) +#define REG_PORT_CLOCK_SEL 0x604 +#define PORT_CLOCK_SEL GENMASK(11, 8) +#define REG_PORT_DATA_SEL 0x608 +#define PORT_DATA_SEL_TXA(cl) (1 << ((cl)*2)) +#define PORT_DATA_SEL_TXB(cl) (2 << ((cl)*2)) + +#define REG_INTSTATE 0x700 +#define REG_INTMASK 0x704 + +/* bases of serdes units (relative to cluster) */ +#define CLUSTER_RXA_OFF 0x200 +#define CLUSTER_TXA_OFF 0x300 +#define CLUSTER_RXB_OFF 0x400 +#define CLUSTER_TXB_OFF 0x500 + +#define CLUSTER_TX_OFF CLUSTER_TXA_OFF + +#ifndef USE_RXB_FOR_CAPTURE +#define CLUSTER_RX_OFF CLUSTER_RXA_OFF +#else +#define CLUSTER_RX_OFF CLUSTER_RXB_OFF +#endif + +/* relative to serdes unit base */ +#define REG_SERDES_STATUS 0x00 +#define SERDES_STATUS_EN BIT(0) +#define SERDES_STATUS_RST BIT(1) +#define REG_TX_SERDES_CONF 0x04 +#define REG_RX_SERDES_CONF 0x08 +#define SERDES_CONF_NCHANS GENMASK(3, 0) +#define SERDES_CONF_WIDTH_MASK GENMASK(8, 4) +#define SERDES_CONF_WIDTH_16BIT 0x40 +#define SERDES_CONF_WIDTH_20BIT 0x80 +#define SERDES_CONF_WIDTH_24BIT 0xc0 +#define SERDES_CONF_WIDTH_32BIT 0x100 +#define SERDES_CONF_BCLK_POL 0x400 +#define SERDES_CONF_LSB_FIRST 0x800 +#define SERDES_CONF_UNK1 BIT(12) +#define SERDES_CONF_UNK2 BIT(13) +#define SERDES_CONF_UNK3 BIT(14) +#define SERDES_CONF_NO_DATA_FEEDBACK BIT(14) +#define SERDES_CONF_SYNC_SEL GENMASK(18, 16) +#define SERDES_CONF_SOME_RST BIT(19) +#define REG_TX_SERDES_BITSTART 0x08 +#define REG_RX_SERDES_BITSTART 0x0c +#define REG_TX_SERDES_SLOTMASK 0x0c +#define REG_RX_SERDES_SLOTMASK 0x10 +#define REG_RX_SERDES_PORT 0x04 + +/* relative to switch base */ +#define REG_DMA_ADAPTER_A(cl) (0x8000 * (cl)) +#define REG_DMA_ADAPTER_B(cl) (0x8000 * (cl) + 0x4000) +#define DMA_ADAPTER_TX_LSB_PAD GENMASK(4, 0) +#define DMA_ADAPTER_TX_NCHANS GENMASK(6, 5) +#define DMA_ADAPTER_RX_MSB_PAD GENMASK(12, 8) +#define DMA_ADAPTER_RX_NCHANS GENMASK(14, 13) +#define DMA_ADAPTER_NCHANS GENMASK(22, 20) + +#define SWITCH_STRIDE 0x8000 +#define CLUSTER_STRIDE 0x4000 + +#define MAX_NCLUSTERS 6 + +struct mca_dai { + struct mca_route *in_route; + unsigned int tdm_slots; + unsigned int tdm_slot_width; + unsigned int tdm_tx_mask; + unsigned int tdm_rx_mask; + unsigned long set_sysclk; + u32 fmt_bitstart; + bool fmt_bclk_inv; +}; + +struct mca_cluster { + int no; + struct mca_data *host; + struct device *pd_dev; + struct clk *clk_parent; + struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + struct mca_dai port; +}; + +#define mca_dai_to_cluster(dai) \ + container_of(dai, struct mca_cluster, port) + +struct mca_data { + struct device *dev; + + __iomem void *base; + __iomem void *switch_base; + + struct device *pd_dev; + struct device_link *pd_link; + + int nclusters; + struct mca_cluster clusters[]; +}; + +struct mca_route { + struct mca_data *host; + + struct clk *clk_parent; + bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; + + struct device_link *pd_link; + + /* + * Cluster selectors for different facilities + * that constitute the 'route' + */ + int clock; + int syncgen; + int serdes; + + int ndais; + struct mca_dai *dais[]; +}; + +static struct mca_route *mca_route_for_rtd(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); + struct mca_data *mca = snd_soc_dai_get_drvdata(dai); + return mca->clusters[dai->id].port.in_route; +} + +static struct mca_dai *mca_dai_for_soc_dai(struct snd_soc_dai *dai) +{ + struct mca_data *mca = snd_soc_dai_get_drvdata(dai); + return &mca->clusters[dai->id].port; +} + +static u32 mca_peek(struct mca_data *mca, int cluster, int regoffset) +{ + int offset = (CLUSTER_STRIDE * cluster) + regoffset; + + return readl_relaxed(mca->base + offset); +} + +static void mca_poke(struct mca_data *mca, int cluster, + int regoffset, u32 val) +{ + int offset = (CLUSTER_STRIDE * cluster) + regoffset; + dev_dbg(mca->dev, "regs: %x <- %x\n", offset, val); + writel_relaxed(val, mca->base + offset); +} + +static void mca_modify(struct mca_data *mca, int cluster, + int regoffset, u32 mask, u32 val) +{ + int offset = (CLUSTER_STRIDE * cluster) + regoffset; + __iomem void *p = mca->base + offset; + u32 newval = (val & mask) | (readl_relaxed(p) & ~mask); + dev_dbg(mca->dev, "regs: %x <- %x\n", offset, newval); + writel_relaxed(newval, p); +} + +static int mca_reset_dais(struct mca_route *route, + struct snd_pcm_substream *substream, int cmd) +{ + struct mca_data *mca = route->host; + bool is_tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int serdes_unit = is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + mca_modify(mca, route->serdes, + serdes_unit + REG_SERDES_STATUS, + SERDES_STATUS_EN | SERDES_STATUS_RST, + SERDES_STATUS_RST); + mca_modify(mca, route->serdes, + serdes_unit + + (is_tx ? REG_TX_SERDES_CONF : REG_RX_SERDES_CONF), + SERDES_CONF_SOME_RST, SERDES_CONF_SOME_RST); + (void)mca_peek(mca, route->serdes, + serdes_unit + + (is_tx ? REG_TX_SERDES_CONF : REG_RX_SERDES_CONF)); + mca_modify(mca, route->serdes, + serdes_unit + + (is_tx ? REG_TX_SERDES_CONF : REG_RX_SERDES_CONF), + SERDES_STATUS_RST, 0); + WARN_ON(mca_peek(mca, route->serdes, REG_SERDES_STATUS) + & SERDES_STATUS_RST); + + dev_dbg(mca->dev, "trigger reset\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + return -EINVAL; + } + return 0; +} + +static int mca_trigger_dais(struct mca_route *route, + struct snd_pcm_substream *substream, int cmd) +{ + struct mca_data *mca = route->host; + bool is_tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int serdes_unit = is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + mca_modify(mca, route->serdes, + serdes_unit + REG_SERDES_STATUS, + SERDES_STATUS_EN | SERDES_STATUS_RST, + SERDES_STATUS_EN); + + dev_dbg(mca->dev, "trigger start\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + mca_modify(mca, route->serdes, + serdes_unit + REG_SERDES_STATUS, + SERDES_STATUS_EN, 0); + + dev_dbg(mca->dev, "trigger stop\n"); + break; + default: + return -EINVAL; + } + return 0; +} + +static bool mca_clocks_in_use(struct mca_route *route) +{ + int stream; + + for_each_pcm_streams(stream) + if (route->clocks_in_use[stream]) + return true; + return false; +} + +static int mca_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct mca_route *route = mca_route_for_rtd(rtd); + struct mca_data *mca = route->host; + struct mca_cluster *cluster; + + int ret; + + if (!mca_clocks_in_use(route)) { + ret = clk_prepare_enable(route->clk_parent); + if (ret) { + dev_err(mca->dev, "unable to enable parent clock %d: %d\n", + route->clock, ret); + return ret; + } + + /* + * We only prop-up PD of the syncgen cluster. That is okay + * in combination with the way we are constructing 'routes' + * where only single cluster needs powering up. + */ + cluster = &mca->clusters[route->syncgen]; + route->pd_link = device_link_add(rtd->dev, cluster->pd_dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!route->pd_link) { + dev_err(mca->dev, "unable to prop-up cluster's power domain " + "(cluster %d)\n", route->syncgen); + clk_disable_unprepare(route->clk_parent); + return -EINVAL; + } + + mca_poke(mca, route->syncgen, REG_SYNCGEN_MCLK_SEL, + route->clock + 1); + mca_modify(mca, route->syncgen, + REG_SYNCGEN_STATUS, + SYNCGEN_STATUS_EN, SYNCGEN_STATUS_EN); + mca_modify(mca, route->clock, + REG_STATUS, + STATUS_MCLK_EN, STATUS_MCLK_EN); + } + + route->clocks_in_use[substream->stream] = true; + + return 0; +} + +static int mca_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct mca_route *route = mca_route_for_rtd(asoc_substream_to_rtd(substream)); + struct mca_data *mca = route->host; + + if (!mca_clocks_in_use(route)) + return 0; /* Nothing to do */ + + route->clocks_in_use[substream->stream] = false; + + if (!mca_clocks_in_use(route)) { + mca_modify(mca, route->syncgen, + REG_SYNCGEN_STATUS, + SYNCGEN_STATUS_EN, 0); + mca_modify(mca, route->clock, + REG_STATUS, + STATUS_MCLK_EN, 0); + + device_link_del(route->pd_link); + clk_disable_unprepare(route->clk_parent); + } + + return 0; +} + +#define div_ceil(A, B) ((A)/(B) + ((A)%(B) ? 1 : 0)) + +static int mca_configure_serdes(struct mca_data *mca, int cluster, int serdes_unit, + unsigned int mask, int slots, int nchans, int slot_width, bool is_tx, int port) +{ + u32 serdes_conf; + + serdes_conf = FIELD_PREP(SERDES_CONF_NCHANS, max(slots, 1) - 1); + + switch (slot_width) { + case 16: + serdes_conf |= SERDES_CONF_WIDTH_16BIT; + break; + case 20: + serdes_conf |= SERDES_CONF_WIDTH_20BIT; + break; + case 24: + serdes_conf |= SERDES_CONF_WIDTH_24BIT; + break; + case 32: + serdes_conf |= SERDES_CONF_WIDTH_32BIT; + break; + default: + goto err; + } + + mca_modify(mca, cluster, + serdes_unit + (is_tx ? REG_TX_SERDES_CONF : REG_RX_SERDES_CONF), + SERDES_CONF_WIDTH_MASK | SERDES_CONF_NCHANS, serdes_conf); + + if (is_tx) { + mca_poke(mca, cluster, + serdes_unit + REG_TX_SERDES_SLOTMASK, + 0xffffffff); + /* + * TODO: Actually consider where the hot bits + * are placed in the mask, instead of assuming + * it's the bottom bits. + */ + mca_poke(mca, cluster, + serdes_unit + REG_TX_SERDES_SLOTMASK + 0x4, + ~((u32) mask & ((1 << nchans) - 1))); + mca_poke(mca, cluster, + serdes_unit + REG_TX_SERDES_SLOTMASK + 0x8, + 0xffffffff); + mca_poke(mca, cluster, + serdes_unit + REG_TX_SERDES_SLOTMASK + 0xc, + ~((u32) mask)); + } else { + mca_poke(mca, cluster, + serdes_unit + REG_RX_SERDES_SLOTMASK, + 0xffffffff); + mca_poke(mca, cluster, + serdes_unit + REG_RX_SERDES_SLOTMASK + 0x4, + ~((u32) mask)); + mca_poke(mca, cluster, + serdes_unit + REG_RX_SERDES_PORT, + 1 << port); + } + + return 0; + +err: + dev_err(mca->dev, "unsupported SERDES configuration requested (mask=0x%x slots=%d slot_width=%d)\n", + mask, slots, slot_width); + return -EINVAL; +} + +static int mca_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct mca_dai *mdai = mca_dai_for_soc_dai(dai); + + mdai->tdm_slots = slots; + mdai->tdm_slot_width = slot_width; + mdai->tdm_tx_mask = tx_mask; + mdai->tdm_rx_mask = rx_mask; + + return 0; +} + +static int mca_dai_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct mca_data *mca = snd_soc_dai_get_drvdata(dai); + struct mca_dai *mdai = mca_dai_for_soc_dai(dai); + struct mca_route *route = mdai->in_route; + bool bclk_inv = false, fpol_inv = false; + u32 bitstart; + + if (WARN_ON(route)) + return -EBUSY; + + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != + SND_SOC_DAIFMT_CBC_CFC) + goto err; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fpol_inv = 0; + bitstart = 1; + break; + case SND_SOC_DAIFMT_LEFT_J: + fpol_inv = 1; + bitstart = 0; + break; + default: + goto err; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + fpol_inv ^= 1; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + bclk_inv = true; + } + + if (!fpol_inv) + goto err; + + mdai->fmt_bitstart = bitstart; + mdai->fmt_bclk_inv = bclk_inv; + + return 0; + +err: + dev_err(mca->dev, "unsupported DAI format (0x%x) requested\n", fmt); + return -EINVAL; +} + +static int mca_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + int ret; + + struct mca_dai *mdai = mca_dai_for_soc_dai(dai); + struct mca_route *route = mdai->in_route; + + if (freq == mdai->set_sysclk) + return 0; + + if (mca_clocks_in_use(route)) + return -EBUSY; + + ret = clk_set_rate(route->clk_parent, freq); + if (!ret) + mdai->set_sysclk = freq; + return ret; +} + +static const struct snd_soc_dai_ops mca_dai_ops = { + .set_fmt = mca_dai_set_fmt, + .set_sysclk = mca_dai_set_sysclk, + .set_tdm_slot = mca_dai_set_tdm_slot, +}; + +static int mca_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream, struct dma_chan *chan) +{ + struct device *dma_dev = chan->device->dev; + struct snd_dmaengine_dai_dma_data dma_data = {}; + int ret; + + struct snd_pcm_hardware hw; + + memset(&hw, 0, sizeof(hw)); + + hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED; + hw.periods_min = 2; + hw.periods_max = UINT_MAX; + hw.period_bytes_min = 256; + hw.period_bytes_max = dma_get_max_seg_size(dma_dev); + hw.buffer_bytes_max = SIZE_MAX; + hw.fifo_size = 16; + + ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream, + &dma_data, &hw, chan); + + if (ret) + return ret; + + return snd_soc_set_runtime_hwparams(substream, &hw); +} + +static int mca_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct mca_data *mca = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct mca_route *route = mca_route_for_rtd(rtd); + struct dma_chan *chan = mca->clusters[route->serdes].dma_chans[substream->stream]; + int ret, i; + + if (WARN_ON(!route)) + return -EINVAL; + + for (i = 0; i < route->ndais; i++) { + int dai_no = mca_dai_to_cluster(route->dais[i])->no; + + mca_poke(mca, dai_no, REG_PORT_ENABLES, + PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA); + mca_poke(mca, dai_no, REG_PORT_CLOCK_SEL, + FIELD_PREP(PORT_CLOCK_SEL, route->syncgen + 1)); + mca_poke(mca, dai_no, REG_PORT_DATA_SEL, + PORT_DATA_SEL_TXA(route->serdes)); + } + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + mca_modify(mca, route->serdes, CLUSTER_TX_OFF + REG_TX_SERDES_CONF, + SERDES_CONF_UNK1 | SERDES_CONF_UNK2 | SERDES_CONF_UNK3, + SERDES_CONF_UNK1 | SERDES_CONF_UNK2 | SERDES_CONF_UNK3); + mca_modify(mca, route->serdes, CLUSTER_TX_OFF + REG_TX_SERDES_CONF, + SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, route->syncgen + 1)); + break; + + case SNDRV_PCM_STREAM_CAPTURE: + mca_modify(mca, route->serdes, CLUSTER_RX_OFF + REG_RX_SERDES_CONF, + SERDES_CONF_UNK1 | SERDES_CONF_UNK2 | SERDES_CONF_UNK3 + | SERDES_CONF_NO_DATA_FEEDBACK, + SERDES_CONF_UNK1 | SERDES_CONF_UNK2 + | SERDES_CONF_NO_DATA_FEEDBACK); + mca_modify(mca, route->serdes, CLUSTER_RX_OFF + REG_RX_SERDES_CONF, + SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, route->syncgen + 1)); + break; + + default: + break; + } + + ret = mca_set_runtime_hwparams(component, substream, chan); + if (ret) + return ret; + + return snd_dmaengine_pcm_open(substream, chan); +} + +static int mca_hw_params_dma(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; + + memset(&slave_config, 0, sizeof(slave_config)); + ret = snd_hwparams_to_dma_slave_config(substream, params, &slave_config); + if (ret < 0) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + slave_config.dst_port_window_size = min((int) params_channels(params), 4); + else + slave_config.src_port_window_size = min((int) params_channels(params), 4); + + return dmaengine_slave_config(chan, &slave_config); +} + +static int mca_get_dais_tdm_slots(struct mca_route *route, bool is_tx, + int *slot_width, int *slots, int *mask) +{ + struct mca_dai *mdai; + unsigned int tdm_slot_width, tdm_tx_mask, tdm_rx_mask; + unsigned int tdm_slots = 0; + int i; + +#define __pick_up_dai_tdm_param(param) \ + { \ + if (tdm_slots && mdai->param != param) \ + return -EINVAL; \ + param = mdai->param; \ + } + + for (i = 0; i < route->ndais; i++) { + mdai = route->dais[i]; + + if (mdai->tdm_slots) { + if (is_tx) { + __pick_up_dai_tdm_param(tdm_tx_mask); + } else { + __pick_up_dai_tdm_param(tdm_rx_mask); + } + + __pick_up_dai_tdm_param(tdm_slot_width); + __pick_up_dai_tdm_param(tdm_slots); + } + } + + if (tdm_slots) { + *slots = tdm_slots; + *slot_width = tdm_slot_width; + *mask = is_tx ? tdm_tx_mask : tdm_rx_mask; + } + + return 0; +} + +static int mca_get_dais_sysclk(struct mca_route *route, unsigned long *sysclk) +{ + struct mca_dai *mdai; + unsigned long set_sysclk = 0; + int i; + + for (i = 0; i < route->ndais; i++) { + mdai = route->dais[i]; + + if (!mdai->set_sysclk) + continue; + + if (set_sysclk && mdai->set_sysclk != set_sysclk) + return -EINVAL; + + set_sysclk = mdai->set_sysclk; + } + + if (set_sysclk) + *sysclk = set_sysclk; + + return 0; +} + +static int mca_hw_params_dais(struct mca_route *route, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct mca_data *mca = route->host; + struct device *dev = route->host->dev; + bool is_tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int samp_rate = params_rate(params); + bool refine_tdm = false; + unsigned int tdm_slots, tdm_slot_width, tdm_mask; + unsigned long bclk_ratio; + unsigned long sysclk; + u32 regval, pad; + int ret, dai_no, nchans_ceiled; + + tdm_slot_width = 0; + ret = mca_get_dais_tdm_slots(route, is_tx, + &tdm_slot_width, &tdm_slots, &tdm_mask); + + if (ret < 0) { + dev_err(dev, "bad dai TDM settings\n"); + return ret; + } + + if (!tdm_slot_width) { + /* + * We were not given TDM settings from above, set initial + * guesses which will later be refined. + */ + tdm_slot_width = params_width(params); + tdm_slots = params_channels(params); + refine_tdm = true; + } + + sysclk = 0; + ret = mca_get_dais_sysclk(route, &sysclk); + + if (ret < 0) { + dev_err(dev, "bad dai sysclk settings\n"); + return ret; + } + + if (sysclk) { + bclk_ratio = sysclk / samp_rate; + } else { + bclk_ratio = tdm_slot_width * tdm_slots; + } + + if (refine_tdm) { + int nchannels = params_channels(params); + + if (nchannels > 2) { + dev_err(dev, "nchannels > 2 and no TDM\n"); + return -EINVAL; + } + + if ((bclk_ratio % nchannels) != 0) { + dev_err(dev, "bclk ratio (%ld) not divisible by nchannels (%d)\n", + bclk_ratio, nchannels); + return -EINVAL; + } + + tdm_slot_width = bclk_ratio / nchannels; + + if (tdm_slot_width > 32 && nchannels == 1) + tdm_slot_width = 32; + + if (tdm_slot_width < params_width(params)) { + dev_err(dev, "TDM slots too narrow tdm=%d params=%d\n", + tdm_slot_width, params_width(params)); + return -EINVAL; + } + + tdm_mask = (1 << tdm_slots) - 1; + } + + dai_no = mca_dai_to_cluster(route->dais[0])->no; + + ret = mca_configure_serdes(mca, route->serdes, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, + tdm_mask, tdm_slots, params_channels(params), + tdm_slot_width, is_tx, dai_no); + if (ret) + return ret; + + pad = 32 - params_width(params); + + /* + * TODO: Here the register semantics aren't clear. + */ + nchans_ceiled = min((int) params_channels(params), 4); + regval = FIELD_PREP(DMA_ADAPTER_NCHANS, nchans_ceiled) + | FIELD_PREP(DMA_ADAPTER_TX_NCHANS, 0x2) + | FIELD_PREP(DMA_ADAPTER_RX_NCHANS, 0x2) + | FIELD_PREP(DMA_ADAPTER_TX_LSB_PAD, pad) + | FIELD_PREP(DMA_ADAPTER_RX_MSB_PAD, pad); + +#ifndef USE_RXB_FOR_CAPTURE + writel_relaxed(regval, mca->switch_base + REG_DMA_ADAPTER_A(route->serdes)); +#else + if (is_tx) + writel_relaxed(regval, mca->switch_base + REG_DMA_ADAPTER_A(route->serdes)); + else + writel_relaxed(regval, mca->switch_base + REG_DMA_ADAPTER_B(route->serdes)); +#endif + + if (!mca_clocks_in_use(route)) { + /* + * Set up FSYNC duty cycle to be as even as possible. + */ + mca_poke(mca, route->syncgen, + REG_SYNCGEN_HI_PERIOD, + (bclk_ratio / 2) - 1); + mca_poke(mca, route->syncgen, + REG_SYNCGEN_LO_PERIOD, + ((bclk_ratio + 1) / 2) - 1); + + mca_poke(mca, route->clock, + REG_MCLK_CONF, + FIELD_PREP(MCLK_CONF_DIV, 0x1)); + + ret = clk_set_rate(route->clk_parent, bclk_ratio * samp_rate); + if (ret) { + dev_err(mca->dev, "unable to set parent clock %d: %d\n", + route->clock, ret); + return ret; + } + } + + return 0; +} + +static int mca_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct mca_route *route = mca_route_for_rtd( + asoc_substream_to_rtd(substream)); + int ret; + + ret = mca_hw_params_dma(component, substream, params); + if (ret < 0) + return ret; + + return mca_hw_params_dais(route, substream, params); +} + +static int mca_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_close(substream); +} + +#if 0 +static void mca_flush_adapter_fifo(struct mca_route *route) +{ + struct mca_data *mca = route->host; + int i; + u32 ptr; + + ptr = readl_relaxed(mca->switch_base + REG_DMA_ADAPTER_A(route->serdes) + 0x8); + dev_dbg(route->host->dev, "flush fifo: entered at %x\n", ptr); + + for (i = 0; i < 256; i++) { + if (ptr == 0xbc) + break; + + writel_relaxed(0, mca->switch_base + REG_DMA_ADAPTER_A(route->serdes) + 0xc); + (void) readl_relaxed(mca->switch_base + REG_DMA_ADAPTER_A(route->serdes) + 0xc); + + ptr = readl_relaxed(mca->switch_base + REG_DMA_ADAPTER_A(route->serdes) + 0x8); + } + + dev_dbg(route->host->dev, "flush fifo: left at %x\n", ptr); +} +#endif + +static int mca_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct mca_route *route = mca_route_for_rtd(asoc_substream_to_rtd(substream)); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = mca_reset_dais(route, substream, cmd); + if (ret < 0) + return ret; + + //mca_flush_adapter_fifo(route); + + ret = snd_dmaengine_pcm_trigger(substream, cmd); + if (ret < 0) + return ret; + + ret = mca_trigger_dais(route, substream, cmd); + if (ret < 0) + goto revert_dmaengine; + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = mca_trigger_dais(route, substream, cmd); + if (ret < 0) + return ret; + + return snd_dmaengine_pcm_trigger(substream, cmd); + } + +revert_dmaengine: + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_dmaengine_pcm_trigger(substream, SNDRV_PCM_TRIGGER_STOP); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + snd_dmaengine_pcm_trigger(substream, SNDRV_PCM_TRIGGER_STOP); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_dmaengine_pcm_trigger(substream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); + break; + } + + return ret; +} + +static snd_pcm_uframes_t mca_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} + +static int mca_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct mca_data *mca = snd_soc_component_get_drvdata(component); + struct mca_route *route; + struct snd_soc_dai *dai; + struct mca_dai *mdai; + struct mca_cluster *cluster; + unsigned int i; + int ret = 0; + + route = devm_kzalloc(mca->dev, struct_size(route, dais, rtd->num_cpus), GFP_KERNEL); + + if (!route) + return -ENOMEM; + + route->host = mca; + + for_each_rtd_cpu_dais(rtd, i, dai) { + if (dai->component != component) { + dev_err(mca->dev, "foreign CPU dai in PCM\n"); + goto exit_free; + } + + mdai = &mca->clusters[dai->id].port; + + if (WARN_ON(mdai->in_route)) { + ret = -EINVAL; + goto exit_free; + } + + mdai->in_route = route; + route->dais[i] = mdai; + } + route->ndais = rtd->num_cpus; + + /* + * Pick facilities from cluster of the first dai. + */ + cluster = mca_dai_to_cluster(route->dais[0]); + + route->clock = cluster->no; + route->syncgen = cluster->no; + route->serdes = cluster->no; + + route->clk_parent = cluster->clk_parent; + + for_each_pcm_streams(i) { + struct snd_pcm_substream *substream = rtd->pcm->streams[i].substream; + struct dma_chan *chan = cluster->dma_chans[i]; + + if (!substream) + continue; + + if (!chan) { + dev_err(component->dev, "missing DMA channel for stream %d " + "on serdes %d\n", i, route->serdes); + return -EINVAL; + } + + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV_IRAM, + chan->device->dev, 512*1024*6, + SIZE_MAX); + } + + /* Look at the first dai for daifmt settings */ + mdai = route->dais[0]; + + mca_modify(mca, route->serdes, CLUSTER_TX_OFF + REG_TX_SERDES_CONF, + SERDES_CONF_BCLK_POL, + mdai->fmt_bclk_inv ? SERDES_CONF_BCLK_POL : 0); + mca_poke(mca, route->serdes, CLUSTER_TX_OFF + REG_TX_SERDES_BITSTART, + mdai->fmt_bitstart); + mca_modify(mca, route->serdes, CLUSTER_RX_OFF + REG_RX_SERDES_CONF, + SERDES_CONF_BCLK_POL, + mdai->fmt_bclk_inv ? SERDES_CONF_BCLK_POL : 0); + mca_poke(mca, route->serdes, CLUSTER_RX_OFF + REG_RX_SERDES_BITSTART, + mdai->fmt_bitstart); + + return ret; + +exit_free: + devm_kfree(mca->dev, route); + return ret; +} + +static void mca_pcm_free(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + struct mca_data *mca = snd_soc_component_get_drvdata(component); + struct mca_route *route = mca_route_for_rtd(asoc_pcm_to_rtd(pcm)); + int i; + + for (i = 0; i < route->ndais; i++) + route->dais[i]->in_route = NULL; + + devm_kfree(mca->dev, route); +} + +#if 0 +static irqreturn_t mca_interrupt(int irq, void *devid) +{ + struct mca_cluster *cl = devid; + struct mca_data *mca = cl->host; + u32 mask = mca_peek(mca, cl->no, REG_INTMASK); + u32 state = mca_peek(mca, cl->no, REG_INTSTATE); + u32 cleared; + + mca_poke(mca, cl->no, REG_INTSTATE, state & mask); + cleared = state & ~mca_peek(mca, cl->no, REG_INTSTATE); + + dev_dbg(mca->dev, "cl%d: took an interrupt. state=%x mask=%x unmasked=%x cleared=%x\n", + cl->no, state, mask, state & mask, cleared); + + mca_poke(mca, cl->no, REG_INTMASK, mask & (~state | cleared)); + + return true ? IRQ_HANDLED : IRQ_NONE; +} +#endif + +static const struct snd_soc_component_driver mca_component = { + .name = "apple-mca", + .open = mca_pcm_open, + .close = mca_close, + .prepare = mca_prepare, + .hw_free = mca_hw_free, + .hw_params = mca_hw_params, + .trigger = mca_trigger, + .pointer = mca_pointer, + .pcm_construct = mca_pcm_new, + .pcm_destruct = mca_pcm_free, +}; + +static void apple_mca_release(struct mca_data *mca) +{ + int i, stream; + + for (i = 0; i < mca->nclusters; i++) { + struct mca_cluster *cl = &mca->clusters[i]; + + for_each_pcm_streams(stream) { + if (IS_ERR_OR_NULL(cl->dma_chans[stream])) + continue; + + dma_release_channel(cl->dma_chans[stream]); + } + + if (!IS_ERR_OR_NULL(cl->clk_parent)) + clk_put(cl->clk_parent); + + if (!IS_ERR_OR_NULL(cl->pd_dev)) + dev_pm_domain_detach(cl->pd_dev, true); + } + + if (mca->pd_link) + device_link_del(mca->pd_link); + + if (!IS_ERR_OR_NULL(mca->pd_dev)) + dev_pm_domain_detach(mca->pd_dev, true); +} + +static int apple_mca_probe(struct platform_device *pdev) +{ + struct mca_data *mca; + struct mca_cluster *clusters; + struct snd_soc_dai_driver *dai_drivers; + int nclusters; + int irq, ret, i; + + ret = of_property_read_u32(pdev->dev.of_node, "apple,nclusters", &nclusters); + if (ret || nclusters > MAX_NCLUSTERS) { + dev_err(&pdev->dev, "missing or invalid apple,nclusters property\n"); + return -EINVAL; + } + + mca = devm_kzalloc(&pdev->dev, struct_size(mca, clusters, nclusters), + GFP_KERNEL); + if (!mca) + return -ENOMEM; + + mca->dev = &pdev->dev; + mca->nclusters = nclusters; + platform_set_drvdata(pdev, mca); + clusters = mca->clusters; + + mca->base = devm_platform_ioremap_resource_byname(pdev, "clusters"); + if (IS_ERR(mca->base)) { + dev_err(&pdev->dev, "unable to obtain clusters MMIO resource: %ld\n", + PTR_ERR(mca->base)); + return PTR_ERR(mca->base); + } + + mca->switch_base = devm_platform_ioremap_resource_byname(pdev, "switch"); + if (IS_ERR(mca->switch_base)) { + dev_err(&pdev->dev, "unable to obtain switch MMIO resource: %ld\n", + PTR_ERR(mca->switch_base)); + return PTR_ERR(mca->switch_base); + } + + { + struct reset_control *rst; + rst = of_reset_control_array_get(pdev->dev.of_node, true, true, false); + if (IS_ERR(rst)) { + dev_err(&pdev->dev, "unable to obtain reset control: %ld\n", + PTR_ERR(rst)); + } else if (rst) { + reset_control_reset(rst); + reset_control_put(rst); + } + } + + dai_drivers = devm_kzalloc(&pdev->dev, sizeof(*dai_drivers) * nclusters, + GFP_KERNEL); + if (!dai_drivers) + return -ENOMEM; + + mca->pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, 0); + if (IS_ERR(mca->pd_dev)) + return -EINVAL; + + mca->pd_link = device_link_add(&pdev->dev, mca->pd_dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!mca->pd_link) { + ret = -EINVAL; + goto err_release; + } + + for (i = 0; i < nclusters; i++) { + struct mca_cluster *cl = &clusters[i]; + struct snd_soc_dai_driver *drv = &dai_drivers[i]; + int stream; + + cl->host = mca; + cl->no = i; + + cl->clk_parent = of_clk_get(pdev->dev.of_node, i); + if (IS_ERR(cl->clk_parent)) { + dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", + i, PTR_ERR(cl->clk_parent)); + ret = PTR_ERR(cl->clk_parent); + goto err_release; + } + + cl->pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i + 1); + if (IS_ERR(cl->pd_dev)) { + dev_err(&pdev->dev, "unable to obtain cluster %d PD: %ld\n", + i, PTR_ERR(cl->pd_dev)); + ret = PTR_ERR(cl->pd_dev); + goto err_release; + } + +#if 0 + ret = clk_rate_exclusive_get(clk); + if (ret) { + dev_err(&pdev->dev, "unable to get clock rate exclusivity\n"); + goto err_release; + } + +#endif + + irq = platform_get_irq_optional(pdev, i); + if (irq >= 0) { + dev_dbg(&pdev->dev, "have IRQs for cluster %d\n", i); +#if 0 + ret = devm_request_irq(&pdev->dev, irq, mca_interrupt, + 0, dev_name(&pdev->dev), cl); + if (ret) { + dev_err(&pdev->dev, "unable to register interrupt: %d\n", ret); + goto err_release; + } + mca_poke(mca, i, REG_INTMASK, 0xffffffff); +#endif + } + + for_each_pcm_streams(stream) { + struct dma_chan *chan; + bool is_tx = (stream == SNDRV_PCM_STREAM_PLAYBACK); +#ifndef USE_RXB_FOR_CAPTURE + char *name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + is_tx ? "tx%da" : "rx%da", i); +#else + char *name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + is_tx ? "tx%da" : "rx%db", i); +#endif + + chan = of_dma_request_slave_channel(pdev->dev.of_node, name); + if (IS_ERR(chan)) { + if (PTR_ERR(chan) != -EPROBE_DEFER) + dev_err(&pdev->dev, "no %s DMA channel: %ld\n", + name, PTR_ERR(chan)); + + ret = PTR_ERR(chan); + goto err_release; + } + + cl->dma_chans[stream] = chan; + } + + drv->id = i; + drv->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "mca-i2s-%d", i); + if (!drv->name) { + ret = -ENOMEM; + goto err_release; + } + drv->ops = &mca_dai_ops; + drv->playback.channels_min = 1; + drv->playback.channels_max = 32; + drv->playback.rates = SNDRV_PCM_RATE_8000_192000; + drv->playback.formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE; + drv->capture.channels_min = 1; + drv->capture.channels_max = 32; + drv->capture.rates = SNDRV_PCM_RATE_8000_192000; + drv->capture.formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE; + drv->symmetric_rate = 1; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &mca_component, + dai_drivers, nclusters); + if (ret) { + dev_err(&pdev->dev, "unable to register ASoC component: %d\n", ret); + goto err_release; + } + + dev_dbg(&pdev->dev, "all good, ready to go!\n"); + return 0; + +err_release: + apple_mca_release(mca); + return ret; +} + +static int apple_mca_remove(struct platform_device *pdev) +{ + struct mca_data *mca = platform_get_drvdata(pdev); + + apple_mca_release(mca); + /* TODO */ + + return 0; +} + +static const struct of_device_id apple_mca_of_match[] = { + { .compatible = "apple,mca", }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_mca_of_match); + +static struct platform_driver apple_mca_driver = { + .driver = { + .name = "apple-mca", + .owner = THIS_MODULE, + .of_match_table = apple_mca_of_match, + }, + .probe = apple_mca_probe, + .remove = apple_mca_remove, +}; +module_platform_driver(apple_mca_driver); + +MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>"); +MODULE_DESCRIPTION("ASoC platform driver for Apple Silicon SoCs"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 4fade2388797..2a98f9bc7144 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -380,7 +380,7 @@ static const struct regmap_config cs42l42_regmap = { .max_register = CS42L42_MAX_REGISTER, .reg_defaults = cs42l42_reg_defaults, .num_reg_defaults = ARRAY_SIZE(cs42l42_reg_defaults), - .cache_type = REGCACHE_RBTREE, + //.cache_type = REGCACHE_RBTREE, .use_single_read = true, .use_single_write = true, @@ -2273,13 +2273,10 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client) goto err_disable; } - if (devid != CS42L42_CHIP_ID) { - ret = -ENODEV; - dev_err(&i2c_client->dev, + if (devid != CS42L42_CHIP_ID) + dev_warn(&i2c_client->dev, "CS42L42 Device ID (%X). Expected %X\n", devid, CS42L42_CHIP_ID); - goto err_disable; - } ret = regmap_read(cs42l42->regmap, CS42L42_REVID, ®); if (ret < 0) { diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index 4cb788f3e5f7..b3c99f470916 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -16,6 +16,7 @@ #include <linux/regmap.h> #include <linux/of.h> #include <linux/of_gpio.h> +#include <linux/of_device.h> #include <linux/slab.h> #include <sound/soc.h> #include <sound/pcm.h> @@ -25,13 +26,20 @@ #include "tas2764.h" +enum tas2764_devid { + DEVID_TAS2764 = 0, + DEVID_SN012776 = 1 +}; + struct tas2764_priv { struct snd_soc_component *component; struct gpio_desc *reset_gpio; struct gpio_desc *sdz_gpio; struct regmap *regmap; struct device *dev; - + + enum tas2764_devid devid; + int v_sense_slot; int i_sense_slot; }; @@ -50,27 +58,47 @@ static void tas2764_reset(struct tas2764_priv *tas2764) usleep_range(1000, 2000); } +static int tas2764_set_power(struct tas2764_priv *tas2764, u8 mode) +{ + int ret; + + // TODO: Does this only affect the SN012776 variant? + if (tas2764->devid == DEVID_SN012776 && mode == TAS2764_PWR_CTRL_MUTE) { + ret = snd_soc_component_update_bits(tas2764->component, + TAS2764_PWR_CTRL, + TAS2764_PWR_CTRL_MASK, + TAS2764_PWR_CTRL_ACTIVE); + if (ret < 0) + return ret; + } + + ret = snd_soc_component_update_bits(tas2764->component, + TAS2764_PWR_CTRL, + TAS2764_PWR_CTRL_MASK, + mode); + if (ret < 0) + return ret; + + return 0; +} + + static int tas2764_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); + u8 mode; switch (level) { case SND_SOC_BIAS_ON: - snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_ACTIVE); + mode = TAS2764_PWR_CTRL_ACTIVE; break; case SND_SOC_BIAS_STANDBY: case SND_SOC_BIAS_PREPARE: - snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_MUTE); + mode = TAS2764_PWR_CTRL_MUTE; break; case SND_SOC_BIAS_OFF: - snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_SHUTDOWN); + mode = TAS2764_PWR_CTRL_SHUTDOWN; break; default: @@ -79,7 +107,7 @@ static int tas2764_set_bias_level(struct snd_soc_component *component, return -EINVAL; } - return 0; + return tas2764_set_power(tas2764, mode); } #ifdef CONFIG_PM @@ -88,10 +116,7 @@ static int tas2764_codec_suspend(struct snd_soc_component *component) struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); int ret; - ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_SHUTDOWN); - + ret = tas2764_set_power(tas2764, TAS2764_PWR_CTRL_SHUTDOWN); if (ret < 0) return ret; @@ -114,10 +139,7 @@ static int tas2764_codec_resume(struct snd_soc_component *component) usleep_range(1000, 2000); } - ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_ACTIVE); - + ret = tas2764_set_power(tas2764, TAS2764_PWR_CTRL_ACTIVE); if (ret < 0) return ret; @@ -146,28 +168,21 @@ static int tas2764_dac_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); - int ret; + u8 mode; switch (event) { case SND_SOC_DAPM_POST_PMU: - ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_MUTE); + mode = TAS2764_PWR_CTRL_MUTE; break; case SND_SOC_DAPM_PRE_PMD: - ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, - TAS2764_PWR_CTRL_MASK, - TAS2764_PWR_CTRL_SHUTDOWN); + mode = TAS2764_PWR_CTRL_SHUTDOWN; break; default: dev_err(tas2764->dev, "Unsupported event\n"); return -EINVAL; } - if (ret < 0) - return ret; - - return 0; + return tas2764_set_power(tas2764, mode); } static const struct snd_kcontrol_new isense_switch = @@ -502,10 +517,16 @@ static struct snd_soc_dai_driver tas2764_dai_driver[] = { }, }; +static uint8_t sn012776_bop_presets[] = { + 0x01, 0x32, 0x02, 0x22, 0x83, 0x2d, 0x80, 0x02, 0x06, + 0x32, 0x46, 0x30, 0x02, 0x06, 0x38, 0x40, 0x30, 0x02, + 0x06, 0x3e, 0x37, 0x30, 0xff, 0xe6 +}; + static int tas2764_codec_probe(struct snd_soc_component *component) { struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); - int ret; + int ret, i; tas2764->component = component; @@ -532,6 +553,23 @@ static int tas2764_codec_probe(struct snd_soc_component *component) if (ret < 0) return ret; + if (tas2764->devid == DEVID_SN012776) { + ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, + TAS2764_PWR_CTRL_BOP_SRC, + TAS2764_PWR_CTRL_BOP_SRC); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(sn012776_bop_presets); i++) { + ret = snd_soc_component_write(component, + TAS2764_BOP_CFG0 + i, + sn012776_bop_presets[i]); + + if (ret < 0) + return ret; + } + } + return 0; } @@ -631,9 +669,12 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764) return 0; } +static const struct of_device_id tas2764_of_match[]; + static int tas2764_i2c_probe(struct i2c_client *client) { struct tas2764_priv *tas2764; + const struct of_device_id *of_id = NULL; int result; tas2764 = devm_kzalloc(&client->dev, sizeof(struct tas2764_priv), @@ -641,6 +682,14 @@ static int tas2764_i2c_probe(struct i2c_client *client) if (!tas2764) return -ENOMEM; + if (client->dev.of_node) + of_id = of_match_device(tas2764_of_match, &client->dev); + + if (of_id) + tas2764->devid = (enum tas2764_devid) of_id->data; + else + tas2764->devid = DEVID_TAS2764; + tas2764->dev = &client->dev; i2c_set_clientdata(client, tas2764); dev_set_drvdata(&client->dev, tas2764); @@ -674,13 +723,12 @@ static const struct i2c_device_id tas2764_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, tas2764_i2c_id); -#if defined(CONFIG_OF) static const struct of_device_id tas2764_of_match[] = { - { .compatible = "ti,tas2764" }, + { .compatible = "ti,tas2764", .data = (void*) DEVID_TAS2764 }, + { .compatible = "ti,sn012776", .data = (void*) DEVID_SN012776 }, {}, }; MODULE_DEVICE_TABLE(of, tas2764_of_match); -#endif static struct i2c_driver tas2764_i2c_driver = { .driver = { diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h index f015f22a083b..d7e171a9480c 100644 --- a/sound/soc/codecs/tas2764.h +++ b/sound/soc/codecs/tas2764.h @@ -29,6 +29,7 @@ #define TAS2764_PWR_CTRL_ACTIVE 0x0 #define TAS2764_PWR_CTRL_MUTE BIT(0) #define TAS2764_PWR_CTRL_SHUTDOWN BIT(1) +#define TAS2764_PWR_CTRL_BOP_SRC BIT(7) #define TAS2764_VSENSE_POWER_EN 3 #define TAS2764_ISENSE_POWER_EN 4 @@ -87,4 +88,6 @@ #define TAS2764_TDM_CFG6_ISNS_ENABLE BIT(6) #define TAS2764_TDM_CFG6_50_MASK GENMASK(5, 0) +#define TAS2764_BOP_CFG0 TAS2764_REG(0X0, 0x1d) + #endif /* __TAS2764__ */ diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index c1dbd978d550..753e3f8d459a 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -337,7 +337,7 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) struct snd_soc_component *component = dai->component; struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component); - u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0; + u8 tdm_rx_start_slot = 0, invert_fpol = 0, fpol_preinv = 0, asi_cfg_1 = 0; int ret; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -349,9 +349,15 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + invert_fpol = 1; + fallthrough; case SND_SOC_DAIFMT_NB_NF: asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_RSING; break; + case SND_SOC_DAIFMT_IB_IF: + invert_fpol = 1; + fallthrough; case SND_SOC_DAIFMT_IB_NF: asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_FALING; break; @@ -369,15 +375,19 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: tdm_rx_start_slot = 1; + fpol_preinv = 0; break; case SND_SOC_DAIFMT_DSP_A: tdm_rx_start_slot = 0; + fpol_preinv = 1; break; case SND_SOC_DAIFMT_DSP_B: tdm_rx_start_slot = 1; + fpol_preinv = 1; break; case SND_SOC_DAIFMT_LEFT_J: tdm_rx_start_slot = 0; + fpol_preinv = 1; break; default: dev_err(tas2770->dev, @@ -391,6 +401,15 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) if (ret < 0) return ret; + + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_FPOL_MASK, + (fpol_preinv ^ invert_fpol) + ? TAS2770_TDM_CFG_REG0_FPOL_RSING + : TAS2770_TDM_CFG_REG0_FPOL_FALING); + if (ret < 0) + return ret; + return 0; } @@ -489,8 +508,8 @@ static struct snd_soc_dai_driver tas2770_dai_driver[] = { .id = 0, .playback = { .stream_name = "ASI1 Playback", - .channels_min = 2, - .channels_max = 2, + .channels_min = 1, + .channels_max = 1, .rates = TAS2770_RATES, .formats = TAS2770_FORMATS, }, diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h index d156666bcc55..42277fb6bc10 100644 --- a/sound/soc/codecs/tas2770.h +++ b/sound/soc/codecs/tas2770.h @@ -41,6 +41,9 @@ #define TAS2770_TDM_CFG_REG0_31_44_1_48KHZ 0x6 #define TAS2770_TDM_CFG_REG0_31_88_2_96KHZ 0x8 #define TAS2770_TDM_CFG_REG0_31_176_4_192KHZ 0xa +#define TAS2770_TDM_CFG_REG0_FPOL_MASK BIT(0) +#define TAS2770_TDM_CFG_REG0_FPOL_RSING 0 +#define TAS2770_TDM_CFG_REG0_FPOL_FALING 1 /* TDM Configuration Reg1 */ #define TAS2770_TDM_CFG_REG1 TAS2770_REG(0X0, 0x0B) #define TAS2770_TDM_CFG_REG1_MASK GENMASK(5, 1) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 9574f86dd4de..37e2a8b2af10 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2069,12 +2069,12 @@ static int snd_soc_bind_card(struct snd_soc_card *card) } } + snd_soc_dapm_new_widgets(card); + ret = snd_soc_card_late_probe(card); if (ret < 0) goto probe_end; - snd_soc_dapm_new_widgets(card); - ret = snd_card_register(card->snd_card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to register soundcard %d\n", @@ -2209,19 +2209,34 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, } EXPORT_SYMBOL_GPL(snd_soc_cnew); -static int snd_soc_add_controls(struct snd_card *card, struct device *dev, +static int snd_soc_add_controls(struct snd_soc_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix, void *data) { - int i; + int i, err; for (i = 0; i < num_controls; i++) { - const struct snd_kcontrol_new *control = &controls[i]; - int err = snd_ctl_add(card, snd_soc_cnew(control, data, - control->name, prefix)); + const struct snd_kcontrol_new *control_new = &controls[i]; + struct snd_kcontrol *control; + + control = snd_soc_cnew(control_new, data, + control_new->name, prefix); + + if (card->filter_controls) { + err = card->filter_controls(card, control); + if (err < 0) { + snd_ctl_free_one(control); + return err; + } else if (err) { + continue; + } + } + + err = snd_ctl_add(card->snd_card, control); + if (err < 0) { dev_err(dev, "ASoC: Failed to add %s: %d\n", - control->name, err); + control_new->name, err); return err; } } @@ -2241,9 +2256,7 @@ static int snd_soc_add_controls(struct snd_card *card, struct device *dev, int snd_soc_add_component_controls(struct snd_soc_component *component, const struct snd_kcontrol_new *controls, unsigned int num_controls) { - struct snd_card *card = component->card->snd_card; - - return snd_soc_add_controls(card, component->dev, controls, + return snd_soc_add_controls(component->card, component->dev, controls, num_controls, component->name_prefix, component); } EXPORT_SYMBOL_GPL(snd_soc_add_component_controls); @@ -2258,13 +2271,11 @@ EXPORT_SYMBOL_GPL(snd_soc_add_component_controls); * * Return 0 for success, else error. */ -int snd_soc_add_card_controls(struct snd_soc_card *soc_card, +int snd_soc_add_card_controls(struct snd_soc_card *card, const struct snd_kcontrol_new *controls, int num_controls) { - struct snd_card *card = soc_card->snd_card; - - return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, - NULL, soc_card); + return snd_soc_add_controls(card, card->dev, controls, num_controls, + NULL, card); } EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); @@ -2281,7 +2292,7 @@ EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); int snd_soc_add_dai_controls(struct snd_soc_dai *dai, const struct snd_kcontrol_new *controls, int num_controls) { - struct snd_card *card = dai->component->card->snd_card; + struct snd_soc_card *card = dai->component->card; return snd_soc_add_controls(card, dai->dev, controls, num_controls, NULL, dai); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a8e842e02cdc..c87137f364da 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -878,7 +878,7 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, int kci) { struct snd_soc_dapm_context *dapm = w->dapm; - struct snd_card *card = dapm->card->snd_card; + struct snd_soc_card *card = dapm->card; const char *prefix; size_t prefix_len; int shared; @@ -962,7 +962,19 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, goto exit_free; } - ret = snd_ctl_add(card, kcontrol); + if (card->filter_controls) { + ret = card->filter_controls(card, kcontrol); + if (ret < 0) { + snd_ctl_free_one(kcontrol); + goto exit_free; + } + + if (!ret) + ret = snd_ctl_add(card->snd_card, kcontrol); + } else { + ret = snd_ctl_add(card->snd_card, kcontrol); + } + if (ret < 0) { dev_err(dapm->dev, "ASoC: failed to add widget %s dapm kcontrol %s: %d\n", @@ -1079,7 +1091,7 @@ static int dapm_new_pga(struct snd_soc_dapm_widget *w) /* create new dapm dai link control */ static int dapm_new_dai_link(struct snd_soc_dapm_widget *w) { - int i; + int i, ret; struct snd_soc_pcm_runtime *rtd = w->priv; /* create control for links with > 1 config */ @@ -1089,10 +1101,22 @@ static int dapm_new_dai_link(struct snd_soc_dapm_widget *w) /* add kcontrol */ for (i = 0; i < w->num_kcontrols; i++) { struct snd_soc_dapm_context *dapm = w->dapm; - struct snd_card *card = dapm->card->snd_card; + struct snd_soc_card *card = dapm->card; struct snd_kcontrol *kcontrol = snd_soc_cnew(&w->kcontrol_news[i], w, w->name, NULL); - int ret = snd_ctl_add(card, kcontrol); + + if (card->filter_controls) { + ret = card->filter_controls(card, kcontrol); + if (ret < 0) { + snd_ctl_free_one(kcontrol); + return ret; + } + + if (!ret) + ret = snd_ctl_add(card->snd_card, kcontrol); + } else { + ret = snd_ctl_add(card->snd_card, kcontrol); + } if (ret < 0) { dev_err(dapm->dev, diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index a827cc3c158a..542c308df17c 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2811,9 +2811,10 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, } else if (rtd->num_cpus == rtd->num_codecs) { cpu_dai = asoc_rtd_to_cpu(rtd, i); } else { - dev_err(rtd->card->dev, - "N cpus to M codecs link is not supported yet\n"); - return -EINVAL; + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + //dev_err(rtd->card->dev, + // "N cpus to M codecs link is not supported yet\n"); + //return -EINVAL; } if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_PLAYBACK) && |