diff options
171 files changed, 16141 insertions, 4012 deletions
diff --git a/Documentation/ABI/testing/debugfs-driver-habanalabs b/Documentation/ABI/testing/debugfs-driver-habanalabs index f0ac14b70ecb..a73601c5121e 100644 --- a/Documentation/ABI/testing/debugfs-driver-habanalabs +++ b/Documentation/ABI/testing/debugfs-driver-habanalabs @@ -43,6 +43,20 @@ Description: Allows the root user to read or write directly through the If the IOMMU is disabled, it also allows the root user to read or write from the host a device VA of a host mapped memory +What: /sys/kernel/debug/habanalabs/hl<n>/data64 +Date: Jan 2020 +KernelVersion: 5.6 +Contact: oded.gabbay@gmail.com +Description: Allows the root user to read or write 64 bit data directly + through the device's PCI bar. Writing to this file generates a + write transaction while reading from the file generates a read + transaction. This custom interface is needed (instead of using + the generic Linux user-space PCI mapping) because the DDR bar + is very small compared to the DDR memory and only the driver can + move the bar before and after the transaction. + If the IOMMU is disabled, it also allows the root user to read + or write from the host a device VA of a host mapped memory + What: /sys/kernel/debug/habanalabs/hl<n>/device Date: Jan 2019 KernelVersion: 5.1 diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti b/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti new file mode 100644 index 000000000000..9d11502b4390 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti @@ -0,0 +1,241 @@ +What: /sys/bus/coresight/devices/<cti-name>/enable +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Enable/Disable the CTI hardware. + +What: /sys/bus/coresight/devices/<cti-name>/powered +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Indicate if the CTI hardware is powered. + +What: /sys/bus/coresight/devices/<cti-name>/ctmid +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Display the associated CTM ID + +What: /sys/bus/coresight/devices/<cti-name>/nr_trigger_cons +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Number of devices connected to triggers on this CTI + +What: /sys/bus/coresight/devices/<cti-name>/triggers<N>/name +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Name of connected device <N> + +What: /sys/bus/coresight/devices/<cti-name>/triggers<N>/in_signals +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Input trigger signals from connected device <N> + +What: /sys/bus/coresight/devices/<cti-name>/triggers<N>/in_types +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Functional types for the input trigger signals + from connected device <N> + +What: /sys/bus/coresight/devices/<cti-name>/triggers<N>/out_signals +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Output trigger signals to connected device <N> + +What: /sys/bus/coresight/devices/<cti-name>/triggers<N>/out_types +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Functional types for the output trigger signals + to connected device <N> + +What: /sys/bus/coresight/devices/<cti-name>/regs/inout_sel +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Select the index for inen and outen registers. + +What: /sys/bus/coresight/devices/<cti-name>/regs/inen +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Read or write the CTIINEN register selected by inout_sel. + +What: /sys/bus/coresight/devices/<cti-name>/regs/outen +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Read or write the CTIOUTEN register selected by inout_sel. + +What: /sys/bus/coresight/devices/<cti-name>/regs/gate +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Read or write CTIGATE register. + +What: /sys/bus/coresight/devices/<cti-name>/regs/asicctl +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Read or write ASICCTL register. + +What: /sys/bus/coresight/devices/<cti-name>/regs/intack +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Write the INTACK register. + +What: /sys/bus/coresight/devices/<cti-name>/regs/appset +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Set CTIAPPSET register to activate channel. Read back to + determine current value of register. + +What: /sys/bus/coresight/devices/<cti-name>/regs/appclear +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Write APPCLEAR register to deactivate channel. + +What: /sys/bus/coresight/devices/<cti-name>/regs/apppulse +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Write APPPULSE to pulse a channel active for one clock + cycle. + +What: /sys/bus/coresight/devices/<cti-name>/regs/chinstatus +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Read current status of channel inputs. + +What: /sys/bus/coresight/devices/<cti-name>/regs/choutstatus +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) read current status of channel outputs. + +What: /sys/bus/coresight/devices/<cti-name>/regs/triginstatus +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) read current status of input trigger signals + +What: /sys/bus/coresight/devices/<cti-name>/regs/trigoutstatus +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) read current status of output trigger signals. + +What: /sys/bus/coresight/devices/<cti-name>/channels/trigin_attach +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Attach a CTI input trigger to a CTM channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/trigin_detach +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Detach a CTI input trigger from a CTM channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/trigout_attach +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Attach a CTI output trigger to a CTM channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/trigout_detach +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Detach a CTI output trigger from a CTM channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_gate_enable +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Enable CTIGATE for single channel (W) or list enabled + channels through the gate (R). + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_gate_disable +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Disable CTIGATE for single channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_set +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Activate a single channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_clear +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Deactivate a single channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_pulse +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Pulse a single channel - activate for a single clock cycle. + +What: /sys/bus/coresight/devices/<cti-name>/channels/trigout_filtered +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) List of output triggers filtered across all connections. + +What: /sys/bus/coresight/devices/<cti-name>/channels/trig_filter_enable +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Enable or disable trigger output signal filtering. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_inuse +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) show channels with at least one attached trigger signal. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_free +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) show channels with no attached trigger signals. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_xtrigs_sel +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (RW) Write channel number to select a channel to view, read to + see selected channel number. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_xtrigs_in +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Read to see input triggers connected to selected view + channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_xtrigs_out +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (R) Read to see output triggers connected to selected view + channel. + +What: /sys/bus/coresight/devices/<cti-name>/channels/chan_xtrigs_reset +Date: March 2020 +KernelVersion 5.7 +Contact: Mike Leach or Mathieu Poirier +Description: (W) Clear all channel / trigger programming. diff --git a/Documentation/ABI/testing/sysfs-bus-intel_th-devices-msc b/Documentation/ABI/testing/sysfs-bus-intel_th-devices-msc index 456cb62b384c..7fd2601c2831 100644 --- a/Documentation/ABI/testing/sysfs-bus-intel_th-devices-msc +++ b/Documentation/ABI/testing/sysfs-bus-intel_th-devices-msc @@ -40,3 +40,11 @@ Description: (RW) Trigger window switch for the MSC's buffer, in triggering a window switch for the buffer. Returns an error in any other operating mode or attempts to write something other than "1". +What: /sys/bus/intel_th/devices/<intel_th_id>-msc<msc-id>/stop_on_full +Date: March 2020 +KernelVersion: 5.7 +Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com> +Description: (RW) Configure whether trace stops when the last available window + becomes full (1/y/Y) or wraps around and continues until the next + window becomes available again (0/n/N). + diff --git a/Documentation/ABI/testing/sysfs-driver-jz4780-efuse b/Documentation/ABI/testing/sysfs-driver-jz4780-efuse new file mode 100644 index 000000000000..bb6f5d6ceea0 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-jz4780-efuse @@ -0,0 +1,16 @@ +What: /sys/devices/*/<our-device>/nvmem +Date: December 2017 +Contact: PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com> +Description: read-only access to the efuse on the Ingenic JZ4780 SoC + The SoC has a one time programmable 8K efuse that is + split into segments. The driver supports read only. + The segments are + 0x000 64 bit Random Number + 0x008 128 bit Ingenic Chip ID + 0x018 128 bit Customer ID + 0x028 3520 bit Reserved + 0x1E0 8 bit Protect Segment + 0x1E1 2296 bit HDMI Key + 0x300 2048 bit Security boot key +Users: any user space application which wants to read the Chip + and Customer ID diff --git a/Documentation/admin-guide/dynamic-debug-howto.rst b/Documentation/admin-guide/dynamic-debug-howto.rst index 252e5ef324e5..0dc2eb8e44e5 100644 --- a/Documentation/admin-guide/dynamic-debug-howto.rst +++ b/Documentation/admin-guide/dynamic-debug-howto.rst @@ -54,6 +54,9 @@ If you make a mistake with the syntax, the write will fail thus:: <debugfs>/dynamic_debug/control -bash: echo: write error: Invalid argument +Note, for systems without 'debugfs' enabled, the control file can be +found in ``/proc/dynamic_debug/control``. + Viewing Dynamic Debug Behaviour =============================== diff --git a/Documentation/devicetree/bindings/arm/coresight-cti.yaml b/Documentation/devicetree/bindings/arm/coresight-cti.yaml new file mode 100644 index 000000000000..3db3642bd532 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/coresight-cti.yaml @@ -0,0 +1,336 @@ +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause +# Copyright 2019 Linaro Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/coresight-cti.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ARM Coresight Cross Trigger Interface (CTI) device. + +description: | + The CoreSight Embedded Cross Trigger (ECT) consists of CTI devices connected + to one or more CoreSight components and/or a CPU, with CTIs interconnected in + a star topology via the Cross Trigger Matrix (CTM), which is not programmable. + The ECT components are not part of the trace generation data path and are thus + not part of the CoreSight graph described in the general CoreSight bindings + file coresight.txt. + + The CTI component properties define the connections between the individual + CTI and the components it is directly connected to, consisting of input and + output hardware trigger signals. CTIs can have a maximum number of input and + output hardware trigger signals (8 each for v1 CTI, 32 each for v2 CTI). The + number is defined at design time, the maximum of each defined in the DEVID + register. + + CTIs are interconnected in a star topology via the CTM, using a number of + programmable channels, usually 4, but again implementation defined and + described in the DEVID register. The star topology is not required to be + described in the bindings as the actual connections are software + programmable. + + In general the connections between CTI and components via the trigger signals + are implementation defined, except when the CTI is connected to an ARM v8 + architecture core and optional ETM. + + In this case the ARM v8 architecture defines the required signal connections + between CTI and the CPU core and ETM if present. In the case of a v8 + architecturally connected CTI an additional compatible string is used to + indicate this feature (arm,coresight-cti-v8-arch). + + When CTI trigger connection information is unavailable then a minimal driver + binding can be declared with no explicit trigger signals. This will result + the driver detecting the maximum available triggers and channels from the + DEVID register and make them all available for use as a single default + connection. Any user / client application will require additional information + on the connections between the CTI and other components for correct operation. + This information might be found by enabling the Integration Test registers in + the driver (set CONFIG_CORESIGHT_CTI_INTEGRATION_TEST in Kernel + configuration). These registers may be used to explore the trigger connections + between CTI and other CoreSight components. + + Certain triggers between CoreSight devices and the CTI have specific types + and usages. These can be defined along with the signal indexes with the + constants defined in <dt-bindings/arm/coresight-cti-dt.h> + + For example a CTI connected to a core will usually have a DBGREQ signal. This + is defined in the binding as type PE_EDBGREQ. These types will appear in an + optional array alongside the signal indexes. Omitting types will default all + signals to GEN_IO. + + Note that some hardware trigger signals can be connected to non-CoreSight + components (e.g. UART etc) depending on hardware implementation. + +maintainers: + - Mike Leach <mike.leach@linaro.org> + +allOf: + - $ref: /schemas/arm/primecell.yaml# + +# Need a custom select here or 'arm,primecell' will match on lots of nodes +select: + properties: + compatible: + contains: + enum: + - arm,coresight-cti + required: + - compatible + +properties: + $nodename: + pattern: "^cti(@[0-9a-f]+)$" + compatible: + oneOf: + - items: + - const: arm,coresight-cti + - const: arm,primecell + - items: + - const: arm,coresight-cti-v8-arch + - const: arm,coresight-cti + - const: arm,primecell + + reg: + maxItems: 1 + + cpu: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Handle to cpu this device is associated with. This must appear in the + base cti node if compatible string arm,coresight-cti-v8-arch is used, + or may appear in a trig-conns child node when appropriate. + + arm,cti-ctm-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Defines the CTM this CTI is connected to, in large systems with multiple + separate CTI/CTM nets. Typically multi-socket systems where the CTM is + propagated between sockets. + + arm,cs-dev-assoc: + $ref: /schemas/types.yaml#/definitions/phandle + description: + defines a phandle reference to an associated CoreSight trace device. + When the associated trace device is enabled, then the respective CTI + will be enabled. Use in a trig-conns node, or in CTI base node when + compatible string arm,coresight-cti-v8-arch used. If the associated + device has not been registered then the node name will be stored as + the connection name for later resolution. If the associated device is + not a CoreSight device or not registered then the node name will remain + the connection name and automatic enabling will not occur. + + # size cells and address cells required if trig-conns node present. + "#size-cells": + const: 0 + + "#address-cells": + const: 1 + +patternProperties: + '^trig-conns@([0-9]+)$': + type: object + description: + A trigger connections child node which describes the trigger signals + between this CTI and another hardware device. This device may be a CPU, + CoreSight device, any other hardware device or simple external IO lines. + The connection may have both input and output triggers, or only one or the + other. + + properties: + reg: + maxItems: 1 + + arm,trig-in-sigs: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 32 + description: + List of CTI trigger in signal numbers in use by a trig-conns node. + + arm,trig-in-types: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 32 + description: + List of constants representing the types for the CTI trigger in + signals. Types in this array match to the corresponding signal in the + arm,trig-in-sigs array. If the -types array is smaller, or omitted + completely, then the types will default to GEN_IO. + + arm,trig-out-sigs: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 32 + description: + List of CTI trigger out signal numbers in use by a trig-conns node. + + arm,trig-out-types: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 32 + description: + List of constants representing the types for the CTI trigger out + signals. Types in this array match to the corresponding signal + in the arm,trig-out-sigs array. If the "-types" array is smaller, + or omitted completely, then the types will default to GEN_IO. + + arm,trig-filters: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 32 + description: + List of CTI trigger out signals that will be blocked from becoming + active, unless filtering is disabled on the driver. + + arm,trig-conn-name: + allOf: + - $ref: /schemas/types.yaml#/definitions/string + description: + Defines a connection name that will be displayed, if the cpu or + arm,cs-dev-assoc properties are not being used in this connection. + Principle use for CTI that are connected to non-CoreSight devices, or + external IO. + + anyOf: + - required: + - arm,trig-in-sigs + - required: + - arm,trig-out-sigs + oneOf: + - required: + - arm,trig-conn-name + - required: + - cpu + - required: + - arm,cs-dev-assoc + required: + - reg + +required: + - compatible + - reg + - clocks + - clock-names + +if: + properties: + compatible: + contains: + const: arm,coresight-cti-v8-arch + +then: + required: + - cpu + +examples: + # minimum CTI definition. DEVID register used to set number of triggers. + - | + cti@20020000 { + compatible = "arm,coresight-cti", "arm,primecell"; + reg = <0x20020000 0x1000>; + + clocks = <&soc_smc50mhz>; + clock-names = "apb_pclk"; + }; + # v8 architecturally defined CTI - CPU + ETM connections generated by the + # driver according to the v8 architecture specification. + - | + cti@859000 { + compatible = "arm,coresight-cti-v8-arch", "arm,coresight-cti", + "arm,primecell"; + reg = <0x859000 0x1000>; + + clocks = <&soc_smc50mhz>; + clock-names = "apb_pclk"; + + cpu = <&CPU1>; + arm,cs-dev-assoc = <&etm1>; + }; + # Implementation defined CTI - CPU + ETM connections explicitly defined.. + # Shows use of type constants from dt-bindings/arm/coresight-cti-dt.h + # #size-cells and #address-cells are required if trig-conns@ nodes present. + - | + #include <dt-bindings/arm/coresight-cti-dt.h> + + cti@858000 { + compatible = "arm,coresight-cti", "arm,primecell"; + reg = <0x858000 0x1000>; + + clocks = <&soc_smc50mhz>; + clock-names = "apb_pclk"; + + arm,cti-ctm-id = <1>; + + #address-cells = <1>; + #size-cells = <0>; + + trig-conns@0 { + reg = <0>; + arm,trig-in-sigs = <4 5 6 7>; + arm,trig-in-types = <ETM_EXTOUT + ETM_EXTOUT + ETM_EXTOUT + ETM_EXTOUT>; + arm,trig-out-sigs = <4 5 6 7>; + arm,trig-out-types = <ETM_EXTIN + ETM_EXTIN + ETM_EXTIN + ETM_EXTIN>; + arm,cs-dev-assoc = <&etm0>; + }; + + trig-conns@1 { + reg = <1>; + cpu = <&CPU0>; + arm,trig-in-sigs = <0 1>; + arm,trig-in-types = <PE_DBGTRIGGER + PE_PMUIRQ>; + arm,trig-out-sigs=<0 1 2 >; + arm,trig-out-types = <PE_EDBGREQ + PE_DBGRESTART + PE_CTIIRQ>; + + arm,trig-filters = <0>; + }; + }; + # Implementation defined CTI - non CoreSight component connections. + - | + cti@20110000 { + compatible = "arm,coresight-cti", "arm,primecell"; + reg = <0 0x20110000 0 0x1000>; + + clocks = <&soc_smc50mhz>; + clock-names = "apb_pclk"; + + #address-cells = <1>; + #size-cells = <0>; + + trig-conns@0 { + reg = <0>; + arm,trig-in-sigs=<0>; + arm,trig-in-types=<GEN_INTREQ>; + arm,trig-out-sigs=<0>; + arm,trig-out-types=<GEN_HALTREQ>; + arm,trig-conn-name = "sys_profiler"; + }; + + trig-conns@1 { + reg = <1>; + arm,trig-out-sigs=<2 3>; + arm,trig-out-types=<GEN_HALTREQ GEN_RESTARTREQ>; + arm,trig-conn-name = "watchdog"; + }; + + trig-conns@2 { + reg = <2>; + arm,trig-in-sigs=<1 6>; + arm,trig-in-types=<GEN_HALTREQ GEN_RESTARTREQ>; + arm,trig-conn-name = "g_counter"; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/arm/coresight.txt b/Documentation/devicetree/bindings/arm/coresight.txt index d02c42d21f2f..846f6daae71b 100644 --- a/Documentation/devicetree/bindings/arm/coresight.txt +++ b/Documentation/devicetree/bindings/arm/coresight.txt @@ -45,6 +45,10 @@ its hardware characteristcs. - Coresight Address Translation Unit (CATU) "arm,coresight-catu", "arm,primecell"; + - Coresight Cross Trigger Interface (CTI): + "arm,coresight-cti", "arm,primecell"; + See coresight-cti.yaml for full CTI definitions. + * reg: physical base address and length of the register set(s) of the component. @@ -72,6 +76,9 @@ its hardware characteristcs. * reg-names: the only acceptable values are "stm-base" and "stm-stimulus-base", each corresponding to the areas defined in "reg". +* Required properties for Coresight Cross Trigger Interface (CTI) + See coresight-cti.yaml for full CTI definitions. + * Required properties for devices that don't show up on the AMBA bus, such as non-configurable replicators and non-configurable funnels: diff --git a/Documentation/devicetree/bindings/extcon/extcon-usbc-cros-ec.txt b/Documentation/devicetree/bindings/extcon/extcon-usbc-cros-ec.txt deleted file mode 100644 index 8e8625c00dfa..000000000000 --- a/Documentation/devicetree/bindings/extcon/extcon-usbc-cros-ec.txt +++ /dev/null @@ -1,24 +0,0 @@ -ChromeOS EC USB Type-C cable and accessories detection - -On ChromeOS systems with USB Type C ports, the ChromeOS Embedded Controller is -able to detect the state of external accessories such as display adapters -or USB devices when said accessories are attached or detached. - -The node for this device must be under a cros-ec node like google,cros-ec-spi -or google,cros-ec-i2c. - -Required properties: -- compatible: Should be "google,extcon-usbc-cros-ec". -- google,usb-port-id: Specifies the USB port ID to use. - -Example: - cros-ec@0 { - compatible = "google,cros-ec-i2c"; - - ... - - extcon { - compatible = "google,extcon-usbc-cros-ec"; - google,usb-port-id = <0>; - }; - } diff --git a/Documentation/devicetree/bindings/extcon/extcon-usbc-cros-ec.yaml b/Documentation/devicetree/bindings/extcon/extcon-usbc-cros-ec.yaml new file mode 100644 index 000000000000..9c5849b341ea --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/extcon-usbc-cros-ec.yaml @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/extcon/extcon-usbc-cros-ec.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ChromeOS EC USB Type-C cable and accessories detection + +maintainers: + - Benson Leung <bleung@chromium.org> + - Enric Balletbo i Serra <enric.balletbo@collabora.com> + +description: | + On ChromeOS systems with USB Type C ports, the ChromeOS Embedded Controller is + able to detect the state of external accessories such as display adapters + or USB devices when said accessories are attached or detached. + The node for this device must be under a cros-ec node like google,cros-ec-spi + or google,cros-ec-i2c. + +properties: + compatible: + const: google,extcon-usbc-cros-ec + + google,usb-port-id: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + description: the port id + minimum: 0 + maximum: 255 + +required: + - compatible + - google,usb-port-id + +additionalProperties: false + +examples: + - | + spi0 { + #address-cells = <1>; + #size-cells = <0>; + cros-ec@0 { + compatible = "google,cros-ec-spi"; + reg = <0>; + + usbc_extcon0: extcon0 { + compatible = "google,extcon-usbc-cros-ec"; + google,usb-port-id = <0>; + }; + + usbc_extcon1: extcon1 { + compatible = "google,extcon-usbc-cros-ec"; + google,usb-port-id = <1>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/interconnect/qcom,bcm-voter.yaml b/Documentation/devicetree/bindings/interconnect/qcom,bcm-voter.yaml new file mode 100644 index 000000000000..5971fc1df08d --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,bcm-voter.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interconnect/qcom,bcm-voter.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm BCM-Voter Interconnect + +maintainers: + - Georgi Djakov <georgi.djakov@linaro.org> + +description: | + The Bus Clock Manager (BCM) is a dedicated hardware accelerator that manages + shared system resources by aggregating requests from multiple Resource State + Coordinators (RSC). Interconnect providers are able to vote for aggregated + thresholds values from consumers by communicating through their respective + RSCs. + +properties: + compatible: + enum: + - qcom,bcm-voter + +required: + - compatible + +additionalProperties: false + +examples: + # Example 1: apps bcm_voter on SDM845 SoC should be defined inside &apps_rsc node + # as defined in Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt + - | + + apps_bcm_voter: bcm_voter { + compatible = "qcom,bcm-voter"; + }; + + # Example 2: disp bcm_voter on SDM845 should be defined inside &disp_rsc node + # as defined in Documentation/devicetree/bindings/soc/qcom/rpmh-rsc.txt + - | + + disp_bcm_voter: bcm_voter { + compatible = "qcom,bcm-voter"; + }; +... diff --git a/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml b/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml new file mode 100644 index 000000000000..91f70c9067d1 --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interconnect/qcom,osm-l3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Operating State Manager (OSM) L3 Interconnect Provider + +maintainers: + - Sibi Sankar <sibis@codeaurora.org> + +description: + L3 cache bandwidth requirements on Qualcomm SoCs is serviced by the OSM. + The OSM L3 interconnect provider aggregates the L3 bandwidth requests + from CPU/GPU and relays it to the OSM. + +properties: + compatible: + enum: + - qcom,sc7180-osm-l3 + - qcom,sdm845-osm-l3 + + reg: + maxItems: 1 + + clocks: + items: + - description: xo clock + - description: alternate clock + + clock-names: + items: + - const: xo + - const: alternate + + '#interconnect-cells': + const: 1 + +required: + - compatible + - reg + - clocks + - clock-names + - '#interconnect-cells' + +additionalProperties: false + +examples: + - | + + #define GPLL0 165 + #define RPMH_CXO_CLK 0 + + osm_l3: interconnect@17d41000 { + compatible = "qcom,sdm845-osm-l3"; + reg = <0x17d41000 0x1400>; + + clocks = <&rpmhcc RPMH_CXO_CLK>, <&gcc GPLL0>; + clock-names = "xo", "alternate"; + + #interconnect-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/interconnect/qcom,sc7180.yaml b/Documentation/devicetree/bindings/interconnect/qcom,sc7180.yaml new file mode 100644 index 000000000000..50f78f87f3fb --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,sc7180.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interconnect/qcom,sc7180.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SC7180 Network-On-Chip Interconnect + +maintainers: + - Odelu Kukatla <okukatla@codeaurora.org> + +description: | + SC7180 interconnect providers support system bandwidth requirements through + RPMh hardware accelerators known as Bus Clock Manager (BCM). The provider is + able to communicate with the BCM through the Resource State Coordinator (RSC) + associated with each execution environment. Provider nodes must point to at + least one RPMh device child node pertaining to their RSC and each provider + can map to multiple RPMh resources. + +properties: + reg: + maxItems: 1 + + compatible: + enum: + - qcom,sc7180-aggre1-noc + - qcom,sc7180-aggre2-noc + - qcom,sc7180-camnoc-virt + - qcom,sc7180-compute-noc + - qcom,sc7180-config-noc + - qcom,sc7180-dc-noc + - qcom,sc7180-gem-noc + - qcom,sc7180-ipa-virt + - qcom,sc7180-mc-virt + - qcom,sc7180-mmss-noc + - qcom,sc7180-npu-noc + - qcom,sc7180-qup-virt + - qcom,sc7180-system-noc + + '#interconnect-cells': + const: 1 + + qcom,bcm-voters: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: | + List of phandles to qcom,bcm-voter nodes that are required by + this interconnect to send RPMh commands. + + qcom,bcm-voter-names: + $ref: /schemas/types.yaml#/definitions/string-array + description: | + Names for each of the qcom,bcm-voters specified. + +required: + - compatible + - reg + - '#interconnect-cells' + - qcom,bcm-voters + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interconnect/qcom,sc7180.h> + + config_noc: interconnect@1500000 { + compatible = "qcom,sc7180-config-noc"; + reg = <0 0x01500000 0 0x28000>; + #interconnect-cells = <1>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + system_noc: interconnect@1620000 { + compatible = "qcom,sc7180-system-noc"; + reg = <0 0x01620000 0 0x17080>; + #interconnect-cells = <1>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + mmss_noc: interconnect@1740000 { + compatible = "qcom,sc7180-mmss-noc"; + reg = <0 0x01740000 0 0x1c100>; + #interconnect-cells = <1>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; diff --git a/Documentation/devicetree/bindings/interconnect/qcom,sdm845.txt b/Documentation/devicetree/bindings/interconnect/qcom,sdm845.txt deleted file mode 100644 index 5c4f1d911630..000000000000 --- a/Documentation/devicetree/bindings/interconnect/qcom,sdm845.txt +++ /dev/null @@ -1,24 +0,0 @@ -Qualcomm SDM845 Network-On-Chip interconnect driver binding ------------------------------------------------------------ - -SDM845 interconnect providers support system bandwidth requirements through -RPMh hardware accelerators known as Bus Clock Manager (BCM). The provider is -able to communicate with the BCM through the Resource State Coordinator (RSC) -associated with each execution environment. Provider nodes must reside within -an RPMh device node pertaining to their RSC and each provider maps to a single -RPMh resource. - -Required properties : -- compatible : shall contain only one of the following: - "qcom,sdm845-rsc-hlos" -- #interconnect-cells : should contain 1 - -Examples: - -apps_rsc: rsc { - rsc_hlos: interconnect { - compatible = "qcom,sdm845-rsc-hlos"; - #interconnect-cells = <1>; - }; -}; - diff --git a/Documentation/devicetree/bindings/interconnect/qcom,sdm845.yaml b/Documentation/devicetree/bindings/interconnect/qcom,sdm845.yaml new file mode 100644 index 000000000000..8b087e0b0b81 --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,sdm845.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interconnect/qcom,sdm845.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SDM845 Network-On-Chip Interconnect + +maintainers: + - Georgi Djakov <georgi.djakov@linaro.org> + +description: | + SDM845 interconnect providers support system bandwidth requirements through + RPMh hardware accelerators known as Bus Clock Manager (BCM). The provider is + able to communicate with the BCM through the Resource State Coordinator (RSC) + associated with each execution environment. Provider nodes must point to at + least one RPMh device child node pertaining to their RSC and each provider + can map to multiple RPMh resources. + +properties: + reg: + maxItems: 1 + + compatible: + enum: + - qcom,sdm845-aggre1-noc + - qcom,sdm845-aggre2-noc + - qcom,sdm845-config-noc + - qcom,sdm845-dc-noc + - qcom,sdm845-gladiator-noc + - qcom,sdm845-mem-noc + - qcom,sdm845-mmss-noc + - qcom,sdm845-system-noc + + '#interconnect-cells': + const: 1 + + qcom,bcm-voters: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: | + List of phandles to qcom,bcm-voter nodes that are required by + this interconnect to send RPMh commands. + + qcom,bcm-voter-names: + $ref: /schemas/types.yaml#/definitions/string-array + description: | + Names for each of the qcom,bcm-voters specified. + +required: + - compatible + - reg + - '#interconnect-cells' + - qcom,bcm-voters + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interconnect/qcom,sdm845.h> + + mem_noc: interconnect@1380000 { + compatible = "qcom,sdm845-mem-noc"; + reg = <0 0x01380000 0 0x27200>; + #interconnect-cells = <1>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + mmss_noc: interconnect@1740000 { + compatible = "qcom,sdm845-mmss-noc"; + reg = <0 0x01740000 0 0x1c1000>; + #interconnect-cells = <1>; + qcom,bcm-voter-names = "apps", "disp"; + qcom,bcm-voters = <&apps_bcm_voter>, <&disp_bcm_voter>; + }; diff --git a/Documentation/devicetree/bindings/nvmem/ingenic,jz4780-efuse.yaml b/Documentation/devicetree/bindings/nvmem/ingenic,jz4780-efuse.yaml new file mode 100644 index 000000000000..1485d3fbabfd --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/ingenic,jz4780-efuse.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/nvmem/ingenic,jz4780-efuse.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic JZ EFUSE driver bindings + +maintainers: + - PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com> + +allOf: + - $ref: "nvmem.yaml#" + +properties: + compatible: + enum: + - ingenic,jz4780-efuse + + reg: + maxItems: 1 + + clocks: + # Handle for the ahb for the efuse. + maxItems: 1 + +required: + - compatible + - reg + - clocks + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/clock/jz4780-cgu.h> + + efuse@134100d0 { + compatible = "ingenic,jz4780-efuse"; + reg = <0x134100d0 0x2c>; + + clocks = <&cgu JZ4780_CLK_AHB2>; + }; + +... diff --git a/Documentation/driver-api/soundwire/stream.rst b/Documentation/driver-api/soundwire/stream.rst index 5351bd2f34a8..8bceece51554 100644 --- a/Documentation/driver-api/soundwire/stream.rst +++ b/Documentation/driver-api/soundwire/stream.rst @@ -156,22 +156,27 @@ Below shows the SoundWire stream states and state transition diagram. :: +-----------+ +------------+ +----------+ +----------+ | ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED | | STATE | | STATE | | STATE | | STATE | - +-----------+ +------------+ +----------+ +----+-----+ - ^ - | - | - v - +----------+ +------------+ +----+-----+ + +-----------+ +------------+ +---+--+---+ +----+-----+ + ^ ^ ^ + | | | + __| |___________ | + | | | + v | v + +----------+ +-----+------+ +-+--+-----+ | RELEASED |<----------+ DEPREPARED |<-------+ DISABLED | | STATE | | STATE | | STATE | +----------+ +------------+ +----------+ -NOTE: State transition between prepare and deprepare is supported in Spec -but not in the software (subsystem) +NOTE: State transitions between ``SDW_STREAM_ENABLED`` and +``SDW_STREAM_DISABLED`` are only relevant when then INFO_PAUSE flag is +supported at the ALSA/ASoC level. Likewise the transition between +``SDW_DISABLED_STATE`` and ``SDW_PREPARED_STATE`` depends on the +INFO_RESUME flag. -NOTE2: Stream state transition checks need to be handled by caller -framework, for example ALSA/ASoC. No checks for stream transition exist in -SoundWire subsystem. +NOTE2: The framework implements basic state transition checks, but +does not e.g. check if a transition from DISABLED to ENABLED is valid +on a specific platform. Such tests need to be added at the ALSA/ASoC +level. Stream State Operations ----------------------- @@ -246,6 +251,9 @@ SDW_STREAM_PREPARED Prepare state of stream. Operations performed before entering in this state: + (0) Steps 1 and 2 are omitted in the case of a resume operation, + where the bus bandwidth is known. + (1) Bus parameters such as bandwidth, frame shape, clock frequency, are computed based on current stream as well as already active stream(s) on Bus. Re-computation is required to accommodate current @@ -270,9 +278,11 @@ Prepare state of stream. Operations performed before entering in this state: After all above operations are successful, stream state is set to ``SDW_STREAM_PREPARED``. -Bus implements below API for PREPARE state which needs to be called once per -stream. From ASoC DPCM framework, this stream state is linked to -.prepare() operation. +Bus implements below API for PREPARE state which needs to be called +once per stream. From ASoC DPCM framework, this stream state is linked +to .prepare() operation. Since the .trigger() operations may not +follow the .prepare(), a direct transition from +``SDW_STREAM_PREPARED`` to ``SDW_STREAM_DEPREPARED`` is allowed. .. code-block:: c @@ -332,6 +342,14 @@ Bus implements below API for DISABLED state which needs to be called once per stream. From ASoC DPCM framework, this stream state is linked to .trigger() stop operation. +When the INFO_PAUSE flag is supported, a direct transition to +``SDW_STREAM_ENABLED`` is allowed. + +For resume operations where ASoC will use the .prepare() callback, the +stream can transition from ``SDW_STREAM_DISABLED`` to +``SDW_STREAM_PREPARED``, with all required settings restored but +without updating the bandwidth and bit allocation. + .. code-block:: c int sdw_disable_stream(struct sdw_stream_runtime * stream); @@ -353,9 +371,18 @@ state: After all above operations are successful, stream state is set to ``SDW_STREAM_DEPREPARED``. -Bus implements below API for DEPREPARED state which needs to be called once -per stream. From ASoC DPCM framework, this stream state is linked to -.trigger() stop operation. +Bus implements below API for DEPREPARED state which needs to be called +once per stream. ALSA/ASoC do not have a concept of 'deprepare', and +the mapping from this stream state to ALSA/ASoC operation may be +implementation specific. + +When the INFO_PAUSE flag is supported, the stream state is linked to +the .hw_free() operation - the stream is not deprepared on a +TRIGGER_STOP. + +Other implementations may transition to the ``SDW_STREAM_DEPREPARED`` +state on TRIGGER_STOP, should they require a transition through the +``SDW_STREAM_PREPARED`` state. .. code-block:: c diff --git a/Documentation/index.rst b/Documentation/index.rst index 01795a40da29..9599c0f3eea8 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -134,6 +134,7 @@ needed). scsi/index misc-devices/index scheduler/index + mhi/index Architecture-agnostic documentation ----------------------------------- diff --git a/Documentation/mhi/index.rst b/Documentation/mhi/index.rst new file mode 100644 index 000000000000..1d8dec302780 --- /dev/null +++ b/Documentation/mhi/index.rst @@ -0,0 +1,18 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=== +MHI +=== + +.. toctree:: + :maxdepth: 1 + + mhi + topology + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/mhi/mhi.rst b/Documentation/mhi/mhi.rst new file mode 100644 index 000000000000..803ff84f7d7b --- /dev/null +++ b/Documentation/mhi/mhi.rst @@ -0,0 +1,218 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================== +MHI (Modem Host Interface) +========================== + +This document provides information about the MHI protocol. + +Overview +======== + +MHI is a protocol developed by Qualcomm Innovation Center, Inc. It is used +by the host processors to control and communicate with modem devices over high +speed peripheral buses or shared memory. Even though MHI can be easily adapted +to any peripheral buses, it is primarily used with PCIe based devices. MHI +provides logical channels over the physical buses and allows transporting the +modem protocols, such as IP data packets, modem control messages, and +diagnostics over at least one of those logical channels. Also, the MHI +protocol provides data acknowledgment feature and manages the power state of the +modems via one or more logical channels. + +MHI Internals +============= + +MMIO +---- + +MMIO (Memory mapped IO) consists of a set of registers in the device hardware, +which are mapped to the host memory space by the peripheral buses like PCIe. +Following are the major components of MMIO register space: + +MHI control registers: Access to MHI configurations registers + +MHI BHI registers: BHI (Boot Host Interface) registers are used by the host +for downloading the firmware to the device before MHI initialization. + +Channel Doorbell array: Channel Doorbell (DB) registers used by the host to +notify the device when there is new work to do. + +Event Doorbell array: Associated with event context array, the Event Doorbell +(DB) registers are used by the host to notify the device when new events are +available. + +Debug registers: A set of registers and counters used by the device to expose +debugging information like performance, functional, and stability to the host. + +Data structures +--------------- + +All data structures used by MHI are in the host system memory. Using the +physical interface, the device accesses those data structures. MHI data +structures and data buffers in the host system memory regions are mapped for +the device. + +Channel context array: All channel configurations are organized in channel +context data array. + +Transfer rings: Used by the host to schedule work items for a channel. The +transfer rings are organized as a circular queue of Transfer Descriptors (TD). + +Event context array: All event configurations are organized in the event context +data array. + +Event rings: Used by the device to send completion and state transition messages +to the host + +Command context array: All command configurations are organized in command +context data array. + +Command rings: Used by the host to send MHI commands to the device. The command +rings are organized as a circular queue of Command Descriptors (CD). + +Channels +-------- + +MHI channels are logical, unidirectional data pipes between a host and a device. +The concept of channels in MHI is similar to endpoints in USB. MHI supports up +to 256 channels. However, specific device implementations may support less than +the maximum number of channels allowed. + +Two unidirectional channels with their associated transfer rings form a +bidirectional data pipe, which can be used by the upper-layer protocols to +transport application data packets (such as IP packets, modem control messages, +diagnostics messages, and so on). Each channel is associated with a single +transfer ring. + +Transfer rings +-------------- + +Transfers between the host and device are organized by channels and defined by +Transfer Descriptors (TD). TDs are managed through transfer rings, which are +defined for each channel between the device and host and reside in the host +memory. TDs consist of one or more ring elements (or transfer blocks):: + + [Read Pointer (RP)] ----------->[Ring Element] } TD + [Write Pointer (WP)]- [Ring Element] + - [Ring Element] + --------->[Ring Element] + [Ring Element] + +Below is the basic usage of transfer rings: + +* Host allocates memory for transfer ring. +* Host sets the base pointer, read pointer, and write pointer in corresponding + channel context. +* Ring is considered empty when RP == WP. +* Ring is considered full when WP + 1 == RP. +* RP indicates the next element to be serviced by the device. +* When the host has a new buffer to send, it updates the ring element with + buffer information, increments the WP to the next element and rings the + associated channel DB. + +Event rings +----------- + +Events from the device to host are organized in event rings and defined by Event +Descriptors (ED). Event rings are used by the device to report events such as +data transfer completion status, command completion status, and state changes +to the host. Event rings are the array of EDs that resides in the host +memory. EDs consist of one or more ring elements (or transfer blocks):: + + [Read Pointer (RP)] ----------->[Ring Element] } ED + [Write Pointer (WP)]- [Ring Element] + - [Ring Element] + --------->[Ring Element] + [Ring Element] + +Below is the basic usage of event rings: + +* Host allocates memory for event ring. +* Host sets the base pointer, read pointer, and write pointer in corresponding + channel context. +* Both host and device has a local copy of RP, WP. +* Ring is considered empty (no events to service) when WP + 1 == RP. +* Ring is considered full of events when RP == WP. +* When there is a new event the device needs to send, the device updates ED + pointed by RP, increments the RP to the next element and triggers the + interrupt. + +Ring Element +------------ + +A Ring Element is a data structure used to transfer a single block +of data between the host and the device. Transfer ring element types contain a +single buffer pointer, the size of the buffer, and additional control +information. Other ring element types may only contain control and status +information. For single buffer operations, a ring descriptor is composed of a +single element. For large multi-buffer operations (such as scatter and gather), +elements can be chained to form a longer descriptor. + +MHI Operations +============== + +MHI States +---------- + +MHI_STATE_RESET +~~~~~~~~~~~~~~~ +MHI is in reset state after power-up or hardware reset. The host is not allowed +to access device MMIO register space. + +MHI_STATE_READY +~~~~~~~~~~~~~~~ +MHI is ready for initialization. The host can start MHI initialization by +programming MMIO registers. + +MHI_STATE_M0 +~~~~~~~~~~~~ +MHI is running and operational in the device. The host can start channels by +issuing channel start command. + +MHI_STATE_M1 +~~~~~~~~~~~~ +MHI operation is suspended by the device. This state is entered when the +device detects inactivity at the physical interface within a preset time. + +MHI_STATE_M2 +~~~~~~~~~~~~ +MHI is in low power state. MHI operation is suspended and the device may +enter lower power mode. + +MHI_STATE_M3 +~~~~~~~~~~~~ +MHI operation stopped by the host. This state is entered when the host suspends +MHI operation. + +MHI Initialization +------------------ + +After system boots, the device is enumerated over the physical interface. +In the case of PCIe, the device is enumerated and assigned BAR-0 for +the device's MMIO register space. To initialize the MHI in a device, +the host performs the following operations: + +* Allocates the MHI context for event, channel and command arrays. +* Initializes the context array, and prepares interrupts. +* Waits until the device enters READY state. +* Programs MHI MMIO registers and sets device into MHI_M0 state. +* Waits for the device to enter M0 state. + +MHI Data Transfer +----------------- + +MHI data transfer is initiated by the host to transfer data to the device. +Following are the sequence of operations performed by the host to transfer +data to device: + +* Host prepares TD with buffer information. +* Host increments the WP of the corresponding channel transfer ring. +* Host rings the channel DB register. +* Device wakes up to process the TD. +* Device generates a completion event for the processed TD by updating ED. +* Device increments the RP of the corresponding event ring. +* Device triggers IRQ to wake up the host. +* Host wakes up and checks the event ring for completion event. +* Host updates the WP of the corresponding event ring to indicate that the + data transfer has been completed successfully. + diff --git a/Documentation/mhi/topology.rst b/Documentation/mhi/topology.rst new file mode 100644 index 000000000000..dc7799d03294 --- /dev/null +++ b/Documentation/mhi/topology.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============ +MHI Topology +============ + +This document provides information about the MHI topology modeling and +representation in the kernel. + +MHI Controller +-------------- + +MHI controller driver manages the interaction with the MHI client devices +such as the external modems and WiFi chipsets. It is also the MHI bus master +which is in charge of managing the physical link between the host and device. +It is however not involved in the actual data transfer as the data transfer +is taken care by the physical bus such as PCIe. Each controller driver exposes +channels and events based on the client device type. + +Below are the roles of the MHI controller driver: + +* Turns on the physical bus and establishes the link to the device +* Configures IRQs, IOMMU, and IOMEM +* Allocates struct mhi_controller and registers with the MHI bus framework + with channel and event configurations using mhi_register_controller. +* Initiates power on and shutdown sequence +* Initiates suspend and resume power management operations of the device. + +MHI Device +---------- + +MHI device is the logical device which binds to a maximum of two MHI channels +for bi-directional communication. Once MHI is in powered on state, the MHI +core will create MHI devices based on the channel configuration exposed +by the controller. There can be a single MHI device for each channel or for a +couple of channels. + +Each supported device is enumerated in:: + + /sys/bus/mhi/devices/ + +MHI Driver +---------- + +MHI driver is the client driver which binds to one or more MHI devices. The MHI +driver sends and receives the upper-layer protocol packets like IP packets, +modem control messages, and diagnostics messages over MHI. The MHI core will +bind the MHI devices to the MHI driver. + +Each supported driver is enumerated in:: + + /sys/bus/mhi/drivers/ + +Below are the roles of the MHI driver: + +* Registers the driver with the MHI bus framework using mhi_driver_register. +* Prepares the device for transfer by calling mhi_prepare_for_transfer. +* Initiates data transfer by calling mhi_queue_transfer. +* Once the data transfer is finished, calls mhi_unprepare_from_transfer to + end data transfer. diff --git a/Documentation/process/embargoed-hardware-issues.rst b/Documentation/process/embargoed-hardware-issues.rst index a19d084f9b2c..43cdc67e4f8e 100644 --- a/Documentation/process/embargoed-hardware-issues.rst +++ b/Documentation/process/embargoed-hardware-issues.rst @@ -246,7 +246,8 @@ an involved disclosed party. The current ambassadors list: ============= ======================================================== ARM Grant Likely <grant.likely@arm.com> AMD Tom Lendacky <tom.lendacky@amd.com> - IBM + IBM Z Christian Borntraeger <borntraeger@de.ibm.com> + IBM Power Anton Blanchard <anton@linux.ibm.com> Intel Tony Luck <tony.luck@intel.com> Qualcomm Trilok Soni <tsoni@codeaurora.org> diff --git a/Documentation/trace/coresight/coresight-ect.rst b/Documentation/trace/coresight/coresight-ect.rst new file mode 100644 index 000000000000..ecc1e57012ef --- /dev/null +++ b/Documentation/trace/coresight/coresight-ect.rst @@ -0,0 +1,222 @@ +.. SPDX-License-Identifier: GPL-2.0 +============================================= +CoreSight Embedded Cross Trigger (CTI & CTM). +============================================= + + :Author: Mike Leach <mike.leach@linaro.org> + :Date: November 2019 + +Hardware Description +-------------------- + +The CoreSight Cross Trigger Interface (CTI) is a hardware device that takes +individual input and output hardware signals known as triggers to and from +devices and interconnects them via the Cross Trigger Matrix (CTM) to other +devices via numbered channels, in order to propagate events between devices. + +e.g.:: + + 0000000 in_trigs ::::::: + 0 C 0----------->: : +======>(other CTI channel IO) + 0 P 0<-----------: : v + 0 U 0 out_trigs : : Channels ***** ::::::: + 0000000 : CTI :<=========>*CTM*<====>: CTI :---+ + ####### in_trigs : : (id 0-3) ***** ::::::: v + # ETM #----------->: : ^ ####### + # #<-----------: : +---# ETR # + ####### out_trigs ::::::: ####### + +The CTI driver enables the programming of the CTI to attach triggers to +channels. When an input trigger becomes active, the attached channel will +become active. Any output trigger attached to that channel will also +become active. The active channel is propagated to other CTIs via the CTM, +activating connected output triggers there, unless filtered by the CTI +channel gate. + +It is also possible to activate a channel using system software directly +programming registers in the CTI. + +The CTIs are registered by the system to be associated with CPUs and/or other +CoreSight devices on the trace data path. When these devices are enabled the +attached CTIs will also be enabled. By default/on power up the CTIs have +no programmed trigger/channel attachments, so will not affect the system +until explicitly programmed. + +The hardware trigger connections between CTIs and devices is implementation +defined, unless the CPU/ETM combination is a v8 architecture, in which case +the connections have an architecturally defined standard layout. + +The hardware trigger signals can also be connected to non-CoreSight devices +(e.g. UART), or be propagated off chip as hardware IO lines. + +All the CTI devices are associated with a CTM. On many systems there will be a +single effective CTM (one CTM, or multiple CTMs all interconnected), but it is +possible that systems can have nets of CTIs+CTM that are not interconnected by +a CTM to each other. On these systems a CTM index is declared to associate +CTI devices that are interconnected via a given CTM. + +Sysfs files and directories +--------------------------- + +The CTI devices appear on the existing CoreSight bus alongside the other +CoreSight devices:: + + >$ ls /sys/bus/coresight/devices + cti_cpu0 cti_cpu2 cti_sys0 etm0 etm2 funnel0 replicator0 tmc_etr0 + cti_cpu1 cti_cpu3 cti_sys1 etm1 etm3 funnel1 tmc_etf0 tpiu0 + +The ``cti_cpu<N>`` named CTIs are associated with a CPU, and any ETM used by +that core. The ``cti_sys<N>`` CTIs are general system infrastructure CTIs that +can be associated with other CoreSight devices, or other system hardware +capable of generating or using trigger signals.:: + + >$ ls /sys/bus/coresight/devices/etm0/cti_cpu0 + channels ctmid enable nr_trigger_cons mgmt power powered regs + subsystem triggers0 triggers1 uevent + +*Key file items are:-* + * ``enable``: enables/disables the CTI. Read to determine current state. + If this shows as enabled (1), but ``powered`` shows unpowered (0), then + the enable indicates a request to enabled when the device is powered. + * ``ctmid`` : associated CTM - only relevant if system has multiple CTI+CTM + clusters that are not interconnected. + * ``nr_trigger_cons`` : total connections - triggers<N> directories. + * ``powered`` : Read to determine if the CTI is currently powered. + +*Sub-directories:-* + * ``triggers<N>``: contains list of triggers for an individual connection. + * ``channels``: Contains the channel API - CTI main programming interface. + * ``regs``: Gives access to the raw programmable CTI regs. + * ``mgmt``: the standard CoreSight management registers. + + +triggers<N> directories +~~~~~~~~~~~~~~~~~~~~~~~ + +Individual trigger connection information. This describes trigger signals for +CoreSight and non-CoreSight connections. + +Each triggers directory has a set of parameters describing the triggers for +the connection. + + * ``name`` : name of connection + * ``in_signals`` : input trigger signal indexes used in this connection. + * ``in_types`` : functional types for in signals. + * ``out_signals`` : output trigger signals for this connection. + * ``out_types`` : functional types for out signals. + +e.g:: + + >$ ls ./cti_cpu0/triggers0/ + in_signals in_types name out_signals out_types + >$ cat ./cti_cpu0/triggers0/name + cpu0 + >$ cat ./cti_cpu0/triggers0/out_signals + 0-2 + >$ cat ./cti_cpu0/triggers0/out_types + pe_edbgreq pe_dbgrestart pe_ctiirq + >$ cat ./cti_cpu0/triggers0/in_signals + 0-1 + >$ cat ./cti_cpu0/triggers0/in_types + pe_dbgtrigger pe_pmuirq + +If a connection has zero signals in either the 'in' or 'out' triggers then +those parameters will be omitted. + +Channels API Directory +~~~~~~~~~~~~~~~~~~~~~~ + +This provides an easy way to attach triggers to channels, without needing +the multiple register operations that are required if manipulating the +'regs' sub-directory elements directly. + +A number of files provide this API:: + + >$ ls ./cti_sys0/channels/ + chan_clear chan_inuse chan_xtrigs_out trigin_attach + chan_free chan_pulse chan_xtrigs_reset trigin_detach + chan_gate_disable chan_set chan_xtrigs_sel trigout_attach + chan_gate_enable chan_xtrigs_in trig_filter_enable trigout_detach + trigout_filtered + +Most access to these elements take the form:: + + echo <chan> [<trigger>] > /<device_path>/<operation> + +where the optional <trigger> is only needed for trigXX_attach | detach +operations. + +e.g.:: + + >$ echo 0 1 > ./cti_sys0/channels/trigout_attach + >$ echo 0 > ./cti_sys0/channels/chan_set + +Attaches trigout(1) to channel(0), then activates channel(0) generating a +set state on cti_sys0.trigout(1) + + +*API operations* + + * ``trigin_attach, trigout_attach``: Attach a channel to a trigger signal. + * ``trigin_detach, trigout_detach``: Detach a channel from a trigger signal. + * ``chan_set``: Set the channel - the set state will be propagated around + the CTM to other connected devices. + * ``chan_clear``: Clear the channel. + * ``chan_pulse``: Set the channel for a single CoreSight clock cycle. + * ``chan_gate_enable``: Write operation sets the CTI gate to propagate + (enable) the channel to other devices. This operation takes a channel + number. CTI gate is enabled for all channels by default at power up. Read + to list the currently enabled channels on the gate. + * ``chan_gate_disable``: Write channel number to disable gate for that + channel. + * ``chan_inuse``: Show the current channels attached to any signal + * ``chan_free``: Show channels with no attached signals. + * ``chan_xtrigs_sel``: write a channel number to select a channel to view, + read to show the selected channel number. + * ``chan_xtrigs_in``: Read to show the input triggers attached to + the selected view channel. + * ``chan_xtrigs_out``:Read to show the output triggers attached to + the selected view channel. + * ``trig_filter_enable``: Defaults to enabled, disable to allow potentially + dangerous output signals to be set. + * ``trigout_filtered``: Trigger out signals that are prevented from being + set if filtering ``trig_filter_enable`` is enabled. One use is to prevent + accidental ``EDBGREQ`` signals stopping a core. + * ``chan_xtrigs_reset``: Write 1 to clear all channel / trigger programming. + Resets device hardware to default state. + + +The example below attaches input trigger index 1 to channel 2, and output +trigger index 6 to the same channel. It then examines the state of the +channel / trigger connections using the appropriate sysfs attributes. + +The settings mean that if either input trigger 1, or channel 2 go active then +trigger out 6 will go active. We then enable the CTI, and use the software +channel control to activate channel 2. We see the active channel on the +``choutstatus`` register and the active signal on the ``trigoutstatus`` +register. Finally clearing the channel removes this. + +e.g.:: + + .../cti_sys0/channels# echo 2 1 > trigin_attach + .../cti_sys0/channels# echo 2 6 > trigout_attach + .../cti_sys0/channels# cat chan_free + 0-1,3 + .../cti_sys0/channels# cat chan_inuse + 2 + .../cti_sys0/channels# echo 2 > chan_xtrigs_sel + .../cti_sys0/channels# cat chan_xtrigs_trigin + 1 + .../cti_sys0/channels# cat chan_xtrigs_trigout + 6 + .../cti_sys0/# echo 1 > enable + .../cti_sys0/channels# echo 2 > chan_set + .../cti_sys0/channels# cat ../regs/choutstatus + 0x4 + .../cti_sys0/channels# cat ../regs/trigoutstatus + 0x40 + .../cti_sys0/channels# echo 2 > chan_clear + .../cti_sys0/channels# cat ../regs/trigoutstatus + 0x0 + .../cti_sys0/channels# cat ../regs/choutstatus + 0x0 diff --git a/Documentation/trace/coresight/coresight.rst b/Documentation/trace/coresight/coresight.rst index a566719f8e7e..108600ee1e12 100644 --- a/Documentation/trace/coresight/coresight.rst +++ b/Documentation/trace/coresight/coresight.rst @@ -491,8 +491,21 @@ interface provided for that purpose by the generic STM API:: Details on how to use the generic STM API can be found here:- :doc:`../stm` [#second]_. +The CTI & CTM Modules +--------------------- + +The CTI (Cross Trigger Interface) provides a set of trigger signals between +individual CTIs and components, and can propagate these between all CTIs via +channels on the CTM (Cross Trigger Matrix). + +A separate documentation file is provided to explain the use of these devices. +(:doc:`coresight-ect`) [#fourth]_. + + .. [#first] Documentation/ABI/testing/sysfs-bus-coresight-devices-stm .. [#second] Documentation/trace/stm.rst .. [#third] https://github.com/Linaro/perf-opencsd + +.. [#fourth] Documentation/trace/coresight/coresight-ect.rst diff --git a/MAINTAINERS b/MAINTAINERS index eeff55560759..901a6f806887 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1693,12 +1693,15 @@ F: arch/arm/mach-ep93xx/micro9.c ARM/CORESIGHT FRAMEWORK AND DRIVERS M: Mathieu Poirier <mathieu.poirier@linaro.org> R: Suzuki K Poulose <suzuki.poulose@arm.com> +R: Mike Leach <mike.leach@linaro.org> L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: drivers/hwtracing/coresight/* +F: include/dt-bindings/arm/coresight-cti-dt.h F: Documentation/trace/coresight/* F: Documentation/devicetree/bindings/arm/coresight.txt F: Documentation/devicetree/bindings/arm/coresight-cpu-debug.txt +F: Documentation/devicetree/bindings/arm/coresight-cti.yaml F: Documentation/ABI/testing/sysfs-bus-coresight-devices-* F: tools/perf/arch/arm/util/pmu.c F: tools/perf/arch/arm/util/auxtrace.c @@ -10993,6 +10996,16 @@ M: Vladimir Vid <vladimir.vid@sartura.hr> S: Maintained F: arch/arm64/boot/dts/marvell/armada-3720-uDPU.dts +MHI BUS +M: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> +M: Hemant Kumar <hemantk@codeaurora.org> +L: linux-arm-msm@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi.git +F: Documentation/mhi/ +F: drivers/bus/mhi/ +F: include/linux/mhi.h + MICROBLAZE ARCHITECTURE M: Michal Simek <monstr@monstr.eu> W: http://www.monstr.eu/fdt/ diff --git a/arch/alpha/configs/defconfig b/arch/alpha/configs/defconfig index 3a132c91d45b..6293675db164 100644 --- a/arch/alpha/configs/defconfig +++ b/arch/alpha/configs/defconfig @@ -52,7 +52,8 @@ CONFIG_NET_PCI=y CONFIG_YELLOWFIN=y CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_RTC=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_CMOS=y CONFIG_EXT2_FS=y CONFIG_REISERFS_FS=m CONFIG_ISO9660_FS=y diff --git a/arch/arm/include/asm/nwflash.h b/arch/arm/include/asm/nwflash.h index 0ec6f07c2d8a..66b7e68c9b58 100644 --- a/arch/arm/include/asm/nwflash.h +++ b/arch/arm/include/asm/nwflash.h @@ -2,7 +2,6 @@ #ifndef _FLASH_H #define _FLASH_H -#define FLASH_MINOR 160 /* MAJOR is 10 - miscdevice */ #define CMD_WRITE_DISABLE 0 #define CMD_WRITE_ENABLE 0x28 #define CMD_WRITE_BASE64K_ENABLE 0x47 diff --git a/arch/ia64/configs/bigsur_defconfig b/arch/ia64/configs/bigsur_defconfig index b630bd7351c4..f3ba813a5b80 100644 --- a/arch/ia64/configs/bigsur_defconfig +++ b/arch/ia64/configs/bigsur_defconfig @@ -57,7 +57,8 @@ CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_SHARE_IRQ=y # CONFIG_HW_RANDOM is not set -CONFIG_EFI_RTC=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y CONFIG_I2C=y CONFIG_I2C_CHARDEV=y CONFIG_AGP=m diff --git a/arch/ia64/configs/generic_defconfig b/arch/ia64/configs/generic_defconfig index 661d90b3e148..cb267a07c57f 100644 --- a/arch/ia64/configs/generic_defconfig +++ b/arch/ia64/configs/generic_defconfig @@ -94,7 +94,8 @@ CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_SHARE_IRQ=y # CONFIG_HW_RANDOM is not set -CONFIG_EFI_RTC=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y CONFIG_RAW_DRIVER=m CONFIG_HPET=y CONFIG_AGP=m diff --git a/arch/ia64/configs/gensparse_defconfig b/arch/ia64/configs/gensparse_defconfig index 7844e6a956a4..7e25f2f031b6 100644 --- a/arch/ia64/configs/gensparse_defconfig +++ b/arch/ia64/configs/gensparse_defconfig @@ -82,7 +82,8 @@ CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_SHARE_IRQ=y # CONFIG_HW_RANDOM is not set -CONFIG_EFI_RTC=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y CONFIG_RAW_DRIVER=m CONFIG_HPET=y CONFIG_AGP=m diff --git a/arch/ia64/configs/tiger_defconfig b/arch/ia64/configs/tiger_defconfig index 1d6e2a01452b..3f486d5bdc2d 100644 --- a/arch/ia64/configs/tiger_defconfig +++ b/arch/ia64/configs/tiger_defconfig @@ -86,7 +86,8 @@ CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_SHARE_IRQ=y # CONFIG_HW_RANDOM is not set -CONFIG_EFI_RTC=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y CONFIG_RAW_DRIVER=m CONFIG_HPET=y CONFIG_AGP=m diff --git a/arch/ia64/configs/zx1_defconfig b/arch/ia64/configs/zx1_defconfig index d42f79a33e91..261e98e1f5fe 100644 --- a/arch/ia64/configs/zx1_defconfig +++ b/arch/ia64/configs/zx1_defconfig @@ -68,7 +68,8 @@ CONFIG_SERIAL_8250_NR_UARTS=8 CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_SHARE_IRQ=y # CONFIG_HW_RANDOM is not set -CONFIG_EFI_RTC=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y CONFIG_I2C_CHARDEV=y CONFIG_AGP=y CONFIG_AGP_HP_ZX1=y diff --git a/arch/um/drivers/random.c b/arch/um/drivers/random.c index 1d5d3057e6f1..ce115fce52f0 100644 --- a/arch/um/drivers/random.c +++ b/arch/um/drivers/random.c @@ -23,8 +23,6 @@ #define RNG_VERSION "1.0.0" #define RNG_MODULE_NAME "hw_random" -#define RNG_MISCDEV_MINOR 183 /* official */ - /* Changed at init time, in the non-modular case, and at module load * time, in the module case. Presumably, the module subsystem * protects against a module being loaded twice at the same time. @@ -104,7 +102,7 @@ static const struct file_operations rng_chrdev_ops = { /* rng_init shouldn't be called more than once at boot time */ static struct miscdevice rng_miscdev = { - RNG_MISCDEV_MINOR, + HWRNG_MINOR, RNG_MODULE_NAME, &rng_chrdev_ops, }; diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index f303106b3362..9ecad74183a3 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -18,7 +18,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/mount.h> -#include <linux/parser.h> +#include <linux/fs_parser.h> #include <linux/radix-tree.h> #include <linux/sched.h> #include <linux/seq_file.h> @@ -48,26 +48,30 @@ static dev_t binderfs_dev; static DEFINE_MUTEX(binderfs_minors_mutex); static DEFINE_IDA(binderfs_minors); -enum { +enum binderfs_param { Opt_max, Opt_stats_mode, - Opt_err }; enum binderfs_stats_mode { - STATS_NONE, - STATS_GLOBAL, + binderfs_stats_mode_unset, + binderfs_stats_mode_global, }; -static const match_table_t tokens = { - { Opt_max, "max=%d" }, - { Opt_stats_mode, "stats=%s" }, - { Opt_err, NULL } +static const struct constant_table binderfs_param_stats[] = { + { "global", binderfs_stats_mode_global }, + {} }; -static inline struct binderfs_info *BINDERFS_I(const struct inode *inode) +const struct fs_parameter_spec binderfs_fs_parameters[] = { + fsparam_u32("max", Opt_max), + fsparam_enum("stats", Opt_stats_mode, binderfs_param_stats), + {} +}; + +static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) { - return inode->i_sb->s_fs_info; + return sb->s_fs_info; } bool is_binderfs_device(const struct inode *inode) @@ -246,7 +250,7 @@ static long binder_ctl_ioctl(struct file *file, unsigned int cmd, static void binderfs_evict_inode(struct inode *inode) { struct binder_device *device = inode->i_private; - struct binderfs_info *info = BINDERFS_I(inode); + struct binderfs_info *info = BINDERFS_SB(inode->i_sb); clear_inode(inode); @@ -264,97 +268,84 @@ static void binderfs_evict_inode(struct inode *inode) } } -/** - * binderfs_parse_mount_opts - parse binderfs mount options - * @data: options to set (can be NULL in which case defaults are used) - */ -static int binderfs_parse_mount_opts(char *data, - struct binderfs_mount_opts *opts) +static int binderfs_fs_context_parse_param(struct fs_context *fc, + struct fs_parameter *param) { - char *p, *stats; - opts->max = BINDERFS_MAX_MINOR; - opts->stats_mode = STATS_NONE; - - while ((p = strsep(&data, ",")) != NULL) { - substring_t args[MAX_OPT_ARGS]; - int token; - int max_devices; - - if (!*p) - continue; - - token = match_token(p, tokens, args); - switch (token) { - case Opt_max: - if (match_int(&args[0], &max_devices) || - (max_devices < 0 || - (max_devices > BINDERFS_MAX_MINOR))) - return -EINVAL; - - opts->max = max_devices; - break; - case Opt_stats_mode: - if (!capable(CAP_SYS_ADMIN)) - return -EINVAL; + int opt; + struct binderfs_mount_opts *ctx = fc->fs_private; + struct fs_parse_result result; - stats = match_strdup(&args[0]); - if (!stats) - return -ENOMEM; + opt = fs_parse(fc, binderfs_fs_parameters, param, &result); + if (opt < 0) + return opt; - if (strcmp(stats, "global") != 0) { - kfree(stats); - return -EINVAL; - } + switch (opt) { + case Opt_max: + if (result.uint_32 > BINDERFS_MAX_MINOR) + return invalfc(fc, "Bad value for '%s'", param->key); - opts->stats_mode = STATS_GLOBAL; - kfree(stats); - break; - default: - pr_err("Invalid mount options\n"); - return -EINVAL; - } + ctx->max = result.uint_32; + break; + case Opt_stats_mode: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + ctx->stats_mode = result.uint_32; + break; + default: + return invalfc(fc, "Unsupported parameter '%s'", param->key); } return 0; } -static int binderfs_remount(struct super_block *sb, int *flags, char *data) +static int binderfs_fs_context_reconfigure(struct fs_context *fc) { - int prev_stats_mode, ret; - struct binderfs_info *info = sb->s_fs_info; + struct binderfs_mount_opts *ctx = fc->fs_private; + struct binderfs_info *info = BINDERFS_SB(fc->root->d_sb); - prev_stats_mode = info->mount_opts.stats_mode; - ret = binderfs_parse_mount_opts(data, &info->mount_opts); - if (ret) - return ret; - - if (prev_stats_mode != info->mount_opts.stats_mode) { - pr_err("Binderfs stats mode cannot be changed during a remount\n"); - info->mount_opts.stats_mode = prev_stats_mode; - return -EINVAL; - } + if (info->mount_opts.stats_mode != ctx->stats_mode) + return invalfc(fc, "Binderfs stats mode cannot be changed during a remount"); + info->mount_opts.stats_mode = ctx->stats_mode; + info->mount_opts.max = ctx->max; return 0; } -static int binderfs_show_mount_opts(struct seq_file *seq, struct dentry *root) +static int binderfs_show_options(struct seq_file *seq, struct dentry *root) { - struct binderfs_info *info; + struct binderfs_info *info = BINDERFS_SB(root->d_sb); - info = root->d_sb->s_fs_info; if (info->mount_opts.max <= BINDERFS_MAX_MINOR) seq_printf(seq, ",max=%d", info->mount_opts.max); - if (info->mount_opts.stats_mode == STATS_GLOBAL) + + switch (info->mount_opts.stats_mode) { + case binderfs_stats_mode_unset: + break; + case binderfs_stats_mode_global: seq_printf(seq, ",stats=global"); + break; + } return 0; } +static void binderfs_put_super(struct super_block *sb) +{ + struct binderfs_info *info = sb->s_fs_info; + + if (info && info->ipc_ns) + put_ipc_ns(info->ipc_ns); + + kfree(info); + sb->s_fs_info = NULL; +} + static const struct super_operations binderfs_super_ops = { .evict_inode = binderfs_evict_inode, - .remount_fs = binderfs_remount, - .show_options = binderfs_show_mount_opts, + .show_options = binderfs_show_options, .statfs = simple_statfs, + .put_super = binderfs_put_super, }; static inline bool is_binderfs_control_device(const struct dentry *dentry) @@ -653,10 +644,11 @@ out: return ret; } -static int binderfs_fill_super(struct super_block *sb, void *data, int silent) +static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc) { int ret; struct binderfs_info *info; + struct binderfs_mount_opts *ctx = fc->fs_private; struct inode *inode = NULL; struct binderfs_device device_info = { 0 }; const char *name; @@ -689,16 +681,14 @@ static int binderfs_fill_super(struct super_block *sb, void *data, int silent) info->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns); - ret = binderfs_parse_mount_opts(data, &info->mount_opts); - if (ret) - return ret; - info->root_gid = make_kgid(sb->s_user_ns, 0); if (!gid_valid(info->root_gid)) info->root_gid = GLOBAL_ROOT_GID; info->root_uid = make_kuid(sb->s_user_ns, 0); if (!uid_valid(info->root_uid)) info->root_uid = GLOBAL_ROOT_UID; + info->mount_opts.max = ctx->max; + info->mount_opts.stats_mode = ctx->stats_mode; inode = new_inode(sb); if (!inode) @@ -730,36 +720,54 @@ static int binderfs_fill_super(struct super_block *sb, void *data, int silent) name++; } - if (info->mount_opts.stats_mode == STATS_GLOBAL) + if (info->mount_opts.stats_mode == binderfs_stats_mode_global) return init_binder_logs(sb); return 0; } -static struct dentry *binderfs_mount(struct file_system_type *fs_type, - int flags, const char *dev_name, - void *data) +static int binderfs_fs_context_get_tree(struct fs_context *fc) { - return mount_nodev(fs_type, flags, data, binderfs_fill_super); + return get_tree_nodev(fc, binderfs_fill_super); } -static void binderfs_kill_super(struct super_block *sb) +static void binderfs_fs_context_free(struct fs_context *fc) { - struct binderfs_info *info = sb->s_fs_info; + struct binderfs_mount_opts *ctx = fc->fs_private; - kill_litter_super(sb); + kfree(ctx); +} - if (info && info->ipc_ns) - put_ipc_ns(info->ipc_ns); +static const struct fs_context_operations binderfs_fs_context_ops = { + .free = binderfs_fs_context_free, + .get_tree = binderfs_fs_context_get_tree, + .parse_param = binderfs_fs_context_parse_param, + .reconfigure = binderfs_fs_context_reconfigure, +}; - kfree(info); +static int binderfs_init_fs_context(struct fs_context *fc) +{ + struct binderfs_mount_opts *ctx = fc->fs_private; + + ctx = kzalloc(sizeof(struct binderfs_mount_opts), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->max = BINDERFS_MAX_MINOR; + ctx->stats_mode = binderfs_stats_mode_unset; + + fc->fs_private = ctx; + fc->ops = &binderfs_fs_context_ops; + + return 0; } static struct file_system_type binder_fs_type = { - .name = "binder", - .mount = binderfs_mount, - .kill_sb = binderfs_kill_super, - .fs_flags = FS_USERNS_MOUNT, + .name = "binder", + .init_fs_context = binderfs_init_fs_context, + .parameters = binderfs_fs_parameters, + .kill_sb = kill_litter_super, + .fs_flags = FS_USERNS_MOUNT, }; int __init init_binderfs(void) diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c index c0da3820454b..d58278ae9e4a 100644 --- a/drivers/auxdisplay/charlcd.c +++ b/drivers/auxdisplay/charlcd.c @@ -22,8 +22,6 @@ #include "charlcd.h" -#define LCD_MINOR 156 - #define DEFAULT_LCD_BWIDTH 40 #define DEFAULT_LCD_HWIDTH 64 diff --git a/drivers/auxdisplay/panel.c b/drivers/auxdisplay/panel.c index 85965953683e..99980aa3644b 100644 --- a/drivers/auxdisplay/panel.c +++ b/drivers/auxdisplay/panel.c @@ -57,8 +57,6 @@ #include "charlcd.h" -#define KEYPAD_MINOR 185 - #define LCD_MAXBYTES 256 /* max burst write */ #define KEYPAD_BUFFER 64 diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 6095b6df8a81..6d4e4497b59b 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -201,5 +201,6 @@ config DA8XX_MSTPRI peripherals. source "drivers/bus/fsl-mc/Kconfig" +source "drivers/bus/mhi/Kconfig" endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 1320bcf9fa9d..05f32cd694a4 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -34,3 +34,6 @@ obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o obj-$(CONFIG_DA8XX_MSTPRI) += da8xx-mstpri.o + +# MHI +obj-$(CONFIG_MHI_BUS) += mhi/ diff --git a/drivers/bus/mhi/Kconfig b/drivers/bus/mhi/Kconfig new file mode 100644 index 000000000000..a8bd9bd7db7c --- /dev/null +++ b/drivers/bus/mhi/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# MHI bus +# +# Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. +# + +config MHI_BUS + tristate "Modem Host Interface (MHI) bus" + help + Bus driver for MHI protocol. Modem Host Interface (MHI) is a + communication protocol used by the host processors to control + and communicate with modem devices over a high speed peripheral + bus or shared memory. diff --git a/drivers/bus/mhi/Makefile b/drivers/bus/mhi/Makefile new file mode 100644 index 000000000000..19e6443b72df --- /dev/null +++ b/drivers/bus/mhi/Makefile @@ -0,0 +1,2 @@ +# core layer +obj-y += core/ diff --git a/drivers/bus/mhi/core/Makefile b/drivers/bus/mhi/core/Makefile new file mode 100644 index 000000000000..66e2700c9032 --- /dev/null +++ b/drivers/bus/mhi/core/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MHI_BUS) := mhi.o + +mhi-y := init.o main.o pm.o boot.o diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c new file mode 100644 index 000000000000..ebad5eb48e5a --- /dev/null +++ b/drivers/bus/mhi/core/boot.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mhi.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include "internal.h" + +/* Setup RDDM vector table for RDDM transfer and program RXVEC */ +void mhi_rddm_prepare(struct mhi_controller *mhi_cntrl, + struct image_info *img_info) +{ + struct mhi_buf *mhi_buf = img_info->mhi_buf; + struct bhi_vec_entry *bhi_vec = img_info->bhi_vec; + void __iomem *base = mhi_cntrl->bhie; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + u32 sequence_id; + unsigned int i; + + for (i = 0; i < img_info->entries - 1; i++, mhi_buf++, bhi_vec++) { + bhi_vec->dma_addr = mhi_buf->dma_addr; + bhi_vec->size = mhi_buf->len; + } + + dev_dbg(dev, "BHIe programming for RDDM\n"); + + mhi_write_reg(mhi_cntrl, base, BHIE_RXVECADDR_HIGH_OFFS, + upper_32_bits(mhi_buf->dma_addr)); + + mhi_write_reg(mhi_cntrl, base, BHIE_RXVECADDR_LOW_OFFS, + lower_32_bits(mhi_buf->dma_addr)); + + mhi_write_reg(mhi_cntrl, base, BHIE_RXVECSIZE_OFFS, mhi_buf->len); + sequence_id = prandom_u32() & BHIE_RXVECSTATUS_SEQNUM_BMSK; + + if (unlikely(!sequence_id)) + sequence_id = 1; + + mhi_write_reg_field(mhi_cntrl, base, BHIE_RXVECDB_OFFS, + BHIE_RXVECDB_SEQNUM_BMSK, BHIE_RXVECDB_SEQNUM_SHFT, + sequence_id); + + dev_dbg(dev, "Address: %p and len: 0x%zx sequence: %u\n", + &mhi_buf->dma_addr, mhi_buf->len, sequence_id); +} + +/* Collect RDDM buffer during kernel panic */ +static int __mhi_download_rddm_in_panic(struct mhi_controller *mhi_cntrl) +{ + int ret; + u32 rx_status; + enum mhi_ee_type ee; + const u32 delayus = 2000; + u32 retry = (mhi_cntrl->timeout_ms * 1000) / delayus; + const u32 rddm_timeout_us = 200000; + int rddm_retry = rddm_timeout_us / delayus; + void __iomem *base = mhi_cntrl->bhie; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + dev_dbg(dev, "Entered with pm_state:%s dev_state:%s ee:%s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state), + TO_MHI_STATE_STR(mhi_cntrl->dev_state), + TO_MHI_EXEC_STR(mhi_cntrl->ee)); + + /* + * This should only be executing during a kernel panic, we expect all + * other cores to shutdown while we're collecting RDDM buffer. After + * returning from this function, we expect the device to reset. + * + * Normaly, we read/write pm_state only after grabbing the + * pm_lock, since we're in a panic, skipping it. Also there is no + * gurantee that this state change would take effect since + * we're setting it w/o grabbing pm_lock + */ + mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT; + /* update should take the effect immediately */ + smp_wmb(); + + /* + * Make sure device is not already in RDDM. In case the device asserts + * and a kernel panic follows, device will already be in RDDM. + * Do not trigger SYS ERR again and proceed with waiting for + * image download completion. + */ + ee = mhi_get_exec_env(mhi_cntrl); + if (ee != MHI_EE_RDDM) { + dev_dbg(dev, "Trigger device into RDDM mode using SYS ERR\n"); + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_SYS_ERR); + + dev_dbg(dev, "Waiting for device to enter RDDM\n"); + while (rddm_retry--) { + ee = mhi_get_exec_env(mhi_cntrl); + if (ee == MHI_EE_RDDM) + break; + + udelay(delayus); + } + + if (rddm_retry <= 0) { + /* Hardware reset so force device to enter RDDM */ + dev_dbg(dev, + "Did not enter RDDM, do a host req reset\n"); + mhi_write_reg(mhi_cntrl, mhi_cntrl->regs, + MHI_SOC_RESET_REQ_OFFSET, + MHI_SOC_RESET_REQ); + udelay(delayus); + } + + ee = mhi_get_exec_env(mhi_cntrl); + } + + dev_dbg(dev, "Waiting for image download completion, current EE: %s\n", + TO_MHI_EXEC_STR(ee)); + + while (retry--) { + ret = mhi_read_reg_field(mhi_cntrl, base, BHIE_RXVECSTATUS_OFFS, + BHIE_RXVECSTATUS_STATUS_BMSK, + BHIE_RXVECSTATUS_STATUS_SHFT, + &rx_status); + if (ret) + return -EIO; + + if (rx_status == BHIE_RXVECSTATUS_STATUS_XFER_COMPL) + return 0; + + udelay(delayus); + } + + ee = mhi_get_exec_env(mhi_cntrl); + ret = mhi_read_reg(mhi_cntrl, base, BHIE_RXVECSTATUS_OFFS, &rx_status); + + dev_err(dev, "Did not complete RDDM transfer\n"); + dev_err(dev, "Current EE: %s\n", TO_MHI_EXEC_STR(ee)); + dev_err(dev, "RXVEC_STATUS: 0x%x\n", rx_status); + + return -EIO; +} + +/* Download RDDM image from device */ +int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic) +{ + void __iomem *base = mhi_cntrl->bhie; + u32 rx_status; + + if (in_panic) + return __mhi_download_rddm_in_panic(mhi_cntrl); + + /* Wait for the image download to complete */ + wait_event_timeout(mhi_cntrl->state_event, + mhi_read_reg_field(mhi_cntrl, base, + BHIE_RXVECSTATUS_OFFS, + BHIE_RXVECSTATUS_STATUS_BMSK, + BHIE_RXVECSTATUS_STATUS_SHFT, + &rx_status) || rx_status, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + return (rx_status == BHIE_RXVECSTATUS_STATUS_XFER_COMPL) ? 0 : -EIO; +} +EXPORT_SYMBOL_GPL(mhi_download_rddm_img); + +static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl, + const struct mhi_buf *mhi_buf) +{ + void __iomem *base = mhi_cntrl->bhie; + rwlock_t *pm_lock = &mhi_cntrl->pm_lock; + u32 tx_status, sequence_id; + + read_lock_bh(pm_lock); + if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) { + read_unlock_bh(pm_lock); + return -EIO; + } + + mhi_write_reg(mhi_cntrl, base, BHIE_TXVECADDR_HIGH_OFFS, + upper_32_bits(mhi_buf->dma_addr)); + + mhi_write_reg(mhi_cntrl, base, BHIE_TXVECADDR_LOW_OFFS, + lower_32_bits(mhi_buf->dma_addr)); + + mhi_write_reg(mhi_cntrl, base, BHIE_TXVECSIZE_OFFS, mhi_buf->len); + + sequence_id = prandom_u32() & BHIE_TXVECSTATUS_SEQNUM_BMSK; + mhi_write_reg_field(mhi_cntrl, base, BHIE_TXVECDB_OFFS, + BHIE_TXVECDB_SEQNUM_BMSK, BHIE_TXVECDB_SEQNUM_SHFT, + sequence_id); + read_unlock_bh(pm_lock); + + /* Wait for the image download to complete */ + wait_event_timeout(mhi_cntrl->state_event, + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state) || + mhi_read_reg_field(mhi_cntrl, base, + BHIE_TXVECSTATUS_OFFS, + BHIE_TXVECSTATUS_STATUS_BMSK, + BHIE_TXVECSTATUS_STATUS_SHFT, + &tx_status) || tx_status, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) + return -EIO; + + return (tx_status == BHIE_TXVECSTATUS_STATUS_XFER_COMPL) ? 0 : -EIO; +} + +static int mhi_fw_load_sbl(struct mhi_controller *mhi_cntrl, + dma_addr_t dma_addr, + size_t size) +{ + u32 tx_status, val, session_id; + int i, ret; + void __iomem *base = mhi_cntrl->bhi; + rwlock_t *pm_lock = &mhi_cntrl->pm_lock; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + struct { + char *name; + u32 offset; + } error_reg[] = { + { "ERROR_CODE", BHI_ERRCODE }, + { "ERROR_DBG1", BHI_ERRDBG1 }, + { "ERROR_DBG2", BHI_ERRDBG2 }, + { "ERROR_DBG3", BHI_ERRDBG3 }, + { NULL }, + }; + + read_lock_bh(pm_lock); + if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) { + read_unlock_bh(pm_lock); + goto invalid_pm_state; + } + + dev_dbg(dev, "Starting SBL download via BHI\n"); + mhi_write_reg(mhi_cntrl, base, BHI_STATUS, 0); + mhi_write_reg(mhi_cntrl, base, BHI_IMGADDR_HIGH, + upper_32_bits(dma_addr)); + mhi_write_reg(mhi_cntrl, base, BHI_IMGADDR_LOW, + lower_32_bits(dma_addr)); + mhi_write_reg(mhi_cntrl, base, BHI_IMGSIZE, size); + session_id = prandom_u32() & BHI_TXDB_SEQNUM_BMSK; + mhi_write_reg(mhi_cntrl, base, BHI_IMGTXDB, session_id); + read_unlock_bh(pm_lock); + + /* Wait for the image download to complete */ + ret = wait_event_timeout(mhi_cntrl->state_event, + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state) || + mhi_read_reg_field(mhi_cntrl, base, BHI_STATUS, + BHI_STATUS_MASK, BHI_STATUS_SHIFT, + &tx_status) || tx_status, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) + goto invalid_pm_state; + + if (tx_status == BHI_STATUS_ERROR) { + dev_err(dev, "Image transfer failed\n"); + read_lock_bh(pm_lock); + if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) { + for (i = 0; error_reg[i].name; i++) { + ret = mhi_read_reg(mhi_cntrl, base, + error_reg[i].offset, &val); + if (ret) + break; + dev_err(dev, "Reg: %s value: 0x%x\n", + error_reg[i].name, val); + } + } + read_unlock_bh(pm_lock); + goto invalid_pm_state; + } + + return (!ret) ? -ETIMEDOUT : 0; + +invalid_pm_state: + + return -EIO; +} + +void mhi_free_bhie_table(struct mhi_controller *mhi_cntrl, + struct image_info *image_info) +{ + int i; + struct mhi_buf *mhi_buf = image_info->mhi_buf; + + for (i = 0; i < image_info->entries; i++, mhi_buf++) + mhi_free_coherent(mhi_cntrl, mhi_buf->len, mhi_buf->buf, + mhi_buf->dma_addr); + + kfree(image_info->mhi_buf); + kfree(image_info); +} + +int mhi_alloc_bhie_table(struct mhi_controller *mhi_cntrl, + struct image_info **image_info, + size_t alloc_size) +{ + size_t seg_size = mhi_cntrl->seg_len; + int segments = DIV_ROUND_UP(alloc_size, seg_size) + 1; + int i; + struct image_info *img_info; + struct mhi_buf *mhi_buf; + + img_info = kzalloc(sizeof(*img_info), GFP_KERNEL); + if (!img_info) + return -ENOMEM; + + /* Allocate memory for entries */ + img_info->mhi_buf = kcalloc(segments, sizeof(*img_info->mhi_buf), + GFP_KERNEL); + if (!img_info->mhi_buf) + goto error_alloc_mhi_buf; + + /* Allocate and populate vector table */ + mhi_buf = img_info->mhi_buf; + for (i = 0; i < segments; i++, mhi_buf++) { + size_t vec_size = seg_size; + + /* Vector table is the last entry */ + if (i == segments - 1) + vec_size = sizeof(struct bhi_vec_entry) * i; + + mhi_buf->len = vec_size; + mhi_buf->buf = mhi_alloc_coherent(mhi_cntrl, vec_size, + &mhi_buf->dma_addr, + GFP_KERNEL); + if (!mhi_buf->buf) + goto error_alloc_segment; + } + + img_info->bhi_vec = img_info->mhi_buf[segments - 1].buf; + img_info->entries = segments; + *image_info = img_info; + + return 0; + +error_alloc_segment: + for (--i, --mhi_buf; i >= 0; i--, mhi_buf--) + mhi_free_coherent(mhi_cntrl, mhi_buf->len, mhi_buf->buf, + mhi_buf->dma_addr); + +error_alloc_mhi_buf: + kfree(img_info); + + return -ENOMEM; +} + +static void mhi_firmware_copy(struct mhi_controller *mhi_cntrl, + const struct firmware *firmware, + struct image_info *img_info) +{ + size_t remainder = firmware->size; + size_t to_cpy; + const u8 *buf = firmware->data; + int i = 0; + struct mhi_buf *mhi_buf = img_info->mhi_buf; + struct bhi_vec_entry *bhi_vec = img_info->bhi_vec; + + while (remainder) { + to_cpy = min(remainder, mhi_buf->len); + memcpy(mhi_buf->buf, buf, to_cpy); + bhi_vec->dma_addr = mhi_buf->dma_addr; + bhi_vec->size = to_cpy; + + buf += to_cpy; + remainder -= to_cpy; + i++; + bhi_vec++; + mhi_buf++; + } +} + +void mhi_fw_load_worker(struct work_struct *work) +{ + struct mhi_controller *mhi_cntrl; + const struct firmware *firmware = NULL; + struct image_info *image_info; + struct device *dev; + const char *fw_name; + void *buf; + dma_addr_t dma_addr; + size_t size; + int ret; + + mhi_cntrl = container_of(work, struct mhi_controller, fw_worker); + dev = &mhi_cntrl->mhi_dev->dev; + + dev_dbg(dev, "Waiting for device to enter PBL from: %s\n", + TO_MHI_EXEC_STR(mhi_cntrl->ee)); + + ret = wait_event_timeout(mhi_cntrl->state_event, + MHI_IN_PBL(mhi_cntrl->ee) || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + dev_err(dev, "Device MHI is not in valid state\n"); + return; + } + + /* If device is in pass through, do reset to ready state transition */ + if (mhi_cntrl->ee == MHI_EE_PTHRU) + goto fw_load_ee_pthru; + + fw_name = (mhi_cntrl->ee == MHI_EE_EDL) ? + mhi_cntrl->edl_image : mhi_cntrl->fw_image; + + if (!fw_name || (mhi_cntrl->fbc_download && (!mhi_cntrl->sbl_size || + !mhi_cntrl->seg_len))) { + dev_err(dev, + "No firmware image defined or !sbl_size || !seg_len\n"); + return; + } + + ret = request_firmware(&firmware, fw_name, dev); + if (ret) { + dev_err(dev, "Error loading firmware: %d\n", ret); + return; + } + + size = (mhi_cntrl->fbc_download) ? mhi_cntrl->sbl_size : firmware->size; + + /* SBL size provided is maximum size, not necessarily the image size */ + if (size > firmware->size) + size = firmware->size; + + buf = mhi_alloc_coherent(mhi_cntrl, size, &dma_addr, GFP_KERNEL); + if (!buf) { + release_firmware(firmware); + return; + } + + /* Download SBL image */ + memcpy(buf, firmware->data, size); + ret = mhi_fw_load_sbl(mhi_cntrl, dma_addr, size); + mhi_free_coherent(mhi_cntrl, size, buf, dma_addr); + + if (!mhi_cntrl->fbc_download || ret || mhi_cntrl->ee == MHI_EE_EDL) + release_firmware(firmware); + + /* Error or in EDL mode, we're done */ + if (ret || mhi_cntrl->ee == MHI_EE_EDL) + return; + + write_lock_irq(&mhi_cntrl->pm_lock); + mhi_cntrl->dev_state = MHI_STATE_RESET; + write_unlock_irq(&mhi_cntrl->pm_lock); + + /* + * If we're doing fbc, populate vector tables while + * device transitioning into MHI READY state + */ + if (mhi_cntrl->fbc_download) { + ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image, + firmware->size); + if (ret) + goto error_alloc_fw_table; + + /* Load the firmware into BHIE vec table */ + mhi_firmware_copy(mhi_cntrl, firmware, mhi_cntrl->fbc_image); + } + +fw_load_ee_pthru: + /* Transitioning into MHI RESET->READY state */ + ret = mhi_ready_state_transition(mhi_cntrl); + + if (!mhi_cntrl->fbc_download) + return; + + if (ret) + goto error_read; + + /* Wait for the SBL event */ + ret = wait_event_timeout(mhi_cntrl->state_event, + mhi_cntrl->ee == MHI_EE_SBL || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + dev_err(dev, "MHI did not enter SBL\n"); + goto error_read; + } + + /* Start full firmware image download */ + image_info = mhi_cntrl->fbc_image; + ret = mhi_fw_load_amss(mhi_cntrl, + /* Vector table is the last entry */ + &image_info->mhi_buf[image_info->entries - 1]); + + release_firmware(firmware); + + return; + +error_read: + mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image); + mhi_cntrl->fbc_image = NULL; + +error_alloc_fw_table: + release_firmware(firmware); +} diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c new file mode 100644 index 000000000000..b38359c480ea --- /dev/null +++ b/drivers/bus/mhi/core/init.c @@ -0,0 +1,1293 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mhi.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/wait.h> +#include "internal.h" + +const char * const mhi_ee_str[MHI_EE_MAX] = { + [MHI_EE_PBL] = "PBL", + [MHI_EE_SBL] = "SBL", + [MHI_EE_AMSS] = "AMSS", + [MHI_EE_RDDM] = "RDDM", + [MHI_EE_WFW] = "WFW", + [MHI_EE_PTHRU] = "PASS THRU", + [MHI_EE_EDL] = "EDL", + [MHI_EE_DISABLE_TRANSITION] = "DISABLE", + [MHI_EE_NOT_SUPPORTED] = "NOT SUPPORTED", +}; + +const char * const dev_state_tran_str[DEV_ST_TRANSITION_MAX] = { + [DEV_ST_TRANSITION_PBL] = "PBL", + [DEV_ST_TRANSITION_READY] = "READY", + [DEV_ST_TRANSITION_SBL] = "SBL", + [DEV_ST_TRANSITION_MISSION_MODE] = "MISSION_MODE", +}; + +const char * const mhi_state_str[MHI_STATE_MAX] = { + [MHI_STATE_RESET] = "RESET", + [MHI_STATE_READY] = "READY", + [MHI_STATE_M0] = "M0", + [MHI_STATE_M1] = "M1", + [MHI_STATE_M2] = "M2", + [MHI_STATE_M3] = "M3", + [MHI_STATE_M3_FAST] = "M3_FAST", + [MHI_STATE_BHI] = "BHI", + [MHI_STATE_SYS_ERR] = "SYS_ERR", +}; + +static const char * const mhi_pm_state_str[] = { + [MHI_PM_STATE_DISABLE] = "DISABLE", + [MHI_PM_STATE_POR] = "POR", + [MHI_PM_STATE_M0] = "M0", + [MHI_PM_STATE_M2] = "M2", + [MHI_PM_STATE_M3_ENTER] = "M?->M3", + [MHI_PM_STATE_M3] = "M3", + [MHI_PM_STATE_M3_EXIT] = "M3->M0", + [MHI_PM_STATE_FW_DL_ERR] = "FW DL Error", + [MHI_PM_STATE_SYS_ERR_DETECT] = "SYS_ERR Detect", + [MHI_PM_STATE_SYS_ERR_PROCESS] = "SYS_ERR Process", + [MHI_PM_STATE_SHUTDOWN_PROCESS] = "SHUTDOWN Process", + [MHI_PM_STATE_LD_ERR_FATAL_DETECT] = "LD or Error Fatal Detect", +}; + +const char *to_mhi_pm_state_str(enum mhi_pm_state state) +{ + int index = find_last_bit((unsigned long *)&state, 32); + + if (index >= ARRAY_SIZE(mhi_pm_state_str)) + return "Invalid State"; + + return mhi_pm_state_str[index]; +} + +/* MHI protocol requires the transfer ring to be aligned with ring length */ +static int mhi_alloc_aligned_ring(struct mhi_controller *mhi_cntrl, + struct mhi_ring *ring, + u64 len) +{ + ring->alloc_size = len + (len - 1); + ring->pre_aligned = mhi_alloc_coherent(mhi_cntrl, ring->alloc_size, + &ring->dma_handle, GFP_KERNEL); + if (!ring->pre_aligned) + return -ENOMEM; + + ring->iommu_base = (ring->dma_handle + (len - 1)) & ~(len - 1); + ring->base = ring->pre_aligned + (ring->iommu_base - ring->dma_handle); + + return 0; +} + +void mhi_deinit_free_irq(struct mhi_controller *mhi_cntrl) +{ + int i; + struct mhi_event *mhi_event = mhi_cntrl->mhi_event; + + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + + free_irq(mhi_cntrl->irq[mhi_event->irq], mhi_event); + } + + free_irq(mhi_cntrl->irq[0], mhi_cntrl); +} + +int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) +{ + struct mhi_event *mhi_event = mhi_cntrl->mhi_event; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int i, ret; + + /* Setup BHI_INTVEC IRQ */ + ret = request_threaded_irq(mhi_cntrl->irq[0], mhi_intvec_handler, + mhi_intvec_threaded_handler, + IRQF_SHARED | IRQF_NO_SUSPEND, + "bhi", mhi_cntrl); + if (ret) + return ret; + + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + + ret = request_irq(mhi_cntrl->irq[mhi_event->irq], + mhi_irq_handler, + IRQF_SHARED | IRQF_NO_SUSPEND, + "mhi", mhi_event); + if (ret) { + dev_err(dev, "Error requesting irq:%d for ev:%d\n", + mhi_cntrl->irq[mhi_event->irq], i); + goto error_request; + } + } + + return 0; + +error_request: + for (--i, --mhi_event; i >= 0; i--, mhi_event--) { + if (mhi_event->offload_ev) + continue; + + free_irq(mhi_cntrl->irq[mhi_event->irq], mhi_event); + } + free_irq(mhi_cntrl->irq[0], mhi_cntrl); + + return ret; +} + +void mhi_deinit_dev_ctxt(struct mhi_controller *mhi_cntrl) +{ + int i; + struct mhi_ctxt *mhi_ctxt = mhi_cntrl->mhi_ctxt; + struct mhi_cmd *mhi_cmd; + struct mhi_event *mhi_event; + struct mhi_ring *ring; + + mhi_cmd = mhi_cntrl->mhi_cmd; + for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++) { + ring = &mhi_cmd->ring; + mhi_free_coherent(mhi_cntrl, ring->alloc_size, + ring->pre_aligned, ring->dma_handle); + ring->base = NULL; + ring->iommu_base = 0; + } + + mhi_free_coherent(mhi_cntrl, + sizeof(*mhi_ctxt->cmd_ctxt) * NR_OF_CMD_RINGS, + mhi_ctxt->cmd_ctxt, mhi_ctxt->cmd_ctxt_addr); + + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + + ring = &mhi_event->ring; + mhi_free_coherent(mhi_cntrl, ring->alloc_size, + ring->pre_aligned, ring->dma_handle); + ring->base = NULL; + ring->iommu_base = 0; + } + + mhi_free_coherent(mhi_cntrl, sizeof(*mhi_ctxt->er_ctxt) * + mhi_cntrl->total_ev_rings, mhi_ctxt->er_ctxt, + mhi_ctxt->er_ctxt_addr); + + mhi_free_coherent(mhi_cntrl, sizeof(*mhi_ctxt->chan_ctxt) * + mhi_cntrl->max_chan, mhi_ctxt->chan_ctxt, + mhi_ctxt->chan_ctxt_addr); + + kfree(mhi_ctxt); + mhi_cntrl->mhi_ctxt = NULL; +} + +int mhi_init_dev_ctxt(struct mhi_controller *mhi_cntrl) +{ + struct mhi_ctxt *mhi_ctxt; + struct mhi_chan_ctxt *chan_ctxt; + struct mhi_event_ctxt *er_ctxt; + struct mhi_cmd_ctxt *cmd_ctxt; + struct mhi_chan *mhi_chan; + struct mhi_event *mhi_event; + struct mhi_cmd *mhi_cmd; + u32 tmp; + int ret = -ENOMEM, i; + + atomic_set(&mhi_cntrl->dev_wake, 0); + atomic_set(&mhi_cntrl->pending_pkts, 0); + + mhi_ctxt = kzalloc(sizeof(*mhi_ctxt), GFP_KERNEL); + if (!mhi_ctxt) + return -ENOMEM; + + /* Setup channel ctxt */ + mhi_ctxt->chan_ctxt = mhi_alloc_coherent(mhi_cntrl, + sizeof(*mhi_ctxt->chan_ctxt) * + mhi_cntrl->max_chan, + &mhi_ctxt->chan_ctxt_addr, + GFP_KERNEL); + if (!mhi_ctxt->chan_ctxt) + goto error_alloc_chan_ctxt; + + mhi_chan = mhi_cntrl->mhi_chan; + chan_ctxt = mhi_ctxt->chan_ctxt; + for (i = 0; i < mhi_cntrl->max_chan; i++, chan_ctxt++, mhi_chan++) { + /* Skip if it is an offload channel */ + if (mhi_chan->offload_ch) + continue; + + tmp = chan_ctxt->chcfg; + tmp &= ~CHAN_CTX_CHSTATE_MASK; + tmp |= (MHI_CH_STATE_DISABLED << CHAN_CTX_CHSTATE_SHIFT); + tmp &= ~CHAN_CTX_BRSTMODE_MASK; + tmp |= (mhi_chan->db_cfg.brstmode << CHAN_CTX_BRSTMODE_SHIFT); + tmp &= ~CHAN_CTX_POLLCFG_MASK; + tmp |= (mhi_chan->db_cfg.pollcfg << CHAN_CTX_POLLCFG_SHIFT); + chan_ctxt->chcfg = tmp; + + chan_ctxt->chtype = mhi_chan->type; + chan_ctxt->erindex = mhi_chan->er_index; + + mhi_chan->ch_state = MHI_CH_STATE_DISABLED; + mhi_chan->tre_ring.db_addr = (void __iomem *)&chan_ctxt->wp; + } + + /* Setup event context */ + mhi_ctxt->er_ctxt = mhi_alloc_coherent(mhi_cntrl, + sizeof(*mhi_ctxt->er_ctxt) * + mhi_cntrl->total_ev_rings, + &mhi_ctxt->er_ctxt_addr, + GFP_KERNEL); + if (!mhi_ctxt->er_ctxt) + goto error_alloc_er_ctxt; + + er_ctxt = mhi_ctxt->er_ctxt; + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++, + mhi_event++) { + struct mhi_ring *ring = &mhi_event->ring; + + /* Skip if it is an offload event */ + if (mhi_event->offload_ev) + continue; + + tmp = er_ctxt->intmod; + tmp &= ~EV_CTX_INTMODC_MASK; + tmp &= ~EV_CTX_INTMODT_MASK; + tmp |= (mhi_event->intmod << EV_CTX_INTMODT_SHIFT); + er_ctxt->intmod = tmp; + + er_ctxt->ertype = MHI_ER_TYPE_VALID; + er_ctxt->msivec = mhi_event->irq; + mhi_event->db_cfg.db_mode = true; + + ring->el_size = sizeof(struct mhi_tre); + ring->len = ring->el_size * ring->elements; + ret = mhi_alloc_aligned_ring(mhi_cntrl, ring, ring->len); + if (ret) + goto error_alloc_er; + + /* + * If the read pointer equals to the write pointer, then the + * ring is empty + */ + ring->rp = ring->wp = ring->base; + er_ctxt->rbase = ring->iommu_base; + er_ctxt->rp = er_ctxt->wp = er_ctxt->rbase; + er_ctxt->rlen = ring->len; + ring->ctxt_wp = &er_ctxt->wp; + } + + /* Setup cmd context */ + mhi_ctxt->cmd_ctxt = mhi_alloc_coherent(mhi_cntrl, + sizeof(*mhi_ctxt->cmd_ctxt) * + NR_OF_CMD_RINGS, + &mhi_ctxt->cmd_ctxt_addr, + GFP_KERNEL); + if (!mhi_ctxt->cmd_ctxt) + goto error_alloc_er; + + mhi_cmd = mhi_cntrl->mhi_cmd; + cmd_ctxt = mhi_ctxt->cmd_ctxt; + for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++, cmd_ctxt++) { + struct mhi_ring *ring = &mhi_cmd->ring; + + ring->el_size = sizeof(struct mhi_tre); + ring->elements = CMD_EL_PER_RING; + ring->len = ring->el_size * ring->elements; + ret = mhi_alloc_aligned_ring(mhi_cntrl, ring, ring->len); + if (ret) + goto error_alloc_cmd; + + ring->rp = ring->wp = ring->base; + cmd_ctxt->rbase = ring->iommu_base; + cmd_ctxt->rp = cmd_ctxt->wp = cmd_ctxt->rbase; + cmd_ctxt->rlen = ring->len; + ring->ctxt_wp = &cmd_ctxt->wp; + } + + mhi_cntrl->mhi_ctxt = mhi_ctxt; + + return 0; + +error_alloc_cmd: + for (--i, --mhi_cmd; i >= 0; i--, mhi_cmd--) { + struct mhi_ring *ring = &mhi_cmd->ring; + + mhi_free_coherent(mhi_cntrl, ring->alloc_size, + ring->pre_aligned, ring->dma_handle); + } + mhi_free_coherent(mhi_cntrl, + sizeof(*mhi_ctxt->cmd_ctxt) * NR_OF_CMD_RINGS, + mhi_ctxt->cmd_ctxt, mhi_ctxt->cmd_ctxt_addr); + i = mhi_cntrl->total_ev_rings; + mhi_event = mhi_cntrl->mhi_event + i; + +error_alloc_er: + for (--i, --mhi_event; i >= 0; i--, mhi_event--) { + struct mhi_ring *ring = &mhi_event->ring; + + if (mhi_event->offload_ev) + continue; + + mhi_free_coherent(mhi_cntrl, ring->alloc_size, + ring->pre_aligned, ring->dma_handle); + } + mhi_free_coherent(mhi_cntrl, sizeof(*mhi_ctxt->er_ctxt) * + mhi_cntrl->total_ev_rings, mhi_ctxt->er_ctxt, + mhi_ctxt->er_ctxt_addr); + +error_alloc_er_ctxt: + mhi_free_coherent(mhi_cntrl, sizeof(*mhi_ctxt->chan_ctxt) * + mhi_cntrl->max_chan, mhi_ctxt->chan_ctxt, + mhi_ctxt->chan_ctxt_addr); + +error_alloc_chan_ctxt: + kfree(mhi_ctxt); + + return ret; +} + +int mhi_init_mmio(struct mhi_controller *mhi_cntrl) +{ + u32 val; + int i, ret; + struct mhi_chan *mhi_chan; + struct mhi_event *mhi_event; + void __iomem *base = mhi_cntrl->regs; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + struct { + u32 offset; + u32 mask; + u32 shift; + u32 val; + } reg_info[] = { + { + CCABAP_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->mhi_ctxt->chan_ctxt_addr), + }, + { + CCABAP_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->mhi_ctxt->chan_ctxt_addr), + }, + { + ECABAP_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->mhi_ctxt->er_ctxt_addr), + }, + { + ECABAP_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->mhi_ctxt->er_ctxt_addr), + }, + { + CRCBAP_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->mhi_ctxt->cmd_ctxt_addr), + }, + { + CRCBAP_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->mhi_ctxt->cmd_ctxt_addr), + }, + { + MHICFG, MHICFG_NER_MASK, MHICFG_NER_SHIFT, + mhi_cntrl->total_ev_rings, + }, + { + MHICFG, MHICFG_NHWER_MASK, MHICFG_NHWER_SHIFT, + mhi_cntrl->hw_ev_rings, + }, + { + MHICTRLBASE_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->iova_start), + }, + { + MHICTRLBASE_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->iova_start), + }, + { + MHIDATABASE_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->iova_start), + }, + { + MHIDATABASE_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->iova_start), + }, + { + MHICTRLLIMIT_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->iova_stop), + }, + { + MHICTRLLIMIT_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->iova_stop), + }, + { + MHIDATALIMIT_HIGHER, U32_MAX, 0, + upper_32_bits(mhi_cntrl->iova_stop), + }, + { + MHIDATALIMIT_LOWER, U32_MAX, 0, + lower_32_bits(mhi_cntrl->iova_stop), + }, + { 0, 0, 0 } + }; + + dev_dbg(dev, "Initializing MHI registers\n"); + + /* Read channel db offset */ + ret = mhi_read_reg_field(mhi_cntrl, base, CHDBOFF, CHDBOFF_CHDBOFF_MASK, + CHDBOFF_CHDBOFF_SHIFT, &val); + if (ret) { + dev_err(dev, "Unable to read CHDBOFF register\n"); + return -EIO; + } + + /* Setup wake db */ + mhi_cntrl->wake_db = base + val + (8 * MHI_DEV_WAKE_DB); + mhi_write_reg(mhi_cntrl, mhi_cntrl->wake_db, 4, 0); + mhi_write_reg(mhi_cntrl, mhi_cntrl->wake_db, 0, 0); + mhi_cntrl->wake_set = false; + + /* Setup channel db address for each channel in tre_ring */ + mhi_chan = mhi_cntrl->mhi_chan; + for (i = 0; i < mhi_cntrl->max_chan; i++, val += 8, mhi_chan++) + mhi_chan->tre_ring.db_addr = base + val; + + /* Read event ring db offset */ + ret = mhi_read_reg_field(mhi_cntrl, base, ERDBOFF, ERDBOFF_ERDBOFF_MASK, + ERDBOFF_ERDBOFF_SHIFT, &val); + if (ret) { + dev_err(dev, "Unable to read ERDBOFF register\n"); + return -EIO; + } + + /* Setup event db address for each ev_ring */ + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, val += 8, mhi_event++) { + if (mhi_event->offload_ev) + continue; + + mhi_event->ring.db_addr = base + val; + } + + /* Setup DB register for primary CMD rings */ + mhi_cntrl->mhi_cmd[PRIMARY_CMD_RING].ring.db_addr = base + CRDB_LOWER; + + /* Write to MMIO registers */ + for (i = 0; reg_info[i].offset; i++) + mhi_write_reg_field(mhi_cntrl, base, reg_info[i].offset, + reg_info[i].mask, reg_info[i].shift, + reg_info[i].val); + + return 0; +} + +void mhi_deinit_chan_ctxt(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan) +{ + struct mhi_ring *buf_ring; + struct mhi_ring *tre_ring; + struct mhi_chan_ctxt *chan_ctxt; + + buf_ring = &mhi_chan->buf_ring; + tre_ring = &mhi_chan->tre_ring; + chan_ctxt = &mhi_cntrl->mhi_ctxt->chan_ctxt[mhi_chan->chan]; + + mhi_free_coherent(mhi_cntrl, tre_ring->alloc_size, + tre_ring->pre_aligned, tre_ring->dma_handle); + vfree(buf_ring->base); + + buf_ring->base = tre_ring->base = NULL; + chan_ctxt->rbase = 0; +} + +int mhi_init_chan_ctxt(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan) +{ + struct mhi_ring *buf_ring; + struct mhi_ring *tre_ring; + struct mhi_chan_ctxt *chan_ctxt; + u32 tmp; + int ret; + + buf_ring = &mhi_chan->buf_ring; + tre_ring = &mhi_chan->tre_ring; + tre_ring->el_size = sizeof(struct mhi_tre); + tre_ring->len = tre_ring->el_size * tre_ring->elements; + chan_ctxt = &mhi_cntrl->mhi_ctxt->chan_ctxt[mhi_chan->chan]; + ret = mhi_alloc_aligned_ring(mhi_cntrl, tre_ring, tre_ring->len); + if (ret) + return -ENOMEM; + + buf_ring->el_size = sizeof(struct mhi_buf_info); + buf_ring->len = buf_ring->el_size * buf_ring->elements; + buf_ring->base = vzalloc(buf_ring->len); + + if (!buf_ring->base) { + mhi_free_coherent(mhi_cntrl, tre_ring->alloc_size, + tre_ring->pre_aligned, tre_ring->dma_handle); + return -ENOMEM; + } + + tmp = chan_ctxt->chcfg; + tmp &= ~CHAN_CTX_CHSTATE_MASK; + tmp |= (MHI_CH_STATE_ENABLED << CHAN_CTX_CHSTATE_SHIFT); + chan_ctxt->chcfg = tmp; + + chan_ctxt->rbase = tre_ring->iommu_base; + chan_ctxt->rp = chan_ctxt->wp = chan_ctxt->rbase; + chan_ctxt->rlen = tre_ring->len; + tre_ring->ctxt_wp = &chan_ctxt->wp; + + tre_ring->rp = tre_ring->wp = tre_ring->base; + buf_ring->rp = buf_ring->wp = buf_ring->base; + mhi_chan->db_cfg.db_mode = 1; + + /* Update to all cores */ + smp_wmb(); + + return 0; +} + +static int parse_ev_cfg(struct mhi_controller *mhi_cntrl, + struct mhi_controller_config *config) +{ + struct mhi_event *mhi_event; + struct mhi_event_config *event_cfg; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int i, num; + + num = config->num_events; + mhi_cntrl->total_ev_rings = num; + mhi_cntrl->mhi_event = kcalloc(num, sizeof(*mhi_cntrl->mhi_event), + GFP_KERNEL); + if (!mhi_cntrl->mhi_event) + return -ENOMEM; + + /* Populate event ring */ + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < num; i++) { + event_cfg = &config->event_cfg[i]; + + mhi_event->er_index = i; + mhi_event->ring.elements = event_cfg->num_elements; + mhi_event->intmod = event_cfg->irq_moderation_ms; + mhi_event->irq = event_cfg->irq; + + if (event_cfg->channel != U32_MAX) { + /* This event ring has a dedicated channel */ + mhi_event->chan = event_cfg->channel; + if (mhi_event->chan >= mhi_cntrl->max_chan) { + dev_err(dev, + "Event Ring channel not available\n"); + goto error_ev_cfg; + } + + mhi_event->mhi_chan = + &mhi_cntrl->mhi_chan[mhi_event->chan]; + } + + /* Priority is fixed to 1 for now */ + mhi_event->priority = 1; + + mhi_event->db_cfg.brstmode = event_cfg->mode; + if (MHI_INVALID_BRSTMODE(mhi_event->db_cfg.brstmode)) + goto error_ev_cfg; + + if (mhi_event->db_cfg.brstmode == MHI_DB_BRST_ENABLE) + mhi_event->db_cfg.process_db = mhi_db_brstmode; + else + mhi_event->db_cfg.process_db = mhi_db_brstmode_disable; + + mhi_event->data_type = event_cfg->data_type; + + switch (mhi_event->data_type) { + case MHI_ER_DATA: + mhi_event->process_event = mhi_process_data_event_ring; + break; + case MHI_ER_CTRL: + mhi_event->process_event = mhi_process_ctrl_ev_ring; + break; + default: + dev_err(dev, "Event Ring type not supported\n"); + goto error_ev_cfg; + } + + mhi_event->hw_ring = event_cfg->hardware_event; + if (mhi_event->hw_ring) + mhi_cntrl->hw_ev_rings++; + else + mhi_cntrl->sw_ev_rings++; + + mhi_event->cl_manage = event_cfg->client_managed; + mhi_event->offload_ev = event_cfg->offload_channel; + mhi_event++; + } + + /* We need IRQ for each event ring + additional one for BHI */ + mhi_cntrl->nr_irqs_req = mhi_cntrl->total_ev_rings + 1; + + return 0; + +error_ev_cfg: + + kfree(mhi_cntrl->mhi_event); + return -EINVAL; +} + +static int parse_ch_cfg(struct mhi_controller *mhi_cntrl, + struct mhi_controller_config *config) +{ + struct mhi_channel_config *ch_cfg; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int i; + u32 chan; + + mhi_cntrl->max_chan = config->max_channels; + + /* + * The allocation of MHI channels can exceed 32KB in some scenarios, + * so to avoid any memory possible allocation failures, vzalloc is + * used here + */ + mhi_cntrl->mhi_chan = vzalloc(mhi_cntrl->max_chan * + sizeof(*mhi_cntrl->mhi_chan)); + if (!mhi_cntrl->mhi_chan) + return -ENOMEM; + + INIT_LIST_HEAD(&mhi_cntrl->lpm_chans); + + /* Populate channel configurations */ + for (i = 0; i < config->num_channels; i++) { + struct mhi_chan *mhi_chan; + + ch_cfg = &config->ch_cfg[i]; + + chan = ch_cfg->num; + if (chan >= mhi_cntrl->max_chan) { + dev_err(dev, "Channel %d not available\n", chan); + goto error_chan_cfg; + } + + mhi_chan = &mhi_cntrl->mhi_chan[chan]; + mhi_chan->name = ch_cfg->name; + mhi_chan->chan = chan; + + mhi_chan->tre_ring.elements = ch_cfg->num_elements; + if (!mhi_chan->tre_ring.elements) + goto error_chan_cfg; + + /* + * For some channels, local ring length should be bigger than + * the transfer ring length due to internal logical channels + * in device. So host can queue much more buffers than transfer + * ring length. Example, RSC channels should have a larger local + * channel length than transfer ring length. + */ + mhi_chan->buf_ring.elements = ch_cfg->local_elements; + if (!mhi_chan->buf_ring.elements) + mhi_chan->buf_ring.elements = mhi_chan->tre_ring.elements; + mhi_chan->er_index = ch_cfg->event_ring; + mhi_chan->dir = ch_cfg->dir; + + /* + * For most channels, chtype is identical to channel directions. + * So, if it is not defined then assign channel direction to + * chtype + */ + mhi_chan->type = ch_cfg->type; + if (!mhi_chan->type) + mhi_chan->type = (enum mhi_ch_type)mhi_chan->dir; + + mhi_chan->ee_mask = ch_cfg->ee_mask; + mhi_chan->db_cfg.pollcfg = ch_cfg->pollcfg; + mhi_chan->lpm_notify = ch_cfg->lpm_notify; + mhi_chan->offload_ch = ch_cfg->offload_channel; + mhi_chan->db_cfg.reset_req = ch_cfg->doorbell_mode_switch; + mhi_chan->pre_alloc = ch_cfg->auto_queue; + mhi_chan->auto_start = ch_cfg->auto_start; + + /* + * If MHI host allocates buffers, then the channel direction + * should be DMA_FROM_DEVICE + */ + if (mhi_chan->pre_alloc && mhi_chan->dir != DMA_FROM_DEVICE) { + dev_err(dev, "Invalid channel configuration\n"); + goto error_chan_cfg; + } + + /* + * Bi-directional and direction less channel must be an + * offload channel + */ + if ((mhi_chan->dir == DMA_BIDIRECTIONAL || + mhi_chan->dir == DMA_NONE) && !mhi_chan->offload_ch) { + dev_err(dev, "Invalid channel configuration\n"); + goto error_chan_cfg; + } + + if (!mhi_chan->offload_ch) { + mhi_chan->db_cfg.brstmode = ch_cfg->doorbell; + if (MHI_INVALID_BRSTMODE(mhi_chan->db_cfg.brstmode)) { + dev_err(dev, "Invalid Door bell mode\n"); + goto error_chan_cfg; + } + } + + if (mhi_chan->db_cfg.brstmode == MHI_DB_BRST_ENABLE) + mhi_chan->db_cfg.process_db = mhi_db_brstmode; + else + mhi_chan->db_cfg.process_db = mhi_db_brstmode_disable; + + mhi_chan->configured = true; + + if (mhi_chan->lpm_notify) + list_add_tail(&mhi_chan->node, &mhi_cntrl->lpm_chans); + } + + return 0; + +error_chan_cfg: + vfree(mhi_cntrl->mhi_chan); + + return -EINVAL; +} + +static int parse_config(struct mhi_controller *mhi_cntrl, + struct mhi_controller_config *config) +{ + int ret; + + /* Parse MHI channel configuration */ + ret = parse_ch_cfg(mhi_cntrl, config); + if (ret) + return ret; + + /* Parse MHI event configuration */ + ret = parse_ev_cfg(mhi_cntrl, config); + if (ret) + goto error_ev_cfg; + + mhi_cntrl->timeout_ms = config->timeout_ms; + if (!mhi_cntrl->timeout_ms) + mhi_cntrl->timeout_ms = MHI_TIMEOUT_MS; + + mhi_cntrl->bounce_buf = config->use_bounce_buf; + mhi_cntrl->buffer_len = config->buf_len; + if (!mhi_cntrl->buffer_len) + mhi_cntrl->buffer_len = MHI_MAX_MTU; + + /* By default, host is allowed to ring DB in both M0 and M2 states */ + mhi_cntrl->db_access = MHI_PM_M0 | MHI_PM_M2; + if (config->m2_no_db) + mhi_cntrl->db_access &= ~MHI_PM_M2; + + return 0; + +error_ev_cfg: + vfree(mhi_cntrl->mhi_chan); + + return ret; +} + +int mhi_register_controller(struct mhi_controller *mhi_cntrl, + struct mhi_controller_config *config) +{ + struct mhi_event *mhi_event; + struct mhi_chan *mhi_chan; + struct mhi_cmd *mhi_cmd; + struct mhi_device *mhi_dev; + u32 soc_info; + int ret, i; + + if (!mhi_cntrl) + return -EINVAL; + + if (!mhi_cntrl->runtime_get || !mhi_cntrl->runtime_put) + return -EINVAL; + + if (!mhi_cntrl->status_cb || !mhi_cntrl->link_status) + return -EINVAL; + + ret = parse_config(mhi_cntrl, config); + if (ret) + return -EINVAL; + + mhi_cntrl->mhi_cmd = kcalloc(NR_OF_CMD_RINGS, + sizeof(*mhi_cntrl->mhi_cmd), GFP_KERNEL); + if (!mhi_cntrl->mhi_cmd) { + ret = -ENOMEM; + goto error_alloc_cmd; + } + + INIT_LIST_HEAD(&mhi_cntrl->transition_list); + mutex_init(&mhi_cntrl->pm_mutex); + rwlock_init(&mhi_cntrl->pm_lock); + spin_lock_init(&mhi_cntrl->transition_lock); + spin_lock_init(&mhi_cntrl->wlock); + INIT_WORK(&mhi_cntrl->st_worker, mhi_pm_st_worker); + INIT_WORK(&mhi_cntrl->syserr_worker, mhi_pm_sys_err_worker); + INIT_WORK(&mhi_cntrl->fw_worker, mhi_fw_load_worker); + init_waitqueue_head(&mhi_cntrl->state_event); + + mhi_cmd = mhi_cntrl->mhi_cmd; + for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++) + spin_lock_init(&mhi_cmd->lock); + + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + /* Skip for offload events */ + if (mhi_event->offload_ev) + continue; + + mhi_event->mhi_cntrl = mhi_cntrl; + spin_lock_init(&mhi_event->lock); + if (mhi_event->data_type == MHI_ER_CTRL) + tasklet_init(&mhi_event->task, mhi_ctrl_ev_task, + (ulong)mhi_event); + else + tasklet_init(&mhi_event->task, mhi_ev_task, + (ulong)mhi_event); + } + + mhi_chan = mhi_cntrl->mhi_chan; + for (i = 0; i < mhi_cntrl->max_chan; i++, mhi_chan++) { + mutex_init(&mhi_chan->mutex); + init_completion(&mhi_chan->completion); + rwlock_init(&mhi_chan->lock); + } + + if (mhi_cntrl->bounce_buf) { + mhi_cntrl->map_single = mhi_map_single_use_bb; + mhi_cntrl->unmap_single = mhi_unmap_single_use_bb; + } else { + mhi_cntrl->map_single = mhi_map_single_no_bb; + mhi_cntrl->unmap_single = mhi_unmap_single_no_bb; + } + + /* Read the MHI device info */ + ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, + SOC_HW_VERSION_OFFS, &soc_info); + if (ret) + goto error_alloc_dev; + + mhi_cntrl->family_number = (soc_info & SOC_HW_VERSION_FAM_NUM_BMSK) >> + SOC_HW_VERSION_FAM_NUM_SHFT; + mhi_cntrl->device_number = (soc_info & SOC_HW_VERSION_DEV_NUM_BMSK) >> + SOC_HW_VERSION_DEV_NUM_SHFT; + mhi_cntrl->major_version = (soc_info & SOC_HW_VERSION_MAJOR_VER_BMSK) >> + SOC_HW_VERSION_MAJOR_VER_SHFT; + mhi_cntrl->minor_version = (soc_info & SOC_HW_VERSION_MINOR_VER_BMSK) >> + SOC_HW_VERSION_MINOR_VER_SHFT; + + /* Register controller with MHI bus */ + mhi_dev = mhi_alloc_device(mhi_cntrl); + if (IS_ERR(mhi_dev)) { + dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate MHI device\n"); + ret = PTR_ERR(mhi_dev); + goto error_alloc_dev; + } + + mhi_dev->dev_type = MHI_DEVICE_CONTROLLER; + mhi_dev->mhi_cntrl = mhi_cntrl; + dev_set_name(&mhi_dev->dev, "%s", dev_name(mhi_cntrl->cntrl_dev)); + + /* Init wakeup source */ + device_init_wakeup(&mhi_dev->dev, true); + + ret = device_add(&mhi_dev->dev); + if (ret) + goto error_add_dev; + + mhi_cntrl->mhi_dev = mhi_dev; + + return 0; + +error_add_dev: + put_device(&mhi_dev->dev); + +error_alloc_dev: + kfree(mhi_cntrl->mhi_cmd); + +error_alloc_cmd: + vfree(mhi_cntrl->mhi_chan); + kfree(mhi_cntrl->mhi_event); + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_register_controller); + +void mhi_unregister_controller(struct mhi_controller *mhi_cntrl) +{ + struct mhi_device *mhi_dev = mhi_cntrl->mhi_dev; + struct mhi_chan *mhi_chan = mhi_cntrl->mhi_chan; + unsigned int i; + + kfree(mhi_cntrl->mhi_cmd); + kfree(mhi_cntrl->mhi_event); + + /* Drop the references to MHI devices created for channels */ + for (i = 0; i < mhi_cntrl->max_chan; i++, mhi_chan++) { + if (!mhi_chan->mhi_dev) + continue; + + put_device(&mhi_chan->mhi_dev->dev); + } + vfree(mhi_cntrl->mhi_chan); + + device_del(&mhi_dev->dev); + put_device(&mhi_dev->dev); +} +EXPORT_SYMBOL_GPL(mhi_unregister_controller); + +int mhi_prepare_for_power_up(struct mhi_controller *mhi_cntrl) +{ + struct device *dev = &mhi_cntrl->mhi_dev->dev; + u32 bhie_off; + int ret; + + mutex_lock(&mhi_cntrl->pm_mutex); + + ret = mhi_init_dev_ctxt(mhi_cntrl); + if (ret) + goto error_dev_ctxt; + + /* + * Allocate RDDM table if specified, this table is for debugging purpose + */ + if (mhi_cntrl->rddm_size) { + mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->rddm_image, + mhi_cntrl->rddm_size); + + /* + * This controller supports RDDM, so we need to manually clear + * BHIE RX registers since POR values are undefined. + */ + ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, BHIEOFF, + &bhie_off); + if (ret) { + dev_err(dev, "Error getting BHIE offset\n"); + goto bhie_error; + } + + mhi_cntrl->bhie = mhi_cntrl->regs + bhie_off; + memset_io(mhi_cntrl->bhie + BHIE_RXVECADDR_LOW_OFFS, + 0, BHIE_RXVECSTATUS_OFFS - BHIE_RXVECADDR_LOW_OFFS + + 4); + + if (mhi_cntrl->rddm_image) + mhi_rddm_prepare(mhi_cntrl, mhi_cntrl->rddm_image); + } + + mhi_cntrl->pre_init = true; + + mutex_unlock(&mhi_cntrl->pm_mutex); + + return 0; + +bhie_error: + if (mhi_cntrl->rddm_image) { + mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->rddm_image); + mhi_cntrl->rddm_image = NULL; + } + +error_dev_ctxt: + mutex_unlock(&mhi_cntrl->pm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_prepare_for_power_up); + +void mhi_unprepare_after_power_down(struct mhi_controller *mhi_cntrl) +{ + if (mhi_cntrl->fbc_image) { + mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image); + mhi_cntrl->fbc_image = NULL; + } + + if (mhi_cntrl->rddm_image) { + mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->rddm_image); + mhi_cntrl->rddm_image = NULL; + } + + mhi_deinit_dev_ctxt(mhi_cntrl); + mhi_cntrl->pre_init = false; +} +EXPORT_SYMBOL_GPL(mhi_unprepare_after_power_down); + +static void mhi_release_device(struct device *dev) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + + /* + * We need to set the mhi_chan->mhi_dev to NULL here since the MHI + * devices for the channels will only get created if the mhi_dev + * associated with it is NULL. This scenario will happen during the + * controller suspend and resume. + */ + if (mhi_dev->ul_chan) + mhi_dev->ul_chan->mhi_dev = NULL; + + if (mhi_dev->dl_chan) + mhi_dev->dl_chan->mhi_dev = NULL; + + kfree(mhi_dev); +} + +struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl) +{ + struct mhi_device *mhi_dev; + struct device *dev; + + mhi_dev = kzalloc(sizeof(*mhi_dev), GFP_KERNEL); + if (!mhi_dev) + return ERR_PTR(-ENOMEM); + + dev = &mhi_dev->dev; + device_initialize(dev); + dev->bus = &mhi_bus_type; + dev->release = mhi_release_device; + dev->parent = mhi_cntrl->cntrl_dev; + mhi_dev->mhi_cntrl = mhi_cntrl; + mhi_dev->dev_wake = 0; + + return mhi_dev; +} + +static int mhi_driver_probe(struct device *dev) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct device_driver *drv = dev->driver; + struct mhi_driver *mhi_drv = to_mhi_driver(drv); + struct mhi_event *mhi_event; + struct mhi_chan *ul_chan = mhi_dev->ul_chan; + struct mhi_chan *dl_chan = mhi_dev->dl_chan; + int ret; + + /* Bring device out of LPM */ + ret = mhi_device_get_sync(mhi_dev); + if (ret) + return ret; + + ret = -EINVAL; + + if (ul_chan) { + /* + * If channel supports LPM notifications then status_cb should + * be provided + */ + if (ul_chan->lpm_notify && !mhi_drv->status_cb) + goto exit_probe; + + /* For non-offload channels then xfer_cb should be provided */ + if (!ul_chan->offload_ch && !mhi_drv->ul_xfer_cb) + goto exit_probe; + + ul_chan->xfer_cb = mhi_drv->ul_xfer_cb; + if (ul_chan->auto_start) { + ret = mhi_prepare_channel(mhi_cntrl, ul_chan); + if (ret) + goto exit_probe; + } + } + + if (dl_chan) { + /* + * If channel supports LPM notifications then status_cb should + * be provided + */ + if (dl_chan->lpm_notify && !mhi_drv->status_cb) + goto exit_probe; + + /* For non-offload channels then xfer_cb should be provided */ + if (!dl_chan->offload_ch && !mhi_drv->dl_xfer_cb) + goto exit_probe; + + mhi_event = &mhi_cntrl->mhi_event[dl_chan->er_index]; + + /* + * If the channel event ring is managed by client, then + * status_cb must be provided so that the framework can + * notify pending data + */ + if (mhi_event->cl_manage && !mhi_drv->status_cb) + goto exit_probe; + + dl_chan->xfer_cb = mhi_drv->dl_xfer_cb; + } + + /* Call the user provided probe function */ + ret = mhi_drv->probe(mhi_dev, mhi_dev->id); + if (ret) + goto exit_probe; + + if (dl_chan && dl_chan->auto_start) + mhi_prepare_channel(mhi_cntrl, dl_chan); + + mhi_device_put(mhi_dev); + + return ret; + +exit_probe: + mhi_unprepare_from_transfer(mhi_dev); + + mhi_device_put(mhi_dev); + + return ret; +} + +static int mhi_driver_remove(struct device *dev) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_driver *mhi_drv = to_mhi_driver(dev->driver); + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan; + enum mhi_ch_state ch_state[] = { + MHI_CH_STATE_DISABLED, + MHI_CH_STATE_DISABLED + }; + int dir; + + /* Skip if it is a controller device */ + if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) + return 0; + + /* Reset both channels */ + for (dir = 0; dir < 2; dir++) { + mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; + + if (!mhi_chan) + continue; + + /* Wake all threads waiting for completion */ + write_lock_irq(&mhi_chan->lock); + mhi_chan->ccs = MHI_EV_CC_INVALID; + complete_all(&mhi_chan->completion); + write_unlock_irq(&mhi_chan->lock); + + /* Set the channel state to disabled */ + mutex_lock(&mhi_chan->mutex); + write_lock_irq(&mhi_chan->lock); + ch_state[dir] = mhi_chan->ch_state; + mhi_chan->ch_state = MHI_CH_STATE_SUSPENDED; + write_unlock_irq(&mhi_chan->lock); + + /* Reset the non-offload channel */ + if (!mhi_chan->offload_ch) + mhi_reset_chan(mhi_cntrl, mhi_chan); + + mutex_unlock(&mhi_chan->mutex); + } + + mhi_drv->remove(mhi_dev); + + /* De-init channel if it was enabled */ + for (dir = 0; dir < 2; dir++) { + mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; + + if (!mhi_chan) + continue; + + mutex_lock(&mhi_chan->mutex); + + if (ch_state[dir] == MHI_CH_STATE_ENABLED && + !mhi_chan->offload_ch) + mhi_deinit_chan_ctxt(mhi_cntrl, mhi_chan); + + mhi_chan->ch_state = MHI_CH_STATE_DISABLED; + + mutex_unlock(&mhi_chan->mutex); + } + + read_lock_bh(&mhi_cntrl->pm_lock); + while (mhi_dev->dev_wake) + mhi_device_put(mhi_dev); + read_unlock_bh(&mhi_cntrl->pm_lock); + + return 0; +} + +int __mhi_driver_register(struct mhi_driver *mhi_drv, struct module *owner) +{ + struct device_driver *driver = &mhi_drv->driver; + + if (!mhi_drv->probe || !mhi_drv->remove) + return -EINVAL; + + driver->bus = &mhi_bus_type; + driver->owner = owner; + driver->probe = mhi_driver_probe; + driver->remove = mhi_driver_remove; + + return driver_register(driver); +} +EXPORT_SYMBOL_GPL(__mhi_driver_register); + +void mhi_driver_unregister(struct mhi_driver *mhi_drv) +{ + driver_unregister(&mhi_drv->driver); +} +EXPORT_SYMBOL_GPL(mhi_driver_unregister); + +static int mhi_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + + return add_uevent_var(env, "MODALIAS=" MHI_DEVICE_MODALIAS_FMT, + mhi_dev->chan_name); +} + +static int mhi_match(struct device *dev, struct device_driver *drv) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_driver *mhi_drv = to_mhi_driver(drv); + const struct mhi_device_id *id; + + /* + * If the device is a controller type then there is no client driver + * associated with it + */ + if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) + return 0; + + for (id = mhi_drv->id_table; id->chan[0]; id++) + if (!strcmp(mhi_dev->chan_name, id->chan)) { + mhi_dev->id = id; + return 1; + } + + return 0; +}; + +struct bus_type mhi_bus_type = { + .name = "mhi", + .dev_name = "mhi", + .match = mhi_match, + .uevent = mhi_uevent, +}; + +static int __init mhi_init(void) +{ + return bus_register(&mhi_bus_type); +} + +static void __exit mhi_exit(void) +{ + bus_unregister(&mhi_bus_type); +} + +postcore_initcall(mhi_init); +module_exit(mhi_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MHI Host Interface"); diff --git a/drivers/bus/mhi/core/internal.h b/drivers/bus/mhi/core/internal.h new file mode 100644 index 000000000000..5deadfaa053a --- /dev/null +++ b/drivers/bus/mhi/core/internal.h @@ -0,0 +1,687 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + */ + +#ifndef _MHI_INT_H +#define _MHI_INT_H + +#include <linux/mhi.h> + +extern struct bus_type mhi_bus_type; + +/* MHI MMIO register mapping */ +#define PCI_INVALID_READ(val) (val == U32_MAX) + +#define MHIREGLEN (0x0) +#define MHIREGLEN_MHIREGLEN_MASK (0xFFFFFFFF) +#define MHIREGLEN_MHIREGLEN_SHIFT (0) + +#define MHIVER (0x8) +#define MHIVER_MHIVER_MASK (0xFFFFFFFF) +#define MHIVER_MHIVER_SHIFT (0) + +#define MHICFG (0x10) +#define MHICFG_NHWER_MASK (0xFF000000) +#define MHICFG_NHWER_SHIFT (24) +#define MHICFG_NER_MASK (0xFF0000) +#define MHICFG_NER_SHIFT (16) +#define MHICFG_NHWCH_MASK (0xFF00) +#define MHICFG_NHWCH_SHIFT (8) +#define MHICFG_NCH_MASK (0xFF) +#define MHICFG_NCH_SHIFT (0) + +#define CHDBOFF (0x18) +#define CHDBOFF_CHDBOFF_MASK (0xFFFFFFFF) +#define CHDBOFF_CHDBOFF_SHIFT (0) + +#define ERDBOFF (0x20) +#define ERDBOFF_ERDBOFF_MASK (0xFFFFFFFF) +#define ERDBOFF_ERDBOFF_SHIFT (0) + +#define BHIOFF (0x28) +#define BHIOFF_BHIOFF_MASK (0xFFFFFFFF) +#define BHIOFF_BHIOFF_SHIFT (0) + +#define BHIEOFF (0x2C) +#define BHIEOFF_BHIEOFF_MASK (0xFFFFFFFF) +#define BHIEOFF_BHIEOFF_SHIFT (0) + +#define DEBUGOFF (0x30) +#define DEBUGOFF_DEBUGOFF_MASK (0xFFFFFFFF) +#define DEBUGOFF_DEBUGOFF_SHIFT (0) + +#define MHICTRL (0x38) +#define MHICTRL_MHISTATE_MASK (0x0000FF00) +#define MHICTRL_MHISTATE_SHIFT (8) +#define MHICTRL_RESET_MASK (0x2) +#define MHICTRL_RESET_SHIFT (1) + +#define MHISTATUS (0x48) +#define MHISTATUS_MHISTATE_MASK (0x0000FF00) +#define MHISTATUS_MHISTATE_SHIFT (8) +#define MHISTATUS_SYSERR_MASK (0x4) +#define MHISTATUS_SYSERR_SHIFT (2) +#define MHISTATUS_READY_MASK (0x1) +#define MHISTATUS_READY_SHIFT (0) + +#define CCABAP_LOWER (0x58) +#define CCABAP_LOWER_CCABAP_LOWER_MASK (0xFFFFFFFF) +#define CCABAP_LOWER_CCABAP_LOWER_SHIFT (0) + +#define CCABAP_HIGHER (0x5C) +#define CCABAP_HIGHER_CCABAP_HIGHER_MASK (0xFFFFFFFF) +#define CCABAP_HIGHER_CCABAP_HIGHER_SHIFT (0) + +#define ECABAP_LOWER (0x60) +#define ECABAP_LOWER_ECABAP_LOWER_MASK (0xFFFFFFFF) +#define ECABAP_LOWER_ECABAP_LOWER_SHIFT (0) + +#define ECABAP_HIGHER (0x64) +#define ECABAP_HIGHER_ECABAP_HIGHER_MASK (0xFFFFFFFF) +#define ECABAP_HIGHER_ECABAP_HIGHER_SHIFT (0) + +#define CRCBAP_LOWER (0x68) +#define CRCBAP_LOWER_CRCBAP_LOWER_MASK (0xFFFFFFFF) +#define CRCBAP_LOWER_CRCBAP_LOWER_SHIFT (0) + +#define CRCBAP_HIGHER (0x6C) +#define CRCBAP_HIGHER_CRCBAP_HIGHER_MASK (0xFFFFFFFF) +#define CRCBAP_HIGHER_CRCBAP_HIGHER_SHIFT (0) + +#define CRDB_LOWER (0x70) +#define CRDB_LOWER_CRDB_LOWER_MASK (0xFFFFFFFF) +#define CRDB_LOWER_CRDB_LOWER_SHIFT (0) + +#define CRDB_HIGHER (0x74) +#define CRDB_HIGHER_CRDB_HIGHER_MASK (0xFFFFFFFF) +#define CRDB_HIGHER_CRDB_HIGHER_SHIFT (0) + +#define MHICTRLBASE_LOWER (0x80) +#define MHICTRLBASE_LOWER_MHICTRLBASE_LOWER_MASK (0xFFFFFFFF) +#define MHICTRLBASE_LOWER_MHICTRLBASE_LOWER_SHIFT (0) + +#define MHICTRLBASE_HIGHER (0x84) +#define MHICTRLBASE_HIGHER_MHICTRLBASE_HIGHER_MASK (0xFFFFFFFF) +#define MHICTRLBASE_HIGHER_MHICTRLBASE_HIGHER_SHIFT (0) + +#define MHICTRLLIMIT_LOWER (0x88) +#define MHICTRLLIMIT_LOWER_MHICTRLLIMIT_LOWER_MASK (0xFFFFFFFF) +#define MHICTRLLIMIT_LOWER_MHICTRLLIMIT_LOWER_SHIFT (0) + +#define MHICTRLLIMIT_HIGHER (0x8C) +#define MHICTRLLIMIT_HIGHER_MHICTRLLIMIT_HIGHER_MASK (0xFFFFFFFF) +#define MHICTRLLIMIT_HIGHER_MHICTRLLIMIT_HIGHER_SHIFT (0) + +#define MHIDATABASE_LOWER (0x98) +#define MHIDATABASE_LOWER_MHIDATABASE_LOWER_MASK (0xFFFFFFFF) +#define MHIDATABASE_LOWER_MHIDATABASE_LOWER_SHIFT (0) + +#define MHIDATABASE_HIGHER (0x9C) +#define MHIDATABASE_HIGHER_MHIDATABASE_HIGHER_MASK (0xFFFFFFFF) +#define MHIDATABASE_HIGHER_MHIDATABASE_HIGHER_SHIFT (0) + +#define MHIDATALIMIT_LOWER (0xA0) +#define MHIDATALIMIT_LOWER_MHIDATALIMIT_LOWER_MASK (0xFFFFFFFF) +#define MHIDATALIMIT_LOWER_MHIDATALIMIT_LOWER_SHIFT (0) + +#define MHIDATALIMIT_HIGHER (0xA4) +#define MHIDATALIMIT_HIGHER_MHIDATALIMIT_HIGHER_MASK (0xFFFFFFFF) +#define MHIDATALIMIT_HIGHER_MHIDATALIMIT_HIGHER_SHIFT (0) + +/* Host request register */ +#define MHI_SOC_RESET_REQ_OFFSET (0xB0) +#define MHI_SOC_RESET_REQ BIT(0) + +/* MHI BHI offfsets */ +#define BHI_BHIVERSION_MINOR (0x00) +#define BHI_BHIVERSION_MAJOR (0x04) +#define BHI_IMGADDR_LOW (0x08) +#define BHI_IMGADDR_HIGH (0x0C) +#define BHI_IMGSIZE (0x10) +#define BHI_RSVD1 (0x14) +#define BHI_IMGTXDB (0x18) +#define BHI_TXDB_SEQNUM_BMSK (0x3FFFFFFF) +#define BHI_TXDB_SEQNUM_SHFT (0) +#define BHI_RSVD2 (0x1C) +#define BHI_INTVEC (0x20) +#define BHI_RSVD3 (0x24) +#define BHI_EXECENV (0x28) +#define BHI_STATUS (0x2C) +#define BHI_ERRCODE (0x30) +#define BHI_ERRDBG1 (0x34) +#define BHI_ERRDBG2 (0x38) +#define BHI_ERRDBG3 (0x3C) +#define BHI_SERIALNU (0x40) +#define BHI_SBLANTIROLLVER (0x44) +#define BHI_NUMSEG (0x48) +#define BHI_MSMHWID(n) (0x4C + (0x4 * n)) +#define BHI_OEMPKHASH(n) (0x64 + (0x4 * n)) +#define BHI_RSVD5 (0xC4) +#define BHI_STATUS_MASK (0xC0000000) +#define BHI_STATUS_SHIFT (30) +#define BHI_STATUS_ERROR (3) +#define BHI_STATUS_SUCCESS (2) +#define BHI_STATUS_RESET (0) + +/* MHI BHIE offsets */ +#define BHIE_MSMSOCID_OFFS (0x0000) +#define BHIE_TXVECADDR_LOW_OFFS (0x002C) +#define BHIE_TXVECADDR_HIGH_OFFS (0x0030) +#define BHIE_TXVECSIZE_OFFS (0x0034) +#define BHIE_TXVECDB_OFFS (0x003C) +#define BHIE_TXVECDB_SEQNUM_BMSK (0x3FFFFFFF) +#define BHIE_TXVECDB_SEQNUM_SHFT (0) +#define BHIE_TXVECSTATUS_OFFS (0x0044) +#define BHIE_TXVECSTATUS_SEQNUM_BMSK (0x3FFFFFFF) +#define BHIE_TXVECSTATUS_SEQNUM_SHFT (0) +#define BHIE_TXVECSTATUS_STATUS_BMSK (0xC0000000) +#define BHIE_TXVECSTATUS_STATUS_SHFT (30) +#define BHIE_TXVECSTATUS_STATUS_RESET (0x00) +#define BHIE_TXVECSTATUS_STATUS_XFER_COMPL (0x02) +#define BHIE_TXVECSTATUS_STATUS_ERROR (0x03) +#define BHIE_RXVECADDR_LOW_OFFS (0x0060) +#define BHIE_RXVECADDR_HIGH_OFFS (0x0064) +#define BHIE_RXVECSIZE_OFFS (0x0068) +#define BHIE_RXVECDB_OFFS (0x0070) +#define BHIE_RXVECDB_SEQNUM_BMSK (0x3FFFFFFF) +#define BHIE_RXVECDB_SEQNUM_SHFT (0) +#define BHIE_RXVECSTATUS_OFFS (0x0078) +#define BHIE_RXVECSTATUS_SEQNUM_BMSK (0x3FFFFFFF) +#define BHIE_RXVECSTATUS_SEQNUM_SHFT (0) +#define BHIE_RXVECSTATUS_STATUS_BMSK (0xC0000000) +#define BHIE_RXVECSTATUS_STATUS_SHFT (30) +#define BHIE_RXVECSTATUS_STATUS_RESET (0x00) +#define BHIE_RXVECSTATUS_STATUS_XFER_COMPL (0x02) +#define BHIE_RXVECSTATUS_STATUS_ERROR (0x03) + +#define SOC_HW_VERSION_OFFS (0x224) +#define SOC_HW_VERSION_FAM_NUM_BMSK (0xF0000000) +#define SOC_HW_VERSION_FAM_NUM_SHFT (28) +#define SOC_HW_VERSION_DEV_NUM_BMSK (0x0FFF0000) +#define SOC_HW_VERSION_DEV_NUM_SHFT (16) +#define SOC_HW_VERSION_MAJOR_VER_BMSK (0x0000FF00) +#define SOC_HW_VERSION_MAJOR_VER_SHFT (8) +#define SOC_HW_VERSION_MINOR_VER_BMSK (0x000000FF) +#define SOC_HW_VERSION_MINOR_VER_SHFT (0) + +#define EV_CTX_RESERVED_MASK GENMASK(7, 0) +#define EV_CTX_INTMODC_MASK GENMASK(15, 8) +#define EV_CTX_INTMODC_SHIFT 8 +#define EV_CTX_INTMODT_MASK GENMASK(31, 16) +#define EV_CTX_INTMODT_SHIFT 16 +struct mhi_event_ctxt { + __u32 intmod; + __u32 ertype; + __u32 msivec; + + __u64 rbase __packed __aligned(4); + __u64 rlen __packed __aligned(4); + __u64 rp __packed __aligned(4); + __u64 wp __packed __aligned(4); +}; + +#define CHAN_CTX_CHSTATE_MASK GENMASK(7, 0) +#define CHAN_CTX_CHSTATE_SHIFT 0 +#define CHAN_CTX_BRSTMODE_MASK GENMASK(9, 8) +#define CHAN_CTX_BRSTMODE_SHIFT 8 +#define CHAN_CTX_POLLCFG_MASK GENMASK(15, 10) +#define CHAN_CTX_POLLCFG_SHIFT 10 +#define CHAN_CTX_RESERVED_MASK GENMASK(31, 16) +struct mhi_chan_ctxt { + __u32 chcfg; + __u32 chtype; + __u32 erindex; + + __u64 rbase __packed __aligned(4); + __u64 rlen __packed __aligned(4); + __u64 rp __packed __aligned(4); + __u64 wp __packed __aligned(4); +}; + +struct mhi_cmd_ctxt { + __u32 reserved0; + __u32 reserved1; + __u32 reserved2; + + __u64 rbase __packed __aligned(4); + __u64 rlen __packed __aligned(4); + __u64 rp __packed __aligned(4); + __u64 wp __packed __aligned(4); +}; + +struct mhi_ctxt { + struct mhi_event_ctxt *er_ctxt; + struct mhi_chan_ctxt *chan_ctxt; + struct mhi_cmd_ctxt *cmd_ctxt; + dma_addr_t er_ctxt_addr; + dma_addr_t chan_ctxt_addr; + dma_addr_t cmd_ctxt_addr; +}; + +struct mhi_tre { + u64 ptr; + u32 dword[2]; +}; + +struct bhi_vec_entry { + u64 dma_addr; + u64 size; +}; + +enum mhi_cmd_type { + MHI_CMD_NOP = 1, + MHI_CMD_RESET_CHAN = 16, + MHI_CMD_STOP_CHAN = 17, + MHI_CMD_START_CHAN = 18, +}; + +/* No operation command */ +#define MHI_TRE_CMD_NOOP_PTR (0) +#define MHI_TRE_CMD_NOOP_DWORD0 (0) +#define MHI_TRE_CMD_NOOP_DWORD1 (MHI_CMD_NOP << 16) + +/* Channel reset command */ +#define MHI_TRE_CMD_RESET_PTR (0) +#define MHI_TRE_CMD_RESET_DWORD0 (0) +#define MHI_TRE_CMD_RESET_DWORD1(chid) ((chid << 24) | \ + (MHI_CMD_RESET_CHAN << 16)) + +/* Channel stop command */ +#define MHI_TRE_CMD_STOP_PTR (0) +#define MHI_TRE_CMD_STOP_DWORD0 (0) +#define MHI_TRE_CMD_STOP_DWORD1(chid) ((chid << 24) | \ + (MHI_CMD_STOP_CHAN << 16)) + +/* Channel start command */ +#define MHI_TRE_CMD_START_PTR (0) +#define MHI_TRE_CMD_START_DWORD0 (0) +#define MHI_TRE_CMD_START_DWORD1(chid) ((chid << 24) | \ + (MHI_CMD_START_CHAN << 16)) + +#define MHI_TRE_GET_CMD_CHID(tre) (((tre)->dword[1] >> 24) & 0xFF) +#define MHI_TRE_GET_CMD_TYPE(tre) (((tre)->dword[1] >> 16) & 0xFF) + +/* Event descriptor macros */ +#define MHI_TRE_EV_PTR(ptr) (ptr) +#define MHI_TRE_EV_DWORD0(code, len) ((code << 24) | len) +#define MHI_TRE_EV_DWORD1(chid, type) ((chid << 24) | (type << 16)) +#define MHI_TRE_GET_EV_PTR(tre) ((tre)->ptr) +#define MHI_TRE_GET_EV_CODE(tre) (((tre)->dword[0] >> 24) & 0xFF) +#define MHI_TRE_GET_EV_LEN(tre) ((tre)->dword[0] & 0xFFFF) +#define MHI_TRE_GET_EV_CHID(tre) (((tre)->dword[1] >> 24) & 0xFF) +#define MHI_TRE_GET_EV_TYPE(tre) (((tre)->dword[1] >> 16) & 0xFF) +#define MHI_TRE_GET_EV_STATE(tre) (((tre)->dword[0] >> 24) & 0xFF) +#define MHI_TRE_GET_EV_EXECENV(tre) (((tre)->dword[0] >> 24) & 0xFF) +#define MHI_TRE_GET_EV_SEQ(tre) ((tre)->dword[0]) +#define MHI_TRE_GET_EV_TIME(tre) ((tre)->ptr) +#define MHI_TRE_GET_EV_COOKIE(tre) lower_32_bits((tre)->ptr) +#define MHI_TRE_GET_EV_VEID(tre) (((tre)->dword[0] >> 16) & 0xFF) +#define MHI_TRE_GET_EV_LINKSPEED(tre) (((tre)->dword[1] >> 24) & 0xFF) +#define MHI_TRE_GET_EV_LINKWIDTH(tre) ((tre)->dword[0] & 0xFF) + +/* Transfer descriptor macros */ +#define MHI_TRE_DATA_PTR(ptr) (ptr) +#define MHI_TRE_DATA_DWORD0(len) (len & MHI_MAX_MTU) +#define MHI_TRE_DATA_DWORD1(bei, ieot, ieob, chain) ((2 << 16) | (bei << 10) \ + | (ieot << 9) | (ieob << 8) | chain) + +/* RSC transfer descriptor macros */ +#define MHI_RSCTRE_DATA_PTR(ptr, len) (((u64)len << 48) | ptr) +#define MHI_RSCTRE_DATA_DWORD0(cookie) (cookie) +#define MHI_RSCTRE_DATA_DWORD1 (MHI_PKT_TYPE_COALESCING << 16) + +enum mhi_pkt_type { + MHI_PKT_TYPE_INVALID = 0x0, + MHI_PKT_TYPE_NOOP_CMD = 0x1, + MHI_PKT_TYPE_TRANSFER = 0x2, + MHI_PKT_TYPE_COALESCING = 0x8, + MHI_PKT_TYPE_RESET_CHAN_CMD = 0x10, + MHI_PKT_TYPE_STOP_CHAN_CMD = 0x11, + MHI_PKT_TYPE_START_CHAN_CMD = 0x12, + MHI_PKT_TYPE_STATE_CHANGE_EVENT = 0x20, + MHI_PKT_TYPE_CMD_COMPLETION_EVENT = 0x21, + MHI_PKT_TYPE_TX_EVENT = 0x22, + MHI_PKT_TYPE_RSC_TX_EVENT = 0x28, + MHI_PKT_TYPE_EE_EVENT = 0x40, + MHI_PKT_TYPE_TSYNC_EVENT = 0x48, + MHI_PKT_TYPE_BW_REQ_EVENT = 0x50, + MHI_PKT_TYPE_STALE_EVENT, /* internal event */ +}; + +/* MHI transfer completion events */ +enum mhi_ev_ccs { + MHI_EV_CC_INVALID = 0x0, + MHI_EV_CC_SUCCESS = 0x1, + MHI_EV_CC_EOT = 0x2, /* End of transfer event */ + MHI_EV_CC_OVERFLOW = 0x3, + MHI_EV_CC_EOB = 0x4, /* End of block event */ + MHI_EV_CC_OOB = 0x5, /* Out of block event */ + MHI_EV_CC_DB_MODE = 0x6, + MHI_EV_CC_UNDEFINED_ERR = 0x10, + MHI_EV_CC_BAD_TRE = 0x11, +}; + +enum mhi_ch_state { + MHI_CH_STATE_DISABLED = 0x0, + MHI_CH_STATE_ENABLED = 0x1, + MHI_CH_STATE_RUNNING = 0x2, + MHI_CH_STATE_SUSPENDED = 0x3, + MHI_CH_STATE_STOP = 0x4, + MHI_CH_STATE_ERROR = 0x5, +}; + +#define MHI_INVALID_BRSTMODE(mode) (mode != MHI_DB_BRST_DISABLE && \ + mode != MHI_DB_BRST_ENABLE) + +extern const char * const mhi_ee_str[MHI_EE_MAX]; +#define TO_MHI_EXEC_STR(ee) (((ee) >= MHI_EE_MAX) ? \ + "INVALID_EE" : mhi_ee_str[ee]) + +#define MHI_IN_PBL(ee) (ee == MHI_EE_PBL || ee == MHI_EE_PTHRU || \ + ee == MHI_EE_EDL) + +#define MHI_IN_MISSION_MODE(ee) (ee == MHI_EE_AMSS || ee == MHI_EE_WFW) + +enum dev_st_transition { + DEV_ST_TRANSITION_PBL, + DEV_ST_TRANSITION_READY, + DEV_ST_TRANSITION_SBL, + DEV_ST_TRANSITION_MISSION_MODE, + DEV_ST_TRANSITION_MAX, +}; + +extern const char * const dev_state_tran_str[DEV_ST_TRANSITION_MAX]; +#define TO_DEV_STATE_TRANS_STR(state) (((state) >= DEV_ST_TRANSITION_MAX) ? \ + "INVALID_STATE" : dev_state_tran_str[state]) + +extern const char * const mhi_state_str[MHI_STATE_MAX]; +#define TO_MHI_STATE_STR(state) ((state >= MHI_STATE_MAX || \ + !mhi_state_str[state]) ? \ + "INVALID_STATE" : mhi_state_str[state]) + +/* internal power states */ +enum mhi_pm_state { + MHI_PM_STATE_DISABLE, + MHI_PM_STATE_POR, + MHI_PM_STATE_M0, + MHI_PM_STATE_M2, + MHI_PM_STATE_M3_ENTER, + MHI_PM_STATE_M3, + MHI_PM_STATE_M3_EXIT, + MHI_PM_STATE_FW_DL_ERR, + MHI_PM_STATE_SYS_ERR_DETECT, + MHI_PM_STATE_SYS_ERR_PROCESS, + MHI_PM_STATE_SHUTDOWN_PROCESS, + MHI_PM_STATE_LD_ERR_FATAL_DETECT, + MHI_PM_STATE_MAX +}; + +#define MHI_PM_DISABLE BIT(0) +#define MHI_PM_POR BIT(1) +#define MHI_PM_M0 BIT(2) +#define MHI_PM_M2 BIT(3) +#define MHI_PM_M3_ENTER BIT(4) +#define MHI_PM_M3 BIT(5) +#define MHI_PM_M3_EXIT BIT(6) +/* firmware download failure state */ +#define MHI_PM_FW_DL_ERR BIT(7) +#define MHI_PM_SYS_ERR_DETECT BIT(8) +#define MHI_PM_SYS_ERR_PROCESS BIT(9) +#define MHI_PM_SHUTDOWN_PROCESS BIT(10) +/* link not accessible */ +#define MHI_PM_LD_ERR_FATAL_DETECT BIT(11) + +#define MHI_REG_ACCESS_VALID(pm_state) ((pm_state & (MHI_PM_POR | MHI_PM_M0 | \ + MHI_PM_M2 | MHI_PM_M3_ENTER | MHI_PM_M3_EXIT | \ + MHI_PM_SYS_ERR_DETECT | MHI_PM_SYS_ERR_PROCESS | \ + MHI_PM_SHUTDOWN_PROCESS | MHI_PM_FW_DL_ERR))) +#define MHI_PM_IN_ERROR_STATE(pm_state) (pm_state >= MHI_PM_FW_DL_ERR) +#define MHI_PM_IN_FATAL_STATE(pm_state) (pm_state == MHI_PM_LD_ERR_FATAL_DETECT) +#define MHI_DB_ACCESS_VALID(mhi_cntrl) (mhi_cntrl->pm_state & \ + mhi_cntrl->db_access) +#define MHI_WAKE_DB_CLEAR_VALID(pm_state) (pm_state & (MHI_PM_M0 | \ + MHI_PM_M2 | MHI_PM_M3_EXIT)) +#define MHI_WAKE_DB_SET_VALID(pm_state) (pm_state & MHI_PM_M2) +#define MHI_WAKE_DB_FORCE_SET_VALID(pm_state) MHI_WAKE_DB_CLEAR_VALID(pm_state) +#define MHI_EVENT_ACCESS_INVALID(pm_state) (pm_state == MHI_PM_DISABLE || \ + MHI_PM_IN_ERROR_STATE(pm_state)) +#define MHI_PM_IN_SUSPEND_STATE(pm_state) (pm_state & \ + (MHI_PM_M3_ENTER | MHI_PM_M3)) + +#define NR_OF_CMD_RINGS 1 +#define CMD_EL_PER_RING 128 +#define PRIMARY_CMD_RING 0 +#define MHI_DEV_WAKE_DB 127 +#define MHI_MAX_MTU 0xffff + +enum mhi_er_type { + MHI_ER_TYPE_INVALID = 0x0, + MHI_ER_TYPE_VALID = 0x1, +}; + +struct db_cfg { + bool reset_req; + bool db_mode; + u32 pollcfg; + enum mhi_db_brst_mode brstmode; + dma_addr_t db_val; + void (*process_db)(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_cfg, void __iomem *io_addr, + dma_addr_t db_val); +}; + +struct mhi_pm_transitions { + enum mhi_pm_state from_state; + u32 to_states; +}; + +struct state_transition { + struct list_head node; + enum dev_st_transition state; +}; + +struct mhi_ring { + dma_addr_t dma_handle; + dma_addr_t iommu_base; + u64 *ctxt_wp; /* point to ctxt wp */ + void *pre_aligned; + void *base; + void *rp; + void *wp; + size_t el_size; + size_t len; + size_t elements; + size_t alloc_size; + void __iomem *db_addr; +}; + +struct mhi_cmd { + struct mhi_ring ring; + spinlock_t lock; +}; + +struct mhi_buf_info { + void *v_addr; + void *bb_addr; + void *wp; + void *cb_buf; + dma_addr_t p_addr; + size_t len; + enum dma_data_direction dir; + bool used; /* Indicates whether the buffer is used or not */ + bool pre_mapped; /* Already pre-mapped by client */ +}; + +struct mhi_event { + struct mhi_controller *mhi_cntrl; + struct mhi_chan *mhi_chan; /* dedicated to channel */ + u32 er_index; + u32 intmod; + u32 irq; + int chan; /* this event ring is dedicated to a channel (optional) */ + u32 priority; + enum mhi_er_data_type data_type; + struct mhi_ring ring; + struct db_cfg db_cfg; + struct tasklet_struct task; + spinlock_t lock; + int (*process_event)(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + u32 event_quota); + bool hw_ring; + bool cl_manage; + bool offload_ev; /* managed by a device driver */ +}; + +struct mhi_chan { + const char *name; + /* + * Important: When consuming, increment tre_ring first and when + * releasing, decrement buf_ring first. If tre_ring has space, buf_ring + * is guranteed to have space so we do not need to check both rings. + */ + struct mhi_ring buf_ring; + struct mhi_ring tre_ring; + u32 chan; + u32 er_index; + u32 intmod; + enum mhi_ch_type type; + enum dma_data_direction dir; + struct db_cfg db_cfg; + enum mhi_ch_ee_mask ee_mask; + enum mhi_ch_state ch_state; + enum mhi_ev_ccs ccs; + struct mhi_device *mhi_dev; + void (*xfer_cb)(struct mhi_device *mhi_dev, struct mhi_result *result); + struct mutex mutex; + struct completion completion; + rwlock_t lock; + struct list_head node; + bool lpm_notify; + bool configured; + bool offload_ch; + bool pre_alloc; + bool auto_start; + bool wake_capable; +}; + +/* Default MHI timeout */ +#define MHI_TIMEOUT_MS (1000) + +struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl); + +int mhi_destroy_device(struct device *dev, void *data); +void mhi_create_devices(struct mhi_controller *mhi_cntrl); + +int mhi_alloc_bhie_table(struct mhi_controller *mhi_cntrl, + struct image_info **image_info, size_t alloc_size); +void mhi_free_bhie_table(struct mhi_controller *mhi_cntrl, + struct image_info *image_info); + +/* Power management APIs */ +enum mhi_pm_state __must_check mhi_tryset_pm_state( + struct mhi_controller *mhi_cntrl, + enum mhi_pm_state state); +const char *to_mhi_pm_state_str(enum mhi_pm_state state); +enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl); +int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl, + enum dev_st_transition state); +void mhi_pm_st_worker(struct work_struct *work); +void mhi_pm_sys_err_worker(struct work_struct *work); +void mhi_fw_load_worker(struct work_struct *work); +int mhi_ready_state_transition(struct mhi_controller *mhi_cntrl); +void mhi_ctrl_ev_task(unsigned long data); +int mhi_pm_m0_transition(struct mhi_controller *mhi_cntrl); +void mhi_pm_m1_transition(struct mhi_controller *mhi_cntrl); +int mhi_pm_m3_transition(struct mhi_controller *mhi_cntrl); +int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl); +int mhi_send_cmd(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan, + enum mhi_cmd_type cmd); + +/* Register access methods */ +void mhi_db_brstmode(struct mhi_controller *mhi_cntrl, struct db_cfg *db_cfg, + void __iomem *db_addr, dma_addr_t db_val); +void mhi_db_brstmode_disable(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_mode, void __iomem *db_addr, + dma_addr_t db_val); +int __must_check mhi_read_reg(struct mhi_controller *mhi_cntrl, + void __iomem *base, u32 offset, u32 *out); +int __must_check mhi_read_reg_field(struct mhi_controller *mhi_cntrl, + void __iomem *base, u32 offset, u32 mask, + u32 shift, u32 *out); +void mhi_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *base, + u32 offset, u32 val); +void mhi_write_reg_field(struct mhi_controller *mhi_cntrl, void __iomem *base, + u32 offset, u32 mask, u32 shift, u32 val); +void mhi_ring_er_db(struct mhi_event *mhi_event); +void mhi_write_db(struct mhi_controller *mhi_cntrl, void __iomem *db_addr, + dma_addr_t db_val); +void mhi_ring_cmd_db(struct mhi_controller *mhi_cntrl, struct mhi_cmd *mhi_cmd); +void mhi_ring_chan_db(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan); + +/* Initialization methods */ +int mhi_init_mmio(struct mhi_controller *mhi_cntrl); +int mhi_init_dev_ctxt(struct mhi_controller *mhi_cntrl); +void mhi_deinit_dev_ctxt(struct mhi_controller *mhi_cntrl); +int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl); +void mhi_deinit_free_irq(struct mhi_controller *mhi_cntrl); +void mhi_rddm_prepare(struct mhi_controller *mhi_cntrl, + struct image_info *img_info); +int mhi_prepare_channel(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan); +int mhi_init_chan_ctxt(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan); +void mhi_deinit_chan_ctxt(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan); +void mhi_reset_chan(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan); + +/* Memory allocation methods */ +static inline void *mhi_alloc_coherent(struct mhi_controller *mhi_cntrl, + size_t size, + dma_addr_t *dma_handle, + gfp_t gfp) +{ + void *buf = dma_alloc_coherent(mhi_cntrl->cntrl_dev, size, dma_handle, + gfp); + + return buf; +} + +static inline void mhi_free_coherent(struct mhi_controller *mhi_cntrl, + size_t size, + void *vaddr, + dma_addr_t dma_handle) +{ + dma_free_coherent(mhi_cntrl->cntrl_dev, size, vaddr, dma_handle); +} + +/* Event processing methods */ +void mhi_ctrl_ev_task(unsigned long data); +void mhi_ev_task(unsigned long data); +int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, u32 event_quota); +int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, u32 event_quota); + +/* ISR handlers */ +irqreturn_t mhi_irq_handler(int irq_number, void *dev); +irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *dev); +irqreturn_t mhi_intvec_handler(int irq_number, void *dev); + +int mhi_gen_tre(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan, + void *buf, void *cb, size_t buf_len, enum mhi_flags flags); + +int mhi_map_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +int mhi_map_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +void mhi_unmap_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +void mhi_unmap_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); + +#endif /* _MHI_INT_H */ diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c new file mode 100644 index 000000000000..eb4256b81406 --- /dev/null +++ b/drivers/bus/mhi/core/main.c @@ -0,0 +1,1529 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mhi.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include "internal.h" + +int __must_check mhi_read_reg(struct mhi_controller *mhi_cntrl, + void __iomem *base, u32 offset, u32 *out) +{ + u32 tmp = readl(base + offset); + + /* If there is any unexpected value, query the link status */ + if (PCI_INVALID_READ(tmp) && + mhi_cntrl->link_status(mhi_cntrl)) + return -EIO; + + *out = tmp; + + return 0; +} + +int __must_check mhi_read_reg_field(struct mhi_controller *mhi_cntrl, + void __iomem *base, u32 offset, + u32 mask, u32 shift, u32 *out) +{ + u32 tmp; + int ret; + + ret = mhi_read_reg(mhi_cntrl, base, offset, &tmp); + if (ret) + return ret; + + *out = (tmp & mask) >> shift; + + return 0; +} + +void mhi_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *base, + u32 offset, u32 val) +{ + writel(val, base + offset); +} + +void mhi_write_reg_field(struct mhi_controller *mhi_cntrl, void __iomem *base, + u32 offset, u32 mask, u32 shift, u32 val) +{ + int ret; + u32 tmp; + + ret = mhi_read_reg(mhi_cntrl, base, offset, &tmp); + if (ret) + return; + + tmp &= ~mask; + tmp |= (val << shift); + mhi_write_reg(mhi_cntrl, base, offset, tmp); +} + +void mhi_write_db(struct mhi_controller *mhi_cntrl, void __iomem *db_addr, + dma_addr_t db_val) +{ + mhi_write_reg(mhi_cntrl, db_addr, 4, upper_32_bits(db_val)); + mhi_write_reg(mhi_cntrl, db_addr, 0, lower_32_bits(db_val)); +} + +void mhi_db_brstmode(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_cfg, + void __iomem *db_addr, + dma_addr_t db_val) +{ + if (db_cfg->db_mode) { + db_cfg->db_val = db_val; + mhi_write_db(mhi_cntrl, db_addr, db_val); + db_cfg->db_mode = 0; + } +} + +void mhi_db_brstmode_disable(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_cfg, + void __iomem *db_addr, + dma_addr_t db_val) +{ + db_cfg->db_val = db_val; + mhi_write_db(mhi_cntrl, db_addr, db_val); +} + +void mhi_ring_er_db(struct mhi_event *mhi_event) +{ + struct mhi_ring *ring = &mhi_event->ring; + + mhi_event->db_cfg.process_db(mhi_event->mhi_cntrl, &mhi_event->db_cfg, + ring->db_addr, *ring->ctxt_wp); +} + +void mhi_ring_cmd_db(struct mhi_controller *mhi_cntrl, struct mhi_cmd *mhi_cmd) +{ + dma_addr_t db; + struct mhi_ring *ring = &mhi_cmd->ring; + + db = ring->iommu_base + (ring->wp - ring->base); + *ring->ctxt_wp = db; + mhi_write_db(mhi_cntrl, ring->db_addr, db); +} + +void mhi_ring_chan_db(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan) +{ + struct mhi_ring *ring = &mhi_chan->tre_ring; + dma_addr_t db; + + db = ring->iommu_base + (ring->wp - ring->base); + *ring->ctxt_wp = db; + mhi_chan->db_cfg.process_db(mhi_cntrl, &mhi_chan->db_cfg, + ring->db_addr, db); +} + +enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl) +{ + u32 exec; + int ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_EXECENV, &exec); + + return (ret) ? MHI_EE_MAX : exec; +} + +enum mhi_state mhi_get_mhi_state(struct mhi_controller *mhi_cntrl) +{ + u32 state; + int ret = mhi_read_reg_field(mhi_cntrl, mhi_cntrl->regs, MHISTATUS, + MHISTATUS_MHISTATE_MASK, + MHISTATUS_MHISTATE_SHIFT, &state); + return ret ? MHI_STATE_MAX : state; +} + +int mhi_map_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ + buf_info->p_addr = dma_map_single(mhi_cntrl->cntrl_dev, + buf_info->v_addr, buf_info->len, + buf_info->dir); + if (dma_mapping_error(mhi_cntrl->cntrl_dev, buf_info->p_addr)) + return -ENOMEM; + + return 0; +} + +int mhi_map_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ + void *buf = mhi_alloc_coherent(mhi_cntrl, buf_info->len, + &buf_info->p_addr, GFP_ATOMIC); + + if (!buf) + return -ENOMEM; + + if (buf_info->dir == DMA_TO_DEVICE) + memcpy(buf, buf_info->v_addr, buf_info->len); + + buf_info->bb_addr = buf; + + return 0; +} + +void mhi_unmap_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ + dma_unmap_single(mhi_cntrl->cntrl_dev, buf_info->p_addr, buf_info->len, + buf_info->dir); +} + +void mhi_unmap_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ + if (buf_info->dir == DMA_FROM_DEVICE) + memcpy(buf_info->v_addr, buf_info->bb_addr, buf_info->len); + + mhi_free_coherent(mhi_cntrl, buf_info->len, buf_info->bb_addr, + buf_info->p_addr); +} + +static int get_nr_avail_ring_elements(struct mhi_controller *mhi_cntrl, + struct mhi_ring *ring) +{ + int nr_el; + + if (ring->wp < ring->rp) { + nr_el = ((ring->rp - ring->wp) / ring->el_size) - 1; + } else { + nr_el = (ring->rp - ring->base) / ring->el_size; + nr_el += ((ring->base + ring->len - ring->wp) / + ring->el_size) - 1; + } + + return nr_el; +} + +static void *mhi_to_virtual(struct mhi_ring *ring, dma_addr_t addr) +{ + return (addr - ring->iommu_base) + ring->base; +} + +static void mhi_add_ring_element(struct mhi_controller *mhi_cntrl, + struct mhi_ring *ring) +{ + ring->wp += ring->el_size; + if (ring->wp >= (ring->base + ring->len)) + ring->wp = ring->base; + /* smp update */ + smp_wmb(); +} + +static void mhi_del_ring_element(struct mhi_controller *mhi_cntrl, + struct mhi_ring *ring) +{ + ring->rp += ring->el_size; + if (ring->rp >= (ring->base + ring->len)) + ring->rp = ring->base; + /* smp update */ + smp_wmb(); +} + +int mhi_destroy_device(struct device *dev, void *data) +{ + struct mhi_device *mhi_dev; + struct mhi_controller *mhi_cntrl; + + if (dev->bus != &mhi_bus_type) + return 0; + + mhi_dev = to_mhi_device(dev); + mhi_cntrl = mhi_dev->mhi_cntrl; + + /* Only destroy virtual devices thats attached to bus */ + if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) + return 0; + + /* + * For the suspend and resume case, this function will get called + * without mhi_unregister_controller(). Hence, we need to drop the + * references to mhi_dev created for ul and dl channels. We can + * be sure that there will be no instances of mhi_dev left after + * this. + */ + if (mhi_dev->ul_chan) + put_device(&mhi_dev->ul_chan->mhi_dev->dev); + + if (mhi_dev->dl_chan) + put_device(&mhi_dev->dl_chan->mhi_dev->dev); + + dev_dbg(&mhi_cntrl->mhi_dev->dev, "destroy device for chan:%s\n", + mhi_dev->chan_name); + + /* Notify the client and remove the device from MHI bus */ + device_del(dev); + put_device(dev); + + return 0; +} + +static void mhi_notify(struct mhi_device *mhi_dev, enum mhi_callback cb_reason) +{ + struct mhi_driver *mhi_drv; + + if (!mhi_dev->dev.driver) + return; + + mhi_drv = to_mhi_driver(mhi_dev->dev.driver); + + if (mhi_drv->status_cb) + mhi_drv->status_cb(mhi_dev, cb_reason); +} + +/* Bind MHI channels to MHI devices */ +void mhi_create_devices(struct mhi_controller *mhi_cntrl) +{ + struct mhi_chan *mhi_chan; + struct mhi_device *mhi_dev; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int i, ret; + + mhi_chan = mhi_cntrl->mhi_chan; + for (i = 0; i < mhi_cntrl->max_chan; i++, mhi_chan++) { + if (!mhi_chan->configured || mhi_chan->mhi_dev || + !(mhi_chan->ee_mask & BIT(mhi_cntrl->ee))) + continue; + mhi_dev = mhi_alloc_device(mhi_cntrl); + if (!mhi_dev) + return; + + mhi_dev->dev_type = MHI_DEVICE_XFER; + switch (mhi_chan->dir) { + case DMA_TO_DEVICE: + mhi_dev->ul_chan = mhi_chan; + mhi_dev->ul_chan_id = mhi_chan->chan; + break; + case DMA_FROM_DEVICE: + /* We use dl_chan as offload channels */ + mhi_dev->dl_chan = mhi_chan; + mhi_dev->dl_chan_id = mhi_chan->chan; + break; + default: + dev_err(dev, "Direction not supported\n"); + put_device(&mhi_dev->dev); + return; + } + + get_device(&mhi_dev->dev); + mhi_chan->mhi_dev = mhi_dev; + + /* Check next channel if it matches */ + if ((i + 1) < mhi_cntrl->max_chan && mhi_chan[1].configured) { + if (!strcmp(mhi_chan[1].name, mhi_chan->name)) { + i++; + mhi_chan++; + if (mhi_chan->dir == DMA_TO_DEVICE) { + mhi_dev->ul_chan = mhi_chan; + mhi_dev->ul_chan_id = mhi_chan->chan; + } else { + mhi_dev->dl_chan = mhi_chan; + mhi_dev->dl_chan_id = mhi_chan->chan; + } + get_device(&mhi_dev->dev); + mhi_chan->mhi_dev = mhi_dev; + } + } + + /* Channel name is same for both UL and DL */ + mhi_dev->chan_name = mhi_chan->name; + dev_set_name(&mhi_dev->dev, "%04x_%s", mhi_chan->chan, + mhi_dev->chan_name); + + /* Init wakeup source if available */ + if (mhi_dev->dl_chan && mhi_dev->dl_chan->wake_capable) + device_init_wakeup(&mhi_dev->dev, true); + + ret = device_add(&mhi_dev->dev); + if (ret) + put_device(&mhi_dev->dev); + } +} + +irqreturn_t mhi_irq_handler(int irq_number, void *dev) +{ + struct mhi_event *mhi_event = dev; + struct mhi_controller *mhi_cntrl = mhi_event->mhi_cntrl; + struct mhi_event_ctxt *er_ctxt = + &mhi_cntrl->mhi_ctxt->er_ctxt[mhi_event->er_index]; + struct mhi_ring *ev_ring = &mhi_event->ring; + void *dev_rp = mhi_to_virtual(ev_ring, er_ctxt->rp); + + /* Only proceed if event ring has pending events */ + if (ev_ring->rp == dev_rp) + return IRQ_HANDLED; + + /* For client managed event ring, notify pending data */ + if (mhi_event->cl_manage) { + struct mhi_chan *mhi_chan = mhi_event->mhi_chan; + struct mhi_device *mhi_dev = mhi_chan->mhi_dev; + + if (mhi_dev) + mhi_notify(mhi_dev, MHI_CB_PENDING_DATA); + } else { + tasklet_schedule(&mhi_event->task); + } + + return IRQ_HANDLED; +} + +irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *dev) +{ + struct mhi_controller *mhi_cntrl = dev; + enum mhi_state state = MHI_STATE_MAX; + enum mhi_pm_state pm_state = 0; + enum mhi_ee_type ee = 0; + + write_lock_irq(&mhi_cntrl->pm_lock); + if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) { + state = mhi_get_mhi_state(mhi_cntrl); + ee = mhi_cntrl->ee; + mhi_cntrl->ee = mhi_get_exec_env(mhi_cntrl); + } + + if (state == MHI_STATE_SYS_ERR) { + dev_dbg(&mhi_cntrl->mhi_dev->dev, "System error detected\n"); + pm_state = mhi_tryset_pm_state(mhi_cntrl, + MHI_PM_SYS_ERR_DETECT); + } + write_unlock_irq(&mhi_cntrl->pm_lock); + + /* If device in RDDM don't bother processing SYS error */ + if (mhi_cntrl->ee == MHI_EE_RDDM) { + if (mhi_cntrl->ee != ee) { + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM); + wake_up_all(&mhi_cntrl->state_event); + } + goto exit_intvec; + } + + if (pm_state == MHI_PM_SYS_ERR_DETECT) { + wake_up_all(&mhi_cntrl->state_event); + + /* For fatal errors, we let controller decide next step */ + if (MHI_IN_PBL(ee)) + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_FATAL_ERROR); + else + schedule_work(&mhi_cntrl->syserr_worker); + } + +exit_intvec: + + return IRQ_HANDLED; +} + +irqreturn_t mhi_intvec_handler(int irq_number, void *dev) +{ + struct mhi_controller *mhi_cntrl = dev; + + /* Wake up events waiting for state change */ + wake_up_all(&mhi_cntrl->state_event); + + return IRQ_WAKE_THREAD; +} + +static void mhi_recycle_ev_ring_element(struct mhi_controller *mhi_cntrl, + struct mhi_ring *ring) +{ + dma_addr_t ctxt_wp; + + /* Update the WP */ + ring->wp += ring->el_size; + ctxt_wp = *ring->ctxt_wp + ring->el_size; + + if (ring->wp >= (ring->base + ring->len)) { + ring->wp = ring->base; + ctxt_wp = ring->iommu_base; + } + + *ring->ctxt_wp = ctxt_wp; + + /* Update the RP */ + ring->rp += ring->el_size; + if (ring->rp >= (ring->base + ring->len)) + ring->rp = ring->base; + + /* Update to all cores */ + smp_wmb(); +} + +static int parse_xfer_event(struct mhi_controller *mhi_cntrl, + struct mhi_tre *event, + struct mhi_chan *mhi_chan) +{ + struct mhi_ring *buf_ring, *tre_ring; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + struct mhi_result result; + unsigned long flags = 0; + u32 ev_code; + + ev_code = MHI_TRE_GET_EV_CODE(event); + buf_ring = &mhi_chan->buf_ring; + tre_ring = &mhi_chan->tre_ring; + + result.transaction_status = (ev_code == MHI_EV_CC_OVERFLOW) ? + -EOVERFLOW : 0; + + /* + * If it's a DB Event then we need to grab the lock + * with preemption disabled and as a write because we + * have to update db register and there are chances that + * another thread could be doing the same. + */ + if (ev_code >= MHI_EV_CC_OOB) + write_lock_irqsave(&mhi_chan->lock, flags); + else + read_lock_bh(&mhi_chan->lock); + + if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) + goto end_process_tx_event; + + switch (ev_code) { + case MHI_EV_CC_OVERFLOW: + case MHI_EV_CC_EOB: + case MHI_EV_CC_EOT: + { + dma_addr_t ptr = MHI_TRE_GET_EV_PTR(event); + struct mhi_tre *local_rp, *ev_tre; + void *dev_rp; + struct mhi_buf_info *buf_info; + u16 xfer_len; + + /* Get the TRB this event points to */ + ev_tre = mhi_to_virtual(tre_ring, ptr); + + dev_rp = ev_tre + 1; + if (dev_rp >= (tre_ring->base + tre_ring->len)) + dev_rp = tre_ring->base; + + result.dir = mhi_chan->dir; + + local_rp = tre_ring->rp; + while (local_rp != dev_rp) { + buf_info = buf_ring->rp; + /* If it's the last TRE, get length from the event */ + if (local_rp == ev_tre) + xfer_len = MHI_TRE_GET_EV_LEN(event); + else + xfer_len = buf_info->len; + + /* Unmap if it's not pre-mapped by client */ + if (likely(!buf_info->pre_mapped)) + mhi_cntrl->unmap_single(mhi_cntrl, buf_info); + + result.buf_addr = buf_info->cb_buf; + result.bytes_xferd = xfer_len; + mhi_del_ring_element(mhi_cntrl, buf_ring); + mhi_del_ring_element(mhi_cntrl, tre_ring); + local_rp = tre_ring->rp; + + /* notify client */ + mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); + + if (mhi_chan->dir == DMA_TO_DEVICE) + atomic_dec(&mhi_cntrl->pending_pkts); + + /* + * Recycle the buffer if buffer is pre-allocated, + * if there is an error, not much we can do apart + * from dropping the packet + */ + if (mhi_chan->pre_alloc) { + if (mhi_queue_buf(mhi_chan->mhi_dev, + mhi_chan->dir, + buf_info->cb_buf, + buf_info->len, MHI_EOT)) { + dev_err(dev, + "Error recycling buffer for chan:%d\n", + mhi_chan->chan); + kfree(buf_info->cb_buf); + } + } + } + break; + } /* CC_EOT */ + case MHI_EV_CC_OOB: + case MHI_EV_CC_DB_MODE: + { + unsigned long flags; + + mhi_chan->db_cfg.db_mode = 1; + read_lock_irqsave(&mhi_cntrl->pm_lock, flags); + if (tre_ring->wp != tre_ring->rp && + MHI_DB_ACCESS_VALID(mhi_cntrl)) { + mhi_ring_chan_db(mhi_cntrl, mhi_chan); + } + read_unlock_irqrestore(&mhi_cntrl->pm_lock, flags); + break; + } + case MHI_EV_CC_BAD_TRE: + default: + dev_err(dev, "Unknown event 0x%x\n", ev_code); + break; + } /* switch(MHI_EV_READ_CODE(EV_TRB_CODE,event)) */ + +end_process_tx_event: + if (ev_code >= MHI_EV_CC_OOB) + write_unlock_irqrestore(&mhi_chan->lock, flags); + else + read_unlock_bh(&mhi_chan->lock); + + return 0; +} + +static int parse_rsc_event(struct mhi_controller *mhi_cntrl, + struct mhi_tre *event, + struct mhi_chan *mhi_chan) +{ + struct mhi_ring *buf_ring, *tre_ring; + struct mhi_buf_info *buf_info; + struct mhi_result result; + int ev_code; + u32 cookie; /* offset to local descriptor */ + u16 xfer_len; + + buf_ring = &mhi_chan->buf_ring; + tre_ring = &mhi_chan->tre_ring; + + ev_code = MHI_TRE_GET_EV_CODE(event); + cookie = MHI_TRE_GET_EV_COOKIE(event); + xfer_len = MHI_TRE_GET_EV_LEN(event); + + /* Received out of bound cookie */ + WARN_ON(cookie >= buf_ring->len); + + buf_info = buf_ring->base + cookie; + + result.transaction_status = (ev_code == MHI_EV_CC_OVERFLOW) ? + -EOVERFLOW : 0; + result.bytes_xferd = xfer_len; + result.buf_addr = buf_info->cb_buf; + result.dir = mhi_chan->dir; + + read_lock_bh(&mhi_chan->lock); + + if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) + goto end_process_rsc_event; + + WARN_ON(!buf_info->used); + + /* notify the client */ + mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); + + /* + * Note: We're arbitrarily incrementing RP even though, completion + * packet we processed might not be the same one, reason we can do this + * is because device guaranteed to cache descriptors in order it + * receive, so even though completion event is different we can re-use + * all descriptors in between. + * Example: + * Transfer Ring has descriptors: A, B, C, D + * Last descriptor host queue is D (WP) and first descriptor + * host queue is A (RP). + * The completion event we just serviced is descriptor C. + * Then we can safely queue descriptors to replace A, B, and C + * even though host did not receive any completions. + */ + mhi_del_ring_element(mhi_cntrl, tre_ring); + buf_info->used = false; + +end_process_rsc_event: + read_unlock_bh(&mhi_chan->lock); + + return 0; +} + +static void mhi_process_cmd_completion(struct mhi_controller *mhi_cntrl, + struct mhi_tre *tre) +{ + dma_addr_t ptr = MHI_TRE_GET_EV_PTR(tre); + struct mhi_cmd *cmd_ring = &mhi_cntrl->mhi_cmd[PRIMARY_CMD_RING]; + struct mhi_ring *mhi_ring = &cmd_ring->ring; + struct mhi_tre *cmd_pkt; + struct mhi_chan *mhi_chan; + u32 chan; + + cmd_pkt = mhi_to_virtual(mhi_ring, ptr); + + chan = MHI_TRE_GET_CMD_CHID(cmd_pkt); + mhi_chan = &mhi_cntrl->mhi_chan[chan]; + write_lock_bh(&mhi_chan->lock); + mhi_chan->ccs = MHI_TRE_GET_EV_CODE(tre); + complete(&mhi_chan->completion); + write_unlock_bh(&mhi_chan->lock); + + mhi_del_ring_element(mhi_cntrl, mhi_ring); +} + +int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + u32 event_quota) +{ + struct mhi_tre *dev_rp, *local_rp; + struct mhi_ring *ev_ring = &mhi_event->ring; + struct mhi_event_ctxt *er_ctxt = + &mhi_cntrl->mhi_ctxt->er_ctxt[mhi_event->er_index]; + struct mhi_chan *mhi_chan; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + u32 chan; + int count = 0; + + /* + * This is a quick check to avoid unnecessary event processing + * in case MHI is already in error state, but it's still possible + * to transition to error state while processing events + */ + if (unlikely(MHI_EVENT_ACCESS_INVALID(mhi_cntrl->pm_state))) + return -EIO; + + dev_rp = mhi_to_virtual(ev_ring, er_ctxt->rp); + local_rp = ev_ring->rp; + + while (dev_rp != local_rp) { + enum mhi_pkt_type type = MHI_TRE_GET_EV_TYPE(local_rp); + + switch (type) { + case MHI_PKT_TYPE_BW_REQ_EVENT: + { + struct mhi_link_info *link_info; + + link_info = &mhi_cntrl->mhi_link_info; + write_lock_irq(&mhi_cntrl->pm_lock); + link_info->target_link_speed = + MHI_TRE_GET_EV_LINKSPEED(local_rp); + link_info->target_link_width = + MHI_TRE_GET_EV_LINKWIDTH(local_rp); + write_unlock_irq(&mhi_cntrl->pm_lock); + dev_dbg(dev, "Received BW_REQ event\n"); + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_BW_REQ); + break; + } + case MHI_PKT_TYPE_STATE_CHANGE_EVENT: + { + enum mhi_state new_state; + + new_state = MHI_TRE_GET_EV_STATE(local_rp); + + dev_dbg(dev, "State change event to state: %s\n", + TO_MHI_STATE_STR(new_state)); + + switch (new_state) { + case MHI_STATE_M0: + mhi_pm_m0_transition(mhi_cntrl); + break; + case MHI_STATE_M1: + mhi_pm_m1_transition(mhi_cntrl); + break; + case MHI_STATE_M3: + mhi_pm_m3_transition(mhi_cntrl); + break; + case MHI_STATE_SYS_ERR: + { + enum mhi_pm_state new_state; + + dev_dbg(dev, "System error detected\n"); + write_lock_irq(&mhi_cntrl->pm_lock); + new_state = mhi_tryset_pm_state(mhi_cntrl, + MHI_PM_SYS_ERR_DETECT); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (new_state == MHI_PM_SYS_ERR_DETECT) + schedule_work(&mhi_cntrl->syserr_worker); + break; + } + default: + dev_err(dev, "Invalid state: %s\n", + TO_MHI_STATE_STR(new_state)); + } + + break; + } + case MHI_PKT_TYPE_CMD_COMPLETION_EVENT: + mhi_process_cmd_completion(mhi_cntrl, local_rp); + break; + case MHI_PKT_TYPE_EE_EVENT: + { + enum dev_st_transition st = DEV_ST_TRANSITION_MAX; + enum mhi_ee_type event = MHI_TRE_GET_EV_EXECENV(local_rp); + + dev_dbg(dev, "Received EE event: %s\n", + TO_MHI_EXEC_STR(event)); + switch (event) { + case MHI_EE_SBL: + st = DEV_ST_TRANSITION_SBL; + break; + case MHI_EE_WFW: + case MHI_EE_AMSS: + st = DEV_ST_TRANSITION_MISSION_MODE; + break; + case MHI_EE_RDDM: + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM); + write_lock_irq(&mhi_cntrl->pm_lock); + mhi_cntrl->ee = event; + write_unlock_irq(&mhi_cntrl->pm_lock); + wake_up_all(&mhi_cntrl->state_event); + break; + default: + dev_err(dev, + "Unhandled EE event: 0x%x\n", type); + } + if (st != DEV_ST_TRANSITION_MAX) + mhi_queue_state_transition(mhi_cntrl, st); + + break; + } + case MHI_PKT_TYPE_TX_EVENT: + chan = MHI_TRE_GET_EV_CHID(local_rp); + mhi_chan = &mhi_cntrl->mhi_chan[chan]; + parse_xfer_event(mhi_cntrl, local_rp, mhi_chan); + event_quota--; + break; + default: + dev_err(dev, "Unhandled event type: %d\n", type); + break; + } + + mhi_recycle_ev_ring_element(mhi_cntrl, ev_ring); + local_rp = ev_ring->rp; + dev_rp = mhi_to_virtual(ev_ring, er_ctxt->rp); + count++; + } + + read_lock_bh(&mhi_cntrl->pm_lock); + if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl))) + mhi_ring_er_db(mhi_event); + read_unlock_bh(&mhi_cntrl->pm_lock); + + return count; +} + +int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + u32 event_quota) +{ + struct mhi_tre *dev_rp, *local_rp; + struct mhi_ring *ev_ring = &mhi_event->ring; + struct mhi_event_ctxt *er_ctxt = + &mhi_cntrl->mhi_ctxt->er_ctxt[mhi_event->er_index]; + int count = 0; + u32 chan; + struct mhi_chan *mhi_chan; + + if (unlikely(MHI_EVENT_ACCESS_INVALID(mhi_cntrl->pm_state))) + return -EIO; + + dev_rp = mhi_to_virtual(ev_ring, er_ctxt->rp); + local_rp = ev_ring->rp; + + while (dev_rp != local_rp && event_quota > 0) { + enum mhi_pkt_type type = MHI_TRE_GET_EV_TYPE(local_rp); + + chan = MHI_TRE_GET_EV_CHID(local_rp); + mhi_chan = &mhi_cntrl->mhi_chan[chan]; + + if (likely(type == MHI_PKT_TYPE_TX_EVENT)) { + parse_xfer_event(mhi_cntrl, local_rp, mhi_chan); + event_quota--; + } else if (type == MHI_PKT_TYPE_RSC_TX_EVENT) { + parse_rsc_event(mhi_cntrl, local_rp, mhi_chan); + event_quota--; + } + + mhi_recycle_ev_ring_element(mhi_cntrl, ev_ring); + local_rp = ev_ring->rp; + dev_rp = mhi_to_virtual(ev_ring, er_ctxt->rp); + count++; + } + read_lock_bh(&mhi_cntrl->pm_lock); + if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl))) + mhi_ring_er_db(mhi_event); + read_unlock_bh(&mhi_cntrl->pm_lock); + + return count; +} + +void mhi_ev_task(unsigned long data) +{ + struct mhi_event *mhi_event = (struct mhi_event *)data; + struct mhi_controller *mhi_cntrl = mhi_event->mhi_cntrl; + + /* process all pending events */ + spin_lock_bh(&mhi_event->lock); + mhi_event->process_event(mhi_cntrl, mhi_event, U32_MAX); + spin_unlock_bh(&mhi_event->lock); +} + +void mhi_ctrl_ev_task(unsigned long data) +{ + struct mhi_event *mhi_event = (struct mhi_event *)data; + struct mhi_controller *mhi_cntrl = mhi_event->mhi_cntrl; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + enum mhi_state state; + enum mhi_pm_state pm_state = 0; + int ret; + + /* + * We can check PM state w/o a lock here because there is no way + * PM state can change from reg access valid to no access while this + * thread being executed. + */ + if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) { + /* + * We may have a pending event but not allowed to + * process it since we are probably in a suspended state, + * so trigger a resume. + */ + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + + return; + } + + /* Process ctrl events events */ + ret = mhi_event->process_event(mhi_cntrl, mhi_event, U32_MAX); + + /* + * We received an IRQ but no events to process, maybe device went to + * SYS_ERR state? Check the state to confirm. + */ + if (!ret) { + write_lock_irq(&mhi_cntrl->pm_lock); + state = mhi_get_mhi_state(mhi_cntrl); + if (state == MHI_STATE_SYS_ERR) { + dev_dbg(dev, "System error detected\n"); + pm_state = mhi_tryset_pm_state(mhi_cntrl, + MHI_PM_SYS_ERR_DETECT); + } + write_unlock_irq(&mhi_cntrl->pm_lock); + if (pm_state == MHI_PM_SYS_ERR_DETECT) + schedule_work(&mhi_cntrl->syserr_worker); + } +} + +static bool mhi_is_ring_full(struct mhi_controller *mhi_cntrl, + struct mhi_ring *ring) +{ + void *tmp = ring->wp + ring->el_size; + + if (tmp >= (ring->base + ring->len)) + tmp = ring->base; + + return (tmp == ring->rp); +} + +int mhi_queue_skb(struct mhi_device *mhi_dev, enum dma_data_direction dir, + struct sk_buff *skb, size_t len, enum mhi_flags mflags) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan = (dir == DMA_TO_DEVICE) ? mhi_dev->ul_chan : + mhi_dev->dl_chan; + struct mhi_ring *tre_ring = &mhi_chan->tre_ring; + struct mhi_ring *buf_ring = &mhi_chan->buf_ring; + struct mhi_buf_info *buf_info; + struct mhi_tre *mhi_tre; + int ret; + + /* If MHI host pre-allocates buffers then client drivers cannot queue */ + if (mhi_chan->pre_alloc) + return -EINVAL; + + if (mhi_is_ring_full(mhi_cntrl, tre_ring)) + return -ENOMEM; + + read_lock_bh(&mhi_cntrl->pm_lock); + if (unlikely(MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))) { + read_unlock_bh(&mhi_cntrl->pm_lock); + return -EIO; + } + + /* we're in M3 or transitioning to M3 */ + if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) { + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + } + + /* Toggle wake to exit out of M2 */ + mhi_cntrl->wake_toggle(mhi_cntrl); + + /* Generate the TRE */ + buf_info = buf_ring->wp; + + buf_info->v_addr = skb->data; + buf_info->cb_buf = skb; + buf_info->wp = tre_ring->wp; + buf_info->dir = mhi_chan->dir; + buf_info->len = len; + ret = mhi_cntrl->map_single(mhi_cntrl, buf_info); + if (ret) + goto map_error; + + mhi_tre = tre_ring->wp; + + mhi_tre->ptr = MHI_TRE_DATA_PTR(buf_info->p_addr); + mhi_tre->dword[0] = MHI_TRE_DATA_DWORD0(buf_info->len); + mhi_tre->dword[1] = MHI_TRE_DATA_DWORD1(1, 1, 0, 0); + + /* increment WP */ + mhi_add_ring_element(mhi_cntrl, tre_ring); + mhi_add_ring_element(mhi_cntrl, buf_ring); + + if (mhi_chan->dir == DMA_TO_DEVICE) + atomic_inc(&mhi_cntrl->pending_pkts); + + if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl))) { + read_lock_bh(&mhi_chan->lock); + mhi_ring_chan_db(mhi_cntrl, mhi_chan); + read_unlock_bh(&mhi_chan->lock); + } + + read_unlock_bh(&mhi_cntrl->pm_lock); + + return 0; + +map_error: + read_unlock_bh(&mhi_cntrl->pm_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_queue_skb); + +int mhi_queue_dma(struct mhi_device *mhi_dev, enum dma_data_direction dir, + struct mhi_buf *mhi_buf, size_t len, enum mhi_flags mflags) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan = (dir == DMA_TO_DEVICE) ? mhi_dev->ul_chan : + mhi_dev->dl_chan; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + struct mhi_ring *tre_ring = &mhi_chan->tre_ring; + struct mhi_ring *buf_ring = &mhi_chan->buf_ring; + struct mhi_buf_info *buf_info; + struct mhi_tre *mhi_tre; + + /* If MHI host pre-allocates buffers then client drivers cannot queue */ + if (mhi_chan->pre_alloc) + return -EINVAL; + + if (mhi_is_ring_full(mhi_cntrl, tre_ring)) + return -ENOMEM; + + read_lock_bh(&mhi_cntrl->pm_lock); + if (unlikely(MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))) { + dev_err(dev, "MHI is not in activate state, PM state: %s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state)); + read_unlock_bh(&mhi_cntrl->pm_lock); + + return -EIO; + } + + /* we're in M3 or transitioning to M3 */ + if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) { + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + } + + /* Toggle wake to exit out of M2 */ + mhi_cntrl->wake_toggle(mhi_cntrl); + + /* Generate the TRE */ + buf_info = buf_ring->wp; + WARN_ON(buf_info->used); + buf_info->p_addr = mhi_buf->dma_addr; + buf_info->pre_mapped = true; + buf_info->cb_buf = mhi_buf; + buf_info->wp = tre_ring->wp; + buf_info->dir = mhi_chan->dir; + buf_info->len = len; + + mhi_tre = tre_ring->wp; + + mhi_tre->ptr = MHI_TRE_DATA_PTR(buf_info->p_addr); + mhi_tre->dword[0] = MHI_TRE_DATA_DWORD0(buf_info->len); + mhi_tre->dword[1] = MHI_TRE_DATA_DWORD1(1, 1, 0, 0); + + /* increment WP */ + mhi_add_ring_element(mhi_cntrl, tre_ring); + mhi_add_ring_element(mhi_cntrl, buf_ring); + + if (mhi_chan->dir == DMA_TO_DEVICE) + atomic_inc(&mhi_cntrl->pending_pkts); + + if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl))) { + read_lock_bh(&mhi_chan->lock); + mhi_ring_chan_db(mhi_cntrl, mhi_chan); + read_unlock_bh(&mhi_chan->lock); + } + + read_unlock_bh(&mhi_cntrl->pm_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(mhi_queue_dma); + +int mhi_gen_tre(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan, + void *buf, void *cb, size_t buf_len, enum mhi_flags flags) +{ + struct mhi_ring *buf_ring, *tre_ring; + struct mhi_tre *mhi_tre; + struct mhi_buf_info *buf_info; + int eot, eob, chain, bei; + int ret; + + buf_ring = &mhi_chan->buf_ring; + tre_ring = &mhi_chan->tre_ring; + + buf_info = buf_ring->wp; + buf_info->v_addr = buf; + buf_info->cb_buf = cb; + buf_info->wp = tre_ring->wp; + buf_info->dir = mhi_chan->dir; + buf_info->len = buf_len; + + ret = mhi_cntrl->map_single(mhi_cntrl, buf_info); + if (ret) + return ret; + + eob = !!(flags & MHI_EOB); + eot = !!(flags & MHI_EOT); + chain = !!(flags & MHI_CHAIN); + bei = !!(mhi_chan->intmod); + + mhi_tre = tre_ring->wp; + mhi_tre->ptr = MHI_TRE_DATA_PTR(buf_info->p_addr); + mhi_tre->dword[0] = MHI_TRE_DATA_DWORD0(buf_len); + mhi_tre->dword[1] = MHI_TRE_DATA_DWORD1(bei, eot, eob, chain); + + /* increment WP */ + mhi_add_ring_element(mhi_cntrl, tre_ring); + mhi_add_ring_element(mhi_cntrl, buf_ring); + + return 0; +} + +int mhi_queue_buf(struct mhi_device *mhi_dev, enum dma_data_direction dir, + void *buf, size_t len, enum mhi_flags mflags) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan = (dir == DMA_TO_DEVICE) ? mhi_dev->ul_chan : + mhi_dev->dl_chan; + struct mhi_ring *tre_ring; + unsigned long flags; + int ret; + + /* + * this check here only as a guard, it's always + * possible mhi can enter error while executing rest of function, + * which is not fatal so we do not need to hold pm_lock + */ + if (unlikely(MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))) + return -EIO; + + tre_ring = &mhi_chan->tre_ring; + if (mhi_is_ring_full(mhi_cntrl, tre_ring)) + return -ENOMEM; + + ret = mhi_gen_tre(mhi_cntrl, mhi_chan, buf, buf, len, mflags); + if (unlikely(ret)) + return ret; + + read_lock_irqsave(&mhi_cntrl->pm_lock, flags); + + /* we're in M3 or transitioning to M3 */ + if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) { + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + } + + /* Toggle wake to exit out of M2 */ + mhi_cntrl->wake_toggle(mhi_cntrl); + + if (mhi_chan->dir == DMA_TO_DEVICE) + atomic_inc(&mhi_cntrl->pending_pkts); + + if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl))) { + unsigned long flags; + + read_lock_irqsave(&mhi_chan->lock, flags); + mhi_ring_chan_db(mhi_cntrl, mhi_chan); + read_unlock_irqrestore(&mhi_chan->lock, flags); + } + + read_unlock_irqrestore(&mhi_cntrl->pm_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(mhi_queue_buf); + +int mhi_send_cmd(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan, + enum mhi_cmd_type cmd) +{ + struct mhi_tre *cmd_tre = NULL; + struct mhi_cmd *mhi_cmd = &mhi_cntrl->mhi_cmd[PRIMARY_CMD_RING]; + struct mhi_ring *ring = &mhi_cmd->ring; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int chan = 0; + + if (mhi_chan) + chan = mhi_chan->chan; + + spin_lock_bh(&mhi_cmd->lock); + if (!get_nr_avail_ring_elements(mhi_cntrl, ring)) { + spin_unlock_bh(&mhi_cmd->lock); + return -ENOMEM; + } + + /* prepare the cmd tre */ + cmd_tre = ring->wp; + switch (cmd) { + case MHI_CMD_RESET_CHAN: + cmd_tre->ptr = MHI_TRE_CMD_RESET_PTR; + cmd_tre->dword[0] = MHI_TRE_CMD_RESET_DWORD0; + cmd_tre->dword[1] = MHI_TRE_CMD_RESET_DWORD1(chan); + break; + case MHI_CMD_START_CHAN: + cmd_tre->ptr = MHI_TRE_CMD_START_PTR; + cmd_tre->dword[0] = MHI_TRE_CMD_START_DWORD0; + cmd_tre->dword[1] = MHI_TRE_CMD_START_DWORD1(chan); + break; + default: + dev_err(dev, "Command not supported\n"); + break; + } + + /* queue to hardware */ + mhi_add_ring_element(mhi_cntrl, ring); + read_lock_bh(&mhi_cntrl->pm_lock); + if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl))) + mhi_ring_cmd_db(mhi_cntrl, mhi_cmd); + read_unlock_bh(&mhi_cntrl->pm_lock); + spin_unlock_bh(&mhi_cmd->lock); + + return 0; +} + +static void __mhi_unprepare_channel(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan) +{ + int ret; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + dev_dbg(dev, "Entered: unprepare channel:%d\n", mhi_chan->chan); + + /* no more processing events for this channel */ + mutex_lock(&mhi_chan->mutex); + write_lock_irq(&mhi_chan->lock); + if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) { + write_unlock_irq(&mhi_chan->lock); + mutex_unlock(&mhi_chan->mutex); + return; + } + + mhi_chan->ch_state = MHI_CH_STATE_DISABLED; + write_unlock_irq(&mhi_chan->lock); + + reinit_completion(&mhi_chan->completion); + read_lock_bh(&mhi_cntrl->pm_lock); + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + read_unlock_bh(&mhi_cntrl->pm_lock); + goto error_invalid_state; + } + + mhi_cntrl->wake_toggle(mhi_cntrl); + read_unlock_bh(&mhi_cntrl->pm_lock); + + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + ret = mhi_send_cmd(mhi_cntrl, mhi_chan, MHI_CMD_RESET_CHAN); + if (ret) + goto error_invalid_state; + + /* even if it fails we will still reset */ + ret = wait_for_completion_timeout(&mhi_chan->completion, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + if (!ret || mhi_chan->ccs != MHI_EV_CC_SUCCESS) + dev_err(dev, + "Failed to receive cmd completion, still resetting\n"); + +error_invalid_state: + if (!mhi_chan->offload_ch) { + mhi_reset_chan(mhi_cntrl, mhi_chan); + mhi_deinit_chan_ctxt(mhi_cntrl, mhi_chan); + } + dev_dbg(dev, "chan:%d successfully resetted\n", mhi_chan->chan); + mutex_unlock(&mhi_chan->mutex); +} + +int mhi_prepare_channel(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan) +{ + int ret = 0; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + dev_dbg(dev, "Preparing channel: %d\n", mhi_chan->chan); + + if (!(BIT(mhi_cntrl->ee) & mhi_chan->ee_mask)) { + dev_err(dev, + "Current EE: %s Required EE Mask: 0x%x for chan: %s\n", + TO_MHI_EXEC_STR(mhi_cntrl->ee), mhi_chan->ee_mask, + mhi_chan->name); + return -ENOTCONN; + } + + mutex_lock(&mhi_chan->mutex); + + /* If channel is not in disable state, do not allow it to start */ + if (mhi_chan->ch_state != MHI_CH_STATE_DISABLED) { + ret = -EIO; + dev_dbg(dev, "channel: %d is not in disabled state\n", + mhi_chan->chan); + goto error_init_chan; + } + + /* Check of client manages channel context for offload channels */ + if (!mhi_chan->offload_ch) { + ret = mhi_init_chan_ctxt(mhi_cntrl, mhi_chan); + if (ret) + goto error_init_chan; + } + + reinit_completion(&mhi_chan->completion); + read_lock_bh(&mhi_cntrl->pm_lock); + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + read_unlock_bh(&mhi_cntrl->pm_lock); + ret = -EIO; + goto error_pm_state; + } + + mhi_cntrl->wake_toggle(mhi_cntrl); + read_unlock_bh(&mhi_cntrl->pm_lock); + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + + ret = mhi_send_cmd(mhi_cntrl, mhi_chan, MHI_CMD_START_CHAN); + if (ret) + goto error_pm_state; + + ret = wait_for_completion_timeout(&mhi_chan->completion, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + if (!ret || mhi_chan->ccs != MHI_EV_CC_SUCCESS) { + ret = -EIO; + goto error_pm_state; + } + + write_lock_irq(&mhi_chan->lock); + mhi_chan->ch_state = MHI_CH_STATE_ENABLED; + write_unlock_irq(&mhi_chan->lock); + + /* Pre-allocate buffer for xfer ring */ + if (mhi_chan->pre_alloc) { + int nr_el = get_nr_avail_ring_elements(mhi_cntrl, + &mhi_chan->tre_ring); + size_t len = mhi_cntrl->buffer_len; + + while (nr_el--) { + void *buf; + + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto error_pre_alloc; + } + + /* Prepare transfer descriptors */ + ret = mhi_gen_tre(mhi_cntrl, mhi_chan, buf, buf, + len, MHI_EOT); + if (ret) { + kfree(buf); + goto error_pre_alloc; + } + } + + read_lock_bh(&mhi_cntrl->pm_lock); + if (MHI_DB_ACCESS_VALID(mhi_cntrl)) { + read_lock_irq(&mhi_chan->lock); + mhi_ring_chan_db(mhi_cntrl, mhi_chan); + read_unlock_irq(&mhi_chan->lock); + } + read_unlock_bh(&mhi_cntrl->pm_lock); + } + + mutex_unlock(&mhi_chan->mutex); + + dev_dbg(dev, "Chan: %d successfully moved to start state\n", + mhi_chan->chan); + + return 0; + +error_pm_state: + if (!mhi_chan->offload_ch) + mhi_deinit_chan_ctxt(mhi_cntrl, mhi_chan); + +error_init_chan: + mutex_unlock(&mhi_chan->mutex); + + return ret; + +error_pre_alloc: + mutex_unlock(&mhi_chan->mutex); + __mhi_unprepare_channel(mhi_cntrl, mhi_chan); + + return ret; +} + +static void mhi_mark_stale_events(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + struct mhi_event_ctxt *er_ctxt, + int chan) + +{ + struct mhi_tre *dev_rp, *local_rp; + struct mhi_ring *ev_ring; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + unsigned long flags; + + dev_dbg(dev, "Marking all events for chan: %d as stale\n", chan); + + ev_ring = &mhi_event->ring; + + /* mark all stale events related to channel as STALE event */ + spin_lock_irqsave(&mhi_event->lock, flags); + dev_rp = mhi_to_virtual(ev_ring, er_ctxt->rp); + + local_rp = ev_ring->rp; + while (dev_rp != local_rp) { + if (MHI_TRE_GET_EV_TYPE(local_rp) == MHI_PKT_TYPE_TX_EVENT && + chan == MHI_TRE_GET_EV_CHID(local_rp)) + local_rp->dword[1] = MHI_TRE_EV_DWORD1(chan, + MHI_PKT_TYPE_STALE_EVENT); + local_rp++; + if (local_rp == (ev_ring->base + ev_ring->len)) + local_rp = ev_ring->base; + } + + dev_dbg(dev, "Finished marking events as stale events\n"); + spin_unlock_irqrestore(&mhi_event->lock, flags); +} + +static void mhi_reset_data_chan(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan) +{ + struct mhi_ring *buf_ring, *tre_ring; + struct mhi_result result; + + /* Reset any pending buffers */ + buf_ring = &mhi_chan->buf_ring; + tre_ring = &mhi_chan->tre_ring; + result.transaction_status = -ENOTCONN; + result.bytes_xferd = 0; + while (tre_ring->rp != tre_ring->wp) { + struct mhi_buf_info *buf_info = buf_ring->rp; + + if (mhi_chan->dir == DMA_TO_DEVICE) + atomic_dec(&mhi_cntrl->pending_pkts); + + if (!buf_info->pre_mapped) + mhi_cntrl->unmap_single(mhi_cntrl, buf_info); + + mhi_del_ring_element(mhi_cntrl, buf_ring); + mhi_del_ring_element(mhi_cntrl, tre_ring); + + if (mhi_chan->pre_alloc) { + kfree(buf_info->cb_buf); + } else { + result.buf_addr = buf_info->cb_buf; + mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); + } + } +} + +void mhi_reset_chan(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan) +{ + struct mhi_event *mhi_event; + struct mhi_event_ctxt *er_ctxt; + int chan = mhi_chan->chan; + + /* Nothing to reset, client doesn't queue buffers */ + if (mhi_chan->offload_ch) + return; + + read_lock_bh(&mhi_cntrl->pm_lock); + mhi_event = &mhi_cntrl->mhi_event[mhi_chan->er_index]; + er_ctxt = &mhi_cntrl->mhi_ctxt->er_ctxt[mhi_chan->er_index]; + + mhi_mark_stale_events(mhi_cntrl, mhi_event, er_ctxt, chan); + + mhi_reset_data_chan(mhi_cntrl, mhi_chan); + + read_unlock_bh(&mhi_cntrl->pm_lock); +} + +/* Move channel to start state */ +int mhi_prepare_for_transfer(struct mhi_device *mhi_dev) +{ + int ret, dir; + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan; + + for (dir = 0; dir < 2; dir++) { + mhi_chan = dir ? mhi_dev->dl_chan : mhi_dev->ul_chan; + if (!mhi_chan) + continue; + + ret = mhi_prepare_channel(mhi_cntrl, mhi_chan); + if (ret) + goto error_open_chan; + } + + return 0; + +error_open_chan: + for (--dir; dir >= 0; dir--) { + mhi_chan = dir ? mhi_dev->dl_chan : mhi_dev->ul_chan; + if (!mhi_chan) + continue; + + __mhi_unprepare_channel(mhi_cntrl, mhi_chan); + } + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_prepare_for_transfer); + +void mhi_unprepare_from_transfer(struct mhi_device *mhi_dev) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan; + int dir; + + for (dir = 0; dir < 2; dir++) { + mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; + if (!mhi_chan) + continue; + + __mhi_unprepare_channel(mhi_cntrl, mhi_chan); + } +} +EXPORT_SYMBOL_GPL(mhi_unprepare_from_transfer); + +int mhi_poll(struct mhi_device *mhi_dev, u32 budget) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct mhi_chan *mhi_chan = mhi_dev->dl_chan; + struct mhi_event *mhi_event = &mhi_cntrl->mhi_event[mhi_chan->er_index]; + int ret; + + spin_lock_bh(&mhi_event->lock); + ret = mhi_event->process_event(mhi_cntrl, mhi_event, budget); + spin_unlock_bh(&mhi_event->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_poll); diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c new file mode 100644 index 000000000000..52690cb5c89c --- /dev/null +++ b/drivers/bus/mhi/core/pm.c @@ -0,0 +1,969 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mhi.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include "internal.h" + +/* + * Not all MHI state transitions are synchronous. Transitions like Linkdown, + * SYS_ERR, and shutdown can happen anytime asynchronously. This function will + * transition to a new state only if we're allowed to. + * + * Priority increases as we go down. For instance, from any state in L0, the + * transition can be made to states in L1, L2 and L3. A notable exception to + * this rule is state DISABLE. From DISABLE state we can only transition to + * POR state. Also, while in L2 state, user cannot jump back to previous + * L1 or L0 states. + * + * Valid transitions: + * L0: DISABLE <--> POR + * POR <--> POR + * POR -> M0 -> M2 --> M0 + * POR -> FW_DL_ERR + * FW_DL_ERR <--> FW_DL_ERR + * M0 <--> M0 + * M0 -> FW_DL_ERR + * M0 -> M3_ENTER -> M3 -> M3_EXIT --> M0 + * L1: SYS_ERR_DETECT -> SYS_ERR_PROCESS --> POR + * L2: SHUTDOWN_PROCESS -> DISABLE + * L3: LD_ERR_FATAL_DETECT <--> LD_ERR_FATAL_DETECT + * LD_ERR_FATAL_DETECT -> SHUTDOWN_PROCESS + */ +static struct mhi_pm_transitions const dev_state_transitions[] = { + /* L0 States */ + { + MHI_PM_DISABLE, + MHI_PM_POR + }, + { + MHI_PM_POR, + MHI_PM_POR | MHI_PM_DISABLE | MHI_PM_M0 | + MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_FW_DL_ERR + }, + { + MHI_PM_M0, + MHI_PM_M0 | MHI_PM_M2 | MHI_PM_M3_ENTER | + MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_FW_DL_ERR + }, + { + MHI_PM_M2, + MHI_PM_M0 | MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT + }, + { + MHI_PM_M3_ENTER, + MHI_PM_M3 | MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT + }, + { + MHI_PM_M3, + MHI_PM_M3_EXIT | MHI_PM_SYS_ERR_DETECT | + MHI_PM_SHUTDOWN_PROCESS | MHI_PM_LD_ERR_FATAL_DETECT + }, + { + MHI_PM_M3_EXIT, + MHI_PM_M0 | MHI_PM_SYS_ERR_DETECT | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT + }, + { + MHI_PM_FW_DL_ERR, + MHI_PM_FW_DL_ERR | MHI_PM_SYS_ERR_DETECT | + MHI_PM_SHUTDOWN_PROCESS | MHI_PM_LD_ERR_FATAL_DETECT + }, + /* L1 States */ + { + MHI_PM_SYS_ERR_DETECT, + MHI_PM_SYS_ERR_PROCESS | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT + }, + { + MHI_PM_SYS_ERR_PROCESS, + MHI_PM_POR | MHI_PM_SHUTDOWN_PROCESS | + MHI_PM_LD_ERR_FATAL_DETECT + }, + /* L2 States */ + { + MHI_PM_SHUTDOWN_PROCESS, + MHI_PM_DISABLE | MHI_PM_LD_ERR_FATAL_DETECT + }, + /* L3 States */ + { + MHI_PM_LD_ERR_FATAL_DETECT, + MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_SHUTDOWN_PROCESS + }, +}; + +enum mhi_pm_state __must_check mhi_tryset_pm_state(struct mhi_controller *mhi_cntrl, + enum mhi_pm_state state) +{ + unsigned long cur_state = mhi_cntrl->pm_state; + int index = find_last_bit(&cur_state, 32); + + if (unlikely(index >= ARRAY_SIZE(dev_state_transitions))) + return cur_state; + + if (unlikely(dev_state_transitions[index].from_state != cur_state)) + return cur_state; + + if (unlikely(!(dev_state_transitions[index].to_states & state))) + return cur_state; + + mhi_cntrl->pm_state = state; + return mhi_cntrl->pm_state; +} + +void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl, enum mhi_state state) +{ + if (state == MHI_STATE_RESET) { + mhi_write_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL, + MHICTRL_RESET_MASK, MHICTRL_RESET_SHIFT, 1); + } else { + mhi_write_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL, + MHICTRL_MHISTATE_MASK, + MHICTRL_MHISTATE_SHIFT, state); + } +} + +/* NOP for backward compatibility, host allowed to ring DB in M2 state */ +static void mhi_toggle_dev_wake_nop(struct mhi_controller *mhi_cntrl) +{ +} + +static void mhi_toggle_dev_wake(struct mhi_controller *mhi_cntrl) +{ + mhi_cntrl->wake_get(mhi_cntrl, false); + mhi_cntrl->wake_put(mhi_cntrl, true); +} + +/* Handle device ready state transition */ +int mhi_ready_state_transition(struct mhi_controller *mhi_cntrl) +{ + void __iomem *base = mhi_cntrl->regs; + struct mhi_event *mhi_event; + enum mhi_pm_state cur_state; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + u32 reset = 1, ready = 0; + int ret, i; + + /* Wait for RESET to be cleared and READY bit to be set by the device */ + wait_event_timeout(mhi_cntrl->state_event, + MHI_PM_IN_FATAL_STATE(mhi_cntrl->pm_state) || + mhi_read_reg_field(mhi_cntrl, base, MHICTRL, + MHICTRL_RESET_MASK, + MHICTRL_RESET_SHIFT, &reset) || + mhi_read_reg_field(mhi_cntrl, base, MHISTATUS, + MHISTATUS_READY_MASK, + MHISTATUS_READY_SHIFT, &ready) || + (!reset && ready), + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + /* Check if device entered error state */ + if (MHI_PM_IN_FATAL_STATE(mhi_cntrl->pm_state)) { + dev_err(dev, "Device link is not accessible\n"); + return -EIO; + } + + /* Timeout if device did not transition to ready state */ + if (reset || !ready) { + dev_err(dev, "Device Ready timeout\n"); + return -ETIMEDOUT; + } + + dev_dbg(dev, "Device in READY State\n"); + write_lock_irq(&mhi_cntrl->pm_lock); + cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_POR); + mhi_cntrl->dev_state = MHI_STATE_READY; + write_unlock_irq(&mhi_cntrl->pm_lock); + + if (cur_state != MHI_PM_POR) { + dev_err(dev, "Error moving to state %s from %s\n", + to_mhi_pm_state_str(MHI_PM_POR), + to_mhi_pm_state_str(cur_state)); + return -EIO; + } + + read_lock_bh(&mhi_cntrl->pm_lock); + if (!MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) { + dev_err(dev, "Device registers not accessible\n"); + goto error_mmio; + } + + /* Configure MMIO registers */ + ret = mhi_init_mmio(mhi_cntrl); + if (ret) { + dev_err(dev, "Error configuring MMIO registers\n"); + goto error_mmio; + } + + /* Add elements to all SW event rings */ + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + struct mhi_ring *ring = &mhi_event->ring; + + /* Skip if this is an offload or HW event */ + if (mhi_event->offload_ev || mhi_event->hw_ring) + continue; + + ring->wp = ring->base + ring->len - ring->el_size; + *ring->ctxt_wp = ring->iommu_base + ring->len - ring->el_size; + /* Update all cores */ + smp_wmb(); + + /* Ring the event ring db */ + spin_lock_irq(&mhi_event->lock); + mhi_ring_er_db(mhi_event); + spin_unlock_irq(&mhi_event->lock); + } + + /* Set MHI to M0 state */ + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M0); + read_unlock_bh(&mhi_cntrl->pm_lock); + + return 0; + +error_mmio: + read_unlock_bh(&mhi_cntrl->pm_lock); + + return -EIO; +} + +int mhi_pm_m0_transition(struct mhi_controller *mhi_cntrl) +{ + enum mhi_pm_state cur_state; + struct mhi_chan *mhi_chan; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int i; + + write_lock_irq(&mhi_cntrl->pm_lock); + mhi_cntrl->dev_state = MHI_STATE_M0; + cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M0); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (unlikely(cur_state != MHI_PM_M0)) { + dev_err(dev, "Unable to transition to M0 state\n"); + return -EIO; + } + + /* Wake up the device */ + read_lock_bh(&mhi_cntrl->pm_lock); + mhi_cntrl->wake_get(mhi_cntrl, true); + + /* Ring all event rings and CMD ring only if we're in mission mode */ + if (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) { + struct mhi_event *mhi_event = mhi_cntrl->mhi_event; + struct mhi_cmd *mhi_cmd = + &mhi_cntrl->mhi_cmd[PRIMARY_CMD_RING]; + + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + + spin_lock_irq(&mhi_event->lock); + mhi_ring_er_db(mhi_event); + spin_unlock_irq(&mhi_event->lock); + } + + /* Only ring primary cmd ring if ring is not empty */ + spin_lock_irq(&mhi_cmd->lock); + if (mhi_cmd->ring.rp != mhi_cmd->ring.wp) + mhi_ring_cmd_db(mhi_cntrl, mhi_cmd); + spin_unlock_irq(&mhi_cmd->lock); + } + + /* Ring channel DB registers */ + mhi_chan = mhi_cntrl->mhi_chan; + for (i = 0; i < mhi_cntrl->max_chan; i++, mhi_chan++) { + struct mhi_ring *tre_ring = &mhi_chan->tre_ring; + + write_lock_irq(&mhi_chan->lock); + if (mhi_chan->db_cfg.reset_req) + mhi_chan->db_cfg.db_mode = true; + + /* Only ring DB if ring is not empty */ + if (tre_ring->base && tre_ring->wp != tre_ring->rp) + mhi_ring_chan_db(mhi_cntrl, mhi_chan); + write_unlock_irq(&mhi_chan->lock); + } + + mhi_cntrl->wake_put(mhi_cntrl, false); + read_unlock_bh(&mhi_cntrl->pm_lock); + wake_up_all(&mhi_cntrl->state_event); + + return 0; +} + +/* + * After receiving the MHI state change event from the device indicating the + * transition to M1 state, the host can transition the device to M2 state + * for keeping it in low power state. + */ +void mhi_pm_m1_transition(struct mhi_controller *mhi_cntrl) +{ + enum mhi_pm_state state; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + write_lock_irq(&mhi_cntrl->pm_lock); + state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M2); + if (state == MHI_PM_M2) { + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M2); + mhi_cntrl->dev_state = MHI_STATE_M2; + + write_unlock_irq(&mhi_cntrl->pm_lock); + wake_up_all(&mhi_cntrl->state_event); + + /* If there are any pending resources, exit M2 immediately */ + if (unlikely(atomic_read(&mhi_cntrl->pending_pkts) || + atomic_read(&mhi_cntrl->dev_wake))) { + dev_dbg(dev, + "Exiting M2, pending_pkts: %d dev_wake: %d\n", + atomic_read(&mhi_cntrl->pending_pkts), + atomic_read(&mhi_cntrl->dev_wake)); + read_lock_bh(&mhi_cntrl->pm_lock); + mhi_cntrl->wake_get(mhi_cntrl, true); + mhi_cntrl->wake_put(mhi_cntrl, true); + read_unlock_bh(&mhi_cntrl->pm_lock); + } else { + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_IDLE); + } + } else { + write_unlock_irq(&mhi_cntrl->pm_lock); + } +} + +/* MHI M3 completion handler */ +int mhi_pm_m3_transition(struct mhi_controller *mhi_cntrl) +{ + enum mhi_pm_state state; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + write_lock_irq(&mhi_cntrl->pm_lock); + mhi_cntrl->dev_state = MHI_STATE_M3; + state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (state != MHI_PM_M3) { + dev_err(dev, "Unable to transition to M3 state\n"); + return -EIO; + } + + wake_up_all(&mhi_cntrl->state_event); + + return 0; +} + +/* Handle device Mission Mode transition */ +static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl) +{ + struct mhi_event *mhi_event; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int i, ret; + + dev_dbg(dev, "Processing Mission Mode transition\n"); + + write_lock_irq(&mhi_cntrl->pm_lock); + if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) + mhi_cntrl->ee = mhi_get_exec_env(mhi_cntrl); + write_unlock_irq(&mhi_cntrl->pm_lock); + + if (!MHI_IN_MISSION_MODE(mhi_cntrl->ee)) + return -EIO; + + wake_up_all(&mhi_cntrl->state_event); + + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_MISSION_MODE); + + /* Force MHI to be in M0 state before continuing */ + ret = __mhi_device_get_sync(mhi_cntrl); + if (ret) + return ret; + + read_lock_bh(&mhi_cntrl->pm_lock); + + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + ret = -EIO; + goto error_mission_mode; + } + + /* Add elements to all HW event rings */ + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + struct mhi_ring *ring = &mhi_event->ring; + + if (mhi_event->offload_ev || !mhi_event->hw_ring) + continue; + + ring->wp = ring->base + ring->len - ring->el_size; + *ring->ctxt_wp = ring->iommu_base + ring->len - ring->el_size; + /* Update to all cores */ + smp_wmb(); + + spin_lock_irq(&mhi_event->lock); + if (MHI_DB_ACCESS_VALID(mhi_cntrl)) + mhi_ring_er_db(mhi_event); + spin_unlock_irq(&mhi_event->lock); + } + + read_unlock_bh(&mhi_cntrl->pm_lock); + + /* + * The MHI devices are only created when the client device switches its + * Execution Environment (EE) to either SBL or AMSS states + */ + mhi_create_devices(mhi_cntrl); + + read_lock_bh(&mhi_cntrl->pm_lock); + +error_mission_mode: + mhi_cntrl->wake_put(mhi_cntrl, false); + read_unlock_bh(&mhi_cntrl->pm_lock); + + return ret; +} + +/* Handle SYS_ERR and Shutdown transitions */ +static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, + enum mhi_pm_state transition_state) +{ + enum mhi_pm_state cur_state, prev_state; + struct mhi_event *mhi_event; + struct mhi_cmd_ctxt *cmd_ctxt; + struct mhi_cmd *mhi_cmd; + struct mhi_event_ctxt *er_ctxt; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int ret, i; + + dev_dbg(dev, "Transitioning from PM state: %s to: %s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state), + to_mhi_pm_state_str(transition_state)); + + /* We must notify MHI control driver so it can clean up first */ + if (transition_state == MHI_PM_SYS_ERR_PROCESS) { + /* + * If controller supports RDDM, we do not process + * SYS error state, instead we will jump directly + * to RDDM state + */ + if (mhi_cntrl->rddm_image) { + dev_dbg(dev, + "Controller supports RDDM, so skip SYS_ERR\n"); + return; + } + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_SYS_ERROR); + } + + mutex_lock(&mhi_cntrl->pm_mutex); + write_lock_irq(&mhi_cntrl->pm_lock); + prev_state = mhi_cntrl->pm_state; + cur_state = mhi_tryset_pm_state(mhi_cntrl, transition_state); + if (cur_state == transition_state) { + mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION; + mhi_cntrl->dev_state = MHI_STATE_RESET; + } + write_unlock_irq(&mhi_cntrl->pm_lock); + + /* Wake up threads waiting for state transition */ + wake_up_all(&mhi_cntrl->state_event); + + if (cur_state != transition_state) { + dev_err(dev, "Failed to transition to state: %s from: %s\n", + to_mhi_pm_state_str(transition_state), + to_mhi_pm_state_str(cur_state)); + mutex_unlock(&mhi_cntrl->pm_mutex); + return; + } + + /* Trigger MHI RESET so that the device will not access host memory */ + if (MHI_REG_ACCESS_VALID(prev_state)) { + u32 in_reset = -1; + unsigned long timeout = msecs_to_jiffies(mhi_cntrl->timeout_ms); + + dev_dbg(dev, "Triggering MHI Reset in device\n"); + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET); + + /* Wait for the reset bit to be cleared by the device */ + ret = wait_event_timeout(mhi_cntrl->state_event, + mhi_read_reg_field(mhi_cntrl, + mhi_cntrl->regs, + MHICTRL, + MHICTRL_RESET_MASK, + MHICTRL_RESET_SHIFT, + &in_reset) || + !in_reset, timeout); + if ((!ret || in_reset) && cur_state == MHI_PM_SYS_ERR_PROCESS) { + dev_err(dev, "Device failed to exit MHI Reset state\n"); + mutex_unlock(&mhi_cntrl->pm_mutex); + return; + } + + /* + * Device will clear BHI_INTVEC as a part of RESET processing, + * hence re-program it + */ + mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0); + } + + dev_dbg(dev, + "Waiting for all pending event ring processing to complete\n"); + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + tasklet_kill(&mhi_event->task); + } + + /* Release lock and wait for all pending threads to complete */ + mutex_unlock(&mhi_cntrl->pm_mutex); + dev_dbg(dev, "Waiting for all pending threads to complete\n"); + wake_up_all(&mhi_cntrl->state_event); + flush_work(&mhi_cntrl->st_worker); + flush_work(&mhi_cntrl->fw_worker); + + dev_dbg(dev, "Reset all active channels and remove MHI devices\n"); + device_for_each_child(mhi_cntrl->cntrl_dev, NULL, mhi_destroy_device); + + mutex_lock(&mhi_cntrl->pm_mutex); + + WARN_ON(atomic_read(&mhi_cntrl->dev_wake)); + WARN_ON(atomic_read(&mhi_cntrl->pending_pkts)); + + /* Reset the ev rings and cmd rings */ + dev_dbg(dev, "Resetting EV CTXT and CMD CTXT\n"); + mhi_cmd = mhi_cntrl->mhi_cmd; + cmd_ctxt = mhi_cntrl->mhi_ctxt->cmd_ctxt; + for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++, cmd_ctxt++) { + struct mhi_ring *ring = &mhi_cmd->ring; + + ring->rp = ring->base; + ring->wp = ring->base; + cmd_ctxt->rp = cmd_ctxt->rbase; + cmd_ctxt->wp = cmd_ctxt->rbase; + } + + mhi_event = mhi_cntrl->mhi_event; + er_ctxt = mhi_cntrl->mhi_ctxt->er_ctxt; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++, + mhi_event++) { + struct mhi_ring *ring = &mhi_event->ring; + + /* Skip offload events */ + if (mhi_event->offload_ev) + continue; + + ring->rp = ring->base; + ring->wp = ring->base; + er_ctxt->rp = er_ctxt->rbase; + er_ctxt->wp = er_ctxt->rbase; + } + + if (cur_state == MHI_PM_SYS_ERR_PROCESS) { + mhi_ready_state_transition(mhi_cntrl); + } else { + /* Move to disable state */ + write_lock_irq(&mhi_cntrl->pm_lock); + cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_DISABLE); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (unlikely(cur_state != MHI_PM_DISABLE)) + dev_err(dev, "Error moving from PM state: %s to: %s\n", + to_mhi_pm_state_str(cur_state), + to_mhi_pm_state_str(MHI_PM_DISABLE)); + } + + dev_dbg(dev, "Exiting with PM state: %s, MHI state: %s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state), + TO_MHI_STATE_STR(mhi_cntrl->dev_state)); + + mutex_unlock(&mhi_cntrl->pm_mutex); +} + +/* Queue a new work item and schedule work */ +int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl, + enum dev_st_transition state) +{ + struct state_transition *item = kmalloc(sizeof(*item), GFP_ATOMIC); + unsigned long flags; + + if (!item) + return -ENOMEM; + + item->state = state; + spin_lock_irqsave(&mhi_cntrl->transition_lock, flags); + list_add_tail(&item->node, &mhi_cntrl->transition_list); + spin_unlock_irqrestore(&mhi_cntrl->transition_lock, flags); + + schedule_work(&mhi_cntrl->st_worker); + + return 0; +} + +/* SYS_ERR worker */ +void mhi_pm_sys_err_worker(struct work_struct *work) +{ + struct mhi_controller *mhi_cntrl = container_of(work, + struct mhi_controller, + syserr_worker); + + mhi_pm_disable_transition(mhi_cntrl, MHI_PM_SYS_ERR_PROCESS); +} + +/* Device State Transition worker */ +void mhi_pm_st_worker(struct work_struct *work) +{ + struct state_transition *itr, *tmp; + LIST_HEAD(head); + struct mhi_controller *mhi_cntrl = container_of(work, + struct mhi_controller, + st_worker); + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + spin_lock_irq(&mhi_cntrl->transition_lock); + list_splice_tail_init(&mhi_cntrl->transition_list, &head); + spin_unlock_irq(&mhi_cntrl->transition_lock); + + list_for_each_entry_safe(itr, tmp, &head, node) { + list_del(&itr->node); + dev_dbg(dev, "Handling state transition: %s\n", + TO_DEV_STATE_TRANS_STR(itr->state)); + + switch (itr->state) { + case DEV_ST_TRANSITION_PBL: + write_lock_irq(&mhi_cntrl->pm_lock); + if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) + mhi_cntrl->ee = mhi_get_exec_env(mhi_cntrl); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (MHI_IN_PBL(mhi_cntrl->ee)) + wake_up_all(&mhi_cntrl->state_event); + break; + case DEV_ST_TRANSITION_SBL: + write_lock_irq(&mhi_cntrl->pm_lock); + mhi_cntrl->ee = MHI_EE_SBL; + write_unlock_irq(&mhi_cntrl->pm_lock); + /* + * The MHI devices are only created when the client + * device switches its Execution Environment (EE) to + * either SBL or AMSS states + */ + mhi_create_devices(mhi_cntrl); + break; + case DEV_ST_TRANSITION_MISSION_MODE: + mhi_pm_mission_mode_transition(mhi_cntrl); + break; + case DEV_ST_TRANSITION_READY: + mhi_ready_state_transition(mhi_cntrl); + break; + default: + break; + } + kfree(itr); + } +} + +int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl) +{ + int ret; + + /* Wake up the device */ + read_lock_bh(&mhi_cntrl->pm_lock); + mhi_cntrl->wake_get(mhi_cntrl, true); + if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) { + pm_wakeup_event(&mhi_cntrl->mhi_dev->dev, 0); + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + } + read_unlock_bh(&mhi_cntrl->pm_lock); + + ret = wait_event_timeout(mhi_cntrl->state_event, + mhi_cntrl->pm_state == MHI_PM_M0 || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + read_lock_bh(&mhi_cntrl->pm_lock); + mhi_cntrl->wake_put(mhi_cntrl, false); + read_unlock_bh(&mhi_cntrl->pm_lock); + return -EIO; + } + + return 0; +} + +/* Assert device wake db */ +static void mhi_assert_dev_wake(struct mhi_controller *mhi_cntrl, bool force) +{ + unsigned long flags; + + /* + * If force flag is set, then increment the wake count value and + * ring wake db + */ + if (unlikely(force)) { + spin_lock_irqsave(&mhi_cntrl->wlock, flags); + atomic_inc(&mhi_cntrl->dev_wake); + if (MHI_WAKE_DB_FORCE_SET_VALID(mhi_cntrl->pm_state) && + !mhi_cntrl->wake_set) { + mhi_write_db(mhi_cntrl, mhi_cntrl->wake_db, 1); + mhi_cntrl->wake_set = true; + } + spin_unlock_irqrestore(&mhi_cntrl->wlock, flags); + } else { + /* + * If resources are already requested, then just increment + * the wake count value and return + */ + if (likely(atomic_add_unless(&mhi_cntrl->dev_wake, 1, 0))) + return; + + spin_lock_irqsave(&mhi_cntrl->wlock, flags); + if ((atomic_inc_return(&mhi_cntrl->dev_wake) == 1) && + MHI_WAKE_DB_SET_VALID(mhi_cntrl->pm_state) && + !mhi_cntrl->wake_set) { + mhi_write_db(mhi_cntrl, mhi_cntrl->wake_db, 1); + mhi_cntrl->wake_set = true; + } + spin_unlock_irqrestore(&mhi_cntrl->wlock, flags); + } +} + +/* De-assert device wake db */ +static void mhi_deassert_dev_wake(struct mhi_controller *mhi_cntrl, + bool override) +{ + unsigned long flags; + + /* + * Only continue if there is a single resource, else just decrement + * and return + */ + if (likely(atomic_add_unless(&mhi_cntrl->dev_wake, -1, 1))) + return; + + spin_lock_irqsave(&mhi_cntrl->wlock, flags); + if ((atomic_dec_return(&mhi_cntrl->dev_wake) == 0) && + MHI_WAKE_DB_CLEAR_VALID(mhi_cntrl->pm_state) && !override && + mhi_cntrl->wake_set) { + mhi_write_db(mhi_cntrl, mhi_cntrl->wake_db, 0); + mhi_cntrl->wake_set = false; + } + spin_unlock_irqrestore(&mhi_cntrl->wlock, flags); +} + +int mhi_async_power_up(struct mhi_controller *mhi_cntrl) +{ + enum mhi_ee_type current_ee; + enum dev_st_transition next_state; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + u32 val; + int ret; + + dev_info(dev, "Requested to power ON\n"); + + if (mhi_cntrl->nr_irqs < mhi_cntrl->total_ev_rings) + return -EINVAL; + + /* Supply default wake routines if not provided by controller driver */ + if (!mhi_cntrl->wake_get || !mhi_cntrl->wake_put || + !mhi_cntrl->wake_toggle) { + mhi_cntrl->wake_get = mhi_assert_dev_wake; + mhi_cntrl->wake_put = mhi_deassert_dev_wake; + mhi_cntrl->wake_toggle = (mhi_cntrl->db_access & MHI_PM_M2) ? + mhi_toggle_dev_wake_nop : mhi_toggle_dev_wake; + } + + mutex_lock(&mhi_cntrl->pm_mutex); + mhi_cntrl->pm_state = MHI_PM_DISABLE; + + if (!mhi_cntrl->pre_init) { + /* Setup device context */ + ret = mhi_init_dev_ctxt(mhi_cntrl); + if (ret) + goto error_dev_ctxt; + } + + ret = mhi_init_irq_setup(mhi_cntrl); + if (ret) + goto error_setup_irq; + + /* Setup BHI offset & INTVEC */ + write_lock_irq(&mhi_cntrl->pm_lock); + ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, BHIOFF, &val); + if (ret) { + write_unlock_irq(&mhi_cntrl->pm_lock); + goto error_bhi_offset; + } + + mhi_cntrl->bhi = mhi_cntrl->regs + val; + + /* Setup BHIE offset */ + if (mhi_cntrl->fbc_download) { + ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, BHIEOFF, &val); + if (ret) { + write_unlock_irq(&mhi_cntrl->pm_lock); + dev_err(dev, "Error reading BHIE offset\n"); + goto error_bhi_offset; + } + + mhi_cntrl->bhie = mhi_cntrl->regs + val; + } + + mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0); + mhi_cntrl->pm_state = MHI_PM_POR; + mhi_cntrl->ee = MHI_EE_MAX; + current_ee = mhi_get_exec_env(mhi_cntrl); + write_unlock_irq(&mhi_cntrl->pm_lock); + + /* Confirm that the device is in valid exec env */ + if (!MHI_IN_PBL(current_ee) && current_ee != MHI_EE_AMSS) { + dev_err(dev, "Not a valid EE for power on\n"); + ret = -EIO; + goto error_bhi_offset; + } + + /* Transition to next state */ + next_state = MHI_IN_PBL(current_ee) ? + DEV_ST_TRANSITION_PBL : DEV_ST_TRANSITION_READY; + + if (next_state == DEV_ST_TRANSITION_PBL) + schedule_work(&mhi_cntrl->fw_worker); + + mhi_queue_state_transition(mhi_cntrl, next_state); + + mutex_unlock(&mhi_cntrl->pm_mutex); + + dev_info(dev, "Power on setup success\n"); + + return 0; + +error_bhi_offset: + mhi_deinit_free_irq(mhi_cntrl); + +error_setup_irq: + if (!mhi_cntrl->pre_init) + mhi_deinit_dev_ctxt(mhi_cntrl); + +error_dev_ctxt: + mutex_unlock(&mhi_cntrl->pm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_async_power_up); + +void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful) +{ + enum mhi_pm_state cur_state; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + + /* If it's not a graceful shutdown, force MHI to linkdown state */ + if (!graceful) { + mutex_lock(&mhi_cntrl->pm_mutex); + write_lock_irq(&mhi_cntrl->pm_lock); + cur_state = mhi_tryset_pm_state(mhi_cntrl, + MHI_PM_LD_ERR_FATAL_DETECT); + write_unlock_irq(&mhi_cntrl->pm_lock); + mutex_unlock(&mhi_cntrl->pm_mutex); + if (cur_state != MHI_PM_LD_ERR_FATAL_DETECT) + dev_dbg(dev, "Failed to move to state: %s from: %s\n", + to_mhi_pm_state_str(MHI_PM_LD_ERR_FATAL_DETECT), + to_mhi_pm_state_str(mhi_cntrl->pm_state)); + } + mhi_pm_disable_transition(mhi_cntrl, MHI_PM_SHUTDOWN_PROCESS); + mhi_deinit_free_irq(mhi_cntrl); + + if (!mhi_cntrl->pre_init) { + /* Free all allocated resources */ + if (mhi_cntrl->fbc_image) { + mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image); + mhi_cntrl->fbc_image = NULL; + } + mhi_deinit_dev_ctxt(mhi_cntrl); + } +} +EXPORT_SYMBOL_GPL(mhi_power_down); + +int mhi_sync_power_up(struct mhi_controller *mhi_cntrl) +{ + int ret = mhi_async_power_up(mhi_cntrl); + + if (ret) + return ret; + + wait_event_timeout(mhi_cntrl->state_event, + MHI_IN_MISSION_MODE(mhi_cntrl->ee) || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + + return (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) ? 0 : -EIO; +} +EXPORT_SYMBOL(mhi_sync_power_up); + +int mhi_force_rddm_mode(struct mhi_controller *mhi_cntrl) +{ + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int ret; + + /* Check if device is already in RDDM */ + if (mhi_cntrl->ee == MHI_EE_RDDM) + return 0; + + dev_dbg(dev, "Triggering SYS_ERR to force RDDM state\n"); + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_SYS_ERR); + + /* Wait for RDDM event */ + ret = wait_event_timeout(mhi_cntrl->state_event, + mhi_cntrl->ee == MHI_EE_RDDM, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + ret = ret ? 0 : -EIO; + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_force_rddm_mode); + +void mhi_device_get(struct mhi_device *mhi_dev) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + + mhi_dev->dev_wake++; + read_lock_bh(&mhi_cntrl->pm_lock); + mhi_cntrl->wake_get(mhi_cntrl, true); + read_unlock_bh(&mhi_cntrl->pm_lock); +} +EXPORT_SYMBOL_GPL(mhi_device_get); + +int mhi_device_get_sync(struct mhi_device *mhi_dev) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + int ret; + + ret = __mhi_device_get_sync(mhi_cntrl); + if (!ret) + mhi_dev->dev_wake++; + + return ret; +} +EXPORT_SYMBOL_GPL(mhi_device_get_sync); + +void mhi_device_put(struct mhi_device *mhi_dev) +{ + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + + mhi_dev->dev_wake--; + read_lock_bh(&mhi_cntrl->pm_lock); + if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) { + mhi_cntrl->runtime_get(mhi_cntrl); + mhi_cntrl->runtime_put(mhi_cntrl); + } + + mhi_cntrl->wake_put(mhi_cntrl, false); + read_unlock_bh(&mhi_cntrl->pm_lock); +} +EXPORT_SYMBOL_GPL(mhi_device_put); diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 26956c006987..fea084e0909b 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -7,28 +7,6 @@ menu "Character devices" source "drivers/tty/Kconfig" -config DEVMEM - bool "/dev/mem virtual device support" - default y - help - Say Y here if you want to support the /dev/mem device. - The /dev/mem device is used to access areas of physical - memory. - When in doubt, say "Y". - -config DEVKMEM - bool "/dev/kmem virtual device support" - # On arm64, VMALLOC_START < PAGE_OFFSET, which confuses kmem read/write - depends on !ARM64 - help - Say Y here if you want to support the /dev/kmem device. The - /dev/kmem device is rarely used, but can be used for certain - kind of kernel debugging operations. - When in doubt, say "N". - -source "drivers/tty/serial/Kconfig" -source "drivers/tty/serdev/Kconfig" - config TTY_PRINTK tristate "TTY driver to output user messages via printk" depends on EXPERT && TTY @@ -113,8 +91,6 @@ config PPDEV If unsure, say N. -source "drivers/tty/hvc/Kconfig" - config VIRTIO_CONSOLE tristate "Virtio console" depends on VIRTIO && TTY @@ -220,89 +196,6 @@ config NWFLASH source "drivers/char/hw_random/Kconfig" -config NVRAM - tristate "/dev/nvram support" - depends on X86 || HAVE_ARCH_NVRAM_OPS - default M68K || PPC - ---help--- - If you say Y here and create a character special file /dev/nvram - with major number 10 and minor number 144 using mknod ("man mknod"), - you get read and write access to the non-volatile memory. - - /dev/nvram may be used to view settings in NVRAM or to change them - (with some utility). It could also be used to frequently - save a few bits of very important data that may not be lost over - power-off and for which writing to disk is too insecure. Note - however that most NVRAM space in a PC belongs to the BIOS and you - should NEVER idly tamper with it. See Ralf Brown's interrupt list - for a guide to the use of CMOS bytes by your BIOS. - - This memory is conventionally called "NVRAM" on PowerPC machines, - "CMOS RAM" on PCs, "NVRAM" on Ataris and "PRAM" on Macintoshes. - - To compile this driver as a module, choose M here: the - module will be called nvram. - -# -# These legacy RTC drivers just cause too many conflicts with the generic -# RTC framework ... let's not even try to coexist any more. -# -if RTC_LIB=n - -config RTC - tristate "Enhanced Real Time Clock Support (legacy PC RTC driver)" - depends on ALPHA - ---help--- - If you say Y here and create a character special file /dev/rtc with - major number 10 and minor number 135 using mknod ("man mknod"), you - will get access to the real time clock (or hardware clock) built - into your computer. - - Every PC has such a clock built in. It can be used to generate - signals from as low as 1Hz up to 8192Hz, and can also be used - as a 24 hour alarm. It reports status information via the file - /proc/driver/rtc and its behaviour is set by various ioctls on - /dev/rtc. - - If you run Linux on a multiprocessor machine and said Y to - "Symmetric Multi Processing" above, you should say Y here to read - and set the RTC in an SMP compatible fashion. - - If you think you have a use for such a device (such as periodic data - sampling), then say Y here, and read <file:Documentation/admin-guide/rtc.rst> - for details. - - To compile this driver as a module, choose M here: the - module will be called rtc. - -config JS_RTC - tristate "Enhanced Real Time Clock Support" - depends on SPARC32 && PCI - ---help--- - If you say Y here and create a character special file /dev/rtc with - major number 10 and minor number 135 using mknod ("man mknod"), you - will get access to the real time clock (or hardware clock) built - into your computer. - - Every PC has such a clock built in. It can be used to generate - signals from as low as 1Hz up to 8192Hz, and can also be used - as a 24 hour alarm. It reports status information via the file - /proc/driver/rtc and its behaviour is set by various ioctls on - /dev/rtc. - - If you think you have a use for such a device (such as periodic data - sampling), then say Y here, and read <file:Documentation/admin-guide/rtc.rst> - for details. - - To compile this driver as a module, choose M here: the - module will be called js-rtc. - -config EFI_RTC - bool "EFI Real Time Clock Services" - depends on IA64 - -endif # RTC_LIB - config DTLK tristate "Double Talk PC internal speech card support" depends on ISA @@ -431,6 +324,48 @@ config NSC_GPIO pc8736x_gpio drivers. If those drivers are built as modules, this one will be too, named nsc_gpio +config DEVMEM + bool "/dev/mem virtual device support" + default y + help + Say Y here if you want to support the /dev/mem device. + The /dev/mem device is used to access areas of physical + memory. + When in doubt, say "Y". + +config DEVKMEM + bool "/dev/kmem virtual device support" + # On arm64, VMALLOC_START < PAGE_OFFSET, which confuses kmem read/write + depends on !ARM64 + help + Say Y here if you want to support the /dev/kmem device. The + /dev/kmem device is rarely used, but can be used for certain + kind of kernel debugging operations. + When in doubt, say "N". + +config NVRAM + tristate "/dev/nvram support" + depends on X86 || HAVE_ARCH_NVRAM_OPS + default M68K || PPC + ---help--- + If you say Y here and create a character special file /dev/nvram + with major number 10 and minor number 144 using mknod ("man mknod"), + you get read and write access to the non-volatile memory. + + /dev/nvram may be used to view settings in NVRAM or to change them + (with some utility). It could also be used to frequently + save a few bits of very important data that may not be lost over + power-off and for which writing to disk is too insecure. Note + however that most NVRAM space in a PC belongs to the BIOS and you + should NEVER idly tamper with it. See Ralf Brown's interrupt list + for a guide to the use of CMOS bytes by your BIOS. + + This memory is conventionally called "NVRAM" on PowerPC machines, + "CMOS RAM" on PCs, "NVRAM" on Ataris and "PRAM" on Macintoshes. + + To compile this driver as a module, choose M here: the + module will be called nvram. + config RAW_DRIVER tristate "RAW driver (/dev/raw/rawN)" depends on BLOCK @@ -452,6 +387,14 @@ config MAX_RAW_DEVS Default is 256. Increase this number in case you need lots of raw devices. +config DEVPORT + bool "/dev/port character device" + depends on ISA || PCI + default y + help + Say Y here if you want to support the /dev/port device. The /dev/port + device is similar to /dev/mem, but for I/O ports. + config HPET bool "HPET - High Precision Event Timer" if (X86 || IA64) default n @@ -511,14 +454,6 @@ config TELCLOCK /sys/devices/platform/telco_clock, with a number of files for controlling the behavior of this hardware. -config DEVPORT - bool "/dev/port character device" - depends on ISA || PCI - default y - help - Say Y here if you want to support the /dev/port device. The /dev/port - device is similar to /dev/mem, but for I/O ports. - source "drivers/s390/char/Kconfig" source "drivers/char/xillybus/Kconfig" diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 7c5ea6f9df14..ffce287ef415 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -20,9 +20,7 @@ obj-$(CONFIG_APM_EMULATION) += apm-emulation.o obj-$(CONFIG_DTLK) += dtlk.o obj-$(CONFIG_APPLICOM) += applicom.o obj-$(CONFIG_SONYPI) += sonypi.o -obj-$(CONFIG_RTC) += rtc.o obj-$(CONFIG_HPET) += hpet.o -obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ obj-$(CONFIG_NVRAM) += nvram.o obj-$(CONFIG_TOSHIBA) += toshiba.o @@ -46,9 +44,6 @@ obj-$(CONFIG_TCG_TPM) += tpm/ obj-$(CONFIG_PS3_FLASH) += ps3flash.o -obj-$(CONFIG_JS_RTC) += js-rtc.o -js-rtc-y = rtc.o - obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o diff --git a/drivers/char/applicom.c b/drivers/char/applicom.c index 51121a4b82c7..14b2d8034c51 100644 --- a/drivers/char/applicom.c +++ b/drivers/char/applicom.c @@ -53,7 +53,6 @@ #define MAX_BOARD 8 /* maximum of pc board possible */ #define MAX_ISA_BOARD 4 #define LEN_RAM_IO 0x800 -#define AC_MINOR 157 #ifndef PCI_VENDOR_ID_APPLICOM #define PCI_VENDOR_ID_APPLICOM 0x1389 diff --git a/drivers/char/efirtc.c b/drivers/char/efirtc.c deleted file mode 100644 index 4f73064d0c6f..000000000000 --- a/drivers/char/efirtc.c +++ /dev/null @@ -1,366 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * EFI Time Services Driver for Linux - * - * Copyright (C) 1999 Hewlett-Packard Co - * Copyright (C) 1999 Stephane Eranian <eranian@hpl.hp.com> - * - * Based on skeleton from the drivers/char/rtc.c driver by P. Gortmaker - * - * This code provides an architected & portable interface to the real time - * clock by using EFI instead of direct bit fiddling. The functionalities are - * quite different from the rtc.c driver. The only way to talk to the device - * is by using ioctl(). There is a /proc interface which provides the raw - * information. - * - * Please note that we have kept the API as close as possible to the - * legacy RTC. The standard /sbin/hwclock program should work normally - * when used to get/set the time. - * - * NOTES: - * - Locking is required for safe execution of EFI calls with regards - * to interrupts and SMP. - * - * TODO (December 1999): - * - provide the API to set/get the WakeUp Alarm (different from the - * rtc.c alarm). - * - SMP testing - * - Add module support - */ - -#include <linux/types.h> -#include <linux/errno.h> -#include <linux/miscdevice.h> -#include <linux/init.h> -#include <linux/rtc.h> -#include <linux/proc_fs.h> -#include <linux/seq_file.h> -#include <linux/efi.h> -#include <linux/uaccess.h> - - -#define EFI_RTC_VERSION "0.4" - -#define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT) -/* - * EFI Epoch is 1/1/1998 - */ -#define EFI_RTC_EPOCH 1998 - -static DEFINE_SPINLOCK(efi_rtc_lock); - -static long efi_rtc_ioctl(struct file *file, unsigned int cmd, - unsigned long arg); - -#define is_leap(year) \ - ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) - -static const unsigned short int __mon_yday[2][13] = -{ - /* Normal years. */ - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, - /* Leap years. */ - { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } -}; - -/* - * returns day of the year [0-365] - */ -static inline int -compute_yday(efi_time_t *eft) -{ - /* efi_time_t.month is in the [1-12] so, we need -1 */ - return __mon_yday[is_leap(eft->year)][eft->month-1]+ eft->day -1; -} -/* - * returns day of the week [0-6] 0=Sunday - * - * Don't try to provide a year that's before 1998, please ! - */ -static int -compute_wday(efi_time_t *eft) -{ - int y; - int ndays = 0; - - if ( eft->year < 1998 ) { - printk(KERN_ERR "efirtc: EFI year < 1998, invalid date\n"); - return -1; - } - - for(y=EFI_RTC_EPOCH; y < eft->year; y++ ) { - ndays += 365 + (is_leap(y) ? 1 : 0); - } - ndays += compute_yday(eft); - - /* - * 4=1/1/1998 was a Thursday - */ - return (ndays + 4) % 7; -} - -static void -convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft) -{ - - eft->year = wtime->tm_year + 1900; - eft->month = wtime->tm_mon + 1; - eft->day = wtime->tm_mday; - eft->hour = wtime->tm_hour; - eft->minute = wtime->tm_min; - eft->second = wtime->tm_sec; - eft->nanosecond = 0; - eft->daylight = wtime->tm_isdst ? EFI_ISDST: 0; - eft->timezone = EFI_UNSPECIFIED_TIMEZONE; -} - -static void -convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime) -{ - memset(wtime, 0, sizeof(*wtime)); - wtime->tm_sec = eft->second; - wtime->tm_min = eft->minute; - wtime->tm_hour = eft->hour; - wtime->tm_mday = eft->day; - wtime->tm_mon = eft->month - 1; - wtime->tm_year = eft->year - 1900; - - /* day of the week [0-6], Sunday=0 */ - wtime->tm_wday = compute_wday(eft); - - /* day in the year [1-365]*/ - wtime->tm_yday = compute_yday(eft); - - - switch (eft->daylight & EFI_ISDST) { - case EFI_ISDST: - wtime->tm_isdst = 1; - break; - case EFI_TIME_ADJUST_DAYLIGHT: - wtime->tm_isdst = 0; - break; - default: - wtime->tm_isdst = -1; - } -} - -static long efi_rtc_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - - efi_status_t status; - unsigned long flags; - efi_time_t eft; - efi_time_cap_t cap; - struct rtc_time wtime; - struct rtc_wkalrm __user *ewp; - unsigned char enabled, pending; - - switch (cmd) { - case RTC_UIE_ON: - case RTC_UIE_OFF: - case RTC_PIE_ON: - case RTC_PIE_OFF: - case RTC_AIE_ON: - case RTC_AIE_OFF: - case RTC_ALM_SET: - case RTC_ALM_READ: - case RTC_IRQP_READ: - case RTC_IRQP_SET: - case RTC_EPOCH_READ: - case RTC_EPOCH_SET: - return -EINVAL; - - case RTC_RD_TIME: - spin_lock_irqsave(&efi_rtc_lock, flags); - - status = efi.get_time(&eft, &cap); - - spin_unlock_irqrestore(&efi_rtc_lock,flags); - - if (status != EFI_SUCCESS) { - /* should never happen */ - printk(KERN_ERR "efitime: can't read time\n"); - return -EINVAL; - } - - convert_from_efi_time(&eft, &wtime); - - return copy_to_user((void __user *)arg, &wtime, - sizeof (struct rtc_time)) ? - EFAULT : 0; - - case RTC_SET_TIME: - - if (!capable(CAP_SYS_TIME)) return -EACCES; - - if (copy_from_user(&wtime, (struct rtc_time __user *)arg, - sizeof(struct rtc_time)) ) - return -EFAULT; - - convert_to_efi_time(&wtime, &eft); - - spin_lock_irqsave(&efi_rtc_lock, flags); - - status = efi.set_time(&eft); - - spin_unlock_irqrestore(&efi_rtc_lock,flags); - - return status == EFI_SUCCESS ? 0 : -EINVAL; - - case RTC_WKALM_SET: - - if (!capable(CAP_SYS_TIME)) return -EACCES; - - ewp = (struct rtc_wkalrm __user *)arg; - - if ( get_user(enabled, &ewp->enabled) - || copy_from_user(&wtime, &ewp->time, sizeof(struct rtc_time)) ) - return -EFAULT; - - convert_to_efi_time(&wtime, &eft); - - spin_lock_irqsave(&efi_rtc_lock, flags); - /* - * XXX Fixme: - * As of EFI 0.92 with the firmware I have on my - * machine this call does not seem to work quite - * right - */ - status = efi.set_wakeup_time((efi_bool_t)enabled, &eft); - - spin_unlock_irqrestore(&efi_rtc_lock,flags); - - return status == EFI_SUCCESS ? 0 : -EINVAL; - - case RTC_WKALM_RD: - - spin_lock_irqsave(&efi_rtc_lock, flags); - - status = efi.get_wakeup_time((efi_bool_t *)&enabled, (efi_bool_t *)&pending, &eft); - - spin_unlock_irqrestore(&efi_rtc_lock,flags); - - if (status != EFI_SUCCESS) return -EINVAL; - - ewp = (struct rtc_wkalrm __user *)arg; - - if ( put_user(enabled, &ewp->enabled) - || put_user(pending, &ewp->pending)) return -EFAULT; - - convert_from_efi_time(&eft, &wtime); - - return copy_to_user(&ewp->time, &wtime, - sizeof(struct rtc_time)) ? -EFAULT : 0; - } - return -ENOTTY; -} - -/* - * The various file operations we support. - */ - -static const struct file_operations efi_rtc_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = efi_rtc_ioctl, - .llseek = no_llseek, -}; - -static struct miscdevice efi_rtc_dev= { - EFI_RTC_MINOR, - "efirtc", - &efi_rtc_fops -}; - -/* - * We export RAW EFI information to /proc/driver/efirtc - */ -static int efi_rtc_proc_show(struct seq_file *m, void *v) -{ - efi_time_t eft, alm; - efi_time_cap_t cap; - efi_bool_t enabled, pending; - unsigned long flags; - - memset(&eft, 0, sizeof(eft)); - memset(&alm, 0, sizeof(alm)); - memset(&cap, 0, sizeof(cap)); - - spin_lock_irqsave(&efi_rtc_lock, flags); - - efi.get_time(&eft, &cap); - efi.get_wakeup_time(&enabled, &pending, &alm); - - spin_unlock_irqrestore(&efi_rtc_lock,flags); - - seq_printf(m, - "Time : %u:%u:%u.%09u\n" - "Date : %u-%u-%u\n" - "Daylight : %u\n", - eft.hour, eft.minute, eft.second, eft.nanosecond, - eft.year, eft.month, eft.day, - eft.daylight); - - if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) - seq_puts(m, "Timezone : unspecified\n"); - else - /* XXX fixme: convert to string? */ - seq_printf(m, "Timezone : %u\n", eft.timezone); - - - seq_printf(m, - "Alarm Time : %u:%u:%u.%09u\n" - "Alarm Date : %u-%u-%u\n" - "Alarm Daylight : %u\n" - "Enabled : %s\n" - "Pending : %s\n", - alm.hour, alm.minute, alm.second, alm.nanosecond, - alm.year, alm.month, alm.day, - alm.daylight, - enabled == 1 ? "yes" : "no", - pending == 1 ? "yes" : "no"); - - if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) - seq_puts(m, "Timezone : unspecified\n"); - else - /* XXX fixme: convert to string? */ - seq_printf(m, "Timezone : %u\n", alm.timezone); - - /* - * now prints the capabilities - */ - seq_printf(m, - "Resolution : %u\n" - "Accuracy : %u\n" - "SetstoZero : %u\n", - cap.resolution, cap.accuracy, cap.sets_to_zero); - - return 0; -} -static int __init -efi_rtc_init(void) -{ - int ret; - struct proc_dir_entry *dir; - - printk(KERN_INFO "EFI Time Services Driver v%s\n", EFI_RTC_VERSION); - - ret = misc_register(&efi_rtc_dev); - if (ret) { - printk(KERN_ERR "efirtc: can't misc_register on minor=%d\n", - EFI_RTC_MINOR); - return ret; - } - - dir = proc_create_single("driver/efirtc", 0, NULL, efi_rtc_proc_show); - if (dir == NULL) { - printk(KERN_ERR "efirtc: can't create /proc/driver/efirtc.\n"); - misc_deregister(&efi_rtc_dev); - return -1; - } - return 0; -} -device_initcall(efi_rtc_init); - -/* -MODULE_LICENSE("GPL"); -*/ diff --git a/drivers/char/mspec.c b/drivers/char/mspec.c index a9d9f074fbd6..7d583222e8fa 100644 --- a/drivers/char/mspec.c +++ b/drivers/char/mspec.c @@ -75,7 +75,7 @@ struct vma_data { enum mspec_page_type type; /* Type of pages allocated. */ unsigned long vm_start; /* Original (unsplit) base. */ unsigned long vm_end; /* Original (unsplit) end. */ - unsigned long maddr[0]; /* Array of MSPEC addresses. */ + unsigned long maddr[]; /* Array of MSPEC addresses. */ }; /* diff --git a/drivers/char/nwbutton.h b/drivers/char/nwbutton.h index 9dedfd7adc0e..f2b9fdc1f9ea 100644 --- a/drivers/char/nwbutton.h +++ b/drivers/char/nwbutton.h @@ -14,7 +14,6 @@ #define NUM_PRESSES_REBOOT 2 /* How many presses to activate shutdown */ #define BUTTON_DELAY 30 /* How many jiffies for sequence to end */ #define VERSION "0.3" /* Driver version number */ -#define BUTTON_MINOR 158 /* Major 10, Minor 158, /dev/nwbutton */ /* Structure definitions: */ diff --git a/drivers/char/nwflash.c b/drivers/char/nwflash.c index a4a0797daa19..0973c2c2b01a 100644 --- a/drivers/char/nwflash.c +++ b/drivers/char/nwflash.c @@ -576,7 +576,7 @@ static const struct file_operations flash_fops = static struct miscdevice flash_miscdev = { - FLASH_MINOR, + NWFLASH_MINOR, "nwflash", &flash_fops }; diff --git a/drivers/char/pcmcia/cm4000_cs.c b/drivers/char/pcmcia/cm4000_cs.c index 15bf585af5d3..4edb4174a1e2 100644 --- a/drivers/char/pcmcia/cm4000_cs.c +++ b/drivers/char/pcmcia/cm4000_cs.c @@ -731,8 +731,9 @@ static void monitor_card(struct timer_list *t) } switch (dev->mstate) { + case M_CARDOFF: { unsigned char flags0; - case M_CARDOFF: + DEBUGP(4, dev, "M_CARDOFF\n"); flags0 = inb(REG_FLAGS0(iobase)); if (flags0 & 0x02) { @@ -755,6 +756,7 @@ static void monitor_card(struct timer_list *t) dev->mdelay = T_50MSEC; } break; + } case M_FETCH_ATR: DEBUGP(4, dev, "M_FETCH_ATR\n"); xoutb(0x80, REG_FLAGS0(iobase)); diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c index 2c2381a806ae..38b46c7d1737 100644 --- a/drivers/char/ppdev.c +++ b/drivers/char/ppdev.c @@ -355,14 +355,19 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg) struct pp_struct *pp = file->private_data; struct parport *port; void __user *argp = (void __user *)arg; + struct ieee1284_info *info; + unsigned char reg; + unsigned char mask; + int mode; + s32 time32[2]; + s64 time64[2]; + struct timespec64 ts; + int ret; /* First handle the cases that don't take arguments. */ switch (cmd) { case PPCLAIM: { - struct ieee1284_info *info; - int ret; - if (pp->flags & PP_CLAIMED) { dev_dbg(&pp->pdev->dev, "you've already got it!\n"); return -EINVAL; @@ -513,15 +518,6 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg) port = pp->pdev->port; switch (cmd) { - struct ieee1284_info *info; - unsigned char reg; - unsigned char mask; - int mode; - s32 time32[2]; - s64 time64[2]; - struct timespec64 ts; - int ret; - case PPRSTATUS: reg = parport_read_status(port); if (copy_to_user(argp, ®, sizeof(reg))) diff --git a/drivers/char/rtc.c b/drivers/char/rtc.c deleted file mode 100644 index 3b91184b77ae..000000000000 --- a/drivers/char/rtc.c +++ /dev/null @@ -1,1311 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Real Time Clock interface for Linux - * - * Copyright (C) 1996 Paul Gortmaker - * - * This driver allows use of the real time clock (built into - * nearly all computers) from user space. It exports the /dev/rtc - * interface supporting various ioctl() and also the - * /proc/driver/rtc pseudo-file for status information. - * - * The ioctls can be used to set the interrupt behaviour and - * generation rate from the RTC via IRQ 8. Then the /dev/rtc - * interface can be used to make use of these timer interrupts, - * be they interval or alarm based. - * - * The /dev/rtc interface will block on reads until an interrupt - * has been received. If a RTC interrupt has already happened, - * it will output an unsigned long and then block. The output value - * contains the interrupt status in the low byte and the number of - * interrupts since the last read in the remaining high bytes. The - * /dev/rtc interface can also be used with the select(2) call. - * - * Based on other minimal char device drivers, like Alan's - * watchdog, Ted's random, etc. etc. - * - * 1.07 Paul Gortmaker. - * 1.08 Miquel van Smoorenburg: disallow certain things on the - * DEC Alpha as the CMOS clock is also used for other things. - * 1.09 Nikita Schmidt: epoch support and some Alpha cleanup. - * 1.09a Pete Zaitcev: Sun SPARC - * 1.09b Jeff Garzik: Modularize, init cleanup - * 1.09c Jeff Garzik: SMP cleanup - * 1.10 Paul Barton-Davis: add support for async I/O - * 1.10a Andrea Arcangeli: Alpha updates - * 1.10b Andrew Morton: SMP lock fix - * 1.10c Cesar Barros: SMP locking fixes and cleanup - * 1.10d Paul Gortmaker: delete paranoia check in rtc_exit - * 1.10e Maciej W. Rozycki: Handle DECstation's year weirdness. - * 1.11 Takashi Iwai: Kernel access functions - * rtc_register/rtc_unregister/rtc_control - * 1.11a Daniele Bellucci: Audit create_proc_read_entry in rtc_init - * 1.12 Venkatesh Pallipadi: Hooks for emulating rtc on HPET base-timer - * CONFIG_HPET_EMULATE_RTC - * 1.12a Maciej W. Rozycki: Handle memory-mapped chips properly. - * 1.12ac Alan Cox: Allow read access to the day of week register - * 1.12b David John: Remove calls to the BKL. - */ - -#define RTC_VERSION "1.12b" - -/* - * Note that *all* calls to CMOS_READ and CMOS_WRITE are done with - * interrupts disabled. Due to the index-port/data-port (0x70/0x71) - * design of the RTC, we don't want two different things trying to - * get to it at once. (e.g. the periodic 11 min sync from - * kernel/time/ntp.c vs. this driver.) - */ - -#include <linux/interrupt.h> -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/types.h> -#include <linux/miscdevice.h> -#include <linux/ioport.h> -#include <linux/fcntl.h> -#include <linux/mc146818rtc.h> -#include <linux/init.h> -#include <linux/poll.h> -#include <linux/proc_fs.h> -#include <linux/seq_file.h> -#include <linux/spinlock.h> -#include <linux/sched/signal.h> -#include <linux/sysctl.h> -#include <linux/wait.h> -#include <linux/bcd.h> -#include <linux/delay.h> -#include <linux/uaccess.h> -#include <linux/ratelimit.h> - -#include <asm/current.h> - -#ifdef CONFIG_X86 -#include <asm/hpet.h> -#endif - -#ifdef CONFIG_SPARC32 -#include <linux/of.h> -#include <linux/of_device.h> -#include <asm/io.h> - -static unsigned long rtc_port; -static int rtc_irq; -#endif - -#ifdef CONFIG_HPET_EMULATE_RTC -#undef RTC_IRQ -#endif - -#ifdef RTC_IRQ -static int rtc_has_irq = 1; -#endif - -#ifndef CONFIG_HPET_EMULATE_RTC -#define is_hpet_enabled() 0 -#define hpet_set_alarm_time(hrs, min, sec) 0 -#define hpet_set_periodic_freq(arg) 0 -#define hpet_mask_rtc_irq_bit(arg) 0 -#define hpet_set_rtc_irq_bit(arg) 0 -#define hpet_rtc_timer_init() do { } while (0) -#define hpet_rtc_dropped_irq() 0 -#define hpet_register_irq_handler(h) ({ 0; }) -#define hpet_unregister_irq_handler(h) ({ 0; }) -#ifdef RTC_IRQ -static irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id) -{ - return 0; -} -#endif -#endif - -/* - * We sponge a minor off of the misc major. No need slurping - * up another valuable major dev number for this. If you add - * an ioctl, make sure you don't conflict with SPARC's RTC - * ioctls. - */ - -static struct fasync_struct *rtc_async_queue; - -static DECLARE_WAIT_QUEUE_HEAD(rtc_wait); - -#ifdef RTC_IRQ -static void rtc_dropped_irq(struct timer_list *unused); - -static DEFINE_TIMER(rtc_irq_timer, rtc_dropped_irq); -#endif - -static ssize_t rtc_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos); - -static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg); -static void rtc_get_rtc_time(struct rtc_time *rtc_tm); - -#ifdef RTC_IRQ -static __poll_t rtc_poll(struct file *file, poll_table *wait); -#endif - -static void get_rtc_alm_time(struct rtc_time *alm_tm); -#ifdef RTC_IRQ -static void set_rtc_irq_bit_locked(unsigned char bit); -static void mask_rtc_irq_bit_locked(unsigned char bit); - -static inline void set_rtc_irq_bit(unsigned char bit) -{ - spin_lock_irq(&rtc_lock); - set_rtc_irq_bit_locked(bit); - spin_unlock_irq(&rtc_lock); -} - -static void mask_rtc_irq_bit(unsigned char bit) -{ - spin_lock_irq(&rtc_lock); - mask_rtc_irq_bit_locked(bit); - spin_unlock_irq(&rtc_lock); -} -#endif - -#ifdef CONFIG_PROC_FS -static int rtc_proc_show(struct seq_file *seq, void *v); -#endif - -/* - * Bits in rtc_status. (6 bits of room for future expansion) - */ - -#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */ -#define RTC_TIMER_ON 0x02 /* missed irq timer active */ - -/* - * rtc_status is never changed by rtc_interrupt, and ioctl/open/close is - * protected by the spin lock rtc_lock. However, ioctl can still disable the - * timer in rtc_status and then with del_timer after the interrupt has read - * rtc_status but before mod_timer is called, which would then reenable the - * timer (but you would need to have an awful timing before you'd trip on it) - */ -static unsigned long rtc_status; /* bitmapped status byte. */ -static unsigned long rtc_freq; /* Current periodic IRQ rate */ -static unsigned long rtc_irq_data; /* our output to the world */ -static unsigned long rtc_max_user_freq = 64; /* > this, need CAP_SYS_RESOURCE */ - -/* - * If this driver ever becomes modularised, it will be really nice - * to make the epoch retain its value across module reload... - */ - -static unsigned long epoch = 1900; /* year corresponding to 0x00 */ - -static const unsigned char days_in_mo[] = -{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - -/* - * Returns true if a clock update is in progress - */ -static inline unsigned char rtc_is_updating(void) -{ - unsigned long flags; - unsigned char uip; - - spin_lock_irqsave(&rtc_lock, flags); - uip = (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP); - spin_unlock_irqrestore(&rtc_lock, flags); - return uip; -} - -#ifdef RTC_IRQ -/* - * A very tiny interrupt handler. It runs with interrupts disabled, - * but there is possibility of conflicting with the set_rtc_mmss() - * call (the rtc irq and the timer irq can easily run at the same - * time in two different CPUs). So we need to serialize - * accesses to the chip with the rtc_lock spinlock that each - * architecture should implement in the timer code. - * (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.) - */ - -static irqreturn_t rtc_interrupt(int irq, void *dev_id) -{ - /* - * Can be an alarm interrupt, update complete interrupt, - * or a periodic interrupt. We store the status in the - * low byte and the number of interrupts received since - * the last read in the remainder of rtc_irq_data. - */ - - spin_lock(&rtc_lock); - rtc_irq_data += 0x100; - rtc_irq_data &= ~0xff; - if (is_hpet_enabled()) { - /* - * In this case it is HPET RTC interrupt handler - * calling us, with the interrupt information - * passed as arg1, instead of irq. - */ - rtc_irq_data |= (unsigned long)irq & 0xF0; - } else { - rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); - } - - if (rtc_status & RTC_TIMER_ON) - mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100); - - spin_unlock(&rtc_lock); - - wake_up_interruptible(&rtc_wait); - - kill_fasync(&rtc_async_queue, SIGIO, POLL_IN); - - return IRQ_HANDLED; -} -#endif - -/* - * sysctl-tuning infrastructure. - */ -static struct ctl_table rtc_table[] = { - { - .procname = "max-user-freq", - .data = &rtc_max_user_freq, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec, - }, - { } -}; - -static struct ctl_table rtc_root[] = { - { - .procname = "rtc", - .mode = 0555, - .child = rtc_table, - }, - { } -}; - -static struct ctl_table dev_root[] = { - { - .procname = "dev", - .mode = 0555, - .child = rtc_root, - }, - { } -}; - -static struct ctl_table_header *sysctl_header; - -static int __init init_sysctl(void) -{ - sysctl_header = register_sysctl_table(dev_root); - return 0; -} - -static void __exit cleanup_sysctl(void) -{ - unregister_sysctl_table(sysctl_header); -} - -/* - * Now all the various file operations that we export. - */ - -static ssize_t rtc_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ -#ifndef RTC_IRQ - return -EIO; -#else - DECLARE_WAITQUEUE(wait, current); - unsigned long data; - ssize_t retval; - - if (rtc_has_irq == 0) - return -EIO; - - /* - * Historically this function used to assume that sizeof(unsigned long) - * is the same in userspace and kernelspace. This lead to problems - * for configurations with multiple ABIs such a the MIPS o32 and 64 - * ABIs supported on the same kernel. So now we support read of both - * 4 and 8 bytes and assume that's the sizeof(unsigned long) in the - * userspace ABI. - */ - if (count != sizeof(unsigned int) && count != sizeof(unsigned long)) - return -EINVAL; - - add_wait_queue(&rtc_wait, &wait); - - do { - /* First make it right. Then make it fast. Putting this whole - * block within the parentheses of a while would be too - * confusing. And no, xchg() is not the answer. */ - - __set_current_state(TASK_INTERRUPTIBLE); - - spin_lock_irq(&rtc_lock); - data = rtc_irq_data; - rtc_irq_data = 0; - spin_unlock_irq(&rtc_lock); - - if (data != 0) - break; - - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - goto out; - } - if (signal_pending(current)) { - retval = -ERESTARTSYS; - goto out; - } - schedule(); - } while (1); - - if (count == sizeof(unsigned int)) { - retval = put_user(data, - (unsigned int __user *)buf) ?: sizeof(int); - } else { - retval = put_user(data, - (unsigned long __user *)buf) ?: sizeof(long); - } - if (!retval) - retval = count; - out: - __set_current_state(TASK_RUNNING); - remove_wait_queue(&rtc_wait, &wait); - - return retval; -#endif -} - -static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel) -{ - struct rtc_time wtime; - -#ifdef RTC_IRQ - if (rtc_has_irq == 0) { - switch (cmd) { - case RTC_AIE_OFF: - case RTC_AIE_ON: - case RTC_PIE_OFF: - case RTC_PIE_ON: - case RTC_UIE_OFF: - case RTC_UIE_ON: - case RTC_IRQP_READ: - case RTC_IRQP_SET: - return -EINVAL; - } - } -#endif - - switch (cmd) { -#ifdef RTC_IRQ - case RTC_AIE_OFF: /* Mask alarm int. enab. bit */ - { - mask_rtc_irq_bit(RTC_AIE); - return 0; - } - case RTC_AIE_ON: /* Allow alarm interrupts. */ - { - set_rtc_irq_bit(RTC_AIE); - return 0; - } - case RTC_PIE_OFF: /* Mask periodic int. enab. bit */ - { - /* can be called from isr via rtc_control() */ - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - mask_rtc_irq_bit_locked(RTC_PIE); - if (rtc_status & RTC_TIMER_ON) { - rtc_status &= ~RTC_TIMER_ON; - del_timer(&rtc_irq_timer); - } - spin_unlock_irqrestore(&rtc_lock, flags); - - return 0; - } - case RTC_PIE_ON: /* Allow periodic ints */ - { - /* can be called from isr via rtc_control() */ - unsigned long flags; - - /* - * We don't really want Joe User enabling more - * than 64Hz of interrupts on a multi-user machine. - */ - if (!kernel && (rtc_freq > rtc_max_user_freq) && - (!capable(CAP_SYS_RESOURCE))) - return -EACCES; - - spin_lock_irqsave(&rtc_lock, flags); - if (!(rtc_status & RTC_TIMER_ON)) { - mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + - 2*HZ/100); - rtc_status |= RTC_TIMER_ON; - } - set_rtc_irq_bit_locked(RTC_PIE); - spin_unlock_irqrestore(&rtc_lock, flags); - - return 0; - } - case RTC_UIE_OFF: /* Mask ints from RTC updates. */ - { - mask_rtc_irq_bit(RTC_UIE); - return 0; - } - case RTC_UIE_ON: /* Allow ints for RTC updates. */ - { - set_rtc_irq_bit(RTC_UIE); - return 0; - } -#endif - case RTC_ALM_READ: /* Read the present alarm time */ - { - /* - * This returns a struct rtc_time. Reading >= 0xc0 - * means "don't care" or "match all". Only the tm_hour, - * tm_min, and tm_sec values are filled in. - */ - memset(&wtime, 0, sizeof(struct rtc_time)); - get_rtc_alm_time(&wtime); - break; - } - case RTC_ALM_SET: /* Store a time into the alarm */ - { - /* - * This expects a struct rtc_time. Writing 0xff means - * "don't care" or "match all". Only the tm_hour, - * tm_min and tm_sec are used. - */ - unsigned char hrs, min, sec; - struct rtc_time alm_tm; - - if (copy_from_user(&alm_tm, (struct rtc_time __user *)arg, - sizeof(struct rtc_time))) - return -EFAULT; - - hrs = alm_tm.tm_hour; - min = alm_tm.tm_min; - sec = alm_tm.tm_sec; - - spin_lock_irq(&rtc_lock); - if (hpet_set_alarm_time(hrs, min, sec)) { - /* - * Fallthru and set alarm time in CMOS too, - * so that we will get proper value in RTC_ALM_READ - */ - } - if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || - RTC_ALWAYS_BCD) { - if (sec < 60) - sec = bin2bcd(sec); - else - sec = 0xff; - - if (min < 60) - min = bin2bcd(min); - else - min = 0xff; - - if (hrs < 24) - hrs = bin2bcd(hrs); - else - hrs = 0xff; - } - CMOS_WRITE(hrs, RTC_HOURS_ALARM); - CMOS_WRITE(min, RTC_MINUTES_ALARM); - CMOS_WRITE(sec, RTC_SECONDS_ALARM); - spin_unlock_irq(&rtc_lock); - - return 0; - } - case RTC_RD_TIME: /* Read the time/date from RTC */ - { - memset(&wtime, 0, sizeof(struct rtc_time)); - rtc_get_rtc_time(&wtime); - break; - } - case RTC_SET_TIME: /* Set the RTC */ - { - struct rtc_time rtc_tm; - unsigned char mon, day, hrs, min, sec, leap_yr; - unsigned char save_control, save_freq_select; - unsigned int yrs; -#ifdef CONFIG_MACH_DECSTATION - unsigned int real_yrs; -#endif - - if (!capable(CAP_SYS_TIME)) - return -EACCES; - - if (copy_from_user(&rtc_tm, (struct rtc_time __user *)arg, - sizeof(struct rtc_time))) - return -EFAULT; - - yrs = rtc_tm.tm_year + 1900; - mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */ - day = rtc_tm.tm_mday; - hrs = rtc_tm.tm_hour; - min = rtc_tm.tm_min; - sec = rtc_tm.tm_sec; - - if (yrs < 1970) - return -EINVAL; - - leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400)); - - if ((mon > 12) || (day == 0)) - return -EINVAL; - - if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr))) - return -EINVAL; - - if ((hrs >= 24) || (min >= 60) || (sec >= 60)) - return -EINVAL; - - yrs -= epoch; - if (yrs > 255) /* They are unsigned */ - return -EINVAL; - - spin_lock_irq(&rtc_lock); -#ifdef CONFIG_MACH_DECSTATION - real_yrs = yrs; - yrs = 72; - - /* - * We want to keep the year set to 73 until March - * for non-leap years, so that Feb, 29th is handled - * correctly. - */ - if (!leap_yr && mon < 3) { - real_yrs--; - yrs = 73; - } -#endif - /* These limits and adjustments are independent of - * whether the chip is in binary mode or not. - */ - if (yrs > 169) { - spin_unlock_irq(&rtc_lock); - return -EINVAL; - } - if (yrs >= 100) - yrs -= 100; - - if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) - || RTC_ALWAYS_BCD) { - sec = bin2bcd(sec); - min = bin2bcd(min); - hrs = bin2bcd(hrs); - day = bin2bcd(day); - mon = bin2bcd(mon); - yrs = bin2bcd(yrs); - } - - save_control = CMOS_READ(RTC_CONTROL); - CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL); - save_freq_select = CMOS_READ(RTC_FREQ_SELECT); - CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT); - -#ifdef CONFIG_MACH_DECSTATION - CMOS_WRITE(real_yrs, RTC_DEC_YEAR); -#endif - CMOS_WRITE(yrs, RTC_YEAR); - CMOS_WRITE(mon, RTC_MONTH); - CMOS_WRITE(day, RTC_DAY_OF_MONTH); - CMOS_WRITE(hrs, RTC_HOURS); - CMOS_WRITE(min, RTC_MINUTES); - CMOS_WRITE(sec, RTC_SECONDS); - - CMOS_WRITE(save_control, RTC_CONTROL); - CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); - - spin_unlock_irq(&rtc_lock); - return 0; - } -#ifdef RTC_IRQ - case RTC_IRQP_READ: /* Read the periodic IRQ rate. */ - { - return put_user(rtc_freq, (unsigned long __user *)arg); - } - case RTC_IRQP_SET: /* Set periodic IRQ rate. */ - { - int tmp = 0; - unsigned char val; - /* can be called from isr via rtc_control() */ - unsigned long flags; - - /* - * The max we can do is 8192Hz. - */ - if ((arg < 2) || (arg > 8192)) - return -EINVAL; - /* - * We don't really want Joe User generating more - * than 64Hz of interrupts on a multi-user machine. - */ - if (!kernel && (arg > rtc_max_user_freq) && - !capable(CAP_SYS_RESOURCE)) - return -EACCES; - - while (arg > (1<<tmp)) - tmp++; - - /* - * Check that the input was really a power of 2. - */ - if (arg != (1<<tmp)) - return -EINVAL; - - rtc_freq = arg; - - spin_lock_irqsave(&rtc_lock, flags); - if (hpet_set_periodic_freq(arg)) { - spin_unlock_irqrestore(&rtc_lock, flags); - return 0; - } - - val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0; - val |= (16 - tmp); - CMOS_WRITE(val, RTC_FREQ_SELECT); - spin_unlock_irqrestore(&rtc_lock, flags); - return 0; - } -#endif - case RTC_EPOCH_READ: /* Read the epoch. */ - { - return put_user(epoch, (unsigned long __user *)arg); - } - case RTC_EPOCH_SET: /* Set the epoch. */ - { - /* - * There were no RTC clocks before 1900. - */ - if (arg < 1900) - return -EINVAL; - - if (!capable(CAP_SYS_TIME)) - return -EACCES; - - epoch = arg; - return 0; - } - default: - return -ENOTTY; - } - return copy_to_user((void __user *)arg, - &wtime, sizeof wtime) ? -EFAULT : 0; -} - -static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - long ret; - ret = rtc_do_ioctl(cmd, arg, 0); - return ret; -} - -/* - * We enforce only one user at a time here with the open/close. - * Also clear the previous interrupt data on an open, and clean - * up things on a close. - */ -static int rtc_open(struct inode *inode, struct file *file) -{ - spin_lock_irq(&rtc_lock); - - if (rtc_status & RTC_IS_OPEN) - goto out_busy; - - rtc_status |= RTC_IS_OPEN; - - rtc_irq_data = 0; - spin_unlock_irq(&rtc_lock); - return 0; - -out_busy: - spin_unlock_irq(&rtc_lock); - return -EBUSY; -} - -static int rtc_fasync(int fd, struct file *filp, int on) -{ - return fasync_helper(fd, filp, on, &rtc_async_queue); -} - -static int rtc_release(struct inode *inode, struct file *file) -{ -#ifdef RTC_IRQ - unsigned char tmp; - - if (rtc_has_irq == 0) - goto no_irq; - - /* - * Turn off all interrupts once the device is no longer - * in use, and clear the data. - */ - - spin_lock_irq(&rtc_lock); - if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) { - tmp = CMOS_READ(RTC_CONTROL); - tmp &= ~RTC_PIE; - tmp &= ~RTC_AIE; - tmp &= ~RTC_UIE; - CMOS_WRITE(tmp, RTC_CONTROL); - CMOS_READ(RTC_INTR_FLAGS); - } - if (rtc_status & RTC_TIMER_ON) { - rtc_status &= ~RTC_TIMER_ON; - del_timer(&rtc_irq_timer); - } - spin_unlock_irq(&rtc_lock); - -no_irq: -#endif - - spin_lock_irq(&rtc_lock); - rtc_irq_data = 0; - rtc_status &= ~RTC_IS_OPEN; - spin_unlock_irq(&rtc_lock); - - return 0; -} - -#ifdef RTC_IRQ -static __poll_t rtc_poll(struct file *file, poll_table *wait) -{ - unsigned long l; - - if (rtc_has_irq == 0) - return 0; - - poll_wait(file, &rtc_wait, wait); - - spin_lock_irq(&rtc_lock); - l = rtc_irq_data; - spin_unlock_irq(&rtc_lock); - - if (l != 0) - return EPOLLIN | EPOLLRDNORM; - return 0; -} -#endif - -/* - * The various file operations we support. - */ - -static const struct file_operations rtc_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .read = rtc_read, -#ifdef RTC_IRQ - .poll = rtc_poll, -#endif - .unlocked_ioctl = rtc_ioctl, - .open = rtc_open, - .release = rtc_release, - .fasync = rtc_fasync, -}; - -static struct miscdevice rtc_dev = { - .minor = RTC_MINOR, - .name = "rtc", - .fops = &rtc_fops, -}; - -static resource_size_t rtc_size; - -static struct resource * __init rtc_request_region(resource_size_t size) -{ - struct resource *r; - - if (RTC_IOMAPPED) - r = request_region(RTC_PORT(0), size, "rtc"); - else - r = request_mem_region(RTC_PORT(0), size, "rtc"); - - if (r) - rtc_size = size; - - return r; -} - -static void rtc_release_region(void) -{ - if (RTC_IOMAPPED) - release_region(RTC_PORT(0), rtc_size); - else - release_mem_region(RTC_PORT(0), rtc_size); -} - -static int __init rtc_init(void) -{ -#ifdef CONFIG_PROC_FS - struct proc_dir_entry *ent; -#endif -#if defined(__alpha__) || defined(__mips__) - unsigned int year, ctrl; - char *guess = NULL; -#endif -#ifdef CONFIG_SPARC32 - struct device_node *ebus_dp; - struct platform_device *op; -#else - void *r; -#ifdef RTC_IRQ - irq_handler_t rtc_int_handler_ptr; -#endif -#endif - -#ifdef CONFIG_SPARC32 - for_each_node_by_name(ebus_dp, "ebus") { - struct device_node *dp; - for_each_child_of_node(ebus_dp, dp) { - if (of_node_name_eq(dp, "rtc")) { - op = of_find_device_by_node(dp); - if (op) { - rtc_port = op->resource[0].start; - rtc_irq = op->irqs[0]; - goto found; - } - } - } - } - rtc_has_irq = 0; - printk(KERN_ERR "rtc_init: no PC rtc found\n"); - return -EIO; - -found: - if (!rtc_irq) { - rtc_has_irq = 0; - goto no_irq; - } - - /* - * XXX Interrupt pin #7 in Espresso is shared between RTC and - * PCI Slot 2 INTA# (and some INTx# in Slot 1). - */ - if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", - (void *)&rtc_port)) { - rtc_has_irq = 0; - printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq); - return -EIO; - } -no_irq: -#else - r = rtc_request_region(RTC_IO_EXTENT); - - /* - * If we've already requested a smaller range (for example, because - * PNPBIOS or ACPI told us how the device is configured), the request - * above might fail because it's too big. - * - * If so, request just the range we actually use. - */ - if (!r) - r = rtc_request_region(RTC_IO_EXTENT_USED); - if (!r) { -#ifdef RTC_IRQ - rtc_has_irq = 0; -#endif - printk(KERN_ERR "rtc: I/O resource %lx is not free.\n", - (long)(RTC_PORT(0))); - return -EIO; - } - -#ifdef RTC_IRQ - if (is_hpet_enabled()) { - int err; - - rtc_int_handler_ptr = hpet_rtc_interrupt; - err = hpet_register_irq_handler(rtc_interrupt); - if (err != 0) { - printk(KERN_WARNING "hpet_register_irq_handler failed " - "in rtc_init()."); - return err; - } - } else { - rtc_int_handler_ptr = rtc_interrupt; - } - - if (request_irq(RTC_IRQ, rtc_int_handler_ptr, 0, "rtc", NULL)) { - /* Yeah right, seeing as irq 8 doesn't even hit the bus. */ - rtc_has_irq = 0; - printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ); - rtc_release_region(); - - return -EIO; - } - hpet_rtc_timer_init(); - -#endif - -#endif /* CONFIG_SPARC32 vs. others */ - - if (misc_register(&rtc_dev)) { -#ifdef RTC_IRQ - free_irq(RTC_IRQ, NULL); - hpet_unregister_irq_handler(rtc_interrupt); - rtc_has_irq = 0; -#endif - rtc_release_region(); - return -ENODEV; - } - -#ifdef CONFIG_PROC_FS - ent = proc_create_single("driver/rtc", 0, NULL, rtc_proc_show); - if (!ent) - printk(KERN_WARNING "rtc: Failed to register with procfs.\n"); -#endif - -#if defined(__alpha__) || defined(__mips__) - rtc_freq = HZ; - - /* Each operating system on an Alpha uses its own epoch. - Let's try to guess which one we are using now. */ - - if (rtc_is_updating() != 0) - msleep(20); - - spin_lock_irq(&rtc_lock); - year = CMOS_READ(RTC_YEAR); - ctrl = CMOS_READ(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) - year = bcd2bin(year); /* This should never happen... */ - - if (year < 20) { - epoch = 2000; - guess = "SRM (post-2000)"; - } else if (year >= 20 && year < 48) { - epoch = 1980; - guess = "ARC console"; - } else if (year >= 48 && year < 72) { - epoch = 1952; - guess = "Digital UNIX"; -#if defined(__mips__) - } else if (year >= 72 && year < 74) { - epoch = 2000; - guess = "Digital DECstation"; -#else - } else if (year >= 70) { - epoch = 1900; - guess = "Standard PC (1900)"; -#endif - } - if (guess) - printk(KERN_INFO "rtc: %s epoch (%lu) detected\n", - guess, epoch); -#endif -#ifdef RTC_IRQ - if (rtc_has_irq == 0) - goto no_irq2; - - spin_lock_irq(&rtc_lock); - rtc_freq = 1024; - if (!hpet_set_periodic_freq(rtc_freq)) { - /* - * Initialize periodic frequency to CMOS reset default, - * which is 1024Hz - */ - CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) & 0xF0) | 0x06), - RTC_FREQ_SELECT); - } - spin_unlock_irq(&rtc_lock); -no_irq2: -#endif - - (void) init_sysctl(); - - printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n"); - - return 0; -} - -static void __exit rtc_exit(void) -{ - cleanup_sysctl(); - remove_proc_entry("driver/rtc", NULL); - misc_deregister(&rtc_dev); - -#ifdef CONFIG_SPARC32 - if (rtc_has_irq) - free_irq(rtc_irq, &rtc_port); -#else - rtc_release_region(); -#ifdef RTC_IRQ - if (rtc_has_irq) { - free_irq(RTC_IRQ, NULL); - hpet_unregister_irq_handler(hpet_rtc_interrupt); - } -#endif -#endif /* CONFIG_SPARC32 */ -} - -module_init(rtc_init); -module_exit(rtc_exit); - -#ifdef RTC_IRQ -/* - * At IRQ rates >= 4096Hz, an interrupt may get lost altogether. - * (usually during an IDE disk interrupt, with IRQ unmasking off) - * Since the interrupt handler doesn't get called, the IRQ status - * byte doesn't get read, and the RTC stops generating interrupts. - * A timer is set, and will call this function if/when that happens. - * To get it out of this stalled state, we just read the status. - * At least a jiffy of interrupts (rtc_freq/HZ) will have been lost. - * (You *really* shouldn't be trying to use a non-realtime system - * for something that requires a steady > 1KHz signal anyways.) - */ - -static void rtc_dropped_irq(struct timer_list *unused) -{ - unsigned long freq; - - spin_lock_irq(&rtc_lock); - - if (hpet_rtc_dropped_irq()) { - spin_unlock_irq(&rtc_lock); - return; - } - - /* Just in case someone disabled the timer from behind our back... */ - if (rtc_status & RTC_TIMER_ON) - mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100); - - rtc_irq_data += ((rtc_freq/HZ)<<8); - rtc_irq_data &= ~0xff; - rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); /* restart */ - - freq = rtc_freq; - - spin_unlock_irq(&rtc_lock); - - printk_ratelimited(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n", - freq); - - /* Now we have new data */ - wake_up_interruptible(&rtc_wait); - - kill_fasync(&rtc_async_queue, SIGIO, POLL_IN); -} -#endif - -#ifdef CONFIG_PROC_FS -/* - * Info exported via "/proc/driver/rtc". - */ - -static int rtc_proc_show(struct seq_file *seq, void *v) -{ -#define YN(bit) ((ctrl & bit) ? "yes" : "no") -#define NY(bit) ((ctrl & bit) ? "no" : "yes") - struct rtc_time tm; - unsigned char batt, ctrl; - unsigned long freq; - - spin_lock_irq(&rtc_lock); - batt = CMOS_READ(RTC_VALID) & RTC_VRT; - ctrl = CMOS_READ(RTC_CONTROL); - freq = rtc_freq; - spin_unlock_irq(&rtc_lock); - - - rtc_get_rtc_time(&tm); - - /* - * There is no way to tell if the luser has the RTC set for local - * time or for Universal Standard Time (GMT). Probably local though. - */ - seq_printf(seq, - "rtc_time\t: %ptRt\n" - "rtc_date\t: %ptRd\n" - "rtc_epoch\t: %04lu\n", - &tm, &tm, epoch); - - get_rtc_alm_time(&tm); - - /* - * We implicitly assume 24hr mode here. Alarm values >= 0xc0 will - * match any value for that particular field. Values that are - * greater than a valid time, but less than 0xc0 shouldn't appear. - */ - seq_puts(seq, "alarm\t\t: "); - if (tm.tm_hour <= 24) - seq_printf(seq, "%02d:", tm.tm_hour); - else - seq_puts(seq, "**:"); - - if (tm.tm_min <= 59) - seq_printf(seq, "%02d:", tm.tm_min); - else - seq_puts(seq, "**:"); - - if (tm.tm_sec <= 59) - seq_printf(seq, "%02d\n", tm.tm_sec); - else - seq_puts(seq, "**\n"); - - seq_printf(seq, - "DST_enable\t: %s\n" - "BCD\t\t: %s\n" - "24hr\t\t: %s\n" - "square_wave\t: %s\n" - "alarm_IRQ\t: %s\n" - "update_IRQ\t: %s\n" - "periodic_IRQ\t: %s\n" - "periodic_freq\t: %ld\n" - "batt_status\t: %s\n", - YN(RTC_DST_EN), - NY(RTC_DM_BINARY), - YN(RTC_24H), - YN(RTC_SQWE), - YN(RTC_AIE), - YN(RTC_UIE), - YN(RTC_PIE), - freq, - batt ? "okay" : "dead"); - - return 0; -#undef YN -#undef NY -} -#endif - -static void rtc_get_rtc_time(struct rtc_time *rtc_tm) -{ - unsigned long uip_watchdog = jiffies, flags; - unsigned char ctrl; -#ifdef CONFIG_MACH_DECSTATION - unsigned int real_year; -#endif - - /* - * read RTC once any update in progress is done. The update - * can take just over 2ms. We wait 20ms. There is no need to - * to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP. - * If you need to know *exactly* when a second has started, enable - * periodic update complete interrupts, (via ioctl) and then - * immediately read /dev/rtc which will block until you get the IRQ. - * Once the read clears, read the RTC time (again via ioctl). Easy. - */ - - while (rtc_is_updating() != 0 && - time_before(jiffies, uip_watchdog + 2*HZ/100)) - cpu_relax(); - - /* - * Only the values that we read from the RTC are set. We leave - * tm_wday, tm_yday and tm_isdst untouched. Note that while the - * RTC has RTC_DAY_OF_WEEK, we should usually ignore it, as it is - * only updated by the RTC when initially set to a non-zero value. - */ - spin_lock_irqsave(&rtc_lock, flags); - rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS); - rtc_tm->tm_min = CMOS_READ(RTC_MINUTES); - rtc_tm->tm_hour = CMOS_READ(RTC_HOURS); - rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH); - rtc_tm->tm_mon = CMOS_READ(RTC_MONTH); - rtc_tm->tm_year = CMOS_READ(RTC_YEAR); - /* Only set from 2.6.16 onwards */ - rtc_tm->tm_wday = CMOS_READ(RTC_DAY_OF_WEEK); - -#ifdef CONFIG_MACH_DECSTATION - real_year = CMOS_READ(RTC_DEC_YEAR); -#endif - ctrl = CMOS_READ(RTC_CONTROL); - spin_unlock_irqrestore(&rtc_lock, flags); - - if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { - rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec); - rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min); - rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour); - rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday); - rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon); - rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year); - rtc_tm->tm_wday = bcd2bin(rtc_tm->tm_wday); - } - -#ifdef CONFIG_MACH_DECSTATION - rtc_tm->tm_year += real_year - 72; -#endif - - /* - * Account for differences between how the RTC uses the values - * and how they are defined in a struct rtc_time; - */ - rtc_tm->tm_year += epoch - 1900; - if (rtc_tm->tm_year <= 69) - rtc_tm->tm_year += 100; - - rtc_tm->tm_mon--; -} - -static void get_rtc_alm_time(struct rtc_time *alm_tm) -{ - unsigned char ctrl; - - /* - * Only the values that we read from the RTC are set. That - * means only tm_hour, tm_min, and tm_sec. - */ - spin_lock_irq(&rtc_lock); - alm_tm->tm_sec = CMOS_READ(RTC_SECONDS_ALARM); - alm_tm->tm_min = CMOS_READ(RTC_MINUTES_ALARM); - alm_tm->tm_hour = CMOS_READ(RTC_HOURS_ALARM); - ctrl = CMOS_READ(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { - alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec); - alm_tm->tm_min = bcd2bin(alm_tm->tm_min); - alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour); - } -} - -#ifdef RTC_IRQ -/* - * Used to disable/enable interrupts for any one of UIE, AIE, PIE. - * Rumour has it that if you frob the interrupt enable/disable - * bits in RTC_CONTROL, you should read RTC_INTR_FLAGS, to - * ensure you actually start getting interrupts. Probably for - * compatibility with older/broken chipset RTC implementations. - * We also clear out any old irq data after an ioctl() that - * meddles with the interrupt enable/disable bits. - */ - -static void mask_rtc_irq_bit_locked(unsigned char bit) -{ - unsigned char val; - - if (hpet_mask_rtc_irq_bit(bit)) - return; - val = CMOS_READ(RTC_CONTROL); - val &= ~bit; - CMOS_WRITE(val, RTC_CONTROL); - CMOS_READ(RTC_INTR_FLAGS); - - rtc_irq_data = 0; -} - -static void set_rtc_irq_bit_locked(unsigned char bit) -{ - unsigned char val; - - if (hpet_set_rtc_irq_bit(bit)) - return; - val = CMOS_READ(RTC_CONTROL); - val |= bit; - CMOS_WRITE(val, RTC_CONTROL); - CMOS_READ(RTC_INTR_FLAGS); - - rtc_irq_data = 0; -} -#endif - -MODULE_AUTHOR("Paul Gortmaker"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(RTC_MINOR); diff --git a/drivers/char/toshiba.c b/drivers/char/toshiba.c index 98f3150e0048..aff0a8e44fff 100644 --- a/drivers/char/toshiba.c +++ b/drivers/char/toshiba.c @@ -61,8 +61,6 @@ #include <linux/mutex.h> #include <linux/toshiba.h> -#define TOSH_MINOR_DEV 181 - MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>"); MODULE_DESCRIPTION("Toshiba laptop SMM driver"); diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 4df9b40d6342..3cbaec925606 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -112,7 +112,7 @@ struct port_buffer { unsigned int sgpages; /* sg is used if spages > 0. sg must be the last in is struct */ - struct scatterlist sg[0]; + struct scatterlist sg[]; }; /* diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c index 34b7afffac28..525345367260 100644 --- a/drivers/extcon/extcon-axp288.c +++ b/drivers/extcon/extcon-axp288.c @@ -443,9 +443,40 @@ static int axp288_extcon_probe(struct platform_device *pdev) /* Start charger cable type detection */ axp288_extcon_enable(info); + device_init_wakeup(dev, true); + platform_set_drvdata(pdev, info); + + return 0; +} + +static int __maybe_unused axp288_extcon_suspend(struct device *dev) +{ + struct axp288_extcon_info *info = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(info->irq[VBUS_RISING_IRQ]); + return 0; } +static int __maybe_unused axp288_extcon_resume(struct device *dev) +{ + struct axp288_extcon_info *info = dev_get_drvdata(dev); + + /* + * Wakeup when a charger is connected to do charger-type + * connection and generate an extcon event which makes the + * axp288 charger driver set the input current limit. + */ + if (device_may_wakeup(dev)) + disable_irq_wake(info->irq[VBUS_RISING_IRQ]); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(axp288_extcon_pm_ops, axp288_extcon_suspend, + axp288_extcon_resume); + static const struct platform_device_id axp288_extcon_table[] = { { .name = "axp288_extcon" }, {}, @@ -457,6 +488,7 @@ static struct platform_driver axp288_extcon_driver = { .id_table = axp288_extcon_table, .driver = { .name = "axp288_extcon", + .pm = &axp288_extcon_pm_ops, }, }; diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index edc5016f46f1..cea58d0cb457 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -205,14 +205,18 @@ static int palmas_usb_probe(struct platform_device *pdev) palmas_usb->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN); - if (IS_ERR(palmas_usb->id_gpiod)) { + if (PTR_ERR(palmas_usb->id_gpiod) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(palmas_usb->id_gpiod)) { dev_err(&pdev->dev, "failed to get id gpio\n"); return PTR_ERR(palmas_usb->id_gpiod); } palmas_usb->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", GPIOD_IN); - if (IS_ERR(palmas_usb->vbus_gpiod)) { + if (PTR_ERR(palmas_usb->vbus_gpiod) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(palmas_usb->vbus_gpiod)) { dev_err(&pdev->dev, "failed to get vbus gpio\n"); return PTR_ERR(palmas_usb->vbus_gpiod); } diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index e055893fd5c3..2dfbfec572f9 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -1406,6 +1406,7 @@ const char *extcon_get_edev_name(struct extcon_dev *edev) { return !edev ? NULL : edev->name; } +EXPORT_SYMBOL_GPL(extcon_get_edev_name); static int __init extcon_class_init(void) { diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index ea869addc89b..8007d4aa76dc 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -206,7 +206,7 @@ config FW_CFG_SYSFS_CMDLINE config INTEL_STRATIX10_SERVICE tristate "Intel Stratix10 Service Layer" - depends on ARCH_STRATIX10 && HAVE_ARM_SMCCC + depends on (ARCH_STRATIX10 || ARCH_AGILEX) && HAVE_ARM_SMCCC default n help Intel Stratix10 service layer runs at privileged exception level, diff --git a/drivers/firmware/imx/Kconfig b/drivers/firmware/imx/Kconfig index 1d2e5b85d7ca..116707a075f3 100644 --- a/drivers/firmware/imx/Kconfig +++ b/drivers/firmware/imx/Kconfig @@ -12,7 +12,7 @@ config IMX_DSP config IMX_SCU bool "IMX SCU Protocol driver" - depends on IMX_MBOX + depends on IMX_MBOX || COMPILE_TEST help The System Controller Firmware (SCFW) is a low-level system function which runs on a dedicated Cortex-M core to provide power, clock, and @@ -24,6 +24,6 @@ config IMX_SCU config IMX_SCU_PD bool "IMX SCU Power Domain driver" - depends on IMX_SCU + depends on IMX_SCU || COMPILE_TEST help The System Controller Firmware (SCFW) based power domain driver. diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 7ffb42b0775e..d5f0769f3761 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -966,6 +966,7 @@ EXPORT_SYMBOL_GPL(stratix10_svc_free_memory); static const struct of_device_id stratix10_svc_drv_match[] = { {.compatible = "intel,stratix10-svc"}, + {.compatible = "intel,agilex-svc"}, {}, }; diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 6ff30e25af55..83e841be1081 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -110,4 +110,25 @@ config CORESIGHT_CPU_DEBUG properly, please refer Documentation/trace/coresight-cpu-debug.rst for detailed description and the example for usage. +config CORESIGHT_CTI + bool "CoreSight Cross Trigger Interface (CTI) driver" + depends on ARM || ARM64 + help + This driver provides support for CoreSight CTI and CTM components. + These provide hardware triggering events between CoreSight trace + source and sink components. These can be used to halt trace or + inject events into the trace stream. CTI also provides a software + control to trigger the same halt events. This can provide fast trace + halt compared to disabling sources and sinks normally in driver + software. + +config CORESIGHT_CTI_INTEGRATION_REGS + bool "Access CTI CoreSight Integration Registers" + depends on CORESIGHT_CTI + help + This option adds support for the CoreSight integration registers on + this device. The integration registers allow the exploration of the + CTI trigger connections between this and other devices.These + registers are not used in normal operation and can leave devices in + an inconsistent state. endif diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 3c0ac421e211..0e3e72f0f510 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -17,3 +17,6 @@ obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o \ obj-$(CONFIG_CORESIGHT_STM) += coresight-stm.o obj-$(CONFIG_CORESIGHT_CPU_DEBUG) += coresight-cpu-debug.o obj-$(CONFIG_CORESIGHT_CATU) += coresight-catu.o +obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o \ + coresight-cti-platform.o \ + coresight-cti-sysfs.o diff --git a/drivers/hwtracing/coresight/coresight-cti-platform.c b/drivers/hwtracing/coresight/coresight-cti-platform.c new file mode 100644 index 000000000000..b44d83142b62 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-cti-platform.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019, The Linaro Limited. All rights reserved. + */ + +#include <dt-bindings/arm/coresight-cti-dt.h> +#include <linux/of.h> + +#include "coresight-cti.h" + +/* Number of CTI signals in the v8 architecturally defined connection */ +#define NR_V8PE_IN_SIGS 2 +#define NR_V8PE_OUT_SIGS 3 +#define NR_V8ETM_INOUT_SIGS 4 + +/* CTI device tree trigger connection node keyword */ +#define CTI_DT_CONNS "trig-conns" + +/* CTI device tree connection property keywords */ +#define CTI_DT_V8ARCH_COMPAT "arm,coresight-cti-v8-arch" +#define CTI_DT_CSDEV_ASSOC "arm,cs-dev-assoc" +#define CTI_DT_TRIGIN_SIGS "arm,trig-in-sigs" +#define CTI_DT_TRIGOUT_SIGS "arm,trig-out-sigs" +#define CTI_DT_TRIGIN_TYPES "arm,trig-in-types" +#define CTI_DT_TRIGOUT_TYPES "arm,trig-out-types" +#define CTI_DT_FILTER_OUT_SIGS "arm,trig-filters" +#define CTI_DT_CONN_NAME "arm,trig-conn-name" +#define CTI_DT_CTM_ID "arm,cti-ctm-id" + +#ifdef CONFIG_OF +/* + * CTI can be bound to a CPU, or a system device. + * CPU can be declared at the device top level or in a connections node + * so need to check relative to node not device. + */ +static int of_cti_get_cpu_at_node(const struct device_node *node) +{ + int cpu; + struct device_node *dn; + + if (node == NULL) + return -1; + + dn = of_parse_phandle(node, "cpu", 0); + /* CTI affinity defaults to no cpu */ + if (!dn) + return -1; + cpu = of_cpu_node_to_id(dn); + of_node_put(dn); + + /* No Affinity if no cpu nodes are found */ + return (cpu < 0) ? -1 : cpu; +} + +#else +static int of_cti_get_cpu_at_node(const struct device_node *node) +{ + return -1; +} + +#endif + +/* + * CTI can be bound to a CPU, or a system device. + * CPU can be declared at the device top level or in a connections node + * so need to check relative to node not device. + */ +static int cti_plat_get_cpu_at_node(struct fwnode_handle *fwnode) +{ + if (is_of_node(fwnode)) + return of_cti_get_cpu_at_node(to_of_node(fwnode)); + return -1; +} + +const char *cti_plat_get_node_name(struct fwnode_handle *fwnode) +{ + if (is_of_node(fwnode)) + return of_node_full_name(to_of_node(fwnode)); + return "unknown"; +} + +/* + * Extract a name from the fwnode. + * If the device associated with the node is a coresight_device, then return + * that name and the coresight_device pointer, otherwise return the node name. + */ +static const char * +cti_plat_get_csdev_or_node_name(struct fwnode_handle *fwnode, + struct coresight_device **csdev) +{ + const char *name = NULL; + *csdev = coresight_find_csdev_by_fwnode(fwnode); + if (*csdev) + name = dev_name(&(*csdev)->dev); + else + name = cti_plat_get_node_name(fwnode); + return name; +} + +static bool cti_plat_node_name_eq(struct fwnode_handle *fwnode, + const char *name) +{ + if (is_of_node(fwnode)) + return of_node_name_eq(to_of_node(fwnode), name); + return false; +} + +static int cti_plat_create_v8_etm_connection(struct device *dev, + struct cti_drvdata *drvdata) +{ + int ret = -ENOMEM, i; + struct fwnode_handle *root_fwnode, *cs_fwnode; + const char *assoc_name = NULL; + struct coresight_device *csdev; + struct cti_trig_con *tc = NULL; + + root_fwnode = dev_fwnode(dev); + if (IS_ERR_OR_NULL(root_fwnode)) + return -EINVAL; + + /* Can optionally have an etm node - return if not */ + cs_fwnode = fwnode_find_reference(root_fwnode, CTI_DT_CSDEV_ASSOC, 0); + if (IS_ERR_OR_NULL(cs_fwnode)) + return 0; + + /* allocate memory */ + tc = cti_allocate_trig_con(dev, NR_V8ETM_INOUT_SIGS, + NR_V8ETM_INOUT_SIGS); + if (!tc) + goto create_v8_etm_out; + + /* build connection data */ + tc->con_in->used_mask = 0xF0; /* sigs <4,5,6,7> */ + tc->con_out->used_mask = 0xF0; /* sigs <4,5,6,7> */ + + /* + * The EXTOUT type signals from the ETM are connected to a set of input + * triggers on the CTI, the EXTIN being connected to output triggers. + */ + for (i = 0; i < NR_V8ETM_INOUT_SIGS; i++) { + tc->con_in->sig_types[i] = ETM_EXTOUT; + tc->con_out->sig_types[i] = ETM_EXTIN; + } + + /* + * We look to see if the ETM coresight device associated with this + * handle has been registered with the system - i.e. probed before + * this CTI. If so csdev will be non NULL and we can use the device + * name and pass the csdev to the connection entry function where + * the association will be recorded. + * If not, then simply record the name in the connection data, the + * probing of the ETM will call into the CTI driver API to update the + * association then. + */ + assoc_name = cti_plat_get_csdev_or_node_name(cs_fwnode, &csdev); + ret = cti_add_connection_entry(dev, drvdata, tc, csdev, assoc_name); + +create_v8_etm_out: + fwnode_handle_put(cs_fwnode); + return ret; +} + +/* + * Create an architecturally defined v8 connection + * must have a cpu, can have an ETM. + */ +static int cti_plat_create_v8_connections(struct device *dev, + struct cti_drvdata *drvdata) +{ + struct cti_device *cti_dev = &drvdata->ctidev; + struct cti_trig_con *tc = NULL; + int cpuid = 0; + char cpu_name_str[16]; + int ret = -ENOMEM; + + /* Must have a cpu node */ + cpuid = cti_plat_get_cpu_at_node(dev_fwnode(dev)); + if (cpuid < 0) { + dev_warn(dev, + "ARM v8 architectural CTI connection: missing cpu\n"); + return -EINVAL; + } + cti_dev->cpu = cpuid; + + /* Allocate the v8 cpu connection memory */ + tc = cti_allocate_trig_con(dev, NR_V8PE_IN_SIGS, NR_V8PE_OUT_SIGS); + if (!tc) + goto of_create_v8_out; + + /* Set the v8 PE CTI connection data */ + tc->con_in->used_mask = 0x3; /* sigs <0 1> */ + tc->con_in->sig_types[0] = PE_DBGTRIGGER; + tc->con_in->sig_types[1] = PE_PMUIRQ; + tc->con_out->used_mask = 0x7; /* sigs <0 1 2 > */ + tc->con_out->sig_types[0] = PE_EDBGREQ; + tc->con_out->sig_types[1] = PE_DBGRESTART; + tc->con_out->sig_types[2] = PE_CTIIRQ; + scnprintf(cpu_name_str, sizeof(cpu_name_str), "cpu%d", cpuid); + + ret = cti_add_connection_entry(dev, drvdata, tc, NULL, cpu_name_str); + if (ret) + goto of_create_v8_out; + + /* Create the v8 ETM associated connection */ + ret = cti_plat_create_v8_etm_connection(dev, drvdata); + if (ret) + goto of_create_v8_out; + + /* filter pe_edbgreq - PE trigout sig <0> */ + drvdata->config.trig_out_filter |= 0x1; + +of_create_v8_out: + return ret; +} + +static int cti_plat_check_v8_arch_compatible(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + + if (is_of_node(fwnode)) + return of_device_is_compatible(to_of_node(fwnode), + CTI_DT_V8ARCH_COMPAT); + return 0; +} + +static int cti_plat_count_sig_elements(const struct fwnode_handle *fwnode, + const char *name) +{ + int nr_elem = fwnode_property_count_u32(fwnode, name); + + return (nr_elem < 0 ? 0 : nr_elem); +} + +static int cti_plat_read_trig_group(struct cti_trig_grp *tgrp, + const struct fwnode_handle *fwnode, + const char *grp_name) +{ + int idx, err = 0; + u32 *values; + + if (!tgrp->nr_sigs) + return 0; + + values = kcalloc(tgrp->nr_sigs, sizeof(u32), GFP_KERNEL); + if (!values) + return -ENOMEM; + + err = fwnode_property_read_u32_array(fwnode, grp_name, + values, tgrp->nr_sigs); + + if (!err) { + /* set the signal usage mask */ + for (idx = 0; idx < tgrp->nr_sigs; idx++) + tgrp->used_mask |= BIT(values[idx]); + } + + kfree(values); + return err; +} + +static int cti_plat_read_trig_types(struct cti_trig_grp *tgrp, + const struct fwnode_handle *fwnode, + const char *type_name) +{ + int items, err = 0, nr_sigs; + u32 *values = NULL, i; + + /* allocate an array according to number of signals in connection */ + nr_sigs = tgrp->nr_sigs; + if (!nr_sigs) + return 0; + + /* see if any types have been included in the device description */ + items = cti_plat_count_sig_elements(fwnode, type_name); + if (items > nr_sigs) + return -EINVAL; + + /* need an array to store the values iff there are any */ + if (items) { + values = kcalloc(items, sizeof(u32), GFP_KERNEL); + if (!values) + return -ENOMEM; + + err = fwnode_property_read_u32_array(fwnode, type_name, + values, items); + if (err) + goto read_trig_types_out; + } + + /* + * Match type id to signal index, 1st type to 1st index etc. + * If fewer types than signals default remainder to GEN_IO. + */ + for (i = 0; i < nr_sigs; i++) { + if (i < items) { + tgrp->sig_types[i] = + values[i] < CTI_TRIG_MAX ? values[i] : GEN_IO; + } else { + tgrp->sig_types[i] = GEN_IO; + } + } + +read_trig_types_out: + kfree(values); + return err; +} + +static int cti_plat_process_filter_sigs(struct cti_drvdata *drvdata, + const struct fwnode_handle *fwnode) +{ + struct cti_trig_grp *tg = NULL; + int err = 0, nr_filter_sigs; + + nr_filter_sigs = cti_plat_count_sig_elements(fwnode, + CTI_DT_FILTER_OUT_SIGS); + if (nr_filter_sigs == 0) + return 0; + + if (nr_filter_sigs > drvdata->config.nr_trig_max) + return -EINVAL; + + tg = kzalloc(sizeof(*tg), GFP_KERNEL); + if (!tg) + return -ENOMEM; + + err = cti_plat_read_trig_group(tg, fwnode, CTI_DT_FILTER_OUT_SIGS); + if (!err) + drvdata->config.trig_out_filter |= tg->used_mask; + + kfree(tg); + return err; +} + +static int cti_plat_create_connection(struct device *dev, + struct cti_drvdata *drvdata, + struct fwnode_handle *fwnode) +{ + struct cti_trig_con *tc = NULL; + int cpuid = -1, err = 0; + struct fwnode_handle *cs_fwnode = NULL; + struct coresight_device *csdev = NULL; + const char *assoc_name = "unknown"; + char cpu_name_str[16]; + int nr_sigs_in, nr_sigs_out; + + /* look to see how many in and out signals we have */ + nr_sigs_in = cti_plat_count_sig_elements(fwnode, CTI_DT_TRIGIN_SIGS); + nr_sigs_out = cti_plat_count_sig_elements(fwnode, CTI_DT_TRIGOUT_SIGS); + + if ((nr_sigs_in > drvdata->config.nr_trig_max) || + (nr_sigs_out > drvdata->config.nr_trig_max)) + return -EINVAL; + + tc = cti_allocate_trig_con(dev, nr_sigs_in, nr_sigs_out); + if (!tc) + return -ENOMEM; + + /* look for the signals properties. */ + err = cti_plat_read_trig_group(tc->con_in, fwnode, + CTI_DT_TRIGIN_SIGS); + if (err) + goto create_con_err; + + err = cti_plat_read_trig_types(tc->con_in, fwnode, + CTI_DT_TRIGIN_TYPES); + if (err) + goto create_con_err; + + err = cti_plat_read_trig_group(tc->con_out, fwnode, + CTI_DT_TRIGOUT_SIGS); + if (err) + goto create_con_err; + + err = cti_plat_read_trig_types(tc->con_out, fwnode, + CTI_DT_TRIGOUT_TYPES); + if (err) + goto create_con_err; + + err = cti_plat_process_filter_sigs(drvdata, fwnode); + if (err) + goto create_con_err; + + /* read the connection name if set - may be overridden by later */ + fwnode_property_read_string(fwnode, CTI_DT_CONN_NAME, &assoc_name); + + /* associated cpu ? */ + cpuid = cti_plat_get_cpu_at_node(fwnode); + if (cpuid >= 0) { + drvdata->ctidev.cpu = cpuid; + scnprintf(cpu_name_str, sizeof(cpu_name_str), "cpu%d", cpuid); + assoc_name = cpu_name_str; + } else { + /* associated device ? */ + cs_fwnode = fwnode_find_reference(fwnode, + CTI_DT_CSDEV_ASSOC, 0); + if (!IS_ERR_OR_NULL(cs_fwnode)) { + assoc_name = cti_plat_get_csdev_or_node_name(cs_fwnode, + &csdev); + fwnode_handle_put(cs_fwnode); + } + } + /* set up a connection */ + err = cti_add_connection_entry(dev, drvdata, tc, csdev, assoc_name); + +create_con_err: + return err; +} + +static int cti_plat_create_impdef_connections(struct device *dev, + struct cti_drvdata *drvdata) +{ + int rc = 0; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct fwnode_handle *child = NULL; + + if (IS_ERR_OR_NULL(fwnode)) + return -EINVAL; + + fwnode_for_each_child_node(fwnode, child) { + if (cti_plat_node_name_eq(child, CTI_DT_CONNS)) + rc = cti_plat_create_connection(dev, drvdata, + child); + if (rc != 0) + break; + } + fwnode_handle_put(child); + + return rc; +} + +/* get the hardware configuration & connection data. */ +int cti_plat_get_hw_data(struct device *dev, + struct cti_drvdata *drvdata) +{ + int rc = 0; + struct cti_device *cti_dev = &drvdata->ctidev; + + /* get any CTM ID - defaults to 0 */ + device_property_read_u32(dev, CTI_DT_CTM_ID, &cti_dev->ctm_id); + + /* check for a v8 architectural CTI device */ + if (cti_plat_check_v8_arch_compatible(dev)) + rc = cti_plat_create_v8_connections(dev, drvdata); + else + rc = cti_plat_create_impdef_connections(dev, drvdata); + if (rc) + return rc; + + /* if no connections, just add a single default based on max IN-OUT */ + if (cti_dev->nr_trig_con == 0) + rc = cti_add_default_connection(dev, drvdata); + return rc; +} + +struct coresight_platform_data * +coresight_cti_get_platform_data(struct device *dev) +{ + int ret = -ENOENT; + struct coresight_platform_data *pdata = NULL; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct cti_drvdata *drvdata = dev_get_drvdata(dev); + + if (IS_ERR_OR_NULL(fwnode)) + goto error; + + /* + * Alloc platform data but leave it zero init. CTI does not use the + * same connection infrastructuree as trace path components but an + * empty struct enables us to use the standard coresight component + * registration code. + */ + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto error; + } + + /* get some CTI specifics */ + ret = cti_plat_get_hw_data(dev, drvdata); + + if (!ret) + return pdata; +error: + return ERR_PTR(ret); +} diff --git a/drivers/hwtracing/coresight/coresight-cti-sysfs.c b/drivers/hwtracing/coresight/coresight-cti-sysfs.c new file mode 100644 index 000000000000..1f8fb7c15e80 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-cti-sysfs.c @@ -0,0 +1,1206 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Linaro Limited, All rights reserved. + * Author: Mike Leach <mike.leach@linaro.org> + */ + +#include <linux/coresight.h> + +#include "coresight-cti.h" + +/* + * Declare the number of static declared attribute groups + * Value includes groups + NULL value at end of table. + */ +#define CORESIGHT_CTI_STATIC_GROUPS_MAX 5 + +/* + * List of trigger signal type names. Match the constants declared in + * include\dt-bindings\arm\coresight-cti-dt.h + */ +static const char * const sig_type_names[] = { + "genio", /* GEN_IO */ + "intreq", /* GEN_INTREQ */ + "intack", /* GEN_INTACK */ + "haltreq", /* GEN_HALTREQ */ + "restartreq", /* GEN_RESTARTREQ */ + "pe_edbgreq", /* PE_EDBGREQ */ + "pe_dbgrestart",/* PE_DBGRESTART */ + "pe_ctiirq", /* PE_CTIIRQ */ + "pe_pmuirq", /* PE_PMUIRQ */ + "pe_dbgtrigger",/* PE_DBGTRIGGER */ + "etm_extout", /* ETM_EXTOUT */ + "etm_extin", /* ETM_EXTIN */ + "snk_full", /* SNK_FULL */ + "snk_acqcomp", /* SNK_ACQCOMP */ + "snk_flushcomp",/* SNK_FLUSHCOMP */ + "snk_flushin", /* SNK_FLUSHIN */ + "snk_trigin", /* SNK_TRIGIN */ + "stm_asyncout", /* STM_ASYNCOUT */ + "stm_tout_spte",/* STM_TOUT_SPTE */ + "stm_tout_sw", /* STM_TOUT_SW */ + "stm_tout_hete",/* STM_TOUT_HETE */ + "stm_hwevent", /* STM_HWEVENT */ + "ela_tstart", /* ELA_TSTART */ + "ela_tstop", /* ELA_TSTOP */ + "ela_dbgreq", /* ELA_DBGREQ */ +}; + +/* Show function pointer used in the connections dynamic declared attributes*/ +typedef ssize_t (*p_show_fn)(struct device *dev, struct device_attribute *attr, + char *buf); + +/* Connection attribute types */ +enum cti_conn_attr_type { + CTI_CON_ATTR_NAME, + CTI_CON_ATTR_TRIGIN_SIG, + CTI_CON_ATTR_TRIGOUT_SIG, + CTI_CON_ATTR_TRIGIN_TYPES, + CTI_CON_ATTR_TRIGOUT_TYPES, + CTI_CON_ATTR_MAX, +}; + +/* Names for the connection attributes */ +static const char * const con_attr_names[CTI_CON_ATTR_MAX] = { + "name", + "in_signals", + "out_signals", + "in_types", + "out_types", +}; + +/* basic attributes */ +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int enable_req; + bool enabled, powered; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + enable_req = atomic_read(&drvdata->config.enable_req_count); + spin_lock(&drvdata->spinlock); + powered = drvdata->config.hw_powered; + enabled = drvdata->config.hw_enabled; + spin_unlock(&drvdata->spinlock); + + if (powered) + return sprintf(buf, "%d\n", enabled); + else + return sprintf(buf, "%d\n", !!enable_req); +} + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret = 0; + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + + if (val) + ret = cti_enable(drvdata->csdev); + else + ret = cti_disable(drvdata->csdev); + if (ret) + return ret; + return size; +} +static DEVICE_ATTR_RW(enable); + +static ssize_t powered_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + bool powered; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + powered = drvdata->config.hw_powered; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%d\n", powered); +} +static DEVICE_ATTR_RO(powered); + +static ssize_t ctmid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return sprintf(buf, "%d\n", drvdata->ctidev.ctm_id); +} +static DEVICE_ATTR_RO(ctmid); + +static ssize_t nr_trigger_cons_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return sprintf(buf, "%d\n", drvdata->ctidev.nr_trig_con); +} +static DEVICE_ATTR_RO(nr_trigger_cons); + +/* attribute and group sysfs tables. */ +static struct attribute *coresight_cti_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_powered.attr, + &dev_attr_ctmid.attr, + &dev_attr_nr_trigger_cons.attr, + NULL, +}; + +/* register based attributes */ + +/* macro to access RO registers with power check only (no enable check). */ +#define coresight_cti_reg(name, offset) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ + u32 val = 0; \ + pm_runtime_get_sync(dev->parent); \ + spin_lock(&drvdata->spinlock); \ + if (drvdata->config.hw_powered) \ + val = readl_relaxed(drvdata->base + offset); \ + spin_unlock(&drvdata->spinlock); \ + pm_runtime_put_sync(dev->parent); \ + return sprintf(buf, "0x%x\n", val); \ +} \ +static DEVICE_ATTR_RO(name) + +/* coresight management registers */ +coresight_cti_reg(devaff0, CTIDEVAFF0); +coresight_cti_reg(devaff1, CTIDEVAFF1); +coresight_cti_reg(authstatus, CORESIGHT_AUTHSTATUS); +coresight_cti_reg(devarch, CORESIGHT_DEVARCH); +coresight_cti_reg(devid, CORESIGHT_DEVID); +coresight_cti_reg(devtype, CORESIGHT_DEVTYPE); +coresight_cti_reg(pidr0, CORESIGHT_PERIPHIDR0); +coresight_cti_reg(pidr1, CORESIGHT_PERIPHIDR1); +coresight_cti_reg(pidr2, CORESIGHT_PERIPHIDR2); +coresight_cti_reg(pidr3, CORESIGHT_PERIPHIDR3); +coresight_cti_reg(pidr4, CORESIGHT_PERIPHIDR4); + +static struct attribute *coresight_cti_mgmt_attrs[] = { + &dev_attr_devaff0.attr, + &dev_attr_devaff1.attr, + &dev_attr_authstatus.attr, + &dev_attr_devarch.attr, + &dev_attr_devid.attr, + &dev_attr_devtype.attr, + &dev_attr_pidr0.attr, + &dev_attr_pidr1.attr, + &dev_attr_pidr2.attr, + &dev_attr_pidr3.attr, + &dev_attr_pidr4.attr, + NULL, +}; + +/* CTI low level programming registers */ + +/* + * Show a simple 32 bit value if enabled and powered. + * If inaccessible & pcached_val not NULL then show cached value. + */ +static ssize_t cti_reg32_show(struct device *dev, char *buf, + u32 *pcached_val, int reg_offset) +{ + u32 val = 0; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + spin_lock(&drvdata->spinlock); + if ((reg_offset >= 0) && cti_active(config)) { + CS_UNLOCK(drvdata->base); + val = readl_relaxed(drvdata->base + reg_offset); + if (pcached_val) + *pcached_val = val; + CS_LOCK(drvdata->base); + } else if (pcached_val) { + val = *pcached_val; + } + spin_unlock(&drvdata->spinlock); + return sprintf(buf, "%#x\n", val); +} + +/* + * Store a simple 32 bit value. + * If pcached_val not NULL, then copy to here too, + * if reg_offset >= 0 then write through if enabled. + */ +static ssize_t cti_reg32_store(struct device *dev, const char *buf, + size_t size, u32 *pcached_val, int reg_offset) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + /* local store */ + if (pcached_val) + *pcached_val = (u32)val; + + /* write through if offset and enabled */ + if ((reg_offset >= 0) && cti_active(config)) + cti_write_single_reg(drvdata, reg_offset, val); + spin_unlock(&drvdata->spinlock); + return size; +} + +/* Standard macro for simple rw cti config registers */ +#define cti_config_reg32_rw(name, cfgname, offset) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ + return cti_reg32_show(dev, buf, \ + &drvdata->config.cfgname, offset); \ +} \ + \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ + return cti_reg32_store(dev, buf, size, \ + &drvdata->config.cfgname, offset); \ +} \ +static DEVICE_ATTR_RW(name) + +static ssize_t inout_sel_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u32 val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = (u32)drvdata->config.ctiinout_sel; + return sprintf(buf, "%d\n", val); +} + +static ssize_t inout_sel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + if (val > (CTIINOUTEN_MAX - 1)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + drvdata->config.ctiinout_sel = val; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(inout_sel); + +static ssize_t inen_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned long val; + int index; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + index = drvdata->config.ctiinout_sel; + val = drvdata->config.ctiinen[index]; + spin_unlock(&drvdata->spinlock); + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t inen_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + int index; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + index = config->ctiinout_sel; + config->ctiinen[index] = val; + + /* write through if enabled */ + if (cti_active(config)) + cti_write_single_reg(drvdata, CTIINEN(index), val); + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(inen); + +static ssize_t outen_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned long val; + int index; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + index = drvdata->config.ctiinout_sel; + val = drvdata->config.ctiouten[index]; + spin_unlock(&drvdata->spinlock); + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t outen_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + int index; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + index = config->ctiinout_sel; + config->ctiouten[index] = val; + + /* write through if enabled */ + if (cti_active(config)) + cti_write_single_reg(drvdata, CTIOUTEN(index), val); + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(outen); + +static ssize_t intack_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + cti_write_intack(dev, val); + return size; +} +static DEVICE_ATTR_WO(intack); + +cti_config_reg32_rw(gate, ctigate, CTIGATE); +cti_config_reg32_rw(asicctl, asicctl, ASICCTL); +cti_config_reg32_rw(appset, ctiappset, CTIAPPSET); + +static ssize_t appclear_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + + /* a 1'b1 in appclr clears down the same bit in appset*/ + config->ctiappset &= ~val; + + /* write through if enabled */ + if (cti_active(config)) + cti_write_single_reg(drvdata, CTIAPPCLEAR, val); + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_WO(appclear); + +static ssize_t apppulse_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + + /* write through if enabled */ + if (cti_active(config)) + cti_write_single_reg(drvdata, CTIAPPPULSE, val); + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_WO(apppulse); + +coresight_cti_reg(triginstatus, CTITRIGINSTATUS); +coresight_cti_reg(trigoutstatus, CTITRIGOUTSTATUS); +coresight_cti_reg(chinstatus, CTICHINSTATUS); +coresight_cti_reg(choutstatus, CTICHOUTSTATUS); + +/* + * Define CONFIG_CORESIGHT_CTI_INTEGRATION_REGS to enable the access to the + * integration control registers. Normally only used to investigate connection + * data. + */ +#ifdef CONFIG_CORESIGHT_CTI_INTEGRATION_REGS + +/* macro to access RW registers with power check only (no enable check). */ +#define coresight_cti_reg_rw(name, offset) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ + u32 val = 0; \ + pm_runtime_get_sync(dev->parent); \ + spin_lock(&drvdata->spinlock); \ + if (drvdata->config.hw_powered) \ + val = readl_relaxed(drvdata->base + offset); \ + spin_unlock(&drvdata->spinlock); \ + pm_runtime_put_sync(dev->parent); \ + return sprintf(buf, "0x%x\n", val); \ +} \ + \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ + unsigned long val = 0; \ + if (kstrtoul(buf, 0, &val)) \ + return -EINVAL; \ + \ + pm_runtime_get_sync(dev->parent); \ + spin_lock(&drvdata->spinlock); \ + if (drvdata->config.hw_powered) \ + cti_write_single_reg(drvdata, offset, val); \ + spin_unlock(&drvdata->spinlock); \ + pm_runtime_put_sync(dev->parent); \ + return size; \ +} \ +static DEVICE_ATTR_RW(name) + +/* macro to access WO registers with power check only (no enable check). */ +#define coresight_cti_reg_wo(name, offset) \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \ + unsigned long val = 0; \ + if (kstrtoul(buf, 0, &val)) \ + return -EINVAL; \ + \ + pm_runtime_get_sync(dev->parent); \ + spin_lock(&drvdata->spinlock); \ + if (drvdata->config.hw_powered) \ + cti_write_single_reg(drvdata, offset, val); \ + spin_unlock(&drvdata->spinlock); \ + pm_runtime_put_sync(dev->parent); \ + return size; \ +} \ +static DEVICE_ATTR_WO(name) + +coresight_cti_reg_rw(itchout, ITCHOUT); +coresight_cti_reg_rw(ittrigout, ITTRIGOUT); +coresight_cti_reg_rw(itctrl, CORESIGHT_ITCTRL); +coresight_cti_reg_wo(itchinack, ITCHINACK); +coresight_cti_reg_wo(ittriginack, ITTRIGINACK); +coresight_cti_reg(ittrigin, ITTRIGIN); +coresight_cti_reg(itchin, ITCHIN); +coresight_cti_reg(itchoutack, ITCHOUTACK); +coresight_cti_reg(ittrigoutack, ITTRIGOUTACK); + +#endif /* CORESIGHT_CTI_INTEGRATION_REGS */ + +static struct attribute *coresight_cti_regs_attrs[] = { + &dev_attr_inout_sel.attr, + &dev_attr_inen.attr, + &dev_attr_outen.attr, + &dev_attr_gate.attr, + &dev_attr_asicctl.attr, + &dev_attr_intack.attr, + &dev_attr_appset.attr, + &dev_attr_appclear.attr, + &dev_attr_apppulse.attr, + &dev_attr_triginstatus.attr, + &dev_attr_trigoutstatus.attr, + &dev_attr_chinstatus.attr, + &dev_attr_choutstatus.attr, +#ifdef CONFIG_CORESIGHT_CTI_INTEGRATION_REGS + &dev_attr_itctrl.attr, + &dev_attr_ittrigin.attr, + &dev_attr_itchin.attr, + &dev_attr_ittrigout.attr, + &dev_attr_itchout.attr, + &dev_attr_itchoutack.attr, + &dev_attr_ittrigoutack.attr, + &dev_attr_ittriginack.attr, + &dev_attr_itchinack.attr, +#endif + NULL, +}; + +/* CTI channel x-trigger programming */ +static int +cti_trig_op_parse(struct device *dev, enum cti_chan_op op, + enum cti_trig_dir dir, const char *buf, size_t size) +{ + u32 chan_idx; + u32 trig_idx; + int items, err = -EINVAL; + + /* extract chan idx and trigger idx */ + items = sscanf(buf, "%d %d", &chan_idx, &trig_idx); + if (items == 2) { + err = cti_channel_trig_op(dev, op, dir, chan_idx, trig_idx); + if (!err) + err = size; + } + return err; +} + +static ssize_t trigin_attach_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return cti_trig_op_parse(dev, CTI_CHAN_ATTACH, CTI_TRIG_IN, + buf, size); +} +static DEVICE_ATTR_WO(trigin_attach); + +static ssize_t trigin_detach_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return cti_trig_op_parse(dev, CTI_CHAN_DETACH, CTI_TRIG_IN, + buf, size); +} +static DEVICE_ATTR_WO(trigin_detach); + +static ssize_t trigout_attach_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return cti_trig_op_parse(dev, CTI_CHAN_ATTACH, CTI_TRIG_OUT, + buf, size); +} +static DEVICE_ATTR_WO(trigout_attach); + +static ssize_t trigout_detach_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return cti_trig_op_parse(dev, CTI_CHAN_DETACH, CTI_TRIG_OUT, + buf, size); +} +static DEVICE_ATTR_WO(trigout_detach); + + +static ssize_t chan_gate_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = 0, channel = 0; + + if (kstrtoint(buf, 0, &channel)) + return -EINVAL; + + err = cti_channel_gate_op(dev, CTI_GATE_CHAN_ENABLE, channel); + return err ? err : size; +} + +static ssize_t chan_gate_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *cfg = &drvdata->config; + unsigned long ctigate_bitmask = cfg->ctigate; + int size = 0; + + if (cfg->ctigate == 0) + size = sprintf(buf, "\n"); + else + size = bitmap_print_to_pagebuf(true, buf, &ctigate_bitmask, + cfg->nr_ctm_channels); + return size; +} +static DEVICE_ATTR_RW(chan_gate_enable); + +static ssize_t chan_gate_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = 0, channel = 0; + + if (kstrtoint(buf, 0, &channel)) + return -EINVAL; + + err = cti_channel_gate_op(dev, CTI_GATE_CHAN_DISABLE, channel); + return err ? err : size; +} +static DEVICE_ATTR_WO(chan_gate_disable); + +static int +chan_op_parse(struct device *dev, enum cti_chan_set_op op, const char *buf) +{ + int err = 0, channel = 0; + + if (kstrtoint(buf, 0, &channel)) + return -EINVAL; + + err = cti_channel_setop(dev, op, channel); + return err; + +} + +static ssize_t chan_set_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = chan_op_parse(dev, CTI_CHAN_SET, buf); + + return err ? err : size; +} +static DEVICE_ATTR_WO(chan_set); + +static ssize_t chan_clear_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = chan_op_parse(dev, CTI_CHAN_CLR, buf); + + return err ? err : size; +} +static DEVICE_ATTR_WO(chan_clear); + +static ssize_t chan_pulse_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = chan_op_parse(dev, CTI_CHAN_PULSE, buf); + + return err ? err : size; +} +static DEVICE_ATTR_WO(chan_pulse); + +static ssize_t trig_filter_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u32 val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->config.trig_filter_enable; + spin_unlock(&drvdata->spinlock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t trig_filter_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + drvdata->config.trig_filter_enable = !!val; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(trig_filter_enable); + +static ssize_t trigout_filtered_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *cfg = &drvdata->config; + int size = 0, nr_trig_max = cfg->nr_trig_max; + unsigned long mask = cfg->trig_out_filter; + + if (mask) + size = bitmap_print_to_pagebuf(true, buf, &mask, nr_trig_max); + return size; +} +static DEVICE_ATTR_RO(trigout_filtered); + +/* clear all xtrigger / channel programming */ +static ssize_t chan_xtrigs_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int i; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + spin_lock(&drvdata->spinlock); + + /* clear the CTI trigger / channel programming registers */ + for (i = 0; i < config->nr_trig_max; i++) { + config->ctiinen[i] = 0; + config->ctiouten[i] = 0; + } + + /* clear the other regs */ + config->ctigate = GENMASK(config->nr_ctm_channels - 1, 0); + config->asicctl = 0; + config->ctiappset = 0; + config->ctiinout_sel = 0; + config->xtrig_rchan_sel = 0; + + /* if enabled then write through */ + if (cti_active(config)) + cti_write_all_hw_regs(drvdata); + + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_WO(chan_xtrigs_reset); + +/* + * Write to select a channel to view, read to display the + * cross triggers for the selected channel. + */ +static ssize_t chan_xtrigs_sel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + if (val > (drvdata->config.nr_ctm_channels - 1)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + drvdata->config.xtrig_rchan_sel = val; + spin_unlock(&drvdata->spinlock); + return size; +} + +static ssize_t chan_xtrigs_sel_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned long val; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->config.xtrig_rchan_sel; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%ld\n", val); +} +static DEVICE_ATTR_RW(chan_xtrigs_sel); + +static ssize_t chan_xtrigs_in_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *cfg = &drvdata->config; + int used = 0, reg_idx; + int nr_trig_max = drvdata->config.nr_trig_max; + u32 chan_mask = BIT(cfg->xtrig_rchan_sel); + + for (reg_idx = 0; reg_idx < nr_trig_max; reg_idx++) { + if (chan_mask & cfg->ctiinen[reg_idx]) + used += sprintf(buf + used, "%d ", reg_idx); + } + + used += sprintf(buf + used, "\n"); + return used; +} +static DEVICE_ATTR_RO(chan_xtrigs_in); + +static ssize_t chan_xtrigs_out_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *cfg = &drvdata->config; + int used = 0, reg_idx; + int nr_trig_max = drvdata->config.nr_trig_max; + u32 chan_mask = BIT(cfg->xtrig_rchan_sel); + + for (reg_idx = 0; reg_idx < nr_trig_max; reg_idx++) { + if (chan_mask & cfg->ctiouten[reg_idx]) + used += sprintf(buf + used, "%d ", reg_idx); + } + + used += sprintf(buf + used, "\n"); + return used; +} +static DEVICE_ATTR_RO(chan_xtrigs_out); + +static ssize_t print_chan_list(struct device *dev, + char *buf, bool inuse) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + int size, i; + unsigned long inuse_bits = 0, chan_mask; + + /* scan regs to get bitmap of channels in use. */ + spin_lock(&drvdata->spinlock); + for (i = 0; i < config->nr_trig_max; i++) { + inuse_bits |= config->ctiinen[i]; + inuse_bits |= config->ctiouten[i]; + } + spin_unlock(&drvdata->spinlock); + + /* inverse bits if printing free channels */ + if (!inuse) + inuse_bits = ~inuse_bits; + + /* list of channels, or 'none' */ + chan_mask = GENMASK(config->nr_ctm_channels - 1, 0); + if (inuse_bits & chan_mask) + size = bitmap_print_to_pagebuf(true, buf, &inuse_bits, + config->nr_ctm_channels); + else + size = sprintf(buf, "\n"); + return size; +} + +static ssize_t chan_inuse_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return print_chan_list(dev, buf, true); +} +static DEVICE_ATTR_RO(chan_inuse); + +static ssize_t chan_free_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return print_chan_list(dev, buf, false); +} +static DEVICE_ATTR_RO(chan_free); + +static struct attribute *coresight_cti_channel_attrs[] = { + &dev_attr_trigin_attach.attr, + &dev_attr_trigin_detach.attr, + &dev_attr_trigout_attach.attr, + &dev_attr_trigout_detach.attr, + &dev_attr_trig_filter_enable.attr, + &dev_attr_trigout_filtered.attr, + &dev_attr_chan_gate_enable.attr, + &dev_attr_chan_gate_disable.attr, + &dev_attr_chan_set.attr, + &dev_attr_chan_clear.attr, + &dev_attr_chan_pulse.attr, + &dev_attr_chan_inuse.attr, + &dev_attr_chan_free.attr, + &dev_attr_chan_xtrigs_sel.attr, + &dev_attr_chan_xtrigs_in.attr, + &dev_attr_chan_xtrigs_out.attr, + &dev_attr_chan_xtrigs_reset.attr, + NULL, +}; + +/* Create the connections trigger groups and attrs dynamically */ +/* + * Each connection has dynamic group triggers<N> + name, trigin/out sigs/types + * attributes, + each device has static nr_trigger_cons giving the number + * of groups. e.g. in sysfs:- + * /cti_<name>/triggers0 + * /cti_<name>/triggers1 + * /cti_<name>/nr_trigger_cons + * where nr_trigger_cons = 2 + */ +static ssize_t con_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ext_attr = + container_of(attr, struct dev_ext_attribute, attr); + struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; + + return sprintf(buf, "%s\n", con->con_dev_name); +} + +static ssize_t trigin_sig_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ext_attr = + container_of(attr, struct dev_ext_attribute, attr); + struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *cfg = &drvdata->config; + unsigned long mask = con->con_in->used_mask; + + return bitmap_print_to_pagebuf(true, buf, &mask, cfg->nr_trig_max); +} + +static ssize_t trigout_sig_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ext_attr = + container_of(attr, struct dev_ext_attribute, attr); + struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *cfg = &drvdata->config; + unsigned long mask = con->con_out->used_mask; + + return bitmap_print_to_pagebuf(true, buf, &mask, cfg->nr_trig_max); +} + +/* convert a sig type id to a name */ +static const char * +cti_sig_type_name(struct cti_trig_con *con, int used_count, bool in) +{ + int idx = 0; + struct cti_trig_grp *grp = in ? con->con_in : con->con_out; + + if (used_count < grp->nr_sigs) + idx = grp->sig_types[used_count]; + return sig_type_names[idx]; +} + +static ssize_t trigin_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ext_attr = + container_of(attr, struct dev_ext_attribute, attr); + struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; + int sig_idx, used = 0; + const char *name; + + for (sig_idx = 0; sig_idx < con->con_in->nr_sigs; sig_idx++) { + name = cti_sig_type_name(con, sig_idx, true); + used += sprintf(buf + used, "%s ", name); + } + used += sprintf(buf + used, "\n"); + return used; +} + +static ssize_t trigout_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ext_attr = + container_of(attr, struct dev_ext_attribute, attr); + struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var; + int sig_idx, used = 0; + const char *name; + + for (sig_idx = 0; sig_idx < con->con_out->nr_sigs; sig_idx++) { + name = cti_sig_type_name(con, sig_idx, false); + used += sprintf(buf + used, "%s ", name); + } + used += sprintf(buf + used, "\n"); + return used; +} + +/* + * Array of show function names declared above to allow selection + * for the connection attributes + */ +static p_show_fn show_fns[CTI_CON_ATTR_MAX] = { + con_name_show, + trigin_sig_show, + trigout_sig_show, + trigin_type_show, + trigout_type_show, +}; + +static int cti_create_con_sysfs_attr(struct device *dev, + struct cti_trig_con *con, + enum cti_conn_attr_type attr_type, + int attr_idx) +{ + struct dev_ext_attribute *eattr = 0; + char *name = 0; + + eattr = devm_kzalloc(dev, sizeof(struct dev_ext_attribute), + GFP_KERNEL); + if (eattr) { + name = devm_kstrdup(dev, con_attr_names[attr_type], + GFP_KERNEL); + if (name) { + /* fill out the underlying attribute struct */ + eattr->attr.attr.name = name; + eattr->attr.attr.mode = 0444; + + /* now the device_attribute struct */ + eattr->attr.show = show_fns[attr_type]; + } else { + return -ENOMEM; + } + } else { + return -ENOMEM; + } + eattr->var = con; + con->con_attrs[attr_idx] = &eattr->attr.attr; + return 0; +} + +static struct attribute_group * +cti_create_con_sysfs_group(struct device *dev, struct cti_device *ctidev, + int con_idx, struct cti_trig_con *tc) +{ + struct attribute_group *group = NULL; + int grp_idx; + + group = devm_kzalloc(dev, sizeof(struct attribute_group), GFP_KERNEL); + if (!group) + return NULL; + + group->name = devm_kasprintf(dev, GFP_KERNEL, "triggers%d", con_idx); + if (!group->name) + return NULL; + + grp_idx = con_idx + CORESIGHT_CTI_STATIC_GROUPS_MAX - 1; + ctidev->con_groups[grp_idx] = group; + tc->attr_group = group; + return group; +} + +/* create a triggers connection group and the attributes for that group */ +static int cti_create_con_attr_set(struct device *dev, int con_idx, + struct cti_device *ctidev, + struct cti_trig_con *tc) +{ + struct attribute_group *attr_group = NULL; + int attr_idx = 0; + int err = -ENOMEM; + + attr_group = cti_create_con_sysfs_group(dev, ctidev, con_idx, tc); + if (!attr_group) + return -ENOMEM; + + /* allocate NULL terminated array of attributes */ + tc->con_attrs = devm_kcalloc(dev, CTI_CON_ATTR_MAX + 1, + sizeof(struct attribute *), GFP_KERNEL); + if (!tc->con_attrs) + return -ENOMEM; + + err = cti_create_con_sysfs_attr(dev, tc, CTI_CON_ATTR_NAME, + attr_idx++); + if (err) + return err; + + if (tc->con_in->nr_sigs > 0) { + err = cti_create_con_sysfs_attr(dev, tc, + CTI_CON_ATTR_TRIGIN_SIG, + attr_idx++); + if (err) + return err; + + err = cti_create_con_sysfs_attr(dev, tc, + CTI_CON_ATTR_TRIGIN_TYPES, + attr_idx++); + if (err) + return err; + } + + if (tc->con_out->nr_sigs > 0) { + err = cti_create_con_sysfs_attr(dev, tc, + CTI_CON_ATTR_TRIGOUT_SIG, + attr_idx++); + if (err) + return err; + + err = cti_create_con_sysfs_attr(dev, tc, + CTI_CON_ATTR_TRIGOUT_TYPES, + attr_idx++); + if (err) + return err; + } + attr_group->attrs = tc->con_attrs; + return 0; +} + +/* create the array of group pointers for the CTI sysfs groups */ +int cti_create_cons_groups(struct device *dev, struct cti_device *ctidev) +{ + int nr_groups; + + /* nr groups = dynamic + static + NULL terminator */ + nr_groups = ctidev->nr_trig_con + CORESIGHT_CTI_STATIC_GROUPS_MAX; + ctidev->con_groups = devm_kcalloc(dev, nr_groups, + sizeof(struct attribute_group *), + GFP_KERNEL); + if (!ctidev->con_groups) + return -ENOMEM; + return 0; +} + +int cti_create_cons_sysfs(struct device *dev, struct cti_drvdata *drvdata) +{ + struct cti_device *ctidev = &drvdata->ctidev; + int err = 0, con_idx = 0, i; + struct cti_trig_con *tc = NULL; + + err = cti_create_cons_groups(dev, ctidev); + if (err) + return err; + + /* populate first locations with the static set of groups */ + for (i = 0; i < (CORESIGHT_CTI_STATIC_GROUPS_MAX - 1); i++) + ctidev->con_groups[i] = coresight_cti_groups[i]; + + /* add dynamic set for each connection */ + list_for_each_entry(tc, &ctidev->trig_cons, node) { + err = cti_create_con_attr_set(dev, con_idx++, ctidev, tc); + if (err) + break; + } + return err; +} + +/* attribute and group sysfs tables. */ +static const struct attribute_group coresight_cti_group = { + .attrs = coresight_cti_attrs, +}; + +static const struct attribute_group coresight_cti_mgmt_group = { + .attrs = coresight_cti_mgmt_attrs, + .name = "mgmt", +}; + +static const struct attribute_group coresight_cti_regs_group = { + .attrs = coresight_cti_regs_attrs, + .name = "regs", +}; + +static const struct attribute_group coresight_cti_channels_group = { + .attrs = coresight_cti_channel_attrs, + .name = "channels", +}; + +const struct attribute_group * +coresight_cti_groups[CORESIGHT_CTI_STATIC_GROUPS_MAX] = { + &coresight_cti_group, + &coresight_cti_mgmt_group, + &coresight_cti_regs_group, + &coresight_cti_channels_group, + NULL, +}; diff --git a/drivers/hwtracing/coresight/coresight-cti.c b/drivers/hwtracing/coresight/coresight-cti.c new file mode 100644 index 000000000000..aa6e0249bd70 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-cti.c @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Linaro Limited, All rights reserved. + * Author: Mike Leach <mike.leach@linaro.org> + */ + +#include <linux/property.h> +#include "coresight-cti.h" + +/** + * CTI devices can be associated with a PE, or be connected to CoreSight + * hardware. We have a list of all CTIs irrespective of CPU bound or + * otherwise. + * + * We assume that the non-CPU CTIs are always powered as we do with sinks etc. + * + * We leave the client to figure out if all the CTIs are interconnected with + * the same CTM, in general this is the case but does not always have to be. + */ + +/* net of CTI devices connected via CTM */ +LIST_HEAD(ect_net); + +/* protect the list */ +static DEFINE_MUTEX(ect_mutex); + +#define csdev_to_cti_drvdata(csdev) \ + dev_get_drvdata(csdev->dev.parent) + +/* + * CTI naming. CTI bound to cores will have the name cti_cpu<N> where + * N is the CPU ID. System CTIs will have the name cti_sys<I> where I + * is an index allocated by order of discovery. + * + * CTI device name list - for CTI not bound to cores. + */ +DEFINE_CORESIGHT_DEVLIST(cti_sys_devs, "cti_sys"); + +/* write set of regs to hardware - call with spinlock claimed */ +void cti_write_all_hw_regs(struct cti_drvdata *drvdata) +{ + struct cti_config *config = &drvdata->config; + int i; + + CS_UNLOCK(drvdata->base); + + /* disable CTI before writing registers */ + writel_relaxed(0, drvdata->base + CTICONTROL); + + /* write the CTI trigger registers */ + for (i = 0; i < config->nr_trig_max; i++) { + writel_relaxed(config->ctiinen[i], drvdata->base + CTIINEN(i)); + writel_relaxed(config->ctiouten[i], + drvdata->base + CTIOUTEN(i)); + } + + /* other regs */ + writel_relaxed(config->ctigate, drvdata->base + CTIGATE); + writel_relaxed(config->asicctl, drvdata->base + ASICCTL); + writel_relaxed(config->ctiappset, drvdata->base + CTIAPPSET); + + /* re-enable CTI */ + writel_relaxed(1, drvdata->base + CTICONTROL); + + CS_LOCK(drvdata->base); +} + +static void cti_enable_hw_smp_call(void *info) +{ + struct cti_drvdata *drvdata = info; + + cti_write_all_hw_regs(drvdata); +} + +/* write regs to hardware and enable */ +static int cti_enable_hw(struct cti_drvdata *drvdata) +{ + struct cti_config *config = &drvdata->config; + struct device *dev = &drvdata->csdev->dev; + int rc = 0; + + pm_runtime_get_sync(dev->parent); + spin_lock(&drvdata->spinlock); + + /* no need to do anything if enabled or unpowered*/ + if (config->hw_enabled || !config->hw_powered) + goto cti_state_unchanged; + + /* claim the device */ + rc = coresight_claim_device(drvdata->base); + if (rc) + goto cti_err_not_enabled; + + if (drvdata->ctidev.cpu >= 0) { + rc = smp_call_function_single(drvdata->ctidev.cpu, + cti_enable_hw_smp_call, + drvdata, 1); + if (rc) + goto cti_err_not_enabled; + } else { + cti_write_all_hw_regs(drvdata); + } + + config->hw_enabled = true; + atomic_inc(&drvdata->config.enable_req_count); + spin_unlock(&drvdata->spinlock); + return rc; + +cti_state_unchanged: + atomic_inc(&drvdata->config.enable_req_count); + + /* cannot enable due to error */ +cti_err_not_enabled: + spin_unlock(&drvdata->spinlock); + pm_runtime_put(dev->parent); + return rc; +} + +/* disable hardware */ +static int cti_disable_hw(struct cti_drvdata *drvdata) +{ + struct cti_config *config = &drvdata->config; + struct device *dev = &drvdata->csdev->dev; + + spin_lock(&drvdata->spinlock); + + /* check refcount - disable on 0 */ + if (atomic_dec_return(&drvdata->config.enable_req_count) > 0) + goto cti_not_disabled; + + /* no need to do anything if disabled or cpu unpowered */ + if (!config->hw_enabled || !config->hw_powered) + goto cti_not_disabled; + + CS_UNLOCK(drvdata->base); + + /* disable CTI */ + writel_relaxed(0, drvdata->base + CTICONTROL); + config->hw_enabled = false; + + coresight_disclaim_device_unlocked(drvdata->base); + CS_LOCK(drvdata->base); + spin_unlock(&drvdata->spinlock); + pm_runtime_put(dev); + return 0; + + /* not disabled this call */ +cti_not_disabled: + spin_unlock(&drvdata->spinlock); + return 0; +} + +void cti_write_single_reg(struct cti_drvdata *drvdata, int offset, u32 value) +{ + CS_UNLOCK(drvdata->base); + writel_relaxed(value, drvdata->base + offset); + CS_LOCK(drvdata->base); +} + +void cti_write_intack(struct device *dev, u32 ackval) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + + spin_lock(&drvdata->spinlock); + /* write if enabled */ + if (cti_active(config)) + cti_write_single_reg(drvdata, CTIINTACK, ackval); + spin_unlock(&drvdata->spinlock); +} + +/* + * Look at the HW DEVID register for some of the HW settings. + * DEVID[15:8] - max number of in / out triggers. + */ +#define CTI_DEVID_MAXTRIGS(devid_val) ((int) BMVAL(devid_val, 8, 15)) + +/* DEVID[19:16] - number of CTM channels */ +#define CTI_DEVID_CTMCHANNELS(devid_val) ((int) BMVAL(devid_val, 16, 19)) + +static void cti_set_default_config(struct device *dev, + struct cti_drvdata *drvdata) +{ + struct cti_config *config = &drvdata->config; + u32 devid; + + devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); + config->nr_trig_max = CTI_DEVID_MAXTRIGS(devid); + + /* + * no current hardware should exceed this, but protect the driver + * in case of fault / out of spec hw + */ + if (config->nr_trig_max > CTIINOUTEN_MAX) { + dev_warn_once(dev, + "Limiting HW MaxTrig value(%d) to driver max(%d)\n", + config->nr_trig_max, CTIINOUTEN_MAX); + config->nr_trig_max = CTIINOUTEN_MAX; + } + + config->nr_ctm_channels = CTI_DEVID_CTMCHANNELS(devid); + + /* Most regs default to 0 as zalloc'ed except...*/ + config->trig_filter_enable = true; + config->ctigate = GENMASK(config->nr_ctm_channels - 1, 0); + atomic_set(&config->enable_req_count, 0); +} + +/* + * Add a connection entry to the list of connections for this + * CTI device. + */ +int cti_add_connection_entry(struct device *dev, struct cti_drvdata *drvdata, + struct cti_trig_con *tc, + struct coresight_device *csdev, + const char *assoc_dev_name) +{ + struct cti_device *cti_dev = &drvdata->ctidev; + + tc->con_dev = csdev; + /* + * Prefer actual associated CS device dev name to supplied value - + * which is likely to be node name / other conn name. + */ + if (csdev) + tc->con_dev_name = dev_name(&csdev->dev); + else if (assoc_dev_name != NULL) { + tc->con_dev_name = devm_kstrdup(dev, + assoc_dev_name, GFP_KERNEL); + if (!tc->con_dev_name) + return -ENOMEM; + } + list_add_tail(&tc->node, &cti_dev->trig_cons); + cti_dev->nr_trig_con++; + + /* add connection usage bit info to overall info */ + drvdata->config.trig_in_use |= tc->con_in->used_mask; + drvdata->config.trig_out_use |= tc->con_out->used_mask; + + return 0; +} + +/* create a trigger connection with appropriately sized signal groups */ +struct cti_trig_con *cti_allocate_trig_con(struct device *dev, int in_sigs, + int out_sigs) +{ + struct cti_trig_con *tc = NULL; + struct cti_trig_grp *in = NULL, *out = NULL; + + tc = devm_kzalloc(dev, sizeof(struct cti_trig_con), GFP_KERNEL); + if (!tc) + return tc; + + in = devm_kzalloc(dev, + offsetof(struct cti_trig_grp, sig_types[in_sigs]), + GFP_KERNEL); + if (!in) + return NULL; + + out = devm_kzalloc(dev, + offsetof(struct cti_trig_grp, sig_types[out_sigs]), + GFP_KERNEL); + if (!out) + return NULL; + + tc->con_in = in; + tc->con_out = out; + tc->con_in->nr_sigs = in_sigs; + tc->con_out->nr_sigs = out_sigs; + return tc; +} + +/* + * Add a default connection if nothing else is specified. + * single connection based on max in/out info, no assoc device + */ +int cti_add_default_connection(struct device *dev, struct cti_drvdata *drvdata) +{ + int ret = 0; + int n_trigs = drvdata->config.nr_trig_max; + u32 n_trig_mask = GENMASK(n_trigs - 1, 0); + struct cti_trig_con *tc = NULL; + + /* + * Assume max trigs for in and out, + * all used, default sig types allocated + */ + tc = cti_allocate_trig_con(dev, n_trigs, n_trigs); + if (!tc) + return -ENOMEM; + + tc->con_in->used_mask = n_trig_mask; + tc->con_out->used_mask = n_trig_mask; + ret = cti_add_connection_entry(dev, drvdata, tc, NULL, "default"); + return ret; +} + +/** cti channel api **/ +/* attach/detach channel from trigger - write through if enabled. */ +int cti_channel_trig_op(struct device *dev, enum cti_chan_op op, + enum cti_trig_dir direction, u32 channel_idx, + u32 trigger_idx) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + u32 trig_bitmask; + u32 chan_bitmask; + u32 reg_value; + int reg_offset; + + /* ensure indexes in range */ + if ((channel_idx >= config->nr_ctm_channels) || + (trigger_idx >= config->nr_trig_max)) + return -EINVAL; + + trig_bitmask = BIT(trigger_idx); + + /* ensure registered triggers and not out filtered */ + if (direction == CTI_TRIG_IN) { + if (!(trig_bitmask & config->trig_in_use)) + return -EINVAL; + } else { + if (!(trig_bitmask & config->trig_out_use)) + return -EINVAL; + + if ((config->trig_filter_enable) && + (config->trig_out_filter & trig_bitmask)) + return -EINVAL; + } + + /* update the local register values */ + chan_bitmask = BIT(channel_idx); + reg_offset = (direction == CTI_TRIG_IN ? CTIINEN(trigger_idx) : + CTIOUTEN(trigger_idx)); + + spin_lock(&drvdata->spinlock); + + /* read - modify write - the trigger / channel enable value */ + reg_value = direction == CTI_TRIG_IN ? config->ctiinen[trigger_idx] : + config->ctiouten[trigger_idx]; + if (op == CTI_CHAN_ATTACH) + reg_value |= chan_bitmask; + else + reg_value &= ~chan_bitmask; + + /* write local copy */ + if (direction == CTI_TRIG_IN) + config->ctiinen[trigger_idx] = reg_value; + else + config->ctiouten[trigger_idx] = reg_value; + + /* write through if enabled */ + if (cti_active(config)) + cti_write_single_reg(drvdata, reg_offset, reg_value); + spin_unlock(&drvdata->spinlock); + return 0; +} + +int cti_channel_gate_op(struct device *dev, enum cti_chan_gate_op op, + u32 channel_idx) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + u32 chan_bitmask; + u32 reg_value; + int err = 0; + + if (channel_idx >= config->nr_ctm_channels) + return -EINVAL; + + chan_bitmask = BIT(channel_idx); + + spin_lock(&drvdata->spinlock); + reg_value = config->ctigate; + switch (op) { + case CTI_GATE_CHAN_ENABLE: + reg_value |= chan_bitmask; + break; + + case CTI_GATE_CHAN_DISABLE: + reg_value &= ~chan_bitmask; + break; + + default: + err = -EINVAL; + break; + } + if (err == 0) { + config->ctigate = reg_value; + if (cti_active(config)) + cti_write_single_reg(drvdata, CTIGATE, reg_value); + } + spin_unlock(&drvdata->spinlock); + return err; +} + +int cti_channel_setop(struct device *dev, enum cti_chan_set_op op, + u32 channel_idx) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_config *config = &drvdata->config; + u32 chan_bitmask; + u32 reg_value; + u32 reg_offset; + int err = 0; + + if (channel_idx >= config->nr_ctm_channels) + return -EINVAL; + + chan_bitmask = BIT(channel_idx); + + spin_lock(&drvdata->spinlock); + reg_value = config->ctiappset; + switch (op) { + case CTI_CHAN_SET: + config->ctiappset |= chan_bitmask; + reg_value = config->ctiappset; + reg_offset = CTIAPPSET; + break; + + case CTI_CHAN_CLR: + config->ctiappset &= ~chan_bitmask; + reg_value = chan_bitmask; + reg_offset = CTIAPPCLEAR; + break; + + case CTI_CHAN_PULSE: + config->ctiappset &= ~chan_bitmask; + reg_value = chan_bitmask; + reg_offset = CTIAPPPULSE; + break; + + default: + err = -EINVAL; + break; + } + + if ((err == 0) && cti_active(config)) + cti_write_single_reg(drvdata, reg_offset, reg_value); + spin_unlock(&drvdata->spinlock); + + return err; +} + +/* + * Look for a matching connection device name in the list of connections. + * If found then swap in the csdev name, set trig con association pointer + * and return found. + */ +static bool +cti_match_fixup_csdev(struct cti_device *ctidev, const char *node_name, + struct coresight_device *csdev) +{ + struct cti_trig_con *tc; + + list_for_each_entry(tc, &ctidev->trig_cons, node) { + if (tc->con_dev_name) { + if (!strcmp(node_name, tc->con_dev_name)) { + /* match: so swap in csdev name & dev */ + tc->con_dev_name = dev_name(&csdev->dev); + tc->con_dev = csdev; + return true; + } + } + } + return false; +} + +/* + * Search the cti list to add an associated CTI into the supplied CS device + * This will set the association if CTI declared before the CS device. + * (called from coresight_register() with coresight_mutex locked). + */ +void cti_add_assoc_to_csdev(struct coresight_device *csdev) +{ + struct cti_drvdata *ect_item; + struct cti_device *ctidev; + const char *node_name = NULL; + + /* protect the list */ + mutex_lock(&ect_mutex); + + /* exit if current is an ECT device.*/ + if ((csdev->type == CORESIGHT_DEV_TYPE_ECT) || list_empty(&ect_net)) + goto cti_add_done; + + /* if we didn't find the csdev previously we used the fwnode name */ + node_name = cti_plat_get_node_name(dev_fwnode(csdev->dev.parent)); + if (!node_name) + goto cti_add_done; + + /* for each CTI in list... */ + list_for_each_entry(ect_item, &ect_net, node) { + ctidev = &ect_item->ctidev; + if (cti_match_fixup_csdev(ctidev, node_name, csdev)) { + /* + * if we found a matching csdev then update the ECT + * association pointer for the device with this CTI. + */ + csdev->ect_dev = ect_item->csdev; + break; + } + } +cti_add_done: + mutex_unlock(&ect_mutex); +} +EXPORT_SYMBOL_GPL(cti_add_assoc_to_csdev); + +/* + * Removing the associated devices is easier. + * A CTI will not have a value for csdev->ect_dev. + */ +void cti_remove_assoc_from_csdev(struct coresight_device *csdev) +{ + struct cti_drvdata *ctidrv; + struct cti_trig_con *tc; + struct cti_device *ctidev; + + mutex_lock(&ect_mutex); + if (csdev->ect_dev) { + ctidrv = csdev_to_cti_drvdata(csdev->ect_dev); + ctidev = &ctidrv->ctidev; + list_for_each_entry(tc, &ctidev->trig_cons, node) { + if (tc->con_dev == csdev->ect_dev) { + tc->con_dev = NULL; + break; + } + } + csdev->ect_dev = NULL; + } + mutex_unlock(&ect_mutex); +} +EXPORT_SYMBOL_GPL(cti_remove_assoc_from_csdev); + +/* + * Update the cross references where the associated device was found + * while we were building the connection info. This will occur if the + * assoc device was registered before the CTI. + */ +static void cti_update_conn_xrefs(struct cti_drvdata *drvdata) +{ + struct cti_trig_con *tc; + struct cti_device *ctidev = &drvdata->ctidev; + + list_for_each_entry(tc, &ctidev->trig_cons, node) { + if (tc->con_dev) + /* set tc->con_dev->ect_dev */ + coresight_set_assoc_ectdev_mutex(tc->con_dev, + drvdata->csdev); + } +} + +static void cti_remove_conn_xrefs(struct cti_drvdata *drvdata) +{ + struct cti_trig_con *tc; + struct cti_device *ctidev = &drvdata->ctidev; + + list_for_each_entry(tc, &ctidev->trig_cons, node) { + if (tc->con_dev) { + coresight_set_assoc_ectdev_mutex(tc->con_dev, + NULL); + } + } +} + +/** cti ect operations **/ +int cti_enable(struct coresight_device *csdev) +{ + struct cti_drvdata *drvdata = csdev_to_cti_drvdata(csdev); + + return cti_enable_hw(drvdata); +} + +int cti_disable(struct coresight_device *csdev) +{ + struct cti_drvdata *drvdata = csdev_to_cti_drvdata(csdev); + + return cti_disable_hw(drvdata); +} + +const struct coresight_ops_ect cti_ops_ect = { + .enable = cti_enable, + .disable = cti_disable, +}; + +const struct coresight_ops cti_ops = { + .ect_ops = &cti_ops_ect, +}; + +/* + * Free up CTI specific resources + * called by dev->release, need to call down to underlying csdev release. + */ +static void cti_device_release(struct device *dev) +{ + struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct cti_drvdata *ect_item, *ect_tmp; + + mutex_lock(&ect_mutex); + cti_remove_conn_xrefs(drvdata); + + /* remove from the list */ + list_for_each_entry_safe(ect_item, ect_tmp, &ect_net, node) { + if (ect_item == drvdata) { + list_del(&ect_item->node); + break; + } + } + mutex_unlock(&ect_mutex); + + if (drvdata->csdev_release) + drvdata->csdev_release(dev); +} + +static int cti_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret = 0; + void __iomem *base; + struct device *dev = &adev->dev; + struct cti_drvdata *drvdata = NULL; + struct coresight_desc cti_desc; + struct coresight_platform_data *pdata = NULL; + struct resource *res = &adev->res; + + /* driver data*/ + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { + ret = -ENOMEM; + dev_info(dev, "%s, mem err\n", __func__); + goto err_out; + } + + /* Validity for the resource is already checked by the AMBA core */ + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + dev_err(dev, "%s, remap err\n", __func__); + goto err_out; + } + drvdata->base = base; + + dev_set_drvdata(dev, drvdata); + + /* default CTI device info */ + drvdata->ctidev.cpu = -1; + drvdata->ctidev.nr_trig_con = 0; + drvdata->ctidev.ctm_id = 0; + INIT_LIST_HEAD(&drvdata->ctidev.trig_cons); + + spin_lock_init(&drvdata->spinlock); + + /* initialise CTI driver config values */ + cti_set_default_config(dev, drvdata); + + pdata = coresight_cti_get_platform_data(dev); + if (IS_ERR(pdata)) { + dev_err(dev, "coresight_cti_get_platform_data err\n"); + ret = PTR_ERR(pdata); + goto err_out; + } + + /* default to powered - could change on PM notifications */ + drvdata->config.hw_powered = true; + + /* set up device name - will depend if cpu bound or otherwise */ + if (drvdata->ctidev.cpu >= 0) + cti_desc.name = devm_kasprintf(dev, GFP_KERNEL, "cti_cpu%d", + drvdata->ctidev.cpu); + else + cti_desc.name = coresight_alloc_device_name(&cti_sys_devs, dev); + if (!cti_desc.name) { + ret = -ENOMEM; + goto err_out; + } + + /* create dynamic attributes for connections */ + ret = cti_create_cons_sysfs(dev, drvdata); + if (ret) { + dev_err(dev, "%s: create dynamic sysfs entries failed\n", + cti_desc.name); + goto err_out; + } + + /* set up coresight component description */ + cti_desc.pdata = pdata; + cti_desc.type = CORESIGHT_DEV_TYPE_ECT; + cti_desc.subtype.ect_subtype = CORESIGHT_DEV_SUBTYPE_ECT_CTI; + cti_desc.ops = &cti_ops; + cti_desc.groups = drvdata->ctidev.con_groups; + cti_desc.dev = dev; + drvdata->csdev = coresight_register(&cti_desc); + if (IS_ERR(drvdata->csdev)) { + ret = PTR_ERR(drvdata->csdev); + goto err_out; + } + + /* add to list of CTI devices */ + mutex_lock(&ect_mutex); + list_add(&drvdata->node, &ect_net); + /* set any cross references */ + cti_update_conn_xrefs(drvdata); + mutex_unlock(&ect_mutex); + + /* set up release chain */ + drvdata->csdev_release = drvdata->csdev->dev.release; + drvdata->csdev->dev.release = cti_device_release; + + /* all done - dec pm refcount */ + pm_runtime_put(&adev->dev); + dev_info(&drvdata->csdev->dev, "CTI initialized\n"); + return 0; + +err_out: + return ret; +} + +static struct amba_cs_uci_id uci_id_cti[] = { + { + /* CTI UCI data */ + .devarch = 0x47701a14, /* CTI v2 */ + .devarch_mask = 0xfff0ffff, + .devtype = 0x00000014, /* maj(0x4-debug) min(0x1-ECT) */ + } +}; + +static const struct amba_id cti_ids[] = { + CS_AMBA_ID(0x000bb906), /* Coresight CTI (SoC 400), C-A72, C-A57 */ + CS_AMBA_ID(0x000bb922), /* CTI - C-A8 */ + CS_AMBA_ID(0x000bb9a8), /* CTI - C-A53 */ + CS_AMBA_ID(0x000bb9aa), /* CTI - C-A73 */ + CS_AMBA_UCI_ID(0x000bb9da, uci_id_cti), /* CTI - C-A35 */ + CS_AMBA_UCI_ID(0x000bb9ed, uci_id_cti), /* Coresight CTI (SoC 600) */ + { 0, 0}, +}; + +static struct amba_driver cti_driver = { + .drv = { + .name = "coresight-cti", + .owner = THIS_MODULE, + .suppress_bind_attrs = true, + }, + .probe = cti_probe, + .id_table = cti_ids, +}; +builtin_amba_driver(cti_driver); diff --git a/drivers/hwtracing/coresight/coresight-cti.h b/drivers/hwtracing/coresight/coresight-cti.h new file mode 100644 index 000000000000..004df3ab9dd0 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-cti.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018 Linaro Limited, All rights reserved. + * Author: Mike Leach <mike.leach@linaro.org> + */ + +#ifndef _CORESIGHT_CORESIGHT_CTI_H +#define _CORESIGHT_CORESIGHT_CTI_H + +#include <asm/local.h> +#include <linux/spinlock.h> +#include "coresight-priv.h" + +/* + * Device registers + * 0x000 - 0x144: CTI programming and status + * 0xEDC - 0xEF8: CTI integration test. + * 0xF00 - 0xFFC: Coresight management registers. + */ +/* CTI programming registers */ +#define CTICONTROL 0x000 +#define CTIINTACK 0x010 +#define CTIAPPSET 0x014 +#define CTIAPPCLEAR 0x018 +#define CTIAPPPULSE 0x01C +#define CTIINEN(n) (0x020 + (4 * n)) +#define CTIOUTEN(n) (0x0A0 + (4 * n)) +#define CTITRIGINSTATUS 0x130 +#define CTITRIGOUTSTATUS 0x134 +#define CTICHINSTATUS 0x138 +#define CTICHOUTSTATUS 0x13C +#define CTIGATE 0x140 +#define ASICCTL 0x144 +/* Integration test registers */ +#define ITCHINACK 0xEDC /* WO CTI CSSoc 400 only*/ +#define ITTRIGINACK 0xEE0 /* WO CTI CSSoc 400 only*/ +#define ITCHOUT 0xEE4 /* WO RW-600 */ +#define ITTRIGOUT 0xEE8 /* WO RW-600 */ +#define ITCHOUTACK 0xEEC /* RO CTI CSSoc 400 only*/ +#define ITTRIGOUTACK 0xEF0 /* RO CTI CSSoc 400 only*/ +#define ITCHIN 0xEF4 /* RO */ +#define ITTRIGIN 0xEF8 /* RO */ +/* management registers */ +#define CTIDEVAFF0 0xFA8 +#define CTIDEVAFF1 0xFAC + +/* + * CTI CSSoc 600 has a max of 32 trigger signals per direction. + * CTI CSSoc 400 has 8 IO triggers - other CTIs can be impl def. + * Max of in and out defined in the DEVID register. + * - pick up actual number used from .dts parameters if present. + */ +#define CTIINOUTEN_MAX 32 + +/** + * Group of related trigger signals + * + * @nr_sigs: number of signals in the group. + * @used_mask: bitmask representing the signal indexes in the group. + * @sig_types: array of types for the signals, length nr_sigs. + */ +struct cti_trig_grp { + int nr_sigs; + u32 used_mask; + int sig_types[]; +}; + +/** + * Trigger connection - connection between a CTI and other (coresight) device + * lists input and output trigger signals for the device + * + * @con_in: connected CTIIN signals for the device. + * @con_out: connected CTIOUT signals for the device. + * @con_dev: coresight device connected to the CTI, NULL if not CS device + * @con_dev_name: name of connected device (CS or CPU) + * @node: entry node in list of connections. + * @con_attrs: Dynamic sysfs attributes specific to this connection. + * @attr_group: Dynamic attribute group created for this connection. + */ +struct cti_trig_con { + struct cti_trig_grp *con_in; + struct cti_trig_grp *con_out; + struct coresight_device *con_dev; + const char *con_dev_name; + struct list_head node; + struct attribute **con_attrs; + struct attribute_group *attr_group; +}; + +/** + * struct cti_device - description of CTI device properties. + * + * @nt_trig_con: Number of external devices connected to this device. + * @ctm_id: which CTM this device is connected to (by default it is + * assumed there is a single CTM per SoC, ID 0). + * @trig_cons: list of connections to this device. + * @cpu: CPU ID if associated with CPU, -1 otherwise. + * @con_groups: combined static and dynamic sysfs groups for trigger + * connections. + */ +struct cti_device { + int nr_trig_con; + u32 ctm_id; + struct list_head trig_cons; + int cpu; + const struct attribute_group **con_groups; +}; + +/** + * struct cti_config - configuration of the CTI device hardware + * + * @nr_trig_max: Max number of trigger signals implemented on device. + * (max of trig_in or trig_out) - from ID register. + * @nr_ctm_channels: number of available CTM channels - from ID register. + * @enable_req_count: CTI is enabled alongside >=1 associated devices. + * @hw_enabled: true if hw is currently enabled. + * @hw_powered: true if associated cpu powered on, or no cpu. + * @trig_in_use: bitfield of in triggers registered as in use. + * @trig_out_use: bitfield of out triggers registered as in use. + * @trig_out_filter: bitfield of out triggers that are blocked if filter + * enabled. Typically this would be dbgreq / restart on + * a core CTI. + * @trig_filter_enable: 1 if filtering enabled. + * @xtrig_rchan_sel: channel selection for xtrigger connection show. + * @ctiappset: CTI Software application channel set. + * @ctiinout_sel: register selector for INEN and OUTEN regs. + * @ctiinen: enable input trigger to a channel. + * @ctiouten: enable output trigger from a channel. + * @ctigate: gate channel output from CTI to CTM. + * @asicctl: asic control register. + */ +struct cti_config { + /* hardware description */ + int nr_ctm_channels; + int nr_trig_max; + + /* cti enable control */ + atomic_t enable_req_count; + bool hw_enabled; + bool hw_powered; + + /* registered triggers and filtering */ + u32 trig_in_use; + u32 trig_out_use; + u32 trig_out_filter; + bool trig_filter_enable; + u8 xtrig_rchan_sel; + + /* cti cross trig programmable regs */ + u32 ctiappset; + u8 ctiinout_sel; + u32 ctiinen[CTIINOUTEN_MAX]; + u32 ctiouten[CTIINOUTEN_MAX]; + u32 ctigate; + u32 asicctl; +}; + +/** + * struct cti_drvdata - specifics for the CTI device + * @base: Memory mapped base address for this component.. + * @csdev: Standard CoreSight device information. + * @ctidev: Extra information needed by the CTI/CTM framework. + * @spinlock: Control data access to one at a time. + * @config: Configuration data for this CTI device. + * @node: List entry of this device in the list of CTI devices. + * @csdev_release: release function for underlying coresight_device. + */ +struct cti_drvdata { + void __iomem *base; + struct coresight_device *csdev; + struct cti_device ctidev; + spinlock_t spinlock; + struct cti_config config; + struct list_head node; + void (*csdev_release)(struct device *dev); +}; + +/* + * Channel operation types. + */ +enum cti_chan_op { + CTI_CHAN_ATTACH, + CTI_CHAN_DETACH, +}; + +enum cti_trig_dir { + CTI_TRIG_IN, + CTI_TRIG_OUT, +}; + +enum cti_chan_gate_op { + CTI_GATE_CHAN_ENABLE, + CTI_GATE_CHAN_DISABLE, +}; + +enum cti_chan_set_op { + CTI_CHAN_SET, + CTI_CHAN_CLR, + CTI_CHAN_PULSE, +}; + +/* private cti driver fns & vars */ +extern const struct attribute_group *coresight_cti_groups[]; +int cti_add_default_connection(struct device *dev, + struct cti_drvdata *drvdata); +int cti_add_connection_entry(struct device *dev, struct cti_drvdata *drvdata, + struct cti_trig_con *tc, + struct coresight_device *csdev, + const char *assoc_dev_name); +struct cti_trig_con *cti_allocate_trig_con(struct device *dev, int in_sigs, + int out_sigs); +int cti_enable(struct coresight_device *csdev); +int cti_disable(struct coresight_device *csdev); +void cti_write_all_hw_regs(struct cti_drvdata *drvdata); +void cti_write_intack(struct device *dev, u32 ackval); +void cti_write_single_reg(struct cti_drvdata *drvdata, int offset, u32 value); +int cti_channel_trig_op(struct device *dev, enum cti_chan_op op, + enum cti_trig_dir direction, u32 channel_idx, + u32 trigger_idx); +int cti_channel_gate_op(struct device *dev, enum cti_chan_gate_op op, + u32 channel_idx); +int cti_channel_setop(struct device *dev, enum cti_chan_set_op op, + u32 channel_idx); +int cti_create_cons_sysfs(struct device *dev, struct cti_drvdata *drvdata); +struct coresight_platform_data * +coresight_cti_get_platform_data(struct device *dev); +const char *cti_plat_get_node_name(struct fwnode_handle *fwnode); + +/* cti powered and enabled */ +static inline bool cti_active(struct cti_config *cfg) +{ + return cfg->hw_powered && cfg->hw_enabled; +} + +#endif /* _CORESIGHT_CORESIGHT_CTI_H */ diff --git a/drivers/hwtracing/coresight/coresight-platform.c b/drivers/hwtracing/coresight/coresight-platform.c index 3c5bee429105..43418a2126ff 100644 --- a/drivers/hwtracing/coresight/coresight-platform.c +++ b/drivers/hwtracing/coresight/coresight-platform.c @@ -57,6 +57,26 @@ coresight_find_device_by_fwnode(struct fwnode_handle *fwnode) return bus_find_device_by_fwnode(&amba_bustype, fwnode); } +/* + * Find a registered coresight device from a device fwnode. + * The node info is associated with the AMBA parent, but the + * csdev keeps a copy so iterate round the coresight bus to + * find the device. + */ +struct coresight_device * +coresight_find_csdev_by_fwnode(struct fwnode_handle *r_fwnode) +{ + struct device *dev; + struct coresight_device *csdev = NULL; + + dev = bus_find_device_by_fwnode(&coresight_bustype, r_fwnode); + if (dev) { + csdev = to_coresight_device(dev); + put_device(dev); + } + return csdev; +} + #ifdef CONFIG_OF static inline bool of_coresight_legacy_ep_is_input(struct device_node *ep) { diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h index 82e563cdc879..890f9a5c97c6 100644 --- a/drivers/hwtracing/coresight/coresight-priv.h +++ b/drivers/hwtracing/coresight/coresight-priv.h @@ -22,6 +22,7 @@ #define CORESIGHT_CLAIMCLR 0xfa4 #define CORESIGHT_LAR 0xfb0 #define CORESIGHT_LSR 0xfb4 +#define CORESIGHT_DEVARCH 0xfbc #define CORESIGHT_AUTHSTATUS 0xfb8 #define CORESIGHT_DEVID 0xfc8 #define CORESIGHT_DEVTYPE 0xfcc @@ -161,6 +162,16 @@ static inline int etm_readl_cp14(u32 off, unsigned int *val) { return 0; } static inline int etm_writel_cp14(u32 off, u32 val) { return 0; } #endif +#ifdef CONFIG_CORESIGHT_CTI +extern void cti_add_assoc_to_csdev(struct coresight_device *csdev); +extern void cti_remove_assoc_from_csdev(struct coresight_device *csdev); + +#else +static inline void cti_add_assoc_to_csdev(struct coresight_device *csdev) {} +static inline void +cti_remove_assoc_from_csdev(struct coresight_device *csdev) {} +#endif + /* * Macros and inline functions to handle CoreSight UCI data and driver * private data in AMBA ID table entries, and extract data values. @@ -201,5 +212,9 @@ static inline void *coresight_get_uci_data(const struct amba_id *id) } void coresight_release_platform_data(struct coresight_platform_data *pdata); +struct coresight_device * +coresight_find_csdev_by_fwnode(struct fwnode_handle *r_fwnode); +void coresight_set_assoc_ectdev_mutex(struct coresight_device *csdev, + struct coresight_device *ect_csdev); #endif diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index ef20f74c85fa..c71553c09f8e 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -216,6 +216,44 @@ void coresight_disclaim_device(void __iomem *base) CS_LOCK(base); } +/* enable or disable an associated CTI device of the supplied CS device */ +static int +coresight_control_assoc_ectdev(struct coresight_device *csdev, bool enable) +{ + int ect_ret = 0; + struct coresight_device *ect_csdev = csdev->ect_dev; + + if (!ect_csdev) + return 0; + + if (enable) { + if (ect_ops(ect_csdev)->enable) + ect_ret = ect_ops(ect_csdev)->enable(ect_csdev); + } else { + if (ect_ops(ect_csdev)->disable) + ect_ret = ect_ops(ect_csdev)->disable(ect_csdev); + } + + /* output warning if ECT enable is preventing trace operation */ + if (ect_ret) + dev_info(&csdev->dev, "Associated ECT device (%s) %s failed\n", + dev_name(&ect_csdev->dev), + enable ? "enable" : "disable"); + return ect_ret; +} + +/* + * Set the associated ect / cti device while holding the coresight_mutex + * to avoid a race with coresight_enable that may try to use this value. + */ +void coresight_set_assoc_ectdev_mutex(struct coresight_device *csdev, + struct coresight_device *ect_csdev) +{ + mutex_lock(&coresight_mutex); + csdev->ect_dev = ect_csdev; + mutex_unlock(&coresight_mutex); +} + static int coresight_enable_sink(struct coresight_device *csdev, u32 mode, void *data) { @@ -228,9 +266,14 @@ static int coresight_enable_sink(struct coresight_device *csdev, if (!sink_ops(csdev)->enable) return -EINVAL; - ret = sink_ops(csdev)->enable(csdev, mode, data); + ret = coresight_control_assoc_ectdev(csdev, true); if (ret) return ret; + ret = sink_ops(csdev)->enable(csdev, mode, data); + if (ret) { + coresight_control_assoc_ectdev(csdev, false); + return ret; + } csdev->enable = true; return 0; @@ -246,6 +289,7 @@ static void coresight_disable_sink(struct coresight_device *csdev) ret = sink_ops(csdev)->disable(csdev); if (ret) return; + coresight_control_assoc_ectdev(csdev, false); csdev->enable = false; } @@ -269,8 +313,15 @@ static int coresight_enable_link(struct coresight_device *csdev, if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT && outport < 0) return outport; - if (link_ops(csdev)->enable) - ret = link_ops(csdev)->enable(csdev, inport, outport); + if (link_ops(csdev)->enable) { + ret = coresight_control_assoc_ectdev(csdev, true); + if (!ret) { + ret = link_ops(csdev)->enable(csdev, inport, outport); + if (ret) + coresight_control_assoc_ectdev(csdev, false); + } + } + if (!ret) csdev->enable = true; @@ -300,8 +351,10 @@ static void coresight_disable_link(struct coresight_device *csdev, nr_conns = 1; } - if (link_ops(csdev)->disable) + if (link_ops(csdev)->disable) { link_ops(csdev)->disable(csdev, inport, outport); + coresight_control_assoc_ectdev(csdev, false); + } for (i = 0; i < nr_conns; i++) if (atomic_read(&csdev->refcnt[i]) != 0) @@ -322,9 +375,14 @@ static int coresight_enable_source(struct coresight_device *csdev, u32 mode) if (!csdev->enable) { if (source_ops(csdev)->enable) { - ret = source_ops(csdev)->enable(csdev, NULL, mode); + ret = coresight_control_assoc_ectdev(csdev, true); if (ret) return ret; + ret = source_ops(csdev)->enable(csdev, NULL, mode); + if (ret) { + coresight_control_assoc_ectdev(csdev, false); + return ret; + }; } csdev->enable = true; } @@ -347,6 +405,7 @@ static bool coresight_disable_source(struct coresight_device *csdev) if (atomic_dec_return(csdev->refcnt) == 0) { if (source_ops(csdev)->disable) source_ops(csdev)->disable(csdev, NULL); + coresight_control_assoc_ectdev(csdev, false); csdev->enable = false; } return !csdev->enable; @@ -955,12 +1014,16 @@ static struct device_type coresight_dev_type[] = { { .name = "helper", }, + { + .name = "ect", + }, }; static void coresight_device_release(struct device *dev) { struct coresight_device *csdev = to_coresight_device(dev); + cti_remove_assoc_from_csdev(csdev); fwnode_handle_put(csdev->dev.fwnode); kfree(csdev->refcnt); kfree(csdev); @@ -1027,17 +1090,11 @@ static void coresight_fixup_device_conns(struct coresight_device *csdev) for (i = 0; i < csdev->pdata->nr_outport; i++) { struct coresight_connection *conn = &csdev->pdata->conns[i]; - struct device *dev = NULL; - dev = bus_find_device_by_fwnode(&coresight_bustype, conn->child_fwnode); - if (dev) { - conn->child_dev = to_coresight_device(dev); - /* and put reference from 'bus_find_device()' */ - put_device(dev); - } else { + conn->child_dev = + coresight_find_csdev_by_fwnode(conn->child_fwnode); + if (!conn->child_dev) csdev->orphan = true; - conn->child_dev = NULL; - } } } @@ -1249,6 +1306,7 @@ struct coresight_device *coresight_register(struct coresight_desc *desc) coresight_fixup_device_conns(csdev); coresight_fixup_orphan_conns(csdev); + cti_add_assoc_to_csdev(csdev); mutex_unlock(&coresight_mutex); diff --git a/drivers/hwtracing/intel_th/intel_th.h b/drivers/hwtracing/intel_th/intel_th.h index 6f4f5486fe6d..5fe694708b7a 100644 --- a/drivers/hwtracing/intel_th/intel_th.h +++ b/drivers/hwtracing/intel_th/intel_th.h @@ -47,11 +47,13 @@ struct intel_th_output { /** * struct intel_th_drvdata - describes hardware capabilities and quirks * @tscu_enable: device needs SW to enable time stamping unit + * @multi_is_broken: device has multiblock mode is broken * @has_mintctl: device has interrupt control (MINTCTL) register * @host_mode_only: device can only operate in 'host debugger' mode */ struct intel_th_drvdata { unsigned int tscu_enable : 1, + multi_is_broken : 1, has_mintctl : 1, host_mode_only : 1; }; diff --git a/drivers/hwtracing/intel_th/msu.c b/drivers/hwtracing/intel_th/msu.c index 255f8f41c8ff..3a77551fb4fc 100644 --- a/drivers/hwtracing/intel_th/msu.c +++ b/drivers/hwtracing/intel_th/msu.c @@ -138,6 +138,7 @@ struct msc { struct list_head win_list; struct sg_table single_sgt; struct msc_window *cur_win; + struct msc_window *switch_on_unlock; unsigned long nr_pages; unsigned long single_sz; unsigned int single_wrap : 1; @@ -154,10 +155,13 @@ struct msc { struct list_head iter_list; + bool stop_on_full; + /* config */ unsigned int enabled : 1, wrap : 1, - do_irq : 1; + do_irq : 1, + multi_is_broken : 1; unsigned int mode; unsigned int burst_len; unsigned int index; @@ -1665,7 +1669,7 @@ static int intel_th_msc_init(struct msc *msc) { atomic_set(&msc->user_count, -1); - msc->mode = MSC_MODE_MULTI; + msc->mode = msc->multi_is_broken ? MSC_MODE_SINGLE : MSC_MODE_MULTI; mutex_init(&msc->buf_mutex); INIT_LIST_HEAD(&msc->win_list); INIT_LIST_HEAD(&msc->iter_list); @@ -1717,6 +1721,10 @@ void intel_th_msc_window_unlock(struct device *dev, struct sg_table *sgt) return; msc_win_set_lockout(win, WIN_LOCKED, WIN_READY); + if (msc->switch_on_unlock == win) { + msc->switch_on_unlock = NULL; + msc_win_switch(msc); + } } EXPORT_SYMBOL_GPL(intel_th_msc_window_unlock); @@ -1757,7 +1765,11 @@ static irqreturn_t intel_th_msc_interrupt(struct intel_th_device *thdev) /* next window: if READY, proceed, if LOCKED, stop the trace */ if (msc_win_set_lockout(next_win, WIN_READY, WIN_INUSE)) { - schedule_work(&msc->work); + if (msc->stop_on_full) + schedule_work(&msc->work); + else + msc->switch_on_unlock = next_win; + return IRQ_HANDLED; } @@ -1877,6 +1889,9 @@ mode_store(struct device *dev, struct device_attribute *attr, const char *buf, return -EINVAL; found: + if (i == MSC_MODE_MULTI && msc->multi_is_broken) + return -EOPNOTSUPP; + mutex_lock(&msc->buf_mutex); ret = 0; @@ -2047,11 +2062,36 @@ win_switch_store(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_WO(win_switch); +static ssize_t stop_on_full_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msc *msc = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", msc->stop_on_full); +} + +static ssize_t stop_on_full_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct msc *msc = dev_get_drvdata(dev); + int ret; + + ret = kstrtobool(buf, &msc->stop_on_full); + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR_RW(stop_on_full); + static struct attribute *msc_output_attrs[] = { &dev_attr_wrap.attr, &dev_attr_mode.attr, &dev_attr_nr_pages.attr, &dev_attr_win_switch.attr, + &dev_attr_stop_on_full.attr, NULL, }; @@ -2083,6 +2123,9 @@ static int intel_th_msc_probe(struct intel_th_device *thdev) if (!res) msc->do_irq = 1; + if (INTEL_TH_CAP(to_intel_th(thdev), multi_is_broken)) + msc->multi_is_broken = 1; + msc->index = thdev->id; msc->thdev = thdev; diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index 86aa6a46bcba..7ccac74553a6 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -120,6 +120,10 @@ static void intel_th_pci_remove(struct pci_dev *pdev) pci_free_irq_vectors(pdev); } +static const struct intel_th_drvdata intel_th_1x_multi_is_broken = { + .multi_is_broken = 1, +}; + static const struct intel_th_drvdata intel_th_2x = { .tscu_enable = 1, .has_mintctl = 1, @@ -152,7 +156,7 @@ static const struct pci_device_id intel_th_pci_id_table[] = { { /* Kaby Lake PCH-H */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa2a6), - .driver_data = (kernel_ulong_t)0, + .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, }, { /* Denverton */ @@ -207,7 +211,7 @@ static const struct pci_device_id intel_th_pci_id_table[] = { { /* Comet Lake PCH-V */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa3a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, + .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, }, { /* Ice Lake NNPI */ diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index 76938ece1658..a88f2f07bc27 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -5,6 +5,9 @@ config INTERCONNECT_QCOM help Support for Qualcomm's Network-on-Chip interconnect hardware. +config INTERCONNECT_QCOM_BCM_VOTER + tristate + config INTERCONNECT_QCOM_MSM8916 tristate "Qualcomm MSM8916 interconnect driver" depends on INTERCONNECT_QCOM @@ -23,6 +26,13 @@ config INTERCONNECT_QCOM_MSM8974 This is a driver for the Qualcomm Network-on-Chip on msm8974-based platforms. +config INTERCONNECT_QCOM_OSM_L3 + tristate "Qualcomm OSM L3 interconnect driver" + depends on INTERCONNECT_QCOM || COMPILE_TEST + help + Say y here to support the Operating State Manager (OSM) interconnect + driver which controls the scaling of L3 caches on Qualcomm SoCs. + config INTERCONNECT_QCOM_QCS404 tristate "Qualcomm QCS404 interconnect driver" depends on INTERCONNECT_QCOM @@ -32,10 +42,25 @@ config INTERCONNECT_QCOM_QCS404 This is a driver for the Qualcomm Network-on-Chip on qcs404-based platforms. +config INTERCONNECT_QCOM_RPMH + tristate + +config INTERCONNECT_QCOM_SC7180 + tristate "Qualcomm SC7180 interconnect driver" + depends on INTERCONNECT_QCOM + depends on (QCOM_RPMH && QCOM_COMMAND_DB && OF) || COMPILE_TEST + select INTERCONNECT_QCOM_RPMH + select INTERCONNECT_QCOM_BCM_VOTER + help + This is a driver for the Qualcomm Network-on-Chip on sc7180-based + platforms. + config INTERCONNECT_QCOM_SDM845 tristate "Qualcomm SDM845 interconnect driver" depends on INTERCONNECT_QCOM depends on (QCOM_RPMH && QCOM_COMMAND_DB && OF) || COMPILE_TEST + select INTERCONNECT_QCOM_RPMH + select INTERCONNECT_QCOM_BCM_VOTER help This is a driver for the Qualcomm Network-on-Chip on sdm845-based platforms. diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index e8271575e3d8..3a047fe6e45a 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -1,13 +1,21 @@ # SPDX-License-Identifier: GPL-2.0 +icc-bcm-voter-objs := bcm-voter.o qnoc-msm8916-objs := msm8916.o qnoc-msm8974-objs := msm8974.o +icc-osm-l3-objs := osm-l3.o qnoc-qcs404-objs := qcs404.o +icc-rpmh-obj := icc-rpmh.o +qnoc-sc7180-objs := sc7180.o qnoc-sdm845-objs := sdm845.o icc-smd-rpm-objs := smd-rpm.o +obj-$(CONFIG_INTERCONNECT_QCOM_BCM_VOTER) += icc-bcm-voter.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o +obj-$(CONFIG_INTERCONNECT_QCOM_OSM_L3) += icc-osm-l3.o obj-$(CONFIG_INTERCONNECT_QCOM_QCS404) += qnoc-qcs404.o +obj-$(CONFIG_INTERCONNECT_QCOM_RPMH) += icc-rpmh.o +obj-$(CONFIG_INTERCONNECT_QCOM_SC7180) += qnoc-sc7180.o obj-$(CONFIG_INTERCONNECT_QCOM_SDM845) += qnoc-sdm845.o obj-$(CONFIG_INTERCONNECT_QCOM_SMD_RPM) += icc-smd-rpm.o diff --git a/drivers/interconnect/qcom/bcm-voter.c b/drivers/interconnect/qcom/bcm-voter.c new file mode 100644 index 000000000000..2adfde8cdf19 --- /dev/null +++ b/drivers/interconnect/qcom/bcm-voter.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include <asm/div64.h> +#include <linux/interconnect-provider.h> +#include <linux/list_sort.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <soc/qcom/rpmh.h> +#include <soc/qcom/tcs.h> + +#include "bcm-voter.h" +#include "icc-rpmh.h" + +static LIST_HEAD(bcm_voters); +static DEFINE_MUTEX(bcm_voter_lock); + +/** + * struct bcm_voter - Bus Clock Manager voter + * @dev: reference to the device that communicates with the BCM + * @np: reference to the device node to match bcm voters + * @lock: mutex to protect commit and wake/sleep lists in the voter + * @commit_list: list containing bcms to be committed to hardware + * @ws_list: list containing bcms that have different wake/sleep votes + * @voter_node: list of bcm voters + */ +struct bcm_voter { + struct device *dev; + struct device_node *np; + struct mutex lock; + struct list_head commit_list; + struct list_head ws_list; + struct list_head voter_node; +}; + +static int cmp_vcd(void *priv, struct list_head *a, struct list_head *b) +{ + const struct qcom_icc_bcm *bcm_a = + list_entry(a, struct qcom_icc_bcm, list); + const struct qcom_icc_bcm *bcm_b = + list_entry(b, struct qcom_icc_bcm, list); + + if (bcm_a->aux_data.vcd < bcm_b->aux_data.vcd) + return -1; + else if (bcm_a->aux_data.vcd == bcm_b->aux_data.vcd) + return 0; + else + return 1; +} + +static void bcm_aggregate(struct qcom_icc_bcm *bcm) +{ + size_t i, bucket; + u64 agg_avg[QCOM_ICC_NUM_BUCKETS] = {0}; + u64 agg_peak[QCOM_ICC_NUM_BUCKETS] = {0}; + u64 temp; + + for (bucket = 0; bucket < QCOM_ICC_NUM_BUCKETS; bucket++) { + for (i = 0; i < bcm->num_nodes; i++) { + temp = bcm->nodes[i]->sum_avg[bucket] * bcm->aux_data.width; + do_div(temp, bcm->nodes[i]->buswidth * bcm->nodes[i]->channels); + agg_avg[bucket] = max(agg_avg[bucket], temp); + + temp = bcm->nodes[i]->max_peak[bucket] * bcm->aux_data.width; + do_div(temp, bcm->nodes[i]->buswidth); + agg_peak[bucket] = max(agg_peak[bucket], temp); + } + + temp = agg_avg[bucket] * 1000ULL; + do_div(temp, bcm->aux_data.unit); + bcm->vote_x[bucket] = temp; + + temp = agg_peak[bucket] * 1000ULL; + do_div(temp, bcm->aux_data.unit); + bcm->vote_y[bucket] = temp; + } + + if (bcm->keepalive && bcm->vote_x[QCOM_ICC_BUCKET_AMC] == 0 && + bcm->vote_y[QCOM_ICC_BUCKET_AMC] == 0) { + bcm->vote_x[QCOM_ICC_BUCKET_AMC] = 1; + bcm->vote_x[QCOM_ICC_BUCKET_WAKE] = 1; + bcm->vote_y[QCOM_ICC_BUCKET_AMC] = 1; + bcm->vote_y[QCOM_ICC_BUCKET_WAKE] = 1; + } +} + +static inline void tcs_cmd_gen(struct tcs_cmd *cmd, u64 vote_x, u64 vote_y, + u32 addr, bool commit) +{ + bool valid = true; + + if (!cmd) + return; + + if (vote_x == 0 && vote_y == 0) + valid = false; + + if (vote_x > BCM_TCS_CMD_VOTE_MASK) + vote_x = BCM_TCS_CMD_VOTE_MASK; + + if (vote_y > BCM_TCS_CMD_VOTE_MASK) + vote_y = BCM_TCS_CMD_VOTE_MASK; + + cmd->addr = addr; + cmd->data = BCM_TCS_CMD(commit, valid, vote_x, vote_y); + + /* + * Set the wait for completion flag on command that need to be completed + * before the next command. + */ + if (commit) + cmd->wait = true; +} + +static void tcs_list_gen(struct list_head *bcm_list, int bucket, + struct tcs_cmd tcs_list[MAX_BCMS], + int n[MAX_VCD + 1]) +{ + struct qcom_icc_bcm *bcm; + bool commit; + size_t idx = 0, batch = 0, cur_vcd_size = 0; + + memset(n, 0, sizeof(int) * (MAX_VCD + 1)); + + list_for_each_entry(bcm, bcm_list, list) { + commit = false; + cur_vcd_size++; + if ((list_is_last(&bcm->list, bcm_list)) || + bcm->aux_data.vcd != list_next_entry(bcm, list)->aux_data.vcd) { + commit = true; + cur_vcd_size = 0; + } + tcs_cmd_gen(&tcs_list[idx], bcm->vote_x[bucket], + bcm->vote_y[bucket], bcm->addr, commit); + idx++; + n[batch]++; + /* + * Batch the BCMs in such a way that we do not split them in + * multiple payloads when they are under the same VCD. This is + * to ensure that every BCM is committed since we only set the + * commit bit on the last BCM request of every VCD. + */ + if (n[batch] >= MAX_RPMH_PAYLOAD) { + if (!commit) { + n[batch] -= cur_vcd_size; + n[batch + 1] = cur_vcd_size; + } + batch++; + } + } +} + +/** + * of_bcm_voter_get - gets a bcm voter handle from DT node + * @dev: device pointer for the consumer device + * @name: name for the bcm voter device + * + * This function will match a device_node pointer for the phandle + * specified in the device DT and return a bcm_voter handle on success. + * + * Returns bcm_voter pointer or ERR_PTR() on error. EPROBE_DEFER is returned + * when matching bcm voter is yet to be found. + */ +struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name) +{ + struct bcm_voter *voter = ERR_PTR(-EPROBE_DEFER); + struct bcm_voter *temp; + struct device_node *np, *node; + int idx = 0; + + if (!dev || !dev->of_node) + return ERR_PTR(-ENODEV); + + np = dev->of_node; + + if (name) { + idx = of_property_match_string(np, "qcom,bcm-voter-names", name); + if (idx < 0) + return ERR_PTR(idx); + } + + node = of_parse_phandle(np, "qcom,bcm-voters", idx); + + mutex_lock(&bcm_voter_lock); + list_for_each_entry(temp, &bcm_voters, voter_node) { + if (temp->np == node) { + voter = temp; + break; + } + } + mutex_unlock(&bcm_voter_lock); + + return voter; +} +EXPORT_SYMBOL_GPL(of_bcm_voter_get); + +/** + * qcom_icc_bcm_voter_add - queues up the bcm nodes that require updates + * @voter: voter that the bcms are being added to + * @bcm: bcm to add to the commit and wake sleep list + */ +void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm) +{ + if (!voter) + return; + + mutex_lock(&voter->lock); + if (list_empty(&bcm->list)) + list_add_tail(&bcm->list, &voter->commit_list); + + if (list_empty(&bcm->ws_list)) + list_add_tail(&bcm->ws_list, &voter->ws_list); + + mutex_unlock(&voter->lock); +} +EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_add); + +/** + * qcom_icc_bcm_voter_commit - generates and commits tcs cmds based on bcms + * @voter: voter that needs flushing + * + * This function generates a set of AMC commands and flushes to the BCM device + * associated with the voter. It conditionally generate WAKE and SLEEP commands + * based on deltas between WAKE/SLEEP requirements. The ws_list persists + * through multiple commit requests and bcm nodes are removed only when the + * requirements for WAKE matches SLEEP. + * + * Returns 0 on success, or an appropriate error code otherwise. + */ +int qcom_icc_bcm_voter_commit(struct bcm_voter *voter) +{ + struct qcom_icc_bcm *bcm; + struct qcom_icc_bcm *bcm_tmp; + int commit_idx[MAX_VCD + 1]; + struct tcs_cmd cmds[MAX_BCMS]; + int ret = 0; + + if (!voter) + return 0; + + mutex_lock(&voter->lock); + list_for_each_entry(bcm, &voter->commit_list, list) + bcm_aggregate(bcm); + + /* + * Pre sort the BCMs based on VCD for ease of generating a command list + * that groups the BCMs with the same VCD together. VCDs are numbered + * with lowest being the most expensive time wise, ensuring that + * those commands are being sent the earliest in the queue. This needs + * to be sorted every commit since we can't guarantee the order in which + * the BCMs are added to the list. + */ + list_sort(NULL, &voter->commit_list, cmp_vcd); + + /* + * Construct the command list based on a pre ordered list of BCMs + * based on VCD. + */ + tcs_list_gen(&voter->commit_list, QCOM_ICC_BUCKET_AMC, cmds, commit_idx); + + if (!commit_idx[0]) + goto out; + + ret = rpmh_invalidate(voter->dev); + if (ret) { + pr_err("Error invalidating RPMH client (%d)\n", ret); + goto out; + } + + ret = rpmh_write_batch(voter->dev, RPMH_ACTIVE_ONLY_STATE, + cmds, commit_idx); + if (ret) { + pr_err("Error sending AMC RPMH requests (%d)\n", ret); + goto out; + } + + list_for_each_entry_safe(bcm, bcm_tmp, &voter->commit_list, list) + list_del_init(&bcm->list); + + list_for_each_entry_safe(bcm, bcm_tmp, &voter->ws_list, ws_list) { + /* + * Only generate WAKE and SLEEP commands if a resource's + * requirements change as the execution environment transitions + * between different power states. + */ + if (bcm->vote_x[QCOM_ICC_BUCKET_WAKE] != + bcm->vote_x[QCOM_ICC_BUCKET_SLEEP] || + bcm->vote_y[QCOM_ICC_BUCKET_WAKE] != + bcm->vote_y[QCOM_ICC_BUCKET_SLEEP]) + list_add_tail(&bcm->list, &voter->commit_list); + else + list_del_init(&bcm->ws_list); + } + + if (list_empty(&voter->commit_list)) + goto out; + + list_sort(NULL, &voter->commit_list, cmp_vcd); + + tcs_list_gen(&voter->commit_list, QCOM_ICC_BUCKET_WAKE, cmds, commit_idx); + + ret = rpmh_write_batch(voter->dev, RPMH_WAKE_ONLY_STATE, cmds, commit_idx); + if (ret) { + pr_err("Error sending WAKE RPMH requests (%d)\n", ret); + goto out; + } + + tcs_list_gen(&voter->commit_list, QCOM_ICC_BUCKET_SLEEP, cmds, commit_idx); + + ret = rpmh_write_batch(voter->dev, RPMH_SLEEP_STATE, cmds, commit_idx); + if (ret) { + pr_err("Error sending SLEEP RPMH requests (%d)\n", ret); + goto out; + } + +out: + list_for_each_entry_safe(bcm, bcm_tmp, &voter->commit_list, list) + list_del_init(&bcm->list); + + mutex_unlock(&voter->lock); + return ret; +} +EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_commit); + +static int qcom_icc_bcm_voter_probe(struct platform_device *pdev) +{ + struct bcm_voter *voter; + + voter = devm_kzalloc(&pdev->dev, sizeof(*voter), GFP_KERNEL); + if (!voter) + return -ENOMEM; + + voter->dev = &pdev->dev; + voter->np = pdev->dev.of_node; + mutex_init(&voter->lock); + INIT_LIST_HEAD(&voter->commit_list); + INIT_LIST_HEAD(&voter->ws_list); + + mutex_lock(&bcm_voter_lock); + list_add_tail(&voter->voter_node, &bcm_voters); + mutex_unlock(&bcm_voter_lock); + + return 0; +} + +static const struct of_device_id bcm_voter_of_match[] = { + { .compatible = "qcom,bcm-voter" }, + { } +}; + +static struct platform_driver qcom_icc_bcm_voter_driver = { + .probe = qcom_icc_bcm_voter_probe, + .driver = { + .name = "bcm_voter", + .of_match_table = bcm_voter_of_match, + }, +}; +module_platform_driver(qcom_icc_bcm_voter_driver); + +MODULE_AUTHOR("David Dai <daidavid1@codeaurora.org>"); +MODULE_DESCRIPTION("Qualcomm BCM Voter interconnect driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/interconnect/qcom/bcm-voter.h b/drivers/interconnect/qcom/bcm-voter.h new file mode 100644 index 000000000000..0f64c0bab2c0 --- /dev/null +++ b/drivers/interconnect/qcom/bcm-voter.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __DRIVERS_INTERCONNECT_QCOM_BCM_VOTER_H__ +#define __DRIVERS_INTERCONNECT_QCOM_BCM_VOTER_H__ + +#include <soc/qcom/cmd-db.h> +#include <soc/qcom/rpmh.h> +#include <soc/qcom/tcs.h> + +#include "icc-rpmh.h" + +#define DEFINE_QBCM(_name, _bcmname, _keepalive, ...) \ +static struct qcom_icc_bcm _name = { \ + .name = _bcmname, \ + .keepalive = _keepalive, \ + .num_nodes = ARRAY_SIZE(((struct qcom_icc_node *[]){ __VA_ARGS__ })), \ + .nodes = { __VA_ARGS__ }, \ +} + +struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name); +void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm); +int qcom_icc_bcm_voter_commit(struct bcm_voter *voter); + +#endif diff --git a/drivers/interconnect/qcom/icc-rpmh.c b/drivers/interconnect/qcom/icc-rpmh.c new file mode 100644 index 000000000000..3ac5182c9ab2 --- /dev/null +++ b/drivers/interconnect/qcom/icc-rpmh.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/interconnect.h> +#include <linux/interconnect-provider.h> +#include <linux/module.h> + +#include "bcm-voter.h" +#include "icc-rpmh.h" + +/** + * qcom_icc_pre_aggregate - cleans up stale values from prior icc_set + * @node: icc node to operate on + */ +void qcom_icc_pre_aggregate(struct icc_node *node) +{ + size_t i; + struct qcom_icc_node *qn; + + qn = node->data; + + for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { + qn->sum_avg[i] = 0; + qn->max_peak[i] = 0; + } +} +EXPORT_SYMBOL_GPL(qcom_icc_pre_aggregate); + +/** + * qcom_icc_aggregate - aggregate bw for buckets indicated by tag + * @node: node to aggregate + * @tag: tag to indicate which buckets to aggregate + * @avg_bw: new bw to sum aggregate + * @peak_bw: new bw to max aggregate + * @agg_avg: existing aggregate avg bw val + * @agg_peak: existing aggregate peak bw val + */ +int qcom_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, + u32 peak_bw, u32 *agg_avg, u32 *agg_peak) +{ + size_t i; + struct qcom_icc_node *qn; + struct qcom_icc_provider *qp; + + qn = node->data; + qp = to_qcom_provider(node->provider); + + if (!tag) + tag = QCOM_ICC_TAG_ALWAYS; + + for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { + if (tag & BIT(i)) { + qn->sum_avg[i] += avg_bw; + qn->max_peak[i] = max_t(u32, qn->max_peak[i], peak_bw); + } + } + + *agg_avg += avg_bw; + *agg_peak = max_t(u32, *agg_peak, peak_bw); + + for (i = 0; i < qn->num_bcms; i++) + qcom_icc_bcm_voter_add(qp->voter, qn->bcms[i]); + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_icc_aggregate); + +/** + * qcom_icc_set - set the constraints based on path + * @src: source node for the path to set constraints on + * @dst: destination node for the path to set constraints on + * + * Return: 0 on success, or an error code otherwise + */ +int qcom_icc_set(struct icc_node *src, struct icc_node *dst) +{ + struct qcom_icc_provider *qp; + struct icc_node *node; + + if (!src) + node = dst; + else + node = src; + + qp = to_qcom_provider(node->provider); + + qcom_icc_bcm_voter_commit(qp->voter); + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_icc_set); + +/** + * qcom_icc_bcm_init - populates bcm aux data and connect qnodes + * @bcm: bcm to be initialized + * @dev: associated provider device + * + * Return: 0 on success, or an error code otherwise + */ +int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev) +{ + struct qcom_icc_node *qn; + const struct bcm_db *data; + size_t data_count; + int i; + + /* BCM is already initialised*/ + if (bcm->addr) + return 0; + + bcm->addr = cmd_db_read_addr(bcm->name); + if (!bcm->addr) { + dev_err(dev, "%s could not find RPMh address\n", + bcm->name); + return -EINVAL; + } + + data = cmd_db_read_aux_data(bcm->name, &data_count); + if (IS_ERR(data)) { + dev_err(dev, "%s command db read error (%ld)\n", + bcm->name, PTR_ERR(data)); + return PTR_ERR(data); + } + if (!data_count) { + dev_err(dev, "%s command db missing or partial aux data\n", + bcm->name); + return -EINVAL; + } + + bcm->aux_data.unit = le32_to_cpu(data->unit); + bcm->aux_data.width = le16_to_cpu(data->width); + bcm->aux_data.vcd = data->vcd; + bcm->aux_data.reserved = data->reserved; + INIT_LIST_HEAD(&bcm->list); + INIT_LIST_HEAD(&bcm->ws_list); + + /* Link Qnodes to their respective BCMs */ + for (i = 0; i < bcm->num_nodes; i++) { + qn = bcm->nodes[i]; + qn->bcms[qn->num_bcms] = bcm; + qn->num_bcms++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_icc_bcm_init); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/interconnect/qcom/icc-rpmh.h b/drivers/interconnect/qcom/icc-rpmh.h new file mode 100644 index 000000000000..903d25e61984 --- /dev/null +++ b/drivers/interconnect/qcom/icc-rpmh.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __DRIVERS_INTERCONNECT_QCOM_ICC_RPMH_H__ +#define __DRIVERS_INTERCONNECT_QCOM_ICC_RPMH_H__ + +#define to_qcom_provider(_provider) \ + container_of(_provider, struct qcom_icc_provider, provider) + +/** + * struct qcom_icc_provider - Qualcomm specific interconnect provider + * @provider: generic interconnect provider + * @dev: reference to the NoC device + * @bcms: list of bcms that maps to the provider + * @num_bcms: number of @bcms + * @voter: bcm voter targeted by this provider + */ +struct qcom_icc_provider { + struct icc_provider provider; + struct device *dev; + struct qcom_icc_bcm **bcms; + size_t num_bcms; + struct bcm_voter *voter; +}; + +/** + * struct bcm_db - Auxiliary data pertaining to each Bus Clock Manager (BCM) + * @unit: divisor used to convert bytes/sec bw value to an RPMh msg + * @width: multiplier used to convert bytes/sec bw value to an RPMh msg + * @vcd: virtual clock domain that this bcm belongs to + * @reserved: reserved field + */ +struct bcm_db { + __le32 unit; + __le16 width; + u8 vcd; + u8 reserved; +}; + +#define MAX_LINKS 128 +#define MAX_BCMS 64 +#define MAX_BCM_PER_NODE 3 +#define MAX_VCD 10 + +/* + * The AMC bucket denotes constraints that are applied to hardware when + * icc_set_bw() completes, whereas the WAKE and SLEEP constraints are applied + * when the execution environment transitions between active and low power mode. + */ +#define QCOM_ICC_BUCKET_AMC 0 +#define QCOM_ICC_BUCKET_WAKE 1 +#define QCOM_ICC_BUCKET_SLEEP 2 +#define QCOM_ICC_NUM_BUCKETS 3 +#define QCOM_ICC_TAG_AMC BIT(QCOM_ICC_BUCKET_AMC) +#define QCOM_ICC_TAG_WAKE BIT(QCOM_ICC_BUCKET_WAKE) +#define QCOM_ICC_TAG_SLEEP BIT(QCOM_ICC_BUCKET_SLEEP) +#define QCOM_ICC_TAG_ACTIVE_ONLY (QCOM_ICC_TAG_AMC | QCOM_ICC_TAG_WAKE) +#define QCOM_ICC_TAG_ALWAYS (QCOM_ICC_TAG_AMC | QCOM_ICC_TAG_WAKE |\ + QCOM_ICC_TAG_SLEEP) + +/** + * struct qcom_icc_node - Qualcomm specific interconnect nodes + * @name: the node name used in debugfs + * @links: an array of nodes where we can go next while traversing + * @id: a unique node identifier + * @num_links: the total number of @links + * @channels: num of channels at this node + * @buswidth: width of the interconnect between a node and the bus + * @sum_avg: current sum aggregate value of all avg bw requests + * @max_peak: current max aggregate value of all peak bw requests + * @bcms: list of bcms associated with this logical node + * @num_bcms: num of @bcms + */ +struct qcom_icc_node { + const char *name; + u16 links[MAX_LINKS]; + u16 id; + u16 num_links; + u16 channels; + u16 buswidth; + u64 sum_avg[QCOM_ICC_NUM_BUCKETS]; + u64 max_peak[QCOM_ICC_NUM_BUCKETS]; + struct qcom_icc_bcm *bcms[MAX_BCM_PER_NODE]; + size_t num_bcms; +}; + +/** + * struct qcom_icc_bcm - Qualcomm specific hardware accelerator nodes + * known as Bus Clock Manager (BCM) + * @name: the bcm node name used to fetch BCM data from command db + * @type: latency or bandwidth bcm + * @addr: address offsets used when voting to RPMH + * @vote_x: aggregated threshold values, represents sum_bw when @type is bw bcm + * @vote_y: aggregated threshold values, represents peak_bw when @type is bw bcm + * @dirty: flag used to indicate whether the bcm needs to be committed + * @keepalive: flag used to indicate whether a keepalive is required + * @aux_data: auxiliary data used when calculating threshold values and + * communicating with RPMh + * @list: used to link to other bcms when compiling lists for commit + * @ws_list: used to keep track of bcms that may transition between wake/sleep + * @num_nodes: total number of @num_nodes + * @nodes: list of qcom_icc_nodes that this BCM encapsulates + */ +struct qcom_icc_bcm { + const char *name; + u32 type; + u32 addr; + u64 vote_x[QCOM_ICC_NUM_BUCKETS]; + u64 vote_y[QCOM_ICC_NUM_BUCKETS]; + bool dirty; + bool keepalive; + struct bcm_db aux_data; + struct list_head list; + struct list_head ws_list; + size_t num_nodes; + struct qcom_icc_node *nodes[]; +}; + +struct qcom_icc_fabric { + struct qcom_icc_node **nodes; + size_t num_nodes; +}; + +struct qcom_icc_desc { + struct qcom_icc_node **nodes; + size_t num_nodes; + struct qcom_icc_bcm **bcms; + size_t num_bcms; +}; + +#define DEFINE_QNODE(_name, _id, _channels, _buswidth, ...) \ + static struct qcom_icc_node _name = { \ + .id = _id, \ + .name = #_name, \ + .channels = _channels, \ + .buswidth = _buswidth, \ + .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \ + .links = { __VA_ARGS__ }, \ + } + +int qcom_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, + u32 peak_bw, u32 *agg_avg, u32 *agg_peak); +int qcom_icc_set(struct icc_node *src, struct icc_node *dst); +int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev); +void qcom_icc_pre_aggregate(struct icc_node *node); + +#endif diff --git a/drivers/interconnect/qcom/osm-l3.c b/drivers/interconnect/qcom/osm-l3.c new file mode 100644 index 000000000000..a03c6d6833df --- /dev/null +++ b/drivers/interconnect/qcom/osm-l3.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/interconnect-provider.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +#include <dt-bindings/interconnect/qcom,osm-l3.h> + +#include "sc7180.h" +#include "sdm845.h" + +#define LUT_MAX_ENTRIES 40U +#define LUT_SRC GENMASK(31, 30) +#define LUT_L_VAL GENMASK(7, 0) +#define LUT_ROW_SIZE 32 +#define CLK_HW_DIV 2 + +/* Register offsets */ +#define REG_ENABLE 0x0 +#define REG_FREQ_LUT 0x110 +#define REG_PERF_STATE 0x920 + +#define OSM_L3_MAX_LINKS 1 + +#define to_qcom_provider(_provider) \ + container_of(_provider, struct qcom_osm_l3_icc_provider, provider) + +struct qcom_osm_l3_icc_provider { + void __iomem *base; + unsigned int max_state; + unsigned long lut_tables[LUT_MAX_ENTRIES]; + struct icc_provider provider; +}; + +/** + * struct qcom_icc_node - Qualcomm specific interconnect nodes + * @name: the node name used in debugfs + * @links: an array of nodes where we can go next while traversing + * @id: a unique node identifier + * @num_links: the total number of @links + * @buswidth: width of the interconnect between a node and the bus + */ +struct qcom_icc_node { + const char *name; + u16 links[OSM_L3_MAX_LINKS]; + u16 id; + u16 num_links; + u16 buswidth; +}; + +struct qcom_icc_desc { + struct qcom_icc_node **nodes; + size_t num_nodes; +}; + +#define DEFINE_QNODE(_name, _id, _buswidth, ...) \ + static struct qcom_icc_node _name = { \ + .name = #_name, \ + .id = _id, \ + .buswidth = _buswidth, \ + .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \ + .links = { __VA_ARGS__ }, \ + } + +DEFINE_QNODE(sdm845_osm_apps_l3, SDM845_MASTER_OSM_L3_APPS, 16, SDM845_SLAVE_OSM_L3); +DEFINE_QNODE(sdm845_osm_l3, SDM845_SLAVE_OSM_L3, 16); + +static struct qcom_icc_node *sdm845_osm_l3_nodes[] = { + [MASTER_OSM_L3_APPS] = &sdm845_osm_apps_l3, + [SLAVE_OSM_L3] = &sdm845_osm_l3, +}; + +const static struct qcom_icc_desc sdm845_icc_osm_l3 = { + .nodes = sdm845_osm_l3_nodes, + .num_nodes = ARRAY_SIZE(sdm845_osm_l3_nodes), +}; + +DEFINE_QNODE(sc7180_osm_apps_l3, SC7180_MASTER_OSM_L3_APPS, 16, SC7180_SLAVE_OSM_L3); +DEFINE_QNODE(sc7180_osm_l3, SC7180_SLAVE_OSM_L3, 16); + +static struct qcom_icc_node *sc7180_osm_l3_nodes[] = { + [MASTER_OSM_L3_APPS] = &sc7180_osm_apps_l3, + [SLAVE_OSM_L3] = &sc7180_osm_l3, +}; + +const static struct qcom_icc_desc sc7180_icc_osm_l3 = { + .nodes = sc7180_osm_l3_nodes, + .num_nodes = ARRAY_SIZE(sc7180_osm_l3_nodes), +}; + +static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) +{ + struct qcom_osm_l3_icc_provider *qp; + struct icc_provider *provider; + struct qcom_icc_node *qn; + struct icc_node *n; + unsigned int index; + u32 agg_peak = 0; + u32 agg_avg = 0; + u64 rate; + + qn = src->data; + provider = src->provider; + qp = to_qcom_provider(provider); + + list_for_each_entry(n, &provider->nodes, node_list) + provider->aggregate(n, 0, n->avg_bw, n->peak_bw, + &agg_avg, &agg_peak); + + rate = max(agg_avg, agg_peak); + rate = icc_units_to_bps(rate); + do_div(rate, qn->buswidth); + + for (index = 0; index < qp->max_state - 1; index++) { + if (qp->lut_tables[index] >= rate) + break; + } + + writel_relaxed(index, qp->base + REG_PERF_STATE); + + return 0; +} + +static int qcom_osm_l3_remove(struct platform_device *pdev) +{ + struct qcom_osm_l3_icc_provider *qp = platform_get_drvdata(pdev); + + icc_nodes_remove(&qp->provider); + return icc_provider_del(&qp->provider); +} + +static int qcom_osm_l3_probe(struct platform_device *pdev) +{ + u32 info, src, lval, i, prev_freq = 0, freq; + static unsigned long hw_rate, xo_rate; + struct qcom_osm_l3_icc_provider *qp; + const struct qcom_icc_desc *desc; + struct icc_onecell_data *data; + struct icc_provider *provider; + struct qcom_icc_node **qnodes; + struct icc_node *node; + size_t num_nodes; + struct clk *clk; + int ret; + + clk = clk_get(&pdev->dev, "xo"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + xo_rate = clk_get_rate(clk); + clk_put(clk); + + clk = clk_get(&pdev->dev, "alternate"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + hw_rate = clk_get_rate(clk) / CLK_HW_DIV; + clk_put(clk); + + qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL); + if (!qp) + return -ENOMEM; + + qp->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(qp->base)) + return PTR_ERR(qp->base); + + /* HW should be in enabled state to proceed */ + if (!(readl_relaxed(qp->base + REG_ENABLE) & 0x1)) { + dev_err(&pdev->dev, "error hardware not enabled\n"); + return -ENODEV; + } + + for (i = 0; i < LUT_MAX_ENTRIES; i++) { + info = readl_relaxed(qp->base + REG_FREQ_LUT + + i * LUT_ROW_SIZE); + src = FIELD_GET(LUT_SRC, info); + lval = FIELD_GET(LUT_L_VAL, info); + if (src) + freq = xo_rate * lval; + else + freq = hw_rate; + + /* Two of the same frequencies signify end of table */ + if (i > 0 && prev_freq == freq) + break; + + dev_dbg(&pdev->dev, "index=%d freq=%d\n", i, freq); + + qp->lut_tables[i] = freq; + prev_freq = freq; + } + qp->max_state = i; + + desc = device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + qnodes = desc->nodes; + num_nodes = desc->num_nodes; + + data = devm_kcalloc(&pdev->dev, num_nodes, sizeof(*node), GFP_KERNEL); + if (!data) + return -ENOMEM; + + provider = &qp->provider; + provider->dev = &pdev->dev; + provider->set = qcom_icc_set; + provider->aggregate = icc_std_aggregate; + provider->xlate = of_icc_xlate_onecell; + INIT_LIST_HEAD(&provider->nodes); + provider->data = data; + + ret = icc_provider_add(provider); + if (ret) { + dev_err(&pdev->dev, "error adding interconnect provider\n"); + return ret; + } + + for (i = 0; i < num_nodes; i++) { + size_t j; + + node = icc_node_create(qnodes[i]->id); + if (IS_ERR(node)) { + ret = PTR_ERR(node); + goto err; + } + + node->name = qnodes[i]->name; + node->data = qnodes[i]; + icc_node_add(node, provider); + + for (j = 0; j < qnodes[i]->num_links; j++) + icc_link_create(node, qnodes[i]->links[j]); + + data->nodes[i] = node; + } + data->num_nodes = num_nodes; + + platform_set_drvdata(pdev, qp); + + return 0; +err: + icc_nodes_remove(provider); + icc_provider_del(provider); + + return ret; +} + +static const struct of_device_id osm_l3_of_match[] = { + { .compatible = "qcom,sc7180-osm-l3", .data = &sc7180_icc_osm_l3 }, + { .compatible = "qcom,sdm845-osm-l3", .data = &sdm845_icc_osm_l3 }, + { } +}; +MODULE_DEVICE_TABLE(of, osm_l3_of_match); + +static struct platform_driver osm_l3_driver = { + .probe = qcom_osm_l3_probe, + .remove = qcom_osm_l3_remove, + .driver = { + .name = "osm-l3", + .of_match_table = osm_l3_of_match, + }, +}; +module_platform_driver(osm_l3_driver); + +MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/interconnect/qcom/sc7180.c b/drivers/interconnect/qcom/sc7180.c new file mode 100644 index 000000000000..dcf493d07928 --- /dev/null +++ b/drivers/interconnect/qcom/sc7180.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/device.h> +#include <linux/interconnect.h> +#include <linux/interconnect-provider.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <dt-bindings/interconnect/qcom,sc7180.h> + +#include "bcm-voter.h" +#include "icc-rpmh.h" +#include "sc7180.h" + +DEFINE_QNODE(qhm_a1noc_cfg, SC7180_MASTER_A1NOC_CFG, 1, 4, SC7180_SLAVE_SERVICE_A1NOC); +DEFINE_QNODE(qhm_qspi, SC7180_MASTER_QSPI, 1, 4, SC7180_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(qhm_qup_0, SC7180_MASTER_QUP_0, 1, 4, SC7180_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_sdc2, SC7180_MASTER_SDCC_2, 1, 8, SC7180_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_emmc, SC7180_MASTER_EMMC, 1, 8, SC7180_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_ufs_mem, SC7180_MASTER_UFS_MEM, 1, 8, SC7180_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(qhm_a2noc_cfg, SC7180_MASTER_A2NOC_CFG, 1, 4, SC7180_SLAVE_SERVICE_A2NOC); +DEFINE_QNODE(qhm_qdss_bam, SC7180_MASTER_QDSS_BAM, 1, 4, SC7180_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qhm_qup_1, SC7180_MASTER_QUP_1, 1, 4, SC7180_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qxm_crypto, SC7180_MASTER_CRYPTO, 1, 8, SC7180_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qxm_ipa, SC7180_MASTER_IPA, 1, 8, SC7180_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(xm_qdss_etr, SC7180_MASTER_QDSS_ETR, 1, 8, SC7180_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qhm_usb3, SC7180_MASTER_USB3, 1, 8, SC7180_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qxm_camnoc_hf0_uncomp, SC7180_MASTER_CAMNOC_HF0_UNCOMP, 1, 32, SC7180_SLAVE_CAMNOC_UNCOMP); +DEFINE_QNODE(qxm_camnoc_hf1_uncomp, SC7180_MASTER_CAMNOC_HF1_UNCOMP, 1, 32, SC7180_SLAVE_CAMNOC_UNCOMP); +DEFINE_QNODE(qxm_camnoc_sf_uncomp, SC7180_MASTER_CAMNOC_SF_UNCOMP, 1, 32, SC7180_SLAVE_CAMNOC_UNCOMP); +DEFINE_QNODE(qnm_npu, SC7180_MASTER_NPU, 2, 32, SC7180_SLAVE_CDSP_GEM_NOC); +DEFINE_QNODE(qxm_npu_dsp, SC7180_MASTER_NPU_PROC, 1, 8, SC7180_SLAVE_CDSP_GEM_NOC); +DEFINE_QNODE(qnm_snoc, SC7180_MASTER_SNOC_CNOC, 1, 8, SC7180_SLAVE_A1NOC_CFG, SC7180_SLAVE_A2NOC_CFG, SC7180_SLAVE_AHB2PHY_SOUTH, SC7180_SLAVE_AHB2PHY_CENTER, SC7180_SLAVE_AOP, SC7180_SLAVE_AOSS, SC7180_SLAVE_BOOT_ROM, SC7180_SLAVE_CAMERA_CFG, SC7180_SLAVE_CAMERA_NRT_THROTTLE_CFG, SC7180_SLAVE_CAMERA_RT_THROTTLE_CFG, SC7180_SLAVE_CLK_CTL, SC7180_SLAVE_RBCPR_CX_CFG, SC7180_SLAVE_RBCPR_MX_CFG, SC7180_SLAVE_CRYPTO_0_CFG, SC7180_SLAVE_DCC_CFG, SC7180_SLAVE_CNOC_DDRSS, SC7180_SLAVE_DISPLAY_CFG, SC7180_SLAVE_DISPLAY_RT_THROTTLE_CFG, SC7180_SLAVE_DISPLAY_THROTTLE_CFG, SC7180_SLAVE_EMMC_CFG, SC7180_SLAVE_GLM, + SC7180_SLAVE_GFX3D_CFG, SC7180_SLAVE_IMEM_CFG, SC7180_SLAVE_IPA_CFG, SC7180_SLAVE_CNOC_MNOC_CFG, SC7180_SLAVE_CNOC_MSS, SC7180_SLAVE_NPU_CFG, SC7180_SLAVE_NPU_DMA_BWMON_CFG, SC7180_SLAVE_NPU_PROC_BWMON_CFG, SC7180_SLAVE_PDM, SC7180_SLAVE_PIMEM_CFG, SC7180_SLAVE_PRNG, SC7180_SLAVE_QDSS_CFG, SC7180_SLAVE_QM_CFG, SC7180_SLAVE_QM_MPU_CFG, SC7180_SLAVE_QSPI_0, SC7180_SLAVE_QUP_0, SC7180_SLAVE_QUP_1, SC7180_SLAVE_SDCC_2, SC7180_SLAVE_SECURITY, SC7180_SLAVE_SNOC_CFG, SC7180_SLAVE_TCSR, SC7180_SLAVE_TLMM_WEST, SC7180_SLAVE_TLMM_NORTH, SC7180_SLAVE_TLMM_SOUTH, SC7180_SLAVE_UFS_MEM_CFG, SC7180_SLAVE_USB3, SC7180_SLAVE_VENUS_CFG, SC7180_SLAVE_VENUS_THROTTLE_CFG, SC7180_SLAVE_VSENSE_CTRL_CFG, SC7180_SLAVE_SERVICE_CNOC); +DEFINE_QNODE(xm_qdss_dap, SC7180_MASTER_QDSS_DAP, 1, 8, SC7180_SLAVE_A1NOC_CFG, SC7180_SLAVE_A2NOC_CFG, SC7180_SLAVE_AHB2PHY_SOUTH, SC7180_SLAVE_AHB2PHY_CENTER, SC7180_SLAVE_AOP, SC7180_SLAVE_AOSS, SC7180_SLAVE_BOOT_ROM, SC7180_SLAVE_CAMERA_CFG, SC7180_SLAVE_CAMERA_NRT_THROTTLE_CFG, SC7180_SLAVE_CAMERA_RT_THROTTLE_CFG, SC7180_SLAVE_CLK_CTL, SC7180_SLAVE_RBCPR_CX_CFG, SC7180_SLAVE_RBCPR_MX_CFG, SC7180_SLAVE_CRYPTO_0_CFG, SC7180_SLAVE_DCC_CFG, SC7180_SLAVE_CNOC_DDRSS, SC7180_SLAVE_DISPLAY_CFG, SC7180_SLAVE_DISPLAY_RT_THROTTLE_CFG, SC7180_SLAVE_DISPLAY_THROTTLE_CFG, SC7180_SLAVE_EMMC_CFG, SC7180_SLAVE_GLM, SC7180_SLAVE_GFX3D_CFG, SC7180_SLAVE_IMEM_CFG, SC7180_SLAVE_IPA_CFG, SC7180_SLAVE_CNOC_MNOC_CFG, SC7180_SLAVE_CNOC_MSS, SC7180_SLAVE_NPU_CFG, SC7180_SLAVE_NPU_DMA_BWMON_CFG, +SC7180_SLAVE_NPU_PROC_BWMON_CFG, SC7180_SLAVE_PDM, SC7180_SLAVE_PIMEM_CFG, SC7180_SLAVE_PRNG, SC7180_SLAVE_QDSS_CFG, SC7180_SLAVE_QM_CFG, SC7180_SLAVE_QM_MPU_CFG, SC7180_SLAVE_QSPI_0, SC7180_SLAVE_QUP_0, SC7180_SLAVE_QUP_1, SC7180_SLAVE_SDCC_2, SC7180_SLAVE_SECURITY, SC7180_SLAVE_SNOC_CFG, SC7180_SLAVE_TCSR, SC7180_SLAVE_TLMM_WEST, SC7180_SLAVE_TLMM_NORTH, SC7180_SLAVE_TLMM_SOUTH, SC7180_SLAVE_UFS_MEM_CFG, SC7180_SLAVE_USB3, SC7180_SLAVE_VENUS_CFG, SC7180_SLAVE_VENUS_THROTTLE_CFG, SC7180_SLAVE_VSENSE_CTRL_CFG, SC7180_SLAVE_SERVICE_CNOC); +DEFINE_QNODE(qhm_cnoc_dc_noc, SC7180_MASTER_CNOC_DC_NOC, 1, 4, SC7180_SLAVE_GEM_NOC_CFG, SC7180_SLAVE_LLCC_CFG); +DEFINE_QNODE(acm_apps0, SC7180_MASTER_APPSS_PROC, 1, 16, SC7180_SLAVE_GEM_NOC_SNOC, SC7180_SLAVE_LLCC); +DEFINE_QNODE(acm_sys_tcu, SC7180_MASTER_SYS_TCU, 1, 8, SC7180_SLAVE_GEM_NOC_SNOC, SC7180_SLAVE_LLCC); +DEFINE_QNODE(qhm_gemnoc_cfg, SC7180_MASTER_GEM_NOC_CFG, 1, 4, SC7180_SLAVE_MSS_PROC_MS_MPU_CFG, SC7180_SLAVE_SERVICE_GEM_NOC); +DEFINE_QNODE(qnm_cmpnoc, SC7180_MASTER_COMPUTE_NOC, 1, 32, SC7180_SLAVE_GEM_NOC_SNOC, SC7180_SLAVE_LLCC); +DEFINE_QNODE(qnm_mnoc_hf, SC7180_MASTER_MNOC_HF_MEM_NOC, 1, 32, SC7180_SLAVE_LLCC); +DEFINE_QNODE(qnm_mnoc_sf, SC7180_MASTER_MNOC_SF_MEM_NOC, 1, 32, SC7180_SLAVE_GEM_NOC_SNOC, SC7180_SLAVE_LLCC); +DEFINE_QNODE(qnm_snoc_gc, SC7180_MASTER_SNOC_GC_MEM_NOC, 1, 8, SC7180_SLAVE_LLCC); +DEFINE_QNODE(qnm_snoc_sf, SC7180_MASTER_SNOC_SF_MEM_NOC, 1, 16, SC7180_SLAVE_LLCC); +DEFINE_QNODE(qxm_gpu, SC7180_MASTER_GFX3D, 2, 32, SC7180_SLAVE_GEM_NOC_SNOC, SC7180_SLAVE_LLCC); +DEFINE_QNODE(ipa_core_master, SC7180_MASTER_IPA_CORE, 1, 8, SC7180_SLAVE_IPA_CORE); +DEFINE_QNODE(llcc_mc, SC7180_MASTER_LLCC, 2, 4, SC7180_SLAVE_EBI1); +DEFINE_QNODE(qhm_mnoc_cfg, SC7180_MASTER_CNOC_MNOC_CFG, 1, 4, SC7180_SLAVE_SERVICE_MNOC); +DEFINE_QNODE(qxm_camnoc_hf0, SC7180_MASTER_CAMNOC_HF0, 2, 32, SC7180_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_camnoc_hf1, SC7180_MASTER_CAMNOC_HF1, 2, 32, SC7180_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_camnoc_sf, SC7180_MASTER_CAMNOC_SF, 1, 32, SC7180_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_mdp0, SC7180_MASTER_MDP0, 1, 32, SC7180_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_rot, SC7180_MASTER_ROTATOR, 1, 16, SC7180_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_venus0, SC7180_MASTER_VIDEO_P0, 1, 32, SC7180_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_venus_arm9, SC7180_MASTER_VIDEO_PROC, 1, 8, SC7180_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(amm_npu_sys, SC7180_MASTER_NPU_SYS, 2, 32, SC7180_SLAVE_NPU_COMPUTE_NOC); +DEFINE_QNODE(qhm_npu_cfg, SC7180_MASTER_NPU_NOC_CFG, 1, 4, SC7180_SLAVE_NPU_CAL_DP0, SC7180_SLAVE_NPU_CP, SC7180_SLAVE_NPU_INT_DMA_BWMON_CFG, SC7180_SLAVE_NPU_DPM, SC7180_SLAVE_ISENSE_CFG, SC7180_SLAVE_NPU_LLM_CFG, SC7180_SLAVE_NPU_TCM, SC7180_SLAVE_SERVICE_NPU_NOC); +DEFINE_QNODE(qup_core_master_1, SC7180_MASTER_QUP_CORE_0, 1, 4, SC7180_SLAVE_QUP_CORE_0); +DEFINE_QNODE(qup_core_master_2, SC7180_MASTER_QUP_CORE_1, 1, 4, SC7180_SLAVE_QUP_CORE_1); +DEFINE_QNODE(qhm_snoc_cfg, SC7180_MASTER_SNOC_CFG, 1, 4, SC7180_SLAVE_SERVICE_SNOC); +DEFINE_QNODE(qnm_aggre1_noc, SC7180_MASTER_A1NOC_SNOC, 1, 16, SC7180_SLAVE_APPSS, SC7180_SLAVE_SNOC_CNOC, SC7180_SLAVE_SNOC_GEM_NOC_SF, SC7180_SLAVE_IMEM, SC7180_SLAVE_PIMEM, SC7180_SLAVE_QDSS_STM); +DEFINE_QNODE(qnm_aggre2_noc, SC7180_MASTER_A2NOC_SNOC, 1, 16, SC7180_SLAVE_APPSS, SC7180_SLAVE_SNOC_CNOC, SC7180_SLAVE_SNOC_GEM_NOC_SF, SC7180_SLAVE_IMEM, SC7180_SLAVE_PIMEM, SC7180_SLAVE_QDSS_STM, SC7180_SLAVE_TCU); +DEFINE_QNODE(qnm_gemnoc, SC7180_MASTER_GEM_NOC_SNOC, 1, 8, SC7180_SLAVE_APPSS, SC7180_SLAVE_SNOC_CNOC, SC7180_SLAVE_IMEM, SC7180_SLAVE_PIMEM, SC7180_SLAVE_QDSS_STM, SC7180_SLAVE_TCU); +DEFINE_QNODE(qxm_pimem, SC7180_MASTER_PIMEM, 1, 8, SC7180_SLAVE_SNOC_GEM_NOC_GC, SC7180_SLAVE_IMEM); +DEFINE_QNODE(qns_a1noc_snoc, SC7180_SLAVE_A1NOC_SNOC, 1, 16, SC7180_MASTER_A1NOC_SNOC); +DEFINE_QNODE(srvc_aggre1_noc, SC7180_SLAVE_SERVICE_A1NOC, 1, 4); +DEFINE_QNODE(qns_a2noc_snoc, SC7180_SLAVE_A2NOC_SNOC, 1, 16, SC7180_MASTER_A2NOC_SNOC); +DEFINE_QNODE(srvc_aggre2_noc, SC7180_SLAVE_SERVICE_A2NOC, 1, 4); +DEFINE_QNODE(qns_camnoc_uncomp, SC7180_SLAVE_CAMNOC_UNCOMP, 1, 32); +DEFINE_QNODE(qns_cdsp_gemnoc, SC7180_SLAVE_CDSP_GEM_NOC, 1, 32, SC7180_MASTER_COMPUTE_NOC); +DEFINE_QNODE(qhs_a1_noc_cfg, SC7180_SLAVE_A1NOC_CFG, 1, 4, SC7180_MASTER_A1NOC_CFG); +DEFINE_QNODE(qhs_a2_noc_cfg, SC7180_SLAVE_A2NOC_CFG, 1, 4, SC7180_MASTER_A2NOC_CFG); +DEFINE_QNODE(qhs_ahb2phy0, SC7180_SLAVE_AHB2PHY_SOUTH, 1, 4); +DEFINE_QNODE(qhs_ahb2phy2, SC7180_SLAVE_AHB2PHY_CENTER, 1, 4); +DEFINE_QNODE(qhs_aop, SC7180_SLAVE_AOP, 1, 4); +DEFINE_QNODE(qhs_aoss, SC7180_SLAVE_AOSS, 1, 4); +DEFINE_QNODE(qhs_boot_rom, SC7180_SLAVE_BOOT_ROM, 1, 4); +DEFINE_QNODE(qhs_camera_cfg, SC7180_SLAVE_CAMERA_CFG, 1, 4); +DEFINE_QNODE(qhs_camera_nrt_throttle_cfg, SC7180_SLAVE_CAMERA_NRT_THROTTLE_CFG, 1, 4); +DEFINE_QNODE(qhs_camera_rt_throttle_cfg, SC7180_SLAVE_CAMERA_RT_THROTTLE_CFG, 1, 4); +DEFINE_QNODE(qhs_clk_ctl, SC7180_SLAVE_CLK_CTL, 1, 4); +DEFINE_QNODE(qhs_cpr_cx, SC7180_SLAVE_RBCPR_CX_CFG, 1, 4); +DEFINE_QNODE(qhs_cpr_mx, SC7180_SLAVE_RBCPR_MX_CFG, 1, 4); +DEFINE_QNODE(qhs_crypto0_cfg, SC7180_SLAVE_CRYPTO_0_CFG, 1, 4); +DEFINE_QNODE(qhs_dcc_cfg, SC7180_SLAVE_DCC_CFG, 1, 4); +DEFINE_QNODE(qhs_ddrss_cfg, SC7180_SLAVE_CNOC_DDRSS, 1, 4, SC7180_MASTER_CNOC_DC_NOC); +DEFINE_QNODE(qhs_display_cfg, SC7180_SLAVE_DISPLAY_CFG, 1, 4); +DEFINE_QNODE(qhs_display_rt_throttle_cfg, SC7180_SLAVE_DISPLAY_RT_THROTTLE_CFG, 1, 4); +DEFINE_QNODE(qhs_display_throttle_cfg, SC7180_SLAVE_DISPLAY_THROTTLE_CFG, 1, 4); +DEFINE_QNODE(qhs_emmc_cfg, SC7180_SLAVE_EMMC_CFG, 1, 4); +DEFINE_QNODE(qhs_glm, SC7180_SLAVE_GLM, 1, 4); +DEFINE_QNODE(qhs_gpuss_cfg, SC7180_SLAVE_GFX3D_CFG, 1, 8); +DEFINE_QNODE(qhs_imem_cfg, SC7180_SLAVE_IMEM_CFG, 1, 4); +DEFINE_QNODE(qhs_ipa, SC7180_SLAVE_IPA_CFG, 1, 4); +DEFINE_QNODE(qhs_mnoc_cfg, SC7180_SLAVE_CNOC_MNOC_CFG, 1, 4, SC7180_MASTER_CNOC_MNOC_CFG); +DEFINE_QNODE(qhs_mss_cfg, SC7180_SLAVE_CNOC_MSS, 1, 4); +DEFINE_QNODE(qhs_npu_cfg, SC7180_SLAVE_NPU_CFG, 1, 4, SC7180_MASTER_NPU_NOC_CFG); +DEFINE_QNODE(qhs_npu_dma_throttle_cfg, SC7180_SLAVE_NPU_DMA_BWMON_CFG, 1, 4); +DEFINE_QNODE(qhs_npu_dsp_throttle_cfg, SC7180_SLAVE_NPU_PROC_BWMON_CFG, 1, 4); +DEFINE_QNODE(qhs_pdm, SC7180_SLAVE_PDM, 1, 4); +DEFINE_QNODE(qhs_pimem_cfg, SC7180_SLAVE_PIMEM_CFG, 1, 4); +DEFINE_QNODE(qhs_prng, SC7180_SLAVE_PRNG, 1, 4); +DEFINE_QNODE(qhs_qdss_cfg, SC7180_SLAVE_QDSS_CFG, 1, 4); +DEFINE_QNODE(qhs_qm_cfg, SC7180_SLAVE_QM_CFG, 1, 4); +DEFINE_QNODE(qhs_qm_mpu_cfg, SC7180_SLAVE_QM_MPU_CFG, 1, 4); +DEFINE_QNODE(qhs_qspi, SC7180_SLAVE_QSPI_0, 1, 4); +DEFINE_QNODE(qhs_qup0, SC7180_SLAVE_QUP_0, 1, 4); +DEFINE_QNODE(qhs_qup1, SC7180_SLAVE_QUP_1, 1, 4); +DEFINE_QNODE(qhs_sdc2, SC7180_SLAVE_SDCC_2, 1, 4); +DEFINE_QNODE(qhs_security, SC7180_SLAVE_SECURITY, 1, 4); +DEFINE_QNODE(qhs_snoc_cfg, SC7180_SLAVE_SNOC_CFG, 1, 4, SC7180_MASTER_SNOC_CFG); +DEFINE_QNODE(qhs_tcsr, SC7180_SLAVE_TCSR, 1, 4); +DEFINE_QNODE(qhs_tlmm_1, SC7180_SLAVE_TLMM_WEST, 1, 4); +DEFINE_QNODE(qhs_tlmm_2, SC7180_SLAVE_TLMM_NORTH, 1, 4); +DEFINE_QNODE(qhs_tlmm_3, SC7180_SLAVE_TLMM_SOUTH, 1, 4); +DEFINE_QNODE(qhs_ufs_mem_cfg, SC7180_SLAVE_UFS_MEM_CFG, 1, 4); +DEFINE_QNODE(qhs_usb3, SC7180_SLAVE_USB3, 1, 4); +DEFINE_QNODE(qhs_venus_cfg, SC7180_SLAVE_VENUS_CFG, 1, 4); +DEFINE_QNODE(qhs_venus_throttle_cfg, SC7180_SLAVE_VENUS_THROTTLE_CFG, 1, 4); +DEFINE_QNODE(qhs_vsense_ctrl_cfg, SC7180_SLAVE_VSENSE_CTRL_CFG, 1, 4); +DEFINE_QNODE(srvc_cnoc, SC7180_SLAVE_SERVICE_CNOC, 1, 4); +DEFINE_QNODE(qhs_gemnoc, SC7180_SLAVE_GEM_NOC_CFG, 1, 4, SC7180_MASTER_GEM_NOC_CFG); +DEFINE_QNODE(qhs_llcc, SC7180_SLAVE_LLCC_CFG, 1, 4); +DEFINE_QNODE(qhs_mdsp_ms_mpu_cfg, SC7180_SLAVE_MSS_PROC_MS_MPU_CFG, 1, 4); +DEFINE_QNODE(qns_gem_noc_snoc, SC7180_SLAVE_GEM_NOC_SNOC, 1, 8, SC7180_MASTER_GEM_NOC_SNOC); +DEFINE_QNODE(qns_llcc, SC7180_SLAVE_LLCC, 1, 16, SC7180_MASTER_LLCC); +DEFINE_QNODE(srvc_gemnoc, SC7180_SLAVE_SERVICE_GEM_NOC, 1, 4); +DEFINE_QNODE(ipa_core_slave, SC7180_SLAVE_IPA_CORE, 1, 8); +DEFINE_QNODE(ebi, SC7180_SLAVE_EBI1, 2, 4); +DEFINE_QNODE(qns_mem_noc_hf, SC7180_SLAVE_MNOC_HF_MEM_NOC, 1, 32, SC7180_MASTER_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qns_mem_noc_sf, SC7180_SLAVE_MNOC_SF_MEM_NOC, 1, 32, SC7180_MASTER_MNOC_SF_MEM_NOC); +DEFINE_QNODE(srvc_mnoc, SC7180_SLAVE_SERVICE_MNOC, 1, 4); +DEFINE_QNODE(qhs_cal_dp0, SC7180_SLAVE_NPU_CAL_DP0, 1, 4); +DEFINE_QNODE(qhs_cp, SC7180_SLAVE_NPU_CP, 1, 4); +DEFINE_QNODE(qhs_dma_bwmon, SC7180_SLAVE_NPU_INT_DMA_BWMON_CFG, 1, 4); +DEFINE_QNODE(qhs_dpm, SC7180_SLAVE_NPU_DPM, 1, 4); +DEFINE_QNODE(qhs_isense, SC7180_SLAVE_ISENSE_CFG, 1, 4); +DEFINE_QNODE(qhs_llm, SC7180_SLAVE_NPU_LLM_CFG, 1, 4); +DEFINE_QNODE(qhs_tcm, SC7180_SLAVE_NPU_TCM, 1, 4); +DEFINE_QNODE(qns_npu_sys, SC7180_SLAVE_NPU_COMPUTE_NOC, 2, 32); +DEFINE_QNODE(srvc_noc, SC7180_SLAVE_SERVICE_NPU_NOC, 1, 4); +DEFINE_QNODE(qup_core_slave_1, SC7180_SLAVE_QUP_CORE_0, 1, 4); +DEFINE_QNODE(qup_core_slave_2, SC7180_SLAVE_QUP_CORE_1, 1, 4); +DEFINE_QNODE(qhs_apss, SC7180_SLAVE_APPSS, 1, 8); +DEFINE_QNODE(qns_cnoc, SC7180_SLAVE_SNOC_CNOC, 1, 8, SC7180_MASTER_SNOC_CNOC); +DEFINE_QNODE(qns_gemnoc_gc, SC7180_SLAVE_SNOC_GEM_NOC_GC, 1, 8, SC7180_MASTER_SNOC_GC_MEM_NOC); +DEFINE_QNODE(qns_gemnoc_sf, SC7180_SLAVE_SNOC_GEM_NOC_SF, 1, 16, SC7180_MASTER_SNOC_SF_MEM_NOC); +DEFINE_QNODE(qxs_imem, SC7180_SLAVE_IMEM, 1, 8); +DEFINE_QNODE(qxs_pimem, SC7180_SLAVE_PIMEM, 1, 8); +DEFINE_QNODE(srvc_snoc, SC7180_SLAVE_SERVICE_SNOC, 1, 4); +DEFINE_QNODE(xs_qdss_stm, SC7180_SLAVE_QDSS_STM, 1, 4); +DEFINE_QNODE(xs_sys_tcu_cfg, SC7180_SLAVE_TCU, 1, 8); + +DEFINE_QBCM(bcm_acv, "ACV", false, &ebi); +DEFINE_QBCM(bcm_mc0, "MC0", true, &ebi); +DEFINE_QBCM(bcm_sh0, "SH0", true, &qns_llcc); +DEFINE_QBCM(bcm_mm0, "MM0", false, &qns_mem_noc_hf); +DEFINE_QBCM(bcm_ce0, "CE0", false, &qxm_crypto); +DEFINE_QBCM(bcm_ip0, "IP0", false, &ipa_core_slave); +DEFINE_QBCM(bcm_cn0, "CN0", true, &qnm_snoc, &xm_qdss_dap, &qhs_a1_noc_cfg, &qhs_a2_noc_cfg, &qhs_ahb2phy0, &qhs_aop, &qhs_aoss, &qhs_boot_rom, &qhs_camera_cfg, &qhs_camera_nrt_throttle_cfg, &qhs_camera_rt_throttle_cfg, &qhs_clk_ctl, &qhs_cpr_cx, &qhs_cpr_mx, &qhs_crypto0_cfg, &qhs_dcc_cfg, &qhs_ddrss_cfg, &qhs_display_cfg, &qhs_display_rt_throttle_cfg, &qhs_display_throttle_cfg, &qhs_glm, &qhs_gpuss_cfg, &qhs_imem_cfg, &qhs_ipa, &qhs_mnoc_cfg, &qhs_mss_cfg, &qhs_npu_cfg, &qhs_npu_dma_throttle_cfg, &qhs_npu_dsp_throttle_cfg, &qhs_pimem_cfg, &qhs_prng, &qhs_qdss_cfg, &qhs_qm_cfg, &qhs_qm_mpu_cfg, &qhs_qup0, &qhs_qup1, &qhs_security, &qhs_snoc_cfg, &qhs_tcsr, &qhs_tlmm_1, &qhs_tlmm_2, &qhs_tlmm_3, &qhs_ufs_mem_cfg, &qhs_usb3, &qhs_venus_cfg, &qhs_venus_throttle_cfg, &qhs_vsense_ctrl_cfg, &srvc_cnoc); +DEFINE_QBCM(bcm_mm1, "MM1", false, &qxm_camnoc_hf0_uncomp, &qxm_camnoc_hf1_uncomp, &qxm_camnoc_sf_uncomp, &qhm_mnoc_cfg, &qxm_mdp0, &qxm_rot, &qxm_venus0, &qxm_venus_arm9); +DEFINE_QBCM(bcm_sh2, "SH2", false, &acm_sys_tcu); +DEFINE_QBCM(bcm_mm2, "MM2", false, &qns_mem_noc_sf); +DEFINE_QBCM(bcm_qup0, "QUP0", false, &qup_core_master_1, &qup_core_master_2); +DEFINE_QBCM(bcm_sh3, "SH3", false, &qnm_cmpnoc); +DEFINE_QBCM(bcm_sh4, "SH4", false, &acm_apps0); +DEFINE_QBCM(bcm_sn0, "SN0", true, &qns_gemnoc_sf); +DEFINE_QBCM(bcm_co0, "CO0", false, &qns_cdsp_gemnoc); +DEFINE_QBCM(bcm_sn1, "SN1", false, &qxs_imem); +DEFINE_QBCM(bcm_cn1, "CN1", false, &qhm_qspi, &xm_sdc2, &xm_emmc, &qhs_ahb2phy2, &qhs_emmc_cfg, &qhs_pdm, &qhs_qspi, &qhs_sdc2); +DEFINE_QBCM(bcm_sn2, "SN2", false, &qxm_pimem, &qns_gemnoc_gc); +DEFINE_QBCM(bcm_co2, "CO2", false, &qnm_npu); +DEFINE_QBCM(bcm_sn3, "SN3", false, &qxs_pimem); +DEFINE_QBCM(bcm_co3, "CO3", false, &qxm_npu_dsp); +DEFINE_QBCM(bcm_sn4, "SN4", false, &xs_qdss_stm); +DEFINE_QBCM(bcm_sn7, "SN7", false, &qnm_aggre1_noc); +DEFINE_QBCM(bcm_sn9, "SN9", false, &qnm_aggre2_noc); +DEFINE_QBCM(bcm_sn12, "SN12", false, &qnm_gemnoc); + +static struct qcom_icc_bcm *aggre1_noc_bcms[] = { + &bcm_cn1, +}; + +static struct qcom_icc_node *aggre1_noc_nodes[] = { + [MASTER_A1NOC_CFG] = &qhm_a1noc_cfg, + [MASTER_QSPI] = &qhm_qspi, + [MASTER_QUP_0] = &qhm_qup_0, + [MASTER_SDCC_2] = &xm_sdc2, + [MASTER_EMMC] = &xm_emmc, + [MASTER_UFS_MEM] = &xm_ufs_mem, + [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, + [SLAVE_SERVICE_A1NOC] = &srvc_aggre1_noc, +}; + +static struct qcom_icc_desc sc7180_aggre1_noc = { + .nodes = aggre1_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre1_noc_nodes), + .bcms = aggre1_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre1_noc_bcms), +}; + +static struct qcom_icc_bcm *aggre2_noc_bcms[] = { + &bcm_ce0, +}; + +static struct qcom_icc_node *aggre2_noc_nodes[] = { + [MASTER_A2NOC_CFG] = &qhm_a2noc_cfg, + [MASTER_QDSS_BAM] = &qhm_qdss_bam, + [MASTER_QUP_1] = &qhm_qup_1, + [MASTER_USB3] = &qhm_usb3, + [MASTER_CRYPTO] = &qxm_crypto, + [MASTER_IPA] = &qxm_ipa, + [MASTER_QDSS_ETR] = &xm_qdss_etr, + [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, + [SLAVE_SERVICE_A2NOC] = &srvc_aggre2_noc, +}; + +static struct qcom_icc_desc sc7180_aggre2_noc = { + .nodes = aggre2_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre2_noc_nodes), + .bcms = aggre2_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre2_noc_bcms), +}; + +static struct qcom_icc_bcm *camnoc_virt_bcms[] = { + &bcm_mm1, +}; + +static struct qcom_icc_node *camnoc_virt_nodes[] = { + [MASTER_CAMNOC_HF0_UNCOMP] = &qxm_camnoc_hf0_uncomp, + [MASTER_CAMNOC_HF1_UNCOMP] = &qxm_camnoc_hf1_uncomp, + [MASTER_CAMNOC_SF_UNCOMP] = &qxm_camnoc_sf_uncomp, + [SLAVE_CAMNOC_UNCOMP] = &qns_camnoc_uncomp, +}; + +static struct qcom_icc_desc sc7180_camnoc_virt = { + .nodes = camnoc_virt_nodes, + .num_nodes = ARRAY_SIZE(camnoc_virt_nodes), + .bcms = camnoc_virt_bcms, + .num_bcms = ARRAY_SIZE(camnoc_virt_bcms), +}; + +static struct qcom_icc_bcm *compute_noc_bcms[] = { + &bcm_co0, + &bcm_co2, + &bcm_co3, +}; + +static struct qcom_icc_node *compute_noc_nodes[] = { + [MASTER_NPU] = &qnm_npu, + [MASTER_NPU_PROC] = &qxm_npu_dsp, + [SLAVE_CDSP_GEM_NOC] = &qns_cdsp_gemnoc, +}; + +static struct qcom_icc_desc sc7180_compute_noc = { + .nodes = compute_noc_nodes, + .num_nodes = ARRAY_SIZE(compute_noc_nodes), + .bcms = compute_noc_bcms, + .num_bcms = ARRAY_SIZE(compute_noc_bcms), +}; + +static struct qcom_icc_bcm *config_noc_bcms[] = { + &bcm_cn0, + &bcm_cn1, +}; + +static struct qcom_icc_node *config_noc_nodes[] = { + [MASTER_SNOC_CNOC] = &qnm_snoc, + [MASTER_QDSS_DAP] = &xm_qdss_dap, + [SLAVE_A1NOC_CFG] = &qhs_a1_noc_cfg, + [SLAVE_A2NOC_CFG] = &qhs_a2_noc_cfg, + [SLAVE_AHB2PHY_SOUTH] = &qhs_ahb2phy0, + [SLAVE_AHB2PHY_CENTER] = &qhs_ahb2phy2, + [SLAVE_AOP] = &qhs_aop, + [SLAVE_AOSS] = &qhs_aoss, + [SLAVE_BOOT_ROM] = &qhs_boot_rom, + [SLAVE_CAMERA_CFG] = &qhs_camera_cfg, + [SLAVE_CAMERA_NRT_THROTTLE_CFG] = &qhs_camera_nrt_throttle_cfg, + [SLAVE_CAMERA_RT_THROTTLE_CFG] = &qhs_camera_rt_throttle_cfg, + [SLAVE_CLK_CTL] = &qhs_clk_ctl, + [SLAVE_RBCPR_CX_CFG] = &qhs_cpr_cx, + [SLAVE_RBCPR_MX_CFG] = &qhs_cpr_mx, + [SLAVE_CRYPTO_0_CFG] = &qhs_crypto0_cfg, + [SLAVE_DCC_CFG] = &qhs_dcc_cfg, + [SLAVE_CNOC_DDRSS] = &qhs_ddrss_cfg, + [SLAVE_DISPLAY_CFG] = &qhs_display_cfg, + [SLAVE_DISPLAY_RT_THROTTLE_CFG] = &qhs_display_rt_throttle_cfg, + [SLAVE_DISPLAY_THROTTLE_CFG] = &qhs_display_throttle_cfg, + [SLAVE_EMMC_CFG] = &qhs_emmc_cfg, + [SLAVE_GLM] = &qhs_glm, + [SLAVE_GFX3D_CFG] = &qhs_gpuss_cfg, + [SLAVE_IMEM_CFG] = &qhs_imem_cfg, + [SLAVE_IPA_CFG] = &qhs_ipa, + [SLAVE_CNOC_MNOC_CFG] = &qhs_mnoc_cfg, + [SLAVE_CNOC_MSS] = &qhs_mss_cfg, + [SLAVE_NPU_CFG] = &qhs_npu_cfg, + [SLAVE_NPU_DMA_BWMON_CFG] = &qhs_npu_dma_throttle_cfg, + [SLAVE_NPU_PROC_BWMON_CFG] = &qhs_npu_dsp_throttle_cfg, + [SLAVE_PDM] = &qhs_pdm, + [SLAVE_PIMEM_CFG] = &qhs_pimem_cfg, + [SLAVE_PRNG] = &qhs_prng, + [SLAVE_QDSS_CFG] = &qhs_qdss_cfg, + [SLAVE_QM_CFG] = &qhs_qm_cfg, + [SLAVE_QM_MPU_CFG] = &qhs_qm_mpu_cfg, + [SLAVE_QSPI_0] = &qhs_qspi, + [SLAVE_QUP_0] = &qhs_qup0, + [SLAVE_QUP_1] = &qhs_qup1, + [SLAVE_SDCC_2] = &qhs_sdc2, + [SLAVE_SECURITY] = &qhs_security, + [SLAVE_SNOC_CFG] = &qhs_snoc_cfg, + [SLAVE_TCSR] = &qhs_tcsr, + [SLAVE_TLMM_WEST] = &qhs_tlmm_1, + [SLAVE_TLMM_NORTH] = &qhs_tlmm_2, + [SLAVE_TLMM_SOUTH] = &qhs_tlmm_3, + [SLAVE_UFS_MEM_CFG] = &qhs_ufs_mem_cfg, + [SLAVE_USB3] = &qhs_usb3, + [SLAVE_VENUS_CFG] = &qhs_venus_cfg, + [SLAVE_VENUS_THROTTLE_CFG] = &qhs_venus_throttle_cfg, + [SLAVE_VSENSE_CTRL_CFG] = &qhs_vsense_ctrl_cfg, + [SLAVE_SERVICE_CNOC] = &srvc_cnoc, +}; + +static struct qcom_icc_desc sc7180_config_noc = { + .nodes = config_noc_nodes, + .num_nodes = ARRAY_SIZE(config_noc_nodes), + .bcms = config_noc_bcms, + .num_bcms = ARRAY_SIZE(config_noc_bcms), +}; + +static struct qcom_icc_node *dc_noc_nodes[] = { + [MASTER_CNOC_DC_NOC] = &qhm_cnoc_dc_noc, + [SLAVE_GEM_NOC_CFG] = &qhs_gemnoc, + [SLAVE_LLCC_CFG] = &qhs_llcc, +}; + +static struct qcom_icc_desc sc7180_dc_noc = { + .nodes = dc_noc_nodes, + .num_nodes = ARRAY_SIZE(dc_noc_nodes), +}; + +static struct qcom_icc_bcm *gem_noc_bcms[] = { + &bcm_sh0, + &bcm_sh2, + &bcm_sh3, + &bcm_sh4, +}; + +static struct qcom_icc_node *gem_noc_nodes[] = { + [MASTER_APPSS_PROC] = &acm_apps0, + [MASTER_SYS_TCU] = &acm_sys_tcu, + [MASTER_GEM_NOC_CFG] = &qhm_gemnoc_cfg, + [MASTER_COMPUTE_NOC] = &qnm_cmpnoc, + [MASTER_MNOC_HF_MEM_NOC] = &qnm_mnoc_hf, + [MASTER_MNOC_SF_MEM_NOC] = &qnm_mnoc_sf, + [MASTER_SNOC_GC_MEM_NOC] = &qnm_snoc_gc, + [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf, + [MASTER_GFX3D] = &qxm_gpu, + [SLAVE_MSS_PROC_MS_MPU_CFG] = &qhs_mdsp_ms_mpu_cfg, + [SLAVE_GEM_NOC_SNOC] = &qns_gem_noc_snoc, + [SLAVE_LLCC] = &qns_llcc, + [SLAVE_SERVICE_GEM_NOC] = &srvc_gemnoc, +}; + +static struct qcom_icc_desc sc7180_gem_noc = { + .nodes = gem_noc_nodes, + .num_nodes = ARRAY_SIZE(gem_noc_nodes), + .bcms = gem_noc_bcms, + .num_bcms = ARRAY_SIZE(gem_noc_bcms), +}; + +static struct qcom_icc_bcm *ipa_virt_bcms[] = { + &bcm_ip0, +}; + +static struct qcom_icc_node *ipa_virt_nodes[] = { + [MASTER_IPA_CORE] = &ipa_core_master, + [SLAVE_IPA_CORE] = &ipa_core_slave, +}; + +static struct qcom_icc_desc sc7180_ipa_virt = { + .nodes = ipa_virt_nodes, + .num_nodes = ARRAY_SIZE(ipa_virt_nodes), + .bcms = ipa_virt_bcms, + .num_bcms = ARRAY_SIZE(ipa_virt_bcms), +}; + +static struct qcom_icc_bcm *mc_virt_bcms[] = { + &bcm_acv, + &bcm_mc0, +}; + +static struct qcom_icc_node *mc_virt_nodes[] = { + [MASTER_LLCC] = &llcc_mc, + [SLAVE_EBI1] = &ebi, +}; + +static struct qcom_icc_desc sc7180_mc_virt = { + .nodes = mc_virt_nodes, + .num_nodes = ARRAY_SIZE(mc_virt_nodes), + .bcms = mc_virt_bcms, + .num_bcms = ARRAY_SIZE(mc_virt_bcms), +}; + +static struct qcom_icc_bcm *mmss_noc_bcms[] = { + &bcm_mm0, + &bcm_mm1, + &bcm_mm2, +}; + +static struct qcom_icc_node *mmss_noc_nodes[] = { + [MASTER_CNOC_MNOC_CFG] = &qhm_mnoc_cfg, + [MASTER_CAMNOC_HF0] = &qxm_camnoc_hf0, + [MASTER_CAMNOC_HF1] = &qxm_camnoc_hf1, + [MASTER_CAMNOC_SF] = &qxm_camnoc_sf, + [MASTER_MDP0] = &qxm_mdp0, + [MASTER_ROTATOR] = &qxm_rot, + [MASTER_VIDEO_P0] = &qxm_venus0, + [MASTER_VIDEO_PROC] = &qxm_venus_arm9, + [SLAVE_MNOC_HF_MEM_NOC] = &qns_mem_noc_hf, + [SLAVE_MNOC_SF_MEM_NOC] = &qns_mem_noc_sf, + [SLAVE_SERVICE_MNOC] = &srvc_mnoc, +}; + +static struct qcom_icc_desc sc7180_mmss_noc = { + .nodes = mmss_noc_nodes, + .num_nodes = ARRAY_SIZE(mmss_noc_nodes), + .bcms = mmss_noc_bcms, + .num_bcms = ARRAY_SIZE(mmss_noc_bcms), +}; + +static struct qcom_icc_node *npu_noc_nodes[] = { + [MASTER_NPU_SYS] = &amm_npu_sys, + [MASTER_NPU_NOC_CFG] = &qhm_npu_cfg, + [SLAVE_NPU_CAL_DP0] = &qhs_cal_dp0, + [SLAVE_NPU_CP] = &qhs_cp, + [SLAVE_NPU_INT_DMA_BWMON_CFG] = &qhs_dma_bwmon, + [SLAVE_NPU_DPM] = &qhs_dpm, + [SLAVE_ISENSE_CFG] = &qhs_isense, + [SLAVE_NPU_LLM_CFG] = &qhs_llm, + [SLAVE_NPU_TCM] = &qhs_tcm, + [SLAVE_NPU_COMPUTE_NOC] = &qns_npu_sys, + [SLAVE_SERVICE_NPU_NOC] = &srvc_noc, +}; + +static struct qcom_icc_desc sc7180_npu_noc = { + .nodes = npu_noc_nodes, + .num_nodes = ARRAY_SIZE(npu_noc_nodes), +}; + +static struct qcom_icc_bcm *qup_virt_bcms[] = { + &bcm_qup0, +}; + +static struct qcom_icc_node *qup_virt_nodes[] = { + [MASTER_QUP_CORE_0] = &qup_core_master_1, + [MASTER_QUP_CORE_1] = &qup_core_master_2, + [SLAVE_QUP_CORE_0] = &qup_core_slave_1, + [SLAVE_QUP_CORE_1] = &qup_core_slave_2, +}; + +static struct qcom_icc_desc sc7180_qup_virt = { + .nodes = qup_virt_nodes, + .num_nodes = ARRAY_SIZE(qup_virt_nodes), + .bcms = qup_virt_bcms, + .num_bcms = ARRAY_SIZE(qup_virt_bcms), +}; + +static struct qcom_icc_bcm *system_noc_bcms[] = { + &bcm_sn0, + &bcm_sn1, + &bcm_sn2, + &bcm_sn3, + &bcm_sn4, + &bcm_sn7, + &bcm_sn9, + &bcm_sn12, +}; + +static struct qcom_icc_node *system_noc_nodes[] = { + [MASTER_SNOC_CFG] = &qhm_snoc_cfg, + [MASTER_A1NOC_SNOC] = &qnm_aggre1_noc, + [MASTER_A2NOC_SNOC] = &qnm_aggre2_noc, + [MASTER_GEM_NOC_SNOC] = &qnm_gemnoc, + [MASTER_PIMEM] = &qxm_pimem, + [SLAVE_APPSS] = &qhs_apss, + [SLAVE_SNOC_CNOC] = &qns_cnoc, + [SLAVE_SNOC_GEM_NOC_GC] = &qns_gemnoc_gc, + [SLAVE_SNOC_GEM_NOC_SF] = &qns_gemnoc_sf, + [SLAVE_IMEM] = &qxs_imem, + [SLAVE_PIMEM] = &qxs_pimem, + [SLAVE_SERVICE_SNOC] = &srvc_snoc, + [SLAVE_QDSS_STM] = &xs_qdss_stm, + [SLAVE_TCU] = &xs_sys_tcu_cfg, +}; + +static struct qcom_icc_desc sc7180_system_noc = { + .nodes = system_noc_nodes, + .num_nodes = ARRAY_SIZE(system_noc_nodes), + .bcms = system_noc_bcms, + .num_bcms = ARRAY_SIZE(system_noc_bcms), +}; + +static int qnoc_probe(struct platform_device *pdev) +{ + const struct qcom_icc_desc *desc; + struct icc_onecell_data *data; + struct icc_provider *provider; + struct qcom_icc_node **qnodes; + struct qcom_icc_provider *qp; + struct icc_node *node; + size_t num_nodes, i; + int ret; + + desc = device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + qnodes = desc->nodes; + num_nodes = desc->num_nodes; + + qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL); + if (!qp) + return -ENOMEM; + + data = devm_kcalloc(&pdev->dev, num_nodes, sizeof(*node), GFP_KERNEL); + if (!data) + return -ENOMEM; + + provider = &qp->provider; + provider->dev = &pdev->dev; + provider->set = qcom_icc_set; + provider->pre_aggregate = qcom_icc_pre_aggregate; + provider->aggregate = qcom_icc_aggregate; + provider->xlate = of_icc_xlate_onecell; + INIT_LIST_HEAD(&provider->nodes); + provider->data = data; + + qp->dev = &pdev->dev; + qp->bcms = desc->bcms; + qp->num_bcms = desc->num_bcms; + + qp->voter = of_bcm_voter_get(qp->dev, NULL); + if (IS_ERR(qp->voter)) + return PTR_ERR(qp->voter); + + ret = icc_provider_add(provider); + if (ret) { + dev_err(&pdev->dev, "error adding interconnect provider\n"); + return ret; + } + + for (i = 0; i < num_nodes; i++) { + size_t j; + + if (!qnodes[i]) + continue; + + node = icc_node_create(qnodes[i]->id); + if (IS_ERR(node)) { + ret = PTR_ERR(node); + goto err; + } + + node->name = qnodes[i]->name; + node->data = qnodes[i]; + icc_node_add(node, provider); + + for (j = 0; j < qnodes[i]->num_links; j++) + icc_link_create(node, qnodes[i]->links[j]); + + data->nodes[i] = node; + } + data->num_nodes = num_nodes; + + for (i = 0; i < qp->num_bcms; i++) + qcom_icc_bcm_init(qp->bcms[i], &pdev->dev); + + platform_set_drvdata(pdev, qp); + + return 0; +err: + icc_nodes_remove(provider); + icc_provider_del(provider); + return ret; +} + +static int qnoc_remove(struct platform_device *pdev) +{ + struct qcom_icc_provider *qp = platform_get_drvdata(pdev); + + icc_nodes_remove(&qp->provider); + return icc_provider_del(&qp->provider); +} + +static const struct of_device_id qnoc_of_match[] = { + { .compatible = "qcom,sc7180-aggre1-noc", + .data = &sc7180_aggre1_noc}, + { .compatible = "qcom,sc7180-aggre2-noc", + .data = &sc7180_aggre2_noc}, + { .compatible = "qcom,sc7180-camnoc-virt", + .data = &sc7180_camnoc_virt}, + { .compatible = "qcom,sc7180-compute-noc", + .data = &sc7180_compute_noc}, + { .compatible = "qcom,sc7180-config-noc", + .data = &sc7180_config_noc}, + { .compatible = "qcom,sc7180-dc-noc", + .data = &sc7180_dc_noc}, + { .compatible = "qcom,sc7180-gem-noc", + .data = &sc7180_gem_noc}, + { .compatible = "qcom,sc7180-ipa-virt", + .data = &sc7180_ipa_virt}, + { .compatible = "qcom,sc7180-mc-virt", + .data = &sc7180_mc_virt}, + { .compatible = "qcom,sc7180-mmss-noc", + .data = &sc7180_mmss_noc}, + { .compatible = "qcom,sc7180-npu-noc", + .data = &sc7180_npu_noc}, + { .compatible = "qcom,sc7180-qup-virt", + .data = &sc7180_qup_virt}, + { .compatible = "qcom,sc7180-system-noc", + .data = &sc7180_system_noc}, + { } +}; +MODULE_DEVICE_TABLE(of, qnoc_of_match); + +static struct platform_driver qnoc_driver = { + .probe = qnoc_probe, + .remove = qnoc_remove, + .driver = { + .name = "qnoc-sc7180", + .of_match_table = qnoc_of_match, + }, +}; +module_platform_driver(qnoc_driver); + +MODULE_DESCRIPTION("Qualcomm SC7180 NoC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/interconnect/qcom/sc7180.h b/drivers/interconnect/qcom/sc7180.h new file mode 100644 index 000000000000..c6212a10c2f6 --- /dev/null +++ b/drivers/interconnect/qcom/sc7180.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Qualcomm #define SC7180 interconnect IDs + * + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __DRIVERS_INTERCONNECT_QCOM_SC7180_H +#define __DRIVERS_INTERCONNECT_QCOM_SC7180_H + +#define SC7180_MASTER_APPSS_PROC 0 +#define SC7180_MASTER_SYS_TCU 1 +#define SC7180_MASTER_NPU_SYS 2 +#define SC7180_MASTER_IPA_CORE 3 +#define SC7180_MASTER_LLCC 4 +#define SC7180_MASTER_A1NOC_CFG 5 +#define SC7180_MASTER_A2NOC_CFG 6 +#define SC7180_MASTER_CNOC_DC_NOC 7 +#define SC7180_MASTER_GEM_NOC_CFG 8 +#define SC7180_MASTER_CNOC_MNOC_CFG 9 +#define SC7180_MASTER_NPU_NOC_CFG 10 +#define SC7180_MASTER_QDSS_BAM 11 +#define SC7180_MASTER_QSPI 12 +#define SC7180_MASTER_QUP_0 13 +#define SC7180_MASTER_QUP_1 14 +#define SC7180_MASTER_SNOC_CFG 15 +#define SC7180_MASTER_A1NOC_SNOC 16 +#define SC7180_MASTER_A2NOC_SNOC 17 +#define SC7180_MASTER_COMPUTE_NOC 18 +#define SC7180_MASTER_GEM_NOC_SNOC 19 +#define SC7180_MASTER_MNOC_HF_MEM_NOC 20 +#define SC7180_MASTER_MNOC_SF_MEM_NOC 21 +#define SC7180_MASTER_NPU 22 +#define SC7180_MASTER_SNOC_CNOC 23 +#define SC7180_MASTER_SNOC_GC_MEM_NOC 24 +#define SC7180_MASTER_SNOC_SF_MEM_NOC 25 +#define SC7180_MASTER_QUP_CORE_0 26 +#define SC7180_MASTER_QUP_CORE_1 27 +#define SC7180_MASTER_CAMNOC_HF0 28 +#define SC7180_MASTER_CAMNOC_HF1 29 +#define SC7180_MASTER_CAMNOC_HF0_UNCOMP 30 +#define SC7180_MASTER_CAMNOC_HF1_UNCOMP 31 +#define SC7180_MASTER_CAMNOC_SF 32 +#define SC7180_MASTER_CAMNOC_SF_UNCOMP 33 +#define SC7180_MASTER_CRYPTO 34 +#define SC7180_MASTER_GFX3D 35 +#define SC7180_MASTER_IPA 36 +#define SC7180_MASTER_MDP0 37 +#define SC7180_MASTER_NPU_PROC 38 +#define SC7180_MASTER_PIMEM 39 +#define SC7180_MASTER_ROTATOR 40 +#define SC7180_MASTER_VIDEO_P0 41 +#define SC7180_MASTER_VIDEO_PROC 42 +#define SC7180_MASTER_QDSS_DAP 43 +#define SC7180_MASTER_QDSS_ETR 44 +#define SC7180_MASTER_SDCC_2 45 +#define SC7180_MASTER_UFS_MEM 46 +#define SC7180_MASTER_USB3 47 +#define SC7180_MASTER_EMMC 48 +#define SC7180_SLAVE_EBI1 49 +#define SC7180_SLAVE_IPA_CORE 50 +#define SC7180_SLAVE_A1NOC_CFG 51 +#define SC7180_SLAVE_A2NOC_CFG 52 +#define SC7180_SLAVE_AHB2PHY_SOUTH 53 +#define SC7180_SLAVE_AHB2PHY_CENTER 54 +#define SC7180_SLAVE_AOP 55 +#define SC7180_SLAVE_AOSS 56 +#define SC7180_SLAVE_APPSS 57 +#define SC7180_SLAVE_BOOT_ROM 58 +#define SC7180_SLAVE_NPU_CAL_DP0 59 +#define SC7180_SLAVE_CAMERA_CFG 60 +#define SC7180_SLAVE_CAMERA_NRT_THROTTLE_CFG 61 +#define SC7180_SLAVE_CAMERA_RT_THROTTLE_CFG 62 +#define SC7180_SLAVE_CLK_CTL 63 +#define SC7180_SLAVE_NPU_CP 64 +#define SC7180_SLAVE_RBCPR_CX_CFG 65 +#define SC7180_SLAVE_RBCPR_MX_CFG 66 +#define SC7180_SLAVE_CRYPTO_0_CFG 67 +#define SC7180_SLAVE_DCC_CFG 68 +#define SC7180_SLAVE_CNOC_DDRSS 69 +#define SC7180_SLAVE_DISPLAY_CFG 70 +#define SC7180_SLAVE_DISPLAY_RT_THROTTLE_CFG 71 +#define SC7180_SLAVE_DISPLAY_THROTTLE_CFG 72 +#define SC7180_SLAVE_NPU_INT_DMA_BWMON_CFG 73 +#define SC7180_SLAVE_NPU_DPM 74 +#define SC7180_SLAVE_EMMC_CFG 75 +#define SC7180_SLAVE_GEM_NOC_CFG 76 +#define SC7180_SLAVE_GLM 77 +#define SC7180_SLAVE_GFX3D_CFG 78 +#define SC7180_SLAVE_IMEM_CFG 79 +#define SC7180_SLAVE_IPA_CFG 80 +#define SC7180_SLAVE_ISENSE_CFG 81 +#define SC7180_SLAVE_LLCC_CFG 82 +#define SC7180_SLAVE_NPU_LLM_CFG 83 +#define SC7180_SLAVE_MSS_PROC_MS_MPU_CFG 84 +#define SC7180_SLAVE_CNOC_MNOC_CFG 85 +#define SC7180_SLAVE_CNOC_MSS 86 +#define SC7180_SLAVE_NPU_CFG 87 +#define SC7180_SLAVE_NPU_DMA_BWMON_CFG 88 +#define SC7180_SLAVE_NPU_PROC_BWMON_CFG 89 +#define SC7180_SLAVE_PDM 90 +#define SC7180_SLAVE_PIMEM_CFG 91 +#define SC7180_SLAVE_PRNG 92 +#define SC7180_SLAVE_QDSS_CFG 93 +#define SC7180_SLAVE_QM_CFG 94 +#define SC7180_SLAVE_QM_MPU_CFG 95 +#define SC7180_SLAVE_QSPI_0 96 +#define SC7180_SLAVE_QUP_0 97 +#define SC7180_SLAVE_QUP_1 98 +#define SC7180_SLAVE_SDCC_2 99 +#define SC7180_SLAVE_SECURITY 100 +#define SC7180_SLAVE_SNOC_CFG 101 +#define SC7180_SLAVE_NPU_TCM 102 +#define SC7180_SLAVE_TCSR 103 +#define SC7180_SLAVE_TLMM_WEST 104 +#define SC7180_SLAVE_TLMM_NORTH 105 +#define SC7180_SLAVE_TLMM_SOUTH 106 +#define SC7180_SLAVE_UFS_MEM_CFG 107 +#define SC7180_SLAVE_USB3 108 +#define SC7180_SLAVE_VENUS_CFG 109 +#define SC7180_SLAVE_VENUS_THROTTLE_CFG 110 +#define SC7180_SLAVE_VSENSE_CTRL_CFG 111 +#define SC7180_SLAVE_A1NOC_SNOC 112 +#define SC7180_SLAVE_A2NOC_SNOC 113 +#define SC7180_SLAVE_CAMNOC_UNCOMP 114 +#define SC7180_SLAVE_CDSP_GEM_NOC 115 +#define SC7180_SLAVE_SNOC_CNOC 116 +#define SC7180_SLAVE_GEM_NOC_SNOC 117 +#define SC7180_SLAVE_SNOC_GEM_NOC_GC 118 +#define SC7180_SLAVE_SNOC_GEM_NOC_SF 119 +#define SC7180_SLAVE_LLCC 120 +#define SC7180_SLAVE_MNOC_HF_MEM_NOC 121 +#define SC7180_SLAVE_MNOC_SF_MEM_NOC 122 +#define SC7180_SLAVE_NPU_COMPUTE_NOC 123 +#define SC7180_SLAVE_QUP_CORE_0 124 +#define SC7180_SLAVE_QUP_CORE_1 125 +#define SC7180_SLAVE_IMEM 126 +#define SC7180_SLAVE_PIMEM 127 +#define SC7180_SLAVE_SERVICE_A1NOC 128 +#define SC7180_SLAVE_SERVICE_A2NOC 129 +#define SC7180_SLAVE_SERVICE_CNOC 130 +#define SC7180_SLAVE_SERVICE_GEM_NOC 131 +#define SC7180_SLAVE_SERVICE_MNOC 132 +#define SC7180_SLAVE_SERVICE_NPU_NOC 133 +#define SC7180_SLAVE_SERVICE_SNOC 134 +#define SC7180_SLAVE_QDSS_STM 135 +#define SC7180_SLAVE_TCU 136 +#define SC7180_MASTER_OSM_L3_APPS 137 +#define SC7180_SLAVE_OSM_L3 138 + +#endif diff --git a/drivers/interconnect/qcom/sdm845.c b/drivers/interconnect/qcom/sdm845.c index f078cf0fce56..b013b80caa45 100644 --- a/drivers/interconnect/qcom/sdm845.c +++ b/drivers/interconnect/qcom/sdm845.c @@ -1,379 +1,245 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. - * + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. */ -#include <asm/div64.h> -#include <dt-bindings/interconnect/qcom,sdm845.h> #include <linux/device.h> #include <linux/interconnect.h> #include <linux/interconnect-provider.h> -#include <linux/io.h> #include <linux/module.h> #include <linux/of_device.h> -#include <linux/of_platform.h> -#include <linux/platform_device.h> -#include <linux/sort.h> - -#include <soc/qcom/cmd-db.h> -#include <soc/qcom/rpmh.h> -#include <soc/qcom/tcs.h> - -#define to_qcom_provider(_provider) \ - container_of(_provider, struct qcom_icc_provider, provider) - -struct qcom_icc_provider { - struct icc_provider provider; - struct device *dev; - struct qcom_icc_bcm **bcms; - size_t num_bcms; -}; - -/** - * struct bcm_db - Auxiliary data pertaining to each Bus Clock Manager (BCM) - * @unit: divisor used to convert bytes/sec bw value to an RPMh msg - * @width: multiplier used to convert bytes/sec bw value to an RPMh msg - * @vcd: virtual clock domain that this bcm belongs to - * @reserved: reserved field - */ -struct bcm_db { - __le32 unit; - __le16 width; - u8 vcd; - u8 reserved; -}; -#define SDM845_MAX_LINKS 43 -#define SDM845_MAX_BCMS 30 -#define SDM845_MAX_BCM_PER_NODE 2 -#define SDM845_MAX_VCD 10 +#include <dt-bindings/interconnect/qcom,sdm845.h> -/* - * The AMC bucket denotes constraints that are applied to hardware when - * icc_set_bw() completes, whereas the WAKE and SLEEP constraints are applied - * when the execution environment transitions between active and low power mode. - */ -#define QCOM_ICC_BUCKET_AMC 0 -#define QCOM_ICC_BUCKET_WAKE 1 -#define QCOM_ICC_BUCKET_SLEEP 2 -#define QCOM_ICC_NUM_BUCKETS 3 -#define QCOM_ICC_TAG_AMC BIT(QCOM_ICC_BUCKET_AMC) -#define QCOM_ICC_TAG_WAKE BIT(QCOM_ICC_BUCKET_WAKE) -#define QCOM_ICC_TAG_SLEEP BIT(QCOM_ICC_BUCKET_SLEEP) -#define QCOM_ICC_TAG_ACTIVE_ONLY (QCOM_ICC_TAG_AMC | QCOM_ICC_TAG_WAKE) -#define QCOM_ICC_TAG_ALWAYS (QCOM_ICC_TAG_AMC | QCOM_ICC_TAG_WAKE |\ - QCOM_ICC_TAG_SLEEP) - -/** - * struct qcom_icc_node - Qualcomm specific interconnect nodes - * @name: the node name used in debugfs - * @links: an array of nodes where we can go next while traversing - * @id: a unique node identifier - * @num_links: the total number of @links - * @channels: num of channels at this node - * @buswidth: width of the interconnect between a node and the bus - * @sum_avg: current sum aggregate value of all avg bw requests - * @max_peak: current max aggregate value of all peak bw requests - * @bcms: list of bcms associated with this logical node - * @num_bcms: num of @bcms - */ -struct qcom_icc_node { - const char *name; - u16 links[SDM845_MAX_LINKS]; - u16 id; - u16 num_links; - u16 channels; - u16 buswidth; - u64 sum_avg[QCOM_ICC_NUM_BUCKETS]; - u64 max_peak[QCOM_ICC_NUM_BUCKETS]; - struct qcom_icc_bcm *bcms[SDM845_MAX_BCM_PER_NODE]; - size_t num_bcms; +#include "bcm-voter.h" +#include "icc-rpmh.h" +#include "sdm845.h" + +DEFINE_QNODE(qhm_a1noc_cfg, SDM845_MASTER_A1NOC_CFG, 1, 4, SDM845_SLAVE_SERVICE_A1NOC); +DEFINE_QNODE(qhm_qup1, SDM845_MASTER_BLSP_1, 1, 4, SDM845_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(qhm_tsif, SDM845_MASTER_TSIF, 1, 4, SDM845_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_sdc2, SDM845_MASTER_SDCC_2, 1, 8, SDM845_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_sdc4, SDM845_MASTER_SDCC_4, 1, 8, SDM845_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_ufs_card, SDM845_MASTER_UFS_CARD, 1, 8, SDM845_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_ufs_mem, SDM845_MASTER_UFS_MEM, 1, 8, SDM845_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(xm_pcie_0, SDM845_MASTER_PCIE_0, 1, 8, SDM845_SLAVE_ANOC_PCIE_A1NOC_SNOC); +DEFINE_QNODE(qhm_a2noc_cfg, SDM845_MASTER_A2NOC_CFG, 1, 4, SDM845_SLAVE_SERVICE_A2NOC); +DEFINE_QNODE(qhm_qdss_bam, SDM845_MASTER_QDSS_BAM, 1, 4, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qhm_qup2, SDM845_MASTER_BLSP_2, 1, 4, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qnm_cnoc, SDM845_MASTER_CNOC_A2NOC, 1, 8, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qxm_crypto, SDM845_MASTER_CRYPTO, 1, 8, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qxm_ipa, SDM845_MASTER_IPA, 1, 8, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(xm_pcie3_1, SDM845_MASTER_PCIE_1, 1, 8, SDM845_SLAVE_ANOC_PCIE_SNOC); +DEFINE_QNODE(xm_qdss_etr, SDM845_MASTER_QDSS_ETR, 1, 8, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(xm_usb3_0, SDM845_MASTER_USB3_0, 1, 8, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(xm_usb3_1, SDM845_MASTER_USB3_1, 1, 8, SDM845_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(qxm_camnoc_hf0_uncomp, SDM845_MASTER_CAMNOC_HF0_UNCOMP, 1, 32, SDM845_SLAVE_CAMNOC_UNCOMP); +DEFINE_QNODE(qxm_camnoc_hf1_uncomp, SDM845_MASTER_CAMNOC_HF1_UNCOMP, 1, 32, SDM845_SLAVE_CAMNOC_UNCOMP); +DEFINE_QNODE(qxm_camnoc_sf_uncomp, SDM845_MASTER_CAMNOC_SF_UNCOMP, 1, 32, SDM845_SLAVE_CAMNOC_UNCOMP); +DEFINE_QNODE(qhm_spdm, SDM845_MASTER_SPDM, 1, 4, SDM845_SLAVE_CNOC_A2NOC); +DEFINE_QNODE(qhm_tic, SDM845_MASTER_TIC, 1, 4, SDM845_SLAVE_A1NOC_CFG, SDM845_SLAVE_A2NOC_CFG, SDM845_SLAVE_AOP, SDM845_SLAVE_AOSS, SDM845_SLAVE_CAMERA_CFG, SDM845_SLAVE_CLK_CTL, SDM845_SLAVE_CDSP_CFG, SDM845_SLAVE_RBCPR_CX_CFG, SDM845_SLAVE_CRYPTO_0_CFG, SDM845_SLAVE_DCC_CFG, SDM845_SLAVE_CNOC_DDRSS, SDM845_SLAVE_DISPLAY_CFG, SDM845_SLAVE_GLM, SDM845_SLAVE_GFX3D_CFG, SDM845_SLAVE_IMEM_CFG, SDM845_SLAVE_IPA_CFG, SDM845_SLAVE_CNOC_MNOC_CFG, SDM845_SLAVE_PCIE_0_CFG, SDM845_SLAVE_PCIE_1_CFG, SDM845_SLAVE_PDM, SDM845_SLAVE_SOUTH_PHY_CFG, SDM845_SLAVE_PIMEM_CFG, SDM845_SLAVE_PRNG, SDM845_SLAVE_QDSS_CFG, SDM845_SLAVE_BLSP_2, SDM845_SLAVE_BLSP_1, SDM845_SLAVE_SDCC_2, SDM845_SLAVE_SDCC_4, SDM845_SLAVE_SNOC_CFG, SDM845_SLAVE_SPDM_WRAPPER, SDM845_SLAVE_SPSS_CFG, SDM845_SLAVE_TCSR, SDM845_SLAVE_TLMM_NORTH, SDM845_SLAVE_TLMM_SOUTH, SDM845_SLAVE_TSIF, SDM845_SLAVE_UFS_CARD_CFG, SDM845_SLAVE_UFS_MEM_CFG, SDM845_SLAVE_USB3_0, SDM845_SLAVE_USB3_1, SDM845_SLAVE_VENUS_CFG, SDM845_SLAVE_VSENSE_CTRL_CFG, SDM845_SLAVE_CNOC_A2NOC, SDM845_SLAVE_SERVICE_CNOC); +DEFINE_QNODE(qnm_snoc, SDM845_MASTER_SNOC_CNOC, 1, 8, SDM845_SLAVE_A1NOC_CFG, SDM845_SLAVE_A2NOC_CFG, SDM845_SLAVE_AOP, SDM845_SLAVE_AOSS, SDM845_SLAVE_CAMERA_CFG, SDM845_SLAVE_CLK_CTL, SDM845_SLAVE_CDSP_CFG, SDM845_SLAVE_RBCPR_CX_CFG, SDM845_SLAVE_CRYPTO_0_CFG, SDM845_SLAVE_DCC_CFG, SDM845_SLAVE_CNOC_DDRSS, SDM845_SLAVE_DISPLAY_CFG, SDM845_SLAVE_GLM, SDM845_SLAVE_GFX3D_CFG, SDM845_SLAVE_IMEM_CFG, SDM845_SLAVE_IPA_CFG, SDM845_SLAVE_CNOC_MNOC_CFG, SDM845_SLAVE_PCIE_0_CFG, SDM845_SLAVE_PCIE_1_CFG, SDM845_SLAVE_PDM, SDM845_SLAVE_SOUTH_PHY_CFG, SDM845_SLAVE_PIMEM_CFG, SDM845_SLAVE_PRNG, SDM845_SLAVE_QDSS_CFG, SDM845_SLAVE_BLSP_2, SDM845_SLAVE_BLSP_1, SDM845_SLAVE_SDCC_2, SDM845_SLAVE_SDCC_4, SDM845_SLAVE_SNOC_CFG, SDM845_SLAVE_SPDM_WRAPPER, SDM845_SLAVE_SPSS_CFG, SDM845_SLAVE_TCSR, SDM845_SLAVE_TLMM_NORTH, SDM845_SLAVE_TLMM_SOUTH, SDM845_SLAVE_TSIF, SDM845_SLAVE_UFS_CARD_CFG, SDM845_SLAVE_UFS_MEM_CFG, SDM845_SLAVE_USB3_0, SDM845_SLAVE_USB3_1, SDM845_SLAVE_VENUS_CFG, SDM845_SLAVE_VSENSE_CTRL_CFG, SDM845_SLAVE_SERVICE_CNOC); +DEFINE_QNODE(xm_qdss_dap, SDM845_MASTER_QDSS_DAP, 1, 8, SDM845_SLAVE_A1NOC_CFG, SDM845_SLAVE_A2NOC_CFG, SDM845_SLAVE_AOP, SDM845_SLAVE_AOSS, SDM845_SLAVE_CAMERA_CFG, SDM845_SLAVE_CLK_CTL, SDM845_SLAVE_CDSP_CFG, SDM845_SLAVE_RBCPR_CX_CFG, SDM845_SLAVE_CRYPTO_0_CFG, SDM845_SLAVE_DCC_CFG, SDM845_SLAVE_CNOC_DDRSS, SDM845_SLAVE_DISPLAY_CFG, SDM845_SLAVE_GLM, SDM845_SLAVE_GFX3D_CFG, SDM845_SLAVE_IMEM_CFG, SDM845_SLAVE_IPA_CFG, SDM845_SLAVE_CNOC_MNOC_CFG, SDM845_SLAVE_PCIE_0_CFG, SDM845_SLAVE_PCIE_1_CFG, SDM845_SLAVE_PDM, SDM845_SLAVE_SOUTH_PHY_CFG, SDM845_SLAVE_PIMEM_CFG, SDM845_SLAVE_PRNG, SDM845_SLAVE_QDSS_CFG, SDM845_SLAVE_BLSP_2, SDM845_SLAVE_BLSP_1, SDM845_SLAVE_SDCC_2, SDM845_SLAVE_SDCC_4, SDM845_SLAVE_SNOC_CFG, SDM845_SLAVE_SPDM_WRAPPER, SDM845_SLAVE_SPSS_CFG, SDM845_SLAVE_TCSR, SDM845_SLAVE_TLMM_NORTH, SDM845_SLAVE_TLMM_SOUTH, SDM845_SLAVE_TSIF, SDM845_SLAVE_UFS_CARD_CFG, SDM845_SLAVE_UFS_MEM_CFG, SDM845_SLAVE_USB3_0, SDM845_SLAVE_USB3_1, SDM845_SLAVE_VENUS_CFG, SDM845_SLAVE_VSENSE_CTRL_CFG, SDM845_SLAVE_CNOC_A2NOC, SDM845_SLAVE_SERVICE_CNOC); +DEFINE_QNODE(qhm_cnoc, SDM845_MASTER_CNOC_DC_NOC, 1, 4, SDM845_SLAVE_LLCC_CFG, SDM845_SLAVE_MEM_NOC_CFG); +DEFINE_QNODE(acm_l3, SDM845_MASTER_APPSS_PROC, 1, 16, SDM845_SLAVE_GNOC_SNOC, SDM845_SLAVE_GNOC_MEM_NOC, SDM845_SLAVE_SERVICE_GNOC); +DEFINE_QNODE(pm_gnoc_cfg, SDM845_MASTER_GNOC_CFG, 1, 4, SDM845_SLAVE_SERVICE_GNOC); +DEFINE_QNODE(llcc_mc, SDM845_MASTER_LLCC, 4, 4, SDM845_SLAVE_EBI1); +DEFINE_QNODE(acm_tcu, SDM845_MASTER_TCU_0, 1, 8, SDM845_SLAVE_MEM_NOC_GNOC, SDM845_SLAVE_LLCC, SDM845_SLAVE_MEM_NOC_SNOC); +DEFINE_QNODE(qhm_memnoc_cfg, SDM845_MASTER_MEM_NOC_CFG, 1, 4, SDM845_SLAVE_MSS_PROC_MS_MPU_CFG, SDM845_SLAVE_SERVICE_MEM_NOC); +DEFINE_QNODE(qnm_apps, SDM845_MASTER_GNOC_MEM_NOC, 2, 32, SDM845_SLAVE_LLCC); +DEFINE_QNODE(qnm_mnoc_hf, SDM845_MASTER_MNOC_HF_MEM_NOC, 2, 32, SDM845_SLAVE_MEM_NOC_GNOC, SDM845_SLAVE_LLCC); +DEFINE_QNODE(qnm_mnoc_sf, SDM845_MASTER_MNOC_SF_MEM_NOC, 1, 32, SDM845_SLAVE_MEM_NOC_GNOC, SDM845_SLAVE_LLCC, SDM845_SLAVE_MEM_NOC_SNOC); +DEFINE_QNODE(qnm_snoc_gc, SDM845_MASTER_SNOC_GC_MEM_NOC, 1, 8, SDM845_SLAVE_LLCC); +DEFINE_QNODE(qnm_snoc_sf, SDM845_MASTER_SNOC_SF_MEM_NOC, 1, 16, SDM845_SLAVE_MEM_NOC_GNOC, SDM845_SLAVE_LLCC); +DEFINE_QNODE(qxm_gpu, SDM845_MASTER_GFX3D, 2, 32, SDM845_SLAVE_MEM_NOC_GNOC, SDM845_SLAVE_LLCC, SDM845_SLAVE_MEM_NOC_SNOC); +DEFINE_QNODE(qhm_mnoc_cfg, SDM845_MASTER_CNOC_MNOC_CFG, 1, 4, SDM845_SLAVE_SERVICE_MNOC); +DEFINE_QNODE(qxm_camnoc_hf0, SDM845_MASTER_CAMNOC_HF0, 1, 32, SDM845_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_camnoc_hf1, SDM845_MASTER_CAMNOC_HF1, 1, 32, SDM845_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_camnoc_sf, SDM845_MASTER_CAMNOC_SF, 1, 32, SDM845_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_mdp0, SDM845_MASTER_MDP0, 1, 32, SDM845_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_mdp1, SDM845_MASTER_MDP1, 1, 32, SDM845_SLAVE_MNOC_HF_MEM_NOC); +DEFINE_QNODE(qxm_rot, SDM845_MASTER_ROTATOR, 1, 32, SDM845_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_venus0, SDM845_MASTER_VIDEO_P0, 1, 32, SDM845_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_venus1, SDM845_MASTER_VIDEO_P1, 1, 32, SDM845_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qxm_venus_arm9, SDM845_MASTER_VIDEO_PROC, 1, 8, SDM845_SLAVE_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qhm_snoc_cfg, SDM845_MASTER_SNOC_CFG, 1, 4, SDM845_SLAVE_SERVICE_SNOC); +DEFINE_QNODE(qnm_aggre1_noc, SDM845_MASTER_A1NOC_SNOC, 1, 16, SDM845_SLAVE_APPSS, SDM845_SLAVE_SNOC_CNOC, SDM845_SLAVE_SNOC_MEM_NOC_SF, SDM845_SLAVE_IMEM, SDM845_SLAVE_PIMEM, SDM845_SLAVE_QDSS_STM); +DEFINE_QNODE(qnm_aggre2_noc, SDM845_MASTER_A2NOC_SNOC, 1, 16, SDM845_SLAVE_APPSS, SDM845_SLAVE_SNOC_CNOC, SDM845_SLAVE_SNOC_MEM_NOC_SF, SDM845_SLAVE_IMEM, SDM845_SLAVE_PCIE_0, SDM845_SLAVE_PCIE_1, SDM845_SLAVE_PIMEM, SDM845_SLAVE_QDSS_STM, SDM845_SLAVE_TCU); +DEFINE_QNODE(qnm_gladiator_sodv, SDM845_MASTER_GNOC_SNOC, 1, 8, SDM845_SLAVE_APPSS, SDM845_SLAVE_SNOC_CNOC, SDM845_SLAVE_IMEM, SDM845_SLAVE_PCIE_0, SDM845_SLAVE_PCIE_1, SDM845_SLAVE_PIMEM, SDM845_SLAVE_QDSS_STM, SDM845_SLAVE_TCU); +DEFINE_QNODE(qnm_memnoc, SDM845_MASTER_MEM_NOC_SNOC, 1, 8, SDM845_SLAVE_APPSS, SDM845_SLAVE_SNOC_CNOC, SDM845_SLAVE_IMEM, SDM845_SLAVE_PIMEM, SDM845_SLAVE_QDSS_STM); +DEFINE_QNODE(qnm_pcie_anoc, SDM845_MASTER_ANOC_PCIE_SNOC, 1, 16, SDM845_SLAVE_APPSS, SDM845_SLAVE_SNOC_CNOC, SDM845_SLAVE_SNOC_MEM_NOC_SF, SDM845_SLAVE_IMEM, SDM845_SLAVE_QDSS_STM); +DEFINE_QNODE(qxm_pimem, SDM845_MASTER_PIMEM, 1, 8, SDM845_SLAVE_SNOC_MEM_NOC_GC, SDM845_SLAVE_IMEM); +DEFINE_QNODE(xm_gic, SDM845_MASTER_GIC, 1, 8, SDM845_SLAVE_SNOC_MEM_NOC_GC, SDM845_SLAVE_IMEM); +DEFINE_QNODE(qns_a1noc_snoc, SDM845_SLAVE_A1NOC_SNOC, 1, 16, SDM845_MASTER_A1NOC_SNOC); +DEFINE_QNODE(srvc_aggre1_noc, SDM845_SLAVE_SERVICE_A1NOC, 1, 4, 0); +DEFINE_QNODE(qns_pcie_a1noc_snoc, SDM845_SLAVE_ANOC_PCIE_A1NOC_SNOC, 1, 16, SDM845_MASTER_ANOC_PCIE_SNOC); +DEFINE_QNODE(qns_a2noc_snoc, SDM845_SLAVE_A2NOC_SNOC, 1, 16, SDM845_MASTER_A2NOC_SNOC); +DEFINE_QNODE(qns_pcie_snoc, SDM845_SLAVE_ANOC_PCIE_SNOC, 1, 16, SDM845_MASTER_ANOC_PCIE_SNOC); +DEFINE_QNODE(srvc_aggre2_noc, SDM845_SLAVE_SERVICE_A2NOC, 1, 4); +DEFINE_QNODE(qns_camnoc_uncomp, SDM845_SLAVE_CAMNOC_UNCOMP, 1, 32); +DEFINE_QNODE(qhs_a1_noc_cfg, SDM845_SLAVE_A1NOC_CFG, 1, 4, SDM845_MASTER_A1NOC_CFG); +DEFINE_QNODE(qhs_a2_noc_cfg, SDM845_SLAVE_A2NOC_CFG, 1, 4, SDM845_MASTER_A2NOC_CFG); +DEFINE_QNODE(qhs_aop, SDM845_SLAVE_AOP, 1, 4); +DEFINE_QNODE(qhs_aoss, SDM845_SLAVE_AOSS, 1, 4); +DEFINE_QNODE(qhs_camera_cfg, SDM845_SLAVE_CAMERA_CFG, 1, 4); +DEFINE_QNODE(qhs_clk_ctl, SDM845_SLAVE_CLK_CTL, 1, 4); +DEFINE_QNODE(qhs_compute_dsp_cfg, SDM845_SLAVE_CDSP_CFG, 1, 4); +DEFINE_QNODE(qhs_cpr_cx, SDM845_SLAVE_RBCPR_CX_CFG, 1, 4); +DEFINE_QNODE(qhs_crypto0_cfg, SDM845_SLAVE_CRYPTO_0_CFG, 1, 4); +DEFINE_QNODE(qhs_dcc_cfg, SDM845_SLAVE_DCC_CFG, 1, 4, SDM845_MASTER_CNOC_DC_NOC); +DEFINE_QNODE(qhs_ddrss_cfg, SDM845_SLAVE_CNOC_DDRSS, 1, 4); +DEFINE_QNODE(qhs_display_cfg, SDM845_SLAVE_DISPLAY_CFG, 1, 4); +DEFINE_QNODE(qhs_glm, SDM845_SLAVE_GLM, 1, 4); +DEFINE_QNODE(qhs_gpuss_cfg, SDM845_SLAVE_GFX3D_CFG, 1, 8); +DEFINE_QNODE(qhs_imem_cfg, SDM845_SLAVE_IMEM_CFG, 1, 4); +DEFINE_QNODE(qhs_ipa, SDM845_SLAVE_IPA_CFG, 1, 4); +DEFINE_QNODE(qhs_mnoc_cfg, SDM845_SLAVE_CNOC_MNOC_CFG, 1, 4, SDM845_MASTER_CNOC_MNOC_CFG); +DEFINE_QNODE(qhs_pcie0_cfg, SDM845_SLAVE_PCIE_0_CFG, 1, 4); +DEFINE_QNODE(qhs_pcie_gen3_cfg, SDM845_SLAVE_PCIE_1_CFG, 1, 4); +DEFINE_QNODE(qhs_pdm, SDM845_SLAVE_PDM, 1, 4); +DEFINE_QNODE(qhs_phy_refgen_south, SDM845_SLAVE_SOUTH_PHY_CFG, 1, 4); +DEFINE_QNODE(qhs_pimem_cfg, SDM845_SLAVE_PIMEM_CFG, 1, 4); +DEFINE_QNODE(qhs_prng, SDM845_SLAVE_PRNG, 1, 4); +DEFINE_QNODE(qhs_qdss_cfg, SDM845_SLAVE_QDSS_CFG, 1, 4); +DEFINE_QNODE(qhs_qupv3_north, SDM845_SLAVE_BLSP_2, 1, 4); +DEFINE_QNODE(qhs_qupv3_south, SDM845_SLAVE_BLSP_1, 1, 4); +DEFINE_QNODE(qhs_sdc2, SDM845_SLAVE_SDCC_2, 1, 4); +DEFINE_QNODE(qhs_sdc4, SDM845_SLAVE_SDCC_4, 1, 4); +DEFINE_QNODE(qhs_snoc_cfg, SDM845_SLAVE_SNOC_CFG, 1, 4, SDM845_MASTER_SNOC_CFG); +DEFINE_QNODE(qhs_spdm, SDM845_SLAVE_SPDM_WRAPPER, 1, 4); +DEFINE_QNODE(qhs_spss_cfg, SDM845_SLAVE_SPSS_CFG, 1, 4); +DEFINE_QNODE(qhs_tcsr, SDM845_SLAVE_TCSR, 1, 4); +DEFINE_QNODE(qhs_tlmm_north, SDM845_SLAVE_TLMM_NORTH, 1, 4); +DEFINE_QNODE(qhs_tlmm_south, SDM845_SLAVE_TLMM_SOUTH, 1, 4); +DEFINE_QNODE(qhs_tsif, SDM845_SLAVE_TSIF, 1, 4); +DEFINE_QNODE(qhs_ufs_card_cfg, SDM845_SLAVE_UFS_CARD_CFG, 1, 4); +DEFINE_QNODE(qhs_ufs_mem_cfg, SDM845_SLAVE_UFS_MEM_CFG, 1, 4); +DEFINE_QNODE(qhs_usb3_0, SDM845_SLAVE_USB3_0, 1, 4); +DEFINE_QNODE(qhs_usb3_1, SDM845_SLAVE_USB3_1, 1, 4); +DEFINE_QNODE(qhs_venus_cfg, SDM845_SLAVE_VENUS_CFG, 1, 4); +DEFINE_QNODE(qhs_vsense_ctrl_cfg, SDM845_SLAVE_VSENSE_CTRL_CFG, 1, 4); +DEFINE_QNODE(qns_cnoc_a2noc, SDM845_SLAVE_CNOC_A2NOC, 1, 8, SDM845_MASTER_CNOC_A2NOC); +DEFINE_QNODE(srvc_cnoc, SDM845_SLAVE_SERVICE_CNOC, 1, 4); +DEFINE_QNODE(qhs_llcc, SDM845_SLAVE_LLCC_CFG, 1, 4); +DEFINE_QNODE(qhs_memnoc, SDM845_SLAVE_MEM_NOC_CFG, 1, 4, SDM845_MASTER_MEM_NOC_CFG); +DEFINE_QNODE(qns_gladiator_sodv, SDM845_SLAVE_GNOC_SNOC, 1, 8, SDM845_MASTER_GNOC_SNOC); +DEFINE_QNODE(qns_gnoc_memnoc, SDM845_SLAVE_GNOC_MEM_NOC, 2, 32, SDM845_MASTER_GNOC_MEM_NOC); +DEFINE_QNODE(srvc_gnoc, SDM845_SLAVE_SERVICE_GNOC, 1, 4); +DEFINE_QNODE(ebi, SDM845_SLAVE_EBI1, 4, 4); +DEFINE_QNODE(qhs_mdsp_ms_mpu_cfg, SDM845_SLAVE_MSS_PROC_MS_MPU_CFG, 1, 4); +DEFINE_QNODE(qns_apps_io, SDM845_SLAVE_MEM_NOC_GNOC, 1, 32); +DEFINE_QNODE(qns_llcc, SDM845_SLAVE_LLCC, 4, 16, SDM845_MASTER_LLCC); +DEFINE_QNODE(qns_memnoc_snoc, SDM845_SLAVE_MEM_NOC_SNOC, 1, 8, SDM845_MASTER_MEM_NOC_SNOC); +DEFINE_QNODE(srvc_memnoc, SDM845_SLAVE_SERVICE_MEM_NOC, 1, 4); +DEFINE_QNODE(qns2_mem_noc, SDM845_SLAVE_MNOC_SF_MEM_NOC, 1, 32, SDM845_MASTER_MNOC_SF_MEM_NOC); +DEFINE_QNODE(qns_mem_noc_hf, SDM845_SLAVE_MNOC_HF_MEM_NOC, 2, 32, SDM845_MASTER_MNOC_HF_MEM_NOC); +DEFINE_QNODE(srvc_mnoc, SDM845_SLAVE_SERVICE_MNOC, 1, 4); +DEFINE_QNODE(qhs_apss, SDM845_SLAVE_APPSS, 1, 8); +DEFINE_QNODE(qns_cnoc, SDM845_SLAVE_SNOC_CNOC, 1, 8, SDM845_MASTER_SNOC_CNOC); +DEFINE_QNODE(qns_memnoc_gc, SDM845_SLAVE_SNOC_MEM_NOC_GC, 1, 8, SDM845_MASTER_SNOC_GC_MEM_NOC); +DEFINE_QNODE(qns_memnoc_sf, SDM845_SLAVE_SNOC_MEM_NOC_SF, 1, 16, SDM845_MASTER_SNOC_SF_MEM_NOC); +DEFINE_QNODE(qxs_imem, SDM845_SLAVE_IMEM, 1, 8); +DEFINE_QNODE(qxs_pcie, SDM845_SLAVE_PCIE_0, 1, 8); +DEFINE_QNODE(qxs_pcie_gen3, SDM845_SLAVE_PCIE_1, 1, 8); +DEFINE_QNODE(qxs_pimem, SDM845_SLAVE_PIMEM, 1, 8); +DEFINE_QNODE(srvc_snoc, SDM845_SLAVE_SERVICE_SNOC, 1, 4); +DEFINE_QNODE(xs_qdss_stm, SDM845_SLAVE_QDSS_STM, 1, 4); +DEFINE_QNODE(xs_sys_tcu_cfg, SDM845_SLAVE_TCU, 1, 8); + +DEFINE_QBCM(bcm_acv, "ACV", false, &ebi); +DEFINE_QBCM(bcm_mc0, "MC0", true, &ebi); +DEFINE_QBCM(bcm_sh0, "SH0", true, &qns_llcc); +DEFINE_QBCM(bcm_mm0, "MM0", false, &qns_mem_noc_hf); +DEFINE_QBCM(bcm_sh1, "SH1", false, &qns_apps_io); +DEFINE_QBCM(bcm_mm1, "MM1", false, &qxm_camnoc_hf0_uncomp, &qxm_camnoc_hf1_uncomp, &qxm_camnoc_sf_uncomp, &qxm_camnoc_hf0, &qxm_camnoc_hf1, &qxm_mdp0, &qxm_mdp1); +DEFINE_QBCM(bcm_sh2, "SH2", false, &qns_memnoc_snoc); +DEFINE_QBCM(bcm_mm2, "MM2", false, &qns2_mem_noc); +DEFINE_QBCM(bcm_sh3, "SH3", false, &acm_tcu); +DEFINE_QBCM(bcm_mm3, "MM3", false, &qxm_camnoc_sf, &qxm_rot, &qxm_venus0, &qxm_venus1, &qxm_venus_arm9); +DEFINE_QBCM(bcm_sh5, "SH5", false, &qnm_apps); +DEFINE_QBCM(bcm_sn0, "SN0", true, &qns_memnoc_sf); +DEFINE_QBCM(bcm_ce0, "CE0", false, &qxm_crypto); +DEFINE_QBCM(bcm_cn0, "CN0", false, &qhm_spdm, &qhm_tic, &qnm_snoc, &xm_qdss_dap, &qhs_a1_noc_cfg, &qhs_a2_noc_cfg, &qhs_aop, &qhs_aoss, &qhs_camera_cfg, &qhs_clk_ctl, &qhs_compute_dsp_cfg, &qhs_cpr_cx, &qhs_crypto0_cfg, &qhs_dcc_cfg, &qhs_ddrss_cfg, &qhs_display_cfg, &qhs_glm, &qhs_gpuss_cfg, &qhs_imem_cfg, &qhs_ipa, &qhs_mnoc_cfg, &qhs_pcie0_cfg, &qhs_pcie_gen3_cfg, &qhs_pdm, &qhs_phy_refgen_south, &qhs_pimem_cfg, &qhs_prng, &qhs_qdss_cfg, &qhs_qupv3_north, &qhs_qupv3_south, &qhs_sdc2, &qhs_sdc4, &qhs_snoc_cfg, &qhs_spdm, &qhs_spss_cfg, &qhs_tcsr, &qhs_tlmm_north, &qhs_tlmm_south, &qhs_tsif, &qhs_ufs_card_cfg, &qhs_ufs_mem_cfg, &qhs_usb3_0, &qhs_usb3_1, &qhs_venus_cfg, &qhs_vsense_ctrl_cfg, &qns_cnoc_a2noc, &srvc_cnoc); +DEFINE_QBCM(bcm_qup0, "QUP0", false, &qhm_qup1, &qhm_qup2); +DEFINE_QBCM(bcm_sn1, "SN1", false, &qxs_imem); +DEFINE_QBCM(bcm_sn2, "SN2", false, &qns_memnoc_gc); +DEFINE_QBCM(bcm_sn3, "SN3", false, &qns_cnoc); +DEFINE_QBCM(bcm_sn4, "SN4", false, &qxm_pimem); +DEFINE_QBCM(bcm_sn5, "SN5", false, &xs_qdss_stm); +DEFINE_QBCM(bcm_sn6, "SN6", false, &qhs_apss, &srvc_snoc, &xs_sys_tcu_cfg); +DEFINE_QBCM(bcm_sn7, "SN7", false, &qxs_pcie); +DEFINE_QBCM(bcm_sn8, "SN8", false, &qxs_pcie_gen3); +DEFINE_QBCM(bcm_sn9, "SN9", false, &srvc_aggre1_noc, &qnm_aggre1_noc); +DEFINE_QBCM(bcm_sn11, "SN11", false, &srvc_aggre2_noc, &qnm_aggre2_noc); +DEFINE_QBCM(bcm_sn12, "SN12", false, &qnm_gladiator_sodv, &xm_gic); +DEFINE_QBCM(bcm_sn14, "SN14", false, &qnm_pcie_anoc); +DEFINE_QBCM(bcm_sn15, "SN15", false, &qnm_memnoc); + +static struct qcom_icc_bcm *aggre1_noc_bcms[] = { + &bcm_sn9, }; -/** - * struct qcom_icc_bcm - Qualcomm specific hardware accelerator nodes - * known as Bus Clock Manager (BCM) - * @name: the bcm node name used to fetch BCM data from command db - * @type: latency or bandwidth bcm - * @addr: address offsets used when voting to RPMH - * @vote_x: aggregated threshold values, represents sum_bw when @type is bw bcm - * @vote_y: aggregated threshold values, represents peak_bw when @type is bw bcm - * @dirty: flag used to indicate whether the bcm needs to be committed - * @keepalive: flag used to indicate whether a keepalive is required - * @aux_data: auxiliary data used when calculating threshold values and - * communicating with RPMh - * @list: used to link to other bcms when compiling lists for commit - * @num_nodes: total number of @num_nodes - * @nodes: list of qcom_icc_nodes that this BCM encapsulates - */ -struct qcom_icc_bcm { - const char *name; - u32 type; - u32 addr; - u64 vote_x[QCOM_ICC_NUM_BUCKETS]; - u64 vote_y[QCOM_ICC_NUM_BUCKETS]; - bool dirty; - bool keepalive; - struct bcm_db aux_data; - struct list_head list; - size_t num_nodes; - struct qcom_icc_node *nodes[]; +static struct qcom_icc_node *aggre1_noc_nodes[] = { + [MASTER_A1NOC_CFG] = &qhm_a1noc_cfg, + [MASTER_TSIF] = &qhm_tsif, + [MASTER_SDCC_2] = &xm_sdc2, + [MASTER_SDCC_4] = &xm_sdc4, + [MASTER_UFS_CARD] = &xm_ufs_card, + [MASTER_UFS_MEM] = &xm_ufs_mem, + [MASTER_PCIE_0] = &xm_pcie_0, + [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, + [SLAVE_SERVICE_A1NOC] = &srvc_aggre1_noc, + [SLAVE_ANOC_PCIE_A1NOC_SNOC] = &qns_pcie_a1noc_snoc, }; -struct qcom_icc_fabric { - struct qcom_icc_node **nodes; - size_t num_nodes; +const static struct qcom_icc_desc sdm845_aggre1_noc = { + .nodes = aggre1_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre1_noc_nodes), + .bcms = aggre1_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre1_noc_bcms), }; -struct qcom_icc_desc { - struct qcom_icc_node **nodes; - size_t num_nodes; - struct qcom_icc_bcm **bcms; - size_t num_bcms; +static struct qcom_icc_bcm *aggre2_noc_bcms[] = { + &bcm_ce0, + &bcm_sn11, + &bcm_qup0, }; -#define DEFINE_QNODE(_name, _id, _channels, _buswidth, \ - _numlinks, ...) \ - static struct qcom_icc_node _name = { \ - .id = _id, \ - .name = #_name, \ - .channels = _channels, \ - .buswidth = _buswidth, \ - .num_links = _numlinks, \ - .links = { __VA_ARGS__ }, \ - } - -DEFINE_QNODE(qhm_a1noc_cfg, MASTER_A1NOC_CFG, 1, 4, 1, SLAVE_SERVICE_A1NOC); -DEFINE_QNODE(qhm_qup1, MASTER_BLSP_1, 1, 4, 1, SLAVE_A1NOC_SNOC); -DEFINE_QNODE(qhm_tsif, MASTER_TSIF, 1, 4, 1, SLAVE_A1NOC_SNOC); -DEFINE_QNODE(xm_sdc2, MASTER_SDCC_2, 1, 8, 1, SLAVE_A1NOC_SNOC); -DEFINE_QNODE(xm_sdc4, MASTER_SDCC_4, 1, 8, 1, SLAVE_A1NOC_SNOC); -DEFINE_QNODE(xm_ufs_card, MASTER_UFS_CARD, 1, 8, 1, SLAVE_A1NOC_SNOC); -DEFINE_QNODE(xm_ufs_mem, MASTER_UFS_MEM, 1, 8, 1, SLAVE_A1NOC_SNOC); -DEFINE_QNODE(xm_pcie_0, MASTER_PCIE_0, 1, 8, 1, SLAVE_ANOC_PCIE_A1NOC_SNOC); -DEFINE_QNODE(qhm_a2noc_cfg, MASTER_A2NOC_CFG, 1, 4, 1, SLAVE_SERVICE_A2NOC); -DEFINE_QNODE(qhm_qdss_bam, MASTER_QDSS_BAM, 1, 4, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(qhm_qup2, MASTER_BLSP_2, 1, 4, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(qnm_cnoc, MASTER_CNOC_A2NOC, 1, 8, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(qxm_crypto, MASTER_CRYPTO, 1, 8, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(qxm_ipa, MASTER_IPA, 1, 8, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(xm_pcie3_1, MASTER_PCIE_1, 1, 8, 1, SLAVE_ANOC_PCIE_SNOC); -DEFINE_QNODE(xm_qdss_etr, MASTER_QDSS_ETR, 1, 8, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(xm_usb3_0, MASTER_USB3_0, 1, 8, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(xm_usb3_1, MASTER_USB3_1, 1, 8, 1, SLAVE_A2NOC_SNOC); -DEFINE_QNODE(qxm_camnoc_hf0_uncomp, MASTER_CAMNOC_HF0_UNCOMP, 1, 32, 1, SLAVE_CAMNOC_UNCOMP); -DEFINE_QNODE(qxm_camnoc_hf1_uncomp, MASTER_CAMNOC_HF1_UNCOMP, 1, 32, 1, SLAVE_CAMNOC_UNCOMP); -DEFINE_QNODE(qxm_camnoc_sf_uncomp, MASTER_CAMNOC_SF_UNCOMP, 1, 32, 1, SLAVE_CAMNOC_UNCOMP); -DEFINE_QNODE(qhm_spdm, MASTER_SPDM, 1, 4, 1, SLAVE_CNOC_A2NOC); -DEFINE_QNODE(qhm_tic, MASTER_TIC, 1, 4, 43, SLAVE_A1NOC_CFG, SLAVE_A2NOC_CFG, SLAVE_AOP, SLAVE_AOSS, SLAVE_CAMERA_CFG, SLAVE_CLK_CTL, SLAVE_CDSP_CFG, SLAVE_RBCPR_CX_CFG, SLAVE_CRYPTO_0_CFG, SLAVE_DCC_CFG, SLAVE_CNOC_DDRSS, SLAVE_DISPLAY_CFG, SLAVE_GLM, SLAVE_GFX3D_CFG, SLAVE_IMEM_CFG, SLAVE_IPA_CFG, SLAVE_CNOC_MNOC_CFG, SLAVE_PCIE_0_CFG, SLAVE_PCIE_1_CFG, SLAVE_PDM, SLAVE_SOUTH_PHY_CFG, SLAVE_PIMEM_CFG, SLAVE_PRNG, SLAVE_QDSS_CFG, SLAVE_BLSP_2, SLAVE_BLSP_1, SLAVE_SDCC_2, SLAVE_SDCC_4, SLAVE_SNOC_CFG, SLAVE_SPDM_WRAPPER, SLAVE_SPSS_CFG, SLAVE_TCSR, SLAVE_TLMM_NORTH, SLAVE_TLMM_SOUTH, SLAVE_TSIF, SLAVE_UFS_CARD_CFG, SLAVE_UFS_MEM_CFG, SLAVE_USB3_0, SLAVE_USB3_1, SLAVE_VENUS_CFG, SLAVE_VSENSE_CTRL_CFG, SLAVE_CNOC_A2NOC, SLAVE_SERVICE_CNOC); -DEFINE_QNODE(qnm_snoc, MASTER_SNOC_CNOC, 1, 8, 42, SLAVE_A1NOC_CFG, SLAVE_A2NOC_CFG, SLAVE_AOP, SLAVE_AOSS, SLAVE_CAMERA_CFG, SLAVE_CLK_CTL, SLAVE_CDSP_CFG, SLAVE_RBCPR_CX_CFG, SLAVE_CRYPTO_0_CFG, SLAVE_DCC_CFG, SLAVE_CNOC_DDRSS, SLAVE_DISPLAY_CFG, SLAVE_GLM, SLAVE_GFX3D_CFG, SLAVE_IMEM_CFG, SLAVE_IPA_CFG, SLAVE_CNOC_MNOC_CFG, SLAVE_PCIE_0_CFG, SLAVE_PCIE_1_CFG, SLAVE_PDM, SLAVE_SOUTH_PHY_CFG, SLAVE_PIMEM_CFG, SLAVE_PRNG, SLAVE_QDSS_CFG, SLAVE_BLSP_2, SLAVE_BLSP_1, SLAVE_SDCC_2, SLAVE_SDCC_4, SLAVE_SNOC_CFG, SLAVE_SPDM_WRAPPER, SLAVE_SPSS_CFG, SLAVE_TCSR, SLAVE_TLMM_NORTH, SLAVE_TLMM_SOUTH, SLAVE_TSIF, SLAVE_UFS_CARD_CFG, SLAVE_UFS_MEM_CFG, SLAVE_USB3_0, SLAVE_USB3_1, SLAVE_VENUS_CFG, SLAVE_VSENSE_CTRL_CFG, SLAVE_SERVICE_CNOC); -DEFINE_QNODE(xm_qdss_dap, MASTER_QDSS_DAP, 1, 8, 43, SLAVE_A1NOC_CFG, SLAVE_A2NOC_CFG, SLAVE_AOP, SLAVE_AOSS, SLAVE_CAMERA_CFG, SLAVE_CLK_CTL, SLAVE_CDSP_CFG, SLAVE_RBCPR_CX_CFG, SLAVE_CRYPTO_0_CFG, SLAVE_DCC_CFG, SLAVE_CNOC_DDRSS, SLAVE_DISPLAY_CFG, SLAVE_GLM, SLAVE_GFX3D_CFG, SLAVE_IMEM_CFG, SLAVE_IPA_CFG, SLAVE_CNOC_MNOC_CFG, SLAVE_PCIE_0_CFG, SLAVE_PCIE_1_CFG, SLAVE_PDM, SLAVE_SOUTH_PHY_CFG, SLAVE_PIMEM_CFG, SLAVE_PRNG, SLAVE_QDSS_CFG, SLAVE_BLSP_2, SLAVE_BLSP_1, SLAVE_SDCC_2, SLAVE_SDCC_4, SLAVE_SNOC_CFG, SLAVE_SPDM_WRAPPER, SLAVE_SPSS_CFG, SLAVE_TCSR, SLAVE_TLMM_NORTH, SLAVE_TLMM_SOUTH, SLAVE_TSIF, SLAVE_UFS_CARD_CFG, SLAVE_UFS_MEM_CFG, SLAVE_USB3_0, SLAVE_USB3_1, SLAVE_VENUS_CFG, SLAVE_VSENSE_CTRL_CFG, SLAVE_CNOC_A2NOC, SLAVE_SERVICE_CNOC); -DEFINE_QNODE(qhm_cnoc, MASTER_CNOC_DC_NOC, 1, 4, 2, SLAVE_LLCC_CFG, SLAVE_MEM_NOC_CFG); -DEFINE_QNODE(acm_l3, MASTER_APPSS_PROC, 1, 16, 3, SLAVE_GNOC_SNOC, SLAVE_GNOC_MEM_NOC, SLAVE_SERVICE_GNOC); -DEFINE_QNODE(pm_gnoc_cfg, MASTER_GNOC_CFG, 1, 4, 1, SLAVE_SERVICE_GNOC); -DEFINE_QNODE(llcc_mc, MASTER_LLCC, 4, 4, 1, SLAVE_EBI1); -DEFINE_QNODE(acm_tcu, MASTER_TCU_0, 1, 8, 3, SLAVE_MEM_NOC_GNOC, SLAVE_LLCC, SLAVE_MEM_NOC_SNOC); -DEFINE_QNODE(qhm_memnoc_cfg, MASTER_MEM_NOC_CFG, 1, 4, 2, SLAVE_MSS_PROC_MS_MPU_CFG, SLAVE_SERVICE_MEM_NOC); -DEFINE_QNODE(qnm_apps, MASTER_GNOC_MEM_NOC, 2, 32, 1, SLAVE_LLCC); -DEFINE_QNODE(qnm_mnoc_hf, MASTER_MNOC_HF_MEM_NOC, 2, 32, 2, SLAVE_MEM_NOC_GNOC, SLAVE_LLCC); -DEFINE_QNODE(qnm_mnoc_sf, MASTER_MNOC_SF_MEM_NOC, 1, 32, 3, SLAVE_MEM_NOC_GNOC, SLAVE_LLCC, SLAVE_MEM_NOC_SNOC); -DEFINE_QNODE(qnm_snoc_gc, MASTER_SNOC_GC_MEM_NOC, 1, 8, 1, SLAVE_LLCC); -DEFINE_QNODE(qnm_snoc_sf, MASTER_SNOC_SF_MEM_NOC, 1, 16, 2, SLAVE_MEM_NOC_GNOC, SLAVE_LLCC); -DEFINE_QNODE(qxm_gpu, MASTER_GFX3D, 2, 32, 3, SLAVE_MEM_NOC_GNOC, SLAVE_LLCC, SLAVE_MEM_NOC_SNOC); -DEFINE_QNODE(qhm_mnoc_cfg, MASTER_CNOC_MNOC_CFG, 1, 4, 1, SLAVE_SERVICE_MNOC); -DEFINE_QNODE(qxm_camnoc_hf0, MASTER_CAMNOC_HF0, 1, 32, 1, SLAVE_MNOC_HF_MEM_NOC); -DEFINE_QNODE(qxm_camnoc_hf1, MASTER_CAMNOC_HF1, 1, 32, 1, SLAVE_MNOC_HF_MEM_NOC); -DEFINE_QNODE(qxm_camnoc_sf, MASTER_CAMNOC_SF, 1, 32, 1, SLAVE_MNOC_SF_MEM_NOC); -DEFINE_QNODE(qxm_mdp0, MASTER_MDP0, 1, 32, 1, SLAVE_MNOC_HF_MEM_NOC); -DEFINE_QNODE(qxm_mdp1, MASTER_MDP1, 1, 32, 1, SLAVE_MNOC_HF_MEM_NOC); -DEFINE_QNODE(qxm_rot, MASTER_ROTATOR, 1, 32, 1, SLAVE_MNOC_SF_MEM_NOC); -DEFINE_QNODE(qxm_venus0, MASTER_VIDEO_P0, 1, 32, 1, SLAVE_MNOC_SF_MEM_NOC); -DEFINE_QNODE(qxm_venus1, MASTER_VIDEO_P1, 1, 32, 1, SLAVE_MNOC_SF_MEM_NOC); -DEFINE_QNODE(qxm_venus_arm9, MASTER_VIDEO_PROC, 1, 8, 1, SLAVE_MNOC_SF_MEM_NOC); -DEFINE_QNODE(qhm_snoc_cfg, MASTER_SNOC_CFG, 1, 4, 1, SLAVE_SERVICE_SNOC); -DEFINE_QNODE(qnm_aggre1_noc, MASTER_A1NOC_SNOC, 1, 16, 6, SLAVE_APPSS, SLAVE_SNOC_CNOC, SLAVE_SNOC_MEM_NOC_SF, SLAVE_IMEM, SLAVE_PIMEM, SLAVE_QDSS_STM); -DEFINE_QNODE(qnm_aggre2_noc, MASTER_A2NOC_SNOC, 1, 16, 9, SLAVE_APPSS, SLAVE_SNOC_CNOC, SLAVE_SNOC_MEM_NOC_SF, SLAVE_IMEM, SLAVE_PCIE_0, SLAVE_PCIE_1, SLAVE_PIMEM, SLAVE_QDSS_STM, SLAVE_TCU); -DEFINE_QNODE(qnm_gladiator_sodv, MASTER_GNOC_SNOC, 1, 8, 8, SLAVE_APPSS, SLAVE_SNOC_CNOC, SLAVE_IMEM, SLAVE_PCIE_0, SLAVE_PCIE_1, SLAVE_PIMEM, SLAVE_QDSS_STM, SLAVE_TCU); -DEFINE_QNODE(qnm_memnoc, MASTER_MEM_NOC_SNOC, 1, 8, 5, SLAVE_APPSS, SLAVE_SNOC_CNOC, SLAVE_IMEM, SLAVE_PIMEM, SLAVE_QDSS_STM); -DEFINE_QNODE(qnm_pcie_anoc, MASTER_ANOC_PCIE_SNOC, 1, 16, 5, SLAVE_APPSS, SLAVE_SNOC_CNOC, SLAVE_SNOC_MEM_NOC_SF, SLAVE_IMEM, SLAVE_QDSS_STM); -DEFINE_QNODE(qxm_pimem, MASTER_PIMEM, 1, 8, 2, SLAVE_SNOC_MEM_NOC_GC, SLAVE_IMEM); -DEFINE_QNODE(xm_gic, MASTER_GIC, 1, 8, 2, SLAVE_SNOC_MEM_NOC_GC, SLAVE_IMEM); -DEFINE_QNODE(qns_a1noc_snoc, SLAVE_A1NOC_SNOC, 1, 16, 1, MASTER_A1NOC_SNOC); -DEFINE_QNODE(srvc_aggre1_noc, SLAVE_SERVICE_A1NOC, 1, 4, 0); -DEFINE_QNODE(qns_pcie_a1noc_snoc, SLAVE_ANOC_PCIE_A1NOC_SNOC, 1, 16, 1, MASTER_ANOC_PCIE_SNOC); -DEFINE_QNODE(qns_a2noc_snoc, SLAVE_A2NOC_SNOC, 1, 16, 1, MASTER_A2NOC_SNOC); -DEFINE_QNODE(qns_pcie_snoc, SLAVE_ANOC_PCIE_SNOC, 1, 16, 1, MASTER_ANOC_PCIE_SNOC); -DEFINE_QNODE(srvc_aggre2_noc, SLAVE_SERVICE_A2NOC, 1, 4, 0); -DEFINE_QNODE(qns_camnoc_uncomp, SLAVE_CAMNOC_UNCOMP, 1, 32, 0); -DEFINE_QNODE(qhs_a1_noc_cfg, SLAVE_A1NOC_CFG, 1, 4, 1, MASTER_A1NOC_CFG); -DEFINE_QNODE(qhs_a2_noc_cfg, SLAVE_A2NOC_CFG, 1, 4, 1, MASTER_A2NOC_CFG); -DEFINE_QNODE(qhs_aop, SLAVE_AOP, 1, 4, 0); -DEFINE_QNODE(qhs_aoss, SLAVE_AOSS, 1, 4, 0); -DEFINE_QNODE(qhs_camera_cfg, SLAVE_CAMERA_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_clk_ctl, SLAVE_CLK_CTL, 1, 4, 0); -DEFINE_QNODE(qhs_compute_dsp_cfg, SLAVE_CDSP_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_cpr_cx, SLAVE_RBCPR_CX_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_crypto0_cfg, SLAVE_CRYPTO_0_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_dcc_cfg, SLAVE_DCC_CFG, 1, 4, 1, MASTER_CNOC_DC_NOC); -DEFINE_QNODE(qhs_ddrss_cfg, SLAVE_CNOC_DDRSS, 1, 4, 0); -DEFINE_QNODE(qhs_display_cfg, SLAVE_DISPLAY_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_glm, SLAVE_GLM, 1, 4, 0); -DEFINE_QNODE(qhs_gpuss_cfg, SLAVE_GFX3D_CFG, 1, 8, 0); -DEFINE_QNODE(qhs_imem_cfg, SLAVE_IMEM_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_ipa, SLAVE_IPA_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_mnoc_cfg, SLAVE_CNOC_MNOC_CFG, 1, 4, 1, MASTER_CNOC_MNOC_CFG); -DEFINE_QNODE(qhs_pcie0_cfg, SLAVE_PCIE_0_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_pcie_gen3_cfg, SLAVE_PCIE_1_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_pdm, SLAVE_PDM, 1, 4, 0); -DEFINE_QNODE(qhs_phy_refgen_south, SLAVE_SOUTH_PHY_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_pimem_cfg, SLAVE_PIMEM_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_prng, SLAVE_PRNG, 1, 4, 0); -DEFINE_QNODE(qhs_qdss_cfg, SLAVE_QDSS_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_qupv3_north, SLAVE_BLSP_2, 1, 4, 0); -DEFINE_QNODE(qhs_qupv3_south, SLAVE_BLSP_1, 1, 4, 0); -DEFINE_QNODE(qhs_sdc2, SLAVE_SDCC_2, 1, 4, 0); -DEFINE_QNODE(qhs_sdc4, SLAVE_SDCC_4, 1, 4, 0); -DEFINE_QNODE(qhs_snoc_cfg, SLAVE_SNOC_CFG, 1, 4, 1, MASTER_SNOC_CFG); -DEFINE_QNODE(qhs_spdm, SLAVE_SPDM_WRAPPER, 1, 4, 0); -DEFINE_QNODE(qhs_spss_cfg, SLAVE_SPSS_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_tcsr, SLAVE_TCSR, 1, 4, 0); -DEFINE_QNODE(qhs_tlmm_north, SLAVE_TLMM_NORTH, 1, 4, 0); -DEFINE_QNODE(qhs_tlmm_south, SLAVE_TLMM_SOUTH, 1, 4, 0); -DEFINE_QNODE(qhs_tsif, SLAVE_TSIF, 1, 4, 0); -DEFINE_QNODE(qhs_ufs_card_cfg, SLAVE_UFS_CARD_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_ufs_mem_cfg, SLAVE_UFS_MEM_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_usb3_0, SLAVE_USB3_0, 1, 4, 0); -DEFINE_QNODE(qhs_usb3_1, SLAVE_USB3_1, 1, 4, 0); -DEFINE_QNODE(qhs_venus_cfg, SLAVE_VENUS_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_vsense_ctrl_cfg, SLAVE_VSENSE_CTRL_CFG, 1, 4, 0); -DEFINE_QNODE(qns_cnoc_a2noc, SLAVE_CNOC_A2NOC, 1, 8, 1, MASTER_CNOC_A2NOC); -DEFINE_QNODE(srvc_cnoc, SLAVE_SERVICE_CNOC, 1, 4, 0); -DEFINE_QNODE(qhs_llcc, SLAVE_LLCC_CFG, 1, 4, 0); -DEFINE_QNODE(qhs_memnoc, SLAVE_MEM_NOC_CFG, 1, 4, 1, MASTER_MEM_NOC_CFG); -DEFINE_QNODE(qns_gladiator_sodv, SLAVE_GNOC_SNOC, 1, 8, 1, MASTER_GNOC_SNOC); -DEFINE_QNODE(qns_gnoc_memnoc, SLAVE_GNOC_MEM_NOC, 2, 32, 1, MASTER_GNOC_MEM_NOC); -DEFINE_QNODE(srvc_gnoc, SLAVE_SERVICE_GNOC, 1, 4, 0); -DEFINE_QNODE(ebi, SLAVE_EBI1, 4, 4, 0); -DEFINE_QNODE(qhs_mdsp_ms_mpu_cfg, SLAVE_MSS_PROC_MS_MPU_CFG, 1, 4, 0); -DEFINE_QNODE(qns_apps_io, SLAVE_MEM_NOC_GNOC, 1, 32, 0); -DEFINE_QNODE(qns_llcc, SLAVE_LLCC, 4, 16, 1, MASTER_LLCC); -DEFINE_QNODE(qns_memnoc_snoc, SLAVE_MEM_NOC_SNOC, 1, 8, 1, MASTER_MEM_NOC_SNOC); -DEFINE_QNODE(srvc_memnoc, SLAVE_SERVICE_MEM_NOC, 1, 4, 0); -DEFINE_QNODE(qns2_mem_noc, SLAVE_MNOC_SF_MEM_NOC, 1, 32, 1, MASTER_MNOC_SF_MEM_NOC); -DEFINE_QNODE(qns_mem_noc_hf, SLAVE_MNOC_HF_MEM_NOC, 2, 32, 1, MASTER_MNOC_HF_MEM_NOC); -DEFINE_QNODE(srvc_mnoc, SLAVE_SERVICE_MNOC, 1, 4, 0); -DEFINE_QNODE(qhs_apss, SLAVE_APPSS, 1, 8, 0); -DEFINE_QNODE(qns_cnoc, SLAVE_SNOC_CNOC, 1, 8, 1, MASTER_SNOC_CNOC); -DEFINE_QNODE(qns_memnoc_gc, SLAVE_SNOC_MEM_NOC_GC, 1, 8, 1, MASTER_SNOC_GC_MEM_NOC); -DEFINE_QNODE(qns_memnoc_sf, SLAVE_SNOC_MEM_NOC_SF, 1, 16, 1, MASTER_SNOC_SF_MEM_NOC); -DEFINE_QNODE(qxs_imem, SLAVE_IMEM, 1, 8, 0); -DEFINE_QNODE(qxs_pcie, SLAVE_PCIE_0, 1, 8, 0); -DEFINE_QNODE(qxs_pcie_gen3, SLAVE_PCIE_1, 1, 8, 0); -DEFINE_QNODE(qxs_pimem, SLAVE_PIMEM, 1, 8, 0); -DEFINE_QNODE(srvc_snoc, SLAVE_SERVICE_SNOC, 1, 4, 0); -DEFINE_QNODE(xs_qdss_stm, SLAVE_QDSS_STM, 1, 4, 0); -DEFINE_QNODE(xs_sys_tcu_cfg, SLAVE_TCU, 1, 8, 0); - -#define DEFINE_QBCM(_name, _bcmname, _keepalive, _numnodes, ...) \ - static struct qcom_icc_bcm _name = { \ - .name = _bcmname, \ - .keepalive = _keepalive, \ - .num_nodes = _numnodes, \ - .nodes = { __VA_ARGS__ }, \ - } - -DEFINE_QBCM(bcm_acv, "ACV", false, 1, &ebi); -DEFINE_QBCM(bcm_mc0, "MC0", true, 1, &ebi); -DEFINE_QBCM(bcm_sh0, "SH0", true, 1, &qns_llcc); -DEFINE_QBCM(bcm_mm0, "MM0", false, 1, &qns_mem_noc_hf); -DEFINE_QBCM(bcm_sh1, "SH1", false, 1, &qns_apps_io); -DEFINE_QBCM(bcm_mm1, "MM1", false, 7, &qxm_camnoc_hf0_uncomp, &qxm_camnoc_hf1_uncomp, &qxm_camnoc_sf_uncomp, &qxm_camnoc_hf0, &qxm_camnoc_hf1, &qxm_mdp0, &qxm_mdp1); -DEFINE_QBCM(bcm_sh2, "SH2", false, 1, &qns_memnoc_snoc); -DEFINE_QBCM(bcm_mm2, "MM2", false, 1, &qns2_mem_noc); -DEFINE_QBCM(bcm_sh3, "SH3", false, 1, &acm_tcu); -DEFINE_QBCM(bcm_mm3, "MM3", false, 5, &qxm_camnoc_sf, &qxm_rot, &qxm_venus0, &qxm_venus1, &qxm_venus_arm9); -DEFINE_QBCM(bcm_sh5, "SH5", false, 1, &qnm_apps); -DEFINE_QBCM(bcm_sn0, "SN0", true, 1, &qns_memnoc_sf); -DEFINE_QBCM(bcm_ce0, "CE0", false, 1, &qxm_crypto); -DEFINE_QBCM(bcm_cn0, "CN0", false, 47, &qhm_spdm, &qhm_tic, &qnm_snoc, &xm_qdss_dap, &qhs_a1_noc_cfg, &qhs_a2_noc_cfg, &qhs_aop, &qhs_aoss, &qhs_camera_cfg, &qhs_clk_ctl, &qhs_compute_dsp_cfg, &qhs_cpr_cx, &qhs_crypto0_cfg, &qhs_dcc_cfg, &qhs_ddrss_cfg, &qhs_display_cfg, &qhs_glm, &qhs_gpuss_cfg, &qhs_imem_cfg, &qhs_ipa, &qhs_mnoc_cfg, &qhs_pcie0_cfg, &qhs_pcie_gen3_cfg, &qhs_pdm, &qhs_phy_refgen_south, &qhs_pimem_cfg, &qhs_prng, &qhs_qdss_cfg, &qhs_qupv3_north, &qhs_qupv3_south, &qhs_sdc2, &qhs_sdc4, &qhs_snoc_cfg, &qhs_spdm, &qhs_spss_cfg, &qhs_tcsr, &qhs_tlmm_north, &qhs_tlmm_south, &qhs_tsif, &qhs_ufs_card_cfg, &qhs_ufs_mem_cfg, &qhs_usb3_0, &qhs_usb3_1, &qhs_venus_cfg, &qhs_vsense_ctrl_cfg, &qns_cnoc_a2noc, &srvc_cnoc); -DEFINE_QBCM(bcm_qup0, "QUP0", false, 2, &qhm_qup1, &qhm_qup2); -DEFINE_QBCM(bcm_sn1, "SN1", false, 1, &qxs_imem); -DEFINE_QBCM(bcm_sn2, "SN2", false, 1, &qns_memnoc_gc); -DEFINE_QBCM(bcm_sn3, "SN3", false, 1, &qns_cnoc); -DEFINE_QBCM(bcm_sn4, "SN4", false, 1, &qxm_pimem); -DEFINE_QBCM(bcm_sn5, "SN5", false, 1, &xs_qdss_stm); -DEFINE_QBCM(bcm_sn6, "SN6", false, 3, &qhs_apss, &srvc_snoc, &xs_sys_tcu_cfg); -DEFINE_QBCM(bcm_sn7, "SN7", false, 1, &qxs_pcie); -DEFINE_QBCM(bcm_sn8, "SN8", false, 1, &qxs_pcie_gen3); -DEFINE_QBCM(bcm_sn9, "SN9", false, 2, &srvc_aggre1_noc, &qnm_aggre1_noc); -DEFINE_QBCM(bcm_sn11, "SN11", false, 2, &srvc_aggre2_noc, &qnm_aggre2_noc); -DEFINE_QBCM(bcm_sn12, "SN12", false, 2, &qnm_gladiator_sodv, &xm_gic); -DEFINE_QBCM(bcm_sn14, "SN14", false, 1, &qnm_pcie_anoc); -DEFINE_QBCM(bcm_sn15, "SN15", false, 1, &qnm_memnoc); - -static struct qcom_icc_node *rsc_hlos_nodes[] = { - [MASTER_APPSS_PROC] = &acm_l3, - [MASTER_TCU_0] = &acm_tcu, - [MASTER_LLCC] = &llcc_mc, - [MASTER_GNOC_CFG] = &pm_gnoc_cfg, - [MASTER_A1NOC_CFG] = &qhm_a1noc_cfg, +static struct qcom_icc_node *aggre2_noc_nodes[] = { [MASTER_A2NOC_CFG] = &qhm_a2noc_cfg, - [MASTER_CNOC_DC_NOC] = &qhm_cnoc, - [MASTER_MEM_NOC_CFG] = &qhm_memnoc_cfg, - [MASTER_CNOC_MNOC_CFG] = &qhm_mnoc_cfg, [MASTER_QDSS_BAM] = &qhm_qdss_bam, - [MASTER_BLSP_1] = &qhm_qup1, - [MASTER_BLSP_2] = &qhm_qup2, - [MASTER_SNOC_CFG] = &qhm_snoc_cfg, - [MASTER_SPDM] = &qhm_spdm, - [MASTER_TIC] = &qhm_tic, - [MASTER_TSIF] = &qhm_tsif, - [MASTER_A1NOC_SNOC] = &qnm_aggre1_noc, - [MASTER_A2NOC_SNOC] = &qnm_aggre2_noc, - [MASTER_GNOC_MEM_NOC] = &qnm_apps, [MASTER_CNOC_A2NOC] = &qnm_cnoc, - [MASTER_GNOC_SNOC] = &qnm_gladiator_sodv, - [MASTER_MEM_NOC_SNOC] = &qnm_memnoc, - [MASTER_MNOC_HF_MEM_NOC] = &qnm_mnoc_hf, - [MASTER_MNOC_SF_MEM_NOC] = &qnm_mnoc_sf, - [MASTER_ANOC_PCIE_SNOC] = &qnm_pcie_anoc, - [MASTER_SNOC_CNOC] = &qnm_snoc, - [MASTER_SNOC_GC_MEM_NOC] = &qnm_snoc_gc, - [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf, - [MASTER_CAMNOC_HF0] = &qxm_camnoc_hf0, - [MASTER_CAMNOC_HF0_UNCOMP] = &qxm_camnoc_hf0_uncomp, - [MASTER_CAMNOC_HF1] = &qxm_camnoc_hf1, - [MASTER_CAMNOC_HF1_UNCOMP] = &qxm_camnoc_hf1_uncomp, - [MASTER_CAMNOC_SF] = &qxm_camnoc_sf, - [MASTER_CAMNOC_SF_UNCOMP] = &qxm_camnoc_sf_uncomp, [MASTER_CRYPTO] = &qxm_crypto, - [MASTER_GFX3D] = &qxm_gpu, [MASTER_IPA] = &qxm_ipa, - [MASTER_MDP0] = &qxm_mdp0, - [MASTER_MDP1] = &qxm_mdp1, - [MASTER_PIMEM] = &qxm_pimem, - [MASTER_ROTATOR] = &qxm_rot, - [MASTER_VIDEO_P0] = &qxm_venus0, - [MASTER_VIDEO_P1] = &qxm_venus1, - [MASTER_VIDEO_PROC] = &qxm_venus_arm9, - [MASTER_GIC] = &xm_gic, [MASTER_PCIE_1] = &xm_pcie3_1, - [MASTER_PCIE_0] = &xm_pcie_0, - [MASTER_QDSS_DAP] = &xm_qdss_dap, [MASTER_QDSS_ETR] = &xm_qdss_etr, - [MASTER_SDCC_2] = &xm_sdc2, - [MASTER_SDCC_4] = &xm_sdc4, - [MASTER_UFS_CARD] = &xm_ufs_card, - [MASTER_UFS_MEM] = &xm_ufs_mem, [MASTER_USB3_0] = &xm_usb3_0, [MASTER_USB3_1] = &xm_usb3_1, - [SLAVE_EBI1] = &ebi, + [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, + [SLAVE_ANOC_PCIE_SNOC] = &qns_pcie_snoc, + [SLAVE_SERVICE_A2NOC] = &srvc_aggre2_noc, +}; + +const static struct qcom_icc_desc sdm845_aggre2_noc = { + .nodes = aggre2_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre2_noc_nodes), + .bcms = aggre2_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre2_noc_bcms), +}; + +static struct qcom_icc_bcm *config_noc_bcms[] = { + &bcm_cn0, +}; + +static struct qcom_icc_node *config_noc_nodes[] = { + [MASTER_SPDM] = &qhm_spdm, + [MASTER_TIC] = &qhm_tic, + [MASTER_SNOC_CNOC] = &qnm_snoc, + [MASTER_QDSS_DAP] = &xm_qdss_dap, [SLAVE_A1NOC_CFG] = &qhs_a1_noc_cfg, [SLAVE_A2NOC_CFG] = &qhs_a2_noc_cfg, [SLAVE_AOP] = &qhs_aop, [SLAVE_AOSS] = &qhs_aoss, - [SLAVE_APPSS] = &qhs_apss, [SLAVE_CAMERA_CFG] = &qhs_camera_cfg, [SLAVE_CLK_CTL] = &qhs_clk_ctl, [SLAVE_CDSP_CFG] = &qhs_compute_dsp_cfg, @@ -386,9 +252,6 @@ static struct qcom_icc_node *rsc_hlos_nodes[] = { [SLAVE_GFX3D_CFG] = &qhs_gpuss_cfg, [SLAVE_IMEM_CFG] = &qhs_imem_cfg, [SLAVE_IPA_CFG] = &qhs_ipa, - [SLAVE_LLCC_CFG] = &qhs_llcc, - [SLAVE_MSS_PROC_MS_MPU_CFG] = &qhs_mdsp_ms_mpu_cfg, - [SLAVE_MEM_NOC_CFG] = &qhs_memnoc, [SLAVE_CNOC_MNOC_CFG] = &qhs_mnoc_cfg, [SLAVE_PCIE_0_CFG] = &qhs_pcie0_cfg, [SLAVE_PCIE_1_CFG] = &qhs_pcie_gen3_cfg, @@ -414,53 +277,122 @@ static struct qcom_icc_node *rsc_hlos_nodes[] = { [SLAVE_USB3_1] = &qhs_usb3_1, [SLAVE_VENUS_CFG] = &qhs_venus_cfg, [SLAVE_VSENSE_CTRL_CFG] = &qhs_vsense_ctrl_cfg, - [SLAVE_MNOC_SF_MEM_NOC] = &qns2_mem_noc, - [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, - [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, - [SLAVE_MEM_NOC_GNOC] = &qns_apps_io, - [SLAVE_CAMNOC_UNCOMP] = &qns_camnoc_uncomp, - [SLAVE_SNOC_CNOC] = &qns_cnoc, [SLAVE_CNOC_A2NOC] = &qns_cnoc_a2noc, + [SLAVE_SERVICE_CNOC] = &srvc_cnoc, +}; + +const static struct qcom_icc_desc sdm845_config_noc = { + .nodes = config_noc_nodes, + .num_nodes = ARRAY_SIZE(config_noc_nodes), + .bcms = config_noc_bcms, + .num_bcms = ARRAY_SIZE(config_noc_bcms), +}; + +static struct qcom_icc_bcm *dc_noc_bcms[] = { +}; + +static struct qcom_icc_node *dc_noc_nodes[] = { + [MASTER_CNOC_DC_NOC] = &qhm_cnoc, + [SLAVE_LLCC_CFG] = &qhs_llcc, + [SLAVE_MEM_NOC_CFG] = &qhs_memnoc, +}; + +const static struct qcom_icc_desc sdm845_dc_noc = { + .nodes = dc_noc_nodes, + .num_nodes = ARRAY_SIZE(dc_noc_nodes), + .bcms = dc_noc_bcms, + .num_bcms = ARRAY_SIZE(dc_noc_bcms), +}; + +static struct qcom_icc_bcm *gladiator_noc_bcms[] = { +}; + +static struct qcom_icc_node *gladiator_noc_nodes[] = { + [MASTER_APPSS_PROC] = &acm_l3, + [MASTER_GNOC_CFG] = &pm_gnoc_cfg, [SLAVE_GNOC_SNOC] = &qns_gladiator_sodv, [SLAVE_GNOC_MEM_NOC] = &qns_gnoc_memnoc, - [SLAVE_LLCC] = &qns_llcc, - [SLAVE_MNOC_HF_MEM_NOC] = &qns_mem_noc_hf, - [SLAVE_SNOC_MEM_NOC_GC] = &qns_memnoc_gc, - [SLAVE_SNOC_MEM_NOC_SF] = &qns_memnoc_sf, - [SLAVE_MEM_NOC_SNOC] = &qns_memnoc_snoc, - [SLAVE_ANOC_PCIE_A1NOC_SNOC] = &qns_pcie_a1noc_snoc, - [SLAVE_ANOC_PCIE_SNOC] = &qns_pcie_snoc, - [SLAVE_IMEM] = &qxs_imem, - [SLAVE_PCIE_0] = &qxs_pcie, - [SLAVE_PCIE_1] = &qxs_pcie_gen3, - [SLAVE_PIMEM] = &qxs_pimem, - [SLAVE_SERVICE_A1NOC] = &srvc_aggre1_noc, - [SLAVE_SERVICE_A2NOC] = &srvc_aggre2_noc, - [SLAVE_SERVICE_CNOC] = &srvc_cnoc, [SLAVE_SERVICE_GNOC] = &srvc_gnoc, - [SLAVE_SERVICE_MEM_NOC] = &srvc_memnoc, - [SLAVE_SERVICE_MNOC] = &srvc_mnoc, - [SLAVE_SERVICE_SNOC] = &srvc_snoc, - [SLAVE_QDSS_STM] = &xs_qdss_stm, - [SLAVE_TCU] = &xs_sys_tcu_cfg, }; -static struct qcom_icc_bcm *rsc_hlos_bcms[] = { - &bcm_acv, +const static struct qcom_icc_desc sdm845_gladiator_noc = { + .nodes = gladiator_noc_nodes, + .num_nodes = ARRAY_SIZE(gladiator_noc_nodes), + .bcms = gladiator_noc_bcms, + .num_bcms = ARRAY_SIZE(gladiator_noc_bcms), +}; + +static struct qcom_icc_bcm *mem_noc_bcms[] = { &bcm_mc0, + &bcm_acv, &bcm_sh0, - &bcm_mm0, &bcm_sh1, - &bcm_mm1, &bcm_sh2, - &bcm_mm2, &bcm_sh3, - &bcm_mm3, &bcm_sh5, +}; + +static struct qcom_icc_node *mem_noc_nodes[] = { + [MASTER_TCU_0] = &acm_tcu, + [MASTER_MEM_NOC_CFG] = &qhm_memnoc_cfg, + [MASTER_GNOC_MEM_NOC] = &qnm_apps, + [MASTER_MNOC_HF_MEM_NOC] = &qnm_mnoc_hf, + [MASTER_MNOC_SF_MEM_NOC] = &qnm_mnoc_sf, + [MASTER_SNOC_GC_MEM_NOC] = &qnm_snoc_gc, + [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf, + [MASTER_GFX3D] = &qxm_gpu, + [SLAVE_MSS_PROC_MS_MPU_CFG] = &qhs_mdsp_ms_mpu_cfg, + [SLAVE_MEM_NOC_GNOC] = &qns_apps_io, + [SLAVE_LLCC] = &qns_llcc, + [SLAVE_MEM_NOC_SNOC] = &qns_memnoc_snoc, + [SLAVE_SERVICE_MEM_NOC] = &srvc_memnoc, + [MASTER_LLCC] = &llcc_mc, + [SLAVE_EBI1] = &ebi, +}; + +const static struct qcom_icc_desc sdm845_mem_noc = { + .nodes = mem_noc_nodes, + .num_nodes = ARRAY_SIZE(mem_noc_nodes), + .bcms = mem_noc_bcms, + .num_bcms = ARRAY_SIZE(mem_noc_bcms), +}; + +static struct qcom_icc_bcm *mmss_noc_bcms[] = { + &bcm_mm0, + &bcm_mm1, + &bcm_mm2, + &bcm_mm3, +}; + +static struct qcom_icc_node *mmss_noc_nodes[] = { + [MASTER_CNOC_MNOC_CFG] = &qhm_mnoc_cfg, + [MASTER_CAMNOC_HF0] = &qxm_camnoc_hf0, + [MASTER_CAMNOC_HF1] = &qxm_camnoc_hf1, + [MASTER_CAMNOC_SF] = &qxm_camnoc_sf, + [MASTER_MDP0] = &qxm_mdp0, + [MASTER_MDP1] = &qxm_mdp1, + [MASTER_ROTATOR] = &qxm_rot, + [MASTER_VIDEO_P0] = &qxm_venus0, + [MASTER_VIDEO_P1] = &qxm_venus1, + [MASTER_VIDEO_PROC] = &qxm_venus_arm9, + [SLAVE_MNOC_SF_MEM_NOC] = &qns2_mem_noc, + [SLAVE_MNOC_HF_MEM_NOC] = &qns_mem_noc_hf, + [SLAVE_SERVICE_MNOC] = &srvc_mnoc, + [MASTER_CAMNOC_HF0_UNCOMP] = &qxm_camnoc_hf0_uncomp, + [MASTER_CAMNOC_HF1_UNCOMP] = &qxm_camnoc_hf1_uncomp, + [MASTER_CAMNOC_SF_UNCOMP] = &qxm_camnoc_sf_uncomp, + [SLAVE_CAMNOC_UNCOMP] = &qns_camnoc_uncomp, +}; + +const static struct qcom_icc_desc sdm845_mmss_noc = { + .nodes = mmss_noc_nodes, + .num_nodes = ARRAY_SIZE(mmss_noc_nodes), + .bcms = mmss_noc_bcms, + .num_bcms = ARRAY_SIZE(mmss_noc_bcms), +}; + +static struct qcom_icc_bcm *system_noc_bcms[] = { &bcm_sn0, - &bcm_ce0, - &bcm_cn0, - &bcm_qup0, &bcm_sn1, &bcm_sn2, &bcm_sn3, @@ -476,297 +408,34 @@ static struct qcom_icc_bcm *rsc_hlos_bcms[] = { &bcm_sn15, }; -static struct qcom_icc_desc sdm845_rsc_hlos = { - .nodes = rsc_hlos_nodes, - .num_nodes = ARRAY_SIZE(rsc_hlos_nodes), - .bcms = rsc_hlos_bcms, - .num_bcms = ARRAY_SIZE(rsc_hlos_bcms), +static struct qcom_icc_node *system_noc_nodes[] = { + [MASTER_SNOC_CFG] = &qhm_snoc_cfg, + [MASTER_A1NOC_SNOC] = &qnm_aggre1_noc, + [MASTER_A2NOC_SNOC] = &qnm_aggre2_noc, + [MASTER_GNOC_SNOC] = &qnm_gladiator_sodv, + [MASTER_MEM_NOC_SNOC] = &qnm_memnoc, + [MASTER_ANOC_PCIE_SNOC] = &qnm_pcie_anoc, + [MASTER_PIMEM] = &qxm_pimem, + [MASTER_GIC] = &xm_gic, + [SLAVE_APPSS] = &qhs_apss, + [SLAVE_SNOC_CNOC] = &qns_cnoc, + [SLAVE_SNOC_MEM_NOC_GC] = &qns_memnoc_gc, + [SLAVE_SNOC_MEM_NOC_SF] = &qns_memnoc_sf, + [SLAVE_IMEM] = &qxs_imem, + [SLAVE_PCIE_0] = &qxs_pcie, + [SLAVE_PCIE_1] = &qxs_pcie_gen3, + [SLAVE_PIMEM] = &qxs_pimem, + [SLAVE_SERVICE_SNOC] = &srvc_snoc, + [SLAVE_QDSS_STM] = &xs_qdss_stm, + [SLAVE_TCU] = &xs_sys_tcu_cfg, }; -static int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev) -{ - struct qcom_icc_node *qn; - const struct bcm_db *data; - size_t data_count; - int i; - - bcm->addr = cmd_db_read_addr(bcm->name); - if (!bcm->addr) { - dev_err(dev, "%s could not find RPMh address\n", - bcm->name); - return -EINVAL; - } - - data = cmd_db_read_aux_data(bcm->name, &data_count); - if (IS_ERR(data)) { - dev_err(dev, "%s command db read error (%ld)\n", - bcm->name, PTR_ERR(data)); - return PTR_ERR(data); - } - if (!data_count) { - dev_err(dev, "%s command db missing or partial aux data\n", - bcm->name); - return -EINVAL; - } - - bcm->aux_data.unit = le32_to_cpu(data->unit); - bcm->aux_data.width = le16_to_cpu(data->width); - bcm->aux_data.vcd = data->vcd; - bcm->aux_data.reserved = data->reserved; - - /* - * Link Qnodes to their respective BCMs - */ - for (i = 0; i < bcm->num_nodes; i++) { - qn = bcm->nodes[i]; - qn->bcms[qn->num_bcms] = bcm; - qn->num_bcms++; - } - - return 0; -} - -inline void tcs_cmd_gen(struct tcs_cmd *cmd, u64 vote_x, u64 vote_y, - u32 addr, bool commit) -{ - bool valid = true; - - if (!cmd) - return; - - if (vote_x == 0 && vote_y == 0) - valid = false; - - if (vote_x > BCM_TCS_CMD_VOTE_MASK) - vote_x = BCM_TCS_CMD_VOTE_MASK; - - if (vote_y > BCM_TCS_CMD_VOTE_MASK) - vote_y = BCM_TCS_CMD_VOTE_MASK; - - cmd->addr = addr; - cmd->data = BCM_TCS_CMD(commit, valid, vote_x, vote_y); - - /* - * Set the wait for completion flag on command that need to be completed - * before the next command. - */ - if (commit) - cmd->wait = true; -} - -static void tcs_list_gen(struct list_head *bcm_list, int bucket, - struct tcs_cmd tcs_list[SDM845_MAX_VCD], - int n[SDM845_MAX_VCD]) -{ - struct qcom_icc_bcm *bcm; - bool commit; - size_t idx = 0, batch = 0, cur_vcd_size = 0; - - memset(n, 0, sizeof(int) * SDM845_MAX_VCD); - - list_for_each_entry(bcm, bcm_list, list) { - commit = false; - cur_vcd_size++; - if ((list_is_last(&bcm->list, bcm_list)) || - bcm->aux_data.vcd != list_next_entry(bcm, list)->aux_data.vcd) { - commit = true; - cur_vcd_size = 0; - } - tcs_cmd_gen(&tcs_list[idx], bcm->vote_x[bucket], - bcm->vote_y[bucket], bcm->addr, commit); - idx++; - n[batch]++; - /* - * Batch the BCMs in such a way that we do not split them in - * multiple payloads when they are under the same VCD. This is - * to ensure that every BCM is committed since we only set the - * commit bit on the last BCM request of every VCD. - */ - if (n[batch] >= MAX_RPMH_PAYLOAD) { - if (!commit) { - n[batch] -= cur_vcd_size; - n[batch + 1] = cur_vcd_size; - } - batch++; - } - } -} - -static void bcm_aggregate(struct qcom_icc_bcm *bcm) -{ - size_t i, bucket; - u64 agg_avg[QCOM_ICC_NUM_BUCKETS] = {0}; - u64 agg_peak[QCOM_ICC_NUM_BUCKETS] = {0}; - u64 temp; - - for (bucket = 0; bucket < QCOM_ICC_NUM_BUCKETS; bucket++) { - for (i = 0; i < bcm->num_nodes; i++) { - temp = bcm->nodes[i]->sum_avg[bucket] * bcm->aux_data.width; - do_div(temp, bcm->nodes[i]->buswidth * bcm->nodes[i]->channels); - agg_avg[bucket] = max(agg_avg[bucket], temp); - - temp = bcm->nodes[i]->max_peak[bucket] * bcm->aux_data.width; - do_div(temp, bcm->nodes[i]->buswidth); - agg_peak[bucket] = max(agg_peak[bucket], temp); - } - - temp = agg_avg[bucket] * 1000ULL; - do_div(temp, bcm->aux_data.unit); - bcm->vote_x[bucket] = temp; - - temp = agg_peak[bucket] * 1000ULL; - do_div(temp, bcm->aux_data.unit); - bcm->vote_y[bucket] = temp; - } - - if (bcm->keepalive && bcm->vote_x[QCOM_ICC_BUCKET_AMC] == 0 && - bcm->vote_y[QCOM_ICC_BUCKET_AMC] == 0) { - bcm->vote_x[QCOM_ICC_BUCKET_AMC] = 1; - bcm->vote_x[QCOM_ICC_BUCKET_WAKE] = 1; - bcm->vote_y[QCOM_ICC_BUCKET_AMC] = 1; - bcm->vote_y[QCOM_ICC_BUCKET_WAKE] = 1; - } - - bcm->dirty = false; -} - -static void qcom_icc_pre_aggregate(struct icc_node *node) -{ - size_t i; - struct qcom_icc_node *qn; - - qn = node->data; - - for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { - qn->sum_avg[i] = 0; - qn->max_peak[i] = 0; - } -} - -static int qcom_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, - u32 peak_bw, u32 *agg_avg, u32 *agg_peak) -{ - size_t i; - struct qcom_icc_node *qn; - - qn = node->data; - - if (!tag) - tag = QCOM_ICC_TAG_ALWAYS; - - for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { - if (tag & BIT(i)) { - qn->sum_avg[i] += avg_bw; - qn->max_peak[i] = max_t(u32, qn->max_peak[i], peak_bw); - } - } - - *agg_avg += avg_bw; - *agg_peak = max_t(u32, *agg_peak, peak_bw); - - for (i = 0; i < qn->num_bcms; i++) - qn->bcms[i]->dirty = true; - - return 0; -} - -static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) -{ - struct qcom_icc_provider *qp; - struct icc_node *node; - struct tcs_cmd cmds[SDM845_MAX_BCMS]; - struct list_head commit_list; - int commit_idx[SDM845_MAX_VCD]; - int ret = 0, i; - - if (!src) - node = dst; - else - node = src; - - qp = to_qcom_provider(node->provider); - - INIT_LIST_HEAD(&commit_list); - - for (i = 0; i < qp->num_bcms; i++) { - if (qp->bcms[i]->dirty) { - bcm_aggregate(qp->bcms[i]); - list_add_tail(&qp->bcms[i]->list, &commit_list); - } - } - - /* - * Construct the command list based on a pre ordered list of BCMs - * based on VCD. - */ - tcs_list_gen(&commit_list, QCOM_ICC_BUCKET_AMC, cmds, commit_idx); - - if (!commit_idx[0]) - return ret; - - ret = rpmh_invalidate(qp->dev); - if (ret) { - pr_err("Error invalidating RPMH client (%d)\n", ret); - return ret; - } - - ret = rpmh_write_batch(qp->dev, RPMH_ACTIVE_ONLY_STATE, - cmds, commit_idx); - if (ret) { - pr_err("Error sending AMC RPMH requests (%d)\n", ret); - return ret; - } - - INIT_LIST_HEAD(&commit_list); - - for (i = 0; i < qp->num_bcms; i++) { - /* - * Only generate WAKE and SLEEP commands if a resource's - * requirements change as the execution environment transitions - * between different power states. - */ - if (qp->bcms[i]->vote_x[QCOM_ICC_BUCKET_WAKE] != - qp->bcms[i]->vote_x[QCOM_ICC_BUCKET_SLEEP] || - qp->bcms[i]->vote_y[QCOM_ICC_BUCKET_WAKE] != - qp->bcms[i]->vote_y[QCOM_ICC_BUCKET_SLEEP]) { - list_add_tail(&qp->bcms[i]->list, &commit_list); - } - } - - if (list_empty(&commit_list)) - return ret; - - tcs_list_gen(&commit_list, QCOM_ICC_BUCKET_WAKE, cmds, commit_idx); - - ret = rpmh_write_batch(qp->dev, RPMH_WAKE_ONLY_STATE, cmds, commit_idx); - if (ret) { - pr_err("Error sending WAKE RPMH requests (%d)\n", ret); - return ret; - } - - tcs_list_gen(&commit_list, QCOM_ICC_BUCKET_SLEEP, cmds, commit_idx); - - ret = rpmh_write_batch(qp->dev, RPMH_SLEEP_STATE, cmds, commit_idx); - if (ret) { - pr_err("Error sending SLEEP RPMH requests (%d)\n", ret); - return ret; - } - - return ret; -} - -static int cmp_vcd(const void *_l, const void *_r) -{ - const struct qcom_icc_bcm **l = (const struct qcom_icc_bcm **)_l; - const struct qcom_icc_bcm **r = (const struct qcom_icc_bcm **)_r; - - if (l[0]->aux_data.vcd < r[0]->aux_data.vcd) - return -1; - else if (l[0]->aux_data.vcd == r[0]->aux_data.vcd) - return 0; - else - return 1; -} +const static struct qcom_icc_desc sdm845_system_noc = { + .nodes = system_noc_nodes, + .num_nodes = ARRAY_SIZE(system_noc_nodes), + .bcms = system_noc_bcms, + .num_bcms = ARRAY_SIZE(system_noc_bcms), +}; static int qnoc_probe(struct platform_device *pdev) { @@ -779,7 +448,7 @@ static int qnoc_probe(struct platform_device *pdev) size_t num_nodes, i; int ret; - desc = of_device_get_match_data(&pdev->dev); + desc = device_get_match_data(&pdev->dev); if (!desc) return -EINVAL; @@ -808,6 +477,12 @@ static int qnoc_probe(struct platform_device *pdev) qp->bcms = desc->bcms; qp->num_bcms = desc->num_bcms; + qp->voter = of_bcm_voter_get(qp->dev, NULL); + if (IS_ERR(qp->voter)) { + dev_err(&pdev->dev, "bcm_voter err:%ld\n", PTR_ERR(qp->voter)); + return PTR_ERR(qp->voter); + } + ret = icc_provider_add(provider); if (ret) { dev_err(&pdev->dev, "error adding interconnect provider\n"); @@ -817,6 +492,9 @@ static int qnoc_probe(struct platform_device *pdev) for (i = 0; i < num_nodes; i++) { size_t j; + if (!qnodes[i]) + continue; + node = icc_node_create(qnodes[i]->id); if (IS_ERR(node)) { ret = PTR_ERR(node); @@ -827,10 +505,6 @@ static int qnoc_probe(struct platform_device *pdev) node->data = qnodes[i]; icc_node_add(node, provider); - dev_dbg(&pdev->dev, "registered node %p %s %d\n", node, - qnodes[i]->name, node->id); - - /* populate links */ for (j = 0; j < qnodes[i]->num_links; j++) icc_link_create(node, qnodes[i]->links[j]); @@ -841,19 +515,9 @@ static int qnoc_probe(struct platform_device *pdev) for (i = 0; i < qp->num_bcms; i++) qcom_icc_bcm_init(qp->bcms[i], &pdev->dev); - /* - * Pre sort the BCMs based on VCD for ease of generating a command list - * that groups the BCMs with the same VCD together. VCDs are numbered - * with lowest being the most expensive time wise, ensuring that - * those commands are being sent the earliest in the queue. - */ - sort(qp->bcms, qp->num_bcms, sizeof(*qp->bcms), cmp_vcd, NULL); - platform_set_drvdata(pdev, qp); - dev_dbg(&pdev->dev, "Registered SDM845 ICC\n"); - - return ret; + return 0; err: icc_nodes_remove(provider); icc_provider_del(provider); @@ -869,8 +533,23 @@ static int qnoc_remove(struct platform_device *pdev) } static const struct of_device_id qnoc_of_match[] = { - { .compatible = "qcom,sdm845-rsc-hlos", .data = &sdm845_rsc_hlos }, - { }, + { .compatible = "qcom,sdm845-aggre1-noc", + .data = &sdm845_aggre1_noc}, + { .compatible = "qcom,sdm845-aggre2-noc", + .data = &sdm845_aggre2_noc}, + { .compatible = "qcom,sdm845-config-noc", + .data = &sdm845_config_noc}, + { .compatible = "qcom,sdm845-dc-noc", + .data = &sdm845_dc_noc}, + { .compatible = "qcom,sdm845-gladiator-noc", + .data = &sdm845_gladiator_noc}, + { .compatible = "qcom,sdm845-mem-noc", + .data = &sdm845_mem_noc}, + { .compatible = "qcom,sdm845-mmss-noc", + .data = &sdm845_mmss_noc}, + { .compatible = "qcom,sdm845-system-noc", + .data = &sdm845_system_noc}, + { } }; MODULE_DEVICE_TABLE(of, qnoc_of_match); diff --git a/drivers/interconnect/qcom/sdm845.h b/drivers/interconnect/qcom/sdm845.h new file mode 100644 index 000000000000..776e9c2acb27 --- /dev/null +++ b/drivers/interconnect/qcom/sdm845.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __DRIVERS_INTERCONNECT_QCOM_SDM845_H__ +#define __DRIVERS_INTERCONNECT_QCOM_SDM845_H__ + +#define SDM845_MASTER_A1NOC_CFG 1 +#define SDM845_MASTER_BLSP_1 2 +#define SDM845_MASTER_TSIF 3 +#define SDM845_MASTER_SDCC_2 4 +#define SDM845_MASTER_SDCC_4 5 +#define SDM845_MASTER_UFS_CARD 6 +#define SDM845_MASTER_UFS_MEM 7 +#define SDM845_MASTER_PCIE_0 8 +#define SDM845_MASTER_A2NOC_CFG 9 +#define SDM845_MASTER_QDSS_BAM 10 +#define SDM845_MASTER_BLSP_2 11 +#define SDM845_MASTER_CNOC_A2NOC 12 +#define SDM845_MASTER_CRYPTO 13 +#define SDM845_MASTER_IPA 14 +#define SDM845_MASTER_PCIE_1 15 +#define SDM845_MASTER_QDSS_ETR 16 +#define SDM845_MASTER_USB3_0 17 +#define SDM845_MASTER_USB3_1 18 +#define SDM845_MASTER_CAMNOC_HF0_UNCOMP 19 +#define SDM845_MASTER_CAMNOC_HF1_UNCOMP 20 +#define SDM845_MASTER_CAMNOC_SF_UNCOMP 21 +#define SDM845_MASTER_SPDM 22 +#define SDM845_MASTER_TIC 23 +#define SDM845_MASTER_SNOC_CNOC 24 +#define SDM845_MASTER_QDSS_DAP 25 +#define SDM845_MASTER_CNOC_DC_NOC 26 +#define SDM845_MASTER_APPSS_PROC 27 +#define SDM845_MASTER_GNOC_CFG 28 +#define SDM845_MASTER_LLCC 29 +#define SDM845_MASTER_TCU_0 30 +#define SDM845_MASTER_MEM_NOC_CFG 31 +#define SDM845_MASTER_GNOC_MEM_NOC 32 +#define SDM845_MASTER_MNOC_HF_MEM_NOC 33 +#define SDM845_MASTER_MNOC_SF_MEM_NOC 34 +#define SDM845_MASTER_SNOC_GC_MEM_NOC 35 +#define SDM845_MASTER_SNOC_SF_MEM_NOC 36 +#define SDM845_MASTER_GFX3D 37 +#define SDM845_MASTER_CNOC_MNOC_CFG 38 +#define SDM845_MASTER_CAMNOC_HF0 39 +#define SDM845_MASTER_CAMNOC_HF1 40 +#define SDM845_MASTER_CAMNOC_SF 41 +#define SDM845_MASTER_MDP0 42 +#define SDM845_MASTER_MDP1 43 +#define SDM845_MASTER_ROTATOR 44 +#define SDM845_MASTER_VIDEO_P0 45 +#define SDM845_MASTER_VIDEO_P1 46 +#define SDM845_MASTER_VIDEO_PROC 47 +#define SDM845_MASTER_SNOC_CFG 48 +#define SDM845_MASTER_A1NOC_SNOC 49 +#define SDM845_MASTER_A2NOC_SNOC 50 +#define SDM845_MASTER_GNOC_SNOC 51 +#define SDM845_MASTER_MEM_NOC_SNOC 52 +#define SDM845_MASTER_ANOC_PCIE_SNOC 53 +#define SDM845_MASTER_PIMEM 54 +#define SDM845_MASTER_GIC 55 +#define SDM845_SLAVE_A1NOC_SNOC 56 +#define SDM845_SLAVE_SERVICE_A1NOC 57 +#define SDM845_SLAVE_ANOC_PCIE_A1NOC_SNOC 58 +#define SDM845_SLAVE_A2NOC_SNOC 59 +#define SDM845_SLAVE_ANOC_PCIE_SNOC 60 +#define SDM845_SLAVE_SERVICE_A2NOC 61 +#define SDM845_SLAVE_CAMNOC_UNCOMP 62 +#define SDM845_SLAVE_A1NOC_CFG 63 +#define SDM845_SLAVE_A2NOC_CFG 64 +#define SDM845_SLAVE_AOP 65 +#define SDM845_SLAVE_AOSS 66 +#define SDM845_SLAVE_CAMERA_CFG 67 +#define SDM845_SLAVE_CLK_CTL 68 +#define SDM845_SLAVE_CDSP_CFG 69 +#define SDM845_SLAVE_RBCPR_CX_CFG 70 +#define SDM845_SLAVE_CRYPTO_0_CFG 71 +#define SDM845_SLAVE_DCC_CFG 72 +#define SDM845_SLAVE_CNOC_DDRSS 73 +#define SDM845_SLAVE_DISPLAY_CFG 74 +#define SDM845_SLAVE_GLM 75 +#define SDM845_SLAVE_GFX3D_CFG 76 +#define SDM845_SLAVE_IMEM_CFG 77 +#define SDM845_SLAVE_IPA_CFG 78 +#define SDM845_SLAVE_CNOC_MNOC_CFG 79 +#define SDM845_SLAVE_PCIE_0_CFG 80 +#define SDM845_SLAVE_PCIE_1_CFG 81 +#define SDM845_SLAVE_PDM 82 +#define SDM845_SLAVE_SOUTH_PHY_CFG 83 +#define SDM845_SLAVE_PIMEM_CFG 84 +#define SDM845_SLAVE_PRNG 85 +#define SDM845_SLAVE_QDSS_CFG 86 +#define SDM845_SLAVE_BLSP_2 87 +#define SDM845_SLAVE_BLSP_1 88 +#define SDM845_SLAVE_SDCC_2 89 +#define SDM845_SLAVE_SDCC_4 90 +#define SDM845_SLAVE_SNOC_CFG 91 +#define SDM845_SLAVE_SPDM_WRAPPER 92 +#define SDM845_SLAVE_SPSS_CFG 93 +#define SDM845_SLAVE_TCSR 94 +#define SDM845_SLAVE_TLMM_NORTH 95 +#define SDM845_SLAVE_TLMM_SOUTH 96 +#define SDM845_SLAVE_TSIF 97 +#define SDM845_SLAVE_UFS_CARD_CFG 98 +#define SDM845_SLAVE_UFS_MEM_CFG 99 +#define SDM845_SLAVE_USB3_0 100 +#define SDM845_SLAVE_USB3_1 101 +#define SDM845_SLAVE_VENUS_CFG 102 +#define SDM845_SLAVE_VSENSE_CTRL_CFG 103 +#define SDM845_SLAVE_CNOC_A2NOC 104 +#define SDM845_SLAVE_SERVICE_CNOC 105 +#define SDM845_SLAVE_LLCC_CFG 106 +#define SDM845_SLAVE_MEM_NOC_CFG 107 +#define SDM845_SLAVE_GNOC_SNOC 108 +#define SDM845_SLAVE_GNOC_MEM_NOC 109 +#define SDM845_SLAVE_SERVICE_GNOC 110 +#define SDM845_SLAVE_EBI1 111 +#define SDM845_SLAVE_MSS_PROC_MS_MPU_CFG 112 +#define SDM845_SLAVE_MEM_NOC_GNOC 113 +#define SDM845_SLAVE_LLCC 114 +#define SDM845_SLAVE_MEM_NOC_SNOC 115 +#define SDM845_SLAVE_SERVICE_MEM_NOC 116 +#define SDM845_SLAVE_MNOC_SF_MEM_NOC 117 +#define SDM845_SLAVE_MNOC_HF_MEM_NOC 118 +#define SDM845_SLAVE_SERVICE_MNOC 119 +#define SDM845_SLAVE_APPSS 120 +#define SDM845_SLAVE_SNOC_CNOC 121 +#define SDM845_SLAVE_SNOC_MEM_NOC_GC 122 +#define SDM845_SLAVE_SNOC_MEM_NOC_SF 123 +#define SDM845_SLAVE_IMEM 124 +#define SDM845_SLAVE_PCIE_0 125 +#define SDM845_SLAVE_PCIE_1 126 +#define SDM845_SLAVE_PIMEM 127 +#define SDM845_SLAVE_SERVICE_SNOC 128 +#define SDM845_SLAVE_QDSS_STM 129 +#define SDM845_SLAVE_TCU 130 +#define SDM845_MASTER_OSM_L3_APPS 131 +#define SDM845_SLAVE_OSM_L3 132 + +#endif /* __DRIVERS_INTERCONNECT_QCOM_SDM845_H__ */ diff --git a/drivers/macintosh/ans-lcd.c b/drivers/macintosh/ans-lcd.c index b1314d104b06..b4821c751d04 100644 --- a/drivers/macintosh/ans-lcd.c +++ b/drivers/macintosh/ans-lcd.c @@ -142,7 +142,7 @@ const struct file_operations anslcd_fops = { }; static struct miscdevice anslcd_dev = { - ANSLCD_MINOR, + LCD_MINOR, "anslcd", &anslcd_fops }; diff --git a/drivers/macintosh/ans-lcd.h b/drivers/macintosh/ans-lcd.h index f0a6e4c68557..bca7d76d441b 100644 --- a/drivers/macintosh/ans-lcd.h +++ b/drivers/macintosh/ans-lcd.h @@ -2,8 +2,6 @@ #ifndef _PPC_ANS_LCD_H #define _PPC_ANS_LCD_H -#define ANSLCD_MINOR 156 - #define ANSLCD_CLEAR 0x01 #define ANSLCD_SENDCTRL 0x02 #define ANSLCD_SETSHORTDELAY 0x03 diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c index d38fb78a3b23..83eb05bf85ff 100644 --- a/drivers/macintosh/via-pmu.c +++ b/drivers/macintosh/via-pmu.c @@ -75,9 +75,6 @@ /* Some compile options */ #undef DEBUG_SLEEP -/* Misc minor number allocated for /dev/pmu */ -#define PMU_MINOR 154 - /* How many iterations between battery polls */ #define BATTERY_POLLING_COUNT 2 diff --git a/drivers/misc/cardreader/rts5227.c b/drivers/misc/cardreader/rts5227.c index 423fecc19fc4..3a9467aaa435 100644 --- a/drivers/misc/cardreader/rts5227.c +++ b/drivers/misc/cardreader/rts5227.c @@ -394,6 +394,7 @@ static const struct pcr_ops rts522a_pcr_ops = { void rts522a_init_params(struct rtsx_pcr *pcr) { rts5227_init_params(pcr); + pcr->ops = &rts522a_pcr_ops; pcr->tx_initial_phase = SET_CLOCK_PHASE(20, 20, 11); pcr->reg_pm_ctrl3 = RTS522A_PM_CTRL3; diff --git a/drivers/misc/habanalabs/command_submission.c b/drivers/misc/habanalabs/command_submission.c index 0bf08678431b..409276b6374d 100644 --- a/drivers/misc/habanalabs/command_submission.c +++ b/drivers/misc/habanalabs/command_submission.c @@ -129,6 +129,8 @@ static int cs_parser(struct hl_fpriv *hpriv, struct hl_cs_job *job) spin_unlock(&job->user_cb->lock); hl_cb_put(job->user_cb); job->user_cb = NULL; + } else if (!rc) { + job->job_cb_size = job->user_cb_size; } return rc; @@ -507,7 +509,7 @@ static int _hl_cs_ioctl(struct hl_fpriv *hpriv, void __user *chunks, struct hl_cb *cb; bool int_queues_only = true; u32 size_to_copy; - int rc, i, parse_cnt; + int rc, i; *cs_seq = ULLONG_MAX; @@ -547,7 +549,7 @@ static int _hl_cs_ioctl(struct hl_fpriv *hpriv, void __user *chunks, hl_debugfs_add_cs(cs); /* Validate ALL the CS chunks before submitting the CS */ - for (i = 0, parse_cnt = 0 ; i < num_chunks ; i++, parse_cnt++) { + for (i = 0 ; i < num_chunks ; i++) { struct hl_cs_chunk *chunk = &cs_chunk_array[i]; enum hl_queue_type queue_type; bool is_kernel_allocated_cb; @@ -585,10 +587,6 @@ static int _hl_cs_ioctl(struct hl_fpriv *hpriv, void __user *chunks, job->cs = cs; job->user_cb = cb; job->user_cb_size = chunk->cb_size; - if (is_kernel_allocated_cb) - job->job_cb_size = cb->size; - else - job->job_cb_size = chunk->cb_size; job->hw_queue_id = chunk->queue_index; cs->jobs_in_queue_cnt[job->hw_queue_id]++; @@ -659,8 +657,8 @@ int hl_cs_ioctl(struct hl_fpriv *hpriv, void *data) struct hl_device *hdev = hpriv->hdev; union hl_cs_args *args = data; struct hl_ctx *ctx = hpriv->ctx; - void __user *chunks; - u32 num_chunks; + void __user *chunks_execute, *chunks_restore; + u32 num_chunks_execute, num_chunks_restore; u64 cs_seq = ULONG_MAX; int rc, do_ctx_switch; bool need_soft_reset = false; @@ -673,13 +671,25 @@ int hl_cs_ioctl(struct hl_fpriv *hpriv, void *data) goto out; } + chunks_execute = (void __user *) (uintptr_t) args->in.chunks_execute; + num_chunks_execute = args->in.num_chunks_execute; + + if (!num_chunks_execute) { + dev_err(hdev->dev, + "Got execute CS with 0 chunks, context %d\n", + ctx->asid); + rc = -EINVAL; + goto out; + } + do_ctx_switch = atomic_cmpxchg(&ctx->thread_ctx_switch_token, 1, 0); if (do_ctx_switch || (args->in.cs_flags & HL_CS_FLAGS_FORCE_RESTORE)) { long ret; - chunks = (void __user *)(uintptr_t)args->in.chunks_restore; - num_chunks = args->in.num_chunks_restore; + chunks_restore = + (void __user *) (uintptr_t) args->in.chunks_restore; + num_chunks_restore = args->in.num_chunks_restore; mutex_lock(&hpriv->restore_phase_mutex); @@ -707,13 +717,13 @@ int hl_cs_ioctl(struct hl_fpriv *hpriv, void *data) hdev->asic_funcs->restore_phase_topology(hdev); - if (num_chunks == 0) { + if (!num_chunks_restore) { dev_dbg(hdev->dev, "Need to run restore phase but restore CS is empty\n"); rc = 0; } else { - rc = _hl_cs_ioctl(hpriv, chunks, num_chunks, - &cs_seq); + rc = _hl_cs_ioctl(hpriv, chunks_restore, + num_chunks_restore, &cs_seq); } mutex_unlock(&hpriv->restore_phase_mutex); @@ -726,7 +736,7 @@ int hl_cs_ioctl(struct hl_fpriv *hpriv, void *data) } /* Need to wait for restore completion before execution phase */ - if (num_chunks > 0) { + if (num_chunks_restore) { ret = _hl_cs_wait_ioctl(hdev, ctx, jiffies_to_usecs(hdev->timeout_jiffies), cs_seq); @@ -754,18 +764,7 @@ int hl_cs_ioctl(struct hl_fpriv *hpriv, void *data) } } - chunks = (void __user *)(uintptr_t)args->in.chunks_execute; - num_chunks = args->in.num_chunks_execute; - - if (num_chunks == 0) { - dev_err(hdev->dev, - "Got execute CS with 0 chunks, context %d\n", - ctx->asid); - rc = -EINVAL; - goto out; - } - - rc = _hl_cs_ioctl(hpriv, chunks, num_chunks, &cs_seq); + rc = _hl_cs_ioctl(hpriv, chunks_execute, num_chunks_execute, &cs_seq); out: if (rc != -EAGAIN) { diff --git a/drivers/misc/habanalabs/debugfs.c b/drivers/misc/habanalabs/debugfs.c index 20413e350343..756d36ed5d95 100644 --- a/drivers/misc/habanalabs/debugfs.c +++ b/drivers/misc/habanalabs/debugfs.c @@ -393,9 +393,10 @@ static int mmu_show(struct seq_file *s, void *data) } is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size, - prop->va_space_dram_start_address, - prop->va_space_dram_end_address); + prop->dmmu.start_addr, + prop->dmmu.end_addr); + /* shifts and masks are the same in PMMU and HPMMU, use one of them */ mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu; mutex_lock(&ctx->mmu_lock); @@ -547,12 +548,15 @@ static bool hl_is_device_va(struct hl_device *hdev, u64 addr) goto out; if (hdev->dram_supports_virtual_memory && - addr >= prop->va_space_dram_start_address && - addr < prop->va_space_dram_end_address) + (addr >= prop->dmmu.start_addr && addr < prop->dmmu.end_addr)) return true; - if (addr >= prop->va_space_host_start_address && - addr < prop->va_space_host_end_address) + if (addr >= prop->pmmu.start_addr && + addr < prop->pmmu.end_addr) + return true; + + if (addr >= prop->pmmu_huge.start_addr && + addr < prop->pmmu_huge.end_addr) return true; out: return false; @@ -575,9 +579,10 @@ static int device_va_to_pa(struct hl_device *hdev, u64 virt_addr, } is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size, - prop->va_space_dram_start_address, - prop->va_space_dram_end_address); + prop->dmmu.start_addr, + prop->dmmu.end_addr); + /* shifts and masks are the same in PMMU and HPMMU, use one of them */ mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu; mutex_lock(&ctx->mmu_lock); @@ -705,6 +710,65 @@ static ssize_t hl_data_write32(struct file *f, const char __user *buf, return count; } +static ssize_t hl_data_read64(struct file *f, char __user *buf, + size_t count, loff_t *ppos) +{ + struct hl_dbg_device_entry *entry = file_inode(f)->i_private; + struct hl_device *hdev = entry->hdev; + char tmp_buf[32]; + u64 addr = entry->addr; + u64 val; + ssize_t rc; + + if (*ppos) + return 0; + + if (hl_is_device_va(hdev, addr)) { + rc = device_va_to_pa(hdev, addr, &addr); + if (rc) + return rc; + } + + rc = hdev->asic_funcs->debugfs_read64(hdev, addr, &val); + if (rc) { + dev_err(hdev->dev, "Failed to read from 0x%010llx\n", addr); + return rc; + } + + sprintf(tmp_buf, "0x%016llx\n", val); + return simple_read_from_buffer(buf, count, ppos, tmp_buf, + strlen(tmp_buf)); +} + +static ssize_t hl_data_write64(struct file *f, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct hl_dbg_device_entry *entry = file_inode(f)->i_private; + struct hl_device *hdev = entry->hdev; + u64 addr = entry->addr; + u64 value; + ssize_t rc; + + rc = kstrtoull_from_user(buf, count, 16, &value); + if (rc) + return rc; + + if (hl_is_device_va(hdev, addr)) { + rc = device_va_to_pa(hdev, addr, &addr); + if (rc) + return rc; + } + + rc = hdev->asic_funcs->debugfs_write64(hdev, addr, value); + if (rc) { + dev_err(hdev->dev, "Failed to write 0x%016llx to 0x%010llx\n", + value, addr); + return rc; + } + + return count; +} + static ssize_t hl_get_power_state(struct file *f, char __user *buf, size_t count, loff_t *ppos) { @@ -912,6 +976,12 @@ static const struct file_operations hl_data32b_fops = { .write = hl_data_write32 }; +static const struct file_operations hl_data64b_fops = { + .owner = THIS_MODULE, + .read = hl_data_read64, + .write = hl_data_write64 +}; + static const struct file_operations hl_i2c_data_fops = { .owner = THIS_MODULE, .read = hl_i2c_data_read, @@ -1025,6 +1095,12 @@ void hl_debugfs_add_device(struct hl_device *hdev) dev_entry, &hl_data32b_fops); + debugfs_create_file("data64", + 0644, + dev_entry->root, + dev_entry, + &hl_data64b_fops); + debugfs_create_file("set_power_state", 0200, dev_entry->root, diff --git a/drivers/misc/habanalabs/device.c b/drivers/misc/habanalabs/device.c index b680b0caa69b..aef4de36b7aa 100644 --- a/drivers/misc/habanalabs/device.c +++ b/drivers/misc/habanalabs/device.c @@ -36,7 +36,7 @@ enum hl_device_status hl_device_status(struct hl_device *hdev) status = HL_DEVICE_STATUS_OPERATIONAL; return status; -}; +} static void hpriv_release(struct kref *ref) { diff --git a/drivers/misc/habanalabs/goya/goya.c b/drivers/misc/habanalabs/goya/goya.c index b8a8de24aaf7..68f065607544 100644 --- a/drivers/misc/habanalabs/goya/goya.c +++ b/drivers/misc/habanalabs/goya/goya.c @@ -324,7 +324,11 @@ static u32 goya_all_events[] = { GOYA_ASYNC_EVENT_ID_DMA_BM_CH1, GOYA_ASYNC_EVENT_ID_DMA_BM_CH2, GOYA_ASYNC_EVENT_ID_DMA_BM_CH3, - GOYA_ASYNC_EVENT_ID_DMA_BM_CH4 + GOYA_ASYNC_EVENT_ID_DMA_BM_CH4, + GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_S, + GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_E, + GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_S, + GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_E }; static int goya_mmu_clear_pgt_range(struct hl_device *hdev); @@ -393,19 +397,21 @@ void goya_get_fixed_properties(struct hl_device *hdev) prop->dmmu.hop2_mask = HOP2_MASK; prop->dmmu.hop3_mask = HOP3_MASK; prop->dmmu.hop4_mask = HOP4_MASK; - prop->dmmu.huge_page_size = PAGE_SIZE_2MB; + prop->dmmu.start_addr = VA_DDR_SPACE_START; + prop->dmmu.end_addr = VA_DDR_SPACE_END; + prop->dmmu.page_size = PAGE_SIZE_2MB; - /* No difference between PMMU and DMMU except of page size */ + /* shifts and masks are the same in PMMU and DMMU */ memcpy(&prop->pmmu, &prop->dmmu, sizeof(prop->dmmu)); - prop->dmmu.page_size = PAGE_SIZE_2MB; + prop->pmmu.start_addr = VA_HOST_SPACE_START; + prop->pmmu.end_addr = VA_HOST_SPACE_END; prop->pmmu.page_size = PAGE_SIZE_4KB; - prop->va_space_host_start_address = VA_HOST_SPACE_START; - prop->va_space_host_end_address = VA_HOST_SPACE_END; - prop->va_space_dram_start_address = VA_DDR_SPACE_START; - prop->va_space_dram_end_address = VA_DDR_SPACE_END; - prop->dram_size_for_default_page_mapping = - prop->va_space_dram_end_address; + /* PMMU and HPMMU are the same except of page size */ + memcpy(&prop->pmmu_huge, &prop->pmmu, sizeof(prop->pmmu)); + prop->pmmu_huge.page_size = PAGE_SIZE_2MB; + + prop->dram_size_for_default_page_mapping = VA_DDR_SPACE_END; prop->cfg_size = CFG_SIZE; prop->max_asid = MAX_ASID; prop->num_of_events = GOYA_ASYNC_EVENT_ID_SIZE; @@ -2573,8 +2579,7 @@ static int goya_hw_init(struct hl_device *hdev) * After CPU initialization is finished, change DDR bar mapping inside * iATU to point to the start address of the MMU page tables */ - if (goya_set_ddr_bar_base(hdev, DRAM_PHYS_BASE + - (MMU_PAGE_TABLES_ADDR & + if (goya_set_ddr_bar_base(hdev, (MMU_PAGE_TABLES_ADDR & ~(prop->dram_pci_bar_size - 0x1ull))) == U64_MAX) { dev_err(hdev->dev, "failed to map DDR bar to MMU page tables\n"); @@ -3443,12 +3448,13 @@ static int goya_validate_dma_pkt_mmu(struct hl_device *hdev, /* * WA for HW-23. * We can't allow user to read from Host using QMANs other than 1. + * PMMU and HPMMU addresses are equal, check only one of them. */ if (parser->hw_queue_id != GOYA_QUEUE_ID_DMA_1 && hl_mem_area_inside_range(le64_to_cpu(user_dma_pkt->src_addr), le32_to_cpu(user_dma_pkt->tsize), - hdev->asic_prop.va_space_host_start_address, - hdev->asic_prop.va_space_host_end_address)) { + hdev->asic_prop.pmmu.start_addr, + hdev->asic_prop.pmmu.end_addr)) { dev_err(hdev->dev, "Can't DMA from host on queue other then 1\n"); return -EFAULT; @@ -4178,6 +4184,96 @@ static int goya_debugfs_write32(struct hl_device *hdev, u64 addr, u32 val) return rc; } +static int goya_debugfs_read64(struct hl_device *hdev, u64 addr, u64 *val) +{ + struct asic_fixed_properties *prop = &hdev->asic_prop; + u64 ddr_bar_addr; + int rc = 0; + + if ((addr >= CFG_BASE) && (addr <= CFG_BASE + CFG_SIZE - sizeof(u64))) { + u32 val_l = RREG32(addr - CFG_BASE); + u32 val_h = RREG32(addr + sizeof(u32) - CFG_BASE); + + *val = (((u64) val_h) << 32) | val_l; + + } else if ((addr >= SRAM_BASE_ADDR) && + (addr <= SRAM_BASE_ADDR + SRAM_SIZE - sizeof(u64))) { + + *val = readq(hdev->pcie_bar[SRAM_CFG_BAR_ID] + + (addr - SRAM_BASE_ADDR)); + + } else if ((addr >= DRAM_PHYS_BASE) && + (addr <= + DRAM_PHYS_BASE + hdev->asic_prop.dram_size - sizeof(u64))) { + + u64 bar_base_addr = DRAM_PHYS_BASE + + (addr & ~(prop->dram_pci_bar_size - 0x1ull)); + + ddr_bar_addr = goya_set_ddr_bar_base(hdev, bar_base_addr); + if (ddr_bar_addr != U64_MAX) { + *val = readq(hdev->pcie_bar[DDR_BAR_ID] + + (addr - bar_base_addr)); + + ddr_bar_addr = goya_set_ddr_bar_base(hdev, + ddr_bar_addr); + } + if (ddr_bar_addr == U64_MAX) + rc = -EIO; + + } else if (addr >= HOST_PHYS_BASE && !iommu_present(&pci_bus_type)) { + *val = *(u64 *) phys_to_virt(addr - HOST_PHYS_BASE); + + } else { + rc = -EFAULT; + } + + return rc; +} + +static int goya_debugfs_write64(struct hl_device *hdev, u64 addr, u64 val) +{ + struct asic_fixed_properties *prop = &hdev->asic_prop; + u64 ddr_bar_addr; + int rc = 0; + + if ((addr >= CFG_BASE) && (addr <= CFG_BASE + CFG_SIZE - sizeof(u64))) { + WREG32(addr - CFG_BASE, lower_32_bits(val)); + WREG32(addr + sizeof(u32) - CFG_BASE, upper_32_bits(val)); + + } else if ((addr >= SRAM_BASE_ADDR) && + (addr <= SRAM_BASE_ADDR + SRAM_SIZE - sizeof(u64))) { + + writeq(val, hdev->pcie_bar[SRAM_CFG_BAR_ID] + + (addr - SRAM_BASE_ADDR)); + + } else if ((addr >= DRAM_PHYS_BASE) && + (addr <= + DRAM_PHYS_BASE + hdev->asic_prop.dram_size - sizeof(u64))) { + + u64 bar_base_addr = DRAM_PHYS_BASE + + (addr & ~(prop->dram_pci_bar_size - 0x1ull)); + + ddr_bar_addr = goya_set_ddr_bar_base(hdev, bar_base_addr); + if (ddr_bar_addr != U64_MAX) { + writeq(val, hdev->pcie_bar[DDR_BAR_ID] + + (addr - bar_base_addr)); + + ddr_bar_addr = goya_set_ddr_bar_base(hdev, + ddr_bar_addr); + } + if (ddr_bar_addr == U64_MAX) + rc = -EIO; + + } else if (addr >= HOST_PHYS_BASE && !iommu_present(&pci_bus_type)) { + *(u64 *) phys_to_virt(addr - HOST_PHYS_BASE) = val; + + } else { + rc = -EFAULT; + } + + return rc; +} + static u64 goya_read_pte(struct hl_device *hdev, u64 addr) { struct goya_device *goya = hdev->asic_specific; @@ -4297,6 +4393,14 @@ static const char *_goya_get_event_desc(u16 event_type) return "TPC%d_bmon_spmu"; case GOYA_ASYNC_EVENT_ID_DMA_BM_CH0 ... GOYA_ASYNC_EVENT_ID_DMA_BM_CH4: return "DMA_bm_ch%d"; + case GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_S: + return "POWER_ENV_S"; + case GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_E: + return "POWER_ENV_E"; + case GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_S: + return "THERMAL_ENV_S"; + case GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_E: + return "THERMAL_ENV_E"; default: return "N/A"; } @@ -4388,22 +4492,22 @@ static void goya_get_event_desc(u16 event_type, char *desc, size_t size) static void goya_print_razwi_info(struct hl_device *hdev) { if (RREG32(mmDMA_MACRO_RAZWI_LBW_WT_VLD)) { - dev_err(hdev->dev, "Illegal write to LBW\n"); + dev_err_ratelimited(hdev->dev, "Illegal write to LBW\n"); WREG32(mmDMA_MACRO_RAZWI_LBW_WT_VLD, 0); } if (RREG32(mmDMA_MACRO_RAZWI_LBW_RD_VLD)) { - dev_err(hdev->dev, "Illegal read from LBW\n"); + dev_err_ratelimited(hdev->dev, "Illegal read from LBW\n"); WREG32(mmDMA_MACRO_RAZWI_LBW_RD_VLD, 0); } if (RREG32(mmDMA_MACRO_RAZWI_HBW_WT_VLD)) { - dev_err(hdev->dev, "Illegal write to HBW\n"); + dev_err_ratelimited(hdev->dev, "Illegal write to HBW\n"); WREG32(mmDMA_MACRO_RAZWI_HBW_WT_VLD, 0); } if (RREG32(mmDMA_MACRO_RAZWI_HBW_RD_VLD)) { - dev_err(hdev->dev, "Illegal read from HBW\n"); + dev_err_ratelimited(hdev->dev, "Illegal read from HBW\n"); WREG32(mmDMA_MACRO_RAZWI_HBW_RD_VLD, 0); } } @@ -4423,7 +4527,8 @@ static void goya_print_mmu_error_info(struct hl_device *hdev) addr <<= 32; addr |= RREG32(mmMMU_PAGE_ERROR_CAPTURE_VA); - dev_err(hdev->dev, "MMU page fault on va 0x%llx\n", addr); + dev_err_ratelimited(hdev->dev, "MMU page fault on va 0x%llx\n", + addr); WREG32(mmMMU_PAGE_ERROR_CAPTURE, 0); } @@ -4435,7 +4540,7 @@ static void goya_print_irq_info(struct hl_device *hdev, u16 event_type, char desc[20] = ""; goya_get_event_desc(event_type, desc, sizeof(desc)); - dev_err(hdev->dev, "Received H/W interrupt %d [\"%s\"]\n", + dev_err_ratelimited(hdev->dev, "Received H/W interrupt %d [\"%s\"]\n", event_type, desc); if (razwi) { @@ -4526,6 +4631,33 @@ static int goya_unmask_irq(struct hl_device *hdev, u16 event_type) return rc; } +static void goya_print_clk_change_info(struct hl_device *hdev, u16 event_type) +{ + switch (event_type) { + case GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_S: + dev_info_ratelimited(hdev->dev, + "Clock throttling due to power consumption\n"); + break; + case GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_E: + dev_info_ratelimited(hdev->dev, + "Power envelop is safe, back to optimal clock\n"); + break; + case GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_S: + dev_info_ratelimited(hdev->dev, + "Clock throttling due to overheating\n"); + break; + case GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_E: + dev_info_ratelimited(hdev->dev, + "Thermal envelop is safe, back to optimal clock\n"); + break; + + default: + dev_err(hdev->dev, "Received invalid clock change event %d\n", + event_type); + break; + } +} + void goya_handle_eqe(struct hl_device *hdev, struct hl_eq_entry *eq_entry) { u32 ctl = le32_to_cpu(eq_entry->hdr.ctl); @@ -4609,6 +4741,14 @@ void goya_handle_eqe(struct hl_device *hdev, struct hl_eq_entry *eq_entry) goya_unmask_irq(hdev, event_type); break; + case GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_S: + case GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_E: + case GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_S: + case GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_E: + goya_print_clk_change_info(hdev, event_type); + goya_unmask_irq(hdev, event_type); + break; + default: dev_err(hdev->dev, "Received invalid H/W interrupt %d\n", event_type); @@ -4776,7 +4916,8 @@ static int goya_mmu_add_mappings_for_device_cpu(struct hl_device *hdev) for (off = 0 ; off < CPU_FW_IMAGE_SIZE ; off += PAGE_SIZE_2MB) { rc = hl_mmu_map(hdev->kernel_ctx, prop->dram_base_address + off, - prop->dram_base_address + off, PAGE_SIZE_2MB); + prop->dram_base_address + off, PAGE_SIZE_2MB, + (off + PAGE_SIZE_2MB) == CPU_FW_IMAGE_SIZE); if (rc) { dev_err(hdev->dev, "Map failed for address 0x%llx\n", prop->dram_base_address + off); @@ -4786,7 +4927,7 @@ static int goya_mmu_add_mappings_for_device_cpu(struct hl_device *hdev) if (!(hdev->cpu_accessible_dma_address & (PAGE_SIZE_2MB - 1))) { rc = hl_mmu_map(hdev->kernel_ctx, VA_CPU_ACCESSIBLE_MEM_ADDR, - hdev->cpu_accessible_dma_address, PAGE_SIZE_2MB); + hdev->cpu_accessible_dma_address, PAGE_SIZE_2MB, true); if (rc) { dev_err(hdev->dev, @@ -4799,7 +4940,7 @@ static int goya_mmu_add_mappings_for_device_cpu(struct hl_device *hdev) rc = hl_mmu_map(hdev->kernel_ctx, VA_CPU_ACCESSIBLE_MEM_ADDR + cpu_off, hdev->cpu_accessible_dma_address + cpu_off, - PAGE_SIZE_4KB); + PAGE_SIZE_4KB, true); if (rc) { dev_err(hdev->dev, "Map failed for CPU accessible memory\n"); @@ -4825,14 +4966,15 @@ unmap_cpu: for (; cpu_off >= 0 ; cpu_off -= PAGE_SIZE_4KB) if (hl_mmu_unmap(hdev->kernel_ctx, VA_CPU_ACCESSIBLE_MEM_ADDR + cpu_off, - PAGE_SIZE_4KB)) + PAGE_SIZE_4KB, true)) dev_warn_ratelimited(hdev->dev, "failed to unmap address 0x%llx\n", VA_CPU_ACCESSIBLE_MEM_ADDR + cpu_off); unmap: for (; off >= 0 ; off -= PAGE_SIZE_2MB) if (hl_mmu_unmap(hdev->kernel_ctx, - prop->dram_base_address + off, PAGE_SIZE_2MB)) + prop->dram_base_address + off, PAGE_SIZE_2MB, + true)) dev_warn_ratelimited(hdev->dev, "failed to unmap address 0x%llx\n", prop->dram_base_address + off); @@ -4857,14 +4999,15 @@ void goya_mmu_remove_device_cpu_mappings(struct hl_device *hdev) if (!(hdev->cpu_accessible_dma_address & (PAGE_SIZE_2MB - 1))) { if (hl_mmu_unmap(hdev->kernel_ctx, VA_CPU_ACCESSIBLE_MEM_ADDR, - PAGE_SIZE_2MB)) + PAGE_SIZE_2MB, true)) dev_warn(hdev->dev, "Failed to unmap CPU accessible memory\n"); } else { for (cpu_off = 0 ; cpu_off < SZ_2M ; cpu_off += PAGE_SIZE_4KB) if (hl_mmu_unmap(hdev->kernel_ctx, VA_CPU_ACCESSIBLE_MEM_ADDR + cpu_off, - PAGE_SIZE_4KB)) + PAGE_SIZE_4KB, + (cpu_off + PAGE_SIZE_4KB) >= SZ_2M)) dev_warn_ratelimited(hdev->dev, "failed to unmap address 0x%llx\n", VA_CPU_ACCESSIBLE_MEM_ADDR + cpu_off); @@ -4872,7 +5015,8 @@ void goya_mmu_remove_device_cpu_mappings(struct hl_device *hdev) for (off = 0 ; off < CPU_FW_IMAGE_SIZE ; off += PAGE_SIZE_2MB) if (hl_mmu_unmap(hdev->kernel_ctx, - prop->dram_base_address + off, PAGE_SIZE_2MB)) + prop->dram_base_address + off, PAGE_SIZE_2MB, + (off + PAGE_SIZE_2MB) >= CPU_FW_IMAGE_SIZE)) dev_warn_ratelimited(hdev->dev, "Failed to unmap address 0x%llx\n", prop->dram_base_address + off); @@ -5113,6 +5257,7 @@ static bool goya_is_device_idle(struct hl_device *hdev, u32 *mask, } static void goya_hw_queues_lock(struct hl_device *hdev) + __acquires(&goya->hw_queues_lock) { struct goya_device *goya = hdev->asic_specific; @@ -5120,6 +5265,7 @@ static void goya_hw_queues_lock(struct hl_device *hdev) } static void goya_hw_queues_unlock(struct hl_device *hdev) + __releases(&goya->hw_queues_lock) { struct goya_device *goya = hdev->asic_specific; @@ -5180,6 +5326,8 @@ static const struct hl_asic_funcs goya_funcs = { .restore_phase_topology = goya_restore_phase_topology, .debugfs_read32 = goya_debugfs_read32, .debugfs_write32 = goya_debugfs_write32, + .debugfs_read64 = goya_debugfs_read64, + .debugfs_write64 = goya_debugfs_write64, .add_device_attr = goya_add_device_attr, .handle_eqe = goya_handle_eqe, .set_pll_profile = goya_set_pll_profile, diff --git a/drivers/misc/habanalabs/goya/goya_coresight.c b/drivers/misc/habanalabs/goya/goya_coresight.c index c1ee6e2b5dff..a1bc930d904f 100644 --- a/drivers/misc/habanalabs/goya/goya_coresight.c +++ b/drivers/misc/habanalabs/goya/goya_coresight.c @@ -364,8 +364,8 @@ static int goya_etr_validate_address(struct hl_device *hdev, u64 addr, u64 range_start, range_end; if (hdev->mmu_enable) { - range_start = prop->va_space_dram_start_address; - range_end = prop->va_space_dram_end_address; + range_start = prop->dmmu.start_addr; + range_end = prop->dmmu.end_addr; } else { range_start = prop->dram_user_base_address; range_end = prop->dram_end_address; diff --git a/drivers/misc/habanalabs/goya/goya_hwmgr.c b/drivers/misc/habanalabs/goya/goya_hwmgr.c index b2ebc01e27f4..cdd4903e48fa 100644 --- a/drivers/misc/habanalabs/goya/goya_hwmgr.c +++ b/drivers/misc/habanalabs/goya/goya_hwmgr.c @@ -298,8 +298,8 @@ static ssize_t pm_mng_profile_store(struct device *dev, /* Make sure we are in LOW PLL when changing modes */ if (hdev->pm_mng_profile == PM_MANUAL) { hdev->curr_pll_profile = PLL_HIGH; - hl_device_set_frequency(hdev, PLL_LOW); hdev->pm_mng_profile = PM_AUTO; + hl_device_set_frequency(hdev, PLL_LOW); } } else if (strncmp("manual", buf, strlen("manual")) == 0) { if (hdev->pm_mng_profile == PM_AUTO) { diff --git a/drivers/misc/habanalabs/habanalabs.h b/drivers/misc/habanalabs/habanalabs.h index 00c949f4ccd1..31ebcf9458fe 100644 --- a/drivers/misc/habanalabs/habanalabs.h +++ b/drivers/misc/habanalabs/habanalabs.h @@ -132,6 +132,8 @@ enum hl_device_hw_state { /** * struct hl_mmu_properties - ASIC specific MMU address translation properties. + * @start_addr: virtual start address of the memory region. + * @end_addr: virtual end address of the memory region. * @hop0_shift: shift of hop 0 mask. * @hop1_shift: shift of hop 1 mask. * @hop2_shift: shift of hop 2 mask. @@ -143,9 +145,10 @@ enum hl_device_hw_state { * @hop3_mask: mask to get the PTE address in hop 3. * @hop4_mask: mask to get the PTE address in hop 4. * @page_size: default page size used to allocate memory. - * @huge_page_size: page size used to allocate memory with huge pages. */ struct hl_mmu_properties { + u64 start_addr; + u64 end_addr; u64 hop0_shift; u64 hop1_shift; u64 hop2_shift; @@ -157,7 +160,6 @@ struct hl_mmu_properties { u64 hop3_mask; u64 hop4_mask; u32 page_size; - u32 huge_page_size; }; /** @@ -169,6 +171,8 @@ struct hl_mmu_properties { * @preboot_ver: F/W Preboot version. * @dmmu: DRAM MMU address translation properties. * @pmmu: PCI (host) MMU address translation properties. + * @pmmu_huge: PCI (host) MMU address translation properties for memory + * allocated with huge pages. * @sram_base_address: SRAM physical start address. * @sram_end_address: SRAM physical end address. * @sram_user_base_address - SRAM physical start address for user access. @@ -178,14 +182,6 @@ struct hl_mmu_properties { * @dram_size: DRAM total size. * @dram_pci_bar_size: size of PCI bar towards DRAM. * @max_power_default: max power of the device after reset - * @va_space_host_start_address: base address of virtual memory range for - * mapping host memory. - * @va_space_host_end_address: end address of virtual memory range for - * mapping host memory. - * @va_space_dram_start_address: base address of virtual memory range for - * mapping DRAM memory. - * @va_space_dram_end_address: end address of virtual memory range for - * mapping DRAM memory. * @dram_size_for_default_page_mapping: DRAM size needed to map to avoid page * fault. * @pcie_dbi_base_address: Base address of the PCIE_DBI block. @@ -218,6 +214,7 @@ struct asic_fixed_properties { char preboot_ver[VERSION_MAX_LEN]; struct hl_mmu_properties dmmu; struct hl_mmu_properties pmmu; + struct hl_mmu_properties pmmu_huge; u64 sram_base_address; u64 sram_end_address; u64 sram_user_base_address; @@ -227,10 +224,6 @@ struct asic_fixed_properties { u64 dram_size; u64 dram_pci_bar_size; u64 max_power_default; - u64 va_space_host_start_address; - u64 va_space_host_end_address; - u64 va_space_dram_start_address; - u64 va_space_dram_end_address; u64 dram_size_for_default_page_mapping; u64 pcie_dbi_base_address; u64 pcie_aux_dbi_reg_addr; @@ -431,10 +424,12 @@ struct hl_eq { * enum hl_asic_type - supported ASIC types. * @ASIC_INVALID: Invalid ASIC type. * @ASIC_GOYA: Goya device. + * @ASIC_GAUDI: Gaudi device. */ enum hl_asic_type { ASIC_INVALID, - ASIC_GOYA + ASIC_GOYA, + ASIC_GAUDI }; struct hl_cs_parser; @@ -589,6 +584,8 @@ struct hl_asic_funcs { void (*restore_phase_topology)(struct hl_device *hdev); int (*debugfs_read32)(struct hl_device *hdev, u64 addr, u32 *val); int (*debugfs_write32)(struct hl_device *hdev, u64 addr, u32 val); + int (*debugfs_read64)(struct hl_device *hdev, u64 addr, u64 *val); + int (*debugfs_write64)(struct hl_device *hdev, u64 addr, u64 val); void (*add_device_attr)(struct hl_device *hdev, struct attribute_group *dev_attr_grp); void (*handle_eqe)(struct hl_device *hdev, @@ -658,6 +655,8 @@ struct hl_va_range { * this hits 0l. It is incremented on CS and CS_WAIT. * @cs_pending: array of DMA fence objects representing pending CS. * @host_va_range: holds available virtual addresses for host mappings. + * @host_huge_va_range: holds available virtual addresses for host mappings + * with huge pages. * @dram_va_range: holds available virtual addresses for DRAM mappings. * @mem_hash_lock: protects the mem_hash. * @mmu_lock: protects the MMU page tables. Any change to the PGT, modifing the @@ -688,8 +687,9 @@ struct hl_ctx { struct hl_device *hdev; struct kref refcount; struct dma_fence *cs_pending[HL_MAX_PENDING_CS]; - struct hl_va_range host_va_range; - struct hl_va_range dram_va_range; + struct hl_va_range *host_va_range; + struct hl_va_range *host_huge_va_range; + struct hl_va_range *dram_va_range; struct mutex mem_hash_lock; struct mutex mmu_lock; struct list_head debugfs_list; @@ -763,7 +763,7 @@ struct hl_userptr { * @aborted: true if CS was aborted due to some device error. */ struct hl_cs { - u8 jobs_in_queue_cnt[HL_MAX_QUEUES]; + u16 jobs_in_queue_cnt[HL_MAX_QUEUES]; struct hl_ctx *ctx; struct list_head job_list; spinlock_t job_lock; @@ -1291,6 +1291,8 @@ struct hl_device_idle_busy_ts { * otherwise. * @dram_supports_virtual_memory: is MMU enabled towards DRAM. * @dram_default_page_mapping: is DRAM default page mapping enabled. + * @pmmu_huge_range: is a different virtual addresses range used for PMMU with + * huge pages. * @init_done: is the initialization of the device done. * @mmu_enable: is MMU enabled. * @device_cpu_disabled: is the device CPU disabled (due to timeouts) @@ -1372,6 +1374,7 @@ struct hl_device { u8 reset_on_lockup; u8 dram_supports_virtual_memory; u8 dram_default_page_mapping; + u8 pmmu_huge_range; u8 init_done; u8 device_cpu_disabled; u8 dma_mask; @@ -1573,8 +1576,10 @@ int hl_mmu_init(struct hl_device *hdev); void hl_mmu_fini(struct hl_device *hdev); int hl_mmu_ctx_init(struct hl_ctx *ctx); void hl_mmu_ctx_fini(struct hl_ctx *ctx); -int hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, u32 page_size); -int hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, u32 page_size); +int hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, + u32 page_size, bool flush_pte); +int hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, u32 page_size, + bool flush_pte); void hl_mmu_swap_out(struct hl_ctx *ctx); void hl_mmu_swap_in(struct hl_ctx *ctx); @@ -1606,11 +1611,18 @@ int hl_pci_set_dma_mask(struct hl_device *hdev, u8 dma_mask); long hl_get_frequency(struct hl_device *hdev, u32 pll_index, bool curr); void hl_set_frequency(struct hl_device *hdev, u32 pll_index, u64 freq); -long hl_get_temperature(struct hl_device *hdev, int sensor_index, u32 attr); -long hl_get_voltage(struct hl_device *hdev, int sensor_index, u32 attr); -long hl_get_current(struct hl_device *hdev, int sensor_index, u32 attr); -long hl_get_fan_speed(struct hl_device *hdev, int sensor_index, u32 attr); -long hl_get_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr); +int hl_get_temperature(struct hl_device *hdev, + int sensor_index, u32 attr, long *value); +int hl_set_temperature(struct hl_device *hdev, + int sensor_index, u32 attr, long value); +int hl_get_voltage(struct hl_device *hdev, + int sensor_index, u32 attr, long *value); +int hl_get_current(struct hl_device *hdev, + int sensor_index, u32 attr, long *value); +int hl_get_fan_speed(struct hl_device *hdev, + int sensor_index, u32 attr, long *value); +int hl_get_pwm_info(struct hl_device *hdev, + int sensor_index, u32 attr, long *value); void hl_set_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr, long value); u64 hl_get_max_power(struct hl_device *hdev); diff --git a/drivers/misc/habanalabs/habanalabs_drv.c b/drivers/misc/habanalabs/habanalabs_drv.c index 8c342fb499ca..b670859c677a 100644 --- a/drivers/misc/habanalabs/habanalabs_drv.c +++ b/drivers/misc/habanalabs/habanalabs_drv.c @@ -40,12 +40,13 @@ MODULE_PARM_DESC(reset_on_lockup, #define PCI_VENDOR_ID_HABANALABS 0x1da3 #define PCI_IDS_GOYA 0x0001 +#define PCI_IDS_GAUDI 0x1000 static const struct pci_device_id ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_HABANALABS, PCI_IDS_GOYA), }, + { PCI_DEVICE(PCI_VENDOR_ID_HABANALABS, PCI_IDS_GAUDI), }, { 0, } }; -MODULE_DEVICE_TABLE(pci, ids); /* * get_asic_type - translate device id to asic type @@ -63,6 +64,9 @@ static enum hl_asic_type get_asic_type(u16 device) case PCI_IDS_GOYA: asic_type = ASIC_GOYA; break; + case PCI_IDS_GAUDI: + asic_type = ASIC_GAUDI; + break; default: asic_type = ASIC_INVALID; break; @@ -263,6 +267,11 @@ int create_hdev(struct hl_device **dev, struct pci_dev *pdev, dev_err(&pdev->dev, "Unsupported ASIC\n"); rc = -ENODEV; goto free_hdev; + } else if (hdev->asic_type == ASIC_GAUDI) { + dev_err(&pdev->dev, + "GAUDI is not supported by the current kernel\n"); + rc = -ENODEV; + goto free_hdev; } } else { hdev->asic_type = asic_type; diff --git a/drivers/misc/habanalabs/hwmon.c b/drivers/misc/habanalabs/hwmon.c index 7be4bace9b4f..a21a26e07c3b 100644 --- a/drivers/misc/habanalabs/hwmon.c +++ b/drivers/misc/habanalabs/hwmon.c @@ -113,6 +113,7 @@ static int hl_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct hl_device *hdev = dev_get_drvdata(dev); + int rc; if (hl_device_disabled_or_in_reset(hdev)) return -ENODEV; @@ -125,36 +126,40 @@ static int hl_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_temp_crit: case hwmon_temp_max_hyst: case hwmon_temp_crit_hyst: + case hwmon_temp_offset: + case hwmon_temp_highest: break; default: return -EINVAL; } - *val = hl_get_temperature(hdev, channel, attr); + rc = hl_get_temperature(hdev, channel, attr, val); break; case hwmon_in: switch (attr) { case hwmon_in_input: case hwmon_in_min: case hwmon_in_max: + case hwmon_in_highest: break; default: return -EINVAL; } - *val = hl_get_voltage(hdev, channel, attr); + rc = hl_get_voltage(hdev, channel, attr, val); break; case hwmon_curr: switch (attr) { case hwmon_curr_input: case hwmon_curr_min: case hwmon_curr_max: + case hwmon_curr_highest: break; default: return -EINVAL; } - *val = hl_get_current(hdev, channel, attr); + rc = hl_get_current(hdev, channel, attr, val); break; case hwmon_fan: switch (attr) { @@ -165,7 +170,7 @@ static int hl_read(struct device *dev, enum hwmon_sensor_types type, default: return -EINVAL; } - *val = hl_get_fan_speed(hdev, channel, attr); + rc = hl_get_fan_speed(hdev, channel, attr, val); break; case hwmon_pwm: switch (attr) { @@ -175,12 +180,12 @@ static int hl_read(struct device *dev, enum hwmon_sensor_types type, default: return -EINVAL; } - *val = hl_get_pwm_info(hdev, channel, attr); + rc = hl_get_pwm_info(hdev, channel, attr, val); break; default: return -EINVAL; } - return 0; + return rc; } static int hl_write(struct device *dev, enum hwmon_sensor_types type, @@ -192,6 +197,15 @@ static int hl_write(struct device *dev, enum hwmon_sensor_types type, return -ENODEV; switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_offset: + break; + default: + return -EINVAL; + } + hl_set_temperature(hdev, channel, attr, val); + break; case hwmon_pwm: switch (attr) { case hwmon_pwm_input: @@ -219,7 +233,10 @@ static umode_t hl_is_visible(const void *data, enum hwmon_sensor_types type, case hwmon_temp_max_hyst: case hwmon_temp_crit: case hwmon_temp_crit_hyst: + case hwmon_temp_highest: return 0444; + case hwmon_temp_offset: + return 0644; } break; case hwmon_in: @@ -227,6 +244,7 @@ static umode_t hl_is_visible(const void *data, enum hwmon_sensor_types type, case hwmon_in_input: case hwmon_in_min: case hwmon_in_max: + case hwmon_in_highest: return 0444; } break; @@ -235,6 +253,7 @@ static umode_t hl_is_visible(const void *data, enum hwmon_sensor_types type, case hwmon_curr_input: case hwmon_curr_min: case hwmon_curr_max: + case hwmon_curr_highest: return 0444; } break; @@ -265,10 +284,10 @@ static const struct hwmon_ops hl_hwmon_ops = { .write = hl_write }; -long hl_get_temperature(struct hl_device *hdev, int sensor_index, u32 attr) +int hl_get_temperature(struct hl_device *hdev, + int sensor_index, u32 attr, long *value) { struct armcp_packet pkt; - long result; int rc; memset(&pkt, 0, sizeof(pkt)); @@ -279,22 +298,47 @@ long hl_get_temperature(struct hl_device *hdev, int sensor_index, u32 attr) pkt.type = __cpu_to_le16(attr); rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), - SENSORS_PKT_TIMEOUT, &result); + SENSORS_PKT_TIMEOUT, value); if (rc) { dev_err(hdev->dev, "Failed to get temperature from sensor %d, error %d\n", sensor_index, rc); - result = 0; + *value = 0; } - return result; + return rc; } -long hl_get_voltage(struct hl_device *hdev, int sensor_index, u32 attr) +int hl_set_temperature(struct hl_device *hdev, + int sensor_index, u32 attr, long value) +{ + struct armcp_packet pkt; + int rc; + + memset(&pkt, 0, sizeof(pkt)); + + pkt.ctl = cpu_to_le32(ARMCP_PACKET_TEMPERATURE_SET << + ARMCP_PKT_CTL_OPCODE_SHIFT); + pkt.sensor_index = __cpu_to_le16(sensor_index); + pkt.type = __cpu_to_le16(attr); + pkt.value = __cpu_to_le64(value); + + rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), + SENSORS_PKT_TIMEOUT, NULL); + + if (rc) + dev_err(hdev->dev, + "Failed to set temperature of sensor %d, error %d\n", + sensor_index, rc); + + return rc; +} + +int hl_get_voltage(struct hl_device *hdev, + int sensor_index, u32 attr, long *value) { struct armcp_packet pkt; - long result; int rc; memset(&pkt, 0, sizeof(pkt)); @@ -305,22 +349,22 @@ long hl_get_voltage(struct hl_device *hdev, int sensor_index, u32 attr) pkt.type = __cpu_to_le16(attr); rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), - SENSORS_PKT_TIMEOUT, &result); + SENSORS_PKT_TIMEOUT, value); if (rc) { dev_err(hdev->dev, "Failed to get voltage from sensor %d, error %d\n", sensor_index, rc); - result = 0; + *value = 0; } - return result; + return rc; } -long hl_get_current(struct hl_device *hdev, int sensor_index, u32 attr) +int hl_get_current(struct hl_device *hdev, + int sensor_index, u32 attr, long *value) { struct armcp_packet pkt; - long result; int rc; memset(&pkt, 0, sizeof(pkt)); @@ -331,22 +375,22 @@ long hl_get_current(struct hl_device *hdev, int sensor_index, u32 attr) pkt.type = __cpu_to_le16(attr); rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), - SENSORS_PKT_TIMEOUT, &result); + SENSORS_PKT_TIMEOUT, value); if (rc) { dev_err(hdev->dev, "Failed to get current from sensor %d, error %d\n", sensor_index, rc); - result = 0; + *value = 0; } - return result; + return rc; } -long hl_get_fan_speed(struct hl_device *hdev, int sensor_index, u32 attr) +int hl_get_fan_speed(struct hl_device *hdev, + int sensor_index, u32 attr, long *value) { struct armcp_packet pkt; - long result; int rc; memset(&pkt, 0, sizeof(pkt)); @@ -357,22 +401,22 @@ long hl_get_fan_speed(struct hl_device *hdev, int sensor_index, u32 attr) pkt.type = __cpu_to_le16(attr); rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), - SENSORS_PKT_TIMEOUT, &result); + SENSORS_PKT_TIMEOUT, value); if (rc) { dev_err(hdev->dev, "Failed to get fan speed from sensor %d, error %d\n", sensor_index, rc); - result = 0; + *value = 0; } - return result; + return rc; } -long hl_get_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr) +int hl_get_pwm_info(struct hl_device *hdev, + int sensor_index, u32 attr, long *value) { struct armcp_packet pkt; - long result; int rc; memset(&pkt, 0, sizeof(pkt)); @@ -383,16 +427,16 @@ long hl_get_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr) pkt.type = __cpu_to_le16(attr); rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), - SENSORS_PKT_TIMEOUT, &result); + SENSORS_PKT_TIMEOUT, value); if (rc) { dev_err(hdev->dev, "Failed to get pwm info from sensor %d, error %d\n", sensor_index, rc); - result = 0; + *value = 0; } - return result; + return rc; } void hl_set_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr, diff --git a/drivers/misc/habanalabs/include/armcp_if.h b/drivers/misc/habanalabs/include/armcp_if.h index e4c6699a1868..bdd0a4c3a9cf 100644 --- a/drivers/misc/habanalabs/include/armcp_if.h +++ b/drivers/misc/habanalabs/include/armcp_if.h @@ -189,6 +189,10 @@ enum pq_init_status { * ArmCP to write to the structure, to prevent data corruption in case of * mismatched driver/FW versions. * + * ARMCP_PACKET_TEMPERATURE_SET - + * Set the value of the offset property of a specified thermal sensor. + * The packet's arguments specify the desired sensor and the field to + * set. */ enum armcp_packet_id { @@ -214,6 +218,8 @@ enum armcp_packet_id { ARMCP_PACKET_MAX_POWER_GET, /* sysfs */ ARMCP_PACKET_MAX_POWER_SET, /* sysfs */ ARMCP_PACKET_EEPROM_DATA_GET, /* sysfs */ + ARMCP_RESERVED, + ARMCP_PACKET_TEMPERATURE_SET, /* sysfs */ }; #define ARMCP_PACKET_FENCE_VAL 0xFE8CE7A5 @@ -271,24 +277,32 @@ enum armcp_packet_rc { armcp_packet_fault }; +/* + * armcp_temp_type should adhere to hwmon_temp_attributes + * defined in Linux kernel hwmon.h file + */ enum armcp_temp_type { armcp_temp_input, armcp_temp_max = 6, armcp_temp_max_hyst, armcp_temp_crit, - armcp_temp_crit_hyst + armcp_temp_crit_hyst, + armcp_temp_offset = 19, + armcp_temp_highest = 22 }; enum armcp_in_attributes { armcp_in_input, armcp_in_min, - armcp_in_max + armcp_in_max, + armcp_in_highest = 7 }; enum armcp_curr_attributes { armcp_curr_input, armcp_curr_min, - armcp_curr_max + armcp_curr_max, + armcp_curr_highest = 7 }; enum armcp_fan_attributes { diff --git a/drivers/misc/habanalabs/include/goya/goya_async_events.h b/drivers/misc/habanalabs/include/goya/goya_async_events.h index bb7a1aa3279e..5fb92362fc5f 100644 --- a/drivers/misc/habanalabs/include/goya/goya_async_events.h +++ b/drivers/misc/habanalabs/include/goya/goya_async_events.h @@ -188,6 +188,10 @@ enum goya_async_event_id { GOYA_ASYNC_EVENT_ID_HALT_MACHINE = 485, GOYA_ASYNC_EVENT_ID_INTS_REGISTER = 486, GOYA_ASYNC_EVENT_ID_SOFT_RESET = 487, + GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_S = 507, + GOYA_ASYNC_EVENT_ID_FIX_POWER_ENV_E = 508, + GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_S = 509, + GOYA_ASYNC_EVENT_ID_FIX_THERMAL_ENV_E = 510, GOYA_ASYNC_EVENT_ID_LAST_VALID_ID = 1023, GOYA_ASYNC_EVENT_ID_SIZE }; diff --git a/drivers/misc/habanalabs/include/goya/goya_reg_map.h b/drivers/misc/habanalabs/include/goya/goya_reg_map.h index cd89723c7f61..08061282cd9c 100644 --- a/drivers/misc/habanalabs/include/goya/goya_reg_map.h +++ b/drivers/misc/habanalabs/include/goya/goya_reg_map.h @@ -11,24 +11,27 @@ /* * PSOC scratch-pad registers */ -#define mmCPU_PQ_BASE_ADDR_LOW mmPSOC_GLOBAL_CONF_SCRATCHPAD_0 -#define mmCPU_PQ_BASE_ADDR_HIGH mmPSOC_GLOBAL_CONF_SCRATCHPAD_1 -#define mmCPU_EQ_BASE_ADDR_LOW mmPSOC_GLOBAL_CONF_SCRATCHPAD_2 -#define mmCPU_EQ_BASE_ADDR_HIGH mmPSOC_GLOBAL_CONF_SCRATCHPAD_3 -#define mmCPU_EQ_LENGTH mmPSOC_GLOBAL_CONF_SCRATCHPAD_4 -#define mmCPU_PQ_LENGTH mmPSOC_GLOBAL_CONF_SCRATCHPAD_5 -#define mmCPU_EQ_CI mmPSOC_GLOBAL_CONF_SCRATCHPAD_6 -#define mmCPU_PQ_INIT_STATUS mmPSOC_GLOBAL_CONF_SCRATCHPAD_7 -#define mmCPU_CQ_BASE_ADDR_LOW mmPSOC_GLOBAL_CONF_SCRATCHPAD_8 -#define mmCPU_CQ_BASE_ADDR_HIGH mmPSOC_GLOBAL_CONF_SCRATCHPAD_9 -#define mmCPU_CQ_LENGTH mmPSOC_GLOBAL_CONF_SCRATCHPAD_10 -#define mmUPD_STS mmPSOC_GLOBAL_CONF_SCRATCHPAD_26 -#define mmUPD_CMD mmPSOC_GLOBAL_CONF_SCRATCHPAD_27 -#define mmPREBOOT_VER_OFFSET mmPSOC_GLOBAL_CONF_SCRATCHPAD_28 -#define mmUBOOT_VER_OFFSET mmPSOC_GLOBAL_CONF_SCRATCHPAD_29 -#define mmUBOOT_OFFSET mmPSOC_GLOBAL_CONF_SCRATCHPAD_30 -#define mmBTL_ID mmPSOC_GLOBAL_CONF_SCRATCHPAD_31 +#define mmCPU_PQ_BASE_ADDR_LOW mmPSOC_GLOBAL_CONF_SCRATCHPAD_0 +#define mmCPU_PQ_BASE_ADDR_HIGH mmPSOC_GLOBAL_CONF_SCRATCHPAD_1 +#define mmCPU_EQ_BASE_ADDR_LOW mmPSOC_GLOBAL_CONF_SCRATCHPAD_2 +#define mmCPU_EQ_BASE_ADDR_HIGH mmPSOC_GLOBAL_CONF_SCRATCHPAD_3 +#define mmCPU_EQ_LENGTH mmPSOC_GLOBAL_CONF_SCRATCHPAD_4 +#define mmCPU_PQ_LENGTH mmPSOC_GLOBAL_CONF_SCRATCHPAD_5 +#define mmCPU_EQ_CI mmPSOC_GLOBAL_CONF_SCRATCHPAD_6 +#define mmCPU_PQ_INIT_STATUS mmPSOC_GLOBAL_CONF_SCRATCHPAD_7 +#define mmCPU_CQ_BASE_ADDR_LOW mmPSOC_GLOBAL_CONF_SCRATCHPAD_8 +#define mmCPU_CQ_BASE_ADDR_HIGH mmPSOC_GLOBAL_CONF_SCRATCHPAD_9 +#define mmCPU_CQ_LENGTH mmPSOC_GLOBAL_CONF_SCRATCHPAD_10 +#define mmCPU_BOOT_ERR0 mmPSOC_GLOBAL_CONF_SCRATCHPAD_24 +#define mmCPU_BOOT_ERR1 mmPSOC_GLOBAL_CONF_SCRATCHPAD_25 +#define mmUPD_STS mmPSOC_GLOBAL_CONF_SCRATCHPAD_26 +#define mmUPD_CMD mmPSOC_GLOBAL_CONF_SCRATCHPAD_27 +#define mmPREBOOT_VER_OFFSET mmPSOC_GLOBAL_CONF_SCRATCHPAD_28 +#define mmUBOOT_VER_OFFSET mmPSOC_GLOBAL_CONF_SCRATCHPAD_29 +#define mmRDWR_TEST mmPSOC_GLOBAL_CONF_SCRATCHPAD_30 +#define mmBTL_ID mmPSOC_GLOBAL_CONF_SCRATCHPAD_31 -#define mmHW_STATE mmPSOC_GLOBAL_CONF_APP_STATUS +#define mmHW_STATE mmPSOC_GLOBAL_CONF_APP_STATUS +#define mmPSOC_GLOBAL_CONF_CPU_BOOT_STATUS mmPSOC_GLOBAL_CONF_WARM_REBOOT #endif /* GOYA_REG_MAP_H_ */ diff --git a/drivers/misc/habanalabs/include/hl_boot_if.h b/drivers/misc/habanalabs/include/hl_boot_if.h index 2853a2de8cf6..f7992a69fd3a 100644 --- a/drivers/misc/habanalabs/include/hl_boot_if.h +++ b/drivers/misc/habanalabs/include/hl_boot_if.h @@ -8,20 +8,35 @@ #ifndef HL_BOOT_IF_H #define HL_BOOT_IF_H +#define LKD_HARD_RESET_MAGIC 0xED7BD694 + +/* CPU error bits in BOOT_ERROR registers */ +#define CPU_BOOT_ERR0_DRAM_INIT_FAIL (1 << 0) +#define CPU_BOOT_ERR0_FIT_CORRUPTED (1 << 1) +#define CPU_BOOT_ERR0_TS_INIT_FAIL (1 << 2) +#define CPU_BOOT_ERR0_DRAM_SKIPPED (1 << 3) +#define CPU_BOOT_ERR0_BMC_WAIT_SKIPPED (1 << 4) +#define CPU_BOOT_ERR0_NIC_DATA_NOT_RDY (1 << 5) +#define CPU_BOOT_ERR0_NIC_FW_FAIL (1 << 6) +#define CPU_BOOT_ERR0_ENABLED (1 << 31) + enum cpu_boot_status { CPU_BOOT_STATUS_NA = 0, /* Default value after reset of chip */ - CPU_BOOT_STATUS_IN_WFE, - CPU_BOOT_STATUS_DRAM_RDY, - CPU_BOOT_STATUS_SRAM_AVAIL, - CPU_BOOT_STATUS_IN_BTL, /* BTL is H/W FSM */ - CPU_BOOT_STATUS_IN_PREBOOT, - CPU_BOOT_STATUS_IN_SPL, - CPU_BOOT_STATUS_IN_UBOOT, - CPU_BOOT_STATUS_DRAM_INIT_FAIL, - CPU_BOOT_STATUS_FIT_CORRUPTED, - CPU_BOOT_STATUS_UBOOT_NOT_READY, - CPU_BOOT_STATUS_RESERVED, - CPU_BOOT_STATUS_TS_INIT_FAIL, + CPU_BOOT_STATUS_IN_WFE = 1, + CPU_BOOT_STATUS_DRAM_RDY = 2, + CPU_BOOT_STATUS_SRAM_AVAIL = 3, + CPU_BOOT_STATUS_IN_BTL = 4, /* BTL is H/W FSM */ + CPU_BOOT_STATUS_IN_PREBOOT = 5, + CPU_BOOT_STATUS_IN_SPL = 6, + CPU_BOOT_STATUS_IN_UBOOT = 7, + CPU_BOOT_STATUS_DRAM_INIT_FAIL, /* deprecated - will be removed */ + CPU_BOOT_STATUS_FIT_CORRUPTED, /* deprecated - will be removed */ + CPU_BOOT_STATUS_UBOOT_NOT_READY = 10, + CPU_BOOT_STATUS_NIC_FW_RDY = 11, + CPU_BOOT_STATUS_TS_INIT_FAIL, /* deprecated - will be removed */ + CPU_BOOT_STATUS_DRAM_SKIPPED, /* deprecated - will be removed */ + CPU_BOOT_STATUS_BMC_WAITING_SKIPPED, /* deprecated - will be removed */ + CPU_BOOT_STATUS_READY_TO_BOOT = 15, }; enum kmd_msg { diff --git a/drivers/misc/habanalabs/memory.c b/drivers/misc/habanalabs/memory.c index 6c72cb4eff54..a72f766ca470 100644 --- a/drivers/misc/habanalabs/memory.c +++ b/drivers/misc/habanalabs/memory.c @@ -530,7 +530,7 @@ static u64 get_va_block(struct hl_device *hdev, * or not, hence we continue with the biggest possible * granularity. */ - page_size = hdev->asic_prop.pmmu.huge_page_size; + page_size = hdev->asic_prop.pmmu_huge.page_size; else page_size = hdev->asic_prop.dmmu.page_size; @@ -638,13 +638,12 @@ static int init_phys_pg_pack_from_userptr(struct hl_ctx *ctx, struct hl_userptr *userptr, struct hl_vm_phys_pg_pack **pphys_pg_pack) { - struct hl_mmu_properties *mmu_prop = &ctx->hdev->asic_prop.pmmu; struct hl_vm_phys_pg_pack *phys_pg_pack; struct scatterlist *sg; dma_addr_t dma_addr; u64 page_mask, total_npages; u32 npages, page_size = PAGE_SIZE, - huge_page_size = mmu_prop->huge_page_size; + huge_page_size = ctx->hdev->asic_prop.pmmu_huge.page_size; bool first = true, is_huge_page_opt = true; int rc, i, j; u32 pgs_in_huge_page = huge_page_size >> __ffs(page_size); @@ -747,7 +746,8 @@ static int map_phys_pg_pack(struct hl_ctx *ctx, u64 vaddr, for (i = 0 ; i < phys_pg_pack->npages ; i++) { paddr = phys_pg_pack->pages[i]; - rc = hl_mmu_map(ctx, next_vaddr, paddr, page_size); + rc = hl_mmu_map(ctx, next_vaddr, paddr, page_size, + (i + 1) == phys_pg_pack->npages); if (rc) { dev_err(hdev->dev, "map failed for handle %u, npages: %llu, mapped: %llu", @@ -765,7 +765,8 @@ static int map_phys_pg_pack(struct hl_ctx *ctx, u64 vaddr, err: next_vaddr = vaddr; for (i = 0 ; i < mapped_pg_cnt ; i++) { - if (hl_mmu_unmap(ctx, next_vaddr, page_size)) + if (hl_mmu_unmap(ctx, next_vaddr, page_size, + (i + 1) == mapped_pg_cnt)) dev_warn_ratelimited(hdev->dev, "failed to unmap handle %u, va: 0x%llx, pa: 0x%llx, page size: %u\n", phys_pg_pack->handle, next_vaddr, @@ -794,7 +795,8 @@ static void unmap_phys_pg_pack(struct hl_ctx *ctx, u64 vaddr, next_vaddr = vaddr; for (i = 0 ; i < phys_pg_pack->npages ; i++, next_vaddr += page_size) { - if (hl_mmu_unmap(ctx, next_vaddr, page_size)) + if (hl_mmu_unmap(ctx, next_vaddr, page_size, + (i + 1) == phys_pg_pack->npages)) dev_warn_ratelimited(hdev->dev, "unmap failed for vaddr: 0x%llx\n", next_vaddr); @@ -853,6 +855,7 @@ static int map_device_va(struct hl_ctx *ctx, struct hl_mem_in *args, struct hl_vm_phys_pg_pack *phys_pg_pack; struct hl_userptr *userptr = NULL; struct hl_vm_hash_node *hnode; + struct hl_va_range *va_range; enum vm_type_t *vm_type; u64 ret_vaddr, hint_addr; u32 handle = 0; @@ -924,9 +927,16 @@ static int map_device_va(struct hl_ctx *ctx, struct hl_mem_in *args, goto hnode_err; } - ret_vaddr = get_va_block(hdev, - is_userptr ? &ctx->host_va_range : &ctx->dram_va_range, - phys_pg_pack->total_size, hint_addr, is_userptr); + if (is_userptr) + if (phys_pg_pack->page_size == hdev->asic_prop.pmmu.page_size) + va_range = ctx->host_va_range; + else + va_range = ctx->host_huge_va_range; + else + va_range = ctx->dram_va_range; + + ret_vaddr = get_va_block(hdev, va_range, phys_pg_pack->total_size, + hint_addr, is_userptr); if (!ret_vaddr) { dev_err(hdev->dev, "no available va block for handle %u\n", handle); @@ -965,10 +975,8 @@ static int map_device_va(struct hl_ctx *ctx, struct hl_mem_in *args, return 0; map_err: - if (add_va_block(hdev, - is_userptr ? &ctx->host_va_range : &ctx->dram_va_range, - ret_vaddr, - ret_vaddr + phys_pg_pack->total_size - 1)) + if (add_va_block(hdev, va_range, ret_vaddr, + ret_vaddr + phys_pg_pack->total_size - 1)) dev_warn(hdev->dev, "release va block failed for handle 0x%x, vaddr: 0x%llx\n", handle, ret_vaddr); @@ -1030,7 +1038,6 @@ static int unmap_device_va(struct hl_ctx *ctx, u64 vaddr, bool ctx_free) if (*vm_type == VM_TYPE_USERPTR) { is_userptr = true; - va_range = &ctx->host_va_range; userptr = hnode->ptr; rc = init_phys_pg_pack_from_userptr(ctx, userptr, &phys_pg_pack); @@ -1040,9 +1047,15 @@ static int unmap_device_va(struct hl_ctx *ctx, u64 vaddr, bool ctx_free) vaddr); goto vm_type_err; } + + if (phys_pg_pack->page_size == + hdev->asic_prop.pmmu.page_size) + va_range = ctx->host_va_range; + else + va_range = ctx->host_huge_va_range; } else if (*vm_type == VM_TYPE_PHYS_PACK) { is_userptr = false; - va_range = &ctx->dram_va_range; + va_range = ctx->dram_va_range; phys_pg_pack = hnode->ptr; } else { dev_warn(hdev->dev, @@ -1438,19 +1451,18 @@ bool hl_userptr_is_pinned(struct hl_device *hdev, u64 addr, } /* - * hl_va_range_init - initialize virtual addresses range - * - * @hdev : pointer to the habanalabs device structure - * @va_range : pointer to the range to initialize - * @start : range start address - * @end : range end address + * va_range_init - initialize virtual addresses range + * @hdev: pointer to the habanalabs device structure + * @va_range: pointer to the range to initialize + * @start: range start address + * @end: range end address * * This function does the following: * - Initializes the virtual addresses list of the given range with the given * addresses. */ -static int hl_va_range_init(struct hl_device *hdev, - struct hl_va_range *va_range, u64 start, u64 end) +static int va_range_init(struct hl_device *hdev, struct hl_va_range *va_range, + u64 start, u64 end) { int rc; @@ -1485,47 +1497,105 @@ static int hl_va_range_init(struct hl_device *hdev, } /* - * hl_vm_ctx_init_with_ranges - initialize virtual memory for context + * va_range_fini() - clear a virtual addresses range + * @hdev: pointer to the habanalabs structure + * va_range: pointer to virtual addresses range * - * @ctx : pointer to the habanalabs context structure - * @host_range_start : host virtual addresses range start - * @host_range_end : host virtual addresses range end - * @dram_range_start : dram virtual addresses range start - * @dram_range_end : dram virtual addresses range end + * This function does the following: + * - Frees the virtual addresses block list and its lock + */ +static void va_range_fini(struct hl_device *hdev, + struct hl_va_range *va_range) +{ + mutex_lock(&va_range->lock); + clear_va_list_locked(hdev, &va_range->list); + mutex_unlock(&va_range->lock); + + mutex_destroy(&va_range->lock); + kfree(va_range); +} + +/* + * vm_ctx_init_with_ranges() - initialize virtual memory for context + * @ctx: pointer to the habanalabs context structure + * @host_range_start: host virtual addresses range start. + * @host_range_end: host virtual addresses range end. + * @host_huge_range_start: host virtual addresses range start for memory + * allocated with huge pages. + * @host_huge_range_end: host virtual addresses range end for memory allocated + * with huge pages. + * @dram_range_start: dram virtual addresses range start. + * @dram_range_end: dram virtual addresses range end. * * This function initializes the following: * - MMU for context * - Virtual address to area descriptor hashtable * - Virtual block list of available virtual memory */ -static int hl_vm_ctx_init_with_ranges(struct hl_ctx *ctx, u64 host_range_start, - u64 host_range_end, u64 dram_range_start, - u64 dram_range_end) +static int vm_ctx_init_with_ranges(struct hl_ctx *ctx, + u64 host_range_start, + u64 host_range_end, + u64 host_huge_range_start, + u64 host_huge_range_end, + u64 dram_range_start, + u64 dram_range_end) { struct hl_device *hdev = ctx->hdev; int rc; + ctx->host_va_range = kzalloc(sizeof(*ctx->host_va_range), GFP_KERNEL); + if (!ctx->host_va_range) + return -ENOMEM; + + ctx->host_huge_va_range = kzalloc(sizeof(*ctx->host_huge_va_range), + GFP_KERNEL); + if (!ctx->host_huge_va_range) { + rc = -ENOMEM; + goto host_huge_va_range_err; + } + + ctx->dram_va_range = kzalloc(sizeof(*ctx->dram_va_range), GFP_KERNEL); + if (!ctx->dram_va_range) { + rc = -ENOMEM; + goto dram_va_range_err; + } + rc = hl_mmu_ctx_init(ctx); if (rc) { dev_err(hdev->dev, "failed to init context %d\n", ctx->asid); - return rc; + goto mmu_ctx_err; } mutex_init(&ctx->mem_hash_lock); hash_init(ctx->mem_hash); - mutex_init(&ctx->host_va_range.lock); + mutex_init(&ctx->host_va_range->lock); - rc = hl_va_range_init(hdev, &ctx->host_va_range, host_range_start, - host_range_end); + rc = va_range_init(hdev, ctx->host_va_range, host_range_start, + host_range_end); if (rc) { dev_err(hdev->dev, "failed to init host vm range\n"); - goto host_vm_err; + goto host_page_range_err; + } + + if (hdev->pmmu_huge_range) { + mutex_init(&ctx->host_huge_va_range->lock); + + rc = va_range_init(hdev, ctx->host_huge_va_range, + host_huge_range_start, + host_huge_range_end); + if (rc) { + dev_err(hdev->dev, + "failed to init host huge vm range\n"); + goto host_hpage_range_err; + } + } else { + ctx->host_huge_va_range = ctx->host_va_range; } - mutex_init(&ctx->dram_va_range.lock); + mutex_init(&ctx->dram_va_range->lock); - rc = hl_va_range_init(hdev, &ctx->dram_va_range, dram_range_start, + rc = va_range_init(hdev, ctx->dram_va_range, dram_range_start, dram_range_end); if (rc) { dev_err(hdev->dev, "failed to init dram vm range\n"); @@ -1537,15 +1607,29 @@ static int hl_vm_ctx_init_with_ranges(struct hl_ctx *ctx, u64 host_range_start, return 0; dram_vm_err: - mutex_destroy(&ctx->dram_va_range.lock); + mutex_destroy(&ctx->dram_va_range->lock); - mutex_lock(&ctx->host_va_range.lock); - clear_va_list_locked(hdev, &ctx->host_va_range.list); - mutex_unlock(&ctx->host_va_range.lock); -host_vm_err: - mutex_destroy(&ctx->host_va_range.lock); + if (hdev->pmmu_huge_range) { + mutex_lock(&ctx->host_huge_va_range->lock); + clear_va_list_locked(hdev, &ctx->host_huge_va_range->list); + mutex_unlock(&ctx->host_huge_va_range->lock); + } +host_hpage_range_err: + if (hdev->pmmu_huge_range) + mutex_destroy(&ctx->host_huge_va_range->lock); + mutex_lock(&ctx->host_va_range->lock); + clear_va_list_locked(hdev, &ctx->host_va_range->list); + mutex_unlock(&ctx->host_va_range->lock); +host_page_range_err: + mutex_destroy(&ctx->host_va_range->lock); mutex_destroy(&ctx->mem_hash_lock); hl_mmu_ctx_fini(ctx); +mmu_ctx_err: + kfree(ctx->dram_va_range); +dram_va_range_err: + kfree(ctx->host_huge_va_range); +host_huge_va_range_err: + kfree(ctx->host_va_range); return rc; } @@ -1553,8 +1637,8 @@ host_vm_err: int hl_vm_ctx_init(struct hl_ctx *ctx) { struct asic_fixed_properties *prop = &ctx->hdev->asic_prop; - u64 host_range_start, host_range_end, dram_range_start, - dram_range_end; + u64 host_range_start, host_range_end, host_huge_range_start, + host_huge_range_end, dram_range_start, dram_range_end; atomic64_set(&ctx->dram_phys_mem, 0); @@ -1566,38 +1650,26 @@ int hl_vm_ctx_init(struct hl_ctx *ctx) * address of the memory related to the given handle. */ if (ctx->hdev->mmu_enable) { - dram_range_start = prop->va_space_dram_start_address; - dram_range_end = prop->va_space_dram_end_address; - host_range_start = prop->va_space_host_start_address; - host_range_end = prop->va_space_host_end_address; + dram_range_start = prop->dmmu.start_addr; + dram_range_end = prop->dmmu.end_addr; + host_range_start = prop->pmmu.start_addr; + host_range_end = prop->pmmu.end_addr; + host_huge_range_start = prop->pmmu_huge.start_addr; + host_huge_range_end = prop->pmmu_huge.end_addr; } else { dram_range_start = prop->dram_user_base_address; dram_range_end = prop->dram_end_address; host_range_start = prop->dram_user_base_address; host_range_end = prop->dram_end_address; + host_huge_range_start = prop->dram_user_base_address; + host_huge_range_end = prop->dram_end_address; } - return hl_vm_ctx_init_with_ranges(ctx, host_range_start, host_range_end, - dram_range_start, dram_range_end); -} - -/* - * hl_va_range_fini - clear a virtual addresses range - * - * @hdev : pointer to the habanalabs structure - * va_range : pointer to virtual addresses range - * - * This function does the following: - * - Frees the virtual addresses block list and its lock - */ -static void hl_va_range_fini(struct hl_device *hdev, - struct hl_va_range *va_range) -{ - mutex_lock(&va_range->lock); - clear_va_list_locked(hdev, &va_range->list); - mutex_unlock(&va_range->lock); - - mutex_destroy(&va_range->lock); + return vm_ctx_init_with_ranges(ctx, host_range_start, host_range_end, + host_huge_range_start, + host_huge_range_end, + dram_range_start, + dram_range_end); } /* @@ -1664,8 +1736,10 @@ void hl_vm_ctx_fini(struct hl_ctx *ctx) } spin_unlock(&vm->idr_lock); - hl_va_range_fini(hdev, &ctx->dram_va_range); - hl_va_range_fini(hdev, &ctx->host_va_range); + va_range_fini(hdev, ctx->dram_va_range); + if (hdev->pmmu_huge_range) + va_range_fini(hdev, ctx->host_huge_va_range); + va_range_fini(hdev, ctx->host_va_range); mutex_destroy(&ctx->mem_hash_lock); hl_mmu_ctx_fini(ctx); diff --git a/drivers/misc/habanalabs/mmu.c b/drivers/misc/habanalabs/mmu.c index 6262b26e2086..a290d6b49d78 100644 --- a/drivers/misc/habanalabs/mmu.c +++ b/drivers/misc/habanalabs/mmu.c @@ -254,6 +254,15 @@ static inline u64 get_phys_addr(struct hl_ctx *ctx, u64 shadow_addr) return phys_hop_addr + pte_offset; } +static bool is_dram_va(struct hl_device *hdev, u64 virt_addr) +{ + struct asic_fixed_properties *prop = &hdev->asic_prop; + + return hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size, + prop->dmmu.start_addr, + prop->dmmu.end_addr); +} + static int dram_default_mapping_init(struct hl_ctx *ctx) { struct hl_device *hdev = ctx->hdev; @@ -548,6 +557,7 @@ static int _hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, bool is_dram_addr) curr_pte; bool is_huge, clear_hop3 = true; + /* shifts and masks are the same in PMMU and HPMMU, use one of them */ mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu; hop0_addr = get_hop0_addr(ctx); @@ -637,29 +647,27 @@ static int _hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, bool is_dram_addr) clear_hop3 = true; if (!clear_hop3) - goto flush; + goto mapped; clear_pte(ctx, hop3_pte_addr); if (put_pte(ctx, hop3_addr)) - goto flush; + goto mapped; clear_pte(ctx, hop2_pte_addr); if (put_pte(ctx, hop2_addr)) - goto flush; + goto mapped; clear_pte(ctx, hop1_pte_addr); if (put_pte(ctx, hop1_addr)) - goto flush; + goto mapped; clear_pte(ctx, hop0_pte_addr); } -flush: - flush(ctx); - +mapped: return 0; not_mapped: @@ -675,6 +683,7 @@ not_mapped: * @ctx: pointer to the context structure * @virt_addr: virt addr to map from * @page_size: size of the page to unmap + * @flush_pte: whether to do a PCI flush * * This function does the following: * - Check that the virt addr is mapped @@ -685,40 +694,43 @@ not_mapped: * changes the MMU hash, it must be protected by a lock. * However, because it maps only a single page, the lock should be implemented * in a higher level in order to protect the entire mapping of the memory area + * + * For optimization reasons PCI flush may be requested once after unmapping of + * large area. */ -int hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, u32 page_size) +int hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, u32 page_size, + bool flush_pte) { struct hl_device *hdev = ctx->hdev; struct asic_fixed_properties *prop = &hdev->asic_prop; struct hl_mmu_properties *mmu_prop; u64 real_virt_addr; u32 real_page_size, npages; - int i, rc; + int i, rc = 0; bool is_dram_addr; if (!hdev->mmu_enable) return 0; - is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size, - prop->va_space_dram_start_address, - prop->va_space_dram_end_address); + is_dram_addr = is_dram_va(hdev, virt_addr); - mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu; + if (is_dram_addr) + mmu_prop = &prop->dmmu; + else if ((page_size % prop->pmmu_huge.page_size) == 0) + mmu_prop = &prop->pmmu_huge; + else + mmu_prop = &prop->pmmu; /* * The H/W handles mapping of specific page sizes. Hence if the page * size is bigger, we break it to sub-pages and unmap them separately. */ - if ((page_size % mmu_prop->huge_page_size) == 0) { - real_page_size = mmu_prop->huge_page_size; - } else if ((page_size % mmu_prop->page_size) == 0) { + if ((page_size % mmu_prop->page_size) == 0) { real_page_size = mmu_prop->page_size; } else { dev_err(hdev->dev, - "page size of %u is not %uKB nor %uMB aligned, can't unmap\n", - page_size, - mmu_prop->page_size >> 10, - mmu_prop->huge_page_size >> 20); + "page size of %u is not %uKB aligned, can't unmap\n", + page_size, mmu_prop->page_size >> 10); return -EFAULT; } @@ -729,12 +741,15 @@ int hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, u32 page_size) for (i = 0 ; i < npages ; i++) { rc = _hl_mmu_unmap(ctx, real_virt_addr, is_dram_addr); if (rc) - return rc; + break; real_virt_addr += real_page_size; } - return 0; + if (flush_pte) + flush(ctx); + + return rc; } static int _hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, @@ -753,8 +768,6 @@ static int _hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, hop4_new = false, is_huge; int rc = -ENOMEM; - mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu; - /* * This mapping function can map a page or a huge page. For huge page * there are only 3 hops rather than 4. Currently the DRAM allocation @@ -762,11 +775,15 @@ static int _hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, * one of the two page sizes. Since this is a common code for all the * three cases, we need this hugs page check. */ - is_huge = page_size == mmu_prop->huge_page_size; - - if (is_dram_addr && !is_huge) { - dev_err(hdev->dev, "DRAM mapping should use huge pages only\n"); - return -EFAULT; + if (is_dram_addr) { + mmu_prop = &prop->dmmu; + is_huge = true; + } else if (page_size == prop->pmmu_huge.page_size) { + mmu_prop = &prop->pmmu_huge; + is_huge = true; + } else { + mmu_prop = &prop->pmmu; + is_huge = false; } hop0_addr = get_hop0_addr(ctx); @@ -885,8 +902,6 @@ static int _hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, get_pte(ctx, hop3_addr); } - flush(ctx); - return 0; err: @@ -909,6 +924,7 @@ err: * @virt_addr: virt addr to map from * @phys_addr: phys addr to map to * @page_size: physical page size + * @flush_pte: whether to do a PCI flush * * This function does the following: * - Check that the virt addr is not mapped @@ -919,8 +935,12 @@ err: * changes the MMU hash, it must be protected by a lock. * However, because it maps only a single page, the lock should be implemented * in a higher level in order to protect the entire mapping of the memory area + * + * For optimization reasons PCI flush may be requested once after mapping of + * large area. */ -int hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, u32 page_size) +int hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, u32 page_size, + bool flush_pte) { struct hl_device *hdev = ctx->hdev; struct asic_fixed_properties *prop = &hdev->asic_prop; @@ -933,26 +953,25 @@ int hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, u32 page_size) if (!hdev->mmu_enable) return 0; - is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size, - prop->va_space_dram_start_address, - prop->va_space_dram_end_address); + is_dram_addr = is_dram_va(hdev, virt_addr); - mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu; + if (is_dram_addr) + mmu_prop = &prop->dmmu; + else if ((page_size % prop->pmmu_huge.page_size) == 0) + mmu_prop = &prop->pmmu_huge; + else + mmu_prop = &prop->pmmu; /* * The H/W handles mapping of specific page sizes. Hence if the page * size is bigger, we break it to sub-pages and map them separately. */ - if ((page_size % mmu_prop->huge_page_size) == 0) { - real_page_size = mmu_prop->huge_page_size; - } else if ((page_size % mmu_prop->page_size) == 0) { + if ((page_size % mmu_prop->page_size) == 0) { real_page_size = mmu_prop->page_size; } else { dev_err(hdev->dev, - "page size of %u is not %dKB nor %dMB aligned, can't unmap\n", - page_size, - mmu_prop->page_size >> 10, - mmu_prop->huge_page_size >> 20); + "page size of %u is not %uKB aligned, can't unmap\n", + page_size, mmu_prop->page_size >> 10); return -EFAULT; } @@ -976,6 +995,9 @@ int hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, u32 page_size) mapped_cnt++; } + if (flush_pte) + flush(ctx); + return 0; err: @@ -988,6 +1010,8 @@ err: real_virt_addr += real_page_size; } + flush(ctx); + return rc; } diff --git a/drivers/misc/lkdtm/stackleak.c b/drivers/misc/lkdtm/stackleak.c index d5a084475abc..d1a5c0705be3 100644 --- a/drivers/misc/lkdtm/stackleak.c +++ b/drivers/misc/lkdtm/stackleak.c @@ -16,6 +16,7 @@ void lkdtm_STACKLEAK_ERASING(void) unsigned long *sp, left, found, i; const unsigned long check_depth = STACKLEAK_SEARCH_DEPTH / sizeof(unsigned long); + bool test_failed = false; /* * For the details about the alignment of the poison values, see @@ -34,7 +35,8 @@ void lkdtm_STACKLEAK_ERASING(void) left--; } else { pr_err("FAIL: not enough stack space for the test\n"); - return; + test_failed = true; + goto end; } pr_info("checking unused part of the thread stack (%lu bytes)...\n", @@ -52,22 +54,29 @@ void lkdtm_STACKLEAK_ERASING(void) } if (found <= check_depth) { - pr_err("FAIL: thread stack is not erased (checked %lu bytes)\n", + pr_err("FAIL: the erased part is not found (checked %lu bytes)\n", i * sizeof(unsigned long)); - return; + test_failed = true; + goto end; } - pr_info("first %lu bytes are unpoisoned\n", + pr_info("the erased part begins after %lu not poisoned bytes\n", (i - found) * sizeof(unsigned long)); /* The rest of thread stack should be erased */ for (; i < left; i++) { if (*(sp - i) != STACKLEAK_POISON) { - pr_err("FAIL: thread stack is NOT properly erased\n"); - return; + pr_err("FAIL: bad value number %lu in the erased part: 0x%lx\n", + i, *(sp - i)); + test_failed = true; } } - pr_info("OK: the rest of the thread stack is properly erased\n"); - return; +end: + if (test_failed) { + pr_err("FAIL: the thread stack is NOT properly erased\n"); + dump_stack(); + } else { + pr_info("OK: the rest of the thread stack is properly erased\n"); + } } diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 9ad9c01ddf41..910f059b3384 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -91,7 +91,7 @@ struct mkhi_rule_id { struct mkhi_fwcaps { struct mkhi_rule_id id; u8 len; - u8 data[0]; + u8 data[]; } __packed; struct mkhi_fw_ver_block { @@ -119,7 +119,7 @@ struct mkhi_msg_hdr { struct mkhi_msg { struct mkhi_msg_hdr hdr; - u8 data[0]; + u8 data[]; } __packed; #define MKHI_OSVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \ diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 1e3edbbacb1e..204d807e755b 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1585,7 +1585,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, goto err; } - hbuf_len = mei_slots2data(hbuf_slots); + hbuf_len = mei_slots2data(hbuf_slots) & MEI_MSG_MAX_LEN_MASK; dr_slots = mei_dma_ring_empty_slots(dev); dr_len = mei_slots2data(dr_slots); @@ -1718,7 +1718,7 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) goto out; } - hbuf_len = mei_slots2data(hbuf_slots); + hbuf_len = mei_slots2data(hbuf_slots) & MEI_MSG_MAX_LEN_MASK; dr_slots = mei_dma_ring_empty_slots(dev); dr_len = mei_slots2data(dr_slots); diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 87a0201ba6b3..9392934e3a06 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -75,9 +75,9 @@ #define MEI_DEV_ID_KBP_2 0xA2BB /* Kaby Point 2 */ #define MEI_DEV_ID_CNP_LP 0x9DE0 /* Cannon Point LP */ -#define MEI_DEV_ID_CNP_LP_4 0x9DE4 /* Cannon Point LP 4 (iTouch) */ +#define MEI_DEV_ID_CNP_LP_3 0x9DE4 /* Cannon Point LP 3 (iTouch) */ #define MEI_DEV_ID_CNP_H 0xA360 /* Cannon Point H */ -#define MEI_DEV_ID_CNP_H_4 0xA364 /* Cannon Point H 4 (iTouch) */ +#define MEI_DEV_ID_CNP_H_3 0xA364 /* Cannon Point H 3 (iTouch) */ #define MEI_DEV_ID_CMP_LP 0x02e0 /* Comet Point LP */ #define MEI_DEV_ID_CMP_LP_3 0x02e4 /* Comet Point LP 3 (iTouch) */ @@ -87,6 +87,8 @@ #define MEI_DEV_ID_CMP_H 0x06e0 /* Comet Lake H */ #define MEI_DEV_ID_CMP_H_3 0x06e4 /* Comet Lake H 3 (iTouch) */ +#define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index d025a5f8317e..b1a8d5ec88b3 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -209,11 +209,14 @@ struct mei_msg_hdr { u32 extension[0]; } __packed; +/* The length is up to 9 bits */ +#define MEI_MSG_MAX_LEN_MASK GENMASK(9, 0) + #define MEI_MSG_HDR_MAX 2 struct mei_bus_message { u8 hbm_cmd; - u8 data[0]; + u8 data[]; } __packed; /** diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 76f8ff5ff974..3a29db07211d 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -533,7 +533,7 @@ struct mei_device { #endif /* CONFIG_DEBUG_FS */ const struct mei_hw_ops *ops; - char hw[0] __aligned(sizeof(void *)); + char hw[] __aligned(sizeof(void *)); }; static inline unsigned long mei_secs_to_jiffies(unsigned long sec) diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 2711451b3d87..3d21c38e2dbb 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -1,25 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2003-2019, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2020, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/device.h> -#include <linux/fs.h> #include <linux/errno.h> #include <linux/types.h> -#include <linux/fcntl.h> #include <linux/pci.h> -#include <linux/poll.h> -#include <linux/ioctl.h> -#include <linux/cdev.h> #include <linux/sched.h> -#include <linux/uuid.h> -#include <linux/compat.h> -#include <linux/jiffies.h> #include <linux/interrupt.h> #include <linux/pm_domain.h> @@ -92,9 +83,9 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_KBP_2, MEI_ME_PCH8_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_CNP_LP, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_CNP_LP_4, MEI_ME_PCH8_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CNP_LP_3, MEI_ME_PCH8_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_CNP_H, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_CNP_H_4, MEI_ME_PCH8_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CNP_H_3, MEI_ME_PCH8_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_LP, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_LP_3, MEI_ME_PCH8_CFG)}, @@ -111,6 +102,8 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_MCC, MEI_ME_PCH15_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_MCC_4, MEI_ME_PCH8_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CDF, MEI_ME_PCH8_CFG)}, + /* required last entry */ {0, } }; diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index f1c16a587495..beacf2a2f2b5 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -1,20 +1,17 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2013-2017, Intel Corporation. All rights reserved. + * Copyright (c) 2013-2020, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/device.h> -#include <linux/fs.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/pci.h> #include <linux/init.h> #include <linux/sched.h> -#include <linux/uuid.h> -#include <linux/jiffies.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/pm_domain.h> diff --git a/drivers/misc/mic/host/mic_boot.c b/drivers/misc/mic/host/mic_boot.c index 4f2d9212432c..fb5b3989753d 100644 --- a/drivers/misc/mic/host/mic_boot.c +++ b/drivers/misc/mic/host/mic_boot.c @@ -137,7 +137,7 @@ static void *__mic_dma_alloc(struct device *dev, size_t size, struct scif_hw_dev *scdev = dev_get_drvdata(dev); struct mic_device *mdev = scdev_to_mdev(scdev); dma_addr_t tmp; - void *va = kmalloc(size, gfp | __GFP_ZERO); + void *va = kzalloc(size, gfp); if (va) { tmp = mic_map_single(mdev, va, size); diff --git a/drivers/misc/mic/host/mic_x100.c b/drivers/misc/mic/host/mic_x100.c index a7743312da9c..d18cda966912 100644 --- a/drivers/misc/mic/host/mic_x100.c +++ b/drivers/misc/mic/host/mic_x100.c @@ -350,10 +350,10 @@ mic_x100_load_command_line(struct mic_device *mdev, const struct firmware *fw) if (!buf) return -ENOMEM; - len += snprintf(buf, CMDLINE_SIZE - len, + len += scnprintf(buf, CMDLINE_SIZE - len, " mem=%dM", boot_mem); if (mdev->cosm_dev->cmdline) - snprintf(buf + len, CMDLINE_SIZE - len, " %s", + scnprintf(buf + len, CMDLINE_SIZE - len, " %s", mdev->cosm_dev->cmdline); memcpy_toio(cmd_line_va, buf, strlen(buf) + 1); kfree(buf); diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index e77d1b1f9d05..85c103923632 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -136,7 +136,7 @@ struct gru_dump_context_header { pid_t pid; unsigned long vaddr; int cch_locked; - unsigned long data[0]; + unsigned long data[]; }; /* diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index a7e44b2eb413..5ce8f3081e96 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -372,7 +372,7 @@ struct gru_thread_state { int ts_data_valid; /* Indicates if ts_gdata has valid data */ struct gru_gseg_statistics ustats; /* User statistics */ - unsigned long ts_gdata[0]; /* save area for GRU data (CB, + unsigned long ts_gdata[]; /* save area for GRU data (CB, DS, CBE) */ }; diff --git a/drivers/misc/vexpress-syscfg.c b/drivers/misc/vexpress-syscfg.c index 058fcd7f9f01..a431787c0898 100644 --- a/drivers/misc/vexpress-syscfg.c +++ b/drivers/misc/vexpress-syscfg.c @@ -42,7 +42,7 @@ struct vexpress_syscfg_func { struct vexpress_syscfg *syscfg; struct regmap *regmap; int num_templates; - u32 template[0]; /* Keep it last! */ + u32 template[]; /* Keep it last! */ }; diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 35efab1ba8d9..d7b7f6d688e7 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -55,6 +55,18 @@ config NVMEM_IMX_OCOTP_SCU This is a driver for the SCU On-Chip OTP Controller (OCOTP) available on i.MX8 SoCs. +config JZ4780_EFUSE + tristate "JZ4780 EFUSE Memory Support" + depends on MACH_INGENIC || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + select REGMAP_MMIO + help + Say Y here to include support for JZ4780 efuse memory found on + all JZ4780 SoC based devices. + To compile this driver as a module, choose M here: the module + will be called nvmem_jz4780_efuse. + config NVMEM_LPC18XX_EEPROM tristate "NXP LPC18XX EEPROM Memory Support" depends on ARCH_LPC18XX || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 6b466cd1427b..a7c377218341 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -6,9 +6,6 @@ obj-$(CONFIG_NVMEM) += nvmem_core.o nvmem_core-y := core.o -obj-$(CONFIG_NVMEM_SYSFS) += nvmem_sysfs.o -nvmem_sysfs-y := nvmem-sysfs.o - # Devices obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o nvmem-bcm-ocotp-y := bcm-ocotp.o @@ -18,6 +15,8 @@ obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o nvmem-imx-ocotp-y := imx-ocotp.o obj-$(CONFIG_NVMEM_IMX_OCOTP_SCU) += nvmem-imx-ocotp-scu.o nvmem-imx-ocotp-scu-y := imx-ocotp-scu.o +obj-$(CONFIG_JZ4780_EFUSE) += nvmem_jz4780_efuse.o +nvmem_jz4780_efuse-y := jz4780-efuse.o obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index ef326f243f36..05c6ae4b0b97 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -18,7 +18,31 @@ #include <linux/gpio/consumer.h> #include <linux/of.h> #include <linux/slab.h> -#include "nvmem.h" + +struct nvmem_device { + struct module *owner; + struct device dev; + int stride; + int word_size; + int id; + struct kref refcnt; + size_t size; + bool read_only; + bool root_only; + int flags; + enum nvmem_type type; + struct bin_attribute eeprom; + struct device *base_dev; + struct list_head cells; + nvmem_reg_read_t reg_read; + nvmem_reg_write_t reg_write; + struct gpio_desc *wp_gpio; + void *priv; +}; + +#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) + +#define FLAG_COMPAT BIT(0) struct nvmem_cell { const char *name; @@ -42,6 +66,250 @@ static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); +#ifdef CONFIG_NVMEM_SYSFS +static const char * const nvmem_type_str[] = { + [NVMEM_TYPE_UNKNOWN] = "Unknown", + [NVMEM_TYPE_EEPROM] = "EEPROM", + [NVMEM_TYPE_OTP] = "OTP", + [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed", +}; + +#ifdef CONFIG_DEBUG_LOCK_ALLOC +static struct lock_class_key eeprom_lock_key; +#endif + +static ssize_t type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return sprintf(buf, "%s\n", nvmem_type_str[nvmem->type]); +} + +static DEVICE_ATTR_RO(type); + +static struct attribute *nvmem_attrs[] = { + &dev_attr_type.attr, + NULL, +}; + +static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct device *dev; + struct nvmem_device *nvmem; + int rc; + + if (attr->private) + dev = attr->private; + else + dev = container_of(kobj, struct device, kobj); + nvmem = to_nvmem_device(dev); + + /* Stop the user from reading */ + if (pos >= nvmem->size) + return 0; + + if (count < nvmem->word_size) + return -EINVAL; + + if (pos + count > nvmem->size) + count = nvmem->size - pos; + + count = round_down(count, nvmem->word_size); + + if (!nvmem->reg_read) + return -EPERM; + + rc = nvmem->reg_read(nvmem->priv, pos, buf, count); + + if (rc) + return rc; + + return count; +} + +static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct device *dev; + struct nvmem_device *nvmem; + int rc; + + if (attr->private) + dev = attr->private; + else + dev = container_of(kobj, struct device, kobj); + nvmem = to_nvmem_device(dev); + + /* Stop the user from writing */ + if (pos >= nvmem->size) + return -EFBIG; + + if (count < nvmem->word_size) + return -EINVAL; + + if (pos + count > nvmem->size) + count = nvmem->size - pos; + + count = round_down(count, nvmem->word_size); + + if (!nvmem->reg_write) + return -EPERM; + + rc = nvmem->reg_write(nvmem->priv, pos, buf, count); + + if (rc) + return rc; + + return count; +} + +static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, + struct bin_attribute *attr, int i) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + umode_t mode = 0400; + + if (!nvmem->root_only) + mode |= 0044; + + if (!nvmem->read_only) + mode |= 0200; + + if (!nvmem->reg_write) + mode &= ~0200; + + if (!nvmem->reg_read) + mode &= ~0444; + + return mode; +} + +/* default read/write permissions */ +static struct bin_attribute bin_attr_rw_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0644, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +static struct bin_attribute *nvmem_bin_attributes[] = { + &bin_attr_rw_nvmem, + NULL, +}; + +static const struct attribute_group nvmem_bin_group = { + .bin_attrs = nvmem_bin_attributes, + .attrs = nvmem_attrs, + .is_bin_visible = nvmem_bin_attr_is_visible, +}; + +static const struct attribute_group *nvmem_dev_groups[] = { + &nvmem_bin_group, + NULL, +}; + +/* read only permission */ +static struct bin_attribute bin_attr_ro_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0444, + }, + .read = bin_attr_nvmem_read, +}; + +/* default read/write permissions, root only */ +static struct bin_attribute bin_attr_rw_root_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0600, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +/* read only permission, root only */ +static struct bin_attribute bin_attr_ro_root_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0400, + }, + .read = bin_attr_nvmem_read, +}; + +/* + * nvmem_setup_compat() - Create an additional binary entry in + * drivers sys directory, to be backwards compatible with the older + * drivers/misc/eeprom drivers. + */ +static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ + int rval; + + if (!config->compat) + return 0; + + if (!config->base_dev) + return -EINVAL; + + if (nvmem->read_only) { + if (config->root_only) + nvmem->eeprom = bin_attr_ro_root_nvmem; + else + nvmem->eeprom = bin_attr_ro_nvmem; + } else { + if (config->root_only) + nvmem->eeprom = bin_attr_rw_root_nvmem; + else + nvmem->eeprom = bin_attr_rw_nvmem; + } + nvmem->eeprom.attr.name = "eeprom"; + nvmem->eeprom.size = nvmem->size; +#ifdef CONFIG_DEBUG_LOCK_ALLOC + nvmem->eeprom.attr.key = &eeprom_lock_key; +#endif + nvmem->eeprom.private = &nvmem->dev; + nvmem->base_dev = config->base_dev; + + rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); + if (rval) { + dev_err(&nvmem->dev, + "Failed to create eeprom binary file %d\n", rval); + return rval; + } + + nvmem->flags |= FLAG_COMPAT; + + return 0; +} + +static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ + if (config->compat) + device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); +} + +#else /* CONFIG_NVMEM_SYSFS */ + +static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ + return -ENOSYS; +} +static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ +} + +#endif /* CONFIG_NVMEM_SYSFS */ static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) @@ -72,6 +340,7 @@ static void nvmem_release(struct device *dev) struct nvmem_device *nvmem = to_nvmem_device(dev); ida_simple_remove(&nvmem_ida, nvmem->id); + gpiod_put(nvmem->wp_gpio); kfree(nvmem); } @@ -338,6 +607,9 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) if (!config->dev) return ERR_PTR(-EINVAL); + if (!config->reg_read && !config->reg_write) + return ERR_PTR(-EINVAL); + nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL); if (!nvmem) return ERR_PTR(-ENOMEM); @@ -347,14 +619,18 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) kfree(nvmem); return ERR_PTR(rval); } + if (config->wp_gpio) nvmem->wp_gpio = config->wp_gpio; else nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp", GPIOD_OUT_HIGH); - if (IS_ERR(nvmem->wp_gpio)) - return ERR_CAST(nvmem->wp_gpio); - + if (IS_ERR(nvmem->wp_gpio)) { + ida_simple_remove(&nvmem_ida, nvmem->id); + rval = PTR_ERR(nvmem->wp_gpio); + kfree(nvmem); + return ERR_PTR(rval); + } kref_init(&nvmem->refcnt); INIT_LIST_HEAD(&nvmem->cells); @@ -369,6 +645,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->dev.type = &nvmem_provider_type; nvmem->dev.bus = &nvmem_bus_type; nvmem->dev.parent = config->dev; + nvmem->root_only = config->root_only; nvmem->priv = config->priv; nvmem->type = config->type; nvmem->reg_read = config->reg_read; @@ -387,13 +664,13 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->read_only = device_property_present(config->dev, "read-only") || config->read_only || !nvmem->reg_write; - nvmem->dev.groups = nvmem_sysfs_get_groups(nvmem, config); - - device_initialize(&nvmem->dev); +#ifdef CONFIG_NVMEM_SYSFS + nvmem->dev.groups = nvmem_dev_groups; +#endif dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); - rval = device_add(&nvmem->dev); + rval = device_register(&nvmem->dev); if (rval) goto err_put_device; @@ -447,8 +724,7 @@ static void nvmem_device_release(struct kref *kref) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); nvmem_device_remove_all_cells(nvmem); - device_del(&nvmem->dev); - put_device(&nvmem->dev); + device_unregister(&nvmem->dev); } /** @@ -1088,16 +1364,8 @@ int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len) } EXPORT_SYMBOL_GPL(nvmem_cell_write); -/** - * nvmem_cell_read_u16() - Read a cell value as an u16 - * - * @dev: Device that requests the nvmem cell. - * @cell_id: Name of nvmem cell to read. - * @val: pointer to output value. - * - * Return: 0 on success or negative errno. - */ -int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) +static int nvmem_cell_read_common(struct device *dev, const char *cell_id, + void *val, size_t count) { struct nvmem_cell *cell; void *buf; @@ -1112,17 +1380,31 @@ int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) nvmem_cell_put(cell); return PTR_ERR(buf); } - if (len != sizeof(*val)) { + if (len != count) { kfree(buf); nvmem_cell_put(cell); return -EINVAL; } - memcpy(val, buf, sizeof(*val)); + memcpy(val, buf, count); kfree(buf); nvmem_cell_put(cell); return 0; } + +/** + * nvmem_cell_read_u16() - Read a cell value as an u16 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} EXPORT_SYMBOL_GPL(nvmem_cell_read_u16); /** @@ -1136,33 +1418,26 @@ EXPORT_SYMBOL_GPL(nvmem_cell_read_u16); */ int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val) { - struct nvmem_cell *cell; - void *buf; - size_t len; - - cell = nvmem_cell_get(dev, cell_id); - if (IS_ERR(cell)) - return PTR_ERR(cell); - - buf = nvmem_cell_read(cell, &len); - if (IS_ERR(buf)) { - nvmem_cell_put(cell); - return PTR_ERR(buf); - } - if (len != sizeof(*val)) { - kfree(buf); - nvmem_cell_put(cell); - return -EINVAL; - } - memcpy(val, buf, sizeof(*val)); - - kfree(buf); - nvmem_cell_put(cell); - return 0; + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); } EXPORT_SYMBOL_GPL(nvmem_cell_read_u32); /** + * nvmem_cell_read_u64() - Read a cell value as an u64 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_u64); + +/** * nvmem_device_cell_read() - Read a given nvmem device and cell * * @nvmem: nvmem device to read from. diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index 4ba9cc8f76df..50bea2aadc1b 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -44,6 +44,11 @@ #define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 #define IMX_OCOTP_BM_CTRL_REL_SHADOWS 0x00000400 +#define IMX_OCOTP_BM_CTRL_ADDR_8MP 0x000001FF +#define IMX_OCOTP_BM_CTRL_BUSY_8MP 0x00000200 +#define IMX_OCOTP_BM_CTRL_ERROR_8MP 0x00000400 +#define IMX_OCOTP_BM_CTRL_REL_SHADOWS_8MP 0x00000800 + #define IMX_OCOTP_BM_CTRL_DEFAULT \ { \ .bm_addr = IMX_OCOTP_BM_CTRL_ADDR, \ @@ -52,6 +57,14 @@ .bm_rel_shadows = IMX_OCOTP_BM_CTRL_REL_SHADOWS,\ } +#define IMX_OCOTP_BM_CTRL_8MP \ + { \ + .bm_addr = IMX_OCOTP_BM_CTRL_ADDR_8MP, \ + .bm_busy = IMX_OCOTP_BM_CTRL_BUSY_8MP, \ + .bm_error = IMX_OCOTP_BM_CTRL_ERROR_8MP, \ + .bm_rel_shadows = IMX_OCOTP_BM_CTRL_REL_SHADOWS_8MP,\ + } + #define TIMING_STROBE_PROG_US 10 /* Min time to blow a fuse */ #define TIMING_STROBE_READ_NS 37 /* Min time before read */ #define TIMING_RELAX_NS 17 @@ -193,9 +206,9 @@ read_end: static void imx_ocotp_set_imx6_timing(struct ocotp_priv *priv) { - unsigned long clk_rate = 0; + unsigned long clk_rate; unsigned long strobe_read, relax, strobe_prog; - u32 timing = 0; + u32 timing; /* 47.3.1.3.1 * Program HW_OCOTP_TIMING[STROBE_PROG] and HW_OCOTP_TIMING[RELAX] @@ -245,9 +258,9 @@ static void imx_ocotp_set_imx6_timing(struct ocotp_priv *priv) static void imx_ocotp_set_imx7_timing(struct ocotp_priv *priv) { - unsigned long clk_rate = 0; + unsigned long clk_rate; u64 fsource, strobe_prog; - u32 timing = 0; + u32 timing; /* i.MX 7Solo Applications Processor Reference Manual, Rev. 0.1 * 6.4.3.3 @@ -520,6 +533,13 @@ static const struct ocotp_params imx8mn_params = { .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; +static const struct ocotp_params imx8mp_params = { + .nregs = 384, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_8MP, +}; + static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6q-ocotp", .data = &imx6q_params }, { .compatible = "fsl,imx6sl-ocotp", .data = &imx6sl_params }, @@ -532,6 +552,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx8mq-ocotp", .data = &imx8mq_params }, { .compatible = "fsl,imx8mm-ocotp", .data = &imx8mm_params }, { .compatible = "fsl,imx8mn-ocotp", .data = &imx8mn_params }, + { .compatible = "fsl,imx8mp-ocotp", .data = &imx8mp_params }, { }, }; MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); diff --git a/drivers/nvmem/jz4780-efuse.c b/drivers/nvmem/jz4780-efuse.c new file mode 100644 index 000000000000..512e1872ba36 --- /dev/null +++ b/drivers/nvmem/jz4780-efuse.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * JZ4780 EFUSE Memory Support driver + * + * Copyright (c) 2017 PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com> + * Copyright (c) 2020 H. Nikolaus Schaller <hns@goldelico.com> + */ + +/* + * Currently supports JZ4780 efuse which has 8K programmable bit. + * Efuse is separated into seven segments as below: + * + * ----------------------------------------------------------------------- + * | 64 bit | 128 bit | 128 bit | 3520 bit | 8 bit | 2296 bit | 2048 bit | + * ----------------------------------------------------------------------- + * + * The rom itself is accessed using a 9 bit address line and an 8 word wide bus + * which reads/writes based on strobes. The strobe is configured in the config + * register and is based on number of cycles of the bus clock. + * + * Driver supports read only as the writes are done in the Factory. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/timer.h> + +#define JZ_EFUCTRL (0x0) /* Control Register */ +#define JZ_EFUCFG (0x4) /* Configure Register*/ +#define JZ_EFUSTATE (0x8) /* Status Register */ +#define JZ_EFUDATA(n) (0xC + (n) * 4) + +/* We read 32 byte chunks to avoid complexity in the driver. */ +#define JZ_EFU_READ_SIZE 32 + +#define EFUCTRL_ADDR_MASK 0x3FF +#define EFUCTRL_ADDR_SHIFT 21 +#define EFUCTRL_LEN_MASK 0x1F +#define EFUCTRL_LEN_SHIFT 16 +#define EFUCTRL_PG_EN BIT(15) +#define EFUCTRL_WR_EN BIT(1) +#define EFUCTRL_RD_EN BIT(0) + +#define EFUCFG_INT_EN BIT(31) +#define EFUCFG_RD_ADJ_MASK 0xF +#define EFUCFG_RD_ADJ_SHIFT 20 +#define EFUCFG_RD_STR_MASK 0xF +#define EFUCFG_RD_STR_SHIFT 16 +#define EFUCFG_WR_ADJ_MASK 0xF +#define EFUCFG_WR_ADJ_SHIFT 12 +#define EFUCFG_WR_STR_MASK 0xFFF +#define EFUCFG_WR_STR_SHIFT 0 + +#define EFUSTATE_WR_DONE BIT(1) +#define EFUSTATE_RD_DONE BIT(0) + +struct jz4780_efuse { + struct device *dev; + struct regmap *map; + struct clk *clk; +}; + +/* main entry point */ +static int jz4780_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct jz4780_efuse *efuse = context; + + while (bytes > 0) { + size_t start = offset & ~(JZ_EFU_READ_SIZE - 1); + size_t chunk = min(bytes, (start + JZ_EFU_READ_SIZE) + - offset); + char buf[JZ_EFU_READ_SIZE]; + unsigned int tmp; + u32 ctrl; + int ret; + + ctrl = (start << EFUCTRL_ADDR_SHIFT) + | ((JZ_EFU_READ_SIZE - 1) << EFUCTRL_LEN_SHIFT) + | EFUCTRL_RD_EN; + + regmap_update_bits(efuse->map, JZ_EFUCTRL, + (EFUCTRL_ADDR_MASK << EFUCTRL_ADDR_SHIFT) | + (EFUCTRL_LEN_MASK << EFUCTRL_LEN_SHIFT) | + EFUCTRL_PG_EN | EFUCTRL_WR_EN | + EFUCTRL_RD_EN, + ctrl); + + ret = regmap_read_poll_timeout(efuse->map, JZ_EFUSTATE, + tmp, tmp & EFUSTATE_RD_DONE, + 1 * MSEC_PER_SEC, + 50 * MSEC_PER_SEC); + if (ret < 0) { + dev_err(efuse->dev, "Time out while reading efuse data"); + return ret; + } + + ret = regmap_bulk_read(efuse->map, JZ_EFUDATA(0), + buf, JZ_EFU_READ_SIZE / sizeof(u32)); + if (ret < 0) + return ret; + + memcpy(val, &buf[offset - start], chunk); + + val += chunk; + offset += chunk; + bytes -= chunk; + } + + return 0; +} + +static struct nvmem_config jz4780_efuse_nvmem_config = { + .name = "jz4780-efuse", + .size = 1024, + .word_size = 1, + .stride = 1, + .owner = THIS_MODULE, + .reg_read = jz4780_efuse_read, +}; + +static const struct regmap_config jz4780_efuse_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = JZ_EFUDATA(7), +}; + +static void clk_disable_unprepare_helper(void *clock) +{ + clk_disable_unprepare(clock); +} + +static int jz4780_efuse_probe(struct platform_device *pdev) +{ + struct nvmem_device *nvmem; + struct jz4780_efuse *efuse; + struct nvmem_config cfg; + unsigned long clk_rate; + unsigned long rd_adj; + unsigned long rd_strobe; + struct device *dev = &pdev->dev; + void __iomem *regs; + int ret; + + efuse = devm_kzalloc(dev, sizeof(*efuse), GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + efuse->map = devm_regmap_init_mmio(dev, regs, + &jz4780_efuse_regmap_config); + if (IS_ERR(efuse->map)) + return PTR_ERR(efuse->map); + + efuse->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(efuse->clk)) + return PTR_ERR(efuse->clk); + + ret = clk_prepare_enable(efuse->clk); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, + clk_disable_unprepare_helper, + efuse->clk); + if (ret < 0) + return ret; + + clk_rate = clk_get_rate(efuse->clk); + + efuse->dev = dev; + + /* + * rd_adj and rd_strobe are 4 bit values + * conditions: + * bus clk_period * (rd_adj + 1) > 6.5ns + * bus clk_period * (rd_adj + 5 + rd_strobe) > 35ns + * i.e. rd_adj >= 6.5ns / clk_period + * i.e. rd_strobe >= 35 ns / clk_period - 5 - rd_adj + 1 + * constants: + * 1 / 6.5ns == 153846154 Hz + * 1 / 35ns == 28571429 Hz + */ + + rd_adj = clk_rate / 153846154; + rd_strobe = clk_rate / 28571429 - 5 - rd_adj + 1; + + if (rd_adj > EFUCFG_RD_ADJ_MASK || + rd_strobe > EFUCFG_RD_STR_MASK) { + dev_err(&pdev->dev, "Cannot set clock configuration\n"); + return -EINVAL; + } + + regmap_update_bits(efuse->map, JZ_EFUCFG, + (EFUCFG_RD_ADJ_MASK << EFUCFG_RD_ADJ_SHIFT) | + (EFUCFG_RD_STR_MASK << EFUCFG_RD_STR_SHIFT), + (rd_adj << EFUCFG_RD_ADJ_SHIFT) | + (rd_strobe << EFUCFG_RD_STR_SHIFT)); + + cfg = jz4780_efuse_nvmem_config; + cfg.dev = &pdev->dev; + cfg.priv = efuse; + + nvmem = devm_nvmem_register(dev, &cfg); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + return 0; +} + +static const struct of_device_id jz4780_efuse_match[] = { + { .compatible = "ingenic,jz4780-efuse" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, jz4780_efuse_match); + +static struct platform_driver jz4780_efuse_driver = { + .probe = jz4780_efuse_probe, + .driver = { + .name = "jz4780-efuse", + .of_match_table = jz4780_efuse_match, + }, +}; +module_platform_driver(jz4780_efuse_driver); + +MODULE_AUTHOR("PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com>"); +MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_DESCRIPTION("Ingenic JZ4780 efuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/mxs-ocotp.c b/drivers/nvmem/mxs-ocotp.c index 8e4898dec002..588ab56d75b7 100644 --- a/drivers/nvmem/mxs-ocotp.c +++ b/drivers/nvmem/mxs-ocotp.c @@ -130,6 +130,11 @@ static const struct of_device_id mxs_ocotp_match[] = { }; MODULE_DEVICE_TABLE(of, mxs_ocotp_match); +static void mxs_ocotp_action(void *data) +{ + clk_unprepare(data); +} + static int mxs_ocotp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -160,39 +165,26 @@ static int mxs_ocotp_probe(struct platform_device *pdev) return ret; } + ret = devm_add_action_or_reset(&pdev->dev, mxs_ocotp_action, otp->clk); + if (ret) + return ret; + data = match->data; ocotp_config.size = data->size; ocotp_config.priv = otp; ocotp_config.dev = dev; otp->nvmem = devm_nvmem_register(dev, &ocotp_config); - if (IS_ERR(otp->nvmem)) { - ret = PTR_ERR(otp->nvmem); - goto err_clk; - } + if (IS_ERR(otp->nvmem)) + return PTR_ERR(otp->nvmem); platform_set_drvdata(pdev, otp); return 0; - -err_clk: - clk_unprepare(otp->clk); - - return ret; -} - -static int mxs_ocotp_remove(struct platform_device *pdev) -{ - struct mxs_ocotp *otp = platform_get_drvdata(pdev); - - clk_unprepare(otp->clk); - - return 0; } static struct platform_driver mxs_ocotp_driver = { .probe = mxs_ocotp_probe, - .remove = mxs_ocotp_remove, .driver = { .name = "mxs-ocotp", .of_match_table = mxs_ocotp_match, diff --git a/drivers/nvmem/nvmem-sysfs.c b/drivers/nvmem/nvmem-sysfs.c deleted file mode 100644 index 9e0c429cd08a..000000000000 --- a/drivers/nvmem/nvmem-sysfs.c +++ /dev/null @@ -1,263 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2019, Linaro Limited - */ -#include "nvmem.h" - -static const char * const nvmem_type_str[] = { - [NVMEM_TYPE_UNKNOWN] = "Unknown", - [NVMEM_TYPE_EEPROM] = "EEPROM", - [NVMEM_TYPE_OTP] = "OTP", - [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed", -}; - -#ifdef CONFIG_DEBUG_LOCK_ALLOC -static struct lock_class_key eeprom_lock_key; -#endif - -static ssize_t type_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct nvmem_device *nvmem = to_nvmem_device(dev); - - return sprintf(buf, "%s\n", nvmem_type_str[nvmem->type]); -} - -static DEVICE_ATTR_RO(type); - -static struct attribute *nvmem_attrs[] = { - &dev_attr_type.attr, - NULL, -}; - -static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, - char *buf, loff_t pos, size_t count) -{ - struct device *dev; - struct nvmem_device *nvmem; - int rc; - - if (attr->private) - dev = attr->private; - else - dev = container_of(kobj, struct device, kobj); - nvmem = to_nvmem_device(dev); - - /* Stop the user from reading */ - if (pos >= nvmem->size) - return 0; - - if (count < nvmem->word_size) - return -EINVAL; - - if (pos + count > nvmem->size) - count = nvmem->size - pos; - - count = round_down(count, nvmem->word_size); - - rc = nvmem->reg_read(nvmem->priv, pos, buf, count); - - if (rc) - return rc; - - return count; -} - -static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, - char *buf, loff_t pos, size_t count) -{ - struct device *dev; - struct nvmem_device *nvmem; - int rc; - - if (attr->private) - dev = attr->private; - else - dev = container_of(kobj, struct device, kobj); - nvmem = to_nvmem_device(dev); - - /* Stop the user from writing */ - if (pos >= nvmem->size) - return -EFBIG; - - if (count < nvmem->word_size) - return -EINVAL; - - if (pos + count > nvmem->size) - count = nvmem->size - pos; - - count = round_down(count, nvmem->word_size); - - rc = nvmem->reg_write(nvmem->priv, pos, buf, count); - - if (rc) - return rc; - - return count; -} - -/* default read/write permissions */ -static struct bin_attribute bin_attr_rw_nvmem = { - .attr = { - .name = "nvmem", - .mode = 0644, - }, - .read = bin_attr_nvmem_read, - .write = bin_attr_nvmem_write, -}; - -static struct bin_attribute *nvmem_bin_rw_attributes[] = { - &bin_attr_rw_nvmem, - NULL, -}; - -static const struct attribute_group nvmem_bin_rw_group = { - .bin_attrs = nvmem_bin_rw_attributes, - .attrs = nvmem_attrs, -}; - -static const struct attribute_group *nvmem_rw_dev_groups[] = { - &nvmem_bin_rw_group, - NULL, -}; - -/* read only permission */ -static struct bin_attribute bin_attr_ro_nvmem = { - .attr = { - .name = "nvmem", - .mode = 0444, - }, - .read = bin_attr_nvmem_read, -}; - -static struct bin_attribute *nvmem_bin_ro_attributes[] = { - &bin_attr_ro_nvmem, - NULL, -}; - -static const struct attribute_group nvmem_bin_ro_group = { - .bin_attrs = nvmem_bin_ro_attributes, - .attrs = nvmem_attrs, -}; - -static const struct attribute_group *nvmem_ro_dev_groups[] = { - &nvmem_bin_ro_group, - NULL, -}; - -/* default read/write permissions, root only */ -static struct bin_attribute bin_attr_rw_root_nvmem = { - .attr = { - .name = "nvmem", - .mode = 0600, - }, - .read = bin_attr_nvmem_read, - .write = bin_attr_nvmem_write, -}; - -static struct bin_attribute *nvmem_bin_rw_root_attributes[] = { - &bin_attr_rw_root_nvmem, - NULL, -}; - -static const struct attribute_group nvmem_bin_rw_root_group = { - .bin_attrs = nvmem_bin_rw_root_attributes, - .attrs = nvmem_attrs, -}; - -static const struct attribute_group *nvmem_rw_root_dev_groups[] = { - &nvmem_bin_rw_root_group, - NULL, -}; - -/* read only permission, root only */ -static struct bin_attribute bin_attr_ro_root_nvmem = { - .attr = { - .name = "nvmem", - .mode = 0400, - }, - .read = bin_attr_nvmem_read, -}; - -static struct bin_attribute *nvmem_bin_ro_root_attributes[] = { - &bin_attr_ro_root_nvmem, - NULL, -}; - -static const struct attribute_group nvmem_bin_ro_root_group = { - .bin_attrs = nvmem_bin_ro_root_attributes, - .attrs = nvmem_attrs, -}; - -static const struct attribute_group *nvmem_ro_root_dev_groups[] = { - &nvmem_bin_ro_root_group, - NULL, -}; - -const struct attribute_group **nvmem_sysfs_get_groups( - struct nvmem_device *nvmem, - const struct nvmem_config *config) -{ - if (config->root_only) - return nvmem->read_only ? - nvmem_ro_root_dev_groups : - nvmem_rw_root_dev_groups; - - return nvmem->read_only ? nvmem_ro_dev_groups : nvmem_rw_dev_groups; -} - -/* - * nvmem_setup_compat() - Create an additional binary entry in - * drivers sys directory, to be backwards compatible with the older - * drivers/misc/eeprom drivers. - */ -int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, - const struct nvmem_config *config) -{ - int rval; - - if (!config->compat) - return 0; - - if (!config->base_dev) - return -EINVAL; - - if (nvmem->read_only) { - if (config->root_only) - nvmem->eeprom = bin_attr_ro_root_nvmem; - else - nvmem->eeprom = bin_attr_ro_nvmem; - } else { - if (config->root_only) - nvmem->eeprom = bin_attr_rw_root_nvmem; - else - nvmem->eeprom = bin_attr_rw_nvmem; - } - nvmem->eeprom.attr.name = "eeprom"; - nvmem->eeprom.size = nvmem->size; -#ifdef CONFIG_DEBUG_LOCK_ALLOC - nvmem->eeprom.attr.key = &eeprom_lock_key; -#endif - nvmem->eeprom.private = &nvmem->dev; - nvmem->base_dev = config->base_dev; - - rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); - if (rval) { - dev_err(&nvmem->dev, - "Failed to create eeprom binary file %d\n", rval); - return rval; - } - - nvmem->flags |= FLAG_COMPAT; - - return 0; -} - -void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, - const struct nvmem_config *config) -{ - if (config->compat) - device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); -} diff --git a/drivers/nvmem/nvmem.h b/drivers/nvmem/nvmem.h deleted file mode 100644 index be0d66d75c8a..000000000000 --- a/drivers/nvmem/nvmem.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef _DRIVERS_NVMEM_H -#define _DRIVERS_NVMEM_H - -#include <linux/device.h> -#include <linux/fs.h> -#include <linux/kref.h> -#include <linux/list.h> -#include <linux/nvmem-consumer.h> -#include <linux/nvmem-provider.h> -#include <linux/gpio/consumer.h> - -struct nvmem_device { - struct module *owner; - struct device dev; - int stride; - int word_size; - int id; - struct kref refcnt; - size_t size; - bool read_only; - int flags; - enum nvmem_type type; - struct bin_attribute eeprom; - struct device *base_dev; - struct list_head cells; - nvmem_reg_read_t reg_read; - nvmem_reg_write_t reg_write; - struct gpio_desc *wp_gpio; - void *priv; -}; - -#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) -#define FLAG_COMPAT BIT(0) - -#ifdef CONFIG_NVMEM_SYSFS -const struct attribute_group **nvmem_sysfs_get_groups( - struct nvmem_device *nvmem, - const struct nvmem_config *config); -int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, - const struct nvmem_config *config); -void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, - const struct nvmem_config *config); -#else -static inline const struct attribute_group **nvmem_sysfs_get_groups( - struct nvmem_device *nvmem, - const struct nvmem_config *config) -{ - return NULL; -} - -static inline int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, - const struct nvmem_config *config) -{ - return -ENOSYS; -} -static inline void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, - const struct nvmem_config *config) -{ -} -#endif /* CONFIG_NVMEM_SYSFS */ - -#endif /* _DRIVERS_NVMEM_H */ diff --git a/drivers/nvmem/sprd-efuse.c b/drivers/nvmem/sprd-efuse.c index 2f1e0fbd1901..925feb21d5ad 100644 --- a/drivers/nvmem/sprd-efuse.c +++ b/drivers/nvmem/sprd-efuse.c @@ -217,12 +217,14 @@ static int sprd_efuse_raw_prog(struct sprd_efuse *efuse, u32 blk, bool doub, * Enable the auto-check function to validate if the programming is * successful. */ - sprd_efuse_set_auto_check(efuse, true); + if (lock) + sprd_efuse_set_auto_check(efuse, true); writel(*data, efuse->base + SPRD_EFUSE_MEM(blk)); /* Disable auto-check and data double after programming */ - sprd_efuse_set_auto_check(efuse, false); + if (lock) + sprd_efuse_set_auto_check(efuse, false); sprd_efuse_set_data_double(efuse, false); /* @@ -237,9 +239,9 @@ static int sprd_efuse_raw_prog(struct sprd_efuse *efuse, u32 blk, bool doub, writel(SPRD_EFUSE_ERR_CLR_MASK, efuse->base + SPRD_EFUSE_ERR_CLR); ret = -EBUSY; - } else { + } else if (lock) { sprd_efuse_set_prog_lock(efuse, lock); - writel(*data, efuse->base + SPRD_EFUSE_MEM(blk)); + writel(0, efuse->base + SPRD_EFUSE_MEM(blk)); sprd_efuse_set_prog_lock(efuse, false); } @@ -322,6 +324,8 @@ unlock: static int sprd_efuse_write(void *context, u32 offset, void *val, size_t bytes) { struct sprd_efuse *efuse = context; + bool blk_double = efuse->data->blk_double; + bool lock; int ret; ret = sprd_efuse_lock(efuse); @@ -332,7 +336,20 @@ static int sprd_efuse_write(void *context, u32 offset, void *val, size_t bytes) if (ret) goto unlock; - ret = sprd_efuse_raw_prog(efuse, offset, false, false, val); + /* + * If the writing bytes are equal with the block width, which means the + * whole block will be programmed. For this case, we should not allow + * this block to be programmed again by locking this block. + * + * If the block was programmed partially, we should allow this block to + * be programmed again. + */ + if (bytes < SPRD_EFUSE_BLOCK_WIDTH) + lock = false; + else + lock = true; + + ret = sprd_efuse_raw_prog(efuse, offset, blk_double, lock, val); clk_disable_unprepare(efuse->clk); diff --git a/drivers/sbus/char/envctrl.c b/drivers/sbus/char/envctrl.c index 12d66aa61ede..843e830b5f87 100644 --- a/drivers/sbus/char/envctrl.c +++ b/drivers/sbus/char/envctrl.c @@ -37,8 +37,6 @@ #define DRIVER_NAME "envctrl" #define PFX DRIVER_NAME ": " -#define ENVCTRL_MINOR 162 - #define PCF8584_ADDRESS 0x55 #define CONTROL_PIN 0x80 diff --git a/drivers/sbus/char/flash.c b/drivers/sbus/char/flash.c index e85a05aca4d6..4147d22fd448 100644 --- a/drivers/sbus/char/flash.c +++ b/drivers/sbus/char/flash.c @@ -31,8 +31,6 @@ static struct { unsigned long busy; /* In use? */ } flash; -#define FLASH_MINOR 152 - static int flash_mmap(struct file *file, struct vm_area_struct *vma) { @@ -157,7 +155,7 @@ static const struct file_operations flash_fops = { .release = flash_release, }; -static struct miscdevice flash_dev = { FLASH_MINOR, "flash", &flash_fops }; +static struct miscdevice flash_dev = { SBUS_FLASH_MINOR, "flash", &flash_fops }; static int flash_probe(struct platform_device *op) { diff --git a/drivers/sbus/char/uctrl.c b/drivers/sbus/char/uctrl.c index 7173a2e4e8cf..37d252f2548d 100644 --- a/drivers/sbus/char/uctrl.c +++ b/drivers/sbus/char/uctrl.c @@ -23,8 +23,6 @@ #include <asm/io.h> #include <asm/pgtable.h> -#define UCTRL_MINOR 174 - #define DEBUG 1 #ifdef DEBUG #define dprintk(x) printk x diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 6106577fb3ed..488c3c9e4947 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -2,6 +2,7 @@ // Copyright(c) 2015-17 Intel Corporation. #include <linux/acpi.h> +#include <linux/delay.h> #include <linux/mod_devicetable.h> #include <linux/pm_runtime.h> #include <linux/soundwire/sdw_registers.h> @@ -113,6 +114,8 @@ static int sdw_delete_slave(struct device *dev, void *data) struct sdw_slave *slave = dev_to_sdw_dev(dev); struct sdw_bus *bus = slave->bus; + pm_runtime_disable(dev); + sdw_slave_debugfs_exit(slave); mutex_lock(&bus->bus_lock); @@ -317,6 +320,92 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, return 0; } +/* + * Read/Write IO functions. + * no_pm versions can only be called by the bus, e.g. while enumerating or + * handling suspend-resume sequences. + * all clients need to use the pm versions + */ + +static int +sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +static int +sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_WRITE, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +static int sdw_write_no_pm(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite_no_pm(slave, addr, 1, &value); +} + +static int +sdw_bread_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr) +{ + struct sdw_msg msg; + u8 buf; + int ret; + + ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num, + SDW_MSG_FLAG_READ, &buf); + if (ret) + return ret; + + ret = sdw_transfer(bus, &msg); + if (ret < 0) + return ret; + else + return buf; +} + +static int +sdw_bwrite_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num, + SDW_MSG_FLAG_WRITE, &value); + if (ret) + return ret; + + return sdw_transfer(bus, &msg); +} + +static int +sdw_read_no_pm(struct sdw_slave *slave, u32 addr) +{ + u8 buf; + int ret; + + ret = sdw_nread_no_pm(slave, addr, 1, &buf); + if (ret < 0) + return ret; + else + return buf; +} + /** * sdw_nread() - Read "n" contiguous SDW Slave registers * @slave: SDW Slave @@ -326,19 +415,17 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, */ int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { - struct sdw_msg msg; int ret; - ret = sdw_fill_msg(&msg, slave, addr, count, - slave->dev_num, SDW_MSG_FLAG_READ, val); - if (ret < 0) - return ret; - ret = pm_runtime_get_sync(slave->bus->dev); - if (ret < 0) + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(slave->bus->dev); return ret; + } + + ret = sdw_nread_no_pm(slave, addr, count, val); - ret = sdw_transfer(slave->bus, &msg); + pm_runtime_mark_last_busy(slave->bus->dev); pm_runtime_put(slave->bus->dev); return ret; @@ -354,19 +441,17 @@ EXPORT_SYMBOL(sdw_nread); */ int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { - struct sdw_msg msg; int ret; - ret = sdw_fill_msg(&msg, slave, addr, count, - slave->dev_num, SDW_MSG_FLAG_WRITE, val); - if (ret < 0) - return ret; - ret = pm_runtime_get_sync(slave->bus->dev); - if (ret < 0) + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(slave->bus->dev); return ret; + } + + ret = sdw_nwrite_no_pm(slave, addr, count, val); - ret = sdw_transfer(slave->bus, &msg); + pm_runtime_mark_last_busy(slave->bus->dev); pm_runtime_put(slave->bus->dev); return ret; @@ -486,7 +571,7 @@ static int sdw_assign_device_num(struct sdw_slave *slave) dev_num = slave->dev_num; slave->dev_num = 0; - ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num); + ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, dev_num); if (ret < 0) { dev_err(&slave->dev, "Program device_num %d failed: %d\n", dev_num, ret); @@ -504,22 +589,11 @@ void sdw_extract_slave_id(struct sdw_bus *bus, { dev_dbg(bus->dev, "SDW Slave Addr: %llx\n", addr); - /* - * Spec definition - * Register Bit Contents - * DevId_0 [7:4] 47:44 sdw_version - * DevId_0 [3:0] 43:40 unique_id - * DevId_1 39:32 mfg_id [15:8] - * DevId_2 31:24 mfg_id [7:0] - * DevId_3 23:16 part_id [15:8] - * DevId_4 15:08 part_id [7:0] - * DevId_5 07:00 class_id - */ - id->sdw_version = (addr >> 44) & GENMASK(3, 0); - id->unique_id = (addr >> 40) & GENMASK(3, 0); - id->mfg_id = (addr >> 24) & GENMASK(15, 0); - id->part_id = (addr >> 8) & GENMASK(15, 0); - id->class_id = addr & GENMASK(7, 0); + id->sdw_version = SDW_VERSION(addr); + id->unique_id = SDW_UNIQUE_ID(addr); + id->mfg_id = SDW_MFG_ID(addr); + id->part_id = SDW_PART_ID(addr); + id->class_id = SDW_CLASS_ID(addr); dev_dbg(bus->dev, "SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x\n", @@ -610,10 +684,320 @@ static void sdw_modify_slave_status(struct sdw_slave *slave, enum sdw_slave_status status) { mutex_lock(&slave->bus->bus_lock); + + dev_vdbg(&slave->dev, + "%s: changing status slave %d status %d new status %d\n", + __func__, slave->dev_num, slave->status, status); + + if (status == SDW_SLAVE_UNATTACHED) { + dev_dbg(&slave->dev, + "%s: initializing completion for Slave %d\n", + __func__, slave->dev_num); + + init_completion(&slave->enumeration_complete); + init_completion(&slave->initialization_complete); + + } else if ((status == SDW_SLAVE_ATTACHED) && + (slave->status == SDW_SLAVE_UNATTACHED)) { + dev_dbg(&slave->dev, + "%s: signaling completion for Slave %d\n", + __func__, slave->dev_num); + + complete(&slave->enumeration_complete); + } slave->status = status; mutex_unlock(&slave->bus->bus_lock); } +static enum sdw_clk_stop_mode sdw_get_clk_stop_mode(struct sdw_slave *slave) +{ + enum sdw_clk_stop_mode mode; + + /* + * Query for clock stop mode if Slave implements + * ops->get_clk_stop_mode, else read from property. + */ + if (slave->ops && slave->ops->get_clk_stop_mode) { + mode = slave->ops->get_clk_stop_mode(slave); + } else { + if (slave->prop.clk_stop_mode1) + mode = SDW_CLK_STOP_MODE1; + else + mode = SDW_CLK_STOP_MODE0; + } + + return mode; +} + +static int sdw_slave_clk_stop_callback(struct sdw_slave *slave, + enum sdw_clk_stop_mode mode, + enum sdw_clk_stop_type type) +{ + int ret; + + if (slave->ops && slave->ops->clk_stop) { + ret = slave->ops->clk_stop(slave, mode, type); + if (ret < 0) { + dev_err(&slave->dev, + "Clk Stop type =%d failed: %d\n", type, ret); + return ret; + } + } + + return 0; +} + +static int sdw_slave_clk_stop_prepare(struct sdw_slave *slave, + enum sdw_clk_stop_mode mode, + bool prepare) +{ + bool wake_en; + u32 val = 0; + int ret; + + wake_en = slave->prop.wake_capable; + + if (prepare) { + val = SDW_SCP_SYSTEMCTRL_CLK_STP_PREP; + + if (mode == SDW_CLK_STOP_MODE1) + val |= SDW_SCP_SYSTEMCTRL_CLK_STP_MODE1; + + if (wake_en) + val |= SDW_SCP_SYSTEMCTRL_WAKE_UP_EN; + } else { + val = sdw_read_no_pm(slave, SDW_SCP_SYSTEMCTRL); + + val &= ~(SDW_SCP_SYSTEMCTRL_CLK_STP_PREP); + } + + ret = sdw_write_no_pm(slave, SDW_SCP_SYSTEMCTRL, val); + + if (ret != 0) + dev_err(&slave->dev, + "Clock Stop prepare failed for slave: %d", ret); + + return ret; +} + +static int sdw_bus_wait_for_clk_prep_deprep(struct sdw_bus *bus, u16 dev_num) +{ + int retry = bus->clk_stop_timeout; + int val; + + do { + val = sdw_bread_no_pm(bus, dev_num, SDW_SCP_STAT) & + SDW_SCP_STAT_CLK_STP_NF; + if (!val) { + dev_info(bus->dev, "clock stop prep/de-prep done slave:%d", + dev_num); + return 0; + } + + usleep_range(1000, 1500); + retry--; + } while (retry); + + dev_err(bus->dev, "clock stop prep/de-prep failed slave:%d", + dev_num); + + return -ETIMEDOUT; +} + +/** + * sdw_bus_prep_clk_stop: prepare Slave(s) for clock stop + * + * @bus: SDW bus instance + * + * Query Slave for clock stop mode and prepare for that mode. + */ +int sdw_bus_prep_clk_stop(struct sdw_bus *bus) +{ + enum sdw_clk_stop_mode slave_mode; + bool simple_clk_stop = true; + struct sdw_slave *slave; + bool is_slave = false; + int ret = 0; + + /* + * In order to save on transition time, prepare + * each Slave and then wait for all Slave(s) to be + * prepared for clock stop. + */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + /* Identify if Slave(s) are available on Bus */ + is_slave = true; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + slave_mode = sdw_get_clk_stop_mode(slave); + slave->curr_clk_stop_mode = slave_mode; + + ret = sdw_slave_clk_stop_callback(slave, slave_mode, + SDW_CLK_PRE_PREPARE); + if (ret < 0) { + dev_err(&slave->dev, + "pre-prepare failed:%d", ret); + return ret; + } + + ret = sdw_slave_clk_stop_prepare(slave, + slave_mode, true); + if (ret < 0) { + dev_err(&slave->dev, + "pre-prepare failed:%d", ret); + return ret; + } + + if (slave_mode == SDW_CLK_STOP_MODE1) + simple_clk_stop = false; + } + + if (is_slave && !simple_clk_stop) { + ret = sdw_bus_wait_for_clk_prep_deprep(bus, + SDW_BROADCAST_DEV_NUM); + if (ret < 0) + return ret; + } + + /* Inform slaves that prep is done */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + slave_mode = slave->curr_clk_stop_mode; + + if (slave_mode == SDW_CLK_STOP_MODE1) { + ret = sdw_slave_clk_stop_callback(slave, + slave_mode, + SDW_CLK_POST_PREPARE); + + if (ret < 0) { + dev_err(&slave->dev, + "post-prepare failed:%d", ret); + } + } + } + + return ret; +} +EXPORT_SYMBOL(sdw_bus_prep_clk_stop); + +/** + * sdw_bus_clk_stop: stop bus clock + * + * @bus: SDW bus instance + * + * After preparing the Slaves for clock stop, stop the clock by broadcasting + * write to SCP_CTRL register. + */ +int sdw_bus_clk_stop(struct sdw_bus *bus) +{ + int ret; + + /* + * broadcast clock stop now, attached Slaves will ACK this, + * unattached will ignore + */ + ret = sdw_bwrite_no_pm(bus, SDW_BROADCAST_DEV_NUM, + SDW_SCP_CTRL, SDW_SCP_CTRL_CLK_STP_NOW); + if (ret < 0) { + if (ret == -ENODATA) + dev_dbg(bus->dev, + "ClockStopNow Broadcast msg ignored %d", ret); + else + dev_err(bus->dev, + "ClockStopNow Broadcast msg failed %d", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sdw_bus_clk_stop); + +/** + * sdw_bus_exit_clk_stop: Exit clock stop mode + * + * @bus: SDW bus instance + * + * This De-prepares the Slaves by exiting Clock Stop Mode 0. For the Slaves + * exiting Clock Stop Mode 1, they will be de-prepared after they enumerate + * back. + */ +int sdw_bus_exit_clk_stop(struct sdw_bus *bus) +{ + enum sdw_clk_stop_mode mode; + bool simple_clk_stop = true; + struct sdw_slave *slave; + bool is_slave = false; + int ret; + + /* + * In order to save on transition time, de-prepare + * each Slave and then wait for all Slave(s) to be + * de-prepared after clock resume. + */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + /* Identify if Slave(s) are available on Bus */ + is_slave = true; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + mode = slave->curr_clk_stop_mode; + + if (mode == SDW_CLK_STOP_MODE1) { + simple_clk_stop = false; + continue; + } + + ret = sdw_slave_clk_stop_callback(slave, mode, + SDW_CLK_PRE_DEPREPARE); + if (ret < 0) + dev_warn(&slave->dev, + "clk stop deprep failed:%d", ret); + + ret = sdw_slave_clk_stop_prepare(slave, mode, + false); + + if (ret < 0) + dev_warn(&slave->dev, + "clk stop deprep failed:%d", ret); + } + + if (is_slave && !simple_clk_stop) + sdw_bus_wait_for_clk_prep_deprep(bus, SDW_BROADCAST_DEV_NUM); + + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + mode = slave->curr_clk_stop_mode; + sdw_slave_clk_stop_callback(slave, mode, + SDW_CLK_POST_DEPREPARE); + } + + return 0; +} +EXPORT_SYMBOL(sdw_bus_exit_clk_stop); + int sdw_configure_dpn_intr(struct sdw_slave *slave, int port, bool enable, int mask) { @@ -672,13 +1056,10 @@ static int sdw_initialize_slave(struct sdw_slave *slave) val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE; ret = sdw_update(slave, SDW_DP0_INTMASK, val, val); - if (ret < 0) { + if (ret < 0) dev_err(slave->bus->dev, "SDW_DP0_INTMASK read failed:%d\n", ret); - return val; - } - - return 0; + return ret; } static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status) @@ -831,12 +1212,19 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) sdw_modify_slave_status(slave, SDW_SLAVE_ALERT); + ret = pm_runtime_get_sync(&slave->dev); + if (ret < 0 && ret != -EACCES) { + dev_err(&slave->dev, "Failed to resume device: %d\n", ret); + pm_runtime_put_noidle(slave->bus->dev); + return ret; + } + /* Read Instat 1, Instat 2 and Instat 3 registers */ ret = sdw_read(slave, SDW_SCP_INT1); if (ret < 0) { dev_err(slave->bus->dev, "SDW_SCP_INT1 read failed:%d\n", ret); - return ret; + goto io_err; } buf = ret; @@ -844,7 +1232,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) if (ret < 0) { dev_err(slave->bus->dev, "SDW_SCP_INT2/3 read failed:%d\n", ret); - return ret; + goto io_err; } do { @@ -924,7 +1312,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) if (ret < 0) { dev_err(slave->bus->dev, "SDW_SCP_INT1 write failed:%d\n", ret); - return ret; + goto io_err; } /* @@ -935,7 +1323,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) if (ret < 0) { dev_err(slave->bus->dev, "SDW_SCP_INT1 read failed:%d\n", ret); - return ret; + goto io_err; } _buf = ret; @@ -943,7 +1331,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) if (ret < 0) { dev_err(slave->bus->dev, "SDW_SCP_INT2/3 read failed:%d\n", ret); - return ret; + goto io_err; } /* Make sure no interrupts are pending */ @@ -964,16 +1352,39 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) if (count == SDW_READ_INTR_CLEAR_RETRY) dev_warn(slave->bus->dev, "Reached MAX_RETRY on alert read\n"); +io_err: + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + return ret; } static int sdw_update_slave_status(struct sdw_slave *slave, enum sdw_slave_status status) { - if (slave->ops && slave->ops->update_status) - return slave->ops->update_status(slave, status); + unsigned long time; - return 0; + if (!slave->probed) { + /* + * the slave status update is typically handled in an + * interrupt thread, which can race with the driver + * probe, e.g. when a module needs to be loaded. + * + * make sure the probe is complete before updating + * status. + */ + time = wait_for_completion_timeout(&slave->probe_complete, + msecs_to_jiffies(DEFAULT_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Probe not complete, timed out\n"); + return -ETIMEDOUT; + } + } + + if (!slave->ops || !slave->ops->update_status) + return 0; + + return slave->ops->update_status(slave, status); } /** @@ -986,6 +1397,7 @@ int sdw_handle_slave_status(struct sdw_bus *bus, { enum sdw_slave_status prev_status; struct sdw_slave *slave; + bool attached_initializing; int i, ret = 0; /* first check if any Slaves fell off the bus */ @@ -1031,6 +1443,8 @@ int sdw_handle_slave_status(struct sdw_bus *bus, if (!slave) continue; + attached_initializing = false; + switch (status[i]) { case SDW_SLAVE_UNATTACHED: if (slave->status == SDW_SLAVE_UNATTACHED) @@ -1057,6 +1471,8 @@ int sdw_handle_slave_status(struct sdw_bus *bus, if (prev_status == SDW_SLAVE_ALERT) break; + attached_initializing = true; + ret = sdw_initialize_slave(slave); if (ret) dev_err(bus->dev, @@ -1075,8 +1491,37 @@ int sdw_handle_slave_status(struct sdw_bus *bus, if (ret) dev_err(slave->bus->dev, "Update Slave status failed:%d\n", ret); + if (attached_initializing) + complete(&slave->initialization_complete); } return ret; } EXPORT_SYMBOL(sdw_handle_slave_status); + +void sdw_clear_slave_status(struct sdw_bus *bus, u32 request) +{ + struct sdw_slave *slave; + int i; + + /* Check all non-zero devices */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + + /* keep track of request, used in pm_runtime resume */ + slave->unattach_request = request; + } +} +EXPORT_SYMBOL(sdw_clear_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index cb482da914da..204204a26db8 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -5,6 +5,7 @@ #define __SDW_BUS_H #define DEFAULT_BANK_SWITCH_TIMEOUT 3000 +#define DEFAULT_PROBE_TIMEOUT 2000 #if IS_ENABLED(CONFIG_ACPI) int sdw_acpi_find_slaves(struct sdw_bus *bus); @@ -164,4 +165,12 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) return sdw_write(slave, addr, tmp); } +/* + * At the moment we only track Master-initiated hw_reset. + * Additional fields can be added as needed + */ +#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0) + +void sdw_clear_slave_status(struct sdw_bus *bus, u32 request); + #endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 4a465f55039f..17f096dd6806 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -110,6 +110,11 @@ static int sdw_drv_probe(struct device *dev) slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout, slave->prop.clk_stop_timeout); + slave->probed = true; + complete(&slave->probe_complete); + + dev_dbg(dev, "probe complete\n"); + return 0; } diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 9bec270d0fa4..ecd357d1c63d 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -183,7 +183,6 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); #define CDNS_PDI_CONFIG_PORT GENMASK(4, 0) /* Driver defaults */ -#define CDNS_DEFAULT_SSP_INTERVAL 0x18 #define CDNS_TX_TIMEOUT 2000 #define CDNS_SCP_RX_FIFOLEVEL 0x2 @@ -211,34 +210,45 @@ static inline void cdns_updatel(struct sdw_cdns *cdns, cdns_writel(cdns, offset, tmp); } -static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) +static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value) { int timeout = 10; u32 reg_read; - writel(value, cdns->registers + offset); - - /* Wait for bit to be self cleared */ + /* Wait for bit to be set */ do { reg_read = readl(cdns->registers + offset); - if ((reg_read & value) == 0) + if ((reg_read & mask) == value) return 0; timeout--; - udelay(50); + usleep_range(50, 100); } while (timeout != 0); - return -EAGAIN; + return -ETIMEDOUT; +} + +static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) +{ + writel(value, cdns->registers + offset); + + /* Wait for bit to be self cleared */ + return cdns_set_wait(cdns, offset, value, 0); } /* * all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL * need to be confirmed with a write to MCP_CONFIG_UPDATE */ -static int cdns_update_config(struct sdw_cdns *cdns) +static int cdns_config_update(struct sdw_cdns *cdns) { int ret; + if (sdw_cdns_is_clock_stop(cdns)) { + dev_err(cdns->dev, "Cannot program MCP_CONFIG_UPDATE in ClockStopMode\n"); + return -EINVAL; + } + ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, CDNS_MCP_CONFIG_UPDATE_BIT); if (ret < 0) @@ -832,17 +842,36 @@ int sdw_cdns_exit_reset(struct sdw_cdns *cdns) CDNS_MCP_CONTROL_HW_RST, CDNS_MCP_CONTROL_HW_RST); - /* enable bus operations with clock and data */ - cdns_updatel(cdns, CDNS_MCP_CONFIG, - CDNS_MCP_CONFIG_OP, - CDNS_MCP_CONFIG_OP_NORMAL); - /* commit changes */ - return cdns_update_config(cdns); + cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT, + CDNS_MCP_CONFIG_UPDATE_BIT); + + /* don't wait here */ + return 0; + } EXPORT_SYMBOL(sdw_cdns_exit_reset); /** + * sdw_cdns_enable_slave_interrupt() - Enable SDW slave interrupts + * @cdns: Cadence instance + * @state: boolean for true/false + */ +static void cdns_enable_slave_interrupts(struct sdw_cdns *cdns, bool state) +{ + u32 mask; + + mask = cdns_readl(cdns, CDNS_MCP_INTMASK); + if (state) + mask |= CDNS_MCP_INT_SLAVE_MASK; + else + mask &= ~CDNS_MCP_INT_SLAVE_MASK; + + cdns_writel(cdns, CDNS_MCP_INTMASK, mask); +} + +/** * sdw_cdns_enable_interrupt() - Enable SDW interrupts * @cdns: Cadence instance * @state: True if we are trying to enable interrupt. @@ -1014,26 +1043,13 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols) return val; } -/** - * sdw_cdns_init() - Cadence initialization - * @cdns: Cadence instance - */ -int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit) +static void cdns_init_clock_ctrl(struct sdw_cdns *cdns) { struct sdw_bus *bus = &cdns->bus; struct sdw_master_prop *prop = &bus->prop; u32 val; + u32 ssp_interval; int divider; - int ret; - - if (clock_stop_exit) { - ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, - CDNS_MCP_CONTROL_CLK_STOP_CLR); - if (ret < 0) { - dev_err(cdns->dev, "Couldn't exit from clock stop\n"); - return ret; - } - } /* Set clock divider */ divider = (prop->mclk_freq / prop->max_clk_freq) - 1; @@ -1052,8 +1068,23 @@ int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit) cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, val); /* Set SSP interval to default value */ - cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, CDNS_DEFAULT_SSP_INTERVAL); - cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, CDNS_DEFAULT_SSP_INTERVAL); + ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ; + cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval); + cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval); +} + +/** + * sdw_cdns_init() - Cadence initialization + * @cdns: Cadence instance + */ +int sdw_cdns_init(struct sdw_cdns *cdns) +{ + u32 val; + + cdns_init_clock_ctrl(cdns); + + /* reset msg_count to default value of FIFOLEVEL */ + cdns->msg_count = cdns_readl(cdns, CDNS_MCP_FIFOLEVEL); /* flush command FIFOs */ cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_RST, @@ -1066,25 +1097,31 @@ int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit) /* Configure mcp config */ val = cdns_readl(cdns, CDNS_MCP_CONFIG); - /* Set Max cmd retry to 15 */ - val |= CDNS_MCP_CONFIG_MCMD_RETRY; + /* enable bus operations with clock and data */ + val &= ~CDNS_MCP_CONFIG_OP; + val |= CDNS_MCP_CONFIG_OP_NORMAL; - /* Set frame delay between PREQ and ping frame to 15 frames */ - val |= 0xF << SDW_REG_SHIFT(CDNS_MCP_CONFIG_MPREQ_DELAY); + /* Set cmd mode for Tx and Rx cmds */ + val &= ~CDNS_MCP_CONFIG_CMD; + + /* Disable sniffer mode */ + val &= ~CDNS_MCP_CONFIG_SNIFFER; /* Disable auto bus release */ val &= ~CDNS_MCP_CONFIG_BUS_REL; - /* Disable sniffer mode */ - val &= ~CDNS_MCP_CONFIG_SNIFFER; + if (cdns->bus.multi_link) + /* Set Multi-master mode to take gsync into account */ + val |= CDNS_MCP_CONFIG_MMASTER; - /* Set cmd mode for Tx and Rx cmds */ - val &= ~CDNS_MCP_CONFIG_CMD; + /* leave frame delay to hardware default of 0x1F */ + + /* leave command retry to hardware default of 0 */ cdns_writel(cdns, CDNS_MCP_CONFIG, val); - /* commit changes */ - return cdns_update_config(cdns); + /* changes will be committed later */ + return 0; } EXPORT_SYMBOL(sdw_cdns_init); @@ -1218,6 +1255,166 @@ static const struct sdw_master_port_ops cdns_port_ops = { }; /** + * sdw_cdns_is_clock_stop: Check clock status + * + * @cdns: Cadence instance + */ +bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns) +{ + return !!(cdns_readl(cdns, CDNS_MCP_STAT) & CDNS_MCP_STAT_CLK_STOP); +} +EXPORT_SYMBOL(sdw_cdns_is_clock_stop); + +/** + * sdw_cdns_clock_stop: Cadence clock stop configuration routine + * + * @cdns: Cadence instance + * @block_wake: prevent wakes if required by the platform + */ +int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake) +{ + bool slave_present = false; + struct sdw_slave *slave; + int ret; + + /* Check suspend status */ + if (sdw_cdns_is_clock_stop(cdns)) { + dev_dbg(cdns->dev, "Clock is already stopped\n"); + return 0; + } + + /* + * Before entering clock stop we mask the Slave + * interrupts. This helps avoid having to deal with e.g. a + * Slave becoming UNATTACHED while the clock is being stopped + */ + cdns_enable_slave_interrupts(cdns, false); + + /* + * For specific platforms, it is required to be able to put + * master into a state in which it ignores wake-up trials + * in clock stop state + */ + if (block_wake) + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_BLOCK_WAKEUP, + CDNS_MCP_CONTROL_BLOCK_WAKEUP); + + list_for_each_entry(slave, &cdns->bus.slaves, node) { + if (slave->status == SDW_SLAVE_ATTACHED || + slave->status == SDW_SLAVE_ALERT) { + slave_present = true; + break; + } + } + + /* + * This CMD_ACCEPT should be used when there are no devices + * attached on the link when entering clock stop mode. If this is + * not set and there is a broadcast write then the command ignored + * will be treated as a failure + */ + if (!slave_present) + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CMD_ACCEPT, + CDNS_MCP_CONTROL_CMD_ACCEPT); + else + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CMD_ACCEPT, 0); + + /* commit changes */ + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config_update failed\n", __func__); + return ret; + } + + /* Prepare slaves for clock stop */ + ret = sdw_bus_prep_clk_stop(&cdns->bus); + if (ret < 0) { + dev_err(cdns->dev, "prepare clock stop failed %d", ret); + return ret; + } + + /* + * Enter clock stop mode and only report errors if there are + * Slave devices present (ALERT or ATTACHED) + */ + ret = sdw_bus_clk_stop(&cdns->bus); + if (ret < 0 && slave_present && ret != -ENODATA) { + dev_err(cdns->dev, "bus clock stop failed %d", ret); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_STAT, + CDNS_MCP_STAT_CLK_STOP, + CDNS_MCP_STAT_CLK_STOP); + if (ret < 0) + dev_err(cdns->dev, "Clock stop failed %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_clock_stop); + +/** + * sdw_cdns_clock_restart: Cadence PM clock restart configuration routine + * + * @cdns: Cadence instance + * @bus_reset: context may be lost while in low power modes and the bus + * may require a Severe Reset and re-enumeration after a wake. + */ +int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset) +{ + int ret; + + /* unmask Slave interrupts that were masked when stopping the clock */ + cdns_enable_slave_interrupts(cdns, true); + + ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CLK_STOP_CLR); + if (ret < 0) { + dev_err(cdns->dev, "Couldn't exit from clock stop\n"); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_STAT, CDNS_MCP_STAT_CLK_STOP, 0); + if (ret < 0) { + dev_err(cdns->dev, "clock stop exit failed %d\n", ret); + return ret; + } + + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_BLOCK_WAKEUP, 0); + + /* + * clear CMD_ACCEPT so that the command ignored + * will be treated as a failure during a broadcast write + */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, 0); + + if (!bus_reset) { + + /* enable bus operations with clock and data */ + cdns_updatel(cdns, CDNS_MCP_CONFIG, + CDNS_MCP_CONFIG_OP, + CDNS_MCP_CONFIG_OP_NORMAL); + + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config_update failed\n", __func__); + return ret; + } + + ret = sdw_bus_exit_clk_stop(&cdns->bus); + if (ret < 0) + dev_err(cdns->dev, "bus failed to exit clock stop %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_clock_restart); + +/** * sdw_cdns_probe() - Cadence probe routine * @cdns: Cadence instance */ @@ -1306,6 +1503,7 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns, cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val); val = pdi->num; + val |= CDNS_PDI_CONFIG_SOFT_RESET; val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL); cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val); } diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 001457cbe5ad..b410656f8194 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -5,6 +5,9 @@ #ifndef __SDW_CADENCE_H #define __SDW_CADENCE_H +#define SDW_CADENCE_GSYNC_KHZ 4 /* 4 kHz */ +#define SDW_CADENCE_GSYNC_HZ (SDW_CADENCE_GSYNC_KHZ * 1000) + /** * struct sdw_cdns_pdi: PDI (Physical Data Interface) instance * @@ -138,30 +141,26 @@ extern struct sdw_master_ops sdw_cdns_master_ops; irqreturn_t sdw_cdns_irq(int irq, void *dev_id); irqreturn_t sdw_cdns_thread(int irq, void *dev_id); -int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit); +int sdw_cdns_init(struct sdw_cdns *cdns); int sdw_cdns_pdi_init(struct sdw_cdns *cdns, struct sdw_cdns_stream_config config); int sdw_cdns_exit_reset(struct sdw_cdns *cdns); int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state); +bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns); +int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake); +int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset); + #ifdef CONFIG_DEBUG_FS void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root); #endif -int sdw_cdns_get_stream(struct sdw_cdns *cdns, - struct sdw_cdns_streams *stream, - u32 ch, u32 dir); struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, struct sdw_cdns_streams *stream, u32 ch, u32 dir, int dai_id); void sdw_cdns_config_stream(struct sdw_cdns *cdns, u32 ch, u32 dir, struct sdw_cdns_pdi *pdi); -int sdw_cdns_pcm_set_stream(struct snd_soc_dai *dai, - void *stream, int direction); -int sdw_cdns_pdm_set_stream(struct snd_soc_dai *dai, - void *stream, int direction); - enum sdw_command_response cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num); diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 06ef3a3ac080..3c83e76c6bf9 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -103,7 +103,7 @@ enum intel_pdi_type { struct sdw_intel { struct sdw_cdns cdns; int instance; - struct sdw_intel_link_res *res; + struct sdw_intel_link_res *link_res; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; #endif @@ -193,8 +193,8 @@ static ssize_t intel_sprintf(void __iomem *mem, bool l, static int intel_reg_show(struct seq_file *s_file, void *data) { struct sdw_intel *sdw = s_file->private; - void __iomem *s = sdw->res->shim; - void __iomem *a = sdw->res->alh; + void __iomem *s = sdw->link_res->shim; + void __iomem *a = sdw->link_res->alh; char *buf; ssize_t ret; int i, j; @@ -289,7 +289,7 @@ static void intel_debugfs_exit(struct sdw_intel *sdw) {} static int intel_link_power_up(struct sdw_intel *sdw) { unsigned int link_id = sdw->instance; - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; int spa_mask, cpa_mask; int link_control, ret; @@ -309,7 +309,7 @@ static int intel_link_power_up(struct sdw_intel *sdw) static int intel_shim_init(struct sdw_intel *sdw) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int sync_reg, ret; u16 ioctl = 0, act = 0; @@ -370,7 +370,7 @@ static int intel_shim_init(struct sdw_intel *sdw) static void intel_pdi_init(struct sdw_intel *sdw, struct sdw_cdns_stream_config *config) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int pcm_cap, pdm_cap; @@ -404,7 +404,7 @@ static void intel_pdi_init(struct sdw_intel *sdw, static int intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int count; @@ -476,7 +476,7 @@ static int intel_pdi_ch_update(struct sdw_intel *sdw) static void intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int pdi_conf = 0; @@ -508,7 +508,7 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) static void intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) { - void __iomem *alh = sdw->res->alh; + void __iomem *alh = sdw->link_res->alh; unsigned int link_id = sdw->instance; unsigned int conf; @@ -535,7 +535,7 @@ static int intel_params_stream(struct sdw_intel *sdw, struct snd_pcm_hw_params *hw_params, int link_id, int alh_stream_id) { - struct sdw_intel_link_res *res = sdw->res; + struct sdw_intel_link_res *res = sdw->link_res; struct sdw_intel_stream_params_data params_data; params_data.substream = substream; @@ -550,6 +550,25 @@ static int intel_params_stream(struct sdw_intel *sdw, return -EIO; } +static int intel_free_stream(struct sdw_intel *sdw, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int link_id) +{ + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_free_data free_data; + + free_data.substream = substream; + free_data.dai = dai; + free_data.link_id = link_id; + + if (res->ops && res->ops->free_stream && res->dev) + return res->ops->free_stream(res->dev, + &free_data); + + return 0; +} + /* * bank switch routines */ @@ -558,7 +577,7 @@ static int intel_pre_bank_switch(struct sdw_bus *bus) { struct sdw_cdns *cdns = bus_to_cdns(bus); struct sdw_intel *sdw = cdns_to_intel(cdns); - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; int sync_reg; /* Write to register only for multi-link */ @@ -577,7 +596,7 @@ static int intel_post_bank_switch(struct sdw_bus *bus) { struct sdw_cdns *cdns = bus_to_cdns(bus); struct sdw_intel *sdw = cdns_to_intel(cdns); - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; int sync_reg, ret; /* Write to register only for multi-link */ @@ -617,6 +636,68 @@ static int intel_post_bank_switch(struct sdw_bus *bus) * DAI routines */ +static int sdw_stream_setup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sdw_stream_runtime *sdw_stream = NULL; + char *name; + int i, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + name = kasprintf(GFP_KERNEL, "%s-Playback", dai->name); + else + name = kasprintf(GFP_KERNEL, "%s-Capture", dai->name); + + if (!name) + return -ENOMEM; + + sdw_stream = sdw_alloc_stream(name); + if (!sdw_stream) { + dev_err(dai->dev, "alloc stream failed for DAI %s", dai->name); + ret = -ENOMEM; + goto error; + } + + /* Set stream pointer on CPU DAI */ + ret = snd_soc_dai_set_sdw_stream(dai, sdw_stream, substream->stream); + if (ret < 0) { + dev_err(dai->dev, "failed to set stream pointer on cpu dai %s", + dai->name); + goto release_stream; + } + + /* Set stream pointer on all CODEC DAIs */ + for (i = 0; i < rtd->num_codecs; i++) { + ret = snd_soc_dai_set_sdw_stream(rtd->codec_dais[i], sdw_stream, + substream->stream); + if (ret < 0) { + dev_err(dai->dev, "failed to set stream pointer on codec dai %s", + rtd->codec_dais[i]->name); + goto release_stream; + } + } + + return 0; + +release_stream: + sdw_release_stream(sdw_stream); +error: + kfree(name); + return ret; +} + +static int intel_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /* + * TODO: add pm_runtime support here, the startup callback + * will make sure the IP is 'active' + */ + + return sdw_stream_setup(substream, dai); +} + static int intel_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -699,10 +780,63 @@ error: return ret; } +static int intel_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns_dma_data *dma; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) { + dev_err(dai->dev, "failed to get dma data in %s", + __func__); + return -EIO; + } + + return sdw_prepare_stream(dma->stream); +} + +static int intel_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sdw_cdns_dma_data *dma; + int ret; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) { + dev_err(dai->dev, "failed to get dma data in %s", __func__); + return -EIO; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + ret = sdw_enable_stream(dma->stream); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = sdw_disable_stream(dma->stream); + break; + + default: + ret = -EINVAL; + break; + } + + if (ret) + dev_err(dai->dev, + "%s trigger %d failed: %d", + __func__, cmd, ret); + return ret; +} + static int intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dma_data *dma; int ret; @@ -710,12 +844,29 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) if (!dma) return -EIO; + ret = sdw_deprepare_stream(dma->stream); + if (ret) { + dev_err(dai->dev, "sdw_deprepare_stream: failed %d", ret); + return ret; + } + ret = sdw_stream_remove_master(&cdns->bus, dma->stream); - if (ret < 0) + if (ret < 0) { dev_err(dai->dev, "remove master from stream %s failed: %d\n", dma->stream->name, ret); + return ret; + } - return ret; + ret = intel_free_stream(sdw, substream, dai, sdw->instance); + if (ret < 0) { + dev_err(dai->dev, "intel_free_stream: failed %d", ret); + return ret; + } + + kfree(dma->stream->name); + sdw_release_stream(dma->stream); + + return 0; } static void intel_shutdown(struct snd_pcm_substream *substream, @@ -744,14 +895,20 @@ static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai, } static const struct snd_soc_dai_ops intel_pcm_dai_ops = { + .startup = intel_startup, .hw_params = intel_hw_params, + .prepare = intel_prepare, + .trigger = intel_trigger, .hw_free = intel_hw_free, .shutdown = intel_shutdown, .set_sdw_stream = intel_pcm_set_sdw_stream, }; static const struct snd_soc_dai_ops intel_pdm_dai_ops = { + .startup = intel_startup, .hw_params = intel_hw_params, + .prepare = intel_prepare, + .trigger = intel_trigger, .hw_free = intel_hw_free, .shutdown = intel_shutdown, .set_sdw_stream = intel_pdm_set_sdw_stream, @@ -920,7 +1077,7 @@ static int intel_init(struct sdw_intel *sdw) intel_link_power_up(sdw); intel_shim_init(sdw); - return sdw_cdns_init(&sdw->cdns, false); + return sdw_cdns_init(&sdw->cdns); } /* @@ -937,9 +1094,9 @@ static int intel_probe(struct platform_device *pdev) return -ENOMEM; sdw->instance = pdev->id; - sdw->res = dev_get_platdata(&pdev->dev); + sdw->link_res = dev_get_platdata(&pdev->dev); sdw->cdns.dev = &pdev->dev; - sdw->cdns.registers = sdw->res->registers; + sdw->cdns.registers = sdw->link_res->registers; sdw->cdns.instance = sdw->instance; sdw->cdns.msg_count = 0; sdw->cdns.bus.dev = &pdev->dev; @@ -979,11 +1136,12 @@ static int intel_probe(struct platform_device *pdev) intel_pdi_ch_update(sdw); /* Acquire IRQ */ - ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq, sdw_cdns_thread, + ret = request_threaded_irq(sdw->link_res->irq, + sdw_cdns_irq, sdw_cdns_thread, IRQF_SHARED, KBUILD_MODNAME, &sdw->cdns); if (ret < 0) { dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n", - sdw->res->irq); + sdw->link_res->irq); goto err_init; } @@ -1013,7 +1171,7 @@ static int intel_probe(struct platform_device *pdev) err_interrupt: sdw_cdns_enable_interrupt(&sdw->cdns, false); - free_irq(sdw->res->irq, sdw); + free_irq(sdw->link_res->irq, sdw); err_init: sdw_delete_bus_master(&sdw->cdns.bus); return ret; @@ -1028,7 +1186,7 @@ static int intel_remove(struct platform_device *pdev) if (!sdw->cdns.bus.prop.hw_disabled) { intel_debugfs_exit(sdw); sdw_cdns_enable_interrupt(&sdw->cdns, false); - free_irq(sdw->res->irq, sdw); + free_irq(sdw->link_res->irq, sdw); snd_soc_unregister_component(sdw->cdns.dev); } sdw_delete_bus_master(&sdw->cdns.bus); diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 440effed6df6..d6c9ad231873 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -588,6 +588,13 @@ static int qcom_swrm_set_sdw_stream(struct snd_soc_dai *dai, return 0; } +static void *qcom_swrm_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + + return ctrl->sruntime[dai->id]; +} + static int qcom_swrm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -632,6 +639,7 @@ static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = { .startup = qcom_swrm_startup, .shutdown = qcom_swrm_shutdown, .set_sdw_stream = qcom_swrm_set_sdw_stream, + .get_sdw_stream = qcom_swrm_get_sdw_stream, }; static const struct snd_soc_component_driver qcom_swrm_dai_component = { diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index 19919975bb6d..aace57fae7f8 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -46,7 +46,11 @@ static int sdw_slave_add(struct sdw_bus *bus, slave->dev.of_node = of_node_get(to_of_node(fwnode)); slave->bus = bus; slave->status = SDW_SLAVE_UNATTACHED; + init_completion(&slave->enumeration_complete); + init_completion(&slave->initialization_complete); slave->dev_num = 0; + init_completion(&slave->probe_complete); + slave->probed = false; mutex_lock(&bus->bus_lock); list_add_tail(&slave->node, &bus->slaves); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 7fb89a94d9c0..a9a72574b34a 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -315,9 +315,9 @@ static int sdw_enable_disable_slave_ports(struct sdw_bus *bus, * it is safe to reset this register */ if (en) - ret = sdw_update(s_rt->slave, addr, 0xFF, p_rt->ch_mask); + ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask); else - ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0); + ret = sdw_write(s_rt->slave, addr, 0x0); if (ret < 0) dev_err(&s_rt->slave->dev, @@ -466,10 +466,9 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, addr = SDW_DPN_PREPARECTRL(p_rt->num); if (prep) - ret = sdw_update(s_rt->slave, addr, - 0xFF, p_rt->ch_mask); + ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask); else - ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0); + ret = sdw_write(s_rt->slave, addr, 0x0); if (ret < 0) { dev_err(&s_rt->slave->dev, @@ -589,10 +588,11 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt) if (slave->ops->bus_config) { ret = slave->ops->bus_config(slave, &bus->params); - if (ret < 0) + if (ret < 0) { dev_err(bus->dev, "Notify Slave: %d failed\n", slave->dev_num); - return ret; + return ret; + } } } @@ -604,13 +604,25 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt) * and Slave(s) * * @bus: SDW bus instance + * @prepare: true if sdw_program_params() is called by _prepare. */ -static int sdw_program_params(struct sdw_bus *bus) +static int sdw_program_params(struct sdw_bus *bus, bool prepare) { struct sdw_master_runtime *m_rt; int ret = 0; list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + + /* + * this loop walks through all master runtimes for a + * bus, but the ports can only be configured while + * explicitly preparing a stream or handling an + * already-prepared stream otherwise. + */ + if (!prepare && + m_rt->stream->state == SDW_STREAM_CONFIGURED) + continue; + ret = sdw_program_port_params(m_rt); if (ret < 0) { dev_err(bus->dev, @@ -1462,7 +1474,8 @@ static void sdw_release_bus_lock(struct sdw_stream_runtime *stream) } } -static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) +static int _sdw_prepare_stream(struct sdw_stream_runtime *stream, + bool update_params) { struct sdw_master_runtime *m_rt; struct sdw_bus *bus = NULL; @@ -1482,6 +1495,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) return -EINVAL; } + if (!update_params) + goto program_params; + /* Increment cumulative bus bandwidth */ /* TODO: Update this during Device-Device support */ bus->params.bandwidth += m_rt->stream->params.rate * @@ -1497,8 +1513,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) } } +program_params: /* Program params */ - ret = sdw_program_params(bus); + ret = sdw_program_params(bus, true); if (ret < 0) { dev_err(bus->dev, "Program params failed: %d\n", ret); goto restore_params; @@ -1546,7 +1563,8 @@ restore_params: */ int sdw_prepare_stream(struct sdw_stream_runtime *stream) { - int ret = 0; + bool update_params = true; + int ret; if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); @@ -1555,8 +1573,32 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); - ret = _sdw_prepare_stream(stream); + if (stream->state == SDW_STREAM_PREPARED) { + ret = 0; + goto state_err; + } + if (stream->state != SDW_STREAM_CONFIGURED && + stream->state != SDW_STREAM_DEPREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + + /* + * when the stream is DISABLED, this means sdw_prepare_stream() + * is called as a result of an underflow or a resume operation. + * In this case, the bus parameters shall not be recomputed, but + * still need to be re-applied + */ + if (stream->state == SDW_STREAM_DISABLED) + update_params = false; + + ret = _sdw_prepare_stream(stream, update_params); + +state_err: sdw_release_bus_lock(stream); return ret; } @@ -1573,7 +1615,7 @@ static int _sdw_enable_stream(struct sdw_stream_runtime *stream) bus = m_rt->bus; /* Program params */ - ret = sdw_program_params(bus); + ret = sdw_program_params(bus, false); if (ret < 0) { dev_err(bus->dev, "Program params failed: %d\n", ret); return ret; @@ -1621,8 +1663,17 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_enable_stream(stream); +state_err: sdw_release_bus_lock(stream); return ret; } @@ -1649,7 +1700,7 @@ static int _sdw_disable_stream(struct sdw_stream_runtime *stream) struct sdw_bus *bus = m_rt->bus; /* Program params */ - ret = sdw_program_params(bus); + ret = sdw_program_params(bus, false); if (ret < 0) { dev_err(bus->dev, "Program params failed: %d\n", ret); return ret; @@ -1695,8 +1746,16 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); + if (stream->state != SDW_STREAM_ENABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_disable_stream(stream); +state_err: sdw_release_bus_lock(stream); return ret; } @@ -1723,7 +1782,7 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) m_rt->ch_count * m_rt->stream->params.bps; /* Program params */ - ret = sdw_program_params(bus); + ret = sdw_program_params(bus, false); if (ret < 0) { dev_err(bus->dev, "Program params failed: %d\n", ret); return ret; @@ -1751,8 +1810,18 @@ int sdw_deprepare_stream(struct sdw_stream_runtime *stream) } sdw_acquire_bus_lock(stream); + + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_deprepare_stream(stream); +state_err: sdw_release_bus_lock(stream); return ret; } diff --git a/drivers/staging/speakup/devsynth.c b/drivers/staging/speakup/devsynth.c index d920256328c3..d30571663585 100644 --- a/drivers/staging/speakup/devsynth.c +++ b/drivers/staging/speakup/devsynth.c @@ -1,16 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/errno.h> -#include <linux/miscdevice.h> /* for misc_register, and SYNTH_MINOR */ +#include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */ #include <linux/types.h> #include <linux/uaccess.h> #include "speakup.h" #include "spk_priv.h" -#ifndef SYNTH_MINOR -#define SYNTH_MINOR 25 -#endif - static int misc_registered; static int dev_opened; @@ -67,7 +63,7 @@ static const struct file_operations synth_fops = { }; static struct miscdevice synth_device = { - .minor = SYNTH_MINOR, + .minor = MISC_DYNAMIC_MINOR, .name = "synth", .fops = &synth_fops, }; @@ -81,7 +77,7 @@ void speakup_register_devsynth(void) pr_warn("Couldn't initialize miscdevice /dev/synth.\n"); } else { pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n", - MISC_MAJOR, SYNTH_MINOR); + MISC_MAJOR, synth_device.minor); misc_registered = 1; } } diff --git a/drivers/staging/speakup/speakup_soft.c b/drivers/staging/speakup/speakup_soft.c index 28cedaec6d8a..f591ec095582 100644 --- a/drivers/staging/speakup/speakup_soft.c +++ b/drivers/staging/speakup/speakup_soft.c @@ -10,7 +10,7 @@ */ #include <linux/unistd.h> -#include <linux/miscdevice.h> /* for misc_register, and SYNTH_MINOR */ +#include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */ #include <linux/poll.h> /* for poll_wait() */ /* schedule(), signal_pending(), TASK_INTERRUPTIBLE */ @@ -20,8 +20,6 @@ #include "speakup.h" #define DRV_VERSION "2.6" -#define SOFTSYNTH_MINOR 26 /* might as well give it one more than /dev/synth */ -#define SOFTSYNTHU_MINOR 27 /* might as well give it one more than /dev/synth */ #define PROCSPEECH 0x0d #define CLEAR_SYNTH 0x18 @@ -375,7 +373,7 @@ static int softsynth_probe(struct spk_synth *synth) if (misc_registered != 0) return 0; memset(&synth_device, 0, sizeof(synth_device)); - synth_device.minor = SOFTSYNTH_MINOR; + synth_device.minor = MISC_DYNAMIC_MINOR; synth_device.name = "softsynth"; synth_device.fops = &softsynth_fops; if (misc_register(&synth_device)) { @@ -384,7 +382,7 @@ static int softsynth_probe(struct spk_synth *synth) } memset(&synthu_device, 0, sizeof(synthu_device)); - synthu_device.minor = SOFTSYNTHU_MINOR; + synthu_device.minor = MISC_DYNAMIC_MINOR; synthu_device.name = "softsynthu"; synthu_device.fops = &softsynthu_fops; if (misc_register(&synthu_device)) { @@ -393,8 +391,10 @@ static int softsynth_probe(struct spk_synth *synth) } misc_registered = 1; - pr_info("initialized device: /dev/softsynth, node (MAJOR 10, MINOR 26)\n"); - pr_info("initialized device: /dev/softsynthu, node (MAJOR 10, MINOR 27)\n"); + pr_info("initialized device: /dev/softsynth, node (MAJOR 10, MINOR %d)\n", + synth_device.minor); + pr_info("initialized device: /dev/softsynthu, node (MAJOR 10, MINOR %d)\n", + synthu_device.minor); return 0; } diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index a312cb33a99b..2dff93d7a501 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -137,7 +137,6 @@ config LEGACY_PTYS security. This option enables these legacy devices; on most systems, it is safe to say N. - config LEGACY_PTY_COUNT int "Maximum number of legacy PTY in use" depends on LEGACY_PTYS @@ -151,6 +150,31 @@ config LEGACY_PTY_COUNT When not in use, each legacy PTY occupies 12 bytes on 32-bit architectures and 24 bytes on 64-bit architectures. +config LDISC_AUTOLOAD + bool "Automatically load TTY Line Disciplines" + default y + help + Historically the kernel has always automatically loaded any + line discipline that is in a kernel module when a user asks + for it to be loaded with the TIOCSETD ioctl, or through other + means. This is not always the best thing to do on systems + where you know you will not be using some of the more + "ancient" line disciplines, so prevent the kernel from doing + this unless the request is coming from a process with the + CAP_SYS_MODULE permissions. + + Say 'Y' here if you trust your userspace users to do the right + thing, or if you have only provided the line disciplines that + you know you will be using, or if you wish to continue to use + the traditional method of on-demand loading of these modules + by any user. + + This functionality can be changed at runtime with the + dev.tty.ldisc_autoload sysctl, this configuration option will + only set the default value of this functionality. + +source "drivers/tty/serial/Kconfig" + config SERIAL_NONSTANDARD bool "Non-standard serial port support" depends on HAS_IOMEM @@ -270,16 +294,6 @@ config SYNCLINK_GT synchronous and asynchronous serial adapters manufactured by Microgate Systems, Ltd. (www.microgate.com) -config NOZOMI - tristate "HSDPA Broadband Wireless Data Card - Globe Trotter" - depends on PCI - help - If you have a HSDPA driver Broadband Wireless Data Card - - Globe Trotter PCMCIA card, say Y here. - - To compile this driver as a module, choose M here, the module - will be called nozomi. - config ISI tristate "Multi-Tech multiport card support" depends on SERIAL_NONSTANDARD && PCI @@ -302,43 +316,6 @@ config N_HDLC The module will be called n_hdlc. If you want to do that, say M here. -config N_GSM - tristate "GSM MUX line discipline support (EXPERIMENTAL)" - depends on NET - help - This line discipline provides support for the GSM MUX protocol and - presents the mux as a set of 61 individual tty devices. - -config TRACE_ROUTER - tristate "Trace data router for MIPI P1149.7 cJTAG standard" - depends on TRACE_SINK - help - The trace router uses the Linux tty line discipline framework to - route trace data coming from a tty port (say UART for example) to - the trace sink line discipline driver and to another tty port (say - USB). This is part of a solution for the MIPI P1149.7, compact JTAG, - standard, which is for debugging mobile devices. The PTI driver in - drivers/misc/pti.c defines the majority of this MIPI solution. - - You should select this driver if the target kernel is meant for - a mobile device containing a modem. Then you will need to select - "Trace data sink for MIPI P1149.7 cJTAG standard" line discipline - driver. - -config TRACE_SINK - tristate "Trace data sink for MIPI P1149.7 cJTAG standard" - help - The trace sink uses the Linux line discipline framework to receive - trace data coming from the trace router line discipline driver - to a user-defined tty port target, like USB. - This is to provide a way to extract modem trace data on - devices that do not have a PTI HW module, or just need modem - trace data to come out of a different HW output port. - This is part of a solution for the P1149.7, compact JTAG, standard. - - If you select this option, you need to select - "Trace data router for MIPI P1149.7 cJTAG standard". - config PPC_EPAPR_HV_BYTECHAN bool "ePAPR hypervisor byte channel driver" depends on PPC @@ -374,20 +351,6 @@ config PPC_EARLY_DEBUG_EHV_BC_HANDLE there simply will be no early console output. This is true also if you don't boot under a hypervisor at all. -config NULL_TTY - tristate "NULL TTY driver" - help - Say Y here if you want a NULL TTY which simply discards messages. - - This is useful to allow userspace applications which expect a console - device to work without modifications even when no console is - available or desired. - - In order to use this driver, you should redirect the console to this - TTY, or boot the kernel with console=ttynull. - - If unsure, say N. - config GOLDFISH_TTY tristate "Goldfish TTY Driver" depends on GOLDFISH @@ -401,6 +364,23 @@ config GOLDFISH_TTY_EARLY_CONSOLE default y if GOLDFISH_TTY=y select SERIAL_EARLYCON +config N_GSM + tristate "GSM MUX line discipline support (EXPERIMENTAL)" + depends on NET + help + This line discipline provides support for the GSM MUX protocol and + presents the mux as a set of 61 individual tty devices. + +config NOZOMI + tristate "HSDPA Broadband Wireless Data Card - Globe Trotter" + depends on PCI + help + If you have a HSDPA driver Broadband Wireless Data Card - + Globe Trotter PCMCIA card, say Y here. + + To compile this driver as a module, choose M here, the module + will be called nozomi. + config MIPS_EJTAG_FDC_TTY bool "MIPS EJTAG Fast Debug Channel TTY" depends on MIPS_CDMM @@ -448,33 +428,58 @@ config MIPS_EJTAG_FDC_KGDB_CHAN help FDC channel number to use for KGDB. +config NULL_TTY + tristate "NULL TTY driver" + help + Say Y here if you want a NULL TTY which simply discards messages. + + This is useful to allow userspace applications which expect a console + device to work without modifications even when no console is + available or desired. + + In order to use this driver, you should redirect the console to this + TTY, or boot the kernel with console=ttynull. + + If unsure, say N. + +config TRACE_ROUTER + tristate "Trace data router for MIPI P1149.7 cJTAG standard" + depends on TRACE_SINK + help + The trace router uses the Linux tty line discipline framework to + route trace data coming from a tty port (say UART for example) to + the trace sink line discipline driver and to another tty port (say + USB). This is part of a solution for the MIPI P1149.7, compact JTAG, + standard, which is for debugging mobile devices. The PTI driver in + drivers/misc/pti.c defines the majority of this MIPI solution. + + You should select this driver if the target kernel is meant for + a mobile device containing a modem. Then you will need to select + "Trace data sink for MIPI P1149.7 cJTAG standard" line discipline + driver. + +config TRACE_SINK + tristate "Trace data sink for MIPI P1149.7 cJTAG standard" + help + The trace sink uses the Linux line discipline framework to receive + trace data coming from the trace router line discipline driver + to a user-defined tty port target, like USB. + This is to provide a way to extract modem trace data on + devices that do not have a PTI HW module, or just need modem + trace data to come out of a different HW output port. + This is part of a solution for the P1149.7, compact JTAG, standard. + + If you select this option, you need to select + "Trace data router for MIPI P1149.7 cJTAG standard". + config VCC tristate "Sun Virtual Console Concentrator" depends on SUN_LDOMS help Support for Sun logical domain consoles. -config LDISC_AUTOLOAD - bool "Automatically load TTY Line Disciplines" - default y - help - Historically the kernel has always automatically loaded any - line discipline that is in a kernel module when a user asks - for it to be loaded with the TIOCSETD ioctl, or through other - means. This is not always the best thing to do on systems - where you know you will not be using some of the more - "ancient" line disciplines, so prevent the kernel from doing - this unless the request is coming from a process with the - CAP_SYS_MODULE permissions. - - Say 'Y' here if you trust your userspace users to do the right - thing, or if you have only provided the line disciplines that - you know you will be using, or if you wish to continue to use - the traditional method of on-demand loading of these modules - by any user. - - This functionality can be changed at runtime with the - dev.tty.ldisc_autoload sysctl, this configuration option will - only set the default value of this functionality. +source "drivers/tty/hvc/Kconfig" endif # TTY + +source "drivers/tty/serdev/Kconfig" diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig index 6a3c97d345a0..31b7e1b03749 100644 --- a/drivers/tty/hvc/Kconfig +++ b/drivers/tty/hvc/Kconfig @@ -1,5 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -if TTY config HVC_DRIVER bool @@ -113,5 +112,3 @@ config HVCS will depend on arch specific APIs exported from hvcserver.ko which will also be compiled when this driver is built as a module. - -endif # TTY diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index d6644f3d81fc..0aea76cd67ff 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -3,8 +3,6 @@ # Serial device configuration # -if TTY - menu "Serial drivers" depends on HAS_IOMEM @@ -1568,5 +1566,3 @@ endmenu config SERIAL_MCTRL_GPIO tristate - -endif # TTY diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index a57698985f9c..6e725c6c6256 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -996,6 +996,44 @@ err_device_create: } EXPORT_SYMBOL_GPL(__uio_register_device); +static void devm_uio_unregister_device(struct device *dev, void *res) +{ + uio_unregister_device(*(struct uio_info **)res); +} + +/** + * devm_uio_register_device - Resource managed uio_register_device() + * @owner: module that creates the new device + * @parent: parent device + * @info: UIO device capabilities + * + * returns zero on success or a negative error code. + */ +int __devm_uio_register_device(struct module *owner, + struct device *parent, + struct uio_info *info) +{ + struct uio_info **ptr; + int ret; + + ptr = devres_alloc(devm_uio_unregister_device, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + *ptr = info; + ret = __uio_register_device(owner, parent, info); + if (ret) { + devres_free(ptr); + return ret; + } + + devres_add(parent, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(__devm_uio_register_device); + /** * uio_unregister_device - unregister a industrial IO device * @info: UIO device capabilities diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index fc25ce90da3b..ae319ef3a832 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -99,6 +99,13 @@ static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) return 0; } +static void uio_pdrv_genirq_cleanup(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + static int uio_pdrv_genirq_probe(struct platform_device *pdev) { struct uio_info *uioinfo = dev_get_platdata(&pdev->dev); @@ -213,28 +220,16 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) */ pm_runtime_enable(&pdev->dev); - ret = uio_register_device(&pdev->dev, priv->uioinfo); - if (ret) { - dev_err(&pdev->dev, "unable to register uio device\n"); - pm_runtime_disable(&pdev->dev); + ret = devm_add_action_or_reset(&pdev->dev, uio_pdrv_genirq_cleanup, + &pdev->dev); + if (ret) return ret; - } - - platform_set_drvdata(pdev, priv); - return 0; -} - -static int uio_pdrv_genirq_remove(struct platform_device *pdev) -{ - struct uio_pdrv_genirq_platdata *priv = platform_get_drvdata(pdev); - uio_unregister_device(priv->uioinfo); - pm_runtime_disable(&pdev->dev); - - priv->uioinfo->handler = NULL; - priv->uioinfo->irqcontrol = NULL; + ret = devm_uio_register_device(&pdev->dev, priv->uioinfo); + if (ret) + dev_err(&pdev->dev, "unable to register uio device\n"); - return 0; + return ret; } static int uio_pdrv_genirq_runtime_nop(struct device *dev) @@ -271,7 +266,6 @@ MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio"); static struct platform_driver uio_pdrv_genirq = { .probe = uio_pdrv_genirq_probe, - .remove = uio_pdrv_genirq_remove, .driver = { .name = DRIVER_NAME, .pm = &uio_pdrv_genirq_dev_pm_ops, diff --git a/drivers/video/fbdev/pxa3xx-gcu.c b/drivers/video/fbdev/pxa3xx-gcu.c index 74ffb446e00c..4279e13a3b58 100644 --- a/drivers/video/fbdev/pxa3xx-gcu.c +++ b/drivers/video/fbdev/pxa3xx-gcu.c @@ -36,7 +36,6 @@ #include "pxa3xx-gcu.h" #define DRV_NAME "pxa3xx-gcu" -#define MISCDEV_MINOR 197 #define REG_GCCR 0x00 #define GCCR_SYNC_CLR (1 << 9) @@ -595,7 +594,7 @@ static int pxa3xx_gcu_probe(struct platform_device *pdev) * container_of(). This isn't really necessary as we have a fixed minor * number anyway, but this is to avoid statics. */ - priv->misc_dev.minor = MISCDEV_MINOR, + priv->misc_dev.minor = PXA3XX_GCU_MINOR, priv->misc_dev.name = DRV_NAME, priv->misc_dev.fops = &pxa3xx_gcu_miscdev_fops; @@ -638,7 +637,7 @@ static int pxa3xx_gcu_probe(struct platform_device *pdev) ret = misc_register(&priv->misc_dev); if (ret < 0) { dev_err(dev, "misc_register() for minor %d failed\n", - MISCDEV_MINOR); + PXA3XX_GCU_MINOR); goto err_free_dma; } @@ -714,7 +713,7 @@ module_platform_driver(pxa3xx_gcu_driver); MODULE_DESCRIPTION("PXA3xx graphics controller unit driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(MISCDEV_MINOR); +MODULE_ALIAS_MISCDEV(PXA3XX_GCU_MINOR); MODULE_AUTHOR("Janine Kropp <nin@directfb.org>, " "Denis Oliver Kropp <dok@directfb.org>, " "Daniel Mack <daniel@caiaq.de>"); diff --git a/drivers/virt/vboxguest/vboxguest_core.c b/drivers/virt/vboxguest/vboxguest_core.c index d823d558c0c4..b690a8a4bf9e 100644 --- a/drivers/virt/vboxguest/vboxguest_core.c +++ b/drivers/virt/vboxguest/vboxguest_core.c @@ -1553,8 +1553,8 @@ int vbg_core_ioctl(struct vbg_session *session, unsigned int req, void *data) #ifdef CONFIG_COMPAT case VBG_IOCTL_HGCM_CALL_32(0): f32bit = true; + fallthrough; #endif - /* Fall through */ case VBG_IOCTL_HGCM_CALL(0): return vbg_ioctl_hgcm_call(gdev, session, f32bit, data); case VBG_IOCTL_LOG(0): diff --git a/drivers/virt/vboxguest/vboxguest_utils.c b/drivers/virt/vboxguest/vboxguest_utils.c index 50920b6fc319..7396187ee32a 100644 --- a/drivers/virt/vboxguest/vboxguest_utils.c +++ b/drivers/virt/vboxguest/vboxguest_utils.c @@ -311,7 +311,7 @@ static u32 hgcm_call_linear_addr_type_to_pagelist_flags( switch (type) { default: WARN_ON(1); - /* Fall through */ + fallthrough; case VMMDEV_HGCM_PARM_TYPE_LINADDR: case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL: return VMMDEV_HGCM_F_PARM_DIRECTION_BOTH; diff --git a/include/dt-bindings/arm/coresight-cti-dt.h b/include/dt-bindings/arm/coresight-cti-dt.h new file mode 100644 index 000000000000..61e7bdf8ea6e --- /dev/null +++ b/include/dt-bindings/arm/coresight-cti-dt.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This header provides constants for the defined trigger signal + * types on CoreSight CTI. + */ + +#ifndef _DT_BINDINGS_ARM_CORESIGHT_CTI_DT_H +#define _DT_BINDINGS_ARM_CORESIGHT_CTI_DT_H + +#define GEN_IO 0 +#define GEN_INTREQ 1 +#define GEN_INTACK 2 +#define GEN_HALTREQ 3 +#define GEN_RESTARTREQ 4 +#define PE_EDBGREQ 5 +#define PE_DBGRESTART 6 +#define PE_CTIIRQ 7 +#define PE_PMUIRQ 8 +#define PE_DBGTRIGGER 9 +#define ETM_EXTOUT 10 +#define ETM_EXTIN 11 +#define SNK_FULL 12 +#define SNK_ACQCOMP 13 +#define SNK_FLUSHCOMP 14 +#define SNK_FLUSHIN 15 +#define SNK_TRIGIN 16 +#define STM_ASYNCOUT 17 +#define STM_TOUT_SPTE 18 +#define STM_TOUT_SW 19 +#define STM_TOUT_HETE 20 +#define STM_HWEVENT 21 +#define ELA_TSTART 22 +#define ELA_TSTOP 23 +#define ELA_DBGREQ 24 +#define CTI_TRIG_MAX 25 + +#endif /*_DT_BINDINGS_ARM_CORESIGHT_CTI_DT_H */ diff --git a/include/dt-bindings/interconnect/qcom,osm-l3.h b/include/dt-bindings/interconnect/qcom,osm-l3.h new file mode 100644 index 000000000000..54858ff7674d --- /dev/null +++ b/include/dt-bindings/interconnect/qcom,osm-l3.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 The Linux Foundation. All rights reserved. + */ + +#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_OSM_L3_H +#define __DT_BINDINGS_INTERCONNECT_QCOM_OSM_L3_H + +#define MASTER_OSM_L3_APPS 0 +#define SLAVE_OSM_L3 1 + +#endif diff --git a/include/dt-bindings/interconnect/qcom,sc7180.h b/include/dt-bindings/interconnect/qcom,sc7180.h new file mode 100644 index 000000000000..f9970f6032eb --- /dev/null +++ b/include/dt-bindings/interconnect/qcom,sc7180.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Qualcomm SC7180 interconnect IDs + * + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_SC7180_H +#define __DT_BINDINGS_INTERCONNECT_QCOM_SC7180_H + +#define MASTER_A1NOC_CFG 0 +#define MASTER_QSPI 1 +#define MASTER_QUP_0 2 +#define MASTER_SDCC_2 3 +#define MASTER_EMMC 4 +#define MASTER_UFS_MEM 5 +#define SLAVE_A1NOC_SNOC 6 +#define SLAVE_SERVICE_A1NOC 7 + +#define MASTER_A2NOC_CFG 0 +#define MASTER_QDSS_BAM 1 +#define MASTER_QUP_1 2 +#define MASTER_USB3 3 +#define MASTER_CRYPTO 4 +#define MASTER_IPA 5 +#define MASTER_QDSS_ETR 6 +#define SLAVE_A2NOC_SNOC 7 +#define SLAVE_SERVICE_A2NOC 8 + +#define MASTER_CAMNOC_HF0_UNCOMP 0 +#define MASTER_CAMNOC_HF1_UNCOMP 1 +#define MASTER_CAMNOC_SF_UNCOMP 2 +#define SLAVE_CAMNOC_UNCOMP 3 + +#define MASTER_NPU 0 +#define MASTER_NPU_PROC 1 +#define SLAVE_CDSP_GEM_NOC 2 + +#define MASTER_SNOC_CNOC 0 +#define MASTER_QDSS_DAP 1 +#define SLAVE_A1NOC_CFG 2 +#define SLAVE_A2NOC_CFG 3 +#define SLAVE_AHB2PHY_SOUTH 4 +#define SLAVE_AHB2PHY_CENTER 5 +#define SLAVE_AOP 6 +#define SLAVE_AOSS 7 +#define SLAVE_BOOT_ROM 8 +#define SLAVE_CAMERA_CFG 9 +#define SLAVE_CAMERA_NRT_THROTTLE_CFG 10 +#define SLAVE_CAMERA_RT_THROTTLE_CFG 11 +#define SLAVE_CLK_CTL 12 +#define SLAVE_RBCPR_CX_CFG 13 +#define SLAVE_RBCPR_MX_CFG 14 +#define SLAVE_CRYPTO_0_CFG 15 +#define SLAVE_DCC_CFG 16 +#define SLAVE_CNOC_DDRSS 17 +#define SLAVE_DISPLAY_CFG 18 +#define SLAVE_DISPLAY_RT_THROTTLE_CFG 19 +#define SLAVE_DISPLAY_THROTTLE_CFG 20 +#define SLAVE_EMMC_CFG 21 +#define SLAVE_GLM 22 +#define SLAVE_GFX3D_CFG 23 +#define SLAVE_IMEM_CFG 24 +#define SLAVE_IPA_CFG 25 +#define SLAVE_CNOC_MNOC_CFG 26 +#define SLAVE_CNOC_MSS 27 +#define SLAVE_NPU_CFG 28 +#define SLAVE_NPU_DMA_BWMON_CFG 29 +#define SLAVE_NPU_PROC_BWMON_CFG 30 +#define SLAVE_PDM 31 +#define SLAVE_PIMEM_CFG 32 +#define SLAVE_PRNG 33 +#define SLAVE_QDSS_CFG 34 +#define SLAVE_QM_CFG 35 +#define SLAVE_QM_MPU_CFG 36 +#define SLAVE_QSPI_0 37 +#define SLAVE_QUP_0 38 +#define SLAVE_QUP_1 39 +#define SLAVE_SDCC_2 40 +#define SLAVE_SECURITY 41 +#define SLAVE_SNOC_CFG 42 +#define SLAVE_TCSR 43 +#define SLAVE_TLMM_WEST 44 +#define SLAVE_TLMM_NORTH 45 +#define SLAVE_TLMM_SOUTH 46 +#define SLAVE_UFS_MEM_CFG 47 +#define SLAVE_USB3 48 +#define SLAVE_VENUS_CFG 49 +#define SLAVE_VENUS_THROTTLE_CFG 50 +#define SLAVE_VSENSE_CTRL_CFG 51 +#define SLAVE_SERVICE_CNOC 52 + +#define MASTER_CNOC_DC_NOC 0 +#define SLAVE_GEM_NOC_CFG 1 +#define SLAVE_LLCC_CFG 2 + +#define MASTER_APPSS_PROC 0 +#define MASTER_SYS_TCU 1 +#define MASTER_GEM_NOC_CFG 2 +#define MASTER_COMPUTE_NOC 3 +#define MASTER_MNOC_HF_MEM_NOC 4 +#define MASTER_MNOC_SF_MEM_NOC 5 +#define MASTER_SNOC_GC_MEM_NOC 6 +#define MASTER_SNOC_SF_MEM_NOC 7 +#define MASTER_GFX3D 8 +#define SLAVE_MSS_PROC_MS_MPU_CFG 9 +#define SLAVE_GEM_NOC_SNOC 10 +#define SLAVE_LLCC 11 +#define SLAVE_SERVICE_GEM_NOC 12 + +#define MASTER_IPA_CORE 0 +#define SLAVE_IPA_CORE 1 + +#define MASTER_LLCC 0 +#define SLAVE_EBI1 1 + +#define MASTER_CNOC_MNOC_CFG 0 +#define MASTER_CAMNOC_HF0 1 +#define MASTER_CAMNOC_HF1 2 +#define MASTER_CAMNOC_SF 3 +#define MASTER_MDP0 4 +#define MASTER_ROTATOR 5 +#define MASTER_VIDEO_P0 6 +#define MASTER_VIDEO_PROC 7 +#define SLAVE_MNOC_HF_MEM_NOC 8 +#define SLAVE_MNOC_SF_MEM_NOC 9 +#define SLAVE_SERVICE_MNOC 10 + +#define MASTER_NPU_SYS 0 +#define MASTER_NPU_NOC_CFG 1 +#define SLAVE_NPU_CAL_DP0 2 +#define SLAVE_NPU_CP 3 +#define SLAVE_NPU_INT_DMA_BWMON_CFG 4 +#define SLAVE_NPU_DPM 5 +#define SLAVE_ISENSE_CFG 6 +#define SLAVE_NPU_LLM_CFG 7 +#define SLAVE_NPU_TCM 8 +#define SLAVE_NPU_COMPUTE_NOC 9 +#define SLAVE_SERVICE_NPU_NOC 10 + +#define MASTER_QUP_CORE_0 0 +#define MASTER_QUP_CORE_1 1 +#define SLAVE_QUP_CORE_0 2 +#define SLAVE_QUP_CORE_1 3 + +#define MASTER_SNOC_CFG 0 +#define MASTER_A1NOC_SNOC 1 +#define MASTER_A2NOC_SNOC 2 +#define MASTER_GEM_NOC_SNOC 3 +#define MASTER_PIMEM 4 +#define SLAVE_APPSS 5 +#define SLAVE_SNOC_CNOC 6 +#define SLAVE_SNOC_GEM_NOC_GC 7 +#define SLAVE_SNOC_GEM_NOC_SF 8 +#define SLAVE_IMEM 9 +#define SLAVE_PIMEM 10 +#define SLAVE_SERVICE_SNOC 11 +#define SLAVE_QDSS_STM 12 +#define SLAVE_TCU 13 + +#endif diff --git a/include/dt-bindings/interconnect/qcom,sdm845.h b/include/dt-bindings/interconnect/qcom,sdm845.h index 7b2393be7361..290be38f40e6 100644 --- a/include/dt-bindings/interconnect/qcom,sdm845.h +++ b/include/dt-bindings/interconnect/qcom,sdm845.h @@ -10,134 +10,139 @@ #define __DT_BINDINGS_INTERCONNECT_QCOM_SDM845_H #define MASTER_A1NOC_CFG 0 -#define MASTER_BLSP_1 1 -#define MASTER_TSIF 2 -#define MASTER_SDCC_2 3 -#define MASTER_SDCC_4 4 -#define MASTER_UFS_CARD 5 -#define MASTER_UFS_MEM 6 -#define MASTER_PCIE_0 7 -#define MASTER_A2NOC_CFG 8 -#define MASTER_QDSS_BAM 9 -#define MASTER_BLSP_2 10 -#define MASTER_CNOC_A2NOC 11 -#define MASTER_CRYPTO 12 -#define MASTER_IPA 13 -#define MASTER_PCIE_1 14 -#define MASTER_QDSS_ETR 15 -#define MASTER_USB3_0 16 -#define MASTER_USB3_1 17 -#define MASTER_CAMNOC_HF0_UNCOMP 18 -#define MASTER_CAMNOC_HF1_UNCOMP 19 -#define MASTER_CAMNOC_SF_UNCOMP 20 -#define MASTER_SPDM 21 -#define MASTER_TIC 22 -#define MASTER_SNOC_CNOC 23 -#define MASTER_QDSS_DAP 24 -#define MASTER_CNOC_DC_NOC 25 -#define MASTER_APPSS_PROC 26 -#define MASTER_GNOC_CFG 27 -#define MASTER_LLCC 28 -#define MASTER_TCU_0 29 -#define MASTER_MEM_NOC_CFG 30 -#define MASTER_GNOC_MEM_NOC 31 -#define MASTER_MNOC_HF_MEM_NOC 32 -#define MASTER_MNOC_SF_MEM_NOC 33 -#define MASTER_SNOC_GC_MEM_NOC 34 -#define MASTER_SNOC_SF_MEM_NOC 35 -#define MASTER_GFX3D 36 -#define MASTER_CNOC_MNOC_CFG 37 -#define MASTER_CAMNOC_HF0 38 -#define MASTER_CAMNOC_HF1 39 -#define MASTER_CAMNOC_SF 40 -#define MASTER_MDP0 41 -#define MASTER_MDP1 42 -#define MASTER_ROTATOR 43 -#define MASTER_VIDEO_P0 44 -#define MASTER_VIDEO_P1 45 -#define MASTER_VIDEO_PROC 46 -#define MASTER_SNOC_CFG 47 -#define MASTER_A1NOC_SNOC 48 -#define MASTER_A2NOC_SNOC 49 -#define MASTER_GNOC_SNOC 50 -#define MASTER_MEM_NOC_SNOC 51 -#define MASTER_ANOC_PCIE_SNOC 52 -#define MASTER_PIMEM 53 -#define MASTER_GIC 54 -#define SLAVE_A1NOC_SNOC 55 -#define SLAVE_SERVICE_A1NOC 56 -#define SLAVE_ANOC_PCIE_A1NOC_SNOC 57 -#define SLAVE_A2NOC_SNOC 58 -#define SLAVE_ANOC_PCIE_SNOC 59 -#define SLAVE_SERVICE_A2NOC 60 -#define SLAVE_CAMNOC_UNCOMP 61 -#define SLAVE_A1NOC_CFG 62 -#define SLAVE_A2NOC_CFG 63 -#define SLAVE_AOP 64 -#define SLAVE_AOSS 65 -#define SLAVE_CAMERA_CFG 66 -#define SLAVE_CLK_CTL 67 -#define SLAVE_CDSP_CFG 68 -#define SLAVE_RBCPR_CX_CFG 69 -#define SLAVE_CRYPTO_0_CFG 70 -#define SLAVE_DCC_CFG 71 -#define SLAVE_CNOC_DDRSS 72 -#define SLAVE_DISPLAY_CFG 73 -#define SLAVE_GLM 74 -#define SLAVE_GFX3D_CFG 75 -#define SLAVE_IMEM_CFG 76 -#define SLAVE_IPA_CFG 77 -#define SLAVE_CNOC_MNOC_CFG 78 -#define SLAVE_PCIE_0_CFG 79 -#define SLAVE_PCIE_1_CFG 80 -#define SLAVE_PDM 81 -#define SLAVE_SOUTH_PHY_CFG 82 -#define SLAVE_PIMEM_CFG 83 -#define SLAVE_PRNG 84 -#define SLAVE_QDSS_CFG 85 -#define SLAVE_BLSP_2 86 -#define SLAVE_BLSP_1 87 -#define SLAVE_SDCC_2 88 -#define SLAVE_SDCC_4 89 -#define SLAVE_SNOC_CFG 90 -#define SLAVE_SPDM_WRAPPER 91 -#define SLAVE_SPSS_CFG 92 -#define SLAVE_TCSR 93 -#define SLAVE_TLMM_NORTH 94 -#define SLAVE_TLMM_SOUTH 95 -#define SLAVE_TSIF 96 -#define SLAVE_UFS_CARD_CFG 97 -#define SLAVE_UFS_MEM_CFG 98 -#define SLAVE_USB3_0 99 -#define SLAVE_USB3_1 100 -#define SLAVE_VENUS_CFG 101 -#define SLAVE_VSENSE_CTRL_CFG 102 -#define SLAVE_CNOC_A2NOC 103 -#define SLAVE_SERVICE_CNOC 104 -#define SLAVE_LLCC_CFG 105 -#define SLAVE_MEM_NOC_CFG 106 -#define SLAVE_GNOC_SNOC 107 -#define SLAVE_GNOC_MEM_NOC 108 -#define SLAVE_SERVICE_GNOC 109 -#define SLAVE_EBI1 110 -#define SLAVE_MSS_PROC_MS_MPU_CFG 111 -#define SLAVE_MEM_NOC_GNOC 112 -#define SLAVE_LLCC 113 -#define SLAVE_MEM_NOC_SNOC 114 -#define SLAVE_SERVICE_MEM_NOC 115 -#define SLAVE_MNOC_SF_MEM_NOC 116 -#define SLAVE_MNOC_HF_MEM_NOC 117 -#define SLAVE_SERVICE_MNOC 118 -#define SLAVE_APPSS 119 -#define SLAVE_SNOC_CNOC 120 -#define SLAVE_SNOC_MEM_NOC_GC 121 -#define SLAVE_SNOC_MEM_NOC_SF 122 -#define SLAVE_IMEM 123 -#define SLAVE_PCIE_0 124 -#define SLAVE_PCIE_1 125 -#define SLAVE_PIMEM 126 -#define SLAVE_SERVICE_SNOC 127 -#define SLAVE_QDSS_STM 128 -#define SLAVE_TCU 129 +#define MASTER_TSIF 1 +#define MASTER_SDCC_2 2 +#define MASTER_SDCC_4 3 +#define MASTER_UFS_CARD 4 +#define MASTER_UFS_MEM 5 +#define MASTER_PCIE_0 6 +#define SLAVE_A1NOC_SNOC 7 +#define SLAVE_SERVICE_A1NOC 8 +#define SLAVE_ANOC_PCIE_A1NOC_SNOC 9 + +#define MASTER_A2NOC_CFG 0 +#define MASTER_QDSS_BAM 1 +#define MASTER_CNOC_A2NOC 2 +#define MASTER_CRYPTO 3 +#define MASTER_IPA 4 +#define MASTER_PCIE_1 5 +#define MASTER_QDSS_ETR 6 +#define MASTER_USB3_0 7 +#define MASTER_USB3_1 8 +#define SLAVE_A2NOC_SNOC 9 +#define SLAVE_ANOC_PCIE_SNOC 10 +#define SLAVE_SERVICE_A2NOC 11 + +#define MASTER_SPDM 0 +#define MASTER_TIC 1 +#define MASTER_SNOC_CNOC 2 +#define MASTER_QDSS_DAP 3 +#define SLAVE_A1NOC_CFG 4 +#define SLAVE_A2NOC_CFG 5 +#define SLAVE_AOP 6 +#define SLAVE_AOSS 7 +#define SLAVE_CAMERA_CFG 8 +#define SLAVE_CLK_CTL 9 +#define SLAVE_CDSP_CFG 10 +#define SLAVE_RBCPR_CX_CFG 11 +#define SLAVE_CRYPTO_0_CFG 12 +#define SLAVE_DCC_CFG 13 +#define SLAVE_CNOC_DDRSS 14 +#define SLAVE_DISPLAY_CFG 15 +#define SLAVE_GLM 16 +#define SLAVE_GFX3D_CFG 17 +#define SLAVE_IMEM_CFG 18 +#define SLAVE_IPA_CFG 19 +#define SLAVE_CNOC_MNOC_CFG 20 +#define SLAVE_PCIE_0_CFG 21 +#define SLAVE_PCIE_1_CFG 22 +#define SLAVE_PDM 23 +#define SLAVE_SOUTH_PHY_CFG 24 +#define SLAVE_PIMEM_CFG 25 +#define SLAVE_PRNG 26 +#define SLAVE_QDSS_CFG 27 +#define SLAVE_BLSP_2 28 +#define SLAVE_BLSP_1 29 +#define SLAVE_SDCC_2 30 +#define SLAVE_SDCC_4 31 +#define SLAVE_SNOC_CFG 32 +#define SLAVE_SPDM_WRAPPER 33 +#define SLAVE_SPSS_CFG 34 +#define SLAVE_TCSR 35 +#define SLAVE_TLMM_NORTH 36 +#define SLAVE_TLMM_SOUTH 37 +#define SLAVE_TSIF 38 +#define SLAVE_UFS_CARD_CFG 39 +#define SLAVE_UFS_MEM_CFG 40 +#define SLAVE_USB3_0 41 +#define SLAVE_USB3_1 42 +#define SLAVE_VENUS_CFG 43 +#define SLAVE_VSENSE_CTRL_CFG 44 +#define SLAVE_CNOC_A2NOC 45 +#define SLAVE_SERVICE_CNOC 46 + +#define MASTER_CNOC_DC_NOC 0 +#define SLAVE_LLCC_CFG 1 +#define SLAVE_MEM_NOC_CFG 2 + +#define MASTER_APPSS_PROC 0 +#define MASTER_GNOC_CFG 1 +#define SLAVE_GNOC_SNOC 2 +#define SLAVE_GNOC_MEM_NOC 3 +#define SLAVE_SERVICE_GNOC 4 + +#define MASTER_TCU_0 0 +#define MASTER_MEM_NOC_CFG 1 +#define MASTER_GNOC_MEM_NOC 2 +#define MASTER_MNOC_HF_MEM_NOC 3 +#define MASTER_MNOC_SF_MEM_NOC 4 +#define MASTER_SNOC_GC_MEM_NOC 5 +#define MASTER_SNOC_SF_MEM_NOC 6 +#define MASTER_GFX3D 7 +#define SLAVE_MSS_PROC_MS_MPU_CFG 8 +#define SLAVE_MEM_NOC_GNOC 9 +#define SLAVE_LLCC 10 +#define SLAVE_MEM_NOC_SNOC 11 +#define SLAVE_SERVICE_MEM_NOC 12 +#define MASTER_LLCC 13 +#define SLAVE_EBI1 14 + +#define MASTER_CNOC_MNOC_CFG 0 +#define MASTER_CAMNOC_HF0 1 +#define MASTER_CAMNOC_HF1 2 +#define MASTER_CAMNOC_SF 3 +#define MASTER_MDP0 4 +#define MASTER_MDP1 5 +#define MASTER_ROTATOR 6 +#define MASTER_VIDEO_P0 7 +#define MASTER_VIDEO_P1 8 +#define MASTER_VIDEO_PROC 9 +#define SLAVE_MNOC_SF_MEM_NOC 10 +#define SLAVE_MNOC_HF_MEM_NOC 11 +#define SLAVE_SERVICE_MNOC 12 +#define MASTER_CAMNOC_HF0_UNCOMP 13 +#define MASTER_CAMNOC_HF1_UNCOMP 14 +#define MASTER_CAMNOC_SF_UNCOMP 15 +#define SLAVE_CAMNOC_UNCOMP 16 + +#define MASTER_SNOC_CFG 0 +#define MASTER_A1NOC_SNOC 1 +#define MASTER_A2NOC_SNOC 2 +#define MASTER_GNOC_SNOC 3 +#define MASTER_MEM_NOC_SNOC 4 +#define MASTER_ANOC_PCIE_SNOC 5 +#define MASTER_PIMEM 6 +#define MASTER_GIC 7 +#define SLAVE_APPSS 8 +#define SLAVE_SNOC_CNOC 9 +#define SLAVE_SNOC_MEM_NOC_GC 10 +#define SLAVE_SNOC_MEM_NOC_SF 11 +#define SLAVE_IMEM 12 +#define SLAVE_PCIE_0 13 +#define SLAVE_PCIE_1 14 +#define SLAVE_PIMEM 15 +#define SLAVE_SERVICE_SNOC 16 +#define SLAVE_QDSS_STM 17 +#define SLAVE_TCU 18 #endif diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 44e552de419c..193cc9dbf448 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -41,6 +41,7 @@ enum coresight_dev_type { CORESIGHT_DEV_TYPE_LINKSINK, CORESIGHT_DEV_TYPE_SOURCE, CORESIGHT_DEV_TYPE_HELPER, + CORESIGHT_DEV_TYPE_ECT, }; enum coresight_dev_subtype_sink { @@ -68,6 +69,12 @@ enum coresight_dev_subtype_helper { CORESIGHT_DEV_SUBTYPE_HELPER_CATU, }; +/* Embedded Cross Trigger (ECT) sub-types */ +enum coresight_dev_subtype_ect { + CORESIGHT_DEV_SUBTYPE_ECT_NONE, + CORESIGHT_DEV_SUBTYPE_ECT_CTI, +}; + /** * union coresight_dev_subtype - further characterisation of a type * @sink_subtype: type of sink this component is, as defined @@ -78,6 +85,8 @@ enum coresight_dev_subtype_helper { * by @coresight_dev_subtype_source. * @helper_subtype: type of helper this component is, as defined * by @coresight_dev_subtype_helper. + * @ect_subtype: type of cross trigger this component is, as + * defined by @coresight_dev_subtype_ect */ union coresight_dev_subtype { /* We have some devices which acts as LINK and SINK */ @@ -87,6 +96,7 @@ union coresight_dev_subtype { }; enum coresight_dev_subtype_source source_subtype; enum coresight_dev_subtype_helper helper_subtype; + enum coresight_dev_subtype_ect ect_subtype; }; /** @@ -153,6 +163,8 @@ struct coresight_connection { * activated but not yet enabled. Enabling for a _sink_ * appens when a source has been selected for that it. * @ea: Device attribute for sink representation under PMU directory. + * @ect_dev: Associated cross trigger device. Not part of the trace data + * path or connections. */ struct coresight_device { struct coresight_platform_data *pdata; @@ -166,6 +178,8 @@ struct coresight_device { /* sink specific fields */ bool activated; /* true only if a sink is part of a path */ struct dev_ext_attribute *ea; + /* cross trigger handling */ + struct coresight_device *ect_dev; }; /* @@ -196,6 +210,7 @@ static struct coresight_dev_list (var) = { \ #define sink_ops(csdev) csdev->ops->sink_ops #define link_ops(csdev) csdev->ops->link_ops #define helper_ops(csdev) csdev->ops->helper_ops +#define ect_ops(csdev) csdev->ops->ect_ops /** * struct coresight_ops_sink - basic operations for a sink @@ -262,11 +277,23 @@ struct coresight_ops_helper { int (*disable)(struct coresight_device *csdev, void *data); }; +/** + * struct coresight_ops_ect - Ops for an embedded cross trigger device + * + * @enable : Enable the device + * @disable : Disable the device + */ +struct coresight_ops_ect { + int (*enable)(struct coresight_device *csdev); + int (*disable)(struct coresight_device *csdev); +}; + struct coresight_ops { const struct coresight_ops_sink *sink_ops; const struct coresight_ops_link *link_ops; const struct coresight_ops_source *source_ops; const struct coresight_ops_helper *helper_ops; + const struct coresight_ops_ect *ect_ops; }; #ifdef CONFIG_CORESIGHT diff --git a/include/linux/extcon-provider.h b/include/linux/extcon-provider.h index 1c143d200caa..fa70945f4e6b 100644 --- a/include/linux/extcon-provider.h +++ b/include/linux/extcon-provider.h @@ -17,30 +17,30 @@ struct extcon_dev; #if IS_ENABLED(CONFIG_EXTCON) /* Following APIs register/unregister the extcon device. */ -extern int extcon_dev_register(struct extcon_dev *edev); -extern void extcon_dev_unregister(struct extcon_dev *edev); -extern int devm_extcon_dev_register(struct device *dev, +int extcon_dev_register(struct extcon_dev *edev); +void extcon_dev_unregister(struct extcon_dev *edev); +int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev); -extern void devm_extcon_dev_unregister(struct device *dev, +void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev); /* Following APIs allocate/free the memory of the extcon device. */ -extern struct extcon_dev *extcon_dev_allocate(const unsigned int *cable); -extern void extcon_dev_free(struct extcon_dev *edev); -extern struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, +struct extcon_dev *extcon_dev_allocate(const unsigned int *cable); +void extcon_dev_free(struct extcon_dev *edev); +struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, const unsigned int *cable); -extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); +void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); /* Synchronize the state and property value for each external connector. */ -extern int extcon_sync(struct extcon_dev *edev, unsigned int id); +int extcon_sync(struct extcon_dev *edev, unsigned int id); /* * Following APIs set the connected state of each external connector. * The 'id' argument indicates the defined external connector. */ -extern int extcon_set_state(struct extcon_dev *edev, unsigned int id, +int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool state); -extern int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, +int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state); /* @@ -52,13 +52,13 @@ extern int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, * for each external connector. They are used to set the capability of the * property of each external connector based on the id and property. */ -extern int extcon_set_property(struct extcon_dev *edev, unsigned int id, +int extcon_set_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value prop_val); -extern int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id, +int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value prop_val); -extern int extcon_set_property_capability(struct extcon_dev *edev, +int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop); #else /* CONFIG_EXTCON */ diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 1b1d77ec2114..fd183fb9c20f 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -286,6 +286,11 @@ static inline struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, { return ERR_PTR(-ENODEV); } + +static inline const char *extcon_get_edev_name(struct extcon_dev *edev) +{ + return NULL; +} #endif /* CONFIG_EXTCON */ /* diff --git a/include/linux/mhi.h b/include/linux/mhi.h new file mode 100644 index 000000000000..ad1996001965 --- /dev/null +++ b/include/linux/mhi.h @@ -0,0 +1,700 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * + */ +#ifndef _MHI_H_ +#define _MHI_H_ + +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/mutex.h> +#include <linux/rwlock_types.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/spinlock_types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> + +struct mhi_chan; +struct mhi_event; +struct mhi_ctxt; +struct mhi_cmd; +struct mhi_buf_info; + +/** + * enum mhi_callback - MHI callback + * @MHI_CB_IDLE: MHI entered idle state + * @MHI_CB_PENDING_DATA: New data available for client to process + * @MHI_CB_LPM_ENTER: MHI host entered low power mode + * @MHI_CB_LPM_EXIT: MHI host about to exit low power mode + * @MHI_CB_EE_RDDM: MHI device entered RDDM exec env + * @MHI_CB_EE_MISSION_MODE: MHI device entered Mission Mode exec env + * @MHI_CB_SYS_ERROR: MHI device entered error state (may recover) + * @MHI_CB_FATAL_ERROR: MHI device entered fatal error state + * @MHI_CB_BW_REQ: Received a bandwidth switch request from device + */ +enum mhi_callback { + MHI_CB_IDLE, + MHI_CB_PENDING_DATA, + MHI_CB_LPM_ENTER, + MHI_CB_LPM_EXIT, + MHI_CB_EE_RDDM, + MHI_CB_EE_MISSION_MODE, + MHI_CB_SYS_ERROR, + MHI_CB_FATAL_ERROR, + MHI_CB_BW_REQ, +}; + +/** + * enum mhi_flags - Transfer flags + * @MHI_EOB: End of buffer for bulk transfer + * @MHI_EOT: End of transfer + * @MHI_CHAIN: Linked transfer + */ +enum mhi_flags { + MHI_EOB, + MHI_EOT, + MHI_CHAIN, +}; + +/** + * enum mhi_device_type - Device types + * @MHI_DEVICE_XFER: Handles data transfer + * @MHI_DEVICE_CONTROLLER: Control device + */ +enum mhi_device_type { + MHI_DEVICE_XFER, + MHI_DEVICE_CONTROLLER, +}; + +/** + * enum mhi_ch_type - Channel types + * @MHI_CH_TYPE_INVALID: Invalid channel type + * @MHI_CH_TYPE_OUTBOUND: Outbound channel to the device + * @MHI_CH_TYPE_INBOUND: Inbound channel from the device + * @MHI_CH_TYPE_INBOUND_COALESCED: Coalesced channel for the device to combine + * multiple packets and send them as a single + * large packet to reduce CPU consumption + */ +enum mhi_ch_type { + MHI_CH_TYPE_INVALID = 0, + MHI_CH_TYPE_OUTBOUND = DMA_TO_DEVICE, + MHI_CH_TYPE_INBOUND = DMA_FROM_DEVICE, + MHI_CH_TYPE_INBOUND_COALESCED = 3, +}; + +/** + * struct image_info - Firmware and RDDM table table + * @mhi_buf - Buffer for firmware and RDDM table + * @entries - # of entries in table + */ +struct image_info { + struct mhi_buf *mhi_buf; + struct bhi_vec_entry *bhi_vec; + u32 entries; +}; + +/** + * struct mhi_link_info - BW requirement + * target_link_speed - Link speed as defined by TLS bits in LinkControl reg + * target_link_width - Link width as defined by NLW bits in LinkStatus reg + */ +struct mhi_link_info { + unsigned int target_link_speed; + unsigned int target_link_width; +}; + +/** + * enum mhi_ee_type - Execution environment types + * @MHI_EE_PBL: Primary Bootloader + * @MHI_EE_SBL: Secondary Bootloader + * @MHI_EE_AMSS: Modem, aka the primary runtime EE + * @MHI_EE_RDDM: Ram dump download mode + * @MHI_EE_WFW: WLAN firmware mode + * @MHI_EE_PTHRU: Passthrough + * @MHI_EE_EDL: Embedded downloader + */ +enum mhi_ee_type { + MHI_EE_PBL, + MHI_EE_SBL, + MHI_EE_AMSS, + MHI_EE_RDDM, + MHI_EE_WFW, + MHI_EE_PTHRU, + MHI_EE_EDL, + MHI_EE_MAX_SUPPORTED = MHI_EE_EDL, + MHI_EE_DISABLE_TRANSITION, /* local EE, not related to mhi spec */ + MHI_EE_NOT_SUPPORTED, + MHI_EE_MAX, +}; + +/** + * enum mhi_state - MHI states + * @MHI_STATE_RESET: Reset state + * @MHI_STATE_READY: Ready state + * @MHI_STATE_M0: M0 state + * @MHI_STATE_M1: M1 state + * @MHI_STATE_M2: M2 state + * @MHI_STATE_M3: M3 state + * @MHI_STATE_M3_FAST: M3 Fast state + * @MHI_STATE_BHI: BHI state + * @MHI_STATE_SYS_ERR: System Error state + */ +enum mhi_state { + MHI_STATE_RESET = 0x0, + MHI_STATE_READY = 0x1, + MHI_STATE_M0 = 0x2, + MHI_STATE_M1 = 0x3, + MHI_STATE_M2 = 0x4, + MHI_STATE_M3 = 0x5, + MHI_STATE_M3_FAST = 0x6, + MHI_STATE_BHI = 0x7, + MHI_STATE_SYS_ERR = 0xFF, + MHI_STATE_MAX, +}; + +/** + * enum mhi_ch_ee_mask - Execution environment mask for channel + * @MHI_CH_EE_PBL: Allow channel to be used in PBL EE + * @MHI_CH_EE_SBL: Allow channel to be used in SBL EE + * @MHI_CH_EE_AMSS: Allow channel to be used in AMSS EE + * @MHI_CH_EE_RDDM: Allow channel to be used in RDDM EE + * @MHI_CH_EE_PTHRU: Allow channel to be used in PTHRU EE + * @MHI_CH_EE_WFW: Allow channel to be used in WFW EE + * @MHI_CH_EE_EDL: Allow channel to be used in EDL EE + */ +enum mhi_ch_ee_mask { + MHI_CH_EE_PBL = BIT(MHI_EE_PBL), + MHI_CH_EE_SBL = BIT(MHI_EE_SBL), + MHI_CH_EE_AMSS = BIT(MHI_EE_AMSS), + MHI_CH_EE_RDDM = BIT(MHI_EE_RDDM), + MHI_CH_EE_PTHRU = BIT(MHI_EE_PTHRU), + MHI_CH_EE_WFW = BIT(MHI_EE_WFW), + MHI_CH_EE_EDL = BIT(MHI_EE_EDL), +}; + +/** + * enum mhi_er_data_type - Event ring data types + * @MHI_ER_DATA: Only client data over this ring + * @MHI_ER_CTRL: MHI control data and client data + */ +enum mhi_er_data_type { + MHI_ER_DATA, + MHI_ER_CTRL, +}; + +/** + * enum mhi_db_brst_mode - Doorbell mode + * @MHI_DB_BRST_DISABLE: Burst mode disable + * @MHI_DB_BRST_ENABLE: Burst mode enable + */ +enum mhi_db_brst_mode { + MHI_DB_BRST_DISABLE = 0x2, + MHI_DB_BRST_ENABLE = 0x3, +}; + +/** + * struct mhi_channel_config - Channel configuration structure for controller + * @name: The name of this channel + * @num: The number assigned to this channel + * @num_elements: The number of elements that can be queued to this channel + * @local_elements: The local ring length of the channel + * @event_ring: The event rung index that services this channel + * @dir: Direction that data may flow on this channel + * @type: Channel type + * @ee_mask: Execution Environment mask for this channel + * @pollcfg: Polling configuration for burst mode. 0 is default. milliseconds + for UL channels, multiple of 8 ring elements for DL channels + * @doorbell: Doorbell mode + * @lpm_notify: The channel master requires low power mode notifications + * @offload_channel: The client manages the channel completely + * @doorbell_mode_switch: Channel switches to doorbell mode on M0 transition + * @auto_queue: Framework will automatically queue buffers for DL traffic + * @auto_start: Automatically start (open) this channel + * @wake-capable: Channel capable of waking up the system + */ +struct mhi_channel_config { + char *name; + u32 num; + u32 num_elements; + u32 local_elements; + u32 event_ring; + enum dma_data_direction dir; + enum mhi_ch_type type; + u32 ee_mask; + u32 pollcfg; + enum mhi_db_brst_mode doorbell; + bool lpm_notify; + bool offload_channel; + bool doorbell_mode_switch; + bool auto_queue; + bool auto_start; + bool wake_capable; +}; + +/** + * struct mhi_event_config - Event ring configuration structure for controller + * @num_elements: The number of elements that can be queued to this ring + * @irq_moderation_ms: Delay irq for additional events to be aggregated + * @irq: IRQ associated with this ring + * @channel: Dedicated channel number. U32_MAX indicates a non-dedicated ring + * @priority: Priority of this ring. Use 1 for now + * @mode: Doorbell mode + * @data_type: Type of data this ring will process + * @hardware_event: This ring is associated with hardware channels + * @client_managed: This ring is client managed + * @offload_channel: This ring is associated with an offloaded channel + */ +struct mhi_event_config { + u32 num_elements; + u32 irq_moderation_ms; + u32 irq; + u32 channel; + u32 priority; + enum mhi_db_brst_mode mode; + enum mhi_er_data_type data_type; + bool hardware_event; + bool client_managed; + bool offload_channel; +}; + +/** + * struct mhi_controller_config - Root MHI controller configuration + * @max_channels: Maximum number of channels supported + * @timeout_ms: Timeout value for operations. 0 means use default + * @buf_len: Size of automatically allocated buffers. 0 means use default + * @num_channels: Number of channels defined in @ch_cfg + * @ch_cfg: Array of defined channels + * @num_events: Number of event rings defined in @event_cfg + * @event_cfg: Array of defined event rings + * @use_bounce_buf: Use a bounce buffer pool due to limited DDR access + * @m2_no_db: Host is not allowed to ring DB in M2 state + */ +struct mhi_controller_config { + u32 max_channels; + u32 timeout_ms; + u32 buf_len; + u32 num_channels; + struct mhi_channel_config *ch_cfg; + u32 num_events; + struct mhi_event_config *event_cfg; + bool use_bounce_buf; + bool m2_no_db; +}; + +/** + * struct mhi_controller - Master MHI controller structure + * @cntrl_dev: Pointer to the struct device of physical bus acting as the MHI + * controller (required) + * @mhi_dev: MHI device instance for the controller + * @regs: Base address of MHI MMIO register space (required) + * @bhi: Points to base of MHI BHI register space + * @bhie: Points to base of MHI BHIe register space + * @wake_db: MHI WAKE doorbell register address + * @iova_start: IOMMU starting address for data (required) + * @iova_stop: IOMMU stop address for data (required) + * @fw_image: Firmware image name for normal booting (required) + * @edl_image: Firmware image name for emergency download mode (optional) + * @rddm_size: RAM dump size that host should allocate for debugging purpose + * @sbl_size: SBL image size downloaded through BHIe (optional) + * @seg_len: BHIe vector size (optional) + * @fbc_image: Points to firmware image buffer + * @rddm_image: Points to RAM dump buffer + * @mhi_chan: Points to the channel configuration table + * @lpm_chans: List of channels that require LPM notifications + * @irq: base irq # to request (required) + * @max_chan: Maximum number of channels the controller supports + * @total_ev_rings: Total # of event rings allocated + * @hw_ev_rings: Number of hardware event rings + * @sw_ev_rings: Number of software event rings + * @nr_irqs_req: Number of IRQs required to operate (optional) + * @nr_irqs: Number of IRQ allocated by bus master (required) + * @family_number: MHI controller family number + * @device_number: MHI controller device number + * @major_version: MHI controller major revision number + * @minor_version: MHI controller minor revision number + * @mhi_event: MHI event ring configurations table + * @mhi_cmd: MHI command ring configurations table + * @mhi_ctxt: MHI device context, shared memory between host and device + * @pm_mutex: Mutex for suspend/resume operation + * @pm_lock: Lock for protecting MHI power management state + * @timeout_ms: Timeout in ms for state transitions + * @pm_state: MHI power management state + * @db_access: DB access states + * @ee: MHI device execution environment + * @dev_state: MHI device state + * @dev_wake: Device wakeup count + * @pending_pkts: Pending packets for the controller + * @transition_list: List of MHI state transitions + * @transition_lock: Lock for protecting MHI state transition list + * @wlock: Lock for protecting device wakeup + * @mhi_link_info: Device bandwidth info + * @st_worker: State transition worker + * @fw_worker: Firmware download worker + * @syserr_worker: System error worker + * @state_event: State change event + * @status_cb: CB function to notify power states of the device (required) + * @link_status: CB function to query link status of the device (required) + * @wake_get: CB function to assert device wake (optional) + * @wake_put: CB function to de-assert device wake (optional) + * @wake_toggle: CB function to assert and de-assert device wake (optional) + * @runtime_get: CB function to controller runtime resume (required) + * @runtimet_put: CB function to decrement pm usage (required) + * @map_single: CB function to create TRE buffer + * @unmap_single: CB function to destroy TRE buffer + * @buffer_len: Bounce buffer length + * @bounce_buf: Use of bounce buffer + * @fbc_download: MHI host needs to do complete image transfer (optional) + * @pre_init: MHI host needs to do pre-initialization before power up + * @wake_set: Device wakeup set flag + * + * Fields marked as (required) need to be populated by the controller driver + * before calling mhi_register_controller(). For the fields marked as (optional) + * they can be populated depending on the usecase. + * + * The following fields are present for the purpose of implementing any device + * specific quirks or customizations for specific MHI revisions used in device + * by the controller drivers. The MHI stack will just populate these fields + * during mhi_register_controller(): + * family_number + * device_number + * major_version + * minor_version + */ +struct mhi_controller { + struct device *cntrl_dev; + struct mhi_device *mhi_dev; + void __iomem *regs; + void __iomem *bhi; + void __iomem *bhie; + void __iomem *wake_db; + + dma_addr_t iova_start; + dma_addr_t iova_stop; + const char *fw_image; + const char *edl_image; + size_t rddm_size; + size_t sbl_size; + size_t seg_len; + struct image_info *fbc_image; + struct image_info *rddm_image; + struct mhi_chan *mhi_chan; + struct list_head lpm_chans; + int *irq; + u32 max_chan; + u32 total_ev_rings; + u32 hw_ev_rings; + u32 sw_ev_rings; + u32 nr_irqs_req; + u32 nr_irqs; + u32 family_number; + u32 device_number; + u32 major_version; + u32 minor_version; + + struct mhi_event *mhi_event; + struct mhi_cmd *mhi_cmd; + struct mhi_ctxt *mhi_ctxt; + + struct mutex pm_mutex; + rwlock_t pm_lock; + u32 timeout_ms; + u32 pm_state; + u32 db_access; + enum mhi_ee_type ee; + enum mhi_state dev_state; + atomic_t dev_wake; + atomic_t pending_pkts; + struct list_head transition_list; + spinlock_t transition_lock; + spinlock_t wlock; + struct mhi_link_info mhi_link_info; + struct work_struct st_worker; + struct work_struct fw_worker; + struct work_struct syserr_worker; + wait_queue_head_t state_event; + + void (*status_cb)(struct mhi_controller *mhi_cntrl, + enum mhi_callback cb); + int (*link_status)(struct mhi_controller *mhi_cntrl); + void (*wake_get)(struct mhi_controller *mhi_cntrl, bool override); + void (*wake_put)(struct mhi_controller *mhi_cntrl, bool override); + void (*wake_toggle)(struct mhi_controller *mhi_cntrl); + int (*runtime_get)(struct mhi_controller *mhi_cntrl); + void (*runtime_put)(struct mhi_controller *mhi_cntrl); + int (*map_single)(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf); + void (*unmap_single)(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf); + + size_t buffer_len; + bool bounce_buf; + bool fbc_download; + bool pre_init; + bool wake_set; +}; + +/** + * struct mhi_device - Structure representing a MHI device which binds + * to channels + * @id: Pointer to MHI device ID struct + * @chan_name: Name of the channel to which the device binds + * @mhi_cntrl: Controller the device belongs to + * @ul_chan: UL channel for the device + * @dl_chan: DL channel for the device + * @dev: Driver model device node for the MHI device + * @dev_type: MHI device type + * @ul_chan_id: MHI channel id for UL transfer + * @dl_chan_id: MHI channel id for DL transfer + * @dev_wake: Device wakeup counter + */ +struct mhi_device { + const struct mhi_device_id *id; + const char *chan_name; + struct mhi_controller *mhi_cntrl; + struct mhi_chan *ul_chan; + struct mhi_chan *dl_chan; + struct device dev; + enum mhi_device_type dev_type; + int ul_chan_id; + int dl_chan_id; + u32 dev_wake; +}; + +/** + * struct mhi_result - Completed buffer information + * @buf_addr: Address of data buffer + * @bytes_xferd: # of bytes transferred + * @dir: Channel direction + * @transaction_status: Status of last transaction + */ +struct mhi_result { + void *buf_addr; + size_t bytes_xferd; + enum dma_data_direction dir; + int transaction_status; +}; + +/** + * struct mhi_buf - MHI Buffer description + * @buf: Virtual address of the buffer + * @name: Buffer label. For offload channel, configurations name must be: + * ECA - Event context array data + * CCA - Channel context array data + * @dma_addr: IOMMU address of the buffer + * @len: # of bytes + */ +struct mhi_buf { + void *buf; + const char *name; + dma_addr_t dma_addr; + size_t len; +}; + +/** + * struct mhi_driver - Structure representing a MHI client driver + * @probe: CB function for client driver probe function + * @remove: CB function for client driver remove function + * @ul_xfer_cb: CB function for UL data transfer + * @dl_xfer_cb: CB function for DL data transfer + * @status_cb: CB functions for asynchronous status + * @driver: Device driver model driver + */ +struct mhi_driver { + const struct mhi_device_id *id_table; + int (*probe)(struct mhi_device *mhi_dev, + const struct mhi_device_id *id); + void (*remove)(struct mhi_device *mhi_dev); + void (*ul_xfer_cb)(struct mhi_device *mhi_dev, + struct mhi_result *result); + void (*dl_xfer_cb)(struct mhi_device *mhi_dev, + struct mhi_result *result); + void (*status_cb)(struct mhi_device *mhi_dev, enum mhi_callback mhi_cb); + struct device_driver driver; +}; + +#define to_mhi_driver(drv) container_of(drv, struct mhi_driver, driver) +#define to_mhi_device(dev) container_of(dev, struct mhi_device, dev) + +/** + * mhi_register_controller - Register MHI controller + * @mhi_cntrl: MHI controller to register + * @config: Configuration to use for the controller + */ +int mhi_register_controller(struct mhi_controller *mhi_cntrl, + struct mhi_controller_config *config); + +/** + * mhi_unregister_controller - Unregister MHI controller + * @mhi_cntrl: MHI controller to unregister + */ +void mhi_unregister_controller(struct mhi_controller *mhi_cntrl); + +/* + * module_mhi_driver() - Helper macro for drivers that don't do + * anything special other than using default mhi_driver_register() and + * mhi_driver_unregister(). This eliminates a lot of boilerplate. + * Each module may only use this macro once. + */ +#define module_mhi_driver(mhi_drv) \ + module_driver(mhi_drv, mhi_driver_register, \ + mhi_driver_unregister) + +/* + * Macro to avoid include chaining to get THIS_MODULE + */ +#define mhi_driver_register(mhi_drv) \ + __mhi_driver_register(mhi_drv, THIS_MODULE) + +/** + * __mhi_driver_register - Register driver with MHI framework + * @mhi_drv: Driver associated with the device + * @owner: The module owner + */ +int __mhi_driver_register(struct mhi_driver *mhi_drv, struct module *owner); + +/** + * mhi_driver_unregister - Unregister a driver for mhi_devices + * @mhi_drv: Driver associated with the device + */ +void mhi_driver_unregister(struct mhi_driver *mhi_drv); + +/** + * mhi_set_mhi_state - Set MHI device state + * @mhi_cntrl: MHI controller + * @state: State to set + */ +void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl, + enum mhi_state state); + +/** + * mhi_prepare_for_power_up - Do pre-initialization before power up. + * This is optional, call this before power up if + * the controller does not want bus framework to + * automatically free any allocated memory during + * shutdown process. + * @mhi_cntrl: MHI controller + */ +int mhi_prepare_for_power_up(struct mhi_controller *mhi_cntrl); + +/** + * mhi_async_power_up - Start MHI power up sequence + * @mhi_cntrl: MHI controller + */ +int mhi_async_power_up(struct mhi_controller *mhi_cntrl); + +/** + * mhi_sync_power_up - Start MHI power up sequence and wait till the device + * device enters valid EE state + * @mhi_cntrl: MHI controller + */ +int mhi_sync_power_up(struct mhi_controller *mhi_cntrl); + +/** + * mhi_power_down - Start MHI power down sequence + * @mhi_cntrl: MHI controller + * @graceful: Link is still accessible, so do a graceful shutdown process + */ +void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful); + +/** + * mhi_unprepare_after_power_down - Free any allocated memory after power down + * @mhi_cntrl: MHI controller + */ +void mhi_unprepare_after_power_down(struct mhi_controller *mhi_cntrl); + +/** + * mhi_download_rddm_img - Download ramdump image from device for + * debugging purpose. + * @mhi_cntrl: MHI controller + * @in_panic: Download rddm image during kernel panic + */ +int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic); + +/** + * mhi_force_rddm_mode - Force device into rddm mode + * @mhi_cntrl: MHI controller + */ +int mhi_force_rddm_mode(struct mhi_controller *mhi_cntrl); + +/** + * mhi_get_mhi_state - Get MHI state of the device + * @mhi_cntrl: MHI controller + */ +enum mhi_state mhi_get_mhi_state(struct mhi_controller *mhi_cntrl); + +/** + * mhi_device_get - Disable device low power mode + * @mhi_dev: Device associated with the channel + */ +void mhi_device_get(struct mhi_device *mhi_dev); + +/** + * mhi_device_get_sync - Disable device low power mode. Synchronously + * take the controller out of suspended state + * @mhi_dev: Device associated with the channel + */ +int mhi_device_get_sync(struct mhi_device *mhi_dev); + +/** + * mhi_device_put - Re-enable device low power mode + * @mhi_dev: Device associated with the channel + */ +void mhi_device_put(struct mhi_device *mhi_dev); + +/** + * mhi_prepare_for_transfer - Setup channel for data transfer + * @mhi_dev: Device associated with the channels + */ +int mhi_prepare_for_transfer(struct mhi_device *mhi_dev); + +/** + * mhi_unprepare_from_transfer - Unprepare the channels + * @mhi_dev: Device associated with the channels + */ +void mhi_unprepare_from_transfer(struct mhi_device *mhi_dev); + +/** + * mhi_poll - Poll for any available data in DL direction + * @mhi_dev: Device associated with the channels + * @budget: # of events to process + */ +int mhi_poll(struct mhi_device *mhi_dev, u32 budget); + +/** + * mhi_queue_dma - Send or receive DMA mapped buffers from client device + * over MHI channel + * @mhi_dev: Device associated with the channels + * @dir: DMA direction for the channel + * @mhi_buf: Buffer for holding the DMA mapped data + * @len: Buffer length + * @mflags: MHI transfer flags used for the transfer + */ +int mhi_queue_dma(struct mhi_device *mhi_dev, enum dma_data_direction dir, + struct mhi_buf *mhi_buf, size_t len, enum mhi_flags mflags); + +/** + * mhi_queue_buf - Send or receive raw buffers from client device over MHI + * channel + * @mhi_dev: Device associated with the channels + * @dir: DMA direction for the channel + * @buf: Buffer for holding the data + * @len: Buffer length + * @mflags: MHI transfer flags used for the transfer + */ +int mhi_queue_buf(struct mhi_device *mhi_dev, enum dma_data_direction dir, + void *buf, size_t len, enum mhi_flags mflags); + +/** + * mhi_queue_skb - Send or receive SKBs from client device over MHI channel + * @mhi_dev: Device associated with the channels + * @dir: DMA direction for the channel + * @skb: Buffer for holding SKBs + * @len: Buffer length + * @mflags: MHI transfer flags used for the transfer + */ +int mhi_queue_skb(struct mhi_device *mhi_dev, enum dma_data_direction dir, + struct sk_buff *skb, size_t len, enum mhi_flags mflags); + +#endif /* _MHI_H_ */ diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index becde6981a95..c7a93002a3c1 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -25,20 +25,31 @@ #define TEMP_MINOR 131 /* Temperature Sensor */ #define APM_MINOR_DEV 134 #define RTC_MINOR 135 -#define EFI_RTC_MINOR 136 /* EFI Time services */ +/*#define EFI_RTC_MINOR 136 was EFI Time services */ #define VHCI_MINOR 137 #define SUN_OPENPROM_MINOR 139 #define DMAPI_MINOR 140 /* unused */ #define NVRAM_MINOR 144 +#define SBUS_FLASH_MINOR 152 #define SGI_MMTIMER 153 +#define PMU_MINOR 154 #define STORE_QUEUE_MINOR 155 /* unused */ +#define LCD_MINOR 156 +#define AC_MINOR 157 +#define BUTTON_MINOR 158 /* Major 10, Minor 158, /dev/nwbutton */ +#define NWFLASH_MINOR 160 /* MAJOR is 10 - miscdevice */ +#define ENVCTRL_MINOR 162 #define I2O_MINOR 166 +#define UCTRL_MINOR 174 #define AGPGART_MINOR 175 +#define TOSH_MINOR_DEV 181 #define HWRNG_MINOR 183 #define MICROCODE_MINOR 184 +#define KEYPAD_MINOR 185 #define IRNET_MINOR 187 #define D7S_MINOR 193 #define VFIO_MINOR 196 +#define PXA3XX_GCU_MINOR 197 #define TUN_MINOR 200 #define CUSE_MINOR 203 #define MWAVE_MINOR 219 /* ACP/Mwave Modem */ @@ -49,6 +60,7 @@ #define MISC_MCELOG_MINOR 227 #define HPET_MINOR 228 #define FUSE_MINOR 229 +#define SNAPSHOT_MINOR 231 #define KVM_MINOR 232 #define BTRFS_MINOR 234 #define AUTOFS_MINOR 235 diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index f8b66d43acf6..4c2ddd0941a7 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -819,4 +819,17 @@ struct wmi_device_id { const void *context; }; +#define MHI_DEVICE_MODALIAS_FMT "mhi:%s" +#define MHI_NAME_SIZE 32 + +/** + * struct mhi_device_id - MHI device identification + * @chan: MHI channel name + * @driver_data: driver data; + */ +struct mhi_device_id { + const char chan[MHI_NAME_SIZE]; + kernel_ulong_t driver_data; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h index d3776be48c53..1b311d27c9b8 100644 --- a/include/linux/nvmem-consumer.h +++ b/include/linux/nvmem-consumer.h @@ -63,6 +63,7 @@ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len); int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len); int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val); int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val); +int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val); /* direct nvmem device read/write interface */ struct nvmem_device *nvmem_device_get(struct device *dev, const char *name); @@ -138,6 +139,12 @@ static inline int nvmem_cell_read_u32(struct device *dev, return -EOPNOTSUPP; } +static inline int nvmem_cell_read_u64(struct device *dev, + const char *cell_id, u64 *val) +{ + return -EOPNOTSUPP; +} + static inline struct nvmem_device *nvmem_device_get(struct device *dev, const char *name) { diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 2dfe14ed3bb0..00f5826092e3 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -80,6 +80,21 @@ enum sdw_slave_status { }; /** + * enum sdw_clk_stop_type: clock stop operations + * + * @SDW_CLK_PRE_PREPARE: pre clock stop prepare + * @SDW_CLK_POST_PREPARE: post clock stop prepare + * @SDW_CLK_PRE_DEPREPARE: pre clock stop de-prepare + * @SDW_CLK_POST_DEPREPARE: post clock stop de-prepare + */ +enum sdw_clk_stop_type { + SDW_CLK_PRE_PREPARE = 0, + SDW_CLK_POST_PREPARE, + SDW_CLK_PRE_DEPREPARE, + SDW_CLK_POST_DEPREPARE, +}; + +/** * enum sdw_command_response - Command response as defined by SDW spec * @SDW_CMD_OK: cmd was successful * @SDW_CMD_IGNORED: cmd was ignored @@ -426,6 +441,29 @@ struct sdw_slave_id { __u8 sdw_version:4; }; +/* + * Helper macros to extract the MIPI-defined IDs + * + * Spec definition + * Register Bit Contents + * DevId_0 [7:4] 47:44 sdw_version + * DevId_0 [3:0] 43:40 unique_id + * DevId_1 39:32 mfg_id [15:8] + * DevId_2 31:24 mfg_id [7:0] + * DevId_3 23:16 part_id [15:8] + * DevId_4 15:08 part_id [7:0] + * DevId_5 07:00 class_id + * + * The MIPI DisCo for SoundWire defines in addition the link_id as bits 51:48 + */ + +#define SDW_DISCO_LINK_ID(adr) (((adr) >> 48) & GENMASK(3, 0)) +#define SDW_VERSION(adr) (((adr) >> 44) & GENMASK(3, 0)) +#define SDW_UNIQUE_ID(adr) (((adr) >> 40) & GENMASK(3, 0)) +#define SDW_MFG_ID(adr) (((adr) >> 24) & GENMASK(15, 0)) +#define SDW_PART_ID(adr) (((adr) >> 8) & GENMASK(15, 0)) +#define SDW_CLASS_ID(adr) ((adr) & GENMASK(7, 0)) + /** * struct sdw_slave_intr_status - Slave interrupt status * @control_port: control port status @@ -535,6 +573,11 @@ struct sdw_slave_ops { int (*port_prep)(struct sdw_slave *slave, struct sdw_prepare_ch *prepare_ch, enum sdw_port_prep_ops pre_ops); + int (*get_clk_stop_mode)(struct sdw_slave *slave); + int (*clk_stop)(struct sdw_slave *slave, + enum sdw_clk_stop_mode mode, + enum sdw_clk_stop_type type); + }; /** @@ -577,6 +620,7 @@ struct sdw_slave { #endif struct list_head node; struct completion *port_ready; + enum sdw_clk_stop_mode curr_clk_stop_mode; u16 dev_num; u16 dev_num_sticky; bool probed; @@ -894,6 +938,9 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream); int sdw_enable_stream(struct sdw_stream_runtime *stream); int sdw_disable_stream(struct sdw_stream_runtime *stream); int sdw_deprepare_stream(struct sdw_stream_runtime *stream); +int sdw_bus_prep_clk_stop(struct sdw_bus *bus); +int sdw_bus_clk_stop(struct sdw_bus *bus); +int sdw_bus_exit_clk_stop(struct sdw_bus *bus); /* messaging and data APIs */ diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h index 01081c4726c0..54bf6b118401 100644 --- a/include/linux/uio_driver.h +++ b/include/linux/uio_driver.h @@ -24,10 +24,10 @@ struct uio_map; * struct uio_mem - description of a UIO memory region * @name: name of the memory region for identification * @addr: address of the device's memory rounded to page - * size (phys_addr is used since addr can be - * logical, virtual, or physical & phys_addr_t - * should always be large enough to handle any of - * the address types) + * size (phys_addr is used since addr can be + * logical, virtual, or physical & phys_addr_t + * should always be large enough to handle any of + * the address types) * @offs: offset of device memory within the page * @size: size of IO (multiple of page size) * @memtype: type of memory addr points to @@ -67,16 +67,16 @@ struct uio_port { #define MAX_UIO_PORT_REGIONS 5 struct uio_device { - struct module *owner; + struct module *owner; struct device dev; - int minor; - atomic_t event; - struct fasync_struct *async_queue; - wait_queue_head_t wait; - struct uio_info *info; + int minor; + atomic_t event; + struct fasync_struct *async_queue; + wait_queue_head_t wait; + struct uio_info *info; struct mutex info_lock; - struct kobject *map_dir; - struct kobject *portio_dir; + struct kobject *map_dir; + struct kobject *portio_dir; }; /** @@ -123,6 +123,15 @@ extern int __must_check extern void uio_unregister_device(struct uio_info *info); extern void uio_event_notify(struct uio_info *info); +extern int __must_check + __devm_uio_register_device(struct module *owner, + struct device *parent, + struct uio_info *info); + +/* use a define to avoid include chaining to get THIS_MODULE */ +#define devm_uio_register_device(parent, info) \ + __devm_uio_register_device(THIS_MODULE, parent, info) + /* defines for uio_info->irq */ #define UIO_IRQ_CUSTOM -1 #define UIO_IRQ_NONE 0 diff --git a/include/uapi/linux/coresight-stm.h b/include/uapi/linux/coresight-stm.h index aac550a52f80..8847dbf24151 100644 --- a/include/uapi/linux/coresight-stm.h +++ b/include/uapi/linux/coresight-stm.h @@ -2,8 +2,10 @@ #ifndef __UAPI_CORESIGHT_STM_H_ #define __UAPI_CORESIGHT_STM_H_ -#define STM_FLAG_TIMESTAMPED BIT(3) -#define STM_FLAG_GUARANTEED BIT(7) +#include <linux/const.h> + +#define STM_FLAG_TIMESTAMPED _BITUL(3) +#define STM_FLAG_GUARANTEED _BITUL(7) /* * The CoreSight STM supports guaranteed and invariant timing diff --git a/kernel/power/user.c b/kernel/power/user.c index 58ed9478787f..ef90eb1fb86e 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -27,8 +27,6 @@ #include "power.h" -#define SNAPSHOT_MINOR 231 - static struct snapshot_data { struct snapshot_handle handle; int swap; diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index a85a6a423bf4..d1398cef3b18 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -98,7 +98,7 @@ config DYNAMIC_DEBUG bool "Enable dynamic printk() support" default n depends on PRINTK - depends on DEBUG_FS + depends on (DEBUG_FS || PROC_FS) help Compiles debug level messages into the kernel, which would not @@ -116,8 +116,9 @@ config DYNAMIC_DEBUG Usage: Dynamic debugging is controlled via the 'dynamic_debug/control' file, - which is contained in the 'debugfs' filesystem. Thus, the debugfs - filesystem must first be mounted before making use of this feature. + which is contained in the 'debugfs' filesystem or procfs. + Thus, the debugfs or procfs filesystem must first be mounted before + making use of this feature. We refer the control file as: <debugfs>/dynamic_debug/control. This file contains a list of the debug statements that can be enabled. The format for each line of the file is: diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c index c60409138e13..aae17d9522e5 100644 --- a/lib/dynamic_debug.c +++ b/lib/dynamic_debug.c @@ -876,6 +876,14 @@ static const struct file_operations ddebug_proc_fops = { .write = ddebug_proc_write }; +static const struct proc_ops proc_fops = { + .proc_open = ddebug_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, + .proc_write = ddebug_proc_write +}; + /* * Allocate a new ddebug_table for the given module * and add it to the global list. @@ -991,15 +999,25 @@ static void ddebug_remove_all_tables(void) static __initdata int ddebug_init_success; -static int __init dynamic_debug_init_debugfs(void) +static int __init dynamic_debug_init_control(void) { - struct dentry *dir; + struct proc_dir_entry *procfs_dir; + struct dentry *debugfs_dir; if (!ddebug_init_success) return -ENODEV; - dir = debugfs_create_dir("dynamic_debug", NULL); - debugfs_create_file("control", 0644, dir, NULL, &ddebug_proc_fops); + /* Create the control file in debugfs if it is enabled */ + if (debugfs_initialized()) { + debugfs_dir = debugfs_create_dir("dynamic_debug", NULL); + debugfs_create_file("control", 0644, debugfs_dir, NULL, + &ddebug_proc_fops); + } + + /* Also create the control file in procfs */ + procfs_dir = proc_mkdir("dynamic_debug", NULL); + if (procfs_dir) + proc_create("control", 0644, procfs_dir, &proc_fops); return 0; } @@ -1077,4 +1095,4 @@ out_err: early_initcall(dynamic_debug_init); /* Debugfs setup must be done later */ -fs_initcall(dynamic_debug_init_debugfs); +fs_initcall(dynamic_debug_init_control); diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index d3c237b9b7c0..010be8ba2116 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -238,5 +238,8 @@ int main(void) DEVID(wmi_device_id); DEVID_FIELD(wmi_device_id, guid_string); + DEVID(mhi_device_id); + DEVID_FIELD(mhi_device_id, chan); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index f81cbe021a47..02d5d79da284 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1353,6 +1353,15 @@ static int do_wmi_entry(const char *filename, void *symval, char *alias) return 1; } +/* Looks like: mhi:S */ +static int do_mhi_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD_ADDR(symval, mhi_device_id, chan); + sprintf(alias, MHI_DEVICE_MODALIAS_FMT, *chan); + + return 1; +} + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { @@ -1426,6 +1435,7 @@ static const struct devtable devtable[] = { {"typec", SIZE_typec_device_id, do_typec_entry}, {"tee", SIZE_tee_client_device_id, do_tee_entry}, {"wmi", SIZE_wmi_device_id, do_wmi_entry}, + {"mhi", SIZE_mhi_device_id, do_mhi_entry}, }; /* Create MODULE_ALIAS() statements. diff --git a/scripts/ver_linux b/scripts/ver_linux index 85005d6b7f10..0968a3070eff 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -14,6 +14,8 @@ BEGIN { printf("\n") vernum = "[0-9]+([.]?[0-9]+)+" + libc = "libc[.]so[.][0-9]+$" + libcpp = "(libg|stdc)[+]+[.]so[.][0-9]+$" printversion("GNU C", version("gcc -dumpversion")) printversion("GNU Make", version("make --version")) @@ -35,26 +37,14 @@ BEGIN { printversion("Bison", version("bison --version")) printversion("Flex", version("flex --version")) - while (getline <"/proc/self/maps" > 0) { - if (/libc.*\.so$/) { - n = split($0, procmaps, "/") - if (match(procmaps[n], vernum)) { - ver = substr(procmaps[n], RSTART, RLENGTH) - printversion("Linux C Library", ver) - break - } - } + while ("ldconfig -p 2>/dev/null" | getline > 0) { + if ($NF ~ libc && !seen[ver = version("readlink " $NF)]++) + printversion("Linux C Library", ver) + else if ($NF ~ libcpp && !seen[ver = version("readlink " $NF)]++) + printversion("Linux C++ Library", ver) } printversion("Dynamic linker (ldd)", version("ldd --version")) - - while ("ldconfig -p 2>/dev/null" | getline > 0) { - if (/(libg|stdc)[+]+\.so/) { - libcpp = $NF - break - } - } - printversion("Linux C++ Library", version("readlink " libcpp)) printversion("Procps", version("ps --version")) printversion("Net-tools", version("ifconfig --version")) printversion("Kbd", version("loadkeys -V")) diff --git a/tools/testing/selftests/filesystems/binderfs/Makefile b/tools/testing/selftests/filesystems/binderfs/Makefile index 58cb659b56b4..8af25ae96049 100644 --- a/tools/testing/selftests/filesystems/binderfs/Makefile +++ b/tools/testing/selftests/filesystems/binderfs/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 -CFLAGS += -I../../../../../usr/include/ +CFLAGS += -I../../../../../usr/include/ -pthread TEST_GEN_PROGS := binderfs_test +binderfs_test: binderfs_test.c ../../kselftest.h ../../kselftest_harness.h + include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/binderfs/binderfs_test.c b/tools/testing/selftests/filesystems/binderfs/binderfs_test.c index 8c2ed962e1c7..8a6b507e34a8 100644 --- a/tools/testing/selftests/filesystems/binderfs/binderfs_test.c +++ b/tools/testing/selftests/filesystems/binderfs/binderfs_test.c @@ -3,114 +3,47 @@ #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> +#include <pthread.h> #include <sched.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/fsuid.h> #include <sys/ioctl.h> #include <sys/mount.h> +#include <sys/socket.h> #include <sys/stat.h> +#include <sys/sysinfo.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include <linux/android/binder.h> #include <linux/android/binderfs.h> -#include "../../kselftest.h" - -static ssize_t write_nointr(int fd, const void *buf, size_t count) -{ - ssize_t ret; -again: - ret = write(fd, buf, count); - if (ret < 0 && errno == EINTR) - goto again; - - return ret; -} -static void write_to_file(const char *filename, const void *buf, size_t count, - int allowed_errno) -{ - int fd, saved_errno; - ssize_t ret; +#include "../../kselftest.h" +#include "../../kselftest_harness.h" - fd = open(filename, O_WRONLY | O_CLOEXEC); - if (fd < 0) - ksft_exit_fail_msg("%s - Failed to open file %s\n", - strerror(errno), filename); +#define DEFAULT_THREADS 4 - ret = write_nointr(fd, buf, count); - if (ret < 0) { - if (allowed_errno && (errno == allowed_errno)) { - close(fd); - return; - } +#define PTR_TO_INT(p) ((int)((intptr_t)(p))) +#define INT_TO_PTR(u) ((void *)((intptr_t)(u))) - goto on_error; +#define close_prot_errno_disarm(fd) \ + if (fd >= 0) { \ + int _e_ = errno; \ + close(fd); \ + errno = _e_; \ + fd = -EBADF; \ } - if ((size_t)ret != count) - goto on_error; - - close(fd); - return; - -on_error: - saved_errno = errno; - close(fd); - errno = saved_errno; - - if (ret < 0) - ksft_exit_fail_msg("%s - Failed to write to file %s\n", - strerror(errno), filename); - - ksft_exit_fail_msg("Failed to write to file %s\n", filename); -} - -static void change_to_userns(void) -{ - int ret; - uid_t uid; - gid_t gid; - /* {g,u}id_map files only allow a max of 4096 bytes written to them */ - char idmap[4096]; - - uid = getuid(); - gid = getgid(); - - ret = unshare(CLONE_NEWUSER); - if (ret < 0) - ksft_exit_fail_msg("%s - Failed to unshare user namespace\n", - strerror(errno)); - - write_to_file("/proc/self/setgroups", "deny", strlen("deny"), ENOENT); - - ret = snprintf(idmap, sizeof(idmap), "0 %d 1", uid); - if (ret < 0 || (size_t)ret >= sizeof(idmap)) - ksft_exit_fail_msg("%s - Failed to prepare uid mapping\n", - strerror(errno)); - - write_to_file("/proc/self/uid_map", idmap, strlen(idmap), 0); - - ret = snprintf(idmap, sizeof(idmap), "0 %d 1", gid); - if (ret < 0 || (size_t)ret >= sizeof(idmap)) - ksft_exit_fail_msg("%s - Failed to prepare uid mapping\n", - strerror(errno)); - - write_to_file("/proc/self/gid_map", idmap, strlen(idmap), 0); - - ret = setgid(0); - if (ret) - ksft_exit_fail_msg("%s - Failed to setgid(0)\n", - strerror(errno)); +#define log_exit(format, ...) \ + ({ \ + fprintf(stderr, format "\n", ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + }) - ret = setuid(0); - if (ret) - ksft_exit_fail_msg("%s - Failed to setgid(0)\n", - strerror(errno)); -} - -static void change_to_mountns(void) +static void change_mountns(void) { int ret; @@ -132,36 +65,31 @@ static void rmdir_protect_errno(const char *dir) errno = saved_errno; } -static void __do_binderfs_test(void) +static int __do_binderfs_test(void) { int fd, ret, saved_errno; size_t len; ssize_t wret; - bool keep = false; struct binderfs_device device = { 0 }; struct binder_version version = { 0 }; + char binderfs_mntpt[] = P_tmpdir "/binderfs_XXXXXX", + device_path[sizeof(P_tmpdir "/binderfs_XXXXXX/") + BINDERFS_MAX_NAME]; - change_to_mountns(); - - ret = mkdir("/dev/binderfs", 0755); - if (ret < 0) { - if (errno != EEXIST) - ksft_exit_fail_msg( - "%s - Failed to create binderfs mountpoint\n", - strerror(errno)); + change_mountns(); - keep = true; - } + if (!mkdtemp(binderfs_mntpt)) + ksft_exit_fail_msg( + "%s - Failed to create binderfs mountpoint\n", + strerror(errno)); - ret = mount(NULL, "/dev/binderfs", "binder", 0, 0); + ret = mount(NULL, binderfs_mntpt, "binder", 0, 0); if (ret < 0) { if (errno != ENODEV) ksft_exit_fail_msg("%s - Failed to mount binderfs\n", strerror(errno)); - keep ? : rmdir_protect_errno("/dev/binderfs"); - ksft_exit_skip( - "The Android binderfs filesystem is not available\n"); + rmdir_protect_errno(binderfs_mntpt); + return 1; } /* binderfs mount test passed */ @@ -169,7 +97,8 @@ static void __do_binderfs_test(void) memcpy(device.name, "my-binder", strlen("my-binder")); - fd = open("/dev/binderfs/binder-control", O_RDONLY | O_CLOEXEC); + snprintf(device_path, sizeof(device_path), "%s/binder-control", binderfs_mntpt); + fd = open(device_path, O_RDONLY | O_CLOEXEC); if (fd < 0) ksft_exit_fail_msg( "%s - Failed to open binder-control device\n", @@ -180,7 +109,7 @@ static void __do_binderfs_test(void) close(fd); errno = saved_errno; if (ret < 0) { - keep ? : rmdir_protect_errno("/dev/binderfs"); + rmdir_protect_errno(binderfs_mntpt); ksft_exit_fail_msg( "%s - Failed to allocate new binder device\n", strerror(errno)); @@ -193,9 +122,10 @@ static void __do_binderfs_test(void) /* binder device allocation test passed */ ksft_inc_pass_cnt(); - fd = open("/dev/binderfs/my-binder", O_CLOEXEC | O_RDONLY); + snprintf(device_path, sizeof(device_path), "%s/my-binder", binderfs_mntpt); + fd = open(device_path, O_CLOEXEC | O_RDONLY); if (fd < 0) { - keep ? : rmdir_protect_errno("/dev/binderfs"); + rmdir_protect_errno(binderfs_mntpt); ksft_exit_fail_msg("%s - Failed to open my-binder device\n", strerror(errno)); } @@ -205,7 +135,7 @@ static void __do_binderfs_test(void) close(fd); errno = saved_errno; if (ret < 0) { - keep ? : rmdir_protect_errno("/dev/binderfs"); + rmdir_protect_errno(binderfs_mntpt); ksft_exit_fail_msg( "%s - Failed to open perform BINDER_VERSION request\n", strerror(errno)); @@ -217,9 +147,9 @@ static void __do_binderfs_test(void) /* binder transaction with binderfs binder device passed */ ksft_inc_pass_cnt(); - ret = unlink("/dev/binderfs/my-binder"); + ret = unlink(device_path); if (ret < 0) { - keep ? : rmdir_protect_errno("/dev/binderfs"); + rmdir_protect_errno(binderfs_mntpt); ksft_exit_fail_msg("%s - Failed to delete binder device\n", strerror(errno)); } @@ -227,12 +157,13 @@ static void __do_binderfs_test(void) /* binder device removal passed */ ksft_inc_pass_cnt(); - ret = unlink("/dev/binderfs/binder-control"); + snprintf(device_path, sizeof(device_path), "%s/binder-control", binderfs_mntpt); + ret = unlink(device_path); if (!ret) { - keep ? : rmdir_protect_errno("/dev/binderfs"); + rmdir_protect_errno(binderfs_mntpt); ksft_exit_fail_msg("Managed to delete binder-control device\n"); } else if (errno != EPERM) { - keep ? : rmdir_protect_errno("/dev/binderfs"); + rmdir_protect_errno(binderfs_mntpt); ksft_exit_fail_msg( "%s - Failed to delete binder-control device but exited with unexpected error code\n", strerror(errno)); @@ -242,34 +173,341 @@ static void __do_binderfs_test(void) ksft_inc_xfail_cnt(); on_error: - ret = umount2("/dev/binderfs", MNT_DETACH); - keep ?: rmdir_protect_errno("/dev/binderfs"); + ret = umount2(binderfs_mntpt, MNT_DETACH); + rmdir_protect_errno(binderfs_mntpt); if (ret < 0) ksft_exit_fail_msg("%s - Failed to unmount binderfs\n", strerror(errno)); /* binderfs unmount test passed */ ksft_inc_pass_cnt(); + return 0; } -static void binderfs_test_privileged() +static int wait_for_pid(pid_t pid) { - if (geteuid() != 0) - ksft_print_msg( - "Tests are not run as root. Skipping privileged tests\n"); - else - __do_binderfs_test(); + int status, ret; + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == EINTR) + goto again; + + return -1; + } + + if (!WIFEXITED(status)) + return -1; + + return WEXITSTATUS(status); +} + +static int setid_userns_root(void) +{ + if (setuid(0)) + return -1; + if (setgid(0)) + return -1; + + setfsuid(0); + setfsgid(0); + + return 0; +} + +enum idmap_type { + UID_MAP, + GID_MAP, +}; + +static ssize_t read_nointr(int fd, void *buf, size_t count) +{ + ssize_t ret; +again: + ret = read(fd, buf, count); + if (ret < 0 && errno == EINTR) + goto again; + + return ret; +} + +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t ret; +again: + ret = write(fd, buf, count); + if (ret < 0 && errno == EINTR) + goto again; + + return ret; +} + +static int write_id_mapping(enum idmap_type type, pid_t pid, const char *buf, + size_t buf_size) +{ + int fd; + int ret; + char path[4096]; + + if (type == GID_MAP) { + int setgroups_fd; + + snprintf(path, sizeof(path), "/proc/%d/setgroups", pid); + setgroups_fd = open(path, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); + if (setgroups_fd < 0 && errno != ENOENT) + return -1; + + if (setgroups_fd >= 0) { + ret = write_nointr(setgroups_fd, "deny", sizeof("deny") - 1); + close_prot_errno_disarm(setgroups_fd); + if (ret != sizeof("deny") - 1) + return -1; + } + } + + switch (type) { + case UID_MAP: + ret = snprintf(path, sizeof(path), "/proc/%d/uid_map", pid); + break; + case GID_MAP: + ret = snprintf(path, sizeof(path), "/proc/%d/gid_map", pid); + break; + default: + return -1; + } + if (ret < 0 || ret >= sizeof(path)) + return -E2BIG; + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); + if (fd < 0) + return -1; + + ret = write_nointr(fd, buf, buf_size); + close_prot_errno_disarm(fd); + if (ret != buf_size) + return -1; + + return 0; +} + +static void change_userns(int syncfds[2]) +{ + int ret; + char buf; + + close_prot_errno_disarm(syncfds[1]); + + ret = unshare(CLONE_NEWUSER); + if (ret < 0) + ksft_exit_fail_msg("%s - Failed to unshare user namespace\n", + strerror(errno)); + + ret = write_nointr(syncfds[0], "1", 1); + if (ret != 1) + ksft_exit_fail_msg("write_nointr() failed\n"); + + ret = read_nointr(syncfds[0], &buf, 1); + if (ret != 1) + ksft_exit_fail_msg("read_nointr() failed\n"); + + close_prot_errno_disarm(syncfds[0]); + + if (setid_userns_root()) + ksft_exit_fail_msg("setid_userns_root() failed"); +} + +static void change_idmaps(int syncfds[2], pid_t pid) +{ + int ret; + char buf; + char id_map[4096]; + + close_prot_errno_disarm(syncfds[0]); + + ret = read_nointr(syncfds[1], &buf, 1); + if (ret != 1) + ksft_exit_fail_msg("read_nointr() failed\n"); + + snprintf(id_map, sizeof(id_map), "0 %d 1\n", getuid()); + ret = write_id_mapping(UID_MAP, pid, id_map, strlen(id_map)); + if (ret) + ksft_exit_fail_msg("write_id_mapping(UID_MAP) failed"); + + snprintf(id_map, sizeof(id_map), "0 %d 1\n", getgid()); + ret = write_id_mapping(GID_MAP, pid, id_map, strlen(id_map)); + if (ret) + ksft_exit_fail_msg("write_id_mapping(GID_MAP) failed"); + + ret = write_nointr(syncfds[1], "1", 1); + if (ret != 1) + ksft_exit_fail_msg("write_nointr() failed"); + + close_prot_errno_disarm(syncfds[1]); } -static void binderfs_test_unprivileged() +static void *binder_version_thread(void *data) { - change_to_userns(); - __do_binderfs_test(); + int fd = PTR_TO_INT(data); + struct binder_version version = { 0 }; + int ret; + + ret = ioctl(fd, BINDER_VERSION, &version); + if (ret < 0) + ksft_print_msg("%s - Failed to open perform BINDER_VERSION request\n", strerror(errno)); + + pthread_exit(data); } -int main(int argc, char *argv[]) +/* + * Regression test: + * 2669b8b0c798 ("binder: prevent UAF for binderfs devices") + * f0fe2c0f050d ("binder: prevent UAF for binderfs devices II") + * 211b64e4b5b6 ("binderfs: use refcount for binder control devices too") + */ +TEST(binderfs_stress) { - binderfs_test_privileged(); - binderfs_test_unprivileged(); - ksft_exit_pass(); + int fds[1000]; + int syncfds[2]; + pid_t pid; + int fd, ret; + size_t len; + struct binderfs_device device = { 0 }; + char binderfs_mntpt[] = P_tmpdir "/binderfs_XXXXXX", + device_path[sizeof(P_tmpdir "/binderfs_XXXXXX/") + BINDERFS_MAX_NAME]; + + ret = socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, syncfds); + if (ret < 0) + ksft_exit_fail_msg("%s - Failed to create socket pair", strerror(errno)); + + pid = fork(); + if (pid < 0) { + close_prot_errno_disarm(syncfds[0]); + close_prot_errno_disarm(syncfds[1]); + ksft_exit_fail_msg("%s - Failed to fork", strerror(errno)); + } + + if (pid == 0) { + int i, j, k, nthreads; + pthread_attr_t attr; + pthread_t threads[DEFAULT_THREADS]; + change_userns(syncfds); + change_mountns(); + + if (!mkdtemp(binderfs_mntpt)) + log_exit("%s - Failed to create binderfs mountpoint\n", + strerror(errno)); + + ret = mount(NULL, binderfs_mntpt, "binder", 0, 0); + if (ret < 0) + log_exit("%s - Failed to mount binderfs\n", strerror(errno)); + + for (int i = 0; i < ARRAY_SIZE(fds); i++) { + + snprintf(device_path, sizeof(device_path), + "%s/binder-control", binderfs_mntpt); + fd = open(device_path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + log_exit("%s - Failed to open binder-control device\n", strerror(errno)); + + memset(&device, 0, sizeof(device)); + snprintf(device.name, sizeof(device.name), "%d", i); + ret = ioctl(fd, BINDER_CTL_ADD, &device); + close_prot_errno_disarm(fd); + if (ret < 0) + log_exit("%s - Failed to allocate new binder device\n", strerror(errno)); + + snprintf(device_path, sizeof(device_path), "%s/%d", + binderfs_mntpt, i); + fds[i] = open(device_path, O_RDONLY | O_CLOEXEC); + if (fds[i] < 0) + log_exit("%s - Failed to open binder device\n", strerror(errno)); + } + + ret = umount2(binderfs_mntpt, MNT_DETACH); + rmdir_protect_errno(binderfs_mntpt); + if (ret < 0) + log_exit("%s - Failed to unmount binderfs\n", strerror(errno)); + + nthreads = get_nprocs_conf(); + if (nthreads > DEFAULT_THREADS) + nthreads = DEFAULT_THREADS; + + pthread_attr_init(&attr); + for (k = 0; k < ARRAY_SIZE(fds); k++) { + for (i = 0; i < nthreads; i++) { + ret = pthread_create(&threads[i], &attr, binder_version_thread, INT_TO_PTR(fds[k])); + if (ret) { + ksft_print_msg("%s - Failed to create thread %d\n", strerror(errno), i); + break; + } + } + + for (j = 0; j < i; j++) { + void *fdptr = NULL; + + ret = pthread_join(threads[j], &fdptr); + if (ret) + ksft_print_msg("%s - Failed to join thread %d for fd %d\n", strerror(errno), j, PTR_TO_INT(fdptr)); + } + } + pthread_attr_destroy(&attr); + + for (k = 0; k < ARRAY_SIZE(fds); k++) + close(fds[k]); + + exit(EXIT_SUCCESS); + } + + change_idmaps(syncfds, pid); + + ret = wait_for_pid(pid); + if (ret) + ksft_exit_fail_msg("wait_for_pid() failed"); } + +TEST(binderfs_test_privileged) +{ + if (geteuid() != 0) + XFAIL(return, "Tests are not run as root. Skipping privileged tests"); + + if (__do_binderfs_test() == 1) + XFAIL(return, "The Android binderfs filesystem is not available"); +} + +TEST(binderfs_test_unprivileged) +{ + int ret; + int syncfds[2]; + pid_t pid; + + ret = socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, syncfds); + if (ret < 0) + ksft_exit_fail_msg("%s - Failed to create socket pair", strerror(errno)); + + pid = fork(); + if (pid < 0) { + close_prot_errno_disarm(syncfds[0]); + close_prot_errno_disarm(syncfds[1]); + ksft_exit_fail_msg("%s - Failed to fork", strerror(errno)); + } + + if (pid == 0) { + change_userns(syncfds); + if (__do_binderfs_test() == 1) + exit(2); + exit(EXIT_SUCCESS); + } + + change_idmaps(syncfds, pid); + + ret = wait_for_pid(pid); + if (ret) { + if (ret == 2) + XFAIL(return, "The Android binderfs filesystem is not available"); + else + ksft_exit_fail_msg("wait_for_pid() failed"); + } +} + +TEST_HARNESS_MAIN |