* [PATCH 2/2] DTS: ARM: gta04: introduce legacy spi-cs-high to make display work again
From: H. Nikolaus Schaller @ 2019-07-08 14:46 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Mark Rutland, Benoît Cousson,
Tony Lindgren
Cc: letux-kernel, linux-spi, devicetree, linux-kernel, linux-omap,
H. Nikolaus Schaller, stable
In-Reply-To: <cover.1562597164.git.hns@goldelico.com>
commit 6953c57ab172 "gpio: of: Handle SPI chipselect legacy bindings"
did introduce logic to centrally handle the legacy spi-cs-high property
in combination with cs-gpios. This assumes that the polarity
of the CS has to be inverted if spi-cs-high is missing, even
and especially if non-legacy GPIO_ACTIVE_HIGH is specified.
The DTS for the GTA04 was orginally introduced under the assumption
that there is no need for spi-cs-high if the gpio is defined with
proper polarity GPIO_ACTIVE_HIGH.
This was not a problem until gpiolib changed the interpretation of
GPIO_ACTIVE_HIGH and missing spi-cs-high.
The effect is that the missing spi-cs-high is now interpreted as CS being
low (despite GPIO_ACTIVE_HIGH) which turns off the SPI interface when the
panel is to be programmed by the panel driver.
Therefore, we have to add the redundant and legacy spi-cs-high property
to properly pass through the legacy handler.
Since this is nowhere documented in the bindings, we add some words of
WARNING.
Cc: stable@vger.kernel.org
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
Documentation/devicetree/bindings/spi/spi-bus.txt | 6 ++++++
arch/arm/boot/dts/omap3-gta04.dtsi | 1 +
2 files changed, 7 insertions(+)
diff --git a/Documentation/devicetree/bindings/spi/spi-bus.txt b/Documentation/devicetree/bindings/spi/spi-bus.txt
index 1f6e86f787ef..982aa590058b 100644
--- a/Documentation/devicetree/bindings/spi/spi-bus.txt
+++ b/Documentation/devicetree/bindings/spi/spi-bus.txt
@@ -47,6 +47,10 @@ cs1 : native
cs2 : &gpio1 1 0
cs3 : &gpio1 2 0
+WARNING: the polarity of cs-gpios may be inverted in some cases compared
+to what is specified in the third parameter. In that case the spi-cs-high
+property must be defined for slave nodes.
+
SPI slave nodes must be children of the SPI controller node.
@@ -69,6 +73,8 @@ All slave nodes can contain the following optional properties:
phase (CPHA) mode.
- spi-cs-high - Empty property indicating device requires chip select
active high.
+ WARNING: this is especially required even if the cs-gpios
+ define the gpio as GPIO_ACTIVE_HIGH
- spi-3wire - Empty property indicating device requires 3-wire mode.
- spi-lsb-first - Empty property indicating device requires LSB first mode.
- spi-tx-bus-width - The bus width (number of data wires) that is used for MOSI.
diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
index 9a9a29fe88ec..47bab8e1040e 100644
--- a/arch/arm/boot/dts/omap3-gta04.dtsi
+++ b/arch/arm/boot/dts/omap3-gta04.dtsi
@@ -124,6 +124,7 @@
spi-max-frequency = <100000>;
spi-cpol;
spi-cpha;
+ spi-cs-high;
backlight= <&backlight>;
label = "lcd";
--
2.19.1
^ permalink raw reply related
* [PATCH 1/2] DTS: ARM: gta04: define chosen/stdout-path
From: H. Nikolaus Schaller @ 2019-07-08 14:46 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Mark Rutland, Benoît Cousson,
Tony Lindgren
Cc: letux-kernel, linux-spi, devicetree, linux-kernel, linux-omap,
H. Nikolaus Schaller
In-Reply-To: <cover.1562597164.git.hns@goldelico.com>
This allows to remove the console= entry in the kernel command line.
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
arch/arm/boot/dts/omap3-gta04.dtsi | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
index 6473d8dfadc0..9a9a29fe88ec 100644
--- a/arch/arm/boot/dts/omap3-gta04.dtsi
+++ b/arch/arm/boot/dts/omap3-gta04.dtsi
@@ -24,6 +24,10 @@
reg = <0x80000000 0x20000000>; /* 512 MB */
};
+ chosen {
+ stdout-path = &uart3;
+ };
+
aliases {
display0 = &lcd;
display1 = &tv0;
--
2.19.1
^ permalink raw reply related
* [PATCH 0/2] DTS: ARM: some minor updates and fixes for GTA04
From: H. Nikolaus Schaller @ 2019-07-08 14:46 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Mark Rutland, Benoît Cousson,
Tony Lindgren
Cc: letux-kernel, linux-spi, devicetree, linux-kernel, linux-omap,
H. Nikolaus Schaller
We define define chosen/stdout-path chosen to remove the
console= entry in the kernel command line.
And we fix the SPI definition to make the LCD panel work
again.
H. Nikolaus Schaller (2):
DTS: ARM: gta04: define chosen/stdout-path
DTS: ARM: gta04: introduce legacy spi-cs-high to make display work
again
Documentation/devicetree/bindings/spi/spi-bus.txt | 6 ++++++
arch/arm/boot/dts/omap3-gta04.dtsi | 5 +++++
2 files changed, 11 insertions(+)
--
2.19.1
^ permalink raw reply
* Re: [PATCH 1/6] dt-bindings: irqchip: Add PRUSS interrupt controller bindings
From: Andrew F. Davis @ 2019-07-08 14:34 UTC (permalink / raw)
To: Suman Anna, Marc Zyngier, Rob Herring, Thomas Gleixner,
Jason Cooper
Cc: Tony Lindgren, Roger Quadros, Lokesh Vutla, Grygorii Strashko,
Sekhar Nori, David Lechner, Murali Karicheri, devicetree,
linux-omap, linux-arm-kernel, linux-kernel
In-Reply-To: <20190708035243.12170-2-s-anna@ti.com>
On 7/7/19 11:52 PM, Suman Anna wrote:
> The Programmable Real-Time Unit Subsystem (PRUSS) contains an interrupt
> controller (INTC) that can handle various system input events and post
> interrupts back to the device-level initiators. The INTC can support
> upto 64 input events on most SoCs with individual control configuration
> and hardware prioritization. These events are mapped onto 10 interrupt
> lines through two levels of many-to-one mapping support. Different
> interrupt lines are routed to the individual PRU cores or to the
> host CPU or to other PRUSS instances.
>
> The K3 AM65x and J721E SoCs have the next generation of the PRU-ICSS IP,
> commonly called ICSSG. The ICSSG interrupt controller on K3 SoCs provide
> a higher number of host interrupts (20 vs 10) and can handle an increased
> number of input events (160 vs 64) from various SoC interrupt sources.
>
> Add the bindings document for these interrupt controllers on all the
> applicable SoCs. It covers the OMAP architecture SoCs - AM33xx, AM437x
> and AM57xx; the Keystone 2 architecture based 66AK2G SoC; the Davinci
> architecture based OMAPL138 SoCs, and the K3 architecture based AM65x
> and J721E SoCs.
>
> Signed-off-by: Suman Anna <s-anna@ti.com>
> Signed-off-by: Andrew F. Davis <afd@ti.com>
> Signed-off-by: Roger Quadros <rogerq@ti.com>
> ---
> Prior version: https://patchwork.kernel.org/patch/10795771/
>
> .../interrupt-controller/ti,pruss-intc.txt | 92 +++++++++++++++++++
> 1 file changed, 92 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/ti,pruss-intc.txt
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/ti,pruss-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/ti,pruss-intc.txt
> new file mode 100644
> index 000000000000..020073c07a92
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/ti,pruss-intc.txt
> @@ -0,0 +1,92 @@
> +PRU ICSS INTC on TI SoCs
> +========================
> +
> +Each PRUSS has a single interrupt controller instance that is common to both
> +the PRU cores. Most interrupt controllers can route 64 input events which are
> +then mapped to 10 possible output interrupts through two levels of mapping.
> +The input events can be triggered by either the PRUs and/or various other
> +PRUSS internal and external peripherals. The first 2 output interrupts are
> +fed exclusively to the internal PRU cores, with the remaining 8 (2 through 9)
> +connected to external interrupt controllers including the MPU and/or other
> +PRUSS instances, DSPs or devices.
> +
> +The K3 family of SoCs can handle 160 input events that can be mapped to 20
> +different possible output interrupts. The additional output interrupts (10
> +through 19) are connected to new sub-modules within the ICSSG instances.
> +
> +This interrupt-controller node should be defined as a child node of the
> +corresponding PRUSS node. The node should be named "interrupt-controller".
> +Please see the overall PRUSS bindings document for additional details
> +including a complete example,
> + Documentation/devicetree/bindings/soc/ti/ti,pruss.txt
> +
> +Required Properties:
> +--------------------
> +- compatible : should be one of the following,
> + "ti,pruss-intc" for OMAP-L13x/AM18x/DA850 SoCs,
> + AM335x family of SoCs,
> + AM437x family of SoCs,
> + AM57xx family of SoCs
> + 66AK2G family of SoCs
> + "ti,icssg-intc" for K3 AM65x & J721E family of SoCs
> +- reg : base address and size for the PRUSS INTC sub-module
> +- interrupts : all the interrupts generated towards the main host
> + processor in the SoC. The format depends on the
> + interrupt specifier for the particular SoC's ARM GIC
> + parent interrupt controller. A shared interrupt can
> + be skipped if the desired destination and usage is by
> + a different processor/device.
> +- interrupt-names : should use one of the following names for each valid
> + interrupt connected to ARM GIC, the name should match
> + the corresponding host interrupt number,
> + "host0", "host1", "host2", "host3", "host4",
> + "host5", "host6" or "host7"
> +- interrupt-controller : mark this node as an interrupt controller
> +- #interrupt-cells : should be 1. Client users shall use the PRU System
> + event number (the interrupt source that the client
> + is interested in) as the value of the interrupts
> + property in their node
> +
> +Optional Properties:
> +--------------------
> +The following properties are _required_ only for some SoCs. If none of the below
> +properties are defined, it implies that all the host interrupts 2 through 9 are
> +connected exclusively to the ARM GIC.
> +
> +- ti,irqs-reserved : an array of 8-bit elements of host interrupts between
> + 0 and 7 (corresponding to PRUSS INTC output interrupts
> + 2 through 9) that are not connected to the ARM GIC.
The reason for 0-7 mapping to 2-9 is not instantly clear to someone
reading this. If you respin this could you note that reason is
interrupts 0 and 1 are always routed back into the PRUSS. Thinking more
on that, the same is true for interrupt 7 ("host5") on AM437x/66AK2G yet
we don't skip that in the naming.. now that we have the reserved IRQ
mechanism above, why not leave the one-to-one interrupt to name mapping,
but always have at least the first two marked as reserved for all the
current devices:
ti,irqs-reserved = /bits/ 8 <0 1>;
Then any "hostx" listed as reserved need not be present in the host
interrupts property array. To me that would solve the "managing
interrupts not targeting the Linux running core" problem and keep the
names consistent, e.g.:
/* AM437x PRU-ICSS */
pruss_intc: interrupt-controller@20000 {
compatible = "ti,pruss-intc";
reg = <0x20000 0x2000>;
interrupts = < 20 21 22
23 24 25 26>;
interrupt-names = "host2", "host3", "host4",
"host5", "host6", "host8", "host9";
interrupt-controller;
#interrupt-cells = <1>;
ti,irqs-reserved = /bits/ 8 <0 1 7>;
};
Instantly clear which are missing and which "hostx" maps to which host
interrupt number.
Andrew
> + Eg: AM437x and 66AK2G SoCs do not have "host5"
> + interrupt connected to MPU
> +- ti,irqs-shared : an array of 8-bit elements of host interrupts between
> + 0 and 7 (corresponding to PRUSS INTC output interrupts
> + 2 through 9) that are also connected to other devices
> + or processors in the SoC.
> + Eg: AM65x and J721E SoCs have "host5", "host6" and
> + "host7" interrupts connected to MPU, and other
> + ICSSG instances
> +
> +
> +Example:
> +--------
> +
> +1. /* AM33xx PRU-ICSS */
> + pruss: pruss@0 {
> + compatible = "ti,am3356-pruss";
> + reg = <0x0 0x80000>;
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ...
> +
> + pruss_intc: interrupt-controller@20000 {
> + compatible = "ti,pruss-intc";
> + reg = <0x20000 0x2000>;
> + interrupts = <20 21 22 23 24 25 26 27>;
> + interrupt-names = "host0", "host1", "host2",
> + "host3", "host4", "host5",
> + "host6", "host7";
> + interrupt-controller;
> + #interrupt-cells = <1>;
> + ti,irqs-shared = /bits/ 8 <0 6 7>;
> + };
> + };
>
^ permalink raw reply
* [PATCH 3/3] ARM: dts: exynos: add initial data for coupled regulators for Exynos5422/5800
From: k.konieczny @ 2019-07-08 14:11 UTC (permalink / raw)
To: k.konieczny
Cc: Marek Szyprowski, Bartlomiej Zolnierkiewicz, Chanwoo Choi,
Krzysztof Kozlowski, Kukjin Kim, Kyungmin Park, Mark Rutland,
MyungJoo Ham, Nishanth Menon, Rob Herring, Stephen Boyd,
Viresh Kumar, devicetree, linux-arm-kernel, linux-kernel,
linux-pm, linux-samsung-soc
In-Reply-To: <20190708141140.24379-1-k.konieczny@partner.samsung.com>
From: Marek Szyprowski <m.szyprowski@samsung.com>
Declare Exynos5422/5800 voltage ranges for opp points for big cpu core and
bus wcore and couple their voltage supllies as vdd_arm and vdd_int should
be in 300mV range.
Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Kamil Konieczny <k.konieczny@partner.samsung.com>
---
arch/arm/boot/dts/exynos5420.dtsi | 34 +++++++++----------
arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 4 +++
arch/arm/boot/dts/exynos5800-peach-pi.dts | 4 +++
arch/arm/boot/dts/exynos5800.dtsi | 32 ++++++++---------
4 files changed, 41 insertions(+), 33 deletions(-)
diff --git a/arch/arm/boot/dts/exynos5420.dtsi b/arch/arm/boot/dts/exynos5420.dtsi
index 5fb2326875dc..0cbf74750553 100644
--- a/arch/arm/boot/dts/exynos5420.dtsi
+++ b/arch/arm/boot/dts/exynos5420.dtsi
@@ -48,62 +48,62 @@
opp-shared;
opp-1800000000 {
opp-hz = /bits/ 64 <1800000000>;
- opp-microvolt = <1250000>;
+ opp-microvolt = <1250000 1250000 1500000>;
clock-latency-ns = <140000>;
};
opp-1700000000 {
opp-hz = /bits/ 64 <1700000000>;
- opp-microvolt = <1212500>;
+ opp-microvolt = <1212500 1212500 1500000>;
clock-latency-ns = <140000>;
};
opp-1600000000 {
opp-hz = /bits/ 64 <1600000000>;
- opp-microvolt = <1175000>;
+ opp-microvolt = <1175000 1175000 1500000>;
clock-latency-ns = <140000>;
};
opp-1500000000 {
opp-hz = /bits/ 64 <1500000000>;
- opp-microvolt = <1137500>;
+ opp-microvolt = <1137500 1137500 1500000>;
clock-latency-ns = <140000>;
};
opp-1400000000 {
opp-hz = /bits/ 64 <1400000000>;
- opp-microvolt = <1112500>;
+ opp-microvolt = <1112500 1112500 1500000>;
clock-latency-ns = <140000>;
};
opp-1300000000 {
opp-hz = /bits/ 64 <1300000000>;
- opp-microvolt = <1062500>;
+ opp-microvolt = <1062500 1062500 1500000>;
clock-latency-ns = <140000>;
};
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>;
- opp-microvolt = <1037500>;
+ opp-microvolt = <1037500 1037500 1500000>;
clock-latency-ns = <140000>;
};
opp-1100000000 {
opp-hz = /bits/ 64 <1100000000>;
- opp-microvolt = <1012500>;
+ opp-microvolt = <1012500 1012500 1500000>;
clock-latency-ns = <140000>;
};
opp-1000000000 {
opp-hz = /bits/ 64 <1000000000>;
- opp-microvolt = < 987500>;
+ opp-microvolt = < 987500 987500 1500000>;
clock-latency-ns = <140000>;
};
opp-900000000 {
opp-hz = /bits/ 64 <900000000>;
- opp-microvolt = < 962500>;
+ opp-microvolt = < 962500 962500 1500000>;
clock-latency-ns = <140000>;
};
opp-800000000 {
opp-hz = /bits/ 64 <800000000>;
- opp-microvolt = < 937500>;
+ opp-microvolt = < 937500 937500 1500000>;
clock-latency-ns = <140000>;
};
opp-700000000 {
opp-hz = /bits/ 64 <700000000>;
- opp-microvolt = < 912500>;
+ opp-microvolt = < 912500 912500 1500000>;
clock-latency-ns = <140000>;
};
};
@@ -1100,23 +1100,23 @@
opp00 {
opp-hz = /bits/ 64 <84000000>;
- opp-microvolt = <925000>;
+ opp-microvolt = <925000 925000 1400000>;
};
opp01 {
opp-hz = /bits/ 64 <111000000>;
- opp-microvolt = <950000>;
+ opp-microvolt = <950000 950000 1400000>;
};
opp02 {
opp-hz = /bits/ 64 <222000000>;
- opp-microvolt = <950000>;
+ opp-microvolt = <950000 950000 1400000>;
};
opp03 {
opp-hz = /bits/ 64 <333000000>;
- opp-microvolt = <950000>;
+ opp-microvolt = <950000 950000 1400000>;
};
opp04 {
opp-hz = /bits/ 64 <400000000>;
- opp-microvolt = <987500>;
+ opp-microvolt = <987500 987500 1400000>;
};
};
diff --git a/arch/arm/boot/dts/exynos5422-odroid-core.dtsi b/arch/arm/boot/dts/exynos5422-odroid-core.dtsi
index 25d95de15c9b..65d094256b54 100644
--- a/arch/arm/boot/dts/exynos5422-odroid-core.dtsi
+++ b/arch/arm/boot/dts/exynos5422-odroid-core.dtsi
@@ -428,6 +428,8 @@
regulator-max-microvolt = <1500000>;
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&buck3_reg>;
+ regulator-coupled-max-spread = <300000>;
};
buck3_reg: BUCK3 {
@@ -436,6 +438,8 @@
regulator-max-microvolt = <1400000>;
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&buck2_reg>;
+ regulator-coupled-max-spread = <300000>;
};
buck4_reg: BUCK4 {
diff --git a/arch/arm/boot/dts/exynos5800-peach-pi.dts b/arch/arm/boot/dts/exynos5800-peach-pi.dts
index e0f470fe54c8..5c1e965ed7e9 100644
--- a/arch/arm/boot/dts/exynos5800-peach-pi.dts
+++ b/arch/arm/boot/dts/exynos5800-peach-pi.dts
@@ -257,6 +257,8 @@
regulator-always-on;
regulator-boot-on;
regulator-ramp-delay = <12500>;
+ regulator-coupled-with = <&buck3_reg>;
+ regulator-coupled-max-spread = <300000>;
regulator-state-mem {
regulator-off-in-suspend;
};
@@ -269,6 +271,8 @@
regulator-always-on;
regulator-boot-on;
regulator-ramp-delay = <12500>;
+ regulator-coupled-with = <&buck2_reg>;
+ regulator-coupled-max-spread = <300000>;
regulator-state-mem {
regulator-off-in-suspend;
};
diff --git a/arch/arm/boot/dts/exynos5800.dtsi b/arch/arm/boot/dts/exynos5800.dtsi
index 57d3b319fd65..2a74735d161c 100644
--- a/arch/arm/boot/dts/exynos5800.dtsi
+++ b/arch/arm/boot/dts/exynos5800.dtsi
@@ -22,61 +22,61 @@
&cluster_a15_opp_table {
opp-1700000000 {
- opp-microvolt = <1250000>;
+ opp-microvolt = <1250000 1250000 1500000>;
};
opp-1600000000 {
- opp-microvolt = <1250000>;
+ opp-microvolt = <1250000 1250000 1500000>;
};
opp-1500000000 {
- opp-microvolt = <1100000>;
+ opp-microvolt = <1100000 1100000 1500000>;
};
opp-1400000000 {
- opp-microvolt = <1100000>;
+ opp-microvolt = <1100000 1100000 1500000>;
};
opp-1300000000 {
- opp-microvolt = <1100000>;
+ opp-microvolt = <1100000 1100000 1500000>;
};
opp-1200000000 {
- opp-microvolt = <1000000>;
+ opp-microvolt = <1000000 1000000 1500000>;
};
opp-1100000000 {
- opp-microvolt = <1000000>;
+ opp-microvolt = <1000000 1000000 1500000>;
};
opp-1000000000 {
- opp-microvolt = <1000000>;
+ opp-microvolt = <1000000 1000000 1500000>;
};
opp-900000000 {
- opp-microvolt = <1000000>;
+ opp-microvolt = <1000000 1000000 1500000>;
};
opp-800000000 {
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
};
opp-700000000 {
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
};
opp-600000000 {
opp-hz = /bits/ 64 <600000000>;
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
clock-latency-ns = <140000>;
};
opp-500000000 {
opp-hz = /bits/ 64 <500000000>;
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
clock-latency-ns = <140000>;
};
opp-400000000 {
opp-hz = /bits/ 64 <400000000>;
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
clock-latency-ns = <140000>;
};
opp-300000000 {
opp-hz = /bits/ 64 <300000000>;
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
clock-latency-ns = <140000>;
};
opp-200000000 {
opp-hz = /bits/ 64 <200000000>;
- opp-microvolt = <900000>;
+ opp-microvolt = <900000 900000 1500000>;
clock-latency-ns = <140000>;
};
};
--
2.22.0
^ permalink raw reply related
* [PATCH 2/3] devfreq: exynos-bus: convert to use dev_pm_opp_set_rate()
From: k.konieczny @ 2019-07-08 14:11 UTC (permalink / raw)
To: k.konieczny
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Krzysztof Kozlowski, Kukjin Kim, Kyungmin Park, Mark Rutland,
MyungJoo Ham, Nishanth Menon, Rob Herring, Stephen Boyd,
Viresh Kumar, devicetree, linux-arm-kernel, linux-kernel,
linux-pm, linux-samsung-soc
In-Reply-To: <20190708141140.24379-1-k.konieczny@partner.samsung.com>
From: Kamil Konieczny <k.konieczny@partner.samsung.com>
Reuse opp core code for setting bus clock and voltage. As a side
effect this allow useage of coupled regulators feature (required
for boards using Exynos5422/5800 SoCs) because dev_pm_opp_set_rate()
uses regulator_set_voltage_triplet() for setting regulator voltage
while the old code used regulator_set_voltage_tol() with fixed
tolerance. This patch also removes no longer needed parsing of DT
property "exynos,voltage-tolerance" (no Exynos devfreq DT node uses
it).
Signed-off-by: Kamil Konieczny <k.konieczny@partner.samsung.com>
---
drivers/devfreq/exynos-bus.c | 172 ++++++++++++++---------------------
1 file changed, 66 insertions(+), 106 deletions(-)
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index 486cc5b422f1..7fc4f76bd848 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -25,7 +25,6 @@
#include <linux/slab.h>
#define DEFAULT_SATURATION_RATIO 40
-#define DEFAULT_VOLTAGE_TOLERANCE 2
struct exynos_bus {
struct device *dev;
@@ -37,9 +36,9 @@ struct exynos_bus {
unsigned long curr_freq;
- struct regulator *regulator;
+ struct opp_table *opp_table;
+
struct clk *clk;
- unsigned int voltage_tolerance;
unsigned int ratio;
};
@@ -99,56 +98,25 @@ static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags)
{
struct exynos_bus *bus = dev_get_drvdata(dev);
struct dev_pm_opp *new_opp;
- unsigned long old_freq, new_freq, new_volt, tol;
int ret = 0;
-
- /* Get new opp-bus instance according to new bus clock */
+ /*
+ * New frequency for bus may not be exactly matched to opp, adjust
+ * *freq to correct value.
+ */
new_opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(new_opp)) {
dev_err(dev, "failed to get recommended opp instance\n");
return PTR_ERR(new_opp);
}
- new_freq = dev_pm_opp_get_freq(new_opp);
- new_volt = dev_pm_opp_get_voltage(new_opp);
dev_pm_opp_put(new_opp);
- old_freq = bus->curr_freq;
-
- if (old_freq == new_freq)
- return 0;
- tol = new_volt * bus->voltage_tolerance / 100;
-
/* Change voltage and frequency according to new OPP level */
mutex_lock(&bus->lock);
+ ret = dev_pm_opp_set_rate(dev, *freq);
+ if (!ret)
+ bus->curr_freq = *freq;
- if (old_freq < new_freq) {
- ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol);
- if (ret < 0) {
- dev_err(bus->dev, "failed to set voltage\n");
- goto out;
- }
- }
-
- ret = clk_set_rate(bus->clk, new_freq);
- if (ret < 0) {
- dev_err(dev, "failed to change clock of bus\n");
- clk_set_rate(bus->clk, old_freq);
- goto out;
- }
-
- if (old_freq > new_freq) {
- ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol);
- if (ret < 0) {
- dev_err(bus->dev, "failed to set voltage\n");
- goto out;
- }
- }
- bus->curr_freq = new_freq;
-
- dev_dbg(dev, "Set the frequency of bus (%luHz -> %luHz, %luHz)\n",
- old_freq, new_freq, clk_get_rate(bus->clk));
-out:
mutex_unlock(&bus->lock);
return ret;
@@ -194,10 +162,11 @@ static void exynos_bus_exit(struct device *dev)
if (ret < 0)
dev_warn(dev, "failed to disable the devfreq-event devices\n");
- if (bus->regulator)
- regulator_disable(bus->regulator);
+ if (bus->opp_table)
+ dev_pm_opp_put_regulators(bus->opp_table);
dev_pm_opp_of_remove_table(dev);
+
clk_disable_unprepare(bus->clk);
}
@@ -209,39 +178,26 @@ static int exynos_bus_passive_target(struct device *dev, unsigned long *freq,
{
struct exynos_bus *bus = dev_get_drvdata(dev);
struct dev_pm_opp *new_opp;
- unsigned long old_freq, new_freq;
- int ret = 0;
+ int ret;
- /* Get new opp-bus instance according to new bus clock */
+ /*
+ * New frequency for bus may not be exactly matched to opp, adjust
+ * *freq to correct value.
+ */
new_opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(new_opp)) {
dev_err(dev, "failed to get recommended opp instance\n");
return PTR_ERR(new_opp);
}
- new_freq = dev_pm_opp_get_freq(new_opp);
dev_pm_opp_put(new_opp);
- old_freq = bus->curr_freq;
-
- if (old_freq == new_freq)
- return 0;
-
/* Change the frequency according to new OPP level */
mutex_lock(&bus->lock);
+ ret = dev_pm_opp_set_rate(dev, *freq);
+ if (!ret)
+ bus->curr_freq = *freq;
- ret = clk_set_rate(bus->clk, new_freq);
- if (ret < 0) {
- dev_err(dev, "failed to set the clock of bus\n");
- goto out;
- }
-
- *freq = new_freq;
- bus->curr_freq = new_freq;
-
- dev_dbg(dev, "Set the frequency of bus (%luHz -> %luHz, %luHz)\n",
- old_freq, new_freq, clk_get_rate(bus->clk));
-out:
mutex_unlock(&bus->lock);
return ret;
@@ -259,20 +215,7 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
struct exynos_bus *bus)
{
struct device *dev = bus->dev;
- int i, ret, count, size;
-
- /* Get the regulator to provide each bus with the power */
- bus->regulator = devm_regulator_get(dev, "vdd");
- if (IS_ERR(bus->regulator)) {
- dev_err(dev, "failed to get VDD regulator\n");
- return PTR_ERR(bus->regulator);
- }
-
- ret = regulator_enable(bus->regulator);
- if (ret < 0) {
- dev_err(dev, "failed to enable VDD regulator\n");
- return ret;
- }
+ int i, count, size;
/*
* Get the devfreq-event devices to get the current utilization of
@@ -281,24 +224,20 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
count = devfreq_event_get_edev_count(dev);
if (count < 0) {
dev_err(dev, "failed to get the count of devfreq-event dev\n");
- ret = count;
- goto err_regulator;
+ return count;
}
+
bus->edev_count = count;
size = sizeof(*bus->edev) * count;
bus->edev = devm_kzalloc(dev, size, GFP_KERNEL);
- if (!bus->edev) {
- ret = -ENOMEM;
- goto err_regulator;
- }
+ if (!bus->edev)
+ return -ENOMEM;
for (i = 0; i < count; i++) {
bus->edev[i] = devfreq_event_get_edev_by_phandle(dev, i);
- if (IS_ERR(bus->edev[i])) {
- ret = -EPROBE_DEFER;
- goto err_regulator;
- }
+ if (IS_ERR(bus->edev[i]))
+ return -EPROBE_DEFER;
}
/*
@@ -314,22 +253,15 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
if (of_property_read_u32(np, "exynos,saturation-ratio", &bus->ratio))
bus->ratio = DEFAULT_SATURATION_RATIO;
- if (of_property_read_u32(np, "exynos,voltage-tolerance",
- &bus->voltage_tolerance))
- bus->voltage_tolerance = DEFAULT_VOLTAGE_TOLERANCE;
-
return 0;
-
-err_regulator:
- regulator_disable(bus->regulator);
-
- return ret;
}
static int exynos_bus_parse_of(struct device_node *np,
- struct exynos_bus *bus)
+ struct exynos_bus *bus, bool passive)
{
struct device *dev = bus->dev;
+ struct opp_table *opp_table;
+ const char *vdd = "vdd";
struct dev_pm_opp *opp;
unsigned long rate;
int ret;
@@ -347,11 +279,22 @@ static int exynos_bus_parse_of(struct device_node *np,
return ret;
}
+ if (!passive) {
+ opp_table = dev_pm_opp_set_regulators(dev, &vdd, 1);
+ if (IS_ERR(opp_table)) {
+ ret = PTR_ERR(opp_table);
+ dev_err(dev, "failed to set regulators %d\n", ret);
+ goto err_clk;
+ }
+
+ bus->opp_table = opp_table;
+ }
+
/* Get the freq and voltage from OPP table to scale the bus freq */
ret = dev_pm_opp_of_add_table(dev);
if (ret < 0) {
dev_err(dev, "failed to get OPP table\n");
- goto err_clk;
+ goto err_regulator;
}
rate = clk_get_rate(bus->clk);
@@ -362,6 +305,7 @@ static int exynos_bus_parse_of(struct device_node *np,
ret = PTR_ERR(opp);
goto err_opp;
}
+
bus->curr_freq = dev_pm_opp_get_freq(opp);
dev_pm_opp_put(opp);
@@ -369,6 +313,13 @@ static int exynos_bus_parse_of(struct device_node *np,
err_opp:
dev_pm_opp_of_remove_table(dev);
+
+err_regulator:
+ if (bus->opp_table) {
+ dev_pm_opp_put_regulators(bus->opp_table);
+ bus->opp_table = NULL;
+ }
+
err_clk:
clk_disable_unprepare(bus->clk);
@@ -386,6 +337,7 @@ static int exynos_bus_probe(struct platform_device *pdev)
struct exynos_bus *bus;
int ret, max_state;
unsigned long min_freq, max_freq;
+ bool passive = false;
if (!np) {
dev_err(dev, "failed to find devicetree node\n");
@@ -395,12 +347,18 @@ static int exynos_bus_probe(struct platform_device *pdev)
bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;
+
mutex_init(&bus->lock);
bus->dev = &pdev->dev;
platform_set_drvdata(pdev, bus);
+ node = of_parse_phandle(dev->of_node, "devfreq", 0);
+ if (node) {
+ of_node_put(node);
+ passive = true;
+ }
/* Parse the device-tree to get the resource information */
- ret = exynos_bus_parse_of(np, bus);
+ ret = exynos_bus_parse_of(np, bus, passive);
if (ret < 0)
return ret;
@@ -410,13 +368,10 @@ static int exynos_bus_probe(struct platform_device *pdev)
goto err;
}
- node = of_parse_phandle(dev->of_node, "devfreq", 0);
- if (node) {
- of_node_put(node);
+ if (passive)
goto passive;
- } else {
- ret = exynos_bus_parent_parse_of(np, bus);
- }
+
+ ret = exynos_bus_parent_parse_of(np, bus);
if (ret < 0)
goto err;
@@ -509,6 +464,11 @@ static int exynos_bus_probe(struct platform_device *pdev)
err:
dev_pm_opp_of_remove_table(dev);
+ if (bus->opp_table) {
+ dev_pm_opp_put_regulators(bus->opp_table);
+ bus->opp_table = NULL;
+ }
+
clk_disable_unprepare(bus->clk);
return ret;
--
2.22.0
^ permalink raw reply related
* [PATCH 1/3] opp: core: add regulators enable and disable
From: k.konieczny @ 2019-07-08 14:11 UTC (permalink / raw)
To: k.konieczny
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Krzysztof Kozlowski, Kukjin Kim, Kyungmin Park, Mark Rutland,
MyungJoo Ham, Nishanth Menon, Rob Herring, Stephen Boyd,
Viresh Kumar, devicetree, linux-arm-kernel, linux-kernel,
linux-pm, linux-samsung-soc
In-Reply-To: <20190708141140.24379-1-k.konieczny@partner.samsung.com>
From: Kamil Konieczny <k.konieczny@partner.samsung.com>
Add enable regulators to dev_pm_opp_set_regulators() and disable
regulators to dev_pm_opp_put_regulators(). This prepares for
converting exynos-bus devfreq driver to use dev_pm_opp_set_rate().
Signed-off-by: Kamil Konieczny <k.konieczny@partner.samsung.com>
---
drivers/opp/core.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 0e7703fe733f..947cac452854 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -1580,8 +1580,19 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
if (ret)
goto free_regulators;
+ for (i = 0; i < opp_table->regulator_count; i++) {
+ ret = regulator_enable(opp_table->regulators[i]);
+ if (ret < 0)
+ goto disable;
+ }
+
return opp_table;
+disable:
+ while (i != 0)
+ regulator_disable(opp_table->regulators[--i]);
+
+ i = opp_table->regulator_count;
free_regulators:
while (i != 0)
regulator_put(opp_table->regulators[--i]);
@@ -1609,6 +1620,8 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
/* Make sure there are no concurrent readers while updating opp_table */
WARN_ON(!list_empty(&opp_table->opp_list));
+ for (i = opp_table->regulator_count - 1; i >= 0; i--)
+ regulator_disable(opp_table->regulators[i]);
for (i = opp_table->regulator_count - 1; i >= 0; i--)
regulator_put(opp_table->regulators[i]);
--
2.22.0
^ permalink raw reply related
* [PATCH 0/3] add coupled regulators for Exynos5422/5800
From: k.konieczny @ 2019-07-08 14:11 UTC (permalink / raw)
To: k.konieczny
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Krzysztof Kozlowski, Kukjin Kim, Kyungmin Park, Mark Rutland,
MyungJoo Ham, Nishanth Menon, Rob Herring, Stephen Boyd,
Viresh Kumar, devicetree, linux-arm-kernel, linux-kernel,
linux-pm, linux-samsung-soc
In-Reply-To: <CGME20190708141158eucas1p17d4b50978dbe1e5c876ce6d8f433cc95@eucas1p1.samsung.com>
From: Kamil Konieczny <k.konieczny@partner.samsung.com>
Hi,
The main purpose of this patch series is to add coupled regulators for
Exynos5422/5800 to keep constrain on voltage difference between vdd_arm
and vdd_int to be at most 300mV. In exynos-bus instead of using
regulator_set_voltage_tol() with default voltage tolerance it should be
used regulator_set_voltage_triplet() with volatege range, and this is
already present in opp/core.c code, so it can be reused. While at this,
move setting regulators into opp/core.
This patchset was tested on Odroid XU3.
The last patch depends on two previous.
Regards,
Kamil
Kamil Konieczny (2):
opp: core: add regulators enable and disable
devfreq: exynos-bus: convert to use dev_pm_opp_set_rate()
Marek Szyprowski (1):
ARM: dts: exynos: add initial data for coupled regulators for
Exynos5422/5800
arch/arm/boot/dts/exynos5420.dtsi | 34 ++--
arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 4 +
arch/arm/boot/dts/exynos5800-peach-pi.dts | 4 +
arch/arm/boot/dts/exynos5800.dtsi | 32 ++--
drivers/devfreq/exynos-bus.c | 172 +++++++-----------
drivers/opp/core.c | 13 ++
6 files changed, 120 insertions(+), 139 deletions(-)
--
2.22.0
^ permalink raw reply
* Re: [PATCH 6/6] arm64: dts: renesas: hihope-rzg2-ex: Enable CAN interfaces
From: Geert Uytterhoeven @ 2019-07-08 13:08 UTC (permalink / raw)
To: Fabrizio Castro
Cc: Simon Horman, Geert Uytterhoeven, Rob Herring, Mark Rutland,
Magnus Damm, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Chris Paterson, Biju Das, xu_shunji
In-Reply-To: <1560513214-28031-7-git-send-email-fabrizio.castro@bp.renesas.com>
On Fri, Jun 14, 2019 at 1:54 PM Fabrizio Castro
<fabrizio.castro@bp.renesas.com> wrote:
> This patch enables both CAN0 and CAN1, both exposed via
> connectors found on the expansion board.
>
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Reviewed-by: Chris Paterson <Chris.Paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
I.e. applied and queued for v5.4.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 5/6] arm64: dts: renesas: r8a774a1: Add CANFD support
From: Geert Uytterhoeven @ 2019-07-08 13:01 UTC (permalink / raw)
To: Fabrizio Castro
Cc: Simon Horman, Geert Uytterhoeven, Rob Herring, Mark Rutland,
Magnus Damm, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Chris Paterson, Biju Das
In-Reply-To: <1560513214-28031-6-git-send-email-fabrizio.castro@bp.renesas.com>
On Fri, Jun 14, 2019 at 1:54 PM Fabrizio Castro
<fabrizio.castro@bp.renesas.com> wrote:
> Add CANFD support to the SoC specific dtsi.
>
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Reviewed-by: Chris Paterson <Chris.Paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
I.e. applied and queued for v5.4.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 4/6] arm64: dts: renesas: r8a774a1: Add missing assigned-clocks for CAN[01]
From: Geert Uytterhoeven @ 2019-07-08 12:57 UTC (permalink / raw)
To: Fabrizio Castro
Cc: Simon Horman, Geert Uytterhoeven, Rob Herring, Mark Rutland,
Magnus Damm, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Chris Paterson, Biju Das
In-Reply-To: <1560513214-28031-5-git-send-email-fabrizio.castro@bp.renesas.com>
On Fri, Jun 14, 2019 at 1:54 PM Fabrizio Castro
<fabrizio.castro@bp.renesas.com> wrote:
> Define "assigned-clocks" and "assigned-clock-rates" properties
> for CAN[01] DT nodes, as required by the dt-bindings.
>
> Fixes: eccc40002972 ("arm64: dts: renesas: r8a774a1: Add clkp2 clock to CAN nodes")
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Reviewed-by: Chris Paterson <Chris.Paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
I.e. applied and queued for v5.4.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 3/6] arm64: dts: renesas: r8a774c0: Add missing assigned-clocks for CAN[01]
From: Geert Uytterhoeven @ 2019-07-08 12:57 UTC (permalink / raw)
To: Fabrizio Castro
Cc: Simon Horman, Geert Uytterhoeven, Rob Herring, Mark Rutland,
Magnus Damm, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Chris Paterson, Biju Das
In-Reply-To: <1560513214-28031-4-git-send-email-fabrizio.castro@bp.renesas.com>
On Fri, Jun 14, 2019 at 1:54 PM Fabrizio Castro
<fabrizio.castro@bp.renesas.com> wrote:
> Define "assigned-clocks" and "assigned-clock-rates" properties
> for CAN[01] DT nodes, as required by the dt-bindings.
>
> Fixes: 036bc85c1d06 ("arm64: dts: renesas: r8a774c0: Add clkp2 clock to CAN nodes")
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Reviewed-by: Chris Paterson <Chris.Paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
I.e. applied and queued for v5.4.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 2/6] dt-bindings: can: rcar_can: Complete documentation for RZ/G2[EM]
From: Geert Uytterhoeven @ 2019-07-08 12:56 UTC (permalink / raw)
To: Fabrizio Castro
Cc: Simon Horman, Geert Uytterhoeven, Wolfgang Grandegger,
Marc Kleine-Budde, Rob Herring, Mark Rutland, David S. Miller,
linux-can, netdev,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Chris Paterson, Biju Das, Linux-Renesas
In-Reply-To: <1560513214-28031-3-git-send-email-fabrizio.castro@bp.renesas.com>
On Fri, Jun 14, 2019 at 1:53 PM Fabrizio Castro
<fabrizio.castro@bp.renesas.com> wrote:
> Add missing RZ/G2[EM] CANFD clock specific documentation.
>
> Fixes: 868b7c0f43e6 ("dt-bindings: can: rcar_can: Add r8a774a1 support")
> Fixes: 0c11e7d0f51c ("dt-bindings: can: rcar_can: Add r8a774c0 support")
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> Reviewed-by: Chris Paterson <Chris.Paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
I.e. applied and queued for v5.4.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 1/6] dt-bindings: can: rcar_canfd: document r8a774a1 support
From: Geert Uytterhoeven @ 2019-07-08 12:55 UTC (permalink / raw)
To: Simon Horman
Cc: Fabrizio Castro, David S. Miller, Geert Uytterhoeven,
Wolfgang Grandegger, Marc Kleine-Budde, Rob Herring, Mark Rutland,
linux-can, netdev,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Chris Paterson, Biju Das, Linux-Renesas
In-Reply-To: <20190617093023.nhvrvujg52gcglko@verge.net.au>
On Mon, Jun 17, 2019 at 11:30 AM Simon Horman <horms@verge.net.au> wrote:
> On Fri, Jun 14, 2019 at 12:53:29PM +0100, Fabrizio Castro wrote:
> > Document the support for rcar_canfd on R8A774A1 SoC devices.
> >
> > Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>
> > Reviewed-by: Chris Paterson <Chris.Paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
> > ---
> > Hello Simon,
> >
> > I think it would make more sense if this patch went through you as it
> > sits on series:
> > https://lkml.org/lkml/2019/5/9/941
> >
> > Do you think that's doable?
>
> That seems reasonable to me.
>
> Dave are you happy with me taking this, and 2/6, through
> the renesas tree?
As the previous change to this file was acked by Dave, and went in through
the Renesas tree, have I applied this patch and patch 2/6, and queued
it for v5.4.
Thanks!
> > Documentation/devicetree/bindings/net/can/rcar_canfd.txt | 9 +++++----
> > 1 file changed, 5 insertions(+), 4 deletions(-)
> >
> > diff --git a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt
> > index 32f051f..00afaff 100644
> > --- a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt
> > +++ b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt
> > @@ -4,6 +4,7 @@ Renesas R-Car CAN FD controller Device Tree Bindings
> > Required properties:
> > - compatible: Must contain one or more of the following:
> > - "renesas,rcar-gen3-canfd" for R-Car Gen3 and RZ/G2 compatible controllers.
> > + - "renesas,r8a774a1-canfd" for R8A774A1 (RZ/G2M) compatible controller.
> > - "renesas,r8a774c0-canfd" for R8A774C0 (RZ/G2E) compatible controller.
> > - "renesas,r8a7795-canfd" for R8A7795 (R-Car H3) compatible controller.
> > - "renesas,r8a7796-canfd" for R8A7796 (R-Car M3-W) compatible controller.
> > @@ -32,10 +33,10 @@ enable/disable the respective channel.
> > Required properties for "renesas,r8a774c0-canfd", "renesas,r8a7795-canfd",
> > "renesas,r8a7796-canfd", "renesas,r8a77965-canfd", and "renesas,r8a77990-canfd"
> > compatible:
> > -In R8A774C0, R8A7795, R8A7796, R8A77965, and R8A77990 SoCs, canfd clock is a
> > -div6 clock and can be used by both CAN and CAN FD controller at the same time.
> > -It needs to be scaled to maximum frequency if any of these controllers use it.
> > -This is done using the below properties:
> > +In R8A774A1, R8A774C0, R8A7795, R8A7796, R8A77965, and R8A77990 SoCs, canfd
> > +clock is a div6 clock and can be used by both CAN and CAN FD controller at the
> > +same time. It needs to be scaled to maximum frequency if any of these
> > +controllers use it. This is done using the below properties:
> >
> > - assigned-clocks: phandle of canfd clock.
> > - assigned-clock-rates: maximum frequency of this clock.
Gr{oetje,eeting}s,
Geert
^ permalink raw reply
* RE: [PATCH v9 5/6] usb:cdns3 Add Cadence USB3 DRD Driver
From: Pawel Laszczak @ 2019-07-08 12:39 UTC (permalink / raw)
To: Felipe Balbi, devicetree@vger.kernel.org
Cc: gregkh@linuxfoundation.org, linux-usb@vger.kernel.org,
hdegoede@redhat.com, heikki.krogerus@linux.intel.com,
robh+dt@kernel.org, rogerq@ti.com, linux-kernel@vger.kernel.org,
jbergsagel@ti.com, nsekhar@ti.com, nm@ti.com, Suresh Punnoose,
peter.chen@nxp.com, Jayshri Dajiram Pawar, Rahul Kumar
In-Reply-To: <874l3whh4g.fsf@linux.intel.com>
>
>(please send it as a formal patch from now on)
I will send it. I think even today.
>
>Hi,
>
>Pawel Laszczak <pawell@cadence.com> writes:
>> The prototype was added , but not all text was past. Sorry for that.
>>
>> Fixed version: with your changes.
>>
>> ///// v2 corrected
>> commit 4553d48dd0500085d90c8280e28714072279c3dd (HEAD)
>> Author: Pawel Laszczak <pawell@cadence.com>
>> Date: Mon Jul 8 12:53:47 2019 +0200
>>
>> usb: common: Add usb_get_dr_mode_from_string and usb_dr_mode_to_string.
>>
>> Patch introduces new function usb_dr_mode_to_string for converting dual role
>> mod to string and removes static from usb_dr_mode_to_string definition.
>>
>> Both changes have made to avoid duplication of code by cdns3 driver.
>
>missing Signed-off-by
>
>> diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
>> index 69f1b6328532..c156817672c4 100644
>> --- a/include/linux/usb/otg.h
>> +++ b/include/linux/usb/otg.h
>> @@ -129,4 +129,20 @@ enum usb_dr_mode {
>> */
>> extern enum usb_dr_mode usb_get_dr_mode(struct device *dev);
>>
>> +/**
>> + * usb_get_dr_mode_from_string - Convert string to dual role mode.
>> + * @str: Pointer to the given string
>> + *
>> + * The function gets string and returns the correspondig enum usb_dr_mode.
>> + */
>
>why duplicate the documentation? Perhaps put it only in the header,
>otherwise we will need to update two locations whenever these functions
>change.
Ok, I've removed it from drviers/usb/common/common.c.
>
>> +extern enum usb_dr_mode usb_get_dr_mode_from_string(const char *str);
>> +
>> +/**
>> + * usb_dr_mode_to_string - Convert dual role mode to string.
>> + * @dr_mode: Pointer to the given dual role mode
>> + *
>> + * The function gets enum usb_dr_mode, and returns the correspondig string.
>> + */
>> +extern const char *usb_dr_mode_to_string(const enum usb_dr_mode dr_mode);
>
>Don't you need a stub for builds when CONFIG_USB_COMMON=n?
>
We don't need it. It will be enabled when USB_GADGET or USB is enabled.
It should cover all cases.
menuconfig USB_GADGET
tristate "USB Gadget Support"
select USB_COMMON
config USB
tristate "Support for Host-side USB"
depends on USB_ARCH_HAS_HCD
select USB_COMMON
Pawel
>--
>balbi
^ permalink raw reply
* Re: [RFC 0/2] Add workaround for core wake-up on IPI for i.MX8MQ
From: Martin Kepplinger @ 2019-07-08 12:20 UTC (permalink / raw)
To: Abel Vesa
Cc: Mark Rutland, devicetree@vger.kernel.org, Lorenzo Pieralisi,
Jacky Bai, Carlo Caione, Marc Zyngier, Fabio Estevam,
Sascha Hauer, linux-kernel@vger.kernel.org, Rob Herring,
dl-linux-imx, Pengutronix Kernel Team, Abel Vesa, Thomas Gleixner,
Leonard Crestez, Shawn Guo, linux-arm-kernel@lists.infradead.org,
Lucas Stach
In-Reply-To: <9bf3c0d4-7a15-90a0-fbe9-336b855faf81@posteo.de>
On 08.07.19 09:54, Martin Kepplinger wrote:
> On 02.07.19 13:33, Abel Vesa wrote:
>> On 19-07-02 08:47:19, Martin Kepplinger wrote:
>>> On 28.06.19 10:54, Abel Vesa wrote:
>>>> On 19-06-23 13:47:26, Martin Kepplinger wrote:
>>>>> On 10.06.19 14:13, Abel Vesa wrote:
>>>>>> This is another alternative for the RFC:
>>>>>> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flkml.org%2Flkml%2F2019%2F3%2F27%2F545&data=02%7C01%7Cabel.vesa%40nxp.com%7Ccfc582f9977d479b7dda08d6feb9258a%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C636976468485275045&sdata=L%2Byn29%2FBS3KMjm9eCPBTZBTl30PmZywSjIj11bMQw5c%3D&reserved=0
>>>>>>
>>>>>> This new workaround proposal is a little bit more hacky but more contained
>>>>>> since everything is done within the irq-imx-gpcv2 driver.
>>>>>>
>>>>>> Basically, it 'hijacks' the registered gic_raise_softirq __smp_cross_call
>>>>>> handler and registers instead a wrapper which calls in the 'hijacked'
>>>>>> handler, after that calling into EL3 which will take care of the actual
>>>>>> wake up. This time, instead of expanding the PSCI ABI, we use a new vendor SIP.
>>>>>>
>>>>>> I also have the patches ready for TF-A but I'll hold on to them until I see if
>>>>>> this has a chance of getting in.
>>>>>
>>>>> Let's leave out of the picture for now, how generally applicable and
>>>>> mergable your changes are. I'd like to reproduce what you do and test
>>>>> cpuidle on imx8mq:
>>>>>
>>>>> When applying your changes here and the corresponding ATF changes (
>>>>> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fabelvesa%2Farm-trusted-firmware%2Ftree%2Fimx8mq-err11171&data=02%7C01%7Cabel.vesa%40nxp.com%7Ccfc582f9977d479b7dda08d6feb9258a%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C636976468485275045&sdata=VT3duSl70DNxcY8Ev4FFrHlWoOjkcckeM8BgxrSkr8A%3D&reserved=0 if
>>>>> I got that right) I don't yet see any difference in the SoC heating up
>>>>> under zero load. __cpu_do_idle() is called about every 1ms (without your
>>>>> changes, that was even more often but I'm not yet sure if that means
>>>>> anything).
>>>>
>>>> You will most probably not see any change in the SoC temp since the cpuidle
>>>> only touches the A53s. There are way many more IPs in the SoC that could
>>>> heat it up. If you want some real numbers you'll have to measure the power
>>>> consumtion on VDD_ARM rail. If you don't want to go through that much trouble
>>>> you can use the idlestat tool to measure the times each A53 speends in cpu-sleep
>>>> state.
>>>>
>>>>>
>>>>> What I also see is that I get about 10x more "arch_timer" (int.3, GICv3)
>>>>> interrupts than without your changes.
>>>
>>>
>>> thanks for getting back at me here. This is run on the imx8mq
>>> librem5-devkit with your wakeup-workaround applied. Typical measurements
>>> under zero load look like this:
>>>
>>> sudo idlestat --trace -f /tmp/mytrace -t 10 -p -c -w
>>> Log is 10.000395 secs long with 31194 events
>>> ------------------------------------------------------------------------
>>> | C-state | min | max | avg | total | hits | over | under |
>>> ------------------------------------------------------------------------
>>> | clusterA |
>>> ------------------------------------------------------------------------
>>> | WFI | 14us | 3.99ms | 3.90ms | 9.93s | 2543 | 0 | 0 |
>>> ------------------------------------------------------------------------
>>> | cpu0 |
>>> ------------------------------------------------------------------------
>>> | WFI | 14us | 3.99ms | 3.89ms | 9.96s | 2561 | 0 | 0 |
>>> ------------------------------------------------------------------------
>>> ...
>>>
>>
>> I don't see the cpu-sleep state at all in your idlestat log. Maybe the cpuidle
>> isn't enabled. Or probably the workaround itself is not applied. You'll have
>> to look into that.
>>
>> Here is how it looks like with the workaround enabled:
>>
>> Log is 10.001685 secs long with 1175 events
>> --------------------------------------------------------------------------------
>> | C-state | min | max | avg | total | hits | over | under |
>> --------------------------------------------------------------------------------
>> | clusterA |
>> --------------------------------------------------------------------------------
>> | WFI | 2us | 50.04ms | 29.63ms | 9.99s | 337 | 0 | 0 |
>> --------------------------------------------------------------------------------
>> | cpu0 |
>> --------------------------------------------------------------------------------
>> | WFI | 11us | 50.04ms | 40.44ms | 9.62s | 238 | 0 | 219 |
>> | cpu-sleep | 537us | 50.58ms | 14.11ms | 366.94ms | 26 | 7 | 0 |
>> --------------------------------------------------------------------------------
>> | cpu1 |
>> --------------------------------------------------------------------------------
>> | WFI | 11us | 539.04ms | 93.20ms | 5.78s | 62 | 0 | 38 |
>> | cpu-sleep | 536us | 607.90ms | 183.38ms | 4.22s | 23 | 12 | 0 |
>> --------------------------------------------------------------------------------
>> | cpu2 |
>> --------------------------------------------------------------------------------
>> | WFI | 41us | 265.99ms | 17.51ms | 332.66ms | 19 | 0 | 11 |
>> | cpu-sleep | 568us | 6.56s | 1.38s | 9.67s | 7 | 2 | 0 |
>> --------------------------------------------------------------------------------
>> | cpu3 |
>> --------------------------------------------------------------------------------
>> | WFI | 7.94ms | 881.50ms | 367.81ms | 1.10s | 3 | 0 | 3 |
>> | cpu-sleep | 549us | 2.02s | 808.72ms | 8.90s | 11 | 1 | 0 |
>> --------------------------------------------------------------------------------
>>
>> You can see that the cpu2 was once for 6.56 seconds (out of 10s) in cpu-sleep.
>>
>
> So I run this ATF tree
> https://github.com/abelvesa/arm-trusted-firmware/tree/imx8mq-err11171
> and, on top of "v5.2-rc7" I have your commits
> ("irqchip: irq-imx-gpcv2: Add workaround for i.MX8MQ ERR11171") and
> ("arm64: dts: imx8mq: Add idle states and gpcv2 wake_request broken
> property") applied.
>
> Then simply enabled CONFIG_ARM_CPUIDLE.
>
> (I also use the "imx-cpufreq-dt" driver, but this should be unrelated here).
>
> I do see the possible cpuidle states:
> /sys/devices/system/cpu/cpu0/cpuidle$ cat state*/name
>
> WFI
>
> cpu-sleep
>
> but idlestat doesn't see it or it is (thus) never used. Do you know a
> needed change I might be missing?
>
looks like I mess things up myself here and somehow prevent
cpu_pm_enter() from being called...
^ permalink raw reply
* RE: [PATCH v9 5/6] usb:cdns3 Add Cadence USB3 DRD Driver
From: Felipe Balbi @ 2019-07-08 11:59 UTC (permalink / raw)
To: Pawel Laszczak, devicetree@vger.kernel.org
Cc: gregkh@linuxfoundation.org, linux-usb@vger.kernel.org,
hdegoede@redhat.com, heikki.krogerus@linux.intel.com,
robh+dt@kernel.org, rogerq@ti.com, linux-kernel@vger.kernel.org,
jbergsagel@ti.com, nsekhar@ti.com, nm@ti.com, Suresh Punnoose,
peter.chen@nxp.com, Jayshri Dajiram Pawar, Rahul Kumar
In-Reply-To: <BYAPR07MB47097C3C2A2BBA2AA63C65DEDDF60@BYAPR07MB4709.namprd07.prod.outlook.com>
[-- Attachment #1: Type: text/plain, Size: 1862 bytes --]
(please send it as a formal patch from now on)
Hi,
Pawel Laszczak <pawell@cadence.com> writes:
> The prototype was added , but not all text was past. Sorry for that.
>
> Fixed version: with your changes.
>
> ///// v2 corrected
> commit 4553d48dd0500085d90c8280e28714072279c3dd (HEAD)
> Author: Pawel Laszczak <pawell@cadence.com>
> Date: Mon Jul 8 12:53:47 2019 +0200
>
> usb: common: Add usb_get_dr_mode_from_string and usb_dr_mode_to_string.
>
> Patch introduces new function usb_dr_mode_to_string for converting dual role
> mod to string and removes static from usb_dr_mode_to_string definition.
>
> Both changes have made to avoid duplication of code by cdns3 driver.
missing Signed-off-by
> diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
> index 69f1b6328532..c156817672c4 100644
> --- a/include/linux/usb/otg.h
> +++ b/include/linux/usb/otg.h
> @@ -129,4 +129,20 @@ enum usb_dr_mode {
> */
> extern enum usb_dr_mode usb_get_dr_mode(struct device *dev);
>
> +/**
> + * usb_get_dr_mode_from_string - Convert string to dual role mode.
> + * @str: Pointer to the given string
> + *
> + * The function gets string and returns the correspondig enum usb_dr_mode.
> + */
why duplicate the documentation? Perhaps put it only in the header,
otherwise we will need to update two locations whenever these functions
change.
> +extern enum usb_dr_mode usb_get_dr_mode_from_string(const char *str);
> +
> +/**
> + * usb_dr_mode_to_string - Convert dual role mode to string.
> + * @dr_mode: Pointer to the given dual role mode
> + *
> + * The function gets enum usb_dr_mode, and returns the correspondig string.
> + */
> +extern const char *usb_dr_mode_to_string(const enum usb_dr_mode dr_mode);
Don't you need a stub for builds when CONFIG_USB_COMMON=n?
--
balbi
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]
^ permalink raw reply
* Re: [PATCH v2] dt-binding: mmc: rename tmio_mmc.txt to renesas,sdhi.txt
From: Ulf Hansson @ 2019-07-08 11:55 UTC (permalink / raw)
To: Masahiro Yamada
Cc: linux-mmc@vger.kernel.org, Simon Horman, Wolfram Sang,
Linux-Renesas, DTML, Linux Kernel Mailing List, Rob Herring,
Mark Rutland
In-Reply-To: <20190624070345.20373-1-yamada.masahiro@socionext.com>
On Mon, 24 Jun 2019 at 09:04, Masahiro Yamada
<yamada.masahiro@socionext.com> wrote:
>
> As commit b6147490e6aa ("mmc: tmio: split core functionality, DMA and
> MFD glue") said, these MMC controllers use the IP from Panasonic.
>
> TMIO (Toshiba Mobile IO) MMC was the first upstreamed user of this IP.
> The common driver code was split and expanded as 'tmio-mmc-core', then
> it became historical misnomer since 'tmio' is not the name of this IP.
>
> In the discussion [1], we decide to keep this name as-is at least in
> Linux driver level because renaming everything is a big churn.
>
> However, DT should not be oriented to a particular project even though
> it is mainly developed in Linux communities.
>
> This is the misfortune only in Linux. Let's stop exporting it to other
> projects, where there is no good reason to call this hardware "TMIO".
> Rename the file to renesas,sdhi.txt. In fact, all the information in
> this file is specific to the Renesas platform.
>
> This commit also removes the first paragraph entirely. The DT-binding
> should describe the hardware. It is strange to talk about Linux driver
> internals such as how the drivers are probed, how platform data are
> handed off, etc.
>
> [1] https://www.spinics.net/lists/linux-mmc/msg46952.html
>
> Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Applied for next, thanks!
Kind regards
Uffe
> ---
>
> Changes in v2:
> - Rename to renesas,sdhi.txt instead of renesas_sdhi.txt
>
> .../bindings/mmc/{tmio_mmc.txt => renesas,sdhi.txt} | 11 +----------
> 1 file changed, 1 insertion(+), 10 deletions(-)
> rename Documentation/devicetree/bindings/mmc/{tmio_mmc.txt => renesas,sdhi.txt} (87%)
>
> diff --git a/Documentation/devicetree/bindings/mmc/tmio_mmc.txt b/Documentation/devicetree/bindings/mmc/renesas,sdhi.txt
> similarity index 87%
> rename from Documentation/devicetree/bindings/mmc/tmio_mmc.txt
> rename to Documentation/devicetree/bindings/mmc/renesas,sdhi.txt
> index 2b4f17ca9087..dd08d038a65c 100644
> --- a/Documentation/devicetree/bindings/mmc/tmio_mmc.txt
> +++ b/Documentation/devicetree/bindings/mmc/renesas,sdhi.txt
> @@ -1,13 +1,4 @@
> -* Toshiba Mobile IO SD/MMC controller
> -
> -The tmio-mmc driver doesn't probe its devices actively, instead its binding to
> -devices is managed by either MFD drivers or by the sh_mobile_sdhi platform
> -driver. Those drivers supply the tmio-mmc driver with platform data, that either
> -describe hardware capabilities, known to them, or are obtained by them from
> -their own platform data or from their DT information. In the latter case all
> -compulsory and any optional properties, common to all SD/MMC drivers, as
> -described in mmc.txt, can be used. Additionally the following tmio_mmc-specific
> -optional bindings can be used.
> +* Renesas SDHI SD/MMC controller
>
> Required properties:
> - compatible: should contain one or more of the following:
> --
> 2.17.1
>
^ permalink raw reply
* Re: [PATCH 0/3] Optimize voltage switch for the SD controller
From: Ulf Hansson @ 2019-07-08 11:55 UTC (permalink / raw)
To: Baolin Wang
Cc: Adrian Hunter, Chunyan Zhang, Orson Zhai, Rob Herring,
Mark Rutland, Vincent Guittot, linux-mmc@vger.kernel.org,
Linux Kernel Mailing List, DTML
In-Reply-To: <cover.1561094029.git.baolin.wang@linaro.org>
On Fri, 21 Jun 2019 at 08:12, Baolin Wang <baolin.wang@linaro.org> wrote:
>
> This patch set is used to optimize voltage switch for the
> Spreadtrum SD host controller.
>
> Any comments are welcome. Thanks.
>
> Baolin Wang (3):
> mmc: sdhci-sprd: Add start_signal_voltage_switch ops
> dt-bindings: mmc: sprd: Add pinctrl support
> mmc: sdhci-sprd: Add pin control support for voltage switch
>
> .../devicetree/bindings/mmc/sdhci-sprd.txt | 7 ++
> drivers/mmc/host/sdhci-sprd.c | 78 ++++++++++++++++++++
> 2 files changed, 85 insertions(+)
>
> --
> 1.7.9.5
>
Applied for next, thanks!
Kind regards
Uffe
^ permalink raw reply
* RE: [PATCH v9 5/6] usb:cdns3 Add Cadence USB3 DRD Driver
From: Pawel Laszczak @ 2019-07-08 11:45 UTC (permalink / raw)
To: Felipe Balbi, devicetree@vger.kernel.org
Cc: gregkh@linuxfoundation.org, linux-usb@vger.kernel.org,
hdegoede@redhat.com, heikki.krogerus@linux.intel.com,
robh+dt@kernel.org, rogerq@ti.com, linux-kernel@vger.kernel.org,
jbergsagel@ti.com, nsekhar@ti.com, nm@ti.com, Suresh Punnoose,
peter.chen@nxp.com, Jayshri Dajiram Pawar, Rahul Kumar
In-Reply-To: <874l3wn5ep.fsf@linux.intel.com>
>
>Hi,
>
>Pawel Laszczak <pawell@cadence.com> writes:
>> Please look at the changes in drivers/usb/common/common.c file.
>> I'm going to add it as next patch to next version.
>>
>>
>>>Pawel Laszczak <pawell@cadence.com> writes:
>>>>>> +void cdns3_role_stop(struct cdns3 *cdns)
>>>
>>>>>> +static const char *const cdns3_mode[] = {
>>>>>> + [USB_DR_MODE_UNKNOWN] = "unknown",
>>>>>> + [USB_DR_MODE_OTG] = "otg",
>>>>>> + [USB_DR_MODE_HOST] = "host",
>>>>>> + [USB_DR_MODE_PERIPHERAL] = "device",
>>>>>> +};
>>>>>
>>>>>don't we have a generic version of this under usb/common ?
>>>>>
>>>> Yes, there is a similar array, but it is defined also as static
>>>> and there is no function for converting value to string.
>>>> There is only function for converting string to value.
>>>
>>>right. You can add the missing interface generically instead of
>>>duplicating the enumeration.
>>>
>>>> There is also:
>>>> [USB_DR_MODE_UNKNOWN] = "",
>>>> Instead of:
>>>> [USB_DR_MODE_UNKNOWN] = "unknown",
>>>>
>>>> So, for USB_DR_MODE_UNKNOWN user will not see anything information.
>>>
>>>But that's what "unknown" means :-) We don't know the information.
>>>
>>
>> ////// start
>> commit 607754c60fabc43408f4f2de82d3560c72870787 (HEAD)
>> Author: Pawel Laszczak <pawell@cadence.com>
>> Date: Mon Jul 8 12:53:47 2019 +0200
>>
>> usb:common Added usb_get_dr_mode_from_string and usb_dr_mode_to_string.
>
>This would read better as:
>
> usb: common: Add usb_get_dr_mode_from_string and usb_dr_mode_to_string.
>
>>
>> Patch introduces new function usb_dr_mode_to_string for converting dual role
>> mod to string and removes static from usb_dr_mode_to_string definition.
>>
>> Both changes have made to avoid duplication of code by cdns3 driver.
>>
>> diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
>> index 18f5dcf58b0d..ab59954deff8 100644
>> --- a/drivers/usb/common/common.c
>> +++ b/drivers/usb/common/common.c
>> @@ -118,13 +118,20 @@ static const char *const usb_dr_modes[] = {
>> [USB_DR_MODE_OTG] = "otg",
>> };
>>
>> -static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
>> +/**
>> + * usb_get_dr_mode_from_string - Convert string to dual role mode.
>> + * @str: Pointer to the given string
>> + *
>> + * The function gets string and returns the correspondig enum usb_dr_mode.
>> + */
>> +enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
>> {
>> int ret;
>>
>> ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str);
>> return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret;
>> }
>> +EXPORT_SYMBOL_GPL(usb_get_dr_mode_from_string);
>>
>> enum usb_dr_mode usb_get_dr_mode(struct device *dev)
>> {
>> @@ -139,6 +146,21 @@ enum usb_dr_mode usb_get_dr_mode(struct device *dev)
>> }
>> EXPORT_SYMBOL_GPL(usb_get_dr_mode);
>>
>> +/**
>> + * usb_dr_mode_to_string - Convert dual role mode to stringi.
>
>s/stringi/string
>
>> + * @dr_mode: Pointer to the given dual role mode
>> + *
>> + * The function gets enum usb_dr_mode, and returns the correspondig string.
>> + */
>> +const *char usb_dr_mode_to_string(const enum usb_dr_mode dr_mode)
>> +{
>> + if (dr_mode > USB_DR_MODE_UNKNOWN || dr_mode <= USB_DR_MODE_PERIPHERAL)
>> + return usb_dr_modes[dr_mode];
>> +
>> + return usb_dr_modes[USB_DR_MODE_UNKNOWN];
>> +}
>> +EXPORT_SYMBOL_GPL(usb_dr_mode_to_string);
>
>missing prototype in a header file.
>
The prototype was added , but not all text was past. Sorry for that.
Fixed version: with your changes.
///// v2 corrected
commit 4553d48dd0500085d90c8280e28714072279c3dd (HEAD)
Author: Pawel Laszczak <pawell@cadence.com>
Date: Mon Jul 8 12:53:47 2019 +0200
usb: common: Add usb_get_dr_mode_from_string and usb_dr_mode_to_string.
Patch introduces new function usb_dr_mode_to_string for converting dual role
mod to string and removes static from usb_dr_mode_to_string definition.
Both changes have made to avoid duplication of code by cdns3 driver.
diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
index 18f5dcf58b0d..76634b8e92db 100644
--- a/drivers/usb/common/common.c
+++ b/drivers/usb/common/common.c
@@ -118,13 +118,20 @@ static const char *const usb_dr_modes[] = {
[USB_DR_MODE_OTG] = "otg",
};
-static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
+/**
+ * usb_get_dr_mode_from_string - Convert string to dual role mode.
+ * @str: Pointer to the given string
+ *
+ * The function gets string and returns the correspondig enum usb_dr_mode.
+ */
+enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
{
int ret;
ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str);
return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret;
}
+EXPORT_SYMBOL_GPL(usb_get_dr_mode_from_string);
enum usb_dr_mode usb_get_dr_mode(struct device *dev)
{
@@ -139,6 +146,21 @@ enum usb_dr_mode usb_get_dr_mode(struct device *dev)
}
EXPORT_SYMBOL_GPL(usb_get_dr_mode);
+/**
+ * usb_dr_mode_to_string - Convert dual role mode to string.
+ * @dr_mode: Pointer to the given dual role mode
+ *
+ * The function gets enum usb_dr_mode, and returns the correspondig string.
+ */
+const char *usb_dr_mode_to_string(const enum usb_dr_mode dr_mode)
+{
+ if (dr_mode > USB_DR_MODE_UNKNOWN || dr_mode <= USB_DR_MODE_PERIPHERAL)
+ return usb_dr_modes[dr_mode];
+
+ return usb_dr_modes[USB_DR_MODE_UNKNOWN];
+}
+EXPORT_SYMBOL_GPL(usb_dr_mode_to_string);
+
#ifdef CONFIG_OF
/**
* of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index 69f1b6328532..c156817672c4 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -129,4 +129,20 @@ enum usb_dr_mode {
*/
extern enum usb_dr_mode usb_get_dr_mode(struct device *dev);
+/**
+ * usb_get_dr_mode_from_string - Convert string to dual role mode.
+ * @str: Pointer to the given string
+ *
+ * The function gets string and returns the correspondig enum usb_dr_mode.
+ */
+extern enum usb_dr_mode usb_get_dr_mode_from_string(const char *str);
+
+/**
+ * usb_dr_mode_to_string - Convert dual role mode to string.
+ * @dr_mode: Pointer to the given dual role mode
+ *
+ * The function gets enum usb_dr_mode, and returns the correspondig string.
+ */
+extern const char *usb_dr_mode_to_string(const enum usb_dr_mode dr_mode);
+
#endif /* __LINUX_USB_OTG_H */
////////////////
>--
>balbi
^ permalink raw reply related
* Re: [PATCHv6 00/28] PCI: mobiveil: fixes for Mobiveil PCIe Host Bridge IP driver
From: Lorenzo Pieralisi @ 2019-07-08 11:35 UTC (permalink / raw)
To: Hou Zhiqiang
Cc: linux-pci, linux-arm-kernel, devicetree, linux-kernel, bhelgaas,
robh+dt, mark.rutland, l.subrahmanya, shawnguo, leoyang.li,
catalin.marinas, will.deacon, Mingkai.Hu, Minghuan.Lian,
Xiaowei.Bao
In-Reply-To: <20190705095656.19191-1-Zhiqiang.Hou@nxp.com>
On Fri, Jul 05, 2019 at 05:56:28PM +0800, Hou Zhiqiang wrote:
> This patch set is to add fixes for Mobiveil PCIe Host driver.
> Splited #2, #3, #9 and #10 of v5 patches.
>
> Hou Zhiqiang (28):
> PCI: mobiveil: Unify register accessors
> PCI: mobiveil: Remove the flag MSI_FLAG_MULTI_PCI_MSI
> PCI: mobiveil: Fix PCI base address in MEM/IO outbound windows
> PCI: mobiveil: Update the resource list traversal function
> PCI: mobiveil: Use WIN_NUM_0 explicitly for CFG outbound window
> PCI: mobiveil: Use the 1st inbound window for MEM inbound
> transactions
> PCI: mobiveil: Fix the Class Code field
> PCI: mobiveil: Move the link up waiting out of mobiveil_host_init()
> PCI: mobiveil: Move IRQ chained handler setup out of DT parse
> PCI: mobiveil: Initialize Primary/Secondary/Subordinate bus numbers
> PCI: mobiveil: Fix devfn check in mobiveil_pcie_valid_device()
> dt-bindings: PCI: mobiveil: Change gpio_slave and apb_csr to optional
> PCI: mobiveil: Reformat the code for readability
> PCI: mobiveil: Make the register updating more readable
> PCI: mobiveil: Revise the MEM/IO outbound window initialization
> PCI: mobiveil: Fix the returned error number
> PCI: mobiveil: Remove an unnecessary return value check
> PCI: mobiveil: Remove redundant var definitions and register read
> operations
> PCI: mobiveil: Fix the valid check for inbound and outbound window
> PCI: mobiveil: Add the statistic of initialized inbound windows
> PCI: mobiveil: Clear the target fields before updating the register
> PCI: mobiveil: Mask out the lower 10-bit hardcode window size
> PCI: mobiveil: Add upper 32-bit CPU base address setup in outbound
> window
> PCI: mobiveil: Add upper 32-bit PCI base address setup in inbound
> window
> PCI: mobiveil: Fix the CPU base address setup in inbound window
> PCI: mobiveil: Move PCIe PIO enablement out of inbound window routine
> PCI: mobiveil: Fix infinite-loop in the INTx process
> PCI: mobiveil: Fix the potential INTx missing problem
>
> .../devicetree/bindings/pci/mobiveil-pcie.txt | 2 +
> drivers/pci/controller/pcie-mobiveil.c | 529 ++++++++++++--------
> 2 files changed, 318 insertions(+), 213 deletions(-)
>
OK, I rewrote most of commit logs, dropped patch 25 since I do not
understand the commit log, pushed to pci/mobiveil tentatively for
v5.3.
Having said that, you should improve commit logs writing it took
me too much time to check them all and rewrite them.
Never ever again post a massive series like this mixing refactoring
fixes and clean-ups it was painful to review/rebase, please split
patch series into small chunks to make my life much easier.
Please check my pci/mobiveil branch and report back if something
is not in order.
Lorenzo
^ permalink raw reply
* RE: [PATCH v9 5/6] usb:cdns3 Add Cadence USB3 DRD Driver
From: Felipe Balbi @ 2019-07-08 11:15 UTC (permalink / raw)
To: Pawel Laszczak, devicetree@vger.kernel.org
Cc: gregkh@linuxfoundation.org, linux-usb@vger.kernel.org,
hdegoede@redhat.com, heikki.krogerus@linux.intel.com,
robh+dt@kernel.org, rogerq@ti.com, linux-kernel@vger.kernel.org,
jbergsagel@ti.com, nsekhar@ti.com, nm@ti.com, Suresh Punnoose,
peter.chen@nxp.com, Jayshri Dajiram Pawar, Rahul Kumar
In-Reply-To: <BYAPR07MB4709E5BA041BBDDB70E69F62DDF60@BYAPR07MB4709.namprd07.prod.outlook.com>
[-- Attachment #1: Type: text/plain, Size: 3465 bytes --]
Hi,
Pawel Laszczak <pawell@cadence.com> writes:
> Please look at the changes in drivers/usb/common/common.c file.
> I'm going to add it as next patch to next version.
>
>
>>Pawel Laszczak <pawell@cadence.com> writes:
>>>>> +void cdns3_role_stop(struct cdns3 *cdns)
>>
>>>>> +static const char *const cdns3_mode[] = {
>>>>> + [USB_DR_MODE_UNKNOWN] = "unknown",
>>>>> + [USB_DR_MODE_OTG] = "otg",
>>>>> + [USB_DR_MODE_HOST] = "host",
>>>>> + [USB_DR_MODE_PERIPHERAL] = "device",
>>>>> +};
>>>>
>>>>don't we have a generic version of this under usb/common ?
>>>>
>>> Yes, there is a similar array, but it is defined also as static
>>> and there is no function for converting value to string.
>>> There is only function for converting string to value.
>>
>>right. You can add the missing interface generically instead of
>>duplicating the enumeration.
>>
>>> There is also:
>>> [USB_DR_MODE_UNKNOWN] = "",
>>> Instead of:
>>> [USB_DR_MODE_UNKNOWN] = "unknown",
>>>
>>> So, for USB_DR_MODE_UNKNOWN user will not see anything information.
>>
>>But that's what "unknown" means :-) We don't know the information.
>>
>
> ////// start
> commit 607754c60fabc43408f4f2de82d3560c72870787 (HEAD)
> Author: Pawel Laszczak <pawell@cadence.com>
> Date: Mon Jul 8 12:53:47 2019 +0200
>
> usb:common Added usb_get_dr_mode_from_string and usb_dr_mode_to_string.
This would read better as:
usb: common: Add usb_get_dr_mode_from_string and usb_dr_mode_to_string.
>
> Patch introduces new function usb_dr_mode_to_string for converting dual role
> mod to string and removes static from usb_dr_mode_to_string definition.
>
> Both changes have made to avoid duplication of code by cdns3 driver.
>
> diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
> index 18f5dcf58b0d..ab59954deff8 100644
> --- a/drivers/usb/common/common.c
> +++ b/drivers/usb/common/common.c
> @@ -118,13 +118,20 @@ static const char *const usb_dr_modes[] = {
> [USB_DR_MODE_OTG] = "otg",
> };
>
> -static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
> +/**
> + * usb_get_dr_mode_from_string - Convert string to dual role mode.
> + * @str: Pointer to the given string
> + *
> + * The function gets string and returns the correspondig enum usb_dr_mode.
> + */
> +enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
> {
> int ret;
>
> ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str);
> return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret;
> }
> +EXPORT_SYMBOL_GPL(usb_get_dr_mode_from_string);
>
> enum usb_dr_mode usb_get_dr_mode(struct device *dev)
> {
> @@ -139,6 +146,21 @@ enum usb_dr_mode usb_get_dr_mode(struct device *dev)
> }
> EXPORT_SYMBOL_GPL(usb_get_dr_mode);
>
> +/**
> + * usb_dr_mode_to_string - Convert dual role mode to stringi.
s/stringi/string
> + * @dr_mode: Pointer to the given dual role mode
> + *
> + * The function gets enum usb_dr_mode, and returns the correspondig string.
> + */
> +const *char usb_dr_mode_to_string(const enum usb_dr_mode dr_mode)
> +{
> + if (dr_mode > USB_DR_MODE_UNKNOWN || dr_mode <= USB_DR_MODE_PERIPHERAL)
> + return usb_dr_modes[dr_mode];
> +
> + return usb_dr_modes[USB_DR_MODE_UNKNOWN];
> +}
> +EXPORT_SYMBOL_GPL(usb_dr_mode_to_string);
missing prototype in a header file.
--
balbi
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]
^ permalink raw reply
* [RFC PATCH V2 6/6] media: platform: mtk-mdp3: Add struct tuning_addr to identify user tuning data
From: frederic.chen-NuS5LvNUpcJWk0Htik3J/w @ 2019-07-08 11:05 UTC (permalink / raw)
To: hans.verkuil-FYB4Gu1CFyUAvxtiuMwx3w,
laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw,
tfiga-F7+t8E8rja9g9hUCZPvPmw, matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
mchehab-DgEjT+Ai2ygdnm+yROfE0A
Cc: shik-F7+t8E8rja9g9hUCZPvPmw, devicetree-u79uwXL29TY76Z2rM5mHXA,
Sean.Cheng-NuS5LvNUpcJWk0Htik3J/w, Rynn.Wu-NuS5LvNUpcJWk0Htik3J/w,
Allan.Yang-NuS5LvNUpcJWk0Htik3J/w,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
holmes.chiou-NuS5LvNUpcJWk0Htik3J/w,
suleiman-F7+t8E8rja9g9hUCZPvPmw,
Jerry-ch.Chen-NuS5LvNUpcJWk0Htik3J/w,
jungo.lin-NuS5LvNUpcJWk0Htik3J/w, sj.huang-NuS5LvNUpcJWk0Htik3J/w,
yuzhao-F7+t8E8rja9g9hUCZPvPmw,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
zwisler-F7+t8E8rja9g9hUCZPvPmw,
christie.yu-NuS5LvNUpcJWk0Htik3J/w,
frederic.chen-NuS5LvNUpcJWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-media-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20190708110500.7242-1-frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
We added a struct tuning_addr which contains a field "present"
so that the driver can tell the firmware if we have user tuning
dataor not.
Signed-off-by: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
drivers/media/platform/mtk-mdp3/mtk-img-ipi.h | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/mtk-mdp3/mtk-img-ipi.h b/drivers/media/platform/mtk-mdp3/mtk-img-ipi.h
index 9fabe7e8b71d..0944fe911d97 100644
--- a/drivers/media/platform/mtk-mdp3/mtk-img-ipi.h
+++ b/drivers/media/platform/mtk-mdp3/mtk-img-ipi.h
@@ -38,6 +38,12 @@ struct img_addr {
u32 iova; /* Used by IOMMU HW access */
} __attribute__ ((__packed__));
+struct tuning_addr {
+ u32 present;
+ u32 pa; /* Used by CM4 access */
+ u32 iova; /* Used by IOMMU HW access */
+} __attribute__ ((__packed__));
+
struct img_sw_addr {
u64 va; /* Used by APMCU access */
u32 pa; /* Used by CM4 access */
@@ -105,7 +111,7 @@ struct img_ipi_frameparam {
u64 drv_data;
struct img_input inputs[IMG_MAX_HW_INPUTS];
struct img_output outputs[IMG_MAX_HW_OUTPUTS];
- struct img_addr tuning_data;
+ struct tuning_addr tuning_data;
struct img_addr subfrm_data;
struct img_sw_addr config_data;
struct img_sw_addr self_data;
--
2.18.0
^ permalink raw reply related
* [RFC PATCH V2 5/6] remoteproc/mediatek: add SCP's shared dma pool support for mt8183
From: frederic.chen-NuS5LvNUpcJWk0Htik3J/w @ 2019-07-08 11:04 UTC (permalink / raw)
To: hans.verkuil-FYB4Gu1CFyUAvxtiuMwx3w,
laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw,
tfiga-F7+t8E8rja9g9hUCZPvPmw, matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
mchehab-DgEjT+Ai2ygdnm+yROfE0A
Cc: shik-F7+t8E8rja9g9hUCZPvPmw, devicetree-u79uwXL29TY76Z2rM5mHXA,
Sean.Cheng-NuS5LvNUpcJWk0Htik3J/w, Rynn.Wu-NuS5LvNUpcJWk0Htik3J/w,
Allan.Yang-NuS5LvNUpcJWk0Htik3J/w,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
holmes.chiou-NuS5LvNUpcJWk0Htik3J/w,
suleiman-F7+t8E8rja9g9hUCZPvPmw,
Jerry-ch.Chen-NuS5LvNUpcJWk0Htik3J/w,
jungo.lin-NuS5LvNUpcJWk0Htik3J/w, sj.huang-NuS5LvNUpcJWk0Htik3J/w,
yuzhao-F7+t8E8rja9g9hUCZPvPmw,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
zwisler-F7+t8E8rja9g9hUCZPvPmw,
christie.yu-NuS5LvNUpcJWk0Htik3J/w,
frederic.chen-NuS5LvNUpcJWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-media-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20190708110500.7242-1-frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
This patch uses of_reserved_mem_device_init_by_idx() to hook the
scp device to DMA mapping API to provide a shared dma pool of
SCP DMA buffers for SCP's client such as DIP and ISP Pass 1
drivers.
Signed-off-by: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
drivers/remoteproc/mtk_scp.c | 54 ++++++++++++++++++++++--------------
1 file changed, 33 insertions(+), 21 deletions(-)
diff --git a/drivers/remoteproc/mtk_scp.c b/drivers/remoteproc/mtk_scp.c
index 4c093dec52b9..0cffe4b63dba 100644
--- a/drivers/remoteproc/mtk_scp.c
+++ b/drivers/remoteproc/mtk_scp.c
@@ -4,12 +4,14 @@
#include <asm/barrier.h>
#include <linux/clk.h>
+#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
#include <linux/platform_data/mtk_scp.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
@@ -487,29 +489,29 @@ EXPORT_SYMBOL_GPL(scp_get_reserve_mem_size);
static int scp_map_memory_region(struct mtk_scp *scp)
{
- struct device_node *node;
- struct resource r;
- int ret;
+ int ret, id;
- node = of_parse_phandle(scp->dev->of_node, "memory-region", 0);
- if (!node) {
- dev_err(scp->dev, "no memory-region specified\n");
- return -EINVAL;
+ ret = of_reserved_mem_device_init_by_idx(scp->dev, scp->dev->of_node,
+ 0);
+ if (ret) {
+ dev_err(scp->dev,
+ "%s:of_reserved_mem_device_init_by_idx(0) failed:(%d)",
+ __func__, ret);
+ return -ENOMEM;
}
- ret = of_address_to_resource(node, 0, &r);
- if (ret)
- return ret;
+ /* Pre-allocate the working buffers */
+ scp->dram_size = MAX_CODE_SIZE;
+ for (id = 0; id < SCP_NUMS_MEM_ID; id++)
+ scp->dram_size += scp_reserve_mblock[id].size;
- scp->phys_addr = r.start;
- scp->dram_size = resource_size(&r);
- scp->cpu_addr =
- devm_ioremap_wc(scp->dev, scp->phys_addr, scp->dram_size);
+ scp->cpu_addr = dma_alloc_coherent(scp->dev, scp->dram_size,
+ &scp->phys_addr, GFP_KERNEL);
if (!scp->cpu_addr) {
- dev_err(scp->dev, "unable to map memory region: %pa+%zx\n",
- &r.start, scp->dram_size);
- return -EBUSY;
+ dev_err(scp->dev, "unable to pre-allocate memory for SCP: %zx\n",
+ scp->dram_size);
+ return -ENOMEM;
}
#if SCP_RESERVED_MEM
@@ -519,6 +521,13 @@ static int scp_map_memory_region(struct mtk_scp *scp)
return 0;
}
+static void scp_unmap_memory_region(struct mtk_scp *scp)
+{
+ dma_free_coherent(scp->dev, scp->dram_size, scp->cpu_addr,
+ scp->phys_addr);
+ of_reserved_mem_device_release(scp->dev);
+}
+
static struct mtk_rpmsg_info mtk_scp_rpmsg_info = {
.send_ipi = scp_ipi_send,
.register_ipi = scp_ipi_register,
@@ -594,20 +603,20 @@ static int scp_probe(struct platform_device *pdev)
if (IS_ERR(scp->clk)) {
dev_err(dev, "Failed to get clock\n");
ret = PTR_ERR(scp->clk);
- goto free_rproc;
+ goto release_dev_mem;
}
ret = clk_prepare_enable(scp->clk);
if (ret) {
dev_err(dev, "failed to enable clocks\n");
- goto free_rproc;
+ goto release_dev_mem;
}
ret = scp_ipi_init(scp);
clk_disable_unprepare(scp->clk);
if (ret) {
dev_err(dev, "Failed to init ipi\n");
- goto free_rproc;
+ goto release_dev_mem;
}
/* register SCP initialization IPI */
@@ -617,7 +626,7 @@ static int scp_probe(struct platform_device *pdev)
scp);
if (ret) {
dev_err(dev, "Failed to register IPI_SCP_INIT\n");
- goto free_rproc;
+ goto release_dev_mem;
}
mutex_init(&scp->lock);
@@ -645,6 +654,8 @@ static int scp_probe(struct platform_device *pdev)
remove_subdev:
scp_remove_rpmsg_subdev(scp);
mutex_destroy(&scp->lock);
+release_dev_mem:
+ scp_unmap_memory_region(scp);
free_rproc:
rproc_free(rproc);
@@ -658,6 +669,7 @@ static int scp_remove(struct platform_device *pdev)
scp_remove_rpmsg_subdev(scp);
rproc_del(scp->rproc);
rproc_free(scp->rproc);
+ scp_unmap_memory_region(scp);
return 0;
}
--
2.18.0
^ permalink raw reply related
* [RFC PATCH V2 4/6] platform: mtk-isp: Add Mediatek DIP driver
From: frederic.chen-NuS5LvNUpcJWk0Htik3J/w @ 2019-07-08 11:04 UTC (permalink / raw)
To: hans.verkuil-FYB4Gu1CFyUAvxtiuMwx3w,
laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw,
tfiga-F7+t8E8rja9g9hUCZPvPmw, matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
mchehab-DgEjT+Ai2ygdnm+yROfE0A
Cc: shik-F7+t8E8rja9g9hUCZPvPmw, devicetree-u79uwXL29TY76Z2rM5mHXA,
Sean.Cheng-NuS5LvNUpcJWk0Htik3J/w, Rynn.Wu-NuS5LvNUpcJWk0Htik3J/w,
Allan.Yang-NuS5LvNUpcJWk0Htik3J/w,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
holmes.chiou-NuS5LvNUpcJWk0Htik3J/w,
suleiman-F7+t8E8rja9g9hUCZPvPmw,
Jerry-ch.Chen-NuS5LvNUpcJWk0Htik3J/w,
jungo.lin-NuS5LvNUpcJWk0Htik3J/w, sj.huang-NuS5LvNUpcJWk0Htik3J/w,
yuzhao-F7+t8E8rja9g9hUCZPvPmw,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
zwisler-F7+t8E8rja9g9hUCZPvPmw,
christie.yu-NuS5LvNUpcJWk0Htik3J/w,
frederic.chen-NuS5LvNUpcJWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-media-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20190708110500.7242-1-frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
This patch adds the driver of Digital Image Processing (DIP)
unit in Mediatek ISP system, providing image format
conversion, resizing, and rotation features.
The mtk-isp directory will contain drivers for multiple IP
blocks found in Mediatek ISP system. It will include ISP
Pass 1 driver(CAM), sensor interface driver, DIP driver and
face detection driver.
Signed-off-by: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
drivers/media/platform/mtk-isp/Makefile | 7 +
.../media/platform/mtk-isp/isp_50/Makefile | 7 +
.../platform/mtk-isp/isp_50/dip/Makefile | 18 +
.../platform/mtk-isp/isp_50/dip/mtk_dip-dev.c | 769 +++++++
.../platform/mtk-isp/isp_50/dip/mtk_dip-dev.h | 337 ++++
.../platform/mtk-isp/isp_50/dip/mtk_dip-hw.h | 155 ++
.../platform/mtk-isp/isp_50/dip/mtk_dip-sys.c | 794 ++++++++
.../mtk-isp/isp_50/dip/mtk_dip-v4l2.c | 1786 +++++++++++++++++
8 files changed, 3873 insertions(+)
create mode 100644 drivers/media/platform/mtk-isp/Makefile
create mode 100644 drivers/media/platform/mtk-isp/isp_50/Makefile
create mode 100644 drivers/media/platform/mtk-isp/isp_50/dip/Makefile
create mode 100644 drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.c
create mode 100644 drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.h
create mode 100644 drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-hw.h
create mode 100644 drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-sys.c
create mode 100644 drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-v4l2.c
diff --git a/drivers/media/platform/mtk-isp/Makefile b/drivers/media/platform/mtk-isp/Makefile
new file mode 100644
index 000000000000..b08d3bdf2800
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 MediaTek Inc.
+#
+
+obj-y += isp_50/
+
diff --git a/drivers/media/platform/mtk-isp/isp_50/Makefile b/drivers/media/platform/mtk-isp/isp_50/Makefile
new file mode 100644
index 000000000000..564c3889c34c
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 MediaTek Inc.
+#
+
+obj-$(CONFIG_VIDEO_MEDIATEK_ISP_DIP) += dip/
+
diff --git a/drivers/media/platform/mtk-isp/isp_50/dip/Makefile b/drivers/media/platform/mtk-isp/isp_50/dip/Makefile
new file mode 100644
index 000000000000..99e760d7d5a9
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/dip/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 MediaTek Inc.
+#
+
+$(info $(srctree))
+ccflags-y += -I$(srctree)/drivers/media/platform/mtk-mdp3
+
+obj-$(CONFIG_VIDEO_MEDIATEK_ISP_DIP) += mtk_dip-v4l2.o
+
+# Utilities to provide frame-based streaming model
+# with v4l2 user interfaces and alloc context managing
+# memory shared between ISP and coprocessor
+mtk_dip_util-objs := \
+mtk_dip-dev.o \
+mtk_dip-sys.o
+
+obj-$(CONFIG_VIDEO_MEDIATEK_ISP_DIP) += mtk_dip_util.o
diff --git a/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.c b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.c
new file mode 100644
index 000000000000..63256fa27428
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.c
@@ -0,0 +1,769 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ *
+ * Author: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ *
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-event.h>
+#include "mtk_dip-dev.h"
+#include "mtk-mdp3-regs.h"
+#include "mtk-img-ipi.h"
+
+int mtk_dip_pipe_init(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_dev *dip_dev,
+ const struct mtk_dip_pipe_desc *setting,
+ struct media_device *media_dev,
+ struct v4l2_device *v4l2_dev)
+{
+ int ret, i, count = 0;
+
+ pipe->dip_dev = dip_dev;
+ pipe->desc = setting;
+ pipe->num_nodes = MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM;
+
+ atomic_set(&pipe->pipe_job_sequence, 0);
+ INIT_LIST_HEAD(&pipe->pipe_job_running_list);
+ INIT_LIST_HEAD(&pipe->pipe_job_pending_list);
+ spin_lock_init(&pipe->job_lock);
+ mutex_init(&pipe->lock);
+
+ for (i = 0; i < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM; i++) {
+ if (i < MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM) {
+ pipe->nodes[i].desc =
+ &pipe->desc->output_queue_descs[i];
+ } else {
+ int cap_idx = i - MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM;
+
+ pipe->nodes[i].desc =
+ &pipe->desc->capture_queue_descs[cap_idx];
+ }
+
+ pipe->nodes[i].flags =
+ pipe->nodes[i].desc->flags;
+ atomic_set(&pipe->nodes[i].sequence, 0);
+ spin_lock_init(&pipe->nodes[i].buf_list_lock);
+ INIT_LIST_HEAD(&pipe->nodes[i].buf_list);
+
+ if (pipe->nodes[i].flags & MEDIA_LNK_FL_ENABLED)
+ count++;
+ }
+
+ if (pipe->desc->master >= 0 &&
+ pipe->desc->master < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM) {
+ if (!(pipe->nodes[pipe->desc->master].flags &
+ MEDIA_LNK_FL_ENABLED))
+ count++;
+
+ pipe->nodes[pipe->desc->master].flags |=
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE;
+ }
+
+ atomic_set(&pipe->cnt_nodes_not_streaming, count);
+
+ ret = mtk_dip_pipe_v4l2_register(pipe, media_dev, v4l2_dev);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s: failed(%d) to create V4L2 devices\n",
+ pipe->desc->name, ret);
+
+ goto err_destroy_pipe_lock;
+ }
+
+ return 0;
+
+err_destroy_pipe_lock:
+ mutex_destroy(&pipe->lock);
+
+ return ret;
+}
+
+int mtk_dip_pipe_release(struct mtk_dip_pipe *pipe)
+{
+ mtk_dip_pipe_v4l2_unregister(pipe);
+ mutex_destroy(&pipe->lock);
+
+ return 0;
+}
+
+int mtk_dip_pipe_next_job_id(struct mtk_dip_pipe *pipe)
+{
+ int global_job_id = atomic_inc_return(&pipe->pipe_job_sequence);
+
+ return (global_job_id & 0x0000FFFF) | (pipe->desc->id << 16);
+}
+
+struct mtk_dip_pipe_job_info*
+mtk_dip_pipe_get_running_job_info(struct mtk_dip_pipe *pipe, int pipe_job_id)
+{
+ struct mtk_dip_pipe_job_info *job_info;
+
+ spin_lock(&pipe->job_lock);
+ list_for_each_entry(job_info,
+ &pipe->pipe_job_running_list, list) {
+ if (job_info->id == pipe_job_id) {
+ spin_unlock(&pipe->job_lock);
+ return job_info;
+ }
+ }
+ spin_unlock(&pipe->job_lock);
+
+ return NULL;
+}
+
+void mtk_dip_pipe_debug_job(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_pipe_job_info *job_info)
+{
+ int i;
+
+ dev_dbg(&pipe->dip_dev->pdev->dev, "%s: pipe-job(%p),id(%d)\n",
+ pipe->desc->name, job_info, job_info->id);
+
+ for (i = 0; i < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM ; i++) {
+ if (job_info->buf_map[i])
+ dev_dbg(&pipe->dip_dev->pdev->dev, "%s:%s:buf(%p)\n",
+ pipe->desc->name, pipe->nodes[i].desc->name, i,
+ job_info->buf_map[i]);
+ }
+}
+
+int mtk_dip_pipe_job_finish(struct mtk_dip_pipe *pipe,
+ unsigned int pipe_job_info_id,
+ enum vb2_buffer_state vbf_state)
+{
+ struct mtk_dip_pipe_job_info *job_info;
+ struct mtk_dip_dev_buffer *in_buf;
+ int i, num_running_jobs;
+
+ job_info = mtk_dip_pipe_get_running_job_info(pipe, pipe_job_info_id);
+ in_buf = job_info->buf_map[MTK_DIP_VIDEO_NODE_ID_RAW_OUT];
+
+ spin_lock(&pipe->job_lock);
+ list_del(&job_info->list);
+ num_running_jobs = --pipe->num_jobs;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: num of running jobs(%d)\n",
+ __func__, pipe->desc->name, pipe->num_jobs);
+ spin_unlock(&pipe->job_lock);
+
+ for (i = 0; i < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM; i++) {
+ struct mtk_dip_dev_buffer *dev_buf = job_info->buf_map[i];
+ struct mtk_dip_video_device *node;
+
+ if (!dev_buf)
+ continue;
+
+ if (in_buf && dev_buf != in_buf)
+ dev_buf->vbb.vb2_buf.timestamp =
+ in_buf->vbb.vb2_buf.timestamp;
+
+ vb2_buffer_done(&dev_buf->vbb.vb2_buf, vbf_state);
+
+ node = mtk_dip_vbq_to_node(dev_buf->vbb.vb2_buf.vb2_queue);
+ spin_lock(&node->buf_list_lock);
+ list_del(&dev_buf->list);
+ spin_unlock(&node->buf_list_lock);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: return buf, idx(%d), state(%d)\n",
+ pipe->desc->name, node->desc->name,
+ dev_buf->vbb.vb2_buf.index, vbf_state);
+ }
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: finished job id(%d), vbf_state(%d), running jobs(%d)\n",
+ __func__, pipe->desc->name, pipe_job_info_id, vbf_state,
+ num_running_jobs);
+
+ return 0;
+}
+
+static __u32 get_pixel_byte_by_fmt(__u32 pix_fmt)
+{
+ switch (pix_fmt) {
+ case V4L2_PIX_FMT_MTISP_B8:
+ case V4L2_PIX_FMT_MTISP_F8:
+ return 8;
+ case V4L2_PIX_FMT_MTISP_B10:
+ case V4L2_PIX_FMT_MTISP_F10:
+ return 10;
+ case V4L2_PIX_FMT_MTISP_B12:
+ case V4L2_PIX_FMT_MTISP_F12:
+ return 12;
+ case V4L2_PIX_FMT_MTISP_B14:
+ case V4L2_PIX_FMT_MTISP_F14:
+ return 14;
+ case V4L2_PIX_FMT_MTISP_U8:
+ case V4L2_PIX_FMT_MTISP_U10:
+ case V4L2_PIX_FMT_MTISP_U12:
+ case V4L2_PIX_FMT_MTISP_U14:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+static __u32
+mtk_dip_pass1_cal_main_stride(__u32 width, __u32 pix_fmt)
+{
+ __u32 stride;
+ __u32 pixel_byte = get_pixel_byte_by_fmt(pix_fmt);
+
+ width = ALIGN(width, 4);
+ stride = ALIGN(DIV_ROUND_UP(width * pixel_byte, 8), 2);
+
+ return ALIGN(stride, 4);
+}
+
+static __u32
+mtk_dip_pass1_cal_pack_stride(__u32 width, __u32 pix_fmt)
+{
+ __u32 stride;
+ __u32 pixel_byte = get_pixel_byte_by_fmt(pix_fmt);
+
+ width = ALIGN(width, 4);
+ stride = DIV_ROUND_UP(width * 3, 2);
+ stride = DIV_ROUND_UP(stride * pixel_byte, 8);
+
+ if (pix_fmt == V4L2_PIX_FMT_MTISP_F10)
+ stride = ALIGN(stride, 4);
+ else
+ stride = ALIGN(stride, 8);
+
+ return stride;
+}
+
+static int is_stride_need_to_align(u32 format, u32 *need_aligned_fmts,
+ int length)
+{
+ int i;
+
+ for (i = 0; i < length; i++) {
+ if (format == need_aligned_fmts[i])
+ return true;
+ }
+
+ return false;
+}
+
+/* Stride that is accepted by MDP HW */
+static u32 dip_mdp_fmt_get_stride(struct v4l2_pix_format_mplane *mfmt,
+ const struct mtk_dip_dev_format *fmt,
+ unsigned int plane)
+{
+ enum mdp_color c = fmt->mdp_color;
+ u32 bytesperline = (mfmt->width * fmt->row_depth[plane]) / 8;
+ u32 stride = (bytesperline * MDP_COLOR_BITS_PER_PIXEL(c))
+ / fmt->row_depth[0];
+
+ if (plane == 0)
+ return stride;
+
+ if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) {
+ if (MDP_COLOR_IS_BLOCK_MODE(c))
+ stride = stride / 2;
+ return stride;
+ }
+
+ return 0;
+}
+
+/* Stride that is accepted by MDP HW of format with contiguous planes */
+static u32
+dip_mdp_fmt_get_stride_contig(const struct mtk_dip_dev_format *fmt,
+ u32 pix_stride,
+ unsigned int plane)
+{
+ enum mdp_color c = fmt->mdp_color;
+ u32 stride = pix_stride;
+
+ if (plane == 0)
+ return stride;
+
+ if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) {
+ stride = stride >> MDP_COLOR_GET_H_SUBSAMPLE(c);
+ if (MDP_COLOR_IS_UV_COPLANE(c) && !MDP_COLOR_IS_BLOCK_MODE(c))
+ stride = stride * 2;
+ return stride;
+ }
+
+ return 0;
+}
+
+/* Plane size that is accepted by MDP HW */
+static u32
+dip_mdp_fmt_get_plane_size(const struct mtk_dip_dev_format *fmt,
+ u32 stride, u32 height,
+ unsigned int plane)
+{
+ enum mdp_color c = fmt->mdp_color;
+ u32 bytesperline;
+
+ bytesperline = (stride * fmt->row_depth[0])
+ / MDP_COLOR_BITS_PER_PIXEL(c);
+ if (plane == 0)
+ return bytesperline * height;
+ if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) {
+ height = height >> MDP_COLOR_GET_V_SUBSAMPLE(c);
+ if (MDP_COLOR_IS_BLOCK_MODE(c))
+ bytesperline = bytesperline * 2;
+ return bytesperline * height;
+ }
+
+ return 0;
+}
+
+static int mtk_dip_pipe_get_stride(struct mtk_dip_pipe *pipe,
+ struct v4l2_pix_format_mplane *mfmt,
+ const struct mtk_dip_dev_format *dfmt,
+ int plane,
+ char *buf_name)
+{
+ int bpl;
+ u32 fmts_pass1_main[] = {
+ V4L2_PIX_FMT_MTISP_B8,
+ V4L2_PIX_FMT_MTISP_B10
+ };
+ u32 fmts_pass1_pack[] = {
+ V4L2_PIX_FMT_MTISP_F8,
+ V4L2_PIX_FMT_MTISP_F10
+ };
+
+ if (is_stride_need_to_align(mfmt->pixelformat, fmts_pass1_main,
+ ARRAY_SIZE(fmts_pass1_main)))
+ bpl = mtk_dip_pass1_cal_main_stride(mfmt->width,
+ mfmt->pixelformat);
+ else if (is_stride_need_to_align(mfmt->pixelformat, fmts_pass1_pack,
+ ARRAY_SIZE(fmts_pass1_pack)))
+ bpl = mtk_dip_pass1_cal_pack_stride(mfmt->width,
+ mfmt->pixelformat);
+ else
+ bpl = dip_mdp_fmt_get_stride(mfmt, dfmt, plane);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: plane(%d), pixelformat(%x) width(%d), stride(%d)",
+ __func__, pipe->desc->name, buf_name, plane,
+ mfmt->pixelformat, mfmt->width, bpl);
+
+ return bpl;
+}
+
+void mtk_dip_pipe_set_img_fmt(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ struct v4l2_pix_format_mplane *mfmt_to_fill,
+ const struct mtk_dip_dev_format *dev_fmt)
+{
+ int i;
+
+ mfmt_to_fill->pixelformat = dev_fmt->format;
+ mfmt_to_fill->num_planes = dev_fmt->num_planes;
+ mfmt_to_fill->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ mfmt_to_fill->quantization = V4L2_QUANTIZATION_DEFAULT;
+ mfmt_to_fill->colorspace = dev_fmt->colorspace;
+ mfmt_to_fill->field = V4L2_FIELD_NONE;
+
+ memset(mfmt_to_fill->reserved, 0, sizeof(mfmt_to_fill->reserved));
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: Fmt(%c%c%c%c), w(%d),h(%d), f(%d)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ mfmt_to_fill->pixelformat & 0xff,
+ mfmt_to_fill->pixelformat >> 8 & 0xff,
+ mfmt_to_fill->pixelformat >> 16 & 0xff,
+ mfmt_to_fill->pixelformat >> 24 & 0xff,
+ mfmt_to_fill->width, mfmt_to_fill->height,
+ mfmt_to_fill->field);
+
+ for (i = 0; i < mfmt_to_fill->num_planes; ++i) {
+ u32 min_bpl = (mfmt_to_fill->width * dev_fmt->row_depth[i]) / 8;
+ u32 bpl = mfmt_to_fill->plane_fmt[i].bytesperline;
+ u32 sizeimage;
+
+ if (bpl < min_bpl)
+ bpl = min_bpl;
+
+ sizeimage = (bpl * mfmt_to_fill->height * dev_fmt->depth[i])
+ / dev_fmt->row_depth[i];
+ mfmt_to_fill->plane_fmt[i].bytesperline = bpl;
+ if (mfmt_to_fill->plane_fmt[i].sizeimage < sizeimage)
+ mfmt_to_fill->plane_fmt[i].sizeimage = sizeimage;
+
+ memset(mfmt_to_fill->plane_fmt[i].reserved,
+ 0, sizeof(mfmt_to_fill->plane_fmt[i].reserved));
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: plane(%d): bpl(%u), min_bpl(%u), s(%u)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ i, bpl, min_bpl, mfmt_to_fill->plane_fmt[i].sizeimage);
+ }
+}
+
+static void set_meta_fmt(struct mtk_dip_pipe *pipe,
+ struct v4l2_meta_format *metafmt_to_fill,
+ const struct mtk_dip_dev_format *dev_fmt)
+{
+ metafmt_to_fill->dataformat = dev_fmt->format;
+
+ if (dev_fmt->buffer_size <= 0) {
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s: Invalid meta buf size(%u), use default(%u)\n",
+ pipe->desc->name, dev_fmt->buffer_size,
+ MTK_DIP_DEV_META_BUF_DEFAULT_SIZE);
+
+ metafmt_to_fill->buffersize =
+ MTK_DIP_DEV_META_BUF_DEFAULT_SIZE;
+ } else {
+ metafmt_to_fill->buffersize = dev_fmt->buffer_size;
+ }
+}
+
+void mtk_dip_pipe_load_default_fmt(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ struct v4l2_format *fmt_to_fill)
+{
+ int idx = node->desc->default_fmt_idx;
+
+ if (node->desc->num_fmts == 0) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: desc->num_fmts is 0, no format support list\n",
+ __func__, node->desc->name);
+
+ return;
+ }
+
+ if (idx >= node->desc->num_fmts) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: invalid idx(%d), must < num_fmts(%d)\n",
+ __func__, node->desc->name, idx, node->desc->num_fmts);
+
+ idx = 0;
+ }
+
+ fmt_to_fill->type = node->desc->buf_type;
+ if (mtk_dip_buf_is_meta(node->desc->buf_type)) {
+ set_meta_fmt(pipe, &fmt_to_fill->fmt.meta,
+ &node->desc->fmts[idx]);
+ } else {
+ fmt_to_fill->fmt.pix_mp.width = node->desc->default_width;
+ fmt_to_fill->fmt.pix_mp.height = node->desc->default_height;
+ mtk_dip_pipe_set_img_fmt(pipe, node, &fmt_to_fill->fmt.pix_mp,
+ &node->desc->fmts[idx]);
+ }
+}
+
+const struct mtk_dip_dev_format*
+mtk_dip_pipe_find_fmt(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ u32 format)
+{
+ int i;
+
+ for (i = 0; i < node->desc->num_fmts; i++) {
+ if (node->desc->fmts[i].format == format)
+ return &node->desc->fmts[i];
+ }
+
+ return NULL;
+}
+
+static enum mdp_ycbcr_profile
+map_ycbcr_prof_mplane(struct v4l2_pix_format_mplane *pix_mp, u32 mdp_color)
+{
+ if (MDP_COLOR_IS_RGB(mdp_color))
+ return MDP_YCBCR_PROFILE_FULL_BT601;
+
+ switch (pix_mp->colorspace) {
+ case V4L2_COLORSPACE_JPEG:
+ return MDP_YCBCR_PROFILE_JPEG;
+ case V4L2_COLORSPACE_REC709:
+ case V4L2_COLORSPACE_DCI_P3:
+ if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
+ return MDP_YCBCR_PROFILE_FULL_BT709;
+ return MDP_YCBCR_PROFILE_BT709;
+ case V4L2_COLORSPACE_BT2020:
+ if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
+ return MDP_YCBCR_PROFILE_FULL_BT2020;
+ return MDP_YCBCR_PROFILE_BT2020;
+ }
+ /* V4L2_COLORSPACE_SRGB or else */
+ if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
+ return MDP_YCBCR_PROFILE_FULL_BT601;
+
+ return MDP_YCBCR_PROFILE_BT601;
+}
+
+static inline int is_contig_mp_buffer(struct mtk_dip_dev_buffer *dev_buf)
+{
+ return !(MDP_COLOR_GET_PLANE_COUNT(dev_buf->dev_fmt->mdp_color) == 1);
+}
+
+static int fill_ipi_img_param_mp(struct mtk_dip_pipe *pipe,
+ struct img_image_buffer *b,
+ struct mtk_dip_dev_buffer *dev_buf,
+ char *buf_name)
+{
+ struct v4l2_pix_format_mplane *pix_mp;
+ const struct mtk_dip_dev_format *mdp_fmt;
+ unsigned int i;
+ unsigned int total_plane_size = 0;
+
+ if (!dev_buf->dev_fmt) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s: %s's dev format not set\n",
+ __func__, buf_name);
+ return -EINVAL;
+ }
+
+ pix_mp = &dev_buf->fmt.fmt.pix_mp;
+ mdp_fmt = dev_buf->dev_fmt;
+
+ b->usage = dev_buf->dma_port;
+ b->format.colorformat = dev_buf->dev_fmt->mdp_color;
+ b->format.width = dev_buf->fmt.fmt.pix_mp.width;
+ b->format.height = dev_buf->fmt.fmt.pix_mp.height;
+ b->format.ycbcr_prof =
+ map_ycbcr_prof_mplane(pix_mp,
+ dev_buf->dev_fmt->mdp_color);
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s: buf(%s), IPI: w(%d),h(%d),c(0x%x)\n",
+ pipe->desc->name,
+ buf_name,
+ b->format.width,
+ b->format.height,
+ b->format.colorformat);
+
+ for (i = 0; i < pix_mp->num_planes; ++i) {
+ u32 stride = mtk_dip_pipe_get_stride(pipe, pix_mp, mdp_fmt,
+ i, buf_name);
+
+ b->format.plane_fmt[i].stride = stride;
+ b->format.plane_fmt[i].size =
+ dip_mdp_fmt_get_plane_size(mdp_fmt, stride,
+ pix_mp->height, i);
+ b->iova[i] = dev_buf->isp_daddr[i];
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "Non-contiguous-mp-buf:plane(%i),stride(%d),size(%d),iova(%lx)",
+ i,
+ b->format.plane_fmt[i].stride,
+ b->format.plane_fmt[i].size,
+ b->iova[i]);
+ total_plane_size += b->format.plane_fmt[i].size;
+ }
+
+ if (!is_contig_mp_buffer(dev_buf)) {
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "Non-contiguous-mp-buf(%s),total-plane-size(%d),dma_port(%d)\n",
+ buf_name,
+ total_plane_size,
+ b->usage);
+ return 0;
+ }
+
+ for (; i < MDP_COLOR_GET_PLANE_COUNT(b->format.colorformat); ++i) {
+ u32 stride = dip_mdp_fmt_get_stride_contig
+ (mdp_fmt, b->format.plane_fmt[0].stride, i);
+
+ b->format.plane_fmt[i].stride = stride;
+ b->format.plane_fmt[i].size =
+ dip_mdp_fmt_get_plane_size(mdp_fmt, stride,
+ pix_mp->height, i);
+ b->iova[i] = b->iova[i - 1] + b->format.plane_fmt[i - 1].size;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "Contiguous-mp-buf:plane(%i),stride(%d),size(%d),iova(%lx)",
+ i,
+ b->format.plane_fmt[i].stride,
+ b->format.plane_fmt[i].size,
+ b->iova[i]);
+ total_plane_size += b->format.plane_fmt[i].size;
+ }
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "Contiguous-mp-buf(%s),v4l2-sizeimage(%d),total-plane-size(%d)\n",
+ buf_name,
+ pix_mp->plane_fmt[0].sizeimage,
+ total_plane_size);
+
+ return 0;
+}
+
+static int fill_input_ipi_param(struct mtk_dip_pipe *pipe,
+ struct img_input *iin,
+ struct mtk_dip_dev_buffer *dev_buf,
+ char *buf_name)
+{
+ struct img_image_buffer *img = &iin->buffer;
+
+ return fill_ipi_img_param_mp(pipe, img, dev_buf,
+ buf_name);
+}
+
+static int fill_output_ipi_param(struct mtk_dip_pipe *pipe,
+ struct img_output *iout,
+ struct mtk_dip_dev_buffer *dev_buf_out,
+ struct mtk_dip_dev_buffer *dev_buf_in,
+ char *buf_name)
+{
+ int ret;
+ struct img_image_buffer *img = &iout->buffer;
+
+ ret = fill_ipi_img_param_mp(pipe, img, dev_buf_out,
+ buf_name);
+ iout->crop.left = 0;
+ iout->crop.top = 0;
+ iout->crop.width = dev_buf_in->fmt.fmt.pix_mp.width;
+ iout->crop.height = dev_buf_in->fmt.fmt.pix_mp.height;
+ iout->crop.left_subpix = 0;
+ iout->crop.top_subpix = 0;
+ iout->crop.width_subpix = 0;
+ iout->crop.height_subpix = 0;
+ iout->rotation = dev_buf_out->rotation;
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s: buf(%s) IPI-ext:c_l(%d),c_t(%d),c_w(%d),c_h(%d)\n",
+ pipe->desc->name,
+ buf_name,
+ iout->crop.left,
+ iout->crop.top,
+ iout->crop.width,
+ iout->crop.height);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "c_ls(%d),c_ts(%d),c_ws(%d),c_hs(%d),rot(%d)\n",
+ iout->crop.left_subpix,
+ iout->crop.top_subpix,
+ iout->crop.width_subpix,
+ iout->crop.height_subpix,
+ iout->rotation);
+
+ return ret;
+}
+
+void mtk_dip_pipe_ipi_params_config(struct mtk_dip_request *req)
+{
+ struct mtk_dip_pipe *pipe = req->dip_pipe;
+ struct platform_device *pdev = pipe->dip_dev->pdev;
+ struct mtk_dip_pipe_job_info *pipe_job_info = &req->job_info;
+ int out_img_buf_idx;
+ int in_img_buf_idx;
+ struct img_ipi_frameparam *dip_param = &req->img_fparam.frameparam;
+ struct mtk_dip_dev_buffer *dev_buf_in;
+ struct mtk_dip_dev_buffer *dev_buf_out;
+ struct mtk_dip_dev_buffer *dev_buf_tuning;
+ int i = 0;
+ int mdp_ids[2] = {MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE,
+ MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE};
+ char *mdp_names[2] = {"mdp0", "mdp1"};
+
+ dev_dbg(&pdev->dev,
+ "%s:%s: pipe-job id(%d)\n",
+ __func__, pipe->desc->name,
+ pipe_job_info->id);
+
+ /* Fill ipi params for DIP driver */
+ memset(dip_param, 0, sizeof(*dip_param));
+ dip_param->index = pipe_job_info->id;
+ dip_param->type = STREAM_ISP_IC;
+
+ /* Tuning buffer */
+ dev_buf_tuning =
+ pipe_job_info->buf_map[MTK_DIP_VIDEO_NODE_ID_TUNING_OUT];
+ if (dev_buf_tuning) {
+ dev_dbg(&pdev->dev,
+ "Tuning buf queued: scp_daddr(%pad),isp_daddr(%pad)\n",
+ &dev_buf_tuning->scp_daddr[0],
+ &dev_buf_tuning->isp_daddr[0]);
+ dip_param->tuning_data.pa =
+ (uint32_t)dev_buf_tuning->scp_daddr[0];
+ dip_param->tuning_data.present = true;
+ dip_param->tuning_data.iova =
+ (uint32_t)dev_buf_tuning->isp_daddr[0];
+ } else {
+ dev_dbg(&pdev->dev,
+ "No enqueued tuning buffer: scp_daddr(%llx),present(%llx),isp_daddr(%llx\n",
+ dip_param->tuning_data.pa,
+ dip_param->tuning_data.present,
+ dip_param->tuning_data.iova);
+ }
+
+ in_img_buf_idx = 0;
+
+ /* Raw-in buffer */
+ dev_buf_in =
+ pipe_job_info->buf_map[MTK_DIP_VIDEO_NODE_ID_RAW_OUT];
+ if (dev_buf_in) {
+ struct img_input *iin = &dip_param->inputs[in_img_buf_idx];
+
+ fill_input_ipi_param(pipe, iin, dev_buf_in, "RAW");
+ in_img_buf_idx++;
+ }
+
+ /* Array of MDP buffers */
+ out_img_buf_idx = 0;
+
+ for (i = 0; i < ARRAY_SIZE(mdp_ids); i++) {
+ dev_buf_out =
+ pipe_job_info->buf_map[mdp_ids[i]];
+ if (dev_buf_out) {
+ struct img_output *iout =
+ &dip_param->outputs[out_img_buf_idx];
+
+ fill_output_ipi_param(pipe, iout, dev_buf_out,
+ dev_buf_in, mdp_names[i]);
+ out_img_buf_idx++;
+ }
+ }
+
+ dip_param->num_outputs = out_img_buf_idx;
+ dip_param->num_inputs = in_img_buf_idx;
+}
+
+void mtk_dip_pipe_try_enqueue(struct mtk_dip_pipe *pipe)
+{
+ struct mtk_dip_pipe_job_info *job_info;
+ struct mtk_dip_pipe_job_info *tmp_job_info;
+ struct list_head enqueue_job_list;
+
+ INIT_LIST_HEAD(&enqueue_job_list);
+ if (!pipe->streaming)
+ return;
+
+ spin_lock(&pipe->job_lock);
+ list_for_each_entry_safe(job_info, tmp_job_info,
+ &pipe->pipe_job_pending_list,
+ list) {
+ list_del(&job_info->list);
+ pipe->num_pending_jobs--;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: current num of pending jobs(%d)\n",
+ __func__, pipe->desc->name,
+ pipe->num_pending_jobs);
+ list_add_tail(&job_info->list,
+ &enqueue_job_list);
+ }
+ spin_unlock(&pipe->job_lock);
+
+ list_for_each_entry_safe(job_info, tmp_job_info,
+ &enqueue_job_list,
+ list) {
+ struct mtk_dip_request *req =
+ mtk_dip_pipe_job_info_to_req(job_info);
+
+ list_del(&job_info->list);
+
+ spin_lock(&pipe->job_lock);
+ list_add_tail(&job_info->list,
+ &pipe->pipe_job_running_list);
+ pipe->num_jobs++;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: current num of running jobs(%d)\n",
+ __func__, pipe->desc->name,
+ pipe->num_jobs);
+ spin_unlock(&pipe->job_lock);
+
+ mtk_dip_hw_enqueue(&pipe->dip_dev->dip_hw, req);
+ }
+}
diff --git a/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.h b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.h
new file mode 100644
index 000000000000..e3372c291f9a
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-dev.h
@@ -0,0 +1,337 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ *
+ * Author: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ *
+ */
+
+#ifndef _MTK_DIP_DEV_H_
+#define _MTK_DIP_DEV_H_
+
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-device.h>
+#include <linux/videodev2.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "mtk_dip-hw.h"
+
+#define MTK_DIP_PIPE_ID_PREVIEW 0
+#define MTK_DIP_PIPE_ID_CAPTURE 1
+#define MTK_DIP_PIPE_ID_REPROCESS 2
+#define MTK_DIP_PIPE_ID_TOTAL_NUM 3
+
+#define MTK_DIP_VIDEO_NODE_ID_RAW_OUT 0
+#define MTK_DIP_VIDEO_NODE_ID_TUNING_OUT 1
+#define MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM 2
+#define MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE 2
+#define MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE 3
+#define MTK_DIP_VIDEO_NODE_ID_CAPTURE_TOTAL_NUM 2
+#define MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM \
+ (MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM + \
+ MTK_DIP_VIDEO_NODE_ID_CAPTURE_TOTAL_NUM)
+
+#define MTK_DIP_VIDEO_NODE_ID_NO_MASTER -1
+
+#define MTK_DIP_OUTPUT_MIN_WIDTH 2U
+#define MTK_DIP_OUTPUT_MIN_HEIGHT 2U
+#define MTK_DIP_OUTPUT_MAX_WIDTH 5376U
+#define MTK_DIP_OUTPUT_MAX_HEIGHT 4032U
+#define MTK_DIP_CAPTURE_MIN_WIDTH 2U
+#define MTK_DIP_CAPTURE_MIN_HEIGHT 2U
+#define MTK_DIP_CAPTURE_MAX_WIDTH 5376U
+#define MTK_DIP_CAPTURE_MAX_HEIGHT 4032U
+
+#define MTK_DIP_DEV_DIP_MEDIA_MODEL_NAME "MTK-ISP-DIP-V4L2"
+#define MTK_DIP_DEV_DIP_PREVIEW_NAME \
+ MTK_DIP_DEV_DIP_MEDIA_MODEL_NAME
+#define MTK_DIP_DEV_DIP_CAPTURE_NAME "MTK-ISP-DIP-CAP-V4L2"
+#define MTK_DIP_DEV_DIP_REPROCESS_NAME "MTK-ISP-DIP-REP-V4L2"
+
+#define MTK_DIP_DEV_META_BUF_DEFAULT_SIZE (1024 * 128)
+#define MTK_DIP_DEV_META_BUF_POOL_MAX_SIZE (1024 * 1024 * 6)
+
+#define V4L2_CID_MTK_DIP_MAX 100
+
+enum mtk_dip_pixel_mode {
+ mtk_dip_pixel_mode_default = 0,
+ mtk_dip_pixel_mode_1,
+ mtk_dip_pixel_mode_2,
+ mtk_dip_pixel_mode_4,
+ mtk_dip_pixel_mode_num,
+};
+
+struct mtk_dip_dev_format {
+ u32 format;
+ u32 mdp_color;
+ u32 colorspace;
+ u8 depth[VIDEO_MAX_PLANES];
+ u8 row_depth[VIDEO_MAX_PLANES];
+ u8 num_planes;
+ u8 walign;
+ u8 halign;
+ u8 salign;
+ u32 flags;
+ u32 buffer_size;
+};
+
+struct mtk_dip_pipe_job_info {
+ int id;
+ struct mtk_dip_dev_buffer*
+ buf_map[MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM];
+ struct list_head list;
+};
+
+struct mtk_dip_dev_buffer {
+ struct vb2_v4l2_buffer vbb;
+ struct v4l2_format fmt;
+ const struct mtk_dip_dev_format *dev_fmt;
+ int pipe_job_id;
+ dma_addr_t isp_daddr[VB2_MAX_PLANES];
+ dma_addr_t scp_daddr[VB2_MAX_PLANES];
+ unsigned int dma_port;
+ int rotation;
+ struct list_head list;
+};
+
+struct mtk_dip_pipe_desc {
+ char *name;
+ int master;
+ int id;
+ const struct mtk_dip_video_device_desc *output_queue_descs;
+ int total_output_queues;
+ const struct mtk_dip_video_device_desc *capture_queue_descs;
+ int total_capture_queues;
+};
+
+struct mtk_dip_video_device_desc {
+ int id;
+ char *name;
+ u32 buf_type;
+ u32 cap;
+ int smem_alloc;
+ const struct mtk_dip_dev_format *fmts;
+ int num_fmts;
+ char *description;
+ int default_width;
+ int default_height;
+ unsigned int dma_port;
+ const struct v4l2_frmsizeenum *frmsizeenum;
+ const struct v4l2_ioctl_ops *ops;
+ u32 flags;
+ int default_fmt_idx;
+};
+
+struct mtk_dip_dev_queue {
+ struct vb2_queue vbq;
+ /* Serializes vb2 queue and video device operations */
+ struct mutex lock;
+ const struct mtk_dip_dev_format *dev_fmt;
+ int rotation;
+};
+
+struct mtk_dip_video_device {
+ struct video_device vdev;
+ struct mtk_dip_dev_queue dev_q;
+ struct v4l2_format vdev_fmt;
+ struct media_pad vdev_pad;
+ struct v4l2_mbus_framefmt pad_fmt;
+ struct v4l2_ctrl_handler ctrl_handler;
+ u32 flags;
+ const struct mtk_dip_video_device_desc *desc;
+ atomic_t sequence;
+ struct list_head buf_list;
+ /* protect the in-device buffer list */
+ spinlock_t buf_list_lock;
+};
+
+struct mtk_dip_pipe {
+ struct mtk_dip_dev *dip_dev;
+ struct mtk_dip_video_device nodes[MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM];
+ int num_nodes;
+ atomic_t cnt_nodes_not_streaming;
+ int streaming;
+ struct media_pad *subdev_pads;
+ struct media_pipeline pipeline;
+ struct v4l2_subdev subdev;
+ struct v4l2_subdev_fh *fh;
+ atomic_t pipe_job_sequence;
+ struct list_head pipe_job_pending_list;
+ int num_pending_jobs;
+ struct list_head pipe_job_running_list;
+ int num_jobs;
+ /* Serializes pipe's stream on/off and buffers enqueue operations */
+ struct mutex lock;
+ spinlock_t job_lock; /* protect the pipe job list */
+ const struct mtk_dip_pipe_desc *desc;
+};
+
+struct mtk_dip_dev {
+ struct platform_device *pdev;
+ struct media_device mdev;
+ struct v4l2_device v4l2_dev;
+ struct mtk_dip_pipe dip_pipe[MTK_DIP_PIPE_ID_TOTAL_NUM];
+ struct mtk_dip_hw dip_hw;
+};
+
+struct mtk_dip_request {
+ struct media_request req;
+ struct mtk_dip_pipe *dip_pipe;
+ struct mtk_dip_pipe_job_info job_info;
+ struct img_frameparam img_fparam;
+ struct work_struct fw_work;
+ struct work_struct mdp_work;
+ /* It is used only in timeout handling flow */
+ struct work_struct mdpcb_work;
+ struct mtk_dip_hw_subframe *working_buf;
+ atomic_t buf_count;
+};
+
+int mtk_dip_dev_media_register(struct device *dev,
+ struct media_device *media_dev,
+ const char *model);
+
+void mtk_dip_dev_v4l2_release(struct mtk_dip_dev *dip_dev);
+
+int mtk_dip_dev_v4l2_register(struct device *dev,
+ struct media_device *media_dev,
+ struct v4l2_device *v4l2_dev);
+
+int mtk_dip_pipe_v4l2_register(struct mtk_dip_pipe *pipe,
+ struct media_device *media_dev,
+ struct v4l2_device *v4l2_dev);
+
+void mtk_dip_pipe_v4l2_unregister(struct mtk_dip_pipe *pipe);
+
+int mtk_dip_pipe_queue_buffers(struct media_request *req, int initial);
+
+int mtk_dip_pipe_init(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_dev *dip_dev,
+ const struct mtk_dip_pipe_desc *setting,
+ struct media_device *media_dev,
+ struct v4l2_device *v4l2_dev);
+
+void mtk_dip_pipe_ipi_params_config(struct mtk_dip_request *req);
+
+int mtk_dip_pipe_release(struct mtk_dip_pipe *pipe);
+
+struct mtk_dip_pipe_job_info *
+mtk_dip_pipe_get_running_job_info(struct mtk_dip_pipe *pipe,
+ int pipe_job_id);
+
+int mtk_dip_pipe_next_job_id(struct mtk_dip_pipe *pipe);
+
+void mtk_dip_pipe_debug_job(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_pipe_job_info *pipe_job_info);
+
+int mtk_dip_pipe_job_finish(struct mtk_dip_pipe *pipe,
+ unsigned int pipe_job_info_id,
+ enum vb2_buffer_state state);
+
+const struct mtk_dip_dev_format *
+mtk_dip_pipe_find_fmt(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ u32 format);
+
+void mtk_dip_pipe_set_img_fmt(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ struct v4l2_pix_format_mplane *mfmt_to_fill,
+ const struct mtk_dip_dev_format *dev_fmt);
+
+void mtk_dip_pipe_load_default_fmt(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ struct v4l2_format *fmt_to_fill);
+
+void mtk_dip_pipe_try_enqueue(struct mtk_dip_pipe *pipe);
+
+void mtk_dip_hw_enqueue(struct mtk_dip_hw *dip_hw, struct mtk_dip_request *req);
+
+int mtk_dip_hw_streamoff(struct mtk_dip_pipe *pipe);
+
+int mtk_dip_hw_streamon(struct mtk_dip_pipe *pipe);
+
+static inline struct mtk_dip_pipe*
+mtk_dip_dev_get_pipe(struct mtk_dip_dev *dip_dev, unsigned int pipe_id)
+{
+ if (pipe_id < 0 && pipe_id >= MTK_DIP_PIPE_ID_TOTAL_NUM)
+ return NULL;
+
+ return &dip_dev->dip_pipe[pipe_id];
+}
+
+static inline struct mtk_dip_video_device*
+mtk_dip_file_to_node(struct file *file)
+{
+ return container_of(video_devdata(file),
+ struct mtk_dip_video_device, vdev);
+}
+
+static inline struct mtk_dip_pipe*
+mtk_dip_subdev_to_pipe(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct mtk_dip_pipe, subdev);
+}
+
+static inline struct mtk_dip_dev*
+mtk_dip_mdev_to_dev(struct media_device *mdev)
+{
+ return container_of(mdev, struct mtk_dip_dev, mdev);
+}
+
+static inline struct mtk_dip_video_device*
+mtk_dip_vbq_to_node(struct vb2_queue *vq)
+{
+ return container_of(vq, struct mtk_dip_video_device, dev_q.vbq);
+}
+
+static inline struct mtk_dip_dev_buffer*
+mtk_dip_vb2_buf_to_dev_buf(struct vb2_buffer *vb)
+{
+ return container_of(vb, struct mtk_dip_dev_buffer, vbb.vb2_buf);
+}
+
+static inline struct mtk_dip_dev *mtk_dip_hw_to_dev(struct mtk_dip_hw *dip_hw)
+{
+ return container_of(dip_hw, struct mtk_dip_dev, dip_hw);
+}
+
+static inline struct mtk_dip_request*
+mtk_dip_pipe_job_info_to_req(struct mtk_dip_pipe_job_info *job_info)
+{
+ return container_of(job_info, struct mtk_dip_request, job_info);
+}
+
+static inline struct mtk_dip_request*
+mtk_dip_hw_fw_work_to_req(struct work_struct *fw_work)
+{
+ return container_of(fw_work, struct mtk_dip_request, fw_work);
+}
+
+static inline struct mtk_dip_request*
+mtk_dip_hw_mdp_work_to_req(struct work_struct *mdp_work)
+{
+ return container_of(mdp_work, struct mtk_dip_request, mdp_work);
+}
+
+static inline struct mtk_dip_request *
+mtk_dip_hw_mdpcb_work_to_req(struct work_struct *mdpcb_work)
+{
+ return container_of(mdpcb_work, struct mtk_dip_request, mdpcb_work);
+}
+
+static inline int mtk_dip_buf_is_meta(u32 type)
+{
+ return type == V4L2_BUF_TYPE_META_CAPTURE ||
+ type == V4L2_BUF_TYPE_META_OUTPUT;
+}
+
+static inline int mtk_dip_pipe_get_pipe_from_job_id(int pipe_job_id)
+{
+ return (pipe_job_id >> 16) & 0x0000FFFF;
+}
+
+#endif /* _MTK_DIP_DEV_H_ */
diff --git a/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-hw.h b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-hw.h
new file mode 100644
index 000000000000..2dd4dae4336c
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-hw.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ *
+ * Author: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ * Holmes Chiou <holmes.chiou-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ *
+ */
+
+#ifndef _MTK_DIP_HW_H_
+#define _MTK_DIP_HW_H_
+
+#include <linux/clk.h>
+#include "mtk-img-ipi.h"
+
+#define DIP_COMPOSER_THREAD_TIMEOUT 16U
+#define DIP_COMPOSING_MAX_NUM 3
+#define DIP_MAX_ERR_COUNT 188U
+#define DIP_FLUSHING_WQ_TIMEOUT (16U * DIP_MAX_ERR_COUNT)
+
+#define DIP_FRM_SZ (76 * 1024)
+#define DIP_SUB_FRM_SZ (16 * 1024)
+#define DIP_TUNING_SZ (32 * 1024)
+#define DIP_COMP_SZ (24 * 1024)
+#define DIP_FRAMEPARAM_SZ (4 * 1024)
+
+#define DIP_TUNING_OFFSET DIP_SUB_FRM_SZ
+#define DIP_COMP_OFFSET (DIP_TUNING_OFFSET + DIP_TUNING_SZ)
+#define DIP_FRAMEPARAM_OFFSET (DIP_COMP_OFFSET + DIP_COMP_SZ)
+#define DIP_SUB_FRM_DATA_NUM 32
+#define DIP_SCP_WORKINGBUF_OFFSET (5 * 1024 * 1024)
+#define DIP_V4l2_META_BUF_OFFSET (DIP_SCP_WORKINGBUF_OFFSET + \
+ DIP_SUB_FRM_DATA_NUM * DIP_FRM_SZ)
+
+#define MTK_DIP_CLK_NUM 2
+
+enum STREAM_TYPE_ENUM {
+ STREAM_UNKNOWN,
+ STREAM_BITBLT,
+ STREAM_GPU_BITBLT,
+ STREAM_DUAL_BITBLT,
+ STREAM_2ND_BITBLT,
+ STREAM_ISP_IC,
+ STREAM_ISP_VR,
+ STREAM_ISP_ZSD,
+ STREAM_ISP_IP,
+ STREAM_ISP_VSS,
+ STREAM_ISP_ZSD_SLOW,
+ STREAM_WPE,
+ STREAM_WPE2,
+};
+
+enum mtk_dip_hw_user_state {
+ DIP_STATE_INIT = 0,
+ DIP_STATE_OPENED,
+ DIP_STATE_STREAMON,
+ DIP_STATE_STREAMOFF
+};
+
+struct mtk_dip_hw_working_buf {
+ dma_addr_t scp_daddr;
+ void *vaddr;
+ dma_addr_t isp_daddr;
+};
+
+struct mtk_dip_hw_subframe {
+ struct mtk_dip_hw_working_buf buffer;
+ int size;
+ struct mtk_dip_hw_working_buf config_data;
+ struct mtk_dip_hw_working_buf tuning_buf;
+ struct mtk_dip_hw_working_buf frameparam;
+ struct list_head list_entry;
+};
+
+enum frame_state {
+ FRAME_STATE_INIT = 0,
+ FRAME_STATE_COMPOSING,
+ FRAME_STATE_RUNNING,
+ FRAME_STATE_DONE,
+ FRAME_STATE_STREAMOFF,
+ FRAME_STATE_ERROR,
+ FRAME_STATE_HW_TIMEOUT
+};
+
+struct mtk_dip_hw_working_buf_list {
+ struct list_head list;
+ u32 cnt;
+ spinlock_t lock; /* protect the list and cnt */
+
+};
+
+struct mtk_dip_hw {
+ struct clk_bulk_data clks[MTK_DIP_CLK_NUM];
+ struct workqueue_struct *composer_wq;
+ struct workqueue_struct *mdp_wq;
+ wait_queue_head_t working_buf_wq;
+ wait_queue_head_t flushing_wq;
+ wait_queue_head_t flushing_hw_wq;
+ atomic_t num_composing; /* increase after ipi */
+ /* increase after calling MDP driver */
+ atomic_t num_running;
+ /*MDP/GCE callback workqueue */
+ struct workqueue_struct *mdpcb_workqueue;
+ /* for MDP driver */
+ struct platform_device *mdp_pdev;
+ /* for SCP driver */
+ struct platform_device *scp_pdev;
+ struct rproc *rproc_handle;
+ struct mtk_dip_hw_working_buf_list dip_freebufferlist;
+ struct mtk_dip_hw_working_buf_list dip_usedbufferlist;
+ dma_addr_t working_buf_mem_scp_daddr;
+ void *working_buf_mem_vaddr;
+ dma_addr_t working_buf_mem_isp_daddr;
+ int working_buf_mem_size;
+ struct mtk_dip_hw_subframe working_buf[DIP_SUB_FRM_DATA_NUM];
+ /* increase after enqueue */
+ atomic_t dip_enque_cnt;
+ /* increase after stream on, decrease when stream off */
+ atomic_t dip_stream_cnt;
+ /* To serialize request opertion to DIP co-procrosser and hadrware */
+ struct mutex hw_op_lock;
+ /* To restrict the max number of request be send to SCP */
+ struct semaphore sem;
+};
+
+static inline void
+mtk_dip_wbuf_to_ipi_img_sw_addr(struct img_sw_addr *ipi_addr,
+ struct mtk_dip_hw_working_buf *wbuf)
+{
+ ipi_addr->va = (u64)wbuf->vaddr;
+ ipi_addr->pa = (u32)wbuf->scp_daddr;
+}
+
+static inline void
+mtk_dip_wbuf_to_ipi_img_addr(struct img_addr *ipi_addr,
+ struct mtk_dip_hw_working_buf *wbuf)
+{
+ ipi_addr->va = (u64)wbuf->vaddr;
+ ipi_addr->pa = (u32)wbuf->scp_daddr;
+ ipi_addr->iova = (u32)wbuf->isp_daddr;
+}
+
+static inline void
+mtk_dip_wbuf_to_ipi_tuning_addr(struct tuning_addr *ipi_addr,
+ struct mtk_dip_hw_working_buf *wbuf)
+{
+ ipi_addr->pa = (u32)wbuf->scp_daddr;
+ ipi_addr->iova = (u32)wbuf->isp_daddr;
+}
+
+int mtk_dip_hw_working_buf_pool_init(struct mtk_dip_hw *dip_hw);
+void mtk_dip_hw_working_buf_pool_release(struct mtk_dip_hw *dip_hw);
+
+#endif /* _MTK_DIP_HW_H_ */
+
diff --git a/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-sys.c b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-sys.c
new file mode 100644
index 000000000000..603be116b03f
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-sys.c
@@ -0,0 +1,794 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ *
+ * Author: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ * Holmes Chiou <holmes.chiou-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/dma-iommu.h>
+#include <linux/freezer.h>
+#include <linux/platform_data/mtk_scp.h>
+#include <linux/pm_runtime.h>
+#include <linux/remoteproc.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include "mtk-mdp3-cmdq.h"
+#include "mtk_dip-dev.h"
+#include "mtk_dip-hw.h"
+
+int mtk_dip_hw_working_buf_pool_init(struct mtk_dip_hw *dip_hw)
+{
+ int i;
+ struct mtk_dip_dev *dip_dev = mtk_dip_hw_to_dev(dip_hw);
+ const int working_buf_size = round_up(DIP_FRM_SZ, PAGE_SIZE);
+ phys_addr_t working_buf_paddr;
+
+ INIT_LIST_HEAD(&dip_hw->dip_freebufferlist.list);
+ spin_lock_init(&dip_hw->dip_freebufferlist.lock);
+ dip_hw->dip_freebufferlist.cnt = 0;
+
+ INIT_LIST_HEAD(&dip_hw->dip_usedbufferlist.list);
+ spin_lock_init(&dip_hw->dip_usedbufferlist.lock);
+ dip_hw->dip_usedbufferlist.cnt = 0;
+
+ init_waitqueue_head(&dip_hw->working_buf_wq);
+
+ dip_hw->working_buf_mem_size = DIP_SUB_FRM_DATA_NUM * working_buf_size +
+ DIP_SCP_WORKINGBUF_OFFSET;
+ dip_hw->working_buf_mem_vaddr =
+ dma_alloc_coherent(&dip_hw->scp_pdev->dev,
+ dip_hw->working_buf_mem_size,
+ &dip_hw->working_buf_mem_scp_daddr,
+ GFP_KERNEL);
+ if (!dip_hw->working_buf_mem_vaddr) {
+ dev_err(&dip_dev->pdev->dev,
+ "memory alloc size %ld failed\n",
+ dip_hw->working_buf_mem_size);
+ return -ENOMEM;
+ }
+
+ /*
+ * We got the incorrect physical address mapped when
+ * using dma_map_single() so I used dma_map_page_attrs()
+ * directly to workaround here.
+ *
+ * When I use dma_map_single() to map the address, the
+ * physical address retrieved back with iommu_get_domain_for_dev()
+ * and iommu_iova_to_phys() was not equal to the
+ * SCP dma address (it should be the same as the physical address
+ * since we don't have iommu), and was shifted by 0x4000000.
+ */
+ working_buf_paddr = dip_hw->working_buf_mem_scp_daddr;
+ dip_hw->working_buf_mem_isp_daddr =
+ dma_map_page_attrs(&dip_dev->pdev->dev,
+ phys_to_page(working_buf_paddr),
+ 0, dip_hw->working_buf_mem_size,
+ DMA_BIDIRECTIONAL,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ if (dma_mapping_error(&dip_dev->pdev->dev,
+ dip_hw->working_buf_mem_isp_daddr)) {
+ dev_err(&dip_dev->pdev->dev,
+ "failed to map buffer: s_daddr(%pad)\n",
+ &dip_hw->working_buf_mem_scp_daddr);
+ dma_free_coherent(&dip_hw->scp_pdev->dev,
+ dip_hw->working_buf_mem_size,
+ dip_hw->working_buf_mem_vaddr,
+ dip_hw->working_buf_mem_scp_daddr);
+
+ return -ENOMEM;
+ }
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: working_buf_mem: vaddr(%p), scp_daddr(%pad)\n",
+ __func__,
+ dip_hw->working_buf_mem_vaddr,
+ &dip_hw->working_buf_mem_scp_daddr);
+
+ for (i = 0; i < DIP_SUB_FRM_DATA_NUM; i++) {
+ struct mtk_dip_hw_subframe *buf = &dip_hw->working_buf[i];
+
+ /*
+ * Total: 0 ~ 72 KB
+ * SubFrame: 0 ~ 16 KB
+ */
+ buf->buffer.scp_daddr = dip_hw->working_buf_mem_scp_daddr +
+ DIP_SCP_WORKINGBUF_OFFSET + i * working_buf_size;
+ buf->buffer.vaddr = dip_hw->working_buf_mem_vaddr +
+ DIP_SCP_WORKINGBUF_OFFSET + i * working_buf_size;
+ buf->buffer.isp_daddr = dip_hw->working_buf_mem_isp_daddr +
+ DIP_SCP_WORKINGBUF_OFFSET + i * working_buf_size;
+ buf->size = working_buf_size;
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: buf(%d), scp_daddr(%pad), isp_daddr(%pad)\n",
+ __func__, i, &buf->buffer.scp_daddr,
+ &buf->buffer.isp_daddr);
+
+ /* Tuning: 16 ~ 48 KB */
+ buf->tuning_buf.scp_daddr =
+ buf->buffer.scp_daddr + DIP_TUNING_OFFSET;
+ buf->tuning_buf.vaddr =
+ buf->buffer.vaddr + DIP_TUNING_OFFSET;
+ buf->tuning_buf.isp_daddr =
+ buf->buffer.isp_daddr + DIP_TUNING_OFFSET;
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: tuning_buf(%d), scp_daddr(%pad), isp_daddr(%pad)\n",
+ __func__, i, &buf->tuning_buf.scp_daddr,
+ &buf->tuning_buf.isp_daddr);
+
+ /* Config_data: 48 ~ 72 KB */
+ buf->config_data.scp_daddr =
+ buf->buffer.scp_daddr + DIP_COMP_OFFSET;
+ buf->config_data.vaddr = buf->buffer.vaddr + DIP_COMP_OFFSET;
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: config_data(%d), scp_daddr(%pad), vaddr(%p)\n",
+ __func__, i, &buf->config_data.scp_daddr,
+ buf->config_data.vaddr);
+
+ /* Frame parameters: 72 ~ 76 KB */
+ buf->frameparam.scp_daddr =
+ buf->buffer.scp_daddr + DIP_FRAMEPARAM_OFFSET;
+ buf->frameparam.vaddr =
+ buf->buffer.vaddr + DIP_FRAMEPARAM_OFFSET;
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: frameparam(%d), scp_daddr(%pad), vaddr(%p)\n",
+ __func__, i, &buf->frameparam.scp_daddr,
+ buf->frameparam.vaddr);
+
+ list_add_tail(&buf->list_entry,
+ &dip_hw->dip_freebufferlist.list);
+ dip_hw->dip_freebufferlist.cnt++;
+ }
+
+ return 0;
+}
+
+void mtk_dip_hw_working_buf_pool_release(struct mtk_dip_hw *dip_hw)
+{
+ /* All the buffer should be in the freebufferlist when release */
+ struct mtk_dip_dev *dip_dev = mtk_dip_hw_to_dev(dip_hw);
+ u32 i;
+
+ if (dip_hw->dip_usedbufferlist.cnt)
+ dev_warn(&dip_dev->pdev->dev,
+ "%s: dip_usedbufferlist is not empty (%d/%d)\n",
+ __func__, dip_hw->dip_usedbufferlist.cnt, i);
+
+ dma_unmap_page_attrs(&dip_dev->pdev->dev,
+ dip_hw->working_buf_mem_isp_daddr,
+ dip_hw->working_buf_mem_size, DMA_BIDIRECTIONAL,
+ DMA_ATTR_SKIP_CPU_SYNC);
+
+ dma_free_coherent(&dip_hw->scp_pdev->dev, dip_hw->working_buf_mem_size,
+ dip_hw->working_buf_mem_vaddr,
+ dip_hw->working_buf_mem_scp_daddr);
+}
+
+static void mtk_dip_hw_working_buf_free(struct mtk_dip_hw *dip_hw,
+ struct mtk_dip_hw_subframe *working_buf)
+{
+ struct mtk_dip_dev *dip_dev = mtk_dip_hw_to_dev(dip_hw);
+
+ if (!working_buf)
+ return;
+
+ spin_lock(&dip_hw->dip_usedbufferlist.lock);
+ list_del(&working_buf->list_entry);
+ dip_hw->dip_usedbufferlist.cnt--;
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: Free used buffer(%pad)\n",
+ __func__, &working_buf->buffer.scp_daddr);
+ spin_unlock(&dip_hw->dip_usedbufferlist.lock);
+
+ spin_lock(&dip_hw->dip_freebufferlist.lock);
+ list_add_tail(&working_buf->list_entry,
+ &dip_hw->dip_freebufferlist.list);
+ dip_hw->dip_freebufferlist.cnt++;
+ spin_unlock(&dip_hw->dip_freebufferlist.lock);
+}
+
+static struct mtk_dip_hw_subframe*
+mtk_dip_hw_working_buf_alloc(struct mtk_dip_hw *dip_hw)
+{
+ struct mtk_dip_hw_subframe *working_buf;
+
+ spin_lock(&dip_hw->dip_freebufferlist.lock);
+ if (list_empty(&dip_hw->dip_freebufferlist.list)) {
+ spin_unlock(&dip_hw->dip_freebufferlist.lock);
+ return NULL;
+ }
+
+ working_buf = list_first_entry(&dip_hw->dip_freebufferlist.list,
+ struct mtk_dip_hw_subframe, list_entry);
+ list_del(&working_buf->list_entry);
+ dip_hw->dip_freebufferlist.cnt--;
+ spin_unlock(&dip_hw->dip_freebufferlist.lock);
+
+ spin_lock(&dip_hw->dip_usedbufferlist.lock);
+ list_add_tail(&working_buf->list_entry,
+ &dip_hw->dip_usedbufferlist.list);
+ dip_hw->dip_usedbufferlist.cnt++;
+ spin_unlock(&dip_hw->dip_usedbufferlist.lock);
+
+ return working_buf;
+}
+
+static void mtk_dip_notify(struct mtk_dip_request *req)
+{
+ struct mtk_dip_hw *dip_hw = &req->dip_pipe->dip_dev->dip_hw;
+ struct mtk_dip_dev *dip_dev = req->dip_pipe->dip_dev;
+ struct mtk_dip_pipe *pipe = req->dip_pipe;
+ struct img_ipi_frameparam *iparam = &req->img_fparam.frameparam;
+ enum vb2_buffer_state vbf_state;
+ int ret;
+
+ if (iparam->state != FRAME_STATE_HW_TIMEOUT)
+ vbf_state = VB2_BUF_STATE_DONE;
+ else
+ vbf_state = VB2_BUF_STATE_ERROR;
+
+ ret = mtk_dip_pipe_job_finish(pipe, iparam->index, vbf_state);
+ if (ret)
+ dev_dbg(&dip_dev->pdev->dev, "%s: finish CB failed(%d)\n",
+ __func__, ret);
+
+ pm_runtime_put_autosuspend(&pipe->dip_dev->pdev->dev);
+
+ mtk_dip_hw_working_buf_free(dip_hw, req->working_buf);
+ req->working_buf = NULL;
+ wake_up(&dip_hw->working_buf_wq);
+ wake_up(&dip_hw->flushing_wq);
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s:%s: job id(%d), frame_no(%d) finished\n",
+ __func__, pipe->desc->name, iparam->index, iparam->frame_no);
+}
+
+static void mdp_cb_timeout_worker(struct work_struct *work)
+{
+ struct mtk_dip_request *req = mtk_dip_hw_mdpcb_work_to_req(work);
+ struct img_ipi_param ipi_param;
+
+ dev_dbg(&req->dip_pipe->dip_dev->pdev->dev,
+ "%s: send frame no(%d) HW timeout dbg IPI\n",
+ __func__, req->img_fparam.frameparam.frame_no);
+
+ ipi_param.usage = IMG_IPI_DEBUG;
+ scp_ipi_send(req->dip_pipe->dip_dev->dip_hw.scp_pdev, SCP_IPI_DIP,
+ (void *)&ipi_param, sizeof(ipi_param), 0);
+ mtk_dip_notify(req);
+}
+
+/* Maybe in IRQ context of cmdq */
+static void dip_mdp_cb_func(struct cmdq_cb_data data)
+{
+ struct mtk_dip_request *req;
+ struct mtk_dip_dev *dip_dev;
+
+ if (!data.data) {
+ pr_err("%s: data->data is NULL\n",
+ __func__);
+ return;
+ }
+
+ req = data.data;
+ dip_dev = req->dip_pipe->dip_dev;
+
+ dev_dbg(&dip_dev->pdev->dev, "%s: req(%p), idx(%d), no(%d), s(%d), n_in(%d), n_out(%d)\n",
+ __func__,
+ req,
+ req->img_fparam.frameparam.index,
+ req->img_fparam.frameparam.frame_no,
+ req->img_fparam.frameparam.state,
+ req->img_fparam.frameparam.num_inputs,
+ req->img_fparam.frameparam.num_outputs);
+
+ if (data.sta != CMDQ_CB_NORMAL) {
+ dev_err(&dip_dev->pdev->dev, "%s: frame no(%d) HW timeout\n",
+ __func__, req->img_fparam.frameparam.frame_no);
+ req->img_fparam.frameparam.state = FRAME_STATE_HW_TIMEOUT;
+ INIT_WORK(&req->mdpcb_work, mdp_cb_timeout_worker);
+ queue_work(req->dip_pipe->dip_dev->dip_hw.mdpcb_workqueue,
+ &req->mdpcb_work);
+ } else {
+ mtk_dip_notify(req);
+ }
+}
+
+static void dip_runner_func(struct work_struct *work)
+{
+ struct mtk_dip_request *req = mtk_dip_hw_mdp_work_to_req(work);
+ struct mtk_dip_dev *dip_dev = req->dip_pipe->dip_dev;
+ struct img_config *config_data =
+ (struct img_config *)req->img_fparam.frameparam.config_data.va;
+ u32 num;
+
+ /*
+ * Call MDP/GCE API to do HW excecution
+ * Pass the framejob to MDP driver
+ */
+ req->img_fparam.frameparam.state = FRAME_STATE_COMPOSING;
+ num = atomic_inc_return(&dip_dev->dip_hw.num_running);
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s,MDP running num(%d)\n", __func__, num);
+ mdp_cmdq_sendtask(dip_dev->dip_hw.mdp_pdev,
+ config_data,
+ &req->img_fparam.frameparam,
+ NULL,
+ false,
+ dip_mdp_cb_func,
+ req);
+}
+
+static void dip_scp_handler(void *data, unsigned int len, void *priv)
+{
+ struct mtk_dip_pipe_job_info *pipe_job_info;
+ struct mtk_dip_pipe *pipe;
+ int pipe_id;
+ struct mtk_dip_request *req;
+ struct img_ipi_frameparam *frameparam;
+ struct mtk_dip_dev *dip_dev = (struct mtk_dip_dev *)priv;
+ struct mtk_dip_hw *dip_hw = &dip_dev->dip_hw;
+ struct img_ipi_param *ipi_param;
+ u32 num;
+ int ret;
+
+ if (WARN_ONCE(!data, "%s: failed due to NULL data\n", __func__))
+ return;
+
+ ipi_param = (struct img_ipi_param *)data;
+
+ if (ipi_param->usage != IMG_IPI_FRAME) {
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: recevied SCP_IPI_DIP ACK, ipi_param.usage(%d)\n",
+ __func__, ipi_param->usage);
+ return;
+ }
+
+ frameparam = (struct img_ipi_frameparam *)ipi_param->frm_param.va;
+ pipe_id = mtk_dip_pipe_get_pipe_from_job_id(frameparam->index);
+ pipe = mtk_dip_dev_get_pipe(dip_dev, pipe_id);
+
+ if (!pipe) {
+ dev_warn(&dip_dev->pdev->dev,
+ "%s: get invalid img_ipi_frameparam index(%d) from firmware\n",
+ __func__, frameparam->frame_no);
+ return;
+ }
+
+ pipe_job_info = mtk_dip_pipe_get_running_job_info(pipe,
+ frameparam->index);
+
+ if (WARN_ONCE(!pipe_job_info, "%s: frame_no(%d) is lost\n",
+ __func__, frameparam->frame_no))
+ return;
+
+ req = mtk_dip_pipe_job_info_to_req(pipe_job_info);
+ memcpy(&req->img_fparam.frameparam, frameparam,
+ sizeof(req->img_fparam.frameparam));
+ num = atomic_dec_return(&dip_hw->num_composing);
+ down(&dip_hw->sem);
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: frame_no(%d) is back, composing num(%d)\n",
+ __func__, frameparam->frame_no, num);
+
+ wake_up(&dip_dev->dip_hw.flushing_hw_wq);
+
+ INIT_WORK(&req->mdp_work, dip_runner_func);
+ ret = queue_work(dip_hw->mdp_wq, &req->mdp_work);
+ if (!ret) {
+ dev_dbg(&dip_dev->pdev->dev,
+ "frame_no(%d) queue_work failed to mdp_wq: %d\n",
+ frameparam->frame_no, ret);
+ }
+}
+
+static bool
+mtk_dip_hw_is_working_buf_allocated(struct mtk_dip_request *req)
+{
+ req->working_buf =
+ mtk_dip_hw_working_buf_alloc(&req->dip_pipe->dip_dev->dip_hw);
+
+ if (!req->working_buf) {
+ dev_dbg(&req->dip_pipe->dip_dev->pdev->dev,
+ "%s:%s:req(%p): no free working buffer available\n",
+ __func__, req->dip_pipe->desc->name, req);
+ return false;
+ }
+
+ return true;
+}
+
+static void dip_submit_worker(struct work_struct *work)
+{
+ struct mtk_dip_request *req = mtk_dip_hw_fw_work_to_req(work);
+ struct mtk_dip_hw *dip_hw = &req->dip_pipe->dip_dev->dip_hw;
+ struct mtk_dip_dev *dip_dev = req->dip_pipe->dip_dev;
+ struct img_ipi_param ipi_param;
+ struct mtk_dip_hw_subframe *buf;
+ int ret;
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: to send frame_no(%d),num(%d)\n",
+ __func__, req->img_fparam.frameparam.frame_no,
+ atomic_read(&dip_hw->num_composing));
+
+ wait_event_freezable(dip_hw->working_buf_wq,
+ mtk_dip_hw_is_working_buf_allocated(req));
+ up(&dip_hw->sem);
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: wakeup frame_no(%d),num(%d)\n",
+ __func__, req->img_fparam.frameparam.frame_no,
+ atomic_read(&dip_hw->num_composing));
+
+ buf = req->working_buf;
+ mtk_dip_wbuf_to_ipi_img_addr(&req->img_fparam.frameparam.subfrm_data,
+ &buf->buffer);
+ memset(buf->buffer.vaddr, 0, DIP_SUB_FRM_SZ);
+ mtk_dip_wbuf_to_ipi_img_sw_addr(&req->img_fparam.frameparam.config_data,
+ &buf->config_data);
+ memset(buf->config_data.vaddr, 0, DIP_COMP_SZ);
+
+ if (!req->img_fparam.frameparam.tuning_data.present) {
+ /*
+ * When user enqueued without tuning buffer,
+ * it would use driver internal buffer.
+ */
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: frame_no(%d) has no tuning_data\n",
+ __func__, req->img_fparam.frameparam.frame_no);
+
+ mtk_dip_wbuf_to_ipi_tuning_addr
+ (&req->img_fparam.frameparam.tuning_data,
+ &buf->tuning_buf);
+ memset(buf->tuning_buf.vaddr, 0, DIP_TUNING_SZ);
+ }
+
+ req->img_fparam.frameparam.state = FRAME_STATE_COMPOSING;
+ mtk_dip_wbuf_to_ipi_img_sw_addr(&req->img_fparam.frameparam.self_data,
+ &buf->frameparam);
+ memcpy(buf->frameparam.vaddr, &req->img_fparam.frameparam,
+ sizeof(req->img_fparam.frameparam));
+
+ ipi_param.usage = IMG_IPI_FRAME;
+ mtk_dip_wbuf_to_ipi_img_sw_addr(&ipi_param.frm_param,
+ &buf->frameparam);
+
+ mutex_lock(&dip_hw->hw_op_lock);
+ atomic_inc(&dip_hw->num_composing);
+ ret = scp_ipi_send(dip_hw->scp_pdev, SCP_IPI_DIP, &ipi_param,
+ sizeof(ipi_param), 0);
+ if (ret)
+ dev_dbg(&dip_dev->pdev->dev, "%s: send SCP_IPI_DIP_FRAME failed %d\n",
+ __func__, ret);
+ mutex_unlock(&dip_hw->hw_op_lock);
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: frame_no(%d),idx(0x%x), composing num(%d)\n",
+ __func__, req->img_fparam.frameparam.frame_no,
+ req->img_fparam.frameparam.index,
+ atomic_read(&dip_hw->num_composing));
+}
+
+static int mtk_dip_hw_res_init(struct mtk_dip_hw *dip_hw)
+{
+ struct mtk_dip_dev *dip_dev = mtk_dip_hw_to_dev(dip_hw);
+ int ret;
+
+ dip_hw->mdp_pdev = mdp_get_plat_device(dip_dev->pdev);
+ if (!dip_hw->mdp_pdev) {
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: failed to get MDP device\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ dip_hw->mdpcb_workqueue =
+ alloc_ordered_workqueue("%s",
+ __WQ_LEGACY | WQ_MEM_RECLAIM |
+ WQ_FREEZABLE,
+ "mdp_callback");
+ if (!dip_hw->mdpcb_workqueue) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: unable to alloc mdpcb workqueue\n", __func__);
+ ret = -ENOMEM;
+ goto destroy_mdpcb_wq;
+ }
+
+ dip_hw->composer_wq =
+ alloc_ordered_workqueue("%s",
+ __WQ_LEGACY | WQ_MEM_RECLAIM |
+ WQ_FREEZABLE,
+ "dip_composer");
+ if (!dip_hw->composer_wq) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: unable to alloc composer workqueue\n", __func__);
+ ret = -ENOMEM;
+ goto destroy_dip_composer_wq;
+ }
+
+ dip_hw->mdp_wq =
+ alloc_ordered_workqueue("%s",
+ __WQ_LEGACY | WQ_MEM_RECLAIM |
+ WQ_FREEZABLE,
+ "dip_runner");
+ if (!dip_hw->mdp_wq) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: unable to alloc dip_runner\n", __func__);
+ ret = -ENOMEM;
+ goto destroy_dip_runner_wq;
+ }
+
+ init_waitqueue_head(&dip_hw->flushing_wq);
+ init_waitqueue_head(&dip_hw->flushing_hw_wq);
+
+ return 0;
+
+destroy_dip_runner_wq:
+ destroy_workqueue(dip_hw->mdp_wq);
+
+destroy_dip_composer_wq:
+ destroy_workqueue(dip_hw->composer_wq);
+
+destroy_mdpcb_wq:
+ destroy_workqueue(dip_hw->mdpcb_workqueue);
+
+ return ret;
+}
+
+static int mtk_dip_hw_res_release(struct mtk_dip_dev *dip_dev)
+{
+ flush_workqueue(dip_dev->dip_hw.mdp_wq);
+ destroy_workqueue(dip_dev->dip_hw.mdp_wq);
+ dip_dev->dip_hw.mdp_wq = NULL;
+
+ flush_workqueue(dip_dev->dip_hw.mdpcb_workqueue);
+ destroy_workqueue(dip_dev->dip_hw.mdpcb_workqueue);
+ dip_dev->dip_hw.mdpcb_workqueue = NULL;
+
+ flush_workqueue(dip_dev->dip_hw.composer_wq);
+ destroy_workqueue(dip_dev->dip_hw.composer_wq);
+ dip_dev->dip_hw.composer_wq = NULL;
+
+ atomic_set(&dip_dev->dip_hw.num_composing, 0);
+ atomic_set(&dip_dev->dip_hw.num_running, 0);
+ atomic_set(&dip_dev->dip_hw.dip_enque_cnt, 0);
+
+ return 0;
+}
+
+static bool mtk_dip_pipe_running_job_list_empty(struct mtk_dip_pipe *pipe,
+ int *num)
+{
+ spin_lock(&pipe->job_lock);
+ *num = pipe->num_jobs;
+ spin_unlock(&pipe->job_lock);
+
+ return !(*num);
+}
+
+static int mtk_dip_hw_flush_pipe_jobs(struct mtk_dip_pipe *pipe)
+{
+ int num;
+ int ret = wait_event_freezable_timeout
+ (pipe->dip_dev->dip_hw.flushing_wq,
+ mtk_dip_pipe_running_job_list_empty(pipe, &num),
+ msecs_to_jiffies(DIP_FLUSHING_WQ_TIMEOUT));
+
+ if (!ret && num) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s: flushing is aborted, num(%d)\n",
+ __func__, num);
+ return -EINVAL;
+ }
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s: wakeup num(%d)\n", __func__, num);
+ return 0;
+}
+
+static int mtk_dip_hw_connect(struct mtk_dip_dev *dip_dev)
+{
+ int ret;
+ struct img_ipi_param ipi_param;
+
+ pm_runtime_get_sync(&dip_dev->pdev->dev);
+
+ scp_ipi_register(dip_dev->dip_hw.scp_pdev, SCP_IPI_DIP, dip_scp_handler,
+ dip_dev);
+ memset(&ipi_param, 0, sizeof(ipi_param));
+ ipi_param.usage = IMG_IPI_INIT;
+ ret = scp_ipi_send(dip_dev->dip_hw.scp_pdev, SCP_IPI_DIP, &ipi_param,
+ sizeof(ipi_param), 200);
+ if (ret) {
+ dev_dbg(&dip_dev->pdev->dev, "%s: send SCP_IPI_DIP_FRAME failed %d\n",
+ __func__, ret);
+ return -EBUSY;
+ }
+
+ pm_runtime_put_autosuspend(&dip_dev->pdev->dev);
+
+ ret = mtk_dip_hw_res_init(&dip_dev->dip_hw);
+ if (ret) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: mtk_dip_hw_res_init failed(%d)\n", __func__, ret);
+
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void mtk_dip_hw_disconnect(struct mtk_dip_dev *dip_dev)
+{
+ int ret;
+
+ ret = mtk_dip_hw_res_release(dip_dev);
+ if (ret)
+ dev_err(&dip_dev->pdev->dev,
+ "%s: mtk_dip_hw_res_release failed ret(%d)\n",
+ __func__, ret);
+}
+
+int mtk_dip_hw_streamon(struct mtk_dip_pipe *pipe)
+{
+ struct mtk_dip_dev *dip_dev = pipe->dip_dev;
+ int count, ret;
+
+ mutex_lock(&dip_dev->dip_hw.hw_op_lock);
+ count = atomic_read(&dip_dev->dip_hw.dip_stream_cnt);
+ if (!count) {
+ ret = mtk_dip_hw_connect(pipe->dip_dev);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: pipe(%d) connect to dip_hw failed\n",
+ __func__, pipe->desc->name, pipe->desc->id);
+
+ mutex_unlock(&dip_dev->dip_hw.hw_op_lock);
+
+ return ret;
+ }
+
+ count = atomic_inc_return(&dip_dev->dip_hw.dip_stream_cnt);
+ }
+ mutex_unlock(&dip_dev->dip_hw.hw_op_lock);
+
+ mutex_lock(&pipe->lock);
+ pipe->streaming = 1;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: started stream, id(%d), stream cnt(%d)\n",
+ __func__, pipe->desc->name, pipe->desc->id, count);
+ mtk_dip_pipe_try_enqueue(pipe);
+ mutex_unlock(&pipe->lock);
+
+ return 0;
+}
+
+int mtk_dip_hw_streamoff(struct mtk_dip_pipe *pipe)
+{
+ struct mtk_dip_dev *dip_dev = pipe->dip_dev;
+ struct mtk_dip_pipe_job_info *job_info;
+ struct mtk_dip_pipe_job_info *tmp_job_info;
+ s32 count;
+ int ret;
+
+ mutex_lock(&pipe->lock);
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s:%s: streamoff, removing all running jobs\n",
+ __func__, pipe->desc->name);
+
+ spin_lock(&pipe->job_lock);
+ list_for_each_entry_safe(job_info, tmp_job_info,
+ &pipe->pipe_job_running_list, list) {
+ struct mtk_dip_request *req =
+ mtk_dip_pipe_job_info_to_req(job_info);
+ int i;
+
+ for (i = 0; i < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM; i++) {
+ struct mtk_dip_dev_buffer *dev_buf =
+ job_info->buf_map[i];
+ struct mtk_dip_video_device *node;
+
+ if (!dev_buf)
+ continue;
+
+ node = mtk_dip_vbq_to_node
+ (dev_buf->vbb.vb2_buf.vb2_queue);
+ vb2_buffer_done(&dev_buf->vbb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s:%s: return buf, idx(%d), state(%d)\n",
+ pipe->desc->name, node->desc->name,
+ dev_buf->vbb.vb2_buf.index,
+ VB2_BUF_STATE_ERROR);
+ }
+
+ pipe->num_jobs--;
+ list_del(&job_info->list);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: not run job, id(%d), no(%d), state(%d), job cnt(%d)\n",
+ __func__, pipe->desc->name, job_info->id,
+ req->img_fparam.frameparam.frame_no,
+ VB2_BUF_STATE_ERROR, pipe->num_jobs);
+ }
+ spin_unlock(&pipe->job_lock);
+
+ ret = mtk_dip_hw_flush_pipe_jobs(pipe);
+ if (WARN_ON(ret != 0)) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s:%s: mtk_dip_hw_flush_pipe_jobs, ret(%d)\n",
+ __func__, pipe->desc->name, ret);
+ }
+
+ pipe->streaming = 0;
+ mutex_unlock(&pipe->lock);
+
+ mutex_lock(&dip_dev->dip_hw.hw_op_lock);
+ count = atomic_read(&dip_dev->dip_hw.dip_stream_cnt);
+ if (count > 0 && atomic_dec_and_test(&dip_dev->dip_hw.dip_stream_cnt)) {
+ mtk_dip_hw_disconnect(dip_dev);
+
+ dev_dbg(&dip_dev->pdev->dev, "%s: dip_hw disconnected, stream cnt(%d)\n",
+ __func__, atomic_read(&dip_dev->dip_hw.dip_stream_cnt));
+ }
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: stopped stream id(%d), stream cnt(%d)\n",
+ __func__, pipe->desc->name, pipe->desc->id, count);
+
+ mutex_unlock(&dip_dev->dip_hw.hw_op_lock);
+
+ return 0;
+}
+
+void mtk_dip_hw_enqueue(struct mtk_dip_hw *dip_hw,
+ struct mtk_dip_request *req)
+{
+ struct img_ipi_frameparam *frameparams = &req->img_fparam.frameparam;
+ struct mtk_dip_dev *dip_dev = mtk_dip_hw_to_dev(dip_hw);
+ int ret;
+ struct mtk_dip_dev_buffer **map = req->job_info.buf_map;
+
+ /*
+ * Workaround to pass v4l2 compliance test when it pass only one buffer
+ * to DIP. The check is already done in request_validate().
+ */
+ if (!map[MTK_DIP_VIDEO_NODE_ID_RAW_OUT] ||
+ (!map[MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE] &&
+ !map[MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE])){
+ dev_dbg(&dip_dev->pdev->dev,
+ "won't trigger hw job: raw(%p), mdp0(%p), mdp1(%p)\n",
+ map[MTK_DIP_VIDEO_NODE_ID_RAW_OUT],
+ map[MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE],
+ map[MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE]);
+
+ mtk_dip_pipe_job_finish(req->dip_pipe, req->job_info.id,
+ VB2_BUF_STATE_DONE);
+ return;
+ }
+
+ mtk_dip_pipe_ipi_params_config(req);
+ frameparams->state = FRAME_STATE_INIT;
+ frameparams->frame_no = atomic_inc_return(&dip_hw->dip_enque_cnt);
+
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s: hw job id(%d), frame_no(%d) into worklist\n",
+ __func__, frameparams->index, frameparams->frame_no);
+
+ INIT_WORK(&req->fw_work, dip_submit_worker);
+ pm_runtime_get_sync(&dip_dev->pdev->dev);
+ ret = queue_work(dip_hw->composer_wq, &req->fw_work);
+ if (!ret)
+ dev_dbg(&dip_dev->pdev->dev,
+ "frame_no(%d) queue_work failed, already on a queue\n",
+ frameparams->frame_no, ret);
+}
diff --git a/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-v4l2.c b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-v4l2.c
new file mode 100644
index 000000000000..7b454952566f
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/dip/mtk_dip-v4l2.c
@@ -0,0 +1,1786 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ *
+ * Author: Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_data/mtk_scp.h>
+#include <linux/pm_runtime.h>
+#include <linux/remoteproc.h>
+#include <linux/videodev2.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-event.h>
+#include "mtk_dip-dev.h"
+#include "mtk_dip-hw.h"
+#include "mtk-mdp3-regs.h"
+
+static int mtk_dip_subdev_open(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct mtk_dip_pipe *pipe = mtk_dip_subdev_to_pipe(sd);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev, "%s:%s: pipe(%d) open\n",
+ __func__, pipe->desc->name, pipe->desc->id);
+
+ pipe->fh = fh;
+
+ return 0;
+}
+
+static int mtk_dip_subdev_close(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct mtk_dip_pipe *pipe = mtk_dip_subdev_to_pipe(sd);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev, "%s:%s: pipe(%d) close\n",
+ __func__, pipe->desc->name, pipe->desc->id);
+
+ return 0;
+}
+
+static int mtk_dip_subdev_s_stream(struct v4l2_subdev *sd,
+ int enable)
+{
+ struct mtk_dip_pipe *pipe = mtk_dip_subdev_to_pipe(sd);
+ int ret;
+
+ if (enable) {
+ ret = mtk_dip_hw_streamon(pipe);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: pipe(%d) streamon failed\n",
+ __func__, pipe->desc->name, pipe->desc->id);
+ }
+
+ return ret;
+ }
+
+ ret = mtk_dip_hw_streamoff(pipe);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: pipe(%d) streamon off with errors\n",
+ __func__, pipe->desc->name, pipe->desc->id);
+ }
+
+ return ret;
+}
+
+static int mtk_dip_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote,
+ u32 flags)
+{
+ struct mtk_dip_pipe *pipe =
+ container_of(entity, struct mtk_dip_pipe, subdev.entity);
+ u32 pad = local->index;
+ int count;
+
+ WARN_ON(entity->obj_type != MEDIA_ENTITY_TYPE_V4L2_SUBDEV);
+ WARN_ON(pad >= pipe->num_nodes);
+
+ mutex_lock(&pipe->lock);
+ if (!vb2_start_streaming_called(&pipe->nodes[pad].dev_q.vbq)) {
+ int enable = flags & MEDIA_LNK_FL_ENABLED;
+ int node_enabled =
+ pipe->nodes[pad].flags & MEDIA_LNK_FL_ENABLED;
+
+ if (enable && !node_enabled)
+ atomic_inc_return(&pipe->cnt_nodes_not_streaming);
+
+ if (!enable && node_enabled)
+ atomic_dec_return(&pipe->cnt_nodes_not_streaming);
+ }
+ pipe->nodes[pad].flags &= ~(MEDIA_LNK_FL_ENABLED);
+ pipe->nodes[pad].flags |= MEDIA_LNK_FL_ENABLED & flags;
+ count = atomic_read(&pipe->cnt_nodes_not_streaming);
+ mutex_unlock(&pipe->lock);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s: link setup, flags(0x%x), (%s)%d -->(%s)%d, cnt_nodes_ns(%d)\n",
+ pipe->desc->name, flags, local->entity->name, local->index,
+ remote->entity->name, remote->index, count);
+
+ return 0;
+}
+
+static int mtk_dip_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+ struct mtk_dip_pipe *pipe = vb2_get_drv_priv(vb->vb2_queue);
+ struct mtk_dip_video_device *node = mtk_dip_vbq_to_node(vb->vb2_queue);
+ struct device *dev = &pipe->dip_dev->pdev->dev;
+ const struct v4l2_format *fmt = &node->vdev_fmt;
+ unsigned int size;
+ int i;
+
+ if (vb->vb2_queue->type == V4L2_BUF_TYPE_META_CAPTURE ||
+ vb->vb2_queue->type == V4L2_BUF_TYPE_META_OUTPUT){
+ if (vb->planes[0].length < fmt->fmt.meta.buffersize) {
+ dev_dbg(dev,
+ "%s:%s:%s: size error(user:%d, required:%d)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ vb->planes[0].length, fmt->fmt.meta.buffersize);
+ return -EINVAL;
+ } else {
+ return 0;
+ }
+ }
+
+ for (i = 0; i < vb->num_planes; i++) {
+ size = fmt->fmt.pix_mp.plane_fmt[i].sizeimage;
+ if (vb->planes[i].length < size) {
+ dev_dbg(dev,
+ "%s:%s:%s: size error(user:%d, max:%d)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ vb->planes[i].length, size);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int mtk_dip_vb2_buf_out_validate(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+
+ if (v4l2_buf->field == V4L2_FIELD_ANY)
+ v4l2_buf->field = V4L2_FIELD_NONE;
+
+ if (v4l2_buf->field != V4L2_FIELD_NONE)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mtk_dip_vb2_queue_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *b = to_vb2_v4l2_buffer(vb);
+ struct mtk_dip_dev_buffer *dev_buf = mtk_dip_vb2_buf_to_dev_buf(vb);
+ struct mtk_dip_pipe *pipe = vb2_get_drv_priv(vb->vb2_queue);
+ struct mtk_dip_video_device *node = mtk_dip_vbq_to_node(vb->vb2_queue);
+ int i;
+
+ for (i = 0; i < vb->num_planes; i++) {
+ if (node->desc->smem_alloc) {
+ void *cpu_addr;
+ phys_addr_t buf_paddr;
+
+ dev_buf->scp_daddr[i] =
+ vb2_dma_contig_plane_dma_addr(vb, i);
+ cpu_addr = vb2_plane_vaddr(vb, i);
+ if (!cpu_addr) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: vb2_plane_vaddr NULL: s_daddr(%pad)\n",
+ pipe->desc->name, node->desc->name,
+ &dev_buf->scp_daddr[i]);
+ return -EINVAL;
+ }
+
+ /*
+ * We got the incorrect physical address mapped when
+ * using dma_map_single() so I used dma_map_page_attrs()
+ * directly to workaround here. Please see the detail in
+ * mtk_dip-sys.c
+ */
+ buf_paddr = dev_buf->scp_daddr[i];
+ dev_buf->isp_daddr[i] =
+ dma_map_page_attrs(&pipe->dip_dev->pdev->dev,
+ phys_to_page(buf_paddr),
+ 0, vb->planes[i].length,
+ DMA_BIDIRECTIONAL,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ if (dma_mapping_error(&pipe->dip_dev->pdev->dev,
+ dev_buf->isp_daddr[i])) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: failed to map buffer: s_daddr(%pad)\n",
+ pipe->desc->name, node->desc->name,
+ &dev_buf->scp_daddr[i]);
+ return -EINVAL;
+ }
+ } else {
+ dev_buf->scp_daddr[i] = 0;
+ dev_buf->isp_daddr[i] =
+ vb2_dma_contig_plane_dma_addr(vb, i);
+ }
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: buf type(%d), idx(%d), mem(%d), p(%d) i_daddr(%pad), s_daddr(%pad)\n",
+ pipe->desc->name, node->desc->name, b->vb2_buf.type,
+ b->vb2_buf.index, b->vb2_buf.memory, i,
+ &dev_buf->isp_daddr[i], &dev_buf->scp_daddr[i]);
+ }
+
+ return 0;
+}
+
+static void mtk_dip_vb2_queue_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct mtk_dip_dev_buffer *dev_buf = mtk_dip_vb2_buf_to_dev_buf(vb);
+ struct mtk_dip_pipe *pipe = vb2_get_drv_priv(vb->vb2_queue);
+ struct mtk_dip_video_device *node = mtk_dip_vbq_to_node(vb->vb2_queue);
+ int i;
+
+ if (!node->desc->smem_alloc)
+ return;
+
+ for (i = 0; i < vb->num_planes; i++) {
+ if (node->desc->smem_alloc)
+ dma_unmap_page_attrs(&pipe->dip_dev->pdev->dev,
+ dev_buf->isp_daddr[i],
+ vb->planes[i].length,
+ DMA_BIDIRECTIONAL,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ }
+}
+
+static void mtk_dip_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *b = to_vb2_v4l2_buffer(vb);
+ struct mtk_dip_dev_buffer *dev_buf = mtk_dip_vb2_buf_to_dev_buf(vb);
+ struct mtk_dip_request *req = (struct mtk_dip_request *)vb->request;
+ struct mtk_dip_video_device *node =
+ mtk_dip_vbq_to_node(vb->vb2_queue);
+ int buf_count;
+
+ dev_buf->fmt = node->vdev_fmt;
+ dev_buf->dev_fmt = node->dev_q.dev_fmt;
+ dev_buf->dma_port = node->desc->dma_port;
+ dev_buf->rotation = node->dev_q.rotation;
+
+ dev_dbg(&req->dip_pipe->dip_dev->pdev->dev,
+ "%s:%s: buf type(%d), idx(%d), mem(%d), i_daddr(%pad), s_daddr(%pad)\n",
+ req->dip_pipe->desc->name, node->desc->name, b->vb2_buf.type,
+ b->vb2_buf.index, b->vb2_buf.memory, &dev_buf->isp_daddr,
+ &dev_buf->scp_daddr);
+
+ spin_lock(&node->buf_list_lock);
+ list_add_tail(&dev_buf->list, &node->buf_list);
+ spin_unlock(&node->buf_list_lock);
+
+ buf_count = atomic_dec_return(&req->buf_count);
+ if (!buf_count) {
+ mutex_lock(&req->dip_pipe->lock);
+ mtk_dip_pipe_try_enqueue(req->dip_pipe);
+ mutex_unlock(&req->dip_pipe->lock);
+ }
+
+ dev_dbg(&req->dip_pipe->dip_dev->pdev->dev,
+ "%s:%s:%s:%s:buf_count(%d)\n",
+ __func__, req->dip_pipe->desc->name, node->desc->name,
+ req->req.debug_str, buf_count);
+}
+
+static int mtk_dip_vb2_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct mtk_dip_pipe *pipe = vb2_get_drv_priv(vq);
+ struct mtk_dip_video_device *node = mtk_dip_vbq_to_node(vq);
+ const struct v4l2_format *fmt = &node->vdev_fmt;
+ unsigned int size;
+ int i;
+
+ vq->dma_attrs |= DMA_ATTR_NON_CONSISTENT;
+
+ if (!*num_planes) {
+ *num_planes = 1;
+ sizes[0] = fmt->fmt.pix_mp.plane_fmt[0].sizeimage;
+ }
+
+ if (node->desc->id == MTK_DIP_VIDEO_NODE_ID_TUNING_OUT) {
+ int max_meta_bufs_per_pipe;
+
+ size = fmt->fmt.meta.buffersize;
+ max_meta_bufs_per_pipe =
+ MTK_DIP_DEV_META_BUF_POOL_MAX_SIZE /
+ round_up(size, PAGE_SIZE) / MTK_DIP_PIPE_ID_TOTAL_NUM;
+ *num_buffers = clamp_val(*num_buffers, 1,
+ max_meta_bufs_per_pipe);
+ sizes[0] = size;
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: n_p(%d), n_b(%d), s[%d](%u)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ *num_planes, *num_buffers, 0, sizes[0]);
+
+ } else {
+ for (i = 0; i < *num_planes; i++) {
+ if (sizes[i] < fmt->fmt.pix_mp.plane_fmt[i].sizeimage) {
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: invalid buf: %u < %u\n",
+ __func__, pipe->desc->name,
+ node->desc->name, sizes[0], size);
+ return -EINVAL;
+ }
+ sizes[i] = fmt->fmt.pix_mp.plane_fmt[i].sizeimage;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: n_p(%d), n_b(%d), s[%d](%u)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ *num_planes, *num_buffers, i, sizes[i]);
+ }
+ }
+
+ return 0;
+}
+
+static void mtk_dip_return_all_buffers(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node,
+ enum vb2_buffer_state state)
+{
+ struct mtk_dip_dev_buffer *b, *b0;
+
+ spin_lock(&node->buf_list_lock);
+ list_for_each_entry_safe(b, b0, &node->buf_list, list) {
+ list_del(&b->list);
+ if (b->vbb.vb2_buf.state == VB2_BUF_STATE_ACTIVE)
+ vb2_buffer_done(&b->vbb.vb2_buf, state);
+ }
+ spin_unlock(&node->buf_list_lock);
+}
+
+static int mtk_dip_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct mtk_dip_pipe *pipe = vb2_get_drv_priv(vq);
+ struct mtk_dip_video_device *node = mtk_dip_vbq_to_node(vq);
+ int cnt_node_ns;
+ int ret;
+
+ if (!(node->flags & MEDIA_LNK_FL_ENABLED)) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: stream on failed, node is not enabled\n",
+ pipe->desc->name, node->desc->name);
+
+ ret = -ENOLINK;
+ goto fail_return_bufs;
+ }
+
+ mutex_lock(&pipe->lock);
+ cnt_node_ns = atomic_dec_return(&pipe->cnt_nodes_not_streaming);
+ mutex_unlock(&pipe->lock);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s cnt_nodes_not_streaming(%d)\n",
+ __func__, pipe->desc->name, node->desc->name, cnt_node_ns);
+
+ if (cnt_node_ns)
+ return 0;
+
+ ret = media_pipeline_start(&node->vdev.entity, &pipe->pipeline);
+ if (ret < 0) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: media_pipeline_start failed(%d)\n",
+ pipe->desc->name, node->desc->name, ret);
+
+ goto fail_return_bufs;
+ }
+
+ /* Start streaming of the whole pipeline */
+ ret = v4l2_subdev_call(&pipe->subdev, video, s_stream, 1);
+ if (ret < 0) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: sub dev s_stream(1) failed(%d)\n",
+ pipe->desc->name, node->desc->name, ret);
+
+ goto fail_stop_pipeline;
+ }
+
+ return 0;
+
+fail_stop_pipeline:
+ media_pipeline_stop(&node->vdev.entity);
+
+fail_return_bufs:
+ mtk_dip_return_all_buffers(pipe, node, VB2_BUF_STATE_QUEUED);
+
+ return ret;
+}
+
+static void mtk_dip_vb2_stop_streaming(struct vb2_queue *vq)
+{
+ struct mtk_dip_pipe *pipe = vb2_get_drv_priv(vq);
+ struct mtk_dip_video_device *node = mtk_dip_vbq_to_node(vq);
+ int ret;
+ int count;
+ int is_pipe_streamon;
+
+ WARN_ON(!(node->flags & MEDIA_LNK_FL_ENABLED));
+
+ mutex_lock(&pipe->lock);
+ count = atomic_inc_return(&pipe->cnt_nodes_not_streaming);
+ is_pipe_streamon = pipe->streaming;
+ mutex_unlock(&pipe->lock);
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s cnt_nodes_not_streaming(%d), is_pipe_streamon(%d)\n",
+ __func__, pipe->desc->name, node->desc->name, count,
+ is_pipe_streamon);
+
+ if (count && is_pipe_streamon) {
+ ret = v4l2_subdev_call(&pipe->subdev, video, s_stream, 0);
+ if (ret)
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s: sub dev s_stream(0) failed(%d)\n",
+ pipe->desc->name, node->desc->name, ret);
+ media_pipeline_stop(&node->vdev.entity);
+ }
+
+ mtk_dip_return_all_buffers(pipe, node, VB2_BUF_STATE_ERROR);
+}
+
+static int mtk_dip_videoc_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct mtk_dip_pipe *pipe = video_drvdata(file);
+
+ strlcpy(cap->driver, pipe->desc->name,
+ sizeof(cap->driver));
+ strlcpy(cap->card, pipe->desc->name,
+ sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "platform:%s", dev_name(pipe->dip_dev->mdev.dev));
+
+ return 0;
+}
+
+static int mtk_dip_videoc_try_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_dip_pipe *pipe = video_drvdata(file);
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+ const struct mtk_dip_dev_format *dev_fmt;
+ struct v4l2_format try_fmt;
+
+ memset(&try_fmt, 0, sizeof(try_fmt));
+ try_fmt.type = node->dev_q.vbq.type;
+ dev_fmt = mtk_dip_pipe_find_fmt(pipe, node,
+ f->fmt.pix_mp.pixelformat);
+
+ if (!dev_fmt) {
+ dev_fmt = &node->desc->fmts[node->desc->default_fmt_idx];
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: dev_fmt(%d) not found, use default(%d)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ f->fmt.pix_mp.pixelformat, dev_fmt->format);
+ }
+
+ if (V4L2_TYPE_IS_OUTPUT(node->desc->buf_type)) {
+ try_fmt.fmt.pix_mp.width = clamp_val(f->fmt.pix_mp.width,
+ MTK_DIP_OUTPUT_MIN_WIDTH,
+ MTK_DIP_OUTPUT_MAX_WIDTH);
+ try_fmt.fmt.pix_mp.height =
+ clamp_val(f->fmt.pix_mp.height,
+ MTK_DIP_OUTPUT_MIN_HEIGHT,
+ MTK_DIP_OUTPUT_MAX_HEIGHT);
+ } else {
+ try_fmt.fmt.pix_mp.width = clamp_val(f->fmt.pix_mp.width,
+ MTK_DIP_CAPTURE_MIN_WIDTH,
+ MTK_DIP_CAPTURE_MAX_WIDTH);
+ try_fmt.fmt.pix_mp.height =
+ clamp_val(f->fmt.pix_mp.height,
+ MTK_DIP_CAPTURE_MIN_HEIGHT,
+ MTK_DIP_CAPTURE_MAX_HEIGHT);
+ }
+
+ node->dev_q.dev_fmt = dev_fmt;
+ try_fmt.fmt.pix_mp.field = V4L2_FIELD_NONE;
+ mtk_dip_pipe_set_img_fmt(pipe, node, &try_fmt.fmt.pix_mp, dev_fmt);
+
+ *f = try_fmt;
+
+ return 0;
+}
+
+static int mtk_dip_videoc_g_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+
+ *f = node->vdev_fmt;
+
+ return 0;
+}
+
+static int mtk_dip_videoc_s_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+ struct mtk_dip_pipe *pipe = video_drvdata(file);
+ int ret;
+
+ if (pipe->streaming)
+ return -EBUSY;
+
+ ret = mtk_dip_videoc_try_fmt(file, fh, f);
+ if (!ret)
+ node->vdev_fmt = *f;
+
+ return ret;
+}
+
+static int mtk_dip_videoc_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *sizes)
+{
+ struct mtk_dip_pipe *pipe = video_drvdata(file);
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+ const struct mtk_dip_dev_format *dev_fmt;
+
+ dev_fmt = mtk_dip_pipe_find_fmt(pipe, node, sizes->pixel_format);
+
+ if (!dev_fmt || sizes->index)
+ return -EINVAL;
+
+ sizes->type = node->desc->frmsizeenum->type;
+ sizes->stepwise.max_width =
+ node->desc->frmsizeenum->stepwise.max_width;
+ sizes->stepwise.min_width =
+ node->desc->frmsizeenum->stepwise.min_width;
+ sizes->stepwise.max_height =
+ node->desc->frmsizeenum->stepwise.max_height;
+ sizes->stepwise.min_height =
+ node->desc->frmsizeenum->stepwise.min_height;
+ sizes->stepwise.step_height =
+ node->desc->frmsizeenum->stepwise.step_height;
+ sizes->stepwise.step_width =
+ node->desc->frmsizeenum->stepwise.step_width;
+
+ return 0;
+}
+
+static int mtk_dip_videoc_enum_fmt(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+
+ if (f->index >= node->desc->num_fmts)
+ return -EINVAL;
+
+ strscpy(f->description, node->desc->description,
+ sizeof(f->description));
+ f->pixelformat = node->desc->fmts[f->index].format;
+ f->flags = 0;
+
+ return 0;
+}
+
+static int mtk_dip_meta_enum_format(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+
+ if (f->index > 0)
+ return -EINVAL;
+
+ strscpy(f->description, node->desc->description,
+ sizeof(f->description));
+
+ f->pixelformat = node->vdev_fmt.fmt.meta.dataformat;
+ f->flags = 0;
+
+ return 0;
+}
+
+static int mtk_dip_videoc_g_meta_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_dip_video_device *node = mtk_dip_file_to_node(file);
+ *f = node->vdev_fmt;
+
+ return 0;
+}
+
+static int handle_buf_rotate_config(struct v4l2_ctrl *ctrl)
+{
+ struct mtk_dip_video_device *node =
+ container_of(ctrl->handler, struct mtk_dip_video_device,
+ ctrl_handler);
+
+ if (ctrl->val != 0 && ctrl->val != 90 &&
+ ctrl->val != 180 && ctrl->val != 270) {
+ pr_err("%s: Invalid buffer rotation %d", node->desc->name,
+ ctrl->val);
+ return -EINVAL;
+ }
+
+ node->dev_q.rotation = ctrl->val;
+
+ return 0;
+}
+
+static int mtk_dip_video_device_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_ROTATE:
+ ret = handle_buf_rotate_config(ctrl);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/******************** function pointers ********************/
+
+static const struct v4l2_subdev_internal_ops mtk_dip_subdev_internal_ops = {
+ .open = mtk_dip_subdev_open,
+ .close = mtk_dip_subdev_close,
+};
+
+static const struct v4l2_subdev_video_ops mtk_dip_subdev_video_ops = {
+ .s_stream = mtk_dip_subdev_s_stream,
+};
+
+static const struct v4l2_subdev_ops mtk_dip_subdev_ops = {
+ .video = &mtk_dip_subdev_video_ops,
+};
+
+static const struct media_entity_operations mtk_dip_media_ops = {
+ .link_setup = mtk_dip_link_setup,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+static struct media_request *mtk_dip_request_alloc(struct media_device *mdev)
+{
+ return kzalloc(sizeof(struct mtk_dip_request), GFP_KERNEL);
+}
+
+static void mtk_dip_request_free(struct media_request *req)
+{
+ kfree(req);
+}
+
+static int mtk_dip_vb2_request_validate(struct media_request *req)
+{
+ struct media_request_object *obj;
+ struct mtk_dip_dev *dip_dev = mtk_dip_mdev_to_dev(req->mdev);
+ struct mtk_dip_request *dip_req = (struct mtk_dip_request *)req;
+ struct mtk_dip_pipe *pipe = NULL;
+ struct mtk_dip_pipe *pipe_prev = NULL;
+ struct mtk_dip_dev_buffer **map = dip_req->job_info.buf_map;
+ int buf_count = 0;
+
+ memset(map, 0, sizeof(dip_req->job_info.buf_map));
+
+ list_for_each_entry(obj, &req->objects, list) {
+ struct vb2_buffer *vb;
+ struct mtk_dip_dev_buffer *dev_buf;
+ struct mtk_dip_video_device *node;
+
+ if (!vb2_request_object_is_buffer(obj))
+ continue;
+
+ vb = container_of(obj, struct vb2_buffer, req_obj);
+ node = mtk_dip_vbq_to_node(vb->vb2_queue);
+ pipe = vb2_get_drv_priv(vb->vb2_queue);
+ if (pipe_prev && pipe != pipe_prev) {
+ dev_warn(&dip_dev->pdev->dev,
+ "%s:%s:%s:found buf of different pipes(%p,%p)\n",
+ __func__, node->desc->name,
+ req->debug_str, pipe, pipe_prev);
+ return -EINVAL;
+ }
+
+ pipe_prev = pipe;
+ dev_buf = mtk_dip_vb2_buf_to_dev_buf(vb);
+ dip_req->job_info.buf_map[node->desc->id] = dev_buf;
+ buf_count++;
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: added buf(%p) to pipe-job(%p), buf_count(%d)\n",
+ pipe->desc->name, node->desc->name, dev_buf,
+ dip_req->job_info, buf_count);
+ }
+
+ if (!pipe) {
+ dev_warn(&dip_dev->pdev->dev,
+ "%s: no buffer in the request(%p)\n",
+ req->debug_str, req);
+
+ return vb2_request_validate(req);
+ }
+
+ /*
+ * Workaround to pass V4L2 comliance test since it can't know
+ * which buffer is required and may enqueue only 1 buffer.
+ * It seems that we should return an error here and let the
+ * user to enqueue required buffer instead of showing a debug
+ * message only.
+ */
+ if (!map[MTK_DIP_VIDEO_NODE_ID_RAW_OUT] ||
+ (!map[MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE] &&
+ !map[MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE]))
+ dev_dbg(&dip_dev->pdev->dev,
+ "won't trigger hw job: raw(%p), mdp0(%p), mdp1(%p)\n",
+ map[MTK_DIP_VIDEO_NODE_ID_RAW_OUT],
+ map[MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE],
+ map[MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE]);
+
+ atomic_set(&dip_req->buf_count, buf_count);
+ dip_req->job_info.id = mtk_dip_pipe_next_job_id(pipe);
+ dip_req->dip_pipe = pipe;
+ mtk_dip_pipe_debug_job(pipe, &dip_req->job_info);
+
+ spin_lock(&pipe->job_lock);
+ list_add_tail(&dip_req->job_info.list, &pipe->pipe_job_pending_list);
+ pipe->num_pending_jobs++;
+ dev_dbg(&dip_dev->pdev->dev,
+ "%s:%s: current num of pending jobs(%d)\n",
+ __func__, pipe->desc->name, pipe->num_pending_jobs);
+ spin_unlock(&pipe->job_lock);
+
+ return vb2_request_validate(req);
+}
+
+static const struct media_device_ops mtk_dip_media_req_ops = {
+ .req_validate = mtk_dip_vb2_request_validate,
+ .req_queue = vb2_request_queue,
+ .req_alloc = mtk_dip_request_alloc,
+ .req_free = mtk_dip_request_free,
+};
+
+static const struct v4l2_ctrl_ops mtk_dip_video_device_ctrl_ops = {
+ .s_ctrl = mtk_dip_video_device_s_ctrl,
+};
+
+static const struct vb2_ops mtk_dip_vb2_ops = {
+ .buf_queue = mtk_dip_vb2_buf_queue,
+ .queue_setup = mtk_dip_vb2_queue_setup,
+ .buf_init = mtk_dip_vb2_queue_buf_init,
+ .buf_prepare = mtk_dip_vb2_buf_prepare,
+ .buf_out_validate = mtk_dip_vb2_buf_out_validate,
+ .buf_cleanup = mtk_dip_vb2_queue_buf_cleanup,
+ .start_streaming = mtk_dip_vb2_start_streaming,
+ .stop_streaming = mtk_dip_vb2_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static const struct v4l2_file_operations mtk_dip_v4l2_fops = {
+ .unlocked_ioctl = video_ioctl2,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+};
+
+int mtk_dip_dev_media_register(struct device *dev,
+ struct media_device *media_dev,
+ const char *model)
+{
+ int ret;
+
+ media_dev->dev = dev;
+ strlcpy(media_dev->model, model, sizeof(media_dev->model));
+ snprintf(media_dev->bus_info, sizeof(media_dev->bus_info),
+ "platform:%s", dev_name(dev));
+ media_dev->hw_revision = 0;
+ media_dev->ops = &mtk_dip_media_req_ops;
+ media_device_init(media_dev);
+
+ ret = media_device_register(media_dev);
+ if (ret) {
+ dev_err(dev, "failed to register media device (%d)\n", ret);
+ media_device_unregister(media_dev);
+ media_device_cleanup(media_dev);
+ }
+
+ dev_dbg(dev, "Registered media device: %s, %p", media_dev->model,
+ media_dev);
+
+ return ret;
+}
+
+int mtk_dip_dev_v4l2_register(struct device *dev,
+ struct media_device *media_dev,
+ struct v4l2_device *v4l2_dev)
+{
+ int ret;
+
+ v4l2_dev->mdev = media_dev;
+ v4l2_dev->ctrl_handler = NULL;
+
+ ret = v4l2_device_register(dev, v4l2_dev);
+ if (ret) {
+ dev_err(dev, "failed to register V4L2 device (%d)\n", ret);
+ v4l2_device_unregister(v4l2_dev);
+ }
+
+ dev_dbg(dev, "Registered v4l2 device: %p", v4l2_dev);
+
+ return ret;
+}
+
+static int mtk_dip_video_device_v4l2_register(struct mtk_dip_pipe *pipe,
+ struct mtk_dip_video_device *node)
+{
+ struct vb2_queue *vbq = &node->dev_q.vbq;
+ struct video_device *vdev = &node->vdev;
+ struct media_link *link;
+ int ret;
+
+ /* Initialize miscellaneous variables */
+ mutex_init(&node->dev_q.lock);
+
+ /* Initialize formats to default values */
+ vdev->device_caps = node->desc->cap;
+ vdev->ioctl_ops = node->desc->ops;
+ node->vdev_fmt.type = node->desc->buf_type;
+ mtk_dip_pipe_load_default_fmt(pipe, node, &node->vdev_fmt);
+
+ /* Initialize media entities */
+ ret = media_entity_pads_init(&vdev->entity, 1, &node->vdev_pad);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "failed initialize media entity (%d)\n", ret);
+ goto err_mutex_destroy;
+ }
+
+ node->vdev_pad.flags = V4L2_TYPE_IS_OUTPUT(node->desc->buf_type) ?
+ MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK;
+
+ /* Initialize vbq */
+ vbq->type = node->vdev_fmt.type;
+ vbq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vbq->ops = &mtk_dip_vb2_ops;
+ vbq->mem_ops = &vb2_dma_contig_memops;
+ vbq->supports_requests = true;
+ vbq->buf_struct_size = sizeof(struct mtk_dip_dev_buffer);
+ vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vbq->min_buffers_needed = 0;
+ vbq->drv_priv = pipe;
+ vbq->lock = &node->dev_q.lock;
+
+ ret = vb2_queue_init(vbq);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s:%s:%s: failed to init vb2 queue(%d)\n",
+ __func__, pipe->desc->name, node->desc->name,
+ ret);
+ goto err_media_entity_cleanup;
+ }
+
+ /* Initialize vdev */
+ snprintf(vdev->name, sizeof(vdev->name), "%s %s", pipe->desc->name,
+ node->desc->name);
+ vdev->entity.name = vdev->name;
+ vdev->entity.function = MEDIA_ENT_F_IO_V4L;
+ vdev->entity.ops = NULL;
+ vdev->release = video_device_release_empty;
+ vdev->fops = &mtk_dip_v4l2_fops;
+ vdev->lock = &node->dev_q.lock;
+ vdev->ctrl_handler = &node->ctrl_handler;
+ vdev->v4l2_dev = &pipe->dip_dev->v4l2_dev;
+ vdev->queue = &node->dev_q.vbq;
+ vdev->vfl_dir = V4L2_TYPE_IS_OUTPUT(node->desc->buf_type) ?
+ VFL_DIR_TX : VFL_DIR_RX;
+
+ if (node->desc->smem_alloc) {
+ vdev->queue->dev = &pipe->dip_dev->dip_hw.scp_pdev->dev;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: select smem_vb2_alloc_ctx(%p)\n",
+ pipe->desc->name, node->desc->name,
+ vdev->queue->dev);
+ } else {
+ vdev->queue->dev = &pipe->dip_dev->pdev->dev;
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "%s:%s: select default_vb2_alloc_ctx(%p)\n",
+ pipe->desc->name, node->desc->name,
+ &pipe->dip_dev->pdev->dev);
+ }
+
+ video_set_drvdata(vdev, pipe);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "failed to register video device (%d)\n", ret);
+ goto err_vb2_queue_release;
+ }
+ dev_dbg(&pipe->dip_dev->pdev->dev, "registered vdev: %s\n",
+ vdev->name);
+
+ /* Create link between video node and the subdev pad */
+ if (V4L2_TYPE_IS_OUTPUT(node->desc->buf_type))
+ ret = media_create_pad_link(&vdev->entity, 0,
+ &pipe->subdev.entity,
+ node->desc->id, node->flags);
+ else
+ ret = media_create_pad_link(&pipe->subdev.entity,
+ node->desc->id, &vdev->entity,
+ 0, node->flags);
+ if (ret)
+ goto err_video_unregister_device;
+
+ vdev->intf_devnode = media_devnode_create(&pipe->dip_dev->mdev,
+ MEDIA_INTF_T_V4L_VIDEO, 0,
+ VIDEO_MAJOR, vdev->minor);
+ if (!vdev->intf_devnode) {
+ ret = -ENOMEM;
+ goto err_rm_links;
+ }
+
+ link = media_create_intf_link(&vdev->entity,
+ &vdev->intf_devnode->intf,
+ node->flags);
+ if (!link) {
+ ret = -ENOMEM;
+ goto err_rm_devnode;
+ }
+
+ return 0;
+
+err_rm_devnode:
+ media_devnode_remove(vdev->intf_devnode);
+
+err_rm_links:
+ media_entity_remove_links(&vdev->entity);
+
+err_video_unregister_device:
+ video_unregister_device(vdev);
+
+err_vb2_queue_release:
+ vb2_queue_release(&node->dev_q.vbq);
+
+err_media_entity_cleanup:
+ media_entity_cleanup(&node->vdev.entity);
+
+err_mutex_destroy:
+ mutex_destroy(&node->dev_q.lock);
+
+ return ret;
+}
+
+static int mtk_dip_pipe_v4l2_ctrl_init(struct mtk_dip_pipe *dip_pipe)
+{
+ struct mtk_dip_video_device *node;
+ int i;
+
+ for (i = 0; i < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM; i++)
+ v4l2_ctrl_handler_init(&dip_pipe->nodes[i].ctrl_handler,
+ V4L2_CID_MTK_DIP_MAX);
+
+ /* Only MDP 0 support rotate in MT8183 */
+ node = &dip_pipe->nodes[MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE];
+ if (v4l2_ctrl_new_std(&node->ctrl_handler,
+ &mtk_dip_video_device_ctrl_ops,
+ V4L2_CID_ROTATE, 0, 270, 90, 0) == NULL) {
+ dev_err(&dip_pipe->dip_dev->pdev->dev,
+ "%s create rotate ctrl failed:(%d)",
+ node->desc->name, node->ctrl_handler.error);
+
+ return -EPROBE_DEFER;
+ }
+
+ return 0;
+}
+
+static void mtk_dip_pipe_v4l2_ctrl_release(struct mtk_dip_pipe *dip_pipe)
+{
+ int i;
+
+ for (i = 0; i < MTK_DIP_VIDEO_NODE_ID_TOTAL_NUM; i++)
+ v4l2_ctrl_handler_free(&dip_pipe->nodes[i].ctrl_handler);
+}
+
+int mtk_dip_pipe_v4l2_register(struct mtk_dip_pipe *pipe,
+ struct media_device *media_dev,
+ struct v4l2_device *v4l2_dev)
+{
+ int i, ret;
+
+ ret = mtk_dip_pipe_v4l2_ctrl_init(pipe);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "%s: failed(%d) to initialize ctrls\n",
+ pipe->desc->name, ret);
+
+ return ret;
+ }
+
+ pipe->streaming = 0;
+
+ /* Initialize subdev media entity */
+ pipe->subdev_pads = devm_kcalloc(&pipe->dip_dev->pdev->dev,
+ pipe->num_nodes,
+ sizeof(*pipe->subdev_pads),
+ GFP_KERNEL);
+ if (!pipe->subdev_pads) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "failed to alloc pipe->subdev_pads (%d)\n", ret);
+ ret = -ENOMEM;
+ goto err_release_ctrl;
+ }
+ ret = media_entity_pads_init(&pipe->subdev.entity,
+ pipe->num_nodes,
+ pipe->subdev_pads);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "failed initialize subdev media entity (%d)\n", ret);
+ goto err_free_subdev_pads;
+ }
+
+ /* Initialize subdev */
+ v4l2_subdev_init(&pipe->subdev, &mtk_dip_subdev_ops);
+
+ pipe->subdev.entity.function =
+ MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+ pipe->subdev.entity.ops = &mtk_dip_media_ops;
+ pipe->subdev.flags =
+ V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+ pipe->subdev.ctrl_handler = NULL;
+ pipe->subdev.internal_ops = &mtk_dip_subdev_internal_ops;
+
+ for (i = 0; i < pipe->num_nodes; i++)
+ pipe->subdev_pads[i].flags =
+ V4L2_TYPE_IS_OUTPUT(pipe->nodes[i].desc->buf_type) ?
+ MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
+
+ snprintf(pipe->subdev.name, sizeof(pipe->subdev.name),
+ "%s", pipe->desc->name);
+ v4l2_set_subdevdata(&pipe->subdev, pipe);
+
+ ret = v4l2_device_register_subdev(&pipe->dip_dev->v4l2_dev,
+ &pipe->subdev);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "failed initialize subdev (%d)\n", ret);
+ goto err_media_entity_cleanup;
+ }
+
+ dev_dbg(&pipe->dip_dev->pdev->dev,
+ "register subdev: %s, ctrl_handler %p\n",
+ pipe->subdev.name, pipe->subdev.ctrl_handler);
+
+ ret = v4l2_device_register_subdev_nodes(&pipe->dip_dev->v4l2_dev);
+ if (ret) {
+ dev_err(&pipe->dip_dev->pdev->dev,
+ "failed to register subdevs (%d)\n", ret);
+ goto err_unregister_subdev;
+ }
+
+ /* Create video nodes and links */
+ for (i = 0; i < pipe->num_nodes; i++) {
+ ret = mtk_dip_video_device_v4l2_register(pipe,
+ &pipe->nodes[i]);
+ if (ret)
+ goto err_unregister_subdev;
+ }
+
+ return 0;
+
+err_unregister_subdev:
+ v4l2_device_unregister_subdev(&pipe->subdev);
+
+err_media_entity_cleanup:
+ media_entity_cleanup(&pipe->subdev.entity);
+
+err_free_subdev_pads:
+ devm_kfree(&pipe->dip_dev->pdev->dev, pipe->subdev_pads);
+
+err_release_ctrl:
+ mtk_dip_pipe_v4l2_ctrl_release(pipe);
+
+ return ret;
+}
+
+void mtk_dip_pipe_v4l2_unregister(struct mtk_dip_pipe *pipe)
+{
+ unsigned int i;
+
+ for (i = 0; i < pipe->num_nodes; i++) {
+ video_unregister_device(&pipe->nodes[i].vdev);
+ vb2_queue_release(&pipe->nodes[i].dev_q.vbq);
+ media_entity_cleanup(&pipe->nodes[i].vdev.entity);
+ mutex_destroy(&pipe->nodes[i].dev_q.lock);
+ }
+
+ v4l2_device_unregister_subdev(&pipe->subdev);
+ media_entity_cleanup(&pipe->subdev.entity);
+ v4l2_device_unregister(&pipe->dip_dev->v4l2_dev);
+ media_device_unregister(&pipe->dip_dev->mdev);
+ media_device_cleanup(&pipe->dip_dev->mdev);
+ mtk_dip_pipe_v4l2_ctrl_release(pipe);
+}
+
+/********************************************
+ * MTK DIP V4L2 Settings *
+ ********************************************/
+
+static const struct v4l2_ioctl_ops mtk_dip_v4l2_video_out_ioctl_ops = {
+ .vidioc_querycap = mtk_dip_videoc_querycap,
+
+ .vidioc_enum_framesizes = mtk_dip_videoc_enum_framesizes,
+ .vidioc_enum_fmt_vid_cap_mplane = mtk_dip_videoc_enum_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = mtk_dip_videoc_g_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = mtk_dip_videoc_s_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = mtk_dip_videoc_try_fmt,
+
+ .vidioc_enum_fmt_vid_out_mplane = mtk_dip_videoc_enum_fmt,
+ .vidioc_g_fmt_vid_out_mplane = mtk_dip_videoc_g_fmt,
+ .vidioc_s_fmt_vid_out_mplane = mtk_dip_videoc_s_fmt,
+ .vidioc_try_fmt_vid_out_mplane = mtk_dip_videoc_try_fmt,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_ioctl_ops mtk_dip_v4l2_video_cap_ioctl_ops = {
+ .vidioc_querycap = mtk_dip_videoc_querycap,
+
+ .vidioc_enum_framesizes = mtk_dip_videoc_enum_framesizes,
+ .vidioc_enum_fmt_vid_cap_mplane = mtk_dip_videoc_enum_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = mtk_dip_videoc_g_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = mtk_dip_videoc_s_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = mtk_dip_videoc_try_fmt,
+
+ .vidioc_enum_fmt_vid_out_mplane = mtk_dip_videoc_enum_fmt,
+ .vidioc_g_fmt_vid_out_mplane = mtk_dip_videoc_g_fmt,
+ .vidioc_s_fmt_vid_out_mplane = mtk_dip_videoc_s_fmt,
+ .vidioc_try_fmt_vid_out_mplane = mtk_dip_videoc_try_fmt,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_ioctl_ops mtk_dip_v4l2_meta_out_ioctl_ops = {
+ .vidioc_querycap = mtk_dip_videoc_querycap,
+
+ .vidioc_enum_fmt_meta_cap = mtk_dip_meta_enum_format,
+ .vidioc_g_fmt_meta_cap = mtk_dip_videoc_g_meta_fmt,
+ .vidioc_s_fmt_meta_cap = mtk_dip_videoc_g_meta_fmt,
+ .vidioc_try_fmt_meta_cap = mtk_dip_videoc_g_meta_fmt,
+
+ .vidioc_enum_fmt_meta_out = mtk_dip_meta_enum_format,
+ .vidioc_g_fmt_meta_out = mtk_dip_videoc_g_meta_fmt,
+ .vidioc_s_fmt_meta_out = mtk_dip_videoc_g_meta_fmt,
+ .vidioc_try_fmt_meta_out = mtk_dip_videoc_g_meta_fmt,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+};
+
+static const struct mtk_dip_dev_format fw_param_fmts[] = {
+ {
+ .format = V4L2_META_FMT_MTISP_PARAMS,
+ .buffer_size = 1024 * 128,
+ },
+};
+
+static const struct mtk_dip_dev_format in_fmts[] = {
+ {
+ .format = V4L2_PIX_FMT_MTISP_B10,
+ .mdp_color = MDP_COLOR_BAYER10,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .depth = { 10 },
+ .row_depth = { 10 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_MTISP_F10,
+ .mdp_color = MDP_COLOR_FULLG10,
+ .colorspace = V4L2_COLORSPACE_RAW,
+ .depth = { 15 },
+ .row_depth = { 15 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_VYUY,
+ .mdp_color = MDP_COLOR_VYUY,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 16 },
+ .row_depth = { 16 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YUYV,
+ .mdp_color = MDP_COLOR_YUYV,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 16 },
+ .row_depth = { 16 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YVYU,
+ .mdp_color = MDP_COLOR_YVYU,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 16 },
+ .row_depth = { 16 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_NV12,
+ .mdp_color = MDP_COLOR_NV12,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 12 },
+ .row_depth = { 8 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YUV420M,
+ .mdp_color = MDP_COLOR_I420,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 8, 2, 2 },
+ .row_depth = { 8, 4, 4 },
+ .num_planes = 3,
+ },
+ {
+ .format = V4L2_PIX_FMT_YVU420M,
+ .mdp_color = MDP_COLOR_YV12,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 8, 2, 2 },
+ .row_depth = { 8, 4, 4 },
+ .num_planes = 3,
+ },
+ {
+ .format = V4L2_PIX_FMT_NV12M,
+ .mdp_color = MDP_COLOR_NV12,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 8, 4 },
+ .row_depth = { 8, 8 },
+ .num_planes = 2,
+ },
+};
+
+static const struct mtk_dip_dev_format out_fmts[] = {
+ {
+ .format = V4L2_PIX_FMT_VYUY,
+ .mdp_color = MDP_COLOR_VYUY,
+ .colorspace = MDP_YCBCR_PROFILE_BT601,
+ .depth = { 16 },
+ .row_depth = { 16 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YUYV,
+ .mdp_color = MDP_COLOR_YUYV,
+ .colorspace = MDP_YCBCR_PROFILE_BT601,
+ .depth = { 16 },
+ .row_depth = { 16 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YVYU,
+ .mdp_color = MDP_COLOR_YVYU,
+ .colorspace = MDP_YCBCR_PROFILE_BT601,
+ .depth = { 16 },
+ .row_depth = { 16 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YVU420,
+ .mdp_color = MDP_COLOR_YV12,
+ .colorspace = MDP_YCBCR_PROFILE_BT601,
+ .depth = { 12 },
+ .row_depth = { 8 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_NV12,
+ .mdp_color = MDP_COLOR_NV12,
+ .colorspace = MDP_YCBCR_PROFILE_BT601,
+ .depth = { 12 },
+ .row_depth = { 8 },
+ .num_planes = 1,
+ },
+ {
+ .format = V4L2_PIX_FMT_YUV420M,
+ .mdp_color = MDP_COLOR_I420,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 8, 2, 2 },
+ .row_depth = { 8, 4, 4 },
+ .num_planes = 3,
+ },
+ {
+ .format = V4L2_PIX_FMT_YVU420M,
+ .mdp_color = MDP_COLOR_YV12,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 8, 2, 2 },
+ .row_depth = { 8, 4, 4 },
+ .num_planes = 3,
+ },
+ {
+ .format = V4L2_PIX_FMT_NV12M,
+ .mdp_color = MDP_COLOR_NV12,
+ .colorspace = V4L2_COLORSPACE_BT2020,
+ .depth = { 8, 4 },
+ .row_depth = { 8, 8 },
+ .num_planes = 2,
+ }
+};
+
+static const struct v4l2_frmsizeenum in_frmsizeenum = {
+ .type = V4L2_FRMSIZE_TYPE_CONTINUOUS,
+ .stepwise.max_width = MTK_DIP_CAPTURE_MAX_WIDTH,
+ .stepwise.min_width = MTK_DIP_CAPTURE_MIN_WIDTH,
+ .stepwise.max_height = MTK_DIP_CAPTURE_MAX_HEIGHT,
+ .stepwise.min_height = MTK_DIP_CAPTURE_MIN_HEIGHT,
+ .stepwise.step_height = 1,
+ .stepwise.step_width = 1,
+};
+
+static const struct v4l2_frmsizeenum out_frmsizeenum = {
+ .type = V4L2_FRMSIZE_TYPE_CONTINUOUS,
+ .stepwise.max_width = MTK_DIP_OUTPUT_MAX_WIDTH,
+ .stepwise.min_width = MTK_DIP_OUTPUT_MIN_WIDTH,
+ .stepwise.max_height = MTK_DIP_OUTPUT_MAX_HEIGHT,
+ .stepwise.min_height = MTK_DIP_OUTPUT_MIN_HEIGHT,
+ .stepwise.step_height = 1,
+ .stepwise.step_width = 1,
+};
+
+static const struct mtk_dip_video_device_desc
+output_queues_setting[MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM] = {
+ {
+ .id = MTK_DIP_VIDEO_NODE_ID_RAW_OUT,
+ .name = "Raw Input",
+ .cap = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING,
+ .buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ .smem_alloc = 0,
+ .flags = MEDIA_LNK_FL_ENABLED,
+ .fmts = in_fmts,
+ .num_fmts = ARRAY_SIZE(in_fmts),
+ .default_fmt_idx = 0,
+ .default_width = MTK_DIP_OUTPUT_MAX_WIDTH,
+ .default_height = MTK_DIP_OUTPUT_MAX_HEIGHT,
+ .dma_port = 0,
+ .frmsizeenum = &in_frmsizeenum,
+ .ops = &mtk_dip_v4l2_video_out_ioctl_ops,
+ .description = "Main image source",
+ },
+ {
+ .id = MTK_DIP_VIDEO_NODE_ID_TUNING_OUT,
+ .name = "Tuning",
+ .cap = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING,
+ .buf_type = V4L2_BUF_TYPE_META_OUTPUT,
+ .smem_alloc = 1,
+ .flags = 0,
+ .fmts = fw_param_fmts,
+ .num_fmts = 1,
+ .default_fmt_idx = 0,
+ .dma_port = 0,
+ .frmsizeenum = &in_frmsizeenum,
+ .ops = &mtk_dip_v4l2_meta_out_ioctl_ops,
+ .description = "Tuning data",
+ },
+};
+
+static const struct mtk_dip_video_device_desc
+reprocess_output_queues_setting[MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM] = {
+ {
+ .id = MTK_DIP_VIDEO_NODE_ID_RAW_OUT,
+ .name = "Raw Input",
+ .cap = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING,
+ .buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ .smem_alloc = 0,
+ .flags = MEDIA_LNK_FL_ENABLED,
+ .fmts = in_fmts,
+ .num_fmts = ARRAY_SIZE(in_fmts),
+ .default_fmt_idx = 5,
+ .default_width = MTK_DIP_OUTPUT_MAX_WIDTH,
+ .default_height = MTK_DIP_OUTPUT_MAX_HEIGHT,
+ .dma_port = 0,
+ .frmsizeenum = &in_frmsizeenum,
+ .ops = &mtk_dip_v4l2_video_out_ioctl_ops,
+ .description = "Source image to be process",
+
+ },
+ {
+ .id = MTK_DIP_VIDEO_NODE_ID_TUNING_OUT,
+ .name = "Tuning",
+ .cap = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING,
+ .buf_type = V4L2_BUF_TYPE_META_OUTPUT,
+ .smem_alloc = 1,
+ .flags = 0,
+ .fmts = fw_param_fmts,
+ .num_fmts = 1,
+ .default_fmt_idx = 0,
+ .dma_port = 0,
+ .frmsizeenum = &in_frmsizeenum,
+ .ops = &mtk_dip_v4l2_meta_out_ioctl_ops,
+ .description = "Tuning data for image enhancement",
+ },
+};
+
+static const struct mtk_dip_video_device_desc
+capture_queues_setting[MTK_DIP_VIDEO_NODE_ID_CAPTURE_TOTAL_NUM] = {
+ {
+ .id = MTK_DIP_VIDEO_NODE_ID_MDP0_CAPTURE,
+ .name = "MDP0",
+ .cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING,
+ .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ .smem_alloc = 0,
+ .flags = MEDIA_LNK_FL_DYNAMIC | MEDIA_LNK_FL_ENABLED,
+ .fmts = out_fmts,
+ .num_fmts = ARRAY_SIZE(out_fmts),
+ .default_fmt_idx = 1,
+ .default_width = MTK_DIP_CAPTURE_MAX_WIDTH,
+ .default_height = MTK_DIP_CAPTURE_MAX_HEIGHT,
+ .dma_port = 0,
+ .frmsizeenum = &out_frmsizeenum,
+ .ops = &mtk_dip_v4l2_video_cap_ioctl_ops,
+ .description = "Output quality enhanced image",
+ },
+ {
+ .id = MTK_DIP_VIDEO_NODE_ID_MDP1_CAPTURE,
+ .name = "MDP1",
+ .cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING,
+ .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ .smem_alloc = 0,
+ .flags = MEDIA_LNK_FL_DYNAMIC | MEDIA_LNK_FL_ENABLED,
+ .fmts = out_fmts,
+ .num_fmts = ARRAY_SIZE(out_fmts),
+ .default_fmt_idx = 1,
+ .default_width = MTK_DIP_CAPTURE_MAX_WIDTH,
+ .default_height = MTK_DIP_CAPTURE_MAX_HEIGHT,
+ .dma_port = 0,
+ .frmsizeenum = &out_frmsizeenum,
+ .ops = &mtk_dip_v4l2_video_cap_ioctl_ops,
+ .description = "Output quality enhanced image",
+
+ },
+};
+
+static const struct mtk_dip_pipe_desc
+pipe_settings[MTK_DIP_PIPE_ID_TOTAL_NUM] = {
+ {
+ .name = MTK_DIP_DEV_DIP_PREVIEW_NAME,
+ .id = MTK_DIP_PIPE_ID_PREVIEW,
+ .master = MTK_DIP_VIDEO_NODE_ID_NO_MASTER,
+ .output_queue_descs = output_queues_setting,
+ .total_output_queues = MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM,
+ .capture_queue_descs = capture_queues_setting,
+ .total_capture_queues = MTK_DIP_VIDEO_NODE_ID_CAPTURE_TOTAL_NUM,
+ },
+ {
+ .name = MTK_DIP_DEV_DIP_CAPTURE_NAME,
+ .id = MTK_DIP_PIPE_ID_CAPTURE,
+ .master = MTK_DIP_VIDEO_NODE_ID_NO_MASTER,
+ .output_queue_descs = output_queues_setting,
+ .total_output_queues = MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM,
+ .capture_queue_descs = capture_queues_setting,
+ .total_capture_queues = MTK_DIP_VIDEO_NODE_ID_CAPTURE_TOTAL_NUM,
+ },
+ {
+ .name = MTK_DIP_DEV_DIP_REPROCESS_NAME,
+ .id = MTK_DIP_PIPE_ID_REPROCESS,
+ .master = MTK_DIP_VIDEO_NODE_ID_NO_MASTER,
+ .output_queue_descs = reprocess_output_queues_setting,
+ .total_output_queues = MTK_DIP_VIDEO_NODE_ID_OUT_TOTAL_NUM,
+ .capture_queue_descs = capture_queues_setting,
+ .total_capture_queues = MTK_DIP_VIDEO_NODE_ID_CAPTURE_TOTAL_NUM,
+ },
+};
+
+static int mtk_dip_dev_v4l2_init(struct mtk_dip_dev *dip_dev)
+{
+ struct media_device *media_dev = &dip_dev->mdev;
+ struct v4l2_device *v4l2_dev = &dip_dev->v4l2_dev;
+ int i;
+ int ret;
+
+ ret = mtk_dip_dev_media_register(&dip_dev->pdev->dev,
+ media_dev,
+ MTK_DIP_DEV_DIP_MEDIA_MODEL_NAME);
+ if (ret) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: media device register failed(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = mtk_dip_dev_v4l2_register(&dip_dev->pdev->dev,
+ media_dev,
+ v4l2_dev);
+ if (ret) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: v4l2 device register failed(%d)\n",
+ __func__, ret);
+ goto err_release_media_device;
+ }
+
+ for (i = 0; i < MTK_DIP_PIPE_ID_TOTAL_NUM; i++) {
+ ret = mtk_dip_pipe_init(&dip_dev->dip_pipe[i], dip_dev,
+ &pipe_settings[i], media_dev, v4l2_dev);
+ if (ret) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: Pipe id(%d) init failed(%d)\n",
+ dip_dev->dip_pipe[i].desc->name,
+ i, ret);
+ goto err_release_pipe;
+ }
+ }
+
+ return 0;
+
+err_release_pipe:
+ for (i = i - 1; i >= 0; i--)
+ mtk_dip_pipe_release(&dip_dev->dip_pipe[i]);
+
+ v4l2_device_unregister(v4l2_dev);
+
+err_release_media_device:
+ media_device_unregister(media_dev);
+ media_device_cleanup(media_dev);
+
+ return ret;
+}
+
+void mtk_dip_dev_v4l2_release(struct mtk_dip_dev *dip_dev)
+{
+ int i;
+
+ for (i = 0; i < MTK_DIP_PIPE_ID_TOTAL_NUM; i++)
+ mtk_dip_pipe_release(&dip_dev->dip_pipe[i]);
+
+ v4l2_device_unregister(&dip_dev->v4l2_dev);
+ media_device_unregister(&dip_dev->mdev);
+ media_device_cleanup(&dip_dev->mdev);
+}
+
+static int mtk_dip_probe(struct platform_device *pdev)
+{
+ struct mtk_dip_dev *dip_dev;
+ struct mtk_dip_hw *dip_hw;
+ struct device_node *node;
+ struct platform_device *larb_pdev;
+ phandle rproc_phandle;
+ int ret;
+
+ dip_dev = devm_kzalloc(&pdev->dev, sizeof(*dip_dev), GFP_KERNEL);
+ if (!dip_dev)
+ return -ENOMEM;
+
+ dip_dev->pdev = pdev;
+ dev_set_drvdata(&pdev->dev, dip_dev);
+ dip_hw = &dip_dev->dip_hw;
+
+ node = of_parse_phandle(pdev->dev.of_node, "mediatek,larb", 0);
+ if (!node) {
+ dev_err(&pdev->dev, "No mediatek,larb found");
+ return -EINVAL;
+ }
+
+ larb_pdev = of_find_device_by_node(node);
+ if (!larb_pdev) {
+ dev_err(&pdev->dev, "No mediatek,larb device found");
+ return -EINVAL;
+ }
+
+ dip_hw->clks[0].id = "larb5";
+ dip_hw->clks[1].id = "dip";
+
+ ret = devm_clk_bulk_get(&pdev->dev, MTK_DIP_CLK_NUM,
+ dip_hw->clks);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get LARB5 and DIP clks:%d\n",
+ ret);
+ return ret;
+ }
+
+ dip_hw->scp_pdev = scp_get_pdev(dip_dev->pdev);
+ if (!dip_hw->scp_pdev) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: failed to get scp device\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(dip_dev->pdev->dev.of_node,
+ "mediatek,scp",
+ &rproc_phandle)) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: could not get scp device\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ dip_hw->rproc_handle = rproc_get_by_phandle(rproc_phandle);
+ if (!dip_hw->rproc_handle) {
+ dev_err(&dip_dev->pdev->dev,
+ "%s: could not get DIP's rproc_handle\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ atomic_set(&dip_hw->dip_stream_cnt, 0);
+ atomic_set(&dip_hw->dip_enque_cnt, 0);
+ atomic_set(&dip_hw->num_composing, 0);
+ atomic_set(&dip_hw->num_running, 0);
+ mutex_init(&dip_hw->hw_op_lock);
+ /* Limited by the co-processor side's stack size */
+ sema_init(&dip_hw->sem, DIP_COMPOSING_MAX_NUM);
+
+ ret = mtk_dip_hw_working_buf_pool_init(dip_hw);
+ if (ret) {
+ dev_err(&pdev->dev, "working buffer init failed(%d)\n", ret);
+ return ret;
+ }
+
+ ret = mtk_dip_dev_v4l2_init(dip_dev);
+ if (ret)
+ dev_err(&pdev->dev, "v4l2 init failed(%d)\n", ret);
+
+ pm_runtime_enable(&pdev->dev);
+
+ return ret;
+}
+
+static int mtk_dip_remove(struct platform_device *pdev)
+{
+ struct mtk_dip_dev *dip_dev = dev_get_drvdata(&pdev->dev);
+
+ mtk_dip_hw_working_buf_pool_release(&dip_dev->dip_hw);
+ mtk_dip_dev_v4l2_release(dip_dev);
+ pm_runtime_disable(&pdev->dev);
+ mutex_destroy(&dip_dev->dip_hw.hw_op_lock);
+
+ return 0;
+}
+
+static int __maybe_unused mtk_dip_pm_suspend(struct device *dev)
+{
+ struct mtk_dip_dev *dip_dev = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(MTK_DIP_CLK_NUM, dip_dev->dip_hw.clks);
+ dev_dbg(dev, "%s: disabled dip clks\n", __func__);
+
+ return 0;
+}
+
+static int __maybe_unused mtk_dip_pm_resume(struct device *dev)
+{
+ struct mtk_dip_dev *dip_dev = dev_get_drvdata(dev);
+ int ret = clk_bulk_prepare_enable(MTK_DIP_CLK_NUM,
+ dip_dev->dip_hw.clks);
+ if (ret) {
+ dev_err(dev, "%s: failed to enable dip clks(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ dev_dbg(dev, "%s: enabled dip clks\n", __func__);
+
+ ret = rproc_boot(dip_dev->dip_hw.rproc_handle);
+ if (ret)
+ dev_err(dev, "%s: FW load failed(rproc:%p):%d\n",
+ __func__, dip_dev->dip_hw.rproc_handle, ret);
+ else
+ dev_dbg(dev, "%s: FW loaded(rproc:%p)\n",
+ __func__, dip_dev->dip_hw.rproc_handle);
+
+ return ret;
+}
+
+static int mtk_dip_get_scp_job_num(struct mtk_dip_dev *dip_dev)
+{
+ int num;
+
+ mutex_lock(&dip_dev->dip_hw.hw_op_lock);
+ num = atomic_read(&dip_dev->dip_hw.num_composing);
+ mutex_unlock(&dip_dev->dip_hw.hw_op_lock);
+
+ return num;
+}
+
+static int __maybe_unused mtk_dip_suspend(struct device *dev)
+{
+ struct mtk_dip_dev *dip_dev = dev_get_drvdata(dev);
+ int ret, num;
+
+ if (pm_runtime_suspended(dev)) {
+ dev_dbg(dev, "%s: pm_runtime_suspended is true, no action\n",
+ __func__);
+ return 0;
+ }
+
+ ret = wait_event_timeout(dip_dev->dip_hw.flushing_hw_wq,
+ !(num = mtk_dip_get_scp_job_num(dip_dev)),
+ msecs_to_jiffies(200));
+ if (!ret && num) {
+ dev_err(dev, "%s: flushing SCP job timeout, num(%d)\n",
+ __func__, num);
+
+ return -EBUSY;
+ }
+
+ return mtk_dip_pm_suspend(dev);
+}
+
+static int __maybe_unused mtk_dip_resume(struct device *dev)
+{
+ if (pm_runtime_suspended(dev)) {
+ dev_dbg(dev, "%s: pm_runtime_suspended is true, no action\n",
+ __func__);
+ return 0;
+ }
+
+ return mtk_dip_pm_resume(dev);
+}
+
+static const struct dev_pm_ops mtk_dip_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mtk_dip_suspend, mtk_dip_resume)
+ SET_RUNTIME_PM_OPS(mtk_dip_suspend, mtk_dip_resume, NULL)
+};
+
+static const struct of_device_id mtk_dip_of_match[] = {
+ { .compatible = "mediatek,mt8183-dip", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mtk_dip_of_match);
+
+static struct platform_driver mtk_dip_driver = {
+ .probe = mtk_dip_probe,
+ .remove = mtk_dip_remove,
+ .driver = {
+ .name = "camera-dip",
+ .pm = &mtk_dip_pm_ops,
+ .of_match_table = of_match_ptr(mtk_dip_of_match),
+ }
+};
+
+module_platform_driver(mtk_dip_driver);
+
+MODULE_AUTHOR("Frederic Chen <frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Mediatek DIP driver");
+
--
2.18.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox