* [PATCH v2 1/4] firmware: meson_sm: Mark chip struct as static const
From: Carlo Caione @ 2019-07-31 8:23 UTC (permalink / raw)
To: srinivas.kandagatla, khilman, narmstrong, robh+dt, tglx, jbrunet,
linux-arm-kernel, linux-amlogic, devicetree
Cc: Carlo Caione
In-Reply-To: <20190731082339.20163-1-ccaione@baylibre.com>
No need to be a global struct.
Reviewed-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
drivers/firmware/meson/meson_sm.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
index 8d908a8e0d20..772ca6726e7b 100644
--- a/drivers/firmware/meson/meson_sm.c
+++ b/drivers/firmware/meson/meson_sm.c
@@ -35,7 +35,7 @@ struct meson_sm_chip {
struct meson_sm_cmd cmd[];
};
-struct meson_sm_chip gxbb_chip = {
+static const struct meson_sm_chip gxbb_chip = {
.shmem_size = SZ_4K,
.cmd_shmem_in_base = 0x82000020,
.cmd_shmem_out_base = 0x82000021,
--
2.20.1
^ permalink raw reply related
* [PATCH v2 0/4] Rework secure-monitor driver
From: Carlo Caione @ 2019-07-31 8:23 UTC (permalink / raw)
To: srinivas.kandagatla, khilman, narmstrong, robh+dt, tglx, jbrunet,
linux-arm-kernel, linux-amlogic, devicetree
Cc: Carlo Caione
The secure-monitor driver is currently in really bad shape, not my
proudest piece of code (thanks Jerome for pointing that out ;). I tried
to rework it a bit to make it a bit more tolerable.
I needed to change a bit the APIs and consequently adapt the only user
we have, that is the nvmem/efuses driver. To not break bisectability I
added one single commit to change both the drivers.
The remaining commits are cosmetic and DTS/docs fixes.
Changelog:
- Changed patches order to not break bisect
- Removed non-functional changes (PATCH 1/5) of the nvmem driver
- Fix gxbb/gxl DTS
Carlo Caione (4):
firmware: meson_sm: Mark chip struct as static const
nvmem: meson-efuse: bindings: Add secure-monitor phandle
arm64: dts: meson: Link nvmem and secure-monitor nodes
firmware: meson_sm: Rework driver as a proper platform driver
.../bindings/nvmem/amlogic-efuse.txt | 6 ++
arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 1 +
arch/arm64/boot/dts/amlogic/meson-g12a.dtsi | 1 +
arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 1 +
drivers/firmware/meson/meson_sm.c | 96 +++++++++++++------
drivers/nvmem/meson-efuse.c | 24 ++++-
include/linux/firmware/meson/meson_sm.h | 15 +--
7 files changed, 104 insertions(+), 40 deletions(-)
--
2.20.1
^ permalink raw reply
* Re: [PATCH 1/3] arm64: dts: qcom: pms405: add unit name adc nodes
From: Vinod Koul @ 2019-07-31 8:15 UTC (permalink / raw)
To: Amit Kucheria
Cc: Andy Gross, linux-arm-msm, Bjorn Andersson, Rob Herring,
Mark Rutland,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS, LKML
In-Reply-To: <CAHLCerNsAX4raauTjogOpwqAjEWfd+jpaZYsFnC10tcmvnD5cg@mail.gmail.com>
Thanks for the review Amit!
On 30-07-19, 22:05, Amit Kucheria wrote:
> On Thu, Jul 25, 2019 at 7:23 PM Vinod Koul <vkoul@kernel.org> wrote:
> >
> > The adc nodes have reg property but were missing the unit name, so add
> > that to fix these warnings:
> >
> > arch/arm64/boot/dts/qcom/pms405.dtsi:91.12-94.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/ref_gnd: node has a reg or ranges property, but no unit name
> > arch/arm64/boot/dts/qcom/pms405.dtsi:96.14-99.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/vref_1p25: node has a reg or ranges property, but no unit name
> > arch/arm64/boot/dts/qcom/pms405.dtsi:101.19-104.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/vph_pwr: node has a reg or ranges property, but no unit name
> > arch/arm64/boot/dts/qcom/pms405.dtsi:106.13-109.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/die_temp: node has a reg or ranges property, but no unit name
> > arch/arm64/boot/dts/qcom/pms405.dtsi:111.27-116.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/thermistor1: node has a reg or ranges property, but no unit name
> > arch/arm64/boot/dts/qcom/pms405.dtsi:118.27-123.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/thermistor3: node has a reg or ranges property, but no unit name
> > arch/arm64/boot/dts/qcom/pms405.dtsi:125.22-130.6: Warning (unit_address_vs_reg): /soc@0/spmi@200f000/pms405@0/adc@3100/xo_temp: node has a reg or ranges property, but no unit name
> >
> > Signed-off-by: Vinod Koul <vkoul@kernel.org>
> > ---
> > arch/arm64/boot/dts/qcom/pms405.dtsi | 14 +++++++-------
> > 1 file changed, 7 insertions(+), 7 deletions(-)
> >
> > diff --git a/arch/arm64/boot/dts/qcom/pms405.dtsi b/arch/arm64/boot/dts/qcom/pms405.dtsi
> > index 14240fedd916..3c10cf04d26e 100644
> > --- a/arch/arm64/boot/dts/qcom/pms405.dtsi
> > +++ b/arch/arm64/boot/dts/qcom/pms405.dtsi
> > @@ -88,41 +88,41 @@
> > #size-cells = <0>;
> > #io-channel-cells = <1>;
> >
> > - ref_gnd {
> > + ref_gnd@0 {
> > reg = <ADC5_REF_GND>;
> > qcom,pre-scaling = <1 1>;
> > };
> >
> > - vref_1p25 {
> > + vref_1p25@1 {
> > reg = <ADC5_1P25VREF>;
> > qcom,pre-scaling = <1 1>;
> > };
> >
> > - pon_1: vph_pwr {
> > + pon_1: vph_pwr@131 {
> > reg = <ADC5_VPH_PWR>;
> > qcom,pre-scaling = <1 3>;
> > };
> >
> > - die_temp {
> > + die_temp@6 {
> > reg = <ADC5_DIE_TEMP>;
> > qcom,pre-scaling = <1 1>;
> > };
> >
> > - pa_therm1: thermistor1 {
> > + pa_therm1: thermistor1@115 {
>
> s/115/77 ?
>
> > reg = <ADC5_AMUX_THM1_100K_PU>;
> > qcom,ratiometric;
> > qcom,hw-settle-time = <200>;
> > qcom,pre-scaling = <1 1>;
> > };
> >
> > - pa_therm3: thermistor3 {
> > + pa_therm3: thermistor3@117 {
>
> s/117/79 ?
>
> > reg = <ADC5_AMUX_THM3_100K_PU>;
> > qcom,ratiometric;
> > qcom,hw-settle-time = <200>;
> > qcom,pre-scaling = <1 1>;
> > };
> >
> > - xo_therm: xo_temp {
> > + xo_therm: xo_temp@114 {
>
> s/114/76 ?
Thanks, will fix these and recheck others.
--
~Vinod
^ permalink raw reply
* Re: [PATCH] arm64: dts: renesas: r8a77995: draak: Fix backlight regulator name
From: Laurent Pinchart @ 2019-07-31 8:12 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: devicetree, Laurent Pinchart, Magnus Damm, linux-renesas-soc,
Simon Horman, linux-arm-kernel, Marek Vasut
In-Reply-To: <20190731074801.5706-1-geert+renesas@glider.be>
Hi Geert,
(CC'ing the device tree mailing list)
Thank you for the patch.
On Wed, Jul 31, 2019 at 09:48:01AM +0200, Geert Uytterhoeven wrote:
> Currently there are two nodes named "regulator1" in the Draak DTS: a
> 3.3V regulator for the eMMC and the LVDS decoder, and a 12V regulator
> for the backlight. This causes the former to be overwritten by the
> latter.
>
> Fix this by renaming all regulators with numerical suffixes to use named
> suffixes, which are less likely to conflict.
Aren't DT node names supposed to describe the device type, not a
particular instance of the device ? This is something that has bothered
me too, but I believe the naming scheme should be decided globally, not
per board. Is there precedent for using this scheme that has been
explicitly approved by the DT maintainers ?
> Fixes: 4fbd4158fe8967e9 ("arm64: dts: renesas: r8a77995: draak: Add backlight")
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
> I guess this is a fix for v5.3?
>
> This fix takes a slightly different approach than commit
> 12105cec654cf906 ("arm64: dts: renesas: r8a77990: ebisu: Fix backlight
> regulator numbering"), which just fixed the conflicting numerical
> suffix.
> ---
> arch/arm64/boot/dts/renesas/r8a77995-draak.dts | 6 +++---
> 1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/boot/dts/renesas/r8a77995-draak.dts b/arch/arm64/boot/dts/renesas/r8a77995-draak.dts
> index 0711170b26b1fe1c..3aa2564dfdc25fff 100644
> --- a/arch/arm64/boot/dts/renesas/r8a77995-draak.dts
> +++ b/arch/arm64/boot/dts/renesas/r8a77995-draak.dts
> @@ -97,7 +97,7 @@
> reg = <0x0 0x48000000 0x0 0x18000000>;
> };
>
> - reg_1p8v: regulator0 {
> + reg_1p8v: regulator-1p8v {
> compatible = "regulator-fixed";
> regulator-name = "fixed-1.8V";
> regulator-min-microvolt = <1800000>;
> @@ -106,7 +106,7 @@
> regulator-always-on;
> };
>
> - reg_3p3v: regulator1 {
> + reg_3p3v: regulator-3p3v {
> compatible = "regulator-fixed";
> regulator-name = "fixed-3.3V";
> regulator-min-microvolt = <3300000>;
> @@ -115,7 +115,7 @@
> regulator-always-on;
> };
>
> - reg_12p0v: regulator1 {
> + reg_12p0v: regulator-12p0v {
> compatible = "regulator-fixed";
> regulator-name = "D12.0V";
> regulator-min-microvolt = <12000000>;
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [alsa-devel] [PATCH v2 3/3] ASoC: TDA7802: Add turn-on diagnostic routine
From: Charles Keepax @ 2019-07-31 8:03 UTC (permalink / raw)
To: Thomas Preston
Cc: Mark Brown, Mark Rutland, devicetree, alsa-devel,
Kuninori Morimoto, Kirill Marinushkin, Liam Girdwood,
Marco Felsch, Annaliese McDermond, Takashi Iwai, Paul Cercueil,
Vinod Koul, Rob Herring, Srinivas Kandagatla, Jerome Brunet,
linux-kernel, Cheng-Yi Chiang
In-Reply-To: <9b47a360-3b62-b968-b8d5-8639dc4b468d@codethink.co.uk>
On Tue, Jul 30, 2019 at 05:28:11PM +0100, Thomas Preston wrote:
> On 30/07/2019 16:50, Mark Brown wrote:
> > On Tue, Jul 30, 2019 at 04:25:56PM +0100, Thomas Preston wrote:
> >> On 30/07/2019 15:19, Mark Brown wrote:
> >
> >>> It is unclear what this mutex usefully protects, it only gets taken when
> >>> writing to the debugfs file to trigger this diagnostic mode but doesn't
> >>> do anything to control interactions with any other code path in the
> >>> driver.
> >
> >> If another process reads the debugfs node "diagnostic" while the turn-on
> >> diagnostic mode is running, this mutex prevents the second process
> >> restarting the diagnostics.
> >
> >> This is redundant if debugfs reads are atomic, but I don't think they are.
> >
> > Like I say it's not just debugfs though, there's the standard driver
> > interface too.
> >
>
> Ah right, I understand. So if we run the turn-on diagnostics routine, there's
> nothing stopping anyone from interacting with the device in other ways.
>
> I guess there's no way to share that mutex with ALSA? In that case, it doesn't
> matter if this mutex is there or not - this feature is incompatible. How
> compatible do debugfs interfaces have to be? I was under the impression anything
> goes. I would argue that the debugfs is better off for having the mutex so
> that no one re-reads "diagnostic" within the 5s poll timeout.
>
> Alternatively, this diagnostic feature could be handled with an external-handler
> kcontrol SOC_SINGLE_EXT? I'm not sure if this is an atomic interface either.
>
> What would be acceptable?
You could take the DAPM mutex in your debugfs handler that would
prevent any changes to the cards power state whilst your debug
stuff is running.
Thanks,
Charles
^ permalink raw reply
* DTC check_duplicate_node_names (was: Re: [PATCH] arm64: dts: renesas: r8a77990: ebisu: Fix backlight regulator numbering)
From: Geert Uytterhoeven @ 2019-07-31 7:56 UTC (permalink / raw)
To: Marek Vasut
Cc: open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Laurent Pinchart, Rob Herring, Linux-Renesas, Wolfram Sang,
Simon Horman, Laurent Pinchart, Frank Rowand, Linux ARM,
Marek Vasut
In-Reply-To: <0d08d3c1-94ec-dcbe-ad3d-b079ab2ad17e@gmail.com>
Hi Marek,
Bringing this to the attention of the DTC people...
On Thu, Jan 10, 2019 at 3:38 PM Marek Vasut <marek.vasut@gmail.com> wrote:
> On 1/10/19 1:59 PM, Laurent Pinchart wrote:
> > On Wednesday, 9 January 2019 18:58:23 EET Simon Horman wrote:
> >> On Wed, Jan 09, 2019 at 04:26:25PM +0100, Geert Uytterhoeven wrote:
> >>> On Wed, Jan 9, 2019 at 3:01 PM <marek.vasut@gmail.com> wrote:
> >>>> From: Marek Vasut <marek.vasut+renesas@gmail.com>
> >>>>
> >>>> There are two regulator1 nodes in the Ebisu DTS right now, one 3.3V for
> >>>> the eMMC and one 12V for the backlight. This causes one to be
> >>>> overwritten
> >>>> by the other, ultimatelly resulting in inoperable eMMC, which depends on
> >>>> the former. Fix this by renumbering the backlight regulator to
> >>>> regulator2.
> >>>>
> >>>> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
> >>>> Cc: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> >>>> Cc: Simon Horman <horms+renesas@verge.net.au>
> >>>> Cc: Wolfram Sang <wsa+renesas@sang-engineering.com>
> >>>> Cc: linux-renesas-soc@vger.kernel.org
> >>>> Reported-by: Simon Horman <horms+renesas@verge.net.au>
> >>>> Fixes: 9d16c4a10e07 ("arm64: dts: renesas: r8a77990: ebisu: Add
> >>>> backlight")
> >>>
> >>> Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
> >>>
> >>>> --- a/arch/arm64/boot/dts/renesas/r8a77990-ebisu.dts
> >>>> +++ b/arch/arm64/boot/dts/renesas/r8a77990-ebisu.dts
> >>>> @@ -191,7 +191,7 @@
> >>>>
> >>>> clock-frequency = <24576000>;
> >>>>
> >>>> };
> >>>>
> >>>> - reg_12p0v: regulator1 {
> >>>> + reg_12p0v: regulator2 {
> >>>>
> >>>> compatible = "regulator-fixed";
> >>>> regulator-name = "D12.0V";
> >>>> regulator-min-microvolt = <12000000>;
> >>>
> >>> Perhaps the node name should get a more descriptive suffix
> >>> (e.g. "regulator-12p0v"), like is already done for some of the other
> >>> regulators?
> >>
> >> I think I would prefer that addressed in a follow-up patch.
> >
> > Agreed, but it would still be a very good idea. I think we need to standardize
> > names for regulators, otherwise this is bound to happen again in the future.
And so it did (patch sent for the same bug in r8a77995-draak.dts).
> Isn't the YAML DT schema validator supposed to catch those problems ?
> I'd even expect DTC to be able to catch such duplicate nodes and warn
> about them.
DTC indeed has check_duplicate_node_names.
However, it only works for the base DTS, not for any later modifications in
the board DTS.
I.e. the original dup-nodename.dts in the DTC testsuite triggers an error,
but the modified version below doesn't.
--- a/tests/dup-nodename.dts
+++ b/tests/dup-nodename.dts
@@ -1,8 +1,11 @@
/dts-v1/;
+/ {
+};
+
/ {
node {
};
node {
};
};
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
* [PATCH v2 2/2] ARM: dts: aspeed: Add Mihawk BMC platform
From: Ben Pai @ 2019-07-31 7:47 UTC (permalink / raw)
To: robh+dt, mark.rutland, joel, andrew, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel
Cc: wangat, Ben Pai
The Mihawk BMC is an ASPEED ast2500 based BMC that is part of an
OpenPower Power9 server.
Signed-off-by: Ben Pai <Ben_Pai@wistron.com>
---
arch/arm/boot/dts/Makefile | 1 +
arch/arm/boot/dts/aspeed-bmc-opp-mihawk.dts | 907 ++++++++++++++++++++
2 files changed, 908 insertions(+)
create mode 100755 arch/arm/boot/dts/aspeed-bmc-opp-mihawk.dts
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index eb6de52c1936..262345544359 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -1281,5 +1281,6 @@ dtb-$(CONFIG_ARCH_ASPEED) += \
aspeed-bmc-opp-vesnin.dtb \
aspeed-bmc-opp-witherspoon.dtb \
aspeed-bmc-opp-zaius.dtb \
+ aspeed-bmc-opp-mihawk.dtb \
aspeed-bmc-portwell-neptune.dtb \
aspeed-bmc-quanta-q71l.dtb
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-mihawk.dts b/arch/arm/boot/dts/aspeed-bmc-opp-mihawk.dts
new file mode 100755
index 000000000000..913c94326f3f
--- /dev/null
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-mihawk.dts
@@ -0,0 +1,907 @@
+/dts-v1/;
+
+#include "aspeed-g5.dtsi"
+#include <dt-bindings/gpio/aspeed-gpio.h>
+#include <dt-bindings/leds/leds-pca955x.h>
+
+/ {
+ model = "Mihawk BMC";
+ compatible = "ibm,mihawk-bmc", "aspeed,ast2500";
+
+
+ chosen {
+ stdout-path = &uart5;
+ bootargs = "console=ttyS4,115200 earlyprintk";
+ };
+
+ memory@80000000 {
+ reg = <0x80000000 0x20000000>; /* address and size of RAM(512MB) */
+ };
+
+ reserved-memory {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ flash_memory: region@98000000 {
+ no-map;
+ reg = <0x98000000 0x04000000>; /* 64M */
+ };
+
+ gfx_memory: framebuffer {
+ size = <0x01000000>;
+ alignment = <0x01000000>;
+ compatible = "shared-dma-pool";
+ reusable;
+ };
+
+ video_engine_memory: jpegbuffer {
+ size = <0x02000000>; /* 32MM */
+ alignment = <0x01000000>;
+ compatible = "shared-dma-pool";
+ reusable;
+ };
+ };
+
+ gpio-keys {
+ compatible = "gpio-keys";
+
+ air-water {
+ label = "air-water";
+ gpios = <&gpio ASPEED_GPIO(F, 6) GPIO_ACTIVE_LOW>;
+ linux,code = <ASPEED_GPIO(F, 6)>;
+ };
+
+ checkstop {
+ label = "checkstop";
+ gpios = <&gpio ASPEED_GPIO(J, 2) GPIO_ACTIVE_LOW>;
+ linux,code = <ASPEED_GPIO(J, 2)>;
+ };
+
+ ps0-presence {
+ label = "ps0-presence";
+ gpios = <&gpio ASPEED_GPIO(Z, 2) GPIO_ACTIVE_LOW>;
+ linux,code = <ASPEED_GPIO(Z, 2)>;
+ };
+
+ ps1-presence {
+ label = "ps1-presence";
+ gpios = <&gpio ASPEED_GPIO(Z, 0) GPIO_ACTIVE_LOW>;
+ linux,code = <ASPEED_GPIO(Z, 0)>;
+ };
+ id-button {
+ label = "id-button";
+ gpios = <&gpio ASPEED_GPIO(F, 1) GPIO_ACTIVE_LOW>;
+ linux,code = <ASPEED_GPIO(F, 1)>;
+ };
+ };
+
+ gpio-keys-polled {
+ compatible = "gpio-keys-polled";
+ #address-cells = <1>;
+ poll-interval = <1000>;
+
+ fan0-presence {
+ label = "fan0-presence";
+ gpios = <&pca9552 9 GPIO_ACTIVE_LOW>;
+ linux,code = <9>;
+ };
+
+ fan1-presence {
+ label = "fan1-presence";
+ gpios = <&pca9552 10 GPIO_ACTIVE_LOW>;
+ linux,code = <10>;
+ };
+
+ fan2-presence {
+ label = "fan2-presence";
+ gpios = <&pca9552 11 GPIO_ACTIVE_LOW>;
+ linux,code = <11>;
+ };
+
+ fan3-presence {
+ label = "fan3-presence";
+ gpios = <&pca9552 12 GPIO_ACTIVE_LOW>;
+ linux,code = <12>;
+ };
+
+ fan4-presence {
+ label = "fan4-presence";
+ gpios = <&pca9552 13 GPIO_ACTIVE_LOW>;
+ linux,code = <13>;
+ };
+
+ fan5-presence {
+ label = "fan5-presence";
+ gpios = <&pca9552 14 GPIO_ACTIVE_LOW>;
+ linux,code = <14>;
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ fault {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&gpio ASPEED_GPIO(AA, 0) GPIO_ACTIVE_LOW>;
+ };
+
+ power {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&gpio ASPEED_GPIO(AA, 1) GPIO_ACTIVE_LOW>;
+ };
+
+ rear-id {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&gpio ASPEED_GPIO(AA, 2) GPIO_ACTIVE_LOW>;
+ };
+
+ rear-g {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&gpio ASPEED_GPIO(AA, 4) GPIO_ACTIVE_LOW>;
+ };
+
+ rear-ok {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&gpio ASPEED_GPIO(Y, 0) GPIO_ACTIVE_LOW>;
+ };
+
+ fan0 {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&pca9552 0 GPIO_ACTIVE_LOW>;
+ };
+
+ fan1 {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&pca9552 1 GPIO_ACTIVE_LOW>;
+ };
+
+ fan2 {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&pca9552 2 GPIO_ACTIVE_LOW>;
+ };
+
+ fan3 {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&pca9552 3 GPIO_ACTIVE_LOW>;
+ };
+
+ fan4 {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&pca9552 4 GPIO_ACTIVE_LOW>;
+ };
+
+ fan5 {
+ retain-state-shutdown;
+ default-state = "keep";
+ gpios = <&pca9552 5 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ fsi: gpio-fsi {
+ compatible = "fsi-master-gpio", "fsi-master";
+ #address-cells = <2>;
+ #size-cells = <0>;
+ no-gpio-delays;
+
+ clock-gpios = <&gpio ASPEED_GPIO(E, 6) GPIO_ACTIVE_HIGH>;
+ data-gpios = <&gpio ASPEED_GPIO(E, 7) GPIO_ACTIVE_HIGH>;
+ mux-gpios = <&gpio ASPEED_GPIO(E, 5) GPIO_ACTIVE_HIGH>;
+ enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>;
+ trans-gpios = <&gpio ASPEED_GPIO(R, 2) GPIO_ACTIVE_HIGH>;
+ };
+ iio-hwmon-12v {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 0>;
+ };
+
+ iio-hwmon-5v {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 1>;
+ };
+
+ iio-hwmon-3v {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 2>;
+ };
+
+ iio-hwmon-vdd0 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 3>;
+ };
+
+ iio-hwmon-vdd1 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 4>;
+ };
+
+ iio-hwmon-vcs0 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 5>;
+ };
+
+ iio-hwmon-vcs1 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 6>;
+ };
+
+ iio-hwmon-vdn0 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 7>;
+ };
+
+ iio-hwmon-vdn1 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 8>;
+ };
+
+ iio-hwmon-vio0 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 9>;
+ };
+
+ iio-hwmon-vio1 {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 10>;
+ };
+
+ iio-hwmon-vddra {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 11>;
+ };
+
+ iio-hwmon-vddrb {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 13>;
+ };
+
+ iio-hwmon-vddrc {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 14>;
+ };
+
+ iio-hwmon-vddrd {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 15>;
+ };
+
+ iio-hwmon-battery {
+ compatible = "iio-hwmon";
+ io-channels = <&adc 12>;
+ };
+};
+
+&pwm_tacho {
+ status = "okay";
+ /*compatible = "aspeed,ast2500-pwm-tacho";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0x1e786000 0x1000>;
+ clocks = <&pwm_tacho_fixed_clk>;*/
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default
+ &pinctrl_pwm2_default &pinctrl_pwm3_default
+ &pinctrl_pwm4_default &pinctrl_pwm5_default>;
+
+ fan@0 {
+ reg = <0x00>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x00>;
+ };
+
+ fan@1 {
+ reg = <0x01>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x01>;
+ };
+
+ fan@2 {
+ reg = <0x02>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x02>;
+ };
+
+ fan@3 {
+ reg = <0x03>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x03>;
+ };
+
+ fan@4 {
+ reg = <0x04>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x04>;
+ };
+
+ fan@5 {
+ reg = <0x05>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x05>;
+ };
+
+ fan@6 {
+ reg = <0x00>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x06>;
+ };
+
+ fan@7 {
+ reg = <0x01>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x07>;
+ };
+
+ fan@8 {
+ reg = <0x02>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x08>;
+ };
+
+ fan@9 {
+ reg = <0x03>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x09>;
+ };
+
+ fan@10 {
+ reg = <0x04>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x0a>;
+ };
+
+ fan@11 {
+ reg = <0x05>;
+ aspeed,fan-tach-ch = /bits/ 8 <0x0b>;
+ };
+};
+
+&fmc {
+ status = "okay";
+ flash@0 {
+ status = "okay";
+ label = "bmc";
+ m25p,fast-read;
+ spi-max-frequency = <50000000>;
+ partitions {
+ #address-cells = < 1 >;
+ #size-cells = < 1 >;
+ compatible = "fixed-partitions";
+ u-boot@0 {
+ reg = < 0 0x60000 >;
+ label = "u-boot";
+ };
+ u-boot-env@60000 {
+ reg = < 0x60000 0x20000 >;
+ label = "u-boot-env";
+ };
+ obmc-ubi@80000 {
+ reg = < 0x80000 0x1F80000 >;
+ label = "obmc-ubi";
+ };
+ };
+ };
+ flash@1 {
+ status = "okay";
+ label = "alt-bmc";
+ m25p,fast-read;
+ spi-max-frequency = <50000000>;
+ partitions {
+ #address-cells = < 1 >;
+ #size-cells = < 1 >;
+ compatible = "fixed-partitions";
+ u-boot@0 {
+ reg = < 0 0x60000 >;
+ label = "alt-u-boot";
+ };
+ u-boot-env@60000 {
+ reg = < 0x60000 0x20000 >;
+ label = "alt-u-boot-env";
+ };
+ obmc-ubi@80000 {
+ reg = < 0x80000 0x1F80000 >;
+ label = "alt-obmc-ubi";
+ };
+ };
+ };
+};
+
+&spi1 {
+ status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_spi1_default>;
+
+ flash@0 {
+ status = "okay";
+ label = "pnor";
+ m25p,fast-read;
+ spi-max-frequency = <100000000>;
+ };
+};
+
+&lpc_ctrl {
+ status = "okay";
+ memory-region = <&flash_memory>;
+ flash = <&spi1>;
+};
+
+&uart1 {
+ /* Rear RS-232 connector */
+ status = "okay";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_txd1_default
+ &pinctrl_rxd1_default
+ &pinctrl_nrts1_default
+ &pinctrl_ndtr1_default
+ &pinctrl_ndsr1_default
+ &pinctrl_ncts1_default
+ &pinctrl_ndcd1_default
+ &pinctrl_nri1_default>;
+};
+
+&uart2 {
+ /* APSS */
+ status = "okay";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_txd2_default &pinctrl_rxd2_default>;
+};
+
+&uart5 {
+ status = "okay";
+};
+
+&mac0 {
+ status = "okay";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_rmii1_default>;
+ use-ncsi;
+};
+
+&mac1 {
+ status = "okay";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_rgmii2_default &pinctrl_mdio2_default>;
+};
+
+&i2c0 {
+ status = "disabled";
+};
+
+&i2c1 {
+ status = "disabled";
+};
+
+&i2c2 {
+ status = "okay";
+
+ /* SAMTEC P0 */
+ /* SAMTEC P1 */
+
+};
+
+&i2c3 {
+ status = "okay";
+
+ /* APSS */
+ /* CPLD */
+
+ /* PCA9516 (repeater) ->
+ * CLK Buffer 9FGS9092
+ * CLK Buffer 9DBL0651BKILFT
+ * CLK Buffer 9DBL0651BKILFT
+ * Power Supply 0
+ * Power Supply 1
+ * PCA 9552 LED
+ */
+
+ power-supply@58 {
+ compatible = "ibm,cffps1";
+ reg = <0x58>;
+ };
+
+ power-supply@5b {
+ compatible = "ibm,cffps1";
+ reg = <0x5b>;
+ };
+
+ pca9552: pca9552@60 {
+ compatible = "nxp,pca9552";
+ reg = <0x60>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ gpio@0 {
+ reg = <0>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@1 {
+ reg = <1>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@2 {
+ reg = <2>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@3 {
+ reg = <3>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@4 {
+ reg = <4>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@5 {
+ reg = <5>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@6 {
+ reg = <6>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@7 {
+ reg = <7>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@8 {
+ reg = <8>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@9 {
+ reg = <9>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@10 {
+ reg = <10>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@11 {
+ reg = <11>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@12 {
+ reg = <12>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@13 {
+ reg = <13>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@14 {
+ reg = <14>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+ gpio@15 {
+ reg = <15>;
+ type = <PCA955X_TYPE_GPIO>;
+ };
+
+ };
+
+};
+
+&i2c4 {
+ status = "okay";
+
+ /* CP0 VDD & VCS : IR35221 */
+ /* CP0 VDN : IR35221 */
+ /* CP0 VIO : IR38064 */
+ /* CP0 VDDR : PXM1330 */
+
+ ir35221@70 {
+ compatible = "infineon,ir35221";
+ reg = <0x70>;
+ };
+
+ ir35221@72 {
+ compatible = "infineon,ir35221";
+ reg = <0x72>;
+ };
+
+};
+
+&i2c5 {
+ status = "okay";
+
+ /* CP0 VDD & VCS : IR35221 */
+ /* CP0 VDN : IR35221 */
+ /* CP0 VIO : IR38064 */
+ /* CP0 VDDR : PXM1330 */
+
+ ir35221@70 {
+ compatible = "infineon,ir35221";
+ reg = <0x70>;
+ };
+
+ ir35221@72 {
+ compatible = "infineon,ir35221";
+ reg = <0x72>;
+ };
+
+};
+
+&i2c6 {
+ status = "okay";
+
+ /* pca9548 -> NVMe1 to 8 */
+
+ pca9548@70 {
+ compatible = "nxp,pca9548";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+ };
+
+};
+
+&i2c7 {
+ status = "okay";
+
+ /* pca9548 -> NVMe9 to 16 */
+
+ pca9548@70 {
+ compatible = "nxp,pca9548";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+ };
+
+};
+
+&i2c8 {
+ status = "okay";
+
+ eeprom@50 {
+ compatible = "atmel,24c64";
+ reg = <0x50>;
+ };
+};
+
+&i2c9 {
+ status = "okay";
+
+ /* pca9545 Riser ->
+ * PCIe x8 Slot3
+ * PCIe x16 slot4
+ * PCIe x8 slot5
+ * I2C BMC RISER PCA9554
+ * BMC SCL/SDA PCA9554
+ * PCA9554
+ */
+
+ /* pca9545 ->
+ * PCIe x16 Slot1
+ * PCIe x8 slot2
+ * PEX8748
+ */
+
+ pca9545riser@70 {
+ compatible = "nxp,pca9545";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+
+ i2c-mux-idle-disconnect;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ pca9545@71 {
+ compatible = "nxp,pca9545";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x71>;
+
+ i2c-mux-idle-disconnect;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+};
+
+&i2c10 {
+ status = "okay";
+
+ /* pca9545 Riser ->
+ * PCIe x8 Slot8
+ * PCIe x16 slot9
+ * PCIe x8 slot10
+ * I2C BMC RISER PCA9554
+ * BMC SCL/SDA PCA9554
+ * PCA9554
+ */
+
+ /* pca9545 ->
+ * PCIe x16 Slot1
+ * PCIe x8 slot2
+ * PEX8748
+ */
+
+ pca9545riser@70 {
+ compatible = "nxp,pca9545";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+
+ i2c-mux-idle-disconnect;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ pca9545@71 {
+ compatible = "nxp,pca9545";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x71>;
+
+ i2c-mux-idle-disconnect;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+};
+
+&i2c11 {
+ status = "okay";
+
+ /* TPM */
+ /* RTC RX8900CE */
+ /* FPGA for power sequence */
+ /* TMP275A */
+ /* TMP275A */
+ /* EMC1462 */
+
+ tpm@57 {
+ compatible = "infineon,slb9645tt";
+ reg = <0x57>;
+ };
+
+ rtc@32 {
+ compatible = "epson,rx8900";
+ reg = <0x32>;
+ };
+
+ tmp275@48 {
+ compatible = "ti,tmp275";
+ reg = <0x48>;
+ };
+
+ tmp275@49 {
+ compatible = "ti,tmp275";
+ reg = <0x49>;
+ };
+
+ /* chip emc1462 use emc1403 driver */
+ emc1403@4c {
+ compatible = "smsc,emc1403";
+ reg = <0x4c>;
+ };
+
+};
+
+&i2c12 {
+ status = "okay";
+
+ /* pca9545 ->
+ * SAS BP1
+ * SAS BP2
+ * NVMe BP
+ * M.2 riser
+ */
+
+ pca9545@70 {
+ compatible = "nxp,pca9545";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
+ i2c@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+
+ eeprom@50 {
+ compatible = "atmel,24c64";
+ reg = <0x50>;
+ };
+ };
+
+ i2c@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+
+ eeprom@50 {
+ compatible = "atmel,24c64";
+ reg = <0x50>;
+ };
+ };
+
+ i2c@2 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <2>;
+
+ eeprom@50 {
+ compatible = "atmel,24c64";
+ reg = <0x50>;
+ };
+ };
+
+ i2c@3 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <3>;
+
+ tmp275@48 {
+ compatible = "ti,tmp275";
+ reg = <0x48>;
+ };
+ };
+
+ };
+
+};
+
+&i2c13 {
+ status = "okay";
+
+ /* pca9548 ->
+ * NVMe BP
+ * NVMe HDD17 to 24
+ */
+
+ pca9548@70 {
+ compatible = "nxp,pca9548";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+ };
+};
+
+&vuart {
+ status = "okay";
+};
+
+&gfx {
+ status = "okay";
+ memory-region = <&gfx_memory>;
+};
+
+&adc {
+ status = "okay";
+};
+
+&wdt1 {
+ aspeed,reset-type = "none";
+ aspeed,external-signal;
+ aspeed,ext-push-pull;
+ aspeed,ext-active-high;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_wdtrst1_default>;
+};
+
+&wdt2 {
+ aspeed,alt-boot;
+};
+
+&ibt {
+ status = "okay";
+};
+
+&vhub {
+ status = "okay";
+};
+
+&video {
+ status = "okay";
+ memory-region = <&video_engine_memory>;
+};
+
+#include "ibm-power9-dual.dtsi"
+
--
2.17.1
^ permalink raw reply related
* Re: [PATCH 1/4] mailbox: arm_mhuv2: add device tree binding documentation
From: Jassi Brar @ 2019-07-31 7:31 UTC (permalink / raw)
To: Morten Borup Petersen
Cc: Tushar Khandelwal, Linux Kernel Mailing List,
tushar.2nov@gmail.com, nd@arm.com, Morten Borup Petersen,
Rob Herring, Mark Rutland, Devicetree List
In-Reply-To: <VI1PR0601MB21113C48E719B2C79EC2FE508FC20@VI1PR0601MB2111.eurprd06.prod.outlook.com>
On Sun, Jul 28, 2019 at 4:28 PM Morten Borup Petersen <morten_bp@live.dk> wrote:
>
>
>
> On 7/25/19 7:49 AM, Jassi Brar wrote:
> > On Sun, Jul 21, 2019 at 4:58 PM Jassi Brar <jassisinghbrar@gmail.com> wrote:
> >>
> >> On Wed, Jul 17, 2019 at 2:26 PM Tushar Khandelwal
> >> <tushar.khandelwal@arm.com> wrote:
> >>
> >>> diff --git a/Documentation/devicetree/bindings/mailbox/arm,mhuv2.txt b/Documentation/devicetree/bindings/mailbox/arm,mhuv2.txt
> >>> new file mode 100644
> >>> index 000000000000..3a05593414bc
> >>> --- /dev/null
> >>> +++ b/Documentation/devicetree/bindings/mailbox/arm,mhuv2.txt
> >>> @@ -0,0 +1,108 @@
> >>> +Arm MHUv2 Mailbox Driver
> >>> +========================
> >>> +
> >>> +The Arm Message-Handling-Unit (MHU) Version 2 is a mailbox controller that has
> >>> +between 1 and 124 channel windows to provide unidirectional communication with
> >>> +remote processor(s).
> >>> +
> >>> +Given the unidirectional nature of the device, an MHUv2 mailbox may only be
> >>> +written to or read from. If a pair of MHU devices is implemented between two
> >>> +processing elements to provide bidirectional communication, these must be
> >>> +specified as two separate mailboxes.
> >>> +
> >>> +A device tree node for an Arm MHUv2 device must specify either a receiver frame
> >>> +or a sender frame, indicating which end of the unidirectional MHU device which
> >>> +the device node entry describes.
> >>> +
> >>> +An MHU device must be specified with a transport protocol. The transport
> >>> +protocol of an MHU device determines the method of data transmission as well as
> >>> +the number of provided mailboxes.
> >>> +Following are the possible transport protocol types:
> >>> +- Single-word: An MHU device implements as many mailboxes as it
> >>> + provides channel windows. Data is transmitted through
> >>> + the MHU registers.
> >>> +- Multi-word: An MHU device implements a single mailbox. All channel windows
> >>> + will be used during transmission. Data is transmitted through
> >>> + the MHU registers.
> >>> +- Doorbell: An MHU device implements as many mailboxes as there are flag
> >>> + bits available in its channel windows. Optionally, data may
> >>> + be transmitted through a shared memory region, wherein the MHU
> >>> + is used strictly as an interrupt generation mechanism.
> >>> +
> >>> +Mailbox Device Node:
> >>> +====================
> >>> +
> >>> +Required properties:
> >>> +--------------------
> >>> +- compatible: Shall be "arm,mhuv2" & "arm,primecell"
> >>> +- reg: Contains the mailbox register address range (base
> >>> + address and length)
> >>> +- #mbox-cells Shall be 1 - the index of the channel needed.
> >>> +- mhu-frame Frame type of the device.
> >>> + Shall be either "sender" or "receiver"
> >>> +- mhu-protocol Transport protocol of the device. Shall be one of the
> >>> + following: "single-word", "multi-word", "doorbell"
> >>> +
> >>> +Required properties (receiver frame):
> >>> +-------------------------------------
> >>> +- interrupts: Contains the interrupt information corresponding to the
> >>> + combined interrupt of the receiver frame
> >>> +
> >>> +Example:
> >>> +--------
> >>> +
> >>> + mbox_mw_tx: mhu@10000000 {
> >>> + compatible = "arm,mhuv2","arm,primecell";
> >>> + reg = <0x10000000 0x1000>;
> >>> + clocks = <&refclk100mhz>;
> >>> + clock-names = "apb_pclk";
> >>> + #mbox-cells = <1>;
> >>> + mhu-protocol = "multi-word";
> >>> + mhu-frame = "sender";
> >>> + };
> >>> +
> >>> + mbox_sw_tx: mhu@10000000 {
> >>> + compatible = "arm,mhuv2","arm,primecell";
> >>> + reg = <0x11000000 0x1000>;
> >>> + clocks = <&refclk100mhz>;
> >>> + clock-names = "apb_pclk";
> >>> + #mbox-cells = <1>;
> >>> + mhu-protocol = "single-word";
> >>> + mhu-frame = "sender";
> >>> + };
> >>> +
> >>> + mbox_db_rx: mhu@10000000 {
> >>> + compatible = "arm,mhuv2","arm,primecell";
> >>> + reg = <0x12000000 0x1000>;
> >>> + clocks = <&refclk100mhz>;
> >>> + clock-names = "apb_pclk";
> >>> + #mbox-cells = <1>;
> >>> + interrupts = <0 45 4>;
> >>> + interrupt-names = "mhu_rx";
> >>> + mhu-protocol = "doorbell";
> >>> + mhu-frame = "receiver";
> >>> + };
> >>> +
> >>> + mhu_client: scb@2e000000 {
> >>> + compatible = "fujitsu,mb86s70-scb-1.0";
> >>> + reg = <0 0x2e000000 0x4000>;
> >>> + mboxes =
> >>> + // For multi-word frames, client may only instantiate a single
> >>> + // mailbox for a mailbox controller
> >>> + <&mbox_mw_tx 0>,
> >>> +
> >>> + // For single-word frames, client may instantiate as many
> >>> + // mailboxes as there are channel windows in the MHU
> >>> + <&mbox_sw_tx 0>,
> >>> + <&mbox_sw_tx 1>,
> >>> + <&mbox_sw_tx 2>,
> >>> + <&mbox_sw_tx 3>,
> >>> +
> >>> + // For doorbell frames, client may instantiate as many mailboxes
> >>> + // as there are bits available in the combined number of channel
> >>> + // windows ((channel windows * 32) mailboxes)
> >>> + <mbox_db_rx 0>,
> >>> + <mbox_db_rx 1>,
> >>> + ...
> >>> + <mbox_db_rx 17>;
> >>> + };
> >>
> >> If the mhuv2 instance implements, say, 3 channel windows between
> >> sender (linux) and receiver (firmware), and Linux runs two protocols
> >> each requiring 1 and 2-word sized messages respectively. The hardware
> >> supports that by assigning windows [0] and [1,2] to each protocol.
> >> However, I don't think the driver can support that. Or does it?
> >>
> > Thinking about it, IMO, the mbox-cell should carry a 128 (4x32) bit
> > mask specifying the set of windows (corresponding to the bits set in
> > the mask) associated with the channel.
> > And the controller driver should see any channel as associated with
> > variable number of windows 'N', where N is [0,124]
> >
> > mhu_client1: proto1@2e000000 {
> > .....
> > mboxes = <&mbox 0x0 0x0 0x0 0x1>
> > }
> >
> > mhu_client2: proto2@2f000000 {
> > .....
> > mboxes = <&mbox 0x0 0x0 0x0 0x6>
> > }
> >
> > Cheers!
> >
>
> As mentioned in the response to your initial comment, the driver does
> not currently support mixing protocols.
>
Thanks for acknowledging that limitation. But lets also address it.
> If mixing protocols is to be supported in the future, then this seems
> like a suitable way of specifying which channels are associated with
> which mailboxes (especially for mixing single- and multi-word modes).
>
We can not change DT bindings again when we feel like updating the driver.
The bindings should precisely and completely define the h/w, not what
mode we currently implement.
It is not for pure idealism, it actually makes the code simpler and futureproof.
> However, there still is an issue in that both single-word and doorbell
> requires only 1 channel window - and as such, the transport protocol
> cannot be deduced from merely the number of masked channel windows.
>
I don't see why the driver should worry -- the channel carries 32-bit
message or some random value just to trigger an interrupt is purely
upto the client driver.
> Furthermore, for doorbell, a mbox may be registered for _each_ available
> bit within a channel window (further complicating things if we were to
> include mixing protocols in this initial driver version), making
> assigning channel windows to mailboxes semantically different from when
> assigning to single- or multi-word.
>
Not sure about that, that would be implementing virtual channels. Each
window carries one signal, and that is the minimum bandwidth assigned
to a channel.
Thanks
^ permalink raw reply
* Re: [PATCH 11/22] ARM: dts: imx6: Add sleep state to can interfaces
From: Michal Vokáč @ 2019-07-31 7:14 UTC (permalink / raw)
To: Philippe Schenker, marcel.ziswiler, max.krummenacher, stefan,
devicetree, Rob Herring, Shawn Guo, Mark Rutland
Cc: Philippe Schenker, Fabio Estevam, linux-kernel, linux-arm-kernel,
Pengutronix Kernel Team, NXP Linux Team, Sascha Hauer
In-Reply-To: <20190730144649.19022-12-dev@pschenker.ch>
On 30. 07. 19 16:46, Philippe Schenker wrote:
> From: Philippe Schenker <philippe.schenker@toradex.com>
>
> This patch prepares the devicetree for the new Ixora V1.2 where we are
> able to turn off the supply of the can transceiver. This implies to use
> a sleep state on transmission pins in order to prevent backfeeding.
>
> Signed-off-by: Philippe Schenker <philippe.schenker@toradex.com>
> ---
What about "ARM: dts: imx6qdl-apalis: " for the subject?
To be clear that this is not related to the imx6 SoC itself.
>
> arch/arm/boot/dts/imx6qdl-apalis.dtsi | 27 +++++++++++++++++++++------
> 1 file changed, 21 insertions(+), 6 deletions(-)
>
> diff --git a/arch/arm/boot/dts/imx6qdl-apalis.dtsi b/arch/arm/boot/dts/imx6qdl-apalis.dtsi
> index 7c4ad541c3f5..59ed2e4a1fd1 100644
> --- a/arch/arm/boot/dts/imx6qdl-apalis.dtsi
> +++ b/arch/arm/boot/dts/imx6qdl-apalis.dtsi
> @@ -148,14 +148,16 @@
> };
>
> &can1 {
> - pinctrl-names = "default";
> - pinctrl-0 = <&pinctrl_flexcan1>;
> + pinctrl-names = "default", "sleep";
> + pinctrl-0 = <&pinctrl_flexcan1_default>;
> + pinctrl-1 = <&pinctrl_flexcan1_sleep>;
> status = "disabled";
> };
>
> &can2 {
> - pinctrl-names = "default";
> - pinctrl-0 = <&pinctrl_flexcan2>;
> + pinctrl-names = "default", "sleep";
> + pinctrl-0 = <&pinctrl_flexcan2_default>;
> + pinctrl-1 = <&pinctrl_flexcan2_sleep>;
> status = "disabled";
> };
>
> @@ -599,19 +601,32 @@
> >;
> };
>
> - pinctrl_flexcan1: flexcan1grp {
> + pinctrl_flexcan1_default: flexcan1defgrp {
> fsl,pins = <
> MX6QDL_PAD_GPIO_7__FLEXCAN1_TX 0x1b0b0
> MX6QDL_PAD_GPIO_8__FLEXCAN1_RX 0x1b0b0
> >;
> };
>
> - pinctrl_flexcan2: flexcan2grp {
> + pinctrl_flexcan1_sleep: flexcan1slpgrp {
> + fsl,pins = <
> + MX6QDL_PAD_GPIO_7__GPIO1_IO07 0x0
> + MX6QDL_PAD_GPIO_8__GPIO1_IO08 0x0
> + >;
> + };
> +
> + pinctrl_flexcan2_default: flexcan2defgrp {
> fsl,pins = <
> MX6QDL_PAD_KEY_COL4__FLEXCAN2_TX 0x1b0b0
> MX6QDL_PAD_KEY_ROW4__FLEXCAN2_RX 0x1b0b0
> >;
> };
> + pinctrl_flexcan2_sleep: flexcan2slpgrp {
> + fsl,pins = <
> + MX6QDL_PAD_KEY_COL4__GPIO4_IO14 0x0
> + MX6QDL_PAD_KEY_ROW4__GPIO4_IO15 0x0
> + >;
> + };
>
> pinctrl_gpio_bl_on: gpioblon {
> fsl,pins = <
>
^ permalink raw reply
* Re: [RFC PATCH V2 4/6] platform: mtk-isp: Add Mediatek DIP driver
From: Tomasz Figa @ 2019-07-31 7:10 UTC (permalink / raw)
To: frederic.chen-NuS5LvNUpcJWk0Htik3J/w
Cc: shik-F7+t8E8rja9g9hUCZPvPmw, devicetree-u79uwXL29TY76Z2rM5mHXA,
Sean.Cheng-NuS5LvNUpcJWk0Htik3J/w,
laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw,
Rynn.Wu-NuS5LvNUpcJWk0Htik3J/w,
christie.yu-NuS5LvNUpcJWk0Htik3J/w,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
Allan.Yang-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,
hans.verkuil-FYB4Gu1CFyUAvxtiuMwx3w,
zwisler-F7+t8E8rja9g9hUCZPvPmw,
matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
mchehab-DgEjT+Ai2ygdnm+yROfE0A,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-media-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20190708110500.7242-5-frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Hi Frederic,
On Mon, Jul 08, 2019 at 07:04:58PM +0800, frederic.chen-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org wrote:
> 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
>
Thanks for v2! The code is getting better, but there are still problems.
Please check my comments inline.
Note that it would be really good if you could collaborate more closely with
Jungo working on the P1 driver, as I noticed similar problems in both
drivers, with some being addressed only in one of the drivers, but not the
other.
> 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;
Rather than hardcoding the number, could we just pass ARRAY_SIZE(desc) to
this function from the caller?
> +
> + 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++) {
i < pipe->num_nodes
> + 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];
> + }
We shouldn't need this if/else block if we put all the descriptors in one
array.
> +
> + pipe->nodes[i].flags =
> + pipe->nodes[i].desc->flags;
No need to wrap the line here.
> + atomic_set(&pipe->nodes[i].sequence, 0);
I don't see this sequence used anywhere else in the driver.
> + 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;
> + }
All the pipes seem to have master set to MTK_DIP_VIDEO_NODE_ID_NO_MASTER, so
this seems to be dead code.
> +
> + 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]);
> + }
> +}
We should remove this kind of excessive debugging code. I'd recommend
keeping it as a local test-only patch on top of the driver.
> +
> +int mtk_dip_pipe_job_finish(struct mtk_dip_pipe *pipe,
> + unsigned int pipe_job_info_id,
> + enum vb2_buffer_state vbf_state)
This function only returns 0. Should it be made void?
> +{
> + 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);
Why don't we just pass mtk_dip_request as an argument ot this function? I
can see that the both callers already have them.
> + 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)
Is it possible that in_buf is NULL?
> + 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);
> + }
This looks almost the same as what's being done inside
mtk_dip_hw_streamoff(). Could we just call this function from the loop
there?
> +
> + 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:
>From all the formats above, we only have B10 and F10 in in_fmts[]. Are they
all supported by DIP? If so, are they only missing in in_fmts[] or the
driver needs more changes to support them?
> + 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);
The __ types are only for UAPI structures. Please use the types without
the prefix.
> +
> + 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);
Could you explain all the calculations done above?
> +
> + 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);
Hmm, stride and bytesperline should be exactly the same thing. Could you
explain what's happening here?
> + 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
> + };
We don't seem to have B8 and F8 formats in in_fmts[]. Are they just missing
there or there is something else missing too?
> +
> + 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);
Please add the information about necessary alignment to struct
mtk_dip_dev_format instead of hardcoding in the local arrays above.
> +
> + 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,
The function name is confusing. "Set" in V4L2 sounds like changing the
active format. How about mtk_dip_pipe_try_fmt()?
> + struct mtk_dip_video_device *node,
> + struct v4l2_pix_format_mplane *mfmt_to_fill,
This is quite a long name. It should be evident that this argument is an
output, so "fmt" should be good enough.
> + 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));
The core already takes care of zeroing the reserved fields.
> +
> + 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);
No need for this print. There is already built in printing of formats in the
core when format-related ioctls are called.
> +
> + for (i = 0; i < mfmt_to_fill->num_planes; ++i) {
> + u32 min_bpl = (mfmt_to_fill->width * dev_fmt->row_depth[i]) / 8;
Shouldn't this use DIV_ROUND_UP()? I can see some row_depth not being a
multiple of 8.
> + u32 bpl = mfmt_to_fill->plane_fmt[i].bytesperline;
In current V4L2 API, bytesperline is always set by the driver, so no need to
preserve what came from the userspace.
> + u32 sizeimage;
> +
> + if (bpl < min_bpl)
> + bpl = min_bpl;
> +
> + sizeimage = (bpl * mfmt_to_fill->height * dev_fmt->depth[i])
> + / dev_fmt->row_depth[i];
Shouldn't this be bpl * fmt->height?
> + 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;
Same with sizeimage. It's defined to be always written by the driver.
> +
> + memset(mfmt_to_fill->plane_fmt[i].reserved,
> + 0, sizeof(mfmt_to_fill->plane_fmt[i].reserved));
The core takes care of this already.
> +
> + 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);
The core already has an appropriate debug message.
> + }
> +}
> +
> +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;
> + }
Is this even possible?
> +
> + 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;
> + }
Is this possible too?
> +
> + 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;
Similar comment as below.
> +
> + 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)
Please prefix the functions with mtk_dip_.
> +{
> + 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;
> + }
Is this even possible?
> +
> + 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);
Shouldn't stride be just equal to pix_mp.plane[i].bytesperline?
> +
> + b->format.plane_fmt[i].stride = stride;
> + b->format.plane_fmt[i].size =
Shouldn't size be just equal to pix_mp.plane[i].sizeimage?
> + 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)) {
Shouldn't this be just (pix_mp->num_planes > 1)?
> + 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) {
I don't see these MDP_ macros defined anywhere. Please don't use macros
defined from other drivers. We can embed this information in the
mtk_dip_dev_format struct. One would normally call it num_cplanes (color
planes).
However, I would just make this driver support M formats only and forget
about formats with only memory planes count != color planes count.
> + 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;
So we don't support cropping?
> + 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];
Why are these casts needed?
> + } 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++;
> + }
Is it possible that we don't have an input buffer?
> +
> + /* 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;
Wouldn't this be always 1?
> +}
> +
> +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);
You can merge this with the declaration by using
> + 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);
> + }
Please check my comments to the P1 RFCv3 for a similar block of code.
> +}
> 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"
Could we have a bit more user friendly names here? For example:
Driver name: "mtk-cam-dip"
Preview pipe: "%s %s", dev_driver_string(), "preview"
Capture pipe: "%s %s", dev_driver_string(), "capture"
Preview pipe: "%s %s", dev_driver_string(), "reprocess"
(For reference, I suggested "mtk-cam-p1" for P1, so that would be consistent
with the above.)
> +
> +#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
Why 100? I think we just have 1 rotation control. Please just use 1
explicitly in the code, without this intermediate constant.
> +
> +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,
> +};
This doesn't seem to be used anywhere.
> +
> +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;
> +};
Please document all the structures defined in the driver using kerneldoc.
> +
> +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;
> +};
Is there any reason to separate this from mtk_dip_request?
> +
> +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;
Shouldn't this be const char *?
> + 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;
Shouldn't this be const char *?
> + u32 buf_type;
> + u32 cap;
> + int smem_alloc;
> + const struct mtk_dip_dev_format *fmts;
> + int num_fmts;
> + char *description;
Shouldn't this be const char *?
> + 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;
nit: There isn't much benefit from having the separate mtk_dip_hw struct
here. On the contrary it causes long dereference chains, which only clutter
the code, e.g. "&pipe->dip_dev->dip_hw.scp_pdev->dev".
> +};
> +
> +struct mtk_dip_request {
> + struct media_request req;
> + struct mtk_dip_pipe *dip_pipe;
> + struct mtk_dip_pipe_job_info job_info;
Is there a need to have mtk_dip_pipe_job_info as a separate struct rather
than just having the fields here?
> + 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;
Please use lowercase for hex numbers.
> +}
> +
> +#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
Unused.
> +#define DIP_COMPOSING_MAX_NUM 3
> +#define DIP_MAX_ERR_COUNT 188U
> +#define DIP_FLUSHING_WQ_TIMEOUT (16U * DIP_MAX_ERR_COUNT)
Any rationale behind this particular numbers? Please add comments explaining
them.
> +
> +#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)
What is this offset? It's 5 MB, which is 2x bigger than the total frame data
(32 * 76 * 1024).
> +#define DIP_V4l2_META_BUF_OFFSET (DIP_SCP_WORKINGBUF_OFFSET + \
Please use only uppsercase in macro names. That said, this macro isn't used
anywhere.
> + 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
> +};
Are these 2 enums above a part of the firmware ABI?
> +
> +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
> +};
Are these values a part of the firmware ABI? If so, they should be defined
as macros, with explicit values assigned.
> +
> +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;
Why would IPI need VA? Actually, we shouldn't even pass kernel pointers to
the firmware for security reasons.
> + 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;
Ditto.
> + 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;
Could you move out the offset to a local variable so that we don't duplicate
the same calculation 3 times?
> + 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);
Do we really need this usedbufferlist?
> +
> + 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);
I can see that function only returns 0.
> +
> + pm_runtime_put_autosuspend(&pipe->dip_dev->pdev->dev);
It's necessary to call pm_runtime_mask_last_busy() before this for
autosuspend to work in any useful way.
> +
> + 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;
> + }
I'd just make this:
if (WARN_ON(!data.data))
return;
> +
> + req = data.data;
We could just assign this in the declaration and also make the check above
test for !req instead.
> + 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;
Could we get a proper pointer from the driver (not IPI) structures? I think
that should be req->working_buf->config_data?
> + u32 num;
> +
> + /*
> + * Call MDP/GCE API to do HW excecution
> + * Pass the framejob to MDP driver
> + */
> + req->img_fparam.frameparam.state = FRAME_STATE_COMPOSING;
Should this be really composing? I thought we just finished composing at
this point.
> + num = atomic_inc_return(&dip_dev->dip_hw.num_running);
I don't see where this counter is decremented. Could we just remove it?
> + 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);
It's not a regular convention in the kernel coding style to wrap the lines
for each single argument.
> +}
> +
> +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) {
Should we check len before dereferencing the pointer?
> + dev_dbg(&dip_dev->pdev->dev,
> + "%s: recevied SCP_IPI_DIP ACK, ipi_param.usage(%d)\n",
> + __func__, ipi_param->usage);
Is this an expected situation? If not, perhaps we should at least print a
warning here?
> + return;
> + }
> +
> + frameparam = (struct img_ipi_frameparam *)ipi_param->frm_param.va;
Are we getting a kernel virtual address from the firmware here? That's a
recipe for a disaster. What if there is a bug (or a security hole) there and
the address ends up accidentally (or deliberately) pointing to some critical
kernel data?
We should get some kind of job identifier from the firmware and then try to
find a matching request in our request list. Then any buffer addresses
should be retrieved from the request struct itself.
> + pipe_id = mtk_dip_pipe_get_pipe_from_job_id(frameparam->index);
> + pipe = mtk_dip_dev_get_pipe(dip_dev, pipe_id);
> +
nit: The next line is directly related to previous, so no need for the blank
line.
> + 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);
I think this should be up()?
> +
> + 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);
> + }
It shouldn't be possible for this to fail given that it's a new request and
the work was just initialized above.
> +}
> +
> +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)
Shouldn't this be called dip_composer_workfunc()?
> +{
> + 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);
Shouldn't this be down_freezable()? The idea to use semaphores was to avoid
such custom wait_event() calls like above.
Basically the semaphore should be initialized with the total number of
composer buffers and once down() is called that number of times, any further
down() would block until some up() is called.
> +
> + 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));
Is img_fparam really used at this stage? I can only see ipi_param passed to
the IPI.
> +
> + 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);
Do we still need this num_composing counter once we have the semaphore?
> + 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);
This is an error, isn't it? We need to have proper error handling here,
which at this stage would mean completing the request with buffers returned
with ERROR state.
> + 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);
nit: To avoid confusing workqueues with waitqueues, I'd suggest using _waitq
suffix, which seems to be quite common in the kernel code. (And _workq or
_workqueue for workqueues are not common at all.)
> +
> + 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);
Let's use consistent naming, i.e. mdpcb_wq.
> +
> + 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;
We can just check list_empty(&pipe->pipe_job_running_list) here and remove
pipe->num_jobs completely.
> + 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
nit: Could we avoid mixing non-trivial code with declarations?
> + (pipe->dip_dev->dip_hw.flushing_wq,
> + mtk_dip_pipe_running_job_list_empty(pipe, &num),
> + msecs_to_jiffies(DIP_FLUSHING_WQ_TIMEOUT));
We shouldn't wait here for all the jobs to complete, just those that are
already queued to hardware. The stop operation should be as instant as
possible.
This should basically be done like this:
- remove any pending jobs not queued to the hardware yet,
- wait for the hardware to finish anything that can't be cancelled -
possibly the same wait as done in mtk_dip_suspend().
> +
> + 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);
There isn't anything in that function that couldn't be just done once in
probe. Could we just move it there?
> + 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;
> +
No need to send IMG_IPI_DEINIT?
> + 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);
No need to call scp_ipi_unregister()?
> +}
> +
> +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);
No need to use atomics if this is already protected with a mutex.
> + 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);
Given that we end up calling this from mtk_dip_vb2_stop_streaming(), after
the locking changes I suggested, this function will be called with this lock
acquired already.
> + dev_dbg(&dip_dev->pdev->dev,
> + "%s:%s: streamoff, removing all running jobs\n",
> + __func__, pipe->desc->name);
> +
> + spin_lock(&pipe->job_lock);
The loop below is quite heavy weight as for something that runs under a
spinlock (that by definition should only protect short critical sections).
Could we just splice the list into a local list_head under the spinlock and
then just iterate over the local list after releasing the spinlock?
> + list_for_each_entry_safe(job_info, tmp_job_info,
> + &pipe->pipe_job_running_list, list) {
Don't we need to stop the hardware first?
> + 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);
> + }
I can see that there is an existing mtk_dip_return_all_buffers() function
that would return all the buffers of given video node when it's being
stopped. How does this code interfere with that code?
Actually, I'm not fully sure anymore what's the expected behavior here. I've
asked on #v4l and will let you know once we figure out.
> +
> + pipe->num_jobs--;
> + list_del(&job_info->list);
We don't need to delete and decrement here. Just set to 0 and init list head
after the loop. (The latter wouldn't be needed if we splice the list into a
local list head.)
> +
> + 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);
> + }
I suppose this should be the very first thing to do in this function.
> +
> + 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)) {
It shouldn't be possible for count to be <= 0 at this point. Are you seeing
cases like that when running the code?
> + 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;
> + }
Please remove the workarounds. There is no value in passing the test if
there is a workaround for it. If it keeps failing we at least know that we
need to fix something.
> +
> + mtk_dip_pipe_ipi_params_config(req);
> + frameparams->state = FRAME_STATE_INIT;
> + frameparams->frame_no = atomic_inc_return(&dip_hw->dip_enque_cnt);
typo: enqueue
> +
> + 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);
Aren't we still just only composing the job? Should we wait with this call
until we actually queue the job to the ISP hardware?
> + 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);
How is this possible? (I recall seeing this somewhere already. Was it the P1 driver?)
> +}
> 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;
What is this needed for?
> +
> + 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;
> +}
Do we need to implement this?
> +
> +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);
> + }
CodingStyle: No brackets needed for only 1 statement.
> +
> + return ret;
> + }
nit: Since the 2 cases are symmetrical (as opposed to success and failure),
I would put them in if/else.
> +
> + 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);
> + }
CodingStyle: No brackets needed for only 1 statement.
> +
> + 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);
Given that we need to acquire the big pipe mutex anyway, there is probably
no need to be too smart here anyway.
I suggested this method with counter
with hope that we could simplify the locking, but apparently that didn't
have such effect.
Let's just make this simple:
1) serialize full link_setup, start_streaming and stop_streaming with pipe->lock,
2) keep two bitmasks, pipe->nodes_streaming and pipe->nodes_enabled,
3) use media_pipeline_start/stop() to lock the enabled state, as suggested
in my comment for mtk_dip_vb2_start_streaming(), so that we don't need to
think about streaming state here in this function.
> + }
> + pipe->nodes[pad].flags &= ~(MEDIA_LNK_FL_ENABLED);
No need for the parentheses.
> + pipe->nodes[pad].flags |= MEDIA_LNK_FL_ENABLED & flags;
The typical convention is to have the constants on the right.
> + 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) {
For OUTPUT we need to check bytesused and also it must be equal to the
metadata size in that case.
> + 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;
> + }
This comment will not apply anymore if we split to separate vb2 ops for meta
queues, but normally one would put tis return 0 outside of the if block.
> + }
> +
> + 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) {
Rather than having two completely different conditional blocks, could we
have different vb2 ops for video and meta queues?
> + 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;
> + }
Why do we need cpu_addr here? It's not used any further.
> +
> + /*
> + * 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,
Is it really bidirectional? I think this would depend on whether it's OUTPUT
or CAPTURE.
> + 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)
This was already checked few lines above.
> + 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,
Sounds like it might be beneficial to have a local variable for dip_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;
Please keep this a downstream patch added on top of the driver patches.
> +
> + 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]);
> +
nit: Stray blank line.
> + } 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;
For VIDIOC_CREATEBUFS we also need to handle the case when *num_planes > 0
and then we need to honor the values already present in sizes[]. (Note that
the code above overrides *num_planes to 1, so we lose the information. The
code needs to be restructured to handle that.)
> + 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)
Are you seeing some buffers with state other than ACTIVE here? If so, that's
a bug somewhere else in the driver, which needs to be fixed. This check
shouldn't be here.
> + 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);
This still isn't really synchronized. One could disable the link at this
point. I believe that in the previous round of the review I suggested
checking the enable state after calling media_pipeline_start(), because it
locks the enable status for links that don't have the DYNAMIC flag. That
would solve the problem.
> +
> + 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);
> +
We protect the access to pipe->streaming with the mutex above, but do we
have any guarantee that after we release the mutex the value doesn't change?
We should likely do the whole stop operation holding the 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) {
For v4l2_subdev_call() shouldn't this be !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);
We should do this one when the last node stops streaming to solve the
enable state locking issue as described in my comment to _start_streaming().
> + }
> +
> + 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)
I don't see this function returning any error codes. Please make it void.
> +{
> + 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;
This should come from f.type and should have been already validated by the core.
Actually, why don't we use f directly?
> + 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);
> + }
We have these limits already as a part of the frmsizeenum field of the
mtk_dip_video_device_desc struct. Could we just use that and remove the
conditional handling?
> +
> + node->dev_q.dev_fmt = dev_fmt;
Why are we changing the format on the node inside TRY_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;
We should use vb2_is_busy() here, because it's not allowed to change formats
after allocating buffers.
> +
> + 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;
> + }
There is no need to check this here, because the framework already ensures
the values are valid.
> +
> + node->dev_q.rotation = ctrl->val;
Given the above, this could be just put directly into
mtk_dip_video_device_s_ctrl().
> +
> + 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);
nit: We can just return directly from here.
> + break;
> + default:
> + break;
There should be no need to handle default here, as the control framework
will not call us with any settable controls we haven't explicitly
registered.
> + }
> +
> + 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);
That's potentially error-prone. I'd add a properly typed local variable,
let's say mtk_req, allocate with sizeof(*mtk_req) and then return
&mtk_dev->req.
> +}
> +
> +static void mtk_dip_request_free(struct media_request *req)
> +{
A similar comment her. Please retrieve mtk_dip_request pointer properly
using a helper (with container_of() inside) to a local variable and then
free it.
> + 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;
Please avoid such direct casts and use properly typed helpers instead, using
container_of() if needed.
> + 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);
Userspace misbehavior should not trigger kernel warnings. Please make this
dev_dbg().
> + 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);
Shouldn't this be an error?
> + }
> +
> + /*
> + * 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++;
.req_validate() must not change any driver state (e.g. internal queues). It
should only validate the request itself. The proper place do to this is
.req_queue().
> + 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));
Could we just use dev_driver_string() instead?
> + 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);
Please return from here to make it clear which code is for failure and which
for success.
> + }
> +
> + dev_dbg(dev, "Registered media device: %s, %p", media_dev->model,
> + media_dev);
> +
> + return ret;
And return 0 here.
> +}
> +
> +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;
> +}
There is only 1 function call inside of this function, so there is not much
point in having a separate function for this. Please just move back to the
caller.
> +
> +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 */
I'd remove comments like this, because this is obvious from reading the
code alone.
> + 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;
This is a memory to memory device so this should be TIMESTAMP_COPY and the
driver should copy the time stamp from the input frame buffer to the output
buffers.
> + 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);
Do we need to init the handler for nodes that don't expose any controls?
> +
> + /* 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;
Why defer here? Isn't this just a critical error?
Also, the proper way to check for control registration error is to check the
control handler error rather than the control pointer.
An example registration code:
https://elixir.bootlin.com/linux/v5.3-rc2/source/drivers/media/platform/qcom/venus/vdec_ctrls.c#L79
> + }
> +
> + 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;
Isn't this the other way around?
> +
> + 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;
> + }
This should be only called once for the v4l2_dev, but the code here would
end up calling this once per each pipe.
> +
> + /* 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);
Wouldn't this end up unregistering the same v4l2_dev more than 1 time?
Please make sure that the driver works when compiled as a module and then
unloaded and reloaded again.
> + 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,
No need for cap ioctls on an out node.
> +
> + .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,
No need for out ioctls on a cap node.
> +
> + .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,
Is the color space tied to particular pixel format on hardware level?
> + .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 },
What is depth and what is row_depth? They both seem to not match NV12, which
should have 16 bits per sample in the CbCr plane.
> + .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,
Is this correct? The other entries above use MDP_YCBCR_PROFILE_ macros,
while this and further entries use V4L2_COLORSPACE_.
> + .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,
Why does NV12M have a different colorspace than NV12?
> + .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,
Please still use ARRAY_SIZE() even if the array has just 1 entry.
> + .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",
> + },
> +};
The entries here seem to be almost the same to output_queues_setting[], the
only difference seems to be default_fmt_idx and description.
What's the difference between the capture and reprocess pipes?
> +
> +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,
This string macro is only used here. Please just explicitly put the string
here. Also, as per my other comment, this should probably be just a suffix
that's added to dev_driver_string().
> + .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,
Why do we need this split into output and capture descriptors? They have the
same type.
> + },
> + {
> + .name = MTK_DIP_DEV_DIP_CAPTURE_NAME,
Ditto.
> + .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,
Ditto.
> + .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,
Do we need to store pdev in dip_dev? Wouldn't storing just dev there
simplify things?
> + media_dev,
> + MTK_DIP_DEV_DIP_MEDIA_MODEL_NAME);
Do we need to pass a constant to this function? Could we just use it
directly from there?
Also, why not just pass dip_dev instead of the 2 first arguments?
> + 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);
nit: The usual convention is to pass the arguments from the "broadest"
datastructures, e.g. the main driver structure - dip_dev, to the most
specific ones, so dip_dev should be made the first argument.
There isn't much point in passing media_dev and v4l2_dev here, because we
already have them in dip_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--)
nit: The initial decrement could be just written as 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);
Could we move this two into a function called mtk_dip_dev_media_unregister()
to be consistent with the initialization?
> +
> + 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;
> + }
We shouldn't need this custom larb handling anymore.
> +
> + dip_hw->clks[0].id = "larb5";
> + dip_hw->clks[1].id = "dip";
> +
> + ret = devm_clk_bulk_get(&pdev->dev, MTK_DIP_CLK_NUM,
Could we use ARRAY_SIZE(clks) instead?
> + 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);
Don't we need to clean up the buffer pool initialization if we fail here?
> +
It's also necessary to initialize autosuspend here by calling
pm_runtime_use_autosuspend() and pm_runtime_set_autosuspend_delay().
The delay should be something around 2-3 times the time it takes to suspend
and resume the device.
> + 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);
There might be already something running at this point and we would free the
buffers here. Generally the order of remove should be the opposite to the
probe.
> + 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)
Perhaps mtk_dip_runtime_suspend()?
> +{
> + 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)
mtk_dip_runtime_resume()?
> +{
> + 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);
nit: Please return ret from here and keep the path below only for success.
> + 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);
It's not allowed to use sleeping operations in a wait_event condition,
because the condition could be evaluated while the current process already
enters the UNINTERRUPTIBLE state.
Given that this is already an atomic_t, there shouldn't be any special
synchronization needed here. Please just use atomic_read() in the
wait_event_timeout() call directly.
> +
> + 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));
Is 200 milliseconds a reasonable timeout here, i.e. for any potential use
case it would always take less than 200 ms to wait for all the tasks running
in the ISP?
> + if (!ret && num) {
> + dev_err(dev, "%s: flushing SCP job timeout, num(%d)\n",
> + __func__, num);
> +
> + return -EBUSY;
> + }
> +
> + return mtk_dip_pm_suspend(dev);
We should call pm_runtime_force_suspend() instead, so that it also handles
any device dependencies or power domains.
> +}
> +
> +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);
pm_runtime_force_resume()
> +}
> +
> +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)
I think you meant mtk_dip_pm_suspend/resume for the runtime PM ops?
Best regards,
Tomasz
^ permalink raw reply
* Re: [PATCH 13/22] ARM: dts: colibri-imx6: Add missing pinmuxing to Toradex eval board
From: Michal Vokáč @ 2019-07-31 7:09 UTC (permalink / raw)
To: Philippe Schenker, marcel.ziswiler, max.krummenacher, stefan,
devicetree, Rob Herring, Shawn Guo, Mark Rutland
Cc: Philippe Schenker, Fabio Estevam, linux-kernel, linux-arm-kernel,
Pengutronix Kernel Team, NXP Linux Team, Sascha Hauer
In-Reply-To: <20190730144649.19022-14-dev@pschenker.ch>
On 30. 07. 19 16:46, Philippe Schenker wrote:
> From: Philippe Schenker <philippe.schenker@toradex.com>
>
> This patch adds some missing pinmuxing that is in the colibri
> standard to the dts.
>
> Signed-off-by: Philippe Schenker <philippe.schenker@toradex.com>
> ---
"ARM: dts: imx6-colibri: " in the subject for consistency.
Same for the Apalis, please.
Michal
>
> arch/arm/boot/dts/imx6dl-colibri-eval-v3.dts | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/arch/arm/boot/dts/imx6dl-colibri-eval-v3.dts b/arch/arm/boot/dts/imx6dl-colibri-eval-v3.dts
> index 63d4f9ca9ad8..13800e60246c 100644
> --- a/arch/arm/boot/dts/imx6dl-colibri-eval-v3.dts
> +++ b/arch/arm/boot/dts/imx6dl-colibri-eval-v3.dts
> @@ -204,6 +204,14 @@
> };
>
> &iomuxc {
> + pinctrl-names = "default";
> + pinctrl-0 = <
> + &pinctrl_weim_gpio_1 &pinctrl_weim_gpio_2
> + &pinctrl_weim_gpio_3 &pinctrl_weim_gpio_4
> + &pinctrl_weim_gpio_5 &pinctrl_weim_gpio_6
> + &pinctrl_usbh_oc_1 &pinctrl_usbc_id_1
> + >;
> +
> pinctrl_pcap_1: pcap-1 {
> fsl,pins = <
> MX6QDL_PAD_GPIO_9__GPIO1_IO09 0x1b0b0 /* SODIMM 28 */
>
^ permalink raw reply
* Re: [PATCH v2 00/14] dmaengine/soc: Add Texas Instruments UDMA support
From: Peter Ujfalusi @ 2019-07-31 7:08 UTC (permalink / raw)
To: vkoul, robh+dt, nm, ssantosh
Cc: dan.j.williams, dmaengine, linux-arm-kernel, devicetree,
linux-kernel, grygorii.strashko, lokeshvutla, t-kristo, tony,
j-keerthy
In-Reply-To: <20190730093450.12664-1-peter.ujfalusi@ti.com>
Rob,
On 30/07/2019 12.34, Peter Ujfalusi wrote:
> Changes since v1
> (https://patchwork.kernel.org/project/linux-dmaengine/list/?series=114105&state=*)
> - Added support for j721e
> - Based on 5.3-rc2
> - dropped ti_sci API patch for RM management as it is already upstream
> - dropped dmadev_get_slave_channel() patch, using __dma_request_channel()
> - Added Rob's Reviewed-by to ringacc DT binding document patch
> - DT bindings changes:
> - linux,udma-mode is gone, I have a simple lookup table in the driver to flag
> TR channels.
> - Support for j721e
I have also made smaller adjustment of some property names within the
psi-l config nodes to make it uniform and more readable.
> - Fix bug in of_node_put() handling in xlate function
>
> Changes since RFC (https://patchwork.kernel.org/cover/10612465/):
> - Based on linux-next (20190506) which now have the ti_sci interrupt support
> - The series can be applied and the UDMA via DMAengine API will be functional
> - Included in the series: ti_sci Resource management API, cppi5 header and
> driver for the ring accelerator.
> - The DMAengine core patches have been updated as per the review comments for
> earlier submittion.
> - The DMAengine driver patch is artificially split up to 6 smaller patches
>
> The k3-udma driver implements the Data Movement Architecture described in
> AM65x TRM (http://www.ti.com/lit/pdf/spruid7) and
> j721e TRM (http://www.ti.com/lit/pdf/spruil1)
>
> This DMA architecture is a big departure from 'traditional' architecture where
> we had either EDMA or sDMA as system DMA.
>
> Packet DMAs were used as dedicated DMAs to service only networking (Kesytone2)
> or USB (am335x) while other peripherals were serviced by EDMA.
>
> In AM65x/j721e the UDMA (Unified DMA) is used for all data movment within the
> SoC, tasked to service all peripherals (UART, McSPI, McASP, networking, etc).
>
> The NAVSS/UDMA is built around CPPI5 (Communications Port Programming Interface)
> and it supports Packet mode (similar to CPPI4.1 in Keystone2 for networking) and
> TR mode (similar to EDMA descriptor).
> The data movement is done within a PSI-L fabric, peripherals (including the
> UDMA-P) are not addressed by their I/O register as with traditional DMAs but
> with their PSI-L thread ID.
>
> In AM65x/j721e we have two main type of peripherals:
> Legacy: McASP, McSPI, UART, etc.
> to provide connectivity they are serviced by PDMA (Peripheral DMA)
> PDMA threads are locked to service a given peripheral, for example PSI-L thread
> 0x4400/0xc400 is to service McASP0 rx/tx.
> The PDMa configuration can be done via the UDMA Real Time Peer registers.
> Native: Networking, security accelerator
> these peripherals have native support for PSI-L.
>
> To be able to use the DMA the following generic steps need to be taken:
> - configure a DMA channel (tchan for TX, rchan for RX)
> - channel mode: Packet or TR mode
> - for memcpy a tchan and rchan pair is used.
> - for packet mode RX we also need to configure a receive flow to configure the
> packet receiption
> - the source and destination threads must be paired
> - at minimum one pair of rings need to be configured:
> - tx: transfer ring and transfer completion ring
> - rx: free descriptor ring and receive ring
> - two interrupts: UDMA-P channel interrupt and ring interrupt for tc_ring/r_ring
> - If the channel is in packet mode or configured to memcpy then we only need
> one interrupt from the ring, events from UDMAP is not used.
>
> When the channel setup is completed we only interract with the rings:
> - TX: push a descriptor to t_ring and wait for it to be pushed to the tc_ring by
> the UDMA-P
> - RX: push a descriptor to the fd_ring and waith for UDMA-P to push it back to
> the r_ring.
>
> Since we have FIFOs in the DMA fabric (UDMA-P, PSI-L and PDMA) which was not the
> case in previous DMAs we need to report the amount of data held in these FIFOs
> to clients (delay calculation for ALSA, UART FIFO flush support).
>
> Metadata support:
> DMAengine user driver was posted upstream based/tested on the v1 of the UDMA
> series: https://lkml.org/lkml/2019/6/28/20
> SA2UL is using the metadata DMAengine API.
>
> Note on the last patch:
> In Keystone2 the networking had dedicated DMA (packet DMA) which is not the case
> anymore and the DMAengine API currently missing support for the features we
> would need to support networking, things like
> - support for receive descriptor 'classification'
> - we need to support several receive queues for a channel.
> - the queues are used for packet priority handling for example, but they can be
> used to have pools of descriptors for different sizes.
> - out of order completion of descriptors on a channel
> - when we have several queues to handle different priority packets the
> descriptors will be completed 'out-of-order'
> - NAPI type of operation (polling instead of interrupt driven transfer)
> - without this we can not sustain gigabit speeds and we need to support NAPI
> - not to limit this to networking, but other high performance operations
>
> It is my intention to work on these to be able to remove the 'glue' layer and
> switch to DMAengine API - or have an API aside of DMAengine to have generic way
> to support networking, but given how controversial and not trivial these changes
> are we need something to support networking.
>
> The series (+DT patch to enabled UDMA/PDMA on AM65x) on top of 5.3-rc2 is
> available:
> https://github.com/omap-audio/linux-audio.git peter/udma/series_v2-5.3-rc2
>
> Regards,
> Peter
> ---
> Grygorii Strashko (3):
> bindings: soc: ti: add documentation for k3 ringacc
> soc: ti: k3: add navss ringacc driver
> dmaengine: ti: k3-udma: Add glue layer for non DMAengine users
>
> Peter Ujfalusi (11):
> dmaengine: doc: Add sections for per descriptor metadata support
> dmaengine: Add metadata_ops for dma_async_tx_descriptor
> dmaengine: Add support for reporting DMA cached data amount
> dmaengine: ti: Add cppi5 header for UDMA
> dt-bindings: dma: ti: Add document for K3 UDMA
> dmaengine: ti: New driver for K3 UDMA - split#1: defines, structs, io
> func
> dmaengine: ti: New driver for K3 UDMA - split#2: probe/remove, xlate
> and filter_fn
> dmaengine: ti: New driver for K3 UDMA - split#3: alloc/free
> chan_resources
> dmaengine: ti: New driver for K3 UDMA - split#4: dma_device callbacks
> 1
> dmaengine: ti: New driver for K3 UDMA - split#5: dma_device callbacks
> 2
> dmaengine: ti: New driver for K3 UDMA - split#6: Kconfig and Makefile
>
> .../devicetree/bindings/dma/ti/k3-udma.txt | 170 +
> .../devicetree/bindings/soc/ti/k3-ringacc.txt | 59 +
> Documentation/driver-api/dmaengine/client.rst | 75 +
> .../driver-api/dmaengine/provider.rst | 46 +
> drivers/dma/dmaengine.c | 73 +
> drivers/dma/dmaengine.h | 8 +
> drivers/dma/ti/Kconfig | 22 +
> drivers/dma/ti/Makefile | 2 +
> drivers/dma/ti/k3-udma-glue.c | 1039 +++++
> drivers/dma/ti/k3-udma-private.c | 124 +
> drivers/dma/ti/k3-udma.c | 3479 +++++++++++++++++
> drivers/dma/ti/k3-udma.h | 160 +
> drivers/soc/ti/Kconfig | 17 +
> drivers/soc/ti/Makefile | 1 +
> drivers/soc/ti/k3-ringacc.c | 1191 ++++++
> include/dt-bindings/dma/k3-udma.h | 10 +
> include/linux/dma/k3-udma-glue.h | 125 +
> include/linux/dma/ti-cppi5.h | 996 +++++
> include/linux/dmaengine.h | 110 +
> include/linux/soc/ti/k3-ringacc.h | 262 ++
> 20 files changed, 7969 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dma/ti/k3-udma.txt
> create mode 100644 Documentation/devicetree/bindings/soc/ti/k3-ringacc.txt
> create mode 100644 drivers/dma/ti/k3-udma-glue.c
> create mode 100644 drivers/dma/ti/k3-udma-private.c
> create mode 100644 drivers/dma/ti/k3-udma.c
> create mode 100644 drivers/dma/ti/k3-udma.h
> create mode 100644 drivers/soc/ti/k3-ringacc.c
> create mode 100644 include/dt-bindings/dma/k3-udma.h
> create mode 100644 include/linux/dma/k3-udma-glue.h
> create mode 100644 include/linux/dma/ti-cppi5.h
> create mode 100644 include/linux/soc/ti/k3-ringacc.h
>
- Péter
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
^ permalink raw reply
* Re: [PATCH 12/22] ARM: dts: imx6: Add touchscreens used on Toradex eval boards
From: Michal Vokáč @ 2019-07-31 6:54 UTC (permalink / raw)
To: Philippe Schenker, festevam@gmail.com
Cc: stefan@agner.ch, Marcel Ziswiler, kernel@pengutronix.de,
Max Krummenacher, mark.rutland@arm.com,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
shawnguo@kernel.org, linux-arm-kernel@lists.infradead.org,
s.hauer@pengutronix.de, robh+dt@kernel.org, linux-imx@nxp.com
In-Reply-To: <60760aa2d4710252e13877c409d0c4d2267824a6.camel@toradex.com>
On 31. 07. 19 8:43, Philippe Schenker wrote:
> On Tue, 2019-07-30 at 17:46 -0300, Fabio Estevam wrote:
>> On Tue, Jul 30, 2019 at 11:57 AM Philippe Schenker <dev@pschenker.ch> wrote:
>>
>>> + /* Atmel maxtouch controller */
>>> + atmel_mxt_ts: atmel_mxt_ts@4a {
>>
>> Generic node names, please:
>>
>> touchscreen@4a
>>
>>> + compatible = "atmel,maxtouch";
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_pcap_1>;
>>> + reg = <0x4a>;
>>> + interrupt-parent = <&gpio1>;
>>> + interrupts = <9 IRQ_TYPE_EDGE_FALLING>; /* SODIMM 28 */
>>> + reset-gpios = <&gpio2 10 GPIO_ACTIVE_HIGH>; /* SODIMM 30 */
>>> + status = "disabled";
>>> + };
>>> +
>>> + /*
>>> + * the PCAPs use SODIMM 28/30, also used for PWM<B>, PWM<C>, aka
>>> pwm1,
>>> + * pwm4. So if you enable one of the PCAP controllers disable the
>>> pwms.
>>> + */
>>> + pcap: pcap@10 {
>>
>> touchscreen@10
>>
>>> + /* TouchRevolution Fusion 7 and 10 multi-touch controller */
>>> + compatible = "touchrevolution,fusion-f0710a";
>>
>> I do not find this binding documented.
>
> Thanks for your feedback Fabio, and sorry such obvious stuff went through. I
> will go through my whole patchset again more carefully lookup all the bindings.
>
Hi Philippe,
also look at the I2C sub-nodes - they should be sorted by the bus address.
In most of the patches it is not correct.
Michal
^ permalink raw reply
* Re: Re: [PATCH 4/6] pwm: sun4i: Add support for H6 PWM
From: Uwe Kleine-König @ 2019-07-31 6:52 UTC (permalink / raw)
To: Maxime Ripard
Cc: Rob Herring, Frank Rowand, linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
Chen-Yu Tsai, Jernej Škrabec, Mark Rutland,
linux-pwm-u79uwXL29TY76Z2rM5mHXA, devicetree, linux-kernel,
Thierry Reding, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel
In-Reply-To: <20190730170601.a7ei43wku6jsjanu@flea>
On Tue, Jul 30, 2019 at 07:06:01PM +0200, Maxime Ripard wrote:
> On Tue, Jul 30, 2019 at 10:09:00AM +0200, Uwe Kleine-König wrote:
> > Hello Rob and Frank,
> >
> > Maxime and Jernej on one side and me on the other cannot agree about a
> > detail in the change to the bindings here. I'm trying to objectively
> > summarize the situation for you to help deciding what is the right thing
> > to do here.
> >
> > TLDR: The sun4i pwm driver is extended to support a new variant of that
> > device on the H6 SoC. Compared to the earlier supported variants
> > allwinner,sun50i-h6-pwm on H6 needs to handle a reset controller and an
> > additional clock.
> >
> > The two positions are:
> >
> > - We need a new compatible because only then the driver and/or the dt
> > schema checker can check that each "allwinner,sun50i-h6-pwm" device
> > has a reset property and a "bus" clock; and the earlier variants
> > don't.
>
> There is two topics here, really. The binding itself really must have
> those properties as required.
>
> You had an analogy before that we shouldn't really do that, since it
> would be verification and that it would be similar to checking whether
> the register range was right. This analogy isn't correct, a better one
> would be checking that the register range exists in the first place.
The relevant difference is that *all* devices supported by the driver
have to have a register range. Compared to that only a subset of the
devices have to have a bus clock.
> Indeed, if you don't have a register range, you have no register to
> write to, and that's a showstopper for any driver. I'm pretty sure we
> all agree on that. But on those SoCs, like Chen-Yu said, having no
> resets or clocks properties result in an equally bad result: either
> any write to that device is completely ignored (missing reset), or the
> system completely (and silently) locks up (missing bus clock).
>
> We *have* to catch that somehow and not let anything like that happen.
IIUC both the clock and the reset stuff is SoC specific, so it's the
same for all machines with the H6, right? So assuming this is correctly
contained in the h6.dtsi, in which cases can this go wrong? I only see
the cases that the dts author includes the wrong dtsi or overrides
stuff. In the first case a non-working PWM is probably one of the
smaller problems and the second is something we're not really able to
catch.
But even if each machine's dts author has to get this right, I don't
think the dts schema is the right place to assert this.
> That being said, we can't really validate that the clock pointed is
> the right one, just like we can't really check that the register range
> is the proper one. Or rather, we could, but it's completely
> impractical and we do agree on that as well.
>
> Having the bus clock and reset line being required however is only a
> few lines in the binding though, and is very practical.
>
> > - The driver can be simpler and the device specific knowledge is only
> > in a single place (the dt) if the device tree is considered valid and
> > a reset property and "bus" clock is used iff it's provided in the
> > device tree without additional comparison for the compatible.
>
> And now we have the discussion on how it's implemented in a
> driver. Since it's pretty cheap to implement (only a couple of lines:
> two for the if block, one for the additional field in the structure,
> one for each SoC using that) and have huge benefits (not silently
> locking up the system at boot), then I'd say we should go for it.
Right, it's only a few lines. Still it (IMHO needlessly) complicates the
driver. From the driver's POV the device tree defines the
characteristics of the device and if the dts defines an h6-pwm without a
bus clock then maybe this is the PWM on the next generation SoC that
doesn't need it. And maybe you're happy in a few year's time when you
don't have to touch the driver again for this next generation SoC
because the driver is not only simpler but also flexible enough to
handle the new PWM without adaptions.
All in all I don't care much about the dt schema stuff, I want to keep
the driver simple. So if we agree that the schema ensures that the h6
pwms have a reset and a bus clock and we just use reset_get_optional and
clk_get_optional that's a compromise I can agree to.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | http://www.pengutronix.de/ |
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190731065230.mqbtn5sfoxrkevw5%40pengutronix.de.
^ permalink raw reply
* Re: [PATCH 12/22] ARM: dts: imx6: Add touchscreens used on Toradex eval boards
From: Philippe Schenker @ 2019-07-31 6:43 UTC (permalink / raw)
To: festevam@gmail.com
Cc: stefan@agner.ch, Marcel Ziswiler, kernel@pengutronix.de,
Max Krummenacher, mark.rutland@arm.com,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
shawnguo@kernel.org, linux-arm-kernel@lists.infradead.org,
s.hauer@pengutronix.de, robh+dt@kernel.org, linux-imx@nxp.com
In-Reply-To: <CAOMZO5DRi6yawn3RF-Mouiejz0nc7htdsCjOBC_EXZZKUZ3nvA@mail.gmail.com>
On Tue, 2019-07-30 at 17:46 -0300, Fabio Estevam wrote:
> On Tue, Jul 30, 2019 at 11:57 AM Philippe Schenker <dev@pschenker.ch> wrote:
>
> > + /* Atmel maxtouch controller */
> > + atmel_mxt_ts: atmel_mxt_ts@4a {
>
> Generic node names, please:
>
> touchscreen@4a
>
> > + compatible = "atmel,maxtouch";
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_pcap_1>;
> > + reg = <0x4a>;
> > + interrupt-parent = <&gpio1>;
> > + interrupts = <9 IRQ_TYPE_EDGE_FALLING>; /* SODIMM 28 */
> > + reset-gpios = <&gpio2 10 GPIO_ACTIVE_HIGH>; /* SODIMM 30 */
> > + status = "disabled";
> > + };
> > +
> > + /*
> > + * the PCAPs use SODIMM 28/30, also used for PWM<B>, PWM<C>, aka
> > pwm1,
> > + * pwm4. So if you enable one of the PCAP controllers disable the
> > pwms.
> > + */
> > + pcap: pcap@10 {
>
> touchscreen@10
>
> > + /* TouchRevolution Fusion 7 and 10 multi-touch controller */
> > + compatible = "touchrevolution,fusion-f0710a";
>
> I do not find this binding documented.
Thanks for your feedback Fabio, and sorry such obvious stuff went through. I
will go through my whole patchset again more carefully lookup all the bindings.
Philippe
^ permalink raw reply
* Re: [PATCH v2 5/5] dt-bindings: Update the isa string description
From: Atish Patra @ 2019-07-31 6:43 UTC (permalink / raw)
To: Paul Walmsley
Cc: linux-kernel@vger.kernel.org, Albert Ou, Alexios Zavras,
Allison Randal, Anup Patel, Daniel Lezcano,
devicetree@vger.kernel.org, Enrico Weigelt, Greg Kroah-Hartman,
Johan Hovold, linux-riscv@lists.infradead.org, Mark Rutland,
Palmer Dabbelt, Rob Herring, Thomas Gleixner
In-Reply-To: <alpine.DEB.2.21.9999.1907302124010.15340@viisi.sifive.com>
On 7/30/19, 9:52 PM, "Paul Walmsley" <paul.walmsley@sifive.com> wrote:
On Tue, 30 Jul 2019, Atish Patra wrote:
> The yaml documentation description of isa strings section doesn't
> specify anything about the case sensitiveness of the isa strings.
> The RISC-V specification clearly specifies it to be case insensitive.
> However, Linux kernel supports only lower case isa strings.
The DT binding documentation specifies an interface. As such the binding
isn't determined by any particular piece of software. So justifying the
binding update by referring to what the Linux kernel currently supports
isn't that relevant. If you still really believe that software should be
required to handle mixed-case DT ISA strings, the right answer would be to
change the software, as your original patches proposed. The way you've
written this patch description, it sounds like you still don't agree with
the conclusion that a strictly lowercase string is a good approach.
If I've misunderstood your intent here, and you do think that specifying
an all lowercase string is sufficient,
I think that specifying an all lowercase string is sufficient. I did not
realize that current commit text could mean something else (.
then instead of the patch
description above, how about something like:
"Since the RISC-V specification states that ISA description strings are
case-insensitive, there's no functional difference between mixed-case,
upper-case, and lower-case ISA strings. Thus, to simplify parsing,
specify that the letters present of riscv,isa must be all lowercase."
Sounds good to me. I will update the patch. Thanks for updated commit text.
That way it's clear that, per the RISC-V specification, there's no
functional difference associated with case.
However, if what you're saying is that you still don't like this outcome,
let me know and I'll write the patch myself. That way you don't have to
have your name associated with a change that you don't believe in.
> Update the yaml documentation accordingly to avoid any confusion.
>
> Signed-off-by: Atish Patra <atish.patra@wdc.com>
> ---
> Documentation/devicetree/bindings/riscv/cpus.yaml | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/riscv/cpus.yaml b/Documentation/devicetree/bindings/riscv/cpus.yaml
> index c899111aa5e3..e22a2b7ebafa 100644
> --- a/Documentation/devicetree/bindings/riscv/cpus.yaml
> +++ b/Documentation/devicetree/bindings/riscv/cpus.yaml
> @@ -46,10 +46,14 @@ properties:
> - rv64imafdc
> description:
> Identifies the specific RISC-V instruction set architecture
> - supported by the hart. These are documented in the RISC-V
> + supported by the hart. These are documented in the RISC-V
> User-Level ISA document, available from
> https://riscv.org/specifications/
>
> + Linux kernel only supports lower case isa strings. Thus,
In the past, the DT maintainers have pushed back against explicitly
mentioning the Linux kernel in binding documentation, since the DT
bindings define an interface that's independent of the underlying software
implementation. How about just stating something like "Letters in the
riscv,isa string must be all lowercase" ?
Sure.
Regards,
Atish
> + isa strings must be specified in lower case in device tree
> + as well.
> +
- Paul
^ permalink raw reply
* Re: [PATCH v8 08/11] usb: roles: get usb-role-switch from parent
From: Chunfeng Yun @ 2019-07-31 6:29 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Mark Rutland, devicetree, Hans de Goede, Greg Kroah-Hartman,
Linus Walleij, linux-usb, linux-kernel, Biju Das,
Badhri Jagan Sridharan, Andy Shevchenko, Rob Herring,
linux-mediatek, Min Guo, Matthias Brugger, Adam Thomson,
linux-arm-kernel, Li Jun
In-Reply-To: <20190729142500.GE28600@kuha.fi.intel.com>
Hi Heikki,
On Mon, 2019-07-29 at 17:25 +0300, Heikki Krogerus wrote:
> On Wed, Jul 24, 2019 at 04:50:42PM +0800, Chunfeng Yun wrote:
> > when the USB host controller is the parent of the connector,
> > usually type-B, sometimes don't need the graph, so we should
> > check whether it's parent registers usb-role-switch or not
> > firstly, and get it if exists.
> >
> > Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
>
> I don't think I actually wrote the patch. I may have proposed the code
> for you, but I never prepared a patch out out that. Please drop the
> above Signed-off-by line if that is the case. I case I really did
> write the patch, then you are missing the "From: Heikki..." first
> line, but I really don't remember preparing the patch.
>
> If the idea came from me, you can use for example the suggested-by
> tag: "Suggested-by: Heikki Krogerus <heikki.krogerus...".
Ok, thanks
>
> > Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> > ---
> > v8: no changes
> > v7:
> > add signed-off-by Chunfeng
> >
> > v6:
> > new patch
> > ---
> > drivers/usb/roles/class.c | 25 +++++++++++++++++++++----
> > 1 file changed, 21 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c
> > index 5b637aaf311f..87439a84c983 100644
> > --- a/drivers/usb/roles/class.c
> > +++ b/drivers/usb/roles/class.c
> > @@ -114,6 +114,19 @@ static void *usb_role_switch_match(struct device_connection *con, int ep,
> > return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);
> > }
> >
> > +static struct usb_role_switch *
> > +usb_role_switch_is_parent(struct fwnode_handle *fwnode)
> > +{
> > + struct fwnode_handle *parent = fwnode_get_parent(fwnode);
> > + struct device *dev;
> > +
> > + if (!parent || !fwnode_property_present(parent, "usb-role-switch"))
> > + return NULL;
> > +
> > + dev = class_find_device(role_class, NULL, parent, switch_fwnode_match);
> > + return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);
> > +}
> > +
> > /**
> > * usb_role_switch_get - Find USB role switch linked with the caller
> > * @dev: The caller device
> > @@ -125,8 +138,10 @@ struct usb_role_switch *usb_role_switch_get(struct device *dev)
> > {
> > struct usb_role_switch *sw;
> >
> > - sw = device_connection_find_match(dev, "usb-role-switch", NULL,
> > - usb_role_switch_match);
> > + sw = usb_role_switch_is_parent(dev_fwnode(dev));
> > + if (!sw)
> > + sw = device_connection_find_match(dev, "usb-role-switch", NULL,
> > + usb_role_switch_match);
> >
> > if (!IS_ERR_OR_NULL(sw))
> > WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
> > @@ -146,8 +161,10 @@ struct usb_role_switch *fwnode_usb_role_switch_get(struct fwnode_handle *fwnode)
> > {
> > struct usb_role_switch *sw;
> >
> > - sw = fwnode_connection_find_match(fwnode, "usb-role-switch", NULL,
> > - usb_role_switch_match);
> > + sw = usb_role_switch_is_parent(fwnode);
> > + if (!sw)
> > + sw = fwnode_connection_find_match(fwnode, "usb-role-switch",
> > + NULL, usb_role_switch_match);
> > if (!IS_ERR_OR_NULL(sw))
> > WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
> >
> > --
> > 2.21.0
>
> thanks,
>
^ permalink raw reply
* Re: [PATCH v2 2/5] RISC-V: Add riscv_isa reprensenting ISA features common across CPUs
From: Atish Patra @ 2019-07-31 6:27 UTC (permalink / raw)
To: Paul Walmsley, Anup Patel
Cc: linux-kernel@vger.kernel.org, Albert Ou, Alexios Zavras,
Allison Randal, Daniel Lezcano, devicetree@vger.kernel.org,
Enrico Weigelt, Greg Kroah-Hartman, Johan Hovold,
linux-riscv@lists.infradead.org, Mark Rutland, Palmer Dabbelt,
Rob Herring, Thomas Gleixner
In-Reply-To: <alpine.DEB.2.21.9999.1907302117420.15340@viisi.sifive.com>
On 7/30/19, 9:23 PM, "Paul Walmsley" <paul.walmsley@sifive.com> wrote:
On Tue, 30 Jul 2019, Atish Patra wrote:
> From: Anup Patel <anup.patel@wdc.com>
>
> This patch adds riscv_isa integer to represent ISA features common
> across all CPUs. The riscv_isa is not same as elf_hwcap because
> elf_hwcap will only have ISA features relevant for user-space apps
> whereas riscv_isa will have ISA features relevant to both kernel
> and user-space apps.
>
> One of the use case is KVM hypervisor where riscv_isa will be used
> to do following operations:
>
> 1. Check whether hypervisor extension is available
> 2. Find ISA features that need to be virtualized (e.g. floating
> point support, vector extension, etc.)
>
> Signed-off-by: Anup Patel <anup.patel@wdc.com>
> Signed-off-by: Atish Patra <atish.patra@wdc.com>
> ---
> arch/riscv/include/asm/hwcap.h | 25 +++++++++++++++++++++
> arch/riscv/kernel/cpufeature.c | 41 +++++++++++++++++++++++++++++++---
> 2 files changed, 63 insertions(+), 3 deletions(-)
>
> diff --git a/arch/riscv/include/asm/hwcap.h b/arch/riscv/include/asm/hwcap.h
> index 7ecb7c6a57b1..e069f60ad5d2 100644
> --- a/arch/riscv/include/asm/hwcap.h
> +++ b/arch/riscv/include/asm/hwcap.h
> @@ -22,5 +22,30 @@ enum {
> };
>
> extern unsigned long elf_hwcap;
> +
> +#define RISCV_ISA_EXT_A (1UL << ('A' - 'A'))
Are these uppercase variants still needed if we define the ISA string to
be all lowercase, per our recent discussion?
Argh. Sorry. We have been carrying this patch so long that I completely forgot about the
case sensitive usage here.
> +#define RISCV_ISA_EXT_a RISCV_ISA_EXT_A
> +#define RISCV_ISA_EXT_C (1UL << ('C' - 'A'))
> +#define RISCV_ISA_EXT_c RISCV_ISA_EXT_C
> +#define RISCV_ISA_EXT_D (1UL << ('D' - 'A'))
> +#define RISCV_ISA_EXT_d RISCV_ISA_EXT_D
> +#define RISCV_ISA_EXT_F (1UL << ('F' - 'A'))
> +#define RISCV_ISA_EXT_f RISCV_ISA_EXT_F
> +#define RISCV_ISA_EXT_H (1UL << ('H' - 'A'))
> +#define RISCV_ISA_EXT_h RISCV_ISA_EXT_H
> +#define RISCV_ISA_EXT_I (1UL << ('I' - 'A'))
> +#define RISCV_ISA_EXT_i RISCV_ISA_EXT_I
> +#define RISCV_ISA_EXT_M (1UL << ('M' - 'A'))
> +#define RISCV_ISA_EXT_m RISCV_ISA_EXT_M
> +#define RISCV_ISA_EXT_S (1UL << ('S' - 'A'))
> +#define RISCV_ISA_EXT_s RISCV_ISA_EXT_S
> +#define RISCV_ISA_EXT_U (1UL << ('U' - 'A'))
> +#define RISCV_ISA_EXT_u RISCV_ISA_EXT_U
> +
> +extern unsigned long riscv_isa;
> +
> +#define riscv_isa_extension_available(ext_char) \
> + (riscv_isa & RISCV_ISA_EXT_##ext_char)
> +
> #endif
> #endif
> diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
> index b1ade9a49347..177529d48d87 100644
> --- a/arch/riscv/kernel/cpufeature.c
> +++ b/arch/riscv/kernel/cpufeature.c
[ ... ]
> @@ -43,8 +49,22 @@ void riscv_fill_hwcap(void)
> continue;
> }
>
> - for (i = 0; i < strlen(isa); ++i)
> + i = 0;
> + isa_len = strlen(isa);
> +#if defined(CONFIG_32BIT)
> + if (strncasecmp(isa, "rv32", 4) != 0)
strcmp()?
> + i += 4;
> +#elif defined(CONFIG_64BIT)
> + if (strncasecmp(isa, "rv64", 4) != 0)
And again here?
> + i += 4;
> +#endif
> + for (; i < isa_len; ++i) {
> this_hwcap |= isa2hwcap[(unsigned char)(isa[i])];
> + if ('a' <= isa[i] && isa[i] <= 'z')
> + this_isa |= (1UL << (isa[i] - 'a'));
> + if ('A' <= isa[i] && isa[i] <= 'Z')
> + this_isa |= (1UL << (isa[i] - 'A'));
Are these uppercase variants still needed?
Nope. Same as above comment. Apologies for forgetting about these usages.
I will send a v3 removing them.
Regards,
Atish
- Paul
^ permalink raw reply
* Re: [alsa-devel] [PATCH v2 2/3] ASoC: Add codec driver for ST TDA7802
From: Marco Felsch @ 2019-07-31 6:06 UTC (permalink / raw)
To: Thomas Preston
Cc: Mark Brown, Mark Rutland, devicetree, alsa-devel, Charles Keepax,
Kuninori Morimoto, Kirill Marinushkin, Cheng-Yi Chiang,
Takashi Iwai, Annaliese McDermond, Liam Girdwood, Paul Cercueil,
Vinod Koul, Rob Herring, Srinivas Kandagatla, Nate Case,
Rob Duncan, Patrick Glaser, linux-kernel, Jerome Brunet
In-Reply-To: <fe11c806-2285-558c-e35c-d8f61de00784@codethink.co.uk>
Hi Thomas,
again sorry for jumping in..
On 19-07-30 18:26, Thomas Preston wrote:
> On 30/07/2019 15:58, Mark Brown wrote:
> > On Tue, Jul 30, 2019 at 01:09:36PM +0100, Thomas Preston wrote:
> >
> >> index 000000000000..0f82a88bc1a4
> >> --- /dev/null
> >> +++ b/sound/soc/codecs/tda7802.c
> >> @@ -0,0 +1,509 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * tda7802.c -- codec driver for ST TDA7802
> >
> > Please make the entire comment a C++ one so this looks intentional.
> >
>
> Ok thanks.
>
> >> +static int tda7802_digital_mute(struct snd_soc_dai *dai, int mute)
> >> +{
> >> + const u8 mute_disabled = mute ? 0 : IB2_DIGITAL_MUTE_DISABLED;
> >
> > Please write normal conditional statements to make the code easier to
> > read.
> >
>
> On it.
>
> >> + case SND_SOC_BIAS_STANDBY:
> >> + err = regulator_enable(tda7802->enable_reg);
> >> + if (err < 0) {
> >> + dev_err(component->dev, "Could not enable.\n");
> >> + return err;
> >> + }
> >> + dev_dbg(component->dev, "Regulator enabled\n");
> >> + msleep(ENABLE_DELAY_MS);
> >
> > Is this delay needed by the device or is it for the regulator to ramp?
> > If it's for the regulator to ramp then the regulator should be doing it.
> >
>
> According to the datasheet the device itself takes 10ms to rise from 0V
> after PLLen is enabled. There are additional rise times but they are
> negligible with default capacitor configuration (which we have).
>
> Good to know about the regulator rising configuration though. Thanks.
Isn't it the regulator we mentioned to not use that because it is a
GPIO?
Regards,
Marco
> >> + case SND_SOC_BIAS_OFF:
> >> + regcache_mark_dirty(component->regmap);
> >
> > If the regulator is going off you should really be marking the device as
> > cache only.
> >
>
> Got it, thanks.
>
> >> + err = regulator_disable(tda7802->enable_reg);
> >> + if (err < 0)
> >> + dev_err(component->dev, "Could not disable.\n");
> >
> > Any non-zero value from regulator_disable() is an error, there's similar
> > error checking issues in other places.
> >
>
> I return the error at the end of the function, but I'll bring it back here
> for consistency.
>
> >> +static const struct snd_kcontrol_new tda7802_snd_controls[] = {
> >> + SOC_SINGLE("Channel 4 Tristate", TDA7802_IB0, 7, 1, 0),
> >> + SOC_SINGLE("Channel 3 Tristate", TDA7802_IB0, 6, 1, 0),
> >> + SOC_SINGLE("Channel 2 Tristate", TDA7802_IB0, 5, 1, 0),
> >> + SOC_SINGLE("Channel 1 Tristate", TDA7802_IB0, 4, 1, 0),
> >
> > These look like simple on/off switches so should have Switch at the end
> > of the control name. It's also not clear to me why this is exported to
> > userspace - why would this change at runtime and won't any changes need
> > to be coordinated with whatever else is connected to the signal?
> >
> >> + SOC_ENUM("Mute time", mute_time),
> >> + SOC_SINGLE("Unmute channels 1 & 3", TDA7802_IB2, 4, 1, 0),
> >> + SOC_SINGLE("Unmute channels 2 & 4", TDA7802_IB2, 3, 1, 0),
> >
> > These are also Switch controls. There are *lots* of problems with
> > control names, see control-names.rst.
> >
>
> Ok thanks, I didn't know about the "Switch" suffix, I will read
> control-names.rst.
>
> I will move Tristate to DT properties. I was also unsure about the
> Impedance Efficiency Optimiser but the datasheet doesn't go into much
> detail about this so I left it exposed.
>
> They both seemed like user configurable options in the context of a
> test rig, but I agree - who knows what this output might be connected
> to in other systems. I will lock them down in DT. Thanks.
>
> >> +static const struct snd_soc_component_driver tda7802_component_driver = {
> >> + .set_bias_level = tda7802_set_bias_level,
> >> + .idle_bias_on = 1,
> >
> > Any reason to keep the device powered up? It looks like the power on
> > process is just powering things up and writing the register cache out
> > and there's not that many registers so the delay is trivial.
> >
>
> Ah no, I think that's a mistake. I want the PLLen to switch off in
> idle/suspend and the device should restore on wake.
>
> >> + tda7802->enable_reg = devm_regulator_get(dev, "enable");
> >> + if (IS_ERR(tda7802->enable_reg)) {
> >> + dev_err(dev, "Failed to get enable regulator\n");
> >
> > It's better to print error codes if you have them and are printing a
> > diagnostic so people have more to go on when debugging problems.
>
> Yep on it.
>
> Many thanks, I appreciate the feedback.
>
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply
* [PATCH net-next v2 4/4] net: ftgmac100: Select ASPEED MDIO driver for the AST2600
From: Andrew Jeffery @ 2019-07-31 5:39 UTC (permalink / raw)
To: netdev
Cc: Andrew Jeffery, davem, robh+dt, mark.rutland, joel, andrew,
f.fainelli, hkallweit1, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel
In-Reply-To: <20190731053959.16293-1-andrew@aj.id.au>
Ensures we can talk to a PHY via MDIO on the AST2600, as the MDIO
controller is now separate from the MAC.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
drivers/net/ethernet/faraday/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/faraday/Kconfig b/drivers/net/ethernet/faraday/Kconfig
index a9b105803fb7..73e4f2648e49 100644
--- a/drivers/net/ethernet/faraday/Kconfig
+++ b/drivers/net/ethernet/faraday/Kconfig
@@ -32,6 +32,7 @@ config FTGMAC100
depends on ARM || NDS32 || COMPILE_TEST
depends on !64BIT || BROKEN
select PHYLIB
+ select MDIO_ASPEED if MACH_ASPEED_G6
---help---
This driver supports the FTGMAC100 Gigabit Ethernet controller
from Faraday. It is used on Faraday A369, Andes AG102 and some
--
2.20.1
^ permalink raw reply related
* [PATCH net-next v2 3/4] net: ftgmac100: Add support for DT phy-handle property
From: Andrew Jeffery @ 2019-07-31 5:39 UTC (permalink / raw)
To: netdev
Cc: Andrew Jeffery, davem, robh+dt, mark.rutland, joel, andrew,
f.fainelli, hkallweit1, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel
In-Reply-To: <20190731053959.16293-1-andrew@aj.id.au>
phy-handle is necessary for the AST2600 which separates the MDIO
controllers from the MAC.
I've tried to minimise the intrusion of supporting the AST2600 to the
FTGMAC100 by leaving in place the existing MDIO support for the embedded
MDIO interface. The AST2400 and AST2500 continue to be supported this
way, as it avoids breaking/reworking existing devicetrees.
The AST2600 support by contrast requires the presence of the phy-handle
property in the MAC devicetree node to specify the appropriate PHY to
associate with the MAC. In the event that someone wants to specify the
MDIO bus topology under the MAC node on an AST2400 or AST2500, the
current auto-probe approach is done conditional on the absence of an
"mdio" child node of the MAC.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
drivers/net/ethernet/faraday/ftgmac100.c | 37 +++++++++++++++++++++---
1 file changed, 33 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c
index 030fed65393e..563384b788ab 100644
--- a/drivers/net/ethernet/faraday/ftgmac100.c
+++ b/drivers/net/ethernet/faraday/ftgmac100.c
@@ -17,6 +17,7 @@
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/of.h>
+#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/property.h>
@@ -1619,8 +1620,13 @@ static int ftgmac100_setup_mdio(struct net_device *netdev)
if (!priv->mii_bus)
return -EIO;
- if (priv->is_aspeed) {
- /* This driver supports the old MDIO interface */
+ if (of_device_is_compatible(np, "aspeed,ast2400-mac") ||
+ of_device_is_compatible(np, "aspeed,ast2500-mac")) {
+ /* The AST2600 has a separate MDIO controller */
+
+ /* For the AST2400 and AST2500 this driver only supports the
+ * old MDIO interface
+ */
reg = ioread32(priv->base + FTGMAC100_OFFSET_REVR);
reg &= ~FTGMAC100_REVR_NEW_MDIO_INTERFACE;
iowrite32(reg, priv->base + FTGMAC100_OFFSET_REVR);
@@ -1797,7 +1803,8 @@ static int ftgmac100_probe(struct platform_device *pdev)
np = pdev->dev.of_node;
if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac") ||
- of_device_is_compatible(np, "aspeed,ast2500-mac"))) {
+ of_device_is_compatible(np, "aspeed,ast2500-mac") ||
+ of_device_is_compatible(np, "aspeed,ast2600-mac"))) {
priv->rxdes0_edorr_mask = BIT(30);
priv->txdes0_edotr_mask = BIT(30);
priv->is_aspeed = true;
@@ -1817,7 +1824,29 @@ static int ftgmac100_probe(struct platform_device *pdev)
priv->ndev = ncsi_register_dev(netdev, ftgmac100_ncsi_handler);
if (!priv->ndev)
goto err_ncsi_dev;
- } else {
+ } else if (np && of_get_property(np, "phy-handle", NULL)) {
+ struct phy_device *phy;
+
+ phy = of_phy_get_and_connect(priv->netdev, np,
+ &ftgmac100_adjust_link);
+ if (!phy) {
+ dev_err(&pdev->dev, "Failed to connect to phy\n");
+ goto err_setup_mdio;
+ }
+
+ /* Indicate that we support PAUSE frames (see comment in
+ * Documentation/networking/phy.txt)
+ */
+ phy_support_asym_pause(phy);
+
+ /* Display what we found */
+ phy_attached_info(phy);
+ } else if (np && !of_get_child_by_name(np, "mdio")) {
+ /* Support legacy ASPEED devicetree descriptions that decribe a
+ * MAC with an embedded MDIO controller but have no "mdio"
+ * child node. Automatically scan the MDIO bus for available
+ * PHYs.
+ */
priv->use_ncsi = false;
err = ftgmac100_setup_mdio(netdev);
if (err)
--
2.20.1
^ permalink raw reply related
* [PATCH net-next v2 2/4] net: phy: Add mdio-aspeed
From: Andrew Jeffery @ 2019-07-31 5:39 UTC (permalink / raw)
To: netdev
Cc: Andrew Jeffery, davem, robh+dt, mark.rutland, joel, andrew,
f.fainelli, hkallweit1, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel
In-Reply-To: <20190731053959.16293-1-andrew@aj.id.au>
The AST2600 design separates the MDIO controllers from the MAC, which is
where they were placed in the AST2400 and AST2500. Further, the register
interface is reworked again, so now we have three possible different
interface implementations, however this driver only supports the
interface provided by the AST2600. The AST2400 and AST2500 will continue
to be supported by the MDIO support embedded in the FTGMAC100 driver.
The hardware supports both C22 and C45 mode, but for the moment only C22
support is implemented.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
v2:
* Use readl_poll_timeout() instead of open-coded loop
* Error on C45 accesses
---
drivers/net/phy/Kconfig | 13 +++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/mdio-aspeed.c | 157 ++++++++++++++++++++++++++++++++++
3 files changed, 171 insertions(+)
create mode 100644 drivers/net/phy/mdio-aspeed.c
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 20f14c5fbb7e..206d8650ee7f 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -21,6 +21,19 @@ config MDIO_BUS
if MDIO_BUS
+config MDIO_ASPEED
+ tristate "ASPEED MDIO bus controller"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ depends on OF_MDIO && HAS_IOMEM
+ help
+ This module provides a driver for the independent MDIO bus
+ controllers found in the ASPEED AST2600 SoC. This is a driver for the
+ third revision of the ASPEED MDIO register interface - the first two
+ revisions are the "old" and "new" interfaces found in the AST2400 and
+ AST2500, embedded in the MAC. For legacy reasons, FTGMAC100 driver
+ continues to drive the embedded MDIO controller for the AST2400 and
+ AST2500 SoCs, so say N if AST2600 support is not required.
+
config MDIO_BCM_IPROC
tristate "Broadcom iProc MDIO bus controller"
depends on ARCH_BCM_IPROC || COMPILE_TEST
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 839acb292c38..ba07c27e4208 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -22,6 +22,7 @@ libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o
obj-$(CONFIG_PHYLINK) += phylink.o
obj-$(CONFIG_PHYLIB) += libphy.o
+obj-$(CONFIG_MDIO_ASPEED) += mdio-aspeed.o
obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o
obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o
obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
diff --git a/drivers/net/phy/mdio-aspeed.c b/drivers/net/phy/mdio-aspeed.c
new file mode 100644
index 000000000000..cad820568f75
--- /dev/null
+++ b/drivers/net/phy/mdio-aspeed.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (C) 2019 IBM Corp. */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#define DRV_NAME "mdio-aspeed"
+
+#define ASPEED_MDIO_CTRL 0x0
+#define ASPEED_MDIO_CTRL_FIRE BIT(31)
+#define ASPEED_MDIO_CTRL_ST BIT(28)
+#define ASPEED_MDIO_CTRL_ST_C45 0
+#define ASPEED_MDIO_CTRL_ST_C22 1
+#define ASPEED_MDIO_CTRL_OP GENMASK(27, 26)
+#define MDIO_C22_OP_WRITE 0b01
+#define MDIO_C22_OP_READ 0b10
+#define ASPEED_MDIO_CTRL_PHYAD GENMASK(25, 21)
+#define ASPEED_MDIO_CTRL_REGAD GENMASK(20, 16)
+#define ASPEED_MDIO_CTRL_MIIWDATA GENMASK(15, 0)
+
+#define ASPEED_MDIO_DATA 0x4
+#define ASPEED_MDIO_DATA_MDC_THRES GENMASK(31, 24)
+#define ASPEED_MDIO_DATA_MDIO_EDGE BIT(23)
+#define ASPEED_MDIO_DATA_MDIO_LATCH GENMASK(22, 20)
+#define ASPEED_MDIO_DATA_IDLE BIT(16)
+#define ASPEED_MDIO_DATA_MIIRDATA GENMASK(15, 0)
+
+#define ASPEED_MDIO_INTERVAL_US 100
+#define ASPEED_MDIO_TIMEOUT_US (ASPEED_MDIO_INTERVAL_US * 10)
+
+struct aspeed_mdio {
+ void __iomem *base;
+};
+
+static int aspeed_mdio_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct aspeed_mdio *ctx = bus->priv;
+ u32 ctrl;
+ u32 data;
+ int rc;
+
+ dev_dbg(&bus->dev, "%s: addr: %d, regnum: %d\n", __func__, addr,
+ regnum);
+
+ /* Just clause 22 for the moment */
+ if (regnum & MII_ADDR_C45)
+ return -EOPNOTSUPP;
+
+ ctrl = ASPEED_MDIO_CTRL_FIRE
+ | FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_READ)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regnum);
+
+ iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL);
+
+ rc = readl_poll_timeout(ctx->base + ASPEED_MDIO_DATA, data,
+ data & ASPEED_MDIO_DATA_IDLE,
+ ASPEED_MDIO_INTERVAL_US,
+ ASPEED_MDIO_TIMEOUT_US);
+ if (rc < 0)
+ return rc;
+
+ return FIELD_GET(ASPEED_MDIO_DATA_MIIRDATA, data);
+}
+
+static int aspeed_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+ struct aspeed_mdio *ctx = bus->priv;
+ u32 ctrl;
+
+ dev_dbg(&bus->dev, "%s: addr: %d, regnum: %d, val: 0x%x\n",
+ __func__, addr, regnum, val);
+
+ /* Just clause 22 for the moment */
+ if (regnum & MII_ADDR_C45)
+ return -EOPNOTSUPP;
+
+ ctrl = ASPEED_MDIO_CTRL_FIRE
+ | FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_WRITE)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regnum)
+ | FIELD_PREP(ASPEED_MDIO_CTRL_MIIWDATA, val);
+
+ iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL);
+
+ return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl,
+ !(ctrl & ASPEED_MDIO_CTRL_FIRE),
+ ASPEED_MDIO_INTERVAL_US,
+ ASPEED_MDIO_TIMEOUT_US);
+}
+
+static int aspeed_mdio_probe(struct platform_device *pdev)
+{
+ struct aspeed_mdio *ctx;
+ struct mii_bus *bus;
+ int rc;
+
+ bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctx));
+ if (!bus)
+ return -ENOMEM;
+
+ ctx = bus->priv;
+ ctx->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctx->base))
+ return PTR_ERR(ctx->base);
+
+ bus->name = DRV_NAME;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id);
+ bus->parent = &pdev->dev;
+ bus->read = aspeed_mdio_read;
+ bus->write = aspeed_mdio_write;
+
+ rc = of_mdiobus_register(bus, pdev->dev.of_node);
+ if (rc) {
+ dev_err(&pdev->dev, "Cannot register MDIO bus!\n");
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, bus);
+
+ return 0;
+}
+
+static int aspeed_mdio_remove(struct platform_device *pdev)
+{
+ mdiobus_unregister(platform_get_drvdata(pdev));
+
+ return 0;
+}
+
+static const struct of_device_id aspeed_mdio_of_match[] = {
+ { .compatible = "aspeed,ast2600-mdio", },
+ { },
+};
+
+static struct platform_driver aspeed_mdio_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = aspeed_mdio_of_match,
+ },
+ .probe = aspeed_mdio_probe,
+ .remove = aspeed_mdio_remove,
+};
+
+module_platform_driver(aspeed_mdio_driver);
+
+MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
+MODULE_LICENSE("GPL");
--
2.20.1
^ permalink raw reply related
* [PATCH net-next v2 1/4] dt-bindings: net: Add aspeed,ast2600-mdio binding
From: Andrew Jeffery @ 2019-07-31 5:39 UTC (permalink / raw)
To: netdev
Cc: Andrew Jeffery, davem, robh+dt, mark.rutland, joel, andrew,
f.fainelli, hkallweit1, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel
In-Reply-To: <20190731053959.16293-1-andrew@aj.id.au>
The AST2600 splits out the MDIO bus controller from the MAC into its own
IP block and rearranges the register layout. Add a new binding to
describe the new hardware.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
v2:
* aspeed: Utilise mdio.yaml
* aspeed: Drop status from example
---
.../bindings/net/aspeed,ast2600-mdio.yaml | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/aspeed,ast2600-mdio.yaml
diff --git a/Documentation/devicetree/bindings/net/aspeed,ast2600-mdio.yaml b/Documentation/devicetree/bindings/net/aspeed,ast2600-mdio.yaml
new file mode 100644
index 000000000000..71808e78a495
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/aspeed,ast2600-mdio.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/aspeed,ast2600-mdio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASPEED AST2600 MDIO Controller
+
+maintainers:
+ - Andrew Jeffery <andrew@aj.id.au>
+
+description: |+
+ The ASPEED AST2600 MDIO controller is the third iteration of ASPEED's MDIO
+ bus register interface, this time also separating out the controller from the
+ MAC.
+
+allOf:
+ - $ref: "mdio.yaml#"
+
+properties:
+ compatible:
+ const: aspeed,ast2600-mdio
+ reg:
+ maxItems: 1
+ description: The register range of the MDIO controller instance
+
+required:
+ - compatible
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+
+examples:
+ - |
+ mdio0: mdio@1e650000 {
+ compatible = "aspeed,ast2600-mdio";
+ reg = <0x1e650000 0x8>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethphy0: ethernet-phy@0 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0>;
+ };
+ };
--
2.20.1
^ permalink raw reply related
* [PATCH net-next v2 0/4] net: phy: Add AST2600 MDIO support
From: Andrew Jeffery @ 2019-07-31 5:39 UTC (permalink / raw)
To: netdev
Cc: Andrew Jeffery, davem, robh+dt, mark.rutland, joel, andrew,
f.fainelli, hkallweit1, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel
Hello,
v2 of the ASPEED MDIO series addresses comments from Rob on the devicetree
bindings and Andrew on the driver itself.
v1 of the series can be found here:
http://patchwork.ozlabs.org/cover/1138140/
Please review!
Andrew
Andrew Jeffery (4):
dt-bindings: net: Add aspeed,ast2600-mdio binding
net: phy: Add mdio-aspeed
net: ftgmac100: Add support for DT phy-handle property
net: ftgmac100: Select ASPEED MDIO driver for the AST2600
.../bindings/net/aspeed,ast2600-mdio.yaml | 45 +++++
drivers/net/ethernet/faraday/Kconfig | 1 +
drivers/net/ethernet/faraday/ftgmac100.c | 37 ++++-
drivers/net/phy/Kconfig | 13 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/mdio-aspeed.c | 157 ++++++++++++++++++
6 files changed, 250 insertions(+), 4 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/aspeed,ast2600-mdio.yaml
create mode 100644 drivers/net/phy/mdio-aspeed.c
--
2.20.1
^ permalink raw reply
* Re: [PATCH v8 00/14] Rockchip ISP1 Driver
From: Hans Verkuil @ 2019-07-31 4:55 UTC (permalink / raw)
To: Helen Koike, linux-rockchip
Cc: devicetree, eddie.cai.linux, kernel, heiko, zhengsq, jeffy.chen,
zyc, linux-kernel, tfiga, hans.verkuil, laurent.pinchart,
sakari.ailus, mchehab, ezequiel, linux-arm-kernel, linux-media
In-Reply-To: <fb1327fb-0903-ce62-4eea-94b81f599b62@xs4all.nl>
On 7/31/19 6:33 AM, Hans Verkuil wrote:
> On 7/31/19 6:29 AM, Hans Verkuil wrote:
>> On 7/31/19 2:08 AM, Helen Koike wrote:
>>>
>>>
>>> On 7/30/19 5:50 PM, Helen Koike wrote:
>>>>
>>>>
>>>> On 7/30/19 5:15 PM, Hans Verkuil wrote:
>>>>> On 7/30/19 8:42 PM, Helen Koike wrote:
>>>>>> Hello,
>>>>>>
>>>>>> I'm re-sending a new version of ISP(Camera) v4l2 driver for rockchip
>>>>>> rk3399 SoC.
>>>>>>
>>>>>> I didn't change much from the last version, just applying the
>>>>>> suggestions made in the previous one.
>>>>>>
>>>>>> This patchset is also available at:
>>>>>> https://gitlab.collabora.com/koike/linux/tree/rockchip/isp/v8
>>>>>>
>>>>>> Libcamera patched to work with this version:
>>>>>> https://gitlab.collabora.com/koike/libcamera
>>>>>> (also sent to the mailing list)
>>>>>>
>>>>>> I tested on the rockpi 4 with a rpi v1.3 sensor and also with the
>>>>>> Scarlet Chromebook.
>>>>>>
>>>>>> Known issues (same as in v7):
>>>>>> -------------
>>>>>> - Reloading the module doesn't work (there is some missing cleanup when
>>>>>> unloading)
>>>>>> - When capturing in bayer format, changing the size doesn't seem to
>>>>>> affect the image.
>>>>>> - crop needs more tests
>>>>>> - v4l2-compliance error:
>>>>>> fail: v4l2-test-controls.cpp(824): subscribe event for control 'Image Processing Controls' failed
>>>>>> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: FAIL
>>>>>
>>>>> Can you mail me the full v4l2-compliance output?
>>>>
>>>> Sure, please check here: http://ix.io/1Q5u
>>>> I updated v4l-utils with the latest version and I re-ran bootstrap/configure/make,
>>>> but for some reason the hash from the link above is not the latest commit, probably some
>>>> old configuration somewhere. I'll resend this log as soon as I get v4l2-compliance
>>>> properly updated.
>>>
>>> Please see the output of v4l2-compliance here with an updated v4l-utils: http://ix.io/1Q6A
>>
>> So this FAIL is for /dev/v4l-subdev0 (rkisp1-isp-subdev).
>>
>> What is weird that this subdev does not appear to have controls at all.
>>
>> What is the output of 'v4l2-ctl -d /dev/v4l-subdev0 -l'? And if it lists
>> controls, then why?
>>
>> If you run 'v4l2-compliance -u /dev/v4l-subdev0', do you get a fail as
>> well?
>
> I see the same issue with v4l-subdev1, but I see no "Media Driver Info"
> in the v4l2-compliance output for that subdev. That's strange. It would
> be good to know why that's happening.
It looks to be some parenting issue: v4l2-compliance expects to find
a mediaX directory in /sys/dev/char/81\:Y/device/ where 81:Y is the major/minor
of /dev/v4l-subdev1.
Because is this mi_get_media_fd() cannot find the media device for the subdev
in v4l2-compliance.
Regards,
Hans
^ permalink raw reply
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