* [PATCH] dt-bindings: arm: renesas: Convert 'renesas, prr' to json-schema
From: Simon Horman @ 2019-09-06 11:14 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Mark Rutland, devicetree, Magnus Damm, linux-renesas-soc,
Rob Herring, Simon Horman, linux-arm-kernel
Convert Renesas Product Register bindings documentation to json-schema.
Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
---
Based on v5.3-rc1
Tested using:
make dtbs_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/arm/renesas,prr.yaml
---
.../devicetree/bindings/arm/renesas,prr.txt | 20 ------------
.../devicetree/bindings/arm/renesas,prr.yaml | 36 ++++++++++++++++++++++
2 files changed, 36 insertions(+), 20 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/arm/renesas,prr.txt
create mode 100644 Documentation/devicetree/bindings/arm/renesas,prr.yaml
diff --git a/Documentation/devicetree/bindings/arm/renesas,prr.txt b/Documentation/devicetree/bindings/arm/renesas,prr.txt
deleted file mode 100644
index 08e482e953ca..000000000000
--- a/Documentation/devicetree/bindings/arm/renesas,prr.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Renesas Product Register
-
-Most Renesas ARM SoCs have a Product Register or Boundary Scan ID Register that
-allows to retrieve SoC product and revision information. If present, a device
-node for this register should be added.
-
-Required properties:
- - compatible: Must be one of:
- "renesas,prr"
- "renesas,bsid"
- - reg: Base address and length of the register block.
-
-
-Examples
---------
-
- prr: chipid@ff000044 {
- compatible = "renesas,prr";
- reg = <0 0xff000044 0 4>;
- };
diff --git a/Documentation/devicetree/bindings/arm/renesas,prr.yaml b/Documentation/devicetree/bindings/arm/renesas,prr.yaml
new file mode 100644
index 000000000000..9df003041456
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/renesas,prr.yaml
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/renesas,prr.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas Product Register
+
+maintainers:
+ - Geert Uytterhoeven <geert+renesas@glider.be>
+ - Magnus Damm <magnus.damm@gmail.com>
+
+description: |
+ Most Renesas ARM SoCs have a Product Register or Boundary Scan ID
+ Register that allows to retrieve SoC product and revision information.
+ If present, a device node for this register should be added.
+
+properties:
+ compatible:
+ oneOf:
+ - const: "renesas,prr"
+ - const: "renesas,bsid"
+ reg:
+ items:
+ - description: Base address and length of the register block.
+
+required:
+ - compatible
+ - reg
+
+examples:
+ - |
+ prr: chipid@ff000044 {
+ compatible = "renesas,prr";
+ reg = <0 0xff000044 0 4>;
+ };
--
2.11.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [PATCH 3/3] dt-bindings: ddr: Add bindings for Samsung LPDDR3 memories
From: Krzysztof Kozlowski @ 2019-09-06 10:56 UTC (permalink / raw)
To: Lukasz Luba
Cc: mark.rutland, devicetree, willy.mh.wolff.ml,
linux-samsung-soc@vger.kernel.org,
Bartłomiej Żołnierkiewicz, linux-pm,
linux-kernel@vger.kernel.org, robh+dt, Chanwoo Choi,
kyungmin.park, kgene, myungjoo.ham, s.nawrocki, linux-arm-kernel,
Marek Szyprowski
In-Reply-To: <20190906101344.3535-4-l.luba@partner.samsung.com>
On Fri, 6 Sep 2019 at 12:14, Lukasz Luba <l.luba@partner.samsung.com> wrote:
>
> Add description of bindings for Samsung k3qf2f20db LPDDR3 memory.
> Minor fixes in the old documentation.
>
> Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
> ---
> .../devicetree/bindings/ddr/lpddr3.txt | 29 +++++++++++++++++--
> 1 file changed, 27 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/ddr/lpddr3.txt b/Documentation/devicetree/bindings/ddr/lpddr3.txt
> index 3b2485b84b3f..de0905239767 100644
> --- a/Documentation/devicetree/bindings/ddr/lpddr3.txt
> +++ b/Documentation/devicetree/bindings/ddr/lpddr3.txt
> @@ -40,10 +40,34 @@ Child nodes:
> a given speed-bin. Please see Documentation/devicetree/
> bindings/ddr/lpddr3-timings.txt for more information on "lpddr3-timings"
>
> +Samsung K3QF2F20DB LPDDR3 memory
> +------------------------------------------------------------
> +
> +This binding uses the LPDDR3 binding (described above)
> +
> +Required properties:
> +- compatible: Should be:
> + "samsung,K3QF2F20DB"
> + followed by "jedec,lpddr3"
> +- density : <u32> representing density in Mb (Mega bits)
> +- io-width : <u32> representing bus width. Possible value 32
> +- #address-cells: Must be set to 1
> +- #size-cells: Must be set to 0
If you decided to repeat all properties again, then it deserves its
own bindings file. However I though about simpler solution - just
document compatible. Exactly the same as AT24 or AT25 EEPROM bindings.
There is not much benefit from copying all these properties.
Best regards,
Krzysztof
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH 1/3] memory: Exynos5422: minor fixes in DMC
From: Krzysztof Kozlowski @ 2019-09-06 10:51 UTC (permalink / raw)
To: Lukasz Luba
Cc: mark.rutland, devicetree, willy.mh.wolff.ml,
linux-samsung-soc@vger.kernel.org,
Bartłomiej Żołnierkiewicz, linux-pm,
linux-kernel@vger.kernel.org, robh+dt, Chanwoo Choi,
kyungmin.park, kgene, myungjoo.ham, s.nawrocki, linux-arm-kernel,
Marek Szyprowski
In-Reply-To: <20190906101344.3535-2-l.luba@partner.samsung.com>
On Fri, 6 Sep 2019 at 12:14, Lukasz Luba <l.luba@partner.samsung.com> wrote:
>
> Small fixes captured by static analyzes.
Explain please what are the errors being fixed. Additionally error
message from tool might be useful.
Also:
Reported-by: Krzysztof Kozlowski <krzk@kernel.org>
Best regards,
Krzysztof
> Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
> ---
> drivers/memory/samsung/exynos5422-dmc.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/memory/samsung/exynos5422-dmc.c b/drivers/memory/samsung/exynos5422-dmc.c
> index 8c2ec29a7d57..a809fa997c03 100644
> --- a/drivers/memory/samsung/exynos5422-dmc.c
> +++ b/drivers/memory/samsung/exynos5422-dmc.c
> @@ -269,7 +269,7 @@ static int exynos5_init_freq_table(struct exynos5_dmc *dmc,
> return 0;
>
> err_free_tables:
> - kfree(dmc->opp);
> + devm_kfree(dmc->dev, dmc->opp);
> err_opp:
> dev_pm_opp_of_remove_table(dmc->dev);
>
> @@ -732,7 +732,7 @@ static struct devfreq_dev_profile exynos5_dmc_df_profile = {
> * statistics engine which supports only registered values. Thus, some alignment
> * must be made.
> */
> -unsigned long
> +static unsigned long
> exynos5_dmc_align_init_freq(struct exynos5_dmc *dmc,
> unsigned long bootloader_init_freq)
> {
> --
> 2.17.1
>
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH] gpio/mpc8xxx: change irq handler from chained to normal
From: Hui Song @ 2019-09-06 10:37 UTC (permalink / raw)
To: Shawn Guo, Li Yang, Rob Herring, Mark Rutland, Linus Walleij,
Bartosz Golaszewski
Cc: devicetree, Song Hui, linux-kernel, linux-arm-kernel, linux-gpio
From: Song Hui <hui.song_1@nxp.com>
more one gpio controller use share one interrupt,
make request interrupt to be shared.
Signed-off-by: Laurentiu Tudor <Laurentiu.Tudor@nxp.com>
Signed-off-by: Alex Marginean <alexandru.marginean@nxp.com>
Signed-off-by: Song Hui <hui.song_1@nxp.com>
---
drivers/gpio/gpio-mpc8xxx.c | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c
index 1a680aa..4006250 100644
--- a/drivers/gpio/gpio-mpc8xxx.c
+++ b/drivers/gpio/gpio-mpc8xxx.c
@@ -22,6 +22,7 @@
#include <linux/irq.h>
#include <linux/gpio/driver.h>
#include <linux/bitops.h>
+#include <linux/interrupt.h>
#define MPC8XXX_GPIO_PINS 32
@@ -127,10 +128,9 @@ static int mpc8xxx_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
return -ENXIO;
}
-static void mpc8xxx_gpio_irq_cascade(struct irq_desc *desc)
+static irqreturn_t mpc8xxx_gpio_irq_cascade(int irq, void *data)
{
- struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_desc_get_handler_data(desc);
- struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = (struct mpc8xxx_gpio_chip *)data;
struct gpio_chip *gc = &mpc8xxx_gc->gc;
unsigned int mask;
@@ -139,8 +139,8 @@ static void mpc8xxx_gpio_irq_cascade(struct irq_desc *desc)
if (mask)
generic_handle_irq(irq_linear_revmap(mpc8xxx_gc->irq,
32 - ffs(mask)));
- if (chip->irq_eoi)
- chip->irq_eoi(&desc->irq_data);
+
+ return IRQ_HANDLED;
}
static void mpc8xxx_irq_unmask(struct irq_data *d)
@@ -319,6 +319,7 @@ static const struct of_device_id mpc8xxx_gpio_ids[] = {
{ .compatible = "fsl,mpc5125-gpio", .data = &mpc5125_gpio_devtype, },
{ .compatible = "fsl,pq3-gpio", },
{ .compatible = "fsl,ls1028a-gpio", .data = &ls1028a_gpio_devtype, },
+ { .compatible = "fsl,ls1088a-gpio", .data = &ls1028a_gpio_devtype, },
{ .compatible = "fsl,qoriq-gpio", },
{}
};
@@ -408,8 +409,14 @@ static int mpc8xxx_probe(struct platform_device *pdev)
if (devtype->gpio_dir_in_init)
devtype->gpio_dir_in_init(gc);
- irq_set_chained_handler_and_data(mpc8xxx_gc->irqn,
- mpc8xxx_gpio_irq_cascade, mpc8xxx_gc);
+ ret = request_irq(mpc8xxx_gc->irqn, mpc8xxx_gpio_irq_cascade,
+ IRQF_NO_THREAD | IRQF_SHARED, "gpio-cascade", mpc8xxx_gc);
+ if (ret) {
+ pr_err("%s: failed to request_irq(%d), ret = %d\n",
+ np->full_name, mpc8xxx_gc->irqn, ret);
+ goto err;
+ }
+
return 0;
err:
iounmap(mpc8xxx_gc->regs);
--
2.9.5
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [PATCH v2] pinctrl: at91-pio4: implement .get_multiple and .set_multiple
From: Alexandre Belloni @ 2019-09-06 10:42 UTC (permalink / raw)
To: David Laight
Cc: Linus Walleij, linux-kernel@vger.kernel.org,
linux-gpio@vger.kernel.org, Ludovic Desroches,
Claudiu.Beznea@microchip.com,
linux-arm-kernel@lists.infradead.org
In-Reply-To: <b010053340ef48dfa244ff48c8decd38@AcuMS.aculab.com>
On 06/09/2019 09:46:02+0000, David Laight wrote:
> From: Alexandre Belloni
> > Sent: 06 September 2019 10:12
> > On 06/09/2019 09:05:36+0000, David Laight wrote:
> > > From: Alexandre Belloni
> > > > Implement .get_multiple and .set_multiple to allow reading or setting
> > > > multiple pins simultaneously. Pins in the same bank will all be switched at
> > > > the same time, improving synchronization and performances.
> > >
> > > Actually it won't 'improve synchronisation', instead it will lead to
> > > random synchronisation errors and potential metastability if one
> > > pin is used as a clock and another as data, or if the code is reading
> > > a free-flowing counter.
> > >
> >
> > It does improve gpio switching synchronisation when they are in the same
> > bank as it will remove the 250ns delay. Of course, if you need this
> > delay between clk and data, then the consumer driver should ensure the
> > delay is present.
>
> With multiple requests the output pin changes will always be in the
> same order and will be separated by (say) 250ns.
> This is a guaranteed synchronisation.
>
> If you change multiple pins with the same 'iowrite()' then the pins
> will change at approximately the same time.
> But the actual order will depend on internal device delays (which
> may depend on the actual silicon and temperature).
> You then have to take account of varying track lengths and the
> target devices input stage properties before knowing which change
> arrives first.
> The delays might be sub-nanosecond, but they matter if you are
> talking about synchronisation.
>
And my point is that this means that your gpio consumer driver is buggy
if it doesn't do multiple requests if it requires a delay between two
pin changes.
> IIRC both SMBus and I2C now quote 0ns setup time.
> Changing both clock and data with the same IOW isn't enough to
> guarantee this.
> (In practise the I2C setup time required by a device is probably
> slightly negative (In order to support 0ns inputs) so a very small
> -ve setup will (mostly) work.)
I'm not sure what is your point exactly as this patch doesn't break any
existing use cases.
--
Alexandre Belloni, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH] arm64: dts: imx8mq: Add CAAM node
From: Horia Geanta @ 2019-09-06 10:36 UTC (permalink / raw)
To: Andrey Smirnov, Herbert Xu, Shawn Guo
Cc: Cory Tusar, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, Iuliana Prodan,
Sascha Hauer, linux-kernel@vger.kernel.org,
linux-crypto@vger.kernel.org, Pengutronix Kernel Team,
dl-linux-imx, Chris Healy, Lucas Stach
In-Reply-To: <20190830210139.7028-1-andrew.smirnov@gmail.com>
On 8/31/2019 12:01 AM, Andrey Smirnov wrote:
> Add node for CAAM - Cryptographic Acceleration and Assurance Module.
>
> Signed-off-by: Horia Geantă <horia.geanta@nxp.com>
> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
> Cc: Cory Tusar <cory.tusar@zii.aero>
> Cc: Chris Healy <cphealy@gmail.com>
> Cc: Lucas Stach <l.stach@pengutronix.de>
> Cc: Herbert Xu <herbert@gondor.apana.org.au>
> Cc: Shawn Guo <shawnguo@kernel.org>
> Cc: Iuliana Prodan <iuliana.prodan@nxp.com>
> Cc: linux-crypto@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> ---
>
> Shawn:
>
> Just a bit of a context: as per this thread
> https://lore.kernel.org/linux-crypto/20190830131547.GA27480@gondor.apana.org.au/
> I am hoping I can get and Ack from you for this patch, so it can go
> via cryptodev tree.
>
Could we please get an Ack in time for v5.4?
Thanks,
Horia
> Thanks,
> Andrey Smirnov
>
> arch/arm64/boot/dts/freescale/imx8mq.dtsi | 30 +++++++++++++++++++++++
> 1 file changed, 30 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> index d09b808eff87..752d5a61878c 100644
> --- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> +++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
> @@ -728,6 +728,36 @@
> status = "disabled";
> };
>
> + crypto: crypto@30900000 {
> + compatible = "fsl,sec-v4.0";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + reg = <0x30900000 0x40000>;
> + ranges = <0 0x30900000 0x40000>;
> + interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk IMX8MQ_CLK_AHB>,
> + <&clk IMX8MQ_CLK_IPG_ROOT>;
> + clock-names = "aclk", "ipg";
> +
> + sec_jr0: jr@1000 {
> + compatible = "fsl,sec-v4.0-job-ring";
> + reg = <0x1000 0x1000>;
> + interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>;
> + };
> +
> + sec_jr1: jr@2000 {
> + compatible = "fsl,sec-v4.0-job-ring";
> + reg = <0x2000 0x1000>;
> + interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
> + };
> +
> + sec_jr2: jr@3000 {
> + compatible = "fsl,sec-v4.0-job-ring";
> + reg = <0x3000 0x1000>;
> + interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + };
> +
> i2c1: i2c@30a20000 {
> compatible = "fsl,imx8mq-i2c", "fsl,imx21-i2c";
> reg = <0x30a20000 0x10000>;
>
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCHv4 01/10] dt-bindings: omap: add new binding for PRM instances
From: Tero Kristo @ 2019-09-06 10:35 UTC (permalink / raw)
To: ssantosh, linux-omap, tony, s-anna, p.zabel, robh+dt
Cc: devicetree, linux-arm-kernel
In-Reply-To: <20190830121816.30034-2-t-kristo@ti.com>
Add new binding for OMAP PRM (Power and Reset Manager) instances. Each
of these will act as a power domain controller and potentially as a reset
provider.
Signed-off-by: Tero Kristo <t-kristo@ti.com>
---
v4:
- renamed nodes as power-controller
- added documentation about hierarchy
.../devicetree/bindings/arm/omap/prm-inst.txt | 31 +++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/omap/prm-inst.txt
diff --git a/Documentation/devicetree/bindings/arm/omap/prm-inst.txt b/Documentation/devicetree/bindings/arm/omap/prm-inst.txt
new file mode 100644
index 000000000000..7c7527c37734
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/omap/prm-inst.txt
@@ -0,0 +1,35 @@
+OMAP PRM instance bindings
+
+Power and Reset Manager is an IP block on OMAP family of devices which
+handle the power domains and their current state, and provide reset
+handling for the domains and/or separate IP blocks under the power domain
+hierarchy. A PRM instance node must be a child of a PRM node [1].
+
+[1] Documentation/devicetree/bindings/arm/omap/prcm.txt
+
+Required properties:
+- compatible: Must be one of:
+ "ti,am3-prm-inst"
+ "ti,am4-prm-inst"
+ "ti,omap4-prm-inst"
+ "ti,omap5-prm-inst"
+ "ti,dra7-prm-inst"
+- reg: Contains PRM instance register address range
+ (base address and length)
+
+Optional properties:
+- #reset-cells: Should be 1 if the PRM instance in question supports resets.
+- clocks: Associated clocks for the reset signals if any. Certain reset
+ signals can't be toggled properly without functional clock
+ being active for them.
+
+Example:
+
+&prm {
+ prm_dsp2: power-controller@1b00 {
+ compatible = "ti,dra7-prm-inst";
+ reg = <0x1b00 0x40>;
+ #reset-cells = <1>;
+ clocks = <&dsp2_clkctrl DRA7_DSP2_MMU0_DSP2_CLKCTRL 0>;
+ };
+};
--
2.17.1
--
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH 0/2] i.MX GPC DMA warning fixes
From: Lucas Stach @ 2019-09-06 10:33 UTC (permalink / raw)
To: Shawn Guo, Fabio Estevam
Cc: kernel, NXP Linux Team, linux-arm-kernel, patchwork-lst
Hi all,
those two patches get rid of an annoying boot warning on all i.MX6+
devices, as the generic OF DMA code tries to forcefully configure the
DMA capabilities for the virtual GPX power domain devices, which will
never do any DMA.
This solution isn't the most elegant, as we are making up a 0 DMA mask
to silence the warning instead of skipping the DMA configuration
completely. A previous submission of this change for just the GPC v1 [1]
was shot down for this reason. But then we haven't made any progress in
fixing this in a better way, as other systems depend on the forceful
OF DMA configuration to work correctly. So we are stuck between a rock
and a hard place here.
IMHO we should accept the slight uglyness of this solution in order to
get rid of the warning now, as it is confusing users into thinking their
kernel configuration or system setup is faulty, while everything is
perfectly in order.
Regards,
Lucas
[1] https://patchwork.kernel.org/patch/10621589/
Lucas Stach (2):
soc: imx: gpc: set DMA mask for PD platform devices
soc: imx: gpcv2: set DMA mask for PD platform devices
drivers/soc/imx/gpc.c | 1 +
drivers/soc/imx/gpcv2.c | 1 +
2 files changed, 2 insertions(+)
--
2.20.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH 2/2] soc: imx: gpcv2: set DMA mask for PD platform devices
From: Lucas Stach @ 2019-09-06 10:34 UTC (permalink / raw)
To: Shawn Guo, Fabio Estevam
Cc: kernel, NXP Linux Team, linux-arm-kernel, patchwork-lst
In-Reply-To: <20190906103401.22294-1-l.stach@pengutronix.de>
The PD platform devices aren't DMA capable (as is correctly reflected
in with a zero mask coherent_dma_mask). As they have a of_node attached
they will go through of_dma_configure, which will warn if it finds an
uninitialized DMA mask. Let the driver core know that this isn't a
missing configuration, but a non DMA capable device by pointing the
dma_mask to the coherent_dma_mask.
Signed-off-by: Lucas Stach <l.stach@pengutronix.de>
---
drivers/soc/imx/gpcv2.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c
index 31b8d002d855..7268f69a8d03 100644
--- a/drivers/soc/imx/gpcv2.c
+++ b/drivers/soc/imx/gpcv2.c
@@ -626,6 +626,7 @@ static int imx_gpcv2_probe(struct platform_device *pdev)
pd_pdev->dev.parent = dev;
pd_pdev->dev.of_node = np;
+ pd_pdev->dev.dma_mask = &pd_pdev->dev.coherent_dma_mask;
ret = platform_device_add(pd_pdev);
if (ret) {
--
2.20.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH 1/2] soc: imx: gpc: set DMA mask for PD platform devices
From: Lucas Stach @ 2019-09-06 10:34 UTC (permalink / raw)
To: Shawn Guo, Fabio Estevam
Cc: kernel, NXP Linux Team, linux-arm-kernel, patchwork-lst
In-Reply-To: <20190906103401.22294-1-l.stach@pengutronix.de>
The PD platform devices aren't DMA capable (as is correctly reflected
in with a zero mask coherent_dma_mask). As they have a of_node attached
they will go through of_dma_configure, which will warn if it finds an
uninitialized DMA mask. Let the driver core know that this isn't a
missing configuration, but a non DMA capable device by pointing the
dma_mask to the coherent_dma_mask.
Signed-off-by: Lucas Stach <l.stach@pengutronix.de>
---
drivers/soc/imx/gpc.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c
index 34d167a8b08a..136d328690fb 100644
--- a/drivers/soc/imx/gpc.c
+++ b/drivers/soc/imx/gpc.c
@@ -498,6 +498,7 @@ static int imx_gpc_probe(struct platform_device *pdev)
pd_pdev->dev.parent = &pdev->dev;
pd_pdev->dev.of_node = np;
+ pd_pdev->dev.dma_mask = &pd_pdev->dev.coherent_dma_mask;
ret = platform_device_add(pd_pdev);
if (ret) {
--
2.20.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [v2] ACPI: support for NXP i2c controller
From: Andy Shevchenko @ 2019-09-06 10:31 UTC (permalink / raw)
To: Biwen Li
Cc: Wolfram Sang, Sascha Hauer, Udit Kumar, Rafael J. Wysocki,
Linux Kernel Mailing List, Leo Li, ACPI Devel Maling List,
Meenakshi Aggarwal, linux-i2c, Chuanhua Han, Rafael J. Wysocki,
Shawn Guo, linux-arm Mailing List
In-Reply-To: <20190906075319.21244-1-biwen.li@nxp.com>
On Fri, Sep 6, 2019 at 11:03 AM Biwen Li <biwen.li@nxp.com> wrote:
>
> From: Chuanhua Han <chuanhua.han@nxp.com>
>
> Enable NXP i2c controller to boot with ACPI
>
Thanks, the code looks good to me,
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
though...
> Signed-off-by: Meenakshi Aggarwal <meenakshi.aggarwal@nxp.com>
> Signed-off-by: Udit Kumar <udit.kumar@nxp.com>
> Signed-off-by: Chuanhua Han <chuanhua.han@nxp.com>
This SoB chain is a bit odd. Who is the author of this? The first SoB
in the chain usually points to the first (main) author. There is also
possible to change that, though in that case for the rest we now use
Co-developed-by tag rather than SoB.
In any case, if Rafael and Wolfram are okay with this, I have no objections.
> Signed-off-by: Biwen Li <biwen.li@nxp.com>
> ---
> Change in v2:
> - Simplify code
> - Adjust header file order
> - Not use ACPI_PTR()
>
> drivers/acpi/acpi_apd.c | 7 +++++++
> drivers/i2c/busses/i2c-imx.c | 17 +++++++++++++----
> 2 files changed, 20 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c
> index 7cd0c9ac71ea..71511ae2dfcd 100644
> --- a/drivers/acpi/acpi_apd.c
> +++ b/drivers/acpi/acpi_apd.c
> @@ -160,11 +160,17 @@ static const struct apd_device_desc hip08_i2c_desc = {
> .setup = acpi_apd_setup,
> .fixed_clk_rate = 250000000,
> };
> +
> static const struct apd_device_desc thunderx2_i2c_desc = {
> .setup = acpi_apd_setup,
> .fixed_clk_rate = 125000000,
> };
>
> +static const struct apd_device_desc nxp_i2c_desc = {
> + .setup = acpi_apd_setup,
> + .fixed_clk_rate = 350000000,
> +};
> +
> static const struct apd_device_desc hip08_spi_desc = {
> .setup = acpi_apd_setup,
> .fixed_clk_rate = 250000000,
> @@ -238,6 +244,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = {
> { "HISI02A1", APD_ADDR(hip07_i2c_desc) },
> { "HISI02A2", APD_ADDR(hip08_i2c_desc) },
> { "HISI0173", APD_ADDR(hip08_spi_desc) },
> + { "NXP0001", APD_ADDR(nxp_i2c_desc) },
> #endif
> { }
> };
> diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
> index 15f6cde6452f..a3b61336fe55 100644
> --- a/drivers/i2c/busses/i2c-imx.c
> +++ b/drivers/i2c/busses/i2c-imx.c
> @@ -20,6 +20,7 @@
> *
> */
>
> +#include <linux/acpi.h>
> #include <linux/clk.h>
> #include <linux/completion.h>
> #include <linux/delay.h>
> @@ -255,6 +256,12 @@ static const struct of_device_id i2c_imx_dt_ids[] = {
> };
> MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);
>
> +static const struct acpi_device_id i2c_imx_acpi_ids[] = {
> + {"NXP0001", .driver_data = (kernel_ulong_t)&vf610_i2c_hwdata},
> + { }
> +};
> +MODULE_DEVICE_TABLE(acpi, i2c_imx_acpi_ids);
> +
> static inline int is_imx1_i2c(struct imx_i2c_struct *i2c_imx)
> {
> return i2c_imx->hwdata->devtype == IMX1_I2C;
> @@ -1048,14 +1055,13 @@ static const struct i2c_algorithm i2c_imx_algo = {
>
> static int i2c_imx_probe(struct platform_device *pdev)
> {
> - const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
> - &pdev->dev);
> struct imx_i2c_struct *i2c_imx;
> struct resource *res;
> struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
> void __iomem *base;
> int irq, ret;
> dma_addr_t phy_addr;
> + const struct imx_i2c_hwdata *match;
>
> dev_dbg(&pdev->dev, "<%s>\n", __func__);
>
> @@ -1075,8 +1081,9 @@ static int i2c_imx_probe(struct platform_device *pdev)
> if (!i2c_imx)
> return -ENOMEM;
>
> - if (of_id)
> - i2c_imx->hwdata = of_id->data;
> + match = device_get_match_data(&pdev->dev);
> + if (match)
> + i2c_imx->hwdata = match;
> else
> i2c_imx->hwdata = (struct imx_i2c_hwdata *)
> platform_get_device_id(pdev)->driver_data;
> @@ -1089,6 +1096,7 @@ static int i2c_imx_probe(struct platform_device *pdev)
> i2c_imx->adapter.nr = pdev->id;
> i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
> i2c_imx->base = base;
> + ACPI_COMPANION_SET(&i2c_imx->adapter.dev, ACPI_COMPANION(&pdev->dev));
>
> /* Get I2C clock */
> i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
> @@ -1247,6 +1255,7 @@ static struct platform_driver i2c_imx_driver = {
> .name = DRIVER_NAME,
> .pm = &i2c_imx_pm_ops,
> .of_match_table = i2c_imx_dt_ids,
> + .acpi_match_table = i2c_imx_acpi_ids,
> },
> .id_table = imx_i2c_devtype,
> };
> --
> 2.17.1
>
--
With Best Regards,
Andy Shevchenko
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v5 02/11] kselftest: arm64: add common utils and one testcase
From: Cristian Marussi @ 2019-09-06 10:26 UTC (permalink / raw)
To: Dave Martin
Cc: amit.kachhap, andreyknvl, shuah, linux-arm-kernel,
linux-kselftest
In-Reply-To: <20190904114752.GS27757@arm.com>
Hi
On 04/09/2019 12:47, Dave Martin wrote:
> ^Nit: "add one testcase" doesn't really describe what is being added here.
>
Yep I know...I was trying to stay under first commit line length limitations
> Maybe the following would work as the subject line:
>
> --8<--
> kselftest: arm64: mangle_pstate_invalid_compat_toggle and common utils
> -->8--
>
I'll grab it
> The remainder of the commit message looks fine.
>
> On Mon, Sep 02, 2019 at 12:29:23pm +0100, Cristian Marussi wrote:
>> Add some arm64/signal specific boilerplate and utility code to help
>> further testcases' development.
>>
>> Introduce also one simple testcase mangle_pstate_invalid_compat_toggle
>> and some related helpers: it is a simple mangle testcase which messes
>> with the ucontext_t from within the signal handler, trying to toggle
>> PSTATE state bits to switch the system between 32bit/64bit execution
>> state. Expects SIGSEGV on test PASS.
>>
>> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
>> ---
>> v4 --> v5
>> - moved kernel headers include search to top level KSFT arm64 Makefile
>> - removed warning about kernel headers not found
>> - moved testcases/.gitignore up one level
>> v3 --> v4
>> - removed standalone mode
>> - fixed arm64/signal/README
>> - add file level comments: test layout / test description
>> - reduced verbosity
>> - removed spurious headers includes
>> - reviewed ID_AA64MMFR[1,2]_EL1 macros
>> - removed unused feats_ok
>> - simplified CPU features gathering
>> - reviewed included headers
>> - fixed/refactored get_header() and validation routines
>> - added test description
>> ---
>
> [...]
>
>> diff --git a/tools/testing/selftests/arm64/signal/test_signals.c b/tools/testing/selftests/arm64/signal/test_signals.c
>> new file mode 100644
>> index 000000000000..f05c6dbf8659
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm64/signal/test_signals.c
>> @@ -0,0 +1,29 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2019 ARM Limited
>> + *
>> + * Generic test wrapper for arm64 signal tests.
>> + *
>> + * Each test provides its own tde struct tddescr descriptor to link with
>
> Typo? tdescr
>
Yes
> [...]
>
>> diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.c b/tools/testing/selftests/arm64/signal/test_signals_utils.c
>> new file mode 100644
>> index 000000000000..e2a5f37e6ad3
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm64/signal/test_signals_utils.c
>> @@ -0,0 +1,269 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (C) 2019 ARM Limited */
>> +
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <signal.h>
>> +#include <string.h>
>> +#include <unistd.h>
>> +#include <assert.h>
>> +#include <sys/auxv.h>
>> +#include <linux/auxvec.h>
>> +#include <ucontext.h>
>> +
>> +#include "test_signals.h"
>> +#include "test_signals_utils.h"
>> +#include "testcases/testcases.h"
>> +
>> +extern struct tdescr *current;
>> +
>> +static char *feats_store[FMAX_END] = {
>
> Nit: can we call this feat_names[]?ok
>
> "store" makes me think of loads and stores...
>
> Also, nit: can this be static const char *const []?
>
> String literals are immutable anyway, and I guess we don't intend too
> modify the pointers to the strings either...
>
Yes of course.
>> + " SSBS ",
>> + " PAN ",
>> + " UAO ",
>> +};
>> +
>> +#define MAX_FEATS_SZ 128
>> +static char feats_string[MAX_FEATS_SZ];
>> +
>> +static inline char *feats_to_string(unsigned long feats)
>> +{
>> + size_t flen = MAX_FEATS_SZ - 1;
>> +
>> + for (int i = 0; i < FMAX_END; i++) {
>> + if (feats & 1UL << i) {
>
> Nit: maybe have () around (1UL << i), though I think it makes no
> difference.
Yes it's better, I feared that, being not required, was frown upon.
>
>> + size_t tlen = strlen(feats_store[i]);
>> +
>> + assert(flen > tlen);
>> + flen -= tlen;
>> + strncat(feats_string, feats_store[i], flen);
>> + }
>> + }
>> +
>> + return feats_string;
>> +}
>> +
>> +static void unblock_signal(int signum)
>> +{
>> + sigset_t sset;
>> +
>> + sigemptyset(&sset);
>> + sigaddset(&sset, signum);
>> + sigprocmask(SIG_UNBLOCK, &sset, NULL);
>> +}
>> +
>> +static void default_result(struct tdescr *td, bool force_exit)
>> +{
>> + if (td->pass)
>> + fprintf(stderr, "==>> completed. PASS(1)\n");
>> + else
>> + fprintf(stdout, "==>> completed. FAIL(0)\n");
>> + if (force_exit)
>> + exit(td->pass ? EXIT_SUCCESS : EXIT_FAILURE);
>> +}
>> +
>> +static inline bool are_feats_ok(struct tdescr *td)
>> +{
>> + return (td->feats_required & td->feats_supported) == td->feats_required;
>> +}
>> +
>> +static void default_handler(int signum, siginfo_t *si, void *uc)
>> +{
>> + if (current->sig_trig && signum == current->sig_trig) {
>
> (Thinking about it, signum is never 0 because there is no signal 0.
> So we could write if (signum == current->sig_trig). But I think your
> code makes the intention clearer -- so no need to change it.)
>
Yes, in fact that's the reason I left it even if unneeded.
>> + fprintf(stderr, "Handling SIG_TRIG\n");
>> + current->triggered = 1;
>> + /* ->run was asserted NON-NULL in test_setup() already */
>> + current->run(current, si, uc);
>> + } else if (signum == SIGILL && !current->initialized) {
>> + /*
>> + * A SIGILL here while still not initialized means we failed
>> + * even to asses the existence of features during init
>> + */
>> + fprintf(stdout,
>> + "Got SIGILL test_init. Marking ALL features UNSUPPORTED.\n");
>> + current->feats_supported = 0;
>> + } else if (current->sig_ok && signum == current->sig_ok) {
>> + /*
>> + * it's a bug in the test code when this assert fail:
>> + * if a sig_trig was defined, it must have been used before
>> + * arriving here.
>> + */
>> + assert(!current->sig_trig || current->triggered);
>> + fprintf(stderr,
>> + "SIG_OK -- SP:0x%llX si_addr@:%p si_code:%d token@:%p offset:%ld\n",
>> + ((ucontext_t *)uc)->uc_mcontext.sp,
>> + si->si_addr, si->si_code, current->token,
>> + current->token - si->si_addr);
>> + /*
>> + * fake_sigreturn tests, which have sanity_enabled=1, set, at
>> + * the very last time, the token field to the SP address used
>> + * to place the fake sigframe: so token==0 means we never made
>> + * it to the end, segfaulting well-before, and the test is
>> + * possibly broken.
>> + */
>> + if (!current->sanity_disabled && !current->token) {
>> + fprintf(stdout,
>> + "current->token ZEROED...test is probably broken!\n");
>> + abort();
>> + }
>> + /*
>> + * Trying to narrow down the SEGV to the ones generated by
>> + * Kernel itself via arm64_notify_segfault().
>> + * This is a best-effort check anyway, and the si_code check may
>> + * need to change if this aspect of the kernel ABI changes.
>> + */
>> + if (current->sig_ok == SIGSEGV && si->si_code != SEGV_ACCERR) {
>> + fprintf(stdout,
>> + "si_code != SEGV_ACCERR...test is probably broken!\n");
>> + abort();
>> + }
>> + fprintf(stderr, "Handling SIG_OK\n");
>> + current->pass = 1;
>> + /*
>> + * Some tests can lead to SEGV loops: in such a case we want
>> + * to terminate immediately exiting straight away
>> + */
>> + default_result(current, 1);
>> + } else {
>> + if (signum == current->sig_unsupp && !are_feats_ok(current)) {
>> + fprintf(stderr,
>> + "-- RX SIG_UNSUPP on unsupported feat...OK\n");
>> + current->pass = 1;
>> + } else if (signum == SIGALRM && current->timeout) {
>> + fprintf(stderr, "-- Timeout !\n");
>> + } else {
>> + fprintf(stderr,
>> + "-- RX UNEXPECTED SIGNAL: %d\n", signum);
>> + }
>> + default_result(current, 1);
>> + }
>> +}
>> +
>> +static int default_setup(struct tdescr *td)
>> +{
>> + struct sigaction sa;
>> +
>> + sa.sa_sigaction = default_handler;
>> + sa.sa_flags = SA_SIGINFO | SA_RESTART;
>> + sa.sa_flags |= td->sa_flags;
>> + sigemptyset(&sa.sa_mask);
>> + /* uncatchable signals naturally skipped ... */
>> + for (int sig = 1; sig < 32; sig++)
>> + sigaction(sig, &sa, NULL);
>> + /*
>> + * RT Signals default disposition is Term but they cannot be
>> + * generated by the Kernel in response to our tests; so just catch
>> + * them all and report them as UNEXPECTED signals.
>> + */
>> + for (int sig = SIGRTMIN; sig <= SIGRTMAX; sig++)
>> + sigaction(sig, &sa, NULL);
>> +
>> + /* just in case...unblock explicitly all we need */
>> + if (td->sig_trig)
>> + unblock_signal(td->sig_trig);
>> + if (td->sig_ok)
>> + unblock_signal(td->sig_ok);
>> + if (td->sig_unsupp)
>> + unblock_signal(td->sig_unsupp);
>> +
>> + if (td->timeout) {
>> + unblock_signal(SIGALRM);
>> + alarm(td->timeout);
>> + }
>> + fprintf(stderr, "Registered handlers for all signals.\n");
>> +
>> + return 1;
>> +}
>> +
>> +static inline int default_trigger(struct tdescr *td)
>> +{
>> + return !raise(td->sig_trig);
>> +}
>> +
>> +static int test_init(struct tdescr *td)
>> +{
>> + td->minsigstksz = getauxval(AT_MINSIGSTKSZ);
>> + if (!td->minsigstksz)
>> + td->minsigstksz = MINSIGSTKSZ;
>> + fprintf(stderr, "Detected MINSTKSIGSZ:%d\n", td->minsigstksz);
>> +
>> + if (td->feats_required) {
>> + bool feats_ok = false;
>> +
>> + td->feats_supported = 0;
>> + /*
>> + * Checking for CPU required features using both the
>> + * auxval and the arm64 MRS Emulation to read sysregs.
>> + */
>> + if (getauxval(AT_HWCAP) & HWCAP_CPUID) {
>> + uint64_t val = 0;
>> +
>> + /* Uses HWCAP to check capability */
>> + if (getauxval(AT_HWCAP) & HWCAP_SSBS)
>> + td->feats_supported |= FEAT_SSBS;
>
> Should this be outside the HWCAP_CPUID check?
Right.
>
> It's only the get_regval(SYS_ID_foo) based checks that depend on
> HWCAP_CPUID.
>
>> + /* Uses MRS emulation to check capability */
>> + get_regval(SYS_ID_AA64MMFR1_EL1, val);
>> + if (ID_AA64MMFR1_EL1_PAN_SUPPORTED(val))
>> + td->feats_supported |= FEAT_PAN;
>> + /* Uses MRS emulation to check capability */
>> + get_regval(SYS_ID_AA64MMFR2_EL1, val);
>> + if (ID_AA64MMFR2_EL1_UAO_SUPPORTED(val))
>> + td->feats_supported |= FEAT_UAO;
>> + } else {
>> + fprintf(stderr,
>> + "HWCAP_CPUID NOT available. Mark ALL feats UNSUPPORTED.\n");
>> + }
>> + feats_ok = are_feats_ok(td);
>> + fprintf(stderr,
>> + "Required Features: [%s] %ssupported\n",
>> + feats_ok ? feats_to_string(td->feats_supported) :
>> + feats_to_string(td->feats_required ^
>> + td->feats_supported),
>
> Should this be something like:
> td->feats_required & ~td->feats_supported ?
>
> Otherwise we'll include features that are supported but not required,
> when printing the features that are NOT supported.
>
> Alternatively, we could just print out the required and supported sets
> separately and leave it up to the user to obverse how they are
> different.
>
> (Watch out for calling feats_to_string() twice in the same printf() call
> though.)
>
Ok. Reported information was poor in fact.
>> + !feats_ok ? "NOT " : "");
>> + }
>> +
>> + td->initialized = 1;
>> + return 1;
>> +}
>> +
>> +int test_setup(struct tdescr *td)
>> +{
>> + /* assert core invariants symptom of a rotten testcase */
>> + assert(current);
>> + assert(td);
>> + assert(td->name);
>> + assert(td->run);
>> +
>> + if (!test_init(td))
>> + return 0;
>> +
>> + if (td->setup)
>> + return td->setup(td);
>> + else
>> + return default_setup(td);
>> +}
>> +
>> +int test_run(struct tdescr *td)
>> +{
>> + if (td->sig_trig) {
>> + if (td->trigger)
>> + return td->trigger(td);
>> + else
>> + return default_trigger(td);
>> + } else {
>> + return td->run(td, NULL, NULL);
>> + }
>> +}
>> +
>> +void test_result(struct tdescr *td)
>> +{
>> + if (td->check_result)
>> + td->check_result(td);
>> + default_result(td, 0);
>> +}
>> +
>> +void test_cleanup(struct tdescr *td)
>> +{
>> + if (td->cleanup)
>> + td->cleanup(td);
>> +}
>> diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.h b/tools/testing/selftests/arm64/signal/test_signals_utils.h
>> new file mode 100644
>> index 000000000000..8658d1a7d4b9
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm64/signal/test_signals_utils.h
>> @@ -0,0 +1,13 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/* Copyright (C) 2019 ARM Limited */
>> +
>> +#ifndef __TEST_SIGNALS_UTILS_H__
>> +#define __TEST_SIGNALS_UTILS_H__
>> +
>> +#include "test_signals.h"
>> +
>> +int test_setup(struct tdescr *td);
>> +void test_cleanup(struct tdescr *td);
>> +int test_run(struct tdescr *td);
>> +void test_result(struct tdescr *td);
>> +#endif
>> diff --git a/tools/testing/selftests/arm64/signal/testcases/mangle_pstate_invalid_compat_toggle.c b/tools/testing/selftests/arm64/signal/testcases/mangle_pstate_invalid_compat_toggle.c
>> new file mode 100644
>> index 000000000000..2cb118b0ba05
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm64/signal/testcases/mangle_pstate_invalid_compat_toggle.c
>> @@ -0,0 +1,31 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2019 ARM Limited
>> + *
>> + * Try to mangle the ucontext from inside a signal handler, toggling
>> + * the execution state bit: this attempt must be spotted by Kernel and
>> + * the test case is expected to be terminated via SEGV.
>> + */
>> +
>> +#include "test_signals_utils.h"
>> +#include "testcases.h"
>> +
>> +static int mangle_invalid_pstate_run(struct tdescr *td, siginfo_t *si,
>> + ucontext_t *uc)
>> +{
>> + ASSERT_GOOD_CONTEXT(uc);
>> +
>> + /* This config should trigger a SIGSEGV by Kernel */
>> + uc->uc_mcontext.pstate ^= PSR_MODE32_BIT;
>> +
>> + return 1;
>> +}
>> +
>> +struct tdescr tde = {
>> + .sanity_disabled = true,
>> + .name = "MANGLE_PSTATE_INVALID_STATE_TOGGLE",
>> + .descr = "Mangling uc_mcontext with INVALID STATE_TOGGLE",
>> + .sig_trig = SIGUSR1,
>> + .sig_ok = SIGSEGV,
>> + .run = mangle_invalid_pstate_run,
>> +};
>> diff --git a/tools/testing/selftests/arm64/signal/testcases/testcases.c b/tools/testing/selftests/arm64/signal/testcases/testcases.c
>> new file mode 100644
>> index 000000000000..72e3f482b177
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm64/signal/testcases/testcases.c
>> @@ -0,0 +1,151 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (C) 2019 ARM Limited */
>> +#include "testcases.h"
>> +
>> +struct _aarch64_ctx *get_header(struct _aarch64_ctx *head, uint32_t magic,
>> + size_t resv_sz, size_t *offset)
>> +{
>> + size_t offs = 0;
>> + struct _aarch64_ctx *found = NULL;
>> +
>> + if (!head)
>> + return found;
>> +
>
> I suggest you also check for resv_sz < HDR_SZ, since the while()
> condition assumes that resv_sz - HDR_SZ doesn't underflow.
>
> For now, I think resv_sz is already sizeof(__reserved) so this is never
> true, but I suspect we will want to reuse this code eventually to looko
> at the contents of extra_context. Then, resv_sz would be the
> extra_context size rather than a fixed constant.
>
Ok....in fact I think I removed recently such check...not sure why o_O
I'll fix it.
>> + while (offs <= resv_sz - HDR_SZ &&
>> + head->magic != magic && head->magic) {
>> + offs += head->size;
>> + head = GET_RESV_NEXT_HEAD(head);
>> + }
>> + if (head->magic == magic) {
>> + found = head;
>> + if (offset)
>> + *offset = offs;
>> + }
>
> Although there appears to be some code duplication here, I guess you
> need things this way to do the right thing if called with magic==0.
>
> So I guess this is fine.
>
Yes that was exactly the point, and it seemed to me that removing further
duplication would have made the code more complex and unreadable.
> Ultimately it would be good to have GET_RESV_NEXT_HEAD() work more
> like an iterator, doing integrity bounds/alignment checks and updating
> offs as it goes, but for now I think the code is sufficient. We can
> always beef it up later to catch more kinds of error from the kernel.
>
Yes I remember you told me that on a previous iteration, but for now I left the
GET_RESV_NEXT_HEAD() as it was without embedding the bounds checking logic
because it is indirectly used also by the validation function that I use in
the ASSERT_GOOD/BAD_CONTEXT() macros, so it should be able to handle
artficially badly formed and corrupted frames without bailing out: it just walks
and any kind of logic is handled outside...but maybe I'm overthinking
(certainly I have not explained this reasons anywhere...I'll add a comment)
>> +
>> + return found;
>> +}
>> +
>> +bool validate_extra_context(struct extra_context *extra, char **err)
>> +{
>> + struct _aarch64_ctx *term;
>> +
>> + if (!extra || !err)
>> + return false;
>> +
>> + fprintf(stderr, "Validating EXTRA...\n");
>> + term = GET_RESV_NEXT_HEAD(extra);
>> + if (!term || term->magic || term->size) {
>> + *err = "Missing terminator after EXTRA context";
>> + return false;
>> + }
>> + if (extra->datap & 0x0fUL)
>> + *err = "Extra DATAP misaligned";
>> + else if (extra->size & 0x0fUL)
>> + *err = "Extra SIZE misaligned";
>> + else if (extra->datap != (uint64_t)term + sizeof(*term))
>> + *err = "Extra DATAP misplaced (not contiguos)";
>> + if (*err)
>> + return false;
>> +
>> + return true;
>> +}
>> +
>> +bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
>> +{
>> + bool terminated = false;
>> + size_t offs = 0;
>> + int flags = 0;
>> + struct extra_context *extra = NULL;
>> + struct _aarch64_ctx *head =
>> + (struct _aarch64_ctx *)uc->uc_mcontext.__reserved;
>> +
>> + if (!err)
>> + return false;
>> + /* Walk till the end terminator verifying __reserved contents */
>> + while (head && !terminated && offs < resv_sz) {
>> + if ((uint64_t)head & 0x0fUL) {
>> + *err = "Misaligned HEAD";
>> + return false;
>> + }
>> +
>> + switch (head->magic) {
>> + case 0:
>> + if (head->size)
>> + *err = "Bad size for terminator";
>> + else
>> + terminated = true;
>> + break;
>> + case FPSIMD_MAGIC:
>> + if (flags & FPSIMD_CTX)
>> + *err = "Multiple FPSIMD_MAGIC";
>> + else if (head->size !=
>> + sizeof(struct fpsimd_context))
>> + *err = "Bad size for fpsimd_context";
>> + flags |= FPSIMD_CTX;
>> + break;
>> + case ESR_MAGIC:
>> + if (head->size != sizeof(struct esr_context))
>> + fprintf(stderr,
>> + "Bad size for esr_context is not an error...just ignore.\n");
>> + break;
>
> Although it's not essential, I'd prefer that we enforce the correct
> size here. All records, including esr_context are intended to be
> fixed-size.
>
> In the kernel we check a bit more loosely -- this allows userspace to
> delete a record using head->size += next_head->size. This way no
> memmove() is needed to shuffle subsequent records down. I don't know
> whether any userspace code makes use of this -- prior to SVE there were
> no optional records except for esr_context, and sigreturn ignores that
> in any case so deleting it is pointless.
>
> The kernel should never insert extra padding between records though,
> so I think it makes sense to have strict size checks in this test.
>
Ok, I'll do. I kept it loose as it is in Kernel, because in some past tests
(now removed) I used to play also with esr_context size to build easily an inflated
fake sigframe (but good) and adding some badness on top of it.
> [...]
>
> Cheers
> ---Dave
>
Cheers
Cristian
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v5 0/3] PM / AVS: SVS: Introduce SVS engine
From: Roger Lu @ 2019-09-06 10:15 UTC (permalink / raw)
To: Kevin Hilman
Cc: Mark Rutland, Nicolas Boichat, Angus Lin, devicetree,
Stephen Boyd, linux-pm, linux-kernel, HenryC Chen, yt.lee,
Fan Chen, Rob Herring, linux-mediatek, Matthias Brugger,
Nishanth Menon, linux-arm-kernel
In-Reply-To: <20190906100514.30803-1-roger.lu@mediatek.com>
Excuse me, I forgot to add "changes since" below. Add it back now.
On Fri, 2019-09-06 at 18:05 +0800, Roger Lu wrote:
> 1. SVS driver use OPP adjust event in [1] to update OPP table voltage part.
> 2. SVS dts node refers to CPU opp table [2] and GPU opp table [3].
> 3. SVS dts node refers to thermal efuse [4] and PMIC regulator [5].
>
> [1] https://patchwork.kernel.org/patch/11092245/
> [2] https://patchwork.kernel.org/patch/10934123/
> [3] https://patchwork.kernel.org/patch/11132381/
> [4] https://patchwork.kernel.org/patch/11093655/
> [5] https://patchwork.kernel.org/patch/11110493/
changes since v4:
- Add SVS dts node patch in SVS patch series.
> Roger Lu (3):
> dt-bindings: soc: add mtk svs dt-bindings
> arm64: dts: mt8183: add svs device information
> PM / AVS: SVS: Introduce SVS engine
>
> .../devicetree/bindings/power/mtk-svs.txt | 88 +
> arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +
> arch/arm64/boot/dts/mediatek/mt8183.dtsi | 38 +
> drivers/power/avs/Kconfig | 10 +
> drivers/power/avs/Makefile | 1 +
> drivers/power/avs/mtk_svs.c | 2075 +++++++++++++++++
> include/linux/power/mtk_svs.h | 23 +
> 7 files changed, 2251 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
> create mode 100644 drivers/power/avs/mtk_svs.c
> create mode 100644 include/linux/power/mtk_svs.h
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH 3/3] dt-bindings: ddr: Add bindings for Samsung LPDDR3 memories
From: Lukasz Luba @ 2019-09-06 10:13 UTC (permalink / raw)
To: devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-arm-kernel
Cc: mark.rutland, willy.mh.wolff.ml, b.zolnierkie, krzk, Lukasz Luba,
cw00.choi, kyungmin.park, robh+dt, kgene, s.nawrocki,
myungjoo.ham, m.szyprowski
In-Reply-To: <20190906101344.3535-1-l.luba@partner.samsung.com>
Add description of bindings for Samsung k3qf2f20db LPDDR3 memory.
Minor fixes in the old documentation.
Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
---
.../devicetree/bindings/ddr/lpddr3.txt | 29 +++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/ddr/lpddr3.txt b/Documentation/devicetree/bindings/ddr/lpddr3.txt
index 3b2485b84b3f..de0905239767 100644
--- a/Documentation/devicetree/bindings/ddr/lpddr3.txt
+++ b/Documentation/devicetree/bindings/ddr/lpddr3.txt
@@ -40,10 +40,34 @@ Child nodes:
a given speed-bin. Please see Documentation/devicetree/
bindings/ddr/lpddr3-timings.txt for more information on "lpddr3-timings"
+Samsung K3QF2F20DB LPDDR3 memory
+------------------------------------------------------------
+
+This binding uses the LPDDR3 binding (described above)
+
+Required properties:
+- compatible: Should be:
+ "samsung,K3QF2F20DB"
+ followed by "jedec,lpddr3"
+- density : <u32> representing density in Mb (Mega bits)
+- io-width : <u32> representing bus width. Possible value 32
+- #address-cells: Must be set to 1
+- #size-cells: Must be set to 0
+
+Optional properties:
+
+The optional properties are the same as in the LPDDR3 generic bindings and
+values should be taken from the data-sheet. Detailed bindings are described
+above.
+
+Child nodes:
+
+Detailed bindings are described in LPDDR3 generic bindings described above.
+
Example:
samsung_K3QF2F20DB: lpddr3 {
- compatible = "Samsung,K3QF2F20DB", "jedec,lpddr3";
+ compatible = "samsung,K3QF2F20DB", "jedec,lpddr3";
density = <16384>;
io-width = <32>;
#address-cells = <1>;
@@ -73,7 +97,8 @@ samsung_K3QF2F20DB: lpddr3 {
timings_samsung_K3QF2F20DB_800mhz: lpddr3-timings@800000000 {
compatible = "jedec,lpddr3-timings";
- reg = <800000000>; /* workaround: it shows max-freq */
+ /* workaround: 'reg' shows max-freq */
+ reg = <800000000>;
min-freq = <100000000>;
tRFC = <65000>;
tRRD = <6000>;
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH 2/3] ARM: dts: exynos: fix too long line in memory device
From: Lukasz Luba @ 2019-09-06 10:13 UTC (permalink / raw)
To: devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-arm-kernel
Cc: mark.rutland, willy.mh.wolff.ml, b.zolnierkie, krzk, Lukasz Luba,
cw00.choi, kyungmin.park, robh+dt, kgene, s.nawrocki,
myungjoo.ham, m.szyprowski
In-Reply-To: <20190906101344.3535-1-l.luba@partner.samsung.com>
Small fix moving the comment to line above making sure the lines do not
exceed 80 characters.
Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
---
arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/exynos5422-odroid-core.dtsi b/arch/arm/boot/dts/exynos5422-odroid-core.dtsi
index fe885ca969af..059fa32d1a8f 100644
--- a/arch/arm/boot/dts/exynos5422-odroid-core.dtsi
+++ b/arch/arm/boot/dts/exynos5422-odroid-core.dtsi
@@ -103,7 +103,8 @@
timings_samsung_K3QF2F20DB_800mhz: lpddr3-timings@800000000 {
compatible = "jedec,lpddr3-timings";
- reg = <800000000>; /* workaround: it shows max-freq */
+ /* workaround: 'reg' shows max-freq */
+ reg = <800000000>;
min-freq = <100000000>;
tRFC = <65000>;
tRRD = <6000>;
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH 1/3] memory: Exynos5422: minor fixes in DMC
From: Lukasz Luba @ 2019-09-06 10:13 UTC (permalink / raw)
To: devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-arm-kernel
Cc: mark.rutland, willy.mh.wolff.ml, b.zolnierkie, krzk, Lukasz Luba,
cw00.choi, kyungmin.park, robh+dt, kgene, s.nawrocki,
myungjoo.ham, m.szyprowski
In-Reply-To: <20190906101344.3535-1-l.luba@partner.samsung.com>
Small fixes captured by static analyzes.
Signed-off-by: Lukasz Luba <l.luba@partner.samsung.com>
---
drivers/memory/samsung/exynos5422-dmc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/memory/samsung/exynos5422-dmc.c b/drivers/memory/samsung/exynos5422-dmc.c
index 8c2ec29a7d57..a809fa997c03 100644
--- a/drivers/memory/samsung/exynos5422-dmc.c
+++ b/drivers/memory/samsung/exynos5422-dmc.c
@@ -269,7 +269,7 @@ static int exynos5_init_freq_table(struct exynos5_dmc *dmc,
return 0;
err_free_tables:
- kfree(dmc->opp);
+ devm_kfree(dmc->dev, dmc->opp);
err_opp:
dev_pm_opp_of_remove_table(dmc->dev);
@@ -732,7 +732,7 @@ static struct devfreq_dev_profile exynos5_dmc_df_profile = {
* statistics engine which supports only registered values. Thus, some alignment
* must be made.
*/
-unsigned long
+static unsigned long
exynos5_dmc_align_init_freq(struct exynos5_dmc *dmc,
unsigned long bootloader_init_freq)
{
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH 0/3] Exynos5 DMC minor fixes
From: Lukasz Luba @ 2019-09-06 10:13 UTC (permalink / raw)
To: devicetree, linux-kernel, linux-pm, linux-samsung-soc,
linux-arm-kernel
Cc: mark.rutland, willy.mh.wolff.ml, b.zolnierkie, krzk, Lukasz Luba,
cw00.choi, kyungmin.park, robh+dt, kgene, s.nawrocki,
myungjoo.ham, m.szyprowski
In-Reply-To: <CGME20190906101404eucas1p1d11e26276bfdacb7748f1391e29f45d0@eucas1p1.samsung.com>
Hi all,
This is a follow up patch set for the Exynos5 Dynamic Memory Controller
driver v13 [1]. The patches are based on Krzysztof's 'for-next' branch [2].
There are a few minor fixes captured during static analysis and a new
binding for 'samsung,K3QF2F20DB' LPDDR3 memory.
Regards,
Lukasz Luba
[1] https://lkml.org/lkml/2019/8/21/283
[2] https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git/log/?h=for-next
Lukasz Luba (3):
memory: Exynos5422: minor fixes in DMC
ARM: dts: exynos: fix too long line in memory device
dt-bindings: ddr: Add bindings for Samsung LPDDR3 memories
.../devicetree/bindings/ddr/lpddr3.txt | 29 +++++++++++++++++--
arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 3 +-
drivers/memory/samsung/exynos5422-dmc.c | 4 +--
3 files changed, 31 insertions(+), 5 deletions(-)
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [RFC PATCH V3 3/3] platform: mtk-isp: Add Mediatek FD driver
From: Jerry-ch Chen @ 2019-09-06 10:11 UTC (permalink / raw)
To: hans.verkuil, laurent.pinchart+renesas, tfiga, matthias.bgg,
mchehab, lkml
Cc: devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream, po-yang.huang,
Jerry-ch Chen, jungo.lin, sj.huang, yuzhao, linux-mediatek,
zwisler, ck.hu, christie.yu, frederic.chen, linux-arm-kernel,
linux-media
In-Reply-To: <20190906101125.3784-1-Jerry-Ch.chen@mediatek.com>
From: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
This patch adds the driver of Face Detection (FD) unit in
Mediatek camera system, providing face detection function.
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: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
---
drivers/media/platform/Kconfig | 2 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/mtk-isp/fd/Kconfig | 19 +
drivers/media/platform/mtk-isp/fd/Makefile | 5 +
drivers/media/platform/mtk-isp/fd/mtk_fd.h | 148 ++
drivers/media/platform/mtk-isp/fd/mtk_fd_40.c | 1219 +++++++++++++++++
include/uapi/linux/mtk-fd-v4l2-controls.h | 69 +
include/uapi/linux/v4l2-controls.h | 4 +
8 files changed, 1468 insertions(+)
create mode 100644 drivers/media/platform/mtk-isp/fd/Kconfig
create mode 100644 drivers/media/platform/mtk-isp/fd/Makefile
create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd.h
create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd_40.c
create mode 100644 include/uapi/linux/mtk-fd-v4l2-controls.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index a505e9f5a1e2..ae99258e26a5 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -32,6 +32,8 @@ source "drivers/media/platform/davinci/Kconfig"
source "drivers/media/platform/omap/Kconfig"
+source "drivers/media/platform/mtk-isp/fd/Kconfig"
+
config VIDEO_ASPEED
tristate "Aspeed AST2400 and AST2500 Video Engine driver"
depends on VIDEO_V4L2
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index e6deb2597738..8b817cc80f43 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -94,6 +94,8 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
+obj-$(CONFIG_VIDEO_MEDIATEK_FD) += mtk-isp/fd/
+
obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
diff --git a/drivers/media/platform/mtk-isp/fd/Kconfig b/drivers/media/platform/mtk-isp/fd/Kconfig
new file mode 100644
index 000000000000..126aad0eb9d8
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/Kconfig
@@ -0,0 +1,19 @@
+config VIDEO_MEDIATEK_FD
+ bool "Mediatek face detection processing function"
+ depends on VIDEO_V4L2
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select VIDEOBUF2_CORE
+ select VIDEOBUF2_V4L2
+ select VIDEOBUF2_MEMOPS
+ select MEDIA_CONTROLLER
+ select MTK_SCP
+
+ default n
+ help
+ Support the Face Detection (FD) feature in the Mediatek
+ mt8183 Soc.
+
+ FD driver is a V4L2 memory-to-memory device driver which
+ provides hardware accelerated face detection function,
+ it can detect different sizes of faces in a raw image.
diff --git a/drivers/media/platform/mtk-isp/fd/Makefile b/drivers/media/platform/mtk-isp/fd/Makefile
new file mode 100644
index 000000000000..9b1c501d73e1
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+mtk-fd-objs += mtk_fd_40.o
+
+obj-$(CONFIG_VIDEO_MEDIATEK_FD) += mtk-fd.o
\ No newline at end of file
diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd.h b/drivers/media/platform/mtk-isp/fd/mtk_fd.h
new file mode 100644
index 000000000000..bdba73fc45ab
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/mtk_fd.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+//
+// Copyright (c) 2018 MediaTek Inc.
+
+#ifndef __MTK_FD_HW_H__
+#define __MTK_FD_HW_H__
+
+#include <linux/completion.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+#include <linux/mtk-fd-v4l2-controls.h>
+
+#define MTK_FD_OUTPUT_MIN_WIDTH 26U
+#define MTK_FD_OUTPUT_MIN_HEIGHT 26U
+#define MTK_FD_OUTPUT_MAX_WIDTH 640U
+#define MTK_FD_OUTPUT_MAX_HEIGHT 480U
+#define MTK_FD_IPI_SEND_TIMEOUT 0U
+
+#define MTK_FD_HW_FMT_VYUY 2
+#define MTK_FD_HW_FMT_UYVY 3
+#define MTK_FD_HW_FMT_YVYU 4
+#define MTK_FD_HW_FMT_YUYV 5
+#define MTK_FD_HW_FMT_YVU_2P 6
+#define MTK_FD_HW_FMT_YUV_2P 7
+#define MTK_FD_HW_FMT_UNKNOWN 8
+
+#define MTK_FD_IPI_CMD_INIT 0
+#define MTK_FD_IPI_CMD_INIT_ACK 1
+#define MTK_FD_IPI_CMD_ENQUEUE 2
+#define MTK_FD_IPI_CMD_ENQ_ACK 3
+#define MTK_FD_IPI_CMD_RESET 4
+#define MTK_FD_IPI_CMD_RESET_ACK 5
+
+#define MTK_FD_REG_OFFSET_HW_ENABLE 0x4
+#define MTK_FD_REG_OFFSET_INT_EN 0x15c
+#define MTK_FD_REG_OFFSET_INT_VAL 0x168
+#define MTK_FD_REG_OFFSET_RESULT 0x178
+
+#define MTK_FD_SET_HW_ENABLE 0x111
+#define MTK_FD_RS_BUF_SIZE 2289664
+#define MTK_FD_HW_WORK_BUF_SIZE 0x100000
+#define MTK_FD_MAX_SPEEDUP 7
+#define MTK_FD_MAX_RESULT_NUM 1026
+
+/* Max scale size counts */
+#define MTK_FD_SCALE_ARR_NUM 15
+
+#define MTK_FD_HW_TIMEOUT 1000
+
+enum face_angle {
+ MTK_FD_FACE_FRONT,
+ MTK_FD_FACE_RIGHT_50,
+ MTK_FD_FACE_LEFT_50,
+ MTK_FD_FACE_RIGHT_90,
+ MTK_FD_FACE_LEFT_90,
+ MTK_FD_FACE_ANGLE_NUM,
+};
+
+struct fd_buffer {
+ __u32 scp_addr; /* used by SCP */
+ __u32 dma_addr; /* used by DMA HW */
+} __packed;
+
+struct fd_face_result {
+ char data[16];
+};
+
+struct fd_user_output {
+ struct fd_face_result results[MTK_FD_MAX_RESULT_NUM];
+ __u16 number;
+};
+
+struct user_param {
+ u8 fd_speedup;
+ u8 fd_extra_model;
+ u8 scale_img_num;
+ u8 src_img_fmt;
+ __u16 scale_img_width[MTK_FD_SCALE_ARR_NUM];
+ __u16 scale_img_height[MTK_FD_SCALE_ARR_NUM];
+ __u16 face_directions[MTK_FD_FACE_ANGLE_NUM];
+} __packed;
+
+struct fd_init_param {
+ struct fd_buffer fd_manager;
+ __u32 rs_dma_addr;
+} __packed;
+
+struct fd_enq_param {
+ __u64 output_vaddr;
+ struct fd_buffer src_img[2];
+ struct fd_buffer user_result;
+ struct user_param user_param;
+} __packed;
+
+struct fd_ack_param {
+ __u32 ret_code;
+ __u32 ret_msg;
+} __packed;
+
+struct ipi_message {
+ u8 cmd_id;
+ union {
+ struct fd_init_param fd_init_param;
+ struct fd_enq_param fd_enq_param;
+ struct fd_ack_param fd_ack_param;
+ };
+} __packed;
+
+struct mtk_fd_dev {
+ struct device *dev;
+ struct mtk_fd_ctx *ctx;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct media_device mdev;
+ struct video_device vfd;
+ struct platform_device *scp_pdev;
+ struct clk *fd_clk;
+ struct rproc *rproc_handle;
+
+ /* Lock for V4L2 operations */
+ struct mutex vfd_lock;
+
+ struct fd_user_output *output;
+ struct fd_buffer scp_mem;
+ void __iomem *fd_base;
+ void *rs_dma_buf;
+ dma_addr_t rs_dma_handle;
+ void *scp_mem_virt_addr;
+
+ u32 fd_stream_count;
+ struct completion fd_irq_done;
+};
+
+struct mtk_fd_ctx {
+ struct mtk_fd_dev *fd_dev;
+ struct device *dev;
+ struct v4l2_fh fh;
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_pix_format_mplane src_fmt;
+ struct v4l2_meta_format dst_fmt;
+ struct user_param user_param;
+};
+
+#endif/*__MTK_FD_HW_H__*/
diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd_40.c b/drivers/media/platform/mtk-isp/fd/mtk_fd_40.c
new file mode 100644
index 000000000000..901fa36a2305
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/mtk_fd_40.c
@@ -0,0 +1,1219 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2018 MediaTek Inc.
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/remoteproc/mtk_scp.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/remoteproc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+
+#include "mtk_fd.h"
+
+static const struct v4l2_pix_format_mplane mtk_fd_img_fmts[] = {
+ {
+ .pixelformat = V4L2_PIX_FMT_VYUY,
+ .num_planes = 1,
+ },
+ {
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .num_planes = 1,
+ },
+ {
+ .pixelformat = V4L2_PIX_FMT_YVYU,
+ .num_planes = 1,
+ },
+ {
+ .pixelformat = V4L2_PIX_FMT_UYVY,
+ .num_planes = 1,
+ },
+ {
+ .pixelformat = V4L2_PIX_FMT_NV16M,
+ .num_planes = 2,
+ },
+ {
+ .pixelformat = V4L2_PIX_FMT_NV61M,
+ .num_planes = 2,
+ },
+};
+
+#define NUM_FORMATS ARRAY_SIZE(mtk_fd_img_fmts)
+
+static inline struct mtk_fd_ctx *fh_to_ctx(struct v4l2_fh *fh)
+{
+ return container_of(fh, struct mtk_fd_ctx, fh);
+}
+
+/* */
+static dma_addr_t mtk_fd_hw_alloc_rs_dma_addr(struct mtk_fd_dev *fd)
+{
+ struct device *dev = fd->dev;
+ void *va;
+ dma_addr_t dma_handle;
+
+ va = dma_alloc_coherent(dev, MTK_FD_RS_BUF_SIZE, &dma_handle,
+ GFP_KERNEL);
+ if (!va) {
+ dev_err(dev, "dma_alloc null va\n");
+ return -ENOMEM;
+ }
+ memset(va, 0, MTK_FD_RS_BUF_SIZE);
+ fd->rs_dma_buf = va;
+ fd->rs_dma_handle = dma_handle;
+
+ return dma_handle;
+}
+
+static int mtk_fd_send_ipi_init(struct mtk_fd_dev *fd)
+{
+ struct ipi_message fd_init_msg;
+ dma_addr_t rs_dma_addr;
+
+ fd_init_msg.cmd_id = MTK_FD_IPI_CMD_INIT;
+
+ fd_init_msg.fd_init_param.fd_manager.scp_addr = fd->scp_mem.scp_addr;
+ fd_init_msg.fd_init_param.fd_manager.dma_addr = fd->scp_mem.dma_addr;
+
+ rs_dma_addr = mtk_fd_hw_alloc_rs_dma_addr(fd);
+ if (!rs_dma_addr)
+ return -ENOMEM;
+ memset(fd->rs_dma_buf, 0, MTK_FD_RS_BUF_SIZE);
+
+ fd_init_msg.fd_init_param.rs_dma_addr = fd->rs_dma_handle;
+
+ return scp_ipi_send(fd->scp_pdev, SCP_IPI_FD_CMD, &fd_init_msg,
+ sizeof(fd_init_msg), MTK_FD_IPI_SEND_TIMEOUT);
+}
+
+static void mtk_fd_free_dma_handle(struct mtk_fd_dev *fd)
+{
+ if (!IS_ERR(fd->rs_dma_buf))
+ dma_free_coherent(fd->dev, MTK_FD_RS_BUF_SIZE,
+ fd->rs_dma_buf,
+ fd->rs_dma_handle);
+}
+
+static int mtk_fd_hw_enable(struct mtk_fd_dev *fd)
+{
+ int ret;
+
+ ret = mtk_fd_send_ipi_init(fd);
+ if (ret) {
+ dev_err(fd->dev, "Failed to send fd ipi init\n");
+ if (!IS_ERR(fd->rs_dma_buf))
+ mtk_fd_free_dma_handle(fd);
+ return ret;
+ }
+ return 0;
+}
+
+static void mtk_fd_hw_job_finish(struct mtk_fd_dev *fd,
+ enum vb2_buffer_state vb_state)
+{
+ struct mtk_fd_ctx *ctx;
+ struct vb2_v4l2_buffer *src_vbuf = NULL, *dst_vbuf = NULL;
+
+ ctx = v4l2_m2m_get_curr_priv(fd->m2m_dev);
+ src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+ v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf,
+ V4L2_BUF_FLAG_TSTAMP_SRC_MASK);
+ v4l2_m2m_buf_done(src_vbuf, vb_state);
+ v4l2_m2m_buf_done(dst_vbuf, vb_state);
+ v4l2_m2m_job_finish(fd->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static void mtk_fd_ipi_handler(void *data, unsigned int len, void *priv)
+{
+ struct mtk_fd_dev *fd = (struct mtk_fd_dev *)priv;
+ struct device *dev = fd->dev;
+ struct ipi_message *fd_ack_msg = (struct ipi_message *)data;
+ struct fd_ack_param *fd_ack = &fd_ack_msg->fd_ack_param;
+
+ dev_dbg(fd->dev, "fd_ipi_ack_id: %d\n", fd_ack_msg->cmd_id);
+ switch (fd_ack_msg->cmd_id) {
+ case MTK_FD_IPI_CMD_INIT_ACK:
+ return;
+ case MTK_FD_IPI_CMD_ENQ_ACK:
+ if (fd_ack->ret_code) {
+ dev_err(dev, "ipi ret: %d, message: %d\n",
+ fd_ack->ret_code, fd_ack->ret_msg);
+ pm_runtime_put((fd->dev));
+ mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_ERROR);
+ }
+ return;
+ case MTK_FD_IPI_CMD_RESET_ACK:
+ return;
+ }
+}
+
+static int mtk_fd_hw_connect(struct mtk_fd_dev *fd)
+{
+ int ret;
+
+ ret = rproc_boot(fd->rproc_handle);
+
+ if (ret < 0) {
+ /**
+ * Return 0 if downloading firmware successfully,
+ * otherwise it is failed
+ */
+ dev_err(fd->dev, "Failed to boot rproc\n");
+ return ret;
+ }
+
+ ret = scp_ipi_register(fd->scp_pdev, SCP_IPI_FD_CMD,
+ mtk_fd_ipi_handler, fd);
+ if (ret) {
+ dev_err(fd->dev, "Failed to register IPI cmd handler\n");
+ goto err_rproc_shutdown;
+ }
+
+ fd->fd_stream_count++;
+ if (fd->fd_stream_count == 1) {
+ if (mtk_fd_hw_enable(fd)) {
+ ret = -EINVAL;
+ goto err_rproc_shutdown;
+ }
+ }
+ return 0;
+
+err_rproc_shutdown:
+ rproc_shutdown(fd->rproc_handle);
+ return ret;
+}
+
+static void mtk_fd_hw_disconnect(struct mtk_fd_dev *fd)
+{
+ fd->fd_stream_count--;
+
+ if (fd->fd_stream_count == 0) {
+ if (!IS_ERR(fd->rs_dma_buf))
+ mtk_fd_free_dma_handle(fd);
+
+ rproc_shutdown(fd->rproc_handle);
+ }
+}
+
+static int mtk_fd_hw_job_exec(struct mtk_fd_dev *fd,
+ struct fd_enq_param *fd_param)
+{
+ struct ipi_message fd_ipi_msg;
+ int ret;
+
+ pm_runtime_get_sync((fd->dev));
+
+ reinit_completion(&fd->fd_irq_done);
+ fd_ipi_msg.cmd_id = MTK_FD_IPI_CMD_ENQUEUE;
+ memcpy(&fd_ipi_msg.fd_enq_param, fd_param, sizeof(struct fd_enq_param));
+ ret = scp_ipi_send(fd->scp_pdev, SCP_IPI_FD_CMD, &fd_ipi_msg,
+ sizeof(fd_ipi_msg), MTK_FD_IPI_SEND_TIMEOUT);
+ if (ret) {
+ pm_runtime_put((fd->dev));
+ mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_ERROR);
+ return ret;
+ }
+ return 0;
+}
+
+static int mtk_fd_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_fd_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+ struct device *dev = ctx->dev;
+ struct v4l2_pix_format_mplane *pixfmt;
+
+ switch (vq->type) {
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ if (vb2_plane_size(vb, 0) < ctx->dst_fmt.buffersize) {
+ dev_dbg(dev, "meta size %d is too small\n");
+ return -EINVAL;
+ }
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ pixfmt = &ctx->src_fmt;
+
+ if (vbuf->field == V4L2_FIELD_ANY)
+ vbuf->field = V4L2_FIELD_NONE;
+
+ if (vb->num_planes > 2 || vbuf->field != V4L2_FIELD_NONE) {
+ dev_dbg(dev, "plane or field %d not supported\n",
+ vb->num_planes, vbuf->field);
+ return -EINVAL;
+ }
+ if (vb2_plane_size(vb, 0) < pixfmt->plane_fmt[0].sizeimage) {
+ dev_dbg(dev, "plane %d is too small\n");
+ return -EINVAL;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static void mtk_fd_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static int mtk_fd_vb2_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+ unsigned int size[2];
+ unsigned int plane;
+
+ switch (vq->type) {
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ size[0] = ctx->dst_fmt.buffersize;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ size[0] = ctx->src_fmt.plane_fmt[0].sizeimage;
+ if (*num_planes == 2)
+ size[1] = ctx->src_fmt.plane_fmt[1].sizeimage;
+ break;
+ }
+
+ if (*num_planes > 2)
+ return -EINVAL;
+ if (*num_planes == 0) {
+ if (vq->type == V4L2_BUF_TYPE_META_CAPTURE) {
+ sizes[0] = ctx->dst_fmt.buffersize;
+ *num_planes = 1;
+ return 0;
+ }
+
+ *num_planes = ctx->src_fmt.num_planes;
+ for (plane = 0; plane < *num_planes; plane++)
+ sizes[plane] = ctx->src_fmt.plane_fmt[plane].sizeimage;
+ return 0;
+ }
+
+ for (plane = 0; plane < *num_planes; plane++) {
+ if (sizes[plane] < size[plane])
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int mtk_fd_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return mtk_fd_hw_connect(ctx->fd_dev);
+ else
+ return 0;
+}
+
+static int mtk_fd_job_abort(struct mtk_fd_dev *fd)
+{
+ u32 ret;
+
+ ret = wait_for_completion_timeout(&fd->fd_irq_done,
+ msecs_to_jiffies(MTK_FD_HW_TIMEOUT));
+ /* Reset FD HW */
+ if (!ret) {
+ struct ipi_message fd_ipi_msg;
+
+ fd_ipi_msg.cmd_id = MTK_FD_IPI_CMD_RESET;
+ if (scp_ipi_send(fd->scp_pdev, SCP_IPI_FD_CMD, &fd_ipi_msg,
+ sizeof(fd_ipi_msg), MTK_FD_IPI_SEND_TIMEOUT))
+ dev_err(fd->dev, "FD Reset HW error\n");
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+static void mtk_fd_vb2_stop_streaming(struct vb2_queue *vq)
+{
+ struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+ struct mtk_fd_dev *fd = ctx->fd_dev;
+ struct vb2_v4l2_buffer *vb;
+ struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
+ struct v4l2_m2m_queue_ctx *queue_ctx;
+
+ mtk_fd_job_abort(fd);
+ queue_ctx = V4L2_TYPE_IS_OUTPUT(vq->type) ?
+ &m2m_ctx->out_q_ctx :
+ &m2m_ctx->cap_q_ctx;
+ while ((vb = v4l2_m2m_buf_remove(queue_ctx)))
+ v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR);
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ mtk_fd_hw_disconnect(fd);
+}
+
+static void mtk_fd_vb2_request_complete(struct vb2_buffer *vb)
+{
+ struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+ v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl);
+}
+
+static int mtk_fd_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct mtk_fd_dev *fd = video_drvdata(file);
+ struct device *dev = fd->dev;
+
+ strscpy(cap->driver, dev_driver_string(dev), sizeof(cap->driver));
+ strscpy(cap->card, dev_driver_string(dev), sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ dev_name(fd->dev));
+
+ return 0;
+}
+
+static int mtk_fd_enum_fmt_out_mp(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index >= NUM_FORMATS)
+ return -EINVAL;
+
+ f->pixelformat = mtk_fd_img_fmts[f->index].pixelformat;
+ return 0;
+}
+
+static void mtk_fd_fill_pixfmt_mp(struct v4l2_pix_format_mplane *dfmt,
+ const struct v4l2_pix_format_mplane *sfmt)
+{
+ dfmt->field = V4L2_FIELD_NONE;
+ dfmt->colorspace = V4L2_COLORSPACE_BT2020;
+ dfmt->num_planes = sfmt->num_planes;
+ dfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ dfmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+ dfmt->xfer_func =
+ V4L2_MAP_XFER_FUNC_DEFAULT(dfmt->colorspace);
+
+ /* Keep user setting as possible */
+ dfmt->width = clamp(dfmt->width,
+ MTK_FD_OUTPUT_MIN_WIDTH,
+ MTK_FD_OUTPUT_MAX_WIDTH);
+ dfmt->height = clamp(dfmt->height,
+ MTK_FD_OUTPUT_MIN_HEIGHT,
+ MTK_FD_OUTPUT_MAX_HEIGHT);
+
+ if (sfmt->num_planes == 2) {
+ /* NV16M and NV61M has 1 byte per pixel */
+ dfmt->plane_fmt[0].bytesperline = dfmt->width;
+ dfmt->plane_fmt[1].bytesperline = dfmt->width;
+ } else {
+ /* 2 bytes per pixel */
+ dfmt->plane_fmt[0].bytesperline = dfmt->width * 2;
+ }
+
+ dfmt->plane_fmt[0].sizeimage =
+ dfmt->height * dfmt->plane_fmt[0].bytesperline;
+}
+
+static const struct v4l2_pix_format_mplane *mtk_fd_find_fmt(u32 format)
+{
+ unsigned int i;
+ const struct v4l2_pix_format_mplane *dev_fmt;
+
+ for (i = 0; i < NUM_FORMATS; i++) {
+ dev_fmt = &mtk_fd_img_fmts[i];
+ if (dev_fmt->pixelformat == format)
+ return dev_fmt;
+ }
+
+ return NULL;
+}
+
+static int mtk_fd_try_fmt_out_mp(struct file *file,
+ void *fh,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ const struct v4l2_pix_format_mplane *fmt;
+
+ fmt = mtk_fd_find_fmt(pix_mp->pixelformat);
+ if (!fmt)
+ fmt = &mtk_fd_img_fmts[0]; /* Get default img fmt */
+
+ mtk_fd_fill_pixfmt_mp(pix_mp, fmt);
+ return 0;
+}
+
+static int mtk_fd_g_fmt_out_mp(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_fd_ctx *ctx = fh_to_ctx(fh);
+
+ f->fmt.pix_mp = ctx->src_fmt;
+
+ return 0;
+}
+
+static int mtk_fd_s_fmt_out_mp(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_fd_ctx *ctx = fh_to_ctx(fh);
+ struct vb2_queue *vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+
+ /* Change not allowed if queue is streaming. */
+ if (vb2_is_streaming(vq)) {
+ dev_err(ctx->dev, "Failed to set format, vb2 is busy\n");
+ return -EBUSY;
+ }
+
+ mtk_fd_try_fmt_out_mp(file, fh, f);
+ ctx->src_fmt = f->fmt.pix_mp;
+
+ return 0;
+}
+
+static int mtk_fd_enum_fmt_meta_cap(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index)
+ return -EINVAL;
+
+ strscpy(f->description, "Face detection result",
+ sizeof(f->description));
+ f->pixelformat = V4L2_META_FMT_MTFD_RESULT;
+ f->flags = 0;
+
+ return 0;
+}
+
+static int mtk_fd_g_fmt_meta_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ f->fmt.meta.dataformat = V4L2_META_FMT_MTFD_RESULT;
+ f->fmt.meta.buffersize = sizeof(struct fd_user_output);
+
+ return 0;
+}
+
+static const struct vb2_ops mtk_fd_vb2_ops = {
+ .queue_setup = mtk_fd_vb2_queue_setup,
+ .buf_out_validate = mtk_fd_vb2_buf_out_validate,
+ .buf_prepare = mtk_fd_vb2_buf_prepare,
+ .buf_queue = mtk_fd_vb2_buf_queue,
+ .start_streaming = mtk_fd_vb2_start_streaming,
+ .stop_streaming = mtk_fd_vb2_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_request_complete = mtk_fd_vb2_request_complete,
+};
+
+static const struct v4l2_ioctl_ops mtk_fd_v4l2_video_out_ioctl_ops = {
+ .vidioc_querycap = mtk_fd_querycap,
+ .vidioc_enum_fmt_vid_out = mtk_fd_enum_fmt_out_mp,
+ .vidioc_g_fmt_vid_out_mplane = mtk_fd_g_fmt_out_mp,
+ .vidioc_s_fmt_vid_out_mplane = mtk_fd_s_fmt_out_mp,
+ .vidioc_try_fmt_vid_out_mplane = mtk_fd_try_fmt_out_mp,
+ .vidioc_enum_fmt_meta_cap = mtk_fd_enum_fmt_meta_cap,
+ .vidioc_g_fmt_meta_cap = mtk_fd_g_fmt_meta_cap,
+ .vidioc_s_fmt_meta_cap = mtk_fd_g_fmt_meta_cap,
+ .vidioc_try_fmt_meta_cap = mtk_fd_g_fmt_meta_cap,
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int
+mtk_fd_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct mtk_fd_ctx *ctx = priv;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ src_vq->supports_requests = true;
+ src_vq->drv_priv = ctx;
+ src_vq->ops = &mtk_fd_vb2_ops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &ctx->fd_dev->vfd_lock;
+ src_vq->dev = ctx->fd_dev->v4l2_dev.dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_META_CAPTURE;
+ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ dst_vq->drv_priv = ctx;
+ dst_vq->ops = &mtk_fd_vb2_ops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &ctx->fd_dev->vfd_lock;
+ dst_vq->dev = ctx->fd_dev->v4l2_dev.dev;
+
+ return vb2_queue_init(dst_vq);
+}
+
+struct v4l2_ctrl_config mtk_fd_controls[] = {
+ {
+ .id = V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH,
+ .name = "FD scale image widths",
+ .type = V4L2_CTRL_TYPE_U16,
+ .min = MTK_FD_OUTPUT_MIN_WIDTH,
+ .max = MTK_FD_OUTPUT_MAX_WIDTH,
+ .step = 1,
+ .def = MTK_FD_OUTPUT_MAX_WIDTH,
+ .dims = { MTK_FD_SCALE_ARR_NUM },
+ },
+ {
+ .id = V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT,
+ .name = "FD scale image heights",
+ .type = V4L2_CTRL_TYPE_U16,
+ .min = MTK_FD_OUTPUT_MIN_HEIGHT,
+ .max = MTK_FD_OUTPUT_MAX_HEIGHT,
+ .step = 1,
+ .def = MTK_FD_OUTPUT_MAX_HEIGHT,
+ .dims = { MTK_FD_SCALE_ARR_NUM },
+ },
+ {
+ .id = V4L2_CID_MTK_FD_SCALE_IMG_NUM,
+ .name = "FD scale size counts",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = MTK_FD_SCALE_ARR_NUM,
+ .step = 1,
+ .def = 0,
+ },
+ {
+ .id = V4L2_CID_MTK_FD_DETECT_POSE,
+ .name = "FD detect face angle and directions",
+ .type = V4L2_CTRL_TYPE_U16,
+ .min = 0,
+ .max = 0xffff,
+ .step = 1,
+ .def = 0x3ff,
+ .dims = { MTK_FD_FACE_ANGLE_NUM},
+ },
+ {
+ .id = V4L2_CID_MTK_FD_DETECT_SPEED,
+ .name = "FD detection speedup",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = MTK_FD_MAX_SPEEDUP,
+ .step = 1,
+ .def = 0,
+ },
+ {
+ .id = V4L2_CID_MTK_FD_DETECTION_MODEL,
+ .name = "FD use extra model",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 0,
+ },
+};
+
+static int mtk_fd_ctrls_setup(struct mtk_fd_ctx *ctx)
+{
+ struct v4l2_ctrl_handler *hdl = &ctx->hdl;
+ struct v4l2_ctrl *ctl;
+ int i;
+
+ v4l2_ctrl_handler_init(hdl, V4L2_CID_MTK_FD_MAX);
+ if (hdl->error)
+ return hdl->error;
+
+ for (i = 0; i < ARRAY_SIZE(mtk_fd_controls); i++) {
+ ctl = v4l2_ctrl_new_custom(hdl, &mtk_fd_controls[i], ctx);
+ if (hdl->error) {
+ v4l2_ctrl_handler_free(hdl);
+ dev_err(ctx->dev, "Failed to register controls:%d", i);
+ return hdl->error;
+ }
+ }
+
+ ctx->fh.ctrl_handler = &ctx->hdl;
+ v4l2_ctrl_handler_setup(hdl);
+
+ return 0;
+}
+
+static unsigned int get_fd_img_fmt(unsigned int fourcc)
+{
+ switch (fourcc) {
+ case V4L2_PIX_FMT_VYUY:
+ return MTK_FD_HW_FMT_VYUY;
+ case V4L2_PIX_FMT_YUYV:
+ return MTK_FD_HW_FMT_YUYV;
+ case V4L2_PIX_FMT_YVYU:
+ return MTK_FD_HW_FMT_YVYU;
+ case V4L2_PIX_FMT_UYVY:
+ return MTK_FD_HW_FMT_UYVY;
+ case V4L2_PIX_FMT_NV16M:
+ return MTK_FD_HW_FMT_YUV_2P;
+ case V4L2_PIX_FMT_NV61M:
+ return MTK_FD_HW_FMT_YVU_2P;
+ default:
+ return MTK_FD_HW_FMT_UNKNOWN;
+ }
+}
+
+static void init_ctx_fmt(struct mtk_fd_ctx *ctx)
+{
+ struct v4l2_pix_format_mplane *src_fmt = &ctx->src_fmt;
+ struct v4l2_meta_format *dst_fmt = &ctx->dst_fmt;
+
+ /* Initialize M2M source fmt */
+ src_fmt->width = MTK_FD_OUTPUT_MAX_WIDTH;
+ src_fmt->height = MTK_FD_OUTPUT_MAX_HEIGHT;
+ mtk_fd_fill_pixfmt_mp(src_fmt, &mtk_fd_img_fmts[0]);
+
+ /* Initialize M2M destination fmt */
+ dst_fmt->buffersize = sizeof(struct fd_user_output);
+ dst_fmt->dataformat = V4L2_META_FMT_MTFD_RESULT;
+}
+
+/*
+ * V4L2 file operations.
+ */
+static int mtk_vfd_open(struct file *filp)
+{
+ struct mtk_fd_dev *fd = video_drvdata(filp);
+ struct video_device *vdev = video_devdata(filp);
+ struct mtk_fd_ctx *ctx;
+ int ret;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->fd_dev = fd;
+ ctx->dev = fd->dev;
+ fd->ctx = ctx;
+
+ v4l2_fh_init(&ctx->fh, vdev);
+ filp->private_data = &ctx->fh;
+
+ init_ctx_fmt(ctx);
+
+ ret = mtk_fd_ctrls_setup(ctx);
+ if (ret) {
+ dev_err(ctx->dev, "Failed to set up controls:%d\n", ret);
+ goto err_fh_exit;
+ }
+
+ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fd->m2m_dev, ctx,
+ &mtk_fd_queue_init);
+ if (IS_ERR(ctx->fh.m2m_ctx)) {
+ ret = PTR_ERR(ctx->fh.m2m_ctx);
+ goto err_free_ctrl_handler;
+ }
+
+ v4l2_fh_add(&ctx->fh);
+
+ return 0;
+
+err_free_ctrl_handler:
+ v4l2_ctrl_handler_free(&ctx->hdl);
+err_fh_exit:
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+
+ return ret;
+}
+
+static int mtk_vfd_release(struct file *filp)
+{
+ struct mtk_fd_ctx *ctx = container_of(filp->private_data,
+ struct mtk_fd_ctx, fh);
+
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+
+ v4l2_ctrl_handler_free(&ctx->hdl);
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+
+ kfree(ctx);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations fd_video_fops = {
+ .owner = THIS_MODULE,
+ .open = mtk_vfd_open,
+ .release = mtk_vfd_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+
+};
+
+static void mtk_fd_fill_user_param(struct user_param *user_param,
+ struct v4l2_ctrl_handler *hdl)
+{
+ struct v4l2_ctrl *ctrl;
+ int i;
+
+ ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH);
+ if (ctrl)
+ for (i = 0; i < ctrl->elems; i++)
+ user_param->scale_img_width[i] = ctrl->p_new.p_u16[i];
+ ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT);
+ if (ctrl)
+ for (i = 0; i < ctrl->elems; i++)
+ user_param->scale_img_height[i] = ctrl->p_new.p_u16[i];
+ ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_SCALE_IMG_NUM);
+ if (ctrl)
+ user_param->scale_img_num = ctrl->val;
+
+ ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_DETECT_POSE);
+ if (ctrl)
+ for (i = 0; i < ctrl->elems; i++)
+ user_param->face_directions[i] = ctrl->p_new.p_u16[i];
+ ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_DETECT_SPEED);
+ if (ctrl)
+ user_param->fd_speedup = ctrl->val;
+ ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_DETECTION_MODEL);
+ if (ctrl)
+ user_param->fd_extra_model = ctrl->val;
+}
+
+static void mtk_fd_device_run(void *priv)
+{
+ struct mtk_fd_ctx *ctx = priv;
+ struct mtk_fd_dev *fd = ctx->fd_dev;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ struct fd_enq_param fd_param;
+
+ src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+ fd_param.src_img[0].dma_addr =
+ vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+ fd_param.user_result.dma_addr =
+ vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+ fd_param.output_vaddr = (u64)vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
+ fd_param.user_param.src_img_fmt =
+ get_fd_img_fmt(ctx->src_fmt.pixelformat);
+ if (ctx->src_fmt.num_planes == 2)
+ fd_param.src_img[1].dma_addr =
+ vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 1);
+ mtk_fd_fill_user_param(&fd_param.user_param, &ctx->hdl);
+
+ /* Complete request controls if any */
+ v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req, &ctx->hdl);
+
+ fd->output = (struct fd_user_output *)fd_param.output_vaddr;
+ mtk_fd_hw_job_exec(fd, &fd_param);
+}
+
+static struct v4l2_m2m_ops fd_m2m_ops = {
+ .device_run = mtk_fd_device_run,
+};
+
+static const struct media_device_ops fd_m2m_media_ops = {
+ .req_validate = vb2_request_validate,
+ .req_queue = v4l2_m2m_request_queue,
+};
+
+static int mtk_fd_video_device_register(struct mtk_fd_dev *fd)
+{
+ struct video_device *vfd = &fd->vfd;
+ struct v4l2_m2m_dev *m2m_dev = fd->m2m_dev;
+ struct device *dev = fd->dev;
+ int ret;
+
+ vfd->fops = &fd_video_fops;
+ vfd->release = video_device_release;
+ vfd->lock = &fd->vfd_lock;
+ vfd->v4l2_dev = &fd->v4l2_dev;
+ vfd->vfl_dir = VFL_DIR_M2M;
+ vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT_MPLANE |
+ V4L2_CAP_META_CAPTURE;
+ vfd->ioctl_ops = &mtk_fd_v4l2_video_out_ioctl_ops;
+
+ strscpy(vfd->name, dev_driver_string(dev), sizeof(vfd->name));
+
+ video_set_drvdata(vfd, fd);
+
+ ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
+ if (ret) {
+ dev_err(dev, "Failed to register video device\n");
+ goto err_free_dev;
+ }
+
+ ret = v4l2_m2m_register_media_controller(m2m_dev, vfd,
+ MEDIA_ENT_F_PROC_VIDEO_STATISTICS);
+ if (ret) {
+ dev_err(dev, "Failed to init mem2mem media controller\n");
+ goto err_unreg_video;
+ }
+ return 0;
+
+err_unreg_video:
+ video_unregister_device(vfd);
+err_free_dev:
+ video_device_release(vfd);
+ return ret;
+}
+
+static int mtk_fd_dev_v4l2_init(struct mtk_fd_dev *fd)
+{
+ struct media_device *mdev = &fd->mdev;
+ struct device *dev = fd->dev;
+ int ret;
+
+ ret = v4l2_device_register(dev, &fd->v4l2_dev);
+ if (ret) {
+ dev_err(dev, "Failed to register v4l2 device\n");
+ return ret;
+ }
+
+ fd->m2m_dev = v4l2_m2m_init(&fd_m2m_ops);
+ if (IS_ERR(fd->m2m_dev)) {
+ dev_err(dev, "Failed to init mem2mem device\n");
+ ret = PTR_ERR(fd->m2m_dev);
+ goto err_unreg_v4l2_dev;
+ }
+
+ mdev->dev = dev;
+ strscpy(mdev->model, dev_driver_string(dev), sizeof(mdev->model));
+ snprintf(mdev->bus_info, sizeof(mdev->bus_info),
+ "platform:%s", dev_name(dev));
+ media_device_init(mdev);
+ mdev->ops = &fd_m2m_media_ops;
+ fd->v4l2_dev.mdev = mdev;
+
+ ret = mtk_fd_video_device_register(fd);
+ if (ret) {
+ dev_err(dev, "Failed to register video device\n");
+ goto err_cleanup_mdev;
+ }
+
+ ret = media_device_register(mdev);
+ if (ret) {
+ dev_err(dev, "Failed to register mem2mem media device\n");
+ goto err_unreg_vdev;
+ }
+
+ return 0;
+
+err_unreg_vdev:
+ v4l2_m2m_unregister_media_controller(fd->m2m_dev);
+ video_unregister_device(&fd->vfd);
+ video_device_release(&fd->vfd);
+err_cleanup_mdev:
+ media_device_cleanup(mdev);
+ v4l2_m2m_release(fd->m2m_dev);
+err_unreg_v4l2_dev:
+ v4l2_device_unregister(&fd->v4l2_dev);
+ return ret;
+}
+
+static void mtk_fd_dev_v4l2_release(struct mtk_fd_dev *fd)
+{
+ v4l2_m2m_unregister_media_controller(fd->m2m_dev);
+ video_unregister_device(&fd->vfd);
+ video_device_release(&fd->vfd);
+ media_device_cleanup(&fd->mdev);
+ v4l2_m2m_release(fd->m2m_dev);
+ v4l2_device_unregister(&fd->v4l2_dev);
+}
+
+static irqreturn_t mtk_fd_irq(int irq, void *data)
+{
+ struct mtk_fd_dev *fd = (struct mtk_fd_dev *)data;
+
+ /* must read this register otherwise HW will keep sending irq */
+ readl(fd->fd_base + MTK_FD_REG_OFFSET_INT_VAL);
+ fd->output->number = readl(fd->fd_base + MTK_FD_REG_OFFSET_RESULT);
+ dev_dbg(fd->dev, "mtk_fd_face_num:%d\n", fd->output->number);
+
+ pm_runtime_put((fd->dev));
+ mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_DONE);
+ complete_all(&fd->fd_irq_done);
+ return IRQ_HANDLED;
+}
+
+static int mtk_fd_hw_get_scp_mem(struct mtk_fd_dev *fd)
+{
+ struct device *dev = fd->dev;
+ dma_addr_t addr;
+ void *ptr;
+ u32 ret;
+
+ /*
+ * Allocate coherent reserved memory for SCP firmware usage.
+ * The size of SCP composer's memory is fixed to 0x100000
+ * for the requirement of firmware.
+ */
+ ptr = dma_alloc_coherent(&fd->scp_pdev->dev,
+ MTK_FD_HW_WORK_BUF_SIZE, &addr, GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ fd->scp_mem.scp_addr = addr;
+ fd->scp_mem_virt_addr = ptr;
+ dev_info(dev, "scp addr:%pad va:%pK\n", &addr, ptr);
+
+ /*
+ * This reserved memory is also be used by FD HW.
+ * Need to get iova address for FD DMA.
+ */
+ addr = dma_map_resource(dev, addr, MTK_FD_HW_WORK_BUF_SIZE,
+ DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC);
+ if (dma_mapping_error(dev, addr)) {
+ dev_err(dev, "Failed to map scp iova\n");
+ ret = -ENOMEM;
+ goto fail_free_mem;
+ }
+ fd->scp_mem.dma_addr = addr;
+ dev_info(dev, "scp iova addr:%pad\n", &addr);
+
+ return 0;
+
+fail_free_mem:
+ dma_free_coherent(&fd->scp_pdev->dev, MTK_FD_HW_WORK_BUF_SIZE,
+ ptr, fd->scp_mem.scp_addr);
+ fd->scp_mem.scp_addr = 0;
+
+ return ret;
+}
+
+static int mtk_fd_probe(struct platform_device *pdev)
+{
+ struct mtk_fd_dev *fd;
+ struct device *dev = &pdev->dev;
+
+ struct resource *res;
+ phandle rproc_phandle;
+ int irq;
+ int ret;
+
+ fd = devm_kzalloc(&pdev->dev, sizeof(*fd), GFP_KERNEL);
+ if (!fd)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, fd);
+ fd->dev = dev;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "Failed to get irq by platform: %d\n", irq);
+ return irq;
+ }
+ ret = devm_request_irq(dev, irq, mtk_fd_irq, IRQF_SHARED,
+ dev_driver_string(dev),
+ fd);
+ if (ret) {
+ dev_err(dev, "Failed to request irq\n");
+ return ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ fd->fd_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(fd->fd_base)) {
+ dev_err(dev, "Failed to get fd reg base\n");
+ return PTR_ERR(fd->fd_base);
+ }
+
+ fd->fd_clk = devm_clk_get(dev, "fd");
+ if (IS_ERR(fd->fd_clk)) {
+ dev_err(dev, "Failed to get fd_clk_img_fd clock\n");
+ return PTR_ERR(fd->fd_clk);
+ }
+
+ /* init scp */
+ fd->scp_pdev = scp_get_pdev(pdev);
+ if (!fd->scp_pdev) {
+ dev_err(dev, "Failed to get scp device\n");
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(fd->dev->of_node, "mediatek,scp",
+ &rproc_phandle)) {
+ dev_err(dev, "Failed to get scp device\n");
+ return -EINVAL;
+ }
+
+ fd->rproc_handle = rproc_get_by_phandle(rproc_phandle);
+ if (!fd->rproc_handle) {
+ dev_err(dev, "Failed to get FD's rproc_handle\n");
+ return -EINVAL;
+ }
+
+ ret = mtk_fd_hw_get_scp_mem(fd);
+ if (ret) {
+ dev_err(dev, "Failed to init scp memory: %d\n", ret);
+ return ret;
+ }
+
+ mutex_init(&fd->vfd_lock);
+ init_completion(&fd->fd_irq_done);
+ pm_runtime_enable(dev);
+
+ ret = mtk_fd_dev_v4l2_init(fd);
+ if (ret) {
+ dev_err(dev, "Failed to init v4l2 device: %d\n", ret);
+ goto err_destroy_mutex;
+ }
+
+ return 0;
+
+err_destroy_mutex:
+ mutex_destroy(&fd->vfd_lock);
+ pm_runtime_disable(fd->dev);
+ return ret;
+}
+
+static int mtk_fd_remove(struct platform_device *pdev)
+{
+ struct mtk_fd_dev *fd = dev_get_drvdata(&pdev->dev);
+
+ mtk_fd_dev_v4l2_release(fd);
+ pm_runtime_disable(&pdev->dev);
+ dma_unmap_page_attrs(fd->dev,
+ fd->scp_mem.dma_addr,
+ MTK_FD_HW_WORK_BUF_SIZE,
+ DMA_TO_DEVICE,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ dma_free_coherent(&fd->scp_pdev->dev,
+ MTK_FD_HW_WORK_BUF_SIZE,
+ fd->scp_mem_virt_addr,
+ fd->scp_mem.scp_addr);
+ mutex_destroy(&fd->vfd_lock);
+ rproc_put(fd->rproc_handle);
+
+ return 0;
+}
+
+static int mtk_fd_suspend(struct device *dev)
+{
+ struct mtk_fd_dev *fd = dev_get_drvdata(dev);
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ if (fd->fd_stream_count)
+ if (mtk_fd_job_abort(fd))
+ mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_ERROR);
+
+ /* suspend FD HW */
+ writel(0x0, fd->fd_base + MTK_FD_REG_OFFSET_INT_EN);
+ writel(0x0, fd->fd_base + MTK_FD_REG_OFFSET_HW_ENABLE);
+ clk_disable_unprepare(fd->fd_clk);
+ dev_dbg(dev, "%s:disable clock\n", __func__);
+
+ return 0;
+}
+
+static int mtk_fd_resume(struct device *dev)
+{
+ struct mtk_fd_dev *fd = dev_get_drvdata(dev);
+ int ret;
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ ret = clk_prepare_enable(fd->fd_clk);
+ if (ret < 0) {
+ dev_dbg(dev, "Failed to open fd clk:%d\n", ret);
+ return ret;
+ }
+
+ /* resume FD HW */
+ writel(MTK_FD_SET_HW_ENABLE, fd->fd_base + MTK_FD_REG_OFFSET_HW_ENABLE);
+ writel(0x1, fd->fd_base + MTK_FD_REG_OFFSET_INT_EN);
+ dev_dbg(dev, "%s:enable clock\n", __func__);
+
+ return 0;
+}
+
+static int mtk_fd_runtime_suspend(struct device *dev)
+{
+ struct mtk_fd_dev *fd = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(fd->fd_clk);
+ return 0;
+}
+
+static int mtk_fd_runtime_resume(struct device *dev)
+{
+ struct mtk_fd_dev *fd = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(fd->fd_clk);
+ if (ret < 0) {
+ dev_err(dev, "Failed to open fd clk:%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops mtk_fd_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mtk_fd_suspend, mtk_fd_resume)
+ SET_RUNTIME_PM_OPS(mtk_fd_runtime_suspend, mtk_fd_runtime_resume, NULL)
+};
+
+static const struct of_device_id mtk_fd_of_ids[] = {
+ { .compatible = "mediatek,mt8183-fd", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mtk_fd_of_ids);
+
+static struct platform_driver mtk_fd_driver = {
+ .probe = mtk_fd_probe,
+ .remove = mtk_fd_remove,
+ .driver = {
+ .name = "mtk-fd-4.0",
+ .of_match_table = of_match_ptr(mtk_fd_of_ids),
+ .pm = &mtk_fd_pm_ops,
+ }
+};
+module_platform_driver(mtk_fd_driver);
+
+MODULE_DESCRIPTION("Mediatek FD driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/mtk-fd-v4l2-controls.h b/include/uapi/linux/mtk-fd-v4l2-controls.h
new file mode 100644
index 000000000000..9b814de05b0c
--- /dev/null
+++ b/include/uapi/linux/mtk-fd-v4l2-controls.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+//
+// Copyright (c) 2019 MediaTek Inc.
+/*
+ * For V4L2_CID_MTK_FD_DETECT_POSE, User can set the desired face direction to
+ * be detected for each face angle, there are five face angle and 12 directions.
+ * Below shows the definition of face angle and face direction,
+ * and a recommended usage of for face detection, the more selected directions
+ * the longer HW process time needed.
+ *
+ * enum face_angle {
+ * MTK_FD_FACE_FRONT,
+ * MTK_FD_FACE_RIGHT_50,
+ * MTK_FD_FACE_LEFT_50,
+ * MTK_FD_FACE_RIGHT_90,
+ * MTK_FD_FACE_LEFT_90,
+ * MTK_FD_FACE_ANGLE_NUM,
+ * };
+ *
+ * struct face_direction_def {
+ * __u16 MTK_FD_FACE_DIR_0 : 1,
+ * MTK_FD_FACE_DIR_30 : 1,
+ * MTK_FD_FACE_DIR_60 : 1,
+ * MTK_FD_FACE_DIR_90 : 1,
+ * MTK_FD_FACE_DIR_120 : 1,
+ * MTK_FD_FACE_DIR_150 : 1,
+ * MTK_FD_FACE_DIR_180 : 1,
+ * MTK_FD_FACE_DIR_210 : 1,
+ * MTK_FD_FACE_DIR_240 : 1,
+ * MTK_FD_FACE_DIR_270 : 1,
+ * MTK_FD_FACE_DIR_300 : 1,
+ * MTK_FD_FACE_DIR_330 : 1,
+ * : 4;
+ * };
+ *
+ * Sample usage:
+ * u16 face_directions[MTK_FD_FACE_ANGLE_NUM] = {0};
+ *
+ * face_directions[MTK_FD_FACE_FRONT] = 0x3ff;
+ *
+ */
+
+#ifndef __UAPI_MTK_FD_V4L2_CONTROLS_H__
+#define __UAPI_MTK_FD_V4L2_CONTROLS_H__
+
+#include <linux/v4l2-controls.h>
+
+/* Set the face angle and directions to be detected */
+#define V4L2_CID_MTK_FD_DETECT_POSE (V4L2_CID_USER_MTK_FD_BASE + 1)
+
+/* Set image widths for an input image to be scaled down for face detection */
+#define V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH (V4L2_CID_USER_MTK_FD_BASE + 2)
+
+/* Set image heights for an input image to be scaled down for face detection */
+#define V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT (V4L2_CID_USER_MTK_FD_BASE + 3)
+
+/* Set the length of scale down size array */
+#define V4L2_CID_MTK_FD_SCALE_IMG_NUM (V4L2_CID_USER_MTK_FD_BASE + 4)
+
+/* Set the detection speed, usually reducing accuracy. */
+#define V4L2_CID_MTK_FD_DETECT_SPEED (V4L2_CID_USER_MTK_FD_BASE + 5)
+
+/* Select the detection model or algorithm to be used. */
+#define V4L2_CID_MTK_FD_DETECTION_MODEL (V4L2_CID_USER_MTK_FD_BASE + 6)
+
+/* We reserve 16 controls for this driver. */
+#define V4L2_CID_MTK_FD_MAX 16
+
+#endif /* __UAPI_MTK_FD_V4L2_CONTROLS_H__ */
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 3dcfc6148f99..eae876ea6d0a 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -192,6 +192,10 @@ enum v4l2_colorfx {
* We reserve 16 controls for this driver. */
#define V4L2_CID_USER_IMX_BASE (V4L2_CID_USER_BASE + 0x10b0)
+/* The base for the mediatek FD driver controls */
+/* We reserve 16 controls for this driver. */
+#define V4L2_CID_USER_MTK_FD_BASE (V4L2_CID_USER_BASE + 0x10d0)
+
/* MPEG-class control IDs */
/* The MPEG controls are applicable to all codec controls
* and the 'MPEG' part of the define is historical */
--
2.18.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH V3 0/3] media: platform: Add support for Face Detection (FD) on mt8183 SoC
From: Jerry-ch Chen @ 2019-09-06 10:11 UTC (permalink / raw)
To: hans.verkuil, laurent.pinchart+renesas, tfiga, matthias.bgg,
mchehab, lkml
Cc: devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream, po-yang.huang,
Jerry-ch.Chen, jungo.lin, sj.huang, yuzhao, linux-mediatek,
zwisler, ck.hu, christie.yu, frederic.chen, linux-arm-kernel,
linux-media
Hello,
This RFC patch series is adding Face Detection (FD) driver on Mediatek
mt8183 SoC. It belongs to the first Mediatek's camera driver series based
on V4L2 and media controller framework. I posted the main part of the FD
driver as RFC to discuss first and would like some review comments.
==============
Introduction
==============
Face Detection (FD) unit provides hardware accelerated face detection
feature. It can detect different sizes of faces in a given image.
The driver is implemented as a normal V4L2 memory-to-memory device and
supports V4L2 controls for detection settings. It has two buffer queues.
1. Video output buffer: RAW image for face detection.
2. Meta capture buffer: Result of the detected faces.
==================
Changes in v3
==================
RFC v3 includes the following modification:
1. Adjust the private control ids and place them in
include/uapi/linux/mtk-fd-v4l2-controls.h
2. Fixing review commnets in mtk_fd_40.c and mtk_fd.h
3. Define FD meta capture buffer in include/uapi/linux/videodev2.h
4. Remove the usage of get_reserved_memory by scp driver and
use dma_alloc api instead
Todo:
- Add v4l2 control menus for private mtk fd control
- Refine the job finish flow when system suspend
==================
Changes in v2
==================
RFC v2 includes the following modification:
1. Implement FD as a V4L2 mem2mem driver
2. Replace meta input with V4L2 controls
==================
Changes in v1
==================
RFC v1 includes the following modification:
1. Uses Request API instead of FD's buffer collection design
2. removed unnecessary abstraction structurally, including mtk_fd_ctx and
related ops
3. removed the fd_smem node from device tree
4. Fixed the common issues Tomasz commented on Mediatek ISP Pass 1's RFC v0
patch series
==================
Dependent patch
==================
FD driver depends on SCP driver. The patches are as following:
[1]. Add support for mt8183 SCP
https://patchwork.kernel.org/cover/11132177/
==================
Compliance test
==================
* Version: https://git.linuxtv.org/v4l-utils.git/commit/?id=b16f9e945d74aa5
* Note: Some of the failures are caused by the implementation of FD driver,
which is a m2m device with VIDEO_OUT and META_CAPTURE queues,
therefore we can't set V4L2_CAP_VIDEO_M2M in device capability, and
fail in some non-m2m v4l2 test cases.
* Test command: v4l2-compliance -m 0
* test output:
v4l2-compliance SHA: not available, 32 bits
Compliance test for mtk-fd-4.0 device /dev/media0:
Media Driver Info:
Driver name : mtk-fd-4.0
Model : mtk-fd-4.0
Serial :
Bus info : platform:1502b000.fd
Media version : 4.19.67
Hardware revision: 0x00000000 (0)
Driver version : 4.19.67
Required ioctls:
test MEDIA_IOC_DEVICE_INFO: OK
Allow for multiple opens:
test second /dev/media0 open: OK
test MEDIA_IOC_DEVICE_INFO: OK
test for unlimited opens: OK
Media Controller ioctls:
test MEDIA_IOC_G_TOPOLOGY: OK
Entities: 3 Interfaces: 1 Pads: 4 Links: 4
test MEDIA_IOC_ENUM_ENTITIES/LINKS: OK
test MEDIA_IOC_SETUP_LINK: OK
Total for mtk-fd-4.0 device /dev/media0: 7, Succeeded: 7, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for mtk-fd-4.0 device /dev/video0:
Driver Info:
Driver name : mtk-fd-4.0
Card type : mtk-fd-4.0
Bus info : platform:1502b000.fd
Driver version : 4.19.67
Capabilities : 0x84a02000
Video Output Multiplanar
Metadata Capture
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04a02000
Video Output Multiplanar
Metadata Capture
Streaming
Extended Pix Format
Media Driver Info:
Driver name : mtk-fd-4.0
Model : mtk-fd-4.0
Serial :
Bus info : platform:1502b000.fd
Media version : 4.19.67
Hardware revision: 0x00000000 (0)
Driver version : 4.19.67
Interface Info:
ID : 0x0300000c
Type : V4L Video
Entity Info:
ID : 0x00000001 (1)
Name : mtk-fd-4.0-source
Function : V4L2 I/O
Pad 0x01000002 : 0: Source
Link 0x02000008: to remote pad 0x1000005 of entity 'mtk-fd-4.0-proc': Data, Enabled, Immutable
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
fail: v4l2-compliance.cpp(668): dcaps & output_caps
test VIDIOC_QUERYCAP: FAIL
Allow for multiple opens:
test second /dev/video0 open: OK
fail: v4l2-compliance.cpp(668): dcaps & output_caps
test VIDIOC_QUERYCAP: FAIL
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 1 Private Controls: 6
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
fail: v4l2-test-formats.cpp(457): pixelformat 00000000 () for buftype 10 not reported by ENUM_FMT
test VIDIOC_G_FMT: FAIL
fail: v4l2-test-formats.cpp(457): pixelformat 00000000 () for buftype 10 not reported by ENUM_FMT
test VIDIOC_TRY_FMT: FAIL
fail: v4l2-test-formats.cpp(457): pixelformat ffffffff (-BE) for buftype 10 not reported by ENUM_FMT
test VIDIOC_S_FMT: FAIL
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
fail: v4l2-test-buffers.cpp(667): q2.reqbufs(node->node2, 1) != EBUSY
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: FAIL
test VIDIOC_EXPBUF: OK
test Requests: OK
Total for mtk-fd-4.0 device /dev/video0: 45, Succeeded: 39, Failed: 6, Warnings: 0
Grand Total for mtk-fd-4.0 device /dev/media0: 52, Succeeded: 46, Failed: 6, Warnings: 0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [RFC PATCH V3 2/3] dts: arm64: mt8183: Add FD nodes
From: Jerry-ch Chen @ 2019-09-06 10:11 UTC (permalink / raw)
To: hans.verkuil, laurent.pinchart+renesas, tfiga, matthias.bgg,
mchehab, lkml
Cc: devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream, po-yang.huang,
Jerry-ch Chen, jungo.lin, sj.huang, yuzhao, linux-mediatek,
zwisler, ck.hu, christie.yu, frederic.chen, linux-arm-kernel,
linux-media
In-Reply-To: <20190906101125.3784-1-Jerry-Ch.chen@mediatek.com>
From: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
This patch adds nodes for Face Detection (FD) unit. FD is embedded
in Mediatek SoCs and works with the co-processor to perform face
detection on the input data and image and output detected face result.
Signed-off-by: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
---
arch/arm64/boot/dts/mediatek/mt8183.dtsi | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt8183.dtsi b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
index c3a516e63141..6f31b5f4c17c 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
@@ -432,6 +432,19 @@
#clock-cells = <1>;
};
+ fd:fd@1502b000 {
+ compatible = "mediatek,mt8183-fd";
+ mediatek,scp = <&scp>;
+ iommus = <&iommu M4U_PORT_CAM_FDVT_RP>,
+ <&iommu M4U_PORT_CAM_FDVT_WR>,
+ <&iommu M4U_PORT_CAM_FDVT_RB>;
+ reg = <0 0x1502b000 0 0x1000>;
+ interrupts = <GIC_SPI 269 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&imgsys CLK_IMG_FDVT>;
+ clock-names = "fd";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_ISP>;
+ };
+
vdecsys: syscon@16000000 {
compatible = "mediatek,mt8183-vdecsys", "syscon";
reg = <0 0x16000000 0 0x1000>;
--
2.18.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH V3 1/3] dt-bindings: mt8183: Added FD dt-bindings
From: Jerry-ch Chen @ 2019-09-06 10:11 UTC (permalink / raw)
To: hans.verkuil, laurent.pinchart+renesas, tfiga, matthias.bgg,
mchehab, lkml
Cc: devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream, po-yang.huang,
Jerry-ch Chen, jungo.lin, sj.huang, yuzhao, linux-mediatek,
zwisler, ck.hu, christie.yu, frederic.chen, linux-arm-kernel,
linux-media
In-Reply-To: <20190906101125.3784-1-Jerry-Ch.chen@mediatek.com>
From: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
This patch adds DT binding documentation for the Face Detection (FD)
unit of the Mediatek's mt8183 SoC.
Signed-off-by: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
---
.../bindings/media/mediatek,mt8183-fd.txt | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/mediatek,mt8183-fd.txt
diff --git a/Documentation/devicetree/bindings/media/mediatek,mt8183-fd.txt b/Documentation/devicetree/bindings/media/mediatek,mt8183-fd.txt
new file mode 100644
index 000000000000..46464175b95a
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/mediatek,mt8183-fd.txt
@@ -0,0 +1,34 @@
+* Mediatek Face Detection Unit (FD)
+
+Face Detection (FD) unit is a typical memory-to-memory HW device.
+It provides hardware accelerated face detection function, and it
+is able to detect different poses of faces. FD will writre result
+of detected face into memory as output.
+
+Required properties:
+- compatible: "mediatek,mt8183-fd"
+- mediatek,scp : the node of system control processor (SCP), see
+ Documentation/devicetree/bindings/remoteproc/mtk,scp.txt for details.
+- iommus: should point to the 3 entries: M4U_PORT_CAM_FDVT_RP,
+ M4U_PORT_CAM_FDVT_WR and M4U_PORT_CAM_FDVT_RB. (Please see
+ Documentation/devicetree/bindings/iommu/mediatek,iommu.txt for details.)
+- reg: Physical base address and length of the register space.
+- interrupts: interrupt number to the cpu.
+- clocks : must contain the FDVT clock, see
+ Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must be "fd".
+- power-domain: must be "MT8183_POWER_DOMAIN_ISP".
+
+Example:
+ fd: fd@1502b000 {
+ compatible = "mediatek,mt8183-fd";
+ mediatek,scp = <&scp>;
+ iommus = <&iommu M4U_PORT_CAM_FDVT_RP>,
+ <&iommu M4U_PORT_CAM_FDVT_WR>,
+ <&iommu M4U_PORT_CAM_FDVT_RB>;
+ reg = <0 0x1502b000 0 0x1000>;
+ interrupts = <GIC_SPI 269 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&imgsys CLK_IMG_FDVT>;
+ clock-names = "fd";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_ISP>;
+ };
--
2.18.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine
From: Roger Lu @ 2019-09-06 10:05 UTC (permalink / raw)
To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
Cc: Mark Rutland, Nishanth Menon, Angus Lin, devicetree, linux-pm,
Roger Lu, linux-kernel, HenryC Chen, yt.lee, Fan Chen,
linux-mediatek, Matthias Brugger, linux-arm-kernel
In-Reply-To: <20190906100514.30803-1-roger.lu@mediatek.com>
The SVS (Smart Voltage Scaling) engine is a piece of hardware which is
used to calculate optimized voltage values of several power domains, e.g.
CPU/GPU/CCI, according to chip process corner, temperatures, and other
factors. Then DVFS driver could apply those optimized voltage values to
reduce power consumption.
Signed-off-by: Roger Lu <roger.lu@mediatek.com>
---
drivers/power/avs/Kconfig | 10 +
drivers/power/avs/Makefile | 1 +
drivers/power/avs/mtk_svs.c | 2075 +++++++++++++++++++++++++++++++++
include/linux/power/mtk_svs.h | 23 +
4 files changed, 2109 insertions(+)
create mode 100644 drivers/power/avs/mtk_svs.c
create mode 100644 include/linux/power/mtk_svs.h
diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig
index b5a217b828dc..7c72504d5593 100644
--- a/drivers/power/avs/Kconfig
+++ b/drivers/power/avs/Kconfig
@@ -19,3 +19,13 @@ config ROCKCHIP_IODOMAIN
Say y here to enable support io domains on Rockchip SoCs. It is
necessary for the io domain setting of the SoC to match the
voltage supplied by the regulators.
+
+config MTK_SVS
+ bool "MediaTek Smart Voltage Scaling(SVS)"
+ depends on POWER_AVS && MTK_EFUSE
+ help
+ The SVS engine is a piece of hardware which is used to calculate
+ optimized voltage values of several power domains, e.g.
+ CPU clusters/GPU/CCI, according to chip process corner, temperatures,
+ and other factors. Then DVFS driver could apply those optimized voltage
+ values to reduce power consumption.
diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile
index a1b8cd453f19..57246b977a93 100644
--- a/drivers/power/avs/Makefile
+++ b/drivers/power/avs/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_POWER_AVS_OMAP) += smartreflex.o
obj-$(CONFIG_ROCKCHIP_IODOMAIN) += rockchip-io-domain.o
+obj-$(CONFIG_MTK_SVS) += mtk_svs.o
diff --git a/drivers/power/avs/mtk_svs.c b/drivers/power/avs/mtk_svs.c
new file mode 100644
index 000000000000..78ec93c3a4a5
--- /dev/null
+++ b/drivers/power/avs/mtk_svs.c
@@ -0,0 +1,2075 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 MediaTek Inc.
+ */
+
+#define pr_fmt(fmt) "[mtk_svs] " fmt
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/pm_runtime.h>
+#include <linux/power/mtk_svs.h>
+#include <linux/proc_fs.h>
+#include <linux/regulator/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/thermal.h>
+#include <linux/uaccess.h>
+
+#define SVS_INIT01_VOLT_IGNORE 1
+#define SVS_INIT01_VOLT_INC_ONLY 2
+
+#define SVS_PHASE_INIT01 0
+#define SVS_PHASE_INIT02 1
+#define SVS_PHASE_MON 2
+#define SVS_PHASE_ERROR 3
+
+#define SVS_CPU_LITTLE 1
+#define SVS_CPU_BIG 2
+#define SVS_CCI 3
+#define SVS_GPU 4
+
+#define proc_fops_rw(name) \
+ static int name ## _proc_open(struct inode *inode, \
+ struct file *file) \
+ { \
+ return single_open(file, name ## _proc_show, \
+ PDE_DATA(inode)); \
+ } \
+ static const struct file_operations name ## _proc_fops = { \
+ .owner = THIS_MODULE, \
+ .open = name ## _proc_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+ .write = name ## _proc_write, \
+ }
+
+#define proc_fops_ro(name) \
+ static int name ## _proc_open(struct inode *inode, \
+ struct file *file) \
+ { \
+ return single_open(file, name ## _proc_show, \
+ PDE_DATA(inode)); \
+ } \
+ static const struct file_operations name ## _proc_fops = { \
+ .owner = THIS_MODULE, \
+ .open = name ## _proc_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+ }
+
+#define proc_entry(name) {__stringify(name), &name ## _proc_fops}
+
+static DEFINE_SPINLOCK(mtk_svs_lock);
+struct mtk_svs;
+
+enum reg_index {
+ TEMPMONCTL0 = 0,
+ TEMPMONCTL1,
+ TEMPMONCTL2,
+ TEMPMONINT,
+ TEMPMONINTSTS,
+ TEMPMONIDET0,
+ TEMPMONIDET1,
+ TEMPMONIDET2,
+ TEMPH2NTHRE,
+ TEMPHTHRE,
+ TEMPCTHRE,
+ TEMPOFFSETH,
+ TEMPOFFSETL,
+ TEMPMSRCTL0,
+ TEMPMSRCTL1,
+ TEMPAHBPOLL,
+ TEMPAHBTO,
+ TEMPADCPNP0,
+ TEMPADCPNP1,
+ TEMPADCPNP2,
+ TEMPADCMUX,
+ TEMPADCEXT,
+ TEMPADCEXT1,
+ TEMPADCEN,
+ TEMPPNPMUXADDR,
+ TEMPADCMUXADDR,
+ TEMPADCEXTADDR,
+ TEMPADCEXT1ADDR,
+ TEMPADCENADDR,
+ TEMPADCVALIDADDR,
+ TEMPADCVOLTADDR,
+ TEMPRDCTRL,
+ TEMPADCVALIDMASK,
+ TEMPADCVOLTAGESHIFT,
+ TEMPADCWRITECTRL,
+ TEMPMSR0,
+ TEMPMSR1,
+ TEMPMSR2,
+ TEMPADCHADDR,
+ TEMPIMMD0,
+ TEMPIMMD1,
+ TEMPIMMD2,
+ TEMPMONIDET3,
+ TEMPADCPNP3,
+ TEMPMSR3,
+ TEMPIMMD3,
+ TEMPPROTCTL,
+ TEMPPROTTA,
+ TEMPPROTTB,
+ TEMPPROTTC,
+ TEMPSPARE0,
+ TEMPSPARE1,
+ TEMPSPARE2,
+ TEMPSPARE3,
+ TEMPMSR0_1,
+ TEMPMSR1_1,
+ TEMPMSR2_1,
+ TEMPMSR3_1,
+ DESCHAR,
+ TEMPCHAR,
+ DETCHAR,
+ AGECHAR,
+ DCCONFIG,
+ AGECONFIG,
+ FREQPCT30,
+ FREQPCT74,
+ LIMITVALS,
+ VBOOT,
+ DETWINDOW,
+ CONFIG,
+ TSCALCS,
+ RUNCONFIG,
+ SVSEN,
+ INIT2VALS,
+ DCVALUES,
+ AGEVALUES,
+ VOP30,
+ VOP74,
+ TEMP,
+ INTSTS,
+ INTSTSRAW,
+ INTEN,
+ CHKINT,
+ CHKSHIFT,
+ STATUS,
+ VDESIGN30,
+ VDESIGN74,
+ DVT30,
+ DVT74,
+ AGECOUNT,
+ SMSTATE0,
+ SMSTATE1,
+ CTL0,
+ DESDETSEC,
+ TEMPAGESEC,
+ CTRLSPARE0,
+ CTRLSPARE1,
+ CTRLSPARE2,
+ CTRLSPARE3,
+ CORESEL,
+ THERMINTST,
+ INTST,
+ THSTAGE0ST,
+ THSTAGE1ST,
+ THSTAGE2ST,
+ THAHBST0,
+ THAHBST1,
+ SPARE0,
+ SPARE1,
+ SPARE2,
+ SPARE3,
+ THSLPEVEB,
+ reg_num,
+};
+
+static const u32 svs_regs_v2[] = {
+ [TEMPMONCTL0] = 0x000,
+ [TEMPMONCTL1] = 0x004,
+ [TEMPMONCTL2] = 0x008,
+ [TEMPMONINT] = 0x00c,
+ [TEMPMONINTSTS] = 0x010,
+ [TEMPMONIDET0] = 0x014,
+ [TEMPMONIDET1] = 0x018,
+ [TEMPMONIDET2] = 0x01c,
+ [TEMPH2NTHRE] = 0x024,
+ [TEMPHTHRE] = 0x028,
+ [TEMPCTHRE] = 0x02c,
+ [TEMPOFFSETH] = 0x030,
+ [TEMPOFFSETL] = 0x034,
+ [TEMPMSRCTL0] = 0x038,
+ [TEMPMSRCTL1] = 0x03c,
+ [TEMPAHBPOLL] = 0x040,
+ [TEMPAHBTO] = 0x044,
+ [TEMPADCPNP0] = 0x048,
+ [TEMPADCPNP1] = 0x04c,
+ [TEMPADCPNP2] = 0x050,
+ [TEMPADCMUX] = 0x054,
+ [TEMPADCEXT] = 0x058,
+ [TEMPADCEXT1] = 0x05c,
+ [TEMPADCEN] = 0x060,
+ [TEMPPNPMUXADDR] = 0x064,
+ [TEMPADCMUXADDR] = 0x068,
+ [TEMPADCEXTADDR] = 0x06c,
+ [TEMPADCEXT1ADDR] = 0x070,
+ [TEMPADCENADDR] = 0x074,
+ [TEMPADCVALIDADDR] = 0x078,
+ [TEMPADCVOLTADDR] = 0x07c,
+ [TEMPRDCTRL] = 0x080,
+ [TEMPADCVALIDMASK] = 0x084,
+ [TEMPADCVOLTAGESHIFT] = 0x088,
+ [TEMPADCWRITECTRL] = 0x08c,
+ [TEMPMSR0] = 0x090,
+ [TEMPMSR1] = 0x094,
+ [TEMPMSR2] = 0x098,
+ [TEMPADCHADDR] = 0x09c,
+ [TEMPIMMD0] = 0x0a0,
+ [TEMPIMMD1] = 0x0a4,
+ [TEMPIMMD2] = 0x0a8,
+ [TEMPMONIDET3] = 0x0b0,
+ [TEMPADCPNP3] = 0x0b4,
+ [TEMPMSR3] = 0x0b8,
+ [TEMPIMMD3] = 0x0bc,
+ [TEMPPROTCTL] = 0x0c0,
+ [TEMPPROTTA] = 0x0c4,
+ [TEMPPROTTB] = 0x0c8,
+ [TEMPPROTTC] = 0x0cc,
+ [TEMPSPARE0] = 0x0f0,
+ [TEMPSPARE1] = 0x0f4,
+ [TEMPSPARE2] = 0x0f8,
+ [TEMPSPARE3] = 0x0fc,
+ [TEMPMSR0_1] = 0x190,
+ [TEMPMSR1_1] = 0x194,
+ [TEMPMSR2_1] = 0x198,
+ [TEMPMSR3_1] = 0x1b8,
+ [DESCHAR] = 0xc00,
+ [TEMPCHAR] = 0xc04,
+ [DETCHAR] = 0xc08,
+ [AGECHAR] = 0xc0c,
+ [DCCONFIG] = 0xc10,
+ [AGECONFIG] = 0xc14,
+ [FREQPCT30] = 0xc18,
+ [FREQPCT74] = 0xc1c,
+ [LIMITVALS] = 0xc20,
+ [VBOOT] = 0xc24,
+ [DETWINDOW] = 0xc28,
+ [CONFIG] = 0xc2c,
+ [TSCALCS] = 0xc30,
+ [RUNCONFIG] = 0xc34,
+ [SVSEN] = 0xc38,
+ [INIT2VALS] = 0xc3c,
+ [DCVALUES] = 0xc40,
+ [AGEVALUES] = 0xc44,
+ [VOP30] = 0xc48,
+ [VOP74] = 0xc4c,
+ [TEMP] = 0xc50,
+ [INTSTS] = 0xc54,
+ [INTSTSRAW] = 0xc58,
+ [INTEN] = 0xc5c,
+ [CHKINT] = 0xc60,
+ [CHKSHIFT] = 0xc64,
+ [STATUS] = 0xc68,
+ [VDESIGN30] = 0xc6c,
+ [VDESIGN74] = 0xc70,
+ [DVT30] = 0xc74,
+ [DVT74] = 0xc78,
+ [AGECOUNT] = 0xc7c,
+ [SMSTATE0] = 0xc80,
+ [SMSTATE1] = 0xc84,
+ [CTL0] = 0xc88,
+ [DESDETSEC] = 0xce0,
+ [TEMPAGESEC] = 0xce4,
+ [CTRLSPARE0] = 0xcf0,
+ [CTRLSPARE1] = 0xcf4,
+ [CTRLSPARE2] = 0xcf8,
+ [CTRLSPARE3] = 0xcfc,
+ [CORESEL] = 0xf00,
+ [THERMINTST] = 0xf04,
+ [INTST] = 0xf08,
+ [THSTAGE0ST] = 0xf0c,
+ [THSTAGE1ST] = 0xf10,
+ [THSTAGE2ST] = 0xf14,
+ [THAHBST0] = 0xf18,
+ [THAHBST1] = 0xf1c,
+ [SPARE0] = 0xf20,
+ [SPARE1] = 0xf24,
+ [SPARE2] = 0xf28,
+ [SPARE3] = 0xf2c,
+ [THSLPEVEB] = 0xf30,
+};
+
+struct thermal_parameter {
+ int adc_ge_t;
+ int adc_oe_t;
+ int ge;
+ int oe;
+ int gain;
+ int o_vtsabb;
+ int o_vtsmcu1;
+ int o_vtsmcu2;
+ int o_vtsmcu3;
+ int o_vtsmcu4;
+ int o_vtsmcu5;
+ int degc_cali;
+ int adc_cali_en_t;
+ int o_slope;
+ int o_slope_sign;
+ int ts_id;
+};
+
+struct svs_bank_ops {
+ void (*set_freqs_pct)(struct mtk_svs *svs);
+ void (*get_vops)(struct mtk_svs *svs);
+};
+
+struct svs_bank {
+ struct svs_bank_ops *ops;
+ struct completion init_completion;
+ struct device *dev;
+ struct regulator *buck;
+ struct mutex lock; /* lock to protect update voltage process */
+ bool suspended;
+ bool mtcmos_request;
+ bool init01_support;
+ bool init02_support;
+ bool mon_mode_support;
+ s32 volt_offset;
+ u32 *opp_freqs;
+ u32 *freqs_pct;
+ u32 *opp_volts;
+ u32 *init02_volts;
+ u32 *volts;
+ u32 reg_data[3][reg_num];
+ u32 freq_base;
+ u32 vboot;
+ u32 volt_step;
+ u32 volt_base;
+ u32 init01_volt_flag;
+ u32 phase;
+ u32 vmax;
+ u32 vmin;
+ u32 bts;
+ u32 mts;
+ u32 bdes;
+ u32 mdes;
+ u32 mtdes;
+ u32 dcbdet;
+ u32 dcmdet;
+ u32 dthi;
+ u32 dtlo;
+ u32 det_window;
+ u32 det_max;
+ u32 age_config;
+ u32 age_voffset_in;
+ u32 agem;
+ u32 dc_config;
+ u32 dc_voffset_in;
+ u32 dvt_fixed;
+ u32 vco;
+ u32 chkshift;
+ u32 svs_temp;
+ u32 upper_temp_bound;
+ u32 lower_temp_bound;
+ u32 low_temp_threashold;
+ u32 low_temp_offset;
+ u32 coresel;
+ u32 opp_count;
+ u32 intst;
+ u32 systemclk_en;
+ u32 sw_id;
+ u32 hw_id;
+ u32 ctl0;
+ u8 *of_compatible;
+ u8 *name;
+ u8 *zone_name;
+ u8 *buck_name;
+};
+
+struct svs_platform {
+ struct svs_bank *banks;
+ int (*efuse_parsing)(struct mtk_svs *svs);
+ bool fake_efuse;
+ const u32 *regs;
+ u32 bank_num;
+ u32 efuse_num;
+ u32 efuse_check;
+ u32 thermal_efuse_num;
+ u8 *name;
+};
+
+struct mtk_svs {
+ const struct svs_platform *platform;
+ struct svs_bank *bank;
+ struct device *dev;
+ void __iomem *base;
+ struct clk *main_clk;
+ u32 *efuse;
+ u32 *thermal_efuse;
+};
+
+unsigned long claim_mtk_svs_lock(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mtk_svs_lock, flags);
+
+ return flags;
+}
+EXPORT_SYMBOL_GPL(claim_mtk_svs_lock);
+
+void release_mtk_svs_lock(unsigned long flags)
+{
+ spin_unlock_irqrestore(&mtk_svs_lock, flags);
+}
+EXPORT_SYMBOL_GPL(release_mtk_svs_lock);
+
+static u32 percent(u32 numerator, u32 denominator)
+{
+ u32 percent;
+
+ /* If not divide 1000, "numerator * 100" would be data overflow. */
+ numerator /= 1000;
+ denominator /= 1000;
+ percent = ((numerator * 100) + denominator - 1) / denominator;
+
+ return percent;
+}
+
+static u32 svs_readl(struct mtk_svs *svs, enum reg_index i)
+{
+ return readl(svs->base + svs->platform->regs[i]);
+}
+
+static void svs_writel(struct mtk_svs *svs, u32 val, enum reg_index i)
+{
+ writel(val, svs->base + svs->platform->regs[i]);
+}
+
+static void svs_switch_bank(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+
+ svs_writel(svs, svsb->coresel, CORESEL);
+}
+
+static u32 svs_volt_to_opp_volt(u32 svsb_volt,
+ u32 svsb_volt_step, u32 svsb_volt_base)
+{
+ u32 u_volt;
+
+ u_volt = (svsb_volt * svsb_volt_step) + svsb_volt_base;
+
+ return u_volt;
+}
+
+static int svs_get_zone_temperature(struct svs_bank *svsb, int *zone_temp)
+{
+ struct thermal_zone_device *tzd;
+ int ret;
+
+ tzd = thermal_zone_get_zone_by_name(svsb->zone_name);
+ ret = thermal_zone_get_temp(tzd, zone_temp);
+
+ return ret;
+}
+
+static int svs_set_volts(struct svs_bank *svsb, bool force_update)
+{
+ u32 i, svsb_volt, opp_volt, low_temp_offset = 0;
+ int zone_temp, ret;
+
+ mutex_lock(&svsb->lock);
+
+ /* If bank is suspended, it means init02 voltage is applied.
+ * Don't need to update opp voltage anymore.
+ */
+ if (svsb->suspended && !force_update) {
+ pr_notice("%s: bank is suspended\n", svsb->name);
+ mutex_unlock(&svsb->lock);
+ return -EPERM;
+ }
+
+ /* get thermal effect */
+ if (svsb->phase == SVS_PHASE_MON) {
+ if (svsb->svs_temp > svsb->upper_temp_bound &&
+ svsb->svs_temp < svsb->lower_temp_bound) {
+ pr_err("%s: svs_temp is abnormal (0x%x)?\n",
+ svsb->name, svsb->svs_temp);
+ mutex_unlock(&svsb->lock);
+ return -EINVAL;
+ }
+
+ ret = svs_get_zone_temperature(svsb, &zone_temp);
+ if (ret) {
+ pr_err("%s: cannot get zone \"%s\" temperature\n",
+ svsb->name, svsb->zone_name);
+ pr_err("%s: add low_temp_offset = %u\n",
+ svsb->name, svsb->low_temp_offset);
+ zone_temp = svsb->low_temp_threashold;
+ }
+
+ if (zone_temp <= svsb->low_temp_threashold)
+ low_temp_offset = svsb->low_temp_offset;
+ }
+
+ /* vmin <= svsb_volt (opp_volt) <= signed-off voltage */
+ for (i = 0; i < svsb->opp_count; i++) {
+ if (svsb->phase == SVS_PHASE_MON) {
+ svsb_volt = max((svsb->volts[i] + svsb->volt_offset +
+ low_temp_offset), svsb->vmin);
+ opp_volt = svs_volt_to_opp_volt(svsb_volt,
+ svsb->volt_step,
+ svsb->volt_base);
+ } else if (svsb->phase == SVS_PHASE_INIT02) {
+ svsb_volt = max((svsb->init02_volts[i] +
+ svsb->volt_offset), svsb->vmin);
+ opp_volt = svs_volt_to_opp_volt(svsb_volt,
+ svsb->volt_step,
+ svsb->volt_base);
+ } else if (svsb->phase == SVS_PHASE_ERROR) {
+ opp_volt = svsb->opp_volts[i];
+ } else {
+ pr_err("%s: unknown phase: %u?\n",
+ svsb->name, svsb->phase);
+ mutex_unlock(&svsb->lock);
+ return -EINVAL;
+ }
+
+ opp_volt = min(opp_volt, svsb->opp_volts[i]);
+ ret = dev_pm_opp_adjust_voltage(svsb->dev, svsb->opp_freqs[i],
+ opp_volt);
+ if (ret) {
+ pr_err("%s: set voltage failed: %d\n", svsb->name, ret);
+ mutex_unlock(&svsb->lock);
+ return ret;
+ }
+ }
+
+ mutex_unlock(&svsb->lock);
+
+ return 0;
+}
+
+static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx)
+{
+ u32 vy;
+
+ if (v0 == v1 || f0 == f1)
+ return v0;
+
+ /* *100 to have decimal fraction factor, +99 for rounding up. */
+ vy = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx));
+ vy = (vy + 99) / 100;
+
+ return vy;
+}
+
+static void svs_get_vops_v2(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+ u32 temp, i;
+
+ temp = svs_readl(svs, VOP30);
+ svsb->volts[6] = (temp >> 24) & 0xff;
+ svsb->volts[4] = (temp >> 16) & 0xff;
+ svsb->volts[2] = (temp >> 8) & 0xff;
+ svsb->volts[0] = (temp & 0xff);
+
+ temp = svs_readl(svs, VOP74);
+ svsb->volts[14] = (temp >> 24) & 0xff;
+ svsb->volts[12] = (temp >> 16) & 0xff;
+ svsb->volts[10] = (temp >> 8) & 0xff;
+ svsb->volts[8] = (temp & 0xff);
+
+ for (i = 0; i <= 7; i++) {
+ if (i < 7) {
+ svsb->volts[(i * 2) + 1] =
+ interpolate(svsb->freqs_pct[i * 2],
+ svsb->freqs_pct[(i + 1) * 2],
+ svsb->volts[i * 2],
+ svsb->volts[(i + 1) * 2],
+ svsb->freqs_pct[(i * 2) + 1]);
+ } else if (i == 7) {
+ svsb->volts[(i * 2) + 1] =
+ interpolate(svsb->freqs_pct[(i - 1) * 2],
+ svsb->freqs_pct[i * 2],
+ svsb->volts[(i - 1) * 2],
+ svsb->volts[i * 2],
+ svsb->freqs_pct[(i * 2) + 1]);
+ }
+ }
+}
+
+static void svs_set_freqs_pct_v2(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+
+ svs_writel(svs,
+ ((svsb->freqs_pct[6] << 24) & 0xff000000) |
+ ((svsb->freqs_pct[4] << 16) & 0xff0000) |
+ ((svsb->freqs_pct[2] << 8) & 0xff00) |
+ (svsb->freqs_pct[0] & 0xff),
+ FREQPCT30);
+ svs_writel(svs,
+ ((svsb->freqs_pct[14] << 24) & 0xff000000) |
+ ((svsb->freqs_pct[12] << 16) & 0xff0000) |
+ ((svsb->freqs_pct[10] << 8) & 0xff00) |
+ ((svsb->freqs_pct[8]) & 0xff),
+ FREQPCT74);
+}
+
+static void svs_set_phase(struct mtk_svs *svs, u32 target_phase)
+{
+ struct svs_bank *svsb = svs->bank;
+ u32 des_char, temp_char, det_char, limit_vals;
+ u32 init2vals, ts_calcs, val, filter, i;
+
+ svs_switch_bank(svs);
+
+ des_char = ((svsb->bdes << 8) & 0xff00) | (svsb->mdes & 0xff);
+ svs_writel(svs, des_char, DESCHAR);
+
+ temp_char = ((svsb->vco << 16) & 0xff0000) |
+ ((svsb->mtdes << 8) & 0xff00) |
+ (svsb->dvt_fixed & 0xff);
+ svs_writel(svs, temp_char, TEMPCHAR);
+
+ det_char = ((svsb->dcbdet << 8) & 0xff00) | (svsb->dcmdet & 0xff);
+ svs_writel(svs, det_char, DETCHAR);
+
+ svs_writel(svs, svsb->dc_config, DCCONFIG);
+ svs_writel(svs, svsb->age_config, AGECONFIG);
+
+ if (svsb->agem == 0x0) {
+ svs_writel(svs, 0x80000000, RUNCONFIG);
+ } else {
+ val = 0x0;
+
+ for (i = 0; i < 24; i += 2) {
+ filter = 0x3 << i;
+
+ if ((svsb->age_config & filter) == 0x0)
+ val |= (0x1 << i);
+ else
+ val |= (svsb->age_config & filter);
+ }
+ svs_writel(svs, val, RUNCONFIG);
+ }
+
+ svsb->ops->set_freqs_pct(svs);
+
+ limit_vals = ((svsb->vmax << 24) & 0xff000000) |
+ ((svsb->vmin << 16) & 0xff0000) |
+ ((svsb->dthi << 8) & 0xff00) |
+ (svsb->dtlo & 0xff);
+ svs_writel(svs, limit_vals, LIMITVALS);
+ svs_writel(svs, (svsb->vboot & 0xff), VBOOT);
+ svs_writel(svs, (svsb->det_window & 0xffff), DETWINDOW);
+ svs_writel(svs, (svsb->det_max & 0xffff), CONFIG);
+
+ if (svsb->chkshift != 0)
+ svs_writel(svs, (svsb->chkshift & 0xff), CHKSHIFT);
+
+ if (svsb->ctl0 != 0)
+ svs_writel(svs, svsb->ctl0, CTL0);
+
+ svs_writel(svs, 0x00ffffff, INTSTS);
+
+ switch (target_phase) {
+ case SVS_PHASE_INIT01:
+ svs_writel(svs, 0x00005f01, INTEN);
+ svs_writel(svs, 0x00000001, SVSEN);
+ break;
+ case SVS_PHASE_INIT02:
+ svs_writel(svs, 0x00005f01, INTEN);
+ init2vals = ((svsb->age_voffset_in << 16) & 0xffff0000) |
+ (svsb->dc_voffset_in & 0xffff);
+ svs_writel(svs, init2vals, INIT2VALS);
+ svs_writel(svs, 0x00000005, SVSEN);
+ break;
+ case SVS_PHASE_MON:
+ ts_calcs = ((svsb->bts << 12) & 0xfff000) | (svsb->mts & 0xfff);
+ svs_writel(svs, ts_calcs, TSCALCS);
+ svs_writel(svs, 0x00FF0000, INTEN);
+ svs_writel(svs, 0x00000002, SVSEN);
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+}
+
+static inline void svs_init01_isr_handler(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+ enum reg_index rg_i;
+
+ pr_notice("%s: %s: VDN74:0x%08x, VDN30:0x%08x, DCVALUES:0x%08x\n",
+ svsb->name, __func__, svs_readl(svs, VDESIGN74),
+ svs_readl(svs, VDESIGN30), svs_readl(svs, DCVALUES));
+
+ for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+ svsb->reg_data[SVS_PHASE_INIT01][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->dc_voffset_in = ~(svs_readl(svs, DCVALUES) & 0xffff) + 1;
+ if (svsb->init01_volt_flag == SVS_INIT01_VOLT_IGNORE)
+ svsb->dc_voffset_in = 0;
+ else if ((svsb->dc_voffset_in & 0x8000) &&
+ (svsb->init01_volt_flag == SVS_INIT01_VOLT_INC_ONLY))
+ svsb->dc_voffset_in = 0;
+
+ svsb->age_voffset_in = svs_readl(svs, AGEVALUES) & 0xffff;
+
+ svs_writel(svs, 0x0, SVSEN);
+ svs_writel(svs, 0x1, INTSTS);
+
+ /* svs init01 clock gating */
+ svsb->coresel &= ~svsb->systemclk_en;
+
+ svsb->phase = SVS_PHASE_INIT01;
+ complete(&svsb->init_completion);
+}
+
+static inline void svs_init02_isr_handler(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+ enum reg_index rg_i;
+
+ pr_notice("%s: %s: VOP74:0x%08x, VOP30:0x%08x, DCVALUES:0x%08x\n",
+ svsb->name, __func__, svs_readl(svs, VOP74),
+ svs_readl(svs, VOP30), svs_readl(svs, DCVALUES));
+
+ for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+ svsb->reg_data[SVS_PHASE_INIT02][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->ops->get_vops(svs);
+ memcpy(svsb->init02_volts, svsb->volts, 4 * svsb->opp_count);
+ svsb->phase = SVS_PHASE_INIT02;
+
+ svs_writel(svs, 0x0, SVSEN);
+ svs_writel(svs, 0x1, INTSTS);
+
+ complete(&svsb->init_completion);
+}
+
+static inline void svs_mon_mode_isr_handler(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+ enum reg_index rg_i;
+
+ for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+ svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->svs_temp = svs_readl(svs, TEMP) & 0xff;
+
+ svsb->ops->get_vops(svs);
+ svsb->phase = SVS_PHASE_MON;
+
+ svs_writel(svs, 0x00ff0000, INTSTS);
+}
+
+static inline void svs_error_isr_handler(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb = svs->bank;
+ enum reg_index rg_i;
+
+ pr_err("%s(): %s(%s)", __func__, svsp->name, svsb->name);
+ pr_err("CORESEL(0x%x) = 0x%08x\n",
+ svsp->regs[CORESEL], svs_readl(svs, CORESEL)),
+ pr_err("SVSEN(0x%x) = 0x%08x, INTSTS(0x%x) = 0x%08x\n",
+ svsp->regs[SVSEN], svs_readl(svs, SVSEN),
+ svsp->regs[INTSTS], svs_readl(svs, INTSTS));
+ pr_err("SMSTATE0(0x%x) = 0x%08x, SMSTATE1(0x%x) = 0x%08x\n",
+ svsp->regs[SMSTATE0], svs_readl(svs, SMSTATE0),
+ svsp->regs[SMSTATE1], svs_readl(svs, SMSTATE1));
+
+ for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+ svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->init01_support = false;
+ svsb->init02_support = false;
+ svsb->mon_mode_support = false;
+
+ if (svsb->phase == SVS_PHASE_MON)
+ svsb->phase = SVS_PHASE_INIT02;
+
+ svs_writel(svs, 0x0, SVSEN);
+ svs_writel(svs, 0x00ffffff, INTSTS);
+}
+
+static inline void svs_isr_handler(struct mtk_svs *svs)
+{
+ u32 intsts, svsen;
+
+ svs_switch_bank(svs);
+
+ intsts = svs_readl(svs, INTSTS);
+ svsen = svs_readl(svs, SVSEN);
+
+ if (intsts == 0x1 && ((svsen & 0x7) == 0x1))
+ svs_init01_isr_handler(svs);
+ else if ((intsts == 0x1) && ((svsen & 0x7) == 0x5))
+ svs_init02_isr_handler(svs);
+ else if ((intsts & 0x00ff0000) != 0x0)
+ svs_mon_mode_isr_handler(svs);
+ else
+ svs_error_isr_handler(svs);
+}
+
+static irqreturn_t svs_isr(int irq, void *data)
+{
+ struct mtk_svs *svs = (struct mtk_svs *)data;
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb = NULL;
+ unsigned long flags;
+ u32 idx;
+
+ flags = claim_mtk_svs_lock();
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svs->bank = svsb;
+
+ if (svsb->suspended)
+ continue;
+ else if (svsb->intst & svs_readl(svs, INTST))
+ continue;
+
+ svs_isr_handler(svs);
+ break;
+ }
+ release_mtk_svs_lock(flags);
+
+ if (svsb->phase != SVS_PHASE_INIT01)
+ svs_set_volts(svsb, false);
+
+ return IRQ_HANDLED;
+}
+
+static void svs_mon_mode(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ unsigned long flags;
+ u32 idx;
+
+ flags = claim_mtk_svs_lock();
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svs->bank = svsb;
+
+ if (!svsb->mon_mode_support)
+ continue;
+
+ svs_set_phase(svs, SVS_PHASE_MON);
+ }
+ release_mtk_svs_lock(flags);
+}
+
+static int svs_init02(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ unsigned long flags, time_left;
+ u32 idx;
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svs->bank = svsb;
+
+ if (!svsb->init02_support)
+ continue;
+
+ reinit_completion(&svsb->init_completion);
+ flags = claim_mtk_svs_lock();
+ svs_set_phase(svs, SVS_PHASE_INIT02);
+ release_mtk_svs_lock(flags);
+ time_left =
+ wait_for_completion_timeout(&svsb->init_completion,
+ msecs_to_jiffies(2000));
+ if (time_left == 0) {
+ pr_err("%s: init02 completion timeout\n", svsb->name);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static int svs_init01(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ struct pm_qos_request qos_request = { {0} };
+ unsigned long flags, time_left;
+ bool search_done;
+ int ret = -EINVAL;
+ u32 opp_freqs, opp_vboot, buck_volt, idx, i;
+
+ /* Let CPUs leave idle-off state for initializing svs_init01. */
+ pm_qos_add_request(&qos_request, PM_QOS_CPU_DMA_LATENCY, 0);
+
+ /* Sometimes two svs_bank use the same buck.
+ * Therefore, we set each svs_bank to vboot voltage first.
+ */
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ search_done = false;
+
+ if (!svsb->init01_support)
+ continue;
+
+ ret = regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST);
+ if (ret)
+ pr_notice("%s: fail to set fast mode: %d\n",
+ svsb->name, ret);
+
+ if (svsb->mtcmos_request) {
+ ret = regulator_enable(svsb->buck);
+ if (ret) {
+ pr_err("%s: fail to enable %s power: %d\n",
+ svsb->name, svsb->buck_name, ret);
+ goto init01_finish;
+ }
+
+ ret = dev_pm_domain_attach(svsb->dev, false);
+ if (ret) {
+ pr_err("%s: attach pm domain fail: %d\n",
+ svsb->name, ret);
+ goto init01_finish;
+ }
+
+ pm_runtime_enable(svsb->dev);
+ ret = pm_runtime_get_sync(svsb->dev);
+ if (ret < 0) {
+ pr_err("%s: turn mtcmos on fail: %d\n",
+ svsb->name, ret);
+ goto init01_finish;
+ }
+ }
+
+ /* Find the fastest freq that can be run at vboot and
+ * fix to that freq until svs_init01 is done.
+ */
+ opp_vboot = svs_volt_to_opp_volt(svsb->vboot,
+ svsb->volt_step,
+ svsb->volt_base);
+
+ for (i = 0; i < svsb->opp_count; i++) {
+ opp_freqs = svsb->opp_freqs[i];
+ if (!search_done && svsb->opp_volts[i] <= opp_vboot) {
+ ret = dev_pm_opp_adjust_voltage(svsb->dev,
+ opp_freqs,
+ opp_vboot);
+ if (ret) {
+ pr_err("%s: set voltage failed: %d\n",
+ svsb->name, ret);
+ goto init01_finish;
+ }
+
+ search_done = true;
+ } else {
+ dev_pm_opp_disable(svsb->dev,
+ svsb->opp_freqs[i]);
+ }
+ }
+ }
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svs->bank = svsb;
+
+ if (!svsb->init01_support)
+ continue;
+
+ opp_vboot = svs_volt_to_opp_volt(svsb->vboot,
+ svsb->volt_step,
+ svsb->volt_base);
+
+ buck_volt = regulator_get_voltage(svsb->buck);
+ if (buck_volt != opp_vboot) {
+ pr_err("%s: buck voltage: %u, expected vboot: %u\n",
+ svsb->name, buck_volt, opp_vboot);
+ ret = -EPERM;
+ goto init01_finish;
+ }
+
+ init_completion(&svsb->init_completion);
+ flags = claim_mtk_svs_lock();
+ svs_set_phase(svs, SVS_PHASE_INIT01);
+ release_mtk_svs_lock(flags);
+ time_left =
+ wait_for_completion_timeout(&svsb->init_completion,
+ msecs_to_jiffies(2000));
+ if (time_left == 0) {
+ pr_err("%s: init01 completion timeout\n", svsb->name);
+ ret = -EBUSY;
+ goto init01_finish;
+ }
+ }
+
+init01_finish:
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+
+ if (!svsb->init01_support)
+ continue;
+
+ for (i = 0; i < svsb->opp_count; i++)
+ dev_pm_opp_enable(svsb->dev, svsb->opp_freqs[i]);
+
+ if (regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL))
+ pr_notice("%s: fail to set normal mode: %d\n",
+ svsb->name, ret);
+
+ if (svsb->mtcmos_request) {
+ if (pm_runtime_put_sync(svsb->dev))
+ pr_err("%s: turn mtcmos off fail: %d\n",
+ svsb->name, ret);
+ pm_runtime_disable(svsb->dev);
+ dev_pm_domain_detach(svsb->dev, 0);
+ if (regulator_disable(svsb->buck))
+ pr_err("%s: fail to disable %s power: %d\n",
+ svsb->name, svsb->buck_name, ret);
+ }
+ }
+
+ pm_qos_remove_request(&qos_request);
+
+ return ret;
+}
+
+static int svs_start(struct mtk_svs *svs)
+{
+ int ret;
+
+ ret = svs_init01(svs);
+ if (ret)
+ return ret;
+
+ ret = svs_init02(svs);
+ if (ret)
+ return ret;
+
+ svs_mon_mode(svs);
+
+ return ret;
+}
+
+static int svs_mt8183_efuse_parsing(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct thermal_parameter tp;
+ struct svs_bank *svsb;
+ bool mon_mode_support = true;
+ int format[6], x_roomt[6], tb_roomt;
+ u32 idx, i, ft_pgm, mts, temp0, temp1, temp2;
+
+ if (svsp->fake_efuse) {
+ pr_notice("fake efuse\n");
+ svs->efuse[0] = 0x00310080;
+ svs->efuse[1] = 0xabfbf757;
+ svs->efuse[2] = 0x47c747c7;
+ svs->efuse[3] = 0xabfbf757;
+ svs->efuse[4] = 0xe7fca0ec;
+ svs->efuse[5] = 0x47bf4b88;
+ svs->efuse[6] = 0xabfb8fa5;
+ svs->efuse[7] = 0xabfb217b;
+ svs->efuse[8] = 0x4bf34be1;
+ svs->efuse[9] = 0xabfb670d;
+ svs->efuse[16] = 0xabfbc653;
+ svs->efuse[17] = 0x47f347e1;
+ svs->efuse[18] = 0xabfbd848;
+
+ svs->thermal_efuse[0] = 0x02873f69;
+ svs->thermal_efuse[1] = 0xa11d9142;
+ svs->thermal_efuse[2] = 0xa2526900;
+ }
+
+ /* svs efuse parsing */
+ ft_pgm = (svs->efuse[0] >> 4) & 0xf;
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ if (ft_pgm <= 1)
+ svsb->init01_volt_flag = SVS_INIT01_VOLT_IGNORE;
+
+ switch (svsb->sw_id) {
+ case SVS_CPU_LITTLE:
+ svsb->bdes = svs->efuse[16] & 0xff;
+ svsb->mdes = (svs->efuse[16] >> 8) & 0xff;
+ svsb->dcbdet = (svs->efuse[16] >> 16) & 0xff;
+ svsb->dcmdet = (svs->efuse[16] >> 24) & 0xff;
+ svsb->mtdes = (svs->efuse[17] >> 16) & 0xff;
+
+ if (ft_pgm <= 3)
+ svsb->volt_offset += 10;
+ else
+ svsb->volt_offset += 2;
+ break;
+ case SVS_CPU_BIG:
+ svsb->bdes = svs->efuse[18] & 0xff;
+ svsb->mdes = (svs->efuse[18] >> 8) & 0xff;
+ svsb->dcbdet = (svs->efuse[18] >> 16) & 0xff;
+ svsb->dcmdet = (svs->efuse[18] >> 24) & 0xff;
+ svsb->mtdes = svs->efuse[17] & 0xff;
+
+ if (ft_pgm <= 3)
+ svsb->volt_offset += 15;
+ else
+ svsb->volt_offset += 12;
+ break;
+ case SVS_CCI:
+ svsb->bdes = svs->efuse[4] & 0xff;
+ svsb->mdes = (svs->efuse[4] >> 8) & 0xff;
+ svsb->dcbdet = (svs->efuse[4] >> 16) & 0xff;
+ svsb->dcmdet = (svs->efuse[4] >> 24) & 0xff;
+ svsb->mtdes = (svs->efuse[5] >> 16) & 0xff;
+
+ if (ft_pgm <= 3)
+ svsb->volt_offset += 10;
+ else
+ svsb->volt_offset += 2;
+ break;
+ case SVS_GPU:
+ svsb->bdes = svs->efuse[6] & 0xff;
+ svsb->mdes = (svs->efuse[6] >> 8) & 0xff;
+ svsb->dcbdet = (svs->efuse[6] >> 16) & 0xff;
+ svsb->dcmdet = (svs->efuse[6] >> 24) & 0xff;
+ svsb->mtdes = svs->efuse[5] & 0xff;
+
+ if (ft_pgm >= 2) {
+ svsb->freq_base = 800000000; /* 800MHz */
+ svsb->dvt_fixed = 2;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (i = 0; i < svsp->efuse_num; i++) {
+ if (svs->efuse[i])
+ pr_notice("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]);
+ }
+
+ /* thermal efuse parsing */
+ if (!svs->thermal_efuse)
+ return 0;
+
+ tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & 0x3ff;
+ tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & 0x3ff;
+
+ tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & 0x1ff;
+ tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & 0x1ff;
+ tp.o_vtsmcu3 = svs->thermal_efuse[1] & 0x1ff;
+ tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & 0x1ff;
+ tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & 0x1ff;
+ tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & 0x1ff;
+
+ tp.degc_cali = (svs->thermal_efuse[0] >> 1) & 0x3f;
+ tp.adc_cali_en_t = svs->thermal_efuse[0] & BIT(0);
+ tp.o_slope_sign = (svs->thermal_efuse[0] >> 7) & BIT(0);
+
+ tp.ts_id = (svs->thermal_efuse[1] >> 9) & BIT(0);
+ tp.o_slope = (svs->thermal_efuse[0] >> 26) & 0x3f;
+
+ if (tp.adc_cali_en_t == 1) {
+ if (tp.ts_id == 0)
+ tp.o_slope = 0;
+
+ if ((tp.adc_ge_t < 265 || tp.adc_ge_t > 758) ||
+ (tp.adc_oe_t < 265 || tp.adc_oe_t > 758) ||
+ (tp.o_vtsmcu1 < -8 || tp.o_vtsmcu1 > 484) ||
+ (tp.o_vtsmcu2 < -8 || tp.o_vtsmcu2 > 484) ||
+ (tp.o_vtsmcu3 < -8 || tp.o_vtsmcu3 > 484) ||
+ (tp.o_vtsmcu4 < -8 || tp.o_vtsmcu4 > 484) ||
+ (tp.o_vtsmcu5 < -8 || tp.o_vtsmcu5 > 484) ||
+ (tp.o_vtsabb < -8 || tp.o_vtsabb > 484) ||
+ (tp.degc_cali < 1 || tp.degc_cali > 63)) {
+ pr_err("bad thermal efuse data. disable mon mode\n");
+ mon_mode_support = false;
+ }
+ } else {
+ pr_err("no thermal efuse data. disable mon mode\n");
+ mon_mode_support = false;
+ }
+
+ if (!mon_mode_support) {
+ for (i = 0; i < svsp->thermal_efuse_num; i++)
+ pr_err("thermal_efuse[%u] = 0x%08x\n",
+ i, svs->thermal_efuse[i]);
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svsb->mon_mode_support = false;
+ }
+
+ return 0;
+ }
+
+ tp.ge = ((tp.adc_ge_t - 512) * 10000) / 4096;
+ tp.oe = (tp.adc_oe_t - 512);
+ tp.gain = (10000 + tp.ge);
+
+ format[0] = (tp.o_vtsmcu1 + 3350 - tp.oe);
+ format[1] = (tp.o_vtsmcu2 + 3350 - tp.oe);
+ format[2] = (tp.o_vtsmcu3 + 3350 - tp.oe);
+ format[3] = (tp.o_vtsmcu4 + 3350 - tp.oe);
+ format[4] = (tp.o_vtsmcu5 + 3350 - tp.oe);
+ format[5] = (tp.o_vtsabb + 3350 - tp.oe);
+
+ for (i = 0; i < 6; i++)
+ x_roomt[i] = (((format[i] * 10000) / 4096) * 10000) / tp.gain;
+
+ temp0 = (10000 * 100000 / tp.gain) * 15 / 18;
+
+ if (tp.o_slope_sign == 0)
+ mts = (temp0 * 10) / (1534 + tp.o_slope * 10);
+ else
+ mts = (temp0 * 10) / (1534 - tp.o_slope * 10);
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svsb->mts = mts;
+
+ switch (svsb->sw_id) {
+ case SVS_CPU_LITTLE:
+ tb_roomt = x_roomt[3];
+ break;
+ case SVS_CPU_BIG:
+ tb_roomt = x_roomt[4];
+ break;
+ case SVS_CCI:
+ tb_roomt = x_roomt[3];
+ break;
+ case SVS_GPU:
+ tb_roomt = x_roomt[1];
+ break;
+ default:
+ pr_err("unknown svsb_id = %u? disable svs\n",
+ svsb->sw_id);
+ return -EINVAL;
+ }
+
+ temp0 = (tp.degc_cali * 10 / 2);
+ temp1 = ((10000 * 100000 / 4096 / tp.gain) *
+ tp.oe + tb_roomt * 10) * 15 / 18;
+
+ if (tp.o_slope_sign == 0)
+ temp2 = temp1 * 100 / (1534 + tp.o_slope * 10);
+ else
+ temp2 = temp1 * 100 / (1534 - tp.o_slope * 10);
+
+ svsb->bts = (temp0 + temp2 - 250) * 4 / 10;
+ }
+
+ return 0;
+}
+
+static int svs_is_support(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ struct nvmem_cell *cell;
+ size_t len;
+ int ret;
+ u32 idx, i;
+
+ if (svsp->fake_efuse) {
+ len = svsp->efuse_num * 4;
+ svs->efuse = kzalloc(len, GFP_KERNEL);
+ if (!svs->efuse)
+ return -ENOMEM;
+
+ len = svsp->thermal_efuse_num * 4;
+ svs->thermal_efuse = kzalloc(len, GFP_KERNEL);
+ if (!svs->thermal_efuse)
+ return -ENOMEM;
+
+ goto svsp_efuse_parsing;
+ }
+
+ /* get svs efuse by nvmem */
+ cell = nvmem_cell_get(svs->dev, "svs-calibration-data");
+ if (IS_ERR(cell)) {
+ pr_err("no \"svs-calibration-data\" from dts? disable svs\n");
+ return PTR_ERR(cell);
+ }
+
+ svs->efuse = (u32 *)nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+
+ ret = (svs->efuse[svsp->efuse_check] == 0) ? -EPERM : 0;
+ if (ret) {
+ pr_err("no svs efuse. disable svs.\n");
+ for (i = 0; i < svsp->efuse_num; i++)
+ pr_err("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]);
+ return ret;
+ }
+
+ /* get thermal efuse by nvmem */
+ cell = nvmem_cell_get(svs->dev, "calibration-data");
+ if (IS_ERR(cell)) {
+ pr_err("no \"calibration-data\" from dts? disable mon mode\n");
+ svs->thermal_efuse = NULL;
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svsb->mon_mode_support = false;
+ }
+ goto svsp_efuse_parsing;
+ }
+
+ svs->thermal_efuse = (u32 *)nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+
+svsp_efuse_parsing:
+ ret = svsp->efuse_parsing(svs);
+
+ return ret;
+}
+
+static int svs_resource_setup(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ struct platform_device *pdev;
+ struct device_node *np = NULL;
+ struct dev_pm_opp *opp;
+ unsigned long freq;
+ size_t opp_size;
+ int count, ret;
+ u32 idx, i;
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+
+ if (!svsb->init01_support)
+ continue;
+
+ switch (svsb->sw_id) {
+ case SVS_CPU_LITTLE:
+ svsb->name = "SVS_CPU_LITTLE";
+ break;
+ case SVS_CPU_BIG:
+ svsb->name = "SVS_CPU_BIG";
+ break;
+ case SVS_CCI:
+ svsb->name = "SVS_CCI";
+ break;
+ case SVS_GPU:
+ svsb->name = "SVS_GPU";
+ break;
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ /* Add svs_bank device for opp-table/mtcmos/buck control */
+ pdev = platform_device_alloc(svsb->name, 0);
+ if (!pdev) {
+ pr_err("%s: fail to alloc pdev for svs_bank\n",
+ svsb->name);
+ return -ENOMEM;
+ }
+
+ for_each_child_of_node(svs->dev->of_node, np) {
+ if (of_device_is_compatible(np, svsb->of_compatible)) {
+ pdev->dev.of_node = np;
+ break;
+ }
+ }
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ pr_err("%s: fail to add svs_bank device: %d\n",
+ svsb->name, ret);
+ return ret;
+ }
+
+ svsb->dev = &pdev->dev;
+ dev_set_drvdata(svsb->dev, svs);
+ ret = dev_pm_opp_of_add_table(svsb->dev);
+ if (ret) {
+ pr_err("%s: fail to add opp table: %d\n",
+ svsb->name, ret);
+ return ret;
+ }
+
+ mutex_init(&svsb->lock);
+
+ svsb->buck = devm_regulator_get_optional(svsb->dev,
+ svsb->buck_name);
+ if (IS_ERR(svsb->buck)) {
+ pr_err("%s: cannot get regulator \"%s-supply\"\n",
+ svsb->name, svsb->buck_name);
+ return PTR_ERR(svsb->buck);
+ }
+
+ count = dev_pm_opp_get_opp_count(svsb->dev);
+ if (svsb->opp_count != count) {
+ pr_err("%s: opp_count not \"%u\" but get \"%d\"?\n",
+ svsb->name, svsb->opp_count, count);
+ return count;
+ }
+
+ opp_size = 4 * svsb->opp_count;
+ svsb->opp_volts = kmalloc(opp_size, GFP_KERNEL);
+ if (!svsb->opp_volts)
+ return -ENOMEM;
+
+ svsb->init02_volts = kmalloc(opp_size, GFP_KERNEL);
+ if (!svsb->init02_volts)
+ return -ENOMEM;
+
+ svsb->volts = kmalloc(opp_size, GFP_KERNEL);
+ if (!svsb->volts)
+ return -ENOMEM;
+
+ svsb->opp_freqs = kmalloc(opp_size, GFP_KERNEL);
+ if (!svsb->opp_freqs)
+ return -ENOMEM;
+
+ svsb->freqs_pct = kmalloc(opp_size, GFP_KERNEL);
+ if (!svsb->freqs_pct)
+ return -ENOMEM;
+
+ for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
+ opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq);
+ if (IS_ERR(opp)) {
+ pr_err("%s: error opp entry!!, err = %ld\n",
+ svsb->name, PTR_ERR(opp));
+ return PTR_ERR(opp);
+ }
+
+ svsb->opp_freqs[i] = freq;
+ svsb->opp_volts[i] = dev_pm_opp_get_voltage(opp);
+ svsb->freqs_pct[i] = percent(svsb->opp_freqs[i],
+ svsb->freq_base) & 0xff;
+ }
+ }
+
+ return 0;
+}
+
+static int svs_suspend(struct device *dev)
+{
+ struct mtk_svs *svs = dev_get_drvdata(dev);
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ unsigned long flags;
+ u32 idx;
+
+ /* Wait if there is processing svs_isr(). Suspend all banks. */
+ flags = claim_mtk_svs_lock();
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svs->bank = svsb;
+ svs_switch_bank(svs);
+ svs_writel(svs, 0x0, SVSEN);
+ svs_writel(svs, 0x00ffffff, INTSTS);
+ svsb->suspended = true;
+ }
+ release_mtk_svs_lock(flags);
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ if (svsb->phase == SVS_PHASE_MON) {
+ svsb->phase = SVS_PHASE_INIT02;
+ svs_set_volts(svsb, true);
+ }
+ }
+
+ clk_disable_unprepare(svs->main_clk);
+
+ return 0;
+}
+
+static int svs_resume(struct device *dev)
+{
+ struct mtk_svs *svs = dev_get_drvdata(dev);
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ int ret;
+ u32 idx;
+
+ ret = clk_prepare_enable(svs->main_clk);
+ if (ret)
+ pr_err("%s(): cannot enable main_clk\n", __func__);
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svsb->suspended = false;
+ }
+
+ ret = svs_init02(svs);
+ if (ret)
+ return ret;
+
+ svs_mon_mode(svs);
+
+ return 0;
+}
+
+static int svs_debug_proc_show(struct seq_file *m, void *v)
+{
+ struct svs_bank *svsb = (struct svs_bank *)m->private;
+
+ if (svsb->phase == SVS_PHASE_INIT01)
+ seq_puts(m, "init1\n");
+ else if (svsb->phase == SVS_PHASE_INIT02)
+ seq_puts(m, "init2\n");
+ else if (svsb->phase == SVS_PHASE_MON)
+ seq_puts(m, "mon mode\n");
+ else if (svsb->phase == SVS_PHASE_ERROR)
+ seq_puts(m, "disabled\n");
+ else
+ seq_puts(m, "unknown\n");
+
+ return 0;
+}
+
+static ssize_t svs_debug_proc_write(struct file *file,
+ const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file));
+ struct mtk_svs *svs = dev_get_drvdata(svsb->dev);
+ char *buf = (char *)__get_free_page(GFP_USER);
+ unsigned long flags;
+ int enabled, ret;
+
+ if (svsb->phase == SVS_PHASE_ERROR)
+ return count;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (count >= PAGE_SIZE) {
+ free_page((unsigned long)buf);
+ return -EINVAL;
+ }
+
+ if (copy_from_user(buf, buffer, count)) {
+ free_page((unsigned long)buf);
+ return -EFAULT;
+ }
+
+ buf[count] = '\0';
+
+ ret = kstrtoint(buf, 10, &enabled);
+ if (ret)
+ return ret;
+
+ if (!enabled) {
+ flags = claim_mtk_svs_lock();
+ svs->bank = svsb;
+
+ svsb->init01_support = false;
+ svsb->init02_support = false;
+ svsb->mon_mode_support = false;
+
+ svs_switch_bank(svs);
+ svs_writel(svs, 0x0, SVSEN);
+ svs_writel(svs, 0x00ffffff, INTSTS);
+ release_mtk_svs_lock(flags);
+ }
+
+ svsb->phase = SVS_PHASE_ERROR;
+ svs_set_volts(svsb, true);
+
+ return count;
+}
+
+proc_fops_rw(svs_debug);
+
+static int svs_dump_proc_show(struct seq_file *m, void *v)
+{
+ struct mtk_svs *svs = (struct mtk_svs *)m->private;
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ unsigned long svs_reg_addr;
+ u32 idx, i, j;
+
+ for (i = 0; i < svsp->efuse_num; i++) {
+ if (svs->efuse[i])
+ seq_printf(m, "M_HW_RES%d = 0x%08x\n",
+ i, svs->efuse[i]);
+ }
+
+ for (i = 0; i < svsp->thermal_efuse_num; i++) {
+ if (svs->thermal_efuse && svs->thermal_efuse[i])
+ seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n",
+ i, svs->thermal_efuse[i]);
+ }
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+
+ if (!svsb->init01_support)
+ continue;
+
+ for (i = SVS_PHASE_INIT01; i <= SVS_PHASE_MON; i++) {
+ seq_printf(m, "Bank_number = %u\n", svsb->hw_id);
+
+ if (i < SVS_PHASE_MON)
+ seq_printf(m, "mode = init%d\n", i + 1);
+ else
+ seq_puts(m, "mode = mon\n");
+
+ for (j = TEMPMONCTL0; j < reg_num; j++) {
+ svs_reg_addr = (unsigned long)(svs->base +
+ svsp->regs[j]);
+ seq_printf(m, "0x%08lx = 0x%08x\n",
+ svs_reg_addr, svsb->reg_data[i][j]);
+ }
+ }
+ }
+
+ return 0;
+}
+
+proc_fops_ro(svs_dump);
+
+static int svs_status_proc_show(struct seq_file *m, void *v)
+{
+ struct svs_bank *svsb = (struct svs_bank *)m->private;
+ struct dev_pm_opp *opp;
+ unsigned long freq;
+ int zone_temp, ret;
+ u32 i;
+
+ ret = svs_get_zone_temperature(svsb, &zone_temp);
+ if (ret)
+ seq_printf(m, "%s: cannot get zone \"%s\" temperature\n",
+ svsb->name, svsb->zone_name);
+ else
+ seq_printf(m, "%s: temperature = %d\n", svsb->name, zone_temp);
+
+ for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
+ opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq);
+ if (IS_ERR(opp)) {
+ seq_printf(m, "%s: error opp entry!!, err = %ld\n",
+ svsb->name, PTR_ERR(opp));
+ return PTR_ERR(opp);
+ }
+
+ seq_printf(m, "opp_freqs[%02u]: %lu, volts[%02u]: %lu, ",
+ i, freq, i, dev_pm_opp_get_voltage(opp));
+ seq_printf(m, "svsb_volts[%02u]: 0x%x, freqs_pct[%02u]: %u\n",
+ i, svsb->volts[i], i, svsb->freqs_pct[i]);
+ }
+
+ return 0;
+}
+
+proc_fops_ro(svs_status);
+
+static int svs_volt_offset_proc_show(struct seq_file *m, void *v)
+{
+ struct svs_bank *svsb = (struct svs_bank *)m->private;
+
+ seq_printf(m, "%d\n", svsb->volt_offset);
+
+ return 0;
+}
+
+static ssize_t svs_volt_offset_proc_write(struct file *file,
+ const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file));
+ char *buf = (char *)__get_free_page(GFP_USER);
+ int ret, volt_offset;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (count >= PAGE_SIZE) {
+ free_page((unsigned long)buf);
+ return -EINVAL;
+ }
+
+ if (copy_from_user(buf, buffer, count)) {
+ free_page((unsigned long)buf);
+ return -EFAULT;
+ }
+
+ buf[count] = '\0';
+
+ if (!kstrtoint(buf, 10, &volt_offset)) {
+ svsb->volt_offset = volt_offset;
+ ret = svs_set_volts(svsb, true);
+ if (ret)
+ return ret;
+ }
+
+ return count;
+}
+
+proc_fops_rw(svs_volt_offset);
+
+static int svs_create_svs_procfs(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ struct proc_dir_entry *svs_dir, *bank_dir;
+ u32 idx, i;
+
+ struct pentry {
+ const char *name;
+ const struct file_operations *fops;
+ };
+
+ struct pentry svs_entries[] = {
+ proc_entry(svs_dump),
+ };
+
+ struct pentry bank_entries[] = {
+ proc_entry(svs_debug),
+ proc_entry(svs_status),
+ proc_entry(svs_volt_offset),
+ };
+
+ svs_dir = proc_mkdir("svs", NULL);
+ if (!svs_dir) {
+ pr_err("mkdir /proc/svs failed\n");
+ return -EPERM;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(svs_entries); i++) {
+ if (!proc_create_data(svs_entries[i].name, 0664,
+ svs_dir, svs_entries[i].fops, svs)) {
+ pr_err("create /proc/svs/%s failed\n",
+ svs_entries[i].name);
+ return -EPERM;
+ }
+ }
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+
+ if (!svsb->init01_support)
+ continue;
+
+ bank_dir = proc_mkdir(svsb->name, svs_dir);
+ if (!bank_dir) {
+ pr_err("mkdir /proc/svs/%s failed\n", svsb->name);
+ return -EPERM;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bank_entries); i++) {
+ if (!proc_create_data(bank_entries[i].name, 0664,
+ bank_dir, bank_entries[i].fops,
+ svsb)) {
+ pr_err("create /proc/svs/%s/%s failed\n",
+ svsb->name, bank_entries[i].name);
+ return -EPERM;
+ }
+ }
+ }
+
+ return 0;
+}
+
+struct svs_bank_ops svs_mt8183_banks_ops = {
+ .set_freqs_pct = svs_set_freqs_pct_v2,
+ .get_vops = svs_get_vops_v2,
+};
+
+struct svs_bank svs_mt8183_banks[4] = {
+ {
+ .of_compatible = "mediatek,mt8183-svs-cpu-little",
+ .sw_id = SVS_CPU_LITTLE,
+ .hw_id = 0,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts4",
+ .buck_name = "vcpu-little",
+ .mtcmos_request = false,
+ .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY,
+ .init01_support = true,
+ .init02_support = true,
+ .mon_mode_support = false,
+ .opp_count = 16,
+ .freq_base = 1989000000,
+ .vboot = 0x30,
+ .volt_step = 6250,
+ .volt_base = 500000,
+ .volt_offset = 0,
+ .vmax = 0x64,
+ .vmin = 0x18,
+ .dthi = 0x1,
+ .dtlo = 0xfe,
+ .det_window = 0xa28,
+ .det_max = 0xffff,
+ .age_config = 0x555555,
+ .agem = 0x0,
+ .dc_config = 0x555555,
+ .dvt_fixed = 0x7,
+ .vco = 0x10,
+ .chkshift = 0x77,
+ .upper_temp_bound = 0x64,
+ .lower_temp_bound = 0xb2,
+ .low_temp_threashold = 25000,
+ .low_temp_offset = 0,
+ .coresel = 0x8fff0000,
+ .systemclk_en = BIT(31),
+ .intst = BIT(0),
+ .ctl0 = 0x00010001,
+ },
+ {
+ .of_compatible = "mediatek,mt8183-svs-cpu-big",
+ .sw_id = SVS_CPU_BIG,
+ .hw_id = 1,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts5",
+ .buck_name = "vcpu-big",
+ .mtcmos_request = false,
+ .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY,
+ .init01_support = true,
+ .init02_support = true,
+ .mon_mode_support = false,
+ .opp_count = 16,
+ .freq_base = 1989000000,
+ .vboot = 0x30,
+ .volt_step = 6250,
+ .volt_base = 500000,
+ .volt_offset = 0,
+ .vmax = 0x58,
+ .vmin = 0x10,
+ .dthi = 0x1,
+ .dtlo = 0xfe,
+ .det_window = 0xa28,
+ .det_max = 0xffff,
+ .age_config = 0x555555,
+ .agem = 0x0,
+ .dc_config = 0x555555,
+ .dvt_fixed = 0x7,
+ .vco = 0x10,
+ .chkshift = 0x77,
+ .upper_temp_bound = 0x64,
+ .lower_temp_bound = 0xb2,
+ .low_temp_threashold = 25000,
+ .low_temp_offset = 0,
+ .coresel = 0x8fff0001,
+ .systemclk_en = BIT(31),
+ .intst = BIT(1),
+ .ctl0 = 0x00000001,
+ },
+ {
+ .of_compatible = "mediatek,mt8183-svs-cci",
+ .sw_id = SVS_CCI,
+ .hw_id = 2,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts4",
+ .buck_name = "vcci",
+ .mtcmos_request = false,
+ .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY,
+ .init01_support = true,
+ .init02_support = true,
+ .mon_mode_support = false,
+ .opp_count = 16,
+ .freq_base = 1196000000,
+ .vboot = 0x30,
+ .volt_step = 6250,
+ .volt_base = 500000,
+ .volt_offset = 0,
+ .vmax = 0x64,
+ .vmin = 0x18,
+ .dthi = 0x1,
+ .dtlo = 0xfe,
+ .det_window = 0xa28,
+ .det_max = 0xffff,
+ .age_config = 0x555555,
+ .agem = 0x0,
+ .dc_config = 0x555555,
+ .dvt_fixed = 0x7,
+ .vco = 0x10,
+ .chkshift = 0x77,
+ .upper_temp_bound = 0x64,
+ .lower_temp_bound = 0xb2,
+ .low_temp_threashold = 25000,
+ .low_temp_offset = 0,
+ .coresel = 0x8fff0002,
+ .systemclk_en = BIT(31),
+ .intst = BIT(2),
+ .ctl0 = 0x00100003,
+ },
+ {
+ .of_compatible = "mediatek,mt8183-svs-gpu",
+ .sw_id = SVS_GPU,
+ .hw_id = 3,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts2",
+ .buck_name = "vgpu",
+ .mtcmos_request = true,
+ .init01_volt_flag = SVS_INIT01_VOLT_INC_ONLY,
+ .init01_support = true,
+ .init02_support = true,
+ .mon_mode_support = true,
+ .opp_count = 16,
+ .freq_base = 900000000,
+ .vboot = 0x30,
+ .volt_step = 6250,
+ .volt_base = 500000,
+ .volt_offset = 0,
+ .vmax = 0x40,
+ .vmin = 0x14,
+ .dthi = 0x1,
+ .dtlo = 0xfe,
+ .det_window = 0xa28,
+ .det_max = 0xffff,
+ .age_config = 0x555555,
+ .agem = 0x0,
+ .dc_config = 0x555555,
+ .dvt_fixed = 0x3,
+ .vco = 0x10,
+ .chkshift = 0x77,
+ .upper_temp_bound = 0x64,
+ .lower_temp_bound = 0xb2,
+ .low_temp_threashold = 25000,
+ .low_temp_offset = 3,
+ .coresel = 0x8fff0003,
+ .systemclk_en = BIT(31),
+ .intst = BIT(3),
+ .ctl0 = 0x00050001,
+ },
+};
+
+static const struct svs_platform svs_mt8183_platform = {
+ .name = "mt8183-svs",
+ .banks = svs_mt8183_banks,
+ .efuse_parsing = svs_mt8183_efuse_parsing,
+ .regs = svs_regs_v2,
+ .fake_efuse = false,
+ .bank_num = 4,
+ .efuse_num = 25,
+ .efuse_check = 2,
+ .thermal_efuse_num = 3,
+};
+
+static const struct of_device_id mtk_svs_of_match[] = {
+ {
+ .compatible = "mediatek,mt8183-svs",
+ .data = &svs_mt8183_platform,
+ }, {
+ /* sentinel */
+ },
+};
+
+static int svs_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_dev_id;
+ struct mtk_svs *svs;
+ int ret;
+ u32 svs_irq;
+
+ svs = devm_kzalloc(&pdev->dev, sizeof(*svs), GFP_KERNEL);
+ if (!svs)
+ return -ENOMEM;
+
+ svs->dev = &pdev->dev;
+ if (!svs->dev->of_node) {
+ pr_err("cannot find device node\n");
+ return -ENODEV;
+ }
+
+ svs->base = of_iomap(svs->dev->of_node, 0);
+ if (IS_ERR(svs->base)) {
+ pr_err("cannot find svs register base\n");
+ return PTR_ERR(svs->base);
+ }
+
+ svs_irq = irq_of_parse_and_map(svs->dev->of_node, 0);
+ ret = devm_request_threaded_irq(svs->dev, svs_irq, NULL, svs_isr,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "mtk-svs", svs);
+ if (ret) {
+ pr_err("register irq(%d) failed: %d\n", svs_irq, ret);
+ return ret;
+ }
+
+ of_dev_id = of_match_node(mtk_svs_of_match, svs->dev->of_node);
+ if (!of_dev_id || !of_dev_id->data)
+ return -EINVAL;
+
+ svs->platform = of_dev_id->data;
+ dev_set_drvdata(svs->dev, svs);
+
+ svs->main_clk = devm_clk_get(svs->dev, "main_clk");
+ if (IS_ERR(svs->main_clk)) {
+ pr_err("failed to get clock: %ld\n", PTR_ERR(svs->main_clk));
+ return PTR_ERR(svs->main_clk);
+ }
+
+ ret = clk_prepare_enable(svs->main_clk);
+ if (ret) {
+ pr_err("cannot enable main_clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = svs_is_support(svs);
+ if (ret)
+ goto svs_probe_fail;
+
+ ret = svs_resource_setup(svs);
+ if (ret)
+ goto svs_probe_fail;
+
+ ret = svs_start(svs);
+ if (ret)
+ goto svs_probe_fail;
+
+ ret = svs_create_svs_procfs(svs);
+ if (ret)
+ goto svs_probe_fail;
+
+ return 0;
+
+svs_probe_fail:
+ clk_disable_unprepare(svs->main_clk);
+
+ return ret;
+}
+
+static const struct dev_pm_ops svs_pm_ops = {
+ .suspend = svs_suspend,
+ .resume = svs_resume,
+};
+
+static struct platform_driver svs_driver = {
+ .probe = svs_probe,
+ .driver = {
+ .name = "mtk-svs",
+ .pm = &svs_pm_ops,
+ .of_match_table = of_match_ptr(mtk_svs_of_match),
+ },
+};
+
+static int __init svs_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&svs_driver);
+ if (ret) {
+ pr_err("svs platform driver register failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+late_initcall_sync(svs_init);
+
+MODULE_DESCRIPTION("MediaTek SVS Driver v1.0");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/mtk_svs.h b/include/linux/power/mtk_svs.h
new file mode 100644
index 000000000000..d5efca8d9dca
--- /dev/null
+++ b/include/linux/power/mtk_svs.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 MediaTek Inc.
+ */
+
+#ifndef __MTK_SVS_H__
+#define __MTK_SVS_H__
+
+#if defined(CONFIG_MTK_SVS)
+unsigned long claim_mtk_svs_lock(void);
+void release_mtk_svs_lock(unsigned long flags);
+#else
+static inline unsigned long claim_mtk_svs_lock(void)
+{
+ return 0;
+}
+
+static inline void release_mtk_svs_lock(unsigned long flags)
+{
+}
+#endif /* CONFIG_MTK_SVS */
+
+#endif /* __MTK_SVS_H__ */
--
2.18.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v5 2/3] arm64: dts: mt8183: add svs device information
From: Roger Lu @ 2019-09-06 10:05 UTC (permalink / raw)
To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
Cc: Mark Rutland, Nishanth Menon, Angus Lin, devicetree, linux-pm,
Roger Lu, linux-kernel, HenryC Chen, yt.lee, Fan Chen,
linux-mediatek, Matthias Brugger, linux-arm-kernel
In-Reply-To: <20190906100514.30803-1-roger.lu@mediatek.com>
Add pmic/clock/irq/efuse setting in svs noce
Signed-off-by: Roger Lu <roger.lu@mediatek.com>
---
arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +++++++++
arch/arm64/boot/dts/mediatek/mt8183.dtsi | 38 +++++++++++++++++++++
2 files changed, 54 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt8183-evb.dts b/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
index d8e555cbb5d3..7c1d6e6a2a85 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
+++ b/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
@@ -135,6 +135,22 @@
};
+&svs_cpu_little {
+ vcpu-little-supply = <&mt6358_vproc12_reg>;
+};
+
+&svs_cpu_big {
+ vcpu-big-supply = <&mt6358_vproc11_reg>;
+};
+
+&svs_cci {
+ vcci-supply = <&mt6358_vproc12_reg>;
+};
+
+&svs_gpu {
+ vgpu-spply = <&mt6358_vgpu_reg>;
+};
+
&uart0 {
status = "okay";
};
diff --git a/arch/arm64/boot/dts/mediatek/mt8183.dtsi b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
index 66aaa07f6cec..48343328bec2 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
@@ -351,6 +351,39 @@
status = "disabled";
};
+ svs: svs@1100b000 {
+ compatible = "mediatek,mt8183-svs";
+ reg = <0 0x1100b000 0 0x1000>;
+ interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&infracfg CLK_INFRA_THERM>;
+ clock-names = "main_clk";
+ nvmem-cells = <&svs_calibration>,
+ <&thermal_calibration>;
+ nvmem-cell-names = "svs-calibration-data",
+ "calibration-data";
+
+ svs_cpu_little: svs_cpu_little {
+ compatible = "mediatek,mt8183-svs-cpu-little";
+ operating-points-v2 = <&cluster0_opp>;
+ };
+
+ svs_cpu_big: svs_cpu_big {
+ compatible = "mediatek,mt8183-svs-cpu-big";
+ operating-points-v2 = <&cluster1_opp>;
+ };
+
+ svs_cci: svs_cci {
+ compatible = "mediatek,mt8183-svs-cci";
+ operating-points-v2 = <&cci_opp>;
+ };
+
+ svs_gpu: svs_gpu {
+ compatible = "mediatek,mt8183-svs-gpu";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
+ operating-points-v2 = <&gpu_opp_table>;
+ };
+ };
+
spi0: spi@1100a000 {
compatible = "mediatek,mt8183-spi";
#address-cells = <1>;
@@ -439,6 +472,11 @@
compatible = "mediatek,mt8183-efuse",
"mediatek,efuse";
reg = <0 0x11f10000 0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ svs_calibration: calib@580 {
+ reg = <0x580 0x64>;
+ };
};
mfgcfg: syscon@13000000 {
--
2.18.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v5 1/3] dt-bindings: soc: add mtk svs dt-bindings
From: Roger Lu @ 2019-09-06 10:05 UTC (permalink / raw)
To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
Cc: Mark Rutland, Nishanth Menon, Angus Lin, devicetree, linux-pm,
Roger Lu, linux-kernel, HenryC Chen, yt.lee, Fan Chen,
linux-mediatek, Matthias Brugger, linux-arm-kernel
In-Reply-To: <20190906100514.30803-1-roger.lu@mediatek.com>
Document the binding for enabling mtk svs on MediaTek SoC.
Signed-off-by: Roger Lu <roger.lu@mediatek.com>
---
.../devicetree/bindings/power/mtk-svs.txt | 88 +++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
diff --git a/Documentation/devicetree/bindings/power/mtk-svs.txt b/Documentation/devicetree/bindings/power/mtk-svs.txt
new file mode 100644
index 000000000000..6a71992ef162
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
@@ -0,0 +1,88 @@
+* Mediatek Smart Voltage Scaling (MTK SVS)
+
+This describes the device tree binding for the MTK SVS controller (bank)
+which helps provide the optimized CPU/GPU/CCI voltages. This device also
+needs thermal data to calculate thermal slope for accurately compensate
+the voltages when temperature change.
+
+Required properties:
+- compatible:
+ - "mediatek,mt8183-svs" : For MT8183 family of SoCs
+- reg: Address range of the MTK SVS controller.
+- interrupts: IRQ for the MTK SVS controller.
+- clocks, clock-names: Clocks needed for the svs controller. required
+ clocks are:
+ "main_clk": Main clock needed for register access
+- nvmem-cells: Phandle to the calibration data provided by a nvmem device.
+- nvmem-cell-names: Should be "svs-calibration-data" and "calibration-data"
+
+Subnodes:
+- svs_cpu_little: SVS bank device node of little CPU
+ compatible: "mediatek,mt8183-svs-cpu-little"
+ operating-points-v2: OPP table hooked by SVS little CPU bank.
+ SVS will optimze this OPP table voltage part.
+ vcpu-little-supply: PMIC buck of little CPU
+- svs_cpu_big: SVS bank device node of big CPU
+ compatible: "mediatek,mt8183-svs-cpu-big"
+ operating-points-v2: OPP table hooked by SVS big CPU bank.
+ SVS will optimze this OPP table voltage part.
+ vcpu-big-supply: PMIC buck of big CPU
+- svs_cci: SVS bank device node of CCI
+ compatible: "mediatek,mt8183-svs-cci"
+ operating-points-v2: OPP table hooked by SVS CCI bank.
+ SVS will optimze this OPP table voltage part.
+ vcci-supply: PMIC buck of CCI
+- svs_gpu: SVS bank device node of GPU
+ compatible: "mediatek,mt8183-svs-gpu"
+ operating-points-v2: OPP table hooked by SVS GPU bank.
+ SVS will optimze this OPP table voltage part.
+ vgpu-spply: PMIC buck of GPU
+
+Example:
+
+ svs: svs@1100b000 {
+ compatible = "mediatek,mt8183-svs";
+ reg = <0 0x1100b000 0 0x1000>;
+ interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW 0>;
+ clocks = <&infracfg CLK_INFRA_THERM>;
+ clock-names = "main_clk";
+ nvmem-cells = <&svs_calibration>, <&thermal_calibration>;
+ nvmem-cell-names = "svs-calibration-data", "calibration-data";
+
+ svs_cpu_little: svs_cpu_little {
+ compatible = "mediatek,mt8183-svs-cpu-little";
+ operating-points-v2 = <&cluster0_opp>;
+ };
+
+ svs_cpu_big: svs_cpu_big {
+ compatible = "mediatek,mt8183-svs-cpu-big";
+ operating-points-v2 = <&cluster1_opp>;
+ };
+
+ svs_cci: svs_cci {
+ compatible = "mediatek,mt8183-svs-cci";
+ operating-points-v2 = <&cci_opp>;
+ };
+
+ svs_gpu: svs_gpu {
+ compatible = "mediatek,mt8183-svs-gpu";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
+ operating-points-v2 = <&gpu_opp_table>;
+ };
+ };
+
+ &svs_cpu_little {
+ vcpu-little-supply = <&mt6358_vproc12_reg>;
+ };
+
+ &svs_cpu_big {
+ vcpu-big-supply = <&mt6358_vproc11_reg>;
+ };
+
+ &svs_cci {
+ vcci-supply = <&mt6358_vproc12_reg>;
+ };
+
+ &svs_gpu {
+ vgpu-spply = <&mt6358_vgpu_reg>;
+ };
--
2.18.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v5 0/3] PM / AVS: SVS: Introduce SVS engine
From: Roger Lu @ 2019-09-06 10:05 UTC (permalink / raw)
To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
Cc: Mark Rutland, Nishanth Menon, Angus Lin, devicetree, linux-pm,
Roger Lu, linux-kernel, HenryC Chen, yt.lee, Fan Chen,
linux-mediatek, Matthias Brugger, linux-arm-kernel
1. SVS driver use OPP adjust event in [1] to update OPP table voltage part.
2. SVS dts node refers to CPU opp table [2] and GPU opp table [3].
3. SVS dts node refers to thermal efuse [4] and PMIC regulator [5].
[1] https://patchwork.kernel.org/patch/11092245/
[2] https://patchwork.kernel.org/patch/10934123/
[3] https://patchwork.kernel.org/patch/11132381/
[4] https://patchwork.kernel.org/patch/11093655/
[5] https://patchwork.kernel.org/patch/11110493/
Roger Lu (3):
dt-bindings: soc: add mtk svs dt-bindings
arm64: dts: mt8183: add svs device information
PM / AVS: SVS: Introduce SVS engine
.../devicetree/bindings/power/mtk-svs.txt | 88 +
arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +
arch/arm64/boot/dts/mediatek/mt8183.dtsi | 38 +
drivers/power/avs/Kconfig | 10 +
drivers/power/avs/Makefile | 1 +
drivers/power/avs/mtk_svs.c | 2075 +++++++++++++++++
include/linux/power/mtk_svs.h | 23 +
7 files changed, 2251 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
create mode 100644 drivers/power/avs/mtk_svs.c
create mode 100644 include/linux/power/mtk_svs.h
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ 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