+++ /dev/null
-# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
-%YAML 1.2
----
-$id: http://devicetree.org/schemas/arm/idle-states.yaml#
-$schema: http://devicetree.org/meta-schemas/core.yaml#
-
-title: ARM idle states binding description
-
-maintainers:
- - Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
-
-description: |+
- ==========================================
- 1 - Introduction
- ==========================================
-
- ARM systems contain HW capable of managing power consumption dynamically,
- where cores can be put in different low-power states (ranging from simple wfi
- to power gating) according to OS PM policies. The CPU states representing the
- range of dynamic idle states that a processor can enter at run-time, can be
- specified through device tree bindings representing the parameters required to
- enter/exit specific idle states on a given processor.
-
- According to the Server Base System Architecture document (SBSA, [3]), the
- power states an ARM CPU can be put into are identified by the following list:
-
- - Running
- - Idle_standby
- - Idle_retention
- - Sleep
- - Off
-
- The power states described in the SBSA document define the basic CPU states on
- top of which ARM platforms implement power management schemes that allow an OS
- PM implementation to put the processor in different idle states (which include
- states listed above; "off" state is not an idle state since it does not have
- wake-up capabilities, hence it is not considered in this document).
-
- Idle state parameters (e.g. entry latency) are platform specific and need to
- be characterized with bindings that provide the required information to OS PM
- code so that it can build the required tables and use them at runtime.
-
- The device tree binding definition for ARM idle states is the subject of this
- document.
-
- ===========================================
- 2 - idle-states definitions
- ===========================================
-
- Idle states are characterized for a specific system through a set of
- timing and energy related properties, that underline the HW behaviour
- triggered upon idle states entry and exit.
-
- The following diagram depicts the CPU execution phases and related timing
- properties required to enter and exit an idle state:
-
- ..__[EXEC]__|__[PREP]__|__[ENTRY]__|__[IDLE]__|__[EXIT]__|__[EXEC]__..
- | | | | |
-
- |<------ entry ------->|
- | latency |
- |<- exit ->|
- | latency |
- |<-------- min-residency -------->|
- |<------- wakeup-latency ------->|
-
- Diagram 1: CPU idle state execution phases
-
- EXEC: Normal CPU execution.
-
- PREP: Preparation phase before committing the hardware to idle mode
- like cache flushing. This is abortable on pending wake-up
- event conditions. The abort latency is assumed to be negligible
- (i.e. less than the ENTRY + EXIT duration). If aborted, CPU
- goes back to EXEC. This phase is optional. If not abortable,
- this should be included in the ENTRY phase instead.
-
- ENTRY: The hardware is committed to idle mode. This period must run
- to completion up to IDLE before anything else can happen.
-
- IDLE: This is the actual energy-saving idle period. This may last
- between 0 and infinite time, until a wake-up event occurs.
-
- EXIT: Period during which the CPU is brought back to operational
- mode (EXEC).
-
- entry-latency: Worst case latency required to enter the idle state. The
- exit-latency may be guaranteed only after entry-latency has passed.
-
- min-residency: Minimum period, including preparation and entry, for a given
- idle state to be worthwhile energywise.
-
- wakeup-latency: Maximum delay between the signaling of a wake-up event and the
- CPU being able to execute normal code again. If not specified, this is assumed
- to be entry-latency + exit-latency.
-
- These timing parameters can be used by an OS in different circumstances.
-
- An idle CPU requires the expected min-residency time to select the most
- appropriate idle state based on the expected expiry time of the next IRQ
- (i.e. wake-up) that causes the CPU to return to the EXEC phase.
-
- An operating system scheduler may need to compute the shortest wake-up delay
- for CPUs in the system by detecting how long will it take to get a CPU out
- of an idle state, e.g.:
-
- wakeup-delay = exit-latency + max(entry-latency - (now - entry-timestamp), 0)
-
- In other words, the scheduler can make its scheduling decision by selecting
- (e.g. waking-up) the CPU with the shortest wake-up delay.
- The wake-up delay must take into account the entry latency if that period
- has not expired. The abortable nature of the PREP period can be ignored
- if it cannot be relied upon (e.g. the PREP deadline may occur much sooner than
- the worst case since it depends on the CPU operating conditions, i.e. caches
- state).
-
- An OS has to reliably probe the wakeup-latency since some devices can enforce
- latency constraint guarantees to work properly, so the OS has to detect the
- worst case wake-up latency it can incur if a CPU is allowed to enter an
- idle state, and possibly to prevent that to guarantee reliable device
- functioning.
-
- The min-residency time parameter deserves further explanation since it is
- expressed in time units but must factor in energy consumption coefficients.
-
- The energy consumption of a cpu when it enters a power state can be roughly
- characterised by the following graph:
-
- |
- |
- |
- e |
- n | /---
- e | /------
- r | /------
- g | /-----
- y | /------
- | ----
- | /|
- | / |
- | / |
- | / |
- | / |
- | / |
- |/ |
- -----|-------+----------------------------------
- 0| 1 time(ms)
-
- Graph 1: Energy vs time example
-
- The graph is split in two parts delimited by time 1ms on the X-axis.
- The graph curve with X-axis values = { x | 0 < x < 1ms } has a steep slope
- and denotes the energy costs incurred while entering and leaving the idle
- state.
- The graph curve in the area delimited by X-axis values = {x | x > 1ms } has
- shallower slope and essentially represents the energy consumption of the idle
- state.
-
- min-residency is defined for a given idle state as the minimum expected
- residency time for a state (inclusive of preparation and entry) after
- which choosing that state become the most energy efficient option. A good
- way to visualise this, is by taking the same graph above and comparing some
- states energy consumptions plots.
-
- For sake of simplicity, let's consider a system with two idle states IDLE1,
- and IDLE2:
-
- |
- |
- |
- | /-- IDLE1
- e | /---
- n | /----
- e | /---
- r | /-----/--------- IDLE2
- g | /-------/---------
- y | ------------ /---|
- | / /---- |
- | / /--- |
- | / /---- |
- | / /--- |
- | --- |
- | / |
- | / |
- |/ | time
- ---/----------------------------+------------------------
- |IDLE1-energy < IDLE2-energy | IDLE2-energy < IDLE1-energy
- |
- IDLE2-min-residency
-
- Graph 2: idle states min-residency example
-
- In graph 2 above, that takes into account idle states entry/exit energy
- costs, it is clear that if the idle state residency time (i.e. time till next
- wake-up IRQ) is less than IDLE2-min-residency, IDLE1 is the better idle state
- choice energywise.
-
- This is mainly down to the fact that IDLE1 entry/exit energy costs are lower
- than IDLE2.
-
- However, the lower power consumption (i.e. shallower energy curve slope) of
- idle state IDLE2 implies that after a suitable time, IDLE2 becomes more energy
- efficient.
-
- The time at which IDLE2 becomes more energy efficient than IDLE1 (and other
- shallower states in a system with multiple idle states) is defined
- IDLE2-min-residency and corresponds to the time when energy consumption of
- IDLE1 and IDLE2 states breaks even.
-
- The definitions provided in this section underpin the idle states
- properties specification that is the subject of the following sections.
-
- ===========================================
- 3 - idle-states node
- ===========================================
-
- ARM processor idle states are defined within the idle-states node, which is
- a direct child of the cpus node [1] and provides a container where the
- processor idle states, defined as device tree nodes, are listed.
-
- On ARM systems, it is a container of processor idle states nodes. If the
- system does not provide CPU power management capabilities, or the processor
- just supports idle_standby, an idle-states node is not required.
-
- ===========================================
- 4 - References
- ===========================================
-
- [1] ARM Linux Kernel documentation - CPUs bindings
- Documentation/devicetree/bindings/arm/cpus.yaml
-
- [2] ARM Linux Kernel documentation - PSCI bindings
- Documentation/devicetree/bindings/arm/psci.yaml
-
- [3] ARM Server Base System Architecture (SBSA)
- http://infocenter.arm.com/help/index.jsp
-
- [4] ARM Architecture Reference Manuals
- http://infocenter.arm.com/help/index.jsp
-
- [6] ARM Linux Kernel documentation - Booting AArch64 Linux
- Documentation/arm64/booting.rst
-
-properties:
- $nodename:
- const: idle-states
-
- entry-method:
- description: |
- Usage and definition depend on ARM architecture version.
-
- On ARM v8 64-bit this property is required.
- On ARM 32-bit systems this property is optional
-
- This assumes that the "enable-method" property is set to "psci" in the cpu
- node[6] that is responsible for setting up CPU idle management in the OS
- implementation.
- const: psci
-
-patternProperties:
- "^(cpu|cluster)-":
- type: object
- description: |
- Each state node represents an idle state description and must be defined
- as follows.
-
- The idle state entered by executing the wfi instruction (idle_standby
- SBSA,[3][4]) is considered standard on all ARM platforms and therefore
- must not be listed.
-
- In addition to the properties listed above, a state node may require
- additional properties specific to the entry-method defined in the
- idle-states node. Please refer to the entry-method bindings
- documentation for properties definitions.
-
- properties:
- compatible:
- const: arm,idle-state
-
- local-timer-stop:
- description:
- If present the CPU local timer control logic is
- lost on state entry, otherwise it is retained.
- type: boolean
-
- entry-latency-us:
- description:
- Worst case latency in microseconds required to enter the idle state.
-
- exit-latency-us:
- description:
- Worst case latency in microseconds required to exit the idle state.
- The exit-latency-us duration may be guaranteed only after
- entry-latency-us has passed.
-
- min-residency-us:
- description:
- Minimum residency duration in microseconds, inclusive of preparation
- and entry, for this idle state to be considered worthwhile energy wise
- (refer to section 2 of this document for a complete description).
-
- wakeup-latency-us:
- description: |
- Maximum delay between the signaling of a wake-up event and the CPU
- being able to execute normal code again. If omitted, this is assumed
- to be equal to:
-
- entry-latency-us + exit-latency-us
-
- It is important to supply this value on systems where the duration of
- PREP phase (see diagram 1, section 2) is non-neglibigle. In such
- systems entry-latency-us + exit-latency-us will exceed
- wakeup-latency-us by this duration.
-
- idle-state-name:
- $ref: /schemas/types.yaml#definitions/string
- description:
- A string used as a descriptive name for the idle state.
-
- required:
- - compatible
- - entry-latency-us
- - exit-latency-us
- - min-residency-us
-
-additionalProperties: false
-
-examples:
- - |
-
- cpus {
- #size-cells = <0>;
- #address-cells = <2>;
-
- cpu@0 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x0>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@1 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x1>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@100 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x100>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@101 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x101>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@10000 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x10000>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@10001 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x10001>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@10100 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x10100>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@10101 {
- device_type = "cpu";
- compatible = "arm,cortex-a57";
- reg = <0x0 0x10101>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
- &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
- };
-
- cpu@100000000 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x0>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100000001 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x1>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100000100 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x100>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100000101 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x101>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100010000 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x10000>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100010001 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x10001>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100010100 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x10100>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- cpu@100010101 {
- device_type = "cpu";
- compatible = "arm,cortex-a53";
- reg = <0x1 0x10101>;
- enable-method = "psci";
- cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
- &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
- };
-
- idle-states {
- entry-method = "psci";
-
- CPU_RETENTION_0_0: cpu-retention-0-0 {
- compatible = "arm,idle-state";
- arm,psci-suspend-param = <0x0010000>;
- entry-latency-us = <20>;
- exit-latency-us = <40>;
- min-residency-us = <80>;
- };
-
- CLUSTER_RETENTION_0: cluster-retention-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- arm,psci-suspend-param = <0x1010000>;
- entry-latency-us = <50>;
- exit-latency-us = <100>;
- min-residency-us = <250>;
- wakeup-latency-us = <130>;
- };
-
- CPU_SLEEP_0_0: cpu-sleep-0-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- arm,psci-suspend-param = <0x0010000>;
- entry-latency-us = <250>;
- exit-latency-us = <500>;
- min-residency-us = <950>;
- };
-
- CLUSTER_SLEEP_0: cluster-sleep-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- arm,psci-suspend-param = <0x1010000>;
- entry-latency-us = <600>;
- exit-latency-us = <1100>;
- min-residency-us = <2700>;
- wakeup-latency-us = <1500>;
- };
-
- CPU_RETENTION_1_0: cpu-retention-1-0 {
- compatible = "arm,idle-state";
- arm,psci-suspend-param = <0x0010000>;
- entry-latency-us = <20>;
- exit-latency-us = <40>;
- min-residency-us = <90>;
- };
-
- CLUSTER_RETENTION_1: cluster-retention-1 {
- compatible = "arm,idle-state";
- local-timer-stop;
- arm,psci-suspend-param = <0x1010000>;
- entry-latency-us = <50>;
- exit-latency-us = <100>;
- min-residency-us = <270>;
- wakeup-latency-us = <100>;
- };
-
- CPU_SLEEP_1_0: cpu-sleep-1-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- arm,psci-suspend-param = <0x0010000>;
- entry-latency-us = <70>;
- exit-latency-us = <100>;
- min-residency-us = <300>;
- wakeup-latency-us = <150>;
- };
-
- CLUSTER_SLEEP_1: cluster-sleep-1 {
- compatible = "arm,idle-state";
- local-timer-stop;
- arm,psci-suspend-param = <0x1010000>;
- entry-latency-us = <500>;
- exit-latency-us = <1200>;
- min-residency-us = <3500>;
- wakeup-latency-us = <1300>;
- };
- };
- };
-
- - |
- // Example 2 (ARM 32-bit, 8-cpu system, two clusters):
-
- cpus {
- #size-cells = <0>;
- #address-cells = <1>;
-
- cpu@0 {
- device_type = "cpu";
- compatible = "arm,cortex-a15";
- reg = <0x0>;
- cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
- };
-
- cpu@1 {
- device_type = "cpu";
- compatible = "arm,cortex-a15";
- reg = <0x1>;
- cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
- };
-
- cpu@2 {
- device_type = "cpu";
- compatible = "arm,cortex-a15";
- reg = <0x2>;
- cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
- };
-
- cpu@3 {
- device_type = "cpu";
- compatible = "arm,cortex-a15";
- reg = <0x3>;
- cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
- };
-
- cpu@100 {
- device_type = "cpu";
- compatible = "arm,cortex-a7";
- reg = <0x100>;
- cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
- };
-
- cpu@101 {
- device_type = "cpu";
- compatible = "arm,cortex-a7";
- reg = <0x101>;
- cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
- };
-
- cpu@102 {
- device_type = "cpu";
- compatible = "arm,cortex-a7";
- reg = <0x102>;
- cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
- };
-
- cpu@103 {
- device_type = "cpu";
- compatible = "arm,cortex-a7";
- reg = <0x103>;
- cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
- };
-
- idle-states {
- cpu_sleep_0_0: cpu-sleep-0-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- entry-latency-us = <200>;
- exit-latency-us = <100>;
- min-residency-us = <400>;
- wakeup-latency-us = <250>;
- };
-
- cluster_sleep_0: cluster-sleep-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- entry-latency-us = <500>;
- exit-latency-us = <1500>;
- min-residency-us = <2500>;
- wakeup-latency-us = <1700>;
- };
-
- cpu_sleep_1_0: cpu-sleep-1-0 {
- compatible = "arm,idle-state";
- local-timer-stop;
- entry-latency-us = <300>;
- exit-latency-us = <500>;
- min-residency-us = <900>;
- wakeup-latency-us = <600>;
- };
-
- cluster_sleep_1: cluster-sleep-1 {
- compatible = "arm,idle-state";
- local-timer-stop;
- entry-latency-us = <800>;
- exit-latency-us = <2000>;
- min-residency-us = <6500>;
- wakeup-latency-us = <2300>;
- };
- };
- };
-
-...
};
};
-[1]. Documentation/devicetree/bindings/arm/idle-states.yaml
+[1]. Documentation/devicetree/bindings/cpu/idle-states.yaml
bindings in [1]) must specify this property.
[1] Kernel documentation - ARM idle states bindings
- Documentation/devicetree/bindings/arm/idle-states.yaml
+ Documentation/devicetree/bindings/cpu/idle-states.yaml
patternProperties:
"^power-domain-":
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/cpu/idle-states.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Idle states binding description
+
+maintainers:
+ - Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ - Anup Patel <anup@brainfault.org>
+
+description: |+
+ ==========================================
+ 1 - Introduction
+ ==========================================
+
+ ARM and RISC-V systems contain HW capable of managing power consumption
+ dynamically, where cores can be put in different low-power states (ranging
+ from simple wfi to power gating) according to OS PM policies. The CPU states
+ representing the range of dynamic idle states that a processor can enter at
+ run-time, can be specified through device tree bindings representing the
+ parameters required to enter/exit specific idle states on a given processor.
+
+ ==========================================
+ 2 - ARM idle states
+ ==========================================
+
+ According to the Server Base System Architecture document (SBSA, [3]), the
+ power states an ARM CPU can be put into are identified by the following list:
+
+ - Running
+ - Idle_standby
+ - Idle_retention
+ - Sleep
+ - Off
+
+ The power states described in the SBSA document define the basic CPU states on
+ top of which ARM platforms implement power management schemes that allow an OS
+ PM implementation to put the processor in different idle states (which include
+ states listed above; "off" state is not an idle state since it does not have
+ wake-up capabilities, hence it is not considered in this document).
+
+ Idle state parameters (e.g. entry latency) are platform specific and need to
+ be characterized with bindings that provide the required information to OS PM
+ code so that it can build the required tables and use them at runtime.
+
+ The device tree binding definition for ARM idle states is the subject of this
+ document.
+
+ ==========================================
+ 3 - RISC-V idle states
+ ==========================================
+
+ On RISC-V systems, the HARTs (or CPUs) [6] can be put in platform specific
+ suspend (or idle) states (ranging from simple WFI, power gating, etc). The
+ RISC-V SBI v0.3 (or higher) [7] hart state management extension provides a
+ standard mechanism for OS to request HART state transitions.
+
+ The platform specific suspend (or idle) states of a hart can be either
+ retentive or non-rententive in nature. A retentive suspend state will
+ preserve HART registers and CSR values for all privilege modes whereas
+ a non-retentive suspend state will not preserve HART registers and CSR
+ values.
+
+ ===========================================
+ 4 - idle-states definitions
+ ===========================================
+
+ Idle states are characterized for a specific system through a set of
+ timing and energy related properties, that underline the HW behaviour
+ triggered upon idle states entry and exit.
+
+ The following diagram depicts the CPU execution phases and related timing
+ properties required to enter and exit an idle state:
+
+ ..__[EXEC]__|__[PREP]__|__[ENTRY]__|__[IDLE]__|__[EXIT]__|__[EXEC]__..
+ | | | | |
+
+ |<------ entry ------->|
+ | latency |
+ |<- exit ->|
+ | latency |
+ |<-------- min-residency -------->|
+ |<------- wakeup-latency ------->|
+
+ Diagram 1: CPU idle state execution phases
+
+ EXEC: Normal CPU execution.
+
+ PREP: Preparation phase before committing the hardware to idle mode
+ like cache flushing. This is abortable on pending wake-up
+ event conditions. The abort latency is assumed to be negligible
+ (i.e. less than the ENTRY + EXIT duration). If aborted, CPU
+ goes back to EXEC. This phase is optional. If not abortable,
+ this should be included in the ENTRY phase instead.
+
+ ENTRY: The hardware is committed to idle mode. This period must run
+ to completion up to IDLE before anything else can happen.
+
+ IDLE: This is the actual energy-saving idle period. This may last
+ between 0 and infinite time, until a wake-up event occurs.
+
+ EXIT: Period during which the CPU is brought back to operational
+ mode (EXEC).
+
+ entry-latency: Worst case latency required to enter the idle state. The
+ exit-latency may be guaranteed only after entry-latency has passed.
+
+ min-residency: Minimum period, including preparation and entry, for a given
+ idle state to be worthwhile energywise.
+
+ wakeup-latency: Maximum delay between the signaling of a wake-up event and the
+ CPU being able to execute normal code again. If not specified, this is assumed
+ to be entry-latency + exit-latency.
+
+ These timing parameters can be used by an OS in different circumstances.
+
+ An idle CPU requires the expected min-residency time to select the most
+ appropriate idle state based on the expected expiry time of the next IRQ
+ (i.e. wake-up) that causes the CPU to return to the EXEC phase.
+
+ An operating system scheduler may need to compute the shortest wake-up delay
+ for CPUs in the system by detecting how long will it take to get a CPU out
+ of an idle state, e.g.:
+
+ wakeup-delay = exit-latency + max(entry-latency - (now - entry-timestamp), 0)
+
+ In other words, the scheduler can make its scheduling decision by selecting
+ (e.g. waking-up) the CPU with the shortest wake-up delay.
+ The wake-up delay must take into account the entry latency if that period
+ has not expired. The abortable nature of the PREP period can be ignored
+ if it cannot be relied upon (e.g. the PREP deadline may occur much sooner than
+ the worst case since it depends on the CPU operating conditions, i.e. caches
+ state).
+
+ An OS has to reliably probe the wakeup-latency since some devices can enforce
+ latency constraint guarantees to work properly, so the OS has to detect the
+ worst case wake-up latency it can incur if a CPU is allowed to enter an
+ idle state, and possibly to prevent that to guarantee reliable device
+ functioning.
+
+ The min-residency time parameter deserves further explanation since it is
+ expressed in time units but must factor in energy consumption coefficients.
+
+ The energy consumption of a cpu when it enters a power state can be roughly
+ characterised by the following graph:
+
+ |
+ |
+ |
+ e |
+ n | /---
+ e | /------
+ r | /------
+ g | /-----
+ y | /------
+ | ----
+ | /|
+ | / |
+ | / |
+ | / |
+ | / |
+ | / |
+ |/ |
+ -----|-------+----------------------------------
+ 0| 1 time(ms)
+
+ Graph 1: Energy vs time example
+
+ The graph is split in two parts delimited by time 1ms on the X-axis.
+ The graph curve with X-axis values = { x | 0 < x < 1ms } has a steep slope
+ and denotes the energy costs incurred while entering and leaving the idle
+ state.
+ The graph curve in the area delimited by X-axis values = {x | x > 1ms } has
+ shallower slope and essentially represents the energy consumption of the idle
+ state.
+
+ min-residency is defined for a given idle state as the minimum expected
+ residency time for a state (inclusive of preparation and entry) after
+ which choosing that state become the most energy efficient option. A good
+ way to visualise this, is by taking the same graph above and comparing some
+ states energy consumptions plots.
+
+ For sake of simplicity, let's consider a system with two idle states IDLE1,
+ and IDLE2:
+
+ |
+ |
+ |
+ | /-- IDLE1
+ e | /---
+ n | /----
+ e | /---
+ r | /-----/--------- IDLE2
+ g | /-------/---------
+ y | ------------ /---|
+ | / /---- |
+ | / /--- |
+ | / /---- |
+ | / /--- |
+ | --- |
+ | / |
+ | / |
+ |/ | time
+ ---/----------------------------+------------------------
+ |IDLE1-energy < IDLE2-energy | IDLE2-energy < IDLE1-energy
+ |
+ IDLE2-min-residency
+
+ Graph 2: idle states min-residency example
+
+ In graph 2 above, that takes into account idle states entry/exit energy
+ costs, it is clear that if the idle state residency time (i.e. time till next
+ wake-up IRQ) is less than IDLE2-min-residency, IDLE1 is the better idle state
+ choice energywise.
+
+ This is mainly down to the fact that IDLE1 entry/exit energy costs are lower
+ than IDLE2.
+
+ However, the lower power consumption (i.e. shallower energy curve slope) of
+ idle state IDLE2 implies that after a suitable time, IDLE2 becomes more energy
+ efficient.
+
+ The time at which IDLE2 becomes more energy efficient than IDLE1 (and other
+ shallower states in a system with multiple idle states) is defined
+ IDLE2-min-residency and corresponds to the time when energy consumption of
+ IDLE1 and IDLE2 states breaks even.
+
+ The definitions provided in this section underpin the idle states
+ properties specification that is the subject of the following sections.
+
+ ===========================================
+ 5 - idle-states node
+ ===========================================
+
+ The processor idle states are defined within the idle-states node, which is
+ a direct child of the cpus node [1] and provides a container where the
+ processor idle states, defined as device tree nodes, are listed.
+
+ On ARM systems, it is a container of processor idle states nodes. If the
+ system does not provide CPU power management capabilities, or the processor
+ just supports idle_standby, an idle-states node is not required.
+
+ ===========================================
+ 6 - References
+ ===========================================
+
+ [1] ARM Linux Kernel documentation - CPUs bindings
+ Documentation/devicetree/bindings/arm/cpus.yaml
+
+ [2] ARM Linux Kernel documentation - PSCI bindings
+ Documentation/devicetree/bindings/arm/psci.yaml
+
+ [3] ARM Server Base System Architecture (SBSA)
+ http://infocenter.arm.com/help/index.jsp
+
+ [4] ARM Architecture Reference Manuals
+ http://infocenter.arm.com/help/index.jsp
+
+ [5] ARM Linux Kernel documentation - Booting AArch64 Linux
+ Documentation/arm64/booting.rst
+
+ [6] RISC-V Linux Kernel documentation - CPUs bindings
+ Documentation/devicetree/bindings/riscv/cpus.yaml
+
+ [7] RISC-V Supervisor Binary Interface (SBI)
+ http://github.com/riscv/riscv-sbi-doc/riscv-sbi.adoc
+
+properties:
+ $nodename:
+ const: idle-states
+
+ entry-method:
+ description: |
+ Usage and definition depend on ARM architecture version.
+
+ On ARM v8 64-bit this property is required.
+ On ARM 32-bit systems this property is optional
+
+ This assumes that the "enable-method" property is set to "psci" in the cpu
+ node[5] that is responsible for setting up CPU idle management in the OS
+ implementation.
+ const: psci
+
+patternProperties:
+ "^(cpu|cluster)-":
+ type: object
+ description: |
+ Each state node represents an idle state description and must be defined
+ as follows.
+
+ The idle state entered by executing the wfi instruction (idle_standby
+ SBSA,[3][4]) is considered standard on all ARM and RISC-V platforms and
+ therefore must not be listed.
+
+ In addition to the properties listed above, a state node may require
+ additional properties specific to the entry-method defined in the
+ idle-states node. Please refer to the entry-method bindings
+ documentation for properties definitions.
+
+ properties:
+ compatible:
+ enum:
+ - arm,idle-state
+ - riscv,idle-state
+
+ arm,psci-suspend-param:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ power_state parameter to pass to the ARM PSCI suspend call.
+
+ Device tree nodes that require usage of PSCI CPU_SUSPEND function
+ (i.e. idle states node with entry-method property is set to "psci")
+ must specify this property.
+
+ riscv,sbi-suspend-param:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ suspend_type parameter to pass to the RISC-V SBI HSM suspend call.
+
+ This property is required in idle state nodes of device tree meant
+ for RISC-V systems. For more details on the suspend_type parameter
+ refer the SBI specifiation v0.3 (or higher) [7].
+
+ local-timer-stop:
+ description:
+ If present the CPU local timer control logic is
+ lost on state entry, otherwise it is retained.
+ type: boolean
+
+ entry-latency-us:
+ description:
+ Worst case latency in microseconds required to enter the idle state.
+
+ exit-latency-us:
+ description:
+ Worst case latency in microseconds required to exit the idle state.
+ The exit-latency-us duration may be guaranteed only after
+ entry-latency-us has passed.
+
+ min-residency-us:
+ description:
+ Minimum residency duration in microseconds, inclusive of preparation
+ and entry, for this idle state to be considered worthwhile energy wise
+ (refer to section 2 of this document for a complete description).
+
+ wakeup-latency-us:
+ description: |
+ Maximum delay between the signaling of a wake-up event and the CPU
+ being able to execute normal code again. If omitted, this is assumed
+ to be equal to:
+
+ entry-latency-us + exit-latency-us
+
+ It is important to supply this value on systems where the duration of
+ PREP phase (see diagram 1, section 2) is non-neglibigle. In such
+ systems entry-latency-us + exit-latency-us will exceed
+ wakeup-latency-us by this duration.
+
+ idle-state-name:
+ $ref: /schemas/types.yaml#definitions/string
+ description:
+ A string used as a descriptive name for the idle state.
+
+ additionalProperties: false
+
+ required:
+ - compatible
+ - entry-latency-us
+ - exit-latency-us
+ - min-residency-us
+
+additionalProperties: false
+
+examples:
+ - |
+
+ cpus {
+ #size-cells = <0>;
+ #address-cells = <2>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x0>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@1 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x1>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x100>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x101>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@10000 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x10000>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@10001 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x10001>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@10100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x10100>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@10101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a57";
+ reg = <0x0 0x10101>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_0_0 &CPU_SLEEP_0_0
+ &CLUSTER_RETENTION_0 &CLUSTER_SLEEP_0>;
+ };
+
+ cpu@100000000 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x0>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100000001 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x1>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100000100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x100>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100000101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x101>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100010000 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x10000>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100010001 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x10001>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100010100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x10100>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ cpu@100010101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0x1 0x10101>;
+ enable-method = "psci";
+ cpu-idle-states = <&CPU_RETENTION_1_0 &CPU_SLEEP_1_0
+ &CLUSTER_RETENTION_1 &CLUSTER_SLEEP_1>;
+ };
+
+ idle-states {
+ entry-method = "psci";
+
+ CPU_RETENTION_0_0: cpu-retention-0-0 {
+ compatible = "arm,idle-state";
+ arm,psci-suspend-param = <0x0010000>;
+ entry-latency-us = <20>;
+ exit-latency-us = <40>;
+ min-residency-us = <80>;
+ };
+
+ CLUSTER_RETENTION_0: cluster-retention-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x1010000>;
+ entry-latency-us = <50>;
+ exit-latency-us = <100>;
+ min-residency-us = <250>;
+ wakeup-latency-us = <130>;
+ };
+
+ CPU_SLEEP_0_0: cpu-sleep-0-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x0010000>;
+ entry-latency-us = <250>;
+ exit-latency-us = <500>;
+ min-residency-us = <950>;
+ };
+
+ CLUSTER_SLEEP_0: cluster-sleep-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x1010000>;
+ entry-latency-us = <600>;
+ exit-latency-us = <1100>;
+ min-residency-us = <2700>;
+ wakeup-latency-us = <1500>;
+ };
+
+ CPU_RETENTION_1_0: cpu-retention-1-0 {
+ compatible = "arm,idle-state";
+ arm,psci-suspend-param = <0x0010000>;
+ entry-latency-us = <20>;
+ exit-latency-us = <40>;
+ min-residency-us = <90>;
+ };
+
+ CLUSTER_RETENTION_1: cluster-retention-1 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x1010000>;
+ entry-latency-us = <50>;
+ exit-latency-us = <100>;
+ min-residency-us = <270>;
+ wakeup-latency-us = <100>;
+ };
+
+ CPU_SLEEP_1_0: cpu-sleep-1-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x0010000>;
+ entry-latency-us = <70>;
+ exit-latency-us = <100>;
+ min-residency-us = <300>;
+ wakeup-latency-us = <150>;
+ };
+
+ CLUSTER_SLEEP_1: cluster-sleep-1 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ arm,psci-suspend-param = <0x1010000>;
+ entry-latency-us = <500>;
+ exit-latency-us = <1200>;
+ min-residency-us = <3500>;
+ wakeup-latency-us = <1300>;
+ };
+ };
+ };
+
+ - |
+ // Example 2 (ARM 32-bit, 8-cpu system, two clusters):
+
+ cpus {
+ #size-cells = <0>;
+ #address-cells = <1>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x0>;
+ cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
+ };
+
+ cpu@1 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x1>;
+ cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
+ };
+
+ cpu@2 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x2>;
+ cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
+ };
+
+ cpu@3 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a15";
+ reg = <0x3>;
+ cpu-idle-states = <&cpu_sleep_0_0 &cluster_sleep_0>;
+ };
+
+ cpu@100 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x100>;
+ cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
+ };
+
+ cpu@101 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x101>;
+ cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
+ };
+
+ cpu@102 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x102>;
+ cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
+ };
+
+ cpu@103 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0x103>;
+ cpu-idle-states = <&cpu_sleep_1_0 &cluster_sleep_1>;
+ };
+
+ idle-states {
+ cpu_sleep_0_0: cpu-sleep-0-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ entry-latency-us = <200>;
+ exit-latency-us = <100>;
+ min-residency-us = <400>;
+ wakeup-latency-us = <250>;
+ };
+
+ cluster_sleep_0: cluster-sleep-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ entry-latency-us = <500>;
+ exit-latency-us = <1500>;
+ min-residency-us = <2500>;
+ wakeup-latency-us = <1700>;
+ };
+
+ cpu_sleep_1_0: cpu-sleep-1-0 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ entry-latency-us = <300>;
+ exit-latency-us = <500>;
+ min-residency-us = <900>;
+ wakeup-latency-us = <600>;
+ };
+
+ cluster_sleep_1: cluster-sleep-1 {
+ compatible = "arm,idle-state";
+ local-timer-stop;
+ entry-latency-us = <800>;
+ exit-latency-us = <2000>;
+ min-residency-us = <6500>;
+ wakeup-latency-us = <2300>;
+ };
+ };
+ };
+
+ - |
+ // Example 3 (RISC-V 64-bit, 4-cpu systems, two clusters):
+
+ cpus {
+ #size-cells = <0>;
+ #address-cells = <1>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "riscv";
+ reg = <0x0>;
+ riscv,isa = "rv64imafdc";
+ mmu-type = "riscv,sv48";
+ cpu-idle-states = <&CPU_RET_0_0 &CPU_NONRET_0_0
+ &CLUSTER_RET_0 &CLUSTER_NONRET_0>;
+
+ cpu_intc0: interrupt-controller {
+ #interrupt-cells = <1>;
+ compatible = "riscv,cpu-intc";
+ interrupt-controller;
+ };
+ };
+
+ cpu@1 {
+ device_type = "cpu";
+ compatible = "riscv";
+ reg = <0x1>;
+ riscv,isa = "rv64imafdc";
+ mmu-type = "riscv,sv48";
+ cpu-idle-states = <&CPU_RET_0_0 &CPU_NONRET_0_0
+ &CLUSTER_RET_0 &CLUSTER_NONRET_0>;
+
+ cpu_intc1: interrupt-controller {
+ #interrupt-cells = <1>;
+ compatible = "riscv,cpu-intc";
+ interrupt-controller;
+ };
+ };
+
+ cpu@10 {
+ device_type = "cpu";
+ compatible = "riscv";
+ reg = <0x10>;
+ riscv,isa = "rv64imafdc";
+ mmu-type = "riscv,sv48";
+ cpu-idle-states = <&CPU_RET_1_0 &CPU_NONRET_1_0
+ &CLUSTER_RET_1 &CLUSTER_NONRET_1>;
+
+ cpu_intc10: interrupt-controller {
+ #interrupt-cells = <1>;
+ compatible = "riscv,cpu-intc";
+ interrupt-controller;
+ };
+ };
+
+ cpu@11 {
+ device_type = "cpu";
+ compatible = "riscv";
+ reg = <0x11>;
+ riscv,isa = "rv64imafdc";
+ mmu-type = "riscv,sv48";
+ cpu-idle-states = <&CPU_RET_1_0 &CPU_NONRET_1_0
+ &CLUSTER_RET_1 &CLUSTER_NONRET_1>;
+
+ cpu_intc11: interrupt-controller {
+ #interrupt-cells = <1>;
+ compatible = "riscv,cpu-intc";
+ interrupt-controller;
+ };
+ };
+
+ idle-states {
+ CPU_RET_0_0: cpu-retentive-0-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x10000000>;
+ entry-latency-us = <20>;
+ exit-latency-us = <40>;
+ min-residency-us = <80>;
+ };
+
+ CPU_NONRET_0_0: cpu-nonretentive-0-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x90000000>;
+ entry-latency-us = <250>;
+ exit-latency-us = <500>;
+ min-residency-us = <950>;
+ };
+
+ CLUSTER_RET_0: cluster-retentive-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x11000000>;
+ local-timer-stop;
+ entry-latency-us = <50>;
+ exit-latency-us = <100>;
+ min-residency-us = <250>;
+ wakeup-latency-us = <130>;
+ };
+
+ CLUSTER_NONRET_0: cluster-nonretentive-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x91000000>;
+ local-timer-stop;
+ entry-latency-us = <600>;
+ exit-latency-us = <1100>;
+ min-residency-us = <2700>;
+ wakeup-latency-us = <1500>;
+ };
+
+ CPU_RET_1_0: cpu-retentive-1-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x10000010>;
+ entry-latency-us = <20>;
+ exit-latency-us = <40>;
+ min-residency-us = <80>;
+ };
+
+ CPU_NONRET_1_0: cpu-nonretentive-1-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x90000010>;
+ entry-latency-us = <250>;
+ exit-latency-us = <500>;
+ min-residency-us = <950>;
+ };
+
+ CLUSTER_RET_1: cluster-retentive-1 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x11000010>;
+ local-timer-stop;
+ entry-latency-us = <50>;
+ exit-latency-us = <100>;
+ min-residency-us = <250>;
+ wakeup-latency-us = <130>;
+ };
+
+ CLUSTER_NONRET_1: cluster-nonretentive-1 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x91000010>;
+ local-timer-stop;
+ entry-latency-us = <600>;
+ exit-latency-us = <1100>;
+ min-residency-us = <2700>;
+ wakeup-latency-us = <1500>;
+ };
+ };
+ };
+
+...
- compatible
- interrupt-controller
+ cpu-idle-states:
+ $ref: '/schemas/types.yaml#/definitions/phandle-array'
+ description: |
+ List of phandles to idle state nodes supported
+ by this hart (see ./idle-states.yaml).
+
required:
- riscv,isa
- interrupt-controller
F: drivers/cpuidle/cpuidle-psci.h
F: drivers/cpuidle/cpuidle-psci-domain.c
+CPUIDLE DRIVER - DT IDLE PM DOMAIN
+M: Ulf Hansson <ulf.hansson@linaro.org>
+L: linux-pm@vger.kernel.org
+S: Supported
+F: drivers/cpuidle/dt_idle_genpd.c
+F: drivers/cpuidle/dt_idle_genpd.h
+
+CPUIDLE DRIVER - RISC-V SBI
+M: Anup Patel <anup@brainfault.org>
+L: linux-pm@vger.kernel.org
+L: linux-riscv@lists.infradead.org
+S: Maintained
+F: drivers/cpuidle/cpuidle-riscv-sbi.c
+
CRAMFS FILESYSTEM
M: Nicolas Pitre <nico@fluxnic.net>
S: Maintained
select CLONE_BACKWARDS
select CLINT_TIMER if !MMU
select COMMON_CLK
- select CPU_PM if (SUSPEND || CPU_IDLE)
+ select CPU_PM if CPU_IDLE
select COMPAT_BINFMT_ELF if BINFMT_ELF && COMPAT
select EDAC_SUPPORT
select DMA_DIRECT_REMAP
endmenu
+menu "CPU Power Management"
+
+source "drivers/cpuidle/Kconfig"
+
+endmenu
+
source "arch/riscv/kvm/Kconfig"
source "drivers/firmware/Kconfig"
select GOLDFISH
select RTC_DRV_GOLDFISH if RTC_CLASS
select SIFIVE_PLIC
+ select PM_GENERIC_DOMAINS if PM
+ select PM_GENERIC_DOMAINS_OF if PM && OF
+ select RISCV_SBI_CPUIDLE if CPU_IDLE
help
This enables support for QEMU Virt Machine.
dtb-$(CONFIG_SOC_THEAD) += light-lpi4a-cluster.dtb light-lpi4a-cluster-16gb.dtb
dtb-$(CONFIG_SOC_THEAD) += light-a-ref.dtb light-a-ref-dsi0.dtb light-a-ref-dsi0-hdmi.dtb
dtb-$(CONFIG_SOC_THEAD) += light-b-ref.dtb
-dtb-$(CONFIG_SOC_THEAD) += light-a-val-crash.dtb light-b-product-crash.dtb light-ant-ref-crash.dtb light-ant-discrete-crash.dtb
+dtb-$(CONFIG_SOC_THEAD) += light-a-val-crash.dtb light-b-product-crash.dtb light-ant-ref-crash.dtb light-ant-discrete-crash.dtb light-lpi4a-crash.dtb light-lpi4a-camera-tuning.dtb light-lpi4a-hdmi.dtb
dtb-$(CONFIG_SOC_THEAD) += light-b-power.dtb
dtb-$(CONFIG_SOC_THEAD) += light-a-val-android.dtb
pagesize = <32>;
};
- codec: wm8960@1a {
- #sound-dai-cells = <0>;
- compatible = "wlf,wm8960";
- reg = <0x1a>;
- wlf,shared-lrclk;
- wlf,hp-cfg = <3 2 3>;
- wlf,gpio-cfg = <1 3>;
- };
-
touch@5d {
#gpio-cells = <2>;
compatible = "goodix,gt911";
pagesize = <32>;
};
- codec: wm8960@1a {
- #sound-dai-cells = <0>;
- compatible = "wlf,wm8960";
- reg = <0x1a>;
- wlf,shared-lrclk;
- wlf,hp-cfg = <3 2 3>;
- wlf,gpio-cfg = <1 3>;
- };
-
touch@5d {
#gpio-cells = <2>;
compatible = "goodix,gt911";
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
pagesize = <32>;
};
- codec: wm8960@1a {
- #sound-dai-cells = <0>;
- compatible = "wlf,wm8960";
- reg = <0x1a>;
- wlf,shared-lrclk;
- wlf,hp-cfg = <3 2 3>;
- wlf,gpio-cfg = <1 3>;
- };
-
touch@5d {
#gpio-cells = <2>;
compatible = "goodix,gt911";
&lightsound {
status = "okay";
-
- simple-audio-card,widgets =
- "Microphone", "Mic Jack",
- "Speaker", "Speaker",
- "Headphone", "Headphone Jack";
- simple-audio-card,routing =
- "Headphone Jack", "HP_L",
- "Headphone Jack", "HP_R",
- "Speaker", "SPK_LP",
- "Speaker", "SPK_LN",
- "Speaker", "SPK_RP",
- "Speaker", "SPK_RN",
- "Mic Jack","MICB",
- "LINPUT1", "Mic Jack",
- "LINPUT3", "Mic Jack";
-
- simple-audio-card,dai-link@0 { /* I2S - CODEC */
- reg = <0>;
- format = "i2s";
- cpu {
- sound-dai = <&light_i2s 0>;
- };
- codec {
- sound-dai = <&codec>;
- };
- };
- simple-audio-card,dai-link@1 { /* I2S - HDMI */
+
+ simple-audio-card,dai-link@0 { /* I2S - AUDIO SYS CODEC 8156*/
+ reg = <0>;
+ format = "i2s";
+ cpu {
+ sound-dai = <&i2s0 0>;
+ };
+ codec {
+ sound-dai = <&es8156_audio_codec>;
+ };
+ };
+
+ simple-audio-card,dai-link@1 { /* I2S - AUDIO SYS CODEC 7210*/
reg = <1>;
format = "i2s";
cpu {
- sound-dai = <&light_i2s 1>;
+ sound-dai = <&i2s_8ch_sd2 2>;
};
codec {
- sound-dai = <&dummy_codec>;
+ sound-dai = <&es7210_audio_codec_adc0>;
};
- };
+ };
+
+ simple-audio-card,dai-link@2 { /* I2S - HDMI */
+ reg = <2>;
+ format = "i2s";
+ cpu {
+ sound-dai = <&light_i2s 1>;
+ };
+ codec {
+ sound-dai = <&dummy_codec>;
+ };
+ };
};
&light_i2s {
+ status = "okay";
+};
+
+&i2s0 {
+ status = "okay";
+};
+
+&i2s_8ch_sd2 {
+ status = "okay";
+};
+
+&es7210_audio_codec_adc0 {
status = "okay";
};
status = "disabled";
};
+&eip_28 {
+ status = "disabled";
+};
chosen {
bootargs = "console=ttyS0,115200 crashkernel=256M-:128M earlycon clk_ignore_unused sram=0xffe0000000,0x180000";
- stdout-path = "serial0:115200n8";
+ stdout-path = "serial0";
};
leds {
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
status = "disabled";
key-volumedown {
label = "Volume Down Key";
+ wakeup-source;
linux,code = <KEY_1>;
debounce-interval = <2>;
gpios = <&ao_gpio_porta 4 GPIO_ACTIVE_LOW>;
};
key-volumeup {
label = "Volume Up Key";
+ wakeup-source;
linux,code = <KEY_2>;
debounce-interval = <2>;
gpios = <&ao_gpio_porta 5 GPIO_ACTIVE_LOW>;
&i2c0 {
clock-frequency = <400000>;
status = "okay";
-
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c0>;
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
pagesize = <32>;
};
- codec: wm8960@1a {
- #sound-dai-cells = <0>;
- compatible = "wlf,wm8960";
- reg = <0x1a>;
- wlf,shared-lrclk;
- wlf,hp-cfg = <3 2 3>;
- wlf,gpio-cfg = <1 3>;
- };
-
touch@5d {
#gpio-cells = <2>;
compatible = "goodix,gt911";
&i2c1 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c1>;
touch1@5d {
#gpio-cells = <2>;
compatible = "goodix,gt911";
num-cs = <1>;
cs-gpios = <&gpio2_porta 15 0>; // GPIO_ACTIVE_HIGH: 0
rx-sample-delay-ns = <10>;
- status = "okay";
spi_norflash@0 {
+ status = "okay";
#address-cells = <1>;
#size-cells = <1>;
compatible = "winbond,w25q64jwm", "jedec,spi-nor";
};
spidev@1 {
+ status = "disable";
compatible = "spidev";
#address-cells = <0x1>;
#size-cells = <0x1>;
&uart0 {
clock-frequency = <100000000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart0>;
+};
+
+&uart1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart1>;
+};
+
+&uart3 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart3>;
+};
+
+&uart4 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart4>;
};
&qspi0 {
num-cs = <1>;
cs-gpios = <&gpio2_porta 3 0>;
- rx-sample-dly = <4>;
- status = "disabled";
+ rx-sample-dly = <5>;
+ status = "okay";
spi-flash@0 {
#address-cells = <1>;
&qspi1 {
num-cs = <1>;
cs-gpios = <&gpio0_porta 1 0>;
- status = "disabled";
+ rx-sample-dly = <5>;
+ status = "okay";
spi-flash@0 {
#address-cells = <1>;
rx-clk-delay = <0x00>; /* for RGMII */
tx-clk-delay = <0x00>; /* for RGMII */
phy-handle = <&phy_88E1111_0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_gmac0>;
status = "okay";
mdio0 {
rx-clk-delay = <0x00>; /* for RGMII */
tx-clk-delay = <0x00>; /* for RGMII */
phy-handle = <&phy_88E1111_1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_gmac1>;
status = "okay";
};
bus-width = <4>;
pull_up;
wprtn_ignore;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdio0>;
status = "okay";
};
*/
pinctrl_uart0: uart0grp {
thead,pins = <
- FM_UART0_TXD 0x0 0x72
- FM_UART0_RXD 0x0 0x72
- >;
- };
-
- pinctrl_spi0: spi0grp {
- thead,pins = <
- FM_SPI_CSN 0x3 0x20a
- FM_SPI_SCLK 0x0 0x20a
- FM_SPI_MISO 0x0 0x23a
- FM_SPI_MOSI 0x0 0x23a
+ FM_UART0_TXD 0x0 0x234
+ FM_UART0_RXD 0x0 0x234
>;
};
>;
};
+ pinctrl_i2c2: i2c2grp {
+ thead,pins = <
+ FM_I2C2_SCL 0x0 0x204
+ FM_I2C2_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_i2c3: i2c3grp {
+ thead,pins = <
+ FM_I2C3_SCL 0x0 0x204
+ FM_I2C3_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_spi0: spi0grp {
+ thead,pins = <
+ FM_SPI_CSN 0x3 0x20a
+ FM_SPI_SCLK 0x0 0x20a
+ FM_SPI_MISO 0x0 0x23a
+ FM_SPI_MOSI 0x0 0x23a
+ >;
+ };
+
+ pinctrl_gmac1: gmac1grp {
+ thead,pins = <
+ FM_GPIO2_18 0x1 0x20f /* GMAC1_TX_CLK */
+ FM_GPIO2_19 0x1 0x20f /* GMAC1_RX_CLK */
+ FM_GPIO2_20 0x1 0x20f /* GMAC1_TXEN */
+ FM_GPIO2_21 0x1 0x20f /* GMAC1_TXD0 */
+ FM_GPIO2_22 0x1 0x20f /* GMAC1_TXD1 */
+ FM_GPIO2_23 0x1 0x20f /* GMAC1_TXD2 */
+ FM_GPIO2_24 0x1 0x20f /* GMAC1_TXD3 */
+ FM_GPIO2_25 0x1 0x20f /* GMAC1_RXDV */
+ FM_GPIO2_30 0x1 0x20f /* GMAC1_RXD0 */
+ FM_GPIO2_31 0x1 0x20f /* GMAC1_RXD1 */
+ FM_GPIO3_0 0x1 0x20f /* GMAC1_RXD2 */
+ FM_GPIO3_1 0x1 0x20f /* GMAC1_RXD3 */
+ >;
+ };
+
+ pinctrl_sdio0: sdio0grp {
+ thead,pins = <
+ FM_SDIO0_DETN 0x0 0x208
+ >;
+ };
+
pinctrl_pwm: pwmgrp {
thead,pins = <
FM_GPIO3_2 0x1 0x208 /* pwm0 */
FM_GPIO3_3 0x1 0x208 /* pwm1 */
>;
};
- };
-};
-&padctrl1_apsys { /* left-pinctrl */
- light-evb-padctrl1 {
- /*
- * Pin Configuration Node:
- * Format: <pin_id mux_node config>
- */
- pinctrl_uart3: uart3grp {
+ pinctrl_hdmi: hdmigrp {
thead,pins = <
- FM_UART3_TXD 0x0 0x72
- FM_UART3_RXD 0x0 0x72
+ FM_HDMI_SCL 0x0 0x208
+ FM_HDMI_SDA 0x0 0x208
+ FM_HDMI_CEC 0x0 0x208
>;
};
- pinctrl_uart4: uart4grp {
+ pinctrl_gmac0: gmac0grp {
thead,pins = <
- FM_UART4_TXD 0x0 0x72
- FM_UART4_RXD 0x0 0x72
- FM_UART4_CTSN 0x0 0x72
- FM_UART4_RTSN 0x0 0x72
+ FM_GMAC0_TX_CLK 0x0 0x20f /* GMAC0_TX_CLK */
+ FM_GMAC0_RX_CLK 0x0 0x20f /* GMAC0_RX_CLK */
+ FM_GMAC0_TXEN 0x0 0x20f /* GMAC0_TXEN */
+ FM_GMAC0_TXD0 0x0 0x20f /* GMAC0_TXD0 */
+ FM_GMAC0_TXD1 0x0 0x20f /* GMAC0_TXD1 */
+ FM_GMAC0_TXD2 0x0 0x20f /* GMAC0_TXD2 */
+ FM_GMAC0_TXD3 0x0 0x20f /* GMAC0_TXD3 */
+ FM_GMAC0_RXDV 0x0 0x20f /* GMAC0_RXDV */
+ FM_GMAC0_RXD0 0x0 0x20f /* GMAC0_RXD0 */
+ FM_GMAC0_RXD1 0x0 0x20f /* GMAC0_RXD1 */
+ FM_GMAC0_RXD2 0x0 0x20f /* GMAC0_RXD2 */
+ FM_GMAC0_RXD3 0x0 0x20f /* GMAC0_RXD3 */
+ FM_GMAC0_MDC 0x0 0x208 /* GMAC0_MDC */
+ FM_GMAC0_MDIO 0x0 0x208 /* GMAC0_MDIO */
+ FM_GMAC0_COL 0x3 0x232 /* PHY0_nRST */
+ FM_GMAC0_CRS 0x3 0x232 /* PHY0_nINT */
>;
};
+ };
+};
+&padctrl1_apsys { /* left-pinctrl */
+ light-evb-padctrl1 {
+ /*
+ * Pin Configuration Node:
+ * Format: <pin_id mux_node config>
+ */
pinctrl_qspi1: qspi1grp {
thead,pins = <
FM_QSPI1_SCLK 0x0 0x20a
>;
};
-
pinctrl_iso7816: iso7816grp {
thead,pins = <
FM_QSPI1_SCLK 0x1 0x208
>;
};
+ pinctrl_i2c0: i2c0grp {
+ thead,pins = <
+ FM_I2C0_SCL 0x0 0x204
+ FM_I2C0_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_i2c1: i2c1grp {
+ thead,pins = <
+ FM_I2C1_SCL 0x0 0x204
+ FM_I2C1_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_uart1: uart1grp {
+ thead,pins = <
+ FM_UART1_TXD 0x0 0x234
+ FM_UART1_RXD 0x0 0x234
+ >;
+ };
+
+ pinctrl_uart4: uart4grp {
+ thead,pins = <
+ FM_UART4_TXD 0x0 0x208
+ FM_UART4_RXD 0x0 0x208
+ FM_UART4_CTSN 0x0 0x208
+ FM_UART4_RTSN 0x0 0x208
+ >;
+ };
+
+ pinctrl_uart3: uart3grp {
+ thead,pins = <
+ FM_UART3_TXD 0x1 0x202
+ FM_UART3_RXD 0x1 0x202
+ FM_GPIO0_20 0x2 0x202 /* UART3_IR_OUT */
+ FM_GPIO0_21 0x2 0x202 /* UART3_IR_IN */
+ >;
+ };
+
+ pinctrl_i2c4: i2c4grp {
+ thead,pins = <
+ FM_GPIO0_18 0x1 0x204 /* I2C4_SCL */
+ FM_GPIO0_19 0x1 0x204 /* I2C4_SDA */
+ >;
+ };
};
};
&i2c2 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c2>;
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
&i2c3 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c3>;
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
&i2c4 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c4>;
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
DOVDD18_RGB-supply = <&soc_dovdd18_rgb_reg>;
DVDD12_RGB-supply = <&soc_dvdd12_rgb_reg>;
AVDD28_RGB-supply = <&soc_avdd28_rgb_reg>;
- i2c_reg_width = /bits/ 8 <2>;
+ i2c_reg_width = /bits/ 8 <2>;
i2c_data_width = /bits/ 8 <1>;
i2c_addr = /bits/ 8 <0x1a>;
i2c_bus = /bits/ 8 <3>;
};
&video0{
+ status = "okay";
vi_mem_pool_region = <2>; // vi_mem: framebuffer, region[2]
channel0 {
sensor0 {
&video1{
+ status = "okay";
vi_mem_pool_region = <2>; // vi_mem: framebuffer, region[2]
channel0 {
sensor0 {
};
&video2{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video3{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video4{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video5{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video6{
+ status = "okay";
vi_mem_pool_region = <1>; // vi_mem: framebuffer, region[1]
channel0 {
sensor0 {
};
&video7{
+ status = "okay";
channel0 {
sensor0 {
subdev_name = "vivcam";
&video8{
+ status = "okay";
vi_mem_pool_region = <1>; // vi_mem: framebuffer, region[1]
channel0 {
sensor0 {
};
&video9{
+ status = "okay";
channel0 {
sensor0 {
subdev_name = "vivcam";
&video10{
+ status = "okay";
channel0 {
sensor0 {
subdev_name = "vivcam";
};
&video11{
+ status = "okay";
channel0 {
channel_id = <0>;
status = "okay";
};
&video12{ // TUNINGTOOL
+ status = "okay";
channel0 { // CSI2
sensor0 {
subdev_name = "vivcam";
};
};
+&video15{
+ status = "okay";
+ vi_mem_pool_region = <0>;
+ channel0 {
+ channel_id = <0>;
+ status = "okay";
+ sensor0 {
+ subdev_name = "vivcam";
+ idx = <0>; //<0>=vivcam0 :2310
+ csi_idx = <0>; //<0>=CSI2
+ flash_led_idx = <0>;
+ mode_idx = <1>;
+ path_type = "SENSOR_1920X1088_26FPS_RAW12_LINER";
+ };
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <7>; //imx334
+ csi_idx = <0>; //<0>=CSI2
+ mode_idx = <0>;
+ path_type = "SENSOR_3840x2180_RAW12_LINER";
+ };
+ dma {
+ subdev_name = "vipre";
+ idx = <0>;
+ path_type = "VIPRE_CSI0_DDR";
+ };
+ };
+};
+
&trng {
status = "disabled";
};
>;
};
};
+
+&hdmi_tx {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hdmi>;
+};
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
&hdmi_tx {
status = "okay";
+ max_width = /bits/ 16 <1280>;
+ max_height = /bits/ 16 <720>;
+
port@0 {
/* input */
hdmi_tx_in: endpoint {
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
pinctrl-names = "default";
key-volumedown {
label = "Volume Down Key";
+ wakeup-source;
linux,code = <KEY_VOLUMEDOWN>;
debounce-interval = <1>;
gpios = <&ao_gpio_porta 11 0x1>;
};
key-volumeup {
label = "Volume Up Key";
+ wakeup-source;
linux,code = <KEY_VOLUMEUP>;
debounce-interval = <1>;
gpios = <&ao_gpio_porta 10 0x1>;
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
ref-clock-frequency = <24000000>;
keep_wifi_power_on;
pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_wifi>;
wifi_chip_type = "rtl8723ds";
WIFI,poweren_gpio = <&gpio2_porta 29 0>;
WIFI,reset_n = <&gpio2_porta 24 0>;
wcn_bt: wireless-bluetooth {
compatible = "bluetooth-platdata";
- pinctrl-names = "default", "rts_gpio";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_bt>;
BT,power_gpio = <&gpio2_porta 25 0>;
status = "okay";
};
pinctrl-names = "default";
key-volumedown {
label = "Volume Down Key";
+ wakeup-source;
linux,code = <KEY_VOLUMEDOWN>;
debounce-interval = <1>;
gpios = <&ao_gpio_porta 11 0x1>;
};
key-volumeup {
label = "Volume Up Key";
+ wakeup-source;
linux,code = <KEY_VOLUMEUP>;
debounce-interval = <1>;
gpios = <&ao_gpio_porta 10 0x1>;
&uart0 {
clock-frequency = <100000000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart0>;
+};
+
+&uart1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart1>;
+};
+
+&uart3 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart3>;
+};
+
+&uart4 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart4>;
};
&qspi0 {
tx-clk-delay = <0x00>; /* for RGMII */
phy-handle = <&phy_88E1111_0>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_gmac0>;
mdio0 {
#address-cells = <1>;
pull_up;
wprtn_ignore;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdio0>;
};
&sdhci1 {
*/
pinctrl_uart0: uart0grp {
thead,pins = <
- FM_UART0_TXD 0x0 0x72
- FM_UART0_RXD 0x0 0x72
- >;
- };
-
- pinctrl_spi0: spi0grp {
- thead,pins = <
- FM_SPI_CSN 0x3 0x20a
- FM_SPI_SCLK 0x0 0x20a
- FM_SPI_MISO 0x0 0x23a
- FM_SPI_MOSI 0x0 0x23a
+ FM_UART0_TXD 0x0 0x234
+ FM_UART0_RXD 0x0 0x234
>;
};
>;
};
+ pinctrl_i2c2: i2c2grp {
+ thead,pins = <
+ FM_I2C2_SCL 0x0 0x204
+ FM_I2C2_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_i2c3: i2c3grp {
+ thead,pins = <
+ FM_I2C3_SCL 0x0 0x204
+ FM_I2C3_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_spi0: spi0grp {
+ thead,pins = <
+ FM_SPI_CSN 0x3 0x20a
+ FM_SPI_SCLK 0x0 0x20a
+ FM_SPI_MISO 0x0 0x23a
+ FM_SPI_MOSI 0x0 0x23a
+ >;
+ };
+
+ pinctrl_wifi: wifi_grp {
+ thead,pins = <
+ FM_GPIO2_22 0x0 0x202
+ FM_GPIO2_24 0x0 0x202
+ >;
+ };
+
+ pinctrl_bt: bt_grp {
+ thead,pins = <
+ FM_GPIO2_23 0x0 0x202
+ FM_GPIO2_25 0x0 0x202
+ >;
+ };
+
+ pinctrl_sdio0: sdio0grp {
+ thead,pins = <
+ FM_SDIO0_DETN 0x0 0x208
+ >;
+ };
+
pinctrl_pwm: pwmgrp {
thead,pins = <
- FM_GPIO3_2 0x1 0x208 /* pwm0 */
+ FM_GPIO3_2 0x1 0x20f /* pwm0 */
>;
};
- };
-};
-&padctrl1_apsys { /* left-pinctrl */
- light-evb-padctrl1 {
- /*
- * Pin Configuration Node:
- * Format: <pin_id mux_node config>
- */
- pinctrl_uart3: uart3grp {
+ pinctrl_hdmi: hdmigrp {
thead,pins = <
- FM_UART3_TXD 0x0 0x72
- FM_UART3_RXD 0x0 0x72
+ FM_HDMI_SCL 0x0 0x208
+ FM_HDMI_SDA 0x0 0x208
+ FM_HDMI_CEC 0x0 0x208
>;
};
- pinctrl_uart4: uart4grp {
+ pinctrl_gmac0: gmac0grp {
thead,pins = <
- FM_UART4_TXD 0x0 0x72
- FM_UART4_RXD 0x0 0x72
- FM_UART4_CTSN 0x0 0x72
- FM_UART4_RTSN 0x0 0x72
+ FM_GMAC0_TX_CLK 0x0 0x20f /* GMAC0_TX_CLK */
+ FM_GMAC0_RX_CLK 0x0 0x20f /* GMAC0_RX_CLK */
+ FM_GMAC0_TXEN 0x0 0x20f /* GMAC0_TXEN */
+ FM_GMAC0_TXD0 0x0 0x20f /* GMAC0_TXD0 */
+ FM_GMAC0_TXD1 0x0 0x20f /* GMAC0_TXD1 */
+ FM_GMAC0_TXD2 0x0 0x20f /* GMAC0_TXD2 */
+ FM_GMAC0_TXD3 0x0 0x20f /* GMAC0_TXD3 */
+ FM_GMAC0_RXDV 0x0 0x20f /* GMAC0_RXDV */
+ FM_GMAC0_RXD0 0x0 0x20f /* GMAC0_RXD0 */
+ FM_GMAC0_RXD1 0x0 0x20f /* GMAC0_RXD1 */
+ FM_GMAC0_RXD2 0x0 0x20f /* GMAC0_RXD2 */
+ FM_GMAC0_RXD3 0x0 0x20f /* GMAC0_RXD3 */
+ FM_GMAC0_MDC 0x0 0x208 /* GMAC0_MDC */
+ FM_GMAC0_MDIO 0x0 0x208 /* GMAC0_MDIO */
+ FM_GMAC0_COL 0x3 0x232 /* PHY0_nRST */
+ FM_GMAC0_CRS 0x3 0x232 /* PHY0_nINT */
>;
};
+ };
+};
+&padctrl1_apsys { /* left-pinctrl */
+ light-evb-padctrl1 {
+ /*
+ * Pin Configuration Node:
+ * Format: <pin_id mux_node config>
+ */
pinctrl_qspi1: qspi1grp {
thead,pins = <
FM_QSPI1_SCLK 0x0 0x20a
>;
};
-
pinctrl_iso7816: iso7816grp {
thead,pins = <
FM_QSPI1_SCLK 0x1 0x208
>;
};
+ pinctrl_i2c0: i2c0grp {
+ thead,pins = <
+ FM_I2C0_SCL 0x0 0x204
+ FM_I2C0_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_i2c1: i2c1grp {
+ thead,pins = <
+ FM_I2C1_SCL 0x0 0x204
+ FM_I2C1_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_uart1: uart1grp {
+ thead,pins = <
+ FM_UART1_TXD 0x0 0x234
+ FM_UART1_RXD 0x0 0x234
+ >;
+ };
+
+ pinctrl_uart4: uart4grp {
+ thead,pins = <
+ FM_UART4_TXD 0x0 0x208
+ FM_UART4_RXD 0x0 0x208
+ FM_UART4_CTSN 0x0 0x208
+ FM_UART4_RTSN 0x0 0x208
+ >;
+ };
+
+ pinctrl_uart3: uart3grp {
+ thead,pins = <
+ FM_UART3_TXD 0x1 0x202
+ FM_UART3_RXD 0x1 0x202
+ >;
+ };
+
+ pinctrl_i2c4: i2c4grp {
+ thead,pins = <
+ FM_GPIO0_18 0x1 0x204 /* I2C4_SCL */
+ FM_GPIO0_19 0x1 0x204 /* I2C4_SDA */
+ >;
+ };
};
};
&i2c0 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c0>;
};
&i2c1 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c1>;
};
&i2c2 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c2>;
};
&i2c3 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c3>;
};
&i2c4 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c4>;
};
&isp0 {
};
&video0{
+ status = "okay";
vi_mem_pool_region = <2>; // vi_mem: framebuffer, region[2]
channel0 {
sensor0 {
&video1{
+ status = "okay";
vi_mem_pool_region = <2>; // vi_mem: framebuffer, region[2]
channel0 {
sensor0 {
};
&video2{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
mode_idx = <0>;
path_type = "SENSOR_1600x1200_RAW10_LINER";
};
- sensor2 {
- subdev_name = "vivcam";
- idx = <7>; //imx334
- csi_idx = <0>; //<0>=CSI2
- mode_idx = <0>;
- path_type = "SENSOR_3840x2180_RAW12_LINER";
- };
isp {
subdev_name = "isp";
idx = <1>;
path_type = "ISP_MI_PATH_MP";
output {
- max_width = <3840>;
- max_height = <2180>;
+ max_width = <1920>;
+ max_height = <1088>;
bit_per_pixel = <16>;
frame_count = <3>;
};
};
&video3{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video4{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video5{
+ status = "okay";
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
channel0 {
sensor0 {
};
&video6{
+ status = "okay";
vi_mem_pool_region = <1>; // vi_mem: framebuffer, region[1]
channel0 {
sensor0 {
};
&video7{
+ status = "okay";
channel0 {
sensor0 {
subdev_name = "vivcam";
&video8{
+ status = "okay";
vi_mem_pool_region = <1>; // vi_mem: framebuffer, region[1]
channel0 {
sensor0 {
};
&video9{
+ status = "okay";
channel0 {
sensor0 {
subdev_name = "vivcam";
&video10{ // TUNINGTOOL
+ status = "okay";
channel0 {
sensor0 {
subdev_name = "vivcam";
};
&video11{
+ status = "okay";
channel0 {
channel_id = <0>;
status = "okay";
};
&video12{ // TUNINGTOOL
+ status = "okay";
channel0 { // CSI2
sensor0 {
subdev_name = "vivcam";
};
&video14{
+ status = "okay";
vi_mem_pool_region = <2>; // vi_mem: framebuffer, region[0]
status = "okay";
channel0 {
&hdmi_tx {
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hdmi>;
port@0 {
/* input */
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+/dts-v1/;
+
+#include "light-lpi4a.dts"
+
+
+
+&video10{ // TUNINGTOOL
+ status = "okay";
+ channel0 {
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <3>;
+ csi_idx = <0>;
+ mode_idx = <1>; // 0=640 480 1=2592x1944
+ path_type = "SENSOR_2592x1944_LINER";
+ };
+ dma {
+ path_type = "VIPRE_CSI0_ISP0";
+ };
+ };
+};
+
+&video15{
+ status = "okay";
+ vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
+ channel0 {
+ status = "okay";
+ sensor0 {
+ subdev_name = "vivcam";
+ idx = <0>;
+ csi_idx = <0>;
+ mode_idx = <0>;
+ path_type = "SENSOR_VGA_RAW12_LINER";
+ };
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <3>;
+ csi_idx = <0>;
+ mode_idx = <1>;
+ path_type = "SENSOR_2592x1944_LINER";
+ };
+ dma {
+ subdev_name = "vipre";
+ idx = <0>;
+ path_type = "VIPRE_CSI0_DDR";
+ };
+ };
+};
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Alibaba Group Holding Limited.
+ */
+
+#include "light-crash.dts"
+
+&aon {
+ aon_reg_dialog: light-dialog-reg {
+ compatible = "thead,light-dialog-pmic-ant";
+ status = "okay";
+
+ dvdd_cpu_reg: appcpu_dvdd {
+ regulator-name = "appcpu_dvdd";
+ regulator-min-microvolt = <300000>;
+ regulator-max-microvolt = <1570000>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ dvddm_cpu_reg: appcpu_dvddm {
+ regulator-name = "appcpu_dvddm";
+ regulator-min-microvolt = <300000>;
+ regulator-max-microvolt = <1570000>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+ };
+};
+
+&cpus {
+ c910_0: cpu@0 {
+ operating-points = <
+ /* kHz uV */
+ 300000 600000
+ 800000 700000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ light,dvddm-operating-points = <
+ /* kHz uV */
+ 300000 800000
+ 800000 800000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ };
+ c910_1: cpu@1 {
+ operating-points = <
+ /* kHz uV */
+ 300000 600000
+ 800000 700000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ light,dvddm-operating-points = <
+ /* kHz uV */
+ 300000 800000
+ 800000 800000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ };
+ c910_2: cpu@2 {
+
+ operating-points = <
+ /* kHz uV */
+ 300000 600000
+ 800000 700000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ light,dvddm-operating-points = <
+ /* kHz uV */
+ 300000 800000
+ 800000 800000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ };
+ c910_3: cpu@3 {
+
+ operating-points = <
+ /* kHz uV */
+ 300000 600000
+ 800000 700000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ light,dvddm-operating-points = <
+ /* kHz uV */
+ 300000 800000
+ 800000 800000
+ 1500000 800000
+ 1848000 1000000
+ >;
+ };
+};
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+/dts-v1/;
+
+#include "light-lpi4a.dts"
+
+&lightsound {
+ status = "okay";
+ simple-audio-card,dai-link@0 { /* I2S - HDMI*/
+ reg = <0>;
+ format = "i2s";
+ cpu {
+ sound-dai = <&light_i2s 1>;
+ };
+ codec {
+ sound-dai = <&dummy_codec>;
+ };
+ };
+ simple-audio-card,dai-link@1 { /* I2S - AUDIO SYS CODEC 7210*/
+ reg = <1>;
+ format = "i2s";
+ cpu {
+ sound-dai = <&i2s1 0>;
+ };
+ codec {
+ sound-dai = <&es7210_audio_codec>;
+ };
+ };
+ simple-audio-card,dai-link@2 { /* I2S - AUDIO SYS CODEC 8156*/
+ reg = <2>;
+ format = "i2s";
+ cpu {
+ sound-dai = <&i2s1 0>;
+ };
+ codec {
+ sound-dai = <&es8156_audio_codec>;
+ };
+ };
+};
+
+&dpu_enc0 {
+ status = "disabled";
+};
+
+&dsi0 {
+ status = "disabled";
+};
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022-2023 Alibaba Group Holding Limited.
+ */
+
+#include "light-lpi4a-ref.dts"
+
+/ {
+ model = "T-HEAD Light Lichee Pi 4A configuration for 8GB DDR board";
+ compatible = "thead,light-val", "thead,light-lpi4a", "thead,light";
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x0 0x200000 0x1 0xffe00000>;
+ };
+};
+
+&cmamem {
+ size = <0 0x20000000>; // 512MB on lpi4a (SOM)
+ alloc-ranges = <0 0xd8000000 0 0x20000000>; // [0x0D800_0000 ~ 0x0F800_0000]
+};
+
+&i2c3 {
+ touch@14 {
+ #gpio-cells = <2>;
+ compatible = "goodix,gt9271";
+ reg = <0x14>;
+ interrupt-parent = <&ao_gpio_porta>;
+ interrupts = <3 0>;
+ irq-gpios = <&ao_gpio_porta 3 0>;
+ reset-gpios = <&pcal6408ahk_d 0 0>;
+ AVDD28-supply = <®_tp_pwr_en>;
+ touchscreen-size-x = <1200>;
+ touchscreen-size-y = <1920>;
+ tp-size = <9271>;
+ status = "okay";
+ };
+};
+
+&dsi0 {
+ status = "okay";
+};
+
+
+&dhost_0 {
+ panel0@0 {
+ compatible = "himax,hx8279";
+ reg = <0>;
+ backlight = <&lcd0_backlight>;
+ reset-gpio = <&pcal6408ahk_d 7 0>; /* active low */
+ hsvcc-supply = <&soc_vdd18_lcd0_en_reg>;
+ vspn3v3-supply = <&soc_vdd33_lcd0_en_reg>;
+
+ port {
+ panel0_in: endpoint {
+ remote-endpoint = <&dsi0_out>;
+ };
+ };
+ };
+};
iopmp_dsp1: IOPMP_DSP1 {
is_default_region;
};
+
+ iopmp_audio0: IOPMP_AUDIO0 {
+ is_default_region;
+ };
+
+ iopmp_audio1: IOPMP_AUDIO1 {
+ is_default_region;
+ };
};
mbox_910t_client1: mbox_910t_client1 {
ref-clock-frequency = <24000000>;
keep_wifi_power_on;
pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_wifi_wake>;
wifi_chip_type = "rtl8723ds";
- WIFI,poweren_gpio = <&pcal6408ahk_c 5 0>;
+ WIFI,poweren_gpio = <&pcal6408ahk_c 4 0>;
status = "okay";
};
wcn_bt: wireless-bluetooth {
compatible = "bluetooth-platdata";
- pinctrl-names = "default", "rts_gpio";
- BT,power_gpio = <&pcal6408ahk_c 6 0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_bt_wake>;
+ BT,power_gpio = <&pcal6408ahk_c 5 0>;
status = "okay";
};
no-map;
};
vi_mem: framebuffer@10000000 {
- reg = <0x0 0x10000000 0x0 0x02C00000 /* vi_mem_pool_region[0] 44 MB (default) */
- 0x0 0x12C00000 0x0 0x01D00000 /* vi_mem_pool_region[1] 29 MB */
- 0x0 0x14900000 0x0 0x01E00000>; /* vi_mem_pool_region[2] 30 MB */
+ reg = <0x0 0x10000000 0x0 0x6700000>; /* vi_mem_pool_region[0] 44 MB (default) */
+ //0x0 0x12C00000 0x0 0x01D00000 /* vi_mem_pool_region[1] 29 MB */
+ //0x0 0x14900000 0x0 0x01E00000>; /* vi_mem_pool_region[2] 30 MB */
no-map;
};
facelib_mem: memory@17000000 {
num-cs = <1>;
cs-gpios = <&gpio2_porta 15 0>; // GPIO_ACTIVE_HIGH: 0
rx-sample-delay-ns = <10>;
- status = "okay";
spi_norflash@0 {
+ status = "okay";
#address-cells = <1>;
#size-cells = <1>;
compatible = "winbond,w25q64jwm", "jedec,spi-nor";
};
spidev@1 {
+ status = "disable";
compatible = "spidev";
#address-cells = <0x1>;
#size-cells = <0x1>;
&uart0 {
clock-frequency = <100000000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart0>;
+};
+
+&uart1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart1>;
+};
+
+&uart3 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart3>;
+};
+
+&uart4 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_uart4>;
};
&qspi0 {
tx-clk-delay = <0x00>; /* for RGMII */
phy-handle = <&phy_88E1111_0>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_gmac0>;
mdio0 {
#address-cells = <1>;
tx-clk-delay = <0x00>; /* for RGMII */
phy-handle = <&phy_88E1111_1>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_gmac1>;
};
&emmc {
pull_up;
wprtn_ignore;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdio0>;
};
&sdhci1 {
*/
pinctrl_uart0: uart0grp {
thead,pins = <
- FM_UART0_TXD 0x0 0x72
- FM_UART0_RXD 0x0 0x72
+ FM_UART0_TXD 0x0 0x202
+ FM_UART0_RXD 0x0 0x202
+ >;
+ };
+
+ pinctrl_i2c2: i2c2grp {
+ thead,pins = <
+ FM_I2C2_SCL 0x0 0x204
+ FM_I2C2_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_i2c3: i2c3grp {
+ thead,pins = <
+ FM_I2C3_SCL 0x0 0x204
+ FM_I2C3_SDA 0x0 0x204
>;
};
>;
};
+ pinctrl_gmac1: gmac1grp {
+ thead,pins = <
+ FM_GPIO2_18 0x1 0x20f /* GMAC1_TX_CLK */
+ FM_GPIO2_19 0x1 0x20f /* GMAC1_RX_CLK */
+ FM_GPIO2_20 0x1 0x20f /* GMAC1_TXEN */
+ FM_GPIO2_21 0x1 0x20f /* GMAC1_TXD0 */
+ FM_GPIO2_22 0x1 0x20f /* GMAC1_TXD1 */
+ FM_GPIO2_23 0x1 0x20f /* GMAC1_TXD2 */
+ FM_GPIO2_24 0x1 0x20f /* GMAC1_TXD3 */
+ FM_GPIO2_25 0x1 0x20f /* GMAC1_RXDV */
+ FM_GPIO2_30 0x1 0x20f /* GMAC1_RXD0 */
+ FM_GPIO2_31 0x1 0x20f /* GMAC1_RXD1 */
+ FM_GPIO3_0 0x1 0x20f /* GMAC1_RXD2 */
+ FM_GPIO3_1 0x1 0x20f /* GMAC1_RXD3 */
+ >;
+ };
+
+ pinctrl_sdio0: sdio0grp {
+ thead,pins = <
+ FM_SDIO0_DETN 0x0 0x202
+ >;
+ };
+
pinctrl_pwm: pwmgrp {
thead,pins = <
- FM_GPIO3_2 0x1 0x208 /* pwm0 */
+ FM_GPIO3_2 0x1 0x20f /* pwm0 */
+ FM_GPIO3_3 0x1 0x20f /* pwm1 */
+ >;
+ };
+
+ pinctrl_hdmi: hdmigrp {
+ thead,pins = <
+ FM_HDMI_SCL 0x0 0x202
+ FM_HDMI_SDA 0x0 0x202
+ FM_HDMI_CEC 0x0 0x202
+ >;
+ };
+
+ pinctrl_gmac0: gmac0grp {
+ thead,pins = <
+ FM_GMAC0_TX_CLK 0x0 0x20f /* GMAC0_TX_CLK */
+ FM_GMAC0_RX_CLK 0x0 0x20f /* GMAC0_RX_CLK */
+ FM_GMAC0_TXEN 0x0 0x20f /* GMAC0_TXEN */
+ FM_GMAC0_TXD0 0x0 0x20f /* GMAC0_TXD0 */
+ FM_GMAC0_TXD1 0x0 0x20f /* GMAC0_TXD1 */
+ FM_GMAC0_TXD2 0x0 0x20f /* GMAC0_TXD2 */
+ FM_GMAC0_TXD3 0x0 0x20f /* GMAC0_TXD3 */
+ FM_GMAC0_RXDV 0x0 0x20f /* GMAC0_RXDV */
+ FM_GMAC0_RXD0 0x0 0x20f /* GMAC0_RXD0 */
+ FM_GMAC0_RXD1 0x0 0x20f /* GMAC0_RXD1 */
+ FM_GMAC0_RXD2 0x0 0x20f /* GMAC0_RXD2 */
+ FM_GMAC0_RXD3 0x0 0x20f /* GMAC0_RXD3 */
+ FM_GMAC0_MDC 0x0 0x208 /* GMAC0_MDC */
+ FM_GMAC0_MDIO 0x0 0x208 /* GMAC0_MDIO */
+ FM_GMAC0_COL 0x3 0x232 /* PHY0_nRST */
+ FM_GMAC0_CRS 0x3 0x232 /* PHY0_nINT */
>;
};
};
* Pin Configuration Node:
* Format: <pin_id mux_node config>
*/
- pinctrl_uart3: uart3grp {
+ pinctrl_qspi1: qspi1grp {
thead,pins = <
- FM_UART3_TXD 0x0 0x72
- FM_UART3_RXD 0x0 0x72
+ FM_QSPI1_SCLK 0x0 0x20a
+ FM_QSPI1_CSN0 0x3 0x20a
+ FM_QSPI1_D0_MOSI 0x0 0x23a
+ FM_QSPI1_D1_MISO 0x0 0x23a
+ >;
+ };
+
+ pinctrl_i2c0: i2c0grp {
+ thead,pins = <
+ FM_I2C0_SCL 0x0 0x204
+ FM_I2C0_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_i2c1: i2c1grp {
+ thead,pins = <
+ FM_I2C1_SCL 0x0 0x204
+ FM_I2C1_SDA 0x0 0x204
+ >;
+ };
+
+ pinctrl_uart1: uart1grp {
+ thead,pins = <
+ FM_UART1_TXD 0x0 0x202
+ FM_UART1_RXD 0x0 0x202
>;
};
pinctrl_uart4: uart4grp {
thead,pins = <
- FM_UART4_TXD 0x0 0x72
- FM_UART4_RXD 0x0 0x72
- FM_UART4_CTSN 0x0 0x72
- FM_UART4_RTSN 0x0 0x72
+ FM_UART4_TXD 0x0 0x202
+ FM_UART4_RXD 0x0 0x202
+ FM_UART4_CTSN 0x0 0x202
+ FM_UART4_RTSN 0x0 0x202
>;
};
- pinctrl_qspi1: qspi1grp {
+ pinctrl_uart3: uart3grp {
thead,pins = <
- FM_QSPI1_SCLK 0x0 0x20a
- FM_QSPI1_CSN0 0x3 0x20a
- FM_QSPI1_D0_MOSI 0x0 0x23a
- FM_QSPI1_D1_MISO 0x0 0x23a
+ FM_UART3_TXD 0x1 0x202
+ FM_UART3_RXD 0x1 0x202
+ >;
+ };
+
+ pinctrl_wifi_wake: wifi_grp {
+ thead,pins = <
+ FM_GPIO0_27 0x0 0x202
>;
};
+ pinctrl_bt_wake: bt_grp {
+ thead,pins = <
+ FM_GPIO0_28 0x0 0x202
+ >;
+ };
pinctrl_iso7816: iso7816grp {
thead,pins = <
&i2c0 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c0>;
pcal6408ahk_b: gpio@20 {
compatible = "nxp,pca9557";
&i2c1 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c1>;
pcal6408ahk_c: gpio@20 {
compatible = "nxp,pca9557";
&i2c2 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c2>;
};
&i2c3 {
clock-frequency = <400000>;
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2c3>;
pcal6408ahk_d: gpio@20 {
compatible = "nxp,pca9557";
channel0 {
channel_id = <0>;
status = "okay";
- sensor0 {
- subdev_name = "vivcam";
- idx = <0>;
- csi_idx = <0>;
- mode_idx = <0>;
- path_type = "SENSOR_VGA_RAW12_LINER";
- };
sensor1 {
subdev_name = "vivcam";
idx = <3>;
};
};
};
+ channel1 {
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <3>;
+ csi_idx = <0>;
+ mode_idx = <1>;
+ path_type = "SENSOR_2592x1944_LINER";
+ };
+ isp {
+ subdev_name = "isp";
+ idx = <1>;
+ path_type = "ISP_MI_PATH_SP";
+ output {
+ max_width = <2600>;
+ max_height = <2000>;
+ bit_per_pixel = <12>;
+ frame_count = <3>;
+ };
+ };
+ };
+ channel2 {
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <3>;
+ csi_idx = <0>;
+ mode_idx = <1>;
+ path_type = "SENSOR_2592x1944_LINER";
+ };
+ isp {
+ subdev_name = "isp";
+ idx = <1>;
+ path_type = "ISP_MI_PATH_SP2_BP";
+ output {
+ max_width = <2600>;
+ max_height = <2000>;
+ bit_per_pixel = <12>;
+ frame_count = <3>;
+ };
+ };
+ };
};
-
&video3{
vi_mem_pool_region = <0>; // vi_mem: framebuffer, region[0]
status = "okay";
channel0 {
- sensor0 {
- subdev_name = "vivcam";
- idx = <0>;
- csi_idx = <0>;
- mode_idx = <0>;
- path_type = "SENSOR_VGA_RAW12_LINER";
-
- };
sensor1 {
subdev_name = "vivcam";
idx = <3>;
idx = <1>;
path_type = "ISP_MI_PATH_MP";
output {
- max_width = <1920>;
- max_height = <1088>;
+ max_width = <2600>;
+ max_height = <2000>;
bit_per_pixel = <12>;
frame_count = <3>;
};
};
};
channel1 {
- sensor0 {
- subdev_name = "vivcam";
- idx = <0>;
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <3>;
csi_idx = <0>;
- mode_idx = <0>;
- path_type = "SENSOR_VGA_RAW12_LINER";
-
+ mode_idx = <1>;
+ path_type = "SENSOR_2592x1944_LINER";
};
dma {
subdev_name = "vipre";
idx = <1>;
path_type = "ISP_MI_PATH_MP";
output {
- max_width = <1920>;
- max_height = <1088>;
+ max_width = <2600>;
+ max_height = <2000>;
bit_per_pixel = <12>;
frame_count = <3>;
};
};
};
channel2 {
- sensor0 {
- subdev_name = "vivcam";
- idx = <0>;
+ sensor1 {
+ subdev_name = "vivcam";
+ idx = <3>;
csi_idx = <0>;
- mode_idx = <0>;
- path_type = "SENSOR_VGA_RAW12_LINER";
-
+ mode_idx = <1>;
+ path_type = "SENSOR_2592x1944_LINER";
};
dma {
subdev_name = "vipre";
idx = <1>;
path_type = "ISP_MI_PATH_MP";
output {
- max_width = <1920>;
- max_height = <1088>;
+ max_width = <2600>;
+ max_height = <2000>;
bit_per_pixel = <12>;
frame_count = <3>;
};
};
};
-&video9{
- status = "okay";
- channel0 {
- sensor0 {
- subdev_name = "vivcam";
- idx = <1>; //vivcam1 sc132gs
- csi_idx = <2>; //<2>=CSI2X2_A
- mode_idx = <0>;
- path_type = "SENSOR_1080X1280_30FPS_RAW10_LINER";
- };
- dsp{
- output {
- max_width = <1080>;
- max_height = <1280>;
- bit_per_pixel = <16>;
- frame_count = <3>;
- };
- };
- };
-};
-
-
-&video10{ // TUNINGTOOL
- status = "okay";
- channel0 {
- sensor0 {
- subdev_name = "vivcam";
- idx = <0>; //<0>=vivcam0 : ov12870
- csi_idx = <0>; //<0>=CSI2
- mode_idx = <1>;
- path_type = "SENSOR_1080P_RAW10_LINER"; //SENSOR_VGA_RAW10_LINER//
- skip_init = <1>;
- };
- dma {
- path_type = "VIPRE_CSI0_ISP0";
- };
- };
-};
-
-&video15{
- status = "okay";
- channel0 {
- status = "okay";
- sensor0 {
- subdev_name = "vivcam";
- idx = <0>;
- csi_idx = <0>;
- mode_idx = <0>;
- path_type = "SENSOR_VGA_RAW12_LINER";
- };
- sensor1 {
- subdev_name = "vivcam";
- idx = <3>;
- csi_idx = <0>;
- mode_idx = <1>;
- path_type = "SENSOR_2592x1944_LINER";
- };
- dma {
- subdev_name = "vipre";
- idx = <0>;
- path_type = "VIPRE_CSI0_DDR";
- };
- };
-};
&trng {
status = "disabled";
status = "okay";
};
+&dhost_0 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ dsi0_in: endpoint {
+ remote-endpoint = <&enc0_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ dsi0_out: endpoint {
+ remote-endpoint = <&panel0_in>;
+ };
+ };
+ };
+};
+
&disp1_out {
remote-endpoint = <&hdmi_tx_in>;
};
&hdmi_tx {
status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hdmi>;
port@0 {
/* input */
&i2s1 {
status = "okay";
- dmas = <&dmac2 11>, <&dmac2 10>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_audiopa14>,
<&pinctrl_audiopa15>,
<&pinctrl_audiopa16>,
<&pinctrl_audiopa17>,
<&pinctrl_audio_i2s1>;
+ light,mclk_keepon = <1>;
};
&i2s2 {
/dts-v1/;
-#include "light-lpi4a.dts"
+#include "light-lpi4a-hdmi.dts"
&light_iopmp {
/* SPDX-License-Identifier: GPL-2.0 */
/*
- * Copyright (C) 2022 Alibaba Group Holding Limited.
+ * Copyright (C) 2022-2023 Alibaba Group Holding Limited.
*/
#include "light-lpi4a-ref.dts"
};
&cmamem {
- alloc-ranges = <0 0xe4000000 0 0x14000000>; // [0x0E400_0000 ~ 0x0F800_0000]
+ size = <0 0x20000000>; // 512MB on lpi4a (SOM)
+ alloc-ranges = <0 0xd8000000 0 0x20000000>; // [0x0D800_0000 ~ 0x0F800_0000]
+};
+
+&i2c3 {
+ touch@14 {
+ #gpio-cells = <2>;
+ compatible = "goodix,gt9271";
+ reg = <0x14>;
+ interrupt-parent = <&ao_gpio_porta>;
+ interrupts = <3 0>;
+ irq-gpios = <&ao_gpio_porta 3 0>;
+ reset-gpios = <&pcal6408ahk_d 0 0>;
+ AVDD28-supply = <®_tp_pwr_en>;
+ touchscreen-size-x = <800>;
+ touchscreen-size-y = <1200>;
+ tp-size = <9271>;
+ status = "okay";
+ };
+};
+
+&dsi0 {
+ status = "okay";
+};
+
+&dhost_0 {
+ panel0@0 {
+ compatible = "chongzhou,cz101b4001", "jadard,jd9365da-h3";
+ reg = <0>;
+ backlight = <&lcd0_backlight>;
+ reset-gpio = <&pcal6408ahk_d 7 0>; /* active low */
+ hsvcc-supply = <&soc_vdd18_lcd0_en_reg>;
+ vspn3v3-supply = <&soc_vdd33_lcd0_en_reg>;
+
+ port {
+ panel0_in: endpoint {
+ remote-endpoint = <&dsi0_out>;
+ };
+ };
+ };
};
*/
&video0{
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
};
&video1{
- status = "okay";
+ status = "disabled";
channel0 { // VSE0
channel_id = <0>;
status = "okay";
};
&video2 {
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
};
&video3 {
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
};
&video4 {
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
};
&video5 {
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
&video6 {
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
&video7{
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
&video8{
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
&video9 { //IR debug
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
&video10{ // TUNING TOOL
- status = "okay";
+ status = "disabled";
channel0 { // CSI2X2_B
status = "okay";
sensor0 {
&video11{
- status = "okay";
+ status = "disabled";
channel0 {
channel_id = <0>;
status = "okay";
&video12{ // TUNING TOOL
- status = "okay";
+ status = "disabled";
channel0 { // CSI2
status = "okay";
sensor0 {
#include <dt-bindings/clock/light-dspsys.h>
#include <dt-bindings/clock/light-audiosys.h>
#include <dt-bindings/firmware/thead/rsrc.h>
+#include <dt-bindings/clock/light-miscsys.h>
#include <dt-bindings/soc/thead,light-iopmp.h>
#include <dt-bindings/thermal/thermal.h>
#include <dt-bindings/reset/light-reset.h>
interrupt-controller;
};
};
+
c910_1: cpu@1 {
device_type = "cpu";
reg = <1>;
interrupt-controller;
};
};
+
+ idle_states: idle-states {
+ CPU_RET_0_0: cpu-retentive-0-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x10000000>;
+ entry-latency-us = <20>;
+ exit-latency-us = <40>;
+ min-residency-us = <80>;
+ };
+
+ CPU_NONRET_0_0: cpu-nonretentive-0-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x90000000>;
+ entry-latency-us = <250>;
+ exit-latency-us = <500>;
+ min-residency-us = <950>;
+ };
+
+ CLUSTER_RET_0: cluster-retentive-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x11000000>;
+ local-timer-stop;
+ entry-latency-us = <50>;
+ exit-latency-us = <100>;
+ min-residency-us = <250>;
+ wakeup-latency-us = <130>;
+ };
+
+ CLUSTER_NONRET_0: cluster-nonretentive-0 {
+ compatible = "riscv,idle-state";
+ riscv,sbi-suspend-param = <0x91000000>;
+ local-timer-stop;
+ entry-latency-us = <600>;
+ exit-latency-us = <1100>;
+ min-residency-us = <2700>;
+ wakeup-latency-us = <1500>;
+ };
+ };
};
display-subsystem {
status = "okay";
};
+ miscsys_reg: miscsys-reg@ffec02c000 {
+ compatible = "thead,light-miscsys-reg", "syscon";
+ reg = <0xff 0xec02c000 0x0 0x1000>;
+ status = "okay";
+ };
+
+ tee_miscsys_reg: tee_miscsys-reg@fffc02d000 {
+ compatible = "thead,light-miscsys-reg", "syscon";
+ reg = <0xff 0xfc02d000 0x0 0x1000>;
+ status = "okay";
+ };
+
audio_ioctrl: audio_ioctrl@ffcb01d000 {
compatible = "thead,light-audio-ioctrl-reg", "syscon";
reg = <0xff 0xcb01d000 0x0 0x1000>;
thead,teesys = <&teesys_syscon>;
#address-cells = <1>;
#size-cells = <1>;
+ clocks = <&miscsys_clk_gate CLKGEN_MISCSYS_EFUSE_PCLK>;
+ clock-names = "pclk";
gmac0_mac_address: mac-address@176 {
reg = <0xb0 6>;
reg = <0xff 0xec005000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
-
+ clocks = <&clk CLKGEN_GPIO0_PCLK>,
+ <&clk CLKGEN_GPIO0_DBCLK>;
+ clock-names = "bus", "db";
gpio0_porta: gpio0-controller@0 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
reg = <0xff 0xec006000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
-
+ clocks = <&clk CLKGEN_GPIO1_PCLK>,
+ <&clk CLKGEN_GPIO1_DBCLK>;
+ clock-names = "bus", "db";
gpio1_porta: gpio1-controller@0 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
reg = <0xff 0xe7f34000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
-
+ clocks = <&clk CLKGEN_GPIO2_PCLK>,
+ <&clk CLKGEN_GPIO2_DBCLK>;
+ clock-names = "bus", "db";
gpio2_porta: gpio2-controller@0 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
reg = <0xff 0xe7f38000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
-
+ clocks = <&clk CLKGEN_GPIO3_PCLK>,
+ <&clk CLKGEN_GPIO3_DBCLK>;
+ clock-names = "bus", "db";
gpio3_porta: gpio3-controller@0 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
};
};
- padctrl1_apsys: pinctrl1-apsys@ffe7f3c000 {
+ padctrl1_apsys: padctrl1-apsys@ffe7f3c000 {
compatible = "thead,light-fm-left-pinctrl";
reg = <0xff 0xe7f3c000 0x0 0x1000>;
+ clocks = <&clk CLKGEN_PADCTRL1_APSYS_PCLK>;
+ clock-names = "pclk";
status = "okay";
};
padctrl0_apsys: padctrl0-apsys@ffec007000 {
compatible = "thead,light-fm-right-pinctrl";
reg = <0xff 0xec007000 0x0 0x1000>;
+ clocks = <&clk CLKGEN_PADCTRL0_APSYS_PCLK>;
+ clock-names = "pclk";
status = "okay";
};
};
uart0: serial@ffe7014000 { /* Normal serial, for C910 log */
- compatible = "snps,dw-apb-uart";
+ compatible = "snps,dw-apb-uart", "light,uart0";
reg = <0xff 0xe7014000 0x0 0x4000>;
interrupt-parent = <&intc>;
interrupts = <36>;
pinctrl-0 = <&pinctrl_spi0>;
interrupt-parent = <&intc>;
interrupts = <54>;
- clocks = <&dummy_clock_spi>;
+ clocks = <&clk CLKGEN_SPI_SSI_CLK>,
+ <&clk CLKGEN_SPI_PCLK>;
+ clock-names = "sclk", "pclk";
num-cs = <2>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&pinctrl_qspi0>;
interrupt-parent = <&intc>;
interrupts = <52>;
- clocks = <&dummy_clock_qspi>;
+ clocks = <&clk CLKGEN_QSPI0_SSI_CLK>,
+ <&clk CLKGEN_QSPI0_PCLK>;
+ clock-names = "sclk", "pclk";
#address-cells = <1>;
#size-cells = <0>;
};
pinctrl-0 = <&pinctrl_qspi1>;
interrupt-parent = <&intc>;
interrupts = <53>;
- clocks = <&dummy_clock_spi>;
+ clocks = <&clk CLKGEN_QSPI1_SSI_CLK>,
+ <&clk CLKGEN_QSPI1_PCLK>;
+ clock-names = "sclk", "pclk";
#address-cells = <1>;
#size-cells = <0>;
};
clocks = <&vosys_clk_gate LIGHT_CLKGEN_HDMI_PCLK>,
<&vosys_clk_gate LIGHT_CLKGEN_HDMI_SFR_CLK>,
<&vosys_clk_gate LIGHT_CLKGEN_HDMI_CEC_CLK>,
- <&vosys_clk_gate LIGHT_CLKGEN_HDMI_PIXCLK>,
- <&vosys_clk_gate LIGHT_CLKGEN_HDMI_I2S_CLK>;
- clock-names = "iahb", "isfr", "cec", "pixclk", "i2s";
+ <&vosys_clk_gate LIGHT_CLKGEN_HDMI_PIXCLK>;
+ clock-names = "iahb", "isfr", "cec", "pixclk";
reg-io-width = <4>;
phy_version = <301>;
/* TODO: add phy property */
<0xff 0xef630010 0x0 0x60>;
interrupt-parent = <&intc>;
interrupts = <93>;
+ vosys-regmap = <&vosys_reg>;
clocks = <&vosys_clk_gate LIGHT_CLKGEN_DPU_CCLK>,
<&vosys_clk_gate LIGHT_CLKGEN_DPU_PIXCLK0>,
<&vosys_clk_gate LIGHT_CLKGEN_DPU_PIXCLK1>,
interrupts = <74>;
clocks = <&dummy_clock_rtc>;
clock-names = "rtc";
+ wakeup-source;
status = "okay";
};
compatible = "thead,dwc3";
usb3-misc-regmap = <&misc_sysreg>;
usb3-drd-regmap = <&usb3_drd>;
+ clocks = <&miscsys_clk_gate CLKGEN_MISCSYS_USB3_DRD_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_USB3_DRD_CTRL_REF_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_USB3_DRD_PHY_REF_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_USB3_DRD_SUSPEND_CLK>;
+ clock-names = "drd", "ctrl", "phy", "suspend";
#address-cells = <2>;
#size-cells = <2>;
ranges;
reg = <0xff 0xe7040000 0x0 0x10000>;
interrupt-parent = <&intc>;
interrupts = <68>;
- clocks = <&dummy_clock_ref>, <&dummy_clock_apb>, <&dummy_clock_suspend>;
- clock-names = "ref", "bus_early", "suspend";
reg-shift = <2>;
reg-io-width = <4>;
maximum-speed = "super-speed";
reg = <0xff 0xefc00000 0x0 0x1000>;
interrupt-parent = <&intc>;
interrupts = <27>;
- clocks = <&dummy_clock_apb>, <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_DMAC_CPUSYS_ACLK>, <&clk CLKGEN_DMAC_CPUSYS_HCLK>;
clock-names = "core-clk", "cfgr-clk";
#dma-cells = <1>;
dma-channels = <4>;
reg = <0xff 0xff340000 0x0 0x1000>;
interrupt-parent = <&intc>;
interrupts = <150>;
- clocks = <&dummy_clock_apb>, <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_DMAC_CPUSYS_ACLK>, <&clk CLKGEN_DMAC_CPUSYS_HCLK>;
clock-names = "core-clk", "cfgr-clk";
#dma-cells = <1>;
dma-channels = <4>;
reg = <0xff 0xc8000000 0x0 0x2000>;
interrupt-parent = <&intc>;
interrupts = <167>;
- clocks = <&dummy_clock_apb>, <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_DMAC_CPUSYS_ACLK>, <&clk CLKGEN_DMAC_CPUSYS_HCLK>;
clock-names = "core-clk", "cfgr-clk";
#dma-cells = <1>;
dma-channels = <16>;
interrupt-parent = <&intc>;
interrupts = <66>;
interrupt-names = "macirq";
- clocks = <&clk CLKGEN_GMAC0_CCLK>;
- clock-names = "gmac_pll_clk";
+ clocks = <&clk CLKGEN_GMAC0_CCLK>,
+ <&clk CLKGEN_GMAC0_PCLK>,
+ <&clk CLKGEN_GMAC_AXI_ACLK>,
+ <&clk CLKGEN_GMAC_AXI_PCLK>;
+ clock-names = "gmac_pll_clk","pclk","axi_aclk","axi_pclk";
snps,pbl = <32>;
snps,fixed-burst;
snps,axi-config = <&stmmac_axi_setup>;
interrupt-parent = <&intc>;
interrupts = <67>;
interrupt-names = "macirq";
- clocks = <&clk CLKGEN_GMAC1_CCLK>;
- clock-names = "gmac_pll_clk";
+ clocks = <&clk CLKGEN_GMAC1_CCLK>,
+ <&clk CLKGEN_GMAC1_PCLK>,
+ <&clk CLKGEN_GMAC_AXI_ACLK>,
+ <&clk CLKGEN_GMAC_AXI_PCLK>;
+ clock-names = "gmac_pll_clk","pclk","axi_aclk","axi_pclk";
snps,pbl = <32>;
snps,fixed-burst;
snps,axi-config = <&stmmac_axi_setup>;
interrupt-parent = <&intc>;
interrupts = <62>;
interrupt-names = "sdhciirq";
- clocks = <&dummy_clock_sdhci>;
- clock-names = "core";
+ clocks = <&clk CLKGEN_EMMC_SDIO_REF_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_EMMC_CLK>;
+ clock-names = "core", "bus";
};
sdhci0: sd@ffe7090000 {
interrupt-parent = <&intc>;
interrupts = <64>;
interrupt-names = "sdhci0irq";
- clocks = <&dummy_clock_sdhci>;
- clock-names = "core";
+ clocks = <&clk CLKGEN_EMMC_SDIO_REF_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_EMMC_CLK>;
+ clock-names = "core", "bus";
};
sdhci1: sd@ffe70a0000 {
- compatible = "snps,dwcmshc-sdhci";
- reg = <0xff 0xe70a0000 0x0 0x10000>;
- interrupt-parent = <&intc>;
- interrupts = <71>;
- interrupt-names = "sdhci1irq";
- clocks = <&dummy_clock_sdhci>;
- clock-names = "core";
+ compatible = "snps,dwcmshc-sdhci";
+ reg = <0xff 0xe70a0000 0x0 0x10000>;
+ interrupt-parent = <&intc>;
+ interrupts = <71>;
+ interrupt-names = "sdhci1irq";
+ clocks = <&clk CLKGEN_EMMC_SDIO_REF_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_EMMC_CLK>;
+ clock-names = "core", "bus";
};
hwspinlock: hwspinlock@ffefc10000 {
#sound-dai-cells = <1>;
compatible = "light,light-i2s";
reg = <0xff 0xe7034000 0x0 0x4000>;
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_light_i2s0>;
light,mode = "i2s-master";
light,sel = "ap_i2s";
interrupt-parent = <&intc>;
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&vosys_clk_gate LIGHT_CLKGEN_HDMI_I2S_CLK>;
clock-names = "pclk";
status = "disabled";
};
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S0>;
clock-names = "pclk";
status = "disabled";
};
light,sel = "i2s1";
interrupt-parent = <&intc>;
interrupts = <175>;
- dmas = <&dmac2 11>, <&dmac2 17>;
+ dmas = <&dmac2 11>, <&dmac2 10>;
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S1>;
clock-names = "pclk";
status = "disabled";
};
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S2>;
clock-names = "pclk";
status = "disabled";
};
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S8CH>;
clock-names = "pclk";
status = "disabled";
};
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S8CH>;
clock-names = "pclk";
status = "disabled";
};
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S8CH>;
clock-names = "pclk";
status = "disabled";
};
dma-names = "tx", "rx";
light,dma_maxburst = <4>;
#dma-cells = <1>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_I2S8CH>;
clock-names = "pclk";
status = "disabled";
};
#dma-cells = <1>;
clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_SPDIF0>;
clock-names = "pclk";
+ id = <0>;
status = "disabled";
};
#dma-cells = <1>;
clocks = <&audiosys_clk_gate LIGHT_CLKGEN_AUDIO_SPDIF1>;
clock-names = "pclk";
+ id = <1>;
status = "disabled";
};
reg = <0xff 0xe7f20000 0x0 0x4000>;
interrupt-parent = <&intc>;
interrupts = <44>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_I2C0_PCLK>;
+ clock-names = "pclk";
clock-frequency = <100000>;
i2c_mode = "dma";
dmas = <&dmac0 12>, <&dmac0 13>;
reg = <0xff 0xe7f24000 0x0 0x4000>;
interrupt-parent = <&intc>;
interrupts = <45>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_I2C1_PCLK>;
+ clock-names = "pclk";
clock-frequency = <100000>;
i2c_mode = "dma";
dmas = <&dmac0 14>, <&dmac0 15>;
reg = <0xff 0xec00c000 0x0 0x4000>;
interrupt-parent = <&intc>;
interrupts = <46>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_I2C2_PCLK>;
+ clock-names = "pclk";
clock-frequency = <100000>;
i2c_mode = "dma";
dmas = <&dmac0 16>, <&dmac0 17>;
reg = <0xff 0xec014000 0x0 0x4000>;
interrupt-parent = <&intc>;
interrupts = <47>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_I2C3_PCLK>;
+ clock-names = "pclk";
clock-frequency = <100000>;
i2c_mode = "dma";
dmas = <&dmac0 18>, <&dmac0 19>;
reg = <0xff 0xe7f28000 0x0 0x4000>;
interrupt-parent = <&intc>;
interrupts = <48>;
- clocks = <&dummy_clock_apb>;
+ clocks = <&clk CLKGEN_I2C4_PCLK>;
+ clock-names = "pclk";
clock-frequency = <100000>;
i2c_mode = "dma";
dmas = <&dmac0 20>, <&dmac0 21>;
reg = <0xff 0xff300000 0x0 0x40000>;
interrupt-parent = <&intc>;
interrupts = <144>,<145>,<146>,<147>;
- clocks = <&dummy_clock_eip>;
+ clocks = <&miscsys_clk_gate CLKGEN_MISCSYS_EIP120SI_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_EIP120SII_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_EIP120SIII_CLK>,
+ <&miscsys_clk_gate CLKGEN_MISCSYS_EIP150B_HCLK>;
+ clock-names = "120si_clk","120sii_clk","120siii_clk","hclk";
status = "disabled";
};
status = "okay";
};
+ aon_suspend_ctrl: aon_suspend_ctrl {
+ compatible = "thead,light-aon-suspend-ctrl";
+ status = "okay";
+ };
+
visys_clk_gate: visys-clk-gate { /* VI_SYSREG_R */
compatible = "thead,visys-gate-controller";
visys-regmap = <&visys_reg>;
#clock-cells = <1>;
status = "okay";
};
+
+ miscsys_clk_gate: miscsys-clk-gate {
+ compatible = "thead,miscsys-gate-controller";
+ miscsys-regmap = <&miscsys_reg>;
+ tee-miscsys-regmap = <&tee_miscsys_reg>;
+ #clock-cells = <1>;
+ status = "okay";
+ };
+
};
};
CONFIG_SOC_THEAD=y
CONFIG_SMP=y
CONFIG_VECTOR=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PM=y
+CONFIG_CPU_IDLE=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=m
+CONFIG_JUMP_LABEL=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_PARTITION_ADVANCED=y
# CONFIG_INPUT_MOUSE is not set
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_GOODIX=y
+CONFIG_TOUCHSCREEN_GT9XX=y
CONFIG_INPUT_MISC=y
CONFIG_INPUT_UINPUT=y
CONFIG_SERIAL_8250=y
CONFIG_DRM_PANEL_ILITEK_ILI9881C=y
CONFIG_DRM_PANEL_ILI9881D=y
CONFIG_DRM_PANEL_HX8394=y
+CONFIG_DRM_PANEL_JADARD_JD9365DA_H3=y
+CONFIG_DRM_PANEL_HX8279=y
CONFIG_DRM_VERISILICON=y
CONFIG_BACKLIGHT_CLASS_DEVICE=y
CONFIG_BACKLIGHT_PWM=y
CONFIG_PM=y
# CONFIG_SUSPEND is not set
# CONFIG_PM_SLEEP is not set
+CONFIG_PM_DEVFREQ=y
+CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y
+CONFIG_DEVFREQ_GOV_PERFORMANCE=y
+CONFIG_DEVFREQ_GOV_POWERSAVE=y
+CONFIG_DEVFREQ_GOV_USERSPACE=y
+CONFIG_DEVFREQ_GOV_PASSIVE=y
+CONFIG_PM_DEVFREQ_EVENT=y
+
CONFIG_SOC_VIRT=y
CONFIG_ARCH_RV32I=y
CONFIG_SMP=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PM=y
+CONFIG_CPU_IDLE=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=m
CONFIG_JUMP_LABEL=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
#error "Unexpected __SIZEOF_SHORT__"
#endif
+#ifdef __ASSEMBLY__
+
+/* Common assembly source macros */
+
+#ifdef CONFIG_XIP_KERNEL
+.macro XIP_FIXUP_OFFSET reg
+ REG_L t0, _xip_fixup
+ add \reg, \reg, t0
+.endm
+.macro XIP_FIXUP_FLASH_OFFSET reg
+ la t1, __data_loc
+ li t0, XIP_OFFSET_MASK
+ and t1, t1, t0
+ li t1, XIP_OFFSET
+ sub t0, t0, t1
+ sub \reg, \reg, t0
+.endm
+_xip_fixup: .dword CONFIG_PHYS_RAM_BASE - CONFIG_XIP_PHYS_ADDR - XIP_OFFSET
+#else
+.macro XIP_FIXUP_OFFSET reg
+.endm
+.macro XIP_FIXUP_FLASH_OFFSET reg
+.endm
+#endif /* CONFIG_XIP_KERNEL */
+
+#endif /* __ASSEMBLY__ */
+
#endif /* _ASM_RISCV_ASM_H */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2021 by Rivos Inc.
+ */
+#ifndef __ASM_CPU_OPS_SBI_H
+#define __ASM_CPU_OPS_SBI_H
+
+#ifndef __ASSEMBLY__
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/threads.h>
+
+/**
+ * struct sbi_hart_boot_data - Hart specific boot used during booting and
+ * cpu hotplug.
+ * @task_ptr: A pointer to the hart specific tp
+ * @stack_ptr: A pointer to the hart specific sp
+ */
+struct sbi_hart_boot_data {
+ void *task_ptr;
+ void *stack_ptr;
+};
+#endif
+
+#endif /* ifndef __ASM_CPU_OPS_SBI_H */
return __pgprot(prot);
}
-
+#define pgprot_dmacoherent pgprot_writecombine
/*
* Encode and decode a swap entry
*
SBI_EXT_HSM_HART_START = 0,
SBI_EXT_HSM_HART_STOP,
SBI_EXT_HSM_HART_STATUS,
+ SBI_EXT_HSM_HART_SUSPEND,
};
-enum sbi_hsm_hart_status {
- SBI_HSM_HART_STATUS_STARTED = 0,
- SBI_HSM_HART_STATUS_STOPPED,
- SBI_HSM_HART_STATUS_START_PENDING,
- SBI_HSM_HART_STATUS_STOP_PENDING,
+enum sbi_hsm_hart_state {
+ SBI_HSM_STATE_STARTED = 0,
+ SBI_HSM_STATE_STOPPED,
+ SBI_HSM_STATE_START_PENDING,
+ SBI_HSM_STATE_STOP_PENDING,
+ SBI_HSM_STATE_SUSPENDED,
+ SBI_HSM_STATE_SUSPEND_PENDING,
+ SBI_HSM_STATE_RESUME_PENDING,
+};
+
+#define SBI_HSM_SUSP_BASE_MASK 0x7fffffff
+#define SBI_HSM_SUSP_NON_RET_BIT 0x80000000
+#define SBI_HSM_SUSP_PLAT_BASE 0x10000000
+
+#define SBI_HSM_SUSPEND_RET_DEFAULT 0x00000000
+#define SBI_HSM_SUSPEND_RET_PLATFORM SBI_HSM_SUSP_PLAT_BASE
+#define SBI_HSM_SUSPEND_RET_LAST SBI_HSM_SUSP_BASE_MASK
+#define SBI_HSM_SUSPEND_NON_RET_DEFAULT SBI_HSM_SUSP_NON_RET_BIT
+#define SBI_HSM_SUSPEND_NON_RET_PLATFORM (SBI_HSM_SUSP_NON_RET_BIT | \
+ SBI_HSM_SUSP_PLAT_BASE)
+#define SBI_HSM_SUSPEND_NON_RET_LAST (SBI_HSM_SUSP_NON_RET_BIT | \
+ SBI_HSM_SUSP_BASE_MASK)
+
+enum sbi_ext_srst_fid {
+ SBI_EXT_SRST_RESET = 0,
+};
+
+enum sbi_srst_reset_type {
+ SBI_SRST_RESET_TYPE_SHUTDOWN = 0,
+ SBI_SRST_RESET_TYPE_COLD_REBOOT,
+ SBI_SRST_RESET_TYPE_WARM_REBOOT,
+};
+
+enum sbi_srst_reset_reason {
+ SBI_SRST_RESET_REASON_NONE = 0,
+ SBI_SRST_RESET_REASON_SYS_FAILURE,
};
#define SBI_SPEC_VERSION_DEFAULT 0x1
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ */
+
+#ifndef _ASM_RISCV_SUSPEND_H
+#define _ASM_RISCV_SUSPEND_H
+
+#include <asm/ptrace.h>
+
+struct suspend_context {
+ /* Saved and restored by low-level functions */
+ struct pt_regs regs;
+ /* Saved and restored by high-level functions */
+ unsigned long scratch;
+ unsigned long tvec;
+ unsigned long ie;
+#ifdef CONFIG_MMU
+ unsigned long satp;
+#endif
+};
+
+/* Low-level CPU suspend entry function */
+int __cpu_suspend_enter(struct suspend_context *context);
+
+/* High-level CPU suspend which will save context and call finish() */
+int cpu_suspend(unsigned long arg,
+ int (*finish)(unsigned long arg,
+ unsigned long entry,
+ unsigned long context));
+
+/* Low-level CPU resume entry function */
+int __cpu_resume_enter(unsigned long hartid, unsigned long context);
+
+#endif
obj-$(CONFIG_MODULES) += module.o
obj-$(CONFIG_MODULE_SECTIONS) += module-sections.o
+obj-$(CONFIG_CPU_PM) += suspend_entry.o suspend.o
+
obj-$(CONFIG_FUNCTION_TRACER) += mcount.o ftrace.o
obj-$(CONFIG_DYNAMIC_FTRACE) += mcount-dyn.o
#include <linux/sched.h>
#include <asm/thread_info.h>
#include <asm/ptrace.h>
+#include <asm/cpu_ops_sbi.h>
+#include <asm/suspend.h>
+
+void asm_offsets(void);
void asm_offsets(void)
{
OFFSET(PT_BADADDR, pt_regs, badaddr);
OFFSET(PT_CAUSE, pt_regs, cause);
+ OFFSET(SUSPEND_CONTEXT_REGS, suspend_context, regs);
+#if 0
+ OFFSET(KVM_ARCH_GUEST_ZERO, kvm_vcpu_arch, guest_context.zero);
+ OFFSET(KVM_ARCH_GUEST_RA, kvm_vcpu_arch, guest_context.ra);
+ OFFSET(KVM_ARCH_GUEST_SP, kvm_vcpu_arch, guest_context.sp);
+ OFFSET(KVM_ARCH_GUEST_GP, kvm_vcpu_arch, guest_context.gp);
+ OFFSET(KVM_ARCH_GUEST_TP, kvm_vcpu_arch, guest_context.tp);
+ OFFSET(KVM_ARCH_GUEST_T0, kvm_vcpu_arch, guest_context.t0);
+ OFFSET(KVM_ARCH_GUEST_T1, kvm_vcpu_arch, guest_context.t1);
+ OFFSET(KVM_ARCH_GUEST_T2, kvm_vcpu_arch, guest_context.t2);
+ OFFSET(KVM_ARCH_GUEST_S0, kvm_vcpu_arch, guest_context.s0);
+ OFFSET(KVM_ARCH_GUEST_S1, kvm_vcpu_arch, guest_context.s1);
+ OFFSET(KVM_ARCH_GUEST_A0, kvm_vcpu_arch, guest_context.a0);
+ OFFSET(KVM_ARCH_GUEST_A1, kvm_vcpu_arch, guest_context.a1);
+ OFFSET(KVM_ARCH_GUEST_A2, kvm_vcpu_arch, guest_context.a2);
+ OFFSET(KVM_ARCH_GUEST_A3, kvm_vcpu_arch, guest_context.a3);
+ OFFSET(KVM_ARCH_GUEST_A4, kvm_vcpu_arch, guest_context.a4);
+ OFFSET(KVM_ARCH_GUEST_A5, kvm_vcpu_arch, guest_context.a5);
+ OFFSET(KVM_ARCH_GUEST_A6, kvm_vcpu_arch, guest_context.a6);
+ OFFSET(KVM_ARCH_GUEST_A7, kvm_vcpu_arch, guest_context.a7);
+ OFFSET(KVM_ARCH_GUEST_S2, kvm_vcpu_arch, guest_context.s2);
+ OFFSET(KVM_ARCH_GUEST_S3, kvm_vcpu_arch, guest_context.s3);
+ OFFSET(KVM_ARCH_GUEST_S4, kvm_vcpu_arch, guest_context.s4);
+ OFFSET(KVM_ARCH_GUEST_S5, kvm_vcpu_arch, guest_context.s5);
+ OFFSET(KVM_ARCH_GUEST_S6, kvm_vcpu_arch, guest_context.s6);
+ OFFSET(KVM_ARCH_GUEST_S7, kvm_vcpu_arch, guest_context.s7);
+ OFFSET(KVM_ARCH_GUEST_S8, kvm_vcpu_arch, guest_context.s8);
+ OFFSET(KVM_ARCH_GUEST_S9, kvm_vcpu_arch, guest_context.s9);
+ OFFSET(KVM_ARCH_GUEST_S10, kvm_vcpu_arch, guest_context.s10);
+ OFFSET(KVM_ARCH_GUEST_S11, kvm_vcpu_arch, guest_context.s11);
+ OFFSET(KVM_ARCH_GUEST_T3, kvm_vcpu_arch, guest_context.t3);
+ OFFSET(KVM_ARCH_GUEST_T4, kvm_vcpu_arch, guest_context.t4);
+ OFFSET(KVM_ARCH_GUEST_T5, kvm_vcpu_arch, guest_context.t5);
+ OFFSET(KVM_ARCH_GUEST_T6, kvm_vcpu_arch, guest_context.t6);
+ OFFSET(KVM_ARCH_GUEST_SEPC, kvm_vcpu_arch, guest_context.sepc);
+ OFFSET(KVM_ARCH_GUEST_SSTATUS, kvm_vcpu_arch, guest_context.sstatus);
+ OFFSET(KVM_ARCH_GUEST_HSTATUS, kvm_vcpu_arch, guest_context.hstatus);
+ OFFSET(KVM_ARCH_GUEST_SCOUNTEREN, kvm_vcpu_arch, guest_csr.scounteren);
+
+ OFFSET(KVM_ARCH_HOST_ZERO, kvm_vcpu_arch, host_context.zero);
+ OFFSET(KVM_ARCH_HOST_RA, kvm_vcpu_arch, host_context.ra);
+ OFFSET(KVM_ARCH_HOST_SP, kvm_vcpu_arch, host_context.sp);
+ OFFSET(KVM_ARCH_HOST_GP, kvm_vcpu_arch, host_context.gp);
+ OFFSET(KVM_ARCH_HOST_TP, kvm_vcpu_arch, host_context.tp);
+ OFFSET(KVM_ARCH_HOST_T0, kvm_vcpu_arch, host_context.t0);
+ OFFSET(KVM_ARCH_HOST_T1, kvm_vcpu_arch, host_context.t1);
+ OFFSET(KVM_ARCH_HOST_T2, kvm_vcpu_arch, host_context.t2);
+ OFFSET(KVM_ARCH_HOST_S0, kvm_vcpu_arch, host_context.s0);
+ OFFSET(KVM_ARCH_HOST_S1, kvm_vcpu_arch, host_context.s1);
+ OFFSET(KVM_ARCH_HOST_A0, kvm_vcpu_arch, host_context.a0);
+ OFFSET(KVM_ARCH_HOST_A1, kvm_vcpu_arch, host_context.a1);
+ OFFSET(KVM_ARCH_HOST_A2, kvm_vcpu_arch, host_context.a2);
+ OFFSET(KVM_ARCH_HOST_A3, kvm_vcpu_arch, host_context.a3);
+ OFFSET(KVM_ARCH_HOST_A4, kvm_vcpu_arch, host_context.a4);
+ OFFSET(KVM_ARCH_HOST_A5, kvm_vcpu_arch, host_context.a5);
+ OFFSET(KVM_ARCH_HOST_A6, kvm_vcpu_arch, host_context.a6);
+ OFFSET(KVM_ARCH_HOST_A7, kvm_vcpu_arch, host_context.a7);
+ OFFSET(KVM_ARCH_HOST_S2, kvm_vcpu_arch, host_context.s2);
+ OFFSET(KVM_ARCH_HOST_S3, kvm_vcpu_arch, host_context.s3);
+ OFFSET(KVM_ARCH_HOST_S4, kvm_vcpu_arch, host_context.s4);
+ OFFSET(KVM_ARCH_HOST_S5, kvm_vcpu_arch, host_context.s5);
+ OFFSET(KVM_ARCH_HOST_S6, kvm_vcpu_arch, host_context.s6);
+ OFFSET(KVM_ARCH_HOST_S7, kvm_vcpu_arch, host_context.s7);
+ OFFSET(KVM_ARCH_HOST_S8, kvm_vcpu_arch, host_context.s8);
+ OFFSET(KVM_ARCH_HOST_S9, kvm_vcpu_arch, host_context.s9);
+ OFFSET(KVM_ARCH_HOST_S10, kvm_vcpu_arch, host_context.s10);
+ OFFSET(KVM_ARCH_HOST_S11, kvm_vcpu_arch, host_context.s11);
+ OFFSET(KVM_ARCH_HOST_T3, kvm_vcpu_arch, host_context.t3);
+ OFFSET(KVM_ARCH_HOST_T4, kvm_vcpu_arch, host_context.t4);
+ OFFSET(KVM_ARCH_HOST_T5, kvm_vcpu_arch, host_context.t5);
+ OFFSET(KVM_ARCH_HOST_T6, kvm_vcpu_arch, host_context.t6);
+ OFFSET(KVM_ARCH_HOST_SEPC, kvm_vcpu_arch, host_context.sepc);
+ OFFSET(KVM_ARCH_HOST_SSTATUS, kvm_vcpu_arch, host_context.sstatus);
+ OFFSET(KVM_ARCH_HOST_HSTATUS, kvm_vcpu_arch, host_context.hstatus);
+ OFFSET(KVM_ARCH_HOST_SSCRATCH, kvm_vcpu_arch, host_sscratch);
+ OFFSET(KVM_ARCH_HOST_STVEC, kvm_vcpu_arch, host_stvec);
+ OFFSET(KVM_ARCH_HOST_SCOUNTEREN, kvm_vcpu_arch, host_scounteren);
+
+ OFFSET(KVM_ARCH_TRAP_SEPC, kvm_cpu_trap, sepc);
+ OFFSET(KVM_ARCH_TRAP_SCAUSE, kvm_cpu_trap, scause);
+ OFFSET(KVM_ARCH_TRAP_STVAL, kvm_cpu_trap, stval);
+ OFFSET(KVM_ARCH_TRAP_HTVAL, kvm_cpu_trap, htval);
+ OFFSET(KVM_ARCH_TRAP_HTINST, kvm_cpu_trap, htinst);
+
+ /* F extension */
+
+ OFFSET(KVM_ARCH_FP_F_F0, kvm_cpu_context, fp.f.f[0]);
+ OFFSET(KVM_ARCH_FP_F_F1, kvm_cpu_context, fp.f.f[1]);
+ OFFSET(KVM_ARCH_FP_F_F2, kvm_cpu_context, fp.f.f[2]);
+ OFFSET(KVM_ARCH_FP_F_F3, kvm_cpu_context, fp.f.f[3]);
+ OFFSET(KVM_ARCH_FP_F_F4, kvm_cpu_context, fp.f.f[4]);
+ OFFSET(KVM_ARCH_FP_F_F5, kvm_cpu_context, fp.f.f[5]);
+ OFFSET(KVM_ARCH_FP_F_F6, kvm_cpu_context, fp.f.f[6]);
+ OFFSET(KVM_ARCH_FP_F_F7, kvm_cpu_context, fp.f.f[7]);
+ OFFSET(KVM_ARCH_FP_F_F8, kvm_cpu_context, fp.f.f[8]);
+ OFFSET(KVM_ARCH_FP_F_F9, kvm_cpu_context, fp.f.f[9]);
+ OFFSET(KVM_ARCH_FP_F_F10, kvm_cpu_context, fp.f.f[10]);
+ OFFSET(KVM_ARCH_FP_F_F11, kvm_cpu_context, fp.f.f[11]);
+ OFFSET(KVM_ARCH_FP_F_F12, kvm_cpu_context, fp.f.f[12]);
+ OFFSET(KVM_ARCH_FP_F_F13, kvm_cpu_context, fp.f.f[13]);
+ OFFSET(KVM_ARCH_FP_F_F14, kvm_cpu_context, fp.f.f[14]);
+ OFFSET(KVM_ARCH_FP_F_F15, kvm_cpu_context, fp.f.f[15]);
+ OFFSET(KVM_ARCH_FP_F_F16, kvm_cpu_context, fp.f.f[16]);
+ OFFSET(KVM_ARCH_FP_F_F17, kvm_cpu_context, fp.f.f[17]);
+ OFFSET(KVM_ARCH_FP_F_F18, kvm_cpu_context, fp.f.f[18]);
+ OFFSET(KVM_ARCH_FP_F_F19, kvm_cpu_context, fp.f.f[19]);
+ OFFSET(KVM_ARCH_FP_F_F20, kvm_cpu_context, fp.f.f[20]);
+ OFFSET(KVM_ARCH_FP_F_F21, kvm_cpu_context, fp.f.f[21]);
+ OFFSET(KVM_ARCH_FP_F_F22, kvm_cpu_context, fp.f.f[22]);
+ OFFSET(KVM_ARCH_FP_F_F23, kvm_cpu_context, fp.f.f[23]);
+ OFFSET(KVM_ARCH_FP_F_F24, kvm_cpu_context, fp.f.f[24]);
+ OFFSET(KVM_ARCH_FP_F_F25, kvm_cpu_context, fp.f.f[25]);
+ OFFSET(KVM_ARCH_FP_F_F26, kvm_cpu_context, fp.f.f[26]);
+ OFFSET(KVM_ARCH_FP_F_F27, kvm_cpu_context, fp.f.f[27]);
+ OFFSET(KVM_ARCH_FP_F_F28, kvm_cpu_context, fp.f.f[28]);
+ OFFSET(KVM_ARCH_FP_F_F29, kvm_cpu_context, fp.f.f[29]);
+ OFFSET(KVM_ARCH_FP_F_F30, kvm_cpu_context, fp.f.f[30]);
+ OFFSET(KVM_ARCH_FP_F_F31, kvm_cpu_context, fp.f.f[31]);
+ OFFSET(KVM_ARCH_FP_F_FCSR, kvm_cpu_context, fp.f.fcsr);
+
+ /* D extension */
+
+ OFFSET(KVM_ARCH_FP_D_F0, kvm_cpu_context, fp.d.f[0]);
+ OFFSET(KVM_ARCH_FP_D_F1, kvm_cpu_context, fp.d.f[1]);
+ OFFSET(KVM_ARCH_FP_D_F2, kvm_cpu_context, fp.d.f[2]);
+ OFFSET(KVM_ARCH_FP_D_F3, kvm_cpu_context, fp.d.f[3]);
+ OFFSET(KVM_ARCH_FP_D_F4, kvm_cpu_context, fp.d.f[4]);
+ OFFSET(KVM_ARCH_FP_D_F5, kvm_cpu_context, fp.d.f[5]);
+ OFFSET(KVM_ARCH_FP_D_F6, kvm_cpu_context, fp.d.f[6]);
+ OFFSET(KVM_ARCH_FP_D_F7, kvm_cpu_context, fp.d.f[7]);
+ OFFSET(KVM_ARCH_FP_D_F8, kvm_cpu_context, fp.d.f[8]);
+ OFFSET(KVM_ARCH_FP_D_F9, kvm_cpu_context, fp.d.f[9]);
+ OFFSET(KVM_ARCH_FP_D_F10, kvm_cpu_context, fp.d.f[10]);
+ OFFSET(KVM_ARCH_FP_D_F11, kvm_cpu_context, fp.d.f[11]);
+ OFFSET(KVM_ARCH_FP_D_F12, kvm_cpu_context, fp.d.f[12]);
+ OFFSET(KVM_ARCH_FP_D_F13, kvm_cpu_context, fp.d.f[13]);
+ OFFSET(KVM_ARCH_FP_D_F14, kvm_cpu_context, fp.d.f[14]);
+ OFFSET(KVM_ARCH_FP_D_F15, kvm_cpu_context, fp.d.f[15]);
+ OFFSET(KVM_ARCH_FP_D_F16, kvm_cpu_context, fp.d.f[16]);
+ OFFSET(KVM_ARCH_FP_D_F17, kvm_cpu_context, fp.d.f[17]);
+ OFFSET(KVM_ARCH_FP_D_F18, kvm_cpu_context, fp.d.f[18]);
+ OFFSET(KVM_ARCH_FP_D_F19, kvm_cpu_context, fp.d.f[19]);
+ OFFSET(KVM_ARCH_FP_D_F20, kvm_cpu_context, fp.d.f[20]);
+ OFFSET(KVM_ARCH_FP_D_F21, kvm_cpu_context, fp.d.f[21]);
+ OFFSET(KVM_ARCH_FP_D_F22, kvm_cpu_context, fp.d.f[22]);
+ OFFSET(KVM_ARCH_FP_D_F23, kvm_cpu_context, fp.d.f[23]);
+ OFFSET(KVM_ARCH_FP_D_F24, kvm_cpu_context, fp.d.f[24]);
+ OFFSET(KVM_ARCH_FP_D_F25, kvm_cpu_context, fp.d.f[25]);
+ OFFSET(KVM_ARCH_FP_D_F26, kvm_cpu_context, fp.d.f[26]);
+ OFFSET(KVM_ARCH_FP_D_F27, kvm_cpu_context, fp.d.f[27]);
+ OFFSET(KVM_ARCH_FP_D_F28, kvm_cpu_context, fp.d.f[28]);
+ OFFSET(KVM_ARCH_FP_D_F29, kvm_cpu_context, fp.d.f[29]);
+ OFFSET(KVM_ARCH_FP_D_F30, kvm_cpu_context, fp.d.f[30]);
+ OFFSET(KVM_ARCH_FP_D_F31, kvm_cpu_context, fp.d.f[31]);
+ OFFSET(KVM_ARCH_FP_D_FCSR, kvm_cpu_context, fp.d.fcsr);
+#endif
/*
* THREAD_{F,X}* might be larger than a S-type offset can handle, but
* these are used in performance-sensitive assembly so we can't resort
* ensures the alignment is sane.
*/
DEFINE(PT_SIZE_ON_STACK, ALIGN(sizeof(struct pt_regs), STACK_ALIGN));
+#if 0
+ OFFSET(KERNEL_MAP_VIRT_ADDR, kernel_mapping, virt_addr);
+#endif
+ OFFSET(SBI_HART_BOOT_TASK_PTR_OFFSET, sbi_hart_boot_data, task_ptr);
+ OFFSET(SBI_HART_BOOT_STACK_PTR_OFFSET, sbi_hart_boot_data, stack_ptr);
}
#include <linux/init.h>
#include <linux/mm.h>
+#include <linux/sched/task_stack.h>
#include <asm/cpu_ops.h>
+#include <asm/cpu_ops_sbi.h>
#include <asm/sbi.h>
#include <asm/smp.h>
extern char secondary_start_sbi[];
const struct cpu_operations cpu_ops_sbi;
+/*
+ * Ordered booting via HSM brings one cpu at a time. However, cpu hotplug can
+ * be invoked from multiple threads in parallel. Define a per cpu data
+ * to handle that.
+ */
+DEFINE_PER_CPU(struct sbi_hart_boot_data, boot_data);
+
static int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr,
unsigned long priv)
{
static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle)
{
- int rc;
unsigned long boot_addr = __pa_symbol(secondary_start_sbi);
int hartid = cpuid_to_hartid_map(cpuid);
-
- cpu_update_secondary_bootdata(cpuid, tidle);
- rc = sbi_hsm_hart_start(hartid, boot_addr, 0);
-
- return rc;
+ unsigned long hsm_data;
+ struct sbi_hart_boot_data *bdata = &per_cpu(boot_data, cpuid);
+
+ /* Make sure tidle is updated */
+ smp_mb();
+ bdata->task_ptr = tidle;
+ bdata->stack_ptr = task_stack_page(tidle) + THREAD_SIZE;
+ /* Make sure boot data is updated */
+ smp_mb();
+ hsm_data = __pa(bdata);
+ return sbi_hsm_hart_start(hartid, boot_addr, hsm_data);
}
static int sbi_cpu_prepare(unsigned int cpuid)
rc = sbi_hsm_hart_get_status(hartid);
- if (rc == SBI_HSM_HART_STATUS_STOPPED)
+ if (rc == SBI_HSM_STATE_STOPPED)
return 0;
return rc;
}
#include <asm/thread_info.h>
#include <asm/page.h>
#include <asm/csr.h>
+#include <asm/cpu_ops_sbi.h>
#include <asm/hwcap.h>
#include <asm/image.h>
#include "efi-header.S"
.align 2
#ifdef CONFIG_MMU
-relocate:
+ .global relocate_enable_mmu
+relocate_enable_mmu:
/* Relocate return address */
li a1, PAGE_OFFSET
la a2, _start
la a3, .Lsecondary_park
csrw CSR_TVEC, a3
- slli a3, a0, LGREG
- la a4, __cpu_up_stack_pointer
- la a5, __cpu_up_task_pointer
- add a4, a3, a4
- add a5, a3, a5
- REG_L sp, (a4)
- REG_L tp, (a5)
+ /* a0 contains the hartid & a1 contains boot data */
+ li a2, SBI_HART_BOOT_TASK_PTR_OFFSET
+ XIP_FIXUP_OFFSET a2
+ add a2, a2, a1
+ REG_L tp, (a2)
+ li a3, SBI_HART_BOOT_STACK_PTR_OFFSET
+ XIP_FIXUP_OFFSET a3
+ add a3, a3, a1
+ REG_L sp, (a3)
.global secondary_start_common
secondary_start_common:
#ifdef CONFIG_MMU
/* Enable virtual memory and relocate to virtual address */
la a0, swapper_pg_dir
- call relocate
+ XIP_FIXUP_OFFSET a0
+ call relocate_enable_mmu
#endif
call setup_trap_vector
tail smp_callin
call setup_vm
#ifdef CONFIG_MMU
la a0, early_pg_dir
- call relocate
+ XIP_FIXUP_OFFSET a0
+ call relocate_enable_mmu
#endif /* CONFIG_MMU */
call setup_trap_vector
bool fill_callchain(unsigned long pc, unsigned long regs, void *entry)
{
- return perf_callchain_store(entry, pc) == 0;
+ return perf_callchain_store(entry, pc);
}
void notrace walk_stackframe(struct task_struct *task,
#include <asm/string.h>
#include <asm/switch_to.h>
#include <asm/thread_info.h>
+#include <asm/cpuidle.h>
register unsigned long gp_in_global __asm__("gp");
void arch_cpu_idle(void)
{
- wait_for_interrupt();
+ cpu_do_idle();
raw_local_irq_enable();
}
struct __riscv_v_state *vstate = &target->thread.vstate;
membuf_write(&to, vstate, offsetof(struct __riscv_v_state, vtype));
+ membuf_store(&to, vstate->vtype);
return membuf_zero(&to, 4); // explicitly pad
}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ */
+
+#include <linux/ftrace.h>
+#include <asm/csr.h>
+#include <asm/suspend.h>
+
+static void suspend_save_csrs(struct suspend_context *context)
+{
+ context->scratch = csr_read(CSR_SCRATCH);
+ context->tvec = csr_read(CSR_TVEC);
+ context->ie = csr_read(CSR_IE);
+
+ /*
+ * No need to save/restore IP CSR (i.e. MIP or SIP) because:
+ *
+ * 1. For no-MMU (M-mode) kernel, the bits in MIP are set by
+ * external devices (such as interrupt controller, timer, etc).
+ * 2. For MMU (S-mode) kernel, the bits in SIP are set by
+ * M-mode firmware and external devices (such as interrupt
+ * controller, etc).
+ */
+
+#ifdef CONFIG_MMU
+ context->satp = csr_read(CSR_SATP);
+#endif
+}
+
+static void suspend_restore_csrs(struct suspend_context *context)
+{
+ csr_write(CSR_SCRATCH, context->scratch);
+ csr_write(CSR_TVEC, context->tvec);
+ csr_write(CSR_IE, context->ie);
+
+#ifdef CONFIG_MMU
+ csr_write(CSR_SATP, context->satp);
+#endif
+}
+
+int cpu_suspend(unsigned long arg,
+ int (*finish)(unsigned long arg,
+ unsigned long entry,
+ unsigned long context))
+{
+ int rc = 0;
+ struct suspend_context context = { 0 };
+
+ /* Finisher should be non-NULL */
+ if (!finish)
+ return -EINVAL;
+
+ /* Save additional CSRs*/
+ suspend_save_csrs(&context);
+
+ /*
+ * Function graph tracer state gets incosistent when the kernel
+ * calls functions that never return (aka finishers) hence disable
+ * graph tracing during their execution.
+ */
+ pause_graph_tracing();
+
+ /* Save context on stack */
+ if (__cpu_suspend_enter(&context)) {
+ /* Call the finisher */
+ rc = finish(arg, __pa_symbol(__cpu_resume_enter),
+ (ulong)&context);
+
+ /*
+ * Should never reach here, unless the suspend finisher
+ * fails. Successful cpu_suspend() should return from
+ * __cpu_resume_entry()
+ */
+ if (!rc)
+ rc = -EOPNOTSUPP;
+ }
+
+ /* Enable function graph tracer */
+ unpause_graph_tracing();
+
+ /* Restore additional CSRs */
+ suspend_restore_csrs(&context);
+
+ return rc;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ */
+
+#include <linux/linkage.h>
+#include <asm/asm.h>
+#include <asm/asm-offsets.h>
+#include <asm/csr.h>
+
+ .text
+ .altmacro
+ .option norelax
+
+ENTRY(__cpu_suspend_enter)
+ /* Save registers (except A0 and T0-T6) */
+ REG_S ra, (SUSPEND_CONTEXT_REGS + PT_RA)(a0)
+ REG_S sp, (SUSPEND_CONTEXT_REGS + PT_SP)(a0)
+ REG_S gp, (SUSPEND_CONTEXT_REGS + PT_GP)(a0)
+ REG_S tp, (SUSPEND_CONTEXT_REGS + PT_TP)(a0)
+ REG_S s0, (SUSPEND_CONTEXT_REGS + PT_S0)(a0)
+ REG_S s1, (SUSPEND_CONTEXT_REGS + PT_S1)(a0)
+ REG_S a1, (SUSPEND_CONTEXT_REGS + PT_A1)(a0)
+ REG_S a2, (SUSPEND_CONTEXT_REGS + PT_A2)(a0)
+ REG_S a3, (SUSPEND_CONTEXT_REGS + PT_A3)(a0)
+ REG_S a4, (SUSPEND_CONTEXT_REGS + PT_A4)(a0)
+ REG_S a5, (SUSPEND_CONTEXT_REGS + PT_A5)(a0)
+ REG_S a6, (SUSPEND_CONTEXT_REGS + PT_A6)(a0)
+ REG_S a7, (SUSPEND_CONTEXT_REGS + PT_A7)(a0)
+ REG_S s2, (SUSPEND_CONTEXT_REGS + PT_S2)(a0)
+ REG_S s3, (SUSPEND_CONTEXT_REGS + PT_S3)(a0)
+ REG_S s4, (SUSPEND_CONTEXT_REGS + PT_S4)(a0)
+ REG_S s5, (SUSPEND_CONTEXT_REGS + PT_S5)(a0)
+ REG_S s6, (SUSPEND_CONTEXT_REGS + PT_S6)(a0)
+ REG_S s7, (SUSPEND_CONTEXT_REGS + PT_S7)(a0)
+ REG_S s8, (SUSPEND_CONTEXT_REGS + PT_S8)(a0)
+ REG_S s9, (SUSPEND_CONTEXT_REGS + PT_S9)(a0)
+ REG_S s10, (SUSPEND_CONTEXT_REGS + PT_S10)(a0)
+ REG_S s11, (SUSPEND_CONTEXT_REGS + PT_S11)(a0)
+
+ /* Save CSRs */
+ csrr t0, CSR_EPC
+ REG_S t0, (SUSPEND_CONTEXT_REGS + PT_EPC)(a0)
+ csrr t0, CSR_STATUS
+ REG_S t0, (SUSPEND_CONTEXT_REGS + PT_STATUS)(a0)
+ csrr t0, CSR_TVAL
+ REG_S t0, (SUSPEND_CONTEXT_REGS + PT_BADADDR)(a0)
+ csrr t0, CSR_CAUSE
+ REG_S t0, (SUSPEND_CONTEXT_REGS + PT_CAUSE)(a0)
+
+ /* Return non-zero value */
+ li a0, 1
+
+ /* Return to C code */
+ ret
+END(__cpu_suspend_enter)
+
+ENTRY(__cpu_resume_enter)
+ /* Load the global pointer */
+ .option push
+ .option norelax
+ la gp, __global_pointer$
+ .option pop
+
+#ifdef CONFIG_MMU
+ /* Save A0 and A1 */
+ add t0, a0, zero
+ add t1, a1, zero
+
+ /* Enable MMU */
+ la a0, swapper_pg_dir
+ XIP_FIXUP_OFFSET a0
+ call relocate_enable_mmu
+
+ /* Restore A0 and A1 */
+ add a0, t0, zero
+ add a1, t1, zero
+#endif
+
+ /* Make A0 point to suspend context */
+ add a0, a1, zero
+
+ /* Restore CSRs */
+ REG_L t0, (SUSPEND_CONTEXT_REGS + PT_EPC)(a0)
+ csrw CSR_EPC, t0
+ REG_L t0, (SUSPEND_CONTEXT_REGS + PT_STATUS)(a0)
+ csrw CSR_STATUS, t0
+ REG_L t0, (SUSPEND_CONTEXT_REGS + PT_BADADDR)(a0)
+ csrw CSR_TVAL, t0
+ REG_L t0, (SUSPEND_CONTEXT_REGS + PT_CAUSE)(a0)
+ csrw CSR_CAUSE, t0
+
+ /* Restore registers (except A0 and T0-T6) */
+ REG_L ra, (SUSPEND_CONTEXT_REGS + PT_RA)(a0)
+ REG_L sp, (SUSPEND_CONTEXT_REGS + PT_SP)(a0)
+ REG_L gp, (SUSPEND_CONTEXT_REGS + PT_GP)(a0)
+ REG_L tp, (SUSPEND_CONTEXT_REGS + PT_TP)(a0)
+ REG_L s0, (SUSPEND_CONTEXT_REGS + PT_S0)(a0)
+ REG_L s1, (SUSPEND_CONTEXT_REGS + PT_S1)(a0)
+ REG_L a1, (SUSPEND_CONTEXT_REGS + PT_A1)(a0)
+ REG_L a2, (SUSPEND_CONTEXT_REGS + PT_A2)(a0)
+ REG_L a3, (SUSPEND_CONTEXT_REGS + PT_A3)(a0)
+ REG_L a4, (SUSPEND_CONTEXT_REGS + PT_A4)(a0)
+ REG_L a5, (SUSPEND_CONTEXT_REGS + PT_A5)(a0)
+ REG_L a6, (SUSPEND_CONTEXT_REGS + PT_A6)(a0)
+ REG_L a7, (SUSPEND_CONTEXT_REGS + PT_A7)(a0)
+ REG_L s2, (SUSPEND_CONTEXT_REGS + PT_S2)(a0)
+ REG_L s3, (SUSPEND_CONTEXT_REGS + PT_S3)(a0)
+ REG_L s4, (SUSPEND_CONTEXT_REGS + PT_S4)(a0)
+ REG_L s5, (SUSPEND_CONTEXT_REGS + PT_S5)(a0)
+ REG_L s6, (SUSPEND_CONTEXT_REGS + PT_S6)(a0)
+ REG_L s7, (SUSPEND_CONTEXT_REGS + PT_S7)(a0)
+ REG_L s8, (SUSPEND_CONTEXT_REGS + PT_S8)(a0)
+ REG_L s9, (SUSPEND_CONTEXT_REGS + PT_S9)(a0)
+ REG_L s10, (SUSPEND_CONTEXT_REGS + PT_S10)(a0)
+ REG_L s11, (SUSPEND_CONTEXT_REGS + PT_S11)(a0)
+
+ /* Return zero value */
+ add a0, zero, zero
+
+ /* Return to C code */
+ ret
+END(__cpu_resume_enter)
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ * Atish Patra <atish.patra@wdc.com>
+ */
+
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/kvm_host.h>
+#include <asm/csr.h>
+#include <asm/sbi.h>
+#include <asm/kvm_vcpu_sbi.h>
+
+static int kvm_sbi_hsm_vcpu_start(struct kvm_vcpu *vcpu)
+{
+ struct kvm_cpu_context *reset_cntx;
+ struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
+ struct kvm_vcpu *target_vcpu;
+ unsigned long target_vcpuid = cp->a0;
+
+ target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, target_vcpuid);
+ if (!target_vcpu)
+ return -EINVAL;
+ if (!target_vcpu->arch.power_off)
+ return -EALREADY;
+
+ reset_cntx = &target_vcpu->arch.guest_reset_context;
+ /* start address */
+ reset_cntx->sepc = cp->a1;
+ /* target vcpu id to start */
+ reset_cntx->a0 = target_vcpuid;
+ /* private data passed from kernel */
+ reset_cntx->a1 = cp->a2;
+ kvm_make_request(KVM_REQ_VCPU_RESET, target_vcpu);
+
+ kvm_riscv_vcpu_power_on(target_vcpu);
+
+ return 0;
+}
+
+static int kvm_sbi_hsm_vcpu_stop(struct kvm_vcpu *vcpu)
+{
+ if (vcpu->arch.power_off)
+ return -EINVAL;
+
+ kvm_riscv_vcpu_power_off(vcpu);
+
+ return 0;
+}
+
+static int kvm_sbi_hsm_vcpu_get_status(struct kvm_vcpu *vcpu)
+{
+ struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
+ unsigned long target_vcpuid = cp->a0;
+ struct kvm_vcpu *target_vcpu;
+
+ target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, target_vcpuid);
+ if (!target_vcpu)
+ return -EINVAL;
+ if (!target_vcpu->arch.power_off)
+ return SBI_HSM_STATE_STARTED;
+ else
+ return SBI_HSM_STATE_STOPPED;
+}
+
+static int kvm_sbi_ext_hsm_handler(struct kvm_vcpu *vcpu, struct kvm_run *run,
+ unsigned long *out_val,
+ struct kvm_cpu_trap *utrap,
+ bool *exit)
+{
+ int ret = 0;
+ struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
+ struct kvm *kvm = vcpu->kvm;
+ unsigned long funcid = cp->a6;
+
+ switch (funcid) {
+ case SBI_EXT_HSM_HART_START:
+ mutex_lock(&kvm->lock);
+ ret = kvm_sbi_hsm_vcpu_start(vcpu);
+ mutex_unlock(&kvm->lock);
+ break;
+ case SBI_EXT_HSM_HART_STOP:
+ ret = kvm_sbi_hsm_vcpu_stop(vcpu);
+ break;
+ case SBI_EXT_HSM_HART_STATUS:
+ ret = kvm_sbi_hsm_vcpu_get_status(vcpu);
+ if (ret >= 0) {
+ *out_val = ret;
+ ret = 0;
+ }
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+const struct kvm_vcpu_sbi_extension vcpu_sbi_ext_hsm = {
+ .extid_start = SBI_EXT_HSM,
+ .extid_end = SBI_EXT_HSM,
+ .handler = kvm_sbi_ext_hsm_handler,
+};
__GFP_KSWAPD_RECLAIM |
__GFP_NOWARN |
__GFP_HIGHMEM |
- __GFP_MOVABLE |
- __GFP_CMA);
+ __GFP_MOVABLE);
if (!handle) {
zcomp_stream_put(zram->comp);
atomic64_inc(&zram->stats.writestall);
handle = zs_malloc(zram->mem_pool, comp_len,
GFP_NOIO | __GFP_HIGHMEM |
- __GFP_MOVABLE | __GFP_CMA);
+ __GFP_MOVABLE);
if (handle)
goto compress_again;
return -ENOMEM;
static u32 share_cnt_uart0_clk_en;
static u32 share_cnt_uart2_clk_en;
static u32 share_cnt_i2c2_clk_en;
+static u32 share_cnt_i2c3_clk_en;
static u32 share_cnt_peri_i2s_clk_en;
static u32 share_cnt_qspi1_clk_en;
static u32 share_cnt_uart1_clk_en;
clks[AONSYS_BUS_CLK] = thead_clk_fixed("aonsys_hclk", 101606400); //from sys_pll, maybe change ?
/* Light Fullmask AP MUX */
- clks[CPU_PLL0_BYPASS] = thead_light_clk_mux_flags("cpu_pll0_bypass", ap_base + 0x4, 30, 1, cpu_pll0_bypass_sels, ARRAY_SIZE(cpu_pll0_bypass_sels), CLK_SET_RATE_PARENT);
- clks[CPU_PLL1_BYPASS] = thead_light_clk_mux_flags("cpu_pll1_bypass", ap_base + 0x14, 30, 1, cpu_pll1_bypass_sels, ARRAY_SIZE(cpu_pll1_bypass_sels), CLK_SET_RATE_PARENT);
- clks[GMAC_PLL_BYPASS] = thead_light_clk_mux_flags("gmac_pll_bypass", ap_base + 0x24, 30, 1, gmac_pll_bypass_sels, ARRAY_SIZE(gmac_pll_bypass_sels), CLK_SET_RATE_PARENT);
- clks[VIDEO_PLL_BYPASS] = thead_light_clk_mux_flags("video_pll_bypass", ap_base + 0x34, 30, 1, video_pll_bypass_sels, ARRAY_SIZE(video_pll_bypass_sels), CLK_SET_RATE_PARENT);
- clks[TEE_PLL_BYPASS] = thead_light_clk_mux_flags("tee_pll_bypass", ap_base + 0x64, 30, 1, tee_pll_bypass_sels, ARRAY_SIZE(tee_pll_bypass_sels), CLK_SET_RATE_PARENT);
- clks[DPU0_PLL_BYPASS] = thead_light_clk_mux_flags("dpu0_pll_bypass", ap_base + 0x44, 30, 1, dpu0_pll_bypass_sels, ARRAY_SIZE(dpu0_pll_bypass_sels), CLK_SET_RATE_PARENT);
- clks[DPU1_PLL_BYPASS] = thead_light_clk_mux_flags("dpu1_pll_bypass", ap_base + 0x54, 30, 1, dpu1_pll_bypass_sels, ARRAY_SIZE(dpu1_pll_bypass_sels), CLK_SET_RATE_PARENT);
-
- clks[AHB2_CPUSYS_HCLK] = thead_light_clk_mux_flags("ahb2_cpusys_hclk", ap_base + 0x120, 5, 1, ahb2_cpusys_hclk_sels, ARRAY_SIZE(ahb2_cpusys_hclk_sels), CLK_SET_RATE_PARENT);
- clks[C910_CCLK_I0] = thead_light_clk_mux_flags("c910_cclk_i0", ap_base + 0x100, 1, 1, c910_cclk_i0_sels, ARRAY_SIZE(c910_cclk_i0_sels), CLK_SET_RATE_PARENT);
- clks[C910_CCLK] = thead_light_clk_mux_flags("c910_cclk", ap_base + 0x100, 0, 1, c910_cclk_sels, ARRAY_SIZE(c910_cclk_sels), CLK_SET_RATE_PARENT);
- clks[CFG_AXI_ACLK] = thead_light_clk_mux_flags("cfg_axi_aclk", ap_base + 0x138, 5, 1, cfg_axi_aclk_sels, ARRAY_SIZE(cfg_axi_aclk_sels), CLK_SET_RATE_PARENT);
+ clks[CPU_PLL0_BYPASS] = thead_light_clk_mux_flags("cpu_pll0_bypass", ap_base + 0x4, 30, 1, cpu_pll0_bypass_sels, ARRAY_SIZE(cpu_pll0_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[CPU_PLL1_BYPASS] = thead_light_clk_mux_flags("cpu_pll1_bypass", ap_base + 0x14, 30, 1, cpu_pll1_bypass_sels, ARRAY_SIZE(cpu_pll1_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[GMAC_PLL_BYPASS] = thead_light_clk_mux_flags("gmac_pll_bypass", ap_base + 0x24, 30, 1, gmac_pll_bypass_sels, ARRAY_SIZE(gmac_pll_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[VIDEO_PLL_BYPASS] = thead_light_clk_mux_flags("video_pll_bypass", ap_base + 0x34, 30, 1, video_pll_bypass_sels, ARRAY_SIZE(video_pll_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[TEE_PLL_BYPASS] = thead_light_clk_mux_flags("tee_pll_bypass", ap_base + 0x64, 30, 1, tee_pll_bypass_sels, ARRAY_SIZE(tee_pll_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[DPU0_PLL_BYPASS] = thead_light_clk_mux_flags("dpu0_pll_bypass", ap_base + 0x44, 30, 1, dpu0_pll_bypass_sels, ARRAY_SIZE(dpu0_pll_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[DPU1_PLL_BYPASS] = thead_light_clk_mux_flags("dpu1_pll_bypass", ap_base + 0x54, 30, 1, dpu1_pll_bypass_sels, ARRAY_SIZE(dpu1_pll_bypass_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+
+ clks[AHB2_CPUSYS_HCLK] = thead_light_clk_mux_flags("ahb2_cpusys_hclk", ap_base + 0x120, 5, 1, ahb2_cpusys_hclk_sels, ARRAY_SIZE(ahb2_cpusys_hclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[C910_CCLK_I0] = thead_light_clk_mux_flags("c910_cclk_i0", ap_base + 0x100, 1, 1, c910_cclk_i0_sels, ARRAY_SIZE(c910_cclk_i0_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[C910_CCLK] = thead_light_clk_mux_flags("c910_cclk", ap_base + 0x100, 0, 1, c910_cclk_sels, ARRAY_SIZE(c910_cclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[CFG_AXI_ACLK] = thead_light_clk_mux_flags("cfg_axi_aclk", ap_base + 0x138, 5, 1, cfg_axi_aclk_sels, ARRAY_SIZE(cfg_axi_aclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
if (teesys)
- clks[TEESYS_HCLK] = thead_light_clk_mux_flags("teesys_hclk", ap_base + 0x1cc, 13, 1, teesys_hclk_sels, ARRAY_SIZE(teesys_hclk_sels), CLK_SET_RATE_PARENT); //just for teesys!!!
-
- clks[PERISYS_AHB_HCLK] = thead_light_clk_mux_flags("perisys_ahb_hclk", ap_base + 0x140, 5, 1, perisys_ahb_hclk_sels, ARRAY_SIZE(perisys_ahb_hclk_sels), CLK_SET_RATE_PARENT);
- clks[CLK_OUT_1] = thead_light_clk_mux_flags("clk_out_1", ap_base + 0x1b4, 4, 1, clk_out_1_sels, ARRAY_SIZE(clk_out_1_sels), CLK_SET_RATE_PARENT);
- clks[CLK_OUT_2] = thead_light_clk_mux_flags("clk_out_2", ap_base + 0x1b8, 4, 1, clk_out_2_sels, ARRAY_SIZE(clk_out_2_sels), CLK_SET_RATE_PARENT);
- clks[CLK_OUT_3] = thead_light_clk_mux_flags("clk_out_3", ap_base + 0x1bc, 4, 1, clk_out_3_sels, ARRAY_SIZE(clk_out_3_sels), CLK_SET_RATE_PARENT);
- clks[CLK_OUT_4] = thead_light_clk_mux_flags("clk_out_4", ap_base + 0x1c0, 4, 1, clk_out_4_sels, ARRAY_SIZE(clk_out_4_sels), CLK_SET_RATE_PARENT);
- clks[PERI_I2S_SRC_CLK] = thead_light_clk_mux_flags("peri_i2s_src_clk", ap_base + 0x1f0, 0, 1, peri_i2s_src_clk_sels, ARRAY_SIZE(peri_i2s_src_clk_sels), CLK_SET_RATE_PARENT);
+ clks[TEESYS_HCLK] = thead_light_clk_mux_flags("teesys_hclk", ap_base + 0x1cc, 13, 1, teesys_hclk_sels, ARRAY_SIZE(teesys_hclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT); //just for teesys!!!
+
+ clks[PERISYS_AHB_HCLK] = thead_light_clk_mux_flags("perisys_ahb_hclk", ap_base + 0x140, 5, 1, perisys_ahb_hclk_sels, ARRAY_SIZE(perisys_ahb_hclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[CLK_OUT_1] = thead_light_clk_mux_flags("clk_out_1", ap_base + 0x1b4, 4, 1, clk_out_1_sels, ARRAY_SIZE(clk_out_1_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[CLK_OUT_2] = thead_light_clk_mux_flags("clk_out_2", ap_base + 0x1b8, 4, 1, clk_out_2_sels, ARRAY_SIZE(clk_out_2_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[CLK_OUT_3] = thead_light_clk_mux_flags("clk_out_3", ap_base + 0x1bc, 4, 1, clk_out_3_sels, ARRAY_SIZE(clk_out_3_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[CLK_OUT_4] = thead_light_clk_mux_flags("clk_out_4", ap_base + 0x1c0, 4, 1, clk_out_4_sels, ARRAY_SIZE(clk_out_4_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[PERI_I2S_SRC_CLK] = thead_light_clk_mux_flags("peri_i2s_src_clk", ap_base + 0x1f0, 0, 1, peri_i2s_src_clk_sels, ARRAY_SIZE(peri_i2s_src_clk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
clks[NPU_CCLK] = thead_light_clk_mux_flags("npu_cclk", ap_base + 0x1c8, 6, 1, npu_cclk_sels, ARRAY_SIZE(npu_cclk_sels), CLK_SET_RATE_PARENT);
- clks[CFG_APB_PCLK] = thead_light_clk_mux_flags("cfg_apb_pclk", ap_base + 0x1c4, 7, 1, cfg_apb_pclk_sels, ARRAY_SIZE(cfg_apb_pclk_sels), CLK_SET_RATE_PARENT);
- clks[UART_SCLK] = thead_light_clk_mux_flags("uart_sclk", ap_base + 0x210, 0, 1, uart_sclk_sels, ARRAY_SIZE(uart_sclk_sels), CLK_SET_RATE_PARENT);
+ clks[CFG_APB_PCLK] = thead_light_clk_mux_flags("cfg_apb_pclk", ap_base + 0x1c4, 7, 1, cfg_apb_pclk_sels, ARRAY_SIZE(cfg_apb_pclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
+ clks[UART_SCLK] = thead_light_clk_mux_flags("uart_sclk", ap_base + 0x210, 0, 1, uart_sclk_sels, ARRAY_SIZE(uart_sclk_sels), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT);
/* Light Fullmask AP Divider */
clks[AHB2_CPUSYS_HCLK_OUT_DIV] = thead_clk_light_divider("ahb2_cpusys_hclk_out_div", "gmac_pll_fout1ph0", ap_base + 0x120, 0, 3, 4, MUX_TYPE_DIV, 2, 7);
/* Light Fullmask PLL FOUT */
clks[GMAC_PLL_FOUT1PH0] = thead_light_clk_fixed_factor("gmac_pll_fout1ph0", "gmac_pll_bypass", 1, 2);
clks[GMAC_PLL_FOUT4] = thead_light_clk_fixed_factor("gmac_pll_fout4", "gmac_pll_bypass", 1, 8);
- clks[VIDEO_PLL_FOUT1PH0] = thead_light_clk_fixed_factor("video_pll_fout1ph0", "video_pll_bybass", 1, 2);
+ clks[VIDEO_PLL_FOUT1PH0] = thead_light_clk_fixed_factor("video_pll_fout1ph0", "video_pll_bypass", 1, 2);
clks[VIDEO_PLL_FOUT4] = thead_light_clk_fixed_factor("video_pll_fout4", "video_pll_bypass", 1, 8);
clks[TEE_PLL_FOUT4] = thead_light_clk_fixed_factor("tee_pll_fout4", "tee_pll_bypass", 1, 8);
clks[CPU_PLL0_FOUT4] = thead_light_clk_fixed_factor("cpu_pll0_fout4", "cpu_pll0_bypass", 1, 8);
clks[QSPI0_SSI_CLK] = thead_light_clk_fixed_factor("qspi0_ssi_clk", "qspi_ssi_clk", 1, 1);
clks[QSPI1_SSI_CLK] = thead_light_clk_fixed_factor("qspi1_ssi_clk", "video_pll_fout1ph0", 1, 1);
clks[SPI_SSI_CLK] = thead_light_clk_fixed_factor("spi_ssi_clk", "video_pll_fout1ph0", 1, 1);
- clks[EMMC_SDIO_REF_CLK] = thead_light_clk_fixed_factor("emmc_sdio_ref_clk", "video_pll_foutpostdiv", 1, 1); /* Note: no mux to select, use default value */
+ clks[EMMC_SDIO_REF_CLK] = thead_light_clk_fixed_factor("emmc_sdio_ref_clk", "video_pll_foutpostdiv", 1, 4); /* Note: base clk is div 4 to 198M*/
clks[PWM_CCLK] = thead_light_clk_fixed_factor("pwm_cclk", "osc_24m", 1, 1);
clks[CHIP_DBG_CCLK] = thead_light_clk_fixed_factor("chip_dbg_cclk", "osc_24m", 1, 1);
clks[GMAC_CCLK] = thead_light_clk_fixed_factor("gmac_cclk", "gmac_pll_fout1ph0", 1, 1);
clks[CLKGEN_UART2_SCLK] = thead_clk_light_gate_shared("clkgen_uart2_sclk", "uart_sclk", ap_base + 0x204, 12, &share_cnt_uart2_clk_en);
clks[CLKGEN_I2C2_PCLK] = thead_clk_light_gate_shared("clkgen_i2c2_pclk", "perisys_apb_pclk", ap_base + 0x204, 3, &share_cnt_i2c2_clk_en);
clks[CLKGEN_I2C2_IC_CLK] = thead_clk_light_gate_shared("clkgen_i2c2_ic_clk", "i2c_ic_clk", ap_base + 0x204, 3, &share_cnt_i2c2_clk_en);
- clks[CLKGEN_I2C3_PCLK] = thead_clk_light_gate_shared("clkgen_i2c3_pclk", "perisys_apb_pclk", ap_base + 0x204, 2, &share_cnt_i2c2_clk_en);
- clks[CLKGEN_I2C3_IC_CLK] = thead_clk_light_gate_shared("clkgen_i2c3_ic_clk", "i2c_ic_clk", ap_base + 0x204, 2, &share_cnt_i2c2_clk_en);
+ clks[CLKGEN_I2C3_PCLK] = thead_clk_light_gate_shared("clkgen_i2c3_pclk", "perisys_apb_pclk", ap_base + 0x204, 2, &share_cnt_i2c3_clk_en);
+ clks[CLKGEN_I2C3_IC_CLK] = thead_clk_light_gate_shared("clkgen_i2c3_ic_clk", "i2c_ic_clk", ap_base + 0x204, 2, &share_cnt_i2c3_clk_en);
clks[CLKGEN_I2S_PCLK] = thead_clk_light_gate_shared("clkgen_i2s_pclk", "perisys_apb_pclk", ap_base + 0x1f0, 1, &share_cnt_peri_i2s_clk_en);
clks[CLKGEN_I2S_SRC_CLK] = thead_clk_light_gate_shared("clkgen_i2s_src_clk", "peri_i2s_src_clk", ap_base + 0x1f0, 1, &share_cnt_peri_i2s_clk_en);
clks[CLKGEN_QSPI1_PCLK] = thead_clk_light_gate_shared("clkgen_qspi1_pclk", "peri2sys_apb_pclk", ap_base + 0x204, 16, &share_cnt_qspi1_clk_en);
unsigned long flags)
{
return clk_register_mux(NULL, name, parents, num_parents,
- flags | CLK_SET_RATE_NO_REPARENT, reg, shift, width, 0,
+ flags , reg, shift, width, 0,
&thead_light_clk_lock);
}
#endif
# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_CLK_LIGHT_FM) += thead-gate.o visys-gate.o vpsys-gate.o vosys-gate.o dspsys-gate.o audiosys-gate.o
+obj-$(CONFIG_CLK_LIGHT_FM) += thead-gate.o visys-gate.o vpsys-gate.o vosys-gate.o dspsys-gate.o audiosys-gate.o miscsys-gate.o
static struct clk *gates[LIGHT_CLKGEN_DSPSYS_CLK_END];
static struct clk_onecell_data clk_gate_data;
+static const char * const dsp0_cclk_sels[] = {"gmac_pll_foutpostdiv", "dspsys_dsp_clk"};
+static const char * const dsp1_cclk_sels[] = {"gmac_pll_foutpostdiv", "dspsys_dsp_clk"};
static int light_dspsys_clk_probe(struct platform_device *pdev)
{
struct regmap *dspsys_regmap, *tee_dspsys_regmap;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
+ struct device_node *np_reg = of_parse_phandle(np, "dspsys-regmap", 0);
+ void __iomem *gate_base;
int ret;
dspsys_regmap = syscon_regmap_lookup_by_phandle(np, "dspsys-regmap");
dev_warn(&pdev->dev, "cannot find regmap for tee dsp system register\n");
tee_dspsys_regmap = NULL;
}
+ gate_base = of_iomap(np_reg,0);
+ // MUX
+ gates[DSPSYS_DSP0_CLK_SWITCH] = thead_light_clk_mux_flags("dspsys_dsp0_clk_switch", gate_base + 0x1c, 0, 1, dsp0_cclk_sels, ARRAY_SIZE(dsp0_cclk_sels), 0);
+ gates[DSPSYS_DSP1_CLK_SWITCH] = thead_light_clk_mux_flags("dspsys_dsp1_clk_switch", gate_base + 0x20, 0, 1, dsp1_cclk_sels, ARRAY_SIZE(dsp1_cclk_sels), 0);
+ // DIV & CDE
+ gates[DSPSYS_DSP_CLK] = thead_light_clk_fixed_factor("dspsys_dsp_clk", "video_pll_foutvco", 1, 3);
+ gates[DSPSYS_DSP0_CLK_CDE] = thead_clk_light_divider("dspsys_dsp0_clk_cde", "dspsys_dsp0_clk_switch", gate_base + 0x0, 0, 3, 4, MUX_TYPE_CDE, 0, 7);
+ gates[DSPSYS_DSP1_CLK_CDE] = thead_clk_light_divider("dspsys_dsp1_clk_cde", "dspsys_dsp1_clk_switch", gate_base + 0x4, 0, 3, 4, MUX_TYPE_CDE, 0, 7);
+
+ // gate
gates[CLKGEN_DSP0_PCLK] = thead_gate_clk_register("clkgen_dsp0_pclk", NULL, dspsys_regmap,
0x24, 0, GATE_NOT_SHARED, NULL, dev);
gates[CLKGEN_DSP1_PCLK] = thead_gate_clk_register("clkgen_dsp1_pclk", NULL, dspsys_regmap,
0x24, 1, GATE_NOT_SHARED, NULL, dev);
- gates[CLKGEN_DSP1_CCLK] = thead_gate_clk_register("clkgen_dsp1_cclk", NULL, dspsys_regmap,
+ gates[CLKGEN_DSP1_CCLK] = thead_gate_clk_register("clkgen_dsp1_cclk", "dspsys_dsp1_clk_cde", dspsys_regmap,
0x24, 2, GATE_NOT_SHARED, NULL, dev);
- gates[CLKGEN_DSP0_CCLK] = thead_gate_clk_register("clkgen_dsp0_cclk", NULL, dspsys_regmap,
+ gates[CLKGEN_DSP0_CCLK] = thead_gate_clk_register("clkgen_dsp0_cclk", "dspsys_dsp0_clk_cde", dspsys_regmap,
0x24, 3, GATE_NOT_SHARED, NULL, dev);
gates[CLKGEN_X2X_DSP2_ACLK_S] = thead_gate_clk_register("clkgen_x2x_dsp2_aclk_s", NULL, dspsys_regmap,
0x24, 4, GATE_NOT_SHARED, NULL, dev);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Alibaba Group Holding Limited.
+ */
+#include <dt-bindings/clock/light-miscsys.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include "clk-gate.h"
+#include "../clk.h"
+static struct clk *gates[CLKGEN_MISCSYS_CLK_END];
+static struct clk_onecell_data clk_gate_data;
+static int light_miscsys_clk_probe(struct platform_device *pdev)
+{
+ struct regmap *miscsys_regmap, *tee_miscsys_regmap = NULL;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ miscsys_regmap = syscon_regmap_lookup_by_phandle(np, "miscsys-regmap");
+ if (IS_ERR(miscsys_regmap)) {
+ dev_err(&pdev->dev, "cannot find regmap for misc system register\n");
+ return PTR_ERR(miscsys_regmap);
+ }
+ tee_miscsys_regmap = syscon_regmap_lookup_by_phandle(np, "tee-miscsys-regmap");
+ if (IS_ERR(tee_miscsys_regmap)) {
+ dev_err(&pdev->dev, "cannot find regmap for tee misc system register\n");
+ return PTR_ERR(tee_miscsys_regmap);
+ }
+ /* we assume that the gate clock is a root clock */
+ gates[CLKGEN_MISCSYS_MISCSYS_ACLK] = thead_gate_clk_register("clkgen_missys_aclk", NULL,
+ miscsys_regmap, 0x100, 0, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_USB3_DRD_CLK] = thead_gate_clk_register("clkgen_usb3_drd_clk", NULL,
+ miscsys_regmap, 0x104, 0, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_USB3_DRD_CTRL_REF_CLK] = thead_gate_clk_register("clkgen_usb3_drd_ctrl_ref_clk", "osc_24m",
+ miscsys_regmap, 0x104, 1, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_USB3_DRD_PHY_REF_CLK] = thead_gate_clk_register("clkgen_usb3_drd_phy_ref_clk", "osc_24m",
+ miscsys_regmap, 0x104, 2, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_USB3_DRD_SUSPEND_CLK] = thead_gate_clk_register("clkgen_usb3_drd_suspend_clk", NULL,
+ miscsys_regmap, 0x104, 3, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_EMMC_CLK] = thead_gate_clk_register("clkgen_emmc_clk", "osc_24m",
+ miscsys_regmap, 0x108, 0, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_SDIO0_CLK] = thead_gate_clk_register("clkgen_sdio0_clk", "osc_24m",
+ miscsys_regmap, 0x10c, 0, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_SDIO1_CLK] = thead_gate_clk_register("clkgen_sdio1_clk", "osc_24m",
+ miscsys_regmap, 0x110, 0, GATE_NOT_SHARED, NULL, dev);
+ if (tee_miscsys_regmap) {
+ gates[CLKGEN_MISCSYS_AHB2_TEESYS_HCLK] = thead_gate_clk_register("clkgen_ahb2_teesys_hclk", NULL,
+ tee_miscsys_regmap, 0x120, 0, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_APB3_TEESYS_HCLK] = thead_gate_clk_register("clkgen_apb3_teesys_hclk", NULL,
+ tee_miscsys_regmap, 0x120, 1, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_AXI4_TEESYS_ACLK] = thead_gate_clk_register("clkgen_axi4_teesys_aclk", NULL,
+ tee_miscsys_regmap, 0x120, 2, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_EIP120SI_CLK] = thead_gate_clk_register("clkgen_eip120si_clk", NULL,
+ tee_miscsys_regmap, 0x120, 3, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_EIP120SII_CLK] = thead_gate_clk_register("clkgen_eip120sii_clk", NULL,
+ tee_miscsys_regmap, 0x120, 4, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_EIP120SIII_CLK] = thead_gate_clk_register("clkgen_eip120siii_clk", NULL,
+ tee_miscsys_regmap, 0x120, 5, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_TEEDMAC_CLK] = thead_gate_clk_register("clkgen_teedmac_clk", NULL,
+ tee_miscsys_regmap, 0x120, 6, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_EIP150B_HCLK] = thead_gate_clk_register("clkgen_eip150b_hclk", NULL,
+ tee_miscsys_regmap, 0x120, 7, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_OCRAM_HCLK] = thead_gate_clk_register("clkgen_ocram_hclk", NULL,
+ tee_miscsys_regmap, 0x120, 8, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_EFUSE_PCLK] = thead_gate_clk_register("clkgen_efuse_pclk", NULL,
+ tee_miscsys_regmap, 0x120, 9, GATE_NOT_SHARED, NULL, dev);
+ gates[CLKGEN_MISCSYS_TEE_SYSREG_PCLK] = thead_gate_clk_register("clkgen_tee_sysreg_pclk", NULL,
+ tee_miscsys_regmap, 0x120, 10, GATE_NOT_SHARED, NULL, dev);
+ }
+ clk_gate_data.clks = gates;
+ clk_gate_data.clk_num = ARRAY_SIZE(gates);
+ ret = of_clk_add_provider(np, of_clk_src_onecell_get, &clk_gate_data);
+ if (ret < 0) {
+ dev_err(dev, "failed to register gate clks for light miscsys\n");
+ goto unregister_clks;
+ }
+ dev_info(dev, "succeed to register miscsys gate clock provider\n");
+ return 0;
+unregister_clks:
+ thead_unregister_clocks(gates, ARRAY_SIZE(gates));
+ return ret;
+}
+static const struct of_device_id miscsys_clk_gate_of_match[] = {
+ { .compatible = "thead,miscsys-gate-controller" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, miscsys_clk_gate_of_match);
+static struct platform_driver light_miscsys_clk_driver = {
+ .probe = light_miscsys_clk_probe,
+ .driver = {
+ .name = "miscsys-clk-gate-provider",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(miscsys_clk_gate_of_match),
+ },
+};
+module_platform_driver(light_miscsys_clk_driver);
+MODULE_AUTHOR("wei.liu <lw312886@linux.alibaba.com>");
+MODULE_AUTHOR("Esther.Z <Esther.Z@linux.alibaba.com>");
+MODULE_DESCRIPTION("Thead Light Fullmask miscsys clock gate provider");
+MODULE_LICENSE("GPL v2");
if (WARN_ON(IS_ERR(gate_base)))
return PTR_ERR(gate_base);
- /* we assume that the gate clock is a root clock */
+ // DIV & CDE
+ gates[LIGHT_VPSYS_G2D_CCLK_DIV] = thead_clk_light_divider("light_vpsys_g2d_cclk_div", "video_pll_foutvco", gate_base + 0x30, 0, 4, 4, MUX_TYPE_DIV, 3, 9);
+
+ /* G2D clock configuration : Completed the upward configuration of CCLK */
gates[LIGHT_VPSYS_G2D_PCLK] = thead_clk_light_gate_shared("clkgen_vpsys_g2d_pclk", NULL,
gate_base + 0x20, 3, &share_cnt_g2d_clk_en);
gates[LIGHT_VPSYS_G2D_ACLK] = thead_clk_light_gate_shared("clkgen_vpsys_g2d_aclk", NULL,
gate_base + 0x20, 3, &share_cnt_g2d_clk_en);
- gates[LIGHT_VPSYS_G2D_CCLK] = thead_clk_light_gate_shared("clkgen_vpsys_g2d_cclk", NULL,
+ gates[LIGHT_VPSYS_G2D_CCLK] = thead_clk_light_gate_shared("clkgen_vpsys_g2d_cclk", "light_vpsys_g2d_cclk_div",
gate_base + 0x20, 3, &share_cnt_g2d_clk_en);
-
+ /* we assume that the gate clock is a root clock */
gates[LIGHT_VPSYS_FCE_PCLK] = thead_clk_light_gate_shared("clkgen_vpsys_fce_pclk", NULL,
gate_base + 0x20, 2, &share_cnt_fce_clk_en);
gates[LIGHT_VPSYS_FCE_ACLK] = thead_clk_light_gate_shared("clkgen_vpsys_fce_aclk", NULL,
#define LIGHT_C910_BUS_CLK_DIV_RATIO_2 0x100
#define LIGHT_C910_BUS_CLK_DIV_RATIO_3 0x200
+#define LIGHT_CPU_PLL_IDX(x) (x)
+#define LIGHT_CPU_PLL_COUNT 2
+
static int num_clks;
static struct clk_bulk_data clks[] = {
{ .id = "c910_cclk" },
static struct device *cpu_dev;
static struct cpufreq_frequency_table *freq_table;
static unsigned int max_freq;
+static unsigned int min_freq;
static unsigned int transition_latency;
static void __iomem *ap_sys_reg;
static bool light_dvfs_sv = false;
static u32 *light_dvddm_volt;
static u32 soc_opp_count = 0;
+static int _light_get_pllid(void)
+{
+ int ret;
+
+ if (!strcmp(__clk_get_name(clk_get_parent(clks[LIGHT_C910_CCLK].clk)),
+ __clk_get_name(clks[LIGHT_C910_CCLK_I0].clk))) // pll index 0
+ ret = LIGHT_CPU_PLL_IDX(0);
+ else // pll index 1
+ ret = LIGHT_CPU_PLL_IDX(1);
+
+ return ret;
+}
+
+static int _light_switch_pllid(int pllid, int target_freq)
+{
+ pr_debug("[%s] switchto pll[%d], freq[%u]\n", __func__, pllid, target_freq);
+ if (pllid == LIGHT_CPU_PLL_IDX(1)) {
+ clk_prepare_enable(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
+ clk_set_rate(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk, target_freq * 1000);
+ clk_set_parent(clks[LIGHT_C910_CCLK].clk, clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
+ udelay(1);
+ clk_disable_unprepare(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk);
+ } else {
+ clk_prepare_enable(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk);
+ clk_set_rate(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk, target_freq * 1000);
+ clk_set_parent(clks[LIGHT_C910_CCLK].clk, clks[LIGHT_C910_CCLK_I0].clk);
+ udelay(1);
+ clk_disable_unprepare(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
+ }
+
+ return 0;
+}
+
+
static int light_set_target(struct cpufreq_policy *policy, unsigned int index)
{
struct dev_pm_opp *opp;
}
}
- if (!strcmp(__clk_get_name(clk_get_parent(clks[LIGHT_C910_CCLK].clk)),
- __clk_get_name(clks[LIGHT_C910_CCLK_I0].clk))) {
- clk_prepare_enable(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
- clk_set_rate(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk, new_freq * 1000);
- ret = clk_set_parent(clks[LIGHT_C910_CCLK].clk, clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
- udelay(1);
- clk_disable_unprepare(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk);
- } else {
- clk_prepare_enable(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk);
- clk_set_rate(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk, new_freq * 1000);
- ret = clk_set_parent(clks[LIGHT_C910_CCLK].clk, clks[LIGHT_C910_CCLK_I0].clk);
- udelay(1);
- clk_disable_unprepare(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
- }
+ /* switch pll */
+ _light_switch_pllid((_light_get_pllid()+1)&(LIGHT_CPU_PLL_COUNT-1), new_freq);
/*add delay for clk-switch*/
udelay(1);
return 0;
}
+static int light_cpufreq_suspend(struct cpufreq_policy *policy)
+{
+ int ret;
+ int index;
+
+ pr_debug("%s: cpu: %d, %u KHz to %u KHz\n",
+ __func__, policy->cpu, policy->cur, policy->suspend_freq);
+
+ ret = cpufreq_generic_suspend(policy);
+ if (ret) {
+ pr_err("%s: failed\n", __func__);
+ return ret;
+ }
+
+ /*
+ * Only CPU PLL0 would be active after STR resume. We should switch
+ * CPU PLL to be PLL0 after policy stopped.
+ */
+ if (_light_get_pllid() == LIGHT_CPU_PLL_IDX(1))
+ _light_switch_pllid(LIGHT_CPU_PLL_IDX(0), policy->suspend_freq);
+
+ return 0;
+}
+
+static int light_cpufreq_resume(struct cpufreq_policy *policy)
+{
+ return 0;
+}
+
static int light_cpufreq_init(struct cpufreq_policy *policy)
{
policy->clk = clks[LIGHT_C910_CCLK].clk;
.init = light_cpufreq_init,
.name = "light-cpufreq",
.attr = cpufreq_generic_attr,
- .suspend = cpufreq_generic_suspend,
+ .suspend = light_cpufreq_suspend,
+ .resume = light_cpufreq_resume,
};
static int light_cpufreq_pm_notify(struct notifier_block *nb,
* set CPU PLL1's frequency as minimum to compatible voltage
* becarefull if the PLL1 is serving the cpu core, swith to PLL0 first
*/
- if (strcmp(__clk_get_name(clk_get_parent(clks[LIGHT_C910_CCLK].clk)),
- __clk_get_name(clks[LIGHT_C910_CCLK_I0].clk))) {
- pr_debug("[%s,%d]\n", __func__, __LINE__);
- clk_prepare_enable(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk);
- clk_set_rate(clks[LIGHT_CPU_PLL0_FOUTPOSTDIV].clk, policy->min * 1000);
- udelay(1);
- clk_set_parent(clks[LIGHT_C910_CCLK].clk, clks[LIGHT_C910_CCLK_I0].clk);
-
+ if (_light_get_pllid() == LIGHT_CPU_PLL_IDX(1)) {
pr_debug("[%s,%d]\n", __func__, __LINE__);
+ _light_switch_pllid(LIGHT_CPU_PLL_IDX(0), policy->min);
}
pr_debug("[%s,%d]\n", __func__, __LINE__);
* set the CPU PLL1's frequency as minimum in advance, otherwise the
* system may crash in crash kernel stage.
*/
- clk_prepare_enable(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk);
- clk_set_rate(clks[LIGHT_CPU_PLL1_FOUTPOSTDIV].clk, policy->min * 1000);
- udelay(1);
+ _light_switch_pllid(LIGHT_CPU_PLL_IDX(1), policy->min);
pr_info("finish to execute cpufreq notifier callback on panic\n");
transition_latency = CPUFREQ_ETERNAL;
max_freq = freq_table[--num].frequency;
+ min_freq = freq_table[0].frequency;
ret = cpufreq_register_driver(&light_cpufreq_driver);
if (ret) {
config DT_IDLE_STATES
bool
+config DT_IDLE_GENPD
+ depends on PM_GENERIC_DOMAINS_OF
+ bool
+
menu "ARM CPU Idle Drivers"
depends on ARM || ARM64
source "drivers/cpuidle/Kconfig.arm"
bool "PSCI CPU idle Domain"
depends on ARM_PSCI_CPUIDLE
depends on PM_GENERIC_DOMAINS_OF
+ select DT_IDLE_GENPD
default y
help
Select this to enable the PSCI based CPUidle driver to use PM domains,
Select this option to enable processor idle state management
through cpuidle subsystem.
+config RISCV_SBI_CPUIDLE
+ bool "RISC-V SBI CPU idle Driver"
+ depends on RISCV_SBI
+ select DT_IDLE_STATES
+ select CPU_IDLE_MULTIPLE_DRIVERS
+ select DT_IDLE_GENPD if PM_GENERIC_DOMAINS_OF
+ help
+ Select this option to enable RISC-V SBI firmware based CPU idle
+ driver for RISC-V systems. This drivers also supports hierarchical
+ DT based layout of the idle state.
obj-y += cpuidle.o driver.o governor.o sysfs.o governors/
obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o
obj-$(CONFIG_DT_IDLE_STATES) += dt_idle_states.o
+obj-$(CONFIG_DT_IDLE_GENPD) += dt_idle_genpd.o
obj-$(CONFIG_ARCH_HAS_CPU_RELAX) += poll_state.o
obj-$(CONFIG_HALTPOLL_CPUIDLE) += cpuidle-haltpoll.o
# POWERPC drivers
obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o
obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o
+
###############################################################################
# RISC-V drivers
obj-$(CONFIG_LIGHT_CPUIDLE) += cpuidle-light.o
+obj-$(CONFIG_RISCV_SBI_CPUIDLE) += cpuidle-riscv-sbi.o
return 0;
}
-static int psci_pd_parse_state_nodes(struct genpd_power_state *states,
- int state_count)
-{
- int i, ret;
- u32 psci_state, *psci_state_buf;
-
- for (i = 0; i < state_count; i++) {
- ret = psci_dt_parse_state_node(to_of_node(states[i].fwnode),
- &psci_state);
- if (ret)
- goto free_state;
-
- psci_state_buf = kmalloc(sizeof(u32), GFP_KERNEL);
- if (!psci_state_buf) {
- ret = -ENOMEM;
- goto free_state;
- }
- *psci_state_buf = psci_state;
- states[i].data = psci_state_buf;
- }
-
- return 0;
-
-free_state:
- i--;
- for (; i >= 0; i--)
- kfree(states[i].data);
- return ret;
-}
-
-static int psci_pd_parse_states(struct device_node *np,
- struct genpd_power_state **states, int *state_count)
-{
- int ret;
-
- /* Parse the domain idle states. */
- ret = of_genpd_parse_idle_states(np, states, state_count);
- if (ret)
- return ret;
-
- /* Fill out the PSCI specifics for each found state. */
- ret = psci_pd_parse_state_nodes(*states, *state_count);
- if (ret)
- kfree(*states);
-
- return ret;
-}
-
-static void psci_pd_free_states(struct genpd_power_state *states,
- unsigned int state_count)
-{
- int i;
-
- for (i = 0; i < state_count; i++)
- kfree(states[i].data);
- kfree(states);
-}
-
static int psci_pd_init(struct device_node *np, bool use_osi)
{
struct generic_pm_domain *pd;
struct psci_pd_provider *pd_provider;
struct dev_power_governor *pd_gov;
- struct genpd_power_state *states = NULL;
int ret = -ENOMEM, state_count = 0;
- pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ pd = dt_idle_pd_alloc(np, psci_dt_parse_state_node);
if (!pd)
goto out;
if (!pd_provider)
goto free_pd;
- pd->name = kasprintf(GFP_KERNEL, "%pOF", np);
- if (!pd->name)
- goto free_pd_prov;
-
- /*
- * Parse the domain idle states and let genpd manage the state selection
- * for those being compatible with "domain-idle-state".
- */
- ret = psci_pd_parse_states(np, &states, &state_count);
- if (ret)
- goto free_name;
-
- pd->free_states = psci_pd_free_states;
- pd->name = kbasename(pd->name);
- pd->states = states;
- pd->state_count = state_count;
pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
/* Allow power off when OSI has been successfully enabled. */
pd_gov = state_count > 0 ? &pm_domain_cpu_gov : NULL;
ret = pm_genpd_init(pd, pd_gov, false);
- if (ret) {
- psci_pd_free_states(states, state_count);
- goto free_name;
- }
+ if (ret)
+ goto free_pd_prov;
ret = of_genpd_add_provider_simple(np, pd);
if (ret)
remove_pd:
pm_genpd_remove(pd);
-free_name:
- kfree(pd->name);
free_pd_prov:
kfree(pd_provider);
free_pd:
- kfree(pd);
+ dt_idle_pd_free(pd);
out:
pr_err("failed to init PM domain ret=%d %pOF\n", ret, np);
return ret;
}
}
-static int psci_pd_init_topology(struct device_node *np)
-{
- struct device_node *node;
- struct of_phandle_args child, parent;
- int ret;
-
- for_each_child_of_node(np, node) {
- if (of_parse_phandle_with_args(node, "power-domains",
- "#power-domain-cells", 0, &parent))
- continue;
-
- child.np = node;
- child.args_count = 0;
- ret = of_genpd_add_subdomain(&parent, &child);
- of_node_put(parent.np);
- if (ret) {
- of_node_put(node);
- return ret;
- }
- }
-
- return 0;
-}
-
static bool psci_pd_try_set_osi_mode(void)
{
int ret;
goto no_pd;
/* Link genpd masters/subdomains to model the CPU topology. */
- ret = psci_pd_init_topology(np);
+ ret = dt_idle_pd_init_topology(np);
if (ret)
goto remove_pd;
return platform_driver_register(&psci_cpuidle_domain_driver);
}
subsys_initcall(psci_idle_init_domains);
-
-struct device *psci_dt_attach_cpu(int cpu)
-{
- struct device *dev;
-
- dev = dev_pm_domain_attach_by_name(get_cpu_device(cpu), "psci");
- if (IS_ERR_OR_NULL(dev))
- return dev;
-
- pm_runtime_irq_safe(dev);
- if (cpu_online(cpu))
- pm_runtime_get_sync(dev);
-
- dev_pm_syscore_device(dev, true);
-
- return dev;
-}
-
-void psci_dt_detach_cpu(struct device *dev)
-{
- if (IS_ERR_OR_NULL(dev))
- return;
-
- dev_pm_domain_detach(dev, false);
-}
int psci_dt_parse_state_node(struct device_node *np, u32 *state);
#ifdef CONFIG_ARM_PSCI_CPUIDLE_DOMAIN
-struct device *psci_dt_attach_cpu(int cpu);
-void psci_dt_detach_cpu(struct device *dev);
+
+#include "dt_idle_genpd.h"
+
+static inline struct device *psci_dt_attach_cpu(int cpu)
+{
+ return dt_idle_attach_cpu(cpu, "psci");
+}
+
+static inline void psci_dt_detach_cpu(struct device *dev)
+{
+ dt_idle_detach_cpu(dev);
+}
+
#else
static inline struct device *psci_dt_attach_cpu(int cpu) { return NULL; }
static inline void psci_dt_detach_cpu(struct device *dev) { }
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RISC-V SBI CPU idle driver.
+ *
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ */
+
+#define pr_fmt(fmt) "cpuidle-riscv-sbi: " fmt
+
+#include <linux/cpuidle.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_pm.h>
+#include <linux/cpu_cooling.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <asm/cpuidle.h>
+#include <asm/sbi.h>
+#include <asm/suspend.h>
+
+#include "dt_idle_states.h"
+#include "dt_idle_genpd.h"
+
+struct sbi_cpuidle_data {
+ u32 *states;
+ struct device *dev;
+};
+
+struct sbi_domain_state {
+ bool available;
+ u32 state;
+};
+
+static DEFINE_PER_CPU_READ_MOSTLY(struct sbi_cpuidle_data, sbi_cpuidle_data);
+static DEFINE_PER_CPU(struct sbi_domain_state, domain_state);
+static bool sbi_cpuidle_use_osi;
+static bool sbi_cpuidle_use_cpuhp;
+static bool sbi_cpuidle_pd_allow_domain_state;
+
+extern void arch_cpu_idle(void);
+
+static inline void sbi_set_domain_state(u32 state)
+{
+ struct sbi_domain_state *data = this_cpu_ptr(&domain_state);
+
+ data->available = true;
+ data->state = state;
+}
+
+static inline u32 sbi_get_domain_state(void)
+{
+ struct sbi_domain_state *data = this_cpu_ptr(&domain_state);
+
+ return data->state;
+}
+
+static inline void sbi_clear_domain_state(void)
+{
+ struct sbi_domain_state *data = this_cpu_ptr(&domain_state);
+
+ data->available = false;
+}
+
+static inline bool sbi_is_domain_state_available(void)
+{
+ struct sbi_domain_state *data = this_cpu_ptr(&domain_state);
+
+ return data->available;
+}
+
+/* Actual code that puts the SoC in different idle states */
+static int light_enter_idle(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
+{
+ arch_cpu_idle();
+ return index;
+}
+
+static int sbi_suspend_finisher(unsigned long suspend_type,
+ unsigned long resume_addr,
+ unsigned long opaque)
+{
+ struct sbiret ret;
+
+ ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_SUSPEND,
+ suspend_type, resume_addr, opaque, 0, 0, 0);
+
+ return (ret.error) ? sbi_err_map_linux_errno(ret.error) : 0;
+}
+
+static int sbi_suspend(u32 state)
+{
+ if (state & SBI_HSM_SUSP_NON_RET_BIT)
+ return cpu_suspend(state, sbi_suspend_finisher);
+ else
+ return sbi_suspend_finisher(state, 0, 0);
+}
+
+static int sbi_cpuidle_enter_state(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int idx)
+{
+ u32 *states = __this_cpu_read(sbi_cpuidle_data.states);
+
+ return CPU_PM_CPU_IDLE_ENTER_PARAM(sbi_suspend, idx, states[idx]);
+}
+
+static int __sbi_enter_domain_idle_state(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int idx,
+ bool s2idle)
+{
+ struct sbi_cpuidle_data *data = this_cpu_ptr(&sbi_cpuidle_data);
+ u32 *states = data->states;
+ struct device *pd_dev = data->dev;
+ u32 state;
+ int ret;
+
+ ret = cpu_pm_enter();
+ if (ret)
+ return -1;
+
+ /* Do runtime PM to manage a hierarchical CPU toplogy. */
+ rcu_irq_enter_irqson();
+ if (s2idle)
+ dev_pm_genpd_suspend(pd_dev);
+ else
+ pm_runtime_put_sync_suspend(pd_dev);
+ rcu_irq_exit_irqson();
+
+ if (sbi_is_domain_state_available())
+ state = sbi_get_domain_state();
+ else
+ state = states[idx];
+
+ ret = sbi_suspend(state) ? -1 : idx;
+
+ rcu_irq_enter_irqson();
+ if (s2idle)
+ dev_pm_genpd_resume(pd_dev);
+ else
+ pm_runtime_get_sync(pd_dev);
+ rcu_irq_exit_irqson();
+
+ cpu_pm_exit();
+
+ /* Clear the domain state to start fresh when back from idle. */
+ sbi_clear_domain_state();
+ return ret;
+}
+
+static int sbi_enter_domain_idle_state(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int idx)
+{
+ return __sbi_enter_domain_idle_state(dev, drv, idx, false);
+}
+
+static int sbi_enter_s2idle_domain_idle_state(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int idx)
+{
+ return __sbi_enter_domain_idle_state(dev, drv, idx, true);
+}
+
+static int sbi_cpuidle_cpuhp_up(unsigned int cpu)
+{
+ struct device *pd_dev = __this_cpu_read(sbi_cpuidle_data.dev);
+
+ if (pd_dev)
+ pm_runtime_get_sync(pd_dev);
+
+ return 0;
+}
+
+static int sbi_cpuidle_cpuhp_down(unsigned int cpu)
+{
+ struct device *pd_dev = __this_cpu_read(sbi_cpuidle_data.dev);
+
+ if (pd_dev) {
+ pm_runtime_put_sync(pd_dev);
+ /* Clear domain state to start fresh at next online. */
+ sbi_clear_domain_state();
+ }
+
+ return 0;
+}
+
+static void sbi_idle_init_cpuhp(void)
+{
+ int err;
+
+ if (!sbi_cpuidle_use_cpuhp)
+ return;
+
+ err = cpuhp_setup_state_nocalls(CPUHP_AP_CPU_PM_STARTING,
+ "cpuidle/sbi:online",
+ sbi_cpuidle_cpuhp_up,
+ sbi_cpuidle_cpuhp_down);
+ if (err)
+ pr_warn("Failed %d while setup cpuhp state\n", err);
+}
+
+static const struct of_device_id sbi_cpuidle_state_match[] = {
+ { .compatible = "riscv,idle-state",
+ .data = sbi_cpuidle_enter_state },
+ { },
+};
+
+static bool sbi_suspend_state_is_valid(u32 state)
+{
+ if (state > SBI_HSM_SUSPEND_RET_DEFAULT &&
+ state < SBI_HSM_SUSPEND_RET_PLATFORM)
+ return false;
+ if (state > SBI_HSM_SUSPEND_NON_RET_DEFAULT &&
+ state < SBI_HSM_SUSPEND_NON_RET_PLATFORM)
+ return false;
+ return true;
+}
+
+static int sbi_dt_parse_state_node(struct device_node *np, u32 *state)
+{
+ int err = of_property_read_u32(np, "riscv,sbi-suspend-param", state);
+
+ if (err) {
+ pr_warn("%pOF missing riscv,sbi-suspend-param property\n", np);
+ return err;
+ }
+
+ if (!sbi_suspend_state_is_valid(*state)) {
+ pr_warn("Invalid SBI suspend state %#x\n", *state);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sbi_dt_cpu_init_topology(struct cpuidle_driver *drv,
+ struct sbi_cpuidle_data *data,
+ unsigned int state_count, int cpu)
+{
+ /* Currently limit the hierarchical topology to be used in OSI mode. */
+ if (!sbi_cpuidle_use_osi)
+ return 0;
+
+ data->dev = dt_idle_attach_cpu(cpu, "sbi");
+ if (IS_ERR_OR_NULL(data->dev))
+ return PTR_ERR_OR_ZERO(data->dev);
+
+ /*
+ * Using the deepest state for the CPU to trigger a potential selection
+ * of a shared state for the domain, assumes the domain states are all
+ * deeper states.
+ */
+ drv->states[state_count - 1].enter = sbi_enter_domain_idle_state;
+ drv->states[state_count - 1].enter_s2idle =
+ sbi_enter_s2idle_domain_idle_state;
+ sbi_cpuidle_use_cpuhp = true;
+
+ return 0;
+}
+
+static int sbi_cpuidle_dt_init_states(struct device *dev,
+ struct cpuidle_driver *drv,
+ unsigned int cpu,
+ unsigned int state_count)
+{
+ struct sbi_cpuidle_data *data = per_cpu_ptr(&sbi_cpuidle_data, cpu);
+ struct device_node *state_node;
+ struct device_node *cpu_node;
+ u32 *states;
+ int i, ret;
+
+ cpu_node = of_cpu_device_node_get(cpu);
+ if (!cpu_node)
+ return -ENODEV;
+
+ states = devm_kcalloc(dev, state_count, sizeof(*states), GFP_KERNEL);
+ if (!states) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ /* Parse SBI specific details from state DT nodes */
+ for (i = 1; i < state_count; i++) {
+ state_node = of_get_cpu_state_node(cpu_node, i - 1);
+ if (!state_node)
+ break;
+
+ ret = sbi_dt_parse_state_node(state_node, &states[i]);
+ of_node_put(state_node);
+
+ if (ret)
+ return ret;
+
+ pr_debug("sbi-state %#x index %d\n", states[i], i);
+ }
+ if (i != state_count) {
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ /* Initialize optional data, used for the hierarchical topology. */
+ ret = sbi_dt_cpu_init_topology(drv, data, state_count, cpu);
+ if (ret < 0)
+ return ret;
+
+ /* Store states in the per-cpu struct. */
+ data->states = states;
+
+fail:
+ of_node_put(cpu_node);
+
+ return ret;
+}
+
+static void sbi_cpuidle_deinit_cpu(int cpu)
+{
+ struct sbi_cpuidle_data *data = per_cpu_ptr(&sbi_cpuidle_data, cpu);
+
+ dt_idle_detach_cpu(data->dev);
+ sbi_cpuidle_use_cpuhp = false;
+}
+
+static int sbi_cpuidle_init_cpu(struct device *dev, int cpu)
+{
+ struct cpuidle_driver *drv;
+ unsigned int state_count = 0;
+ int ret = 0;
+
+ drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ drv->name = "sbi_cpuidle";
+ drv->owner = THIS_MODULE;
+ drv->cpumask = (struct cpumask *)cpumask_of(cpu);
+
+ /* RISC-V architectural WFI to be represented as state index 0. */
+ drv->states[0].enter = sbi_cpuidle_enter_state;
+ drv->states[0].exit_latency = 1;
+ drv->states[0].target_residency = 1;
+ drv->states[0].power_usage = UINT_MAX;
+ strcpy(drv->states[0].name, "WFI");
+ strcpy(drv->states[0].desc, "RISC-V WFI");
+
+ /*
+ * If no DT idle states are detected (ret == 0) let the driver
+ * initialization fail accordingly since there is no reason to
+ * initialize the idle driver if only wfi is supported, the
+ * default archictectural back-end already executes wfi
+ * on idle entry.
+ */
+ ret = dt_init_idle_driver(drv, sbi_cpuidle_state_match, 1);
+ if (ret <= 0) {
+ pr_debug("HART%ld: failed to parse DT idle states\n",
+ cpuid_to_hartid_map(cpu));
+ return ret ? : -ENODEV;
+ }
+ state_count = ret + 1; /* Include WFI state as well */
+
+ /* Initialize idle states from DT. */
+ ret = sbi_cpuidle_dt_init_states(dev, drv, cpu, state_count);
+ if (ret) {
+ pr_err("HART%ld: failed to init idle states\n",
+ cpuid_to_hartid_map(cpu));
+ return ret;
+ }
+
+ ret = cpuidle_register(drv, NULL);
+ if (ret)
+ goto deinit;
+
+ cpuidle_cooling_register(drv);
+
+ return 0;
+deinit:
+ sbi_cpuidle_deinit_cpu(cpu);
+ return ret;
+}
+
+static void sbi_cpuidle_domain_sync_state(struct device *dev)
+{
+ /*
+ * All devices have now been attached/probed to the PM domain
+ * topology, hence it's fine to allow domain states to be picked.
+ */
+ sbi_cpuidle_pd_allow_domain_state = true;
+}
+
+#ifdef CONFIG_DT_IDLE_GENPD
+
+static int sbi_cpuidle_pd_power_off(struct generic_pm_domain *pd)
+{
+ struct genpd_power_state *state = &pd->states[pd->state_idx];
+ u32 *pd_state;
+
+ if (!state->data)
+ return 0;
+
+ if (!sbi_cpuidle_pd_allow_domain_state)
+ return -EBUSY;
+
+ /* OSI mode is enabled, set the corresponding domain state. */
+ pd_state = state->data;
+ sbi_set_domain_state(*pd_state);
+
+ return 0;
+}
+
+struct sbi_pd_provider {
+ struct list_head link;
+ struct device_node *node;
+};
+
+static LIST_HEAD(sbi_pd_providers);
+
+static int sbi_pd_init(struct device_node *np)
+{
+ struct generic_pm_domain *pd;
+ struct sbi_pd_provider *pd_provider;
+ struct dev_power_governor *pd_gov;
+ int ret = -ENOMEM, state_count = 0;
+
+ pd = dt_idle_pd_alloc(np, sbi_dt_parse_state_node);
+ if (!pd)
+ goto out;
+
+ pd_provider = kzalloc(sizeof(*pd_provider), GFP_KERNEL);
+ if (!pd_provider)
+ goto free_pd;
+
+ pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
+
+ /* Allow power off when OSI is available. */
+ if (sbi_cpuidle_use_osi)
+ pd->power_off = sbi_cpuidle_pd_power_off;
+ else
+ pd->flags |= GENPD_FLAG_ALWAYS_ON;
+
+ /* Use governor for CPU PM domains if it has some states to manage. */
+ pd_gov = state_count > 0 ? &pm_domain_cpu_gov : NULL;
+
+ ret = pm_genpd_init(pd, pd_gov, false);
+ if (ret)
+ goto free_pd_prov;
+
+ ret = of_genpd_add_provider_simple(np, pd);
+ if (ret)
+ goto remove_pd;
+
+ pd_provider->node = of_node_get(np);
+ list_add(&pd_provider->link, &sbi_pd_providers);
+
+ pr_debug("init PM domain %s\n", pd->name);
+ return 0;
+
+remove_pd:
+ pm_genpd_remove(pd);
+free_pd_prov:
+ kfree(pd_provider);
+free_pd:
+ dt_idle_pd_free(pd);
+out:
+ pr_err("failed to init PM domain ret=%d %pOF\n", ret, np);
+ return ret;
+}
+
+static void sbi_pd_remove(void)
+{
+ struct sbi_pd_provider *pd_provider, *it;
+ struct generic_pm_domain *genpd;
+
+ list_for_each_entry_safe(pd_provider, it, &sbi_pd_providers, link) {
+ of_genpd_del_provider(pd_provider->node);
+
+ genpd = of_genpd_remove_last(pd_provider->node);
+ if (!IS_ERR(genpd))
+ kfree(genpd);
+
+ of_node_put(pd_provider->node);
+ list_del(&pd_provider->link);
+ kfree(pd_provider);
+ }
+}
+
+static int sbi_genpd_probe(struct device_node *np)
+{
+ struct device_node *node;
+ int ret = 0, pd_count = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ /*
+ * Parse child nodes for the "#power-domain-cells" property and
+ * initialize a genpd/genpd-of-provider pair when it's found.
+ */
+ for_each_child_of_node(np, node) {
+ if (!of_find_property(node, "#power-domain-cells", NULL))
+ continue;
+
+ ret = sbi_pd_init(node);
+ if (ret)
+ goto put_node;
+
+ pd_count++;
+ }
+
+ /* Bail out if not using the hierarchical CPU topology. */
+ if (!pd_count)
+ goto no_pd;
+
+ /* Link genpd masters/subdomains to model the CPU topology. */
+ ret = dt_idle_pd_init_topology(np);
+ if (ret)
+ goto remove_pd;
+
+ return 0;
+
+put_node:
+ of_node_put(node);
+remove_pd:
+ sbi_pd_remove();
+ pr_err("failed to create CPU PM domains ret=%d\n", ret);
+no_pd:
+ return ret;
+}
+
+#else
+
+static inline int sbi_genpd_probe(struct device_node *np)
+{
+ return 0;
+}
+
+#endif
+
+static int sbi_cpuidle_probe(struct platform_device *pdev)
+{
+ int cpu, ret;
+ struct cpuidle_driver *drv;
+ struct cpuidle_device *dev;
+ struct device_node *np, *pds_node;
+
+ /* Detect OSI support based on CPU DT nodes */
+ sbi_cpuidle_use_osi = true;
+ for_each_possible_cpu(cpu) {
+ np = of_cpu_device_node_get(cpu);
+ if (np &&
+ of_find_property(np, "power-domains", NULL) &&
+ of_find_property(np, "power-domain-names", NULL)) {
+ continue;
+ } else {
+ sbi_cpuidle_use_osi = false;
+ break;
+ }
+ }
+
+ /* Populate generic power domains from DT nodes */
+ pds_node = of_find_node_by_path("/cpus/power-domains");
+ if (pds_node) {
+ ret = sbi_genpd_probe(pds_node);
+ of_node_put(pds_node);
+ if (ret)
+ return ret;
+ }
+
+ /* Initialize CPU idle driver for each CPU */
+ for_each_possible_cpu(cpu) {
+ ret = sbi_cpuidle_init_cpu(&pdev->dev, cpu);
+ if (ret) {
+ pr_debug("HART%ld: idle driver init failed\n",
+ cpuid_to_hartid_map(cpu));
+ goto out_fail;
+ }
+ }
+
+ /* Setup CPU hotplut notifiers */
+ sbi_idle_init_cpuhp();
+
+ pr_info("idle driver registered for all CPUs\n");
+
+ return 0;
+
+out_fail:
+ while (--cpu >= 0) {
+ dev = per_cpu(cpuidle_devices, cpu);
+ drv = cpuidle_get_cpu_driver(dev);
+ cpuidle_unregister(drv);
+ sbi_cpuidle_deinit_cpu(cpu);
+ }
+
+ return ret;
+}
+
+static struct platform_driver sbi_cpuidle_driver = {
+ .probe = sbi_cpuidle_probe,
+ .driver = {
+ .name = "sbi-cpuidle",
+ .sync_state = sbi_cpuidle_domain_sync_state,
+ },
+};
+
+static int __init sbi_cpuidle_init(void)
+{
+ int ret;
+ struct platform_device *pdev;
+
+#if 0
+ /*
+ * The SBI HSM suspend function is only available when:
+ * 1) SBI version is 0.3 or higher
+ * 2) SBI HSM extension is available
+ */
+ if ((sbi_spec_version < sbi_mk_version(0, 3)) ||
+ sbi_probe_extension(SBI_EXT_HSM) <= 0) {
+ pr_info("HSM suspend not available\n");
+ return 0;
+ }
+#endif
+ ret = platform_driver_register(&sbi_cpuidle_driver);
+ if (ret)
+ return ret;
+
+ pdev = platform_device_register_simple("sbi-cpuidle",
+ -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ platform_driver_unregister(&sbi_cpuidle_driver);
+ return PTR_ERR(pdev);
+ }
+
+ return 0;
+}
+device_initcall(sbi_cpuidle_init);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PM domains for CPUs via genpd.
+ *
+ * Copyright (C) 2019 Linaro Ltd.
+ * Author: Ulf Hansson <ulf.hansson@linaro.org>
+ *
+ * Copyright (c) 2021 Western Digital Corporation or its affiliates.
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ */
+
+#define pr_fmt(fmt) "dt-idle-genpd: " fmt
+
+#include <linux/cpu.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "dt_idle_genpd.h"
+
+static int pd_parse_state_nodes(
+ int (*parse_state)(struct device_node *, u32 *),
+ struct genpd_power_state *states, int state_count)
+{
+ int i, ret;
+ u32 state, *state_buf;
+
+ for (i = 0; i < state_count; i++) {
+ ret = parse_state(to_of_node(states[i].fwnode), &state);
+ if (ret)
+ goto free_state;
+
+ state_buf = kmalloc(sizeof(u32), GFP_KERNEL);
+ if (!state_buf) {
+ ret = -ENOMEM;
+ goto free_state;
+ }
+ *state_buf = state;
+ states[i].data = state_buf;
+ }
+
+ return 0;
+
+free_state:
+ i--;
+ for (; i >= 0; i--)
+ kfree(states[i].data);
+ return ret;
+}
+
+static int pd_parse_states(struct device_node *np,
+ int (*parse_state)(struct device_node *, u32 *),
+ struct genpd_power_state **states,
+ int *state_count)
+{
+ int ret;
+
+ /* Parse the domain idle states. */
+ ret = of_genpd_parse_idle_states(np, states, state_count);
+ if (ret)
+ return ret;
+
+ /* Fill out the dt specifics for each found state. */
+ ret = pd_parse_state_nodes(parse_state, *states, *state_count);
+ if (ret)
+ kfree(*states);
+
+ return ret;
+}
+
+static void pd_free_states(struct genpd_power_state *states,
+ unsigned int state_count)
+{
+ int i;
+
+ for (i = 0; i < state_count; i++)
+ kfree(states[i].data);
+ kfree(states);
+}
+
+void dt_idle_pd_free(struct generic_pm_domain *pd)
+{
+ pd_free_states(pd->states, pd->state_count);
+ kfree(pd->name);
+ kfree(pd);
+}
+
+struct generic_pm_domain *dt_idle_pd_alloc(struct device_node *np,
+ int (*parse_state)(struct device_node *, u32 *))
+{
+ struct generic_pm_domain *pd;
+ struct genpd_power_state *states = NULL;
+ int ret, state_count = 0;
+
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ goto out;
+
+ pd->name = kasprintf(GFP_KERNEL, "%pOF", np);
+ if (!pd->name)
+ goto free_pd;
+
+ /*
+ * Parse the domain idle states and let genpd manage the state selection
+ * for those being compatible with "domain-idle-state".
+ */
+ ret = pd_parse_states(np, parse_state, &states, &state_count);
+ if (ret)
+ goto free_name;
+
+ pd->free_states = pd_free_states;
+ pd->name = kbasename(pd->name);
+ pd->states = states;
+ pd->state_count = state_count;
+
+ pr_debug("alloc PM domain %s\n", pd->name);
+ return pd;
+
+free_name:
+ kfree(pd->name);
+free_pd:
+ kfree(pd);
+out:
+ pr_err("failed to alloc PM domain %pOF\n", np);
+ return NULL;
+}
+
+int dt_idle_pd_init_topology(struct device_node *np)
+{
+ struct device_node *node;
+ struct of_phandle_args child, parent;
+ int ret;
+
+ for_each_child_of_node(np, node) {
+ if (of_parse_phandle_with_args(node, "power-domains",
+ "#power-domain-cells", 0, &parent))
+ continue;
+
+ child.np = node;
+ child.args_count = 0;
+ ret = of_genpd_add_subdomain(&parent, &child);
+ of_node_put(parent.np);
+ if (ret) {
+ of_node_put(node);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+struct device *dt_idle_attach_cpu(int cpu, const char *name)
+{
+ struct device *dev;
+
+ dev = dev_pm_domain_attach_by_name(get_cpu_device(cpu), name);
+ if (IS_ERR_OR_NULL(dev))
+ return dev;
+
+ pm_runtime_irq_safe(dev);
+ if (cpu_online(cpu))
+ pm_runtime_get_sync(dev);
+
+ dev_pm_syscore_device(dev, true);
+
+ return dev;
+}
+
+void dt_idle_detach_cpu(struct device *dev)
+{
+ if (IS_ERR_OR_NULL(dev))
+ return;
+
+ dev_pm_domain_detach(dev, false);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DT_IDLE_GENPD
+#define __DT_IDLE_GENPD
+
+struct device_node;
+struct generic_pm_domain;
+
+#ifdef CONFIG_DT_IDLE_GENPD
+
+void dt_idle_pd_free(struct generic_pm_domain *pd);
+
+struct generic_pm_domain *dt_idle_pd_alloc(struct device_node *np,
+ int (*parse_state)(struct device_node *, u32 *));
+
+int dt_idle_pd_init_topology(struct device_node *np);
+
+struct device *dt_idle_attach_cpu(int cpu, const char *name);
+
+void dt_idle_detach_cpu(struct device *dev);
+
+#else
+
+static inline void dt_idle_pd_free(struct generic_pm_domain *pd)
+{
+}
+
+static inline struct generic_pm_domain *dt_idle_pd_alloc(
+ struct device_node *np,
+ int (*parse_state)(struct device_node *, u32 *))
+{
+ return NULL;
+}
+
+static inline int dt_idle_pd_init_topology(struct device_node *np)
+{
+ return 0;
+}
+
+static inline struct device *dt_idle_attach_cpu(int cpu, const char *name)
+{
+ return NULL;
+}
+
+static inline void dt_idle_detach_cpu(struct device *dev)
+{
+}
+
+#endif
+
+#endif
}
dev_vdbg(dchan2dev(dchan), "%s: allocating\n", axi_chan_name(chan));
- pm_runtime_get(chan->chip->dev);
+ pm_runtime_get_sync(chan->chip->dev);
return 0;
}
"%s: free resources, descriptor still allocated: %u\n",
axi_chan_name(chan), atomic_read(&chan->descs_allocated));
- pm_runtime_put(chan->chip->dev);
+ pm_runtime_put_sync(chan->chip->dev);
}
static void dw_axi_dma_set_hw_channel(struct axi_dma_chan *chan, bool set)
axi_chan_disable(chan);
ret = readl_poll_timeout_atomic(chan->chip->regs + DMAC_CHEN, val,
- !(val & chan_active), 1000, 10000);
+ !(val & chan_active), 1000, 100000);
+
if (ret == -ETIMEDOUT)
dev_warn(dchan2dev(dchan),
"%s failed to stop\n", axi_chan_name(chan));
unsigned long flags;
unsigned int timeout = 20; /* timeout iterations */
u32 val;
+ int ret;
+ u32 chan_active = BIT(chan->id) << DMAC_CHAN_EN_SHIFT;
spin_lock_irqsave(&chan->vc.lock, flags);
spin_unlock_irqrestore(&chan->vc.lock, flags);
+ chan->ch_sar = axi_chan_ioread32(chan, CH_SAR);
+ chan->ch_dar = axi_chan_ioread32(chan, CH_DAR);
+ chan->ch_dar_h = axi_chan_ioread32(chan, CH_DAR_H);
+ chan->ch_block_ts = axi_chan_ioread32(chan, CH_BLOCK_TS);
+ chan->ch_ctl_l = axi_chan_ioread32(chan, CH_CTL_L);
+ chan->ch_ctl_h = axi_chan_ioread32(chan, CH_CTL_H);
+ chan->ch_cfg_l = axi_chan_ioread32(chan, CH_CFG_L);
+ chan->ch_cfg_h = axi_chan_ioread32(chan, CH_CFG_H);
+ chan->ch_llp = axi_chan_ioread32(chan, CH_LLP);
+ //printk("%s for %s ch_sar=0x%x ch_dar=0x%x ch_dar_h=0x%x ch_block_ts=0x%x ch_ctl_l=0x%x ch_ctl_h=0x%x ch_cfg_l=0x%x ch_cfg_h=0x%x ch_llp=0x%x\n", __func__,
+ // axi_chan_name(chan), chan->ch_sar, chan->ch_dar, chan->ch_dar_h, chan->ch_block_ts, chan->ch_ctl_l, chan->ch_ctl_h, chan->ch_cfg_l, chan->ch_cfg_h, chan->ch_llp);
+
+ axi_chan_disable(chan);
+ ret = readl_poll_timeout_atomic(chan->chip->regs + DMAC_CHEN, val,
+ !(val & chan_active), 1000, 100000);
+ if (ret == -ETIMEDOUT)
+ printk("%s %s failed to stop\n", __func__, axi_chan_name(chan));
+
return timeout ? 0 : -EAGAIN;
}
/* Called in chan locked context */
static inline void axi_chan_resume(struct axi_dma_chan *chan)
{
- u32 val;
+ u32 val, irq_mask;
+ struct axi_dma_desc *desc = chan->desc;
+ struct axi_dma_hw_desc *hw_desc = desc->hw_desc;
+
+ axi_chan_iowrite32(chan, CH_SAR, chan->ch_sar);
+ axi_chan_iowrite32(chan, CH_DAR, chan->ch_dar);
+ axi_chan_iowrite32(chan, CH_DAR_H, chan->ch_dar_h);
+ axi_chan_iowrite32(chan, CH_BLOCK_TS, chan->ch_block_ts);
+ axi_chan_iowrite32(chan, CH_CTL_L, chan->ch_ctl_l);
+ axi_chan_iowrite32(chan, CH_CTL_H, chan->ch_ctl_h);
+ axi_chan_iowrite32(chan, CH_CFG_L, chan->ch_cfg_l);
+ axi_chan_iowrite32(chan, CH_CFG_H, chan->ch_cfg_h);
+ axi_chan_iowrite32(chan, CH_LLP, chan->ch_llp);
+ irq_mask = DWAXIDMAC_IRQ_DMA_TRF | DWAXIDMAC_IRQ_ALL_ERR;
+ axi_chan_irq_sig_set(chan, irq_mask);
+ /* Generate 'suspend' status but don't generate interrupt */
+ irq_mask |= DWAXIDMAC_IRQ_SUSPENDED;
+ axi_chan_irq_set(chan, irq_mask);
val = axi_dma_ioread32(chan->chip, DMAC_CHEN);
if (chan->chip->dw->hdata->reg_map_8_channels) {
axi_dma_iowrite32(chan->chip, DMAC_CHSUSPREG, val);
}
+ axi_chan_enable(chan);
+
chan->is_paused = false;
+
+ return;
}
static int dma_chan_resume(struct dma_chan *dchan)
return 0;
}
+static void axi_dma_dump(struct axi_dma_chip *chip)
+{
+ struct dw_axi_dma *dw = chip->dw;
+ struct axi_dma_chan *chan;
+ u32 i;
+ struct virt_dma_desc *vd;
+ for (i = 0; i < dw->hdata->nr_channels; i++) {
+ chan = &dw->chan[i];
+ printk("%s chan name %s\n", __func__, axi_chan_name(chan));
+ vd = vchan_next_desc(&chan->vc);
+ axi_chan_list_dump_lli(chan, vd_to_axi_desc(vd));
+ }
+ return;
+}
+
static int __maybe_unused axi_dma_runtime_suspend(struct device *dev)
{
struct axi_dma_chip *chip = dev_get_drvdata(dev);
return axi_dma_resume(chip);
}
+static int __maybe_unused axi_dma_sleep_suspend(struct device *dev)
+{
+
+ //struct axi_dma_chip *chip = dev_get_drvdata(dev);
+ //axi_dma_irq_disable(chip);
+ //axi_dma_disable(chip);
+
+ //clk_disable_unprepare(chip->core_clk);
+ //clk_disable_unprepare(chip->cfgr_clk);
+
+
+ dev_err(dev, "%s, %d\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static int __maybe_unused axi_dma_sleep_resume(struct device *dev)
+{
+ struct axi_dma_chip *chip = dev_get_drvdata(dev);
+ int ret = 0;
+
+ ret = clk_prepare_enable(chip->cfgr_clk);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_prepare_enable(chip->core_clk);
+ if (ret < 0)
+ return ret;
+
+ axi_dma_enable(chip);
+ axi_dma_irq_enable(chip);
+ dev_err(dev, "%s, %d\n", __func__, __LINE__);
+
+ return 0;
+}
+
static struct dma_chan *dw_axi_dma_of_xlate(struct of_phandle_args *dma_spec,
struct of_dma *ofdma)
{
return 0;
}
+#ifdef CONFIG_PM
+static const struct dev_pm_ops dw_axi_dma_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(axi_dma_sleep_suspend, axi_dma_sleep_resume)
+ SET_RUNTIME_PM_OPS(axi_dma_runtime_suspend, axi_dma_runtime_resume, NULL)
+};
+#else
static const struct dev_pm_ops dw_axi_dma_pm_ops = {
- SET_RUNTIME_PM_OPS(axi_dma_runtime_suspend, axi_dma_runtime_resume, NULL)
+ SET_RUNTIME_PM_OPS(axi_dma_runtime_suspend, axi_dma_runtime_resume, NULL)
};
+#endif
static const struct of_device_id dw_dma_of_id_table[] = {
{ .compatible = "snps,axi-dma-1.01a" },
bool cyclic;
/* these other elements are all protected by vc.lock */
bool is_paused;
+ u32 ch_sar;
+ u32 ch_dar;
+ u32 ch_dar_h;
+ u32 ch_block_ts;
+ u32 ch_ctl_l;
+ u32 ch_ctl_h;
+ u32 ch_cfg_l;
+ u32 ch_cfg_h;
+ u32 ch_llp;
};
struct dw_axi_dma {
/* DMA channel registers offset */
#define CH_SAR 0x000 /* R/W Chan Source Address */
#define CH_DAR 0x008 /* R/W Chan Destination Address */
+#define CH_DAR_H 0x00C
#define CH_BLOCK_TS 0x010 /* R/W Chan Block Transfer Size */
#define CH_CTL 0x018 /* R/W Chan Control */
#define CH_CTL_L 0x018 /* R/W Chan Control 00-31 */
{ /* Sentinel */ }
};
+static int __maybe_unused light_aon_resume_noirq(struct device *dev)
+{
+ struct light_aon_chan *aon_chan;
+ int ret;
+
+ aon_chan = &light_aon_ipc_handle->chans;
+
+ complete(&aon_chan->tx_done);
+ return 0;
+}
+
+static const struct dev_pm_ops light_aon_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL,
+ light_aon_resume_noirq)
+};
static struct platform_driver light_aon_driver = {
.driver = {
.name = "light-aon",
.of_match_table = light_aon_match,
+ .pm = &light_aon_pm_ops,
},
.probe = light_aon_probe,
};
MODULE_DEVICE_TABLE(of, pca953x_dt_ids);
-static SIMPLE_DEV_PM_OPS(pca953x_pm_ops, pca953x_suspend, pca953x_resume);
+#ifdef CONFIG_PM_SLEEP
+static const struct dev_pm_ops pca953x_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(pca953x_suspend,
+ pca953x_resume)
+};
+#define PCA593X_PM_OPS &pca953x_pm_ops
+#else
+#define PCA593X_PM_OPS NULL
+#endif
static struct i2c_driver pca953x_driver = {
.driver = {
.name = "pca953x",
- .pm = &pca953x_pm_ops,
+ .pm = PCA593X_PM_OPS,
.of_match_table = pca953x_dt_ids,
.acpi_match_table = pca953x_acpi_ids,
},
gckGALDEVICE device = galDevice;
gckHARDWARE hardware;
int len = 0;
+ int i;
#ifdef CONFIG_DEBUG_FS
void* ptr = m;
#else
if (!device)
return -ENXIO;
- hardware = device->kernels[0]->hardware;
-
+ for (i = 0; i < gcvCORE_COUNT; ++i)
+ {
+ if (!device->kernels[i])
+ {
+ continue;
+ }
+ hardware = device->kernels[i]->hardware;
#ifdef CONFIG_DEBUG_FS
- len += fs_printf(ptr + len, "power off timeout: %d ms.\n", hardware->powerOffTimeout);
+ len += fs_printf(ptr + len, "power off timeout: %d ms.\n", hardware->powerOffTimeout);
#else
- len += sprintf(ptr + len, "power off timeout: %d ms.\n", hardware->powerOffTimeout);
+ len += sprintf(ptr + len, "power off timeout: %d ms.\n", hardware->powerOffTimeout);
#endif
-
+ }
return len;
}
#include <linux/regmap.h>
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
-
#include <media/cec-notifier.h>
#include <uapi/linux/media-bus-format.h>
goto err_res;
}
- hdmi->i2s_clk = devm_clk_get_optional(hdmi->dev, "i2s");
- if (IS_ERR(hdmi->i2s_clk)) {
- ret = PTR_ERR(hdmi->i2s_clk);
- dev_err(hdmi->dev, "Unable to get HDMI i2s clk: %d\n", ret);
- goto err_res;
- }
-
clk_prepare_enable(hdmi->iahb_clk);
clk_prepare_enable(hdmi->isfr_clk);
void dw_hdmi_resume(struct dw_hdmi *hdmi)
{
dw_hdmi_init_hw(hdmi);
+ hdmi_init_clk_regenerator(hdmi);
}
EXPORT_SYMBOL_GPL(dw_hdmi_resume);
#ifdef CONFIG_PM
int dw_hdmi_runtime_suspend(struct dw_hdmi *hdmi)
{
- clk_disable_unprepare(hdmi->i2s_clk);
clk_disable_unprepare(hdmi->pix_clk);
clk_disable_unprepare(hdmi->cec_clk);
-
return 0;
}
EXPORT_SYMBOL_GPL(dw_hdmi_runtime_suspend);
{
clk_prepare_enable(hdmi->cec_clk);
clk_prepare_enable(hdmi->pix_clk);
- clk_prepare_enable(hdmi->i2s_clk);
-
return 0;
}
EXPORT_SYMBOL_GPL(dw_hdmi_runtime_resume);
#endif
+
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
Say Y here if you want to enable support for MingJun 070BI30IA2
MIPI DSI panel. The panel support TFT dot matrix LCD with
800RGBx1280 dots at maximum.
+
+config DRM_PANEL_HX8279
+ tristate "HX8279-based panels"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y if you want to enable support for panels based on the
+ HX8279 controller.
+
endmenu
obj-$(CONFIG_DRM_PANEL_HX8394) += panel-himax8394.o
obj-$(CONFIG_DRM_PANEL_JADARD_JD9365DA_H3) += panel-jadard-jd9365da-h3.o
obj-$(CONFIG_DRM_PANEL_MINGJUN_070BI30IA2) += panel-mingjun-070bi30ia2.o
+obj-$(CONFIG_DRM_PANEL_HX8279) += panel-hx8279.o
--- /dev/null
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <video/mipi_display.h>
+
+struct hx8279_panel_desc {
+ const struct drm_display_mode *display_mode;
+
+ unsigned long mode_flags;
+ enum mipi_dsi_pixel_format format;
+ unsigned int lanes;
+ // const struct jadard_panel_cmd *on_cmds;
+ // unsigned int on_cmds_num;
+};
+
+struct panel_info {
+ struct drm_panel base;
+ struct mipi_dsi_device *link;
+ const struct hx8279_panel_desc *desc;
+
+ struct gpio_desc *reset;
+ struct regulator *hsvcc;
+ struct regulator *vspn3v3;
+
+ bool prepared;
+ bool enabled;
+};
+
+static inline struct panel_info *to_panel_info(struct drm_panel *panel)
+{
+ return container_of(panel, struct panel_info, base);
+}
+
+static int hx8279_panel_disable(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int err;
+
+ if (!pinfo->enabled)
+ return 0;
+
+ err = mipi_dsi_dcs_set_display_off(pinfo->link);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to set display off: %d\n", err);
+ return err;
+ }
+
+ pinfo->enabled = false;
+
+ return 0;
+}
+
+static int hx8279_panel_unprepare(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int err;
+
+ if (!pinfo->prepared)
+ return 0;
+
+ err = mipi_dsi_dcs_set_display_off(pinfo->link);
+ if (err < 0)
+ dev_err(panel->dev, "failed to set display off: %d\n", err);
+
+ err = mipi_dsi_dcs_enter_sleep_mode(pinfo->link);
+ if (err < 0)
+ dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
+
+ /* sleep_mode_delay: 1ms - 2ms */
+ usleep_range(1000, 2000);
+
+ gpiod_set_value(pinfo->reset, 1);
+ regulator_disable(pinfo->hsvcc);
+ regulator_disable(pinfo->vspn3v3);
+
+ pinfo->prepared = false;
+
+ return 0;
+}
+
+static int hx8279_panel_prepare(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int ret;
+
+ if (pinfo->prepared)
+ return 0;
+ gpiod_set_value(pinfo->reset, 0);
+
+ /* Power the panel */
+ ret = regulator_enable(pinfo->hsvcc);
+ if (ret) {
+ dev_err(pinfo->base.dev, "Failed to enable hsvcc supply: %d\n", ret);
+ return ret;
+ }
+
+ usleep_range(1000, 2000);
+ ret = regulator_enable(pinfo->vspn3v3);
+ if (ret) {
+ dev_err(pinfo->base.dev, "Failed to enable vspn3v3 supply: %d\n", ret);
+ goto fail;
+ }
+ usleep_range(5000, 6000);
+
+ gpiod_set_value(pinfo->reset, 1);
+ msleep(180);
+
+ pinfo->prepared = true;
+
+ return 0;
+
+fail:
+ gpiod_set_value(pinfo->reset, 1);
+ regulator_disable(pinfo->hsvcc);
+ return ret;
+}
+
+static int hx8279_panel_enable(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int ret;
+ u8 id1;
+
+ if (pinfo->enabled)
+ return 0;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(pinfo->link);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(pinfo->link);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to set display on: %d\n", ret);
+ return ret;
+ }
+
+ pinfo->enabled = true;
+
+ return 0;
+}
+
+static int hx8279_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ const struct drm_display_mode *m = pinfo->desc->display_mode;
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, m);
+ if (!mode) {
+ dev_err(pinfo->base.dev, "failed to add mode %ux%u@%u\n",
+ m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
+ return -ENOMEM;
+ }
+
+ drm_mode_set_name(mode);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+
+ return 1;
+}
+
+static const struct drm_panel_funcs panel_funcs = {
+ .disable = hx8279_panel_disable,
+ .unprepare = hx8279_panel_unprepare,
+ .prepare = hx8279_panel_prepare,
+ .enable = hx8279_panel_enable,
+ .get_modes = hx8279_panel_get_modes,
+};
+
+static const struct drm_display_mode hx8279_default_mode = {
+ .clock = 196500,
+ .hdisplay = 1200,
+ .hsync_start = 1200 + 96,
+ .hsync_end = 1200 + 96 + 128,
+ .htotal = 1200 + 96 + 128 + 224,
+
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 3,
+ .vsync_end = 1920 + 3 + 10,
+ .vtotal = 1920 + 3 + 10 + 56,
+
+ .width_mm = 62,
+ .height_mm = 110,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static const struct hx8279_panel_desc hx8279_desc = {
+ .display_mode = &hx8279_default_mode,
+ .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_BURST,
+ .format = MIPI_DSI_FMT_RGB888,
+ .lanes = 4,
+// .on_cmds = hx8279_on_cmds,
+// .on_cmds_num = ARRAY_SIZE(hx8279_on_cmds),
+};
+
+static const struct of_device_id panel_of_match[] = {
+ {
+ .compatible = "himax,hx8279",
+ .data = &hx8279_desc,
+ },
+ {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+static int hx8279_panel_add(struct panel_info *pinfo)
+{
+ struct device *dev = &pinfo->link->dev;
+ int ret;
+
+ pinfo->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(pinfo->reset))
+ return dev_err_probe(dev, PTR_ERR(pinfo->reset),
+ "Couldn't get our reset GPIO\n");
+
+ pinfo->hsvcc = devm_regulator_get(dev, "hsvcc");
+ if (IS_ERR(pinfo->hsvcc))
+ return dev_err_probe(dev, PTR_ERR(pinfo->hsvcc),
+ "Failed to request hsvcc regulator\n");
+
+ pinfo->vspn3v3 = devm_regulator_get(dev, "vspn3v3");
+ if (IS_ERR(pinfo->vspn3v3))
+ return dev_err_probe(dev, PTR_ERR(pinfo->vspn3v3),
+ "Failed to request vspn3v3 regulator\n");
+
+ drm_panel_init(&pinfo->base, dev, &panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&pinfo->base);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&pinfo->base);
+
+ return 0;
+}
+
+static int hx8279_panel_probe(struct mipi_dsi_device *dsi)
+{
+ struct panel_info *pinfo;
+ const struct hx8279_panel_desc *desc;
+ int err;
+
+ pinfo = devm_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL);
+ if (!pinfo)
+ return -ENOMEM;
+
+ desc = of_device_get_match_data(&dsi->dev);
+ dsi->mode_flags = desc->mode_flags;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+ pinfo->desc = desc;
+
+ pinfo->link = dsi;
+ mipi_dsi_set_drvdata(dsi, pinfo);
+
+ err = hx8279_panel_add(pinfo);
+ if (err < 0)
+ return err;
+
+ err = mipi_dsi_attach(dsi);
+ if (err < 0)
+ drm_panel_remove(&pinfo->base);
+
+ return err;
+}
+
+static int hx8279_panel_remove(struct mipi_dsi_device *dsi)
+{
+ struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
+ int err;
+
+ err = hx8279_panel_disable(&pinfo->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
+
+ err = hx8279_panel_unprepare(&pinfo->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
+
+ err = mipi_dsi_detach(dsi);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
+
+ drm_panel_remove(&pinfo->base);
+
+ return 0;
+}
+
+static void hx8279_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
+
+ hx8279_panel_disable(&pinfo->base);
+ hx8279_panel_unprepare(&pinfo->base);
+}
+
+static struct mipi_dsi_driver panel_driver = {
+ .driver = {
+ .name = "panel-himax8279",
+ .of_match_table = panel_of_match,
+ },
+ .probe = hx8279_panel_probe,
+ .remove = hx8279_panel_remove,
+ .shutdown = hx8279_panel_shutdown,
+};
+module_mipi_dsi_driver(panel_driver);
unsigned long mode_flags;
enum mipi_dsi_pixel_format format;
unsigned int lanes;
-// const struct jadard_panel_cmd *on_cmds;
+ //const struct jadard_panel_cmd *on_cmds;
unsigned int on_cmds_num;
};
return container_of(panel, struct panel_info, base);
}
-//static int jadard_send_mipi_cmds(struct drm_panel *panel, const struct jadard_panel_cmd *cmds)
-//{
-// struct panel_info *pinfo = to_panel_info(panel);
-// unsigned int i = 0;
-// int err;
-//
-// for (i = 0; i < pinfo->desc->on_cmds_num; i++) {
-// err = mipi_dsi_dcs_write_buffer(pinfo->link, &(cmds[i].cmddata[0]), cmds[i].cmdlen);
-// if (err < 0)
-// return err;
-// }
-//
-// return 0;
-//}
+// static int jadard_send_mipi_cmds(struct drm_panel *panel, const struct jadard_panel_cmd *cmds)
+// {
+// struct panel_info *pinfo = to_panel_info(panel);
+// unsigned int i = 0;
+// int err;
+
+// for (i = 0; i < pinfo->desc->on_cmds_num; i++) {
+// err = mipi_dsi_dcs_write_buffer(pinfo->link, &(cmds[i].cmddata[0]), cmds[i].cmdlen);
+// if (err < 0)
+// return err;
+// }
+
+// return 0;
+// }
static int jadard_disable(struct drm_panel *panel)
{
MODULE_AUTHOR("Jagan Teki <jagan@edgeble.ai>");
MODULE_AUTHOR("Stephen Chen <stephen@radxa.com>");
MODULE_DESCRIPTION("Jadard JD9365DA-H3 WXGA DSI panel");
-MODULE_LICENSE("GPL");
\ No newline at end of file
+MODULE_LICENSE("GPL");
.atomic_check = dw_hdmi_light_encoder_atomic_check,
};
+struct dw_hdmi_light_private dw_hdmi_priv_data = {
+ .max_pixclock = 594000,
+ .max_width = 0,
+ .max_height = 0,
+};
+
static int dw_hdmi_light_bind(struct device *dev, struct device *master,
void *data)
{
int ret = 0;
+ u32 max_pixclock = 0;
+ u16 max_width = 0;
+ u16 max_height = 0;
+ int property_ret = 0;
struct platform_device *pdev = to_platform_device(dev);
struct device_node *np = dev->of_node;
const struct of_device_id *match;
if (unlikely(!match))
return -ENODEV;
+ property_ret = of_property_read_u32(np, "max_pixclock", &max_pixclock);
+ if(property_ret == 0){
+ printk(KERN_INFO "Hdmi max pixel clock = %d\n", max_pixclock);
+ if(dw_hdmi_priv_data.max_pixclock > max_pixclock){
+ dw_hdmi_priv_data.max_pixclock = max_pixclock;
+ printk(KERN_INFO"Hdmi max pixel clock adjust to %d\n", max_pixclock);
+ }
+ else{
+ printk(KERN_INFO"Hdmi max pixel clock not take effect\n");
+ }
+ }
+
+ property_ret = of_property_read_u16(np, "max_width", &max_width);
+ if(property_ret == 0){
+ dw_hdmi_priv_data.max_width = max_width;
+ }
+
+ property_ret = of_property_read_u16(np, "max_height", &max_height);
+ if(property_ret == 0){
+ dw_hdmi_priv_data.max_height = max_height;
+ }
+
+ light_hdmi_drv_data.priv_data = (void*)&dw_hdmi_priv_data;
+
plat_data = match->data;
hdmi->dev = &pdev->dev;
encoder = &hdmi->encoder;
ret = PTR_ERR(hdmi->dw_hdmi);
drm_encoder_cleanup(encoder);
}
-
pm_runtime_enable(dev);
return ret;
static int dw_hdmi_light_probe(struct platform_device *pdev)
{
struct light_hdmi *hdmi;
-
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
return dw_hdmi_runtime_resume(hdmi->dw_hdmi);
}
#endif
-
+#ifdef CONFIG_PM_SLEEP
+static int hdmi_resume(struct device *dev)
+{
+ struct light_hdmi *hdmi = dev_get_drvdata(dev);
+ dev_info(dev,"hdmi resume\n");
+ dw_hdmi_resume(hdmi->dw_hdmi);
+ return 0;
+}
+#endif
static const struct dev_pm_ops dw_hdmi_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(hdmi_runtime_suspend, hdmi_runtime_resume, NULL)
+ #ifdef CONFIG_PM_SLEEP
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL,hdmi_resume)
+ #endif
};
struct platform_driver dw_hdmi_light_platform_driver = {
} voltage;
};
+struct dw_hdmi_light_private {
+ u32 max_pixclock;
+ u16 max_width;
+ u16 max_height;
+};
+
static const struct dw_hdmi_mpll_gen_config mpll_configs[] = {
{
.pixelclock = 25175,
{
int i;
- if (mode->clock < 13500)
- return MODE_CLOCK_LOW;
- else if (mode->clock > 594000)
- return MODE_CLOCK_HIGH;
+ u32 max_clock = 594000;
+ u16 max_height = 0;
+ u16 max_width = 0;
+
+ struct dw_hdmi_light_private *dw_hdmi_private = (struct dw_hdmi_light_private*)data;
+ if(dw_hdmi_private != NULL){
+ max_clock = dw_hdmi_private->max_pixclock;
+ max_height = dw_hdmi_private->max_height;
+ max_width = dw_hdmi_private->max_width;
+ }
+
+ if (mode->clock < 13500)
+ {
+ return MODE_CLOCK_LOW;
+ }
+ else if (mode->clock > max_clock)
+ {
+ return MODE_CLOCK_HIGH;
+ }
+
+ if((max_width > 0) && (mode->hdisplay > max_width)){
+ return MODE_H_ILLEGAL;
+ }
+
+ if((max_height > 0) && (mode->vdisplay > max_height)){
+ return MODE_V_ILLEGAL;
+ }
for (i = 0; i < ARRAY_SIZE(mpll_configs); i++) {
- if (abs(mode->clock - mpll_configs[i].pixelclock) <= 100)
+ if (abs(mode->clock - mpll_configs[i].pixelclock) <= 100)
return MODE_OK;
else if (mode->clock < mpll_configs[i].pixelclock)
break;
#include <linux/of_graph.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_atomic.h>
display.enable = true;
if (dc->pix_clk_rate[display.id] != mode->clock) {
- clk_set_rate(dc->pixclk[display.id], mode->clock * 1000);
+ clk_set_rate(dc->pixclk[display.id], mode->clock * 1000);
dc->pix_clk_rate[display.id] = mode->clock;
}
struct vs_plane_info *plane_info;
int i, ret;
u32 ctrc_mask = 0;
-
if (!drm_dev || !dc) {
dev_err(dev, "devices are not created.\n");
return -ENODEV;
}
dc->funcs = &dc_funcs;
-
vs_drm_update_pitch_alignment(drm_dev, dc_info->pitch_alignment);
return 0;
struct vs_dc *dc;
int irq, ret, i;
char pixclk[16];
+ struct device_node *np = dev->of_node;
dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
if (!dc)
return PTR_ERR(dc->hw.mmu_base);
#endif
+ dc->hw.vosys_regmap = syscon_regmap_lookup_by_phandle(np, "vosys-regmap");
+ if (IS_ERR(dc->hw.vosys_regmap))
+ {
+ dev_err(dev, "Failed to remap vosys\n");
+ return PTR_ERR(dc->hw.vosys_regmap);
+ }
+
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(dev, irq, dc_isr, 0, dev_name(dev), dc);
if (ret < 0) {
}
for (i = 0; i < DC_DISPLAY_NUM; i++) {
- snprintf(pixclk, ARRAY_SIZE(pixclk), "%s%d", "pix_clk", i);
- dc->pix_clk[i] = devm_clk_get_optional(dev, pixclk);
+ snprintf(pixclk, ARRAY_SIZE(pixclk), "%s%d", "pix_clk", i);
+ dc->pix_clk[i] = devm_clk_get_optional(dev, pixclk);
- if (IS_ERR(dc->pix_clk[i])) {
- dev_err(dev, "failed to get pix_clk %d source\n", i);
- return PTR_ERR(dc->pix_clk[i]);
- }
+ if (IS_ERR(dc->pix_clk[i])) {
+ dev_err(dev, "failed to get pix_clk %d source\n", i);
+ return PTR_ERR(dc->pix_clk[i]);
+ }
- snprintf(pixclk, ARRAY_SIZE(pixclk), "%s%d", "pixclk", i);
- dc->pixclk[i] = devm_clk_get_optional(dev, pixclk);
- if (IS_ERR(dc->pixclk[i])) {
- dev_err(dev, "failed to get pixclk %d source\n", i);
- return PTR_ERR(dc->pixclk[i]);
- }
+ snprintf(pixclk, ARRAY_SIZE(pixclk), "%s%d", "pixclk", i);
+ dc->pixclk[i] = devm_clk_get_optional(dev, pixclk);
+ if (IS_ERR(dc->pixclk[i])) {
+ dev_err(dev, "failed to get pixclk %d source\n", i);
+ return PTR_ERR(dc->pixclk[i]);
+ }
}
dc->axi_clk = devm_clk_get_optional(dev, "axi_clk");
return PTR_ERR(dc->dpu1pll_clk);
}
+ dc->def_pix_clk_rate[0] = clk_get_rate(dc->dpu0pll_clk)/1000;
+ dc->def_pix_clk_rate[1] = clk_get_rate(dc->dpu1pll_clk)/1000;
+
dc_get_display_pll(dev, dc);
dev_set_drvdata(dev, dc);
dev_dbg(dev, "%s\n", __func__);
clk_disable_unprepare(dc->axi_clk);
- for (i = 0; i < DC_DISPLAY_NUM; i++)
+ for (i = 0; i < DC_DISPLAY_NUM; i++){
clk_disable_unprepare(dc->pix_clk[i]);
+ clk_disable_unprepare(dc->pixclk[i]);
+ }
clk_disable_unprepare(dc->core_clk);
for (i = 0; i < DC_DISPLAY_NUM; i++) {
ret = clk_prepare_enable(dc->pix_clk[i]);
+ if (ret < 0) {
+ dev_err(dev, "failed to prepare/enable pix_clk %d\n", i);
+ return ret;
+ }
+ ret = clk_prepare_enable(dc->pixclk[i]);
if (ret < 0) {
- dev_err(dev, "failed to prepare/enable pix_clk %d\n", i);
+ dev_err(dev, "failed to prepare/enable pixclk %d\n", i);
return ret;
}
}
return 0;
}
#endif
+#ifdef CONFIG_PM_SLEEP
+static int dc_suspend(struct device *dev)
+{
+ int i;
+ struct vs_dc *dc = dev_get_drvdata(dev);
+
+ for (i = 0; i < DC_DISPLAY_NUM; i++){
+ dc->pix_clk_rate[i] = dc->def_pix_clk_rate[i];
+ clk_set_rate(dc->pixclk[i], dc->pix_clk_rate[i] * 1000);
+ }
+
+ return 0;
+}
+
+static int dc_resume(struct device *dev)
+{
+ int i, ret;
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ dev_info(dev,"dc resume\n");
+ regmap_write(dc->hw.vosys_regmap,0x4,0x7); //release dpu reset
+
+ ret = dc_hw_init(&dc->hw);
+ if (ret)
+ dev_err(dev, "failed to init DC HW\n");
+ return 0;
+}
+
+#endif
static const struct dev_pm_ops dc_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
+ #ifdef CONFIG_PM_SLEEP
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
+ #endif
};
struct platform_driver dc_platform_driver = {
struct clk *dpu1pll_clk;
unsigned int pix_clk_rate[DC_DISPLAY_NUM]; /* in KHz */
+ unsigned int def_pix_clk_rate[DC_DISPLAY_NUM];
bool first_frame;
bool dpu0pll_on;
struct dc_hw_qos qos;
struct dc_hw_funcs *func;
struct vs_dc_info *info;
+ struct regmap *vosys_regmap;
};
int dc_hw_init(struct dc_hw *hw);
n &= SAMPLE_DATA_MSK;
/* Convert the N bitstream count into voltage */
*val = (PVT_N_CONST * n - PVT_R_CONST) >> PVT_CONV_BITS;
-
return 0;
default:
return -EOPNOTSUPP;
return ret;
}
+ platform_set_drvdata(pdev, pvt);
+
pvt_chip_info.info = pvt_info;
hwmon_dev = devm_hwmon_device_register_with_info(dev, "pvt",
pvt,
return PTR_ERR_OR_ZERO(hwmon_dev);
}
+#ifdef CONFIG_PM
+
+static int mr75203_suspend(struct device *dev)
+{
+ /* nothing to do */
+ return 0;
+}
+
+static int mr75203_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pvt_device *pvt = platform_get_drvdata(pdev);
+ pvt_init(pvt);
+ return 0;
+}
+
+static const struct dev_pm_ops mr75203_dev_pm_ops = {
+ .suspend = mr75203_suspend,
+ .resume = mr75203_resume,
+};
+
+#define MR75203_DEV_PM_OPS (&mr75203_dev_pm_ops)
+#else
+#define MR75203_DEV_PM_OPS NULL
+#endif /* CONFIG_PM */
static const struct of_device_id moortec_pvt_of_match[] = {
{ .compatible = "moortec,mr75203" },
static struct platform_driver moortec_pvt_driver = {
.driver = {
.name = "moortec-pvt",
+ .pm = MR75203_DEV_PM_OPS,
.of_match_table = moortec_pvt_of_match,
},
.probe = mr75203_probe,
{
int ret = 0;
struct i2c_dw_dma *dma = &dev->dma;
- unsigned long start_jiffies = 0;
u32 stat;
__dev_vdgb(dev->dev, "%s, %d, enter\n", __func__, __LINE__);
static int dw_i2c_plat_probe(struct platform_device *pdev)
{
+ dev_info(&pdev->dev, "going to probe light i2c driver\n");
struct dw_i2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct i2c_adapter *adap;
struct dw_i2c_dev *dev;
#ifdef CONFIG_PM
static int dw_i2c_plat_suspend(struct device *dev)
{
+ dev_info(dev, "light i2c suspend\n");
struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
i_dev->suspended = true;
}
static int dw_i2c_plat_resume(struct device *dev)
+{
+ dev_info(dev, "light i2c resume\n");
+ struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
+
+ if (!i_dev->shared_with_punit)
+ i2c_dw_prepare_clk(i_dev, true);
+
+ i_dev->init(i_dev);
+ i_dev->suspended = false;
+
+ return 0;
+}
+
+static int dw_i2c_plat_runtime_suspend(struct device *dev)
+{
+ struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
+
+ i_dev->suspended = true;
+
+ if (i_dev->shared_with_punit)
+ return 0;
+
+ i_dev->disable(i_dev);
+ i2c_dw_prepare_clk(i_dev, false);
+
+ return 0;
+}
+
+static int dw_i2c_plat_runtime_resume(struct device *dev)
{
struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
.prepare = dw_i2c_plat_prepare,
.complete = dw_i2c_plat_complete,
SET_LATE_SYSTEM_SLEEP_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume)
- SET_RUNTIME_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume, NULL)
+ SET_RUNTIME_PM_OPS(dw_i2c_plat_runtime_suspend, dw_i2c_plat_runtime_resume, NULL)
};
#define DW_I2C_DEV_PMOPS (&dw_i2c_dev_pm_ops)
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
+#include <linux/syscore_ops.h>
#include <asm/smp.h>
/*
struct irq_domain *irqdomain;
void __iomem *regs;
unsigned int nr_irqs;
+ unsigned long *prio_save;
};
struct plic_handler {
*/
raw_spinlock_t enable_lock;
void __iomem *enable_base;
+ u32 *enable_save;
struct plic_priv *priv;
};
+
static int plic_parent_irq;
static bool plic_cpuhp_setup_done;
static DEFINE_PER_CPU(struct plic_handler, plic_handlers);
#endif
};
+static int plic_irq_suspend(void)
+{
+ unsigned int i, cpu;
+ u32 __iomem *reg;
+ struct plic_priv *priv;
+
+ priv = per_cpu_ptr(&plic_handlers, smp_processor_id())->priv;
+
+ for (i = 0; i < priv->nr_irqs; i++)
+ if (readl(priv->regs + PRIORITY_BASE + i * PRIORITY_PER_ID))
+ __set_bit(i, priv->prio_save);
+ else
+ __clear_bit(i, priv->prio_save);
+
+ for_each_cpu(cpu, cpu_present_mask) {
+ struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
+
+ if (!handler->present)
+ continue;
+
+ raw_spin_lock(&handler->enable_lock);
+ for (i = 0; i < DIV_ROUND_UP(priv->nr_irqs, 32); i++) {
+ reg = handler->enable_base + i * sizeof(u32);
+ handler->enable_save[i] = readl(reg);
+ }
+ raw_spin_unlock(&handler->enable_lock);
+ }
+
+ return 0;
+}
+
+static void plic_irq_resume(void)
+{
+ unsigned int i, index, cpu;
+ u32 __iomem *reg;
+ struct plic_priv *priv;
+
+ priv = per_cpu_ptr(&plic_handlers, smp_processor_id())->priv;
+
+ for (i = 0; i < priv->nr_irqs; i++) {
+ index = BIT_WORD(i);
+ writel((priv->prio_save[index] & BIT_MASK(i)) ? 1 : 0,
+ priv->regs + PRIORITY_BASE + i * PRIORITY_PER_ID);
+ }
+
+ for_each_cpu(cpu, cpu_present_mask) {
+ struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
+
+ if (!handler->present)
+ continue;
+
+ raw_spin_lock(&handler->enable_lock);
+ for (i = 0; i < DIV_ROUND_UP(priv->nr_irqs, 32); i++) {
+ reg = handler->enable_base + i * sizeof(u32);
+ writel(handler->enable_save[i], reg);
+ }
+ raw_spin_unlock(&handler->enable_lock);
+ }
+}
+
+static struct syscore_ops plic_irq_syscore_ops = {
+ .suspend = plic_irq_suspend,
+ .resume = plic_irq_resume,
+};
+
static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
struct device_node *parent)
{
int error = 0, nr_contexts, nr_handlers = 0, i;
+ u32 nr_irqs;
struct plic_priv *priv;
struct plic_handler *handler;
+ unsigned int cpu;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
}
error = -EINVAL;
- of_property_read_u32(node, "riscv,ndev", &priv->nr_irqs);
- if (WARN_ON(!priv->nr_irqs))
+ of_property_read_u32(node, "riscv,ndev", &nr_irqs);
+ if (WARN_ON(!nr_irqs))
goto out_iounmap;
+ priv->nr_irqs = nr_irqs;
+
+ priv->prio_save = bitmap_alloc(nr_irqs, GFP_KERNEL);
+ if (!priv->prio_save)
+ goto out_free_priority_reg;
+
nr_contexts = of_irq_count(node);
if (WARN_ON(!nr_contexts))
- goto out_iounmap;
+ goto out_free_priority_reg;
error = -ENOMEM;
- priv->irqdomain = irq_domain_add_linear(node, priv->nr_irqs + 1,
+ priv->irqdomain = irq_domain_add_linear(node, nr_irqs + 1,
&plic_irqdomain_ops, priv);
if (WARN_ON(!priv->irqdomain))
- goto out_iounmap;
+ goto out_free_priority_reg;
for (i = 0; i < nr_contexts; i++) {
struct of_phandle_args parent;
handler->enable_base =
priv->regs + ENABLE_BASE + i * ENABLE_PER_HART;
handler->priv = priv;
+
+ handler->enable_save = kcalloc(DIV_ROUND_UP(nr_irqs, 32),
+ sizeof(*handler->enable_save), GFP_KERNEL);
+ if (!handler->enable_save)
+ goto out_free_enable_reg;
done:
nr_handlers++;
}
plic_cpuhp_setup_done = true;
}
+ register_syscore_ops(&plic_irq_syscore_ops);
pr_info("%pOFP: mapped %d interrupts with %d handlers for"
" %d contexts.\n", node, nr_irqs, nr_handlers, nr_contexts);
return 0;
+out_free_enable_reg:
+ for_each_cpu(cpu, cpu_present_mask) {
+ handler = per_cpu_ptr(&plic_handlers, cpu);
+ kfree(handler->enable_save);
+ }
+out_free_priority_reg:
+ kfree(priv->prio_save);
out_iounmap:
iounmap(priv->regs);
out_free_priv:
#define LIGHT_MBOX_ACK_MAGIC 0xdeadbeaf
+#ifdef CONFIG_PM_SLEEP
+/* store MBOX context across system-wide suspend/resume transitions */
+struct light_mbox_context{
+ u32 intr_mask[LIGHT_MBOX_CHANS - 1];
+};
+
+#endif
enum light_mbox_chan_type {
LIGHT_MBOX_TYPE_TXRX, /* Tx & Rx chan */
LIGHT_MBOX_TYPE_DB, /* Tx & Rx doorbell */
struct mbox_controller mbox;
struct mbox_chan mbox_chans[LIGHT_MBOX_CHANS];
- struct light_mbox_con_priv con_priv[LIGHT_MBOX_CHANS];
+ struct light_mbox_con_priv con_priv[LIGHT_MBOX_CHANS];
struct clk *clk;
int irq;
+#ifdef CONFIG_PM_SLEEP
+ struct light_mbox_context *ctx;
+#endif
};
static struct light_mbox_priv *to_light_mbox_priv(struct mbox_controller *mbox)
.shutdown = light_mbox_shutdown,
};
-static void light_mbox_init_generic(struct light_mbox_priv *priv)
+static int light_mbox_init_generic(struct light_mbox_priv *priv)
{
+#ifdef CONFIG_PM_SLEEP
+ priv->ctx = devm_kzalloc(priv->dev, sizeof(*priv->ctx), GFP_KERNEL);
+ if(!priv->ctx)
+ return -ENOMEM;
+#endif
/* Set default configuration */
light_mbox_write(priv, 0xff, LIGHT_MBOX_CLR);
light_mbox_write(priv, 0x0, LIGHT_MBOX_MASK);
+ return 0;
}
static struct mbox_chan *light_mbox_xlate(struct mbox_controller *mbox,
platform_set_drvdata(pdev, priv);
- light_mbox_init_generic(priv);
+ ret = light_mbox_init_generic(priv);
+ if (ret) {
+ dev_err(dev, "Failed to init mailbox context\n");
+ return ret;
+ }
return devm_mbox_controller_register(dev, &priv->mbox);
}
};
MODULE_DEVICE_TABLE(of, light_mbox_dt_ids);
+#ifdef CONFIG_PM_SLEEP
+static int __maybe_unused light_mbox_suspend_noirq(struct device *dev)
+{
+ struct light_mbox_priv *priv = dev_get_drvdata(dev);
+ struct light_mbox_context *ctx = priv->ctx;
+ u32 i;
+ /*
+ * ONLY interrupt mask bit should be stored and restores.
+ * INFO data all assumed to be lost.
+ */
+ for(i = 0 ; i < LIGHT_MBOX_CHANS; i++) {
+ ctx->intr_mask[i] = ioread32(priv->local_icu[i] + LIGHT_MBOX_MASK);
+ }
+ return 0;
+}
+
+static int __maybe_unused light_mbox_resume_noirq(struct device *dev)
+{
+ struct light_mbox_priv *priv = dev_get_drvdata(dev);
+ struct light_mbox_context *ctx = priv->ctx;
+ u32 i;
+
+ for(i = 0 ; i < LIGHT_MBOX_CHANS; i++) {
+ iowrite32(ctx->intr_mask[i], priv->local_icu[i] + LIGHT_MBOX_MASK);
+ }
+ return 0;
+}
+
+#endif
+
+
+static int __maybe_unused light_mbox_runtime_suspend(struct device *dev)
+{
+ struct light_mbox_priv *priv = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int __maybe_unused light_mbox_runtime_resume(struct device *dev)
+{
+ struct light_mbox_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ dev_err(dev, "failed to enable clock\n");
+
+ return ret;
+}
+
+static const struct dev_pm_ops light_mbox_pm_ops = {
+#ifdef CONFIG_PM_SLEEP
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(light_mbox_suspend_noirq,
+ light_mbox_resume_noirq)
+#endif
+ SET_RUNTIME_PM_OPS(light_mbox_runtime_suspend,
+ light_mbox_runtime_resume, NULL)
+};
+
+
static struct platform_driver light_mbox_driver = {
.probe = light_mbox_probe,
.remove = light_mbox_remove,
.driver = {
.name = "light_mbox",
.of_match_table = light_mbox_dt_ids,
+ .pm = &light_mbox_pm_ops,
},
};
module_platform_driver(light_mbox_driver);
#include <linux/of_device.h>
#include <linux/of_net.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include "stmmac_platform.h"
phy_interface_t interface;
struct clk *gmac_pll_clk;
unsigned long gmac_pll_clk_freq;
-
+ struct clk *gmac_axi_aclk;
+ struct clk *gmac_axi_pclk;
const struct thead_dwmac_ops *ops;
struct plat_stmmacenet_data *plat_dat;
};
+#define pm_debug dev_dbg // for suspend/resume interface debug info show,replace to dev_info
+
/* set GMAC PHY interface, 0:MII/GMII, 1:RGMII, 4:RMII */
static void thead_dwmac_set_phy_if(struct plat_stmmacenet_data *plat_dat)
{
void __iomem *phy_if_reg = thead_plat_dat->phy_if_reg;
phy_interface_t interface = thead_plat_dat->interface;
struct device *dev = thead_plat_dat->dev;
- int devid = thead_plat_dat->id;
+ //int devid = thead_plat_dat->id;
unsigned int phyif = PHY_INTERFACE_MODE_MII;
uint32_t reg;
};
reg = readl(phy_if_reg);
- reg &= ~(DWMAC_PHYIF_MASK << (DWMAC_PHYIF_BIT_WIDTH * devid));
- reg |= (phyif & DWMAC_PHYIF_MASK) << (DWMAC_PHYIF_BIT_WIDTH * devid);
+ //This reg defined bit not related to devid
+ reg &= ~(DWMAC_PHYIF_MASK );
+ reg |= (phyif & DWMAC_PHYIF_MASK) ;
+ dev_info(dev,"set phy_if_reg val 0x%x \n",reg);
writel(reg, phy_if_reg);
}
static int thead_dwmac_init(struct platform_device *pdev, void *bsp_priv)
{
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
- struct plat_stmmacenet_data *plat_dat = thead_plat_dat->plat_dat;
+ //struct plat_stmmacenet_data *plat_dat = thead_plat_dat->plat_dat;
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct resource *res;
void __iomem *ptr;
- struct clk *clktmp;
- int ret;
+ //struct clk *clktmp;
+ //int ret;
thead_plat_dat->id = of_alias_get_id(np, "ethernet");
if (thead_plat_dat->id < 0) {
} else {
thead_plat_dat->gmac_clk_reg = ptr;
}
-
+#if 0
/* get gmac pll clk */
clktmp = devm_clk_get(dev, "gmac_pll_clk");
if (IS_ERR(clktmp)) {
clk_get_rate(thead_plat_dat->gmac_pll_clk);
}
+
thead_dwmac_set_phy_if(plat_dat);
thead_dwmac_set_txclk_dir(plat_dat);
if (thead_plat_dat->ops->enable_clk)
thead_plat_dat->ops->enable_clk(plat_dat);
-
+#endif
return 0;
}
}
return x;
}
+static void __maybe_unused thead_dwmac_dump_priv_reg(struct platform_device *pdev, void *bsp_priv)
+{
+ struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
+ struct device *dev = &pdev->dev;
+ void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
+ unsigned int reg = 0;
+ int i;
+ dev_info(dev,"dump gmac_clk_reg %p\n",gmac_clk_reg);
+ if(gmac_clk_reg == NULL)
+ return ;
+ for(i=0; i< 0x1c; i+=4)
+ {
+ reg = readl(gmac_clk_reg + GMAC_CLK_CFG0+i);
+ pr_info("%08x ",reg);
+ }
+ pr_info("\n");
+ reg = readl(thead_plat_dat->phy_if_reg);
+ pr_info("phy_if_reg %08x ",reg);
+ reg = readl(thead_plat_dat->txclk_dir_reg);
+ pr_info("txclk_dir_reg %08x ",reg);
+}
+
+int thead_dwmac_clk_enable(struct platform_device *pdev, void *bsp_priv)
+{
+ struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
+ struct device *dev = &pdev->dev;
+ int ret;
+ pm_debug(dev,"enter %s()\n",__func__);
+ ret = clk_prepare_enable(thead_plat_dat->gmac_pll_clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable clk 'gmac_pll_clk'\n");
+ return -1;
+ }
+ ret = clk_prepare_enable(thead_plat_dat->gmac_axi_aclk);
+ if (ret) {
+ clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
+ dev_err(dev, "Failed to enable clk 'gmac_axi_aclk'\n");
+ return -1;
+ }
+ ret = clk_prepare_enable(thead_plat_dat->gmac_axi_pclk);
+ if (ret) {
+ clk_disable_unprepare(thead_plat_dat->gmac_axi_aclk);
+ clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
+ dev_err(dev, "Failed to enable clk 'gmac_axi_pclk'\n");
+ return -1;
+ }
+
+ return ret;
+}
+
+int thead_dwmac_clk_init(struct platform_device *pdev, void *bsp_priv)
+{
+ struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
+ struct device *dev = &pdev->dev;
+ struct plat_stmmacenet_data *plat_dat = thead_plat_dat->plat_dat;
+ int ret = 0;
+ pm_debug(dev,"enter %s()\n",__func__);
+
+ thead_dwmac_set_phy_if(plat_dat);
+ thead_dwmac_set_txclk_dir(plat_dat);
+
+ if (thead_plat_dat->ops->set_clk_source)
+ thead_plat_dat->ops->set_clk_source(plat_dat);
+
+ thead_dwmac_set_clock_delay(plat_dat);
+
+ if (thead_plat_dat->ops->set_clk_pll)
+ thead_plat_dat->ops->set_clk_pll(plat_dat);
+
+ if (thead_plat_dat->ops->set_clk_div)
+ thead_plat_dat->ops->set_clk_div(plat_dat, SPEED_1000);
+
+ if (thead_plat_dat->ops->enable_clk)
+ thead_plat_dat->ops->enable_clk(plat_dat);
+
+ //thead_dwmac_dump_priv_reg(pdev,bsp_priv);
+ return ret;
+}
+void thead_dwmac_clk_disable(struct platform_device *pdev, void *bsp_priv)
+{
+ struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
+ struct device *dev = &pdev->dev;
+ pm_debug(dev,"enter %s()\n",__func__);
+ //thead_dwmac_dump_priv_reg(pdev,bsp_priv);
+
+ clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
+ clk_disable_unprepare(thead_plat_dat->gmac_axi_aclk);
+ clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
+
+ return ;
+}
static int thead_dwmac_probe(struct platform_device *pdev)
{
plat_dat->unicast_filter_entries = 1;
}
- /* Custom initialisation (if needed) */
- if (plat_dat->init) {
- ret = plat_dat->init(pdev, plat_dat->bsp_priv);
- if (ret)
- goto err_remove_config_dt;
- }
+
/* populate bsp private data */
thead_plat_dat->dev = &pdev->dev;
plat_dat->bsp_priv = thead_plat_dat;
plat_dat->fix_mac_speed = thead_dwmac_fix_speed;
+ plat_dat->init = thead_dwmac_clk_init;
of_property_read_u32(np, "max-frame-size", &plat_dat->maxmtu);
of_property_read_u32(np, "snps,multicast-filter-bins",
&plat_dat->multicast_filter_bins);
plat_dat->pmt = 1;
thead_plat_dat->plat_dat = plat_dat;
+ /* get gmac pll clk */
+ thead_plat_dat->gmac_pll_clk = devm_clk_get(dev, "gmac_pll_clk");
+ if (IS_ERR(thead_plat_dat->gmac_pll_clk)) {
+ dev_err(dev, "gmac_pll_clk not exist, dts error\n");
+ goto err_remove_config_dt;
+ }
+
+ thead_plat_dat->gmac_axi_aclk = devm_clk_get(dev, "axi_aclk");
+ if (IS_ERR(thead_plat_dat->gmac_axi_aclk)) {
+ dev_err(dev, "gmac axi_aclk not exist, skipped it\n");
+ }
+ thead_plat_dat->gmac_axi_pclk = devm_clk_get(dev, "axi_pclk");
+ if (IS_ERR(thead_plat_dat->gmac_axi_pclk)) {
+ dev_err(dev, "gmac axi_pclk not exist, skipped it\n");
+ }
+
+
+ thead_plat_dat->gmac_pll_clk_freq =
+ clk_get_rate(thead_plat_dat->gmac_pll_clk);
+ dev_info(dev,"get_rate gmac_pll_clk_freq %ld \n",thead_plat_dat->gmac_pll_clk_freq);
+
ret = thead_dwmac_init(pdev, plat_dat->bsp_priv);
if (ret)
- goto err_exit;
+ goto err_remove_config_dt;
+
+ ret = thead_dwmac_clk_enable(pdev, plat_dat->bsp_priv);
+ if (ret)
+ goto err_remove_config_dt;
+
+ /* Custom initialisation (if needed) -- init clks*/
+ if (plat_dat->init) {
+ ret = plat_dat->init(pdev, plat_dat->bsp_priv);
+ if (ret)
+ goto err_exit;
+ }
ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
if (ret)
err_exit:
if (plat_dat->exit)
plat_dat->exit(pdev, plat_dat->bsp_priv);
+ thead_dwmac_clk_disable(pdev, plat_dat->bsp_priv);
err_remove_config_dt:
if (pdev->dev.of_node)
stmmac_remove_config_dt(pdev, plat_dat);
return ret;
}
+/**
+ * stmmac_pltfr_suspend
+ * @dev: device pointer
+ * Description: this function is invoked when suspend the driver and it direcly
+ * call the main suspend function and then, if required, on some platform, it
+ * can call an exit helper.
+ */
+static int __maybe_unused thead_dwmac_suspend(struct device *dev)
+{
+ int ret;
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct platform_device *pdev = to_platform_device(dev);
+ pm_debug(dev,"enter %s()\n",__func__);
+ ret = stmmac_suspend(dev);
+ if (priv->plat->exit)
+ priv->plat->exit(pdev, priv->plat->bsp_priv);
+
+ return ret;
+}
+
+/**
+ * thead_dwmac_resume
+ * @dev: device pointer
+ * Description: this function is invoked when resume the driver before calling
+ * the main resume function, on some platforms, it can call own init helper
+ * if required.
+ */
+static int __maybe_unused thead_dwmac_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct platform_device *pdev = to_platform_device(dev);
+ pm_debug(dev,"enter %s()\n",__func__);
+
+ if (priv->plat->init)
+ priv->plat->init(pdev, priv->plat->bsp_priv);
+
+ return stmmac_resume(dev);
+}
+
+static int __maybe_unused thead_dwmac_runtime_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct platform_device *pdev = to_platform_device(dev);
+ pm_debug(dev,"enter %s()\n",__func__);
+ stmmac_bus_clks_config(priv, false);
+ thead_dwmac_clk_disable(pdev, priv->plat->bsp_priv);
+ return 0;
+}
+
+static int __maybe_unused thead_dwmac_runtime_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct platform_device *pdev = to_platform_device(dev);
+ int ret;
+ pm_debug(dev,"enter %s()\n",__func__);
+ ret = stmmac_bus_clks_config(priv, true);
+ if(ret)
+ return ret;
+ ret = thead_dwmac_clk_enable(pdev, priv->plat->bsp_priv);
+ if(ret)
+ return ret;
+ return 0;
+}
+
+static int __maybe_unused thead_dwmac_noirq_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int ret;
+ pm_debug(dev,"enter %s()\n",__func__);
+ if (!netif_running(ndev))
+ return 0;
+
+ if (!device_may_wakeup(priv->device) || !priv->plat->pmt) {
+ /* Disable clock in case of PWM is off */
+ clk_disable_unprepare(priv->plat->clk_ptp_ref);
+
+ ret = pm_runtime_force_suspend(dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused thead_dwmac_noirq_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int ret;
+ pm_debug(dev,"enter %s()\n",__func__);
+ if (!netif_running(ndev))
+ return 0;
+
+ if (!device_may_wakeup(priv->device) || !priv->plat->pmt) {
+ /* enable the clk previously disabled */
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
+ if (ret < 0) {
+ netdev_warn(priv->dev,
+ "failed to enable PTP reference clock: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/*similar with stmmac_pltfr_pm_ops,but clks enable/disable add this drv need */
+const struct dev_pm_ops thead_dwmac_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(thead_dwmac_suspend, thead_dwmac_resume)
+ SET_RUNTIME_PM_OPS(thead_dwmac_runtime_suspend, thead_dwmac_runtime_resume, NULL)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(thead_dwmac_noirq_suspend, thead_dwmac_noirq_resume)
+};
+
+
static struct thead_dwmac_ops thead_ice_dwmac_data = {
.set_clk_source = thead_dwmac_ice_set_clk_source,
.set_clk_pll = thead_dwmac_ice_set_pll,
.remove = stmmac_pltfr_remove,
.driver = {
.name = "light_dwmac_eth",
- .pm = &stmmac_pltfr_pm_ops,
+ .pm = &thead_dwmac_pm_ops,
.of_match_table = of_match_ptr(thead_dwmac_match),
},
};
return readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
!(value & DMA_BUS_MODE_SFT_RESET),
- 10000, 200000);
+ 10000, 1000000); //inspired from : net: stmmac: increase the timeout for dma reset
}
/* CSR1 enables the transmit DMA to check for new descriptor */
struct timespec64 now;
u32 sec_inc = 0;
u64 temp = 0;
- int ret;
if (!(priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp))
return -EOPNOTSUPP;
- ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
- if (ret < 0) {
- netdev_warn(priv->dev,
- "failed to enable PTP reference clock: %pe\n",
- ERR_PTR(ret));
- return ret;
- }
-
stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags);
priv->systime_flags = systime_flags;
stmmac_mmc_setup(priv);
+ if (ptp_register) {
+ ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
+ if (ret < 0)
+ netdev_warn(priv->dev,
+ "failed to enable PTP reference clock: %pe\n",
+ ERR_PTR(ret));
+ }
+
ret = stmmac_init_ptp(priv);
if (ret == -EOPNOTSUPP)
netdev_warn(priv->dev, "PTP not supported by HW\n");
if (ret)
return ret;
- stmmac_init_tstamp_counter(priv, priv->systime_flags);
+ ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
+ if (ret < 0) {
+ netdev_warn(priv->dev,
+ "failed to enable PTP reference clock: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
}
return 0;
dev_dbg(dev, "[%s,%d] ret = %d, pd status: 0x%lx\n", __func__, __LINE__, ret,
readl(priv->base + CON) & EFUSE_CON_POWER_MSK);
+
+ clk_disable_unprepare(priv->clk);
return ret;
}
static int __maybe_unused light_efuse_runtime_resume(struct device *dev)
{
struct light_efuse_priv *priv = dev_get_drvdata(dev);
+ int ret;
dev_dbg(dev, "[%s,%d]efuse runtime power on\n", __func__, __LINE__);
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(dev, "failed to get efuse clk\n");
+ return ret;
+ }
+
+ return efuse_poweron(priv->base);
+}
+
+static int __maybe_unused light_efuse_suspend(struct device *dev)
+{
+ struct light_efuse_priv *priv = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ dev_dbg(dev, "[%s,%d]efuse suspend enter\n", __func__, __LINE__);
+
+ if (!efuse_poweron_status_get(priv->base))
+ return 0;
+
+ data = readl(priv->base + CON);
+ data |= EFUSE_CON_POWER_MSK;
+ writel(data, priv->base + CON);
+
+ ret = efuse_idle_check(priv->base);
+
+ ret |= efuse_status_check(priv->base);
+
+ dev_dbg(dev, "[%s,%d] ret = %d, pd status: 0x%lx\n", __func__, __LINE__, ret,
+ readl(priv->base + CON) & EFUSE_CON_POWER_MSK);
+
+ clk_disable_unprepare(priv->clk);
+
+ return ret;
+}
+
+static int __maybe_unused light_efuse_resume(struct device *dev)
+{
+ struct light_efuse_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "[%s,%d]efuse resume enter\n", __func__, __LINE__);
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(dev, "failed to get efuse clk\n");
+ return ret;
+ }
+
return efuse_poweron(priv->base);
}
return PTR_ERR(priv->base);
/* optional clock, default open */
- priv->clk = devm_clk_get(dev, NULL);
- if (IS_ERR(priv->clk))
- priv->clk = NULL;
+ priv->clk = devm_clk_get(dev, "pclk");
+ if (IS_ERR_OR_NULL(priv->clk)) {
+ if (PTR_ERR(priv->clk) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "failed to get efuse clk\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(dev, "failed to get efuse clk\n");
+ return ret;
+ }
priv->teesys_regs = syscon_regmap_lookup_by_phandle(dev->of_node, "thead,teesys");
if (IS_ERR(priv->teesys_regs)) {
dev_set_drvdata(dev, priv);
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to get the efuse device(%d)\n", ret);
+ pm_runtime_put_noidle(dev);
+ return ret;
+ }
+
ret = sysfs_create_group(&dev->kobj, &dev_attr_efuse_sysfs_group);
if (ret) {
dev_err(dev, "failed to create efuse debug sysfs\n");
if (IS_ERR(nvmem))
return PTR_ERR_OR_ZERO(nvmem);
- pm_runtime_enable(dev);
+ pm_runtime_put_sync(dev);
dev_info(dev, "succeed to register light efuse driver\n");
static const struct dev_pm_ops efuse_runtime_pm_ops = {
SET_RUNTIME_PM_OPS(light_efuse_runtime_suspend, light_efuse_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(light_efuse_suspend, light_efuse_resume)
};
static struct platform_driver light_efuse_driver = {
return 0;
}
-#endif
+
+static int dw_dphy_resume(struct device *dev)
+{
+ struct dw_dphy *dphy = dev_get_drvdata(dev);
+
+ dw_dphy_init(dphy->phy);
+
+ return 0;
+}
static const struct dev_pm_ops dw_dphy_pm_ops = {
- SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
- pm_runtime_force_resume)
- SET_RUNTIME_PM_OPS(dw_dphy_runtime_suspend, dw_dphy_runtime_resume, NULL)
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL,
+ dw_dphy_resume)
+ SET_RUNTIME_PM_OPS(dw_dphy_runtime_suspend, dw_dphy_runtime_resume, NULL)
};
+#define DW_DPHY_PM_OPS &dw_dphy_pm_ops
+#else
+#define DW_DPHY_PM_OPS NULL
+#endif
+
+
static struct platform_driver dw_dphy_driver = {
.probe = dw_dphy_probe,
.remove = dw_dphy_remove,
.driver = {
.name = "dw-mipi-dphy",
.of_match_table = dw_dphy_of_match,
- .pm = &dw_dphy_pm_ops,
+ .pm = DW_DPHY_PM_OPS,
},
};
module_platform_driver(dw_dphy_driver);
* Copyright (C) 2021 Alibaba Group Holding Limited.
*
*/
+#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
int (*covert_pin_off)(const unsigned int pin_id);
};
+#ifdef CONFIG_PM_SLEEP
+#define MAX_CFG_REG_NUMS 32
+#define MAX_MUX_REG_NUMS 8
+#define LIGHT_PADCTRL0_CFG_REG_NUMS 28
+#define LIGHT_PADCTRL0_MUX_REG_NUMS 7
+#define LIGHT_PADCTRL1_CFG_REG_NUMS 32
+#define LIGHT_PADCTRL1_MUX_REG_NUMS 8
+#define LIGHT_AUDIO_CFG_REG_NUMS 16
+#define LIGHT_AUDIO_MUX_REG_NUMS 2
+#define LIGHT_AUDIO_IO_SEL_IDX 2
+#define LIGHT_PM_PAD_CFG(idx) (priv->base + priv->info->cfg_off + idx * 4)
+#define LIGHT_PM_PAD_MUX(idx) (priv->base + priv->info->mux_off + idx * 4)
+#endif
+
struct light_pinctrl {
struct device *dev;
struct pinctrl_dev *pctl;
void __iomem *base;
+ struct clk *clk;
+#ifdef CONFIG_PM_SLEEP
+ unsigned int cfg_bak[MAX_CFG_REG_NUMS];
+ unsigned int mux_bak[MAX_MUX_REG_NUMS];
+#endif
const struct light_pinctrl_soc_info *info;
};
priv->info = info;
priv->dev = info->dev;
+ if ((priv->info->type == LIGHT_FM_LEFT) ||
+ (priv->info->type == LIGHT_FM_RIGHT)) {
+ priv->clk = devm_clk_get_optional(&pdev->dev, "pclk");
+ if(priv->clk == NULL) {
+ dev_err(&pdev->dev, "could not get padctrl clk\n");
+ return -EINVAL;
+ }
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "could not enable padctrl clk\n");
+ return -EINVAL;
+ }
+ }
+
platform_set_drvdata(pdev, priv);
priv->pctl = pinctrl_register(info->desc, &pdev->dev, priv);
if (!priv->pctl) {
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int light_pinctrl_backup_regs(struct light_pinctrl *priv, unsigned int cfg_reg_nums,
+ unsigned int mux_reg_nums)
+{
+ int i;
+
+ for (i = 0; i < cfg_reg_nums; i++)
+ priv->cfg_bak[i] = readl(LIGHT_PM_PAD_CFG(i));
+ for (i = 0; i < mux_reg_nums; i++)
+ priv->mux_bak[i] = readl(LIGHT_PM_PAD_MUX(i));
+
+ return 0;
+}
+
+static int light_pinctrl_restore_regs(struct light_pinctrl *priv, unsigned int cfg_reg_nums,
+ unsigned int mux_reg_nums)
+{
+ int i;
+
+ for (i = 0; i < cfg_reg_nums; i++)
+ writel(priv->cfg_bak[i], LIGHT_PM_PAD_CFG(i));
+ for (i = 0; i < mux_reg_nums; i++)
+ writel(priv->mux_bak[i], LIGHT_PM_PAD_MUX(i));
+
+ return 0;
+}
+
+static int light_pinctrl_suspend(struct device *dev)
+{
+ dev_info(dev, "light pinctrl suspend\n");
+ struct light_pinctrl *priv = dev_get_drvdata(dev);
+ int ret = 0;
+
+ switch(priv->info->type) {
+ case LIGHT_FM_RIGHT:
+ ret = light_pinctrl_backup_regs(priv, LIGHT_PADCTRL0_CFG_REG_NUMS, LIGHT_PADCTRL0_MUX_REG_NUMS);
+ clk_disable_unprepare(priv->clk);
+ break;
+ case LIGHT_FM_LEFT:
+ ret = light_pinctrl_backup_regs(priv, LIGHT_PADCTRL1_CFG_REG_NUMS, LIGHT_PADCTRL1_MUX_REG_NUMS);
+ clk_disable_unprepare(priv->clk);
+ break;
+ case LIGHT_FM_AON:
+ break;
+ case LIGHT_FM_AUDIO:
+ ret = light_pinctrl_backup_regs(priv, LIGHT_AUDIO_CFG_REG_NUMS, LIGHT_AUDIO_MUX_REG_NUMS);
+ priv->mux_bak[LIGHT_AUDIO_IO_SEL_IDX] = readl(priv->base);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int light_pinctrl_resume(struct device *dev)
+{
+ dev_info(dev, "light pinctrl resume\n");
+ struct light_pinctrl *priv = dev_get_drvdata(dev);
+ int ret = 0;
+
+ switch(priv->info->type) {
+ case LIGHT_FM_RIGHT:
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(dev, "could not enable padctrl clk\n");
+ return -EINVAL;
+ }
+ ret = light_pinctrl_restore_regs(priv, LIGHT_PADCTRL0_CFG_REG_NUMS, LIGHT_PADCTRL0_MUX_REG_NUMS);
+ break;
+ case LIGHT_FM_LEFT:
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(dev, "could not enable padctrl clk\n");
+ return -EINVAL;
+ }
+ ret = light_pinctrl_restore_regs(priv, LIGHT_PADCTRL1_CFG_REG_NUMS, LIGHT_PADCTRL1_MUX_REG_NUMS);
+ break;
+ case LIGHT_FM_AON:
+ break;
+ case LIGHT_FM_AUDIO:
+ ret = light_pinctrl_restore_regs(priv, LIGHT_AUDIO_CFG_REG_NUMS, LIGHT_AUDIO_MUX_REG_NUMS);
+ writel(priv->mux_bak[LIGHT_AUDIO_IO_SEL_IDX], priv->base);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+#endif //CONFIG_PM_SLEEP
+
/* Pad names for the pinmux subsystem */
static const struct pinctrl_pin_desc light_mpw_pinctrl_pads[] = {
LIGHT_PINCTRL_PIN(BOOT_SEL0),
.mux_off = 0x4,
};
+//static SIMPLE_DEV_PM_OPS(light_pinctrl_dev_pm_ops,
+// light_pinctrl_suspend, light_pinctrl_resume);
+
+static const struct dev_pm_ops light_pinctrl_dev_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(light_pinctrl_suspend, light_pinctrl_resume)
+};
+
static const struct of_device_id light_pinctrl_of_match[] = {
{ .compatible = "thead,light-mpw-pinctrl", .data = &light_mpw_pinctrl_info},
{ .compatible = "thead,light-fm-right-pinctrl", .data = &light_fm_right_pinctrl_info},
.driver = {
.name = "light-pinctrl",
.owner = THIS_MODULE,
+ .pm = &light_pinctrl_dev_pm_ops,
.of_match_table = light_pinctrl_of_match,
.suppress_bind_attrs = true,
},
int ret;
struct light_rpmsg_vq_info *rpvq = vq->priv;
+#ifdef CONFIG_PM_SLEEP
+ if(rpvq->rpdev->sleep_flag) {
+ dev_err(tdev_priv->dev, "dev in deep sleep, Channel cannot do Tx+++\n");
+ return -EINVAL;
+ }
+#endif
+
mu_rpmsg = rpvq->vq_id << 16;
mutex_lock(&rpvq->rpdev->lock);
INIT_DELAYED_WORK(&(rpdev->rpmsg_work), rpmsg_work_handler);
BLOCKING_INIT_NOTIFIER_HEAD(&(rpdev->notifier));
-
+#ifdef CONFIG_PM_SLEEP
+ sema_init(&rpdev->pm_sem, 0);
+#endif
pr_info("light rpmsg: Ready for cross core communication!\n");
ret = of_property_read_u32(np, "vdev-nums", &rpdev->vdev_nums);
}
#ifdef CONFIG_PM_SLEEP
-static int light_rpmsg_suspend(struct device *dev)
-{
- struct light_rpmsg_vproc *rpdev = dev_get_drvdata(dev);
-
- clk_disable_unprepare(rpdev->mu_clk);
+typedef enum {
+ RPMSG_MAILBOX_TYPE_PM = 0xA0,
+ RPMSG_MAILBOX_TYPE_MAX
+} rpmsg_mailbox_message_type_en;
+
+typedef enum {
+ RPMSG_PM_CTRL = 0x50,
+ RPMSG_PM_GET,
+ RPMSG_PM_STATUS,
+ RPMSG_PM_MAX
+} rpmsg_pm_message_type_en;
+
+typedef enum {
+ LIGHT_PM_DISABLE = 0xA0,
+ LIGHT_PM_OFF,
+ LIGHT_PM_HW_VAD,
+ LIGHT_PM_TYPE_MAX
+} light_pm_type_en;
+
+typedef enum {
+ LIGHT_PM_WAKEUP = 0x50,
+ LIGHT_PM_SLEEP,
+ LIGHT_PM_STATUS_MAX
+} light_pm_status_en;
+
+#define MAX_PM_NOTIFY_TIME 10
+#define MAX_PM_ASK_TIME 10
+
+static int light_rpmsg_sleep_notify(struct virtqueue *vq, light_pm_type_en type)
+{
+ int ret;
+ struct light_rpmsg_vq_info *rpvq = vq->priv;
+ uint8_t sleep_ctrl[4] = {RPMSG_MAILBOX_TYPE_PM, RPMSG_PM_CTRL, type, '\n'};
+ mutex_lock(&rpvq->rpdev->lock);
+ ret = mbox_send_message(tdev_priv->tx_channel, sleep_ctrl);
+ if(ret < 0) {
+ pr_err("sleep notify faild %d", ret);
+ mutex_unlock(&rpvq->rpdev->lock);
+ return ret;
+ }
+ mutex_unlock(&rpvq->rpdev->lock);
return 0;
}
-static int light_rpmsg_resume(struct device *dev)
+static int light_rpmsg_sleep_ask(struct virtqueue *vq)
{
- struct light_rpmsg_vproc *rpdev = dev_get_drvdata(dev);
int ret;
+ struct light_rpmsg_vq_info *rpvq = vq->priv;
+ uint8_t sleep_get[3] = {RPMSG_MAILBOX_TYPE_PM, RPMSG_PM_GET, '\n'};
+ mutex_lock(&rpvq->rpdev->lock);
+ ret = mbox_send_message(tdev_priv->tx_channel, sleep_get);
+ if(ret < 0) {
+ pr_err("sleep ask send faild %d", ret);
+ mutex_unlock(&rpvq->rpdev->lock);
+ return ret;
+ }
+ mutex_unlock(&rpvq->rpdev->lock);
+ return 0;
+}
- ret = clk_prepare_enable(rpdev->mu_clk);
- if (ret) {
- pr_err("unable to enable mu clock\n");
- return ret;
+static int light_rpmsg_suspend(struct device *dev)
+
+{
+ int ret;
+ int try_num = 0;
+ struct light_rpmsg_vproc *rpdev = dev_get_drvdata(dev);
+
+ //clk_disable_unprepare(rpdev->mu_clk);
+ printk("%s,%d,enter",__func__,__LINE__);
+ light_rpmsg_sleep_notify(rpdev->ivdev[0].vq[0], LIGHT_PM_OFF);
+ try_num++;
+ down_timeout(&rpdev->pm_sem, msecs_to_jiffies(200));
+ while(!rpdev->sleep_flag) {
+ light_rpmsg_sleep_notify(rpdev->ivdev[0].vq[0], LIGHT_PM_OFF);
+ down_timeout(&rpdev->pm_sem, msecs_to_jiffies(200));
+ if(try_num++ > MAX_PM_NOTIFY_TIME) {
+ pr_err("sleep notify faild after try %d time", MAX_PM_NOTIFY_TIME);
+ printk("%s,%d,try %d times, exist",__func__,__LINE__, try_num);
+ return -1;
}
+ }
+ printk("%s,%d,try %d times, exist",__func__,__LINE__, try_num);
+ return 0;
+}
- return ret;
+static int light_rpmsg_resume(struct device *dev)
+{
+ struct light_rpmsg_vproc *rpdev = dev_get_drvdata(dev);
+ int ret;
+ int try_num = 0;
+ printk("%s,%d,enter",__func__,__LINE__);
+ while(rpdev->sleep_flag) {
+ ret = light_rpmsg_sleep_ask(rpdev->ivdev[0].vq[0]);
+ down_timeout(&rpdev->pm_sem, msecs_to_jiffies(200));
+ if(try_num++ > MAX_PM_ASK_TIME) {
+ pr_err("sleep status check faild after try %d time", MAX_PM_ASK_TIME);
+ printk("%s,%d,try %d times, exist",__func__,__LINE__, try_num);
+ return -1;
+ }
+ }
+ printk("%s,%d,try %d times, exist",__func__,__LINE__, try_num);
+ return ret;
}
+
#endif
static SIMPLE_DEV_PM_OPS(light_rpmsg_pm_ops, light_rpmsg_suspend, light_rpmsg_resume);
//printk("mbox_client receive rpmsg_work\n");
schedule_delayed_work(&(pri_rpdev->rpmsg_work), 0);
-
+#ifdef CONFIG_PM_SLEEP
+ if(data[0] == RPMSG_MAILBOX_TYPE_PM && data[1] == RPMSG_PM_STATUS) {
+ if(data[2] == LIGHT_PM_WAKEUP) {
+ pri_rpdev->sleep_flag = 0;
+ up(&pri_rpdev->pm_sem);
+ printk("audio wakeup");
+ } else if(data[2] == LIGHT_PM_SLEEP) {
+ pri_rpdev->sleep_flag = 1;
+ up(&pri_rpdev->pm_sem);
+ printk("audio sleep");
+ }
+ }
+#endif
//print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_NONE, 16, 1, tdev->rx_buffer, MBOX_MAX_MSG_LEN, true);
}
struct light_iopmp_info {
struct device *dev;
-
int entries;
struct light_iopmp_list *iopmp_list;
struct light_iopmp_cur_sys_entry cur_entry;
int iopmp_type;
resource_size_t offset;
void __iomem *base;
+ bool bypass_en;
+ bool is_default_region;
+ u32 dummy_slave;
+ u32 attr;
+ int lock;
+ struct light_iopmp_entry iopmp_entry;
} light_iopmp_lists[] = {
- {IOPMP_EMMC, 0xFFFC028000, NULL},
- {IOPMP_SDIO0, 0xFFFC029000, NULL},
- {IOPMP_SDIO1, 0xFFFC02A000, NULL},
- {IOPMP_USB0, 0xFFFC02E000, NULL},
- {IOPMP_AO, 0xFFFFC21000, NULL},
- {IOPMP_AUD, 0xFFFFC22000, NULL},
- {IOPMP_CHIP_DBG, 0xFFFFC37000, NULL},
- {IOPMP_EIP120I, 0xFFFF220000, NULL},
- {IOPMP_EIP120II, 0xFFFF230000, NULL},
- {IOPMP_EIP120III, 0xFFFF240000, NULL},
- {IOPMP_ISP0, 0xFFF4080000, NULL},
- {IOPMP_ISP1, 0xFFF4081000, NULL},
- {IOPMP_DW200, 0xFFF4082000, NULL},
- {IOPMP_VIPRE, 0xFFF4083000, NULL},
- {IOPMP_VENC, 0xFFFCC60000, NULL},
- {IOPMP_VDEC, 0xFFFCC61000, NULL},
- {IOPMP_G2D, 0xFFFCC62000, NULL},
- {IOPMP_FCE, 0xFFFCC63000, NULL},
- {IOPMP_NPU, 0xFFFF01C000, NULL},
- {IOPMP0_DPU, 0xFFFF520000, NULL},
- {IOPMP1_DPU, 0xFFFF521000, NULL},
- {IOPMP_GPU, 0xFFFF522000, NULL},
- {IOPMP_GMAC1, 0xFFFC001000, NULL},
- {IOPMP_GMAC2, 0xFFFC002000, NULL},
- {IOPMP_DMAC, 0xFFFFC20000, NULL},
- {IOPMP_TEE_DMAC, 0xFFFF250000, NULL},
- {IOPMP_DSP0, 0xFFFF058000, NULL},
- {IOPMP_DSP1, 0xFFFF059000, NULL},
+ {IOPMP_EMMC, 0xFFFC028000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_SDIO0, 0xFFFC029000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_SDIO1, 0xFFFC02A000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_USB0, 0xFFFC02E000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_AO, 0xFFFFC21000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_AUD, 0xFFFFC22000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_CHIP_DBG, 0xFFFFC37000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_EIP120I, 0xFFFF220000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_EIP120II, 0xFFFF230000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_EIP120III, 0xFFFF240000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_ISP0, 0xFFF4080000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_ISP1, 0xFFF4081000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_DW200, 0xFFF4082000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_VIPRE, 0xFFF4083000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_VENC, 0xFFFCC60000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_VDEC, 0xFFFCC61000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_G2D, 0xFFFCC62000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_FCE, 0xFFFCC63000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_NPU, 0xFFFF01C000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP0_DPU, 0xFFFF520000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP1_DPU, 0xFFFF521000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_GPU, 0xFFFF522000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_GMAC1, 0xFFFC001000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_GMAC2, 0xFFFC002000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_DMAC, 0xFFFFC20000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_TEE_DMAC, 0xFFFF250000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_DSP0, 0xFFFF058000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
+ {IOPMP_DSP1, 0xFFFF059000, NULL, false, false, LIGHT_IOPMP_DUMMY_ADDR, 0xFFFFFFFF, 0x7FFFF, {{0,},{0,},0} },
};
static const struct light_iopmp_driver_data {
return count;
}
+
+static int light_iopmp_config(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct light_iopmp_info *info = platform_get_drvdata(pdev);
+ struct light_iopmp_list *list = info->iopmp_list;
+ int i, j;
+
+ for (i = 0; i < info->entries; i++) {
+ if (list[i].iopmp_entry.entry_valid_num > 0) {
+ /* config the iopmp entry */
+ light_iopmp_set_attr(info, i,
+ list[i].attr,
+ list[i].dummy_slave,
+ &list[i].iopmp_entry);
+
+ } else {
+ /* config the iopmp entry */
+ if (list[i].bypass_en)
+ light_iopmp_set_attr_bypass(info, i);
+ else if (list[i].is_default_region)
+ light_iopmp_set_attr_default(info, i, list[i].attr);
+ }
+ }
+
+ return 0;
+}
+
+
static DEVICE_ATTR(light_iopmp_tap, 0644, light_iopmp_tap_show, light_iopmp_tap_store);
static DEVICE_ATTR(light_iopmp_start_addr, 0644, light_iopmp_start_addr_show, light_iopmp_start_addr_store);
static DEVICE_ATTR(light_iopmp_end_addr, 0644, light_iopmp_end_addr_show, light_iopmp_end_addr_store);
{
struct device *dev = &pdev->dev;
const struct light_iopmp_driver_data *drvdata = NULL;
- struct light_iopmp_list *match;
+ struct light_iopmp_list *list;
struct light_iopmp_info *info;
struct device_node *entry_np;
int size, entries;
int ret;
+ int i, j;
drvdata = device_get_match_data(dev);
if (!drvdata) {
dev_err(dev, "cannot get driver data\n");
return -ENOENT;
}
- match = drvdata->iopmp_list;
- entries = light_iopmp_get_entries(match);
+ list = drvdata->iopmp_list;
+ entries = light_iopmp_get_entries(list);
if (entries <= 0)
return -ENOENT;
return -ENOMEM;
info->entries = entries;
- info->iopmp_list = match;
+ info->iopmp_list = list;
info->dev = dev;
for_each_child_of_node(dev->of_node, entry_np) {
bool bypass_en = false;
int region_size;
int type;
- int i = 0, j;
if (!of_device_is_available(entry_np))
continue;
dev_err(dev, "missing/invalid reg property\n");
continue;
}
-
- /* config the iopmp entry */
- if (bypass_en)
- light_iopmp_set_attr_bypass(info, type);
- else if (is_default_region)
- light_iopmp_set_attr_default(info, type, attr);
-
} else {
region_size >>= 1;
if (region_size > LIGHT_IOPMP_REGION_NUM) {
for (j = 0; j < region_size; j++) {
ret = of_property_read_u32_index(entry_np, "regions",
- j << 1, &info->iopmp_entry[i].reg_start[j]);
+ j << 1, &list[type].iopmp_entry.reg_start[j]);
if (ret)
break;
ret = of_property_read_u32_index(entry_np, "regions",
- (j << 1) + 1, &info->iopmp_entry[i].reg_end[j]);
+ (j << 1) + 1, &list[type].iopmp_entry.reg_end[j]);
if (ret)
break;
/* check the region valid, otherwise drop the iopmp setting */
- if (j && info->iopmp_entry[i].reg_end[j - 1] > info->iopmp_entry[i].reg_start[j])
+ if (j && list[type].iopmp_entry.reg_end[j - 1] > list[type].iopmp_entry.reg_start[j])
break;
}
- if (j == region_size) {
- info->iopmp_entry[i].entry_valid_num = region_size;
+ if (j < region_size)
+ continue;
- /* config the iopmp entry */
- light_iopmp_set_attr(info, type, attr, dummy_slave, &info->iopmp_entry[i]);
- }
+ list[type].iopmp_entry.entry_valid_num = region_size;
}
- i++;
+ // store valid config
+ list[type].attr = attr;
+ list[type].dummy_slave = dummy_slave;
+ list[type].is_default_region = is_default_region;
+ list[type].bypass_en = bypass_en;
}
/* init the cur entry config */
platform_set_drvdata(pdev, info);
+ light_iopmp_config(&pdev->dev);
+
ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_light_iopmp_group);
if (ret) {
dev_err(&pdev->dev, "Failed to create light iopmp debug sysfs.\n");
static int __maybe_unused light_iopmp_noirq_suspend(struct device *dev)
{
- /* the iopmp clocks depend on module theirself,
- * we should keep them always enabled in clock's driver
- */
+ // nothing to do
return 0;
}
static int __maybe_unused light_iopmp_noirq_resume(struct device *dev)
{
- /* TBD: restore IOPMP in noirq stage is too late ?
- * we should restore these registers setting in early arch resume ?
- */
+ /* reinit iopmp register setting
+ ensure system clock enabled before here
+ */
+ light_iopmp_config(dev);
+
return 0;
}
struct light_aon_ipc *ipc = event->ipc_handle;
int ret = 0;
- dev_dbg(light_event->dev, "aon reservemem...\n");
+ dev_dbg(event->dev, "aon reservemem...\n");
light_event_msg_hdr_fill(&event->msg.hdr, LIGHT_AON_MISC_FUNC_AON_RESERVE_MEM);
event->msg.reserve_offset = LIGHT_EVENT_OFFSET;
ret = light_aon_call_rpc(ipc, &event->msg, true);
if (ret)
- dev_err(light_event->dev, "failed to set aon reservemem\n");
+ dev_err(event->dev, "failed to set aon reservemem\n");
return ret;
}
{
int ret;
- if (!light_event->init)
+ if (!light_event || !light_event->init)
return -EINVAL;
ret = regmap_write(light_event->aon_iram, LIGHT_EVENT_OFFSET, mode);
{
int ret;
- if (!light_event->init)
+ if (!light_event || !light_event->init)
return -EINVAL;
ret = regmap_read(light_event->aon_iram, LIGHT_EVENT_OFFSET, mode);
thead = devm_kzalloc(&pdev->dev, sizeof(*thead), GFP_KERNEL);
if (!thead)
return -ENOMEM;
- light_event = thead;
ret = light_aon_get_handle(&(thead->ipc_handle));
if (ret == -EPROBE_DEFER)
return -EPERM;
}
thead->init = true;
+ light_event = thead;
ret = light_event_check_powerup();
if (ret) {
dev_err(dev, "check powerup failed!\n");
+ light_event = NULL;
return -EPERM;
}
dev_info(dev, "light-event driver init successfully\n");
-/* SPDX-License-Identifier: GPL-2.0 */
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Alibaba Group Holding Limited.
*/
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/types.h>
+#include <asm/sbi.h>
+#include <asm/suspend.h>
+#include <linux/firmware/thead/ipc.h>
+#include <linux/platform_device.h>
#undef pr_fmt
#define pr_fmt(fmt) "light-system-suspend" ": " fmt
-#ifdef CONFIG_PLIC_INT_CLEAR
-void __iomem *hart0_sbase;
-/*
- * Each hart context has a set of control registers associated with it. Right
- * now there's only two: a source priority threshold over which the hart will
- * take an interrupt, and a register to claim interrupts.
- */
-#define CONTEXT_BASE 0x200000
-#define CONTEXT_PER_HART 0x1000
-#define CONTEXT_THRESHOLD 0x00
-#define CONTEXT_CLAIM 0x04
-#endif
+struct rpc_msg_cpu_info{
+ u16 cpu_id;
+ u16 status;
+ u32 reserved[5];
+} __packed __aligned(4);
+
+struct light_aon_msg_pm_ctrl {
+ struct light_aon_rpc_msg_hdr hdr;
+ union rpc_func_t {
+ struct rpc_msg_cpu_info cpu_info;
+ } __packed __aligned(4) rpc;
+} __packed __aligned(4);
+
+struct light_aon_pm_ctrl {
+ struct device *dev;
+ struct light_aon_ipc *ipc_handle;
+ struct light_aon_msg_pm_ctrl msg;
+ bool suspend_flag;
+};
-static int light_suspend_prepare_late(void)
+static struct light_aon_pm_ctrl *aon_pm_ctrl;
+
+static int light_require_state_pm_ctrl(struct light_aon_msg_pm_ctrl *msg, enum light_aon_misc_func func, bool ack)
{
-#ifdef CONFIG_PLIC_INT_CLEAR
- void __iomem *claim = hart0_sbase + CONTEXT_CLAIM;
- irq_hw_number_t hwirq;
+ pr_debug("notify aon subsys...\n");
+ struct light_aon_ipc *ipc = aon_pm_ctrl->ipc_handle;
+ struct light_aon_rpc_msg_hdr *hdr = &msg->hdr;
- pr_debug("clear plic pending interrupt\n");
- pr_debug("claim base = 0x%lx\n", (unsigned long)claim);
+ hdr->ver = LIGHT_AON_RPC_VERSION;
+ hdr->svc = (uint8_t)LIGHT_AON_RPC_SVC_MISC;
+ hdr->func = (uint8_t)func;
+ hdr->size = LIGHT_AON_RPC_MSG_NUM;
- while ((hwirq = readl(claim))) {
- pr_debug("claim hwirq%ld\n", hwirq);
- writel(hwirq, claim);
- }
-#endif
- /*
- * Two-level switch for interrupt control: it needs to disable interrupts in SIE, not just in SSTATUS,
- * otherwise the pending interrupts will wakup the cpu immediately after wfi.
- */
- csr_write(CSR_IE, 0);
+ return light_aon_call_rpc(ipc, msg, ack);
+}
- return 0;
+static int sbi_suspend_finisher(unsigned long suspend_type,
+ unsigned long resume_addr,
+ unsigned long opaque)
+{
+ struct sbiret ret;
+
+ ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_SUSPEND,
+ suspend_type, resume_addr, opaque, 0, 0, 0);
+
+ return (ret.error) ? sbi_err_map_linux_errno(ret.error) : 0;
}
static int light_suspend_enter(suspend_state_t state)
{
+ struct light_aon_msg_pm_ctrl msg = {0};
+ unsigned long suspend_type;
+
if (!IS_ENABLED(CONFIG_PM))
return 0;
+ pr_debug("[%s,%d]enter platform system suspend... state:%d\n", __func__, __LINE__, state);
- switch (state) {
- case PM_SUSPEND_MEM:
- pr_debug("enter platform system suspend...\n");
- cpu_do_idle();
- pr_debug("wakeup from wfi...\n");
- break;
- default:
+ if (state == PM_SUSPEND_MEM)
+ suspend_type = SBI_HSM_SUSP_NON_RET_BIT;
+ else
return -EINVAL;
- }
+ cpu_suspend(suspend_type, sbi_suspend_finisher);
+ pr_debug("[%s,%d]wakeup from system suspend\n",__func__, __LINE__);
return 0;
}
-static const struct platform_suspend_ops light_suspend_ops = {
- .enter = light_suspend_enter,
- .valid = suspend_valid_only_mem,
- .prepare_late = light_suspend_prepare_late,
-};
+static int light_suspend_prepare(void)
+{
+ int ret;
+ aon_pm_ctrl->suspend_flag = true;
+ struct light_aon_msg_pm_ctrl msg = {0};
+ ret = light_require_state_pm_ctrl(&msg, LIGHT_AON_MISC_FUNC_REQUIRE_STR, false);
+ if (ret) {
+ pr_err("[%s,%d]failed to initiate Suspend to Ram process to AON subsystem\n",__func__, __LINE__);
+ return ret;
+ }
+ return 0;
+}
-static int __init pm_light_init(void)
+static void light_resume_wake(void)
{
-#ifdef CONFIG_PLIC_INT_CLEAR
- struct device_node *np;
- void __iomem *regs;
+ aon_pm_ctrl->suspend_flag = false;
+}
- np = of_find_node_by_path("/soc/interrupt-controller@ffd8000000");
- if (!np) {
- pr_err("no plic interrupt controller found\n");
- return -EINVAL;
+static int thead_cpuhp_offline(unsigned int cpu)
+{
+ int ret;
+ if(!aon_pm_ctrl->suspend_flag)
+ {
+ struct light_aon_msg_pm_ctrl msg = {0};
+ msg.rpc.cpu_info.cpu_id = (u16)cpu;
+ msg.rpc.cpu_info.status = 0;
+ ret = light_require_state_pm_ctrl(&msg, LIGHT_AON_MISC_FUNC_CPUHP, false);
+ if (ret) {
+ pr_info("failed to notify aon subsys with cpuhp...%08x\n", ret);
+ return ret;
+ }
}
+ return 0;
+}
- regs = of_iomap(np, 0);
- if (WARN_ON(!regs)) {
- pr_err("failed to ioremap interrupt regs\n");
- return -EIO;
+static int thead_cpuhp_online(unsigned int cpu)
+{
+ int ret;
+ if(!aon_pm_ctrl->suspend_flag)
+ {
+ struct light_aon_msg_pm_ctrl msg = {0};
+ msg.rpc.cpu_info.cpu_id = (u16)cpu;
+ msg.rpc.cpu_info.status = 1;
+ ret = light_require_state_pm_ctrl(&msg, LIGHT_AON_MISC_FUNC_CPUHP, false);
+ if (ret) {
+ pr_info("[%s,%d]failed to bring up aon subsys with cpuhp...%08x\n", __func__, __LINE__, ret);
+ return ret;
+ }
}
+ return 0;
+}
- hart0_sbase = regs + CONTEXT_BASE + 1 * CONTEXT_PER_HART; /* 1 means s mode */
+static const struct of_device_id aon_ctrl_ids[] = {
+ { .compatible = "thead,light-aon-suspend-ctrl" },
+ {}
+};
- pr_debug("hart0_sbase = 0x%lx\n", (unsigned long)hart0_sbase);
-#endif
+static const struct platform_suspend_ops light_suspend_ops = {
+ .enter = light_suspend_enter,
+ .valid = suspend_valid_only_mem,
+ .prepare_late = light_suspend_prepare,
+ .wake = light_resume_wake,
+};
- pr_info("set system suspend platform callbacks\n");
+static int light_pm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct light_aon_pm_ctrl *pm_ctrl;
+
+ pm_ctrl = devm_kzalloc(&pdev->dev, sizeof(*aon_pm_ctrl), GFP_KERNEL);
+ if (!pm_ctrl)
+ return -ENOMEM;
+ aon_pm_ctrl = pm_ctrl;
+
+ ret = light_aon_get_handle(&(aon_pm_ctrl->ipc_handle));
+ if (ret == -EPROBE_DEFER) {
+ pr_err("[%s, %d]failed to register ipc_handler.\n",__func__, __LINE__);
+ return ret;
+ }
suspend_set_ops(&light_suspend_ops);
+ ret = cpuhp_setup_state_nocalls(CPUHP_BP_PREPARE_DYN, "soc/thead:online",
+ thead_cpuhp_online,
+ thead_cpuhp_offline);
+ if(ret < 0) {
+ pr_err("[%s,%d]failed to register hotplug callbacks with err %08x.\n", __func__, __LINE__, ret);
+ return ret;
+ }
+
+ aon_pm_ctrl->dev = &pdev->dev;
+ aon_pm_ctrl->suspend_flag = false;
+ platform_set_drvdata(pdev, aon_pm_ctrl);
+
+ dev_info(&pdev->dev, "Light power management control sys successfully registered\n");
return 0;
}
-late_initcall(pm_light_init);
+static struct platform_driver light_pm_driver = {
+ .driver = {
+ .name = "light-pm",
+ .of_match_table = aon_ctrl_ids,
+ },
+ .probe = light_pm_probe,
+};
+
+module_platform_driver(light_pm_driver);
}
#endif /* CONFIG_DEBUG_FS */
+#ifdef CONFIG_PM_SLEEP
+static void dw_spi_register_suspend(struct dw_spi *dws)
+{
+ struct dw_spi_context *ctx = &dws->ctx;
+
+ ctx->ctrlr0 = dw_readl(dws, DW_SPI_CTRLR0);
+ ctx->ctrlr1 = dw_readl(dws, DW_SPI_CTRLR1);
+ ctx->ssienr = dw_readl(dws, DW_SPI_SSIENR);
+ ctx->mwcr = dw_readl(dws, DW_SPI_MWCR);
+ ctx->ser = dw_readl(dws, DW_SPI_SER);
+ ctx->baudr = dw_readl(dws, DW_SPI_BAUDR);
+ ctx->txftlr = dw_readl(dws, DW_SPI_TXFTLR);
+ ctx->rxftlr = dw_readl(dws, DW_SPI_RXFTLR);
+ ctx->txflr = dw_readl(dws, DW_SPI_TXFLR);
+ ctx->rxflr = dw_readl(dws, DW_SPI_RXFLR);
+ ctx->imr = dw_readl(dws, DW_SPI_IMR);
+ ctx->dmacr = dw_readl(dws, DW_SPI_DMACR);
+ ctx->dmatdlr = dw_readl(dws, DW_SPI_DMATDLR);
+ ctx->dmardlr = dw_readl(dws, DW_SPI_DMARDLR);
+ ctx->rx_sample_dly = dw_readl(dws, DW_SPI_RX_SAMPLE_DLY);
+
+}
+
+static void dw_spi_register_resume(struct dw_spi *dws)
+{
+ struct dw_spi_context *ctx = &dws->ctx;
+
+ dw_writel(dws, DW_SPI_SSIENR, 0);
+ dw_writel(dws, DW_SPI_CTRLR0, ctx->ctrlr0);
+ dw_writel(dws, DW_SPI_CTRLR1, ctx->ctrlr1);
+ dw_writel(dws, DW_SPI_TXFTLR, ctx->txftlr);
+ dw_writel(dws, DW_SPI_RXFTLR, ctx->rxftlr);
+ dw_writel(dws, DW_SPI_TXFLR, ctx->txflr);
+ dw_writel(dws, DW_SPI_RXFLR, ctx->rxflr);
+ dw_writel(dws, DW_SPI_IMR, ctx->imr);
+ dw_writel(dws, DW_SPI_DMATDLR, ctx->dmatdlr);
+ dw_writel(dws, DW_SPI_DMARDLR, ctx->dmardlr);
+ dw_writel(dws, DW_SPI_RX_SAMPLE_DLY, ctx->rx_sample_dly);
+ dw_writel(dws, DW_SPI_SER, ctx->ser);
+ dw_writel(dws, DW_SPI_BAUDR, ctx->baudr);
+ dw_writel(dws, DW_SPI_MWCR, ctx->mwcr);
+ dw_writel(dws, DW_SPI_DMACR, ctx->dmacr);
+ dw_writel(dws, DW_SPI_SSIENR, ctx->ssienr);
+}
+#endif
+
void dw_spi_set_cs(struct spi_device *spi, bool enable)
{
struct dw_spi *dws = spi_controller_get_devdata(spi->controller);
if (ret)
return ret;
+#ifdef CONFIG_PM_SLEEP
+ dw_spi_register_suspend(dws);
+#endif
spi_shutdown_chip(dws);
return 0;
}
int dw_spi_resume_host(struct dw_spi *dws)
{
spi_hw_init(&dws->master->dev, dws);
+#ifdef CONFIG_PM_SLEEP
+ dw_spi_register_resume(dws);
+#endif
return spi_controller_resume(dws->master);
}
EXPORT_SYMBOL_GPL(dw_spi_resume_host);
struct dw_spi dws;
struct clk *clk;
struct clk *pclk;
+ struct clk *sclk;
void *priv;
};
+static int qspi_clk_prepare_enable(struct dw_qspi_mmio *dwsmmio)
+{
+ int ret;
+
+ ret = clk_prepare_enable(dwsmmio->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dwsmmio->sclk);
+ if (ret) {
+ clk_disable_unprepare(dwsmmio->pclk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void qspi_clk_disable_unprepare(struct dw_qspi_mmio *dwsmmio)
+{
+ clk_disable_unprepare(dwsmmio->pclk);
+ clk_disable_unprepare(dwsmmio->sclk);
+}
+
+static int __maybe_unused dw_qspi_mmio_suspend(struct device *dev)
+{
+ int ret;
+ struct dw_qspi_mmio *dwsmmio = dev_get_drvdata(dev);
+
+ ret = dw_qspi_suspend_host(&dwsmmio->dws);
+
+ qspi_clk_disable_unprepare(dwsmmio);
+
+ return ret;
+}
+
+static int __maybe_unused dw_qspi_mmio_resume(struct device *dev)
+{
+ struct dw_qspi_mmio *dwsmmio = dev_get_drvdata(dev);
+ int ret;
+
+ ret = qspi_clk_prepare_enable(dwsmmio);
+ if (ret) {
+ dev_err(dev, "failed to enable spi clock(%d)\n", ret);
+ return ret;
+ }
+
+ return dw_qspi_resume_host(&dwsmmio->dws);
+}
+
static int dw_qspi_mmio_probe(struct platform_device *pdev)
{
int (*init_func)(struct platform_device *pdev,
if (ret)
goto out_clk;
+ dwsmmio->sclk = devm_clk_get_optional(&pdev->dev, "sclk");
+ if (IS_ERR(dwsmmio->sclk)) {
+ ret = PTR_ERR(dwsmmio->sclk);
+ goto out_clk;
+ }
+ ret = clk_prepare_enable(dwsmmio->sclk);
+ if (ret)
+ goto out_clk;
+
/* set bus number */
dws->bus_num = pdev->id;
out:
clk_disable_unprepare(dwsmmio->pclk);
+ clk_disable_unprepare(dwsmmio->sclk);
out_clk:
clk_disable_unprepare(dwsmmio->clk);
return ret;
dw_qspi_remove_host(&dwsmmio->dws);
clk_disable_unprepare(dwsmmio->pclk);
+ clk_disable_unprepare(dwsmmio->sclk);
clk_disable_unprepare(dwsmmio->clk);
return 0;
};
MODULE_DEVICE_TABLE(of, dw_qspi_mmio_of_match);
+static const struct dev_pm_ops qspi_mmio_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dw_qspi_mmio_suspend, dw_qspi_mmio_resume)
+};
+
static struct platform_driver dw_qspi_mmio_driver = {
.probe = dw_qspi_mmio_probe,
.remove = dw_qspi_mmio_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = dw_qspi_mmio_of_match,
+ .pm = &qspi_mmio_pm_ops,
},
};
module_platform_driver(dw_qspi_mmio_driver);
struct dw_spi dws;
struct clk *clk;
struct clk *pclk;
+ struct clk *sclk;
void *priv;
struct reset_control *rstc;
};
return 0;
}
+static int spi_clk_prepare_enable(struct dw_spi_mmio *dwsmmio)
+{
+ int ret;
+
+ ret = clk_prepare_enable(dwsmmio->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dwsmmio->sclk);
+ if (ret) {
+ clk_disable_unprepare(dwsmmio->pclk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void spi_clk_disable_unprepare(struct dw_spi_mmio *dwsmmio)
+{
+ clk_disable_unprepare(dwsmmio->pclk);
+ clk_disable_unprepare(dwsmmio->sclk);
+}
+
+static int __maybe_unused dw_spi_mmio_suspend(struct device *dev)
+{
+ int ret;
+ struct dw_spi_mmio *dwsmmio = dev_get_drvdata(dev);
+
+ ret = dw_spi_suspend_host(&dwsmmio->dws);
+ spi_clk_disable_unprepare(dwsmmio);
+
+ return ret;
+}
+
+static int __maybe_unused dw_spi_mmio_resume(struct device *dev)
+{
+ struct dw_spi_mmio *dwsmmio = dev_get_drvdata(dev);
+ int ret;
+
+ ret = spi_clk_prepare_enable(dwsmmio);
+ if (ret) {
+ dev_err(dev, "failed to enable spi clock(%d)\n", ret);
+ return ret;
+ }
+
+ return dw_spi_resume_host(&dwsmmio->dws);
+}
+
static int dw_spi_mmio_probe(struct platform_device *pdev)
{
int (*init_func)(struct platform_device *pdev,
if (ret)
goto out_clk;
+ dwsmmio->sclk = devm_clk_get_optional(&pdev->dev, "sclk");
+ if (IS_ERR(dwsmmio->sclk)) {
+ ret = PTR_ERR(dwsmmio->sclk);
+ goto out_clk;
+ }
+ ret = clk_prepare_enable(dwsmmio->sclk);
+ if (ret)
+ goto out_clk;
+
/* find an optional reset controller */
dwsmmio->rstc = devm_reset_control_get_optional_exclusive(&pdev->dev, "spi");
if (IS_ERR(dwsmmio->rstc)) {
out:
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(dwsmmio->pclk);
+ clk_disable_unprepare(dwsmmio->sclk);
out_clk:
clk_disable_unprepare(dwsmmio->clk);
reset_control_assert(dwsmmio->rstc);
dw_spi_remove_host(&dwsmmio->dws);
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(dwsmmio->pclk);
+ clk_disable_unprepare(dwsmmio->sclk);
clk_disable_unprepare(dwsmmio->clk);
reset_control_assert(dwsmmio->rstc);
MODULE_DEVICE_TABLE(acpi, dw_spi_mmio_acpi_match);
#endif
+static const struct dev_pm_ops spi_mmio_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dw_spi_mmio_suspend, dw_spi_mmio_resume)
+};
+
static struct platform_driver dw_spi_mmio_driver = {
.probe = dw_spi_mmio_probe,
.remove = dw_spi_mmio_remove,
.name = DRIVER_NAME,
.of_match_table = dw_spi_mmio_of_match,
.acpi_match_table = ACPI_PTR(dw_spi_mmio_acpi_match),
+ .pm = &spi_mmio_pm_ops,
},
};
module_platform_driver(dw_spi_mmio_driver);
}
#endif /* CONFIG_DEBUG_FS */
+#ifdef CONFIG_PM_SLEEP
+static void dw_qspi_register_suspend(struct dw_spi *dws)
+{
+ struct dw_qspi_context *ctx = &dws->ctx;
+
+ ctx->ctrlr0 = dw_readl(dws, DW_SPI_CTRL0);
+ ctx->ctrlr1 = dw_readl(dws, DW_SPI_CTRL1);
+ ctx->ssienr = dw_readl(dws, DW_SPI_SSIENR);
+ ctx->mwcr = dw_readl(dws, DW_SPI_MWCR);
+ ctx->ser = dw_readl(dws, DW_SPI_SER);
+ ctx->baudr = dw_readl(dws, DW_SPI_BAUDR);
+ ctx->txftlr = dw_readl(dws, DW_SPI_TXFLTR);
+ ctx->rxftlr = dw_readl(dws, DW_SPI_RXFLTR);
+ ctx->txflr = dw_readl(dws, DW_SPI_TXFLR);
+ ctx->rxflr = dw_readl(dws, DW_SPI_RXFLR);
+ ctx->imr = dw_readl(dws, DW_SPI_IMR);
+ ctx->dmacr = dw_readl(dws, DW_SPI_DMACR);
+ ctx->dmatdlr = dw_readl(dws, DW_SPI_DMATDLR);
+ ctx->dmardlr = dw_readl(dws, DW_SPI_DMARDLR);
+ ctx->rx_sample_dly = dw_readl(dws, DW_SPI_RX_SMP_DLY);
+ ctx->spi_ctrlr0 = dw_readl(dws, DW_SPI_SPI_CTRLR0);
+ ctx->txd_drv_edge = dw_readl(dws, DW_SPI_TXD_DRV_EDGE);
+
+}
+
+static void dw_qspi_register_resume(struct dw_spi *dws)
+{
+ struct dw_qspi_context *ctx = &dws->ctx;
+
+ dw_writel(dws, DW_SPI_SSIENR, 0);
+ dw_writel(dws, DW_SPI_CTRL0, ctx->ctrlr0);
+ dw_writel(dws, DW_SPI_CTRL1, ctx->ctrlr1);
+ dw_writel(dws, DW_SPI_TXFLTR, ctx->txftlr);
+ dw_writel(dws, DW_SPI_RXFLTR, ctx->rxftlr);
+ dw_writel(dws, DW_SPI_TXFLR, ctx->txflr);
+ dw_writel(dws, DW_SPI_RXFLR, ctx->rxflr);
+ dw_writel(dws, DW_SPI_IMR, ctx->imr);
+ dw_writel(dws,DW_SPI_DMATDLR, ctx->dmatdlr);
+ dw_writel(dws, DW_SPI_DMARDLR, ctx->dmardlr);
+ dw_writel(dws, DW_SPI_RX_SMP_DLY, ctx->rx_sample_dly);
+ dw_writel(dws, DW_SPI_SPI_CTRLR0, ctx->spi_ctrlr0);
+ dw_writel(dws, DW_SPI_TXD_DRV_EDGE, ctx->txd_drv_edge);
+ dw_writel(dws, DW_SPI_SER, ctx->ser);
+ dw_writel(dws, DW_SPI_BAUDR, ctx->baudr);
+ dw_writel(dws, DW_SPI_MWCR, ctx->mwcr);
+ dw_writel(dws, DW_SPI_DMACR, ctx->dmacr);
+ dw_writel(dws, DW_SPI_SSIENR, ctx->ssienr);
+}
+#endif
+
+
void dw_qspi_set_cs(struct spi_device *spi,struct dw_spi *dws, bool enable)
{
bool enable1 = !enable;
if (ret)
return ret;
+#ifdef CONFIG_PM_SLEEP
+ dw_qspi_register_suspend(dws);
+#endif
+
spi_shutdown_chip(dws);
return 0;
}
int dw_qspi_resume_host(struct dw_spi *dws)
{
qspi_hw_init(&dws->master->dev, dws);
+#ifdef CONFIG_PM_SLEEP
+ dw_qspi_register_resume(dws);
+#endif
return spi_controller_resume(dws->master);
}
EXPORT_SYMBOL_GPL(dw_qspi_resume_host);
SSI_NS_MICROWIRE,
};
+#ifdef CONFIG_PM_SLEEP
+struct dw_qspi_context {
+ u32 ctrlr0;
+ u32 ctrlr1;
+ u32 ssienr;
+ u32 mwcr;
+ u32 ser;
+ u32 baudr;
+ u32 txftlr;
+ u32 rxftlr;
+ u32 txflr;
+ u32 rxflr;
+ u32 imr;
+ u32 dmacr;
+ u32 dmatdlr;
+ u32 dmardlr;
+ u32 rx_sample_dly;
+ u32 spi_ctrlr0;
+ u32 txd_drv_edge;
+};
+#endif
+
struct dw_spi;
struct dw_spi_dma_ops {
int (*dma_init)(struct dw_spi *dws);
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs;
#endif
+
+#ifdef CONFIG_PM_SLEEP
+ struct dw_qspi_context ctx;
+#endif
};
static inline u32 dw_readl(struct dw_spi *dws, u32 offset)
u32 freq;
};
+#ifdef CONFIG_PM_SLEEP
+struct dw_spi_context {
+ u32 ctrlr0;
+ u32 ctrlr1;
+ u32 ssienr;
+ u32 mwcr;
+ u32 ser;
+ u32 baudr;
+ u32 txftlr;
+ u32 rxftlr;
+ u32 txflr;
+ u32 rxflr;
+ u32 imr;
+ u32 dmacr;
+ u32 dmatdlr;
+ u32 dmardlr;
+ u32 rx_sample_dly;
+};
+#endif
+
struct dw_spi;
struct dw_spi_dma_ops {
int (*dma_init)(struct device *dev, struct dw_spi *dws);
struct dentry *debugfs;
struct debugfs_regset32 regset;
#endif
+
+#ifdef CONFIG_PM_SLEEP
+ struct dw_spi_context ctx;
+#endif
};
static inline u32 dw_readl(struct dw_spi *dws, u32 offset)
#define USB_CLK_GATE_STS 0x0
#define USB_LOGIC_ANALYZER_TRACE_STS0 0x4
#define USB_LOGIC_ANALYZER_TRACE_STS1 0x8
-#define USB_GPIO 0xc
+#define USB_GPIO 0xc
#define USB_DEBUG_STS0 0x10
#define USB_DEBUG_STS1 0x14
#define USB_DEBUG_STS2 0x18
#define USBPHY_TEST_CTRL1 0x28
#define USBPHY_TEST_CTRL2 0x2c
#define USBPHY_TEST_CTRL3 0x30
-#define USB_SSP_EN 0x34
+#define USB_SSP_EN 0x34
#define USB_HADDR_SEL 0x38
-#define USB_SYS 0x3c
-#define USB_HOST_STATUS 0x40
+#define USB_SYS 0x3c
+#define USB_HOST_STATUS 0x40
#define USB_HOST_CTRL 0x44
#define USBPHY_HOST_CTRL 0x48
#define USBPHY_HOST_STATUS 0x4c
/* USB_SYS */
#define TEST_POWERDOWN_SSP BIT(2)
#define TEST_POWERDOWN_HSP BIT(1)
-#define COMMONONN BIT(0)
+#define COMMONONN BIT(0)
/* USB_SSP_EN */
-#define REF_SSP_EN BIT(0)
+#define REF_SSP_EN BIT(0)
/* USBPHY_HOST_CTRL */
#define HOST_U2_PORT_DISABLE BIT(6)
struct dwc3_thead {
struct device *dev;
- struct platform_device *dwc3;
+ struct clk_bulk_data *clks;
+ int num_clocks;
- struct regmap *usb0_apb;
+ struct regmap *usb3_drd;
struct regmap *misc_sysreg;
- struct extcon_dev *edev;
- struct extcon_dev *host_edev;
- struct notifier_block vbus_nb;
- struct notifier_block host_nb;
-
struct gpio_desc *hubswitch;
struct regulator *hub1v2;
struct regulator *hub5v;
struct regulator *vbus;
-
- enum usb_dr_mode mode;
- bool is_suspended;
- bool pm_suspended;
};
-static int dwc3_thead_vbus_notifier(struct notifier_block *nb,
- unsigned long event, void *ptr)
-{
- struct dwc3_thead *thead = container_of(nb, struct dwc3_thead, vbus_nb);
-
- /* enable vbus override for device mode */
- thead->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST;
-
- return NOTIFY_DONE;
-}
-
-static int dwc3_thead_host_notifier(struct notifier_block *nb,
- unsigned long event, void *ptr)
-{
- struct dwc3_thead *thead = container_of(nb, struct dwc3_thead, host_nb);
-
- /* disable vbus override in host mode */
- thead->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL;
-
- return NOTIFY_DONE;
-}
-
-static int dwc3_thead_register_extcon(struct dwc3_thead *thead)
-{
- struct device *dev = thead->dev;
- struct extcon_dev *host_edev;
- int ret;
-
- if (!of_property_read_bool(dev->of_node, "extcon"))
- return 0;
-
- thead->edev = extcon_get_edev_by_phandle(dev, 0);
- if (IS_ERR(thead->edev))
- return PTR_ERR(thead->edev);
-
- thead->vbus_nb.notifier_call = dwc3_thead_vbus_notifier;
-
- thead->host_edev = extcon_get_edev_by_phandle(dev, 1);
- if (IS_ERR(thead->host_edev))
- thead->host_edev = NULL;
-
- ret = devm_extcon_register_notifier(dev, thead->edev, EXTCON_USB,
- &thead->vbus_nb);
- if (ret < 0) {
- dev_err(dev, "VBUS notifier register failed\n");
- return ret;
- }
-
- if (thead->host_edev)
- host_edev = thead->host_edev;
- else
- host_edev = thead->edev;
-
- thead->host_nb.notifier_call = dwc3_thead_host_notifier;
- ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST,
- &thead->host_nb);
- if (ret < 0) {
- dev_err(dev, "Host notifier register failed\n");
- return ret;
- }
-
- /* Update initial VBUS override based on extcon state */
- if (extcon_get_state(thead->edev, EXTCON_USB) ||
- !extcon_get_state(host_edev, EXTCON_USB_HOST))
- dwc3_thead_vbus_notifier(&thead->vbus_nb, true, thead->edev);
- else
- dwc3_thead_vbus_notifier(&thead->vbus_nb, false, thead->edev);
-
- return 0;
-}
-
-static int dwc3_thead_suspend(struct dwc3_thead *thead)
+static void dwc3_thead_deassert(struct dwc3_thead *thead)
{
- return 0;
-}
-
-static int dwc3_thead_resume(struct dwc3_thead *thead)
-{
- return 0;
-}
-
-static int dwc3_thead_of_register_core(struct platform_device *pdev)
-{
- struct dwc3_thead *thead = platform_get_drvdata(pdev);
- struct device_node *np = pdev->dev.of_node, *dwc3_np;
- struct device *dev = &pdev->dev;
- int ret;
-
- dwc3_np = of_get_child_by_name(np, "dwc3");
- if (!dwc3_np) {
- dev_err(dev, "failed to find dwc3 core child\n");
- return -ENODEV;
- }
-
- ret = of_platform_populate(np, NULL, NULL, dev);
- if (ret) {
- dev_err(dev, "failed to register dwc3 core - %d\n", ret);
- goto node_put;
- }
-
- thead->dwc3 = of_find_device_by_node(dwc3_np);
- if (!thead->dwc3) {
- ret = -ENODEV;
- dev_err(dev, "failed to get dwc3 platform device\n");
- }
-
-node_put:
- of_node_put(dwc3_np);
-
- return ret;
-}
-
-static void dwc3_thead_savepower_u3phy(struct dwc3_thead *thead)
-{
- struct device *dev = thead->dev;
- int reg;
-
- /* config usb top within USB ctrl & PHY reset */
+ /* 1. reset assert */
regmap_update_bits(thead->misc_sysreg, USB3_DRD_SWRST,
USB3_DRD_MASK, USB3_DRD_PRST);
- /*
- * dwc reg also need to be configed to save power
- * 1 set USB_SYS[COMMONONN] while GCTL[SOFITPSYNC]=1
- * notice GCTL[SOFITPSYNC] should be mutex with GFLADJ[LPM_SEL].
- * 2 enable GUSB3PIPECLT[SUSPENDEN]
+ /*
+ * 2. Common Block Power-Down Control.
+ * Controls the power-down signals in the PLL block
+ * when the USB 3.0 femtoPHY is in Suspend or Sleep mode.
*/
- regmap_update_bits(thead->usb0_apb, USB_SYS,
+ regmap_update_bits(thead->usb3_drd, USB_SYS,
COMMONONN, COMMONONN);
- regmap_update_bits(thead->usb0_apb, USB_SSP_EN,
+
+ /*
+ * 3. Reference Clock Enable for SS function.
+ * Enables the reference clock to the prescaler.
+ * The ref_ssp_en signal must remain de-asserted until
+ * the reference clock is running at the appropriate frequency,
+ * at which point ref_ssp_en can be asserted.
+ * For lower power states, ref_ssp_en can also be de-asserted.
+ */
+ regmap_update_bits(thead->usb3_drd, USB_SSP_EN,
REF_SSP_EN, REF_SSP_EN);
- regmap_write(thead->usb0_apb, USB_HOST_CTRL, 0x1101);
+ /* 4. set host ctrl */
+ regmap_write(thead->usb3_drd, USB_HOST_CTRL, 0x1101);
+
+ /* 5. reset deassert */
regmap_update_bits(thead->misc_sysreg, USB3_DRD_SWRST,
USB3_DRD_MASK, USB3_DRD_MASK);
- regmap_read(thead->usb0_apb, USB_SYS, ®);
- dev_dbg(dev, "usb_sys:0x%x\n", reg);
+ /* 6. wait deassert complete */
+ udelay(10);
+}
+
+static void dwc3_thead_assert(struct dwc3_thead *thead)
+{
+ /* close ssp */
+ regmap_update_bits(thead->usb3_drd, USB_SSP_EN,
+ REF_SSP_EN, 0);
- regmap_read(thead->usb0_apb, USB_HOST_CTRL, ®);
- dev_dbg(dev, "host_ctrl:0x%x\n", reg);
+ /* reset assert usb */
+ regmap_update_bits(thead->misc_sysreg, USB3_DRD_SWRST,
+ USB3_DRD_MASK, 0);
- regmap_read(thead->misc_sysreg, USB3_DRD_SWRST, ®);
- dev_dbg(dev, "drd_swrst:0x%x\n", reg);
}
static int dwc3_thead_probe(struct platform_device *pdev)
thead->vbus = devm_regulator_get(dev, "vbus");
if (IS_ERR(thead->vbus))
dev_dbg(dev, "no need to get vbus\n");
- else
- regulator_enable(thead->vbus);
+ else {
+ ret = regulator_enable(thead->vbus);
+
+ if (ret) {
+ dev_err(dev, "failed to enable regulator vbus %d\n", ret);
+ }
+ }
thead->hub1v2 = devm_regulator_get(dev, "hub1v2");
if (IS_ERR(thead->hub1v2))
dev_dbg(dev, "no need to set hub1v2\n");
- else
- regulator_enable(thead->hub1v2);
+ else {
+ ret = regulator_enable(thead->hub1v2);
+
+ if (ret) {
+ dev_err(dev, "failed to enable regulator hub1v2 %d\n", ret);
+ }
+ }
thead->hub5v = devm_regulator_get(dev, "hub5v");
if (IS_ERR(thead->hub5v))
dev_dbg(dev, "no need to set hub5v\n");
- else
- regulator_enable(thead->hub5v);
+ else {
+ ret = regulator_enable(thead->hub5v);
+
+ if (ret) {
+ dev_err(dev, "failed to enable regulator hub1v2 %d\n", ret);
+ }
+ }
thead->misc_sysreg = syscon_regmap_lookup_by_phandle(np, "usb3-misc-regmap");
if (IS_ERR(thead->misc_sysreg))
return PTR_ERR(thead->misc_sysreg);
- thead->usb0_apb = syscon_regmap_lookup_by_phandle(np, "usb3-drd-regmap");
- if (IS_ERR(thead->usb0_apb))
- return PTR_ERR(thead->usb0_apb);
+ thead->usb3_drd = syscon_regmap_lookup_by_phandle(np, "usb3-drd-regmap");
+ if (IS_ERR(thead->usb3_drd))
+ return PTR_ERR(thead->usb3_drd);
- dwc3_thead_savepower_u3phy(thead);
+ ret = clk_bulk_get_all(thead->dev, &thead->clks);
+ if (ret < 0)
+ goto err;
- ret = dwc3_thead_of_register_core(pdev);
- if (ret) {
- dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret);
- goto depopulate;
- }
+ thead->num_clocks = ret;
- /* fixme: need to verify because hw can't support detect event */
- ret = dwc3_thead_register_extcon(thead);
+ ret = clk_bulk_prepare_enable(thead->num_clocks, thead->clks);
if (ret)
goto err;
+ dwc3_thead_deassert(thead);
+
+ ret = of_platform_populate(np, NULL, NULL, dev);
+ if (ret) {
+ dev_err(dev, "failed to register dwc3 core - %d\n", ret);
+ goto err_clk_put;
+ }
- device_init_wakeup(&pdev->dev, 1);
- thead->is_suspended = false;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
- pm_runtime_forbid(dev);
+ pm_runtime_get_sync(dev);
+
dev_info(dev, "light dwc3 probe ok!\n");
return 0;
-depopulate:
- of_platform_depopulate(&pdev->dev);
+err_clk_put:
+ clk_bulk_disable_unprepare(thead->num_clocks, thead->clks);
+ clk_bulk_put_all(thead->num_clocks, thead->clks);
err:
return ret;
}
static int dwc3_thead_remove(struct platform_device *pdev)
{
- struct device *dev = &pdev->dev;
+ struct dwc3_thead *thead = platform_get_drvdata(pdev);
- pm_runtime_allow(dev);
- pm_runtime_disable(dev);
+ dwc3_thead_assert(thead);
- return 0;
-}
+ of_platform_depopulate(thead->dev);
-static int __maybe_unused dwc3_thead_pm_suspend(struct device *dev)
-{
- struct dwc3_thead *thead = dev_get_drvdata(dev);
- int ret = 0;
+ clk_bulk_disable_unprepare(thead->num_clocks, thead->clks);
+ clk_bulk_put_all(thead->num_clocks, thead->clks);
- ret = dwc3_thead_suspend(thead);
- if (!ret)
- thead->pm_suspended = true;
+ pm_runtime_disable(thead->dev);
+ pm_runtime_set_suspended(thead->dev);
- return ret;
+ return 0;
}
-static int __maybe_unused dwc3_thead_pm_resume(struct device *dev)
+#ifdef CONFIG_PM_SLEEP
+static int dwc3_thead_pm_suspend(struct device *dev)
{
struct dwc3_thead *thead = dev_get_drvdata(dev);
- int ret;
- ret = dwc3_thead_resume(thead);
- if (!ret)
- thead->pm_suspended = false;
+ dwc3_thead_assert(thead);
- return ret;
+ clk_bulk_disable(thead->num_clocks, thead->clks);
+
+ return 0;
}
-static int __maybe_unused dwc3_thead_runtime_suspend(struct device *dev)
+
+static int dwc3_thead_pm_resume(struct device *dev)
{
struct dwc3_thead *thead = dev_get_drvdata(dev);
+ int ret;
- return dwc3_thead_suspend(thead);
-}
+ ret = clk_bulk_prepare_enable(thead->num_clocks, thead->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clk ret=%d\n", ret);
+ return ret;
+ }
-static int __maybe_unused dwc3_thead_runtime_resume(struct device *dev)
-{
- struct dwc3_thead *thead = dev_get_drvdata(dev);
+ dwc3_thead_deassert(thead);
- return dwc3_thead_resume(thead);
+ return ret;
}
static const struct dev_pm_ops dwc3_thead_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_thead_pm_suspend, dwc3_thead_pm_resume)
- SET_RUNTIME_PM_OPS(dwc3_thead_runtime_suspend, dwc3_thead_runtime_resume,
- NULL)
};
+#define DEV_PM_OPS (&dwc3_thead_dev_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
static const struct of_device_id dwc3_thead_of_match[] = {
{ .compatible = "thead,dwc3" },
.remove = dwc3_thead_remove,
.driver = {
.name = "dwc3-thead",
- /*
- * fixme: need to verify because hw can't support plug event
- */
-// .pm = &dwc3_thead_dev_pm_ops,
+ .pm = DEV_PM_OPS,
.of_match_table = dwc3_thead_of_match,
},
};
node = of_find_node_by_name(NULL, "vdmabuf_reserved_memory");
if (!node) {
ret = -EINVAL;
- dev_err(drv_info->dev,
- "failed to find vdmabuf_reserved_memory node\n");
+ dev_info(drv_info->dev, "no vdmabuf_reserved_memory node\n");
}
for (i = 0; i <= VIRTIO_VDMABUF_CARVEOUTS_NUM; i++) {
ret = carveout_buf_setup();
if (ret < 0)
- dev_warn(drv_info->dev,
- "vhost-vdmabuf: carveout bufs setup failed %d\n",
- ret);
+ dev_info(drv_info->dev, "vhost-vdmabuf: carveout buf not setup %d\n", ret);
mutex_init(&drv_info->g_mutex);
node = of_find_node_by_name(NULL, "vdmabuf_reserved_memory");
if (!node) {
ret = -EINVAL;
- dev_err(drv_info->dev,
- "failed to find vdmabuf_reserved_memory node\n");
+ pr_info("no vdmabuf_reserved_memory node\n");
}
for (i = 0; i <= VIRTIO_VDMABUF_CARVEOUTS_NUM; i++) {
struct dma_buf *dmabuf;
unsigned long irqflags;
struct page *page = NULL;
+ unsigned int gfp = GFP_KERNEL;
int ret, i = 0, npages, bp_num;
/* For carveout, buf size is fixed, user don't need specify it */
exp_info.flags = O_RDWR;
exp_info.priv = exp_buf;
- unsigned int gfp = GFP_KERNEL;
if(attr->flags & VIRTIO_VDAMBUF_DMA32)
{
gfp |= __GFP_DMA32;
ret = carveout_buf_setup();
if (ret < 0)
- pr_warn("virtio-vdmabuf: carveout bufs setup failed %d\n",
- ret);
+ pr_info("virtio-vdmabuf: carveout buf not setup %d\n", ret);
mutex_init(&drv_info->g_mutex);
#ifndef _LIGHT_DSPSYS_H
#define _LIGHT_DSPSYS_H
-
+// gate
#define CLKGEN_DSP0_PCLK 0
#define CLKGEN_DSP0_CCLK 1
#define CLKGEN_DSP1_PCLK 2
#define CLKGEN_AXI4_DSPSYS_PCLK 11
#define CLKGEN_X2X_DSP0_ACLK_S 12
#define CLKGEN_X2X_DSP2_ACLK_S 13
-#define LIGHT_CLKGEN_DSPSYS_CLK_END 14
+// MUX
+#define DSPSYS_DSP0_CLK_SWITCH 14
+#define DSPSYS_DSP1_CLK_SWITCH 15
+// DIV
+#define DSPSYS_DSP_CLK 16
+#define DSPSYS_DSP0_CLK_CDE 17
+#define DSPSYS_DSP1_CLK_CDE 18
+
+#define LIGHT_CLKGEN_DSPSYS_CLK_END 19
#endif
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Alibaba Group Holding Limited.
+ */
+#ifndef _LIGHT_MISCSYS_H
+#define _LIGHT_MISCSYS_H
+#define CLKGEN_MISCSYS_MISCSYS_ACLK 0
+#define CLKGEN_MISCSYS_USB3_DRD_CLK 1
+#define CLKGEN_MISCSYS_USB3_DRD_CTRL_REF_CLK 2
+#define CLKGEN_MISCSYS_USB3_DRD_PHY_REF_CLK 3
+#define CLKGEN_MISCSYS_USB3_DRD_SUSPEND_CLK 4
+#define CLKGEN_MISCSYS_EMMC_CLK 5
+#define CLKGEN_MISCSYS_SDIO0_CLK 6
+#define CLKGEN_MISCSYS_SDIO1_CLK 7
+#define CLKGEN_MISCSYS_AHB2_TEESYS_HCLK 8
+#define CLKGEN_MISCSYS_APB3_TEESYS_HCLK 9
+#define CLKGEN_MISCSYS_AXI4_TEESYS_ACLK 10
+#define CLKGEN_MISCSYS_EIP120SI_CLK 11
+#define CLKGEN_MISCSYS_EIP120SII_CLK 12
+#define CLKGEN_MISCSYS_EIP120SIII_CLK 13
+#define CLKGEN_MISCSYS_TEEDMAC_CLK 14
+#define CLKGEN_MISCSYS_EIP150B_HCLK 15
+#define CLKGEN_MISCSYS_OCRAM_HCLK 16
+#define CLKGEN_MISCSYS_EFUSE_PCLK 17
+#define CLKGEN_MISCSYS_TEE_SYSREG_PCLK 18
+#define CLKGEN_MISCSYS_CLK_END 19
+#endif
+
#define LIGHT_VPSYS_VENC_CCLK 9
#define LIGHT_VPSYS_VENC_PCLK 10
#define LIGHT_VPSYS_VENC_ACLK 11
-#define LIGHT_VPSYS_CLK_END 12
+#define LIGHT_VPSYS_G2D_CCLK_DIV 12
+#define LIGHT_VPSYS_CLK_END 13
#endif
#define IOPMP_TEE_DMAC 25
#define IOPMP_DSP0 26
#define IOPMP_DSP1 27
+#define IOPMP_AUDIO0 28
+#define IOPMP_AUDIO1 29
#endif /* __DT_THEAD_LIGHT_IOPMP_H__ */
LIGHT_AON_MISC_FUNC_AON_WDT_ON = 10,
LIGHT_AON_MISC_FUNC_AON_WDT_OFF = 11,
LIGHT_AON_MISC_FUNC_AON_RESERVE_MEM = 12,
+ LIGHT_AON_MISC_FUNC_REQUIRE_STR = 13,
+ LIGHT_AON_MISC_FUNC_REQUIRE_STD = 14,
+ LIGHT_AON_MISC_FUNC_CPUHP = 15,
};
enum light_aon_pm_func {
#define ___GFP_ACCOUNT 0x400000u
#define ___GFP_ZEROTAGS 0x800000u
#define ___GFP_SKIP_KASAN_POISON 0x1000000u
-#ifdef CONFIG_CMA
-#define ___GFP_CMA 0x2000000u
-#else
-#define ___GFP_CMA 0
-#endif
#ifdef CONFIG_LOCKDEP
-#ifdef CONFIG_CMA
-#define ___GFP_NOLOCKDEP 0x4000000u
-#else
#define ___GFP_NOLOCKDEP 0x2000000u
-#endif
#else
#define ___GFP_NOLOCKDEP 0
#endif
-
/* If the above are modified, __GFP_BITS_SHIFT may need updating */
/*
#define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) /* ZONE_MOVABLE allowed */
-#define __GFP_CMA ((__force gfp_t)___GFP_CMA)
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
/**
#define __GFP_NOLOCKDEP ((__force gfp_t)___GFP_NOLOCKDEP)
/* Room for N __GFP_FOO bits */
-#ifdef CONFIG_CMA
-#define __GFP_BITS_SHIFT (26 + IS_ENABLED(CONFIG_LOCKDEP))
-#else
#define __GFP_BITS_SHIFT (25 + IS_ENABLED(CONFIG_LOCKDEP))
-#endif
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
/**
alloc_zeroed_user_highpage_movable(struct vm_area_struct *vma,
unsigned long vaddr)
{
- struct page *page = alloc_page_vma(GFP_HIGHUSER_MOVABLE | __GFP_CMA, vma, vaddr);
+ struct page *page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vaddr);
if (page)
clear_user_highpage(page, vaddr);
u32 out_idx;
u32 core_id;
spinlock_t mu_lock;
+#ifdef CONFIG_PM_SLEEP
+ struct semaphore pm_sem;
+ int sleep_flag;
+#endif
};
struct light_rpmsg_head {
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
MIGRATE_RECLAIMABLE,
+ MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
+ MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
/*
* MIGRATE_CMA migration type is designed to mimic the way
*/
MIGRATE_CMA,
#endif
- MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
- MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* can't allocate from here */
#endif
#ifdef CONFIG_CMA
# define is_migrate_cma(migratetype) unlikely((migratetype) == MIGRATE_CMA)
# define is_migrate_cma_page(_page) (get_pageblock_migratetype(_page) == MIGRATE_CMA)
-# define get_cma_migrate_type() MIGRATE_CMA
#else
# define is_migrate_cma(migratetype) false
# define is_migrate_cma_page(_page) false
-# define get_cma_migrate_type() MIGRATE_MOVABLE
#endif
static inline bool is_migrate_movable(int mt)
struct cma cma_areas[MAX_CMA_AREAS];
unsigned cma_area_count;
+static DEFINE_MUTEX(cma_mutex);
phys_addr_t cma_get_base(const struct cma *cma)
{
mutex_unlock(&cma->lock);
pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
+ mutex_lock(&cma_mutex);
ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA, gfp_mask, &info);
+ mutex_unlock(&cma_mutex);
cma_info.nr_migrated += info.nr_migrated;
cma_info.nr_reclaimed += info.nr_reclaimed;
cma_info.nr_mapped += info.nr_mapped;
"Unmovable",
"Movable",
"Reclaimable",
+ "HighAtomic",
#ifdef CONFIG_CMA
"CMA",
#endif
- "HighAtomic",
#ifdef CONFIG_MEMORY_ISOLATION
"Isolate",
#endif
#endif
};
-#ifdef CONFIG_CMA
-static __always_inline struct page *__rmqueue_cma_fallback(struct zone *zone,
- unsigned int order)
-{
- return __rmqueue_smallest(zone, order, MIGRATE_CMA);
-}
-#else
-static inline struct page *__rmqueue_cma_fallback(struct zone *zone,
- unsigned int order) { return NULL; }
-#endif
-
/*
* Move the free pages in a range to the freelist tail of the requested type.
* Note that start_page and end_pages are not aligned on a pageblock
retry:
page = __rmqueue_smallest(zone, order, migratetype);
-
- if (unlikely(!page) && __rmqueue_fallback(zone, order, migratetype,
- alloc_flags))
- goto retry;
+ if (unlikely(!page)) {
+ if (!page && __rmqueue_fallback(zone, order, migratetype,
+ alloc_flags))
+ goto retry;
+ }
trace_mm_page_alloc_zone_locked(page, order, migratetype);
return page;
}
-#ifdef CONFIG_CMA
-static struct page *__rmqueue_cma(struct zone *zone, unsigned int order,
- int migratetype,
- unsigned int alloc_flags)
-{
- struct page *page = __rmqueue_cma_fallback(zone, order);
- trace_mm_page_alloc_zone_locked(page, order, MIGRATE_CMA);
- return page;
-}
-#else
-static inline struct page *__rmqueue_cma(struct zone *zone, unsigned int order,
- int migratetype,
- unsigned int alloc_flags)
-{
- return NULL;
-}
-#endif
-
/*
* Obtain a specified number of elements from the buddy allocator, all under
* a single hold of the lock, for efficiency. Add them to the supplied list.
spin_lock(&zone->lock);
for (i = 0; i < count; ++i) {
- struct page *page;
-
- if (is_migrate_cma(migratetype))
- page = __rmqueue_cma(zone, order, migratetype,
- alloc_flags);
- else
- page = __rmqueue(zone, order, migratetype, alloc_flags);
-
+ struct page *page = __rmqueue(zone, order, migratetype,
+ alloc_flags);
if (unlikely(page == NULL))
break;
return alloced;
}
-/*
- * Return the pcp list that corresponds to the migrate type if that list isn't
- * empty.
- * If the list is empty return NULL.
- */
-static struct list_head *get_populated_pcp_list(struct zone *zone,
- unsigned int order, struct per_cpu_pages *pcp,
- int migratetype, unsigned int alloc_flags)
-{
- struct list_head *list = &pcp->lists[migratetype];
-
- if (list_empty(list)) {
- pcp->count += rmqueue_bulk(zone, order,
- pcp->batch, list,
- migratetype, alloc_flags);
-
- if (list_empty(list))
- list = NULL;
- }
- return list;
-}
-
#ifdef CONFIG_NUMA
/*
* Called from the vmstat counter updater to drain pagesets of this
trace_android_vh_pcplist_add_cma_pages_bypass(migratetype,
&pcp_skip_cma_pages);
if (unlikely(is_migrate_isolate(migratetype)) ||
+ is_migrate_cma(migratetype) ||
pcp_skip_cma_pages) {
free_one_page(zone, page, pfn, 0, migratetype,
FPI_NONE);
static struct page *__rmqueue_pcplist(struct zone *zone, int migratetype,
unsigned int alloc_flags,
struct per_cpu_pages *pcp,
- gfp_t gfp_flags)
+ struct list_head *list)
{
- struct page *page = NULL;
- struct list_head *list = NULL;
+ struct page *page;
do {
- /* First try to get CMA pages */
- if (migratetype == MIGRATE_MOVABLE &&
- alloc_flags & ALLOC_CMA) {
- list = get_populated_pcp_list(zone, 0, pcp,
- get_cma_migrate_type(), alloc_flags);
- }
-
- if (list == NULL) {
- /*
- * Either CMA is not suitable or there are no
- * free CMA pages.
- */
- list = get_populated_pcp_list(zone, 0, pcp,
+ if (list_empty(list)) {
+ pcp->count += rmqueue_bulk(zone, 0,
+ pcp->batch, list,
migratetype, alloc_flags);
- if (unlikely(list == NULL) ||
- unlikely(list_empty(list)))
+ if (unlikely(list_empty(list)))
return NULL;
}
int migratetype, unsigned int alloc_flags)
{
struct per_cpu_pages *pcp;
+ struct list_head *list;
struct page *page;
unsigned long flags;
local_irq_save(flags);
pcp = &this_cpu_ptr(zone->pageset)->pcp;
- page = __rmqueue_pcplist(zone, migratetype, alloc_flags, pcp,
- gfp_flags);
+ list = &pcp->lists[migratetype];
+ page = __rmqueue_pcplist(zone, migratetype, alloc_flags, pcp, list);
if (page) {
__count_zid_vm_events(PGALLOC, page_zonenum(page), 1);
zone_statistics(preferred_zone, zone);
struct page *page;
if (likely(order == 0)) {
- page = rmqueue_pcplist(preferred_zone, zone, gfp_flags,
- migratetype, alloc_flags);
- goto out;
+ /*
+ * MIGRATE_MOVABLE pcplist could have the pages on CMA area and
+ * we need to skip it when CMA area isn't allowed.
+ */
+ if (!IS_ENABLED(CONFIG_CMA) || alloc_flags & ALLOC_CMA ||
+ migratetype != MIGRATE_MOVABLE) {
+ page = rmqueue_pcplist(preferred_zone, zone, gfp_flags,
+ migratetype, alloc_flags);
+ goto out;
+ }
}
/*
if (page)
trace_mm_page_alloc_zone_locked(page, order, migratetype);
}
- if (!page) {
- if (migratetype == MIGRATE_MOVABLE &&
- alloc_flags & ALLOC_CMA)
- page = __rmqueue_cma(zone, order, migratetype,
- alloc_flags);
- if (!page)
- page = __rmqueue(zone, order, migratetype,
- alloc_flags);
- }
+ if (!page)
+ page = __rmqueue(zone, order, migratetype, alloc_flags);
} while (page && check_new_pages(page, order));
spin_unlock(&zone->lock);
if (!page)
continue;
for (mt = 0; mt < MIGRATE_PCPTYPES; mt++) {
-#ifdef CONFIG_CMA
- /*
- * Note that this check is needed only
- * when MIGRATE_CMA < MIGRATE_PCPTYPES.
- */
- if (mt == MIGRATE_CMA)
- continue;
-#endif
if (!free_area_empty(area, mt))
return true;
}
unsigned int pflags = current->flags;
if (!(pflags & PF_MEMALLOC_NOCMA) &&
- gfp_migratetype(gfp_mask) == MIGRATE_MOVABLE &&
- gfp_mask & __GFP_CMA)
+ gfp_migratetype(gfp_mask) == MIGRATE_MOVABLE)
alloc_flags |= ALLOC_CMA;
#endif
trace_android_vh_cma_drain_all_pages_bypass(migratetype,
&skip_drain_all_pages);
- if (skip_drain_all_pages)
+ if (!skip_drain_all_pages)
drain_all_pages(cc.zone);
/*
static unsigned long cache_alloc_handle(struct zs_pool *pool, gfp_t gfp)
{
return (unsigned long)kmem_cache_alloc(pool->handle_cachep,
- gfp & ~(__GFP_HIGHMEM|__GFP_MOVABLE|__GFP_CMA));
+ gfp & ~(__GFP_HIGHMEM|__GFP_MOVABLE));
}
static void cache_free_handle(struct zs_pool *pool, unsigned long handle)
static struct zspage *cache_alloc_zspage(struct zs_pool *pool, gfp_t flags)
{
return kmem_cache_alloc(pool->zspage_cachep,
- flags & ~(__GFP_HIGHMEM|__GFP_MOVABLE|__GFP_CMA));
+ flags & ~(__GFP_HIGHMEM|__GFP_MOVABLE));
}
static void cache_free_zspage(struct zs_pool *pool, struct zspage *zspage)
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/fs.h>
+#include <linux/errno.h>
#include <dt-bindings/gpio/gpio.h>
#include <uapi/linux/rfkill.h>
#ifdef CONFIG_OF
}
gpio = of_get_named_gpio_flags(node, "BT,power_gpio", 0, &flags);
+ LOG("%s: BT,power_gpio = %d\n", __func__, gpio);
+
+ if (gpio == -EPROBE_DEFER) {
+ return gpio;
+ }
+
if (gpio_is_valid(gpio)){
data->poweron_gpio.io = gpio;
data->poweron_gpio.enable = (flags == GPIO_ACTIVE_HIGH)? 1:0;
data->mregulator.power_ctrl_by_pmu = false;
LOG("%s: wifi power controled by gpio.\n", __func__);
gpio = of_get_named_gpio_flags(node, "WIFI,poweren_gpio", 0, &flags);
+ if (gpio == -EPROBE_DEFER) {
+ return gpio;
+ }
if (gpio_is_valid(gpio)){
data->power_n.io = gpio;
data->power_n.enable = (flags == GPIO_ACTIVE_HIGH)? 1:0;
aw87519_spk_cnt->data[i],
aw87519_spk_cnt->data[i+1]);
}
+ mdelay(40); // aw87519 chip need 40ms setup time.
return 0;
}
usleep_range(2500, 3000);
gpio_set_value_cansleep(aw87519->reset_gpio, 1);
usleep_range(5000, 5500);
- aw87519->hwen_flag = 1;
} else {
- aw87519->hwen_flag = 0;
dev_err(&aw87519->i2c_client->dev, "%s: failed\n", __func__);
}
struct aw87519 *pa = snd_soc_component_get_drvdata(c);
if (SND_SOC_DAPM_EVENT_ON(event)) {
+ aw87519_hw_reset(pa);
/* Before widget power up: turn chip on, sync registers */
aw87519_audio_speaker();
} else {
#define CPR_PERI_DIV_SEL_REG 0x004 /*audio sys i2s clock div Register*/
#define CPR_PERI_CLK_SEL_REG 0x008 /*audio sys i2s clock selection Register*/
#define CPR_PERI_CTRL_REG 0x00C /*Peripheral control signal configuration register*/
-
+#define CPR_IP_RST_REG 0x014
/* AUDIO SYS DIV SEL REG, offset: 0x4 */
#define CPR_AUDIO_DIV0_SEL_POS (4U)
#define CPR_I2S2_SRC_SEL_24M (0x1U << AUDIOSYS_I2S2_SRC_SEL_POS)
#define CPR_I2S2_SRC_SEL_AUDIO_DIVCLK1 (0x2U << AUDIOSYS_I2S2_SRC_SEL_POS)
+#define CPR_TDM_SRC_SEL_POS (16U)
+#define CPR_TDM_SRC_SEL_MSK (0x3U << CPR_TDM_SRC_SEL_POS)
+#define CPR_TDM_SRC_SEL(X) (X << CPR_TDM_SRC_SEL_POS)
+
/* PERI_CTRL_REG, Offset: 0xC */
#define CPR_VAD_I2SIN_SYNC_POS (12U)
#define CPR_VAD_I2SIN_SYNC_MSK (0x1U << CPR_VAD_I2SIN_SYNC_POS)
#define CPR_SPDIF_SYNC_MSK (0x1U << CPR_SPDIF_SYNC_POS)
#define CPR_SPDIF_SYNC_EN (CPR_SPDIF_SYNC_MSK)
+/* CPR_IP_RST_REG, Offset: 0x014 */
+#define CPR_I2S0_SRST_N_SEL_POS (17U)
+#define CPR_I2S0_SRST_N_SEL_MSK (0x1U << CPR_I2S0_SRST_N_SEL_POS)
+#define CPR_I2S0_SRST_N_SEL(X) (X << CPR_I2S0_SRST_N_SEL_POS)
+#define CPR_I2S1_SRST_N_SEL_POS (18U)
+#define CPR_I2S1_SRST_N_SEL_MSK (0x1U << CPR_I2S1_SRST_N_SEL_POS)
+#define CPR_I2S1_SRST_N_SEL(X) (X << CPR_I2S1_SRST_N_SEL_POS)
+#define CPR_I2S2_SRST_N_SEL_POS (19U)
+#define CPR_I2S2_SRST_N_SEL_MSK (0x1U << CPR_I2S2_SRST_N_SEL_POS)
+#define CPR_I2S2_SRST_N_SEL(X) (X << CPR_I2S2_SRST_N_SEL_POS)
+#define CPR_I2S8CH_SRST_N_SEL_POS (20U)
+#define CPR_I2S8CH_SRST_N_SEL_MSK (0x1U << CPR_I2S8CH_SRST_N_SEL_POS)
+#define CPR_I2S8CH_SRST_N_SEL(X) (X << CPR_I2S8CH_SRST_N_SEL_POS)
+#define CPR_TDM_SRST_N_SEL_POS (21U)
+#define CPR_TDM_SRST_N_SEL_MSK (0x1U << CPR_TDM_SRST_N_SEL_POS)
+#define CPR_TDM_SRST_N_SEL(X) (X << CPR_TDM_SRST_N_SEL_POS)
#endif /* _LIGHT_AUDIO_CPR_H */
#define LIGHT_AUDIO_PAD_CONFIG(idx) (priv->cfg_off + ((idx-25) >> 1) * 4)
static int i2s_8ch_probe_flag = 0;
-static u32 light_special_sample_rates[] = { 11025, 22050, 44100, 88200 };
+//static u32 light_special_sample_rates[] = { 11025, 22050, 44100, 88200 };
static int light_audio_cpr_set(struct light_i2s_priv *chip, unsigned int cpr_off,
unsigned int mask, unsigned int val)
struct light_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ if(!priv->regmap) {
+ return 0;
+ }
+
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (tx)
+ if (tx) {
light_snd_txctrl(priv, 1);
- else
+ priv->state |= I2S_STATE_TX_RUNNING;
+ }
+ else {
light_snd_rxctrl(priv, 1);
+ priv->state |= I2S_STATE_RX_RUNNING;
+ }
break;
case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (tx) {
dmaengine_terminate_async(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
light_snd_txctrl(priv, 0);
+ priv->state &= ~I2S_STATE_TX_RUNNING;
+ } else {
+ light_snd_rxctrl(priv, 0);
+ priv->state &= ~I2S_STATE_RX_RUNNING;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ regmap_read(priv->regmap, I2S_FUNCMODE, &priv->suspend_funcmode);
+ if (tx) {
+ dmaengine_pause(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
+ light_snd_txctrl(priv, 0);
+ } else {
+ light_snd_rxctrl(priv, 0);
}
break;
default:
return 0;
}
+ pm_runtime_resume_and_get(priv->dev);
+
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
cnfout |= IISCNFOUT_TSAFS_I2S;
CNFIN_I2S_RXMODE_Msk,
cnfin);
+ pm_runtime_put_sync(priv->dev);
+
return 0;
}
iiscnf_in &= ~CNFIN_RX_CH_SEL_LEFT;
iiscnf_in &= ~CNFIN_RVOICEEN_MONO;
}
+ iiscnf_in |= CNFIN_I2S_RXMODE_MASTER_MODE;
if (tx)
writel(iiscnf_out, priv->regs + I2S_IISCNF_OUT);
static bool light_i2s_8ch_wr_reg(struct device *dev, unsigned int reg)
{
- switch (reg) {
- case I2S_IISEN:
- case I2S_FUNCMODE:
- case I2S_IISCNF_IN:
- case I2S_FSSTA:
- case I2S_IISCNF_OUT:
- case I2S_DMACR:
- return true;
- default:
- return false;
- }
+ return true;
}
static bool light_i2s_8ch_rd_reg(struct device *dev, unsigned int reg)
{
- switch (reg) {
- case I2S_IISEN:
- case I2S_FUNCMODE:
- case I2S_IISCNF_IN:
- case I2S_FSSTA:
- case I2S_IISCNF_OUT:
- case I2S_DMACR:
- return true;
- default:
- return false;
- }
+ return true;
}
static const struct regmap_config light_i2s_8ch_regmap_config = {
.max_register = I2S_DR4,
.writeable_reg = light_i2s_8ch_wr_reg,
.readable_reg = light_i2s_8ch_rd_reg,
- .cache_type = REGCACHE_FLAT,
+ .cache_type = REGCACHE_NONE,
};
+#if 0
static int light_audio_pinconf_set(struct device *dev, unsigned int pin_id, unsigned int val)
{
struct light_i2s_priv *priv = dev_get_drvdata(dev);
{
return 0;
}
+#endif
static int light_i2s_8ch_runtime_suspend(struct device *dev)
{
return ret;
}
+#ifdef CONFIG_PM_SLEEP
+static int light_i2s_8ch_suspend(struct device *dev)
+{
+ struct light_i2s_priv *priv = dev_get_drvdata(dev);
+
+ if(!priv->regmap || priv->state == I2S_STATE_IDLE) {
+ return 0;
+ }
+
+ regmap_read(priv->regmap, I2S_DIV0_LEVEL, &priv->suspend_div0_level);
+ regmap_read(priv->regmap, I2S_DIV3_LEVEL, &priv->suspend_div3_level);
+ regmap_read(priv->regmap, I2S_IISCNF_IN, &priv->suspend_iiscnf_in);
+ regmap_read(priv->regmap, I2S_FSSTA, &priv->suspend_fssta);
+ regmap_read(priv->regmap, I2S_IISCNF_OUT, &priv->suspend_ii2cnf_out);
+ regmap_read(priv->regmap, I2S_FADTLR, &priv->suspend_fadtlr);
+ regmap_read(priv->regmap, I2S_SCCR, &priv->suspend_sccr);
+ regmap_read(priv->regmap, I2S_TXFTLR, &priv->suspend_txftlr);
+ regmap_read(priv->regmap, I2S_RXFTLR, &priv->suspend_rxftlr);
+ regmap_read(priv->regmap, I2S_IMR, &priv->suspend_imr);
+ regmap_read(priv->regmap, I2S_DMATDLR, &priv->suspend_dmatdlr);
+ regmap_read(priv->regmap, I2S_DMARDLR, &priv->suspend_dmardlr);
+
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, &priv->cpr_peri_div_sel);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CTRL_REG, &priv->cpr_peri_ctrl);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, &priv->cpr_peri_clk_sel);
+
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_I2S8CH_SRST_N_SEL_MSK, CPR_I2S8CH_SRST_N_SEL(0));
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int light_i2s_8ch_resume(struct device *dev)
+{
+ struct light_i2s_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if(!priv->regmap || priv->state == I2S_STATE_IDLE) {
+ return 0;
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_I2S8CH_SRST_N_SEL_MSK, CPR_I2S8CH_SRST_N_SEL(1));
+
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CTRL_REG, priv->cpr_peri_ctrl);
+
+
+ regmap_write(priv->regmap, I2S_IISEN, 0);
+ regmap_write(priv->regmap, I2S_FSSTA, priv->suspend_fssta);
+ regmap_write(priv->regmap, I2S_FUNCMODE, priv->suspend_funcmode | FUNCMODE_TMODE_WEN | FUNCMODE_RMODE_WEN);
+ regmap_write(priv->regmap, I2S_IISCNF_IN, priv->suspend_iiscnf_in);
+ regmap_write(priv->regmap, I2S_IISCNF_OUT, priv->suspend_ii2cnf_out);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, priv->cpr_peri_clk_sel);
+ regmap_write(priv->regmap, I2S_DIV0_LEVEL, priv->suspend_div0_level);
+ regmap_write(priv->regmap, I2S_DIV3_LEVEL, priv->suspend_div3_level);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, priv->cpr_peri_div_sel);
+
+ return ret;
+}
+#endif
+
static const struct of_device_id light_i2s_8ch_of_match[] = {
{ .compatible = "light,light-i2s-8ch"},
{},
return PTR_ERR(priv->clk);
pm_runtime_enable(&pdev->dev);
- if (!pm_runtime_enabled(&pdev->dev)) {
- ret = light_i2s_8ch_runtime_resume(&pdev->dev);
- if (ret)
- goto err_pm_disable;
- }
+ pm_runtime_resume_and_get(&pdev->dev); // clk gate is enabled by hardware as default register value
+ pm_runtime_put_sync(&pdev->dev);
irq = platform_get_irq(pdev, 0);
static const struct dev_pm_ops light_i2s_8ch_pm_ops = {
SET_RUNTIME_PM_OPS(light_i2s_8ch_runtime_suspend, light_i2s_8ch_runtime_resume,
NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(light_i2s_8ch_suspend,
+ light_i2s_8ch_resume)
};
static struct platform_driver light_i2s_8ch_driver = {
{
u32 dma_en = 0;
u32 i2s_en = 0;
- u32 i2s_status = 0;
u32 i2s_imr = 0;
+ /* get current dma status, save rx configuration */
+ dma_en = readl(chip->regs + I2S_DMACR);
if (on) {
dma_en |= DMACR_TDMAE_EN;
i2s_en |= IISEN_I2SEN;
writel(i2s_en, chip->regs + I2S_IISEN);
} else {
dma_en &= ~DMACR_TDMAE_EN;
- i2s_en &= ~IISEN_I2SEN;
i2s_imr = readl(chip->regs + I2S_IMR);
i2s_imr &= ~(IMR_TXUIRM_INTR_MSK);
i2s_imr &= ~(IMR_TXEIM_INTR_MSK);
writel(i2s_imr, chip->regs + I2S_IMR);
writel(dma_en, chip->regs + I2S_DMACR);
- writel(i2s_en, chip->regs + I2S_IISEN);
+
+ if (!chip->mclk_keepon) {
+ /*
+ * The enablement of I2S can onlybe truned off when
+ * the DMA configuration for RX and TX is completely disabled.
+ */
+ if ( ((DMACR_TDMAE_MSK | DMACR_RDMAE_MSK) & dma_en) == 0) {
+ i2s_en &= ~IISEN_I2SEN;
+ writel(i2s_en, chip->regs + I2S_IISEN);
+ }
+ }
}
}
static inline void light_snd_rxctrl(struct light_i2s_priv *chip, bool on)
{
- u32 dma_en;
+ u32 dma_en;
u32 i2s_en;
+ /* get current dma status, save tx configuration */
+ dma_en = readl(chip->regs + I2S_DMACR);
if (on) {
dma_en |= DMACR_RDMAE_EN;
i2s_en |= IISEN_I2SEN;
+ writel(dma_en, chip->regs + I2S_DMACR);
+ writel(i2s_en, chip->regs + I2S_IISEN);
} else {
dma_en &= ~DMACR_RDMAE_EN;
- i2s_en &= ~IISEN_I2SEN;
- }
-
writel(dma_en, chip->regs + I2S_DMACR);
- writel(i2s_en, chip->regs + I2S_IISEN);
+ if (!chip->mclk_keepon) {
+ /*
+ * The enablement of I2S can onlybe truned off when
+ * the DMA confgiguratio for RX and TX is completely disabled.
+ */
+ if ( ((DMACR_TDMAE_MSK | DMACR_RDMAE_MSK) & dma_en) == 0) {
+ i2s_en &= ~IISEN_I2SEN;
+ writel(i2s_en, chip->regs + I2S_IISEN);
+ }
+ }
+ }
}
static int light_i2s_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
+ //printk("%s: %s %s\n", __func__, substream->pcm->name, dai->name);
return 0;
}
{
struct light_i2s_priv *i2s_private = snd_soc_dai_get_drvdata(dai);
+ //printk("%s: %s %s\n", __func__, substream->pcm->name, dai->name);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
light_snd_rxctrl(i2s_private, 0);
-
- clk_disable_unprepare(i2s_private->clk);
}
/**
{
int ret = 0;
- struct light_i2s_priv *i2s_private = snd_soc_dai_get_drvdata(dai);
+ struct light_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ //printk("%s: %s %s cmd=%d tx=%d\n", __func__, substream->pcm->name, dai->name, cmd, tx);
+
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (tx)
- light_snd_txctrl(i2s_private, 1);
- else
- light_snd_rxctrl(i2s_private, 1);
+ if (tx) {
+ light_snd_txctrl(priv, 1);
+ priv->state |= I2S_STATE_TX_RUNNING;
+ }
+ else {
+ light_snd_rxctrl(priv, 1);
+ priv->state |= I2S_STATE_RX_RUNNING;
+ }
break;
case SNDRV_PCM_TRIGGER_STOP:
+ if (tx) {
+ dmaengine_terminate_async(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
+ light_snd_txctrl(priv, 0);
+ priv->state &= ~I2S_STATE_TX_RUNNING;
+ } else {
+ light_snd_rxctrl(priv, 0);
+ priv->state &= ~I2S_STATE_RX_RUNNING;
+ }
+ break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ regmap_read(priv->regmap, I2S_FUNCMODE, &priv->suspend_funcmode);
if (tx) {
- dmaengine_terminate_async(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
- light_snd_txctrl(i2s_private, 0);
+ dmaengine_pause(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
+ light_snd_txctrl(priv, 0);
+ } else {
+ light_snd_rxctrl(priv, 0);
}
break;
default:
return -EINVAL;
-
}
return ret;
u32 cnfout = 0;
u32 cnfin = 0;
+ pm_runtime_resume_and_get(i2s_private->dev);
+
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
cnfout |= IISCNFOUT_TSAFS_I2S;
cnfout);
cnfin |= CNFIN_I2S_RXMODE_MASTER_MODE;
+
regmap_update_bits(i2s_private->regmap, I2S_IISCNF_IN,
CNFIN_I2S_RXMODE_Msk,
cnfin);
+ pm_runtime_put_sync(i2s_private->dev);
+
return 0;
}
u32 funcmode;
u32 iiscnf_out;
u32 iiscnf_in;
- u32 i2s_en;
+ u32 i2s_en = 0;
u32 channels = params_channels(params);
return -EINVAL;
}
+ /*
+ * FUNCMODE,I2S_IISCNF_OUT,I2S_IISCNF_IN registers,
+ * it is impossible to write to this register when I2S is enabled
+ */
i2s_en &= ~IISEN_I2SEN;
writel(i2s_en, i2s_private->regs + I2S_IISEN);
funcmode = readl(i2s_private->regs + I2S_FUNCMODE);
if (tx) {
funcmode |= FUNCMODE_TMODE_WEN;
- funcmode &= ~FUNCMODE_CH1_ENABLE;
- funcmode |= FUNCMODE_RMODE_WEN;
- funcmode &= ~FUNCMODE_RMODE;
funcmode &= ~FUNCMODE_TMODE;
funcmode |= FUNCMODE_TMODE;
} else {
funcmode |= FUNCMODE_RMODE_WEN;
- funcmode |= FUNCMODE_CH0_ENABLE;
- funcmode |= FUNCMODE_CH1_ENABLE;
- funcmode |= FUNCMODE_CH2_ENABLE;
- funcmode |= FUNCMODE_TMODE_WEN;
- funcmode &= ~FUNCMODE_TMODE;
funcmode &= ~FUNCMODE_RMODE;
funcmode |= FUNCMODE_RMODE;
}
+ funcmode |= FUNCMODE_CH0_ENABLE;
writel(funcmode, i2s_private->regs + I2S_FUNCMODE);
+ //printk("%s: %s %s channels=%d rate=%d fm=%x\n", __func__, substream->pcm->name, dai->name, channels, rate, funcmode);
+
if (channels == MONO_SOURCE) {
iiscnf_out |= IISCNFOUT_TX_VOICE_EN_MONO;
iiscnf_in |= CNFIN_RX_CH_SEL_LEFT;
light_i2s_set_div_sclk(i2s_private, rate, DIV_DEFAULT);
+ /* Turn on the I2S enable switch ahead of time,
+ * and start the MCLK before the PA is turned on
+ * to solve the pop noise caused by the sudden change in I2S startup.
+ */
+ i2s_en |= IISEN_I2SEN;
+ writel(i2s_en, i2s_private->regs + I2S_IISEN);
+
return 0;
}
{
struct light_i2s_priv *i2s = snd_soc_dai_get_drvdata(dai);
+ //printk("%s: %p %p\n", __func__, i2s->base, i2s->regs);
+
if(i2s)
snd_soc_dai_init_dma_data(dai, &i2s->dma_params_tx,
&i2s->dma_params_rx);
.channels_max = 2,
},
.ops = &light_i2s_dai_ops,
+ .symmetric_rates = 1,
},
{
.probe = light_i2s_dai_probe,
static bool light_i2s_wr_reg(struct device *dev, unsigned int reg)
{
- switch (reg) {
- case I2S_IISEN:
- case I2S_FUNCMODE:
- case I2S_IISCNF_IN:
- case I2S_FSSTA:
- case I2S_IISCNF_OUT:
- case I2S_DMACR:
- return true;
- default:
- return false;
- }
+ return true;
}
static bool light_i2s_rd_reg(struct device *dev, unsigned int reg)
{
- switch (reg) {
- case I2S_IISEN:
- case I2S_FUNCMODE:
- case I2S_IISCNF_IN:
- case I2S_FSSTA:
- case I2S_IISCNF_OUT:
- case I2S_DMACR:
- return true;
- default:
- return false;
- }
+ return true;
}
static const struct regmap_config light_i2s_regmap_config = {
.max_register = I2S_DR4,
.writeable_reg = light_i2s_wr_reg,
.readable_reg = light_i2s_rd_reg,
- .cache_type = REGCACHE_FLAT,
+ .cache_type = REGCACHE_NONE,
};
static int light_audio_pinconf_set(struct device *dev, unsigned int pin_id, unsigned int val)
static int light_i2s_runtime_suspend(struct device *dev)
{
- struct light_i2s_priv *i2s_priv = dev_get_drvdata(dev);
+ struct light_i2s_priv *i2s_priv = dev_get_drvdata(dev);
- regcache_cache_only(i2s_priv->regmap, true);
- clk_disable_unprepare(i2s_priv->clk);
+ //printk("%s: %s\n", __func__, i2s_priv->name);
+
+ if (!i2s_priv->mclk_keepon) {
+ regcache_cache_only(i2s_priv->regmap, true);
+ clk_disable_unprepare(i2s_priv->clk);
+ }
return 0;
}
static int light_i2s_runtime_resume(struct device *dev)
{
- struct light_i2s_priv *i2s_priv = dev_get_drvdata(dev);
- int ret;
+ struct light_i2s_priv *i2s_priv = dev_get_drvdata(dev);
+ int ret;
+
+ //printk("%s: %s\n", __func__, i2s_priv->name);
+
+ if (!i2s_priv->mclk_keepon) {
+ ret = clk_prepare_enable(i2s_priv->clk);
+ if (ret) {
+ dev_err(i2s_priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(i2s_priv->regmap, false);
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int light_i2s_suspend(struct device *dev)
+{
+ struct light_i2s_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->state == I2S_STATE_IDLE)
+ return 0;
+
+ regmap_read(priv->regmap, I2S_DIV0_LEVEL, &priv->suspend_div0_level);
+ regmap_read(priv->regmap, I2S_DIV3_LEVEL, &priv->suspend_div3_level);
+ regmap_read(priv->regmap, I2S_IISCNF_IN, &priv->suspend_iiscnf_in);
+ regmap_read(priv->regmap, I2S_FSSTA, &priv->suspend_fssta);
+ regmap_read(priv->regmap, I2S_IISCNF_OUT, &priv->suspend_ii2cnf_out);
+ regmap_read(priv->regmap, I2S_FADTLR, &priv->suspend_fadtlr);
+ regmap_read(priv->regmap, I2S_SCCR, &priv->suspend_sccr);
+ regmap_read(priv->regmap, I2S_TXFTLR, &priv->suspend_txftlr);
+ regmap_read(priv->regmap, I2S_RXFTLR, &priv->suspend_rxftlr);
+ regmap_read(priv->regmap, I2S_IMR, &priv->suspend_imr);
+ regmap_read(priv->regmap, I2S_DMATDLR, &priv->suspend_dmatdlr);
+ regmap_read(priv->regmap, I2S_DMARDLR, &priv->suspend_dmardlr);
+
+ if (strcmp(priv->name, AP_I2S)) {
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, &priv->cpr_peri_div_sel);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CTRL_REG, &priv->cpr_peri_ctrl);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, &priv->cpr_peri_clk_sel);
+
+ if (!strcmp(priv->name, AUDIO_I2S0))
+ regmap_update_bits(priv->audio_cpr_regmap, CPR_IP_RST_REG, CPR_I2S0_SRST_N_SEL_MSK, CPR_I2S0_SRST_N_SEL(0));
+ else if (!strcmp(priv->name, AUDIO_I2S1))
+ regmap_update_bits(priv->audio_cpr_regmap, CPR_IP_RST_REG, CPR_I2S1_SRST_N_SEL_MSK, CPR_I2S1_SRST_N_SEL(0));
+ else if (!strcmp(priv->name, AUDIO_I2S2))
+ regmap_update_bits(priv->audio_cpr_regmap, CPR_IP_RST_REG, CPR_I2S2_SRST_N_SEL_MSK, CPR_I2S2_SRST_N_SEL(0));
+ }
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int light_i2s_resume(struct device *dev)
+{
+ struct light_i2s_priv *priv = dev_get_drvdata(dev);
+ int ret;
- ret = clk_prepare_enable(i2s_priv->clk);
- if (ret) {
- dev_err(i2s_priv->dev, "clock enable failed %d\n", ret);
- return ret;
- }
+ if (priv->state == I2S_STATE_IDLE)
+ return 0;
- regcache_cache_only(i2s_priv->regmap, false);
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ if (strcmp(priv->name, AP_I2S)) {
+ if (!strcmp(priv->name, AUDIO_I2S0))
+ regmap_update_bits(priv->audio_cpr_regmap, CPR_IP_RST_REG, CPR_I2S0_SRST_N_SEL_MSK, CPR_I2S0_SRST_N_SEL(1));
+ else if (!strcmp(priv->name, AUDIO_I2S1))
+ regmap_update_bits(priv->audio_cpr_regmap, CPR_IP_RST_REG, CPR_I2S1_SRST_N_SEL_MSK, CPR_I2S1_SRST_N_SEL(1));
+ else if (!strcmp(priv->name, AUDIO_I2S2))
+ regmap_update_bits(priv->audio_cpr_regmap, CPR_IP_RST_REG, CPR_I2S2_SRST_N_SEL_MSK, CPR_I2S2_SRST_N_SEL(1));
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CTRL_REG, priv->cpr_peri_ctrl);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, priv->cpr_peri_clk_sel);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, priv->cpr_peri_div_sel);
+ }
- return ret;
+ regmap_write(priv->regmap, I2S_IISEN, 0);
+ regmap_write(priv->regmap, I2S_FSSTA, priv->suspend_fssta);
+ regmap_write(priv->regmap, I2S_FUNCMODE, priv->suspend_funcmode | FUNCMODE_TMODE_WEN | FUNCMODE_RMODE_WEN);
+ regmap_write(priv->regmap, I2S_IISCNF_IN, priv->suspend_iiscnf_in);
+ regmap_write(priv->regmap, I2S_IISCNF_OUT, priv->suspend_ii2cnf_out);
+ regmap_write(priv->regmap, I2S_DIV0_LEVEL, priv->suspend_div0_level);
+ regmap_write(priv->regmap, I2S_DIV3_LEVEL, priv->suspend_div3_level);
+
+ return ret;
}
+#endif
static const struct of_device_id light_i2s_of_match[] = {
{ .compatible = "light,light-i2s"},
if (!strcmp(sprop, "i2s-master"))
i2s_priv->dai_fmt = SND_SOC_DAIFMT_I2S;
else
- printk("mode is not i2s-master");
+ printk("mode is not i2s-master\n");
}
sprop = of_get_property(np, "light,sel", NULL);
else
i2s_priv->dma_maxburst = 8;
+
+ iprop = of_get_property(np, "light,mclk_keepon", NULL);
+ if (iprop)
+ i2s_priv->mclk_keepon = be32_to_cpup(iprop);
+ else
+ i2s_priv->mclk_keepon = 0;
+
dev_set_drvdata(&pdev->dev, i2s_priv);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2s_priv->regs = devm_ioremap_resource(dev, res);
+
+ //printk("%s: %p %p\n", __func__, i2s_priv->base, i2s_priv->regs);
+
if (IS_ERR(i2s_priv->regs))
return PTR_ERR(i2s_priv->regs);
- i2s_priv->regmap = devm_regmap_init_mmio(&pdev->dev, i2s_priv->regs,
- &light_i2s_regmap_config);
- if (IS_ERR(i2s_priv->regmap)) {
- dev_err(&pdev->dev,
- "Failed to initialise managed register map\n");
- return PTR_ERR(i2s_priv->regmap);
- }
+ i2s_priv->regmap = devm_regmap_init_mmio(&pdev->dev, i2s_priv->regs,
+ &light_i2s_regmap_config);
+ if (IS_ERR(i2s_priv->regmap)) {
+ dev_err(&pdev->dev,
+ "Failed to initialise managed register map\n");
+ return PTR_ERR(i2s_priv->regmap);
+ }
if (!strcmp(i2s_priv->name, AUDIO_I2S0) || !strcmp(i2s_priv->name, AUDIO_I2S1) || !strcmp(i2s_priv->name, AUDIO_I2S2)) {
i2s_priv->audio_pin_regmap = syscon_regmap_lookup_by_phandle(np, "audio-pin-regmap");
light_audio_cpr_set(i2s_priv, CPR_PERI_CTRL_REG, CPR_I2S_SYNC_MSK, CPR_I2S_SYNC_EN);
}
- pm_runtime_enable(&pdev->dev);
- if (!pm_runtime_enabled(&pdev->dev)) {
- ret = light_i2s_runtime_resume(&pdev->dev);
- if (ret)
- goto err_pm_disable;
- }
-
irq = platform_get_irq(pdev, 0);
if (!res || (int)irq <= 0) {
if (IS_ERR(i2s_priv->clk))
return PTR_ERR(i2s_priv->clk);
- ret = clk_prepare_enable(i2s_priv->clk);
- if (ret < 0)
- return ret;
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_resume_and_get(&pdev->dev); // clk gate is enabled by hardware as default register value
+ pm_runtime_put_sync(&pdev->dev);
i2s_priv->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s_priv->dma_params_rx.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
light_pcm_probe(pdev, i2s_priv);
+ if (i2s_priv->mclk_keepon) {
+ clk_prepare_enable(i2s_priv->clk);
+ }
+
ret = devm_snd_soc_register_component(&pdev->dev, &light_i2s_soc_component,
light_i2s_soc_dai, ARRAY_SIZE(light_i2s_soc_dai));
if (ret < 0) {
static const struct dev_pm_ops light_i2s_pm_ops = {
SET_RUNTIME_PM_OPS(light_i2s_runtime_suspend, light_i2s_runtime_resume,
NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(light_i2s_suspend,
+ light_i2s_resume)
};
static struct platform_driver light_i2s_driver = {
#define RXFIFO_IRQ_TH (0x20U)
#define I2S_MAX_FIFO (0x20U)
+#define I2S_STATE_IDLE 0
+#define I2S_STATE_TX_RUNNING 1
+#define I2S_STATE_RX_RUNNING 2
+
struct light_i2s_priv {
void __iomem *base;
phys_addr_t phys;
unsigned int dai_fmt;
u32 dma_maxburst;
unsigned int cfg_off;
+ u32 mclk_keepon;
struct device *dev;
char name[16];
int chan_num:16;
unsigned int clk_master:1;
+
+ u32 suspend_iisen;
+ u32 suspend_funcmode;
+ u32 suspend_iiscnf_in;
+ u32 suspend_fssta;
+ u32 suspend_ii2cnf_out;
+ u32 suspend_fadtlr;
+ u32 suspend_sccr;
+ u32 suspend_txftlr;
+ u32 suspend_rxftlr;
+ u32 suspend_imr;
+ u32 suspend_dmacr;
+ u32 suspend_dmatdlr;
+ u32 suspend_dmardlr;
+ u32 suspend_div0_level;
+ u32 suspend_div3_level;
+ u32 cpr_peri_div_sel;
+ u32 cpr_peri_ctrl;
+ u32 cpr_peri_clk_sel;
+
+ u32 state;
};
#endif /* _LIGHT_I2S_H */
static int light_spdif_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct light_spdif_priv *priv = snd_soc_dai_get_drvdata(dai);
+ //struct light_spdif_priv *priv = snd_soc_dai_get_drvdata(dai);
pm_runtime_get_sync(dai->dev);
- regmap_update_bits(priv->regmap, SPDIF_EN,
- SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(1));
- atomic_inc(&priv->spdif_ref_cnt);
return 0;
}
static void light_spdif_snd_txctrl(struct light_spdif_priv *priv, char on)
{
- unsigned int reg[24];
- int i;
regmap_update_bits(priv->regmap, SPDIF_TX_DMA_EN,
SPDIF_TDMA_EN_MSK, SPDIF_TDMA_EN_SEL(on));
regmap_update_bits(priv->regmap, SPDIF_TX_EN,
SPDIF_TXEN_MSK, SPDIF_TXEN_SEL(on));
+ regmap_read(priv->regmap, SPDIF_TX_FIFO_TH, &priv->suspend_tx_fifo_th);
return;
}
{
struct light_spdif_priv *priv = snd_soc_dai_get_drvdata(dai);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
light_spdif_snd_rxctrl(priv, 0);
- else
+ } else {
light_spdif_snd_txctrl(priv, 0);
-
- atomic_dec(&priv->spdif_ref_cnt);
- if (atomic_read(&priv->spdif_ref_cnt) == 0) {
- regmap_update_bits(priv->regmap, SPDIF_EN,
- SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(0));
}
+
pm_runtime_put(dai->dev);
}
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (tx)
+ if (tx) {
light_spdif_snd_txctrl(priv, 1);
- else
+ priv->state |= SPDIF_STATE_TX_RUNNING;
+ }
+ else {
light_spdif_snd_rxctrl(priv, 1);
+ priv->state |= SPDIF_STATE_RX_RUNNING;
+ }
break;
case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (tx) {
dmaengine_terminate_async(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
light_spdif_snd_txctrl(priv, 0);
+ priv->state &= ~SPDIF_STATE_TX_RUNNING;
+ }
+ else {
+ light_spdif_snd_rxctrl(priv, 0);
+ priv->state &= ~SPDIF_STATE_RX_RUNNING;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (tx) {
+ dmaengine_pause(snd_dmaengine_pcm_get_chan(substream)); // work around for DMAC stop issue
+ light_spdif_snd_txctrl(priv, 0);
}
else
light_spdif_snd_rxctrl(priv, 0);
break;
default:
return -EINVAL;
- }
+ }
return ret;
}
u32 datawidth, chn_num, i;
u32 sample_rate = params_rate(params);
bool is_divclk1 = false; //audio_divclk1 for 44.1k...etc. audio_divclk0 for 48k....etc
- u32 src_clk, tx_div, rx_div;
+ u32 src_clk, tx_div;
switch (params_channels(params)) {
case 1:
CPR_PERI_CLK_SEL_REG, CPR_SPDIF_SRC_SEL_MSK, CPR_SPDIF_SRC_SEL(1));
src_clk = AUDIO_DIVCLK1;
}
- //printk("selected src_clk=%d substream->stream=%d sample_rate=%d\n", src_clk, substream->stream, sample_rate);
- regmap_update_bits(priv->regmap, SPDIF_EN,
- SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(1));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
tx_div = src_clk / (sample_rate * (32 * 2) * 2);
.name = "light_spdif",
};
+#if 0
static void light_spdif_pinctrl(struct light_spdif_priv *priv)
{
//GPIO_SEL AUDIO_PA21~24
0x38,0xF000F, 0x70007); // AUDIO_PA22 SPDIF0_DOUT / AUDIO_PA23 SPDIF1_DOUT
return;
}
+#endif
static const struct regmap_config light_spdif_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SPDIF_RX_USER_B5,
- .cache_type = REGCACHE_FLAT,
+ .cache_type = REGCACHE_NONE,
};
static int light_spdif_runtime_suspend(struct device *dev)
{
- struct light_spdif_priv *priv = dev_get_drvdata(dev);
+ struct light_spdif_priv *priv = dev_get_drvdata(dev);
- regcache_cache_only(priv->regmap, true);
- clk_disable_unprepare(priv->clk);
+ regmap_update_bits(priv->regmap, SPDIF_EN,
+ SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(0));
+
+ if (priv->id == 0) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF0_SRST_N_SEL_MSK, CPR_SPDIF0_SRST_N_SEL(0));
+ } else if (priv->id == 1) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF1_SRST_N_SEL_MSK, CPR_SPDIF1_SRST_N_SEL(0));
+ }
+
+ clk_disable_unprepare(priv->clk);
return 0;
}
static int light_spdif_runtime_resume(struct device *dev)
{
- struct light_spdif_priv *priv = dev_get_drvdata(dev);
- int ret;
+ struct light_spdif_priv *priv = dev_get_drvdata(dev);
+ int ret;
- ret = clk_prepare_enable(priv->clk);
- if (ret) {
- dev_err(priv->dev, "clock enable failed %d\n", ret);
- return ret;
- }
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ if (priv->id == 0) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF0_SRST_N_SEL_MSK, CPR_SPDIF0_SRST_N_SEL(1));
+ } else if (priv->id == 1) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF1_SRST_N_SEL_MSK, CPR_SPDIF1_SRST_N_SEL(1));
+ }
- regcache_cache_only(priv->regmap, false);
+ regmap_update_bits(priv->regmap, SPDIF_EN,
+ SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(1));
- return ret;
+ return ret;
}
+#ifdef CONFIG_PM_SLEEP
+static int light_spdif_suspend(struct device *dev)
+{
+ struct light_spdif_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->state == SPDIF_STATE_IDLE)
+ return 0;
+
+ regmap_read(priv->regmap, SPDIF_TX_EN, &priv->suspend_tx_en);
+ regmap_read(priv->regmap, SPDIF_TX_CTL, &priv->suspend_tx_ctl);
+ regmap_read(priv->regmap, SPDIF_TX_DMA_EN, &priv->suspend_tx_dma_en);
+ regmap_read(priv->regmap, SPDIF_RX_EN, &priv->suspend_rx_en);
+ regmap_read(priv->regmap, SPDIF_RX_CTL, &priv->suspend_rx_ctl);
+ regmap_read(priv->regmap, SPDIF_RX_DMA_EN, &priv->suspend_rx_dma_en);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, &priv->cpr_peri_div_sel);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CTRL_REG, &priv->cpr_peri_ctrl);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, &priv->cpr_peri_clk_sel);
+
+ regmap_update_bits(priv->regmap, SPDIF_EN,
+ SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(0));
+
+ if (priv->id == 0) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF0_SRST_N_SEL_MSK, CPR_SPDIF0_SRST_N_SEL(0));
+ } else if (priv->id == 1) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF1_SRST_N_SEL_MSK, CPR_SPDIF1_SRST_N_SEL(0));
+ }
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int light_spdif_resume(struct device *dev)
+{
+ struct light_spdif_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (priv->state == SPDIF_STATE_IDLE)
+ return 0;
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, priv->cpr_peri_div_sel);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CTRL_REG, priv->cpr_peri_ctrl);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, priv->cpr_peri_clk_sel);
+
+ if (priv->id == 0) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF0_SRST_N_SEL_MSK, CPR_SPDIF0_SRST_N_SEL(1));
+ } else if (priv->id == 1) {
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_SPDIF1_SRST_N_SEL_MSK, CPR_SPDIF1_SRST_N_SEL(1));
+ }
+
+ regmap_update_bits(priv->regmap, SPDIF_EN,
+ SPDIF_SPDIFEN_MSK, SPDIF_SPDIFEN_SEL(1));
+
+ regmap_write(priv->regmap, SPDIF_TX_EN, priv->suspend_tx_en);
+ regmap_write(priv->regmap, SPDIF_TX_CTL, priv->suspend_tx_ctl);
+ regmap_write(priv->regmap, SPDIF_TX_DMA_EN, priv->suspend_tx_dma_en);
+ regmap_write(priv->regmap, SPDIF_RX_EN, priv->suspend_rx_en);
+ regmap_write(priv->regmap, SPDIF_RX_CTL, priv->suspend_rx_ctl);
+ regmap_write(priv->regmap, SPDIF_RX_DMA_EN, priv->suspend_rx_dma_en);
+
+ return ret;
+}
+#endif
+
static const struct of_device_id light_spdif_of_match[] = {
{ .compatible = "light,light-spdif"},
{},
irqreturn_t spdif_interrupt(int irq, void* dev_id)
{
- struct light_spdif_priv* priv = (struct light_spdif_priv*)dev_id;
+ //struct light_spdif_priv* priv = (struct light_spdif_priv*)dev_id;
return IRQ_HANDLED;
}
static int light_spdif_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
- const char *sprop;
const uint32_t *iprop;
struct light_spdif_priv *priv;
struct resource *res;
struct device *dev = &pdev->dev;
- unsigned int irq;
- int data_register, ret = 0;
+ int ret = 0;
priv = devm_kzalloc(&pdev->dev, sizeof(struct light_spdif_priv), GFP_KERNEL);
if (!priv) {
else
priv->dma_maxburst = 8;
+ iprop = of_get_property(np, "id", NULL);
+ if (iprop)
+ priv->id = be32_to_cpup(iprop);
+
dev_set_drvdata(dev, priv);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev_err(dev, "cannot find regmap for audio cpr register\n");
return -EINVAL;
}
+
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_resume_and_get(&pdev->dev); // clk gate is enabled by hardware as default register value
+ pm_runtime_put_sync(&pdev->dev);
+
//AUDIO_DIV1 set to 1/6. 812.8512MHz / 6 = 135.4752MHz
regmap_update_bits(priv->audio_cpr_regmap,
CPR_PERI_DIV_SEL_REG, CPR_AUDIO_DIV1_SEL_MSK, CPR_AUDIO_DIV1_SEL(5));
regmap_update_bits(priv->audio_cpr_regmap,
CPR_PERI_CTRL_REG, CPR_SPDIF_SYNC_MSK, CPR_SPDIF_SYNC_EN);
- pm_runtime_enable(&pdev->dev);
- pm_runtime_resume_and_get(&pdev->dev); // clk gate is enabled by hardware as default register value
- pm_runtime_put_sync(&pdev->dev);
-
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq== 0) {
dev_err(dev, "could not map IRQ.\n");
static const struct dev_pm_ops light_spdif_pm_ops = {
SET_RUNTIME_PM_OPS(light_spdif_runtime_suspend, light_spdif_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(light_spdif_suspend,
+ light_spdif_resume)
};
static struct platform_driver light_spdif_driver = {
#define CPR_PERI_CLK_SEL_REG 0x008 /*audio sys clock selection Register*/
#define CPR_PERI_CTRL_REG 0x00C /*Peripheral control signal configuration register*/
#define CPR_IP_CG_REG 0x010 /* ip clock gate register */
+#define CPR_IP_RST_REG 0x014
/* AUDIO SYS DIV SEL REG, offset: 0x4 */
#define CPR_AUDIO_DIV1_CG_POS (17U)
#define CPR_SPDIF1_CG_SEL_POS (24U)
#define CPR_SPDIF1_CG_SEL_MSK (0x1U << CPR_SPDIF1_CG_SEL_POS)
#define CPR_SPDIF1_CG_SEL(X) (X << CPR_SPDIF1_CG_SEL_POS)
-
+/* CPR_IP_RST_REG */
+#define CPR_SPDIF0_SRST_N_SEL_POS (23U)
+#define CPR_SPDIF0_SRST_N_SEL_MSK (0x1U << CPR_SPDIF0_SRST_N_SEL_POS)
+#define CPR_SPDIF0_SRST_N_SEL(X) (X << CPR_SPDIF0_SRST_N_SEL_POS)
+#define CPR_SPDIF1_SRST_N_SEL_POS (24U)
+#define CPR_SPDIF1_SRST_N_SEL_MSK (0x1U << CPR_SPDIF1_SRST_N_SEL_POS)
+#define CPR_SPDIF1_SRST_N_SEL(X) (X << CPR_SPDIF1_SRST_N_SEL_POS)
#define LIGHT_TDM_DMABUF_SIZE (64 * 1024)
+#define SPDIF_STATE_IDLE 0
+#define SPDIF_STATE_TX_RUNNING 1
+#define SPDIF_STATE_RX_RUNNING 2
struct light_spdif_priv {
void __iomem *regs;
u32 dma_maxburst;
struct device *dev;
unsigned int irq;
- atomic_t spdif_ref_cnt;
+ u32 suspend_tx_en;
+ u32 suspend_tx_ctl;
+ u32 suspend_tx_fifo_th;
+ u32 suspend_tx_fifo_dl;
+ u32 suspend_tx_dma_en;
+ u32 suspend_tx_dma_th;
+ u32 suspend_spdif_imr;
+ u32 suspend_rx_en;
+ u32 suspend_rx_ctl;
+ u32 suspend_rx_fifo_th;
+ u32 suspend_rx_fifo_dl;
+ u32 suspend_rx_dma_en;
+ u32 suspend_rx_dma_th;
+ u32 cpr_peri_div_sel;
+ u32 cpr_peri_ctrl;
+ u32 cpr_peri_clk_sel;
+ u32 id;
+ u32 state;
};
#include <sound/pcm_params.h>
#include <sound/sh_fsi.h>
#include "light-pcm.h"
+#include "light-audio-cpr.h"
#include "light-tdm.h"
#include <linux/dmaengine.h>
#include <linux/regmap.h>
static void light_tdm_snd_rxctrl(struct light_tdm_priv *priv, char on)
{
+ u32 dmactl;
+ u32 tdmen;
+
if (priv->slot_num != 1) {
return;
}
DMACTL_DMAEN_MSK, DMACTL_DMAEN_SEL(on));
regmap_update_bits(priv->regmap, TDM_TDMEN,
TDMCTL_TDMEN_MSK, TDMCTL_TDMEN_SEL(on));
- u32 dmactl;
- u32 tdmen;
regmap_read(priv->regmap, TDM_DMACTL, &dmactl);
regmap_read(priv->regmap, TDM_TDMEN, &tdmen);
//printk("%s TDM_DMACTL=0x%x TDM_TDMEN=0x%x\n", __func__, dmactl, tdmen);
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
light_tdm_snd_rxctrl(priv, 1);
+ priv->state = TDM_STATE_RUNNING;
break;
case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ priv->state = TDM_STATE_IDLE;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
light_tdm_snd_rxctrl(priv, 0);
break;
default:
static int light_tdm_set_fmt_dai(struct snd_soc_dai *dai, unsigned int fmt)
{
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
- u32 cnfin = 0;
if (priv->slot_num != 1) {
return 0;
}
+
+ pm_runtime_resume_and_get(priv->dev);
switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_B:
TDMCTL_SPEDGE_SEL(1));
regmap_update_bits(priv->regmap, TDM_DMADL,
DMACTL_DMADL_MSK, DMACTL_DMADL_SEL(0));
- u32 tdmctl;
- u32 dmadl;
- regmap_read(priv->regmap, TDM_TDMCTL, &tdmctl);
- regmap_read(priv->regmap, TDM_DMADL, &dmadl);
+
+ pm_runtime_put_sync(priv->dev);
return 0;
}
{
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
u32 datawth, chn_num;
+ u32 tdmctl;
if ( params_channels(params) != 1) {
pr_err("Not support channel num\n");
regmap_update_bits(priv->regmap, TDM_TDMCTL,
TDMCTL_DATAWTH_MSK, TDMCTL_DATAWTH_SEL(datawth));
regmap_update_bits(priv->regmap, TDM_TDMCTL,
- TDMCTL_CHNUM_MSK, TDMCTL_CHNUM_SEL(chn_num));
- u32 tdmctl;
+ TDMCTL_CHNUM_MSK, TDMCTL_CHNUM_SEL(chn_num));
regmap_read(priv->regmap, TDM_TDMCTL, &tdmctl);
//printk("%s TDM_TDMCTL=0x%x\n", __func__, tdmctl);
light_tdm_set_div(priv, params);
.name = "light_tdm",
};
+#if 0
static void light_tdm_pinctrl(struct light_tdm_priv *tdm_priv)
{
//GPIO_SEL
0x40,0xF0000, 0x70000); // AUDIO_PA27
return;
}
+#endif
+
+static bool light_tdm_wr_reg(struct device *dev, unsigned int reg)
+{
+ return true;
+}
+
+static bool light_tdm_rd_reg(struct device *dev, unsigned int reg)
+{
+ return true;
+}
static const struct regmap_config light_tdm_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TDM_DIV0_LEVEL,
- .cache_type = REGCACHE_FLAT,
+ .writeable_reg = light_tdm_wr_reg,
+ .readable_reg = light_tdm_rd_reg,
+ .cache_type = REGCACHE_NONE,
};
static int light_tdm_runtime_suspend(struct device *dev)
{
- struct light_tdm_priv *priv = dev_get_drvdata(dev);
+ struct light_tdm_priv *priv = dev_get_drvdata(dev);
- if (priv->slot_num != 1) {
- return 0;
- }
+ if (priv->slot_num != 1) {
+ return 0;
+ }
- regcache_cache_only(priv->regmap, true);
- clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk);
return 0;
}
static int light_tdm_runtime_resume(struct device *dev)
{
- struct light_tdm_priv *priv = dev_get_drvdata(dev);
- int ret;
+ struct light_tdm_priv *priv = dev_get_drvdata(dev);
+ int ret;
- if (priv->slot_num != 1) {
- return 0;
- }
+ if (priv->slot_num != 1) {
+ return 0;
+ }
- ret = clk_prepare_enable(priv->clk);
- if (ret) {
- dev_err(priv->dev, "clock enable failed %d\n", ret);
- return ret;
- }
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
- regcache_cache_only(priv->regmap, false);
+ return ret;
+}
- return ret;
+#ifdef CONFIG_PM_SLEEP
+static int light_tdm_suspend(struct device *dev)
+{
+ struct light_tdm_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->slot_num != 1 || priv->state == TDM_STATE_IDLE) {
+ return 0;
+ }
+
+ regmap_read(priv->regmap, TDM_TDMCTL, &priv->suspend_tdmctl);
+ regmap_read(priv->regmap, TDM_CHOFFSET1, &priv->suspend_choffset1);
+ regmap_read(priv->regmap, TDM_CHOFFSET2, &priv->suspend_choffset2);
+ regmap_read(priv->regmap, TDM_CHOFFSET3, &priv->suspend_choffset3);
+ regmap_read(priv->regmap, TDM_CHOFFSET4, &priv->suspend_choffset4);
+ regmap_read(priv->regmap, TDM_FIFOTL1, &priv->suspend_fifotl1);
+ regmap_read(priv->regmap, TDM_FIFOTL2, &priv->suspend_fifotl2);
+ regmap_read(priv->regmap, TDM_FIFOTL3, &priv->suspend_fifotl3);
+ regmap_read(priv->regmap, TDM_FIFOTL4, &priv->suspend_fifotl4);
+ regmap_read(priv->regmap, TDM_IMR, &priv->suspend_imr);
+ regmap_read(priv->regmap, TDM_DMADL, &priv->suspend_dmadl);
+ regmap_read(priv->regmap, TDM_DIV0_LEVEL, &priv->suspend_div0level);
+
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, &priv->cpr_peri_div_sel);
+ regmap_read(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, &priv->cpr_peri_clk_sel);
+
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_TDM_SRST_N_SEL_MSK, CPR_TDM_SRST_N_SEL(0));
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
}
+static int light_tdm_resume(struct device *dev)
+{
+ struct light_tdm_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (priv->slot_num != 1 || priv->state == TDM_STATE_IDLE) {
+ return 0;
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ dev_err(priv->dev, "clock enable failed %d\n", ret);
+ return ret;
+ }
+
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_DIV_SEL_REG, priv->cpr_peri_div_sel);
+ regmap_write(priv->audio_cpr_regmap, CPR_PERI_CLK_SEL_REG, priv->cpr_peri_clk_sel);
+
+ regmap_update_bits(priv->audio_cpr_regmap,
+ CPR_IP_RST_REG, CPR_TDM_SRST_N_SEL_MSK, CPR_TDM_SRST_N_SEL(1));
+
+ regmap_write(priv->regmap, TDM_TDMCTL, priv->suspend_tdmctl);
+ regmap_write(priv->regmap, TDM_CHOFFSET1, priv->suspend_choffset1);
+ regmap_write(priv->regmap, TDM_CHOFFSET2, priv->suspend_choffset2);
+ regmap_write(priv->regmap, TDM_CHOFFSET3, priv->suspend_choffset3);
+ regmap_write(priv->regmap, TDM_CHOFFSET4, priv->suspend_choffset4);
+ regmap_write(priv->regmap, TDM_FIFOTL1, priv->suspend_fifotl1);
+ regmap_write(priv->regmap, TDM_FIFOTL2, priv->suspend_fifotl2);
+ regmap_write(priv->regmap, TDM_FIFOTL3, priv->suspend_fifotl3);
+ regmap_write(priv->regmap, TDM_FIFOTL4, priv->suspend_fifotl4);
+ regmap_write(priv->regmap, TDM_IMR, priv->suspend_imr);
+ regmap_write(priv->regmap, TDM_DMADL, priv->suspend_dmadl);
+ regmap_write(priv->regmap, TDM_DIV0_LEVEL, priv->suspend_div0level);
+
+ return ret;
+}
+#endif
+
static const struct of_device_id light_tdm_of_match[] = {
{ .compatible = "light,light-tdm"},
{},
irqreturn_t tdm_interrupt(int irq, void* dev_id)
{
- struct light_tdm_priv* priv = (struct light_tdm_priv*)dev_id;
+ //struct light_tdm_priv* priv = (struct light_tdm_priv*)dev_id;
return IRQ_HANDLED;
}
struct light_tdm_priv *tdm_priv;
struct resource *res;
struct device *dev = &pdev->dev;
- unsigned int irq;
+
int data_register, ret = 0;
tdm_priv = devm_kzalloc(&pdev->dev, sizeof(struct light_tdm_priv), GFP_KERNEL);
static const struct dev_pm_ops light_tdm_pm_ops = {
SET_RUNTIME_PM_OPS(light_tdm_runtime_suspend, light_tdm_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(light_tdm_suspend,
+ light_tdm_resume)
};
static struct platform_driver light_tdm_driver = {
#define TDM_RDR4 0x60
#define TDM_DIV0_LEVEL 0x64
-#define CPR_PERI_DIV_SEL_REG 0x004 /*audio sys i2s clock div Register*/
-#define CPR_PERI_CLK_SEL_REG 0x008 /*audio sys i2s clock selection Register*/
-#define CPR_IP_CG_REG 0x010 /* ip clock gate register */
-
-/* AUDIO SYS DIV SEL REG, offset: 0x4 */
-#define CPR_AUDIO_DIV1_SEL_POS (12U)
-#define CPR_AUDIO_DIV1_SEL_MSK (0x1FU << CPR_AUDIO_DIV1_SEL_POS)
-#define CPR_AUDIO_DIV1_SEL(X) (X << CPR_AUDIO_DIV1_SEL_POS)
-#define CPR_AUDIO_DIV0_SEL_POS (4U)
-#define CPR_AUDIO_DIV0_SEL_MSK (0x1FU << CPR_AUDIO_DIV0_SEL_POS)
-#define CPR_AUDIO_DIV0_SEL(X) (X << CPR_AUDIO_DIV0_SEL_POS)
-#define CPR_TDM_CG_SEL_POS (21U)
-#define CPR_TDM_CG_SEL_MSK (0x1U << CPR_TDM_CG_SEL_POS)
-#define CPR_TDM_CG_SEL(X) (X << CPR_TDM_CG_SEL_POS)
-#define CPR_TDM_SRC_SEL_POS (16U)
-#define CPR_TDM_SRC_SEL_MSK (0x3U << CPR_TDM_SRC_SEL_POS)
-#define CPR_TDM_SRC_SEL(X) (X << CPR_TDM_SRC_SEL_POS)
-
#define TDM_MODE_MASTER 0x1
#define TDM_MODE_SLAVE 0x0
#define TDMCTL_MODE_POS (0U)
#define TDMCTL_TDMEN_SEL(X) (X << TDMCTL_TDMEN_POS)
#define LIGHT_TDM_DMABUF_SIZE (64 * 1024)
+#define TDM_STATE_IDLE 0
+#define TDM_STATE_RUNNING 1
struct light_tdm_priv {
void __iomem *regs;
char slots;
char slot_num;
unsigned int irq;
+ u32 suspend_tdmctl;
+ u32 suspend_choffset1;
+ u32 suspend_choffset2;
+ u32 suspend_choffset3;
+ u32 suspend_choffset4;
+ u32 suspend_fifotl1;
+ u32 suspend_fifotl2;
+ u32 suspend_fifotl3;
+ u32 suspend_fifotl4;
+ u32 suspend_imr;
+ u32 suspend_dmadl;
+ u32 suspend_div0level;
+ u32 cpr_peri_div_sel;
+ u32 cpr_peri_clk_sel;
+ u32 state;
};
if (!found_cmd_mmap)
found_cmd_mmap = !strcmp(bname + 1, cmd);
if (!found_coreutils_mmap)
- found_coreutils_mmap = !strcmp(bname + 1, "coreutils");
+ found_coreutils_mmap = !strcmp(bname + 1, "coreutils") + !strcmp(bname + 1, "sleep.coreutils");
if (!found_libc_mmap)
found_libc_mmap = !strncmp(bname + 1, "libc", 4);
if (!found_ld_mmap)