All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-12 21:50 ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

This adds support for dynamic scaling of the DDR Controller (ddrc) present in
imx8m series. Actual frequency switching is implemented inside TF-A, this
driver wraps the SMC calls and synchronizes the clk tree.

DRAM frequency switching requires clock manipulation but during this operation
DRAM itself is briefly inaccessible so this operation is performed a SMC call
to by TF-A which runs from a SRAM area. Upon returning to linux the clock tree
is updated to correspond to hardware configuration.

This is handled via CLK_GET_RATE_NO_CACHE for dividers but muxes are handled
manually: the driver will prepare/enable the new parents ahead of switching (so
that the expected roots are enabled) and afterwards it will call clk_set_parent
to ensure the parents in clock framework are up-to-date.

This series is atomically useful and roughly similar to devfreq drivers for
tegra and rockchip.

Running at lower dram rates saves power but can affect the functionality of
other blocks in the chip (display, vpu etc). Support for in-kernel constraints
will some separately.

Angus/Martin: You previously attempted to test on purism boards, this updated
version should work without hacks and has no dependencies.

Changes since v4:
* Restore empty _get_dev_status: testing shows this is *NOT* optional. If
absent then switching to simple_ondemand governor will trigger an Oops.
* Keep clk registration on single-line in clk-imx8m* for consistency with rest
of the file.
* Drop explicit "select PM_OPP"
* Check for NULL new_dram_core_parent
* Rename "out_dis_" labels to out_disable_*
* Use dev_warn on imx8m_ddrc_set_freq error paths after SMC call (where
operation is not abandoned).
* More elaborate error messages in imx8m_ddrc_target
* More elaborate checks when fetching clks in imx8m_ddrc_set_freq
* Rename ddrc nodes to memory-controller@* as per devicetree.org "Generic Names
Recommendation"
* Collect reviews
* Defer perf support
Link to v4: https://patchwork.kernel.org/cover/11235685/

Changes since v3:
* Rename to imx8m-ddrc. Similar blocks are present on imx7d and imx8qxp/imx8qm
but soc integration is different.
* Move dt bindings to /memory-controllers/fsl/
* Fix dt validation issues
* Fix imx8mm.dtsi ddrc referencing ddrc_opp_table which is only defined in evk
* Move opps to child of ddrc device node
* Only add imx_ddrc_get_dev_status in perf patch.
* Adjust print messages
Link to v3: https://patchwork.kernel.org/cover/11221935/

Changes since v2:
* Add support for entire imx8m family including imx8mq B0.
* Also mark dram PLLs as CLK_GET_RATE_NO_CACHE (required for imx8mq b0 low OPP)
* Explicitly update dram pll rate at the end of imx_ddrc_set_freq.
* Use do_div in imx-ddrc (kbuild robot)
* Improve explanations around adding CLK_GET_RATE_NO_CACHE to dram clks.
(Stephen Boyd)
* Handle ddrc devfreq-events earlier for fewer probe defers.
* Validate DDRC opp tables versus firmware: supported OPPs depend on board and
SOC revision.
* Move DDRC opp tables to board dts because they can vary based on ram type on
board.
* Verify DDRC rate is changed in clk tree and otherwise report an error.
* Change imx_ddrc_freq.rate to be measure in MT/s and round down from HZ in
imx_ddrc_find_freq instead.
* Split away from NOC scaling and interconnect support.
Link to v2: https://patchwork.kernel.org/cover/11104113/

Changes since v1:
* bindings: Stop using "contains" for "compatible"
* bindings: Set "additionalProperties: false" and document missing stuff.
* Remove (c) from NXP copyright notice
* Fix various checkpatch issues
* Remove unused dram_alt_root clk from imx-ddrc
Link to v1: https://patchwork.kernel.org/cover/11090649/

Changes since RFC v3:
* Implement passive support and set NOC's parent to DDRC
* Drop scaling AHB/AXI for now (NOC/DDRC use most power anyway)
* Stop relying on clk_min_rate
* Split into two devreq drivers (and bindings) because the ddrc is
really a distinct piece of hardware.
* Perform DRAM frequency inside devfreq instead of clk, mostly due to
objections to earlier RFCs for imx8m-dram-clk.
* Fetch info about dram clk parents from firmware instead of
hardcoding in driver. This can more easily support additional rates.
* Link: https://patchwork.kernel.org/cover/11056779/
* Link: https://patchwork.kernel.org/patch/11049429/

Scaling buses can cause problems for devices with realtime bandwith
requirements such as display, the intention is to use the interconnect
framework to make DEV_PM_QOS_MIN_FREQUENCY to devfreq. There are
separate patches for that:

* https://patchwork.kernel.org/cover/11104055/
* https://patchwork.kernel.org/cover/11078671/

Leonard Crestez (5):
  clk: imx8m: Set CLK_GET_RATE_NOCACHE on dram clocks
  clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
  dt-bindings: memory: Add bindings for imx8m ddr controller
  PM / devfreq: Add dynamic scaling for imx8m ddr controller
  arm64: dts: imx8m: Add ddr controller nodes

 .../memory-controllers/fsl/imx8m-ddrc.yaml    |  57 +++
 arch/arm64/boot/dts/freescale/imx8mm-evk.dts  |  18 +
 arch/arm64/boot/dts/freescale/imx8mm.dtsi     |  10 +
 .../boot/dts/freescale/imx8mn-ddr4-evk.dts    |  18 +
 arch/arm64/boot/dts/freescale/imx8mn.dtsi     |  10 +
 arch/arm64/boot/dts/freescale/imx8mq-evk.dts  |  24 +
 arch/arm64/boot/dts/freescale/imx8mq.dtsi     |  10 +
 drivers/clk/imx/clk-imx8mm.c                  |  11 +-
 drivers/clk/imx/clk-imx8mn.c                  |  12 +-
 drivers/clk/imx/clk-imx8mq.c                  |  12 +-
 drivers/clk/imx/clk-pll14xx.c                 |   7 +
 drivers/clk/imx/clk.h                         |   1 +
 drivers/devfreq/Kconfig                       |   9 +
 drivers/devfreq/Makefile                      |   1 +
 drivers/devfreq/imx8m-ddrc.c                  | 460 ++++++++++++++++++
 15 files changed, 650 insertions(+), 10 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
 create mode 100644 drivers/devfreq/imx8m-ddrc.c

-- 
2.17.1


^ permalink raw reply	[flat|nested] 41+ messages in thread

* [PATCH v5 0/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-12 21:50 ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

This adds support for dynamic scaling of the DDR Controller (ddrc) present in
imx8m series. Actual frequency switching is implemented inside TF-A, this
driver wraps the SMC calls and synchronizes the clk tree.

DRAM frequency switching requires clock manipulation but during this operation
DRAM itself is briefly inaccessible so this operation is performed a SMC call
to by TF-A which runs from a SRAM area. Upon returning to linux the clock tree
is updated to correspond to hardware configuration.

This is handled via CLK_GET_RATE_NO_CACHE for dividers but muxes are handled
manually: the driver will prepare/enable the new parents ahead of switching (so
that the expected roots are enabled) and afterwards it will call clk_set_parent
to ensure the parents in clock framework are up-to-date.

This series is atomically useful and roughly similar to devfreq drivers for
tegra and rockchip.

Running at lower dram rates saves power but can affect the functionality of
other blocks in the chip (display, vpu etc). Support for in-kernel constraints
will some separately.

Angus/Martin: You previously attempted to test on purism boards, this updated
version should work without hacks and has no dependencies.

Changes since v4:
* Restore empty _get_dev_status: testing shows this is *NOT* optional. If
absent then switching to simple_ondemand governor will trigger an Oops.
* Keep clk registration on single-line in clk-imx8m* for consistency with rest
of the file.
* Drop explicit "select PM_OPP"
* Check for NULL new_dram_core_parent
* Rename "out_dis_" labels to out_disable_*
* Use dev_warn on imx8m_ddrc_set_freq error paths after SMC call (where
operation is not abandoned).
* More elaborate error messages in imx8m_ddrc_target
* More elaborate checks when fetching clks in imx8m_ddrc_set_freq
* Rename ddrc nodes to memory-controller@* as per devicetree.org "Generic Names
Recommendation"
* Collect reviews
* Defer perf support
Link to v4: https://patchwork.kernel.org/cover/11235685/

Changes since v3:
* Rename to imx8m-ddrc. Similar blocks are present on imx7d and imx8qxp/imx8qm
but soc integration is different.
* Move dt bindings to /memory-controllers/fsl/
* Fix dt validation issues
* Fix imx8mm.dtsi ddrc referencing ddrc_opp_table which is only defined in evk
* Move opps to child of ddrc device node
* Only add imx_ddrc_get_dev_status in perf patch.
* Adjust print messages
Link to v3: https://patchwork.kernel.org/cover/11221935/

Changes since v2:
* Add support for entire imx8m family including imx8mq B0.
* Also mark dram PLLs as CLK_GET_RATE_NO_CACHE (required for imx8mq b0 low OPP)
* Explicitly update dram pll rate at the end of imx_ddrc_set_freq.
* Use do_div in imx-ddrc (kbuild robot)
* Improve explanations around adding CLK_GET_RATE_NO_CACHE to dram clks.
(Stephen Boyd)
* Handle ddrc devfreq-events earlier for fewer probe defers.
* Validate DDRC opp tables versus firmware: supported OPPs depend on board and
SOC revision.
* Move DDRC opp tables to board dts because they can vary based on ram type on
board.
* Verify DDRC rate is changed in clk tree and otherwise report an error.
* Change imx_ddrc_freq.rate to be measure in MT/s and round down from HZ in
imx_ddrc_find_freq instead.
* Split away from NOC scaling and interconnect support.
Link to v2: https://patchwork.kernel.org/cover/11104113/

Changes since v1:
* bindings: Stop using "contains" for "compatible"
* bindings: Set "additionalProperties: false" and document missing stuff.
* Remove (c) from NXP copyright notice
* Fix various checkpatch issues
* Remove unused dram_alt_root clk from imx-ddrc
Link to v1: https://patchwork.kernel.org/cover/11090649/

Changes since RFC v3:
* Implement passive support and set NOC's parent to DDRC
* Drop scaling AHB/AXI for now (NOC/DDRC use most power anyway)
* Stop relying on clk_min_rate
* Split into two devreq drivers (and bindings) because the ddrc is
really a distinct piece of hardware.
* Perform DRAM frequency inside devfreq instead of clk, mostly due to
objections to earlier RFCs for imx8m-dram-clk.
* Fetch info about dram clk parents from firmware instead of
hardcoding in driver. This can more easily support additional rates.
* Link: https://patchwork.kernel.org/cover/11056779/
* Link: https://patchwork.kernel.org/patch/11049429/

Scaling buses can cause problems for devices with realtime bandwith
requirements such as display, the intention is to use the interconnect
framework to make DEV_PM_QOS_MIN_FREQUENCY to devfreq. There are
separate patches for that:

* https://patchwork.kernel.org/cover/11104055/
* https://patchwork.kernel.org/cover/11078671/

Leonard Crestez (5):
  clk: imx8m: Set CLK_GET_RATE_NOCACHE on dram clocks
  clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
  dt-bindings: memory: Add bindings for imx8m ddr controller
  PM / devfreq: Add dynamic scaling for imx8m ddr controller
  arm64: dts: imx8m: Add ddr controller nodes

 .../memory-controllers/fsl/imx8m-ddrc.yaml    |  57 +++
 arch/arm64/boot/dts/freescale/imx8mm-evk.dts  |  18 +
 arch/arm64/boot/dts/freescale/imx8mm.dtsi     |  10 +
 .../boot/dts/freescale/imx8mn-ddr4-evk.dts    |  18 +
 arch/arm64/boot/dts/freescale/imx8mn.dtsi     |  10 +
 arch/arm64/boot/dts/freescale/imx8mq-evk.dts  |  24 +
 arch/arm64/boot/dts/freescale/imx8mq.dtsi     |  10 +
 drivers/clk/imx/clk-imx8mm.c                  |  11 +-
 drivers/clk/imx/clk-imx8mn.c                  |  12 +-
 drivers/clk/imx/clk-imx8mq.c                  |  12 +-
 drivers/clk/imx/clk-pll14xx.c                 |   7 +
 drivers/clk/imx/clk.h                         |   1 +
 drivers/devfreq/Kconfig                       |   9 +
 drivers/devfreq/Makefile                      |   1 +
 drivers/devfreq/imx8m-ddrc.c                  | 460 ++++++++++++++++++
 15 files changed, 650 insertions(+), 10 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
 create mode 100644 drivers/devfreq/imx8m-ddrc.c

-- 
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	[flat|nested] 41+ messages in thread

* [PATCH v5 1/5] clk: imx8m: Set CLK_GET_RATE_NOCACHE on dram clocks
  2019-11-12 21:50 ` Leonard Crestez
@ 2019-11-12 21:50   ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

These clocks are only modified as part of DRAM frequency switches during
which DRAM itself is briefly inaccessible. The switch is performed with
a SMC call to by TF-A which runs from a SRAM area; upon returning to
linux several clocks bits are modified and we need to update them.

For rate bits an easy solution is to just mark with
CLK_GET_RATE_NOCACHE so that new rates are always read back from
registers.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
---
 drivers/clk/imx/clk-imx8mm.c |  9 +++++++--
 drivers/clk/imx/clk-imx8mn.c | 10 ++++++++--
 drivers/clk/imx/clk-imx8mq.c | 12 ++++++++----
 3 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
index 030b15d7c0ce..e2bc3c90d93c 100644
--- a/drivers/clk/imx/clk-imx8mm.c
+++ b/drivers/clk/imx/clk-imx8mm.c
@@ -440,13 +440,18 @@ static int imx8mm_clocks_probe(struct platform_device *pdev)
 
 	/* IPG */
 	clks[IMX8MM_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1);
 	clks[IMX8MM_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1);
 
+	/*
+	 * DRAM clocks are manipulated from TF-A outside clock framework.
+	 * Mark with GET_RATE_NOCACHE to always read div value from hardware
+	 */
+	clks[IMX8MM_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000, CLK_GET_RATE_NOCACHE);
+	clks[IMX8MM_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mm_dram_apb_sels, base + 0xa080, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
+
 	/* IP */
-	clks[IMX8MM_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000);
-	clks[IMX8MM_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mm_dram_apb_sels, base + 0xa080);
 	clks[IMX8MM_CLK_VPU_G1] = imx8m_clk_composite("vpu_g1", imx8mm_vpu_g1_sels, base + 0xa100);
 	clks[IMX8MM_CLK_VPU_G2] = imx8m_clk_composite("vpu_g2", imx8mm_vpu_g2_sels, base + 0xa180);
 	clks[IMX8MM_CLK_DISP_DTRC] = imx8m_clk_composite("disp_dtrc", imx8mm_disp_dtrc_sels, base + 0xa200);
 	clks[IMX8MM_CLK_DISP_DC8000] = imx8m_clk_composite("disp_dc8000", imx8mm_disp_dc8000_sels, base + 0xa280);
 	clks[IMX8MM_CLK_PCIE1_CTRL] = imx8m_clk_composite("pcie1_ctrl", imx8mm_pcie1_ctrl_sels, base + 0xa300);
diff --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c
index 9f5a5a56b45e..de905e278b80 100644
--- a/drivers/clk/imx/clk-imx8mn.c
+++ b/drivers/clk/imx/clk-imx8mn.c
@@ -428,12 +428,18 @@ static int imx8mn_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MN_CLK_AHB] = imx8m_clk_composite_critical("ahb", imx8mn_ahb_sels, base + 0x9000);
 	clks[IMX8MN_CLK_AUDIO_AHB] = imx8m_clk_composite("audio_ahb", imx8mn_audio_ahb_sels, base + 0x9100);
 	clks[IMX8MN_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1);
 	clks[IMX8MN_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1);
 	clks[IMX8MN_CLK_DRAM_CORE] = imx_clk_mux2_flags("dram_core_clk", base + 0x9800, 24, 1, imx8mn_dram_core_sels, ARRAY_SIZE(imx8mn_dram_core_sels), CLK_IS_CRITICAL);
-	clks[IMX8MN_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mn_dram_alt_sels, base + 0xa000);
-	clks[IMX8MN_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mn_dram_apb_sels, base + 0xa080);
+
+	/*
+	 * DRAM clocks are manipulated from TF-A outside clock framework.
+	 * Mark with GET_RATE_NOCACHE to always read div value from hardware
+	 */
+	clks[IMX8MN_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mn_dram_alt_sels, base + 0xa000, CLK_GET_RATE_NOCACHE);
+	clks[IMX8MN_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mn_dram_apb_sels, base + 0xa080, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
+
 	clks[IMX8MN_CLK_DISP_PIXEL] = imx8m_clk_composite("disp_pixel", imx8mn_disp_pixel_sels, base + 0xa500);
 	clks[IMX8MN_CLK_SAI2] = imx8m_clk_composite("sai2", imx8mn_sai2_sels, base + 0xa600);
 	clks[IMX8MN_CLK_SAI3] = imx8m_clk_composite("sai3", imx8mn_sai3_sels, base + 0xa680);
 	clks[IMX8MN_CLK_SAI5] = imx8m_clk_composite("sai5", imx8mn_sai5_sels, base + 0xa780);
 	clks[IMX8MN_CLK_SAI6] = imx8m_clk_composite("sai6", imx8mn_sai6_sels, base + 0xa800);
diff --git a/drivers/clk/imx/clk-imx8mq.c b/drivers/clk/imx/clk-imx8mq.c
index 5f10a606d836..c8ab86fcba7c 100644
--- a/drivers/clk/imx/clk-imx8mq.c
+++ b/drivers/clk/imx/clk-imx8mq.c
@@ -341,11 +341,11 @@ static int imx8mq_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MQ_VIDEO_PLL1_OUT] = imx_clk_gate("video_pll1_out", "video_pll1_bypass", base + 0x10, 21);
 
 	clks[IMX8MQ_SYS1_PLL_OUT] = imx_clk_fixed("sys1_pll_out", 800000000);
 	clks[IMX8MQ_SYS2_PLL_OUT] = imx_clk_fixed("sys2_pll_out", 1000000000);
 	clks[IMX8MQ_SYS3_PLL_OUT] = imx_clk_sccg_pll("sys3_pll_out", sys3_pll_out_sels, ARRAY_SIZE(sys3_pll_out_sels), 0, 0, 0, base + 0x48, CLK_IS_CRITICAL);
-	clks[IMX8MQ_DRAM_PLL_OUT] = imx_clk_sccg_pll("dram_pll_out", dram_pll_out_sels, ARRAY_SIZE(dram_pll_out_sels), 0, 0, 0, base + 0x60, CLK_IS_CRITICAL);
+	clks[IMX8MQ_DRAM_PLL_OUT] = imx_clk_sccg_pll("dram_pll_out", dram_pll_out_sels, ARRAY_SIZE(dram_pll_out_sels), 0, 0, 0, base + 0x60, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
 	clks[IMX8MQ_VIDEO2_PLL_OUT] = imx_clk_sccg_pll("video2_pll_out", video2_pll_out_sels, ARRAY_SIZE(video2_pll_out_sels), 0, 0, 0, base + 0x54, 0);
 
 	/* SYS PLL1 fixed output */
 	clks[IMX8MQ_SYS1_PLL_40M_CG] = imx_clk_gate("sys1_pll_40m_cg", "sys1_pll_out", base + 0x30, 9);
 	clks[IMX8MQ_SYS1_PLL_80M_CG] = imx_clk_gate("sys1_pll_80m_cg", "sys1_pll_out", base + 0x30, 11);
@@ -433,15 +433,19 @@ static int imx8mq_clocks_probe(struct platform_device *pdev)
 
 	/* IPG */
 	clks[IMX8MQ_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1);
 	clks[IMX8MQ_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1);
 
-	/* IP */
+	/*
+	 * DRAM clocks are manipulated from TF-A outside clock framework.
+	 * Mark with GET_RATE_NOCACHE to always read div value from hardware
+	 */
 	clks[IMX8MQ_CLK_DRAM_CORE] = imx_clk_mux2_flags("dram_core_clk", base + 0x9800, 24, 1, imx8mq_dram_core_sels, ARRAY_SIZE(imx8mq_dram_core_sels), CLK_IS_CRITICAL);
+	clks[IMX8MQ_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mq_dram_alt_sels, base + 0xa000, CLK_GET_RATE_NOCACHE);
+	clks[IMX8MQ_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mq_dram_apb_sels, base + 0xa080, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
 
-	clks[IMX8MQ_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mq_dram_alt_sels, base + 0xa000);
-	clks[IMX8MQ_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mq_dram_apb_sels, base + 0xa080);
+	/* IP */
 	clks[IMX8MQ_CLK_VPU_G1] = imx8m_clk_composite("vpu_g1", imx8mq_vpu_g1_sels, base + 0xa100);
 	clks[IMX8MQ_CLK_VPU_G2] = imx8m_clk_composite("vpu_g2", imx8mq_vpu_g2_sels, base + 0xa180);
 	clks[IMX8MQ_CLK_DISP_DTRC] = imx8m_clk_composite("disp_dtrc", imx8mq_disp_dtrc_sels, base + 0xa200);
 	clks[IMX8MQ_CLK_DISP_DC8000] = imx8m_clk_composite("disp_dc8000", imx8mq_disp_dc8000_sels, base + 0xa280);
 	clks[IMX8MQ_CLK_PCIE1_CTRL] = imx8m_clk_composite("pcie1_ctrl", imx8mq_pcie1_ctrl_sels, base + 0xa300);
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH v5 1/5] clk: imx8m: Set CLK_GET_RATE_NOCACHE on dram clocks
@ 2019-11-12 21:50   ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

These clocks are only modified as part of DRAM frequency switches during
which DRAM itself is briefly inaccessible. The switch is performed with
a SMC call to by TF-A which runs from a SRAM area; upon returning to
linux several clocks bits are modified and we need to update them.

For rate bits an easy solution is to just mark with
CLK_GET_RATE_NOCACHE so that new rates are always read back from
registers.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
---
 drivers/clk/imx/clk-imx8mm.c |  9 +++++++--
 drivers/clk/imx/clk-imx8mn.c | 10 ++++++++--
 drivers/clk/imx/clk-imx8mq.c | 12 ++++++++----
 3 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
index 030b15d7c0ce..e2bc3c90d93c 100644
--- a/drivers/clk/imx/clk-imx8mm.c
+++ b/drivers/clk/imx/clk-imx8mm.c
@@ -440,13 +440,18 @@ static int imx8mm_clocks_probe(struct platform_device *pdev)
 
 	/* IPG */
 	clks[IMX8MM_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1);
 	clks[IMX8MM_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1);
 
+	/*
+	 * DRAM clocks are manipulated from TF-A outside clock framework.
+	 * Mark with GET_RATE_NOCACHE to always read div value from hardware
+	 */
+	clks[IMX8MM_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000, CLK_GET_RATE_NOCACHE);
+	clks[IMX8MM_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mm_dram_apb_sels, base + 0xa080, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
+
 	/* IP */
-	clks[IMX8MM_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000);
-	clks[IMX8MM_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mm_dram_apb_sels, base + 0xa080);
 	clks[IMX8MM_CLK_VPU_G1] = imx8m_clk_composite("vpu_g1", imx8mm_vpu_g1_sels, base + 0xa100);
 	clks[IMX8MM_CLK_VPU_G2] = imx8m_clk_composite("vpu_g2", imx8mm_vpu_g2_sels, base + 0xa180);
 	clks[IMX8MM_CLK_DISP_DTRC] = imx8m_clk_composite("disp_dtrc", imx8mm_disp_dtrc_sels, base + 0xa200);
 	clks[IMX8MM_CLK_DISP_DC8000] = imx8m_clk_composite("disp_dc8000", imx8mm_disp_dc8000_sels, base + 0xa280);
 	clks[IMX8MM_CLK_PCIE1_CTRL] = imx8m_clk_composite("pcie1_ctrl", imx8mm_pcie1_ctrl_sels, base + 0xa300);
diff --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c
index 9f5a5a56b45e..de905e278b80 100644
--- a/drivers/clk/imx/clk-imx8mn.c
+++ b/drivers/clk/imx/clk-imx8mn.c
@@ -428,12 +428,18 @@ static int imx8mn_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MN_CLK_AHB] = imx8m_clk_composite_critical("ahb", imx8mn_ahb_sels, base + 0x9000);
 	clks[IMX8MN_CLK_AUDIO_AHB] = imx8m_clk_composite("audio_ahb", imx8mn_audio_ahb_sels, base + 0x9100);
 	clks[IMX8MN_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1);
 	clks[IMX8MN_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1);
 	clks[IMX8MN_CLK_DRAM_CORE] = imx_clk_mux2_flags("dram_core_clk", base + 0x9800, 24, 1, imx8mn_dram_core_sels, ARRAY_SIZE(imx8mn_dram_core_sels), CLK_IS_CRITICAL);
-	clks[IMX8MN_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mn_dram_alt_sels, base + 0xa000);
-	clks[IMX8MN_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mn_dram_apb_sels, base + 0xa080);
+
+	/*
+	 * DRAM clocks are manipulated from TF-A outside clock framework.
+	 * Mark with GET_RATE_NOCACHE to always read div value from hardware
+	 */
+	clks[IMX8MN_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mn_dram_alt_sels, base + 0xa000, CLK_GET_RATE_NOCACHE);
+	clks[IMX8MN_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mn_dram_apb_sels, base + 0xa080, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
+
 	clks[IMX8MN_CLK_DISP_PIXEL] = imx8m_clk_composite("disp_pixel", imx8mn_disp_pixel_sels, base + 0xa500);
 	clks[IMX8MN_CLK_SAI2] = imx8m_clk_composite("sai2", imx8mn_sai2_sels, base + 0xa600);
 	clks[IMX8MN_CLK_SAI3] = imx8m_clk_composite("sai3", imx8mn_sai3_sels, base + 0xa680);
 	clks[IMX8MN_CLK_SAI5] = imx8m_clk_composite("sai5", imx8mn_sai5_sels, base + 0xa780);
 	clks[IMX8MN_CLK_SAI6] = imx8m_clk_composite("sai6", imx8mn_sai6_sels, base + 0xa800);
diff --git a/drivers/clk/imx/clk-imx8mq.c b/drivers/clk/imx/clk-imx8mq.c
index 5f10a606d836..c8ab86fcba7c 100644
--- a/drivers/clk/imx/clk-imx8mq.c
+++ b/drivers/clk/imx/clk-imx8mq.c
@@ -341,11 +341,11 @@ static int imx8mq_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MQ_VIDEO_PLL1_OUT] = imx_clk_gate("video_pll1_out", "video_pll1_bypass", base + 0x10, 21);
 
 	clks[IMX8MQ_SYS1_PLL_OUT] = imx_clk_fixed("sys1_pll_out", 800000000);
 	clks[IMX8MQ_SYS2_PLL_OUT] = imx_clk_fixed("sys2_pll_out", 1000000000);
 	clks[IMX8MQ_SYS3_PLL_OUT] = imx_clk_sccg_pll("sys3_pll_out", sys3_pll_out_sels, ARRAY_SIZE(sys3_pll_out_sels), 0, 0, 0, base + 0x48, CLK_IS_CRITICAL);
-	clks[IMX8MQ_DRAM_PLL_OUT] = imx_clk_sccg_pll("dram_pll_out", dram_pll_out_sels, ARRAY_SIZE(dram_pll_out_sels), 0, 0, 0, base + 0x60, CLK_IS_CRITICAL);
+	clks[IMX8MQ_DRAM_PLL_OUT] = imx_clk_sccg_pll("dram_pll_out", dram_pll_out_sels, ARRAY_SIZE(dram_pll_out_sels), 0, 0, 0, base + 0x60, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
 	clks[IMX8MQ_VIDEO2_PLL_OUT] = imx_clk_sccg_pll("video2_pll_out", video2_pll_out_sels, ARRAY_SIZE(video2_pll_out_sels), 0, 0, 0, base + 0x54, 0);
 
 	/* SYS PLL1 fixed output */
 	clks[IMX8MQ_SYS1_PLL_40M_CG] = imx_clk_gate("sys1_pll_40m_cg", "sys1_pll_out", base + 0x30, 9);
 	clks[IMX8MQ_SYS1_PLL_80M_CG] = imx_clk_gate("sys1_pll_80m_cg", "sys1_pll_out", base + 0x30, 11);
@@ -433,15 +433,19 @@ static int imx8mq_clocks_probe(struct platform_device *pdev)
 
 	/* IPG */
 	clks[IMX8MQ_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1);
 	clks[IMX8MQ_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1);
 
-	/* IP */
+	/*
+	 * DRAM clocks are manipulated from TF-A outside clock framework.
+	 * Mark with GET_RATE_NOCACHE to always read div value from hardware
+	 */
 	clks[IMX8MQ_CLK_DRAM_CORE] = imx_clk_mux2_flags("dram_core_clk", base + 0x9800, 24, 1, imx8mq_dram_core_sels, ARRAY_SIZE(imx8mq_dram_core_sels), CLK_IS_CRITICAL);
+	clks[IMX8MQ_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mq_dram_alt_sels, base + 0xa000, CLK_GET_RATE_NOCACHE);
+	clks[IMX8MQ_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mq_dram_apb_sels, base + 0xa080, CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE);
 
-	clks[IMX8MQ_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mq_dram_alt_sels, base + 0xa000);
-	clks[IMX8MQ_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mq_dram_apb_sels, base + 0xa080);
+	/* IP */
 	clks[IMX8MQ_CLK_VPU_G1] = imx8m_clk_composite("vpu_g1", imx8mq_vpu_g1_sels, base + 0xa100);
 	clks[IMX8MQ_CLK_VPU_G2] = imx8m_clk_composite("vpu_g2", imx8mq_vpu_g2_sels, base + 0xa180);
 	clks[IMX8MQ_CLK_DISP_DTRC] = imx8m_clk_composite("disp_dtrc", imx8mq_disp_dtrc_sels, base + 0xa200);
 	clks[IMX8MQ_CLK_DISP_DC8000] = imx8m_clk_composite("disp_dc8000", imx8mq_disp_dc8000_sels, base + 0xa280);
 	clks[IMX8MQ_CLK_PCIE1_CTRL] = imx8m_clk_composite("pcie1_ctrl", imx8mq_pcie1_ctrl_sels, base + 0xa300);
-- 
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	[flat|nested] 41+ messages in thread

* [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
  2019-11-12 21:50 ` Leonard Crestez
@ 2019-11-12 21:50   ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

DRAM frequency switches are executed in firmware and can change the
configuration of the DRAM PLL outside linux. Mark these CLKs with
CLK_GET_RATE_NOCACHE so we always read back the PLL config registers and
recalculate rates.

In current DRAM frequency tables on 8mm/8mn only the maximum frequency
uses the PLL so it's always configured in the same way. However reading
back the PLL configuration is the correct behavior and allows additional
setpoints in the future.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
---
 drivers/clk/imx/clk-imx8mm.c  | 2 +-
 drivers/clk/imx/clk-imx8mn.c  | 2 +-
 drivers/clk/imx/clk-pll14xx.c | 7 +++++++
 drivers/clk/imx/clk.h         | 1 +
 4 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
index e2bc3c90d93c..9246e89bb5fd 100644
--- a/drivers/clk/imx/clk-imx8mm.c
+++ b/drivers/clk/imx/clk-imx8mm.c
@@ -326,11 +326,11 @@ static int imx8mm_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MM_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel", base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
 
 	clks[IMX8MM_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1", "audio_pll1_ref_sel", base, &imx_1443x_pll);
 	clks[IMX8MM_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2", "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
 	clks[IMX8MM_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1", "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
-	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
+	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
 	clks[IMX8MM_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel", base + 0x64, &imx_1416x_pll);
 	clks[IMX8MM_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel", base + 0x74, &imx_1416x_pll);
 	clks[IMX8MM_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel", base + 0x84, &imx_1416x_pll);
 	clks[IMX8MM_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
 	clks[IMX8MM_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000);
diff --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c
index de905e278b80..4749beab9fc8 100644
--- a/drivers/clk/imx/clk-imx8mn.c
+++ b/drivers/clk/imx/clk-imx8mn.c
@@ -323,11 +323,11 @@ static int imx8mn_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MN_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel", base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
 
 	clks[IMX8MN_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1", "audio_pll1_ref_sel", base, &imx_1443x_pll);
 	clks[IMX8MN_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2", "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
 	clks[IMX8MN_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1", "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
-	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
+	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
 	clks[IMX8MN_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel", base + 0x64, &imx_1416x_pll);
 	clks[IMX8MN_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel", base + 0x74, &imx_1416x_pll);
 	clks[IMX8MN_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel", base + 0x84, &imx_1416x_pll);
 	clks[IMX8MN_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
 	clks[IMX8MN_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000);
diff --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c
index 5c458199060a..a6d31a7262ef 100644
--- a/drivers/clk/imx/clk-pll14xx.c
+++ b/drivers/clk/imx/clk-pll14xx.c
@@ -65,10 +65,17 @@ struct imx_pll14xx_clk imx_1443x_pll = {
 	.type = PLL_1443X,
 	.rate_table = imx_pll1443x_tbl,
 	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
 };
 
+struct imx_pll14xx_clk imx_1443x_dram_pll = {
+	.type = PLL_1443X,
+	.rate_table = imx_pll1443x_tbl,
+	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
+	.flags = CLK_GET_RATE_NOCACHE,
+};
+
 struct imx_pll14xx_clk imx_1416x_pll = {
 	.type = PLL_1416X,
 	.rate_table = imx_pll1416x_tbl,
 	.rate_count = ARRAY_SIZE(imx_pll1416x_tbl),
 };
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index bc5bb6ac8636..81122c9ab842 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -50,10 +50,11 @@ struct imx_pll14xx_clk {
 	int flags;
 };
 
 extern struct imx_pll14xx_clk imx_1416x_pll;
 extern struct imx_pll14xx_clk imx_1443x_pll;
+extern struct imx_pll14xx_clk imx_1443x_dram_pll;
 
 #define imx_clk_cpu(name, parent_name, div, mux, pll, step) \
 	imx_clk_hw_cpu(name, parent_name, div, mux, pll, step)->clk
 
 #define clk_register_gate2(dev, name, parent_name, flags, reg, bit_idx, \
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
@ 2019-11-12 21:50   ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

DRAM frequency switches are executed in firmware and can change the
configuration of the DRAM PLL outside linux. Mark these CLKs with
CLK_GET_RATE_NOCACHE so we always read back the PLL config registers and
recalculate rates.

In current DRAM frequency tables on 8mm/8mn only the maximum frequency
uses the PLL so it's always configured in the same way. However reading
back the PLL configuration is the correct behavior and allows additional
setpoints in the future.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
---
 drivers/clk/imx/clk-imx8mm.c  | 2 +-
 drivers/clk/imx/clk-imx8mn.c  | 2 +-
 drivers/clk/imx/clk-pll14xx.c | 7 +++++++
 drivers/clk/imx/clk.h         | 1 +
 4 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
index e2bc3c90d93c..9246e89bb5fd 100644
--- a/drivers/clk/imx/clk-imx8mm.c
+++ b/drivers/clk/imx/clk-imx8mm.c
@@ -326,11 +326,11 @@ static int imx8mm_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MM_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel", base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
 
 	clks[IMX8MM_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1", "audio_pll1_ref_sel", base, &imx_1443x_pll);
 	clks[IMX8MM_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2", "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
 	clks[IMX8MM_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1", "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
-	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
+	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
 	clks[IMX8MM_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel", base + 0x64, &imx_1416x_pll);
 	clks[IMX8MM_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel", base + 0x74, &imx_1416x_pll);
 	clks[IMX8MM_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel", base + 0x84, &imx_1416x_pll);
 	clks[IMX8MM_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
 	clks[IMX8MM_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000);
diff --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c
index de905e278b80..4749beab9fc8 100644
--- a/drivers/clk/imx/clk-imx8mn.c
+++ b/drivers/clk/imx/clk-imx8mn.c
@@ -323,11 +323,11 @@ static int imx8mn_clocks_probe(struct platform_device *pdev)
 	clks[IMX8MN_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel", base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
 
 	clks[IMX8MN_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1", "audio_pll1_ref_sel", base, &imx_1443x_pll);
 	clks[IMX8MN_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2", "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
 	clks[IMX8MN_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1", "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
-	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
+	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll", "dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
 	clks[IMX8MN_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel", base + 0x64, &imx_1416x_pll);
 	clks[IMX8MN_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel", base + 0x74, &imx_1416x_pll);
 	clks[IMX8MN_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel", base + 0x84, &imx_1416x_pll);
 	clks[IMX8MN_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
 	clks[IMX8MN_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000);
diff --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c
index 5c458199060a..a6d31a7262ef 100644
--- a/drivers/clk/imx/clk-pll14xx.c
+++ b/drivers/clk/imx/clk-pll14xx.c
@@ -65,10 +65,17 @@ struct imx_pll14xx_clk imx_1443x_pll = {
 	.type = PLL_1443X,
 	.rate_table = imx_pll1443x_tbl,
 	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
 };
 
+struct imx_pll14xx_clk imx_1443x_dram_pll = {
+	.type = PLL_1443X,
+	.rate_table = imx_pll1443x_tbl,
+	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
+	.flags = CLK_GET_RATE_NOCACHE,
+};
+
 struct imx_pll14xx_clk imx_1416x_pll = {
 	.type = PLL_1416X,
 	.rate_table = imx_pll1416x_tbl,
 	.rate_count = ARRAY_SIZE(imx_pll1416x_tbl),
 };
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index bc5bb6ac8636..81122c9ab842 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -50,10 +50,11 @@ struct imx_pll14xx_clk {
 	int flags;
 };
 
 extern struct imx_pll14xx_clk imx_1416x_pll;
 extern struct imx_pll14xx_clk imx_1443x_pll;
+extern struct imx_pll14xx_clk imx_1443x_dram_pll;
 
 #define imx_clk_cpu(name, parent_name, div, mux, pll, step) \
 	imx_clk_hw_cpu(name, parent_name, div, mux, pll, step)->clk
 
 #define clk_register_gate2(dev, name, parent_name, flags, reg, bit_idx, \
-- 
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	[flat|nested] 41+ messages in thread

* [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
  2019-11-12 21:50 ` Leonard Crestez
@ 2019-11-12 21:50   ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

Add devicetree bindings for the i.MX DDR Controller on imx8m series
chips. It supports dynamic frequency switching between multiple data
rates and this is exposed to Linux via the devfreq subsystem.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml

diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
new file mode 100644
index 000000000000..7c98e3509f75
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/memory-controllers/fsl/imx8m-ddrc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: i.MX8M DDR Controller
+
+maintainers:
+  - Leonard Crestez <leonard.crestez@nxp.com>
+
+properties:
+  compatible:
+    items:
+      - enum:
+        - fsl,imx8mn-ddrc
+        - fsl,imx8mm-ddrc
+        - fsl,imx8mq-ddrc
+      - const: fsl,imx8m-ddrc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 4
+
+  clock-names:
+    items:
+      - const: core
+      - const: pll
+      - const: alt
+      - const: apb
+
+  operating-points-v2: true
+  opp-table: true
+
+required:
+  - reg
+  - compatible
+  - clocks
+  - clock-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8mm-clock.h>
+    ddrc: memory-controller@3d400000 {
+        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
+        reg = <0x3d400000 0x400000>;
+        clock-names = "core", "pll", "alt", "apb";
+        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
+                 <&clk IMX8MM_DRAM_PLL>,
+                 <&clk IMX8MM_CLK_DRAM_ALT>,
+                 <&clk IMX8MM_CLK_DRAM_APB>;
+        operating-points-v2 = <&ddrc_opp_table>;
+    };
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
@ 2019-11-12 21:50   ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

Add devicetree bindings for the i.MX DDR Controller on imx8m series
chips. It supports dynamic frequency switching between multiple data
rates and this is exposed to Linux via the devfreq subsystem.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml

diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
new file mode 100644
index 000000000000..7c98e3509f75
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/memory-controllers/fsl/imx8m-ddrc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: i.MX8M DDR Controller
+
+maintainers:
+  - Leonard Crestez <leonard.crestez@nxp.com>
+
+properties:
+  compatible:
+    items:
+      - enum:
+        - fsl,imx8mn-ddrc
+        - fsl,imx8mm-ddrc
+        - fsl,imx8mq-ddrc
+      - const: fsl,imx8m-ddrc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 4
+
+  clock-names:
+    items:
+      - const: core
+      - const: pll
+      - const: alt
+      - const: apb
+
+  operating-points-v2: true
+  opp-table: true
+
+required:
+  - reg
+  - compatible
+  - clocks
+  - clock-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8mm-clock.h>
+    ddrc: memory-controller@3d400000 {
+        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
+        reg = <0x3d400000 0x400000>;
+        clock-names = "core", "pll", "alt", "apb";
+        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
+                 <&clk IMX8MM_DRAM_PLL>,
+                 <&clk IMX8MM_CLK_DRAM_ALT>,
+                 <&clk IMX8MM_CLK_DRAM_APB>;
+        operating-points-v2 = <&ddrc_opp_table>;
+    };
-- 
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	[flat|nested] 41+ messages in thread

* [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-12 21:50 ` Leonard Crestez
@ 2019-11-12 21:50   ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
frequency switching is implemented inside TF-A, this driver wraps the
SMC calls and synchronizes the clk tree.

The DRAM clocks on imx8m have the following structure (abridged):

 +----------+       |\            +------+
 | dram_pll |-------|M| dram_core |      |
 +----------+       |U|---------->| D    |
                 /--|X|           |  D   |
   dram_alt_root |  |/            |   R  |
                 |                |    C |
            +---------+           |      |
            |FIX DIV/4|           |      |
            +---------+           |      |
  composite:     |                |      |
 +----------+    |                |      |
 | dram_alt |----/                |      |
 +----------+                     |      |
 | dram_apb |-------------------->|      |
 +----------+                     +------+

The dram_pll is used for higher rates and dram_alt is used for lower
rates. The dram_alt and dram_apb clocks are "imx composite" and their
parent can also be modified.

This driver will prepare/enable the new parents ahead of switching (so
that the expected roots are enabled) and afterwards it will call
clk_set_parent to ensure the parents in clock framework are up-to-date.

The driver relies on dram_pll dram_alt and dram_apb being marked with
CLK_GET_RATE_NOCACHE for rate updates.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 drivers/devfreq/Kconfig      |   9 +
 drivers/devfreq/Makefile     |   1 +
 drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
 3 files changed, 470 insertions(+)
 create mode 100644 drivers/devfreq/imx8m-ddrc.c

diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index 066e6c4efaa2..923a6132e741 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
 	  Each memory bus group could contain many memoby bus block. It reads
 	  PPMU counters of memory controllers by using DEVFREQ-event device
 	  and adjusts the operating frequencies and voltages with OPP support.
 	  This does not yet operate with optimal voltages.
 
+config ARM_IMX8M_DDRC_DEVFREQ
+	tristate "i.MX8M DDRC DEVFREQ Driver"
+	depends on ARCH_MXC || COMPILE_TEST
+	select DEVFREQ_GOV_SIMPLE_ONDEMAND
+	select DEVFREQ_GOV_USERSPACE
+	help
+	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
+	  adjusting DRAM frequency.
+
 config ARM_TEGRA_DEVFREQ
 	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
 	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
 		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
 		ARCH_TEGRA_210_SOC || \
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 338ae8440db6..3eb4d5e6635c 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
 obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
 obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
 
 # DEVFREQ Drivers
 obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
+obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
 obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
 obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
 obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
 
 # DEVFREQ Event Drivers
diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
new file mode 100644
index 000000000000..62abb9b79d8a
--- /dev/null
+++ b/drivers/devfreq/imx8m-ddrc.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/devfreq.h>
+#include <linux/pm_opp.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/arm-smccc.h>
+
+#define IMX_SIP_DDR_DVFS			0xc2000004
+
+/* Values starting from 0 switch to specific frequency */
+#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
+
+/* Deprecated after moving IRQ handling to ATF */
+#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
+
+/* Query available frequencies. */
+#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
+#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
+
+/*
+ * This should be in a 1:1 mapping with devicetree OPPs but
+ * firmware provides additional info.
+ */
+struct imx8m_ddrc_freq {
+	unsigned long rate;
+	unsigned long smcarg;
+	int dram_core_parent_index;
+	int dram_alt_parent_index;
+	int dram_apb_parent_index;
+};
+
+/* Hardware limitation */
+#define IMX8M_DDRC_MAX_FREQ_COUNT 4
+
+/*
+ * i.MX8M DRAM Controller clocks have the following structure (abridged):
+ *
+ * +----------+       |\            +------+
+ * | dram_pll |-------|M| dram_core |      |
+ * +----------+       |U|---------->| D    |
+ *                 /--|X|           |  D   |
+ *   dram_alt_root |  |/            |   R  |
+ *                 |                |    C |
+ *            +---------+           |      |
+ *            |FIX DIV/4|           |      |
+ *            +---------+           |      |
+ *  composite:     |                |      |
+ * +----------+    |                |      |
+ * | dram_alt |----/                |      |
+ * +----------+                     |      |
+ * | dram_apb |-------------------->|      |
+ * +----------+                     +------+
+ *
+ * The dram_pll is used for higher rates and dram_alt is used for lower rates.
+ *
+ * Frequency switching is implemented in TF-A (via SMC call) and can change the
+ * configuration of the clocks, including mux parents. The dram_alt and
+ * dram_apb clocks are "imx composite" and their parent can change too.
+ *
+ * We need to prepare/enable the new mux parents head of switching and update
+ * their information afterwards.
+ */
+struct imx8m_ddrc {
+	struct devfreq_dev_profile profile;
+	struct devfreq *devfreq;
+
+	/* For frequency switching: */
+	struct clk *dram_core;
+	struct clk *dram_pll;
+	struct clk *dram_alt;
+	struct clk *dram_apb;
+
+	int freq_count;
+	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
+};
+
+static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
+						    unsigned long rate)
+{
+	struct imx8m_ddrc_freq *freq;
+	int i;
+
+	/*
+	 * Firmware reports values in MT/s, so we round-down from Hz
+	 * Rounding is extra generous to ensure a match.
+	 */
+	rate = DIV_ROUND_CLOSEST(rate, 250000);
+	for (i = 0; i < priv->freq_count; ++i) {
+		freq = &priv->freq_table[i];
+		if (freq->rate == rate ||
+				freq->rate + 1 == rate ||
+				freq->rate - 1 == rate)
+			return freq;
+	}
+
+	return NULL;
+}
+
+static void imx8m_ddrc_smc_set_freq(int target_freq)
+{
+	struct arm_smccc_res res;
+	u32 online_cpus = 0;
+	int cpu;
+
+	local_irq_disable();
+
+	for_each_online_cpu(cpu)
+		online_cpus |= (1 << (cpu * 8));
+
+	/* change the ddr freqency */
+	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
+			0, 0, 0, 0, 0, &res);
+
+	local_irq_enable();
+}
+
+struct clk *clk_get_parent_by_index(struct clk *clk, int index)
+{
+	struct clk_hw *hw;
+
+	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
+
+	return hw ? hw->clk : NULL;
+}
+
+static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct clk *new_dram_core_parent;
+	struct clk *new_dram_alt_parent;
+	struct clk *new_dram_apb_parent;
+	int ret;
+
+	/*
+	 * Fetch new parents
+	 *
+	 * new_dram_alt_parent and new_dram_apb_parent are optional but
+	 * new_dram_core_parent is not.
+	 */
+	new_dram_core_parent = clk_get_parent_by_index(
+			priv->dram_core, freq->dram_core_parent_index - 1);
+	if (!new_dram_core_parent) {
+		dev_err(dev, "failed to fetch new dram_core parent\n");
+		return -EINVAL;
+	}
+	if (freq->dram_alt_parent_index) {
+		new_dram_alt_parent = clk_get_parent_by_index(
+				priv->dram_alt,
+				freq->dram_alt_parent_index - 1);
+		if (!new_dram_alt_parent) {
+			dev_err(dev, "failed to fetch new dram_alt parent\n");
+			return -EINVAL;
+		}
+	} else
+		new_dram_alt_parent = NULL;
+
+	if (freq->dram_alt_parent_index) {
+		new_dram_apb_parent = clk_get_parent_by_index(
+				priv->dram_apb, freq->dram_apb_parent_index - 1);
+		if (!new_dram_alt_parent) {
+			dev_err(dev, "failed to fetch new dram_apb parent\n");
+			return -EINVAL;
+		}
+	} else
+		new_dram_apb_parent = NULL;
+
+	/* increase reference counts and ensure clks are ON before switch */
+	ret = clk_prepare_enable(new_dram_core_parent);
+	if (ret) {
+		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
+		goto out;
+	}
+	ret = clk_prepare_enable(new_dram_alt_parent);
+	if (ret) {
+		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
+		goto out_disable_core_parent;
+	}
+	ret = clk_prepare_enable(new_dram_apb_parent);
+	if (ret) {
+		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
+		goto out_disable_alt_parent;
+	}
+
+	imx8m_ddrc_smc_set_freq(freq->smcarg);
+
+	/* update parents in clk tree after switch. */
+	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
+	if (ret)
+		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
+	if (new_dram_alt_parent) {
+		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
+		if (ret)
+			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
+	}
+	if (new_dram_apb_parent) {
+		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
+		if (ret)
+			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
+	}
+
+	/*
+	 * Explicitly refresh dram PLL rate.
+	 *
+	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
+	 * automatically refreshed when clk_get_rate is called on children.
+	 */
+	clk_get_rate(priv->dram_pll);
+
+	/*
+	 * clk_set_parent transfer the reference count from old parent.
+	 * now we drop extra reference counts used during the switch
+	 */
+	clk_disable_unprepare(new_dram_apb_parent);
+out_disable_alt_parent:
+	clk_disable_unprepare(new_dram_alt_parent);
+out_disable_core_parent:
+	clk_disable_unprepare(new_dram_core_parent);
+out:
+	return ret;
+}
+
+static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct imx8m_ddrc_freq *freq_info;
+	struct dev_pm_opp *new_opp;
+	unsigned long old_freq, new_freq;
+	int ret;
+
+	new_opp = devfreq_recommended_opp(dev, freq, flags);
+	if (IS_ERR(new_opp)) {
+		ret = PTR_ERR(new_opp);
+		dev_err(dev, "failed to get recommended opp: %d\n", ret);
+		return ret;
+	}
+	dev_pm_opp_put(new_opp);
+
+	old_freq = clk_get_rate(priv->dram_core);
+	if (*freq == old_freq)
+		return 0;
+
+	freq_info = imx8m_ddrc_find_freq(priv, *freq);
+	if (!freq_info)
+		return -EINVAL;
+
+	/*
+	 * Read back the clk rate to verify switch was correct and so that
+	 * we can report it on all error paths.
+	 */
+	ret = imx8m_ddrc_set_freq(dev, freq_info);
+
+	new_freq = clk_get_rate(priv->dram_core);
+	if (ret)
+		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
+			old_freq, *freq, ret, new_freq);
+	else if (*freq != new_freq)
+		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
+			old_freq, *freq, new_freq);
+	else
+		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
+			*freq, old_freq);
+
+	return ret;
+}
+
+static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+
+	*freq = clk_get_rate(priv->dram_core);
+
+	return 0;
+}
+
+static int imx8m_ddrc_get_dev_status(struct device *dev,
+				     struct devfreq_dev_status *stat)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+
+	stat->busy_time = 0;
+	stat->total_time = 0;
+	stat->current_frequency = clk_get_rate(priv->dram_core);
+
+	return 0;
+}
+
+static int imx8m_ddrc_init_freq_info(struct device *dev)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct arm_smccc_res res;
+	int index;
+
+	/* An error here means DDR DVFS API not supported by firmware */
+	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
+			0, 0, 0, 0, 0, 0, &res);
+	priv->freq_count = res.a0;
+	if (priv->freq_count <= 0 ||
+			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
+		return -ENODEV;
+
+	for (index = 0; index < priv->freq_count; ++index) {
+		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
+
+		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
+			      index, 0, 0, 0, 0, 0, &res);
+		/* Result should be strictly positive */
+		if ((long)res.a0 <= 0)
+			return -ENODEV;
+
+		freq->rate = res.a0;
+		freq->smcarg = index;
+		freq->dram_core_parent_index = res.a1;
+		freq->dram_alt_parent_index = res.a2;
+		freq->dram_apb_parent_index = res.a3;
+
+		/* dram_core has 2 options: dram_pll or dram_alt_root */
+		if (freq->dram_core_parent_index != 1 &&
+				freq->dram_core_parent_index != 2)
+			return -ENODEV;
+		/* dram_apb and dram_alt have exactly 8 possible parents */
+		if (freq->dram_alt_parent_index > 8 ||
+				freq->dram_apb_parent_index > 8)
+			return -ENODEV;
+		/* dram_core from alt requires explicit dram_alt parent */
+		if (freq->dram_core_parent_index == 2 &&
+				freq->dram_alt_parent_index == 0)
+			return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int imx8m_ddrc_check_opps(struct device *dev)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct imx8m_ddrc_freq *freq_info;
+	struct dev_pm_opp *opp;
+	unsigned long freq;
+
+	/* Enumerate DT OPPs and disable those not supported by firmware */
+	freq = ULONG_MAX;
+	while (true) {
+		opp = dev_pm_opp_find_freq_floor(dev, &freq);
+		if (opp == ERR_PTR(-ERANGE))
+			break;
+		if (IS_ERR(opp)) {
+			dev_err(dev, "Failed enumerating OPPs: %ld\n",
+				PTR_ERR(opp));
+			return PTR_ERR(opp);
+		}
+		dev_pm_opp_put(opp);
+
+		freq_info = imx8m_ddrc_find_freq(priv, freq);
+		if (!freq_info) {
+			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
+					freq, DIV_ROUND_CLOSEST(freq, 250000));
+			dev_pm_opp_disable(dev, freq);
+		}
+
+		freq--;
+	}
+
+	return 0;
+}
+
+static void imx8m_ddrc_exit(struct device *dev)
+{
+	dev_pm_opp_of_remove_table(dev);
+}
+
+static int imx8m_ddrc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imx8m_ddrc *priv;
+	const char *gov = DEVFREQ_GOV_USERSPACE;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = imx8m_ddrc_init_freq_info(dev);
+	if (ret) {
+		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
+		return ret;
+	}
+
+	priv->dram_core = devm_clk_get(dev, "core");
+	priv->dram_pll = devm_clk_get(dev, "pll");
+	priv->dram_alt = devm_clk_get(dev, "alt");
+	priv->dram_apb = devm_clk_get(dev, "apb");
+	if (IS_ERR(priv->dram_core) ||
+		IS_ERR(priv->dram_pll) ||
+		IS_ERR(priv->dram_alt) ||
+		IS_ERR(priv->dram_apb)) {
+		ret = PTR_ERR(priv->devfreq);
+		dev_err(dev, "failed to fetch clocks: %d\n", ret);
+		return ret;
+	}
+
+	ret = dev_pm_opp_of_add_table(dev);
+	if (ret < 0) {
+		dev_err(dev, "failed to get OPP table\n");
+		return ret;
+	}
+
+	ret = imx8m_ddrc_check_opps(dev);
+	if (ret < 0)
+		goto err;
+
+	priv->profile.polling_ms = 1000;
+	priv->profile.target = imx8m_ddrc_target;
+	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
+	priv->profile.exit = imx8m_ddrc_exit;
+	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
+	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
+
+	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
+						gov, NULL);
+	if (IS_ERR(priv->devfreq)) {
+		ret = PTR_ERR(priv->devfreq);
+		dev_err(dev, "failed to add devfreq device: %d\n", ret);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	dev_pm_opp_of_remove_table(dev);
+	return ret;
+}
+
+static const struct of_device_id imx8m_ddrc_of_match[] = {
+	{ .compatible = "fsl,imx8m-ddrc", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
+
+static struct platform_driver imx8m_ddrc_platdrv = {
+	.probe		= imx8m_ddrc_probe,
+	.driver = {
+		.name	= "imx8m-ddrc-devfreq",
+		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
+	},
+};
+module_platform_driver(imx8m_ddrc_platdrv);
+
+MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
+MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-12 21:50   ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
frequency switching is implemented inside TF-A, this driver wraps the
SMC calls and synchronizes the clk tree.

The DRAM clocks on imx8m have the following structure (abridged):

 +----------+       |\            +------+
 | dram_pll |-------|M| dram_core |      |
 +----------+       |U|---------->| D    |
                 /--|X|           |  D   |
   dram_alt_root |  |/            |   R  |
                 |                |    C |
            +---------+           |      |
            |FIX DIV/4|           |      |
            +---------+           |      |
  composite:     |                |      |
 +----------+    |                |      |
 | dram_alt |----/                |      |
 +----------+                     |      |
 | dram_apb |-------------------->|      |
 +----------+                     +------+

The dram_pll is used for higher rates and dram_alt is used for lower
rates. The dram_alt and dram_apb clocks are "imx composite" and their
parent can also be modified.

This driver will prepare/enable the new parents ahead of switching (so
that the expected roots are enabled) and afterwards it will call
clk_set_parent to ensure the parents in clock framework are up-to-date.

The driver relies on dram_pll dram_alt and dram_apb being marked with
CLK_GET_RATE_NOCACHE for rate updates.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 drivers/devfreq/Kconfig      |   9 +
 drivers/devfreq/Makefile     |   1 +
 drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
 3 files changed, 470 insertions(+)
 create mode 100644 drivers/devfreq/imx8m-ddrc.c

diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index 066e6c4efaa2..923a6132e741 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
 	  Each memory bus group could contain many memoby bus block. It reads
 	  PPMU counters of memory controllers by using DEVFREQ-event device
 	  and adjusts the operating frequencies and voltages with OPP support.
 	  This does not yet operate with optimal voltages.
 
+config ARM_IMX8M_DDRC_DEVFREQ
+	tristate "i.MX8M DDRC DEVFREQ Driver"
+	depends on ARCH_MXC || COMPILE_TEST
+	select DEVFREQ_GOV_SIMPLE_ONDEMAND
+	select DEVFREQ_GOV_USERSPACE
+	help
+	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
+	  adjusting DRAM frequency.
+
 config ARM_TEGRA_DEVFREQ
 	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
 	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
 		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
 		ARCH_TEGRA_210_SOC || \
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 338ae8440db6..3eb4d5e6635c 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
 obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
 obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
 
 # DEVFREQ Drivers
 obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
+obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
 obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
 obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
 obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
 
 # DEVFREQ Event Drivers
diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
new file mode 100644
index 000000000000..62abb9b79d8a
--- /dev/null
+++ b/drivers/devfreq/imx8m-ddrc.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/devfreq.h>
+#include <linux/pm_opp.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/arm-smccc.h>
+
+#define IMX_SIP_DDR_DVFS			0xc2000004
+
+/* Values starting from 0 switch to specific frequency */
+#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
+
+/* Deprecated after moving IRQ handling to ATF */
+#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
+
+/* Query available frequencies. */
+#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
+#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
+
+/*
+ * This should be in a 1:1 mapping with devicetree OPPs but
+ * firmware provides additional info.
+ */
+struct imx8m_ddrc_freq {
+	unsigned long rate;
+	unsigned long smcarg;
+	int dram_core_parent_index;
+	int dram_alt_parent_index;
+	int dram_apb_parent_index;
+};
+
+/* Hardware limitation */
+#define IMX8M_DDRC_MAX_FREQ_COUNT 4
+
+/*
+ * i.MX8M DRAM Controller clocks have the following structure (abridged):
+ *
+ * +----------+       |\            +------+
+ * | dram_pll |-------|M| dram_core |      |
+ * +----------+       |U|---------->| D    |
+ *                 /--|X|           |  D   |
+ *   dram_alt_root |  |/            |   R  |
+ *                 |                |    C |
+ *            +---------+           |      |
+ *            |FIX DIV/4|           |      |
+ *            +---------+           |      |
+ *  composite:     |                |      |
+ * +----------+    |                |      |
+ * | dram_alt |----/                |      |
+ * +----------+                     |      |
+ * | dram_apb |-------------------->|      |
+ * +----------+                     +------+
+ *
+ * The dram_pll is used for higher rates and dram_alt is used for lower rates.
+ *
+ * Frequency switching is implemented in TF-A (via SMC call) and can change the
+ * configuration of the clocks, including mux parents. The dram_alt and
+ * dram_apb clocks are "imx composite" and their parent can change too.
+ *
+ * We need to prepare/enable the new mux parents head of switching and update
+ * their information afterwards.
+ */
+struct imx8m_ddrc {
+	struct devfreq_dev_profile profile;
+	struct devfreq *devfreq;
+
+	/* For frequency switching: */
+	struct clk *dram_core;
+	struct clk *dram_pll;
+	struct clk *dram_alt;
+	struct clk *dram_apb;
+
+	int freq_count;
+	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
+};
+
+static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
+						    unsigned long rate)
+{
+	struct imx8m_ddrc_freq *freq;
+	int i;
+
+	/*
+	 * Firmware reports values in MT/s, so we round-down from Hz
+	 * Rounding is extra generous to ensure a match.
+	 */
+	rate = DIV_ROUND_CLOSEST(rate, 250000);
+	for (i = 0; i < priv->freq_count; ++i) {
+		freq = &priv->freq_table[i];
+		if (freq->rate == rate ||
+				freq->rate + 1 == rate ||
+				freq->rate - 1 == rate)
+			return freq;
+	}
+
+	return NULL;
+}
+
+static void imx8m_ddrc_smc_set_freq(int target_freq)
+{
+	struct arm_smccc_res res;
+	u32 online_cpus = 0;
+	int cpu;
+
+	local_irq_disable();
+
+	for_each_online_cpu(cpu)
+		online_cpus |= (1 << (cpu * 8));
+
+	/* change the ddr freqency */
+	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
+			0, 0, 0, 0, 0, &res);
+
+	local_irq_enable();
+}
+
+struct clk *clk_get_parent_by_index(struct clk *clk, int index)
+{
+	struct clk_hw *hw;
+
+	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
+
+	return hw ? hw->clk : NULL;
+}
+
+static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct clk *new_dram_core_parent;
+	struct clk *new_dram_alt_parent;
+	struct clk *new_dram_apb_parent;
+	int ret;
+
+	/*
+	 * Fetch new parents
+	 *
+	 * new_dram_alt_parent and new_dram_apb_parent are optional but
+	 * new_dram_core_parent is not.
+	 */
+	new_dram_core_parent = clk_get_parent_by_index(
+			priv->dram_core, freq->dram_core_parent_index - 1);
+	if (!new_dram_core_parent) {
+		dev_err(dev, "failed to fetch new dram_core parent\n");
+		return -EINVAL;
+	}
+	if (freq->dram_alt_parent_index) {
+		new_dram_alt_parent = clk_get_parent_by_index(
+				priv->dram_alt,
+				freq->dram_alt_parent_index - 1);
+		if (!new_dram_alt_parent) {
+			dev_err(dev, "failed to fetch new dram_alt parent\n");
+			return -EINVAL;
+		}
+	} else
+		new_dram_alt_parent = NULL;
+
+	if (freq->dram_alt_parent_index) {
+		new_dram_apb_parent = clk_get_parent_by_index(
+				priv->dram_apb, freq->dram_apb_parent_index - 1);
+		if (!new_dram_alt_parent) {
+			dev_err(dev, "failed to fetch new dram_apb parent\n");
+			return -EINVAL;
+		}
+	} else
+		new_dram_apb_parent = NULL;
+
+	/* increase reference counts and ensure clks are ON before switch */
+	ret = clk_prepare_enable(new_dram_core_parent);
+	if (ret) {
+		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
+		goto out;
+	}
+	ret = clk_prepare_enable(new_dram_alt_parent);
+	if (ret) {
+		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
+		goto out_disable_core_parent;
+	}
+	ret = clk_prepare_enable(new_dram_apb_parent);
+	if (ret) {
+		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
+		goto out_disable_alt_parent;
+	}
+
+	imx8m_ddrc_smc_set_freq(freq->smcarg);
+
+	/* update parents in clk tree after switch. */
+	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
+	if (ret)
+		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
+	if (new_dram_alt_parent) {
+		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
+		if (ret)
+			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
+	}
+	if (new_dram_apb_parent) {
+		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
+		if (ret)
+			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
+	}
+
+	/*
+	 * Explicitly refresh dram PLL rate.
+	 *
+	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
+	 * automatically refreshed when clk_get_rate is called on children.
+	 */
+	clk_get_rate(priv->dram_pll);
+
+	/*
+	 * clk_set_parent transfer the reference count from old parent.
+	 * now we drop extra reference counts used during the switch
+	 */
+	clk_disable_unprepare(new_dram_apb_parent);
+out_disable_alt_parent:
+	clk_disable_unprepare(new_dram_alt_parent);
+out_disable_core_parent:
+	clk_disable_unprepare(new_dram_core_parent);
+out:
+	return ret;
+}
+
+static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct imx8m_ddrc_freq *freq_info;
+	struct dev_pm_opp *new_opp;
+	unsigned long old_freq, new_freq;
+	int ret;
+
+	new_opp = devfreq_recommended_opp(dev, freq, flags);
+	if (IS_ERR(new_opp)) {
+		ret = PTR_ERR(new_opp);
+		dev_err(dev, "failed to get recommended opp: %d\n", ret);
+		return ret;
+	}
+	dev_pm_opp_put(new_opp);
+
+	old_freq = clk_get_rate(priv->dram_core);
+	if (*freq == old_freq)
+		return 0;
+
+	freq_info = imx8m_ddrc_find_freq(priv, *freq);
+	if (!freq_info)
+		return -EINVAL;
+
+	/*
+	 * Read back the clk rate to verify switch was correct and so that
+	 * we can report it on all error paths.
+	 */
+	ret = imx8m_ddrc_set_freq(dev, freq_info);
+
+	new_freq = clk_get_rate(priv->dram_core);
+	if (ret)
+		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
+			old_freq, *freq, ret, new_freq);
+	else if (*freq != new_freq)
+		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
+			old_freq, *freq, new_freq);
+	else
+		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
+			*freq, old_freq);
+
+	return ret;
+}
+
+static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+
+	*freq = clk_get_rate(priv->dram_core);
+
+	return 0;
+}
+
+static int imx8m_ddrc_get_dev_status(struct device *dev,
+				     struct devfreq_dev_status *stat)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+
+	stat->busy_time = 0;
+	stat->total_time = 0;
+	stat->current_frequency = clk_get_rate(priv->dram_core);
+
+	return 0;
+}
+
+static int imx8m_ddrc_init_freq_info(struct device *dev)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct arm_smccc_res res;
+	int index;
+
+	/* An error here means DDR DVFS API not supported by firmware */
+	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
+			0, 0, 0, 0, 0, 0, &res);
+	priv->freq_count = res.a0;
+	if (priv->freq_count <= 0 ||
+			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
+		return -ENODEV;
+
+	for (index = 0; index < priv->freq_count; ++index) {
+		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
+
+		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
+			      index, 0, 0, 0, 0, 0, &res);
+		/* Result should be strictly positive */
+		if ((long)res.a0 <= 0)
+			return -ENODEV;
+
+		freq->rate = res.a0;
+		freq->smcarg = index;
+		freq->dram_core_parent_index = res.a1;
+		freq->dram_alt_parent_index = res.a2;
+		freq->dram_apb_parent_index = res.a3;
+
+		/* dram_core has 2 options: dram_pll or dram_alt_root */
+		if (freq->dram_core_parent_index != 1 &&
+				freq->dram_core_parent_index != 2)
+			return -ENODEV;
+		/* dram_apb and dram_alt have exactly 8 possible parents */
+		if (freq->dram_alt_parent_index > 8 ||
+				freq->dram_apb_parent_index > 8)
+			return -ENODEV;
+		/* dram_core from alt requires explicit dram_alt parent */
+		if (freq->dram_core_parent_index == 2 &&
+				freq->dram_alt_parent_index == 0)
+			return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int imx8m_ddrc_check_opps(struct device *dev)
+{
+	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
+	struct imx8m_ddrc_freq *freq_info;
+	struct dev_pm_opp *opp;
+	unsigned long freq;
+
+	/* Enumerate DT OPPs and disable those not supported by firmware */
+	freq = ULONG_MAX;
+	while (true) {
+		opp = dev_pm_opp_find_freq_floor(dev, &freq);
+		if (opp == ERR_PTR(-ERANGE))
+			break;
+		if (IS_ERR(opp)) {
+			dev_err(dev, "Failed enumerating OPPs: %ld\n",
+				PTR_ERR(opp));
+			return PTR_ERR(opp);
+		}
+		dev_pm_opp_put(opp);
+
+		freq_info = imx8m_ddrc_find_freq(priv, freq);
+		if (!freq_info) {
+			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
+					freq, DIV_ROUND_CLOSEST(freq, 250000));
+			dev_pm_opp_disable(dev, freq);
+		}
+
+		freq--;
+	}
+
+	return 0;
+}
+
+static void imx8m_ddrc_exit(struct device *dev)
+{
+	dev_pm_opp_of_remove_table(dev);
+}
+
+static int imx8m_ddrc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imx8m_ddrc *priv;
+	const char *gov = DEVFREQ_GOV_USERSPACE;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = imx8m_ddrc_init_freq_info(dev);
+	if (ret) {
+		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
+		return ret;
+	}
+
+	priv->dram_core = devm_clk_get(dev, "core");
+	priv->dram_pll = devm_clk_get(dev, "pll");
+	priv->dram_alt = devm_clk_get(dev, "alt");
+	priv->dram_apb = devm_clk_get(dev, "apb");
+	if (IS_ERR(priv->dram_core) ||
+		IS_ERR(priv->dram_pll) ||
+		IS_ERR(priv->dram_alt) ||
+		IS_ERR(priv->dram_apb)) {
+		ret = PTR_ERR(priv->devfreq);
+		dev_err(dev, "failed to fetch clocks: %d\n", ret);
+		return ret;
+	}
+
+	ret = dev_pm_opp_of_add_table(dev);
+	if (ret < 0) {
+		dev_err(dev, "failed to get OPP table\n");
+		return ret;
+	}
+
+	ret = imx8m_ddrc_check_opps(dev);
+	if (ret < 0)
+		goto err;
+
+	priv->profile.polling_ms = 1000;
+	priv->profile.target = imx8m_ddrc_target;
+	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
+	priv->profile.exit = imx8m_ddrc_exit;
+	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
+	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
+
+	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
+						gov, NULL);
+	if (IS_ERR(priv->devfreq)) {
+		ret = PTR_ERR(priv->devfreq);
+		dev_err(dev, "failed to add devfreq device: %d\n", ret);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	dev_pm_opp_of_remove_table(dev);
+	return ret;
+}
+
+static const struct of_device_id imx8m_ddrc_of_match[] = {
+	{ .compatible = "fsl,imx8m-ddrc", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
+
+static struct platform_driver imx8m_ddrc_platdrv = {
+	.probe		= imx8m_ddrc_probe,
+	.driver = {
+		.name	= "imx8m-ddrc-devfreq",
+		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
+	},
+};
+module_platform_driver(imx8m_ddrc_platdrv);
+
+MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
+MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
+MODULE_LICENSE("GPL v2");
-- 
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	[flat|nested] 41+ messages in thread

* [PATCH v5 5/5] arm64: dts: imx8m: Add ddr controller nodes
  2019-11-12 21:50 ` Leonard Crestez
@ 2019-11-12 21:50   ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

This is used by the imx-ddrc devfreq driver to implement dynamic
frequency scaling of DRAM.

Support for proactive scaling via interconnect will come later. The
high-performance bus masters which need that (display, vpu, gpu) are
mostly not yet enabled in upstream anyway.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 arch/arm64/boot/dts/freescale/imx8mm-evk.dts  | 18 ++++++++++++++
 arch/arm64/boot/dts/freescale/imx8mm.dtsi     | 10 ++++++++
 .../boot/dts/freescale/imx8mn-ddr4-evk.dts    | 18 ++++++++++++++
 arch/arm64/boot/dts/freescale/imx8mn.dtsi     | 10 ++++++++
 arch/arm64/boot/dts/freescale/imx8mq-evk.dts  | 24 +++++++++++++++++++
 arch/arm64/boot/dts/freescale/imx8mq.dtsi     | 10 ++++++++
 6 files changed, 90 insertions(+)

diff --git a/arch/arm64/boot/dts/freescale/imx8mm-evk.dts b/arch/arm64/boot/dts/freescale/imx8mm-evk.dts
index 28ab17a277bb..ecf0d385c164 100644
--- a/arch/arm64/boot/dts/freescale/imx8mm-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mm-evk.dts
@@ -75,10 +75,28 @@
 
 &A53_0 {
 	cpu-supply = <&buck2_reg>;
 };
 
+&ddrc {
+	operating-points-v2 = <&ddrc_opp_table>;
+
+	ddrc_opp_table: opp-table {
+		compatible = "operating-points-v2";
+
+		opp-25M {
+			opp-hz = /bits/ 64 <25000000>;
+		};
+		opp-100M {
+			opp-hz = /bits/ 64 <100000000>;
+		};
+		opp-750M {
+			opp-hz = /bits/ 64 <750000000>;
+		};
+	};
+};
+
 &fec1 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_fec1>;
 	phy-mode = "rgmii-id";
 	phy-handle = <&ethphy0>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mm.dtsi b/arch/arm64/boot/dts/freescale/imx8mm.dtsi
index 6edbdfe2d0d7..3d4802375715 100644
--- a/arch/arm64/boot/dts/freescale/imx8mm.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mm.dtsi
@@ -856,10 +856,20 @@
 			#interrupt-cells = <3>;
 			interrupt-controller;
 			interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		ddrc: memory-controller@3d400000 {
+			compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
+			reg = <0x3d400000 0x400000>;
+			clock-names = "core", "pll", "alt", "apb";
+			clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
+				 <&clk IMX8MM_DRAM_PLL>,
+				 <&clk IMX8MM_CLK_DRAM_ALT>,
+				 <&clk IMX8MM_CLK_DRAM_APB>;
+		};
+
 		ddr-pmu@3d800000 {
 			compatible = "fsl,imx8mm-ddr-pmu", "fsl,imx8m-ddr-pmu";
 			reg = <0x3d800000 0x400000>;
 			interrupt-parent = <&gic>;
 			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts b/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts
index 071949412caf..b051c927c11e 100644
--- a/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts
@@ -15,10 +15,28 @@
 
 &A53_0 {
 	cpu-supply = <&buck2_reg>;
 };
 
+&ddrc {
+	operating-points-v2 = <&ddrc_opp_table>;
+
+	ddrc_opp_table: opp-table {
+		compatible = "operating-points-v2";
+
+		opp-25M {
+			opp-hz = /bits/ 64 <25000000>;
+		};
+		opp-100M {
+			opp-hz = /bits/ 64 <100000000>;
+		};
+		opp-600M {
+			opp-hz = /bits/ 64 <600000000>;
+		};
+	};
+};
+
 &i2c1 {
 	pmic@4b {
 		compatible = "rohm,bd71847";
 		reg = <0x4b>;
 		pinctrl-0 = <&pinctrl_pmic>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mn.dtsi b/arch/arm64/boot/dts/freescale/imx8mn.dtsi
index e91625063f8e..3a79fdddc72b 100644
--- a/arch/arm64/boot/dts/freescale/imx8mn.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mn.dtsi
@@ -757,10 +757,20 @@
 			#interrupt-cells = <3>;
 			interrupt-controller;
 			interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		ddrc: memory-controller@3d400000 {
+			compatible = "fsl,imx8mn-ddrc", "fsl,imx8m-ddrc";
+			reg = <0x3d400000 0x400000>;
+			clock-names = "core", "pll", "alt", "apb";
+			clocks = <&clk IMX8MN_CLK_DRAM_CORE>,
+				 <&clk IMX8MN_DRAM_PLL>,
+				 <&clk IMX8MN_CLK_DRAM_ALT>,
+				 <&clk IMX8MN_CLK_DRAM_APB>;
+		};
+
 		ddr-pmu@3d800000 {
 			compatible = "fsl,imx8mn-ddr-pmu", "fsl,imx8m-ddr-pmu";
 			reg = <0x3d800000 0x400000>;
 			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
 		};
diff --git a/arch/arm64/boot/dts/freescale/imx8mq-evk.dts b/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
index c36685916683..ee6dc5f07622 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
@@ -103,10 +103,34 @@
 
 &A53_3 {
 	cpu-supply = <&buck2_reg>;
 };
 
+&ddrc {
+	operating-points-v2 = <&ddrc_opp_table>;
+
+	ddrc_opp_table: opp-table {
+		compatible = "operating-points-v2";
+
+		opp-25M {
+			opp-hz = /bits/ 64 <25000000>;
+		};
+		opp-100M {
+			opp-hz = /bits/ 64 <100000000>;
+		};
+		/*
+		 * On imx8mq B0 PLL can't be bypassed so low bus is 166M
+		 */
+		opp-166M {
+			opp-hz = /bits/ 64 <166935483>;
+		};
+		opp-800M {
+			opp-hz = /bits/ 64 <800000000>;
+		};
+	};
+};
+
 &fec1 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_fec1>;
 	phy-mode = "rgmii-id";
 	phy-handle = <&ethphy0>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
index 7f9319452b58..d1fcf9887f8b 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
@@ -1111,10 +1111,20 @@
 			interrupt-controller;
 			interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
 			interrupt-parent = <&gic>;
 		};
 
+		ddrc: memory-controller@3d400000 {
+			compatible = "fsl,imx8mq-ddrc", "fsl,imx8m-ddrc";
+			reg = <0x3d400000 0x400000>;
+			clock-names = "core", "pll", "alt", "apb";
+			clocks = <&clk IMX8MQ_CLK_DRAM_CORE>,
+				 <&clk IMX8MQ_DRAM_PLL_OUT>,
+				 <&clk IMX8MQ_CLK_DRAM_ALT>,
+				 <&clk IMX8MQ_CLK_DRAM_APB>;
+		};
+
 		ddr-pmu@3d800000 {
 			compatible = "fsl,imx8mq-ddr-pmu", "fsl,imx8m-ddr-pmu";
 			reg = <0x3d800000 0x400000>;
 			interrupt-parent = <&gic>;
 			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH v5 5/5] arm64: dts: imx8m: Add ddr controller nodes
@ 2019-11-12 21:50   ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-12 21:50 UTC (permalink / raw)
  To: Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

This is used by the imx-ddrc devfreq driver to implement dynamic
frequency scaling of DRAM.

Support for proactive scaling via interconnect will come later. The
high-performance bus masters which need that (display, vpu, gpu) are
mostly not yet enabled in upstream anyway.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 arch/arm64/boot/dts/freescale/imx8mm-evk.dts  | 18 ++++++++++++++
 arch/arm64/boot/dts/freescale/imx8mm.dtsi     | 10 ++++++++
 .../boot/dts/freescale/imx8mn-ddr4-evk.dts    | 18 ++++++++++++++
 arch/arm64/boot/dts/freescale/imx8mn.dtsi     | 10 ++++++++
 arch/arm64/boot/dts/freescale/imx8mq-evk.dts  | 24 +++++++++++++++++++
 arch/arm64/boot/dts/freescale/imx8mq.dtsi     | 10 ++++++++
 6 files changed, 90 insertions(+)

diff --git a/arch/arm64/boot/dts/freescale/imx8mm-evk.dts b/arch/arm64/boot/dts/freescale/imx8mm-evk.dts
index 28ab17a277bb..ecf0d385c164 100644
--- a/arch/arm64/boot/dts/freescale/imx8mm-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mm-evk.dts
@@ -75,10 +75,28 @@
 
 &A53_0 {
 	cpu-supply = <&buck2_reg>;
 };
 
+&ddrc {
+	operating-points-v2 = <&ddrc_opp_table>;
+
+	ddrc_opp_table: opp-table {
+		compatible = "operating-points-v2";
+
+		opp-25M {
+			opp-hz = /bits/ 64 <25000000>;
+		};
+		opp-100M {
+			opp-hz = /bits/ 64 <100000000>;
+		};
+		opp-750M {
+			opp-hz = /bits/ 64 <750000000>;
+		};
+	};
+};
+
 &fec1 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_fec1>;
 	phy-mode = "rgmii-id";
 	phy-handle = <&ethphy0>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mm.dtsi b/arch/arm64/boot/dts/freescale/imx8mm.dtsi
index 6edbdfe2d0d7..3d4802375715 100644
--- a/arch/arm64/boot/dts/freescale/imx8mm.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mm.dtsi
@@ -856,10 +856,20 @@
 			#interrupt-cells = <3>;
 			interrupt-controller;
 			interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		ddrc: memory-controller@3d400000 {
+			compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
+			reg = <0x3d400000 0x400000>;
+			clock-names = "core", "pll", "alt", "apb";
+			clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
+				 <&clk IMX8MM_DRAM_PLL>,
+				 <&clk IMX8MM_CLK_DRAM_ALT>,
+				 <&clk IMX8MM_CLK_DRAM_APB>;
+		};
+
 		ddr-pmu@3d800000 {
 			compatible = "fsl,imx8mm-ddr-pmu", "fsl,imx8m-ddr-pmu";
 			reg = <0x3d800000 0x400000>;
 			interrupt-parent = <&gic>;
 			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts b/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts
index 071949412caf..b051c927c11e 100644
--- a/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mn-ddr4-evk.dts
@@ -15,10 +15,28 @@
 
 &A53_0 {
 	cpu-supply = <&buck2_reg>;
 };
 
+&ddrc {
+	operating-points-v2 = <&ddrc_opp_table>;
+
+	ddrc_opp_table: opp-table {
+		compatible = "operating-points-v2";
+
+		opp-25M {
+			opp-hz = /bits/ 64 <25000000>;
+		};
+		opp-100M {
+			opp-hz = /bits/ 64 <100000000>;
+		};
+		opp-600M {
+			opp-hz = /bits/ 64 <600000000>;
+		};
+	};
+};
+
 &i2c1 {
 	pmic@4b {
 		compatible = "rohm,bd71847";
 		reg = <0x4b>;
 		pinctrl-0 = <&pinctrl_pmic>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mn.dtsi b/arch/arm64/boot/dts/freescale/imx8mn.dtsi
index e91625063f8e..3a79fdddc72b 100644
--- a/arch/arm64/boot/dts/freescale/imx8mn.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mn.dtsi
@@ -757,10 +757,20 @@
 			#interrupt-cells = <3>;
 			interrupt-controller;
 			interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		ddrc: memory-controller@3d400000 {
+			compatible = "fsl,imx8mn-ddrc", "fsl,imx8m-ddrc";
+			reg = <0x3d400000 0x400000>;
+			clock-names = "core", "pll", "alt", "apb";
+			clocks = <&clk IMX8MN_CLK_DRAM_CORE>,
+				 <&clk IMX8MN_DRAM_PLL>,
+				 <&clk IMX8MN_CLK_DRAM_ALT>,
+				 <&clk IMX8MN_CLK_DRAM_APB>;
+		};
+
 		ddr-pmu@3d800000 {
 			compatible = "fsl,imx8mn-ddr-pmu", "fsl,imx8m-ddr-pmu";
 			reg = <0x3d800000 0x400000>;
 			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
 		};
diff --git a/arch/arm64/boot/dts/freescale/imx8mq-evk.dts b/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
index c36685916683..ee6dc5f07622 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
@@ -103,10 +103,34 @@
 
 &A53_3 {
 	cpu-supply = <&buck2_reg>;
 };
 
+&ddrc {
+	operating-points-v2 = <&ddrc_opp_table>;
+
+	ddrc_opp_table: opp-table {
+		compatible = "operating-points-v2";
+
+		opp-25M {
+			opp-hz = /bits/ 64 <25000000>;
+		};
+		opp-100M {
+			opp-hz = /bits/ 64 <100000000>;
+		};
+		/*
+		 * On imx8mq B0 PLL can't be bypassed so low bus is 166M
+		 */
+		opp-166M {
+			opp-hz = /bits/ 64 <166935483>;
+		};
+		opp-800M {
+			opp-hz = /bits/ 64 <800000000>;
+		};
+	};
+};
+
 &fec1 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_fec1>;
 	phy-mode = "rgmii-id";
 	phy-handle = <&ethphy0>;
diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
index 7f9319452b58..d1fcf9887f8b 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
@@ -1111,10 +1111,20 @@
 			interrupt-controller;
 			interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
 			interrupt-parent = <&gic>;
 		};
 
+		ddrc: memory-controller@3d400000 {
+			compatible = "fsl,imx8mq-ddrc", "fsl,imx8m-ddrc";
+			reg = <0x3d400000 0x400000>;
+			clock-names = "core", "pll", "alt", "apb";
+			clocks = <&clk IMX8MQ_CLK_DRAM_CORE>,
+				 <&clk IMX8MQ_DRAM_PLL_OUT>,
+				 <&clk IMX8MQ_CLK_DRAM_ALT>,
+				 <&clk IMX8MQ_CLK_DRAM_APB>;
+		};
+
 		ddr-pmu@3d800000 {
 			compatible = "fsl,imx8mq-ddr-pmu", "fsl,imx8m-ddr-pmu";
 			reg = <0x3d800000 0x400000>;
 			interrupt-parent = <&gic>;
 			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
-- 
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	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-12 21:50   ` Leonard Crestez
@ 2019-11-13  2:30     ` Chanwoo Choi
  -1 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-13  2:30 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

Hi Leonard,

On 11/13/19 6:50 AM, Leonard Crestez wrote:
> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
> frequency switching is implemented inside TF-A, this driver wraps the
> SMC calls and synchronizes the clk tree.
> 
> The DRAM clocks on imx8m have the following structure (abridged):
> 
>  +----------+       |\            +------+
>  | dram_pll |-------|M| dram_core |      |
>  +----------+       |U|---------->| D    |
>                  /--|X|           |  D   |
>    dram_alt_root |  |/            |   R  |
>                  |                |    C |
>             +---------+           |      |
>             |FIX DIV/4|           |      |
>             +---------+           |      |
>   composite:     |                |      |
>  +----------+    |                |      |
>  | dram_alt |----/                |      |
>  +----------+                     |      |
>  | dram_apb |-------------------->|      |
>  +----------+                     +------+
> 
> The dram_pll is used for higher rates and dram_alt is used for lower
> rates. The dram_alt and dram_apb clocks are "imx composite" and their
> parent can also be modified.
> 
> This driver will prepare/enable the new parents ahead of switching (so
> that the expected roots are enabled) and afterwards it will call
> clk_set_parent to ensure the parents in clock framework are up-to-date.
> 
> The driver relies on dram_pll dram_alt and dram_apb being marked with
> CLK_GET_RATE_NOCACHE for rate updates.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  drivers/devfreq/Kconfig      |   9 +
>  drivers/devfreq/Makefile     |   1 +
>  drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>  3 files changed, 470 insertions(+)
>  create mode 100644 drivers/devfreq/imx8m-ddrc.c
> 
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index 066e6c4efaa2..923a6132e741 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>  	  Each memory bus group could contain many memoby bus block. It reads
>  	  PPMU counters of memory controllers by using DEVFREQ-event device
>  	  and adjusts the operating frequencies and voltages with OPP support.
>  	  This does not yet operate with optimal voltages.
>  
> +config ARM_IMX8M_DDRC_DEVFREQ
> +	tristate "i.MX8M DDRC DEVFREQ Driver"
> +	depends on ARCH_MXC || COMPILE_TEST
> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> +	select DEVFREQ_GOV_USERSPACE
> +	help
> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
> +	  adjusting DRAM frequency.
> +
>  config ARM_TEGRA_DEVFREQ
>  	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>  	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>  		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>  		ARCH_TEGRA_210_SOC || \
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index 338ae8440db6..3eb4d5e6635c 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>  
>  # DEVFREQ Drivers
>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>  obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>  obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>  obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>  
>  # DEVFREQ Event Drivers
> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
> new file mode 100644
> index 000000000000..62abb9b79d8a
> --- /dev/null
> +++ b/drivers/devfreq/imx8m-ddrc.c
> @@ -0,0 +1,460 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 NXP
> + */
> +
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/devfreq.h>
> +#include <linux/pm_opp.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/arm-smccc.h>
> +
> +#define IMX_SIP_DDR_DVFS			0xc2000004
> +
> +/* Values starting from 0 switch to specific frequency */
> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
> +
> +/* Deprecated after moving IRQ handling to ATF */
> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
> +
> +/* Query available frequencies. */
> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
> +
> +/*
> + * This should be in a 1:1 mapping with devicetree OPPs but
> + * firmware provides additional info.
> + */
> +struct imx8m_ddrc_freq {
> +	unsigned long rate;
> +	unsigned long smcarg;
> +	int dram_core_parent_index;
> +	int dram_alt_parent_index;
> +	int dram_apb_parent_index;
> +};
> +
> +/* Hardware limitation */
> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
> +
> +/*
> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
> + *
> + * +----------+       |\            +------+
> + * | dram_pll |-------|M| dram_core |      |
> + * +----------+       |U|---------->| D    |
> + *                 /--|X|           |  D   |
> + *   dram_alt_root |  |/            |   R  |
> + *                 |                |    C |
> + *            +---------+           |      |
> + *            |FIX DIV/4|           |      |
> + *            +---------+           |      |
> + *  composite:     |                |      |
> + * +----------+    |                |      |
> + * | dram_alt |----/                |      |
> + * +----------+                     |      |
> + * | dram_apb |-------------------->|      |
> + * +----------+                     +------+
> + *
> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
> + *
> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
> + * configuration of the clocks, including mux parents. The dram_alt and
> + * dram_apb clocks are "imx composite" and their parent can change too.
> + *
> + * We need to prepare/enable the new mux parents head of switching and update
> + * their information afterwards.
> + */
> +struct imx8m_ddrc {
> +	struct devfreq_dev_profile profile;
> +	struct devfreq *devfreq;
> +
> +	/* For frequency switching: */
> +	struct clk *dram_core;
> +	struct clk *dram_pll;
> +	struct clk *dram_alt;
> +	struct clk *dram_apb;
> +
> +	int freq_count;
> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
> +};
> +
> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
> +						    unsigned long rate)
> +{
> +	struct imx8m_ddrc_freq *freq;
> +	int i;
> +
> +	/*
> +	 * Firmware reports values in MT/s, so we round-down from Hz
> +	 * Rounding is extra generous to ensure a match.
> +	 */
> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
> +	for (i = 0; i < priv->freq_count; ++i) {
> +		freq = &priv->freq_table[i];
> +		if (freq->rate == rate ||
> +				freq->rate + 1 == rate ||
> +				freq->rate - 1 == rate)
> +			return freq;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void imx8m_ddrc_smc_set_freq(int target_freq)
> +{
> +	struct arm_smccc_res res;
> +	u32 online_cpus = 0;
> +	int cpu;
> +
> +	local_irq_disable();
> +
> +	for_each_online_cpu(cpu)
> +		online_cpus |= (1 << (cpu * 8));
> +
> +	/* change the ddr freqency */
> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
> +			0, 0, 0, 0, 0, &res);
> +
> +	local_irq_enable();
> +}
> +
> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
> +{
> +	struct clk_hw *hw;
> +
> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
> +
> +	return hw ? hw->clk : NULL;
> +}
> +
> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct clk *new_dram_core_parent;
> +	struct clk *new_dram_alt_parent;
> +	struct clk *new_dram_apb_parent;
> +	int ret;
> +
> +	/*
> +	 * Fetch new parents
> +	 *
> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
> +	 * new_dram_core_parent is not.
> +	 */
> +	new_dram_core_parent = clk_get_parent_by_index(
> +			priv->dram_core, freq->dram_core_parent_index - 1);
> +	if (!new_dram_core_parent) {
> +		dev_err(dev, "failed to fetch new dram_core parent\n");
> +		return -EINVAL;
> +	}
> +	if (freq->dram_alt_parent_index) {
> +		new_dram_alt_parent = clk_get_parent_by_index(
> +				priv->dram_alt,
> +				freq->dram_alt_parent_index - 1);
> +		if (!new_dram_alt_parent) {
> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
> +			return -EINVAL;
> +		}
> +	} else
> +		new_dram_alt_parent = NULL;
> +
> +	if (freq->dram_alt_parent_index) {
> +		new_dram_apb_parent = clk_get_parent_by_index(
> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
> +		if (!new_dram_alt_parent) {
> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
> +			return -EINVAL;
> +		}
> +	} else
> +		new_dram_apb_parent = NULL;
> +
> +	/* increase reference counts and ensure clks are ON before switch */
> +	ret = clk_prepare_enable(new_dram_core_parent);
> +	if (ret) {
> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);

s/failed enable/failed to enable

> +		goto out;
> +	}
> +	ret = clk_prepare_enable(new_dram_alt_parent);
> +	if (ret) {
> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);

s/failed enable/failed to enable

> +		goto out_disable_core_parent;
> +	}
> +	ret = clk_prepare_enable(new_dram_apb_parent);
> +	if (ret) {
> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);

s/failed enable/failed to enable

> +		goto out_disable_alt_parent;
> +	}
> +
> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
> +
> +	/* update parents in clk tree after switch. */
> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
> +	if (ret)
> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);

s/failed set/failed to set

> +	if (new_dram_alt_parent) {
> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
> +		if (ret)
> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);

s/failed set/failed to set

> +	}
> +	if (new_dram_apb_parent) {
> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
> +		if (ret)
> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);

s/failed set/failed to set

> +	}
> +
> +	/*
> +	 * Explicitly refresh dram PLL rate.
> +	 *
> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
> +	 * automatically refreshed when clk_get_rate is called on children.
> +	 */
> +	clk_get_rate(priv->dram_pll);
> +
> +	/*
> +	 * clk_set_parent transfer the reference count from old parent.
> +	 * now we drop extra reference counts used during the switch
> +	 */
> +	clk_disable_unprepare(new_dram_apb_parent);
> +out_disable_alt_parent:
> +	clk_disable_unprepare(new_dram_alt_parent);
> +out_disable_core_parent:
> +	clk_disable_unprepare(new_dram_core_parent);
> +out:
> +	return ret;
> +}
> +
> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct imx8m_ddrc_freq *freq_info;
> +	struct dev_pm_opp *new_opp;
> +	unsigned long old_freq, new_freq;
> +	int ret;
> +
> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
> +	if (IS_ERR(new_opp)) {
> +		ret = PTR_ERR(new_opp);
> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
> +		return ret;
> +	}
> +	dev_pm_opp_put(new_opp);
> +
> +	old_freq = clk_get_rate(priv->dram_core);
> +	if (*freq == old_freq)
> +		return 0;
> +
> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
> +	if (!freq_info)
> +		return -EINVAL;
> +
> +	/*
> +	 * Read back the clk rate to verify switch was correct and so that
> +	 * we can report it on all error paths.
> +	 */
> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
> +
> +	new_freq = clk_get_rate(priv->dram_core);
> +	if (ret)
> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
> +			old_freq, *freq, ret, new_freq);
> +	else if (*freq != new_freq)
> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
> +			old_freq, *freq, new_freq);

Actually, is it error? When use clk_set_rate with target_freq,
if target_freq is not same with supported clock of h/w clock,
the clk_set_rate set the similiar clock rate among the supported clock table.

It means that if the user of clock_set_rate() enters the unsupported clock rate,
the case of (*freq != new_freq) happen. 

Are you sure that you want to show the error when this case (*freq != new_freq)?
The your origin code is not wrong. Just question from me.

> +	else
> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
> +			*freq, old_freq);
> +
> +	return ret;
> +}
> +
> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +
> +	*freq = clk_get_rate(priv->dram_core);
> +
> +	return 0;
> +}
> +
> +static int imx8m_ddrc_get_dev_status(struct device *dev,
> +				     struct devfreq_dev_status *stat)

get_dev_status() callback is called by only simpleondemand governor.
When userspace governor is used, this function is never called.
So, need to drop this function and then add this function on next time.

> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +
> +	stat->busy_time = 0;
> +	stat->total_time = 0;
> +	stat->current_frequency = clk_get_rate(priv->dram_core);
> +
> +	return 0;
> +}
> +
> +static int imx8m_ddrc_init_freq_info(struct device *dev)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct arm_smccc_res res;
> +	int index;
> +
> +	/* An error here means DDR DVFS API not supported by firmware */
> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
> +			0, 0, 0, 0, 0, 0, &res);
> +	priv->freq_count = res.a0;
> +	if (priv->freq_count <= 0 ||
> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
> +		return -ENODEV;
> +
> +	for (index = 0; index < priv->freq_count; ++index) {
> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
> +
> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
> +			      index, 0, 0, 0, 0, 0, &res);
> +		/* Result should be strictly positive */
> +		if ((long)res.a0 <= 0)
> +			return -ENODEV;
> +
> +		freq->rate = res.a0;
> +		freq->smcarg = index;
> +		freq->dram_core_parent_index = res.a1;
> +		freq->dram_alt_parent_index = res.a2;
> +		freq->dram_apb_parent_index = res.a3;
> +
> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
> +		if (freq->dram_core_parent_index != 1 &&
> +				freq->dram_core_parent_index != 2)
> +			return -ENODEV;
> +		/* dram_apb and dram_alt have exactly 8 possible parents */
> +		if (freq->dram_alt_parent_index > 8 ||
> +				freq->dram_apb_parent_index > 8)
> +			return -ENODEV;
> +		/* dram_core from alt requires explicit dram_alt parent */
> +		if (freq->dram_core_parent_index == 2 &&
> +				freq->dram_alt_parent_index == 0)
> +			return -ENODEV;
> +	}
> +
> +	return 0;
> +}
> +
> +static int imx8m_ddrc_check_opps(struct device *dev)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct imx8m_ddrc_freq *freq_info;
> +	struct dev_pm_opp *opp;
> +	unsigned long freq;
> +
> +	/* Enumerate DT OPPs and disable those not supported by firmware */
> +	freq = ULONG_MAX;
> +	while (true) {
> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
> +		if (opp == ERR_PTR(-ERANGE))
> +			break;
> +		if (IS_ERR(opp)) {
> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
> +				PTR_ERR(opp));
> +			return PTR_ERR(opp);
> +		}
> +		dev_pm_opp_put(opp);
> +
> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
> +		if (!freq_info) {
> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
> +			dev_pm_opp_disable(dev, freq);
> +		}
> +
> +		freq--;
> +	}
> +
> +	return 0;
> +}
> +
> +static void imx8m_ddrc_exit(struct device *dev)
> +{
> +	dev_pm_opp_of_remove_table(dev);
> +}
> +
> +static int imx8m_ddrc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct imx8m_ddrc *priv;
> +	const char *gov = DEVFREQ_GOV_USERSPACE;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	ret = imx8m_ddrc_init_freq_info(dev);
> +	if (ret) {
> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->dram_core = devm_clk_get(dev, "core");
> +	priv->dram_pll = devm_clk_get(dev, "pll");
> +	priv->dram_alt = devm_clk_get(dev, "alt");
> +	priv->dram_apb = devm_clk_get(dev, "apb");
> +	if (IS_ERR(priv->dram_core) ||
> +		IS_ERR(priv->dram_pll) ||
> +		IS_ERR(priv->dram_alt) ||
> +		IS_ERR(priv->dram_apb)) {
> +		ret = PTR_ERR(priv->devfreq);
> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = dev_pm_opp_of_add_table(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to get OPP table\n");
> +		return ret;
> +	}
> +
> +	ret = imx8m_ddrc_check_opps(dev);
> +	if (ret < 0)
> +		goto err;
> +
> +	priv->profile.polling_ms = 1000;
> +	priv->profile.target = imx8m_ddrc_target;
> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;

ditto. It is not used on this patch. On later, add the get_dev_status
for the ondemand governor.

> +	priv->profile.exit = imx8m_ddrc_exit;
> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
> +
> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
> +						gov, NULL);
> +	if (IS_ERR(priv->devfreq)) {
> +		ret = PTR_ERR(priv->devfreq);
> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
> +		goto err;
> +	}
> +
> +	return 0;
> +
> +err:
> +	dev_pm_opp_of_remove_table(dev);
> +	return ret;
> +}
> +
> +static const struct of_device_id imx8m_ddrc_of_match[] = {
> +	{ .compatible = "fsl,imx8m-ddrc", },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
> +
> +static struct platform_driver imx8m_ddrc_platdrv = {
> +	.probe		= imx8m_ddrc_probe,
> +	.driver = {
> +		.name	= "imx8m-ddrc-devfreq",
> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
> +	},
> +};
> +module_platform_driver(imx8m_ddrc_platdrv);
> +
> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
> +MODULE_LICENSE("GPL v2");
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-13  2:30     ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-13  2:30 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

Hi Leonard,

On 11/13/19 6:50 AM, Leonard Crestez wrote:
> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
> frequency switching is implemented inside TF-A, this driver wraps the
> SMC calls and synchronizes the clk tree.
> 
> The DRAM clocks on imx8m have the following structure (abridged):
> 
>  +----------+       |\            +------+
>  | dram_pll |-------|M| dram_core |      |
>  +----------+       |U|---------->| D    |
>                  /--|X|           |  D   |
>    dram_alt_root |  |/            |   R  |
>                  |                |    C |
>             +---------+           |      |
>             |FIX DIV/4|           |      |
>             +---------+           |      |
>   composite:     |                |      |
>  +----------+    |                |      |
>  | dram_alt |----/                |      |
>  +----------+                     |      |
>  | dram_apb |-------------------->|      |
>  +----------+                     +------+
> 
> The dram_pll is used for higher rates and dram_alt is used for lower
> rates. The dram_alt and dram_apb clocks are "imx composite" and their
> parent can also be modified.
> 
> This driver will prepare/enable the new parents ahead of switching (so
> that the expected roots are enabled) and afterwards it will call
> clk_set_parent to ensure the parents in clock framework are up-to-date.
> 
> The driver relies on dram_pll dram_alt and dram_apb being marked with
> CLK_GET_RATE_NOCACHE for rate updates.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  drivers/devfreq/Kconfig      |   9 +
>  drivers/devfreq/Makefile     |   1 +
>  drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>  3 files changed, 470 insertions(+)
>  create mode 100644 drivers/devfreq/imx8m-ddrc.c
> 
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index 066e6c4efaa2..923a6132e741 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>  	  Each memory bus group could contain many memoby bus block. It reads
>  	  PPMU counters of memory controllers by using DEVFREQ-event device
>  	  and adjusts the operating frequencies and voltages with OPP support.
>  	  This does not yet operate with optimal voltages.
>  
> +config ARM_IMX8M_DDRC_DEVFREQ
> +	tristate "i.MX8M DDRC DEVFREQ Driver"
> +	depends on ARCH_MXC || COMPILE_TEST
> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> +	select DEVFREQ_GOV_USERSPACE
> +	help
> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
> +	  adjusting DRAM frequency.
> +
>  config ARM_TEGRA_DEVFREQ
>  	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>  	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>  		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>  		ARCH_TEGRA_210_SOC || \
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index 338ae8440db6..3eb4d5e6635c 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>  
>  # DEVFREQ Drivers
>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>  obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>  obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>  obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>  
>  # DEVFREQ Event Drivers
> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
> new file mode 100644
> index 000000000000..62abb9b79d8a
> --- /dev/null
> +++ b/drivers/devfreq/imx8m-ddrc.c
> @@ -0,0 +1,460 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 NXP
> + */
> +
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/devfreq.h>
> +#include <linux/pm_opp.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/arm-smccc.h>
> +
> +#define IMX_SIP_DDR_DVFS			0xc2000004
> +
> +/* Values starting from 0 switch to specific frequency */
> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
> +
> +/* Deprecated after moving IRQ handling to ATF */
> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
> +
> +/* Query available frequencies. */
> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
> +
> +/*
> + * This should be in a 1:1 mapping with devicetree OPPs but
> + * firmware provides additional info.
> + */
> +struct imx8m_ddrc_freq {
> +	unsigned long rate;
> +	unsigned long smcarg;
> +	int dram_core_parent_index;
> +	int dram_alt_parent_index;
> +	int dram_apb_parent_index;
> +};
> +
> +/* Hardware limitation */
> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
> +
> +/*
> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
> + *
> + * +----------+       |\            +------+
> + * | dram_pll |-------|M| dram_core |      |
> + * +----------+       |U|---------->| D    |
> + *                 /--|X|           |  D   |
> + *   dram_alt_root |  |/            |   R  |
> + *                 |                |    C |
> + *            +---------+           |      |
> + *            |FIX DIV/4|           |      |
> + *            +---------+           |      |
> + *  composite:     |                |      |
> + * +----------+    |                |      |
> + * | dram_alt |----/                |      |
> + * +----------+                     |      |
> + * | dram_apb |-------------------->|      |
> + * +----------+                     +------+
> + *
> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
> + *
> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
> + * configuration of the clocks, including mux parents. The dram_alt and
> + * dram_apb clocks are "imx composite" and their parent can change too.
> + *
> + * We need to prepare/enable the new mux parents head of switching and update
> + * their information afterwards.
> + */
> +struct imx8m_ddrc {
> +	struct devfreq_dev_profile profile;
> +	struct devfreq *devfreq;
> +
> +	/* For frequency switching: */
> +	struct clk *dram_core;
> +	struct clk *dram_pll;
> +	struct clk *dram_alt;
> +	struct clk *dram_apb;
> +
> +	int freq_count;
> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
> +};
> +
> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
> +						    unsigned long rate)
> +{
> +	struct imx8m_ddrc_freq *freq;
> +	int i;
> +
> +	/*
> +	 * Firmware reports values in MT/s, so we round-down from Hz
> +	 * Rounding is extra generous to ensure a match.
> +	 */
> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
> +	for (i = 0; i < priv->freq_count; ++i) {
> +		freq = &priv->freq_table[i];
> +		if (freq->rate == rate ||
> +				freq->rate + 1 == rate ||
> +				freq->rate - 1 == rate)
> +			return freq;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void imx8m_ddrc_smc_set_freq(int target_freq)
> +{
> +	struct arm_smccc_res res;
> +	u32 online_cpus = 0;
> +	int cpu;
> +
> +	local_irq_disable();
> +
> +	for_each_online_cpu(cpu)
> +		online_cpus |= (1 << (cpu * 8));
> +
> +	/* change the ddr freqency */
> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
> +			0, 0, 0, 0, 0, &res);
> +
> +	local_irq_enable();
> +}
> +
> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
> +{
> +	struct clk_hw *hw;
> +
> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
> +
> +	return hw ? hw->clk : NULL;
> +}
> +
> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct clk *new_dram_core_parent;
> +	struct clk *new_dram_alt_parent;
> +	struct clk *new_dram_apb_parent;
> +	int ret;
> +
> +	/*
> +	 * Fetch new parents
> +	 *
> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
> +	 * new_dram_core_parent is not.
> +	 */
> +	new_dram_core_parent = clk_get_parent_by_index(
> +			priv->dram_core, freq->dram_core_parent_index - 1);
> +	if (!new_dram_core_parent) {
> +		dev_err(dev, "failed to fetch new dram_core parent\n");
> +		return -EINVAL;
> +	}
> +	if (freq->dram_alt_parent_index) {
> +		new_dram_alt_parent = clk_get_parent_by_index(
> +				priv->dram_alt,
> +				freq->dram_alt_parent_index - 1);
> +		if (!new_dram_alt_parent) {
> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
> +			return -EINVAL;
> +		}
> +	} else
> +		new_dram_alt_parent = NULL;
> +
> +	if (freq->dram_alt_parent_index) {
> +		new_dram_apb_parent = clk_get_parent_by_index(
> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
> +		if (!new_dram_alt_parent) {
> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
> +			return -EINVAL;
> +		}
> +	} else
> +		new_dram_apb_parent = NULL;
> +
> +	/* increase reference counts and ensure clks are ON before switch */
> +	ret = clk_prepare_enable(new_dram_core_parent);
> +	if (ret) {
> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);

s/failed enable/failed to enable

> +		goto out;
> +	}
> +	ret = clk_prepare_enable(new_dram_alt_parent);
> +	if (ret) {
> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);

s/failed enable/failed to enable

> +		goto out_disable_core_parent;
> +	}
> +	ret = clk_prepare_enable(new_dram_apb_parent);
> +	if (ret) {
> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);

s/failed enable/failed to enable

> +		goto out_disable_alt_parent;
> +	}
> +
> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
> +
> +	/* update parents in clk tree after switch. */
> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
> +	if (ret)
> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);

s/failed set/failed to set

> +	if (new_dram_alt_parent) {
> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
> +		if (ret)
> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);

s/failed set/failed to set

> +	}
> +	if (new_dram_apb_parent) {
> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
> +		if (ret)
> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);

s/failed set/failed to set

> +	}
> +
> +	/*
> +	 * Explicitly refresh dram PLL rate.
> +	 *
> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
> +	 * automatically refreshed when clk_get_rate is called on children.
> +	 */
> +	clk_get_rate(priv->dram_pll);
> +
> +	/*
> +	 * clk_set_parent transfer the reference count from old parent.
> +	 * now we drop extra reference counts used during the switch
> +	 */
> +	clk_disable_unprepare(new_dram_apb_parent);
> +out_disable_alt_parent:
> +	clk_disable_unprepare(new_dram_alt_parent);
> +out_disable_core_parent:
> +	clk_disable_unprepare(new_dram_core_parent);
> +out:
> +	return ret;
> +}
> +
> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct imx8m_ddrc_freq *freq_info;
> +	struct dev_pm_opp *new_opp;
> +	unsigned long old_freq, new_freq;
> +	int ret;
> +
> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
> +	if (IS_ERR(new_opp)) {
> +		ret = PTR_ERR(new_opp);
> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
> +		return ret;
> +	}
> +	dev_pm_opp_put(new_opp);
> +
> +	old_freq = clk_get_rate(priv->dram_core);
> +	if (*freq == old_freq)
> +		return 0;
> +
> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
> +	if (!freq_info)
> +		return -EINVAL;
> +
> +	/*
> +	 * Read back the clk rate to verify switch was correct and so that
> +	 * we can report it on all error paths.
> +	 */
> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
> +
> +	new_freq = clk_get_rate(priv->dram_core);
> +	if (ret)
> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
> +			old_freq, *freq, ret, new_freq);
> +	else if (*freq != new_freq)
> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
> +			old_freq, *freq, new_freq);

Actually, is it error? When use clk_set_rate with target_freq,
if target_freq is not same with supported clock of h/w clock,
the clk_set_rate set the similiar clock rate among the supported clock table.

It means that if the user of clock_set_rate() enters the unsupported clock rate,
the case of (*freq != new_freq) happen. 

Are you sure that you want to show the error when this case (*freq != new_freq)?
The your origin code is not wrong. Just question from me.

> +	else
> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
> +			*freq, old_freq);
> +
> +	return ret;
> +}
> +
> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +
> +	*freq = clk_get_rate(priv->dram_core);
> +
> +	return 0;
> +}
> +
> +static int imx8m_ddrc_get_dev_status(struct device *dev,
> +				     struct devfreq_dev_status *stat)

get_dev_status() callback is called by only simpleondemand governor.
When userspace governor is used, this function is never called.
So, need to drop this function and then add this function on next time.

> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +
> +	stat->busy_time = 0;
> +	stat->total_time = 0;
> +	stat->current_frequency = clk_get_rate(priv->dram_core);
> +
> +	return 0;
> +}
> +
> +static int imx8m_ddrc_init_freq_info(struct device *dev)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct arm_smccc_res res;
> +	int index;
> +
> +	/* An error here means DDR DVFS API not supported by firmware */
> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
> +			0, 0, 0, 0, 0, 0, &res);
> +	priv->freq_count = res.a0;
> +	if (priv->freq_count <= 0 ||
> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
> +		return -ENODEV;
> +
> +	for (index = 0; index < priv->freq_count; ++index) {
> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
> +
> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
> +			      index, 0, 0, 0, 0, 0, &res);
> +		/* Result should be strictly positive */
> +		if ((long)res.a0 <= 0)
> +			return -ENODEV;
> +
> +		freq->rate = res.a0;
> +		freq->smcarg = index;
> +		freq->dram_core_parent_index = res.a1;
> +		freq->dram_alt_parent_index = res.a2;
> +		freq->dram_apb_parent_index = res.a3;
> +
> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
> +		if (freq->dram_core_parent_index != 1 &&
> +				freq->dram_core_parent_index != 2)
> +			return -ENODEV;
> +		/* dram_apb and dram_alt have exactly 8 possible parents */
> +		if (freq->dram_alt_parent_index > 8 ||
> +				freq->dram_apb_parent_index > 8)
> +			return -ENODEV;
> +		/* dram_core from alt requires explicit dram_alt parent */
> +		if (freq->dram_core_parent_index == 2 &&
> +				freq->dram_alt_parent_index == 0)
> +			return -ENODEV;
> +	}
> +
> +	return 0;
> +}
> +
> +static int imx8m_ddrc_check_opps(struct device *dev)
> +{
> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
> +	struct imx8m_ddrc_freq *freq_info;
> +	struct dev_pm_opp *opp;
> +	unsigned long freq;
> +
> +	/* Enumerate DT OPPs and disable those not supported by firmware */
> +	freq = ULONG_MAX;
> +	while (true) {
> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
> +		if (opp == ERR_PTR(-ERANGE))
> +			break;
> +		if (IS_ERR(opp)) {
> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
> +				PTR_ERR(opp));
> +			return PTR_ERR(opp);
> +		}
> +		dev_pm_opp_put(opp);
> +
> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
> +		if (!freq_info) {
> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
> +			dev_pm_opp_disable(dev, freq);
> +		}
> +
> +		freq--;
> +	}
> +
> +	return 0;
> +}
> +
> +static void imx8m_ddrc_exit(struct device *dev)
> +{
> +	dev_pm_opp_of_remove_table(dev);
> +}
> +
> +static int imx8m_ddrc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct imx8m_ddrc *priv;
> +	const char *gov = DEVFREQ_GOV_USERSPACE;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	ret = imx8m_ddrc_init_freq_info(dev);
> +	if (ret) {
> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->dram_core = devm_clk_get(dev, "core");
> +	priv->dram_pll = devm_clk_get(dev, "pll");
> +	priv->dram_alt = devm_clk_get(dev, "alt");
> +	priv->dram_apb = devm_clk_get(dev, "apb");
> +	if (IS_ERR(priv->dram_core) ||
> +		IS_ERR(priv->dram_pll) ||
> +		IS_ERR(priv->dram_alt) ||
> +		IS_ERR(priv->dram_apb)) {
> +		ret = PTR_ERR(priv->devfreq);
> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = dev_pm_opp_of_add_table(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to get OPP table\n");
> +		return ret;
> +	}
> +
> +	ret = imx8m_ddrc_check_opps(dev);
> +	if (ret < 0)
> +		goto err;
> +
> +	priv->profile.polling_ms = 1000;
> +	priv->profile.target = imx8m_ddrc_target;
> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;

ditto. It is not used on this patch. On later, add the get_dev_status
for the ondemand governor.

> +	priv->profile.exit = imx8m_ddrc_exit;
> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
> +
> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
> +						gov, NULL);
> +	if (IS_ERR(priv->devfreq)) {
> +		ret = PTR_ERR(priv->devfreq);
> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
> +		goto err;
> +	}
> +
> +	return 0;
> +
> +err:
> +	dev_pm_opp_of_remove_table(dev);
> +	return ret;
> +}
> +
> +static const struct of_device_id imx8m_ddrc_of_match[] = {
> +	{ .compatible = "fsl,imx8m-ddrc", },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
> +
> +static struct platform_driver imx8m_ddrc_platdrv = {
> +	.probe		= imx8m_ddrc_probe,
> +	.driver = {
> +		.name	= "imx8m-ddrc-devfreq",
> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
> +	},
> +};
> +module_platform_driver(imx8m_ddrc_platdrv);
> +
> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
> +MODULE_LICENSE("GPL v2");
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
  2019-11-12 21:50   ` Leonard Crestez
@ 2019-11-13  2:38     ` Chanwoo Choi
  -1 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-13  2:38 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

On 11/13/19 6:50 AM, Leonard Crestez wrote:
> Add devicetree bindings for the i.MX DDR Controller on imx8m series
> chips. It supports dynamic frequency switching between multiple data
> rates and this is exposed to Linux via the devfreq subsystem.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
>  1 file changed, 57 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
> new file mode 100644
> index 000000000000..7c98e3509f75
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
> @@ -0,0 +1,57 @@
> +# SPDX-License-Identifier: GPL-2.0
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/memory-controllers/fsl/imx8m-ddrc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: i.MX8M DDR Controller
> +
> +maintainers:
> +  - Leonard Crestez <leonard.crestez@nxp.com>
> +
> +properties:
> +  compatible:
> +    items:
> +      - enum:
> +        - fsl,imx8mn-ddrc
> +        - fsl,imx8mm-ddrc
> +        - fsl,imx8mq-ddrc
> +      - const: fsl,imx8m-ddrc
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 4
> +
> +  clock-names:
> +    items:
> +      - const: core
> +      - const: pll
> +      - const: alt
> +      - const: apb
> +
> +  operating-points-v2: true
> +  opp-table: true
> +
> +required:
> +  - reg
> +  - compatible
> +  - clocks
> +  - clock-names
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/imx8mm-clock.h>
> +    ddrc: memory-controller@3d400000 {
> +        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
> +        reg = <0x3d400000 0x400000>;

The probe() function doesn't get the IORESOURCE_MEM from dt?
Is it needed?

> +        clock-names = "core", "pll", "alt", "apb";
> +        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
> +                 <&clk IMX8MM_DRAM_PLL>,
> +                 <&clk IMX8MM_CLK_DRAM_ALT>,
> +                 <&clk IMX8MM_CLK_DRAM_APB>;
> +        operating-points-v2 = <&ddrc_opp_table>;
> +    };
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
@ 2019-11-13  2:38     ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-13  2:38 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

On 11/13/19 6:50 AM, Leonard Crestez wrote:
> Add devicetree bindings for the i.MX DDR Controller on imx8m series
> chips. It supports dynamic frequency switching between multiple data
> rates and this is exposed to Linux via the devfreq subsystem.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
>  1 file changed, 57 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
> new file mode 100644
> index 000000000000..7c98e3509f75
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
> @@ -0,0 +1,57 @@
> +# SPDX-License-Identifier: GPL-2.0
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/memory-controllers/fsl/imx8m-ddrc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: i.MX8M DDR Controller
> +
> +maintainers:
> +  - Leonard Crestez <leonard.crestez@nxp.com>
> +
> +properties:
> +  compatible:
> +    items:
> +      - enum:
> +        - fsl,imx8mn-ddrc
> +        - fsl,imx8mm-ddrc
> +        - fsl,imx8mq-ddrc
> +      - const: fsl,imx8m-ddrc
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 4
> +
> +  clock-names:
> +    items:
> +      - const: core
> +      - const: pll
> +      - const: alt
> +      - const: apb
> +
> +  operating-points-v2: true
> +  opp-table: true
> +
> +required:
> +  - reg
> +  - compatible
> +  - clocks
> +  - clock-names
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/imx8mm-clock.h>
> +    ddrc: memory-controller@3d400000 {
> +        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
> +        reg = <0x3d400000 0x400000>;

The probe() function doesn't get the IORESOURCE_MEM from dt?
Is it needed?

> +        clock-names = "core", "pll", "alt", "apb";
> +        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
> +                 <&clk IMX8MM_DRAM_PLL>,
> +                 <&clk IMX8MM_CLK_DRAM_ALT>,
> +                 <&clk IMX8MM_CLK_DRAM_APB>;
> +        operating-points-v2 = <&ddrc_opp_table>;
> +    };
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-13  2:30     ` Chanwoo Choi
@ 2019-11-13  6:28       ` Chanwoo Choi
  -1 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-13  6:28 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

On 11/13/19 11:30 AM, Chanwoo Choi wrote:
> Hi Leonard,
> 
> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>> frequency switching is implemented inside TF-A, this driver wraps the
>> SMC calls and synchronizes the clk tree.
>>
>> The DRAM clocks on imx8m have the following structure (abridged):
>>
>>  +----------+       |\            +------+
>>  | dram_pll |-------|M| dram_core |      |
>>  +----------+       |U|---------->| D    |
>>                  /--|X|           |  D   |
>>    dram_alt_root |  |/            |   R  |
>>                  |                |    C |
>>             +---------+           |      |
>>             |FIX DIV/4|           |      |
>>             +---------+           |      |
>>   composite:     |                |      |
>>  +----------+    |                |      |
>>  | dram_alt |----/                |      |
>>  +----------+                     |      |
>>  | dram_apb |-------------------->|      |
>>  +----------+                     +------+
>>
>> The dram_pll is used for higher rates and dram_alt is used for lower
>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>> parent can also be modified.
>>
>> This driver will prepare/enable the new parents ahead of switching (so
>> that the expected roots are enabled) and afterwards it will call
>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>
>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>> CLK_GET_RATE_NOCACHE for rate updates.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> ---
>>  drivers/devfreq/Kconfig      |   9 +
>>  drivers/devfreq/Makefile     |   1 +
>>  drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>  3 files changed, 470 insertions(+)
>>  create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>
>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>> index 066e6c4efaa2..923a6132e741 100644
>> --- a/drivers/devfreq/Kconfig
>> +++ b/drivers/devfreq/Kconfig
>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>  	  Each memory bus group could contain many memoby bus block. It reads
>>  	  PPMU counters of memory controllers by using DEVFREQ-event device
>>  	  and adjusts the operating frequencies and voltages with OPP support.
>>  	  This does not yet operate with optimal voltages.
>>  
>> +config ARM_IMX8M_DDRC_DEVFREQ
>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>> +	depends on ARCH_MXC || COMPILE_TEST
>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>> +	select DEVFREQ_GOV_USERSPACE
>> +	help
>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>> +	  adjusting DRAM frequency.
>> +
>>  config ARM_TEGRA_DEVFREQ
>>  	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>  	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>  		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>  		ARCH_TEGRA_210_SOC || \
>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>> index 338ae8440db6..3eb4d5e6635c 100644
>> --- a/drivers/devfreq/Makefile
>> +++ b/drivers/devfreq/Makefile
>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>  
>>  # DEVFREQ Drivers
>>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>  obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>  obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>  obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>  
>>  # DEVFREQ Event Drivers
>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>> new file mode 100644
>> index 000000000000..62abb9b79d8a
>> --- /dev/null
>> +++ b/drivers/devfreq/imx8m-ddrc.c
>> @@ -0,0 +1,460 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2019 NXP
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/device.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/devfreq.h>
>> +#include <linux/pm_opp.h>
>> +#include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/arm-smccc.h>
>> +
>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>> +
>> +/* Values starting from 0 switch to specific frequency */
>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>> +
>> +/* Deprecated after moving IRQ handling to ATF */
>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>> +
>> +/* Query available frequencies. */
>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>> +
>> +/*
>> + * This should be in a 1:1 mapping with devicetree OPPs but
>> + * firmware provides additional info.
>> + */
>> +struct imx8m_ddrc_freq {
>> +	unsigned long rate;
>> +	unsigned long smcarg;
>> +	int dram_core_parent_index;
>> +	int dram_alt_parent_index;
>> +	int dram_apb_parent_index;
>> +};
>> +
>> +/* Hardware limitation */
>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>> +
>> +/*
>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>> + *
>> + * +----------+       |\            +------+
>> + * | dram_pll |-------|M| dram_core |      |
>> + * +----------+       |U|---------->| D    |
>> + *                 /--|X|           |  D   |
>> + *   dram_alt_root |  |/            |   R  |
>> + *                 |                |    C |
>> + *            +---------+           |      |
>> + *            |FIX DIV/4|           |      |
>> + *            +---------+           |      |
>> + *  composite:     |                |      |
>> + * +----------+    |                |      |
>> + * | dram_alt |----/                |      |
>> + * +----------+                     |      |
>> + * | dram_apb |-------------------->|      |
>> + * +----------+                     +------+
>> + *
>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>> + *
>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>> + * configuration of the clocks, including mux parents. The dram_alt and
>> + * dram_apb clocks are "imx composite" and their parent can change too.
>> + *
>> + * We need to prepare/enable the new mux parents head of switching and update
>> + * their information afterwards.
>> + */
>> +struct imx8m_ddrc {
>> +	struct devfreq_dev_profile profile;
>> +	struct devfreq *devfreq;
>> +
>> +	/* For frequency switching: */
>> +	struct clk *dram_core;
>> +	struct clk *dram_pll;
>> +	struct clk *dram_alt;
>> +	struct clk *dram_apb;
>> +
>> +	int freq_count;
>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>> +};
>> +
>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>> +						    unsigned long rate)
>> +{
>> +	struct imx8m_ddrc_freq *freq;
>> +	int i;
>> +
>> +	/*
>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>> +	 * Rounding is extra generous to ensure a match.
>> +	 */
>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>> +	for (i = 0; i < priv->freq_count; ++i) {
>> +		freq = &priv->freq_table[i];
>> +		if (freq->rate == rate ||
>> +				freq->rate + 1 == rate ||
>> +				freq->rate - 1 == rate)
>> +			return freq;
>> +	}
>> +
>> +	return NULL;
>> +}
>> +
>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>> +{
>> +	struct arm_smccc_res res;
>> +	u32 online_cpus = 0;
>> +	int cpu;
>> +
>> +	local_irq_disable();
>> +
>> +	for_each_online_cpu(cpu)
>> +		online_cpus |= (1 << (cpu * 8));
>> +
>> +	/* change the ddr freqency */
>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>> +			0, 0, 0, 0, 0, &res);
>> +
>> +	local_irq_enable();
>> +}
>> +
>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>> +{
>> +	struct clk_hw *hw;
>> +
>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>> +
>> +	return hw ? hw->clk : NULL;
>> +}
>> +
>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct clk *new_dram_core_parent;
>> +	struct clk *new_dram_alt_parent;
>> +	struct clk *new_dram_apb_parent;
>> +	int ret;
>> +
>> +	/*
>> +	 * Fetch new parents
>> +	 *
>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>> +	 * new_dram_core_parent is not.
>> +	 */
>> +	new_dram_core_parent = clk_get_parent_by_index(
>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>> +	if (!new_dram_core_parent) {
>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>> +		return -EINVAL;
>> +	}
>> +	if (freq->dram_alt_parent_index) {
>> +		new_dram_alt_parent = clk_get_parent_by_index(
>> +				priv->dram_alt,
>> +				freq->dram_alt_parent_index - 1);
>> +		if (!new_dram_alt_parent) {
>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>> +			return -EINVAL;
>> +		}
>> +	} else
>> +		new_dram_alt_parent = NULL;
>> +
>> +	if (freq->dram_alt_parent_index) {
>> +		new_dram_apb_parent = clk_get_parent_by_index(
>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>> +		if (!new_dram_alt_parent) {
>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>> +			return -EINVAL;
>> +		}
>> +	} else
>> +		new_dram_apb_parent = NULL;
>> +
>> +	/* increase reference counts and ensure clks are ON before switch */
>> +	ret = clk_prepare_enable(new_dram_core_parent);
>> +	if (ret) {
>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
> 
> s/failed enable/failed to enable
> 
>> +		goto out;
>> +	}
>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>> +	if (ret) {
>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
> 
> s/failed enable/failed to enable
> 
>> +		goto out_disable_core_parent;
>> +	}
>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>> +	if (ret) {
>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
> 
> s/failed enable/failed to enable
> 
>> +		goto out_disable_alt_parent;
>> +	}
>> +
>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>> +
>> +	/* update parents in clk tree after switch. */
>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>> +	if (ret)
>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
> 
> s/failed set/failed to set
> 
>> +	if (new_dram_alt_parent) {
>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>> +		if (ret)
>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
> 
> s/failed set/failed to set
> 
>> +	}
>> +	if (new_dram_apb_parent) {
>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>> +		if (ret)
>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
> 
> s/failed set/failed to set
> 
>> +	}
>> +
>> +	/*
>> +	 * Explicitly refresh dram PLL rate.
>> +	 *
>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>> +	 * automatically refreshed when clk_get_rate is called on children.
>> +	 */
>> +	clk_get_rate(priv->dram_pll);
>> +
>> +	/*
>> +	 * clk_set_parent transfer the reference count from old parent.
>> +	 * now we drop extra reference counts used during the switch
>> +	 */
>> +	clk_disable_unprepare(new_dram_apb_parent);
>> +out_disable_alt_parent:
>> +	clk_disable_unprepare(new_dram_alt_parent);
>> +out_disable_core_parent:
>> +	clk_disable_unprepare(new_dram_core_parent);
>> +out:
>> +	return ret;
>> +}
>> +
>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct imx8m_ddrc_freq *freq_info;
>> +	struct dev_pm_opp *new_opp;
>> +	unsigned long old_freq, new_freq;
>> +	int ret;
>> +
>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>> +	if (IS_ERR(new_opp)) {
>> +		ret = PTR_ERR(new_opp);
>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>> +		return ret;
>> +	}
>> +	dev_pm_opp_put(new_opp);
>> +
>> +	old_freq = clk_get_rate(priv->dram_core);
>> +	if (*freq == old_freq)
>> +		return 0;
>> +
>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>> +	if (!freq_info)
>> +		return -EINVAL;
>> +
>> +	/*
>> +	 * Read back the clk rate to verify switch was correct and so that
>> +	 * we can report it on all error paths.
>> +	 */
>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>> +
>> +	new_freq = clk_get_rate(priv->dram_core);
>> +	if (ret)
>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>> +			old_freq, *freq, ret, new_freq);
>> +	else if (*freq != new_freq)
>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>> +			old_freq, *freq, new_freq);
> 
> Actually, is it error? When use clk_set_rate with target_freq,
> if target_freq is not same with supported clock of h/w clock,
> the clk_set_rate set the similiar clock rate among the supported clock table.
> 
> It means that if the user of clock_set_rate() enters the unsupported clock rate,
> the case of (*freq != new_freq) happen. 
> 
> Are you sure that you want to show the error when this case (*freq != new_freq)?
> The your origin code is not wrong. Just question from me.
> 
>> +	else
>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>> +			*freq, old_freq);
>> +
>> +	return ret;
>> +}
>> +
>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +
>> +	*freq = clk_get_rate(priv->dram_core);
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>> +				     struct devfreq_dev_status *stat)
> 
> get_dev_status() callback is called by only simpleondemand governor.
> When userspace governor is used, this function is never called.
> So, need to drop this function and then add this function on next time.
> 
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +
>> +	stat->busy_time = 0;
>> +	stat->total_time = 0;
>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct arm_smccc_res res;
>> +	int index;
>> +
>> +	/* An error here means DDR DVFS API not supported by firmware */
>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>> +			0, 0, 0, 0, 0, 0, &res);
>> +	priv->freq_count = res.a0;
>> +	if (priv->freq_count <= 0 ||
>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>> +		return -ENODEV;
>> +
>> +	for (index = 0; index < priv->freq_count; ++index) {
>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>> +
>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>> +			      index, 0, 0, 0, 0, 0, &res);
>> +		/* Result should be strictly positive */
>> +		if ((long)res.a0 <= 0)
>> +			return -ENODEV;
>> +
>> +		freq->rate = res.a0;
>> +		freq->smcarg = index;
>> +		freq->dram_core_parent_index = res.a1;
>> +		freq->dram_alt_parent_index = res.a2;
>> +		freq->dram_apb_parent_index = res.a3;
>> +
>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>> +		if (freq->dram_core_parent_index != 1 &&
>> +				freq->dram_core_parent_index != 2)
>> +			return -ENODEV;
>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>> +		if (freq->dram_alt_parent_index > 8 ||
>> +				freq->dram_apb_parent_index > 8)
>> +			return -ENODEV;
>> +		/* dram_core from alt requires explicit dram_alt parent */
>> +		if (freq->dram_core_parent_index == 2 &&
>> +				freq->dram_alt_parent_index == 0)
>> +			return -ENODEV;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx8m_ddrc_check_opps(struct device *dev)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct imx8m_ddrc_freq *freq_info;
>> +	struct dev_pm_opp *opp;
>> +	unsigned long freq;
>> +
>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>> +	freq = ULONG_MAX;
>> +	while (true) {

You can get the number of OPP entries int the opp table
with dev_pm_opp_get_count(dev). I think that better to
use the correct number of OPP entries instead of 'while(true)' style.

>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>> +		if (opp == ERR_PTR(-ERANGE))
>> +			break;
>> +		if (IS_ERR(opp)) {
>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>> +				PTR_ERR(opp));
>> +			return PTR_ERR(opp);
>> +		}
>> +		dev_pm_opp_put(opp);
>> +
>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>> +		if (!freq_info) {
>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>> +			dev_pm_opp_disable(dev, freq);
>> +		}
>> +
>> +		freq--;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void imx8m_ddrc_exit(struct device *dev)
>> +{
>> +	dev_pm_opp_of_remove_table(dev);
>> +}
>> +
>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct imx8m_ddrc *priv;
>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>> +	int ret;
>> +
>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	platform_set_drvdata(pdev, priv);
>> +
>> +	ret = imx8m_ddrc_init_freq_info(dev);
>> +	if (ret) {
>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	priv->dram_core = devm_clk_get(dev, "core");
>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>> +	if (IS_ERR(priv->dram_core) ||
>> +		IS_ERR(priv->dram_pll) ||
>> +		IS_ERR(priv->dram_alt) ||
>> +		IS_ERR(priv->dram_apb)) {
>> +		ret = PTR_ERR(priv->devfreq);
>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = dev_pm_opp_of_add_table(dev);
>> +	if (ret < 0) {
>> +		dev_err(dev, "failed to get OPP table\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = imx8m_ddrc_check_opps(dev);
>> +	if (ret < 0)
>> +		goto err;
>> +
>> +	priv->profile.polling_ms = 1000;
>> +	priv->profile.target = imx8m_ddrc_target;
>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
> 
> ditto. It is not used on this patch. On later, add the get_dev_status
> for the ondemand governor.
> 
>> +	priv->profile.exit = imx8m_ddrc_exit;
>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>> +
>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>> +						gov, NULL);
>> +	if (IS_ERR(priv->devfreq)) {
>> +		ret = PTR_ERR(priv->devfreq);
>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>> +		goto err;
>> +	}
>> +
>> +	return 0;
>> +
>> +err:
>> +	dev_pm_opp_of_remove_table(dev);
>> +	return ret;
>> +}
>> +
>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>> +	{ .compatible = "fsl,imx8m-ddrc", },
>> +	{ /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>> +
>> +static struct platform_driver imx8m_ddrc_platdrv = {
>> +	.probe		= imx8m_ddrc_probe,
>> +	.driver = {
>> +		.name	= "imx8m-ddrc-devfreq",
>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>> +	},
>> +};
>> +module_platform_driver(imx8m_ddrc_platdrv);
>> +
>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>> +MODULE_LICENSE("GPL v2");
>>
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-13  6:28       ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-13  6:28 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Dong Aisheng, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

On 11/13/19 11:30 AM, Chanwoo Choi wrote:
> Hi Leonard,
> 
> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>> frequency switching is implemented inside TF-A, this driver wraps the
>> SMC calls and synchronizes the clk tree.
>>
>> The DRAM clocks on imx8m have the following structure (abridged):
>>
>>  +----------+       |\            +------+
>>  | dram_pll |-------|M| dram_core |      |
>>  +----------+       |U|---------->| D    |
>>                  /--|X|           |  D   |
>>    dram_alt_root |  |/            |   R  |
>>                  |                |    C |
>>             +---------+           |      |
>>             |FIX DIV/4|           |      |
>>             +---------+           |      |
>>   composite:     |                |      |
>>  +----------+    |                |      |
>>  | dram_alt |----/                |      |
>>  +----------+                     |      |
>>  | dram_apb |-------------------->|      |
>>  +----------+                     +------+
>>
>> The dram_pll is used for higher rates and dram_alt is used for lower
>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>> parent can also be modified.
>>
>> This driver will prepare/enable the new parents ahead of switching (so
>> that the expected roots are enabled) and afterwards it will call
>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>
>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>> CLK_GET_RATE_NOCACHE for rate updates.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> ---
>>  drivers/devfreq/Kconfig      |   9 +
>>  drivers/devfreq/Makefile     |   1 +
>>  drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>  3 files changed, 470 insertions(+)
>>  create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>
>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>> index 066e6c4efaa2..923a6132e741 100644
>> --- a/drivers/devfreq/Kconfig
>> +++ b/drivers/devfreq/Kconfig
>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>  	  Each memory bus group could contain many memoby bus block. It reads
>>  	  PPMU counters of memory controllers by using DEVFREQ-event device
>>  	  and adjusts the operating frequencies and voltages with OPP support.
>>  	  This does not yet operate with optimal voltages.
>>  
>> +config ARM_IMX8M_DDRC_DEVFREQ
>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>> +	depends on ARCH_MXC || COMPILE_TEST
>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>> +	select DEVFREQ_GOV_USERSPACE
>> +	help
>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>> +	  adjusting DRAM frequency.
>> +
>>  config ARM_TEGRA_DEVFREQ
>>  	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>  	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>  		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>  		ARCH_TEGRA_210_SOC || \
>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>> index 338ae8440db6..3eb4d5e6635c 100644
>> --- a/drivers/devfreq/Makefile
>> +++ b/drivers/devfreq/Makefile
>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>  
>>  # DEVFREQ Drivers
>>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>  obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>  obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>  obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>  
>>  # DEVFREQ Event Drivers
>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>> new file mode 100644
>> index 000000000000..62abb9b79d8a
>> --- /dev/null
>> +++ b/drivers/devfreq/imx8m-ddrc.c
>> @@ -0,0 +1,460 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2019 NXP
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/device.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/devfreq.h>
>> +#include <linux/pm_opp.h>
>> +#include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/arm-smccc.h>
>> +
>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>> +
>> +/* Values starting from 0 switch to specific frequency */
>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>> +
>> +/* Deprecated after moving IRQ handling to ATF */
>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>> +
>> +/* Query available frequencies. */
>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>> +
>> +/*
>> + * This should be in a 1:1 mapping with devicetree OPPs but
>> + * firmware provides additional info.
>> + */
>> +struct imx8m_ddrc_freq {
>> +	unsigned long rate;
>> +	unsigned long smcarg;
>> +	int dram_core_parent_index;
>> +	int dram_alt_parent_index;
>> +	int dram_apb_parent_index;
>> +};
>> +
>> +/* Hardware limitation */
>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>> +
>> +/*
>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>> + *
>> + * +----------+       |\            +------+
>> + * | dram_pll |-------|M| dram_core |      |
>> + * +----------+       |U|---------->| D    |
>> + *                 /--|X|           |  D   |
>> + *   dram_alt_root |  |/            |   R  |
>> + *                 |                |    C |
>> + *            +---------+           |      |
>> + *            |FIX DIV/4|           |      |
>> + *            +---------+           |      |
>> + *  composite:     |                |      |
>> + * +----------+    |                |      |
>> + * | dram_alt |----/                |      |
>> + * +----------+                     |      |
>> + * | dram_apb |-------------------->|      |
>> + * +----------+                     +------+
>> + *
>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>> + *
>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>> + * configuration of the clocks, including mux parents. The dram_alt and
>> + * dram_apb clocks are "imx composite" and their parent can change too.
>> + *
>> + * We need to prepare/enable the new mux parents head of switching and update
>> + * their information afterwards.
>> + */
>> +struct imx8m_ddrc {
>> +	struct devfreq_dev_profile profile;
>> +	struct devfreq *devfreq;
>> +
>> +	/* For frequency switching: */
>> +	struct clk *dram_core;
>> +	struct clk *dram_pll;
>> +	struct clk *dram_alt;
>> +	struct clk *dram_apb;
>> +
>> +	int freq_count;
>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>> +};
>> +
>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>> +						    unsigned long rate)
>> +{
>> +	struct imx8m_ddrc_freq *freq;
>> +	int i;
>> +
>> +	/*
>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>> +	 * Rounding is extra generous to ensure a match.
>> +	 */
>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>> +	for (i = 0; i < priv->freq_count; ++i) {
>> +		freq = &priv->freq_table[i];
>> +		if (freq->rate == rate ||
>> +				freq->rate + 1 == rate ||
>> +				freq->rate - 1 == rate)
>> +			return freq;
>> +	}
>> +
>> +	return NULL;
>> +}
>> +
>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>> +{
>> +	struct arm_smccc_res res;
>> +	u32 online_cpus = 0;
>> +	int cpu;
>> +
>> +	local_irq_disable();
>> +
>> +	for_each_online_cpu(cpu)
>> +		online_cpus |= (1 << (cpu * 8));
>> +
>> +	/* change the ddr freqency */
>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>> +			0, 0, 0, 0, 0, &res);
>> +
>> +	local_irq_enable();
>> +}
>> +
>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>> +{
>> +	struct clk_hw *hw;
>> +
>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>> +
>> +	return hw ? hw->clk : NULL;
>> +}
>> +
>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct clk *new_dram_core_parent;
>> +	struct clk *new_dram_alt_parent;
>> +	struct clk *new_dram_apb_parent;
>> +	int ret;
>> +
>> +	/*
>> +	 * Fetch new parents
>> +	 *
>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>> +	 * new_dram_core_parent is not.
>> +	 */
>> +	new_dram_core_parent = clk_get_parent_by_index(
>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>> +	if (!new_dram_core_parent) {
>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>> +		return -EINVAL;
>> +	}
>> +	if (freq->dram_alt_parent_index) {
>> +		new_dram_alt_parent = clk_get_parent_by_index(
>> +				priv->dram_alt,
>> +				freq->dram_alt_parent_index - 1);
>> +		if (!new_dram_alt_parent) {
>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>> +			return -EINVAL;
>> +		}
>> +	} else
>> +		new_dram_alt_parent = NULL;
>> +
>> +	if (freq->dram_alt_parent_index) {
>> +		new_dram_apb_parent = clk_get_parent_by_index(
>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>> +		if (!new_dram_alt_parent) {
>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>> +			return -EINVAL;
>> +		}
>> +	} else
>> +		new_dram_apb_parent = NULL;
>> +
>> +	/* increase reference counts and ensure clks are ON before switch */
>> +	ret = clk_prepare_enable(new_dram_core_parent);
>> +	if (ret) {
>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
> 
> s/failed enable/failed to enable
> 
>> +		goto out;
>> +	}
>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>> +	if (ret) {
>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
> 
> s/failed enable/failed to enable
> 
>> +		goto out_disable_core_parent;
>> +	}
>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>> +	if (ret) {
>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
> 
> s/failed enable/failed to enable
> 
>> +		goto out_disable_alt_parent;
>> +	}
>> +
>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>> +
>> +	/* update parents in clk tree after switch. */
>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>> +	if (ret)
>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
> 
> s/failed set/failed to set
> 
>> +	if (new_dram_alt_parent) {
>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>> +		if (ret)
>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
> 
> s/failed set/failed to set
> 
>> +	}
>> +	if (new_dram_apb_parent) {
>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>> +		if (ret)
>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
> 
> s/failed set/failed to set
> 
>> +	}
>> +
>> +	/*
>> +	 * Explicitly refresh dram PLL rate.
>> +	 *
>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>> +	 * automatically refreshed when clk_get_rate is called on children.
>> +	 */
>> +	clk_get_rate(priv->dram_pll);
>> +
>> +	/*
>> +	 * clk_set_parent transfer the reference count from old parent.
>> +	 * now we drop extra reference counts used during the switch
>> +	 */
>> +	clk_disable_unprepare(new_dram_apb_parent);
>> +out_disable_alt_parent:
>> +	clk_disable_unprepare(new_dram_alt_parent);
>> +out_disable_core_parent:
>> +	clk_disable_unprepare(new_dram_core_parent);
>> +out:
>> +	return ret;
>> +}
>> +
>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct imx8m_ddrc_freq *freq_info;
>> +	struct dev_pm_opp *new_opp;
>> +	unsigned long old_freq, new_freq;
>> +	int ret;
>> +
>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>> +	if (IS_ERR(new_opp)) {
>> +		ret = PTR_ERR(new_opp);
>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>> +		return ret;
>> +	}
>> +	dev_pm_opp_put(new_opp);
>> +
>> +	old_freq = clk_get_rate(priv->dram_core);
>> +	if (*freq == old_freq)
>> +		return 0;
>> +
>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>> +	if (!freq_info)
>> +		return -EINVAL;
>> +
>> +	/*
>> +	 * Read back the clk rate to verify switch was correct and so that
>> +	 * we can report it on all error paths.
>> +	 */
>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>> +
>> +	new_freq = clk_get_rate(priv->dram_core);
>> +	if (ret)
>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>> +			old_freq, *freq, ret, new_freq);
>> +	else if (*freq != new_freq)
>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>> +			old_freq, *freq, new_freq);
> 
> Actually, is it error? When use clk_set_rate with target_freq,
> if target_freq is not same with supported clock of h/w clock,
> the clk_set_rate set the similiar clock rate among the supported clock table.
> 
> It means that if the user of clock_set_rate() enters the unsupported clock rate,
> the case of (*freq != new_freq) happen. 
> 
> Are you sure that you want to show the error when this case (*freq != new_freq)?
> The your origin code is not wrong. Just question from me.
> 
>> +	else
>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>> +			*freq, old_freq);
>> +
>> +	return ret;
>> +}
>> +
>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +
>> +	*freq = clk_get_rate(priv->dram_core);
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>> +				     struct devfreq_dev_status *stat)
> 
> get_dev_status() callback is called by only simpleondemand governor.
> When userspace governor is used, this function is never called.
> So, need to drop this function and then add this function on next time.
> 
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +
>> +	stat->busy_time = 0;
>> +	stat->total_time = 0;
>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct arm_smccc_res res;
>> +	int index;
>> +
>> +	/* An error here means DDR DVFS API not supported by firmware */
>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>> +			0, 0, 0, 0, 0, 0, &res);
>> +	priv->freq_count = res.a0;
>> +	if (priv->freq_count <= 0 ||
>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>> +		return -ENODEV;
>> +
>> +	for (index = 0; index < priv->freq_count; ++index) {
>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>> +
>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>> +			      index, 0, 0, 0, 0, 0, &res);
>> +		/* Result should be strictly positive */
>> +		if ((long)res.a0 <= 0)
>> +			return -ENODEV;
>> +
>> +		freq->rate = res.a0;
>> +		freq->smcarg = index;
>> +		freq->dram_core_parent_index = res.a1;
>> +		freq->dram_alt_parent_index = res.a2;
>> +		freq->dram_apb_parent_index = res.a3;
>> +
>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>> +		if (freq->dram_core_parent_index != 1 &&
>> +				freq->dram_core_parent_index != 2)
>> +			return -ENODEV;
>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>> +		if (freq->dram_alt_parent_index > 8 ||
>> +				freq->dram_apb_parent_index > 8)
>> +			return -ENODEV;
>> +		/* dram_core from alt requires explicit dram_alt parent */
>> +		if (freq->dram_core_parent_index == 2 &&
>> +				freq->dram_alt_parent_index == 0)
>> +			return -ENODEV;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx8m_ddrc_check_opps(struct device *dev)
>> +{
>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>> +	struct imx8m_ddrc_freq *freq_info;
>> +	struct dev_pm_opp *opp;
>> +	unsigned long freq;
>> +
>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>> +	freq = ULONG_MAX;
>> +	while (true) {

You can get the number of OPP entries int the opp table
with dev_pm_opp_get_count(dev). I think that better to
use the correct number of OPP entries instead of 'while(true)' style.

>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>> +		if (opp == ERR_PTR(-ERANGE))
>> +			break;
>> +		if (IS_ERR(opp)) {
>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>> +				PTR_ERR(opp));
>> +			return PTR_ERR(opp);
>> +		}
>> +		dev_pm_opp_put(opp);
>> +
>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>> +		if (!freq_info) {
>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>> +			dev_pm_opp_disable(dev, freq);
>> +		}
>> +
>> +		freq--;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void imx8m_ddrc_exit(struct device *dev)
>> +{
>> +	dev_pm_opp_of_remove_table(dev);
>> +}
>> +
>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct imx8m_ddrc *priv;
>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>> +	int ret;
>> +
>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	platform_set_drvdata(pdev, priv);
>> +
>> +	ret = imx8m_ddrc_init_freq_info(dev);
>> +	if (ret) {
>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	priv->dram_core = devm_clk_get(dev, "core");
>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>> +	if (IS_ERR(priv->dram_core) ||
>> +		IS_ERR(priv->dram_pll) ||
>> +		IS_ERR(priv->dram_alt) ||
>> +		IS_ERR(priv->dram_apb)) {
>> +		ret = PTR_ERR(priv->devfreq);
>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = dev_pm_opp_of_add_table(dev);
>> +	if (ret < 0) {
>> +		dev_err(dev, "failed to get OPP table\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = imx8m_ddrc_check_opps(dev);
>> +	if (ret < 0)
>> +		goto err;
>> +
>> +	priv->profile.polling_ms = 1000;
>> +	priv->profile.target = imx8m_ddrc_target;
>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
> 
> ditto. It is not used on this patch. On later, add the get_dev_status
> for the ondemand governor.
> 
>> +	priv->profile.exit = imx8m_ddrc_exit;
>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>> +
>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>> +						gov, NULL);
>> +	if (IS_ERR(priv->devfreq)) {
>> +		ret = PTR_ERR(priv->devfreq);
>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>> +		goto err;
>> +	}
>> +
>> +	return 0;
>> +
>> +err:
>> +	dev_pm_opp_of_remove_table(dev);
>> +	return ret;
>> +}
>> +
>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>> +	{ .compatible = "fsl,imx8m-ddrc", },
>> +	{ /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>> +
>> +static struct platform_driver imx8m_ddrc_platdrv = {
>> +	.probe		= imx8m_ddrc_probe,
>> +	.driver = {
>> +		.name	= "imx8m-ddrc-devfreq",
>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>> +	},
>> +};
>> +module_platform_driver(imx8m_ddrc_platdrv);
>> +
>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>> +MODULE_LICENSE("GPL v2");
>>
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* RE: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
  2019-11-12 21:50   ` Leonard Crestez
@ 2019-11-13  7:29     ` Peng Fan
  -1 siblings, 0 replies; 41+ messages in thread
From: Peng Fan @ 2019-11-13  7:29 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	dl-linux-imx, kernel, linux-arm-kernel

Hi Leonard,

> Subject: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with
> CLK_GET_RATE_NOCACHE

This patch will conflict with https://patchwork.kernel.org/cover/11224933/
And I just post a new patch https://patchwork.kernel.org/patch/11241231/
 
Then no need add imx_1443x_dram_pll

Regards,
Peng.

> 
> DRAM frequency switches are executed in firmware and can change the
> configuration of the DRAM PLL outside linux. Mark these CLKs with
> CLK_GET_RATE_NOCACHE so we always read back the PLL config registers
> and recalculate rates.
> 
> In current DRAM frequency tables on 8mm/8mn only the maximum frequency
> uses the PLL so it's always configured in the same way. However reading back
> the PLL configuration is the correct behavior and allows additional setpoints in
> the future.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
> ---
>  drivers/clk/imx/clk-imx8mm.c  | 2 +-
>  drivers/clk/imx/clk-imx8mn.c  | 2 +-
>  drivers/clk/imx/clk-pll14xx.c | 7 +++++++
>  drivers/clk/imx/clk.h         | 1 +
>  4 files changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
> index e2bc3c90d93c..9246e89bb5fd 100644
> --- a/drivers/clk/imx/clk-imx8mm.c
> +++ b/drivers/clk/imx/clk-imx8mm.c
> @@ -326,11 +326,11 @@ static int imx8mm_clocks_probe(struct
> platform_device *pdev)
>  	clks[IMX8MM_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
> 
>  	clks[IMX8MM_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>  	clks[IMX8MM_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>  	clks[IMX8MM_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
> -	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
> +	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>  	clks[IMX8MM_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
> base + 0x64, &imx_1416x_pll);
>  	clks[IMX8MM_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
> base + 0x74, &imx_1416x_pll);
>  	clks[IMX8MM_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
> base + 0x84, &imx_1416x_pll);
>  	clks[IMX8MM_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>  	clks[IMX8MM_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
> --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c index
> de905e278b80..4749beab9fc8 100644
> --- a/drivers/clk/imx/clk-imx8mn.c
> +++ b/drivers/clk/imx/clk-imx8mn.c
> @@ -323,11 +323,11 @@ static int imx8mn_clocks_probe(struct
> platform_device *pdev)
>  	clks[IMX8MN_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
> 
>  	clks[IMX8MN_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>  	clks[IMX8MN_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>  	clks[IMX8MN_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
> -	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
> +	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>  	clks[IMX8MN_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
> base + 0x64, &imx_1416x_pll);
>  	clks[IMX8MN_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
> base + 0x74, &imx_1416x_pll);
>  	clks[IMX8MN_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
> base + 0x84, &imx_1416x_pll);
>  	clks[IMX8MN_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>  	clks[IMX8MN_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
> --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c index
> 5c458199060a..a6d31a7262ef 100644
> --- a/drivers/clk/imx/clk-pll14xx.c
> +++ b/drivers/clk/imx/clk-pll14xx.c
> @@ -65,10 +65,17 @@ struct imx_pll14xx_clk imx_1443x_pll = {
>  	.type = PLL_1443X,
>  	.rate_table = imx_pll1443x_tbl,
>  	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),  };
> 
> +struct imx_pll14xx_clk imx_1443x_dram_pll = {
> +	.type = PLL_1443X,
> +	.rate_table = imx_pll1443x_tbl,
> +	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
> +	.flags = CLK_GET_RATE_NOCACHE,
> +};
> +
>  struct imx_pll14xx_clk imx_1416x_pll = {
>  	.type = PLL_1416X,
>  	.rate_table = imx_pll1416x_tbl,
>  	.rate_count = ARRAY_SIZE(imx_pll1416x_tbl),  }; diff --git
> a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index
> bc5bb6ac8636..81122c9ab842 100644
> --- a/drivers/clk/imx/clk.h
> +++ b/drivers/clk/imx/clk.h
> @@ -50,10 +50,11 @@ struct imx_pll14xx_clk {
>  	int flags;
>  };
> 
>  extern struct imx_pll14xx_clk imx_1416x_pll;  extern struct
> imx_pll14xx_clk imx_1443x_pll;
> +extern struct imx_pll14xx_clk imx_1443x_dram_pll;
> 
>  #define imx_clk_cpu(name, parent_name, div, mux, pll, step) \
>  	imx_clk_hw_cpu(name, parent_name, div, mux, pll, step)->clk
> 
>  #define clk_register_gate2(dev, name, parent_name, flags, reg, bit_idx, \
> --
> 2.17.1


^ permalink raw reply	[flat|nested] 41+ messages in thread

* RE: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
@ 2019-11-13  7:29     ` Peng Fan
  0 siblings, 0 replies; 41+ messages in thread
From: Peng Fan @ 2019-11-13  7:29 UTC (permalink / raw)
  To: Leonard Crestez, Stephen Boyd, Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, dl-linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Aisheng Dong, Anson Huang, Rafael J. Wysocki, Kyungmin Park,
	kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

Hi Leonard,

> Subject: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with
> CLK_GET_RATE_NOCACHE

This patch will conflict with https://patchwork.kernel.org/cover/11224933/
And I just post a new patch https://patchwork.kernel.org/patch/11241231/
 
Then no need add imx_1443x_dram_pll

Regards,
Peng.

> 
> DRAM frequency switches are executed in firmware and can change the
> configuration of the DRAM PLL outside linux. Mark these CLKs with
> CLK_GET_RATE_NOCACHE so we always read back the PLL config registers
> and recalculate rates.
> 
> In current DRAM frequency tables on 8mm/8mn only the maximum frequency
> uses the PLL so it's always configured in the same way. However reading back
> the PLL configuration is the correct behavior and allows additional setpoints in
> the future.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
> ---
>  drivers/clk/imx/clk-imx8mm.c  | 2 +-
>  drivers/clk/imx/clk-imx8mn.c  | 2 +-
>  drivers/clk/imx/clk-pll14xx.c | 7 +++++++
>  drivers/clk/imx/clk.h         | 1 +
>  4 files changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
> index e2bc3c90d93c..9246e89bb5fd 100644
> --- a/drivers/clk/imx/clk-imx8mm.c
> +++ b/drivers/clk/imx/clk-imx8mm.c
> @@ -326,11 +326,11 @@ static int imx8mm_clocks_probe(struct
> platform_device *pdev)
>  	clks[IMX8MM_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
> 
>  	clks[IMX8MM_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>  	clks[IMX8MM_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>  	clks[IMX8MM_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
> -	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
> +	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>  	clks[IMX8MM_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
> base + 0x64, &imx_1416x_pll);
>  	clks[IMX8MM_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
> base + 0x74, &imx_1416x_pll);
>  	clks[IMX8MM_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
> base + 0x84, &imx_1416x_pll);
>  	clks[IMX8MM_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>  	clks[IMX8MM_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
> --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c index
> de905e278b80..4749beab9fc8 100644
> --- a/drivers/clk/imx/clk-imx8mn.c
> +++ b/drivers/clk/imx/clk-imx8mn.c
> @@ -323,11 +323,11 @@ static int imx8mn_clocks_probe(struct
> platform_device *pdev)
>  	clks[IMX8MN_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
> 
>  	clks[IMX8MN_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>  	clks[IMX8MN_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>  	clks[IMX8MN_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
> -	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
> +	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>  	clks[IMX8MN_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
> base + 0x64, &imx_1416x_pll);
>  	clks[IMX8MN_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
> base + 0x74, &imx_1416x_pll);
>  	clks[IMX8MN_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
> base + 0x84, &imx_1416x_pll);
>  	clks[IMX8MN_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>  	clks[IMX8MN_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
> --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c index
> 5c458199060a..a6d31a7262ef 100644
> --- a/drivers/clk/imx/clk-pll14xx.c
> +++ b/drivers/clk/imx/clk-pll14xx.c
> @@ -65,10 +65,17 @@ struct imx_pll14xx_clk imx_1443x_pll = {
>  	.type = PLL_1443X,
>  	.rate_table = imx_pll1443x_tbl,
>  	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),  };
> 
> +struct imx_pll14xx_clk imx_1443x_dram_pll = {
> +	.type = PLL_1443X,
> +	.rate_table = imx_pll1443x_tbl,
> +	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
> +	.flags = CLK_GET_RATE_NOCACHE,
> +};
> +
>  struct imx_pll14xx_clk imx_1416x_pll = {
>  	.type = PLL_1416X,
>  	.rate_table = imx_pll1416x_tbl,
>  	.rate_count = ARRAY_SIZE(imx_pll1416x_tbl),  }; diff --git
> a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index
> bc5bb6ac8636..81122c9ab842 100644
> --- a/drivers/clk/imx/clk.h
> +++ b/drivers/clk/imx/clk.h
> @@ -50,10 +50,11 @@ struct imx_pll14xx_clk {
>  	int flags;
>  };
> 
>  extern struct imx_pll14xx_clk imx_1416x_pll;  extern struct
> imx_pll14xx_clk imx_1443x_pll;
> +extern struct imx_pll14xx_clk imx_1443x_dram_pll;
> 
>  #define imx_clk_cpu(name, parent_name, div, mux, pll, step) \
>  	imx_clk_hw_cpu(name, parent_name, div, mux, pll, step)->clk
> 
>  #define clk_register_gate2(dev, name, parent_name, flags, reg, bit_idx, \
> --
> 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	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
  2019-11-13  7:29     ` Peng Fan
@ 2019-11-13 12:02       ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-13 12:02 UTC (permalink / raw)
  To: Peng Fan, Stephen Boyd, Shawn Guo
  Cc: Chanwoo Choi, Rob Herring, MyungJoo Ham, Kyungmin Park,
	Rafael J. Wysocki, Mark Rutland, Michael Turquette,
	Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	dl-linux-imx, kernel, linux-arm-kernel

On 13.11.2019 09:29, Peng Fan wrote:
> Hi Leonard,
> 
>> Subject: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with
>> CLK_GET_RATE_NOCACHE
> 
> This patch will conflict with https://patchwork.kernel.org/cover/11224933/
> And I just post a new patch https://patchwork.kernel.org/patch/11241231/
>   
> Then no need add imx_1443x_dram_pll

I saw those patches and the conflicts are minor (API cleanups, no 
functionality changes).

I usually send patches against latest linux-next/master and this usually 
includes all accepted patches. If after the clk_hw refactorings are 
accepted I will rebase and resend.

> Regards,
> Peng.
> 
>>
>> DRAM frequency switches are executed in firmware and can change the
>> configuration of the DRAM PLL outside linux. Mark these CLKs with
>> CLK_GET_RATE_NOCACHE so we always read back the PLL config registers
>> and recalculate rates.
>>
>> In current DRAM frequency tables on 8mm/8mn only the maximum frequency
>> uses the PLL so it's always configured in the same way. However reading back
>> the PLL configuration is the correct behavior and allows additional setpoints in
>> the future.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
>> ---
>>   drivers/clk/imx/clk-imx8mm.c  | 2 +-
>>   drivers/clk/imx/clk-imx8mn.c  | 2 +-
>>   drivers/clk/imx/clk-pll14xx.c | 7 +++++++
>>   drivers/clk/imx/clk.h         | 1 +
>>   4 files changed, 10 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
>> index e2bc3c90d93c..9246e89bb5fd 100644
>> --- a/drivers/clk/imx/clk-imx8mm.c
>> +++ b/drivers/clk/imx/clk-imx8mm.c
>> @@ -326,11 +326,11 @@ static int imx8mm_clocks_probe(struct
>> platform_device *pdev)
>>   	clks[IMX8MM_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
>> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
>>
>>   	clks[IMX8MM_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
>> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>>   	clks[IMX8MM_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
>> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>>   	clks[IMX8MM_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
>> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
>> -	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
>> +	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>>   	clks[IMX8MM_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
>> base + 0x64, &imx_1416x_pll);
>>   	clks[IMX8MM_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
>> base + 0x74, &imx_1416x_pll);
>>   	clks[IMX8MM_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
>> base + 0x84, &imx_1416x_pll);
>>   	clks[IMX8MM_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>>   	clks[IMX8MM_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
>> --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c index
>> de905e278b80..4749beab9fc8 100644
>> --- a/drivers/clk/imx/clk-imx8mn.c
>> +++ b/drivers/clk/imx/clk-imx8mn.c
>> @@ -323,11 +323,11 @@ static int imx8mn_clocks_probe(struct
>> platform_device *pdev)
>>   	clks[IMX8MN_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
>> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
>>
>>   	clks[IMX8MN_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
>> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>>   	clks[IMX8MN_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
>> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>>   	clks[IMX8MN_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
>> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
>> -	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
>> +	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>>   	clks[IMX8MN_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
>> base + 0x64, &imx_1416x_pll);
>>   	clks[IMX8MN_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
>> base + 0x74, &imx_1416x_pll);
>>   	clks[IMX8MN_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
>> base + 0x84, &imx_1416x_pll);
>>   	clks[IMX8MN_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>>   	clks[IMX8MN_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
>> --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c index
>> 5c458199060a..a6d31a7262ef 100644
>> --- a/drivers/clk/imx/clk-pll14xx.c
>> +++ b/drivers/clk/imx/clk-pll14xx.c
>> @@ -65,10 +65,17 @@ struct imx_pll14xx_clk imx_1443x_pll = {
>>   	.type = PLL_1443X,
>>   	.rate_table = imx_pll1443x_tbl,
>>   	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),  };
>>
>> +struct imx_pll14xx_clk imx_1443x_dram_pll = {
>> +	.type = PLL_1443X,
>> +	.rate_table = imx_pll1443x_tbl,
>> +	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
>> +	.flags = CLK_GET_RATE_NOCACHE,
>> +};
>> +
>>   struct imx_pll14xx_clk imx_1416x_pll = {
>>   	.type = PLL_1416X,
>>   	.rate_table = imx_pll1416x_tbl,
>>   	.rate_count = ARRAY_SIZE(imx_pll1416x_tbl),  }; diff --git
>> a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index
>> bc5bb6ac8636..81122c9ab842 100644
>> --- a/drivers/clk/imx/clk.h
>> +++ b/drivers/clk/imx/clk.h
>> @@ -50,10 +50,11 @@ struct imx_pll14xx_clk {
>>   	int flags;
>>   };
>>
>>   extern struct imx_pll14xx_clk imx_1416x_pll;  extern struct
>> imx_pll14xx_clk imx_1443x_pll;
>> +extern struct imx_pll14xx_clk imx_1443x_dram_pll;
>>
>>   #define imx_clk_cpu(name, parent_name, div, mux, pll, step) \
>>   	imx_clk_hw_cpu(name, parent_name, div, mux, pll, step)->clk
>>
>>   #define clk_register_gate2(dev, name, parent_name, flags, reg, bit_idx, \
>> --
>> 2.17.1
> 
> 


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE
@ 2019-11-13 12:02       ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-13 12:02 UTC (permalink / raw)
  To: Peng Fan, Stephen Boyd, Shawn Guo
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, Chanwoo Choi, MyungJoo Ham,
	dl-linux-imx, devicetree, linux-pm, Rob Herring,
	Martin Kepplinger, linux-arm-kernel, Aisheng Dong, Anson Huang,
	Rafael J. Wysocki, Kyungmin Park, kernel, Fabio Estevam,
	Georgi Djakov

On 13.11.2019 09:29, Peng Fan wrote:
> Hi Leonard,
> 
>> Subject: [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with
>> CLK_GET_RATE_NOCACHE
> 
> This patch will conflict with https://patchwork.kernel.org/cover/11224933/
> And I just post a new patch https://patchwork.kernel.org/patch/11241231/
>   
> Then no need add imx_1443x_dram_pll

I saw those patches and the conflicts are minor (API cleanups, no 
functionality changes).

I usually send patches against latest linux-next/master and this usually 
includes all accepted patches. If after the clk_hw refactorings are 
accepted I will rebase and resend.

> Regards,
> Peng.
> 
>>
>> DRAM frequency switches are executed in firmware and can change the
>> configuration of the DRAM PLL outside linux. Mark these CLKs with
>> CLK_GET_RATE_NOCACHE so we always read back the PLL config registers
>> and recalculate rates.
>>
>> In current DRAM frequency tables on 8mm/8mn only the maximum frequency
>> uses the PLL so it's always configured in the same way. However reading back
>> the PLL configuration is the correct behavior and allows additional setpoints in
>> the future.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> Reviewed-by: Abel Vesa <abel.vesa@nxp.com>
>> ---
>>   drivers/clk/imx/clk-imx8mm.c  | 2 +-
>>   drivers/clk/imx/clk-imx8mn.c  | 2 +-
>>   drivers/clk/imx/clk-pll14xx.c | 7 +++++++
>>   drivers/clk/imx/clk.h         | 1 +
>>   4 files changed, 10 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c
>> index e2bc3c90d93c..9246e89bb5fd 100644
>> --- a/drivers/clk/imx/clk-imx8mm.c
>> +++ b/drivers/clk/imx/clk-imx8mm.c
>> @@ -326,11 +326,11 @@ static int imx8mm_clocks_probe(struct
>> platform_device *pdev)
>>   	clks[IMX8MM_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
>> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
>>
>>   	clks[IMX8MM_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
>> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>>   	clks[IMX8MM_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
>> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>>   	clks[IMX8MM_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
>> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
>> -	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
>> +	clks[IMX8MM_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>>   	clks[IMX8MM_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
>> base + 0x64, &imx_1416x_pll);
>>   	clks[IMX8MM_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
>> base + 0x74, &imx_1416x_pll);
>>   	clks[IMX8MM_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
>> base + 0x84, &imx_1416x_pll);
>>   	clks[IMX8MM_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>>   	clks[IMX8MM_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
>> --git a/drivers/clk/imx/clk-imx8mn.c b/drivers/clk/imx/clk-imx8mn.c index
>> de905e278b80..4749beab9fc8 100644
>> --- a/drivers/clk/imx/clk-imx8mn.c
>> +++ b/drivers/clk/imx/clk-imx8mn.c
>> @@ -323,11 +323,11 @@ static int imx8mn_clocks_probe(struct
>> platform_device *pdev)
>>   	clks[IMX8MN_SYS_PLL3_REF_SEL] = imx_clk_mux("sys_pll3_ref_sel",
>> base + 0x114, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));
>>
>>   	clks[IMX8MN_AUDIO_PLL1] = imx_clk_pll14xx("audio_pll1",
>> "audio_pll1_ref_sel", base, &imx_1443x_pll);
>>   	clks[IMX8MN_AUDIO_PLL2] = imx_clk_pll14xx("audio_pll2",
>> "audio_pll2_ref_sel", base + 0x14, &imx_1443x_pll);
>>   	clks[IMX8MN_VIDEO_PLL1] = imx_clk_pll14xx("video_pll1",
>> "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);
>> -	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> "dram_pll_ref_sel", base + 0x50, &imx_1443x_pll);
>> +	clks[IMX8MN_DRAM_PLL] = imx_clk_pll14xx("dram_pll",
>> +"dram_pll_ref_sel", base + 0x50, &imx_1443x_dram_pll);
>>   	clks[IMX8MN_GPU_PLL] = imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
>> base + 0x64, &imx_1416x_pll);
>>   	clks[IMX8MN_VPU_PLL] = imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
>> base + 0x74, &imx_1416x_pll);
>>   	clks[IMX8MN_ARM_PLL] = imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
>> base + 0x84, &imx_1416x_pll);
>>   	clks[IMX8MN_SYS_PLL1] = imx_clk_fixed("sys_pll1", 800000000);
>>   	clks[IMX8MN_SYS_PLL2] = imx_clk_fixed("sys_pll2", 1000000000); diff
>> --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c index
>> 5c458199060a..a6d31a7262ef 100644
>> --- a/drivers/clk/imx/clk-pll14xx.c
>> +++ b/drivers/clk/imx/clk-pll14xx.c
>> @@ -65,10 +65,17 @@ struct imx_pll14xx_clk imx_1443x_pll = {
>>   	.type = PLL_1443X,
>>   	.rate_table = imx_pll1443x_tbl,
>>   	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),  };
>>
>> +struct imx_pll14xx_clk imx_1443x_dram_pll = {
>> +	.type = PLL_1443X,
>> +	.rate_table = imx_pll1443x_tbl,
>> +	.rate_count = ARRAY_SIZE(imx_pll1443x_tbl),
>> +	.flags = CLK_GET_RATE_NOCACHE,
>> +};
>> +
>>   struct imx_pll14xx_clk imx_1416x_pll = {
>>   	.type = PLL_1416X,
>>   	.rate_table = imx_pll1416x_tbl,
>>   	.rate_count = ARRAY_SIZE(imx_pll1416x_tbl),  }; diff --git
>> a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index
>> bc5bb6ac8636..81122c9ab842 100644
>> --- a/drivers/clk/imx/clk.h
>> +++ b/drivers/clk/imx/clk.h
>> @@ -50,10 +50,11 @@ struct imx_pll14xx_clk {
>>   	int flags;
>>   };
>>
>>   extern struct imx_pll14xx_clk imx_1416x_pll;  extern struct
>> imx_pll14xx_clk imx_1443x_pll;
>> +extern struct imx_pll14xx_clk imx_1443x_dram_pll;
>>
>>   #define imx_clk_cpu(name, parent_name, div, mux, pll, step) \
>>   	imx_clk_hw_cpu(name, parent_name, div, mux, pll, step)->clk
>>
>>   #define clk_register_gate2(dev, name, parent_name, flags, reg, bit_idx, \
>> --
>> 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	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
  2019-11-13  2:38     ` Chanwoo Choi
@ 2019-11-13 12:35       ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-13 12:35 UTC (permalink / raw)
  To: Chanwoo Choi, Rob Herring
  Cc: Stephen Boyd, MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki,
	Shawn Guo, Mark Rutland, Michael Turquette,
	Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	dl-linux-imx, kernel, linux-arm-kernel

On 13.11.2019 04:32, Chanwoo Choi wrote:
> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>> Add devicetree bindings for the i.MX DDR Controller on imx8m series
>> chips. It supports dynamic frequency switching between multiple data
>> rates and this is exposed to Linux via the devfreq subsystem.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> ---
>>   .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
>>   1 file changed, 57 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>> new file mode 100644
>> index 000000000000..7c98e3509f75
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>> @@ -0,0 +1,57 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +%YAML 1.2
>> +---
>> +$id: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fschemas%2Fmemory-controllers%2Ffsl%2Fimx8m-ddrc.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=frWd1MENZm%2FsPjQp%2FWbphMgkkCMtwsgV8hLQyIhC3%2BI%3D&amp;reserved=0
>> +$schema: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fmeta-schemas%2Fcore.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=4IweKQJO9ZsB%2B9QxixSQjfYOFm3%2FY7iMHFBSsquK1B0%3D&amp;reserved=0
>> +
>> +title: i.MX8M DDR Controller
>> +
>> +maintainers:
>> +  - Leonard Crestez <leonard.crestez@nxp.com>
>> +
>> +properties:
>> +  compatible:
>> +    items:
>> +      - enum:
>> +        - fsl,imx8mn-ddrc
>> +        - fsl,imx8mm-ddrc
>> +        - fsl,imx8mq-ddrc
>> +      - const: fsl,imx8m-ddrc
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    maxItems: 4
>> +
>> +  clock-names:
>> +    items:
>> +      - const: core
>> +      - const: pll
>> +      - const: alt
>> +      - const: apb
>> +
>> +  operating-points-v2: true
>> +  opp-table: true
>> +
>> +required:
>> +  - reg
>> +  - compatible
>> +  - clocks
>> +  - clock-names
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/clock/imx8mm-clock.h>
>> +    ddrc: memory-controller@3d400000 {
>> +        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
>> +        reg = <0x3d400000 0x400000>;
> 
> The probe() function doesn't get the IORESOURCE_MEM from dt?
> Is it needed?

This area is not currently mapped by the driver. As far as I understand 
it's acceptable to "describe hardware" even if you don't use the full 
description in driver code.

If I were to remove the "reg" area wouldn't I also have to move the node 
outside of the bus to keep DT validation? It's better to keep the address.

Maybe it will be mapped in the future or maybe firmware will start to 
parse linux DT instead of hardcoding SOC-specific addresses (this 
already happens in some cases).

>> +        clock-names = "core", "pll", "alt", "apb";
>> +        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
>> +                 <&clk IMX8MM_DRAM_PLL>,
>> +                 <&clk IMX8MM_CLK_DRAM_ALT>,
>> +                 <&clk IMX8MM_CLK_DRAM_APB>;
>> +        operating-points-v2 = <&ddrc_opp_table>;
>> +    };

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
@ 2019-11-13 12:35       ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-13 12:35 UTC (permalink / raw)
  To: Chanwoo Choi, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, dl-linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Aisheng Dong, Anson Huang, Stephen Boyd, Rafael J. Wysocki,
	Kyungmin Park, kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

On 13.11.2019 04:32, Chanwoo Choi wrote:
> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>> Add devicetree bindings for the i.MX DDR Controller on imx8m series
>> chips. It supports dynamic frequency switching between multiple data
>> rates and this is exposed to Linux via the devfreq subsystem.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> ---
>>   .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
>>   1 file changed, 57 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>> new file mode 100644
>> index 000000000000..7c98e3509f75
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>> @@ -0,0 +1,57 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +%YAML 1.2
>> +---
>> +$id: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fschemas%2Fmemory-controllers%2Ffsl%2Fimx8m-ddrc.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=frWd1MENZm%2FsPjQp%2FWbphMgkkCMtwsgV8hLQyIhC3%2BI%3D&amp;reserved=0
>> +$schema: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fmeta-schemas%2Fcore.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=4IweKQJO9ZsB%2B9QxixSQjfYOFm3%2FY7iMHFBSsquK1B0%3D&amp;reserved=0
>> +
>> +title: i.MX8M DDR Controller
>> +
>> +maintainers:
>> +  - Leonard Crestez <leonard.crestez@nxp.com>
>> +
>> +properties:
>> +  compatible:
>> +    items:
>> +      - enum:
>> +        - fsl,imx8mn-ddrc
>> +        - fsl,imx8mm-ddrc
>> +        - fsl,imx8mq-ddrc
>> +      - const: fsl,imx8m-ddrc
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    maxItems: 4
>> +
>> +  clock-names:
>> +    items:
>> +      - const: core
>> +      - const: pll
>> +      - const: alt
>> +      - const: apb
>> +
>> +  operating-points-v2: true
>> +  opp-table: true
>> +
>> +required:
>> +  - reg
>> +  - compatible
>> +  - clocks
>> +  - clock-names
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/clock/imx8mm-clock.h>
>> +    ddrc: memory-controller@3d400000 {
>> +        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
>> +        reg = <0x3d400000 0x400000>;
> 
> The probe() function doesn't get the IORESOURCE_MEM from dt?
> Is it needed?

This area is not currently mapped by the driver. As far as I understand 
it's acceptable to "describe hardware" even if you don't use the full 
description in driver code.

If I were to remove the "reg" area wouldn't I also have to move the node 
outside of the bus to keep DT validation? It's better to keep the address.

Maybe it will be mapped in the future or maybe firmware will start to 
parse linux DT instead of hardcoding SOC-specific addresses (this 
already happens in some cases).

>> +        clock-names = "core", "pll", "alt", "apb";
>> +        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
>> +                 <&clk IMX8MM_DRAM_PLL>,
>> +                 <&clk IMX8MM_CLK_DRAM_ALT>,
>> +                 <&clk IMX8MM_CLK_DRAM_APB>;
>> +        operating-points-v2 = <&ddrc_opp_table>;
>> +    };

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-13  6:28       ` Chanwoo Choi
@ 2019-11-13 13:10         ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-13 13:10 UTC (permalink / raw)
  To: Chanwoo Choi, Viresh Kumar
  Cc: Stephen Boyd, Rob Herring, MyungJoo Ham, Kyungmin Park,
	Rafael J. Wysocki, Shawn Guo, Mark Rutland, Michael Turquette,
	Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, devicetree, linux-pm, linux-clk, dl-linux-imx,
	kernel, linux-arm-kernel

On 13.11.2019 08:23, Chanwoo Choi wrote:
> On 11/13/19 11:30 AM, Chanwoo Choi wrote:
>> Hi Leonard,
>>
>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>>> frequency switching is implemented inside TF-A, this driver wraps the
>>> SMC calls and synchronizes the clk tree.
>>>
>>> The DRAM clocks on imx8m have the following structure (abridged):
>>>
>>>   +----------+       |\            +------+
>>>   | dram_pll |-------|M| dram_core |      |
>>>   +----------+       |U|---------->| D    |
>>>                   /--|X|           |  D   |
>>>     dram_alt_root |  |/            |   R  |
>>>                   |                |    C |
>>>              +---------+           |      |
>>>              |FIX DIV/4|           |      |
>>>              +---------+           |      |
>>>    composite:     |                |      |
>>>   +----------+    |                |      |
>>>   | dram_alt |----/                |      |
>>>   +----------+                     |      |
>>>   | dram_apb |-------------------->|      |
>>>   +----------+                     +------+
>>>
>>> The dram_pll is used for higher rates and dram_alt is used for lower
>>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>>> parent can also be modified.
>>>
>>> This driver will prepare/enable the new parents ahead of switching (so
>>> that the expected roots are enabled) and afterwards it will call
>>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>>
>>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>>> CLK_GET_RATE_NOCACHE for rate updates.
>>>
>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>> ---
>>>   drivers/devfreq/Kconfig      |   9 +
>>>   drivers/devfreq/Makefile     |   1 +
>>>   drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>>   3 files changed, 470 insertions(+)
>>>   create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>>
>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>> index 066e6c4efaa2..923a6132e741 100644
>>> --- a/drivers/devfreq/Kconfig
>>> +++ b/drivers/devfreq/Kconfig
>>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>>   	  Each memory bus group could contain many memoby bus block. It reads
>>>   	  PPMU counters of memory controllers by using DEVFREQ-event device
>>>   	  and adjusts the operating frequencies and voltages with OPP support.
>>>   	  This does not yet operate with optimal voltages.
>>>   
>>> +config ARM_IMX8M_DDRC_DEVFREQ
>>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>>> +	depends on ARCH_MXC || COMPILE_TEST
>>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>> +	select DEVFREQ_GOV_USERSPACE
>>> +	help
>>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>>> +	  adjusting DRAM frequency.
>>> +
>>>   config ARM_TEGRA_DEVFREQ
>>>   	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>>   	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>>   		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>>   		ARCH_TEGRA_210_SOC || \
>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>> index 338ae8440db6..3eb4d5e6635c 100644
>>> --- a/drivers/devfreq/Makefile
>>> +++ b/drivers/devfreq/Makefile
>>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>>   obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>>   obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>>   
>>>   # DEVFREQ Drivers
>>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>>   obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>>   obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>>   
>>>   # DEVFREQ Event Drivers
>>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>>> new file mode 100644
>>> index 000000000000..62abb9b79d8a
>>> --- /dev/null
>>> +++ b/drivers/devfreq/imx8m-ddrc.c
>>> @@ -0,0 +1,460 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Copyright 2019 NXP
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/device.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/devfreq.h>
>>> +#include <linux/pm_opp.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/clk-provider.h>
>>> +#include <linux/arm-smccc.h>
>>> +
>>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>>> +
>>> +/* Values starting from 0 switch to specific frequency */
>>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>>> +
>>> +/* Deprecated after moving IRQ handling to ATF */
>>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>>> +
>>> +/* Query available frequencies. */
>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>>> +
>>> +/*
>>> + * This should be in a 1:1 mapping with devicetree OPPs but
>>> + * firmware provides additional info.
>>> + */
>>> +struct imx8m_ddrc_freq {
>>> +	unsigned long rate;
>>> +	unsigned long smcarg;
>>> +	int dram_core_parent_index;
>>> +	int dram_alt_parent_index;
>>> +	int dram_apb_parent_index;
>>> +};
>>> +
>>> +/* Hardware limitation */
>>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>>> +
>>> +/*
>>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>>> + *
>>> + * +----------+       |\            +------+
>>> + * | dram_pll |-------|M| dram_core |      |
>>> + * +----------+       |U|---------->| D    |
>>> + *                 /--|X|           |  D   |
>>> + *   dram_alt_root |  |/            |   R  |
>>> + *                 |                |    C |
>>> + *            +---------+           |      |
>>> + *            |FIX DIV/4|           |      |
>>> + *            +---------+           |      |
>>> + *  composite:     |                |      |
>>> + * +----------+    |                |      |
>>> + * | dram_alt |----/                |      |
>>> + * +----------+                     |      |
>>> + * | dram_apb |-------------------->|      |
>>> + * +----------+                     +------+
>>> + *
>>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>>> + *
>>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>>> + * configuration of the clocks, including mux parents. The dram_alt and
>>> + * dram_apb clocks are "imx composite" and their parent can change too.
>>> + *
>>> + * We need to prepare/enable the new mux parents head of switching and update
>>> + * their information afterwards.
>>> + */
>>> +struct imx8m_ddrc {
>>> +	struct devfreq_dev_profile profile;
>>> +	struct devfreq *devfreq;
>>> +
>>> +	/* For frequency switching: */
>>> +	struct clk *dram_core;
>>> +	struct clk *dram_pll;
>>> +	struct clk *dram_alt;
>>> +	struct clk *dram_apb;
>>> +
>>> +	int freq_count;
>>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>>> +};
>>> +
>>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>>> +						    unsigned long rate)
>>> +{
>>> +	struct imx8m_ddrc_freq *freq;
>>> +	int i;
>>> +
>>> +	/*
>>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>>> +	 * Rounding is extra generous to ensure a match.
>>> +	 */
>>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>>> +	for (i = 0; i < priv->freq_count; ++i) {
>>> +		freq = &priv->freq_table[i];
>>> +		if (freq->rate == rate ||
>>> +				freq->rate + 1 == rate ||
>>> +				freq->rate - 1 == rate)
>>> +			return freq;
>>> +	}
>>> +
>>> +	return NULL;
>>> +}
>>> +
>>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>>> +{
>>> +	struct arm_smccc_res res;
>>> +	u32 online_cpus = 0;
>>> +	int cpu;
>>> +
>>> +	local_irq_disable();
>>> +
>>> +	for_each_online_cpu(cpu)
>>> +		online_cpus |= (1 << (cpu * 8));
>>> +
>>> +	/* change the ddr freqency */
>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>>> +			0, 0, 0, 0, 0, &res);
>>> +
>>> +	local_irq_enable();
>>> +}
>>> +
>>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>>> +{
>>> +	struct clk_hw *hw;
>>> +
>>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>>> +
>>> +	return hw ? hw->clk : NULL;
>>> +}
>>> +
>>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct clk *new_dram_core_parent;
>>> +	struct clk *new_dram_alt_parent;
>>> +	struct clk *new_dram_apb_parent;
>>> +	int ret;
>>> +
>>> +	/*
>>> +	 * Fetch new parents
>>> +	 *
>>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>>> +	 * new_dram_core_parent is not.
>>> +	 */
>>> +	new_dram_core_parent = clk_get_parent_by_index(
>>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>>> +	if (!new_dram_core_parent) {
>>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>>> +		return -EINVAL;
>>> +	}
>>> +	if (freq->dram_alt_parent_index) {
>>> +		new_dram_alt_parent = clk_get_parent_by_index(
>>> +				priv->dram_alt,
>>> +				freq->dram_alt_parent_index - 1);
>>> +		if (!new_dram_alt_parent) {
>>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>>> +			return -EINVAL;
>>> +		}
>>> +	} else
>>> +		new_dram_alt_parent = NULL;
>>> +
>>> +	if (freq->dram_alt_parent_index) {
>>> +		new_dram_apb_parent = clk_get_parent_by_index(
>>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>>> +		if (!new_dram_alt_parent) {
>>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>>> +			return -EINVAL;
>>> +		}
>>> +	} else
>>> +		new_dram_apb_parent = NULL;
>>> +
>>> +	/* increase reference counts and ensure clks are ON before switch */
>>> +	ret = clk_prepare_enable(new_dram_core_parent);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
>>
>> s/failed enable/failed to enable
>>
>>> +		goto out;
>>> +	}
>>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
>>
>> s/failed enable/failed to enable
>>
>>> +		goto out_disable_core_parent;
>>> +	}
>>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
>>
>> s/failed enable/failed to enable
>>
>>> +		goto out_disable_alt_parent;
>>> +	}
>>> +
>>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>>> +
>>> +	/* update parents in clk tree after switch. */
>>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>>> +	if (ret)
>>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
>>
>> s/failed set/failed to set
>>
>>> +	if (new_dram_alt_parent) {
>>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>>> +		if (ret)
>>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
>>
>> s/failed set/failed to set
>>
>>> +	}
>>> +	if (new_dram_apb_parent) {
>>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>>> +		if (ret)
>>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
>>
>> s/failed set/failed to set

OK, but this might make a few messages longer than 80 chars.

>>> +	}
>>> +
>>> +	/*
>>> +	 * Explicitly refresh dram PLL rate.
>>> +	 *
>>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>>> +	 * automatically refreshed when clk_get_rate is called on children.
>>> +	 */
>>> +	clk_get_rate(priv->dram_pll);
>>> +
>>> +	/*
>>> +	 * clk_set_parent transfer the reference count from old parent.
>>> +	 * now we drop extra reference counts used during the switch
>>> +	 */
>>> +	clk_disable_unprepare(new_dram_apb_parent);
>>> +out_disable_alt_parent:
>>> +	clk_disable_unprepare(new_dram_alt_parent);
>>> +out_disable_core_parent:
>>> +	clk_disable_unprepare(new_dram_core_parent);
>>> +out:
>>> +	return ret;
>>> +}
>>> +
>>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct imx8m_ddrc_freq *freq_info;
>>> +	struct dev_pm_opp *new_opp;
>>> +	unsigned long old_freq, new_freq;
>>> +	int ret;
>>> +
>>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>>> +	if (IS_ERR(new_opp)) {
>>> +		ret = PTR_ERR(new_opp);
>>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +	dev_pm_opp_put(new_opp);
>>> +
>>> +	old_freq = clk_get_rate(priv->dram_core);
>>> +	if (*freq == old_freq)
>>> +		return 0;
>>> +
>>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>>> +	if (!freq_info)
>>> +		return -EINVAL;
>>> +
>>> +	/*
>>> +	 * Read back the clk rate to verify switch was correct and so that
>>> +	 * we can report it on all error paths.
>>> +	 */
>>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>>> +
>>> +	new_freq = clk_get_rate(priv->dram_core);
>>> +	if (ret)
>>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>>> +			old_freq, *freq, ret, new_freq);
>>> +	else if (*freq != new_freq)
>>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>>> +			old_freq, *freq, new_freq);
>>
>> Actually, is it error? When use clk_set_rate with target_freq,
>> if target_freq is not same with supported clock of h/w clock,
>> the clk_set_rate set the similiar clock rate among the supported clock table.
>>
>> It means that if the user of clock_set_rate() enters the unsupported clock rate,
>> the case of (*freq != new_freq) happen.
>>
>> Are you sure that you want to show the error when this case (*freq != new_freq)?
>> The your origin code is not wrong. Just question from me.

The assumption here is that the OPP table will contain the precise 
frequency as reported by clk_get_rate after a switch.

For example imx8mq-evk.dts has an OPP of exactly 166935483 Hz.

>>> +	else
>>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>>> +			*freq, old_freq);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +
>>> +	*freq = clk_get_rate(priv->dram_core);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>>> +				     struct devfreq_dev_status *stat)
>>
>> get_dev_status() callback is called by only simpleondemand governor.
>> When userspace governor is used, this function is never called.
>> So, need to drop this function and then add this function on next time.

Then you get an oops on "echo simple_ondemand > governor".

In theory the simple_ondemand governor could check for NULL 
"get_dev_status" or devfreq core could reject switching to 
simple_ondemand if no get_dev_status is implemented. For example a 
devfreq_governor.validate callback could be implemented?

But right now the "get_dev_status" callback is NOT optional.

>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +
>>> +	stat->busy_time = 0;
>>> +	stat->total_time = 0;
>>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct arm_smccc_res res;
>>> +	int index;
>>> +
>>> +	/* An error here means DDR DVFS API not supported by firmware */
>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>>> +			0, 0, 0, 0, 0, 0, &res);
>>> +	priv->freq_count = res.a0;
>>> +	if (priv->freq_count <= 0 ||
>>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>>> +		return -ENODEV;
>>> +
>>> +	for (index = 0; index < priv->freq_count; ++index) {
>>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>>> +
>>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>>> +			      index, 0, 0, 0, 0, 0, &res);
>>> +		/* Result should be strictly positive */
>>> +		if ((long)res.a0 <= 0)
>>> +			return -ENODEV;
>>> +
>>> +		freq->rate = res.a0;
>>> +		freq->smcarg = index;
>>> +		freq->dram_core_parent_index = res.a1;
>>> +		freq->dram_alt_parent_index = res.a2;
>>> +		freq->dram_apb_parent_index = res.a3;
>>> +
>>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>>> +		if (freq->dram_core_parent_index != 1 &&
>>> +				freq->dram_core_parent_index != 2)
>>> +			return -ENODEV;
>>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>>> +		if (freq->dram_alt_parent_index > 8 ||
>>> +				freq->dram_apb_parent_index > 8)
>>> +			return -ENODEV;
>>> +		/* dram_core from alt requires explicit dram_alt parent */
>>> +		if (freq->dram_core_parent_index == 2 &&
>>> +				freq->dram_alt_parent_index == 0)
>>> +			return -ENODEV;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int imx8m_ddrc_check_opps(struct device *dev)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct imx8m_ddrc_freq *freq_info;
>>> +	struct dev_pm_opp *opp;
>>> +	unsigned long freq;
>>> +
>>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>>> +	freq = ULONG_MAX;
>>> +	while (true) {
> 
> You can get the number of OPP entries int the opp table
> with dev_pm_opp_get_count(dev). I think that better to
> use the correct number of OPP entries instead of 'while(true)' style.

I need to enumerate frequencies and there's no "get_freq_by_index" in 
opp core that I can find so I'd still need to use 
dev_pm_opp_find_freq_floor.

It's strange that OPP core doesn't offer additional support for 
enumerating OPPs like a for_each macro?

>>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>>> +		if (opp == ERR_PTR(-ERANGE))
>>> +			break;
>>> +		if (IS_ERR(opp)) {
>>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>>> +				PTR_ERR(opp));
>>> +			return PTR_ERR(opp);
>>> +		}
>>> +		dev_pm_opp_put(opp);
>>> +
>>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>>> +		if (!freq_info) {
>>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>>> +			dev_pm_opp_disable(dev, freq);
>>> +		}
>>> +
>>> +		freq--;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static void imx8m_ddrc_exit(struct device *dev)
>>> +{
>>> +	dev_pm_opp_of_remove_table(dev);
>>> +}
>>> +
>>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>>> +{
>>> +	struct device *dev = &pdev->dev;
>>> +	struct imx8m_ddrc *priv;
>>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>>> +	int ret;
>>> +
>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> +	if (!priv)
>>> +		return -ENOMEM;
>>> +
>>> +	platform_set_drvdata(pdev, priv);
>>> +
>>> +	ret = imx8m_ddrc_init_freq_info(dev);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	priv->dram_core = devm_clk_get(dev, "core");
>>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>>> +	if (IS_ERR(priv->dram_core) ||
>>> +		IS_ERR(priv->dram_pll) ||
>>> +		IS_ERR(priv->dram_alt) ||
>>> +		IS_ERR(priv->dram_apb)) {
>>> +		ret = PTR_ERR(priv->devfreq);
>>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = dev_pm_opp_of_add_table(dev);
>>> +	if (ret < 0) {
>>> +		dev_err(dev, "failed to get OPP table\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = imx8m_ddrc_check_opps(dev);
>>> +	if (ret < 0)
>>> +		goto err;
>>> +
>>> +	priv->profile.polling_ms = 1000;
>>> +	priv->profile.target = imx8m_ddrc_target;
>>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
>>
>> ditto. It is not used on this patch. On later, add the get_dev_status
>> for the ondemand governor.
>>
>>> +	priv->profile.exit = imx8m_ddrc_exit;
>>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>>> +
>>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>>> +						gov, NULL);
>>> +	if (IS_ERR(priv->devfreq)) {
>>> +		ret = PTR_ERR(priv->devfreq);
>>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>>> +		goto err;
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err:
>>> +	dev_pm_opp_of_remove_table(dev);
>>> +	return ret;
>>> +}
>>> +
>>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>>> +	{ .compatible = "fsl,imx8m-ddrc", },
>>> +	{ /* sentinel */ },
>>> +};
>>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>>> +
>>> +static struct platform_driver imx8m_ddrc_platdrv = {
>>> +	.probe		= imx8m_ddrc_probe,
>>> +	.driver = {
>>> +		.name	= "imx8m-ddrc-devfreq",
>>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>>> +	},
>>> +};
>>> +module_platform_driver(imx8m_ddrc_platdrv);
>>> +
>>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>>> +MODULE_LICENSE("GPL v2");

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-13 13:10         ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-13 13:10 UTC (permalink / raw)
  To: Chanwoo Choi, Viresh Kumar
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Michael Turquette, Angus Ainslie, Alexandre Bailon,
	Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, dl-linux-imx,
	devicetree, linux-pm, Rob Herring, Martin Kepplinger,
	linux-arm-kernel, Aisheng Dong, Anson Huang, Stephen Boyd,
	Rafael J. Wysocki, Kyungmin Park, kernel, Fabio Estevam,
	Shawn Guo, Georgi Djakov

On 13.11.2019 08:23, Chanwoo Choi wrote:
> On 11/13/19 11:30 AM, Chanwoo Choi wrote:
>> Hi Leonard,
>>
>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>>> frequency switching is implemented inside TF-A, this driver wraps the
>>> SMC calls and synchronizes the clk tree.
>>>
>>> The DRAM clocks on imx8m have the following structure (abridged):
>>>
>>>   +----------+       |\            +------+
>>>   | dram_pll |-------|M| dram_core |      |
>>>   +----------+       |U|---------->| D    |
>>>                   /--|X|           |  D   |
>>>     dram_alt_root |  |/            |   R  |
>>>                   |                |    C |
>>>              +---------+           |      |
>>>              |FIX DIV/4|           |      |
>>>              +---------+           |      |
>>>    composite:     |                |      |
>>>   +----------+    |                |      |
>>>   | dram_alt |----/                |      |
>>>   +----------+                     |      |
>>>   | dram_apb |-------------------->|      |
>>>   +----------+                     +------+
>>>
>>> The dram_pll is used for higher rates and dram_alt is used for lower
>>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>>> parent can also be modified.
>>>
>>> This driver will prepare/enable the new parents ahead of switching (so
>>> that the expected roots are enabled) and afterwards it will call
>>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>>
>>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>>> CLK_GET_RATE_NOCACHE for rate updates.
>>>
>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>> ---
>>>   drivers/devfreq/Kconfig      |   9 +
>>>   drivers/devfreq/Makefile     |   1 +
>>>   drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>>   3 files changed, 470 insertions(+)
>>>   create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>>
>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>> index 066e6c4efaa2..923a6132e741 100644
>>> --- a/drivers/devfreq/Kconfig
>>> +++ b/drivers/devfreq/Kconfig
>>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>>   	  Each memory bus group could contain many memoby bus block. It reads
>>>   	  PPMU counters of memory controllers by using DEVFREQ-event device
>>>   	  and adjusts the operating frequencies and voltages with OPP support.
>>>   	  This does not yet operate with optimal voltages.
>>>   
>>> +config ARM_IMX8M_DDRC_DEVFREQ
>>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>>> +	depends on ARCH_MXC || COMPILE_TEST
>>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>> +	select DEVFREQ_GOV_USERSPACE
>>> +	help
>>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>>> +	  adjusting DRAM frequency.
>>> +
>>>   config ARM_TEGRA_DEVFREQ
>>>   	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>>   	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>>   		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>>   		ARCH_TEGRA_210_SOC || \
>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>> index 338ae8440db6..3eb4d5e6635c 100644
>>> --- a/drivers/devfreq/Makefile
>>> +++ b/drivers/devfreq/Makefile
>>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>>   obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>>   obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>>   
>>>   # DEVFREQ Drivers
>>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>>   obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>>   obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>>   
>>>   # DEVFREQ Event Drivers
>>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>>> new file mode 100644
>>> index 000000000000..62abb9b79d8a
>>> --- /dev/null
>>> +++ b/drivers/devfreq/imx8m-ddrc.c
>>> @@ -0,0 +1,460 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Copyright 2019 NXP
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/device.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/devfreq.h>
>>> +#include <linux/pm_opp.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/clk-provider.h>
>>> +#include <linux/arm-smccc.h>
>>> +
>>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>>> +
>>> +/* Values starting from 0 switch to specific frequency */
>>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>>> +
>>> +/* Deprecated after moving IRQ handling to ATF */
>>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>>> +
>>> +/* Query available frequencies. */
>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>>> +
>>> +/*
>>> + * This should be in a 1:1 mapping with devicetree OPPs but
>>> + * firmware provides additional info.
>>> + */
>>> +struct imx8m_ddrc_freq {
>>> +	unsigned long rate;
>>> +	unsigned long smcarg;
>>> +	int dram_core_parent_index;
>>> +	int dram_alt_parent_index;
>>> +	int dram_apb_parent_index;
>>> +};
>>> +
>>> +/* Hardware limitation */
>>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>>> +
>>> +/*
>>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>>> + *
>>> + * +----------+       |\            +------+
>>> + * | dram_pll |-------|M| dram_core |      |
>>> + * +----------+       |U|---------->| D    |
>>> + *                 /--|X|           |  D   |
>>> + *   dram_alt_root |  |/            |   R  |
>>> + *                 |                |    C |
>>> + *            +---------+           |      |
>>> + *            |FIX DIV/4|           |      |
>>> + *            +---------+           |      |
>>> + *  composite:     |                |      |
>>> + * +----------+    |                |      |
>>> + * | dram_alt |----/                |      |
>>> + * +----------+                     |      |
>>> + * | dram_apb |-------------------->|      |
>>> + * +----------+                     +------+
>>> + *
>>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>>> + *
>>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>>> + * configuration of the clocks, including mux parents. The dram_alt and
>>> + * dram_apb clocks are "imx composite" and their parent can change too.
>>> + *
>>> + * We need to prepare/enable the new mux parents head of switching and update
>>> + * their information afterwards.
>>> + */
>>> +struct imx8m_ddrc {
>>> +	struct devfreq_dev_profile profile;
>>> +	struct devfreq *devfreq;
>>> +
>>> +	/* For frequency switching: */
>>> +	struct clk *dram_core;
>>> +	struct clk *dram_pll;
>>> +	struct clk *dram_alt;
>>> +	struct clk *dram_apb;
>>> +
>>> +	int freq_count;
>>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>>> +};
>>> +
>>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>>> +						    unsigned long rate)
>>> +{
>>> +	struct imx8m_ddrc_freq *freq;
>>> +	int i;
>>> +
>>> +	/*
>>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>>> +	 * Rounding is extra generous to ensure a match.
>>> +	 */
>>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>>> +	for (i = 0; i < priv->freq_count; ++i) {
>>> +		freq = &priv->freq_table[i];
>>> +		if (freq->rate == rate ||
>>> +				freq->rate + 1 == rate ||
>>> +				freq->rate - 1 == rate)
>>> +			return freq;
>>> +	}
>>> +
>>> +	return NULL;
>>> +}
>>> +
>>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>>> +{
>>> +	struct arm_smccc_res res;
>>> +	u32 online_cpus = 0;
>>> +	int cpu;
>>> +
>>> +	local_irq_disable();
>>> +
>>> +	for_each_online_cpu(cpu)
>>> +		online_cpus |= (1 << (cpu * 8));
>>> +
>>> +	/* change the ddr freqency */
>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>>> +			0, 0, 0, 0, 0, &res);
>>> +
>>> +	local_irq_enable();
>>> +}
>>> +
>>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>>> +{
>>> +	struct clk_hw *hw;
>>> +
>>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>>> +
>>> +	return hw ? hw->clk : NULL;
>>> +}
>>> +
>>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct clk *new_dram_core_parent;
>>> +	struct clk *new_dram_alt_parent;
>>> +	struct clk *new_dram_apb_parent;
>>> +	int ret;
>>> +
>>> +	/*
>>> +	 * Fetch new parents
>>> +	 *
>>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>>> +	 * new_dram_core_parent is not.
>>> +	 */
>>> +	new_dram_core_parent = clk_get_parent_by_index(
>>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>>> +	if (!new_dram_core_parent) {
>>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>>> +		return -EINVAL;
>>> +	}
>>> +	if (freq->dram_alt_parent_index) {
>>> +		new_dram_alt_parent = clk_get_parent_by_index(
>>> +				priv->dram_alt,
>>> +				freq->dram_alt_parent_index - 1);
>>> +		if (!new_dram_alt_parent) {
>>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>>> +			return -EINVAL;
>>> +		}
>>> +	} else
>>> +		new_dram_alt_parent = NULL;
>>> +
>>> +	if (freq->dram_alt_parent_index) {
>>> +		new_dram_apb_parent = clk_get_parent_by_index(
>>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>>> +		if (!new_dram_alt_parent) {
>>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>>> +			return -EINVAL;
>>> +		}
>>> +	} else
>>> +		new_dram_apb_parent = NULL;
>>> +
>>> +	/* increase reference counts and ensure clks are ON before switch */
>>> +	ret = clk_prepare_enable(new_dram_core_parent);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
>>
>> s/failed enable/failed to enable
>>
>>> +		goto out;
>>> +	}
>>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
>>
>> s/failed enable/failed to enable
>>
>>> +		goto out_disable_core_parent;
>>> +	}
>>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
>>
>> s/failed enable/failed to enable
>>
>>> +		goto out_disable_alt_parent;
>>> +	}
>>> +
>>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>>> +
>>> +	/* update parents in clk tree after switch. */
>>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>>> +	if (ret)
>>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
>>
>> s/failed set/failed to set
>>
>>> +	if (new_dram_alt_parent) {
>>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>>> +		if (ret)
>>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
>>
>> s/failed set/failed to set
>>
>>> +	}
>>> +	if (new_dram_apb_parent) {
>>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>>> +		if (ret)
>>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
>>
>> s/failed set/failed to set

OK, but this might make a few messages longer than 80 chars.

>>> +	}
>>> +
>>> +	/*
>>> +	 * Explicitly refresh dram PLL rate.
>>> +	 *
>>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>>> +	 * automatically refreshed when clk_get_rate is called on children.
>>> +	 */
>>> +	clk_get_rate(priv->dram_pll);
>>> +
>>> +	/*
>>> +	 * clk_set_parent transfer the reference count from old parent.
>>> +	 * now we drop extra reference counts used during the switch
>>> +	 */
>>> +	clk_disable_unprepare(new_dram_apb_parent);
>>> +out_disable_alt_parent:
>>> +	clk_disable_unprepare(new_dram_alt_parent);
>>> +out_disable_core_parent:
>>> +	clk_disable_unprepare(new_dram_core_parent);
>>> +out:
>>> +	return ret;
>>> +}
>>> +
>>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct imx8m_ddrc_freq *freq_info;
>>> +	struct dev_pm_opp *new_opp;
>>> +	unsigned long old_freq, new_freq;
>>> +	int ret;
>>> +
>>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>>> +	if (IS_ERR(new_opp)) {
>>> +		ret = PTR_ERR(new_opp);
>>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +	dev_pm_opp_put(new_opp);
>>> +
>>> +	old_freq = clk_get_rate(priv->dram_core);
>>> +	if (*freq == old_freq)
>>> +		return 0;
>>> +
>>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>>> +	if (!freq_info)
>>> +		return -EINVAL;
>>> +
>>> +	/*
>>> +	 * Read back the clk rate to verify switch was correct and so that
>>> +	 * we can report it on all error paths.
>>> +	 */
>>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>>> +
>>> +	new_freq = clk_get_rate(priv->dram_core);
>>> +	if (ret)
>>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>>> +			old_freq, *freq, ret, new_freq);
>>> +	else if (*freq != new_freq)
>>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>>> +			old_freq, *freq, new_freq);
>>
>> Actually, is it error? When use clk_set_rate with target_freq,
>> if target_freq is not same with supported clock of h/w clock,
>> the clk_set_rate set the similiar clock rate among the supported clock table.
>>
>> It means that if the user of clock_set_rate() enters the unsupported clock rate,
>> the case of (*freq != new_freq) happen.
>>
>> Are you sure that you want to show the error when this case (*freq != new_freq)?
>> The your origin code is not wrong. Just question from me.

The assumption here is that the OPP table will contain the precise 
frequency as reported by clk_get_rate after a switch.

For example imx8mq-evk.dts has an OPP of exactly 166935483 Hz.

>>> +	else
>>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>>> +			*freq, old_freq);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +
>>> +	*freq = clk_get_rate(priv->dram_core);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>>> +				     struct devfreq_dev_status *stat)
>>
>> get_dev_status() callback is called by only simpleondemand governor.
>> When userspace governor is used, this function is never called.
>> So, need to drop this function and then add this function on next time.

Then you get an oops on "echo simple_ondemand > governor".

In theory the simple_ondemand governor could check for NULL 
"get_dev_status" or devfreq core could reject switching to 
simple_ondemand if no get_dev_status is implemented. For example a 
devfreq_governor.validate callback could be implemented?

But right now the "get_dev_status" callback is NOT optional.

>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +
>>> +	stat->busy_time = 0;
>>> +	stat->total_time = 0;
>>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct arm_smccc_res res;
>>> +	int index;
>>> +
>>> +	/* An error here means DDR DVFS API not supported by firmware */
>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>>> +			0, 0, 0, 0, 0, 0, &res);
>>> +	priv->freq_count = res.a0;
>>> +	if (priv->freq_count <= 0 ||
>>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>>> +		return -ENODEV;
>>> +
>>> +	for (index = 0; index < priv->freq_count; ++index) {
>>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>>> +
>>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>>> +			      index, 0, 0, 0, 0, 0, &res);
>>> +		/* Result should be strictly positive */
>>> +		if ((long)res.a0 <= 0)
>>> +			return -ENODEV;
>>> +
>>> +		freq->rate = res.a0;
>>> +		freq->smcarg = index;
>>> +		freq->dram_core_parent_index = res.a1;
>>> +		freq->dram_alt_parent_index = res.a2;
>>> +		freq->dram_apb_parent_index = res.a3;
>>> +
>>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>>> +		if (freq->dram_core_parent_index != 1 &&
>>> +				freq->dram_core_parent_index != 2)
>>> +			return -ENODEV;
>>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>>> +		if (freq->dram_alt_parent_index > 8 ||
>>> +				freq->dram_apb_parent_index > 8)
>>> +			return -ENODEV;
>>> +		/* dram_core from alt requires explicit dram_alt parent */
>>> +		if (freq->dram_core_parent_index == 2 &&
>>> +				freq->dram_alt_parent_index == 0)
>>> +			return -ENODEV;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int imx8m_ddrc_check_opps(struct device *dev)
>>> +{
>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>> +	struct imx8m_ddrc_freq *freq_info;
>>> +	struct dev_pm_opp *opp;
>>> +	unsigned long freq;
>>> +
>>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>>> +	freq = ULONG_MAX;
>>> +	while (true) {
> 
> You can get the number of OPP entries int the opp table
> with dev_pm_opp_get_count(dev). I think that better to
> use the correct number of OPP entries instead of 'while(true)' style.

I need to enumerate frequencies and there's no "get_freq_by_index" in 
opp core that I can find so I'd still need to use 
dev_pm_opp_find_freq_floor.

It's strange that OPP core doesn't offer additional support for 
enumerating OPPs like a for_each macro?

>>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>>> +		if (opp == ERR_PTR(-ERANGE))
>>> +			break;
>>> +		if (IS_ERR(opp)) {
>>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>>> +				PTR_ERR(opp));
>>> +			return PTR_ERR(opp);
>>> +		}
>>> +		dev_pm_opp_put(opp);
>>> +
>>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>>> +		if (!freq_info) {
>>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>>> +			dev_pm_opp_disable(dev, freq);
>>> +		}
>>> +
>>> +		freq--;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static void imx8m_ddrc_exit(struct device *dev)
>>> +{
>>> +	dev_pm_opp_of_remove_table(dev);
>>> +}
>>> +
>>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>>> +{
>>> +	struct device *dev = &pdev->dev;
>>> +	struct imx8m_ddrc *priv;
>>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>>> +	int ret;
>>> +
>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> +	if (!priv)
>>> +		return -ENOMEM;
>>> +
>>> +	platform_set_drvdata(pdev, priv);
>>> +
>>> +	ret = imx8m_ddrc_init_freq_info(dev);
>>> +	if (ret) {
>>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	priv->dram_core = devm_clk_get(dev, "core");
>>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>>> +	if (IS_ERR(priv->dram_core) ||
>>> +		IS_ERR(priv->dram_pll) ||
>>> +		IS_ERR(priv->dram_alt) ||
>>> +		IS_ERR(priv->dram_apb)) {
>>> +		ret = PTR_ERR(priv->devfreq);
>>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = dev_pm_opp_of_add_table(dev);
>>> +	if (ret < 0) {
>>> +		dev_err(dev, "failed to get OPP table\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = imx8m_ddrc_check_opps(dev);
>>> +	if (ret < 0)
>>> +		goto err;
>>> +
>>> +	priv->profile.polling_ms = 1000;
>>> +	priv->profile.target = imx8m_ddrc_target;
>>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
>>
>> ditto. It is not used on this patch. On later, add the get_dev_status
>> for the ondemand governor.
>>
>>> +	priv->profile.exit = imx8m_ddrc_exit;
>>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>>> +
>>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>>> +						gov, NULL);
>>> +	if (IS_ERR(priv->devfreq)) {
>>> +		ret = PTR_ERR(priv->devfreq);
>>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>>> +		goto err;
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err:
>>> +	dev_pm_opp_of_remove_table(dev);
>>> +	return ret;
>>> +}
>>> +
>>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>>> +	{ .compatible = "fsl,imx8m-ddrc", },
>>> +	{ /* sentinel */ },
>>> +};
>>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>>> +
>>> +static struct platform_driver imx8m_ddrc_platdrv = {
>>> +	.probe		= imx8m_ddrc_probe,
>>> +	.driver = {
>>> +		.name	= "imx8m-ddrc-devfreq",
>>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>>> +	},
>>> +};
>>> +module_platform_driver(imx8m_ddrc_platdrv);
>>> +
>>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>>> +MODULE_LICENSE("GPL v2");

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-12 21:50   ` Leonard Crestez
  (?)
@ 2019-11-13 14:07     ` kbuild test robot
  -1 siblings, 0 replies; 41+ messages in thread
From: kbuild test robot @ 2019-11-13 14:07 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: kbuild-all, Stephen Boyd, Chanwoo Choi, Rob Herring,
	MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

Hi Leonard,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on shawnguo/for-next]
[also build test WARNING on next-20191113]
[cannot apply to v5.4-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Leonard-Crestez/PM-devfreq-Add-dynamic-scaling-for-imx8m-ddr-controller/20191113-173930
base:   https://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux.git for-next
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-31-gfd3528a-dirty
        make ARCH=x86_64 allmodconfig
        make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)

>> drivers/devfreq/imx8m-ddrc.c:125:12: sparse: sparse: symbol 'clk_get_parent_by_index' was not declared. Should it be static?

Please review and possibly fold the followup patch.

---
0-DAY kernel test infrastructure                 Open Source Technology Center
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org Intel Corporation

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-13 14:07     ` kbuild test robot
  0 siblings, 0 replies; 41+ messages in thread
From: kbuild test robot @ 2019-11-13 14:07 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, Chanwoo Choi, MyungJoo Ham,
	linux-imx, devicetree, linux-pm, Rob Herring, Martin Kepplinger,
	linux-arm-kernel, Dong Aisheng, kbuild-all, Anson Huang,
	Stephen Boyd, Rafael J. Wysocki, Kyungmin Park, kernel,
	Fabio Estevam, Shawn Guo, Georgi Djakov

Hi Leonard,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on shawnguo/for-next]
[also build test WARNING on next-20191113]
[cannot apply to v5.4-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Leonard-Crestez/PM-devfreq-Add-dynamic-scaling-for-imx8m-ddr-controller/20191113-173930
base:   https://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux.git for-next
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-31-gfd3528a-dirty
        make ARCH=x86_64 allmodconfig
        make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)

>> drivers/devfreq/imx8m-ddrc.c:125:12: sparse: sparse: symbol 'clk_get_parent_by_index' was not declared. Should it be static?

Please review and possibly fold the followup patch.

---
0-DAY kernel test infrastructure                 Open Source Technology Center
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org Intel Corporation

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-13 14:07     ` kbuild test robot
  0 siblings, 0 replies; 41+ messages in thread
From: kbuild test robot @ 2019-11-13 14:07 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 1357 bytes --]

Hi Leonard,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on shawnguo/for-next]
[also build test WARNING on next-20191113]
[cannot apply to v5.4-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Leonard-Crestez/PM-devfreq-Add-dynamic-scaling-for-imx8m-ddr-controller/20191113-173930
base:   https://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux.git for-next
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-31-gfd3528a-dirty
        make ARCH=x86_64 allmodconfig
        make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)

>> drivers/devfreq/imx8m-ddrc.c:125:12: sparse: sparse: symbol 'clk_get_parent_by_index' was not declared. Should it be static?

Please review and possibly fold the followup patch.

---
0-DAY kernel test infrastructure                 Open Source Technology Center
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org Intel Corporation

^ permalink raw reply	[flat|nested] 41+ messages in thread

* [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static
  2019-11-12 21:50   ` Leonard Crestez
  (?)
@ 2019-11-13 14:07     ` kbuild test robot
  -1 siblings, 0 replies; 41+ messages in thread
From: kbuild test robot @ 2019-11-13 14:07 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: kbuild-all, Stephen Boyd, Chanwoo Choi, Rob Herring,
	MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki, Shawn Guo,
	Mark Rutland, Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel


Fixes: f01e004107f3 ("PM / devfreq: Add dynamic scaling for imx8m ddr controller")
Signed-off-by: kbuild test robot <lkp@intel.com>
---
 imx8m-ddrc.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
index 62abb9b79d8a0..228561de94425 100644
--- a/drivers/devfreq/imx8m-ddrc.c
+++ b/drivers/devfreq/imx8m-ddrc.c
@@ -122,7 +122,7 @@ static void imx8m_ddrc_smc_set_freq(int target_freq)
 	local_irq_enable();
 }
 
-struct clk *clk_get_parent_by_index(struct clk *clk, int index)
+static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
 {
 	struct clk_hw *hw;
 

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static
@ 2019-11-13 14:07     ` kbuild test robot
  0 siblings, 0 replies; 41+ messages in thread
From: kbuild test robot @ 2019-11-13 14:07 UTC (permalink / raw)
  To: Leonard Crestez
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, Chanwoo Choi, MyungJoo Ham,
	linux-imx, devicetree, linux-pm, Rob Herring, Martin Kepplinger,
	linux-arm-kernel, Dong Aisheng, kbuild-all, Anson Huang,
	Stephen Boyd, Rafael J. Wysocki, Kyungmin Park, kernel,
	Fabio Estevam, Shawn Guo, Georgi Djakov


Fixes: f01e004107f3 ("PM / devfreq: Add dynamic scaling for imx8m ddr controller")
Signed-off-by: kbuild test robot <lkp@intel.com>
---
 imx8m-ddrc.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
index 62abb9b79d8a0..228561de94425 100644
--- a/drivers/devfreq/imx8m-ddrc.c
+++ b/drivers/devfreq/imx8m-ddrc.c
@@ -122,7 +122,7 @@ static void imx8m_ddrc_smc_set_freq(int target_freq)
 	local_irq_enable();
 }
 
-struct clk *clk_get_parent_by_index(struct clk *clk, int index)
+static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
 {
 	struct clk_hw *hw;
 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static
@ 2019-11-13 14:07     ` kbuild test robot
  0 siblings, 0 replies; 41+ messages in thread
From: kbuild test robot @ 2019-11-13 14:07 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 675 bytes --]


Fixes: f01e004107f3 ("PM / devfreq: Add dynamic scaling for imx8m ddr controller")
Signed-off-by: kbuild test robot <lkp@intel.com>
---
 imx8m-ddrc.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
index 62abb9b79d8a0..228561de94425 100644
--- a/drivers/devfreq/imx8m-ddrc.c
+++ b/drivers/devfreq/imx8m-ddrc.c
@@ -122,7 +122,7 @@ static void imx8m_ddrc_smc_set_freq(int target_freq)
 	local_irq_enable();
 }
 
-struct clk *clk_get_parent_by_index(struct clk *clk, int index)
+static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
 {
 	struct clk_hw *hw;
 

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
  2019-11-13 12:35       ` Leonard Crestez
@ 2019-11-14  1:07         ` Chanwoo Choi
  -1 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:07 UTC (permalink / raw)
  To: Leonard Crestez, Rob Herring
  Cc: Stephen Boyd, MyungJoo Ham, Kyungmin Park, Rafael J. Wysocki,
	Shawn Guo, Mark Rutland, Michael Turquette,
	Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	dl-linux-imx, kernel, linux-arm-kernel

On 11/13/19 9:35 PM, Leonard Crestez wrote:
> On 13.11.2019 04:32, Chanwoo Choi wrote:
>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>> Add devicetree bindings for the i.MX DDR Controller on imx8m series
>>> chips. It supports dynamic frequency switching between multiple data
>>> rates and this is exposed to Linux via the devfreq subsystem.
>>>
>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>> ---
>>>   .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
>>>   1 file changed, 57 insertions(+)
>>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>> new file mode 100644
>>> index 000000000000..7c98e3509f75
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>> @@ -0,0 +1,57 @@
>>> +# SPDX-License-Identifier: GPL-2.0
>>> +%YAML 1.2
>>> +---
>>> +$id: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fschemas%2Fmemory-controllers%2Ffsl%2Fimx8m-ddrc.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=frWd1MENZm%2FsPjQp%2FWbphMgkkCMtwsgV8hLQyIhC3%2BI%3D&amp;reserved=0
>>> +$schema: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fmeta-schemas%2Fcore.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=4IweKQJO9ZsB%2B9QxixSQjfYOFm3%2FY7iMHFBSsquK1B0%3D&amp;reserved=0
>>> +
>>> +title: i.MX8M DDR Controller
>>> +
>>> +maintainers:
>>> +  - Leonard Crestez <leonard.crestez@nxp.com>
>>> +
>>> +properties:
>>> +  compatible:
>>> +    items:
>>> +      - enum:
>>> +        - fsl,imx8mn-ddrc
>>> +        - fsl,imx8mm-ddrc
>>> +        - fsl,imx8mq-ddrc
>>> +      - const: fsl,imx8m-ddrc
>>> +
>>> +  reg:
>>> +    maxItems: 1
>>> +
>>> +  clocks:
>>> +    maxItems: 4
>>> +
>>> +  clock-names:
>>> +    items:
>>> +      - const: core
>>> +      - const: pll
>>> +      - const: alt
>>> +      - const: apb
>>> +
>>> +  operating-points-v2: true
>>> +  opp-table: true
>>> +
>>> +required:
>>> +  - reg
>>> +  - compatible
>>> +  - clocks
>>> +  - clock-names
>>> +
>>> +additionalProperties: false
>>> +
>>> +examples:
>>> +  - |
>>> +    #include <dt-bindings/clock/imx8mm-clock.h>
>>> +    ddrc: memory-controller@3d400000 {
>>> +        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
>>> +        reg = <0x3d400000 0x400000>;
>>
>> The probe() function doesn't get the IORESOURCE_MEM from dt?
>> Is it needed?
> 
> This area is not currently mapped by the driver. As far as I understand 
> it's acceptable to "describe hardware" even if you don't use the full 
> description in driver code.
> 
> If I were to remove the "reg" area wouldn't I also have to move the node 
> outside of the bus to keep DT validation? It's better to keep the address.

The dt bidning documentation and device driver have not any explanation
about this. It makes the confusion to user who don't know the history.
I'd like you to add the explanation on dt bidning documentation
in order to remove the confusion.

Actually, you get the confirm from DT maintainer. I'm OK.

> 
> Maybe it will be mapped in the future or maybe firmware will start to 
> parse linux DT instead of hardcoding SOC-specific addresses (this 
> already happens in some cases).

OK. Do you implement them in the future? If you have a plan,
you better to do this on this time.

> 
>>> +        clock-names = "core", "pll", "alt", "apb";
>>> +        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
>>> +                 <&clk IMX8MM_DRAM_PLL>,
>>> +                 <&clk IMX8MM_CLK_DRAM_ALT>,
>>> +                 <&clk IMX8MM_CLK_DRAM_APB>;
>>> +        operating-points-v2 = <&ddrc_opp_table>;
>>> +    };
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller
@ 2019-11-14  1:07         ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:07 UTC (permalink / raw)
  To: Leonard Crestez, Rob Herring
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, dl-linux-imx,
	devicetree, linux-pm, Martin Kepplinger, linux-arm-kernel,
	Aisheng Dong, Anson Huang, Stephen Boyd, Rafael J. Wysocki,
	Kyungmin Park, kernel, Fabio Estevam, Shawn Guo, Georgi Djakov

On 11/13/19 9:35 PM, Leonard Crestez wrote:
> On 13.11.2019 04:32, Chanwoo Choi wrote:
>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>> Add devicetree bindings for the i.MX DDR Controller on imx8m series
>>> chips. It supports dynamic frequency switching between multiple data
>>> rates and this is exposed to Linux via the devfreq subsystem.
>>>
>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>> ---
>>>   .../memory-controllers/fsl/imx8m-ddrc.yaml    | 57 +++++++++++++++++++
>>>   1 file changed, 57 insertions(+)
>>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>> new file mode 100644
>>> index 000000000000..7c98e3509f75
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/memory-controllers/fsl/imx8m-ddrc.yaml
>>> @@ -0,0 +1,57 @@
>>> +# SPDX-License-Identifier: GPL-2.0
>>> +%YAML 1.2
>>> +---
>>> +$id: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fschemas%2Fmemory-controllers%2Ffsl%2Fimx8m-ddrc.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=frWd1MENZm%2FsPjQp%2FWbphMgkkCMtwsgV8hLQyIhC3%2BI%3D&amp;reserved=0
>>> +$schema: https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevicetree.org%2Fmeta-schemas%2Fcore.yaml%23&amp;data=02%7C01%7Cleonard.crestez%40nxp.com%7C23e819d42b664965975808d767e1c084%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637092091602846215&amp;sdata=4IweKQJO9ZsB%2B9QxixSQjfYOFm3%2FY7iMHFBSsquK1B0%3D&amp;reserved=0
>>> +
>>> +title: i.MX8M DDR Controller
>>> +
>>> +maintainers:
>>> +  - Leonard Crestez <leonard.crestez@nxp.com>
>>> +
>>> +properties:
>>> +  compatible:
>>> +    items:
>>> +      - enum:
>>> +        - fsl,imx8mn-ddrc
>>> +        - fsl,imx8mm-ddrc
>>> +        - fsl,imx8mq-ddrc
>>> +      - const: fsl,imx8m-ddrc
>>> +
>>> +  reg:
>>> +    maxItems: 1
>>> +
>>> +  clocks:
>>> +    maxItems: 4
>>> +
>>> +  clock-names:
>>> +    items:
>>> +      - const: core
>>> +      - const: pll
>>> +      - const: alt
>>> +      - const: apb
>>> +
>>> +  operating-points-v2: true
>>> +  opp-table: true
>>> +
>>> +required:
>>> +  - reg
>>> +  - compatible
>>> +  - clocks
>>> +  - clock-names
>>> +
>>> +additionalProperties: false
>>> +
>>> +examples:
>>> +  - |
>>> +    #include <dt-bindings/clock/imx8mm-clock.h>
>>> +    ddrc: memory-controller@3d400000 {
>>> +        compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
>>> +        reg = <0x3d400000 0x400000>;
>>
>> The probe() function doesn't get the IORESOURCE_MEM from dt?
>> Is it needed?
> 
> This area is not currently mapped by the driver. As far as I understand 
> it's acceptable to "describe hardware" even if you don't use the full 
> description in driver code.
> 
> If I were to remove the "reg" area wouldn't I also have to move the node 
> outside of the bus to keep DT validation? It's better to keep the address.

The dt bidning documentation and device driver have not any explanation
about this. It makes the confusion to user who don't know the history.
I'd like you to add the explanation on dt bidning documentation
in order to remove the confusion.

Actually, you get the confirm from DT maintainer. I'm OK.

> 
> Maybe it will be mapped in the future or maybe firmware will start to 
> parse linux DT instead of hardcoding SOC-specific addresses (this 
> already happens in some cases).

OK. Do you implement them in the future? If you have a plan,
you better to do this on this time.

> 
>>> +        clock-names = "core", "pll", "alt", "apb";
>>> +        clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
>>> +                 <&clk IMX8MM_DRAM_PLL>,
>>> +                 <&clk IMX8MM_CLK_DRAM_ALT>,
>>> +                 <&clk IMX8MM_CLK_DRAM_APB>;
>>> +        operating-points-v2 = <&ddrc_opp_table>;
>>> +    };
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-13 13:10         ` Leonard Crestez
@ 2019-11-14  1:21           ` Chanwoo Choi
  -1 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:21 UTC (permalink / raw)
  To: Leonard Crestez, Viresh Kumar
  Cc: Stephen Boyd, Rob Herring, MyungJoo Ham, Kyungmin Park,
	Rafael J. Wysocki, Shawn Guo, Mark Rutland, Michael Turquette,
	Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, devicetree, linux-pm, linux-clk, dl-linux-imx,
	kernel, linux-arm-kernel

On 11/13/19 10:10 PM, Leonard Crestez wrote:
> On 13.11.2019 08:23, Chanwoo Choi wrote:
>> On 11/13/19 11:30 AM, Chanwoo Choi wrote:
>>> Hi Leonard,
>>>
>>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>>>> frequency switching is implemented inside TF-A, this driver wraps the
>>>> SMC calls and synchronizes the clk tree.
>>>>
>>>> The DRAM clocks on imx8m have the following structure (abridged):
>>>>
>>>>   +----------+       |\            +------+
>>>>   | dram_pll |-------|M| dram_core |      |
>>>>   +----------+       |U|---------->| D    |
>>>>                   /--|X|           |  D   |
>>>>     dram_alt_root |  |/            |   R  |
>>>>                   |                |    C |
>>>>              +---------+           |      |
>>>>              |FIX DIV/4|           |      |
>>>>              +---------+           |      |
>>>>    composite:     |                |      |
>>>>   +----------+    |                |      |
>>>>   | dram_alt |----/                |      |
>>>>   +----------+                     |      |
>>>>   | dram_apb |-------------------->|      |
>>>>   +----------+                     +------+
>>>>
>>>> The dram_pll is used for higher rates and dram_alt is used for lower
>>>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>>>> parent can also be modified.
>>>>
>>>> This driver will prepare/enable the new parents ahead of switching (so
>>>> that the expected roots are enabled) and afterwards it will call
>>>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>>>
>>>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>>>> CLK_GET_RATE_NOCACHE for rate updates.
>>>>
>>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>>> ---
>>>>   drivers/devfreq/Kconfig      |   9 +
>>>>   drivers/devfreq/Makefile     |   1 +
>>>>   drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>>>   3 files changed, 470 insertions(+)
>>>>   create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>>>
>>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>>> index 066e6c4efaa2..923a6132e741 100644
>>>> --- a/drivers/devfreq/Kconfig
>>>> +++ b/drivers/devfreq/Kconfig
>>>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>>>   	  Each memory bus group could contain many memoby bus block. It reads
>>>>   	  PPMU counters of memory controllers by using DEVFREQ-event device
>>>>   	  and adjusts the operating frequencies and voltages with OPP support.
>>>>   	  This does not yet operate with optimal voltages.
>>>>   
>>>> +config ARM_IMX8M_DDRC_DEVFREQ
>>>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>>>> +	depends on ARCH_MXC || COMPILE_TEST
>>>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>> +	select DEVFREQ_GOV_USERSPACE
>>>> +	help
>>>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>>>> +	  adjusting DRAM frequency.
>>>> +
>>>>   config ARM_TEGRA_DEVFREQ
>>>>   	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>>>   	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>>>   		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>>>   		ARCH_TEGRA_210_SOC || \
>>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>>> index 338ae8440db6..3eb4d5e6635c 100644
>>>> --- a/drivers/devfreq/Makefile
>>>> +++ b/drivers/devfreq/Makefile
>>>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>>>   obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>>>   obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>>>   
>>>>   # DEVFREQ Drivers
>>>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>>>   obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>>>   obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>>>   
>>>>   # DEVFREQ Event Drivers
>>>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>>>> new file mode 100644
>>>> index 000000000000..62abb9b79d8a
>>>> --- /dev/null
>>>> +++ b/drivers/devfreq/imx8m-ddrc.c
>>>> @@ -0,0 +1,460 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * Copyright 2019 NXP
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/devfreq.h>
>>>> +#include <linux/pm_opp.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/clk-provider.h>
>>>> +#include <linux/arm-smccc.h>
>>>> +
>>>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>>>> +
>>>> +/* Values starting from 0 switch to specific frequency */
>>>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>>>> +
>>>> +/* Deprecated after moving IRQ handling to ATF */
>>>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>>>> +
>>>> +/* Query available frequencies. */
>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>>>> +
>>>> +/*
>>>> + * This should be in a 1:1 mapping with devicetree OPPs but
>>>> + * firmware provides additional info.
>>>> + */
>>>> +struct imx8m_ddrc_freq {
>>>> +	unsigned long rate;
>>>> +	unsigned long smcarg;
>>>> +	int dram_core_parent_index;
>>>> +	int dram_alt_parent_index;
>>>> +	int dram_apb_parent_index;
>>>> +};
>>>> +
>>>> +/* Hardware limitation */
>>>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>>>> +
>>>> +/*
>>>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>>>> + *
>>>> + * +----------+       |\            +------+
>>>> + * | dram_pll |-------|M| dram_core |      |
>>>> + * +----------+       |U|---------->| D    |
>>>> + *                 /--|X|           |  D   |
>>>> + *   dram_alt_root |  |/            |   R  |
>>>> + *                 |                |    C |
>>>> + *            +---------+           |      |
>>>> + *            |FIX DIV/4|           |      |
>>>> + *            +---------+           |      |
>>>> + *  composite:     |                |      |
>>>> + * +----------+    |                |      |
>>>> + * | dram_alt |----/                |      |
>>>> + * +----------+                     |      |
>>>> + * | dram_apb |-------------------->|      |
>>>> + * +----------+                     +------+
>>>> + *
>>>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>>>> + *
>>>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>>>> + * configuration of the clocks, including mux parents. The dram_alt and
>>>> + * dram_apb clocks are "imx composite" and their parent can change too.
>>>> + *
>>>> + * We need to prepare/enable the new mux parents head of switching and update
>>>> + * their information afterwards.
>>>> + */
>>>> +struct imx8m_ddrc {
>>>> +	struct devfreq_dev_profile profile;
>>>> +	struct devfreq *devfreq;
>>>> +
>>>> +	/* For frequency switching: */
>>>> +	struct clk *dram_core;
>>>> +	struct clk *dram_pll;
>>>> +	struct clk *dram_alt;
>>>> +	struct clk *dram_apb;
>>>> +
>>>> +	int freq_count;
>>>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>>>> +};
>>>> +
>>>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>>>> +						    unsigned long rate)
>>>> +{
>>>> +	struct imx8m_ddrc_freq *freq;
>>>> +	int i;
>>>> +
>>>> +	/*
>>>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>>>> +	 * Rounding is extra generous to ensure a match.
>>>> +	 */
>>>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>>>> +	for (i = 0; i < priv->freq_count; ++i) {
>>>> +		freq = &priv->freq_table[i];
>>>> +		if (freq->rate == rate ||
>>>> +				freq->rate + 1 == rate ||
>>>> +				freq->rate - 1 == rate)
>>>> +			return freq;
>>>> +	}
>>>> +
>>>> +	return NULL;
>>>> +}
>>>> +
>>>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>>>> +{
>>>> +	struct arm_smccc_res res;
>>>> +	u32 online_cpus = 0;
>>>> +	int cpu;
>>>> +
>>>> +	local_irq_disable();
>>>> +
>>>> +	for_each_online_cpu(cpu)
>>>> +		online_cpus |= (1 << (cpu * 8));
>>>> +
>>>> +	/* change the ddr freqency */
>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>>>> +			0, 0, 0, 0, 0, &res);
>>>> +
>>>> +	local_irq_enable();
>>>> +}
>>>> +
>>>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>>>> +{
>>>> +	struct clk_hw *hw;
>>>> +
>>>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>>>> +
>>>> +	return hw ? hw->clk : NULL;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct clk *new_dram_core_parent;
>>>> +	struct clk *new_dram_alt_parent;
>>>> +	struct clk *new_dram_apb_parent;
>>>> +	int ret;
>>>> +
>>>> +	/*
>>>> +	 * Fetch new parents
>>>> +	 *
>>>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>>>> +	 * new_dram_core_parent is not.
>>>> +	 */
>>>> +	new_dram_core_parent = clk_get_parent_by_index(
>>>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>>>> +	if (!new_dram_core_parent) {
>>>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +	if (freq->dram_alt_parent_index) {
>>>> +		new_dram_alt_parent = clk_get_parent_by_index(
>>>> +				priv->dram_alt,
>>>> +				freq->dram_alt_parent_index - 1);
>>>> +		if (!new_dram_alt_parent) {
>>>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>>>> +			return -EINVAL;
>>>> +		}
>>>> +	} else
>>>> +		new_dram_alt_parent = NULL;
>>>> +
>>>> +	if (freq->dram_alt_parent_index) {
>>>> +		new_dram_apb_parent = clk_get_parent_by_index(
>>>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>>>> +		if (!new_dram_alt_parent) {
>>>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>>>> +			return -EINVAL;
>>>> +		}
>>>> +	} else
>>>> +		new_dram_apb_parent = NULL;
>>>> +
>>>> +	/* increase reference counts and ensure clks are ON before switch */
>>>> +	ret = clk_prepare_enable(new_dram_core_parent);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
>>>
>>> s/failed enable/failed to enable
>>>
>>>> +		goto out;
>>>> +	}
>>>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
>>>
>>> s/failed enable/failed to enable
>>>
>>>> +		goto out_disable_core_parent;
>>>> +	}
>>>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
>>>
>>> s/failed enable/failed to enable
>>>
>>>> +		goto out_disable_alt_parent;
>>>> +	}
>>>> +
>>>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>>>> +
>>>> +	/* update parents in clk tree after switch. */
>>>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>>>> +	if (ret)
>>>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
>>>
>>> s/failed set/failed to set
>>>
>>>> +	if (new_dram_alt_parent) {
>>>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>>>> +		if (ret)
>>>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
>>>
>>> s/failed set/failed to set
>>>
>>>> +	}
>>>> +	if (new_dram_apb_parent) {
>>>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>>>> +		if (ret)
>>>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
>>>
>>> s/failed set/failed to set
> 
> OK, but this might make a few messages longer than 80 chars.

I don't like over 80 chars as I already commented.

	dev_warn(dev,
		"failed set dram_apb parent: %d\n", ret);

> 
>>>> +	}
>>>> +
>>>> +	/*
>>>> +	 * Explicitly refresh dram PLL rate.
>>>> +	 *
>>>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>>>> +	 * automatically refreshed when clk_get_rate is called on children.
>>>> +	 */
>>>> +	clk_get_rate(priv->dram_pll);
>>>> +
>>>> +	/*
>>>> +	 * clk_set_parent transfer the reference count from old parent.
>>>> +	 * now we drop extra reference counts used during the switch
>>>> +	 */
>>>> +	clk_disable_unprepare(new_dram_apb_parent);
>>>> +out_disable_alt_parent:
>>>> +	clk_disable_unprepare(new_dram_alt_parent);
>>>> +out_disable_core_parent:
>>>> +	clk_disable_unprepare(new_dram_core_parent);
>>>> +out:
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>> +	struct dev_pm_opp *new_opp;
>>>> +	unsigned long old_freq, new_freq;
>>>> +	int ret;
>>>> +
>>>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>>>> +	if (IS_ERR(new_opp)) {
>>>> +		ret = PTR_ERR(new_opp);
>>>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +	dev_pm_opp_put(new_opp);
>>>> +
>>>> +	old_freq = clk_get_rate(priv->dram_core);
>>>> +	if (*freq == old_freq)
>>>> +		return 0;
>>>> +
>>>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>>>> +	if (!freq_info)
>>>> +		return -EINVAL;
>>>> +
>>>> +	/*
>>>> +	 * Read back the clk rate to verify switch was correct and so that
>>>> +	 * we can report it on all error paths.
>>>> +	 */
>>>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>>>> +
>>>> +	new_freq = clk_get_rate(priv->dram_core);
>>>> +	if (ret)
>>>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>>>> +			old_freq, *freq, ret, new_freq);
>>>> +	else if (*freq != new_freq)
>>>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>>>> +			old_freq, *freq, new_freq);
>>>
>>> Actually, is it error? When use clk_set_rate with target_freq,
>>> if target_freq is not same with supported clock of h/w clock,
>>> the clk_set_rate set the similiar clock rate among the supported clock table.
>>>
>>> It means that if the user of clock_set_rate() enters the unsupported clock rate,
>>> the case of (*freq != new_freq) happen.
>>>
>>> Are you sure that you want to show the error when this case (*freq != new_freq)?
>>> The your origin code is not wrong. Just question from me.
> 
> The assumption here is that the OPP table will contain the precise 
> frequency as reported by clk_get_rate after a switch.

nitpick:
As I said, I think it's not error. If failed to set the clock rate
with any value, it is error.  But, if clk_set_rate() selected
the supported clock, it is not error.

But, I'm sure that you completed the test and you could want to
keep this line. I'm OK.

> 
> For example imx8mq-evk.dts has an OPP of exactly 166935483 Hz.> 
>>>> +	else
>>>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>>>> +			*freq, old_freq);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	*freq = clk_get_rate(priv->dram_core);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>>>> +				     struct devfreq_dev_status *stat)
>>>
>>> get_dev_status() callback is called by only simpleondemand governor.
>>> When userspace governor is used, this function is never called.
>>> So, need to drop this function and then add this function on next time.
> 
> Then you get an oops on "echo simple_ondemand > governor".
> 
> In theory the simple_ondemand governor could check for NULL 
> "get_dev_status" or devfreq core could reject switching to 
> simple_ondemand if no get_dev_status is implemented. For example a 
> devfreq_governor.validate callback could be implemented?

Don't do that. I'll re-implement the governor flag like immutable
/interrupt-driven and add the feature that the devfreq device driver
specifies the supported governors when adding the device. I'll.

> 
> But right now the "get_dev_status" callback is NOT optional.

OK. Keep the get_dev_status().

> 
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	stat->busy_time = 0;
>>>> +	stat->total_time = 0;
>>>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct arm_smccc_res res;
>>>> +	int index;
>>>> +
>>>> +	/* An error here means DDR DVFS API not supported by firmware */
>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>>>> +			0, 0, 0, 0, 0, 0, &res);
>>>> +	priv->freq_count = res.a0;
>>>> +	if (priv->freq_count <= 0 ||
>>>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>>>> +		return -ENODEV;
>>>> +
>>>> +	for (index = 0; index < priv->freq_count; ++index) {
>>>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>>>> +
>>>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>>>> +			      index, 0, 0, 0, 0, 0, &res);
>>>> +		/* Result should be strictly positive */
>>>> +		if ((long)res.a0 <= 0)
>>>> +			return -ENODEV;
>>>> +
>>>> +		freq->rate = res.a0;
>>>> +		freq->smcarg = index;
>>>> +		freq->dram_core_parent_index = res.a1;
>>>> +		freq->dram_alt_parent_index = res.a2;
>>>> +		freq->dram_apb_parent_index = res.a3;
>>>> +
>>>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>>>> +		if (freq->dram_core_parent_index != 1 &&
>>>> +				freq->dram_core_parent_index != 2)
>>>> +			return -ENODEV;
>>>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>>>> +		if (freq->dram_alt_parent_index > 8 ||
>>>> +				freq->dram_apb_parent_index > 8)
>>>> +			return -ENODEV;
>>>> +		/* dram_core from alt requires explicit dram_alt parent */
>>>> +		if (freq->dram_core_parent_index == 2 &&
>>>> +				freq->dram_alt_parent_index == 0)
>>>> +			return -ENODEV;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_check_opps(struct device *dev)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>> +	struct dev_pm_opp *opp;
>>>> +	unsigned long freq;
>>>> +
>>>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>>>> +	freq = ULONG_MAX;
>>>> +	while (true) {
>>
>> You can get the number of OPP entries int the opp table
>> with dev_pm_opp_get_count(dev). I think that better to
>> use the correct number of OPP entries instead of 'while(true)' style.
> 
> I need to enumerate frequencies and there's no "get_freq_by_index" in 
> opp core that I can find so I'd still need to use 
> dev_pm_opp_find_freq_floor.

Yes. I agree. Just I recommend that use the dev_pm_opp_get_opp_count()
instead of infinite loop style with 'while(true)'. I don't prefer to
use the infinite loop coding-sytle.

> 
> It's strange that OPP core doesn't offer additional support for 
> enumerating OPPs like a for_each macro?

Right there are no for_each_macro. 

imx8m_ddrc_check_opps() is similiar with 'set_freq_table()'
in devfreq.c with dev_pm_opp_get_opp_count(). You can refer to it.

> 
>>>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>>>> +		if (opp == ERR_PTR(-ERANGE))
>>>> +			break;
>>>> +		if (IS_ERR(opp)) {
>>>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>>>> +				PTR_ERR(opp));
>>>> +			return PTR_ERR(opp);
>>>> +		}
>>>> +		dev_pm_opp_put(opp);
>>>> +
>>>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>>>> +		if (!freq_info) {
>>>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>>>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>>>> +			dev_pm_opp_disable(dev, freq);
>>>> +		}
>>>> +
>>>> +		freq--;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void imx8m_ddrc_exit(struct device *dev)
>>>> +{
>>>> +	dev_pm_opp_of_remove_table(dev);
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct device *dev = &pdev->dev;
>>>> +	struct imx8m_ddrc *priv;
>>>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>>>> +	int ret;
>>>> +
>>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>>> +	if (!priv)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	platform_set_drvdata(pdev, priv);
>>>> +
>>>> +	ret = imx8m_ddrc_init_freq_info(dev);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	priv->dram_core = devm_clk_get(dev, "core");
>>>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>>>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>>>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>>>> +	if (IS_ERR(priv->dram_core) ||
>>>> +		IS_ERR(priv->dram_pll) ||
>>>> +		IS_ERR(priv->dram_alt) ||
>>>> +		IS_ERR(priv->dram_apb)) {
>>>> +		ret = PTR_ERR(priv->devfreq);
>>>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = dev_pm_opp_of_add_table(dev);
>>>> +	if (ret < 0) {
>>>> +		dev_err(dev, "failed to get OPP table\n");
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = imx8m_ddrc_check_opps(dev);
>>>> +	if (ret < 0)
>>>> +		goto err;
>>>> +
>>>> +	priv->profile.polling_ms = 1000;
>>>> +	priv->profile.target = imx8m_ddrc_target;
>>>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
>>>
>>> ditto. It is not used on this patch. On later, add the get_dev_status
>>> for the ondemand governor.
>>>
>>>> +	priv->profile.exit = imx8m_ddrc_exit;
>>>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>>>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>>>> +
>>>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>>>> +						gov, NULL);
>>>> +	if (IS_ERR(priv->devfreq)) {
>>>> +		ret = PTR_ERR(priv->devfreq);
>>>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>>>> +		goto err;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err:
>>>> +	dev_pm_opp_of_remove_table(dev);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>>>> +	{ .compatible = "fsl,imx8m-ddrc", },
>>>> +	{ /* sentinel */ },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>>>> +
>>>> +static struct platform_driver imx8m_ddrc_platdrv = {
>>>> +	.probe		= imx8m_ddrc_probe,
>>>> +	.driver = {
>>>> +		.name	= "imx8m-ddrc-devfreq",
>>>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>>>> +	},
>>>> +};
>>>> +module_platform_driver(imx8m_ddrc_platdrv);
>>>> +
>>>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>>>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>>>> +MODULE_LICENSE("GPL v2");
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-14  1:21           ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:21 UTC (permalink / raw)
  To: Leonard Crestez, Viresh Kumar
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Michael Turquette, Angus Ainslie, Alexandre Bailon,
	Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, dl-linux-imx,
	devicetree, linux-pm, Rob Herring, Martin Kepplinger,
	linux-arm-kernel, Aisheng Dong, Anson Huang, Stephen Boyd,
	Rafael J. Wysocki, Kyungmin Park, kernel, Fabio Estevam,
	Shawn Guo, Georgi Djakov

On 11/13/19 10:10 PM, Leonard Crestez wrote:
> On 13.11.2019 08:23, Chanwoo Choi wrote:
>> On 11/13/19 11:30 AM, Chanwoo Choi wrote:
>>> Hi Leonard,
>>>
>>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>>>> frequency switching is implemented inside TF-A, this driver wraps the
>>>> SMC calls and synchronizes the clk tree.
>>>>
>>>> The DRAM clocks on imx8m have the following structure (abridged):
>>>>
>>>>   +----------+       |\            +------+
>>>>   | dram_pll |-------|M| dram_core |      |
>>>>   +----------+       |U|---------->| D    |
>>>>                   /--|X|           |  D   |
>>>>     dram_alt_root |  |/            |   R  |
>>>>                   |                |    C |
>>>>              +---------+           |      |
>>>>              |FIX DIV/4|           |      |
>>>>              +---------+           |      |
>>>>    composite:     |                |      |
>>>>   +----------+    |                |      |
>>>>   | dram_alt |----/                |      |
>>>>   +----------+                     |      |
>>>>   | dram_apb |-------------------->|      |
>>>>   +----------+                     +------+
>>>>
>>>> The dram_pll is used for higher rates and dram_alt is used for lower
>>>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>>>> parent can also be modified.
>>>>
>>>> This driver will prepare/enable the new parents ahead of switching (so
>>>> that the expected roots are enabled) and afterwards it will call
>>>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>>>
>>>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>>>> CLK_GET_RATE_NOCACHE for rate updates.
>>>>
>>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>>> ---
>>>>   drivers/devfreq/Kconfig      |   9 +
>>>>   drivers/devfreq/Makefile     |   1 +
>>>>   drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>>>   3 files changed, 470 insertions(+)
>>>>   create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>>>
>>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>>> index 066e6c4efaa2..923a6132e741 100644
>>>> --- a/drivers/devfreq/Kconfig
>>>> +++ b/drivers/devfreq/Kconfig
>>>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>>>   	  Each memory bus group could contain many memoby bus block. It reads
>>>>   	  PPMU counters of memory controllers by using DEVFREQ-event device
>>>>   	  and adjusts the operating frequencies and voltages with OPP support.
>>>>   	  This does not yet operate with optimal voltages.
>>>>   
>>>> +config ARM_IMX8M_DDRC_DEVFREQ
>>>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>>>> +	depends on ARCH_MXC || COMPILE_TEST
>>>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>> +	select DEVFREQ_GOV_USERSPACE
>>>> +	help
>>>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>>>> +	  adjusting DRAM frequency.
>>>> +
>>>>   config ARM_TEGRA_DEVFREQ
>>>>   	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>>>   	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>>>   		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>>>   		ARCH_TEGRA_210_SOC || \
>>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>>> index 338ae8440db6..3eb4d5e6635c 100644
>>>> --- a/drivers/devfreq/Makefile
>>>> +++ b/drivers/devfreq/Makefile
>>>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>>>   obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>>>   obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>>>   
>>>>   # DEVFREQ Drivers
>>>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>>>   obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>>>   obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>>>   
>>>>   # DEVFREQ Event Drivers
>>>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>>>> new file mode 100644
>>>> index 000000000000..62abb9b79d8a
>>>> --- /dev/null
>>>> +++ b/drivers/devfreq/imx8m-ddrc.c
>>>> @@ -0,0 +1,460 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * Copyright 2019 NXP
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/devfreq.h>
>>>> +#include <linux/pm_opp.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/clk-provider.h>
>>>> +#include <linux/arm-smccc.h>
>>>> +
>>>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>>>> +
>>>> +/* Values starting from 0 switch to specific frequency */
>>>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>>>> +
>>>> +/* Deprecated after moving IRQ handling to ATF */
>>>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>>>> +
>>>> +/* Query available frequencies. */
>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>>>> +
>>>> +/*
>>>> + * This should be in a 1:1 mapping with devicetree OPPs but
>>>> + * firmware provides additional info.
>>>> + */
>>>> +struct imx8m_ddrc_freq {
>>>> +	unsigned long rate;
>>>> +	unsigned long smcarg;
>>>> +	int dram_core_parent_index;
>>>> +	int dram_alt_parent_index;
>>>> +	int dram_apb_parent_index;
>>>> +};
>>>> +
>>>> +/* Hardware limitation */
>>>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>>>> +
>>>> +/*
>>>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>>>> + *
>>>> + * +----------+       |\            +------+
>>>> + * | dram_pll |-------|M| dram_core |      |
>>>> + * +----------+       |U|---------->| D    |
>>>> + *                 /--|X|           |  D   |
>>>> + *   dram_alt_root |  |/            |   R  |
>>>> + *                 |                |    C |
>>>> + *            +---------+           |      |
>>>> + *            |FIX DIV/4|           |      |
>>>> + *            +---------+           |      |
>>>> + *  composite:     |                |      |
>>>> + * +----------+    |                |      |
>>>> + * | dram_alt |----/                |      |
>>>> + * +----------+                     |      |
>>>> + * | dram_apb |-------------------->|      |
>>>> + * +----------+                     +------+
>>>> + *
>>>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>>>> + *
>>>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>>>> + * configuration of the clocks, including mux parents. The dram_alt and
>>>> + * dram_apb clocks are "imx composite" and their parent can change too.
>>>> + *
>>>> + * We need to prepare/enable the new mux parents head of switching and update
>>>> + * their information afterwards.
>>>> + */
>>>> +struct imx8m_ddrc {
>>>> +	struct devfreq_dev_profile profile;
>>>> +	struct devfreq *devfreq;
>>>> +
>>>> +	/* For frequency switching: */
>>>> +	struct clk *dram_core;
>>>> +	struct clk *dram_pll;
>>>> +	struct clk *dram_alt;
>>>> +	struct clk *dram_apb;
>>>> +
>>>> +	int freq_count;
>>>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>>>> +};
>>>> +
>>>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>>>> +						    unsigned long rate)
>>>> +{
>>>> +	struct imx8m_ddrc_freq *freq;
>>>> +	int i;
>>>> +
>>>> +	/*
>>>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>>>> +	 * Rounding is extra generous to ensure a match.
>>>> +	 */
>>>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>>>> +	for (i = 0; i < priv->freq_count; ++i) {
>>>> +		freq = &priv->freq_table[i];
>>>> +		if (freq->rate == rate ||
>>>> +				freq->rate + 1 == rate ||
>>>> +				freq->rate - 1 == rate)
>>>> +			return freq;
>>>> +	}
>>>> +
>>>> +	return NULL;
>>>> +}
>>>> +
>>>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>>>> +{
>>>> +	struct arm_smccc_res res;
>>>> +	u32 online_cpus = 0;
>>>> +	int cpu;
>>>> +
>>>> +	local_irq_disable();
>>>> +
>>>> +	for_each_online_cpu(cpu)
>>>> +		online_cpus |= (1 << (cpu * 8));
>>>> +
>>>> +	/* change the ddr freqency */
>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>>>> +			0, 0, 0, 0, 0, &res);
>>>> +
>>>> +	local_irq_enable();
>>>> +}
>>>> +
>>>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>>>> +{
>>>> +	struct clk_hw *hw;
>>>> +
>>>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>>>> +
>>>> +	return hw ? hw->clk : NULL;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct clk *new_dram_core_parent;
>>>> +	struct clk *new_dram_alt_parent;
>>>> +	struct clk *new_dram_apb_parent;
>>>> +	int ret;
>>>> +
>>>> +	/*
>>>> +	 * Fetch new parents
>>>> +	 *
>>>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>>>> +	 * new_dram_core_parent is not.
>>>> +	 */
>>>> +	new_dram_core_parent = clk_get_parent_by_index(
>>>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>>>> +	if (!new_dram_core_parent) {
>>>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +	if (freq->dram_alt_parent_index) {
>>>> +		new_dram_alt_parent = clk_get_parent_by_index(
>>>> +				priv->dram_alt,
>>>> +				freq->dram_alt_parent_index - 1);
>>>> +		if (!new_dram_alt_parent) {
>>>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>>>> +			return -EINVAL;
>>>> +		}
>>>> +	} else
>>>> +		new_dram_alt_parent = NULL;
>>>> +
>>>> +	if (freq->dram_alt_parent_index) {
>>>> +		new_dram_apb_parent = clk_get_parent_by_index(
>>>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>>>> +		if (!new_dram_alt_parent) {
>>>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>>>> +			return -EINVAL;
>>>> +		}
>>>> +	} else
>>>> +		new_dram_apb_parent = NULL;
>>>> +
>>>> +	/* increase reference counts and ensure clks are ON before switch */
>>>> +	ret = clk_prepare_enable(new_dram_core_parent);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
>>>
>>> s/failed enable/failed to enable
>>>
>>>> +		goto out;
>>>> +	}
>>>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
>>>
>>> s/failed enable/failed to enable
>>>
>>>> +		goto out_disable_core_parent;
>>>> +	}
>>>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
>>>
>>> s/failed enable/failed to enable
>>>
>>>> +		goto out_disable_alt_parent;
>>>> +	}
>>>> +
>>>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>>>> +
>>>> +	/* update parents in clk tree after switch. */
>>>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>>>> +	if (ret)
>>>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
>>>
>>> s/failed set/failed to set
>>>
>>>> +	if (new_dram_alt_parent) {
>>>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>>>> +		if (ret)
>>>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
>>>
>>> s/failed set/failed to set
>>>
>>>> +	}
>>>> +	if (new_dram_apb_parent) {
>>>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>>>> +		if (ret)
>>>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
>>>
>>> s/failed set/failed to set
> 
> OK, but this might make a few messages longer than 80 chars.

I don't like over 80 chars as I already commented.

	dev_warn(dev,
		"failed set dram_apb parent: %d\n", ret);

> 
>>>> +	}
>>>> +
>>>> +	/*
>>>> +	 * Explicitly refresh dram PLL rate.
>>>> +	 *
>>>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>>>> +	 * automatically refreshed when clk_get_rate is called on children.
>>>> +	 */
>>>> +	clk_get_rate(priv->dram_pll);
>>>> +
>>>> +	/*
>>>> +	 * clk_set_parent transfer the reference count from old parent.
>>>> +	 * now we drop extra reference counts used during the switch
>>>> +	 */
>>>> +	clk_disable_unprepare(new_dram_apb_parent);
>>>> +out_disable_alt_parent:
>>>> +	clk_disable_unprepare(new_dram_alt_parent);
>>>> +out_disable_core_parent:
>>>> +	clk_disable_unprepare(new_dram_core_parent);
>>>> +out:
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>> +	struct dev_pm_opp *new_opp;
>>>> +	unsigned long old_freq, new_freq;
>>>> +	int ret;
>>>> +
>>>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>>>> +	if (IS_ERR(new_opp)) {
>>>> +		ret = PTR_ERR(new_opp);
>>>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +	dev_pm_opp_put(new_opp);
>>>> +
>>>> +	old_freq = clk_get_rate(priv->dram_core);
>>>> +	if (*freq == old_freq)
>>>> +		return 0;
>>>> +
>>>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>>>> +	if (!freq_info)
>>>> +		return -EINVAL;
>>>> +
>>>> +	/*
>>>> +	 * Read back the clk rate to verify switch was correct and so that
>>>> +	 * we can report it on all error paths.
>>>> +	 */
>>>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>>>> +
>>>> +	new_freq = clk_get_rate(priv->dram_core);
>>>> +	if (ret)
>>>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>>>> +			old_freq, *freq, ret, new_freq);
>>>> +	else if (*freq != new_freq)
>>>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>>>> +			old_freq, *freq, new_freq);
>>>
>>> Actually, is it error? When use clk_set_rate with target_freq,
>>> if target_freq is not same with supported clock of h/w clock,
>>> the clk_set_rate set the similiar clock rate among the supported clock table.
>>>
>>> It means that if the user of clock_set_rate() enters the unsupported clock rate,
>>> the case of (*freq != new_freq) happen.
>>>
>>> Are you sure that you want to show the error when this case (*freq != new_freq)?
>>> The your origin code is not wrong. Just question from me.
> 
> The assumption here is that the OPP table will contain the precise 
> frequency as reported by clk_get_rate after a switch.

nitpick:
As I said, I think it's not error. If failed to set the clock rate
with any value, it is error.  But, if clk_set_rate() selected
the supported clock, it is not error.

But, I'm sure that you completed the test and you could want to
keep this line. I'm OK.

> 
> For example imx8mq-evk.dts has an OPP of exactly 166935483 Hz.> 
>>>> +	else
>>>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>>>> +			*freq, old_freq);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	*freq = clk_get_rate(priv->dram_core);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>>>> +				     struct devfreq_dev_status *stat)
>>>
>>> get_dev_status() callback is called by only simpleondemand governor.
>>> When userspace governor is used, this function is never called.
>>> So, need to drop this function and then add this function on next time.
> 
> Then you get an oops on "echo simple_ondemand > governor".
> 
> In theory the simple_ondemand governor could check for NULL 
> "get_dev_status" or devfreq core could reject switching to 
> simple_ondemand if no get_dev_status is implemented. For example a 
> devfreq_governor.validate callback could be implemented?

Don't do that. I'll re-implement the governor flag like immutable
/interrupt-driven and add the feature that the devfreq device driver
specifies the supported governors when adding the device. I'll.

> 
> But right now the "get_dev_status" callback is NOT optional.

OK. Keep the get_dev_status().

> 
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	stat->busy_time = 0;
>>>> +	stat->total_time = 0;
>>>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct arm_smccc_res res;
>>>> +	int index;
>>>> +
>>>> +	/* An error here means DDR DVFS API not supported by firmware */
>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>>>> +			0, 0, 0, 0, 0, 0, &res);
>>>> +	priv->freq_count = res.a0;
>>>> +	if (priv->freq_count <= 0 ||
>>>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>>>> +		return -ENODEV;
>>>> +
>>>> +	for (index = 0; index < priv->freq_count; ++index) {
>>>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>>>> +
>>>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>>>> +			      index, 0, 0, 0, 0, 0, &res);
>>>> +		/* Result should be strictly positive */
>>>> +		if ((long)res.a0 <= 0)
>>>> +			return -ENODEV;
>>>> +
>>>> +		freq->rate = res.a0;
>>>> +		freq->smcarg = index;
>>>> +		freq->dram_core_parent_index = res.a1;
>>>> +		freq->dram_alt_parent_index = res.a2;
>>>> +		freq->dram_apb_parent_index = res.a3;
>>>> +
>>>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>>>> +		if (freq->dram_core_parent_index != 1 &&
>>>> +				freq->dram_core_parent_index != 2)
>>>> +			return -ENODEV;
>>>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>>>> +		if (freq->dram_alt_parent_index > 8 ||
>>>> +				freq->dram_apb_parent_index > 8)
>>>> +			return -ENODEV;
>>>> +		/* dram_core from alt requires explicit dram_alt parent */
>>>> +		if (freq->dram_core_parent_index == 2 &&
>>>> +				freq->dram_alt_parent_index == 0)
>>>> +			return -ENODEV;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_check_opps(struct device *dev)
>>>> +{
>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>> +	struct dev_pm_opp *opp;
>>>> +	unsigned long freq;
>>>> +
>>>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>>>> +	freq = ULONG_MAX;
>>>> +	while (true) {
>>
>> You can get the number of OPP entries int the opp table
>> with dev_pm_opp_get_count(dev). I think that better to
>> use the correct number of OPP entries instead of 'while(true)' style.
> 
> I need to enumerate frequencies and there's no "get_freq_by_index" in 
> opp core that I can find so I'd still need to use 
> dev_pm_opp_find_freq_floor.

Yes. I agree. Just I recommend that use the dev_pm_opp_get_opp_count()
instead of infinite loop style with 'while(true)'. I don't prefer to
use the infinite loop coding-sytle.

> 
> It's strange that OPP core doesn't offer additional support for 
> enumerating OPPs like a for_each macro?

Right there are no for_each_macro. 

imx8m_ddrc_check_opps() is similiar with 'set_freq_table()'
in devfreq.c with dev_pm_opp_get_opp_count(). You can refer to it.

> 
>>>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>>>> +		if (opp == ERR_PTR(-ERANGE))
>>>> +			break;
>>>> +		if (IS_ERR(opp)) {
>>>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>>>> +				PTR_ERR(opp));
>>>> +			return PTR_ERR(opp);
>>>> +		}
>>>> +		dev_pm_opp_put(opp);
>>>> +
>>>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>>>> +		if (!freq_info) {
>>>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>>>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>>>> +			dev_pm_opp_disable(dev, freq);
>>>> +		}
>>>> +
>>>> +		freq--;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void imx8m_ddrc_exit(struct device *dev)
>>>> +{
>>>> +	dev_pm_opp_of_remove_table(dev);
>>>> +}
>>>> +
>>>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct device *dev = &pdev->dev;
>>>> +	struct imx8m_ddrc *priv;
>>>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>>>> +	int ret;
>>>> +
>>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>>> +	if (!priv)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	platform_set_drvdata(pdev, priv);
>>>> +
>>>> +	ret = imx8m_ddrc_init_freq_info(dev);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	priv->dram_core = devm_clk_get(dev, "core");
>>>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>>>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>>>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>>>> +	if (IS_ERR(priv->dram_core) ||
>>>> +		IS_ERR(priv->dram_pll) ||
>>>> +		IS_ERR(priv->dram_alt) ||
>>>> +		IS_ERR(priv->dram_apb)) {
>>>> +		ret = PTR_ERR(priv->devfreq);
>>>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = dev_pm_opp_of_add_table(dev);
>>>> +	if (ret < 0) {
>>>> +		dev_err(dev, "failed to get OPP table\n");
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = imx8m_ddrc_check_opps(dev);
>>>> +	if (ret < 0)
>>>> +		goto err;
>>>> +
>>>> +	priv->profile.polling_ms = 1000;
>>>> +	priv->profile.target = imx8m_ddrc_target;
>>>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
>>>
>>> ditto. It is not used on this patch. On later, add the get_dev_status
>>> for the ondemand governor.
>>>
>>>> +	priv->profile.exit = imx8m_ddrc_exit;
>>>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>>>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>>>> +
>>>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>>>> +						gov, NULL);
>>>> +	if (IS_ERR(priv->devfreq)) {
>>>> +		ret = PTR_ERR(priv->devfreq);
>>>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>>>> +		goto err;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err:
>>>> +	dev_pm_opp_of_remove_table(dev);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>>>> +	{ .compatible = "fsl,imx8m-ddrc", },
>>>> +	{ /* sentinel */ },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>>>> +
>>>> +static struct platform_driver imx8m_ddrc_platdrv = {
>>>> +	.probe		= imx8m_ddrc_probe,
>>>> +	.driver = {
>>>> +		.name	= "imx8m-ddrc-devfreq",
>>>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>>>> +	},
>>>> +};
>>>> +module_platform_driver(imx8m_ddrc_platdrv);
>>>> +
>>>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>>>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>>>> +MODULE_LICENSE("GPL v2");
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static
  2019-11-13 14:07     ` kbuild test robot
  (?)
@ 2019-11-14  1:23       ` Chanwoo Choi
  -1 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:23 UTC (permalink / raw)
  To: kbuild test robot, Leonard Crestez
  Cc: kbuild-all, Stephen Boyd, Rob Herring, MyungJoo Ham,
	Kyungmin Park, Rafael J. Wysocki, Shawn Guo, Mark Rutland,
	Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Dong Aisheng, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, Viresh Kumar, devicetree, linux-pm, linux-clk,
	linux-imx, kernel, linux-arm-kernel

Hi Leonard,

Please apply this to next version.

On 11/13/19 11:07 PM, kbuild test robot wrote:
> 
> Fixes: f01e004107f3 ("PM / devfreq: Add dynamic scaling for imx8m ddr controller")
> Signed-off-by: kbuild test robot <lkp@intel.com>
> ---
>  imx8m-ddrc.c |    2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
> index 62abb9b79d8a0..228561de94425 100644
> --- a/drivers/devfreq/imx8m-ddrc.c
> +++ b/drivers/devfreq/imx8m-ddrc.c
> @@ -122,7 +122,7 @@ static void imx8m_ddrc_smc_set_freq(int target_freq)
>  	local_irq_enable();
>  }
>  
> -struct clk *clk_get_parent_by_index(struct clk *clk, int index)
> +static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>  {
>  	struct clk_hw *hw;
>  
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static
@ 2019-11-14  1:23       ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:23 UTC (permalink / raw)
  To: kbuild test robot, Leonard Crestez
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, linux-imx,
	devicetree, linux-pm, Rob Herring, Martin Kepplinger,
	linux-arm-kernel, Dong Aisheng, kbuild-all, Anson Huang,
	Stephen Boyd, Rafael J. Wysocki, Kyungmin Park, kernel,
	Fabio Estevam, Shawn Guo, Georgi Djakov

Hi Leonard,

Please apply this to next version.

On 11/13/19 11:07 PM, kbuild test robot wrote:
> 
> Fixes: f01e004107f3 ("PM / devfreq: Add dynamic scaling for imx8m ddr controller")
> Signed-off-by: kbuild test robot <lkp@intel.com>
> ---
>  imx8m-ddrc.c |    2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
> index 62abb9b79d8a0..228561de94425 100644
> --- a/drivers/devfreq/imx8m-ddrc.c
> +++ b/drivers/devfreq/imx8m-ddrc.c
> @@ -122,7 +122,7 @@ static void imx8m_ddrc_smc_set_freq(int target_freq)
>  	local_irq_enable();
>  }
>  
> -struct clk *clk_get_parent_by_index(struct clk *clk, int index)
> +static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>  {
>  	struct clk_hw *hw;
>  
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static
@ 2019-11-14  1:23       ` Chanwoo Choi
  0 siblings, 0 replies; 41+ messages in thread
From: Chanwoo Choi @ 2019-11-14  1:23 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 883 bytes --]

Hi Leonard,

Please apply this to next version.

On 11/13/19 11:07 PM, kbuild test robot wrote:
> 
> Fixes: f01e004107f3 ("PM / devfreq: Add dynamic scaling for imx8m ddr controller")
> Signed-off-by: kbuild test robot <lkp@intel.com>
> ---
>  imx8m-ddrc.c |    2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
> index 62abb9b79d8a0..228561de94425 100644
> --- a/drivers/devfreq/imx8m-ddrc.c
> +++ b/drivers/devfreq/imx8m-ddrc.c
> @@ -122,7 +122,7 @@ static void imx8m_ddrc_smc_set_freq(int target_freq)
>  	local_irq_enable();
>  }
>  
> -struct clk *clk_get_parent_by_index(struct clk *clk, int index)
> +static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>  {
>  	struct clk_hw *hw;
>  
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
  2019-11-14  1:21           ` Chanwoo Choi
@ 2019-11-14 16:01             ` Leonard Crestez
  -1 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-14 16:01 UTC (permalink / raw)
  To: Chanwoo Choi
  Cc: Viresh Kumar, Stephen Boyd, Rob Herring, MyungJoo Ham,
	Kyungmin Park, Rafael J. Wysocki, Shawn Guo, Mark Rutland,
	Michael Turquette, Artur Świgoń,
	Saravana Kannan, Angus Ainslie, Martin Kepplinger,
	Matthias Kaehlcke, Krzysztof Kozlowski, Alexandre Bailon,
	Georgi Djakov, Aisheng Dong, Abel Vesa, Jacky Bai, Anson Huang,
	Fabio Estevam, devicetree, linux-pm, linux-clk, dl-linux-imx,
	kernel, linux-arm-kernel

On 14.11.2019 03:16, Chanwoo Choi wrote:
> On 11/13/19 10:10 PM, Leonard Crestez wrote:
>> On 13.11.2019 08:23, Chanwoo Choi wrote:
>>> On 11/13/19 11:30 AM, Chanwoo Choi wrote:
>>>> Hi Leonard,
>>>>
>>>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>>>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>>>>> frequency switching is implemented inside TF-A, this driver wraps the
>>>>> SMC calls and synchronizes the clk tree.
>>>>>
>>>>> The DRAM clocks on imx8m have the following structure (abridged):
>>>>>
>>>>>    +----------+       |\            +------+
>>>>>    | dram_pll |-------|M| dram_core |      |
>>>>>    +----------+       |U|---------->| D    |
>>>>>                    /--|X|           |  D   |
>>>>>      dram_alt_root |  |/            |   R  |
>>>>>                    |                |    C |
>>>>>               +---------+           |      |
>>>>>               |FIX DIV/4|           |      |
>>>>>               +---------+           |      |
>>>>>     composite:     |                |      |
>>>>>    +----------+    |                |      |
>>>>>    | dram_alt |----/                |      |
>>>>>    +----------+                     |      |
>>>>>    | dram_apb |-------------------->|      |
>>>>>    +----------+                     +------+
>>>>>
>>>>> The dram_pll is used for higher rates and dram_alt is used for lower
>>>>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>>>>> parent can also be modified.
>>>>>
>>>>> This driver will prepare/enable the new parents ahead of switching (so
>>>>> that the expected roots are enabled) and afterwards it will call
>>>>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>>>>
>>>>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>>>>> CLK_GET_RATE_NOCACHE for rate updates.
>>>>>
>>>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>>>> ---
>>>>>    drivers/devfreq/Kconfig      |   9 +
>>>>>    drivers/devfreq/Makefile     |   1 +
>>>>>    drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>>>>    3 files changed, 470 insertions(+)
>>>>>    create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>>>>
>>>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>>>> index 066e6c4efaa2..923a6132e741 100644
>>>>> --- a/drivers/devfreq/Kconfig
>>>>> +++ b/drivers/devfreq/Kconfig
>>>>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>>>>    	  Each memory bus group could contain many memoby bus block. It reads
>>>>>    	  PPMU counters of memory controllers by using DEVFREQ-event device
>>>>>    	  and adjusts the operating frequencies and voltages with OPP support.
>>>>>    	  This does not yet operate with optimal voltages.
>>>>>    
>>>>> +config ARM_IMX8M_DDRC_DEVFREQ
>>>>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>>>>> +	depends on ARCH_MXC || COMPILE_TEST
>>>>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>>> +	select DEVFREQ_GOV_USERSPACE
>>>>> +	help
>>>>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>>>>> +	  adjusting DRAM frequency.
>>>>> +
>>>>>    config ARM_TEGRA_DEVFREQ
>>>>>    	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>>>>    	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>>>>    		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>>>>    		ARCH_TEGRA_210_SOC || \
>>>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>>>> index 338ae8440db6..3eb4d5e6635c 100644
>>>>> --- a/drivers/devfreq/Makefile
>>>>> +++ b/drivers/devfreq/Makefile
>>>>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>>>>    obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>>>>    obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>>>>    
>>>>>    # DEVFREQ Drivers
>>>>>    obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>>>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>>>>    obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>>>>    obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>>>>    obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>>>>    
>>>>>    # DEVFREQ Event Drivers
>>>>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>>>>> new file mode 100644
>>>>> index 000000000000..62abb9b79d8a
>>>>> --- /dev/null
>>>>> +++ b/drivers/devfreq/imx8m-ddrc.c
>>>>> @@ -0,0 +1,460 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +/*
>>>>> + * Copyright 2019 NXP
>>>>> + */
>>>>> +
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/device.h>
>>>>> +#include <linux/of_device.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/devfreq.h>
>>>>> +#include <linux/pm_opp.h>
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/clk-provider.h>
>>>>> +#include <linux/arm-smccc.h>
>>>>> +
>>>>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>>>>> +
>>>>> +/* Values starting from 0 switch to specific frequency */
>>>>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>>>>> +
>>>>> +/* Deprecated after moving IRQ handling to ATF */
>>>>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>>>>> +
>>>>> +/* Query available frequencies. */
>>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>>>>> +
>>>>> +/*
>>>>> + * This should be in a 1:1 mapping with devicetree OPPs but
>>>>> + * firmware provides additional info.
>>>>> + */
>>>>> +struct imx8m_ddrc_freq {
>>>>> +	unsigned long rate;
>>>>> +	unsigned long smcarg;
>>>>> +	int dram_core_parent_index;
>>>>> +	int dram_alt_parent_index;
>>>>> +	int dram_apb_parent_index;
>>>>> +};
>>>>> +
>>>>> +/* Hardware limitation */
>>>>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>>>>> +
>>>>> +/*
>>>>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>>>>> + *
>>>>> + * +----------+       |\            +------+
>>>>> + * | dram_pll |-------|M| dram_core |      |
>>>>> + * +----------+       |U|---------->| D    |
>>>>> + *                 /--|X|           |  D   |
>>>>> + *   dram_alt_root |  |/            |   R  |
>>>>> + *                 |                |    C |
>>>>> + *            +---------+           |      |
>>>>> + *            |FIX DIV/4|           |      |
>>>>> + *            +---------+           |      |
>>>>> + *  composite:     |                |      |
>>>>> + * +----------+    |                |      |
>>>>> + * | dram_alt |----/                |      |
>>>>> + * +----------+                     |      |
>>>>> + * | dram_apb |-------------------->|      |
>>>>> + * +----------+                     +------+
>>>>> + *
>>>>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>>>>> + *
>>>>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>>>>> + * configuration of the clocks, including mux parents. The dram_alt and
>>>>> + * dram_apb clocks are "imx composite" and their parent can change too.
>>>>> + *
>>>>> + * We need to prepare/enable the new mux parents head of switching and update
>>>>> + * their information afterwards.
>>>>> + */
>>>>> +struct imx8m_ddrc {
>>>>> +	struct devfreq_dev_profile profile;
>>>>> +	struct devfreq *devfreq;
>>>>> +
>>>>> +	/* For frequency switching: */
>>>>> +	struct clk *dram_core;
>>>>> +	struct clk *dram_pll;
>>>>> +	struct clk *dram_alt;
>>>>> +	struct clk *dram_apb;
>>>>> +
>>>>> +	int freq_count;
>>>>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>>>>> +};
>>>>> +
>>>>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>>>>> +						    unsigned long rate)
>>>>> +{
>>>>> +	struct imx8m_ddrc_freq *freq;
>>>>> +	int i;
>>>>> +
>>>>> +	/*
>>>>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>>>>> +	 * Rounding is extra generous to ensure a match.
>>>>> +	 */
>>>>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>>>>> +	for (i = 0; i < priv->freq_count; ++i) {
>>>>> +		freq = &priv->freq_table[i];
>>>>> +		if (freq->rate == rate ||
>>>>> +				freq->rate + 1 == rate ||
>>>>> +				freq->rate - 1 == rate)
>>>>> +			return freq;
>>>>> +	}
>>>>> +
>>>>> +	return NULL;
>>>>> +}
>>>>> +
>>>>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>>>>> +{
>>>>> +	struct arm_smccc_res res;
>>>>> +	u32 online_cpus = 0;
>>>>> +	int cpu;
>>>>> +
>>>>> +	local_irq_disable();
>>>>> +
>>>>> +	for_each_online_cpu(cpu)
>>>>> +		online_cpus |= (1 << (cpu * 8));
>>>>> +
>>>>> +	/* change the ddr freqency */
>>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>>>>> +			0, 0, 0, 0, 0, &res);
>>>>> +
>>>>> +	local_irq_enable();
>>>>> +}
>>>>> +
>>>>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>>>>> +{
>>>>> +	struct clk_hw *hw;
>>>>> +
>>>>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>>>>> +
>>>>> +	return hw ? hw->clk : NULL;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct clk *new_dram_core_parent;
>>>>> +	struct clk *new_dram_alt_parent;
>>>>> +	struct clk *new_dram_apb_parent;
>>>>> +	int ret;
>>>>> +
>>>>> +	/*
>>>>> +	 * Fetch new parents
>>>>> +	 *
>>>>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>>>>> +	 * new_dram_core_parent is not.
>>>>> +	 */
>>>>> +	new_dram_core_parent = clk_get_parent_by_index(
>>>>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>>>>> +	if (!new_dram_core_parent) {
>>>>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>>>>> +		return -EINVAL;
>>>>> +	}
>>>>> +	if (freq->dram_alt_parent_index) {
>>>>> +		new_dram_alt_parent = clk_get_parent_by_index(
>>>>> +				priv->dram_alt,
>>>>> +				freq->dram_alt_parent_index - 1);
>>>>> +		if (!new_dram_alt_parent) {
>>>>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>>>>> +			return -EINVAL;
>>>>> +		}
>>>>> +	} else
>>>>> +		new_dram_alt_parent = NULL;
>>>>> +
>>>>> +	if (freq->dram_alt_parent_index) {
>>>>> +		new_dram_apb_parent = clk_get_parent_by_index(
>>>>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>>>>> +		if (!new_dram_alt_parent) {
>>>>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>>>>> +			return -EINVAL;
>>>>> +		}
>>>>> +	} else
>>>>> +		new_dram_apb_parent = NULL;
>>>>> +
>>>>> +	/* increase reference counts and ensure clks are ON before switch */
>>>>> +	ret = clk_prepare_enable(new_dram_core_parent);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
>>>>
>>>> s/failed enable/failed to enable
>>>>
>>>>> +		goto out;
>>>>> +	}
>>>>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
>>>>
>>>> s/failed enable/failed to enable
>>>>
>>>>> +		goto out_disable_core_parent;
>>>>> +	}
>>>>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
>>>>
>>>> s/failed enable/failed to enable
>>>>
>>>>> +		goto out_disable_alt_parent;
>>>>> +	}
>>>>> +
>>>>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>>>>> +
>>>>> +	/* update parents in clk tree after switch. */
>>>>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>>>>> +	if (ret)
>>>>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
>>>>
>>>> s/failed set/failed to set
>>>>
>>>>> +	if (new_dram_alt_parent) {
>>>>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>>>>> +		if (ret)
>>>>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
>>>>
>>>> s/failed set/failed to set
>>>>
>>>>> +	}
>>>>> +	if (new_dram_apb_parent) {
>>>>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>>>>> +		if (ret)
>>>>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
>>>>
>>>> s/failed set/failed to set
>>
>> OK, but this might make a few messages longer than 80 chars.
> 
> I don't like over 80 chars as I already commented.
> 
> 	dev_warn(dev,
> 		"failed set dram_apb parent: %d\n", ret);
> 
>>
>>>>> +	}
>>>>> +
>>>>> +	/*
>>>>> +	 * Explicitly refresh dram PLL rate.
>>>>> +	 *
>>>>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>>>>> +	 * automatically refreshed when clk_get_rate is called on children.
>>>>> +	 */
>>>>> +	clk_get_rate(priv->dram_pll);
>>>>> +
>>>>> +	/*
>>>>> +	 * clk_set_parent transfer the reference count from old parent.
>>>>> +	 * now we drop extra reference counts used during the switch
>>>>> +	 */
>>>>> +	clk_disable_unprepare(new_dram_apb_parent);
>>>>> +out_disable_alt_parent:
>>>>> +	clk_disable_unprepare(new_dram_alt_parent);
>>>>> +out_disable_core_parent:
>>>>> +	clk_disable_unprepare(new_dram_core_parent);
>>>>> +out:
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>>> +	struct dev_pm_opp *new_opp;
>>>>> +	unsigned long old_freq, new_freq;
>>>>> +	int ret;
>>>>> +
>>>>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>>>>> +	if (IS_ERR(new_opp)) {
>>>>> +		ret = PTR_ERR(new_opp);
>>>>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>>>>> +		return ret;
>>>>> +	}
>>>>> +	dev_pm_opp_put(new_opp);
>>>>> +
>>>>> +	old_freq = clk_get_rate(priv->dram_core);
>>>>> +	if (*freq == old_freq)
>>>>> +		return 0;
>>>>> +
>>>>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>>>>> +	if (!freq_info)
>>>>> +		return -EINVAL;
>>>>> +
>>>>> +	/*
>>>>> +	 * Read back the clk rate to verify switch was correct and so that
>>>>> +	 * we can report it on all error paths.
>>>>> +	 */
>>>>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>>>>> +
>>>>> +	new_freq = clk_get_rate(priv->dram_core);
>>>>> +	if (ret)
>>>>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>>>>> +			old_freq, *freq, ret, new_freq);
>>>>> +	else if (*freq != new_freq)
>>>>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>>>>> +			old_freq, *freq, new_freq);
>>>>
>>>> Actually, is it error? When use clk_set_rate with target_freq,
>>>> if target_freq is not same with supported clock of h/w clock,
>>>> the clk_set_rate set the similiar clock rate among the supported clock table.
>>>>
>>>> It means that if the user of clock_set_rate() enters the unsupported clock rate,
>>>> the case of (*freq != new_freq) happen.
>>>>
>>>> Are you sure that you want to show the error when this case (*freq != new_freq)?
>>>> The your origin code is not wrong. Just question from me.
>>
>> The assumption here is that the OPP table will contain the precise
>> frequency as reported by clk_get_rate after a switch.
> 
> nitpick:
> As I said, I think it's not error. If failed to set the clock rate
> with any value, it is error.  But, if clk_set_rate() selected
> the supported clock, it is not error.
> 
> But, I'm sure that you completed the test and you could want to
> keep this line. I'm OK.

Yes, I think it's helpful to be extra paranoid here.
>> For example imx8mq-evk.dts has an OPP of exactly 166935483 Hz.>
>>>>> +	else
>>>>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>>>>> +			*freq, old_freq);
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +
>>>>> +	*freq = clk_get_rate(priv->dram_core);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>>>>> +				     struct devfreq_dev_status *stat)
>>>>
>>>> get_dev_status() callback is called by only simpleondemand governor.
>>>> When userspace governor is used, this function is never called.
>>>> So, need to drop this function and then add this function on next time.
>>
>> Then you get an oops on "echo simple_ondemand > governor".
>>
>> In theory the simple_ondemand governor could check for NULL
>> "get_dev_status" or devfreq core could reject switching to
>> simple_ondemand if no get_dev_status is implemented. For example a
>> devfreq_governor.validate callback could be implemented?
> 
> Don't do that. I'll re-implement the governor flag like immutable
> /interrupt-driven and add the feature that the devfreq device driver
> specifies the supported governors when adding the device. I'll.
> 
>>
>> But right now the "get_dev_status" callback is NOT optional.
> 
> OK. Keep the get_dev_status().

Yes, it can be removed after devfreq core makes it optional.
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +
>>>>> +	stat->busy_time = 0;
>>>>> +	stat->total_time = 0;
>>>>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct arm_smccc_res res;
>>>>> +	int index;
>>>>> +
>>>>> +	/* An error here means DDR DVFS API not supported by firmware */
>>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>>>>> +			0, 0, 0, 0, 0, 0, &res);
>>>>> +	priv->freq_count = res.a0;
>>>>> +	if (priv->freq_count <= 0 ||
>>>>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>>>>> +		return -ENODEV;
>>>>> +
>>>>> +	for (index = 0; index < priv->freq_count; ++index) {
>>>>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>>>>> +
>>>>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>>>>> +			      index, 0, 0, 0, 0, 0, &res);
>>>>> +		/* Result should be strictly positive */
>>>>> +		if ((long)res.a0 <= 0)
>>>>> +			return -ENODEV;
>>>>> +
>>>>> +		freq->rate = res.a0;
>>>>> +		freq->smcarg = index;
>>>>> +		freq->dram_core_parent_index = res.a1;
>>>>> +		freq->dram_alt_parent_index = res.a2;
>>>>> +		freq->dram_apb_parent_index = res.a3;
>>>>> +
>>>>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>>>>> +		if (freq->dram_core_parent_index != 1 &&
>>>>> +				freq->dram_core_parent_index != 2)
>>>>> +			return -ENODEV;
>>>>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>>>>> +		if (freq->dram_alt_parent_index > 8 ||
>>>>> +				freq->dram_apb_parent_index > 8)
>>>>> +			return -ENODEV;
>>>>> +		/* dram_core from alt requires explicit dram_alt parent */
>>>>> +		if (freq->dram_core_parent_index == 2 &&
>>>>> +				freq->dram_alt_parent_index == 0)
>>>>> +			return -ENODEV;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_check_opps(struct device *dev)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>>> +	struct dev_pm_opp *opp;
>>>>> +	unsigned long freq;
>>>>> +
>>>>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>>>>> +	freq = ULONG_MAX;
>>>>> +	while (true) {
>>>
>>> You can get the number of OPP entries int the opp table
>>> with dev_pm_opp_get_count(dev). I think that better to
>>> use the correct number of OPP entries instead of 'while(true)' style.
>>
>> I need to enumerate frequencies and there's no "get_freq_by_index" in
>> opp core that I can find so I'd still need to use
>> dev_pm_opp_find_freq_floor.
> 
> Yes. I agree. Just I recommend that use the dev_pm_opp_get_opp_count()
> instead of infinite loop style with 'while(true)'. I don't prefer to
> use the infinite loop coding-sytle.
> 
>>
>> It's strange that OPP core doesn't offer additional support for
>> enumerating OPPs like a for_each macro?
> 
> Right there are no for_each_macro.
> 
> imx8m_ddrc_check_opps() is similiar with 'set_freq_table()'
> in devfreq.c with dev_pm_opp_get_opp_count(). You can refer to it.

OK

>>>>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>>>>> +		if (opp == ERR_PTR(-ERANGE))
>>>>> +			break;
>>>>> +		if (IS_ERR(opp)) {
>>>>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>>>>> +				PTR_ERR(opp));
>>>>> +			return PTR_ERR(opp);
>>>>> +		}
>>>>> +		dev_pm_opp_put(opp);
>>>>> +
>>>>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>>>>> +		if (!freq_info) {
>>>>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>>>>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>>>>> +			dev_pm_opp_disable(dev, freq);
>>>>> +		}
>>>>> +
>>>>> +		freq--;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static void imx8m_ddrc_exit(struct device *dev)
>>>>> +{
>>>>> +	dev_pm_opp_of_remove_table(dev);
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct device *dev = &pdev->dev;
>>>>> +	struct imx8m_ddrc *priv;
>>>>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>>>>> +	int ret;
>>>>> +
>>>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>>>> +	if (!priv)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	platform_set_drvdata(pdev, priv);
>>>>> +
>>>>> +	ret = imx8m_ddrc_init_freq_info(dev);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	priv->dram_core = devm_clk_get(dev, "core");
>>>>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>>>>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>>>>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>>>>> +	if (IS_ERR(priv->dram_core) ||
>>>>> +		IS_ERR(priv->dram_pll) ||
>>>>> +		IS_ERR(priv->dram_alt) ||
>>>>> +		IS_ERR(priv->dram_apb)) {
>>>>> +		ret = PTR_ERR(priv->devfreq);
>>>>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	ret = dev_pm_opp_of_add_table(dev);
>>>>> +	if (ret < 0) {
>>>>> +		dev_err(dev, "failed to get OPP table\n");
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	ret = imx8m_ddrc_check_opps(dev);
>>>>> +	if (ret < 0)
>>>>> +		goto err;
>>>>> +
>>>>> +	priv->profile.polling_ms = 1000;
>>>>> +	priv->profile.target = imx8m_ddrc_target;
>>>>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
>>>>
>>>> ditto. It is not used on this patch. On later, add the get_dev_status
>>>> for the ondemand governor.
>>>>
>>>>> +	priv->profile.exit = imx8m_ddrc_exit;
>>>>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>>>>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>>>>> +
>>>>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>>>>> +						gov, NULL);
>>>>> +	if (IS_ERR(priv->devfreq)) {
>>>>> +		ret = PTR_ERR(priv->devfreq);
>>>>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>>>>> +		goto err;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +
>>>>> +err:
>>>>> +	dev_pm_opp_of_remove_table(dev);
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>>>>> +	{ .compatible = "fsl,imx8m-ddrc", },
>>>>> +	{ /* sentinel */ },
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>>>>> +
>>>>> +static struct platform_driver imx8m_ddrc_platdrv = {
>>>>> +	.probe		= imx8m_ddrc_probe,
>>>>> +	.driver = {
>>>>> +		.name	= "imx8m-ddrc-devfreq",
>>>>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>>>>> +	},
>>>>> +};
>>>>> +module_platform_driver(imx8m_ddrc_platdrv);
>>>>> +
>>>>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>>>>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>>>>> +MODULE_LICENSE("GPL v2");
>>
>>
> 
> 


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH v5 4/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller
@ 2019-11-14 16:01             ` Leonard Crestez
  0 siblings, 0 replies; 41+ messages in thread
From: Leonard Crestez @ 2019-11-14 16:01 UTC (permalink / raw)
  To: Chanwoo Choi
  Cc: Mark Rutland, Artur Świgoń,
	Jacky Bai, Viresh Kumar, Michael Turquette, Angus Ainslie,
	Alexandre Bailon, Matthias Kaehlcke, Abel Vesa, Saravana Kannan,
	Krzysztof Kozlowski, linux-clk, MyungJoo Ham, dl-linux-imx,
	devicetree, linux-pm, Rob Herring, Martin Kepplinger,
	linux-arm-kernel, Aisheng Dong, Anson Huang, Stephen Boyd,
	Rafael J. Wysocki, Kyungmin Park, kernel, Fabio Estevam,
	Shawn Guo, Georgi Djakov

On 14.11.2019 03:16, Chanwoo Choi wrote:
> On 11/13/19 10:10 PM, Leonard Crestez wrote:
>> On 13.11.2019 08:23, Chanwoo Choi wrote:
>>> On 11/13/19 11:30 AM, Chanwoo Choi wrote:
>>>> Hi Leonard,
>>>>
>>>> On 11/13/19 6:50 AM, Leonard Crestez wrote:
>>>>> Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual
>>>>> frequency switching is implemented inside TF-A, this driver wraps the
>>>>> SMC calls and synchronizes the clk tree.
>>>>>
>>>>> The DRAM clocks on imx8m have the following structure (abridged):
>>>>>
>>>>>    +----------+       |\            +------+
>>>>>    | dram_pll |-------|M| dram_core |      |
>>>>>    +----------+       |U|---------->| D    |
>>>>>                    /--|X|           |  D   |
>>>>>      dram_alt_root |  |/            |   R  |
>>>>>                    |                |    C |
>>>>>               +---------+           |      |
>>>>>               |FIX DIV/4|           |      |
>>>>>               +---------+           |      |
>>>>>     composite:     |                |      |
>>>>>    +----------+    |                |      |
>>>>>    | dram_alt |----/                |      |
>>>>>    +----------+                     |      |
>>>>>    | dram_apb |-------------------->|      |
>>>>>    +----------+                     +------+
>>>>>
>>>>> The dram_pll is used for higher rates and dram_alt is used for lower
>>>>> rates. The dram_alt and dram_apb clocks are "imx composite" and their
>>>>> parent can also be modified.
>>>>>
>>>>> This driver will prepare/enable the new parents ahead of switching (so
>>>>> that the expected roots are enabled) and afterwards it will call
>>>>> clk_set_parent to ensure the parents in clock framework are up-to-date.
>>>>>
>>>>> The driver relies on dram_pll dram_alt and dram_apb being marked with
>>>>> CLK_GET_RATE_NOCACHE for rate updates.
>>>>>
>>>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>>>> ---
>>>>>    drivers/devfreq/Kconfig      |   9 +
>>>>>    drivers/devfreq/Makefile     |   1 +
>>>>>    drivers/devfreq/imx8m-ddrc.c | 460 +++++++++++++++++++++++++++++++++++
>>>>>    3 files changed, 470 insertions(+)
>>>>>    create mode 100644 drivers/devfreq/imx8m-ddrc.c
>>>>>
>>>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>>>> index 066e6c4efaa2..923a6132e741 100644
>>>>> --- a/drivers/devfreq/Kconfig
>>>>> +++ b/drivers/devfreq/Kconfig
>>>>> @@ -89,10 +89,19 @@ config ARM_EXYNOS_BUS_DEVFREQ
>>>>>    	  Each memory bus group could contain many memoby bus block. It reads
>>>>>    	  PPMU counters of memory controllers by using DEVFREQ-event device
>>>>>    	  and adjusts the operating frequencies and voltages with OPP support.
>>>>>    	  This does not yet operate with optimal voltages.
>>>>>    
>>>>> +config ARM_IMX8M_DDRC_DEVFREQ
>>>>> +	tristate "i.MX8M DDRC DEVFREQ Driver"
>>>>> +	depends on ARCH_MXC || COMPILE_TEST
>>>>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>>> +	select DEVFREQ_GOV_USERSPACE
>>>>> +	help
>>>>> +	  This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
>>>>> +	  adjusting DRAM frequency.
>>>>> +
>>>>>    config ARM_TEGRA_DEVFREQ
>>>>>    	tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
>>>>>    	depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
>>>>>    		ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
>>>>>    		ARCH_TEGRA_210_SOC || \
>>>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>>>> index 338ae8440db6..3eb4d5e6635c 100644
>>>>> --- a/drivers/devfreq/Makefile
>>>>> +++ b/drivers/devfreq/Makefile
>>>>> @@ -7,10 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>>>>>    obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>>>>>    obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>>>>    
>>>>>    # DEVFREQ Drivers
>>>>>    obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>>>> +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ)	+= imx8m-ddrc.o
>>>>>    obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>>>>>    obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra30-devfreq.o
>>>>>    obj-$(CONFIG_ARM_TEGRA20_DEVFREQ)	+= tegra20-devfreq.o
>>>>>    
>>>>>    # DEVFREQ Event Drivers
>>>>> diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c
>>>>> new file mode 100644
>>>>> index 000000000000..62abb9b79d8a
>>>>> --- /dev/null
>>>>> +++ b/drivers/devfreq/imx8m-ddrc.c
>>>>> @@ -0,0 +1,460 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +/*
>>>>> + * Copyright 2019 NXP
>>>>> + */
>>>>> +
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/device.h>
>>>>> +#include <linux/of_device.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/devfreq.h>
>>>>> +#include <linux/pm_opp.h>
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/clk-provider.h>
>>>>> +#include <linux/arm-smccc.h>
>>>>> +
>>>>> +#define IMX_SIP_DDR_DVFS			0xc2000004
>>>>> +
>>>>> +/* Values starting from 0 switch to specific frequency */
>>>>> +#define IMX_SIP_DDR_FREQ_SET_HIGH		0x00
>>>>> +
>>>>> +/* Deprecated after moving IRQ handling to ATF */
>>>>> +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE		0x0F
>>>>> +
>>>>> +/* Query available frequencies. */
>>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT		0x10
>>>>> +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO		0x11
>>>>> +
>>>>> +/*
>>>>> + * This should be in a 1:1 mapping with devicetree OPPs but
>>>>> + * firmware provides additional info.
>>>>> + */
>>>>> +struct imx8m_ddrc_freq {
>>>>> +	unsigned long rate;
>>>>> +	unsigned long smcarg;
>>>>> +	int dram_core_parent_index;
>>>>> +	int dram_alt_parent_index;
>>>>> +	int dram_apb_parent_index;
>>>>> +};
>>>>> +
>>>>> +/* Hardware limitation */
>>>>> +#define IMX8M_DDRC_MAX_FREQ_COUNT 4
>>>>> +
>>>>> +/*
>>>>> + * i.MX8M DRAM Controller clocks have the following structure (abridged):
>>>>> + *
>>>>> + * +----------+       |\            +------+
>>>>> + * | dram_pll |-------|M| dram_core |      |
>>>>> + * +----------+       |U|---------->| D    |
>>>>> + *                 /--|X|           |  D   |
>>>>> + *   dram_alt_root |  |/            |   R  |
>>>>> + *                 |                |    C |
>>>>> + *            +---------+           |      |
>>>>> + *            |FIX DIV/4|           |      |
>>>>> + *            +---------+           |      |
>>>>> + *  composite:     |                |      |
>>>>> + * +----------+    |                |      |
>>>>> + * | dram_alt |----/                |      |
>>>>> + * +----------+                     |      |
>>>>> + * | dram_apb |-------------------->|      |
>>>>> + * +----------+                     +------+
>>>>> + *
>>>>> + * The dram_pll is used for higher rates and dram_alt is used for lower rates.
>>>>> + *
>>>>> + * Frequency switching is implemented in TF-A (via SMC call) and can change the
>>>>> + * configuration of the clocks, including mux parents. The dram_alt and
>>>>> + * dram_apb clocks are "imx composite" and their parent can change too.
>>>>> + *
>>>>> + * We need to prepare/enable the new mux parents head of switching and update
>>>>> + * their information afterwards.
>>>>> + */
>>>>> +struct imx8m_ddrc {
>>>>> +	struct devfreq_dev_profile profile;
>>>>> +	struct devfreq *devfreq;
>>>>> +
>>>>> +	/* For frequency switching: */
>>>>> +	struct clk *dram_core;
>>>>> +	struct clk *dram_pll;
>>>>> +	struct clk *dram_alt;
>>>>> +	struct clk *dram_apb;
>>>>> +
>>>>> +	int freq_count;
>>>>> +	struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
>>>>> +};
>>>>> +
>>>>> +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
>>>>> +						    unsigned long rate)
>>>>> +{
>>>>> +	struct imx8m_ddrc_freq *freq;
>>>>> +	int i;
>>>>> +
>>>>> +	/*
>>>>> +	 * Firmware reports values in MT/s, so we round-down from Hz
>>>>> +	 * Rounding is extra generous to ensure a match.
>>>>> +	 */
>>>>> +	rate = DIV_ROUND_CLOSEST(rate, 250000);
>>>>> +	for (i = 0; i < priv->freq_count; ++i) {
>>>>> +		freq = &priv->freq_table[i];
>>>>> +		if (freq->rate == rate ||
>>>>> +				freq->rate + 1 == rate ||
>>>>> +				freq->rate - 1 == rate)
>>>>> +			return freq;
>>>>> +	}
>>>>> +
>>>>> +	return NULL;
>>>>> +}
>>>>> +
>>>>> +static void imx8m_ddrc_smc_set_freq(int target_freq)
>>>>> +{
>>>>> +	struct arm_smccc_res res;
>>>>> +	u32 online_cpus = 0;
>>>>> +	int cpu;
>>>>> +
>>>>> +	local_irq_disable();
>>>>> +
>>>>> +	for_each_online_cpu(cpu)
>>>>> +		online_cpus |= (1 << (cpu * 8));
>>>>> +
>>>>> +	/* change the ddr freqency */
>>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
>>>>> +			0, 0, 0, 0, 0, &res);
>>>>> +
>>>>> +	local_irq_enable();
>>>>> +}
>>>>> +
>>>>> +struct clk *clk_get_parent_by_index(struct clk *clk, int index)
>>>>> +{
>>>>> +	struct clk_hw *hw;
>>>>> +
>>>>> +	hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
>>>>> +
>>>>> +	return hw ? hw->clk : NULL;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct clk *new_dram_core_parent;
>>>>> +	struct clk *new_dram_alt_parent;
>>>>> +	struct clk *new_dram_apb_parent;
>>>>> +	int ret;
>>>>> +
>>>>> +	/*
>>>>> +	 * Fetch new parents
>>>>> +	 *
>>>>> +	 * new_dram_alt_parent and new_dram_apb_parent are optional but
>>>>> +	 * new_dram_core_parent is not.
>>>>> +	 */
>>>>> +	new_dram_core_parent = clk_get_parent_by_index(
>>>>> +			priv->dram_core, freq->dram_core_parent_index - 1);
>>>>> +	if (!new_dram_core_parent) {
>>>>> +		dev_err(dev, "failed to fetch new dram_core parent\n");
>>>>> +		return -EINVAL;
>>>>> +	}
>>>>> +	if (freq->dram_alt_parent_index) {
>>>>> +		new_dram_alt_parent = clk_get_parent_by_index(
>>>>> +				priv->dram_alt,
>>>>> +				freq->dram_alt_parent_index - 1);
>>>>> +		if (!new_dram_alt_parent) {
>>>>> +			dev_err(dev, "failed to fetch new dram_alt parent\n");
>>>>> +			return -EINVAL;
>>>>> +		}
>>>>> +	} else
>>>>> +		new_dram_alt_parent = NULL;
>>>>> +
>>>>> +	if (freq->dram_alt_parent_index) {
>>>>> +		new_dram_apb_parent = clk_get_parent_by_index(
>>>>> +				priv->dram_apb, freq->dram_apb_parent_index - 1);
>>>>> +		if (!new_dram_alt_parent) {
>>>>> +			dev_err(dev, "failed to fetch new dram_apb parent\n");
>>>>> +			return -EINVAL;
>>>>> +		}
>>>>> +	} else
>>>>> +		new_dram_apb_parent = NULL;
>>>>> +
>>>>> +	/* increase reference counts and ensure clks are ON before switch */
>>>>> +	ret = clk_prepare_enable(new_dram_core_parent);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed enable new dram_core parent: %d\n", ret);
>>>>
>>>> s/failed enable/failed to enable
>>>>
>>>>> +		goto out;
>>>>> +	}
>>>>> +	ret = clk_prepare_enable(new_dram_alt_parent);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed enable new dram_alt parent: %d\n", ret);
>>>>
>>>> s/failed enable/failed to enable
>>>>
>>>>> +		goto out_disable_core_parent;
>>>>> +	}
>>>>> +	ret = clk_prepare_enable(new_dram_apb_parent);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed enable new dram_apb parent: %d\n", ret);
>>>>
>>>> s/failed enable/failed to enable
>>>>
>>>>> +		goto out_disable_alt_parent;
>>>>> +	}
>>>>> +
>>>>> +	imx8m_ddrc_smc_set_freq(freq->smcarg);
>>>>> +
>>>>> +	/* update parents in clk tree after switch. */
>>>>> +	ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
>>>>> +	if (ret)
>>>>> +		dev_warn(dev, "failed set dram_core parent: %d\n", ret);
>>>>
>>>> s/failed set/failed to set
>>>>
>>>>> +	if (new_dram_alt_parent) {
>>>>> +		ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
>>>>> +		if (ret)
>>>>> +			dev_warn(dev, "failed set dram_alt parent: %d\n", ret);
>>>>
>>>> s/failed set/failed to set
>>>>
>>>>> +	}
>>>>> +	if (new_dram_apb_parent) {
>>>>> +		ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
>>>>> +		if (ret)
>>>>> +			dev_warn(dev, "failed set dram_apb parent: %d\n", ret);
>>>>
>>>> s/failed set/failed to set
>>
>> OK, but this might make a few messages longer than 80 chars.
> 
> I don't like over 80 chars as I already commented.
> 
> 	dev_warn(dev,
> 		"failed set dram_apb parent: %d\n", ret);
> 
>>
>>>>> +	}
>>>>> +
>>>>> +	/*
>>>>> +	 * Explicitly refresh dram PLL rate.
>>>>> +	 *
>>>>> +	 * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
>>>>> +	 * automatically refreshed when clk_get_rate is called on children.
>>>>> +	 */
>>>>> +	clk_get_rate(priv->dram_pll);
>>>>> +
>>>>> +	/*
>>>>> +	 * clk_set_parent transfer the reference count from old parent.
>>>>> +	 * now we drop extra reference counts used during the switch
>>>>> +	 */
>>>>> +	clk_disable_unprepare(new_dram_apb_parent);
>>>>> +out_disable_alt_parent:
>>>>> +	clk_disable_unprepare(new_dram_alt_parent);
>>>>> +out_disable_core_parent:
>>>>> +	clk_disable_unprepare(new_dram_core_parent);
>>>>> +out:
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>>> +	struct dev_pm_opp *new_opp;
>>>>> +	unsigned long old_freq, new_freq;
>>>>> +	int ret;
>>>>> +
>>>>> +	new_opp = devfreq_recommended_opp(dev, freq, flags);
>>>>> +	if (IS_ERR(new_opp)) {
>>>>> +		ret = PTR_ERR(new_opp);
>>>>> +		dev_err(dev, "failed to get recommended opp: %d\n", ret);
>>>>> +		return ret;
>>>>> +	}
>>>>> +	dev_pm_opp_put(new_opp);
>>>>> +
>>>>> +	old_freq = clk_get_rate(priv->dram_core);
>>>>> +	if (*freq == old_freq)
>>>>> +		return 0;
>>>>> +
>>>>> +	freq_info = imx8m_ddrc_find_freq(priv, *freq);
>>>>> +	if (!freq_info)
>>>>> +		return -EINVAL;
>>>>> +
>>>>> +	/*
>>>>> +	 * Read back the clk rate to verify switch was correct and so that
>>>>> +	 * we can report it on all error paths.
>>>>> +	 */
>>>>> +	ret = imx8m_ddrc_set_freq(dev, freq_info);
>>>>> +
>>>>> +	new_freq = clk_get_rate(priv->dram_core);
>>>>> +	if (ret)
>>>>> +		dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
>>>>> +			old_freq, *freq, ret, new_freq);
>>>>> +	else if (*freq != new_freq)
>>>>> +		dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
>>>>> +			old_freq, *freq, new_freq);
>>>>
>>>> Actually, is it error? When use clk_set_rate with target_freq,
>>>> if target_freq is not same with supported clock of h/w clock,
>>>> the clk_set_rate set the similiar clock rate among the supported clock table.
>>>>
>>>> It means that if the user of clock_set_rate() enters the unsupported clock rate,
>>>> the case of (*freq != new_freq) happen.
>>>>
>>>> Are you sure that you want to show the error when this case (*freq != new_freq)?
>>>> The your origin code is not wrong. Just question from me.
>>
>> The assumption here is that the OPP table will contain the precise
>> frequency as reported by clk_get_rate after a switch.
> 
> nitpick:
> As I said, I think it's not error. If failed to set the clock rate
> with any value, it is error.  But, if clk_set_rate() selected
> the supported clock, it is not error.
> 
> But, I'm sure that you completed the test and you could want to
> keep this line. I'm OK.

Yes, I think it's helpful to be extra paranoid here.
>> For example imx8mq-evk.dts has an OPP of exactly 166935483 Hz.>
>>>>> +	else
>>>>> +		dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
>>>>> +			*freq, old_freq);
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +
>>>>> +	*freq = clk_get_rate(priv->dram_core);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_get_dev_status(struct device *dev,
>>>>> +				     struct devfreq_dev_status *stat)
>>>>
>>>> get_dev_status() callback is called by only simpleondemand governor.
>>>> When userspace governor is used, this function is never called.
>>>> So, need to drop this function and then add this function on next time.
>>
>> Then you get an oops on "echo simple_ondemand > governor".
>>
>> In theory the simple_ondemand governor could check for NULL
>> "get_dev_status" or devfreq core could reject switching to
>> simple_ondemand if no get_dev_status is implemented. For example a
>> devfreq_governor.validate callback could be implemented?
> 
> Don't do that. I'll re-implement the governor flag like immutable
> /interrupt-driven and add the feature that the devfreq device driver
> specifies the supported governors when adding the device. I'll.
> 
>>
>> But right now the "get_dev_status" callback is NOT optional.
> 
> OK. Keep the get_dev_status().

Yes, it can be removed after devfreq core makes it optional.
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +
>>>>> +	stat->busy_time = 0;
>>>>> +	stat->total_time = 0;
>>>>> +	stat->current_frequency = clk_get_rate(priv->dram_core);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_init_freq_info(struct device *dev)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct arm_smccc_res res;
>>>>> +	int index;
>>>>> +
>>>>> +	/* An error here means DDR DVFS API not supported by firmware */
>>>>> +	arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
>>>>> +			0, 0, 0, 0, 0, 0, &res);
>>>>> +	priv->freq_count = res.a0;
>>>>> +	if (priv->freq_count <= 0 ||
>>>>> +			priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
>>>>> +		return -ENODEV;
>>>>> +
>>>>> +	for (index = 0; index < priv->freq_count; ++index) {
>>>>> +		struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
>>>>> +
>>>>> +		arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
>>>>> +			      index, 0, 0, 0, 0, 0, &res);
>>>>> +		/* Result should be strictly positive */
>>>>> +		if ((long)res.a0 <= 0)
>>>>> +			return -ENODEV;
>>>>> +
>>>>> +		freq->rate = res.a0;
>>>>> +		freq->smcarg = index;
>>>>> +		freq->dram_core_parent_index = res.a1;
>>>>> +		freq->dram_alt_parent_index = res.a2;
>>>>> +		freq->dram_apb_parent_index = res.a3;
>>>>> +
>>>>> +		/* dram_core has 2 options: dram_pll or dram_alt_root */
>>>>> +		if (freq->dram_core_parent_index != 1 &&
>>>>> +				freq->dram_core_parent_index != 2)
>>>>> +			return -ENODEV;
>>>>> +		/* dram_apb and dram_alt have exactly 8 possible parents */
>>>>> +		if (freq->dram_alt_parent_index > 8 ||
>>>>> +				freq->dram_apb_parent_index > 8)
>>>>> +			return -ENODEV;
>>>>> +		/* dram_core from alt requires explicit dram_alt parent */
>>>>> +		if (freq->dram_core_parent_index == 2 &&
>>>>> +				freq->dram_alt_parent_index == 0)
>>>>> +			return -ENODEV;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_check_opps(struct device *dev)
>>>>> +{
>>>>> +	struct imx8m_ddrc *priv = dev_get_drvdata(dev);
>>>>> +	struct imx8m_ddrc_freq *freq_info;
>>>>> +	struct dev_pm_opp *opp;
>>>>> +	unsigned long freq;
>>>>> +
>>>>> +	/* Enumerate DT OPPs and disable those not supported by firmware */
>>>>> +	freq = ULONG_MAX;
>>>>> +	while (true) {
>>>
>>> You can get the number of OPP entries int the opp table
>>> with dev_pm_opp_get_count(dev). I think that better to
>>> use the correct number of OPP entries instead of 'while(true)' style.
>>
>> I need to enumerate frequencies and there's no "get_freq_by_index" in
>> opp core that I can find so I'd still need to use
>> dev_pm_opp_find_freq_floor.
> 
> Yes. I agree. Just I recommend that use the dev_pm_opp_get_opp_count()
> instead of infinite loop style with 'while(true)'. I don't prefer to
> use the infinite loop coding-sytle.
> 
>>
>> It's strange that OPP core doesn't offer additional support for
>> enumerating OPPs like a for_each macro?
> 
> Right there are no for_each_macro.
> 
> imx8m_ddrc_check_opps() is similiar with 'set_freq_table()'
> in devfreq.c with dev_pm_opp_get_opp_count(). You can refer to it.

OK

>>>>> +		opp = dev_pm_opp_find_freq_floor(dev, &freq);
>>>>> +		if (opp == ERR_PTR(-ERANGE))
>>>>> +			break;
>>>>> +		if (IS_ERR(opp)) {
>>>>> +			dev_err(dev, "Failed enumerating OPPs: %ld\n",
>>>>> +				PTR_ERR(opp));
>>>>> +			return PTR_ERR(opp);
>>>>> +		}
>>>>> +		dev_pm_opp_put(opp);
>>>>> +
>>>>> +		freq_info = imx8m_ddrc_find_freq(priv, freq);
>>>>> +		if (!freq_info) {
>>>>> +			dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
>>>>> +					freq, DIV_ROUND_CLOSEST(freq, 250000));
>>>>> +			dev_pm_opp_disable(dev, freq);
>>>>> +		}
>>>>> +
>>>>> +		freq--;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static void imx8m_ddrc_exit(struct device *dev)
>>>>> +{
>>>>> +	dev_pm_opp_of_remove_table(dev);
>>>>> +}
>>>>> +
>>>>> +static int imx8m_ddrc_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct device *dev = &pdev->dev;
>>>>> +	struct imx8m_ddrc *priv;
>>>>> +	const char *gov = DEVFREQ_GOV_USERSPACE;
>>>>> +	int ret;
>>>>> +
>>>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>>>> +	if (!priv)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	platform_set_drvdata(pdev, priv);
>>>>> +
>>>>> +	ret = imx8m_ddrc_init_freq_info(dev);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "failed to init firmware freq info: %d\n", ret);
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	priv->dram_core = devm_clk_get(dev, "core");
>>>>> +	priv->dram_pll = devm_clk_get(dev, "pll");
>>>>> +	priv->dram_alt = devm_clk_get(dev, "alt");
>>>>> +	priv->dram_apb = devm_clk_get(dev, "apb");
>>>>> +	if (IS_ERR(priv->dram_core) ||
>>>>> +		IS_ERR(priv->dram_pll) ||
>>>>> +		IS_ERR(priv->dram_alt) ||
>>>>> +		IS_ERR(priv->dram_apb)) {
>>>>> +		ret = PTR_ERR(priv->devfreq);
>>>>> +		dev_err(dev, "failed to fetch clocks: %d\n", ret);
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	ret = dev_pm_opp_of_add_table(dev);
>>>>> +	if (ret < 0) {
>>>>> +		dev_err(dev, "failed to get OPP table\n");
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	ret = imx8m_ddrc_check_opps(dev);
>>>>> +	if (ret < 0)
>>>>> +		goto err;
>>>>> +
>>>>> +	priv->profile.polling_ms = 1000;
>>>>> +	priv->profile.target = imx8m_ddrc_target;
>>>>> +	priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
>>>>
>>>> ditto. It is not used on this patch. On later, add the get_dev_status
>>>> for the ondemand governor.
>>>>
>>>>> +	priv->profile.exit = imx8m_ddrc_exit;
>>>>> +	priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
>>>>> +	priv->profile.initial_freq = clk_get_rate(priv->dram_core);
>>>>> +
>>>>> +	priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
>>>>> +						gov, NULL);
>>>>> +	if (IS_ERR(priv->devfreq)) {
>>>>> +		ret = PTR_ERR(priv->devfreq);
>>>>> +		dev_err(dev, "failed to add devfreq device: %d\n", ret);
>>>>> +		goto err;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +
>>>>> +err:
>>>>> +	dev_pm_opp_of_remove_table(dev);
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id imx8m_ddrc_of_match[] = {
>>>>> +	{ .compatible = "fsl,imx8m-ddrc", },
>>>>> +	{ /* sentinel */ },
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
>>>>> +
>>>>> +static struct platform_driver imx8m_ddrc_platdrv = {
>>>>> +	.probe		= imx8m_ddrc_probe,
>>>>> +	.driver = {
>>>>> +		.name	= "imx8m-ddrc-devfreq",
>>>>> +		.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
>>>>> +	},
>>>>> +};
>>>>> +module_platform_driver(imx8m_ddrc_platdrv);
>>>>> +
>>>>> +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
>>>>> +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
>>>>> +MODULE_LICENSE("GPL v2");
>>
>>
> 
> 


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 41+ messages in thread

end of thread, other threads:[~2019-11-14 16:03 UTC | newest]

Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-12 21:50 [PATCH v5 0/5] PM / devfreq: Add dynamic scaling for imx8m ddr controller Leonard Crestez
2019-11-12 21:50 ` Leonard Crestez
2019-11-12 21:50 ` [PATCH v5 1/5] clk: imx8m: Set CLK_GET_RATE_NOCACHE on dram clocks Leonard Crestez
2019-11-12 21:50   ` Leonard Crestez
2019-11-12 21:50 ` [PATCH v5 2/5] clk: imx: Mark dram pll on 8mm and 8mn with CLK_GET_RATE_NOCACHE Leonard Crestez
2019-11-12 21:50   ` Leonard Crestez
2019-11-13  7:29   ` Peng Fan
2019-11-13  7:29     ` Peng Fan
2019-11-13 12:02     ` Leonard Crestez
2019-11-13 12:02       ` Leonard Crestez
2019-11-12 21:50 ` [PATCH v5 3/5] dt-bindings: memory: Add bindings for imx8m ddr controller Leonard Crestez
2019-11-12 21:50   ` Leonard Crestez
2019-11-13  2:38   ` Chanwoo Choi
2019-11-13  2:38     ` Chanwoo Choi
2019-11-13 12:35     ` Leonard Crestez
2019-11-13 12:35       ` Leonard Crestez
2019-11-14  1:07       ` Chanwoo Choi
2019-11-14  1:07         ` Chanwoo Choi
2019-11-12 21:50 ` [PATCH v5 4/5] PM / devfreq: Add dynamic scaling " Leonard Crestez
2019-11-12 21:50   ` Leonard Crestez
2019-11-13  2:30   ` Chanwoo Choi
2019-11-13  2:30     ` Chanwoo Choi
2019-11-13  6:28     ` Chanwoo Choi
2019-11-13  6:28       ` Chanwoo Choi
2019-11-13 13:10       ` Leonard Crestez
2019-11-13 13:10         ` Leonard Crestez
2019-11-14  1:21         ` Chanwoo Choi
2019-11-14  1:21           ` Chanwoo Choi
2019-11-14 16:01           ` Leonard Crestez
2019-11-14 16:01             ` Leonard Crestez
2019-11-13 14:07   ` kbuild test robot
2019-11-13 14:07     ` kbuild test robot
2019-11-13 14:07     ` kbuild test robot
2019-11-13 14:07   ` [RFC PATCH] PM / devfreq: clk_get_parent_by_index() can be static kbuild test robot
2019-11-13 14:07     ` kbuild test robot
2019-11-13 14:07     ` kbuild test robot
2019-11-14  1:23     ` Chanwoo Choi
2019-11-14  1:23       ` Chanwoo Choi
2019-11-14  1:23       ` Chanwoo Choi
2019-11-12 21:50 ` [PATCH v5 5/5] arm64: dts: imx8m: Add ddr controller nodes Leonard Crestez
2019-11-12 21:50   ` Leonard Crestez

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.