diff mbox

[v7,3/4] clk: samsung: exynos5433: Add runtime PM support

Message ID 1501839294-478-4-git-send-email-m.szyprowski@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Marek Szyprowski Aug. 4, 2017, 9:34 a.m. UTC
Add runtime pm support for all clock controller units (CMU), which belongs
to power domains and require special handling during on/off operations.
Typically special values has to be written to MUX registers to change
internal clocks parents to OSC clock before turning power off. During such
operation all clocks, which enters CMU has to be enabled to let MUX to
stabilize. Also for each CMU there is one special parent clock, which has
to be enabled all the time when any access to CMU registers is done.

This patch solves most of the mysterious external abort and freeze issues
caused by a lack of proper parent CMU clock enabled or incorrect turn off
procedure.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
---
 .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
 drivers/clk/samsung/clk-exynos5433.c               | 409 ++++++++++++++++-----
 drivers/clk/samsung/clk.h                          |   6 +
 3 files changed, 346 insertions(+), 85 deletions(-)

Comments

Chanwoo Choi Aug. 4, 2017, 2:19 p.m. UTC | #1
Hi,

As I replied on patch2, I tested these patches on Exynos5433-based TM2.

Reviewed-by: Chanwoo Choi <cw00.choi@samsung.com>
Tested-by: Chanwoo Choi <cw00.choi@samsung.com>

Regards,
Chanwoo Choi


On Fri, Aug 4, 2017 at 6:34 PM, Marek Szyprowski
<m.szyprowski@samsung.com> wrote:
> Add runtime pm support for all clock controller units (CMU), which belongs
> to power domains and require special handling during on/off operations.
> Typically special values has to be written to MUX registers to change
> internal clocks parents to OSC clock before turning power off. During such
> operation all clocks, which enters CMU has to be enabled to let MUX to
> stabilize. Also for each CMU there is one special parent clock, which has
> to be enabled all the time when any access to CMU registers is done.
>
> This patch solves most of the mysterious external abort and freeze issues
> caused by a lack of proper parent CMU clock enabled or incorrect turn off
> procedure.
>
> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
> ---
>  .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
>  drivers/clk/samsung/clk-exynos5433.c               | 409 ++++++++++++++++-----
>  drivers/clk/samsung/clk.h                          |   6 +
>  3 files changed, 346 insertions(+), 85 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
> index 1dc80f8811fe..5c7dd12e667a 100644
> --- a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
> +++ b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
> @@ -168,6 +168,11 @@ Required Properties:
>                 - aclk_cam1_400
>                 - aclk_cam1_552
>
> +Optional properties:
> +  - power-domains: a phandle to respective power domain node as described by
> +       generic PM domain bindings (see power/power_domain.txt for more
> +       information).
> +
>  Each clock is assigned an identifier and client nodes can use this identifier
>  to specify the clock which they consume.
>
> @@ -270,6 +275,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                 clocks = <&xxti>,
>                        <&cmu_top CLK_ACLK_G2D_266>,
>                        <&cmu_top CLK_ACLK_G2D_400>;
> +               power-domains = <&pd_g2d>;
>         };
>
>         cmu_disp: clock-controller@13b90000 {
> @@ -295,6 +301,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                        <&cmu_mif CLK_SCLK_DECON_ECLK_DISP>,
>                        <&cmu_mif CLK_SCLK_DECON_TV_VCLK_DISP>,
>                        <&cmu_mif CLK_ACLK_DISP_333>;
> +               power-domains = <&pd_disp>;
>         };
>
>         cmu_aud: clock-controller@114c0000 {
> @@ -304,6 +311,7 @@ Example 2: Examples of clock controller nodes are listed below.
>
>                 clock-names = "oscclk", "fout_aud_pll";
>                 clocks = <&xxti>, <&cmu_top CLK_FOUT_AUD_PLL>;
> +               power-domains = <&pd_aud>;
>         };
>
>         cmu_bus0: clock-controller@13600000 {
> @@ -340,6 +348,7 @@ Example 2: Examples of clock controller nodes are listed below.
>
>                 clock-names = "oscclk", "aclk_g3d_400";
>                 clocks = <&xxti>, <&cmu_top CLK_ACLK_G3D_400>;
> +               power-domains = <&pd_g3d>;
>         };
>
>         cmu_gscl: clock-controller@13cf0000 {
> @@ -353,6 +362,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                 clocks = <&xxti>,
>                         <&cmu_top CLK_ACLK_GSCL_111>,
>                         <&cmu_top CLK_ACLK_GSCL_333>;
> +               power-domains = <&pd_gscl>;
>         };
>
>         cmu_apollo: clock-controller@11900000 {
> @@ -384,6 +394,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                 clocks = <&xxti>,
>                        <&cmu_top CLK_SCLK_JPEG_MSCL>,
>                        <&cmu_top CLK_ACLK_MSCL_400>;
> +               power-domains = <&pd_mscl>;
>         };
>
>         cmu_mfc: clock-controller@15280000 {
> @@ -393,6 +404,7 @@ Example 2: Examples of clock controller nodes are listed below.
>
>                 clock-names = "oscclk", "aclk_mfc_400";
>                 clocks = <&xxti>, <&cmu_top CLK_ACLK_MFC_400>;
> +               power-domains = <&pd_mfc>;
>         };
>
>         cmu_hevc: clock-controller@14f80000 {
> @@ -402,6 +414,7 @@ Example 2: Examples of clock controller nodes are listed below.
>
>                 clock-names = "oscclk", "aclk_hevc_400";
>                 clocks = <&xxti>, <&cmu_top CLK_ACLK_HEVC_400>;
> +               power-domains = <&pd_hevc>;
>         };
>
>         cmu_isp: clock-controller@146d0000 {
> @@ -415,6 +428,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                 clocks = <&xxti>,
>                        <&cmu_top CLK_ACLK_ISP_DIS_400>,
>                        <&cmu_top CLK_ACLK_ISP_400>;
> +               power-domains = <&pd_isp>;
>         };
>
>         cmu_cam0: clock-controller@120d0000 {
> @@ -430,6 +444,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                        <&cmu_top CLK_ACLK_CAM0_333>,
>                        <&cmu_top CLK_ACLK_CAM0_400>,
>                        <&cmu_top CLK_ACLK_CAM0_552>;
> +               power-domains = <&pd_cam0>;
>         };
>
>         cmu_cam1: clock-controller@145d0000 {
> @@ -451,6 +466,7 @@ Example 2: Examples of clock controller nodes are listed below.
>                        <&cmu_top CLK_ACLK_CAM1_333>,
>                        <&cmu_top CLK_ACLK_CAM1_400>,
>                        <&cmu_top CLK_ACLK_CAM1_552>;
> +               power-domains = <&pd_cam1>;
>         };
>
>  Example 3: UART controller node that consumes the clock generated by the clock
> diff --git a/drivers/clk/samsung/clk-exynos5433.c b/drivers/clk/samsung/clk-exynos5433.c
> index 11343a597093..7ae9bccb1192 100644
> --- a/drivers/clk/samsung/clk-exynos5433.c
> +++ b/drivers/clk/samsung/clk-exynos5433.c
> @@ -9,9 +9,14 @@
>   * Common Clock Framework support for Exynos5433 SoC.
>   */
>
> +#include <linux/clk.h>
>  #include <linux/clk-provider.h>
>  #include <linux/of.h>
>  #include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
>
>  #include <dt-bindings/clock/exynos5433.h>
>
> @@ -1991,6 +1996,14 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
>         ENABLE_IP_FSYS1,
>  };
>
> +static const struct samsung_clk_reg_dump fsys_suspend_regs[] = {
> +       { MUX_SEL_FSYS0, 0 },
> +       { MUX_SEL_FSYS1, 0 },
> +       { MUX_SEL_FSYS2, 0 },
> +       { MUX_SEL_FSYS3, 0 },
> +       { MUX_SEL_FSYS4, 0 },
> +};
> +
>  static const struct samsung_fixed_rate_clock fsys_fixed_clks[] __initconst = {
>         /* PHY clocks from USBDRD30_PHY */
>         FRATE(CLK_PHYCLK_USBDRD30_UDRD30_PHYCLOCK_PHY,
> @@ -2296,16 +2309,11 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
>         .nr_clk_ids             = FSYS_NR_CLK,
>         .clk_regs               = fsys_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(fsys_clk_regs),
> +       .suspend_regs           = fsys_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(fsys_suspend_regs),
> +       .clk_name               = "aclk_fsys_200",
>  };
>
> -static void __init exynos5433_cmu_fsys_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &fsys_cmu_info);
> -}
> -
> -CLK_OF_DECLARE(exynos5433_cmu_fsys, "samsung,exynos5433-cmu-fsys",
> -               exynos5433_cmu_fsys_init);
> -
>  /*
>   * Register offset definitions for CMU_G2D
>   */
> @@ -2335,6 +2343,10 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>         DIV_ENABLE_IP_G2D_SECURE_SMMU_G2D,
>  };
>
> +static const struct samsung_clk_reg_dump g2d_suspend_regs[] = {
> +       { MUX_SEL_G2D0, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_aclk_g2d_266_user_p)                = { "oscclk", "aclk_g2d_266", };
>  PNAME(mout_aclk_g2d_400_user_p)                = { "oscclk", "aclk_g2d_400", };
> @@ -2420,16 +2432,11 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>         .nr_clk_ids             = G2D_NR_CLK,
>         .clk_regs               = g2d_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(g2d_clk_regs),
> +       .suspend_regs           = g2d_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(g2d_suspend_regs),
> +       .clk_name               = "aclk_g2d_400",
>  };
>
> -static void __init exynos5433_cmu_g2d_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &g2d_cmu_info);
> -}
> -
> -CLK_OF_DECLARE(exynos5433_cmu_g2d, "samsung,exynos5433-cmu-g2d",
> -               exynos5433_cmu_g2d_init);
> -
>  /*
>   * Register offset definitions for CMU_DISP
>   */
> @@ -2494,6 +2501,18 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>         CLKOUT_CMU_DISP_DIV_STAT,
>  };
>
> +static const struct samsung_clk_reg_dump disp_suspend_regs[] = {
> +       /* PLL has to be enabled for suspend */
> +       { DISP_PLL_CON0, 0x85f40502 },
> +       /* ignore status of external PHY muxes during suspend to avoid hangs */
> +       { MUX_IGNORE_DISP2, 0x00111111 },
> +       { MUX_SEL_DISP0, 0 },
> +       { MUX_SEL_DISP1, 0 },
> +       { MUX_SEL_DISP2, 0 },
> +       { MUX_SEL_DISP3, 0 },
> +       { MUX_SEL_DISP4, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_disp_pll_p)                 = { "oscclk", "fout_disp_pll", };
>  PNAME(mout_sclk_dsim1_user_p)          = { "oscclk", "sclk_dsim1_disp", };
> @@ -2841,16 +2860,11 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>         .nr_clk_ids             = DISP_NR_CLK,
>         .clk_regs               = disp_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(disp_clk_regs),
> +       .suspend_regs           = disp_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(disp_suspend_regs),
> +       .clk_name               = "aclk_disp_333",
>  };
>
> -static void __init exynos5433_cmu_disp_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &disp_cmu_info);
> -}
> -
> -CLK_OF_DECLARE(exynos5433_cmu_disp, "samsung,exynos5433-cmu-disp",
> -               exynos5433_cmu_disp_init);
> -
>  /*
>   * Register offset definitions for CMU_AUD
>   */
> @@ -2885,6 +2899,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
>         ENABLE_IP_AUD1,
>  };
>
> +static const struct samsung_clk_reg_dump aud_suspend_regs[] = {
> +       { MUX_SEL_AUD0, 0 },
> +       { MUX_SEL_AUD1, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_aud_pll_user_aud_p) = { "oscclk", "fout_aud_pll", };
>  PNAME(mout_sclk_aud_pcm_p)     = { "mout_aud_pll_user", "ioclk_audiocdclk0",};
> @@ -3011,16 +3030,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
>         .nr_clk_ids             = AUD_NR_CLK,
>         .clk_regs               = aud_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(aud_clk_regs),
> +       .suspend_regs           = aud_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(aud_suspend_regs),
> +       .clk_name               = "fout_aud_pll",
>  };
>
> -static void __init exynos5433_cmu_aud_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &aud_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_aud, "samsung,exynos5433-cmu-aud",
> -               exynos5433_cmu_aud_init);
> -
> -
>  /*
>   * Register offset definitions for CMU_BUS{0|1|2}
>   */
> @@ -3222,6 +3236,10 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
>         CLK_STOPCTRL,
>  };
>
> +static const struct samsung_clk_reg_dump g3d_suspend_regs[] = {
> +       { MUX_SEL_G3D, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_aclk_g3d_400_p)     = { "mout_g3d_pll", "aclk_g3d_400", };
>  PNAME(mout_g3d_pll_p)          = { "oscclk", "fout_g3d_pll", };
> @@ -3295,15 +3313,11 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
>         .nr_clk_ids             = G3D_NR_CLK,
>         .clk_regs               = g3d_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(g3d_clk_regs),
> +       .suspend_regs           = g3d_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(g3d_suspend_regs),
> +       .clk_name               = "aclk_g3d_400",
>  };
>
> -static void __init exynos5433_cmu_g3d_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &g3d_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_g3d, "samsung,exynos5433-cmu-g3d",
> -               exynos5433_cmu_g3d_init);
> -
>  /*
>   * Register offset definitions for CMU_GSCL
>   */
> @@ -3342,6 +3356,12 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>         ENABLE_IP_GSCL_SECURE_SMMU_GSCL2,
>  };
>
> +static const struct samsung_clk_reg_dump gscl_suspend_regs[] = {
> +       { MUX_SEL_GSCL, 0 },
> +       { ENABLE_ACLK_GSCL, 0xfff },
> +       { ENABLE_PCLK_GSCL, 0xff },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(aclk_gscl_111_user_p)    = { "oscclk", "aclk_gscl_111", };
>  PNAME(aclk_gscl_333_user_p)    = { "oscclk", "aclk_gscl_333", };
> @@ -3436,15 +3456,11 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>         .nr_clk_ids             = GSCL_NR_CLK,
>         .clk_regs               = gscl_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(gscl_clk_regs),
> +       .suspend_regs           = gscl_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(gscl_suspend_regs),
> +       .clk_name               = "aclk_gscl_111",
>  };
>
> -static void __init exynos5433_cmu_gscl_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &gscl_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_gscl, "samsung,exynos5433-cmu-gscl",
> -               exynos5433_cmu_gscl_init);
> -
>  /*
>   * Register offset definitions for CMU_APOLLO
>   */
> @@ -3970,6 +3986,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
>         ENABLE_IP_MSCL_SECURE_SMMU_JPEG,
>  };
>
> +static const struct samsung_clk_reg_dump mscl_suspend_regs[] = {
> +       { MUX_SEL_MSCL0, 0 },
> +       { MUX_SEL_MSCL1, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_sclk_jpeg_user_p)           = { "oscclk", "sclk_jpeg_mscl", };
>  PNAME(mout_aclk_mscl_400_user_p)       = { "oscclk", "aclk_mscl_400", };
> @@ -4082,15 +4103,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
>         .nr_clk_ids             = MSCL_NR_CLK,
>         .clk_regs               = mscl_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(mscl_clk_regs),
> +       .suspend_regs           = mscl_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(mscl_suspend_regs),
> +       .clk_name               = "aclk_mscl_400",
>  };
>
> -static void __init exynos5433_cmu_mscl_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &mscl_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_mscl, "samsung,exynos5433-cmu-mscl",
> -               exynos5433_cmu_mscl_init);
> -
>  /*
>   * Register offset definitions for CMU_MFC
>   */
> @@ -4120,6 +4137,10 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>         ENABLE_IP_MFC_SECURE_SMMU_MFC,
>  };
>
> +static const struct samsung_clk_reg_dump mfc_suspend_regs[] = {
> +       { MUX_SEL_MFC, 0 },
> +};
> +
>  PNAME(mout_aclk_mfc_400_user_p)                = { "oscclk", "aclk_mfc_400", };
>
>  static const struct samsung_mux_clock mfc_mux_clks[] __initconst = {
> @@ -4190,15 +4211,11 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>         .nr_clk_ids             = MFC_NR_CLK,
>         .clk_regs               = mfc_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(mfc_clk_regs),
> +       .suspend_regs           = mfc_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(mfc_suspend_regs),
> +       .clk_name               = "aclk_mfc_400",
>  };
>
> -static void __init exynos5433_cmu_mfc_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &mfc_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_mfc, "samsung,exynos5433-cmu-mfc",
> -               exynos5433_cmu_mfc_init);
> -
>  /*
>   * Register offset definitions for CMU_HEVC
>   */
> @@ -4228,6 +4245,10 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>         ENABLE_IP_HEVC_SECURE_SMMU_HEVC,
>  };
>
> +static const struct samsung_clk_reg_dump hevc_suspend_regs[] = {
> +       { MUX_SEL_HEVC, 0 },
> +};
> +
>  PNAME(mout_aclk_hevc_400_user_p)       = { "oscclk", "aclk_hevc_400", };
>
>  static const struct samsung_mux_clock hevc_mux_clks[] __initconst = {
> @@ -4300,15 +4321,11 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>         .nr_clk_ids             = HEVC_NR_CLK,
>         .clk_regs               = hevc_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(hevc_clk_regs),
> +       .suspend_regs           = hevc_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(hevc_suspend_regs),
> +       .clk_name               = "aclk_hevc_400",
>  };
>
> -static void __init exynos5433_cmu_hevc_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &hevc_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_hevc, "samsung,exynos5433-cmu-hevc",
> -               exynos5433_cmu_hevc_init);
> -
>  /*
>   * Register offset definitions for CMU_ISP
>   */
> @@ -4342,6 +4359,10 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>         ENABLE_IP_ISP3,
>  };
>
> +static const struct samsung_clk_reg_dump isp_suspend_regs[] = {
> +       { MUX_SEL_ISP, 0 },
> +};
> +
>  PNAME(mout_aclk_isp_dis_400_user_p)    = { "oscclk", "aclk_isp_dis_400", };
>  PNAME(mout_aclk_isp_400_user_p)                = { "oscclk", "aclk_isp_400", };
>
> @@ -4553,15 +4574,11 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>         .nr_clk_ids             = ISP_NR_CLK,
>         .clk_regs               = isp_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(isp_clk_regs),
> +       .suspend_regs           = isp_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(isp_suspend_regs),
> +       .clk_name               = "aclk_isp_400",
>  };
>
> -static void __init exynos5433_cmu_isp_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &isp_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_isp, "samsung,exynos5433-cmu-isp",
> -               exynos5433_cmu_isp_init);
> -
>  /*
>   * Register offset definitions for CMU_CAM0
>   */
> @@ -4625,6 +4642,15 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
>         ENABLE_IP_CAM02,
>         ENABLE_IP_CAM03,
>  };
> +
> +static const struct samsung_clk_reg_dump cam0_suspend_regs[] = {
> +       { MUX_SEL_CAM00, 0 },
> +       { MUX_SEL_CAM01, 0 },
> +       { MUX_SEL_CAM02, 0 },
> +       { MUX_SEL_CAM03, 0 },
> +       { MUX_SEL_CAM04, 0 },
> +};
> +
>  PNAME(mout_aclk_cam0_333_user_p)       = { "oscclk", "aclk_cam0_333", };
>  PNAME(mout_aclk_cam0_400_user_p)       = { "oscclk", "aclk_cam0_400", };
>  PNAME(mout_aclk_cam0_552_user_p)       = { "oscclk", "aclk_cam0_552", };
> @@ -5030,15 +5056,11 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
>         .nr_clk_ids             = CAM0_NR_CLK,
>         .clk_regs               = cam0_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(cam0_clk_regs),
> +       .suspend_regs           = cam0_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(cam0_suspend_regs),
> +       .clk_name               = "aclk_cam0_400",
>  };
>
> -static void __init exynos5433_cmu_cam0_init(struct device_node *np)
> -{
> -       samsung_cmu_register_one(np, &cam0_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_cam0, "samsung,exynos5433-cmu-cam0",
> -               exynos5433_cmu_cam0_init);
> -
>  /*
>   * Register offset definitions for CMU_CAM1
>   */
> @@ -5085,6 +5107,12 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>         ENABLE_IP_CAM12,
>  };
>
> +static const struct samsung_clk_reg_dump cam1_suspend_regs[] = {
> +       { MUX_SEL_CAM10, 0 },
> +       { MUX_SEL_CAM11, 0 },
> +       { MUX_SEL_CAM12, 0 },
> +};
> +
>  PNAME(mout_sclk_isp_uart_user_p)       = { "oscclk", "sclk_isp_uart_cam1", };
>  PNAME(mout_sclk_isp_spi1_user_p)       = { "oscclk", "sclk_isp_spi1_cam1", };
>  PNAME(mout_sclk_isp_spi0_user_p)       = { "oscclk", "sclk_isp_spi0_cam1", };
> @@ -5403,11 +5431,222 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>         .nr_clk_ids             = CAM1_NR_CLK,
>         .clk_regs               = cam1_clk_regs,
>         .nr_clk_regs            = ARRAY_SIZE(cam1_clk_regs),
> +       .suspend_regs           = cam1_suspend_regs,
> +       .nr_suspend_regs        = ARRAY_SIZE(cam1_suspend_regs),
> +       .clk_name               = "aclk_cam1_400",
> +};
> +
> +
> +struct exynos5433_cmu_data {
> +       struct samsung_clk_reg_dump *clk_save;
> +       unsigned int nr_clk_save;
> +       const struct samsung_clk_reg_dump *clk_suspend;
> +       unsigned int nr_clk_suspend;
> +
> +       struct clk *clk;
> +       struct clk **pclks;
> +       int nr_pclks;
> +
> +       /* must be the last entry */
> +       struct samsung_clk_provider ctx;
> +};
> +
> +static int exynos5433_cmu_suspend(struct device *dev)
> +{
> +       struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
> +       int i;
> +
> +       samsung_clk_save(data->ctx.reg_base, data->clk_save,
> +                        data->nr_clk_save);
> +
> +       for (i = 0; i < data->nr_pclks; i++)
> +               clk_prepare_enable(data->pclks[i]);
> +
> +       samsung_clk_restore(data->ctx.reg_base, data->clk_suspend,
> +                           data->nr_clk_suspend);
> +
> +       for (i = 0; i < data->nr_pclks; i++)
> +               clk_disable_unprepare(data->pclks[i]);
> +
> +       clk_disable_unprepare(data->clk);
> +
> +       return 0;
> +}
> +
> +static int exynos5433_cmu_resume(struct device *dev)
> +{
> +       struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
> +       int i;
> +
> +       clk_prepare_enable(data->clk);
> +
> +       for (i = 0; i < data->nr_pclks; i++)
> +               clk_prepare_enable(data->pclks[i]);
> +
> +       samsung_clk_restore(data->ctx.reg_base, data->clk_save,
> +                           data->nr_clk_save);
> +
> +       for (i = 0; i < data->nr_pclks; i++)
> +               clk_disable_unprepare(data->pclks[i]);
> +
> +       return 0;
> +}
> +
> +static int __init exynos5433_cmu_probe(struct platform_device *pdev)
> +{
> +       const struct samsung_cmu_info *info;
> +       struct exynos5433_cmu_data *data;
> +       struct samsung_clk_provider *ctx;
> +       struct device *dev = &pdev->dev;
> +       struct resource *res;
> +       void __iomem *reg_base;
> +       int i;
> +
> +       info = of_device_get_match_data(dev);
> +
> +       data = devm_kzalloc(dev, sizeof(*data) +
> +                           sizeof(*data->ctx.clk_data.hws) * info->nr_clk_ids,
> +                           GFP_KERNEL);
> +       if (!data)
> +               return -ENOMEM;
> +       ctx = &data->ctx;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       reg_base = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(reg_base)) {
> +               dev_err(dev, "failed to map registers\n");
> +               return PTR_ERR(reg_base);
> +       }
> +
> +       for (i = 0; i < info->nr_clk_ids; ++i)
> +               ctx->clk_data.hws[i] = ERR_PTR(-ENOENT);
> +
> +       ctx->clk_data.num = info->nr_clk_ids;
> +       ctx->reg_base = reg_base;
> +       ctx->dev = dev;
> +       spin_lock_init(&ctx->lock);
> +
> +       data->clk_save = samsung_clk_alloc_reg_dump(info->clk_regs,
> +                                                   info->nr_clk_regs);
> +       data->nr_clk_save = info->nr_clk_regs;
> +       data->clk_suspend = info->suspend_regs;
> +       data->nr_clk_suspend = info->nr_suspend_regs;
> +       data->nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
> +                                                   "#clock-cells");
> +       if (data->nr_pclks > 0) {
> +               data->pclks = devm_kcalloc(dev, sizeof(struct clk *),
> +                                          data->nr_pclks, GFP_KERNEL);
> +
> +               for (i = 0; i < data->nr_pclks; i++) {
> +                       struct clk *clk = of_clk_get(dev->of_node, i);
> +
> +                       if (IS_ERR(clk))
> +                               return PTR_ERR(clk);
> +                       data->pclks[i] = clk;
> +               }
> +       }
> +
> +       if (info->clk_name)
> +               data->clk = clk_get(dev, info->clk_name);
> +       clk_prepare_enable(data->clk);
> +
> +       platform_set_drvdata(pdev, data);
> +
> +       /*
> +        * Enable runtime PM here to allow the clock core using runtime PM
> +        * for the registered clocks. Additionally, we increase the runtime
> +        * PM usage count before registering the clocks, to prevent the
> +        * clock core from runtime suspending the device.
> +        */
> +       pm_runtime_get_noresume(dev);
> +       pm_runtime_set_active(dev);
> +       pm_runtime_enable(dev);
> +
> +       if (info->pll_clks)
> +               samsung_clk_register_pll(ctx, info->pll_clks, info->nr_pll_clks,
> +                                        reg_base);
> +       if (info->mux_clks)
> +               samsung_clk_register_mux(ctx, info->mux_clks,
> +                                        info->nr_mux_clks);
> +       if (info->div_clks)
> +               samsung_clk_register_div(ctx, info->div_clks,
> +                                        info->nr_div_clks);
> +       if (info->gate_clks)
> +               samsung_clk_register_gate(ctx, info->gate_clks,
> +                                         info->nr_gate_clks);
> +       if (info->fixed_clks)
> +               samsung_clk_register_fixed_rate(ctx, info->fixed_clks,
> +                                               info->nr_fixed_clks);
> +       if (info->fixed_factor_clks)
> +               samsung_clk_register_fixed_factor(ctx, info->fixed_factor_clks,
> +                                                 info->nr_fixed_factor_clks);
> +
> +       samsung_clk_of_add_provider(dev->of_node, ctx);
> +       pm_runtime_put_sync(dev);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id exynos5433_cmu_of_match[] = {
> +       {
> +               .compatible = "samsung,exynos5433-cmu-aud",
> +               .data = &aud_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-cam0",
> +               .data = &cam0_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-cam1",
> +               .data = &cam1_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-disp",
> +               .data = &disp_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-g2d",
> +               .data = &g2d_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-g3d",
> +               .data = &g3d_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-fsys",
> +               .data = &fsys_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-gscl",
> +               .data = &gscl_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-mfc",
> +               .data = &mfc_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-hevc",
> +               .data = &hevc_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-isp",
> +               .data = &isp_cmu_info,
> +       }, {
> +               .compatible = "samsung,exynos5433-cmu-mscl",
> +               .data = &mscl_cmu_info,
> +       }, {
> +       },
> +};
> +
> +static const struct dev_pm_ops exynos5433_cmu_pm_ops = {
> +       SET_RUNTIME_PM_OPS(exynos5433_cmu_suspend, exynos5433_cmu_resume,
> +                          NULL)
> +       SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +                                    pm_runtime_force_resume)
> +};
> +
> +static struct platform_driver exynos5433_cmu_driver __refdata = {
> +       .driver = {
> +               .name = "exynos5433-cmu",
> +               .of_match_table = exynos5433_cmu_of_match,
> +               .suppress_bind_attrs = true,
> +               .pm = &exynos5433_cmu_pm_ops,
> +       },
> +       .probe = exynos5433_cmu_probe,
>  };
>
> -static void __init exynos5433_cmu_cam1_init(struct device_node *np)
> +static int __init exynos5433_cmu_init(void)
>  {
> -       samsung_cmu_register_one(np, &cam1_cmu_info);
> +       return platform_driver_register(&exynos5433_cmu_driver);
>  }
> -CLK_OF_DECLARE(exynos5433_cmu_cam1, "samsung,exynos5433-cmu-cam1",
> -               exynos5433_cmu_cam1_init);
> +core_initcall(exynos5433_cmu_init);
> diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
> index f0acae4f5d1b..d93031e94387 100644
> --- a/drivers/clk/samsung/clk.h
> +++ b/drivers/clk/samsung/clk.h
> @@ -353,6 +353,12 @@ struct samsung_cmu_info {
>         /* list and number of clocks registers */
>         const unsigned long *clk_regs;
>         unsigned int nr_clk_regs;
> +
> +       /* list and number of clocks registers to set before suspend */
> +       const struct samsung_clk_reg_dump *suspend_regs;
> +       unsigned int nr_suspend_regs;
> +       /* name of the parent clock needed for CMU register access */
> +       const char *clk_name;
>  };
>
>  extern struct samsung_clk_provider *__init samsung_clk_init(
> --
> 1.9.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Krzysztof Kozlowski Aug. 8, 2017, 4:11 p.m. UTC | #2
On Fri, Aug 04, 2017 at 11:34:53AM +0200, Marek Szyprowski wrote:
> Add runtime pm support for all clock controller units (CMU), which belongs

s/belongs/belong/

> to power domains and require special handling during on/off operations.
> Typically special values has to be written to MUX registers to change
> internal clocks parents to OSC clock before turning power off. During such
> operation all clocks, which enters CMU has to be enabled to let MUX to

s/enters/enter/

Beside that, how about unifying the title with previous mail ("Add
support for runtime PM")?

> stabilize. Also for each CMU there is one special parent clock, which has
> to be enabled all the time when any access to CMU registers is done.
> 
> This patch solves most of the mysterious external abort and freeze issues
> caused by a lack of proper parent CMU clock enabled or incorrect turn off
> procedure.
> 
> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
> ---
>  .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
>  drivers/clk/samsung/clk-exynos5433.c               | 409 ++++++++++++++++-----
>  drivers/clk/samsung/clk.h                          |   6 +
>  3 files changed, 346 insertions(+), 85 deletions(-)
> 

Looks okay for me, few minor nits below and:
Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>


> diff --git a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
> index 1dc80f8811fe..5c7dd12e667a 100644
> --- a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
> +++ b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
> @@ -168,6 +168,11 @@ Required Properties:
>  		- aclk_cam1_400
>  		- aclk_cam1_552
>  
> +Optional properties:
> +  - power-domains: a phandle to respective power domain node as described by
> +	generic PM domain bindings (see power/power_domain.txt for more
> +	information).
> +
>  Each clock is assigned an identifier and client nodes can use this identifier
>  to specify the clock which they consume.
>  
> @@ -270,6 +275,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		clocks = <&xxti>,
>  		       <&cmu_top CLK_ACLK_G2D_266>,
>  		       <&cmu_top CLK_ACLK_G2D_400>;
> +		power-domains = <&pd_g2d>;
>  	};
>  
>  	cmu_disp: clock-controller@13b90000 {
> @@ -295,6 +301,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		       <&cmu_mif CLK_SCLK_DECON_ECLK_DISP>,
>  		       <&cmu_mif CLK_SCLK_DECON_TV_VCLK_DISP>,
>  		       <&cmu_mif CLK_ACLK_DISP_333>;
> +		power-domains = <&pd_disp>;
>  	};
>  
>  	cmu_aud: clock-controller@114c0000 {
> @@ -304,6 +311,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  
>  		clock-names = "oscclk", "fout_aud_pll";
>  		clocks = <&xxti>, <&cmu_top CLK_FOUT_AUD_PLL>;
> +		power-domains = <&pd_aud>;
>  	};
>  
>  	cmu_bus0: clock-controller@13600000 {
> @@ -340,6 +348,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  
>  		clock-names = "oscclk", "aclk_g3d_400";
>  		clocks = <&xxti>, <&cmu_top CLK_ACLK_G3D_400>;
> +		power-domains = <&pd_g3d>;
>  	};
>  
>  	cmu_gscl: clock-controller@13cf0000 {
> @@ -353,6 +362,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		clocks = <&xxti>,
>  			<&cmu_top CLK_ACLK_GSCL_111>,
>  			<&cmu_top CLK_ACLK_GSCL_333>;
> +		power-domains = <&pd_gscl>;
>  	};
>  
>  	cmu_apollo: clock-controller@11900000 {
> @@ -384,6 +394,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		clocks = <&xxti>,
>  		       <&cmu_top CLK_SCLK_JPEG_MSCL>,
>  		       <&cmu_top CLK_ACLK_MSCL_400>;
> +		power-domains = <&pd_mscl>;
>  	};
>  
>  	cmu_mfc: clock-controller@15280000 {
> @@ -393,6 +404,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  
>  		clock-names = "oscclk", "aclk_mfc_400";
>  		clocks = <&xxti>, <&cmu_top CLK_ACLK_MFC_400>;
> +		power-domains = <&pd_mfc>;
>  	};
>  
>  	cmu_hevc: clock-controller@14f80000 {
> @@ -402,6 +414,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  
>  		clock-names = "oscclk", "aclk_hevc_400";
>  		clocks = <&xxti>, <&cmu_top CLK_ACLK_HEVC_400>;
> +		power-domains = <&pd_hevc>;
>  	};
>  
>  	cmu_isp: clock-controller@146d0000 {
> @@ -415,6 +428,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		clocks = <&xxti>,
>  		       <&cmu_top CLK_ACLK_ISP_DIS_400>,
>  		       <&cmu_top CLK_ACLK_ISP_400>;
> +		power-domains = <&pd_isp>;
>  	};
>  
>  	cmu_cam0: clock-controller@120d0000 {
> @@ -430,6 +444,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		       <&cmu_top CLK_ACLK_CAM0_333>,
>  		       <&cmu_top CLK_ACLK_CAM0_400>,
>  		       <&cmu_top CLK_ACLK_CAM0_552>;
> +		power-domains = <&pd_cam0>;
>  	};
>  
>  	cmu_cam1: clock-controller@145d0000 {
> @@ -451,6 +466,7 @@ Example 2: Examples of clock controller nodes are listed below.
>  		       <&cmu_top CLK_ACLK_CAM1_333>,
>  		       <&cmu_top CLK_ACLK_CAM1_400>,
>  		       <&cmu_top CLK_ACLK_CAM1_552>;
> +		power-domains = <&pd_cam1>;
>  	};
>  
>  Example 3: UART controller node that consumes the clock generated by the clock
> diff --git a/drivers/clk/samsung/clk-exynos5433.c b/drivers/clk/samsung/clk-exynos5433.c
> index 11343a597093..7ae9bccb1192 100644
> --- a/drivers/clk/samsung/clk-exynos5433.c
> +++ b/drivers/clk/samsung/clk-exynos5433.c
> @@ -9,9 +9,14 @@
>   * Common Clock Framework support for Exynos5433 SoC.
>   */
>  
> +#include <linux/clk.h>
>  #include <linux/clk-provider.h>
>  #include <linux/of.h>
>  #include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>

Why you need slab.h?

>  
>  #include <dt-bindings/clock/exynos5433.h>
>  
> @@ -1991,6 +1996,14 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
>  	ENABLE_IP_FSYS1,
>  };
>  
> +static const struct samsung_clk_reg_dump fsys_suspend_regs[] = {
> +	{ MUX_SEL_FSYS0, 0 },
> +	{ MUX_SEL_FSYS1, 0 },
> +	{ MUX_SEL_FSYS2, 0 },
> +	{ MUX_SEL_FSYS3, 0 },
> +	{ MUX_SEL_FSYS4, 0 },
> +};
> +
>  static const struct samsung_fixed_rate_clock fsys_fixed_clks[] __initconst = {
>  	/* PHY clocks from USBDRD30_PHY */
>  	FRATE(CLK_PHYCLK_USBDRD30_UDRD30_PHYCLOCK_PHY,
> @@ -2296,16 +2309,11 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
>  	.nr_clk_ids		= FSYS_NR_CLK,
>  	.clk_regs		= fsys_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(fsys_clk_regs),
> +	.suspend_regs		= fsys_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(fsys_suspend_regs),
> +	.clk_name		= "aclk_fsys_200",
>  };
>  
> -static void __init exynos5433_cmu_fsys_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &fsys_cmu_info);
> -}
> -
> -CLK_OF_DECLARE(exynos5433_cmu_fsys, "samsung,exynos5433-cmu-fsys",
> -		exynos5433_cmu_fsys_init);
> -
>  /*
>   * Register offset definitions for CMU_G2D
>   */
> @@ -2335,6 +2343,10 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>  	DIV_ENABLE_IP_G2D_SECURE_SMMU_G2D,
>  };
>  
> +static const struct samsung_clk_reg_dump g2d_suspend_regs[] = {
> +	{ MUX_SEL_G2D0, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_aclk_g2d_266_user_p)		= { "oscclk", "aclk_g2d_266", };
>  PNAME(mout_aclk_g2d_400_user_p)		= { "oscclk", "aclk_g2d_400", };
> @@ -2420,16 +2432,11 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>  	.nr_clk_ids		= G2D_NR_CLK,
>  	.clk_regs		= g2d_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(g2d_clk_regs),
> +	.suspend_regs		= g2d_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(g2d_suspend_regs),
> +	.clk_name		= "aclk_g2d_400",
>  };
>  
> -static void __init exynos5433_cmu_g2d_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &g2d_cmu_info);
> -}
> -
> -CLK_OF_DECLARE(exynos5433_cmu_g2d, "samsung,exynos5433-cmu-g2d",
> -		exynos5433_cmu_g2d_init);
> -
>  /*
>   * Register offset definitions for CMU_DISP
>   */
> @@ -2494,6 +2501,18 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>  	CLKOUT_CMU_DISP_DIV_STAT,
>  };
>  
> +static const struct samsung_clk_reg_dump disp_suspend_regs[] = {
> +	/* PLL has to be enabled for suspend */
> +	{ DISP_PLL_CON0, 0x85f40502 },
> +	/* ignore status of external PHY muxes during suspend to avoid hangs */
> +	{ MUX_IGNORE_DISP2, 0x00111111 },
> +	{ MUX_SEL_DISP0, 0 },
> +	{ MUX_SEL_DISP1, 0 },
> +	{ MUX_SEL_DISP2, 0 },
> +	{ MUX_SEL_DISP3, 0 },
> +	{ MUX_SEL_DISP4, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_disp_pll_p)			= { "oscclk", "fout_disp_pll", };
>  PNAME(mout_sclk_dsim1_user_p)		= { "oscclk", "sclk_dsim1_disp", };
> @@ -2841,16 +2860,11 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>  	.nr_clk_ids		= DISP_NR_CLK,
>  	.clk_regs		= disp_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(disp_clk_regs),
> +	.suspend_regs		= disp_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(disp_suspend_regs),
> +	.clk_name		= "aclk_disp_333",
>  };
>  
> -static void __init exynos5433_cmu_disp_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &disp_cmu_info);
> -}
> -
> -CLK_OF_DECLARE(exynos5433_cmu_disp, "samsung,exynos5433-cmu-disp",
> -		exynos5433_cmu_disp_init);
> -
>  /*
>   * Register offset definitions for CMU_AUD
>   */
> @@ -2885,6 +2899,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
>  	ENABLE_IP_AUD1,
>  };
>  
> +static const struct samsung_clk_reg_dump aud_suspend_regs[] = {
> +	{ MUX_SEL_AUD0, 0 },
> +	{ MUX_SEL_AUD1, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_aud_pll_user_aud_p)	= { "oscclk", "fout_aud_pll", };
>  PNAME(mout_sclk_aud_pcm_p)	= { "mout_aud_pll_user", "ioclk_audiocdclk0",};
> @@ -3011,16 +3030,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
>  	.nr_clk_ids		= AUD_NR_CLK,
>  	.clk_regs		= aud_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(aud_clk_regs),
> +	.suspend_regs		= aud_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(aud_suspend_regs),
> +	.clk_name		= "fout_aud_pll",
>  };
>  
> -static void __init exynos5433_cmu_aud_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &aud_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_aud, "samsung,exynos5433-cmu-aud",
> -		exynos5433_cmu_aud_init);
> -
> -
>  /*
>   * Register offset definitions for CMU_BUS{0|1|2}
>   */
> @@ -3222,6 +3236,10 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
>  	CLK_STOPCTRL,
>  };
>  
> +static const struct samsung_clk_reg_dump g3d_suspend_regs[] = {
> +	{ MUX_SEL_G3D, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_aclk_g3d_400_p)	= { "mout_g3d_pll", "aclk_g3d_400", };
>  PNAME(mout_g3d_pll_p)		= { "oscclk", "fout_g3d_pll", };
> @@ -3295,15 +3313,11 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
>  	.nr_clk_ids		= G3D_NR_CLK,
>  	.clk_regs		= g3d_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(g3d_clk_regs),
> +	.suspend_regs		= g3d_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(g3d_suspend_regs),
> +	.clk_name		= "aclk_g3d_400",
>  };
>  
> -static void __init exynos5433_cmu_g3d_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &g3d_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_g3d, "samsung,exynos5433-cmu-g3d",
> -		exynos5433_cmu_g3d_init);
> -
>  /*
>   * Register offset definitions for CMU_GSCL
>   */
> @@ -3342,6 +3356,12 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>  	ENABLE_IP_GSCL_SECURE_SMMU_GSCL2,
>  };
>  
> +static const struct samsung_clk_reg_dump gscl_suspend_regs[] = {
> +	{ MUX_SEL_GSCL, 0 },
> +	{ ENABLE_ACLK_GSCL, 0xfff },
> +	{ ENABLE_PCLK_GSCL, 0xff },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(aclk_gscl_111_user_p)	= { "oscclk", "aclk_gscl_111", };
>  PNAME(aclk_gscl_333_user_p)	= { "oscclk", "aclk_gscl_333", };
> @@ -3436,15 +3456,11 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>  	.nr_clk_ids		= GSCL_NR_CLK,
>  	.clk_regs		= gscl_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(gscl_clk_regs),
> +	.suspend_regs		= gscl_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(gscl_suspend_regs),
> +	.clk_name		= "aclk_gscl_111",
>  };
>  
> -static void __init exynos5433_cmu_gscl_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &gscl_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_gscl, "samsung,exynos5433-cmu-gscl",
> -		exynos5433_cmu_gscl_init);
> -
>  /*
>   * Register offset definitions for CMU_APOLLO
>   */
> @@ -3970,6 +3986,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
>  	ENABLE_IP_MSCL_SECURE_SMMU_JPEG,
>  };
>  
> +static const struct samsung_clk_reg_dump mscl_suspend_regs[] = {
> +	{ MUX_SEL_MSCL0, 0 },
> +	{ MUX_SEL_MSCL1, 0 },
> +};
> +
>  /* list of all parent clock list */
>  PNAME(mout_sclk_jpeg_user_p)		= { "oscclk", "sclk_jpeg_mscl", };
>  PNAME(mout_aclk_mscl_400_user_p)	= { "oscclk", "aclk_mscl_400", };
> @@ -4082,15 +4103,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
>  	.nr_clk_ids		= MSCL_NR_CLK,
>  	.clk_regs		= mscl_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(mscl_clk_regs),
> +	.suspend_regs		= mscl_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(mscl_suspend_regs),
> +	.clk_name		= "aclk_mscl_400",
>  };
>  
> -static void __init exynos5433_cmu_mscl_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &mscl_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_mscl, "samsung,exynos5433-cmu-mscl",
> -		exynos5433_cmu_mscl_init);
> -
>  /*
>   * Register offset definitions for CMU_MFC
>   */
> @@ -4120,6 +4137,10 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>  	ENABLE_IP_MFC_SECURE_SMMU_MFC,
>  };
>  
> +static const struct samsung_clk_reg_dump mfc_suspend_regs[] = {
> +	{ MUX_SEL_MFC, 0 },
> +};
> +
>  PNAME(mout_aclk_mfc_400_user_p)		= { "oscclk", "aclk_mfc_400", };
>  
>  static const struct samsung_mux_clock mfc_mux_clks[] __initconst = {
> @@ -4190,15 +4211,11 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>  	.nr_clk_ids		= MFC_NR_CLK,
>  	.clk_regs		= mfc_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(mfc_clk_regs),
> +	.suspend_regs		= mfc_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(mfc_suspend_regs),
> +	.clk_name		= "aclk_mfc_400",
>  };
>  
> -static void __init exynos5433_cmu_mfc_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &mfc_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_mfc, "samsung,exynos5433-cmu-mfc",
> -		exynos5433_cmu_mfc_init);
> -
>  /*
>   * Register offset definitions for CMU_HEVC
>   */
> @@ -4228,6 +4245,10 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>  	ENABLE_IP_HEVC_SECURE_SMMU_HEVC,
>  };
>  
> +static const struct samsung_clk_reg_dump hevc_suspend_regs[] = {
> +	{ MUX_SEL_HEVC, 0 },
> +};
> +
>  PNAME(mout_aclk_hevc_400_user_p)	= { "oscclk", "aclk_hevc_400", };
>  
>  static const struct samsung_mux_clock hevc_mux_clks[] __initconst = {
> @@ -4300,15 +4321,11 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>  	.nr_clk_ids		= HEVC_NR_CLK,
>  	.clk_regs		= hevc_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(hevc_clk_regs),
> +	.suspend_regs		= hevc_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(hevc_suspend_regs),
> +	.clk_name		= "aclk_hevc_400",
>  };
>  
> -static void __init exynos5433_cmu_hevc_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &hevc_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_hevc, "samsung,exynos5433-cmu-hevc",
> -		exynos5433_cmu_hevc_init);
> -
>  /*
>   * Register offset definitions for CMU_ISP
>   */
> @@ -4342,6 +4359,10 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>  	ENABLE_IP_ISP3,
>  };
>  
> +static const struct samsung_clk_reg_dump isp_suspend_regs[] = {
> +	{ MUX_SEL_ISP, 0 },
> +};
> +
>  PNAME(mout_aclk_isp_dis_400_user_p)	= { "oscclk", "aclk_isp_dis_400", };
>  PNAME(mout_aclk_isp_400_user_p)		= { "oscclk", "aclk_isp_400", };
>  
> @@ -4553,15 +4574,11 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>  	.nr_clk_ids		= ISP_NR_CLK,
>  	.clk_regs		= isp_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(isp_clk_regs),
> +	.suspend_regs		= isp_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(isp_suspend_regs),
> +	.clk_name		= "aclk_isp_400",
>  };
>  
> -static void __init exynos5433_cmu_isp_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &isp_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_isp, "samsung,exynos5433-cmu-isp",
> -		exynos5433_cmu_isp_init);
> -
>  /*
>   * Register offset definitions for CMU_CAM0
>   */
> @@ -4625,6 +4642,15 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
>  	ENABLE_IP_CAM02,
>  	ENABLE_IP_CAM03,
>  };
> +
> +static const struct samsung_clk_reg_dump cam0_suspend_regs[] = {
> +	{ MUX_SEL_CAM00, 0 },
> +	{ MUX_SEL_CAM01, 0 },
> +	{ MUX_SEL_CAM02, 0 },
> +	{ MUX_SEL_CAM03, 0 },
> +	{ MUX_SEL_CAM04, 0 },
> +};
> +
>  PNAME(mout_aclk_cam0_333_user_p)	= { "oscclk", "aclk_cam0_333", };
>  PNAME(mout_aclk_cam0_400_user_p)	= { "oscclk", "aclk_cam0_400", };
>  PNAME(mout_aclk_cam0_552_user_p)	= { "oscclk", "aclk_cam0_552", };
> @@ -5030,15 +5056,11 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
>  	.nr_clk_ids		= CAM0_NR_CLK,
>  	.clk_regs		= cam0_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(cam0_clk_regs),
> +	.suspend_regs		= cam0_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(cam0_suspend_regs),
> +	.clk_name		= "aclk_cam0_400",
>  };
>  
> -static void __init exynos5433_cmu_cam0_init(struct device_node *np)
> -{
> -	samsung_cmu_register_one(np, &cam0_cmu_info);
> -}
> -CLK_OF_DECLARE(exynos5433_cmu_cam0, "samsung,exynos5433-cmu-cam0",
> -		exynos5433_cmu_cam0_init);
> -
>  /*
>   * Register offset definitions for CMU_CAM1
>   */
> @@ -5085,6 +5107,12 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>  	ENABLE_IP_CAM12,
>  };
>  
> +static const struct samsung_clk_reg_dump cam1_suspend_regs[] = {
> +	{ MUX_SEL_CAM10, 0 },
> +	{ MUX_SEL_CAM11, 0 },
> +	{ MUX_SEL_CAM12, 0 },
> +};
> +
>  PNAME(mout_sclk_isp_uart_user_p)	= { "oscclk", "sclk_isp_uart_cam1", };
>  PNAME(mout_sclk_isp_spi1_user_p)	= { "oscclk", "sclk_isp_spi1_cam1", };
>  PNAME(mout_sclk_isp_spi0_user_p)	= { "oscclk", "sclk_isp_spi0_cam1", };
> @@ -5403,11 +5431,222 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>  	.nr_clk_ids		= CAM1_NR_CLK,
>  	.clk_regs		= cam1_clk_regs,
>  	.nr_clk_regs		= ARRAY_SIZE(cam1_clk_regs),
> +	.suspend_regs		= cam1_suspend_regs,
> +	.nr_suspend_regs	= ARRAY_SIZE(cam1_suspend_regs),
> +	.clk_name		= "aclk_cam1_400",
> +};
> +
> +
> +struct exynos5433_cmu_data {
> +	struct samsung_clk_reg_dump *clk_save;
> +	unsigned int nr_clk_save;
> +	const struct samsung_clk_reg_dump *clk_suspend;
> +	unsigned int nr_clk_suspend;
> +
> +	struct clk *clk;
> +	struct clk **pclks;
> +	int nr_pclks;
> +
> +	/* must be the last entry */
> +	struct samsung_clk_provider ctx;
> +};
> +
> +static int exynos5433_cmu_suspend(struct device *dev)
> +{
> +	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
> +	int i;
> +
> +	samsung_clk_save(data->ctx.reg_base, data->clk_save,
> +			 data->nr_clk_save);
> +
> +	for (i = 0; i < data->nr_pclks; i++)
> +		clk_prepare_enable(data->pclks[i]);
> +
> +	samsung_clk_restore(data->ctx.reg_base, data->clk_suspend,
> +			    data->nr_clk_suspend);

This looks surprising and requires one to read the commit message to get
the idea. How about adding a comment why this is not usual save
operation?

> +
> +	for (i = 0; i < data->nr_pclks; i++)
> +		clk_disable_unprepare(data->pclks[i]);
> +
> +	clk_disable_unprepare(data->clk);
> +
> +	return 0;
> +}
> +
> +static int exynos5433_cmu_resume(struct device *dev)
> +{
> +	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
> +	int i;
> +
> +	clk_prepare_enable(data->clk);
> +
> +	for (i = 0; i < data->nr_pclks; i++)
> +		clk_prepare_enable(data->pclks[i]);
> +
> +	samsung_clk_restore(data->ctx.reg_base, data->clk_save,
> +			    data->nr_clk_save);
> +
> +	for (i = 0; i < data->nr_pclks; i++)
> +		clk_disable_unprepare(data->pclks[i]);
> +
> +	return 0;
> +}
> +
> +static int __init exynos5433_cmu_probe(struct platform_device *pdev)
> +{
> +	const struct samsung_cmu_info *info;
> +	struct exynos5433_cmu_data *data;
> +	struct samsung_clk_provider *ctx;
> +	struct device *dev = &pdev->dev;
> +	struct resource *res;
> +	void __iomem *reg_base;
> +	int i;
> +
> +	info = of_device_get_match_data(dev);
> +
> +	data = devm_kzalloc(dev, sizeof(*data) +
> +			    sizeof(*data->ctx.clk_data.hws) * info->nr_clk_ids,
> +			    GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +	ctx = &data->ctx;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	reg_base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(reg_base)) {
> +		dev_err(dev, "failed to map registers\n");
> +		return PTR_ERR(reg_base);
> +	}
> +
> +	for (i = 0; i < info->nr_clk_ids; ++i)
> +		ctx->clk_data.hws[i] = ERR_PTR(-ENOENT);
> +
> +	ctx->clk_data.num = info->nr_clk_ids;
> +	ctx->reg_base = reg_base;
> +	ctx->dev = dev;
> +	spin_lock_init(&ctx->lock);
> +
> +	data->clk_save = samsung_clk_alloc_reg_dump(info->clk_regs,
> +						    info->nr_clk_regs);
> +	data->nr_clk_save = info->nr_clk_regs;
> +	data->clk_suspend = info->suspend_regs;
> +	data->nr_clk_suspend = info->nr_suspend_regs;
> +	data->nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
> +						    "#clock-cells");
> +	if (data->nr_pclks > 0) {
> +		data->pclks = devm_kcalloc(dev, sizeof(struct clk *),
> +					   data->nr_pclks, GFP_KERNEL);
> +
> +		for (i = 0; i < data->nr_pclks; i++) {
> +			struct clk *clk = of_clk_get(dev->of_node, i);
> +
> +			if (IS_ERR(clk))
> +				return PTR_ERR(clk);
> +			data->pclks[i] = clk;
> +		}
> +	}
> +
> +	if (info->clk_name)
> +		data->clk = clk_get(dev, info->clk_name);
> +	clk_prepare_enable(data->clk);
> +
> +	platform_set_drvdata(pdev, data);
> +
> +	/*
> +	 * Enable runtime PM here to allow the clock core using runtime PM
> +	 * for the registered clocks. Additionally, we increase the runtime
> +	 * PM usage count before registering the clocks, to prevent the
> +	 * clock core from runtime suspending the device.
> +	 */
> +	pm_runtime_get_noresume(dev);
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +
> +	if (info->pll_clks)
> +		samsung_clk_register_pll(ctx, info->pll_clks, info->nr_pll_clks,
> +					 reg_base);
> +	if (info->mux_clks)
> +		samsung_clk_register_mux(ctx, info->mux_clks,
> +					 info->nr_mux_clks);
> +	if (info->div_clks)
> +		samsung_clk_register_div(ctx, info->div_clks,
> +					 info->nr_div_clks);
> +	if (info->gate_clks)
> +		samsung_clk_register_gate(ctx, info->gate_clks,
> +					  info->nr_gate_clks);
> +	if (info->fixed_clks)
> +		samsung_clk_register_fixed_rate(ctx, info->fixed_clks,
> +						info->nr_fixed_clks);
> +	if (info->fixed_factor_clks)
> +		samsung_clk_register_fixed_factor(ctx, info->fixed_factor_clks,
> +						  info->nr_fixed_factor_clks);
> +
> +	samsung_clk_of_add_provider(dev->of_node, ctx);
> +	pm_runtime_put_sync(dev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id exynos5433_cmu_of_match[] = {

Since this is used only when matching devices maybe it can be also
marked as refdata?


Best regards,
Krzysztof

> +	{
> +		.compatible = "samsung,exynos5433-cmu-aud",
> +		.data = &aud_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-cam0",
> +		.data = &cam0_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-cam1",
> +		.data = &cam1_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-disp",
> +		.data = &disp_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-g2d",
> +		.data = &g2d_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-g3d",
> +		.data = &g3d_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-fsys",
> +		.data = &fsys_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-gscl",
> +		.data = &gscl_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-mfc",
> +		.data = &mfc_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-hevc",
> +		.data = &hevc_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-isp",
> +		.data = &isp_cmu_info,
> +	}, {
> +		.compatible = "samsung,exynos5433-cmu-mscl",
> +		.data = &mscl_cmu_info,
> +	}, {
> +	},
> +};
> +
> +static const struct dev_pm_ops exynos5433_cmu_pm_ops = {
> +	SET_RUNTIME_PM_OPS(exynos5433_cmu_suspend, exynos5433_cmu_resume,
> +			   NULL)
> +	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				     pm_runtime_force_resume)
> +};
> +
> +static struct platform_driver exynos5433_cmu_driver __refdata = {
> +	.driver	= {
> +		.name = "exynos5433-cmu",
> +		.of_match_table = exynos5433_cmu_of_match,
> +		.suppress_bind_attrs = true,
> +		.pm = &exynos5433_cmu_pm_ops,
> +	},
> +	.probe = exynos5433_cmu_probe,
>  };
>  
> -static void __init exynos5433_cmu_cam1_init(struct device_node *np)
> +static int __init exynos5433_cmu_init(void)
>  {
> -	samsung_cmu_register_one(np, &cam1_cmu_info);
> +	return platform_driver_register(&exynos5433_cmu_driver);
>  }
> -CLK_OF_DECLARE(exynos5433_cmu_cam1, "samsung,exynos5433-cmu-cam1",
> -		exynos5433_cmu_cam1_init);
> +core_initcall(exynos5433_cmu_init);
> diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
> index f0acae4f5d1b..d93031e94387 100644
> --- a/drivers/clk/samsung/clk.h
> +++ b/drivers/clk/samsung/clk.h
> @@ -353,6 +353,12 @@ struct samsung_cmu_info {
>  	/* list and number of clocks registers */
>  	const unsigned long *clk_regs;
>  	unsigned int nr_clk_regs;
> +
> +	/* list and number of clocks registers to set before suspend */
> +	const struct samsung_clk_reg_dump *suspend_regs;
> +	unsigned int nr_suspend_regs;
> +	/* name of the parent clock needed for CMU register access */
> +	const char *clk_name;
>  };
>  
>  extern struct samsung_clk_provider *__init samsung_clk_init(
> -- 
> 1.9.1
>
Marek Szyprowski Aug. 9, 2017, 8:19 a.m. UTC | #3
Hi Krzysztof,

On 2017-08-08 18:11, Krzysztof Kozlowski wrote:
> On Fri, Aug 04, 2017 at 11:34:53AM +0200, Marek Szyprowski wrote:
>> Add runtime pm support for all clock controller units (CMU), which belongs
> s/belongs/belong/
>
>> to power domains and require special handling during on/off operations.
>> Typically special values has to be written to MUX registers to change
>> internal clocks parents to OSC clock before turning power off. During such
>> operation all clocks, which enters CMU has to be enabled to let MUX to
> s/enters/enter/
>
> Beside that, how about unifying the title with previous mail ("Add
> support for runtime PM")?
>
>> stabilize. Also for each CMU there is one special parent clock, which has
>> to be enabled all the time when any access to CMU registers is done.
>>
>> This patch solves most of the mysterious external abort and freeze issues
>> caused by a lack of proper parent CMU clock enabled or incorrect turn off
>> procedure.
>>
>> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
>> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
>> ---
>>   .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
>>   drivers/clk/samsung/clk-exynos5433.c               | 409 ++++++++++++++++-----
>>   drivers/clk/samsung/clk.h                          |   6 +
>>   3 files changed, 346 insertions(+), 85 deletions(-)
>>
> Looks okay for me, few minor nits below and:
> Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
>
>
>> diff --git a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>> index 1dc80f8811fe..5c7dd12e667a 100644
>> --- a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>> +++ b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>> @@ -168,6 +168,11 @@ Required Properties:
>>   		- aclk_cam1_400
>>   		- aclk_cam1_552
>>   
>> +Optional properties:
>> +  - power-domains: a phandle to respective power domain node as described by
>> +	generic PM domain bindings (see power/power_domain.txt for more
>> +	information).
>> +
>>   Each clock is assigned an identifier and client nodes can use this identifier
>>   to specify the clock which they consume.
>>   
>> @@ -270,6 +275,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		clocks = <&xxti>,
>>   		       <&cmu_top CLK_ACLK_G2D_266>,
>>   		       <&cmu_top CLK_ACLK_G2D_400>;
>> +		power-domains = <&pd_g2d>;
>>   	};
>>   
>>   	cmu_disp: clock-controller@13b90000 {
>> @@ -295,6 +301,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		       <&cmu_mif CLK_SCLK_DECON_ECLK_DISP>,
>>   		       <&cmu_mif CLK_SCLK_DECON_TV_VCLK_DISP>,
>>   		       <&cmu_mif CLK_ACLK_DISP_333>;
>> +		power-domains = <&pd_disp>;
>>   	};
>>   
>>   	cmu_aud: clock-controller@114c0000 {
>> @@ -304,6 +311,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   
>>   		clock-names = "oscclk", "fout_aud_pll";
>>   		clocks = <&xxti>, <&cmu_top CLK_FOUT_AUD_PLL>;
>> +		power-domains = <&pd_aud>;
>>   	};
>>   
>>   	cmu_bus0: clock-controller@13600000 {
>> @@ -340,6 +348,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   
>>   		clock-names = "oscclk", "aclk_g3d_400";
>>   		clocks = <&xxti>, <&cmu_top CLK_ACLK_G3D_400>;
>> +		power-domains = <&pd_g3d>;
>>   	};
>>   
>>   	cmu_gscl: clock-controller@13cf0000 {
>> @@ -353,6 +362,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		clocks = <&xxti>,
>>   			<&cmu_top CLK_ACLK_GSCL_111>,
>>   			<&cmu_top CLK_ACLK_GSCL_333>;
>> +		power-domains = <&pd_gscl>;
>>   	};
>>   
>>   	cmu_apollo: clock-controller@11900000 {
>> @@ -384,6 +394,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		clocks = <&xxti>,
>>   		       <&cmu_top CLK_SCLK_JPEG_MSCL>,
>>   		       <&cmu_top CLK_ACLK_MSCL_400>;
>> +		power-domains = <&pd_mscl>;
>>   	};
>>   
>>   	cmu_mfc: clock-controller@15280000 {
>> @@ -393,6 +404,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   
>>   		clock-names = "oscclk", "aclk_mfc_400";
>>   		clocks = <&xxti>, <&cmu_top CLK_ACLK_MFC_400>;
>> +		power-domains = <&pd_mfc>;
>>   	};
>>   
>>   	cmu_hevc: clock-controller@14f80000 {
>> @@ -402,6 +414,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   
>>   		clock-names = "oscclk", "aclk_hevc_400";
>>   		clocks = <&xxti>, <&cmu_top CLK_ACLK_HEVC_400>;
>> +		power-domains = <&pd_hevc>;
>>   	};
>>   
>>   	cmu_isp: clock-controller@146d0000 {
>> @@ -415,6 +428,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		clocks = <&xxti>,
>>   		       <&cmu_top CLK_ACLK_ISP_DIS_400>,
>>   		       <&cmu_top CLK_ACLK_ISP_400>;
>> +		power-domains = <&pd_isp>;
>>   	};
>>   
>>   	cmu_cam0: clock-controller@120d0000 {
>> @@ -430,6 +444,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		       <&cmu_top CLK_ACLK_CAM0_333>,
>>   		       <&cmu_top CLK_ACLK_CAM0_400>,
>>   		       <&cmu_top CLK_ACLK_CAM0_552>;
>> +		power-domains = <&pd_cam0>;
>>   	};
>>   
>>   	cmu_cam1: clock-controller@145d0000 {
>> @@ -451,6 +466,7 @@ Example 2: Examples of clock controller nodes are listed below.
>>   		       <&cmu_top CLK_ACLK_CAM1_333>,
>>   		       <&cmu_top CLK_ACLK_CAM1_400>,
>>   		       <&cmu_top CLK_ACLK_CAM1_552>;
>> +		power-domains = <&pd_cam1>;
>>   	};
>>   
>>   Example 3: UART controller node that consumes the clock generated by the clock
>> diff --git a/drivers/clk/samsung/clk-exynos5433.c b/drivers/clk/samsung/clk-exynos5433.c
>> index 11343a597093..7ae9bccb1192 100644
>> --- a/drivers/clk/samsung/clk-exynos5433.c
>> +++ b/drivers/clk/samsung/clk-exynos5433.c
>> @@ -9,9 +9,14 @@
>>    * Common Clock Framework support for Exynos5433 SoC.
>>    */
>>   
>> +#include <linux/clk.h>
>>   #include <linux/clk-provider.h>
>>   #include <linux/of.h>
>>   #include <linux/of_address.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/slab.h>
> Why you need slab.h?

It was needed for kzalloc, which was later converted to devm_kzalloc. 
Maybe it would make sense to include "linux/device.h" then to avoid 
indirect inclusion.

>>    #include <dt-bindings/clock/exynos5433.h>
>>   
>> @@ -1991,6 +1996,14 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
>>   	ENABLE_IP_FSYS1,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump fsys_suspend_regs[] = {
>> +	{ MUX_SEL_FSYS0, 0 },
>> +	{ MUX_SEL_FSYS1, 0 },
>> +	{ MUX_SEL_FSYS2, 0 },
>> +	{ MUX_SEL_FSYS3, 0 },
>> +	{ MUX_SEL_FSYS4, 0 },
>> +};
>> +
>>   static const struct samsung_fixed_rate_clock fsys_fixed_clks[] __initconst = {
>>   	/* PHY clocks from USBDRD30_PHY */
>>   	FRATE(CLK_PHYCLK_USBDRD30_UDRD30_PHYCLOCK_PHY,
>> @@ -2296,16 +2309,11 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
>>   	.nr_clk_ids		= FSYS_NR_CLK,
>>   	.clk_regs		= fsys_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(fsys_clk_regs),
>> +	.suspend_regs		= fsys_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(fsys_suspend_regs),
>> +	.clk_name		= "aclk_fsys_200",
>>   };
>>   
>> -static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &fsys_cmu_info);
>> -}
>> -
>> -CLK_OF_DECLARE(exynos5433_cmu_fsys, "samsung,exynos5433-cmu-fsys",
>> -		exynos5433_cmu_fsys_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_G2D
>>    */
>> @@ -2335,6 +2343,10 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>>   	DIV_ENABLE_IP_G2D_SECURE_SMMU_G2D,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump g2d_suspend_regs[] = {
>> +	{ MUX_SEL_G2D0, 0 },
>> +};
>> +
>>   /* list of all parent clock list */
>>   PNAME(mout_aclk_g2d_266_user_p)		= { "oscclk", "aclk_g2d_266", };
>>   PNAME(mout_aclk_g2d_400_user_p)		= { "oscclk", "aclk_g2d_400", };
>> @@ -2420,16 +2432,11 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
>>   	.nr_clk_ids		= G2D_NR_CLK,
>>   	.clk_regs		= g2d_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(g2d_clk_regs),
>> +	.suspend_regs		= g2d_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(g2d_suspend_regs),
>> +	.clk_name		= "aclk_g2d_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &g2d_cmu_info);
>> -}
>> -
>> -CLK_OF_DECLARE(exynos5433_cmu_g2d, "samsung,exynos5433-cmu-g2d",
>> -		exynos5433_cmu_g2d_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_DISP
>>    */
>> @@ -2494,6 +2501,18 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>>   	CLKOUT_CMU_DISP_DIV_STAT,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump disp_suspend_regs[] = {
>> +	/* PLL has to be enabled for suspend */
>> +	{ DISP_PLL_CON0, 0x85f40502 },
>> +	/* ignore status of external PHY muxes during suspend to avoid hangs */
>> +	{ MUX_IGNORE_DISP2, 0x00111111 },
>> +	{ MUX_SEL_DISP0, 0 },
>> +	{ MUX_SEL_DISP1, 0 },
>> +	{ MUX_SEL_DISP2, 0 },
>> +	{ MUX_SEL_DISP3, 0 },
>> +	{ MUX_SEL_DISP4, 0 },
>> +};
>> +
>>   /* list of all parent clock list */
>>   PNAME(mout_disp_pll_p)			= { "oscclk", "fout_disp_pll", };
>>   PNAME(mout_sclk_dsim1_user_p)		= { "oscclk", "sclk_dsim1_disp", };
>> @@ -2841,16 +2860,11 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
>>   	.nr_clk_ids		= DISP_NR_CLK,
>>   	.clk_regs		= disp_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(disp_clk_regs),
>> +	.suspend_regs		= disp_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(disp_suspend_regs),
>> +	.clk_name		= "aclk_disp_333",
>>   };
>>   
>> -static void __init exynos5433_cmu_disp_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &disp_cmu_info);
>> -}
>> -
>> -CLK_OF_DECLARE(exynos5433_cmu_disp, "samsung,exynos5433-cmu-disp",
>> -		exynos5433_cmu_disp_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_AUD
>>    */
>> @@ -2885,6 +2899,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
>>   	ENABLE_IP_AUD1,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump aud_suspend_regs[] = {
>> +	{ MUX_SEL_AUD0, 0 },
>> +	{ MUX_SEL_AUD1, 0 },
>> +};
>> +
>>   /* list of all parent clock list */
>>   PNAME(mout_aud_pll_user_aud_p)	= { "oscclk", "fout_aud_pll", };
>>   PNAME(mout_sclk_aud_pcm_p)	= { "mout_aud_pll_user", "ioclk_audiocdclk0",};
>> @@ -3011,16 +3030,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
>>   	.nr_clk_ids		= AUD_NR_CLK,
>>   	.clk_regs		= aud_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(aud_clk_regs),
>> +	.suspend_regs		= aud_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(aud_suspend_regs),
>> +	.clk_name		= "fout_aud_pll",
>>   };
>>   
>> -static void __init exynos5433_cmu_aud_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &aud_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_aud, "samsung,exynos5433-cmu-aud",
>> -		exynos5433_cmu_aud_init);
>> -
>> -
>>   /*
>>    * Register offset definitions for CMU_BUS{0|1|2}
>>    */
>> @@ -3222,6 +3236,10 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
>>   	CLK_STOPCTRL,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump g3d_suspend_regs[] = {
>> +	{ MUX_SEL_G3D, 0 },
>> +};
>> +
>>   /* list of all parent clock list */
>>   PNAME(mout_aclk_g3d_400_p)	= { "mout_g3d_pll", "aclk_g3d_400", };
>>   PNAME(mout_g3d_pll_p)		= { "oscclk", "fout_g3d_pll", };
>> @@ -3295,15 +3313,11 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
>>   	.nr_clk_ids		= G3D_NR_CLK,
>>   	.clk_regs		= g3d_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(g3d_clk_regs),
>> +	.suspend_regs		= g3d_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(g3d_suspend_regs),
>> +	.clk_name		= "aclk_g3d_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &g3d_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_g3d, "samsung,exynos5433-cmu-g3d",
>> -		exynos5433_cmu_g3d_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_GSCL
>>    */
>> @@ -3342,6 +3356,12 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>>   	ENABLE_IP_GSCL_SECURE_SMMU_GSCL2,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump gscl_suspend_regs[] = {
>> +	{ MUX_SEL_GSCL, 0 },
>> +	{ ENABLE_ACLK_GSCL, 0xfff },
>> +	{ ENABLE_PCLK_GSCL, 0xff },
>> +};
>> +
>>   /* list of all parent clock list */
>>   PNAME(aclk_gscl_111_user_p)	= { "oscclk", "aclk_gscl_111", };
>>   PNAME(aclk_gscl_333_user_p)	= { "oscclk", "aclk_gscl_333", };
>> @@ -3436,15 +3456,11 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
>>   	.nr_clk_ids		= GSCL_NR_CLK,
>>   	.clk_regs		= gscl_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(gscl_clk_regs),
>> +	.suspend_regs		= gscl_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(gscl_suspend_regs),
>> +	.clk_name		= "aclk_gscl_111",
>>   };
>>   
>> -static void __init exynos5433_cmu_gscl_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &gscl_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_gscl, "samsung,exynos5433-cmu-gscl",
>> -		exynos5433_cmu_gscl_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_APOLLO
>>    */
>> @@ -3970,6 +3986,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
>>   	ENABLE_IP_MSCL_SECURE_SMMU_JPEG,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump mscl_suspend_regs[] = {
>> +	{ MUX_SEL_MSCL0, 0 },
>> +	{ MUX_SEL_MSCL1, 0 },
>> +};
>> +
>>   /* list of all parent clock list */
>>   PNAME(mout_sclk_jpeg_user_p)		= { "oscclk", "sclk_jpeg_mscl", };
>>   PNAME(mout_aclk_mscl_400_user_p)	= { "oscclk", "aclk_mscl_400", };
>> @@ -4082,15 +4103,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
>>   	.nr_clk_ids		= MSCL_NR_CLK,
>>   	.clk_regs		= mscl_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(mscl_clk_regs),
>> +	.suspend_regs		= mscl_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(mscl_suspend_regs),
>> +	.clk_name		= "aclk_mscl_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &mscl_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_mscl, "samsung,exynos5433-cmu-mscl",
>> -		exynos5433_cmu_mscl_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_MFC
>>    */
>> @@ -4120,6 +4137,10 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>>   	ENABLE_IP_MFC_SECURE_SMMU_MFC,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump mfc_suspend_regs[] = {
>> +	{ MUX_SEL_MFC, 0 },
>> +};
>> +
>>   PNAME(mout_aclk_mfc_400_user_p)		= { "oscclk", "aclk_mfc_400", };
>>   
>>   static const struct samsung_mux_clock mfc_mux_clks[] __initconst = {
>> @@ -4190,15 +4211,11 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
>>   	.nr_clk_ids		= MFC_NR_CLK,
>>   	.clk_regs		= mfc_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(mfc_clk_regs),
>> +	.suspend_regs		= mfc_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(mfc_suspend_regs),
>> +	.clk_name		= "aclk_mfc_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &mfc_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_mfc, "samsung,exynos5433-cmu-mfc",
>> -		exynos5433_cmu_mfc_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_HEVC
>>    */
>> @@ -4228,6 +4245,10 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>>   	ENABLE_IP_HEVC_SECURE_SMMU_HEVC,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump hevc_suspend_regs[] = {
>> +	{ MUX_SEL_HEVC, 0 },
>> +};
>> +
>>   PNAME(mout_aclk_hevc_400_user_p)	= { "oscclk", "aclk_hevc_400", };
>>   
>>   static const struct samsung_mux_clock hevc_mux_clks[] __initconst = {
>> @@ -4300,15 +4321,11 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
>>   	.nr_clk_ids		= HEVC_NR_CLK,
>>   	.clk_regs		= hevc_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(hevc_clk_regs),
>> +	.suspend_regs		= hevc_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(hevc_suspend_regs),
>> +	.clk_name		= "aclk_hevc_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &hevc_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_hevc, "samsung,exynos5433-cmu-hevc",
>> -		exynos5433_cmu_hevc_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_ISP
>>    */
>> @@ -4342,6 +4359,10 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>>   	ENABLE_IP_ISP3,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump isp_suspend_regs[] = {
>> +	{ MUX_SEL_ISP, 0 },
>> +};
>> +
>>   PNAME(mout_aclk_isp_dis_400_user_p)	= { "oscclk", "aclk_isp_dis_400", };
>>   PNAME(mout_aclk_isp_400_user_p)		= { "oscclk", "aclk_isp_400", };
>>   
>> @@ -4553,15 +4574,11 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
>>   	.nr_clk_ids		= ISP_NR_CLK,
>>   	.clk_regs		= isp_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(isp_clk_regs),
>> +	.suspend_regs		= isp_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(isp_suspend_regs),
>> +	.clk_name		= "aclk_isp_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_isp_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &isp_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_isp, "samsung,exynos5433-cmu-isp",
>> -		exynos5433_cmu_isp_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_CAM0
>>    */
>> @@ -4625,6 +4642,15 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
>>   	ENABLE_IP_CAM02,
>>   	ENABLE_IP_CAM03,
>>   };
>> +
>> +static const struct samsung_clk_reg_dump cam0_suspend_regs[] = {
>> +	{ MUX_SEL_CAM00, 0 },
>> +	{ MUX_SEL_CAM01, 0 },
>> +	{ MUX_SEL_CAM02, 0 },
>> +	{ MUX_SEL_CAM03, 0 },
>> +	{ MUX_SEL_CAM04, 0 },
>> +};
>> +
>>   PNAME(mout_aclk_cam0_333_user_p)	= { "oscclk", "aclk_cam0_333", };
>>   PNAME(mout_aclk_cam0_400_user_p)	= { "oscclk", "aclk_cam0_400", };
>>   PNAME(mout_aclk_cam0_552_user_p)	= { "oscclk", "aclk_cam0_552", };
>> @@ -5030,15 +5056,11 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
>>   	.nr_clk_ids		= CAM0_NR_CLK,
>>   	.clk_regs		= cam0_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(cam0_clk_regs),
>> +	.suspend_regs		= cam0_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(cam0_suspend_regs),
>> +	.clk_name		= "aclk_cam0_400",
>>   };
>>   
>> -static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>> -{
>> -	samsung_cmu_register_one(np, &cam0_cmu_info);
>> -}
>> -CLK_OF_DECLARE(exynos5433_cmu_cam0, "samsung,exynos5433-cmu-cam0",
>> -		exynos5433_cmu_cam0_init);
>> -
>>   /*
>>    * Register offset definitions for CMU_CAM1
>>    */
>> @@ -5085,6 +5107,12 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>>   	ENABLE_IP_CAM12,
>>   };
>>   
>> +static const struct samsung_clk_reg_dump cam1_suspend_regs[] = {
>> +	{ MUX_SEL_CAM10, 0 },
>> +	{ MUX_SEL_CAM11, 0 },
>> +	{ MUX_SEL_CAM12, 0 },
>> +};
>> +
>>   PNAME(mout_sclk_isp_uart_user_p)	= { "oscclk", "sclk_isp_uart_cam1", };
>>   PNAME(mout_sclk_isp_spi1_user_p)	= { "oscclk", "sclk_isp_spi1_cam1", };
>>   PNAME(mout_sclk_isp_spi0_user_p)	= { "oscclk", "sclk_isp_spi0_cam1", };
>> @@ -5403,11 +5431,222 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
>>   	.nr_clk_ids		= CAM1_NR_CLK,
>>   	.clk_regs		= cam1_clk_regs,
>>   	.nr_clk_regs		= ARRAY_SIZE(cam1_clk_regs),
>> +	.suspend_regs		= cam1_suspend_regs,
>> +	.nr_suspend_regs	= ARRAY_SIZE(cam1_suspend_regs),
>> +	.clk_name		= "aclk_cam1_400",
>> +};
>> +
>> +
>> +struct exynos5433_cmu_data {
>> +	struct samsung_clk_reg_dump *clk_save;
>> +	unsigned int nr_clk_save;
>> +	const struct samsung_clk_reg_dump *clk_suspend;
>> +	unsigned int nr_clk_suspend;
>> +
>> +	struct clk *clk;
>> +	struct clk **pclks;
>> +	int nr_pclks;
>> +
>> +	/* must be the last entry */
>> +	struct samsung_clk_provider ctx;
>> +};
>> +
>> +static int exynos5433_cmu_suspend(struct device *dev)
>> +{
>> +	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
>> +	int i;
>> +
>> +	samsung_clk_save(data->ctx.reg_base, data->clk_save,
>> +			 data->nr_clk_save);
>> +
>> +	for (i = 0; i < data->nr_pclks; i++)
>> +		clk_prepare_enable(data->pclks[i]);
>> +
>> +	samsung_clk_restore(data->ctx.reg_base, data->clk_suspend,
>> +			    data->nr_clk_suspend);
> This looks surprising and requires one to read the commit message to get
> the idea. How about adding a comment why this is not usual save
> operation?

Okay, I will add a comment that before suspend, some registers has to be set
to certain values stored in data->clk_suspend array.

>> +
>> +	for (i = 0; i < data->nr_pclks; i++)
>> +		clk_disable_unprepare(data->pclks[i]);
>> +
>> +	clk_disable_unprepare(data->clk);
>> +
>> +	return 0;
>> +}
>> +
>> +static int exynos5433_cmu_resume(struct device *dev)
>> +{
>> +	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
>> +	int i;
>> +
>> +	clk_prepare_enable(data->clk);
>> +
>> +	for (i = 0; i < data->nr_pclks; i++)
>> +		clk_prepare_enable(data->pclks[i]);
>> +
>> +	samsung_clk_restore(data->ctx.reg_base, data->clk_save,
>> +			    data->nr_clk_save);
>> +
>> +	for (i = 0; i < data->nr_pclks; i++)
>> +		clk_disable_unprepare(data->pclks[i]);
>> +
>> +	return 0;
>> +}
>> +
>> +static int __init exynos5433_cmu_probe(struct platform_device *pdev)
>> +{
>> +	const struct samsung_cmu_info *info;
>> +	struct exynos5433_cmu_data *data;
>> +	struct samsung_clk_provider *ctx;
>> +	struct device *dev = &pdev->dev;
>> +	struct resource *res;
>> +	void __iomem *reg_base;
>> +	int i;
>> +
>> +	info = of_device_get_match_data(dev);
>> +
>> +	data = devm_kzalloc(dev, sizeof(*data) +
>> +			    sizeof(*data->ctx.clk_data.hws) * info->nr_clk_ids,
>> +			    GFP_KERNEL);
>> +	if (!data)
>> +		return -ENOMEM;
>> +	ctx = &data->ctx;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	reg_base = devm_ioremap_resource(dev, res);
>> +	if (IS_ERR(reg_base)) {
>> +		dev_err(dev, "failed to map registers\n");
>> +		return PTR_ERR(reg_base);
>> +	}
>> +
>> +	for (i = 0; i < info->nr_clk_ids; ++i)
>> +		ctx->clk_data.hws[i] = ERR_PTR(-ENOENT);
>> +
>> +	ctx->clk_data.num = info->nr_clk_ids;
>> +	ctx->reg_base = reg_base;
>> +	ctx->dev = dev;
>> +	spin_lock_init(&ctx->lock);
>> +
>> +	data->clk_save = samsung_clk_alloc_reg_dump(info->clk_regs,
>> +						    info->nr_clk_regs);
>> +	data->nr_clk_save = info->nr_clk_regs;
>> +	data->clk_suspend = info->suspend_regs;
>> +	data->nr_clk_suspend = info->nr_suspend_regs;
>> +	data->nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
>> +						    "#clock-cells");
>> +	if (data->nr_pclks > 0) {
>> +		data->pclks = devm_kcalloc(dev, sizeof(struct clk *),
>> +					   data->nr_pclks, GFP_KERNEL);
>> +
>> +		for (i = 0; i < data->nr_pclks; i++) {
>> +			struct clk *clk = of_clk_get(dev->of_node, i);
>> +
>> +			if (IS_ERR(clk))
>> +				return PTR_ERR(clk);
>> +			data->pclks[i] = clk;
>> +		}
>> +	}
>> +
>> +	if (info->clk_name)
>> +		data->clk = clk_get(dev, info->clk_name);
>> +	clk_prepare_enable(data->clk);
>> +
>> +	platform_set_drvdata(pdev, data);
>> +
>> +	/*
>> +	 * Enable runtime PM here to allow the clock core using runtime PM
>> +	 * for the registered clocks. Additionally, we increase the runtime
>> +	 * PM usage count before registering the clocks, to prevent the
>> +	 * clock core from runtime suspending the device.
>> +	 */
>> +	pm_runtime_get_noresume(dev);
>> +	pm_runtime_set_active(dev);
>> +	pm_runtime_enable(dev);
>> +
>> +	if (info->pll_clks)
>> +		samsung_clk_register_pll(ctx, info->pll_clks, info->nr_pll_clks,
>> +					 reg_base);
>> +	if (info->mux_clks)
>> +		samsung_clk_register_mux(ctx, info->mux_clks,
>> +					 info->nr_mux_clks);
>> +	if (info->div_clks)
>> +		samsung_clk_register_div(ctx, info->div_clks,
>> +					 info->nr_div_clks);
>> +	if (info->gate_clks)
>> +		samsung_clk_register_gate(ctx, info->gate_clks,
>> +					  info->nr_gate_clks);
>> +	if (info->fixed_clks)
>> +		samsung_clk_register_fixed_rate(ctx, info->fixed_clks,
>> +						info->nr_fixed_clks);
>> +	if (info->fixed_factor_clks)
>> +		samsung_clk_register_fixed_factor(ctx, info->fixed_factor_clks,
>> +						  info->nr_fixed_factor_clks);
>> +
>> +	samsung_clk_of_add_provider(dev->of_node, ctx);
>> +	pm_runtime_put_sync(dev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id exynos5433_cmu_of_match[] = {
> Since this is used only when matching devices maybe it can be also
> marked as refdata?

It is indirectly used by of_device_get_match_data() in probe(). I'm not sure
if this would be fine for refdata.

> Best regards,
> Krzysztof
>
>> +	{
>> +		.compatible = "samsung,exynos5433-cmu-aud",
>> +		.data = &aud_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-cam0",
>> +		.data = &cam0_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-cam1",
>> +		.data = &cam1_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-disp",
>> +		.data = &disp_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-g2d",
>> +		.data = &g2d_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-g3d",
>> +		.data = &g3d_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-fsys",
>> +		.data = &fsys_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-gscl",
>> +		.data = &gscl_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-mfc",
>> +		.data = &mfc_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-hevc",
>> +		.data = &hevc_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-isp",
>> +		.data = &isp_cmu_info,
>> +	}, {
>> +		.compatible = "samsung,exynos5433-cmu-mscl",
>> +		.data = &mscl_cmu_info,
>> +	}, {
>> +	},
>> +};
>> +
>> +static const struct dev_pm_ops exynos5433_cmu_pm_ops = {
>> +	SET_RUNTIME_PM_OPS(exynos5433_cmu_suspend, exynos5433_cmu_resume,
>> +			   NULL)
>> +	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +				     pm_runtime_force_resume)
>> +};
>> +
>> +static struct platform_driver exynos5433_cmu_driver __refdata = {
>> +	.driver	= {
>> +		.name = "exynos5433-cmu",
>> +		.of_match_table = exynos5433_cmu_of_match,
>> +		.suppress_bind_attrs = true,
>> +		.pm = &exynos5433_cmu_pm_ops,
>> +	},
>> +	.probe = exynos5433_cmu_probe,
>>   };
>>   
>> -static void __init exynos5433_cmu_cam1_init(struct device_node *np)
>> +static int __init exynos5433_cmu_init(void)
>>   {
>> -	samsung_cmu_register_one(np, &cam1_cmu_info);
>> +	return platform_driver_register(&exynos5433_cmu_driver);
>>   }
>> -CLK_OF_DECLARE(exynos5433_cmu_cam1, "samsung,exynos5433-cmu-cam1",
>> -		exynos5433_cmu_cam1_init);
>> +core_initcall(exynos5433_cmu_init);
>> diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
>> index f0acae4f5d1b..d93031e94387 100644
>> --- a/drivers/clk/samsung/clk.h
>> +++ b/drivers/clk/samsung/clk.h
>> @@ -353,6 +353,12 @@ struct samsung_cmu_info {
>>   	/* list and number of clocks registers */
>>   	const unsigned long *clk_regs;
>>   	unsigned int nr_clk_regs;
>> +
>> +	/* list and number of clocks registers to set before suspend */
>> +	const struct samsung_clk_reg_dump *suspend_regs;
>> +	unsigned int nr_suspend_regs;
>> +	/* name of the parent clock needed for CMU register access */
>> +	const char *clk_name;
>>   };
>>   
>>   extern struct samsung_clk_provider *__init samsung_clk_init(
>> -- 
>> 1.9.1
>>

Best regards
Krzysztof Kozlowski Aug. 9, 2017, 8:50 a.m. UTC | #4
On Wed, Aug 9, 2017 at 10:19 AM, Marek Szyprowski
<m.szyprowski@samsung.com> wrote:
> Hi Krzysztof,
>
>
> On 2017-08-08 18:11, Krzysztof Kozlowski wrote:
>>
>> On Fri, Aug 04, 2017 at 11:34:53AM +0200, Marek Szyprowski wrote:
>>>
>>> Add runtime pm support for all clock controller units (CMU), which
>>> belongs
>>
>> s/belongs/belong/
>>
>>> to power domains and require special handling during on/off operations.
>>> Typically special values has to be written to MUX registers to change
>>> internal clocks parents to OSC clock before turning power off. During
>>> such
>>> operation all clocks, which enters CMU has to be enabled to let MUX to
>>
>> s/enters/enter/
>>
>> Beside that, how about unifying the title with previous mail ("Add
>> support for runtime PM")?
>>
>>> stabilize. Also for each CMU there is one special parent clock, which has
>>> to be enabled all the time when any access to CMU registers is done.
>>>
>>> This patch solves most of the mysterious external abort and freeze issues
>>> caused by a lack of proper parent CMU clock enabled or incorrect turn off
>>> procedure.
>>>
>>> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
>>> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
>>> ---
>>>   .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
>>>   drivers/clk/samsung/clk-exynos5433.c               | 409
>>> ++++++++++++++++-----
>>>   drivers/clk/samsung/clk.h                          |   6 +
>>>   3 files changed, 346 insertions(+), 85 deletions(-)
>>>
>> Looks okay for me, few minor nits below and:
>> Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
>>
>>
>>> diff --git a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>>> b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>>> index 1dc80f8811fe..5c7dd12e667a 100644
>>> --- a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>>> +++ b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
>>> @@ -168,6 +168,11 @@ Required Properties:
>>>                 - aclk_cam1_400
>>>                 - aclk_cam1_552
>>>   +Optional properties:
>>> +  - power-domains: a phandle to respective power domain node as
>>> described by
>>> +       generic PM domain bindings (see power/power_domain.txt for more
>>> +       information).
>>> +
>>>   Each clock is assigned an identifier and client nodes can use this
>>> identifier
>>>   to specify the clock which they consume.
>>>   @@ -270,6 +275,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clocks = <&xxti>,
>>>                        <&cmu_top CLK_ACLK_G2D_266>,
>>>                        <&cmu_top CLK_ACLK_G2D_400>;
>>> +               power-domains = <&pd_g2d>;
>>>         };
>>>         cmu_disp: clock-controller@13b90000 {
>>> @@ -295,6 +301,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                        <&cmu_mif CLK_SCLK_DECON_ECLK_DISP>,
>>>                        <&cmu_mif CLK_SCLK_DECON_TV_VCLK_DISP>,
>>>                        <&cmu_mif CLK_ACLK_DISP_333>;
>>> +               power-domains = <&pd_disp>;
>>>         };
>>>         cmu_aud: clock-controller@114c0000 {
>>> @@ -304,6 +311,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clock-names = "oscclk", "fout_aud_pll";
>>>                 clocks = <&xxti>, <&cmu_top CLK_FOUT_AUD_PLL>;
>>> +               power-domains = <&pd_aud>;
>>>         };
>>>         cmu_bus0: clock-controller@13600000 {
>>> @@ -340,6 +348,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clock-names = "oscclk", "aclk_g3d_400";
>>>                 clocks = <&xxti>, <&cmu_top CLK_ACLK_G3D_400>;
>>> +               power-domains = <&pd_g3d>;
>>>         };
>>>         cmu_gscl: clock-controller@13cf0000 {
>>> @@ -353,6 +362,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clocks = <&xxti>,
>>>                         <&cmu_top CLK_ACLK_GSCL_111>,
>>>                         <&cmu_top CLK_ACLK_GSCL_333>;
>>> +               power-domains = <&pd_gscl>;
>>>         };
>>>         cmu_apollo: clock-controller@11900000 {
>>> @@ -384,6 +394,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clocks = <&xxti>,
>>>                        <&cmu_top CLK_SCLK_JPEG_MSCL>,
>>>                        <&cmu_top CLK_ACLK_MSCL_400>;
>>> +               power-domains = <&pd_mscl>;
>>>         };
>>>         cmu_mfc: clock-controller@15280000 {
>>> @@ -393,6 +404,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clock-names = "oscclk", "aclk_mfc_400";
>>>                 clocks = <&xxti>, <&cmu_top CLK_ACLK_MFC_400>;
>>> +               power-domains = <&pd_mfc>;
>>>         };
>>>         cmu_hevc: clock-controller@14f80000 {
>>> @@ -402,6 +414,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clock-names = "oscclk", "aclk_hevc_400";
>>>                 clocks = <&xxti>, <&cmu_top CLK_ACLK_HEVC_400>;
>>> +               power-domains = <&pd_hevc>;
>>>         };
>>>         cmu_isp: clock-controller@146d0000 {
>>> @@ -415,6 +428,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                 clocks = <&xxti>,
>>>                        <&cmu_top CLK_ACLK_ISP_DIS_400>,
>>>                        <&cmu_top CLK_ACLK_ISP_400>;
>>> +               power-domains = <&pd_isp>;
>>>         };
>>>         cmu_cam0: clock-controller@120d0000 {
>>> @@ -430,6 +444,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                        <&cmu_top CLK_ACLK_CAM0_333>,
>>>                        <&cmu_top CLK_ACLK_CAM0_400>,
>>>                        <&cmu_top CLK_ACLK_CAM0_552>;
>>> +               power-domains = <&pd_cam0>;
>>>         };
>>>         cmu_cam1: clock-controller@145d0000 {
>>> @@ -451,6 +466,7 @@ Example 2: Examples of clock controller nodes are
>>> listed below.
>>>                        <&cmu_top CLK_ACLK_CAM1_333>,
>>>                        <&cmu_top CLK_ACLK_CAM1_400>,
>>>                        <&cmu_top CLK_ACLK_CAM1_552>;
>>> +               power-domains = <&pd_cam1>;
>>>         };
>>>     Example 3: UART controller node that consumes the clock generated by
>>> the clock
>>> diff --git a/drivers/clk/samsung/clk-exynos5433.c
>>> b/drivers/clk/samsung/clk-exynos5433.c
>>> index 11343a597093..7ae9bccb1192 100644
>>> --- a/drivers/clk/samsung/clk-exynos5433.c
>>> +++ b/drivers/clk/samsung/clk-exynos5433.c
>>> @@ -9,9 +9,14 @@
>>>    * Common Clock Framework support for Exynos5433 SoC.
>>>    */
>>>   +#include <linux/clk.h>
>>>   #include <linux/clk-provider.h>
>>>   #include <linux/of.h>
>>>   #include <linux/of_address.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/slab.h>
>>
>> Why you need slab.h?
>
>
> It was needed for kzalloc, which was later converted to devm_kzalloc. Maybe
> it would make sense to include "linux/device.h" then to avoid indirect
> inclusion.

platform_device.h pulls device.h so I think this could be removed.

(...)

>>> +
>>> info->nr_fixed_factor_clks);
>>> +
>>> +       samsung_clk_of_add_provider(dev->of_node, ctx);
>>> +       pm_runtime_put_sync(dev);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id exynos5433_cmu_of_match[] = {
>>
>> Since this is used only when matching devices maybe it can be also
>> marked as refdata?
>
>
> It is indirectly used by of_device_get_match_data() in probe(). I'm not sure
> if this would be fine for refdata.

Yes, right, it shouldn't be refdata then. Thanks for explanation.

Best regards,
Krzysztof
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
index 1dc80f8811fe..5c7dd12e667a 100644
--- a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
+++ b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
@@ -168,6 +168,11 @@  Required Properties:
 		- aclk_cam1_400
 		- aclk_cam1_552
 
+Optional properties:
+  - power-domains: a phandle to respective power domain node as described by
+	generic PM domain bindings (see power/power_domain.txt for more
+	information).
+
 Each clock is assigned an identifier and client nodes can use this identifier
 to specify the clock which they consume.
 
@@ -270,6 +275,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 		       <&cmu_top CLK_ACLK_G2D_266>,
 		       <&cmu_top CLK_ACLK_G2D_400>;
+		power-domains = <&pd_g2d>;
 	};
 
 	cmu_disp: clock-controller@13b90000 {
@@ -295,6 +301,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		       <&cmu_mif CLK_SCLK_DECON_ECLK_DISP>,
 		       <&cmu_mif CLK_SCLK_DECON_TV_VCLK_DISP>,
 		       <&cmu_mif CLK_ACLK_DISP_333>;
+		power-domains = <&pd_disp>;
 	};
 
 	cmu_aud: clock-controller@114c0000 {
@@ -304,6 +311,7 @@  Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "fout_aud_pll";
 		clocks = <&xxti>, <&cmu_top CLK_FOUT_AUD_PLL>;
+		power-domains = <&pd_aud>;
 	};
 
 	cmu_bus0: clock-controller@13600000 {
@@ -340,6 +348,7 @@  Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "aclk_g3d_400";
 		clocks = <&xxti>, <&cmu_top CLK_ACLK_G3D_400>;
+		power-domains = <&pd_g3d>;
 	};
 
 	cmu_gscl: clock-controller@13cf0000 {
@@ -353,6 +362,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 			<&cmu_top CLK_ACLK_GSCL_111>,
 			<&cmu_top CLK_ACLK_GSCL_333>;
+		power-domains = <&pd_gscl>;
 	};
 
 	cmu_apollo: clock-controller@11900000 {
@@ -384,6 +394,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 		       <&cmu_top CLK_SCLK_JPEG_MSCL>,
 		       <&cmu_top CLK_ACLK_MSCL_400>;
+		power-domains = <&pd_mscl>;
 	};
 
 	cmu_mfc: clock-controller@15280000 {
@@ -393,6 +404,7 @@  Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "aclk_mfc_400";
 		clocks = <&xxti>, <&cmu_top CLK_ACLK_MFC_400>;
+		power-domains = <&pd_mfc>;
 	};
 
 	cmu_hevc: clock-controller@14f80000 {
@@ -402,6 +414,7 @@  Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "aclk_hevc_400";
 		clocks = <&xxti>, <&cmu_top CLK_ACLK_HEVC_400>;
+		power-domains = <&pd_hevc>;
 	};
 
 	cmu_isp: clock-controller@146d0000 {
@@ -415,6 +428,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 		       <&cmu_top CLK_ACLK_ISP_DIS_400>,
 		       <&cmu_top CLK_ACLK_ISP_400>;
+		power-domains = <&pd_isp>;
 	};
 
 	cmu_cam0: clock-controller@120d0000 {
@@ -430,6 +444,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		       <&cmu_top CLK_ACLK_CAM0_333>,
 		       <&cmu_top CLK_ACLK_CAM0_400>,
 		       <&cmu_top CLK_ACLK_CAM0_552>;
+		power-domains = <&pd_cam0>;
 	};
 
 	cmu_cam1: clock-controller@145d0000 {
@@ -451,6 +466,7 @@  Example 2: Examples of clock controller nodes are listed below.
 		       <&cmu_top CLK_ACLK_CAM1_333>,
 		       <&cmu_top CLK_ACLK_CAM1_400>,
 		       <&cmu_top CLK_ACLK_CAM1_552>;
+		power-domains = <&pd_cam1>;
 	};
 
 Example 3: UART controller node that consumes the clock generated by the clock
diff --git a/drivers/clk/samsung/clk-exynos5433.c b/drivers/clk/samsung/clk-exynos5433.c
index 11343a597093..7ae9bccb1192 100644
--- a/drivers/clk/samsung/clk-exynos5433.c
+++ b/drivers/clk/samsung/clk-exynos5433.c
@@ -9,9 +9,14 @@ 
  * Common Clock Framework support for Exynos5433 SoC.
  */
 
+#include <linux/clk.h>
 #include <linux/clk-provider.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
 
 #include <dt-bindings/clock/exynos5433.h>
 
@@ -1991,6 +1996,14 @@  static void __init exynos5433_cmu_peris_init(struct device_node *np)
 	ENABLE_IP_FSYS1,
 };
 
+static const struct samsung_clk_reg_dump fsys_suspend_regs[] = {
+	{ MUX_SEL_FSYS0, 0 },
+	{ MUX_SEL_FSYS1, 0 },
+	{ MUX_SEL_FSYS2, 0 },
+	{ MUX_SEL_FSYS3, 0 },
+	{ MUX_SEL_FSYS4, 0 },
+};
+
 static const struct samsung_fixed_rate_clock fsys_fixed_clks[] __initconst = {
 	/* PHY clocks from USBDRD30_PHY */
 	FRATE(CLK_PHYCLK_USBDRD30_UDRD30_PHYCLOCK_PHY,
@@ -2296,16 +2309,11 @@  static void __init exynos5433_cmu_peris_init(struct device_node *np)
 	.nr_clk_ids		= FSYS_NR_CLK,
 	.clk_regs		= fsys_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(fsys_clk_regs),
+	.suspend_regs		= fsys_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(fsys_suspend_regs),
+	.clk_name		= "aclk_fsys_200",
 };
 
-static void __init exynos5433_cmu_fsys_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &fsys_cmu_info);
-}
-
-CLK_OF_DECLARE(exynos5433_cmu_fsys, "samsung,exynos5433-cmu-fsys",
-		exynos5433_cmu_fsys_init);
-
 /*
  * Register offset definitions for CMU_G2D
  */
@@ -2335,6 +2343,10 @@  static void __init exynos5433_cmu_fsys_init(struct device_node *np)
 	DIV_ENABLE_IP_G2D_SECURE_SMMU_G2D,
 };
 
+static const struct samsung_clk_reg_dump g2d_suspend_regs[] = {
+	{ MUX_SEL_G2D0, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_aclk_g2d_266_user_p)		= { "oscclk", "aclk_g2d_266", };
 PNAME(mout_aclk_g2d_400_user_p)		= { "oscclk", "aclk_g2d_400", };
@@ -2420,16 +2432,11 @@  static void __init exynos5433_cmu_fsys_init(struct device_node *np)
 	.nr_clk_ids		= G2D_NR_CLK,
 	.clk_regs		= g2d_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(g2d_clk_regs),
+	.suspend_regs		= g2d_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(g2d_suspend_regs),
+	.clk_name		= "aclk_g2d_400",
 };
 
-static void __init exynos5433_cmu_g2d_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &g2d_cmu_info);
-}
-
-CLK_OF_DECLARE(exynos5433_cmu_g2d, "samsung,exynos5433-cmu-g2d",
-		exynos5433_cmu_g2d_init);
-
 /*
  * Register offset definitions for CMU_DISP
  */
@@ -2494,6 +2501,18 @@  static void __init exynos5433_cmu_g2d_init(struct device_node *np)
 	CLKOUT_CMU_DISP_DIV_STAT,
 };
 
+static const struct samsung_clk_reg_dump disp_suspend_regs[] = {
+	/* PLL has to be enabled for suspend */
+	{ DISP_PLL_CON0, 0x85f40502 },
+	/* ignore status of external PHY muxes during suspend to avoid hangs */
+	{ MUX_IGNORE_DISP2, 0x00111111 },
+	{ MUX_SEL_DISP0, 0 },
+	{ MUX_SEL_DISP1, 0 },
+	{ MUX_SEL_DISP2, 0 },
+	{ MUX_SEL_DISP3, 0 },
+	{ MUX_SEL_DISP4, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_disp_pll_p)			= { "oscclk", "fout_disp_pll", };
 PNAME(mout_sclk_dsim1_user_p)		= { "oscclk", "sclk_dsim1_disp", };
@@ -2841,16 +2860,11 @@  static void __init exynos5433_cmu_g2d_init(struct device_node *np)
 	.nr_clk_ids		= DISP_NR_CLK,
 	.clk_regs		= disp_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(disp_clk_regs),
+	.suspend_regs		= disp_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(disp_suspend_regs),
+	.clk_name		= "aclk_disp_333",
 };
 
-static void __init exynos5433_cmu_disp_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &disp_cmu_info);
-}
-
-CLK_OF_DECLARE(exynos5433_cmu_disp, "samsung,exynos5433-cmu-disp",
-		exynos5433_cmu_disp_init);
-
 /*
  * Register offset definitions for CMU_AUD
  */
@@ -2885,6 +2899,11 @@  static void __init exynos5433_cmu_disp_init(struct device_node *np)
 	ENABLE_IP_AUD1,
 };
 
+static const struct samsung_clk_reg_dump aud_suspend_regs[] = {
+	{ MUX_SEL_AUD0, 0 },
+	{ MUX_SEL_AUD1, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_aud_pll_user_aud_p)	= { "oscclk", "fout_aud_pll", };
 PNAME(mout_sclk_aud_pcm_p)	= { "mout_aud_pll_user", "ioclk_audiocdclk0",};
@@ -3011,16 +3030,11 @@  static void __init exynos5433_cmu_disp_init(struct device_node *np)
 	.nr_clk_ids		= AUD_NR_CLK,
 	.clk_regs		= aud_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(aud_clk_regs),
+	.suspend_regs		= aud_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(aud_suspend_regs),
+	.clk_name		= "fout_aud_pll",
 };
 
-static void __init exynos5433_cmu_aud_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &aud_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_aud, "samsung,exynos5433-cmu-aud",
-		exynos5433_cmu_aud_init);
-
-
 /*
  * Register offset definitions for CMU_BUS{0|1|2}
  */
@@ -3222,6 +3236,10 @@  static void __init exynos5433_cmu_aud_init(struct device_node *np)
 	CLK_STOPCTRL,
 };
 
+static const struct samsung_clk_reg_dump g3d_suspend_regs[] = {
+	{ MUX_SEL_G3D, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_aclk_g3d_400_p)	= { "mout_g3d_pll", "aclk_g3d_400", };
 PNAME(mout_g3d_pll_p)		= { "oscclk", "fout_g3d_pll", };
@@ -3295,15 +3313,11 @@  static void __init exynos5433_cmu_aud_init(struct device_node *np)
 	.nr_clk_ids		= G3D_NR_CLK,
 	.clk_regs		= g3d_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(g3d_clk_regs),
+	.suspend_regs		= g3d_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(g3d_suspend_regs),
+	.clk_name		= "aclk_g3d_400",
 };
 
-static void __init exynos5433_cmu_g3d_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &g3d_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_g3d, "samsung,exynos5433-cmu-g3d",
-		exynos5433_cmu_g3d_init);
-
 /*
  * Register offset definitions for CMU_GSCL
  */
@@ -3342,6 +3356,12 @@  static void __init exynos5433_cmu_g3d_init(struct device_node *np)
 	ENABLE_IP_GSCL_SECURE_SMMU_GSCL2,
 };
 
+static const struct samsung_clk_reg_dump gscl_suspend_regs[] = {
+	{ MUX_SEL_GSCL, 0 },
+	{ ENABLE_ACLK_GSCL, 0xfff },
+	{ ENABLE_PCLK_GSCL, 0xff },
+};
+
 /* list of all parent clock list */
 PNAME(aclk_gscl_111_user_p)	= { "oscclk", "aclk_gscl_111", };
 PNAME(aclk_gscl_333_user_p)	= { "oscclk", "aclk_gscl_333", };
@@ -3436,15 +3456,11 @@  static void __init exynos5433_cmu_g3d_init(struct device_node *np)
 	.nr_clk_ids		= GSCL_NR_CLK,
 	.clk_regs		= gscl_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(gscl_clk_regs),
+	.suspend_regs		= gscl_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(gscl_suspend_regs),
+	.clk_name		= "aclk_gscl_111",
 };
 
-static void __init exynos5433_cmu_gscl_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &gscl_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_gscl, "samsung,exynos5433-cmu-gscl",
-		exynos5433_cmu_gscl_init);
-
 /*
  * Register offset definitions for CMU_APOLLO
  */
@@ -3970,6 +3986,11 @@  static void __init exynos5433_cmu_atlas_init(struct device_node *np)
 	ENABLE_IP_MSCL_SECURE_SMMU_JPEG,
 };
 
+static const struct samsung_clk_reg_dump mscl_suspend_regs[] = {
+	{ MUX_SEL_MSCL0, 0 },
+	{ MUX_SEL_MSCL1, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_sclk_jpeg_user_p)		= { "oscclk", "sclk_jpeg_mscl", };
 PNAME(mout_aclk_mscl_400_user_p)	= { "oscclk", "aclk_mscl_400", };
@@ -4082,15 +4103,11 @@  static void __init exynos5433_cmu_atlas_init(struct device_node *np)
 	.nr_clk_ids		= MSCL_NR_CLK,
 	.clk_regs		= mscl_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(mscl_clk_regs),
+	.suspend_regs		= mscl_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(mscl_suspend_regs),
+	.clk_name		= "aclk_mscl_400",
 };
 
-static void __init exynos5433_cmu_mscl_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &mscl_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_mscl, "samsung,exynos5433-cmu-mscl",
-		exynos5433_cmu_mscl_init);
-
 /*
  * Register offset definitions for CMU_MFC
  */
@@ -4120,6 +4137,10 @@  static void __init exynos5433_cmu_mscl_init(struct device_node *np)
 	ENABLE_IP_MFC_SECURE_SMMU_MFC,
 };
 
+static const struct samsung_clk_reg_dump mfc_suspend_regs[] = {
+	{ MUX_SEL_MFC, 0 },
+};
+
 PNAME(mout_aclk_mfc_400_user_p)		= { "oscclk", "aclk_mfc_400", };
 
 static const struct samsung_mux_clock mfc_mux_clks[] __initconst = {
@@ -4190,15 +4211,11 @@  static void __init exynos5433_cmu_mscl_init(struct device_node *np)
 	.nr_clk_ids		= MFC_NR_CLK,
 	.clk_regs		= mfc_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(mfc_clk_regs),
+	.suspend_regs		= mfc_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(mfc_suspend_regs),
+	.clk_name		= "aclk_mfc_400",
 };
 
-static void __init exynos5433_cmu_mfc_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &mfc_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_mfc, "samsung,exynos5433-cmu-mfc",
-		exynos5433_cmu_mfc_init);
-
 /*
  * Register offset definitions for CMU_HEVC
  */
@@ -4228,6 +4245,10 @@  static void __init exynos5433_cmu_mfc_init(struct device_node *np)
 	ENABLE_IP_HEVC_SECURE_SMMU_HEVC,
 };
 
+static const struct samsung_clk_reg_dump hevc_suspend_regs[] = {
+	{ MUX_SEL_HEVC, 0 },
+};
+
 PNAME(mout_aclk_hevc_400_user_p)	= { "oscclk", "aclk_hevc_400", };
 
 static const struct samsung_mux_clock hevc_mux_clks[] __initconst = {
@@ -4300,15 +4321,11 @@  static void __init exynos5433_cmu_mfc_init(struct device_node *np)
 	.nr_clk_ids		= HEVC_NR_CLK,
 	.clk_regs		= hevc_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(hevc_clk_regs),
+	.suspend_regs		= hevc_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(hevc_suspend_regs),
+	.clk_name		= "aclk_hevc_400",
 };
 
-static void __init exynos5433_cmu_hevc_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &hevc_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_hevc, "samsung,exynos5433-cmu-hevc",
-		exynos5433_cmu_hevc_init);
-
 /*
  * Register offset definitions for CMU_ISP
  */
@@ -4342,6 +4359,10 @@  static void __init exynos5433_cmu_hevc_init(struct device_node *np)
 	ENABLE_IP_ISP3,
 };
 
+static const struct samsung_clk_reg_dump isp_suspend_regs[] = {
+	{ MUX_SEL_ISP, 0 },
+};
+
 PNAME(mout_aclk_isp_dis_400_user_p)	= { "oscclk", "aclk_isp_dis_400", };
 PNAME(mout_aclk_isp_400_user_p)		= { "oscclk", "aclk_isp_400", };
 
@@ -4553,15 +4574,11 @@  static void __init exynos5433_cmu_hevc_init(struct device_node *np)
 	.nr_clk_ids		= ISP_NR_CLK,
 	.clk_regs		= isp_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(isp_clk_regs),
+	.suspend_regs		= isp_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(isp_suspend_regs),
+	.clk_name		= "aclk_isp_400",
 };
 
-static void __init exynos5433_cmu_isp_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &isp_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_isp, "samsung,exynos5433-cmu-isp",
-		exynos5433_cmu_isp_init);
-
 /*
  * Register offset definitions for CMU_CAM0
  */
@@ -4625,6 +4642,15 @@  static void __init exynos5433_cmu_isp_init(struct device_node *np)
 	ENABLE_IP_CAM02,
 	ENABLE_IP_CAM03,
 };
+
+static const struct samsung_clk_reg_dump cam0_suspend_regs[] = {
+	{ MUX_SEL_CAM00, 0 },
+	{ MUX_SEL_CAM01, 0 },
+	{ MUX_SEL_CAM02, 0 },
+	{ MUX_SEL_CAM03, 0 },
+	{ MUX_SEL_CAM04, 0 },
+};
+
 PNAME(mout_aclk_cam0_333_user_p)	= { "oscclk", "aclk_cam0_333", };
 PNAME(mout_aclk_cam0_400_user_p)	= { "oscclk", "aclk_cam0_400", };
 PNAME(mout_aclk_cam0_552_user_p)	= { "oscclk", "aclk_cam0_552", };
@@ -5030,15 +5056,11 @@  static void __init exynos5433_cmu_isp_init(struct device_node *np)
 	.nr_clk_ids		= CAM0_NR_CLK,
 	.clk_regs		= cam0_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(cam0_clk_regs),
+	.suspend_regs		= cam0_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(cam0_suspend_regs),
+	.clk_name		= "aclk_cam0_400",
 };
 
-static void __init exynos5433_cmu_cam0_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &cam0_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_cam0, "samsung,exynos5433-cmu-cam0",
-		exynos5433_cmu_cam0_init);
-
 /*
  * Register offset definitions for CMU_CAM1
  */
@@ -5085,6 +5107,12 @@  static void __init exynos5433_cmu_cam0_init(struct device_node *np)
 	ENABLE_IP_CAM12,
 };
 
+static const struct samsung_clk_reg_dump cam1_suspend_regs[] = {
+	{ MUX_SEL_CAM10, 0 },
+	{ MUX_SEL_CAM11, 0 },
+	{ MUX_SEL_CAM12, 0 },
+};
+
 PNAME(mout_sclk_isp_uart_user_p)	= { "oscclk", "sclk_isp_uart_cam1", };
 PNAME(mout_sclk_isp_spi1_user_p)	= { "oscclk", "sclk_isp_spi1_cam1", };
 PNAME(mout_sclk_isp_spi0_user_p)	= { "oscclk", "sclk_isp_spi0_cam1", };
@@ -5403,11 +5431,222 @@  static void __init exynos5433_cmu_cam0_init(struct device_node *np)
 	.nr_clk_ids		= CAM1_NR_CLK,
 	.clk_regs		= cam1_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(cam1_clk_regs),
+	.suspend_regs		= cam1_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(cam1_suspend_regs),
+	.clk_name		= "aclk_cam1_400",
+};
+
+
+struct exynos5433_cmu_data {
+	struct samsung_clk_reg_dump *clk_save;
+	unsigned int nr_clk_save;
+	const struct samsung_clk_reg_dump *clk_suspend;
+	unsigned int nr_clk_suspend;
+
+	struct clk *clk;
+	struct clk **pclks;
+	int nr_pclks;
+
+	/* must be the last entry */
+	struct samsung_clk_provider ctx;
+};
+
+static int exynos5433_cmu_suspend(struct device *dev)
+{
+	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
+	int i;
+
+	samsung_clk_save(data->ctx.reg_base, data->clk_save,
+			 data->nr_clk_save);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_prepare_enable(data->pclks[i]);
+
+	samsung_clk_restore(data->ctx.reg_base, data->clk_suspend,
+			    data->nr_clk_suspend);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_disable_unprepare(data->pclks[i]);
+
+	clk_disable_unprepare(data->clk);
+
+	return 0;
+}
+
+static int exynos5433_cmu_resume(struct device *dev)
+{
+	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
+	int i;
+
+	clk_prepare_enable(data->clk);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_prepare_enable(data->pclks[i]);
+
+	samsung_clk_restore(data->ctx.reg_base, data->clk_save,
+			    data->nr_clk_save);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_disable_unprepare(data->pclks[i]);
+
+	return 0;
+}
+
+static int __init exynos5433_cmu_probe(struct platform_device *pdev)
+{
+	const struct samsung_cmu_info *info;
+	struct exynos5433_cmu_data *data;
+	struct samsung_clk_provider *ctx;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	void __iomem *reg_base;
+	int i;
+
+	info = of_device_get_match_data(dev);
+
+	data = devm_kzalloc(dev, sizeof(*data) +
+			    sizeof(*data->ctx.clk_data.hws) * info->nr_clk_ids,
+			    GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	ctx = &data->ctx;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	reg_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(reg_base)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(reg_base);
+	}
+
+	for (i = 0; i < info->nr_clk_ids; ++i)
+		ctx->clk_data.hws[i] = ERR_PTR(-ENOENT);
+
+	ctx->clk_data.num = info->nr_clk_ids;
+	ctx->reg_base = reg_base;
+	ctx->dev = dev;
+	spin_lock_init(&ctx->lock);
+
+	data->clk_save = samsung_clk_alloc_reg_dump(info->clk_regs,
+						    info->nr_clk_regs);
+	data->nr_clk_save = info->nr_clk_regs;
+	data->clk_suspend = info->suspend_regs;
+	data->nr_clk_suspend = info->nr_suspend_regs;
+	data->nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
+						    "#clock-cells");
+	if (data->nr_pclks > 0) {
+		data->pclks = devm_kcalloc(dev, sizeof(struct clk *),
+					   data->nr_pclks, GFP_KERNEL);
+
+		for (i = 0; i < data->nr_pclks; i++) {
+			struct clk *clk = of_clk_get(dev->of_node, i);
+
+			if (IS_ERR(clk))
+				return PTR_ERR(clk);
+			data->pclks[i] = clk;
+		}
+	}
+
+	if (info->clk_name)
+		data->clk = clk_get(dev, info->clk_name);
+	clk_prepare_enable(data->clk);
+
+	platform_set_drvdata(pdev, data);
+
+	/*
+	 * Enable runtime PM here to allow the clock core using runtime PM
+	 * for the registered clocks. Additionally, we increase the runtime
+	 * PM usage count before registering the clocks, to prevent the
+	 * clock core from runtime suspending the device.
+	 */
+	pm_runtime_get_noresume(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	if (info->pll_clks)
+		samsung_clk_register_pll(ctx, info->pll_clks, info->nr_pll_clks,
+					 reg_base);
+	if (info->mux_clks)
+		samsung_clk_register_mux(ctx, info->mux_clks,
+					 info->nr_mux_clks);
+	if (info->div_clks)
+		samsung_clk_register_div(ctx, info->div_clks,
+					 info->nr_div_clks);
+	if (info->gate_clks)
+		samsung_clk_register_gate(ctx, info->gate_clks,
+					  info->nr_gate_clks);
+	if (info->fixed_clks)
+		samsung_clk_register_fixed_rate(ctx, info->fixed_clks,
+						info->nr_fixed_clks);
+	if (info->fixed_factor_clks)
+		samsung_clk_register_fixed_factor(ctx, info->fixed_factor_clks,
+						  info->nr_fixed_factor_clks);
+
+	samsung_clk_of_add_provider(dev->of_node, ctx);
+	pm_runtime_put_sync(dev);
+
+	return 0;
+}
+
+static const struct of_device_id exynos5433_cmu_of_match[] = {
+	{
+		.compatible = "samsung,exynos5433-cmu-aud",
+		.data = &aud_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-cam0",
+		.data = &cam0_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-cam1",
+		.data = &cam1_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-disp",
+		.data = &disp_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-g2d",
+		.data = &g2d_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-g3d",
+		.data = &g3d_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-fsys",
+		.data = &fsys_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-gscl",
+		.data = &gscl_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-mfc",
+		.data = &mfc_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-hevc",
+		.data = &hevc_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-isp",
+		.data = &isp_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-mscl",
+		.data = &mscl_cmu_info,
+	}, {
+	},
+};
+
+static const struct dev_pm_ops exynos5433_cmu_pm_ops = {
+	SET_RUNTIME_PM_OPS(exynos5433_cmu_suspend, exynos5433_cmu_resume,
+			   NULL)
+	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				     pm_runtime_force_resume)
+};
+
+static struct platform_driver exynos5433_cmu_driver __refdata = {
+	.driver	= {
+		.name = "exynos5433-cmu",
+		.of_match_table = exynos5433_cmu_of_match,
+		.suppress_bind_attrs = true,
+		.pm = &exynos5433_cmu_pm_ops,
+	},
+	.probe = exynos5433_cmu_probe,
 };
 
-static void __init exynos5433_cmu_cam1_init(struct device_node *np)
+static int __init exynos5433_cmu_init(void)
 {
-	samsung_cmu_register_one(np, &cam1_cmu_info);
+	return platform_driver_register(&exynos5433_cmu_driver);
 }
-CLK_OF_DECLARE(exynos5433_cmu_cam1, "samsung,exynos5433-cmu-cam1",
-		exynos5433_cmu_cam1_init);
+core_initcall(exynos5433_cmu_init);
diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
index f0acae4f5d1b..d93031e94387 100644
--- a/drivers/clk/samsung/clk.h
+++ b/drivers/clk/samsung/clk.h
@@ -353,6 +353,12 @@  struct samsung_cmu_info {
 	/* list and number of clocks registers */
 	const unsigned long *clk_regs;
 	unsigned int nr_clk_regs;
+
+	/* list and number of clocks registers to set before suspend */
+	const struct samsung_clk_reg_dump *suspend_regs;
+	unsigned int nr_suspend_regs;
+	/* name of the parent clock needed for CMU register access */
+	const char *clk_name;
 };
 
 extern struct samsung_clk_provider *__init samsung_clk_init(