diff mbox

[U-Boot,06/12] aspeed/ast2500: Add Clock Driver

Message ID 20170104194656.124368-7-maxims@google.com
State Superseded
Delegated to: Tom Rini
Headers show

Commit Message

Maxim Sloyko Jan. 4, 2017, 7:46 p.m. UTC
This driver is ast2500 specific and is not compatible with earlier
versions of this chip. The differences are not that large, but they are
in somewhat random places, so making it compatible with ast2400 is not
worth the effort at the moment.

Signed-off-by: Maxim Sloyko <maxims@google.com>
---

 arch/arm/include/asm/arch-aspeed/scu_ast2500.h | 108 +++++++++++
 drivers/clk/Makefile                           |   2 +
 drivers/clk/aspeed/Makefile                    |   7 +
 drivers/clk/aspeed/clk_ast2500.c               | 255 +++++++++++++++++++++++++
 4 files changed, 372 insertions(+)
 create mode 100644 arch/arm/include/asm/arch-aspeed/scu_ast2500.h
 create mode 100644 drivers/clk/aspeed/Makefile
 create mode 100644 drivers/clk/aspeed/clk_ast2500.c

Comments

Simon Glass Jan. 14, 2017, 5:14 p.m. UTC | #1
Hi Maxim,

On 4 January 2017 at 12:46, Maxim Sloyko <maxims@google.com> wrote:
> This driver is ast2500 specific and is not compatible with earlier

ast2500-specific

> versions of this chip. The differences are not that large, but they are
> in somewhat random places, so making it compatible with ast2400 is not
> worth the effort at the moment.
>
> Signed-off-by: Maxim Sloyko <maxims@google.com>
> ---
>
>  arch/arm/include/asm/arch-aspeed/scu_ast2500.h | 108 +++++++++++
>  drivers/clk/Makefile                           |   2 +
>  drivers/clk/aspeed/Makefile                    |   7 +
>  drivers/clk/aspeed/clk_ast2500.c               | 255 +++++++++++++++++++++++++
>  4 files changed, 372 insertions(+)
>  create mode 100644 arch/arm/include/asm/arch-aspeed/scu_ast2500.h
>  create mode 100644 drivers/clk/aspeed/Makefile
>  create mode 100644 drivers/clk/aspeed/clk_ast2500.c
>
> diff --git a/arch/arm/include/asm/arch-aspeed/scu_ast2500.h b/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
> new file mode 100644
> index 0000000000..febff9d2d3
> --- /dev/null
> +++ b/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
> @@ -0,0 +1,108 @@
> +#ifndef _ASM_ARCH_SCU_AST2500_H
> +#define _ASM_ARCH_SCU_AST2500_H
> +
> +#define SCU_UNLOCK_VALUE               0x1688a8a8
> +
> +#define SCU_HWSTRAP_VGAMEM_MASK                3
> +#define SCU_HWSTRAP_VGAMEM_SHIFT       2
> +#define SCU_HWSTRAP_DDR4               (1 << 24)
> +#define SCU_HWSTRAP_CLKIN_25MHZ                (1 << 23)
> +
> +#define SCU_MPLL_DENUM_SHIFT           0
> +#define SCU_MPLL_DENUM_MASK            0x1f
> +#define SCU_MPLL_NUM_SHIFT             5
> +#define SCU_MPLL_NUM_MASK              0xff
> +#define SCU_MPLL_POST_SHIFT            13
> +#define SCU_MPLL_POST_MASK             0x3f
> +
> +#define SCU_HPLL_DENUM_SHIFT           0
> +#define SCU_HPLL_DENUM_MASK            0x1f
> +#define SCU_HPLL_NUM_SHIFT             5
> +#define SCU_HPLL_NUM_MASK              0xff
> +#define SCU_HPLL_POST_SHIFT            13
> +#define SCU_HPLL_POST_MASK             0x3f
> +
> +#define SCU_MISC2_UARTCLK_SHIFT                24
> +
> +#define SCU_MISC_UARTCLK_DIV13         (1 << 12)
> +
> +#ifndef __ASSEMBLY__
> +
> +struct ast2500_clk_priv {
> +       struct ast2500_scu *scu;
> +};
> +
> +struct ast2500_scu {
> +       u32 protection_key;
> +       u32 sysreset_ctrl1;
> +       u32 clk_sel1;
> +       u32 clk_stop_ctrl1;
> +       u32 freq_counter_ctrl;
> +       u32 freq_counter_cmp;
> +       u32 intr_ctrl;
> +       u32 d2_pll_param;
> +       u32 m_pll_param;
> +       u32 h_pll_param;
> +       u32 d_pll_param;
> +       u32 misc_ctrl1;
> +       u32 pci_config[3];
> +       u32 sysreset_status;
> +       u32 vga_handshake[2];
> +       u32 mac_clk_delay;
> +       u32 misc_ctrl2;
> +       u32 vga_scratch[8];
> +       u32 hwstrap;
> +       u32 rng_ctrl;
> +       u32 rng_data;
> +       u32 rev_id;
> +       u32 pinmux_ctrl[6];
> +       u32 reserved0;
> +       u32 extrst_sel;
> +       u32 pinmux_ctrl1[4];
> +       u32 reserved1[2];
> +       u32 mac_clk_delay_100M;
> +       u32 mac_clk_delay_10M;
> +       u32 wakeup_enable;
> +       u32 wakeup_control;
> +       u32 reserved2[3];
> +       u32 sysreset_ctrl2;
> +       u32 clk_sel2;
> +       u32 clk_stop_ctrl2;
> +       u32 freerun_counter;
> +       u32 freerun_counter_ext;
> +       u32 clk_duty_meas_ctrl;
> +       u32 clk_duty_meas_res;
> +       u32 reserved3[4];
> +       /* The next registers are not key protected */

key-protected

> +       struct ast2500_cpu2 {
> +               u32 ctrl;
> +               u32 base_addr[9];
> +               u32 cache_ctrl;
> +       } cpu2;
> +       u32 reserved4;
> +       u32 d_pll_ext_param[3];
> +       u32 d2_pll_ext_param[3];
> +       u32 mh_pll_ext_param;
> +       u32 reserved5;
> +       u32 chip_id[2];
> +       u32 reserved6[2];
> +       u32 uart_clk_ctrl;
> +       u32 reserved7[7];
> +       u32 pcie_config;
> +       u32 mmio_decode;
> +       u32 reloc_ctrl_decode[2];
> +       u32 mailbox_addr;
> +       u32 shared_sram_decode[2];
> +       u32 bmc_rev_id;
> +       u32 reserved8;
> +       u32 bmc_device_id;
> +       u32 reserved9[13];
> +       u32 clk_duty_sel;
> +};
> +
> +int ast_get_clk(struct udevice **devp);
> +void *ast_get_scu(void);

Function comments please.

> +
> +#endif  /* __ASSEMBLY__ */
> +
> +#endif  /* _ASM_ARCH_SCU_AST2500_H */
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 40a5e8cae8..625513789c 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -16,3 +16,5 @@ obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
>  obj-$(CONFIG_CLK_EXYNOS) += exynos/
>  obj-$(CONFIG_CLK_AT91) += at91/
>  obj-$(CONFIG_CLK_BOSTON) += clk_boston.o
> +
> +obj-$(CONFIG_ARCH_ASPEED) += aspeed/
> diff --git a/drivers/clk/aspeed/Makefile b/drivers/clk/aspeed/Makefile
> new file mode 100644
> index 0000000000..65d1cd6e29
> --- /dev/null
> +++ b/drivers/clk/aspeed/Makefile
> @@ -0,0 +1,7 @@
> +#
> +# Copyright (c) 2016 Google, Inc
> +#
> +# SPDX-License-Identifier:      GPL-2.0+
> +#
> +
> +obj-$(CONFIG_ASPEED_AST2500) += clk_ast2500.o
> diff --git a/drivers/clk/aspeed/clk_ast2500.c b/drivers/clk/aspeed/clk_ast2500.c
> new file mode 100644
> index 0000000000..c888a6d35b
> --- /dev/null
> +++ b/drivers/clk/aspeed/clk_ast2500.c
> @@ -0,0 +1,255 @@
> +/*
> + * (C) Copyright 2016 Google, Inc
> + *
> + * SPDX-License-Identifier:    GPL-2.0
> + */
> +
> +#include <common.h>
> +#include <asm/arch/scu_ast2500.h>
> +#include <asm/io.h>
> +#include <clk-uclass.h>
> +#include <dm.h>
> +#include <dm/lists.h>
> +#include <dt-bindings/clock/ast2500-scu.h>
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +/*
> + * For H-PLL and M-PLL the formula is
> + * (Output Frequency) = CLKIN * ((M + 1) / (N + 1)) / (P + 1)
> + * M - Numerator
> + * N - Denumerator
> + * P - Post Divider
> + * They have the same layout in their control register.
> + */
> +
> +/*
> + * Get the rate of the M-PLL clock from input clock frequency and
> + * the value of the M-PLL Parameter Register.
> + */
> +static ulong ast2500_get_mpll_rate(ulong clkin, u32 mpll_reg)
> +{
> +       const ulong num = (mpll_reg >> SCU_MPLL_NUM_SHIFT) & SCU_MPLL_NUM_MASK;
> +       const ulong denum = (mpll_reg >> SCU_MPLL_DENUM_SHIFT)
> +                       & SCU_MPLL_DENUM_MASK;
> +       const ulong post_div = (mpll_reg >> SCU_MPLL_POST_SHIFT)
> +                       & SCU_MPLL_POST_MASK;
> +
> +       return (clkin * ((num + 1) / (denum + 1))) / post_div;
> +}
> +
> +/*
> + * Get the rate of the H-PLL clock from input clock frequency and
> + * the value of the H-PLL Parameter Register.
> + */
> +static ulong ast2500_get_hpll_rate(ulong clkin, u32 hpll_reg)
> +{
> +       const ulong num = (hpll_reg >> SCU_HPLL_NUM_SHIFT) & SCU_HPLL_NUM_MASK;
> +       const ulong denum = (hpll_reg >> SCU_HPLL_DENUM_SHIFT)
> +                       & SCU_HPLL_DENUM_MASK;
> +       const ulong post_div = (hpll_reg >> SCU_HPLL_POST_SHIFT)
> +                       & SCU_HPLL_POST_MASK;
> +
> +       return (clkin * ((num + 1) / (denum + 1))) / post_div;
> +}
> +
> +static ulong ast2500_get_clkin(struct ast2500_scu *scu)
> +{
> +       return readl(&scu->hwstrap) & SCU_HWSTRAP_CLKIN_25MHZ
> +                       ? 25*1000*1000 : 24*1000*1000;

Spaces around *

> +}
> +
> +static ulong ast2500_get_uart_clk_rate(struct ast2500_scu *scu, int uart)

Function comment. What is 'uart' for?

> +{
> +       /*
> +        * ast2500 datasheet is very confusing when it comes to UART clocks,
> +        * especially when CLKIN = 25 MHz. The settings are in
> +        * different registers and it is unclear how they interact.
> +        *
> +        * This has only been tested with default settings and CLKIN = 24 MHz.

Can you detect this and return -EINVAL if it can't work? If not, that's fine.

> +        */
> +       ulong uart_clkin;
> +
> +       if (readl(&scu->misc_ctrl2) & (1 << (uart + SCU_MISC2_UARTCLK_SHIFT)))
> +               uart_clkin = 192 * 1000 * 1000;
> +       else
> +               uart_clkin = 24 * 1000 * 1000;
> +
> +       if (readl(&scu->misc_ctrl1) & SCU_MISC_UARTCLK_DIV13)
> +               uart_clkin /= 13;
> +
> +       return uart_clkin;
> +}
> +
> +static ulong ast2500_clk_get_rate(struct clk *clk)
> +{
> +       struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
> +       ulong clkin = ast2500_get_clkin(priv->scu);
> +       ulong rate;
> +
> +       switch (clk->id) {
> +       case PLL_HPLL:
> +       case ARMCLK:
> +               /*
> +                * This ignores dynamic/static slowdown of ARMCLK and may
> +                * be inaccurate.
> +                */
> +               rate = ast2500_get_hpll_rate(clkin,
> +                                            readl(&priv->scu->h_pll_param));
> +               break;
> +       case MCLK_DDR:
> +               rate = ast2500_get_mpll_rate(clkin,
> +                                            readl(&priv->scu->m_pll_param));
> +               break;
> +       case PCLK_UART1:
> +               rate = ast2500_get_uart_clk_rate(priv->scu, 1);
> +               break;
> +       case PCLK_UART2:
> +               rate = ast2500_get_uart_clk_rate(priv->scu, 2);
> +               break;
> +       case PCLK_UART3:
> +               rate = ast2500_get_uart_clk_rate(priv->scu, 3);
> +               break;
> +       case PCLK_UART4:
> +               rate = ast2500_get_uart_clk_rate(priv->scu, 4);
> +               break;
> +       case PCLK_UART5:
> +               rate = ast2500_get_uart_clk_rate(priv->scu, 5);
> +               break;
> +       default:
> +               return -ENOENT;
> +       }
> +
> +       return rate;
> +}
> +
> +static void ast2500_scu_unlock(struct ast2500_scu *scu)
> +{
> +       writel(SCU_UNLOCK_VALUE, &scu->protection_key);
> +       while (!readl(&scu->protection_key))
> +               ;

Can this ever timeout?

> +}
> +
> +static void ast2500_scu_lock(struct ast2500_scu *scu)
> +{
> +       writel(~SCU_UNLOCK_VALUE, &scu->protection_key);
> +       while (readl(&scu->protection_key))
> +               ;
> +}
> +
> +static ulong ast2500_configure_ddr(struct ast2500_scu *scu, ulong rate)
> +{
> +       ulong clkin = ast2500_get_clkin(scu);
> +       u32 mpll_reg;
> +
> +       /*
> +        * There are not that many combinations of numerator, denumerator
> +        * and post divider, so just brute force the best combination.
> +        * However, to avoid overflow when multiplying, use kHz.
> +        */
> +       const ulong clkin_khz = clkin / 1000;
> +       const ulong rate_khz = rate / 1000;
> +

drop blank line

> +       ulong best_num = 0;
> +       ulong best_denum = 0;
> +       ulong best_post = 0;
> +       ulong delta = rate;
> +

drop blank line

> +       ulong num, denum, post;

add blank line

> +       for (denum = 0; denum <= SCU_MPLL_DENUM_MASK; ++denum) {
> +               for (post = 0; post <= SCU_MPLL_POST_MASK; ++post) {
> +                       num = (rate_khz * (post + 1) / clkin_khz) * (denum + 1);
> +                       ulong new_rate_khz = (clkin_khz
> +                                             * ((num + 1) / (denum + 1)))
> +                                            / (post + 1);
> +
> +                       /* Keep the rate below requested one. */
> +                       if (new_rate_khz > rate_khz)
> +                               continue;
> +
> +                       if (new_rate_khz - rate_khz < delta) {
> +                               delta = new_rate_khz - rate_khz;
> +
> +                               best_num = num;
> +                               best_denum = denum;
> +                               best_post = post;
> +
> +                               if (delta == 0)
> +                                       goto rate_calc_done;
> +                       }
> +               }
> +       }
> +
> + rate_calc_done:

Drop blank line

Can it fail? Do you need to check delta?

> +
> +       mpll_reg = readl(&scu->m_pll_param);
> +       mpll_reg &= ~((SCU_MPLL_POST_MASK << SCU_MPLL_POST_SHIFT)
> +                     | (SCU_MPLL_NUM_MASK << SCU_MPLL_NUM_SHIFT)
> +                     | (SCU_MPLL_DENUM_MASK << SCU_MPLL_DENUM_SHIFT));
> +       mpll_reg |= (best_post << SCU_MPLL_POST_SHIFT)
> +           | (best_num << SCU_MPLL_NUM_SHIFT)
> +           | (best_denum << SCU_MPLL_DENUM_SHIFT);
> +
> +       ast2500_scu_unlock(scu);
> +       writel(mpll_reg, &scu->m_pll_param);
> +       ast2500_scu_lock(scu);
> +
> +       return ast2500_get_mpll_rate(clkin, mpll_reg);
> +}
> +
> +static ulong ast2500_clk_set_rate(struct clk *clk, ulong rate)
> +{
> +       struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
> +
> +       ulong new_rate;
> +       switch (clk->id) {
> +       case PLL_MPLL:
> +       case MCLK_DDR:
> +               new_rate = ast2500_configure_ddr(priv->scu, rate);
> +               break;
> +       default:
> +               return -ENOENT;
> +       }
> +
> +       return new_rate;
> +}
> +
> +struct clk_ops ast2500_clk_ops = {
> +       .get_rate = ast2500_clk_get_rate,
> +       .set_rate = ast2500_clk_set_rate,
> +};
> +
> +static int ast2500_clk_probe(struct udevice *dev)
> +{
> +       struct ast2500_clk_priv *priv = dev_get_priv(dev);

blank line

> +       priv->scu = (struct ast2500_scu *)dev_get_addr(dev);

Maybe dev_get_addr_ptr()

Also should check that it is not FDT_ADDR_NONE.

> +
> +       return 0;
> +}
> +
> +static int ast2500_clk_bind(struct udevice *dev)
> +{
> +       int ret;
> +
> +       /* The reset driver does not have a device node, so bind it here */
> +       ret = device_bind_driver(gd->dm_root, "ast_sysreset", "reset", &dev);
> +       if (ret)
> +               debug("Warning: No reset driver: ret=%d\n", ret);
> +
> +       return 0;
> +}
> +
> +static const struct udevice_id ast2500_clk_ids[] = {
> +       { .compatible = "aspeed,ast2500-scu" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(aspeed_ast2500_scu) = {
> +       .name           = "aspeed_ast2500_scu",
> +       .id             = UCLASS_CLK,
> +       .of_match       = ast2500_clk_ids,
> +       .priv_auto_alloc_size = sizeof(struct ast2500_clk_priv),
> +       .ops            = &ast2500_clk_ops,
> +       .bind           = ast2500_clk_bind,
> +       .probe          = ast2500_clk_probe,
> +};
> --
> 2.11.0.390.gc69c2f50cf-goog
>

Regards,
Simon
Maxim Sloyko Jan. 17, 2017, 11:18 p.m. UTC | #2
On Sat, Jan 14, 2017 at 9:14 AM, Simon Glass <sjg@chromium.org> wrote:

> Hi Maxim,
>
> On 4 January 2017 at 12:46, Maxim Sloyko <maxims@google.com> wrote:
> > This driver is ast2500 specific and is not compatible with earlier
>
> ast2500-specific
>
> > versions of this chip. The differences are not that large, but they are
> > in somewhat random places, so making it compatible with ast2400 is not
> > worth the effort at the moment.
> >
> > Signed-off-by: Maxim Sloyko <maxims@google.com>
> > ---
> >
> >  arch/arm/include/asm/arch-aspeed/scu_ast2500.h | 108 +++++++++++
> >  drivers/clk/Makefile                           |   2 +
> >  drivers/clk/aspeed/Makefile                    |   7 +
> >  drivers/clk/aspeed/clk_ast2500.c               | 255
> +++++++++++++++++++++++++
> >  4 files changed, 372 insertions(+)
> >  create mode 100644 arch/arm/include/asm/arch-aspeed/scu_ast2500.h
> >  create mode 100644 drivers/clk/aspeed/Makefile
> >  create mode 100644 drivers/clk/aspeed/clk_ast2500.c
> >
> > diff --git a/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
> b/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
> > new file mode 100644
> > index 0000000000..febff9d2d3
> > --- /dev/null
> > +++ b/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
> > @@ -0,0 +1,108 @@
> > +#ifndef _ASM_ARCH_SCU_AST2500_H
> > +#define _ASM_ARCH_SCU_AST2500_H
> > +
> > +#define SCU_UNLOCK_VALUE               0x1688a8a8
> > +
> > +#define SCU_HWSTRAP_VGAMEM_MASK                3
> > +#define SCU_HWSTRAP_VGAMEM_SHIFT       2
> > +#define SCU_HWSTRAP_DDR4               (1 << 24)
> > +#define SCU_HWSTRAP_CLKIN_25MHZ                (1 << 23)
> > +
> > +#define SCU_MPLL_DENUM_SHIFT           0
> > +#define SCU_MPLL_DENUM_MASK            0x1f
> > +#define SCU_MPLL_NUM_SHIFT             5
> > +#define SCU_MPLL_NUM_MASK              0xff
> > +#define SCU_MPLL_POST_SHIFT            13
> > +#define SCU_MPLL_POST_MASK             0x3f
> > +
> > +#define SCU_HPLL_DENUM_SHIFT           0
> > +#define SCU_HPLL_DENUM_MASK            0x1f
> > +#define SCU_HPLL_NUM_SHIFT             5
> > +#define SCU_HPLL_NUM_MASK              0xff
> > +#define SCU_HPLL_POST_SHIFT            13
> > +#define SCU_HPLL_POST_MASK             0x3f
> > +
> > +#define SCU_MISC2_UARTCLK_SHIFT                24
> > +
> > +#define SCU_MISC_UARTCLK_DIV13         (1 << 12)
> > +
> > +#ifndef __ASSEMBLY__
> > +
> > +struct ast2500_clk_priv {
> > +       struct ast2500_scu *scu;
> > +};
> > +
> > +struct ast2500_scu {
> > +       u32 protection_key;
> > +       u32 sysreset_ctrl1;
> > +       u32 clk_sel1;
> > +       u32 clk_stop_ctrl1;
> > +       u32 freq_counter_ctrl;
> > +       u32 freq_counter_cmp;
> > +       u32 intr_ctrl;
> > +       u32 d2_pll_param;
> > +       u32 m_pll_param;
> > +       u32 h_pll_param;
> > +       u32 d_pll_param;
> > +       u32 misc_ctrl1;
> > +       u32 pci_config[3];
> > +       u32 sysreset_status;
> > +       u32 vga_handshake[2];
> > +       u32 mac_clk_delay;
> > +       u32 misc_ctrl2;
> > +       u32 vga_scratch[8];
> > +       u32 hwstrap;
> > +       u32 rng_ctrl;
> > +       u32 rng_data;
> > +       u32 rev_id;
> > +       u32 pinmux_ctrl[6];
> > +       u32 reserved0;
> > +       u32 extrst_sel;
> > +       u32 pinmux_ctrl1[4];
> > +       u32 reserved1[2];
> > +       u32 mac_clk_delay_100M;
> > +       u32 mac_clk_delay_10M;
> > +       u32 wakeup_enable;
> > +       u32 wakeup_control;
> > +       u32 reserved2[3];
> > +       u32 sysreset_ctrl2;
> > +       u32 clk_sel2;
> > +       u32 clk_stop_ctrl2;
> > +       u32 freerun_counter;
> > +       u32 freerun_counter_ext;
> > +       u32 clk_duty_meas_ctrl;
> > +       u32 clk_duty_meas_res;
> > +       u32 reserved3[4];
> > +       /* The next registers are not key protected */
>
> key-protected
>

Fixed.


>
> > +       struct ast2500_cpu2 {
> > +               u32 ctrl;
> > +               u32 base_addr[9];
> > +               u32 cache_ctrl;
> > +       } cpu2;
> > +       u32 reserved4;
> > +       u32 d_pll_ext_param[3];
> > +       u32 d2_pll_ext_param[3];
> > +       u32 mh_pll_ext_param;
> > +       u32 reserved5;
> > +       u32 chip_id[2];
> > +       u32 reserved6[2];
> > +       u32 uart_clk_ctrl;
> > +       u32 reserved7[7];
> > +       u32 pcie_config;
> > +       u32 mmio_decode;
> > +       u32 reloc_ctrl_decode[2];
> > +       u32 mailbox_addr;
> > +       u32 shared_sram_decode[2];
> > +       u32 bmc_rev_id;
> > +       u32 reserved8;
> > +       u32 bmc_device_id;
> > +       u32 reserved9[13];
> > +       u32 clk_duty_sel;
> > +};
> > +
> > +int ast_get_clk(struct udevice **devp);
> > +void *ast_get_scu(void);
>
> Function comments please.
>

Fixed.


>
> > +
> > +#endif  /* __ASSEMBLY__ */
> > +
> > +#endif  /* _ASM_ARCH_SCU_AST2500_H */
> > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> > index 40a5e8cae8..625513789c 100644
> > --- a/drivers/clk/Makefile
> > +++ b/drivers/clk/Makefile
> > @@ -16,3 +16,5 @@ obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
> >  obj-$(CONFIG_CLK_EXYNOS) += exynos/
> >  obj-$(CONFIG_CLK_AT91) += at91/
> >  obj-$(CONFIG_CLK_BOSTON) += clk_boston.o
> > +
> > +obj-$(CONFIG_ARCH_ASPEED) += aspeed/
> > diff --git a/drivers/clk/aspeed/Makefile b/drivers/clk/aspeed/Makefile
> > new file mode 100644
> > index 0000000000..65d1cd6e29
> > --- /dev/null
> > +++ b/drivers/clk/aspeed/Makefile
> > @@ -0,0 +1,7 @@
> > +#
> > +# Copyright (c) 2016 Google, Inc
> > +#
> > +# SPDX-License-Identifier:      GPL-2.0+
> > +#
> > +
> > +obj-$(CONFIG_ASPEED_AST2500) += clk_ast2500.o
> > diff --git a/drivers/clk/aspeed/clk_ast2500.c b/drivers/clk/aspeed/clk_
> ast2500.c
> > new file mode 100644
> > index 0000000000..c888a6d35b
> > --- /dev/null
> > +++ b/drivers/clk/aspeed/clk_ast2500.c
> > @@ -0,0 +1,255 @@
> > +/*
> > + * (C) Copyright 2016 Google, Inc
> > + *
> > + * SPDX-License-Identifier:    GPL-2.0
> > + */
> > +
> > +#include <common.h>
> > +#include <asm/arch/scu_ast2500.h>
> > +#include <asm/io.h>
> > +#include <clk-uclass.h>
> > +#include <dm.h>
> > +#include <dm/lists.h>
> > +#include <dt-bindings/clock/ast2500-scu.h>
> > +
> > +DECLARE_GLOBAL_DATA_PTR;
> > +
> > +/*
> > + * For H-PLL and M-PLL the formula is
> > + * (Output Frequency) = CLKIN * ((M + 1) / (N + 1)) / (P + 1)
> > + * M - Numerator
> > + * N - Denumerator
> > + * P - Post Divider
> > + * They have the same layout in their control register.
> > + */
> > +
> > +/*
> > + * Get the rate of the M-PLL clock from input clock frequency and
> > + * the value of the M-PLL Parameter Register.
> > + */
> > +static ulong ast2500_get_mpll_rate(ulong clkin, u32 mpll_reg)
> > +{
> > +       const ulong num = (mpll_reg >> SCU_MPLL_NUM_SHIFT) &
> SCU_MPLL_NUM_MASK;
> > +       const ulong denum = (mpll_reg >> SCU_MPLL_DENUM_SHIFT)
> > +                       & SCU_MPLL_DENUM_MASK;
> > +       const ulong post_div = (mpll_reg >> SCU_MPLL_POST_SHIFT)
> > +                       & SCU_MPLL_POST_MASK;
> > +
> > +       return (clkin * ((num + 1) / (denum + 1))) / post_div;
> > +}
> > +
> > +/*
> > + * Get the rate of the H-PLL clock from input clock frequency and
> > + * the value of the H-PLL Parameter Register.
> > + */
> > +static ulong ast2500_get_hpll_rate(ulong clkin, u32 hpll_reg)
> > +{
> > +       const ulong num = (hpll_reg >> SCU_HPLL_NUM_SHIFT) &
> SCU_HPLL_NUM_MASK;
> > +       const ulong denum = (hpll_reg >> SCU_HPLL_DENUM_SHIFT)
> > +                       & SCU_HPLL_DENUM_MASK;
> > +       const ulong post_div = (hpll_reg >> SCU_HPLL_POST_SHIFT)
> > +                       & SCU_HPLL_POST_MASK;
> > +
> > +       return (clkin * ((num + 1) / (denum + 1))) / post_div;
> > +}
> > +
> > +static ulong ast2500_get_clkin(struct ast2500_scu *scu)
> > +{
> > +       return readl(&scu->hwstrap) & SCU_HWSTRAP_CLKIN_25MHZ
> > +                       ? 25*1000*1000 : 24*1000*1000;
>
> Spaces around *
>

Fixed.


>
> > +}
> > +
> > +static ulong ast2500_get_uart_clk_rate(struct ast2500_scu *scu, int
> uart)
>
> Function comment. What is 'uart' for?
>

Fixed. Renamed uart to uart_index to be more clear about its purpose.


>
> > +{
> > +       /*
> > +        * ast2500 datasheet is very confusing when it comes to UART
> clocks,
> > +        * especially when CLKIN = 25 MHz. The settings are in
> > +        * different registers and it is unclear how they interact.
> > +        *
> > +        * This has only been tested with default settings and CLKIN =
> 24 MHz.
>
> Can you detect this and return -EINVAL if it can't work? If not, that's
> fine.
>

I can detect if CLKIN = 24 MHz or 25 MHz, but it's not clear what UART
input clock rate is for CLKIN = 25MHz
There is a setting (on by default) to generate UART 24MHz clock from 25MHz,
but there are also somewhat
conflicting settings for dividers.

I don't have access to 25MHz based hardware to test this at the moment, all
our boards use 24MHz crystal.


>
> > +        */
> > +       ulong uart_clkin;
> > +
> > +       if (readl(&scu->misc_ctrl2) & (1 << (uart +
> SCU_MISC2_UARTCLK_SHIFT)))
> > +               uart_clkin = 192 * 1000 * 1000;
> > +       else
> > +               uart_clkin = 24 * 1000 * 1000;
> > +
> > +       if (readl(&scu->misc_ctrl1) & SCU_MISC_UARTCLK_DIV13)
> > +               uart_clkin /= 13;
> > +
> > +       return uart_clkin;
> > +}
> > +
> > +static ulong ast2500_clk_get_rate(struct clk *clk)
> > +{
> > +       struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
> > +       ulong clkin = ast2500_get_clkin(priv->scu);
> > +       ulong rate;
> > +
> > +       switch (clk->id) {
> > +       case PLL_HPLL:
> > +       case ARMCLK:
> > +               /*
> > +                * This ignores dynamic/static slowdown of ARMCLK and may
> > +                * be inaccurate.
> > +                */
> > +               rate = ast2500_get_hpll_rate(clkin,
> > +
> readl(&priv->scu->h_pll_param));
> > +               break;
> > +       case MCLK_DDR:
> > +               rate = ast2500_get_mpll_rate(clkin,
> > +
> readl(&priv->scu->m_pll_param));
> > +               break;
> > +       case PCLK_UART1:
> > +               rate = ast2500_get_uart_clk_rate(priv->scu, 1);
> > +               break;
> > +       case PCLK_UART2:
> > +               rate = ast2500_get_uart_clk_rate(priv->scu, 2);
> > +               break;
> > +       case PCLK_UART3:
> > +               rate = ast2500_get_uart_clk_rate(priv->scu, 3);
> > +               break;
> > +       case PCLK_UART4:
> > +               rate = ast2500_get_uart_clk_rate(priv->scu, 4);
> > +               break;
> > +       case PCLK_UART5:
> > +               rate = ast2500_get_uart_clk_rate(priv->scu, 5);
> > +               break;
> > +       default:
> > +               return -ENOENT;
> > +       }
> > +
> > +       return rate;
> > +}
> > +
> > +static void ast2500_scu_unlock(struct ast2500_scu *scu)
> > +{
> > +       writel(SCU_UNLOCK_VALUE, &scu->protection_key);
> > +       while (!readl(&scu->protection_key))
> > +               ;
>
> Can this ever timeout?
>

Not according to the datasheet.


>
> > +}
> > +
> > +static void ast2500_scu_lock(struct ast2500_scu *scu)
> > +{
> > +       writel(~SCU_UNLOCK_VALUE, &scu->protection_key);
> > +       while (readl(&scu->protection_key))
> > +               ;
> > +}
> > +
> > +static ulong ast2500_configure_ddr(struct ast2500_scu *scu, ulong rate)
> > +{
> > +       ulong clkin = ast2500_get_clkin(scu);
> > +       u32 mpll_reg;
> > +
> > +       /*
> > +        * There are not that many combinations of numerator, denumerator
> > +        * and post divider, so just brute force the best combination.
> > +        * However, to avoid overflow when multiplying, use kHz.
> > +        */
> > +       const ulong clkin_khz = clkin / 1000;
> > +       const ulong rate_khz = rate / 1000;
> > +
>
> drop blank line
>
> > +       ulong best_num = 0;
> > +       ulong best_denum = 0;
> > +       ulong best_post = 0;
> > +       ulong delta = rate;
> > +
>
> drop blank line
>
> > +       ulong num, denum, post;
>
> add blank line
>
> > +       for (denum = 0; denum <= SCU_MPLL_DENUM_MASK; ++denum) {
> > +               for (post = 0; post <= SCU_MPLL_POST_MASK; ++post) {
> > +                       num = (rate_khz * (post + 1) / clkin_khz) *
> (denum + 1);
> > +                       ulong new_rate_khz = (clkin_khz
> > +                                             * ((num + 1) / (denum +
> 1)))
> > +                                            / (post + 1);
> > +
> > +                       /* Keep the rate below requested one. */
> > +                       if (new_rate_khz > rate_khz)
> > +                               continue;
> > +
> > +                       if (new_rate_khz - rate_khz < delta) {
> > +                               delta = new_rate_khz - rate_khz;
> > +
> > +                               best_num = num;
> > +                               best_denum = denum;
> > +                               best_post = post;
> > +
> > +                               if (delta == 0)
> > +                                       goto rate_calc_done;
> > +                       }
> > +               }
> > +       }
> > +
> > + rate_calc_done:
>
> Drop blank line
>
> Can it fail? Do you need to check delta?
>

I just check delta for an early exit, if we found a match.
It returns the best approximation for target rate we could achieve and it
can fail
in a sense that this approximation might not be good enough. In which case
DDR init will fail --
I tried to play with the configured rate during development and that's how
failure manifested itself.
However, I don't know any way of verifying the rate in advance.


>
> > +
> > +       mpll_reg = readl(&scu->m_pll_param);
> > +       mpll_reg &= ~((SCU_MPLL_POST_MASK << SCU_MPLL_POST_SHIFT)
> > +                     | (SCU_MPLL_NUM_MASK << SCU_MPLL_NUM_SHIFT)
> > +                     | (SCU_MPLL_DENUM_MASK << SCU_MPLL_DENUM_SHIFT));
> > +       mpll_reg |= (best_post << SCU_MPLL_POST_SHIFT)
> > +           | (best_num << SCU_MPLL_NUM_SHIFT)
> > +           | (best_denum << SCU_MPLL_DENUM_SHIFT);
> > +
> > +       ast2500_scu_unlock(scu);
> > +       writel(mpll_reg, &scu->m_pll_param);
> > +       ast2500_scu_lock(scu);
> > +
> > +       return ast2500_get_mpll_rate(clkin, mpll_reg);
> > +}
> > +
> > +static ulong ast2500_clk_set_rate(struct clk *clk, ulong rate)
> > +{
> > +       struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
> > +
> > +       ulong new_rate;
> > +       switch (clk->id) {
> > +       case PLL_MPLL:
> > +       case MCLK_DDR:
> > +               new_rate = ast2500_configure_ddr(priv->scu, rate);
> > +               break;
> > +       default:
> > +               return -ENOENT;
> > +       }
> > +
> > +       return new_rate;
> > +}
> > +
> > +struct clk_ops ast2500_clk_ops = {
> > +       .get_rate = ast2500_clk_get_rate,
> > +       .set_rate = ast2500_clk_set_rate,
> > +};
> > +
> > +static int ast2500_clk_probe(struct udevice *dev)
> > +{
> > +       struct ast2500_clk_priv *priv = dev_get_priv(dev);
>
> blank line
>

Done.


>
> > +       priv->scu = (struct ast2500_scu *)dev_get_addr(dev);
>
> Maybe dev_get_addr_ptr()
>
> Also should check that it is not FDT_ADDR_NONE.
>

Done.


>
> > +
> > +       return 0;
> > +}
> > +
> > +static int ast2500_clk_bind(struct udevice *dev)
> > +{
> > +       int ret;
> > +
> > +       /* The reset driver does not have a device node, so bind it here
> */
> > +       ret = device_bind_driver(gd->dm_root, "ast_sysreset", "reset",
> &dev);
> > +       if (ret)
> > +               debug("Warning: No reset driver: ret=%d\n", ret);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct udevice_id ast2500_clk_ids[] = {
> > +       { .compatible = "aspeed,ast2500-scu" },
> > +       { }
> > +};
> > +
> > +U_BOOT_DRIVER(aspeed_ast2500_scu) = {
> > +       .name           = "aspeed_ast2500_scu",
> > +       .id             = UCLASS_CLK,
> > +       .of_match       = ast2500_clk_ids,
> > +       .priv_auto_alloc_size = sizeof(struct ast2500_clk_priv),
> > +       .ops            = &ast2500_clk_ops,
> > +       .bind           = ast2500_clk_bind,
> > +       .probe          = ast2500_clk_probe,
> > +};
> > --
> > 2.11.0.390.gc69c2f50cf-goog
> >
>
> Regards,
> Simon
>
diff mbox

Patch

diff --git a/arch/arm/include/asm/arch-aspeed/scu_ast2500.h b/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
new file mode 100644
index 0000000000..febff9d2d3
--- /dev/null
+++ b/arch/arm/include/asm/arch-aspeed/scu_ast2500.h
@@ -0,0 +1,108 @@ 
+#ifndef _ASM_ARCH_SCU_AST2500_H
+#define _ASM_ARCH_SCU_AST2500_H
+
+#define SCU_UNLOCK_VALUE		0x1688a8a8
+
+#define SCU_HWSTRAP_VGAMEM_MASK		3
+#define SCU_HWSTRAP_VGAMEM_SHIFT	2
+#define SCU_HWSTRAP_DDR4		(1 << 24)
+#define SCU_HWSTRAP_CLKIN_25MHZ		(1 << 23)
+
+#define SCU_MPLL_DENUM_SHIFT		0
+#define SCU_MPLL_DENUM_MASK		0x1f
+#define SCU_MPLL_NUM_SHIFT		5
+#define SCU_MPLL_NUM_MASK		0xff
+#define SCU_MPLL_POST_SHIFT		13
+#define SCU_MPLL_POST_MASK		0x3f
+
+#define SCU_HPLL_DENUM_SHIFT		0
+#define SCU_HPLL_DENUM_MASK		0x1f
+#define SCU_HPLL_NUM_SHIFT		5
+#define SCU_HPLL_NUM_MASK		0xff
+#define SCU_HPLL_POST_SHIFT		13
+#define SCU_HPLL_POST_MASK		0x3f
+
+#define SCU_MISC2_UARTCLK_SHIFT		24
+
+#define SCU_MISC_UARTCLK_DIV13		(1 << 12)
+
+#ifndef __ASSEMBLY__
+
+struct ast2500_clk_priv {
+	struct ast2500_scu *scu;
+};
+
+struct ast2500_scu {
+	u32 protection_key;
+	u32 sysreset_ctrl1;
+	u32 clk_sel1;
+	u32 clk_stop_ctrl1;
+	u32 freq_counter_ctrl;
+	u32 freq_counter_cmp;
+	u32 intr_ctrl;
+	u32 d2_pll_param;
+	u32 m_pll_param;
+	u32 h_pll_param;
+	u32 d_pll_param;
+	u32 misc_ctrl1;
+	u32 pci_config[3];
+	u32 sysreset_status;
+	u32 vga_handshake[2];
+	u32 mac_clk_delay;
+	u32 misc_ctrl2;
+	u32 vga_scratch[8];
+	u32 hwstrap;
+	u32 rng_ctrl;
+	u32 rng_data;
+	u32 rev_id;
+	u32 pinmux_ctrl[6];
+	u32 reserved0;
+	u32 extrst_sel;
+	u32 pinmux_ctrl1[4];
+	u32 reserved1[2];
+	u32 mac_clk_delay_100M;
+	u32 mac_clk_delay_10M;
+	u32 wakeup_enable;
+	u32 wakeup_control;
+	u32 reserved2[3];
+	u32 sysreset_ctrl2;
+	u32 clk_sel2;
+	u32 clk_stop_ctrl2;
+	u32 freerun_counter;
+	u32 freerun_counter_ext;
+	u32 clk_duty_meas_ctrl;
+	u32 clk_duty_meas_res;
+	u32 reserved3[4];
+	/* The next registers are not key protected */
+	struct ast2500_cpu2 {
+		u32 ctrl;
+		u32 base_addr[9];
+		u32 cache_ctrl;
+	} cpu2;
+	u32 reserved4;
+	u32 d_pll_ext_param[3];
+	u32 d2_pll_ext_param[3];
+	u32 mh_pll_ext_param;
+	u32 reserved5;
+	u32 chip_id[2];
+	u32 reserved6[2];
+	u32 uart_clk_ctrl;
+	u32 reserved7[7];
+	u32 pcie_config;
+	u32 mmio_decode;
+	u32 reloc_ctrl_decode[2];
+	u32 mailbox_addr;
+	u32 shared_sram_decode[2];
+	u32 bmc_rev_id;
+	u32 reserved8;
+	u32 bmc_device_id;
+	u32 reserved9[13];
+	u32 clk_duty_sel;
+};
+
+int ast_get_clk(struct udevice **devp);
+void *ast_get_scu(void);
+
+#endif  /* __ASSEMBLY__ */
+
+#endif  /* _ASM_ARCH_SCU_AST2500_H */
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 40a5e8cae8..625513789c 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -16,3 +16,5 @@  obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
 obj-$(CONFIG_CLK_EXYNOS) += exynos/
 obj-$(CONFIG_CLK_AT91) += at91/
 obj-$(CONFIG_CLK_BOSTON) += clk_boston.o
+
+obj-$(CONFIG_ARCH_ASPEED) += aspeed/
diff --git a/drivers/clk/aspeed/Makefile b/drivers/clk/aspeed/Makefile
new file mode 100644
index 0000000000..65d1cd6e29
--- /dev/null
+++ b/drivers/clk/aspeed/Makefile
@@ -0,0 +1,7 @@ 
+#
+# Copyright (c) 2016 Google, Inc
+#
+# SPDX-License-Identifier:      GPL-2.0+
+#
+
+obj-$(CONFIG_ASPEED_AST2500) += clk_ast2500.o
diff --git a/drivers/clk/aspeed/clk_ast2500.c b/drivers/clk/aspeed/clk_ast2500.c
new file mode 100644
index 0000000000..c888a6d35b
--- /dev/null
+++ b/drivers/clk/aspeed/clk_ast2500.c
@@ -0,0 +1,255 @@ 
+/*
+ * (C) Copyright 2016 Google, Inc
+ *
+ * SPDX-License-Identifier:	GPL-2.0
+ */
+
+#include <common.h>
+#include <asm/arch/scu_ast2500.h>
+#include <asm/io.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <dt-bindings/clock/ast2500-scu.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/*
+ * For H-PLL and M-PLL the formula is
+ * (Output Frequency) = CLKIN * ((M + 1) / (N + 1)) / (P + 1)
+ * M - Numerator
+ * N - Denumerator
+ * P - Post Divider
+ * They have the same layout in their control register.
+ */
+
+/*
+ * Get the rate of the M-PLL clock from input clock frequency and
+ * the value of the M-PLL Parameter Register.
+ */
+static ulong ast2500_get_mpll_rate(ulong clkin, u32 mpll_reg)
+{
+	const ulong num = (mpll_reg >> SCU_MPLL_NUM_SHIFT) & SCU_MPLL_NUM_MASK;
+	const ulong denum = (mpll_reg >> SCU_MPLL_DENUM_SHIFT)
+			& SCU_MPLL_DENUM_MASK;
+	const ulong post_div = (mpll_reg >> SCU_MPLL_POST_SHIFT)
+			& SCU_MPLL_POST_MASK;
+
+	return (clkin * ((num + 1) / (denum + 1))) / post_div;
+}
+
+/*
+ * Get the rate of the H-PLL clock from input clock frequency and
+ * the value of the H-PLL Parameter Register.
+ */
+static ulong ast2500_get_hpll_rate(ulong clkin, u32 hpll_reg)
+{
+	const ulong num = (hpll_reg >> SCU_HPLL_NUM_SHIFT) & SCU_HPLL_NUM_MASK;
+	const ulong denum = (hpll_reg >> SCU_HPLL_DENUM_SHIFT)
+			& SCU_HPLL_DENUM_MASK;
+	const ulong post_div = (hpll_reg >> SCU_HPLL_POST_SHIFT)
+			& SCU_HPLL_POST_MASK;
+
+	return (clkin * ((num + 1) / (denum + 1))) / post_div;
+}
+
+static ulong ast2500_get_clkin(struct ast2500_scu *scu)
+{
+	return readl(&scu->hwstrap) & SCU_HWSTRAP_CLKIN_25MHZ
+			? 25*1000*1000 : 24*1000*1000;
+}
+
+static ulong ast2500_get_uart_clk_rate(struct ast2500_scu *scu, int uart)
+{
+	/*
+	 * ast2500 datasheet is very confusing when it comes to UART clocks,
+	 * especially when CLKIN = 25 MHz. The settings are in
+	 * different registers and it is unclear how they interact.
+	 *
+	 * This has only been tested with default settings and CLKIN = 24 MHz.
+	 */
+	ulong uart_clkin;
+
+	if (readl(&scu->misc_ctrl2) & (1 << (uart + SCU_MISC2_UARTCLK_SHIFT)))
+		uart_clkin = 192 * 1000 * 1000;
+	else
+		uart_clkin = 24 * 1000 * 1000;
+
+	if (readl(&scu->misc_ctrl1) & SCU_MISC_UARTCLK_DIV13)
+		uart_clkin /= 13;
+
+	return uart_clkin;
+}
+
+static ulong ast2500_clk_get_rate(struct clk *clk)
+{
+	struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
+	ulong clkin = ast2500_get_clkin(priv->scu);
+	ulong rate;
+
+	switch (clk->id) {
+	case PLL_HPLL:
+	case ARMCLK:
+		/*
+		 * This ignores dynamic/static slowdown of ARMCLK and may
+		 * be inaccurate.
+		 */
+		rate = ast2500_get_hpll_rate(clkin,
+					     readl(&priv->scu->h_pll_param));
+		break;
+	case MCLK_DDR:
+		rate = ast2500_get_mpll_rate(clkin,
+					     readl(&priv->scu->m_pll_param));
+		break;
+	case PCLK_UART1:
+		rate = ast2500_get_uart_clk_rate(priv->scu, 1);
+		break;
+	case PCLK_UART2:
+		rate = ast2500_get_uart_clk_rate(priv->scu, 2);
+		break;
+	case PCLK_UART3:
+		rate = ast2500_get_uart_clk_rate(priv->scu, 3);
+		break;
+	case PCLK_UART4:
+		rate = ast2500_get_uart_clk_rate(priv->scu, 4);
+		break;
+	case PCLK_UART5:
+		rate = ast2500_get_uart_clk_rate(priv->scu, 5);
+		break;
+	default:
+		return -ENOENT;
+	}
+
+	return rate;
+}
+
+static void ast2500_scu_unlock(struct ast2500_scu *scu)
+{
+	writel(SCU_UNLOCK_VALUE, &scu->protection_key);
+	while (!readl(&scu->protection_key))
+		;
+}
+
+static void ast2500_scu_lock(struct ast2500_scu *scu)
+{
+	writel(~SCU_UNLOCK_VALUE, &scu->protection_key);
+	while (readl(&scu->protection_key))
+		;
+}
+
+static ulong ast2500_configure_ddr(struct ast2500_scu *scu, ulong rate)
+{
+	ulong clkin = ast2500_get_clkin(scu);
+	u32 mpll_reg;
+
+	/*
+	 * There are not that many combinations of numerator, denumerator
+	 * and post divider, so just brute force the best combination.
+	 * However, to avoid overflow when multiplying, use kHz.
+	 */
+	const ulong clkin_khz = clkin / 1000;
+	const ulong rate_khz = rate / 1000;
+
+	ulong best_num = 0;
+	ulong best_denum = 0;
+	ulong best_post = 0;
+	ulong delta = rate;
+
+	ulong num, denum, post;
+	for (denum = 0; denum <= SCU_MPLL_DENUM_MASK; ++denum) {
+		for (post = 0; post <= SCU_MPLL_POST_MASK; ++post) {
+			num = (rate_khz * (post + 1) / clkin_khz) * (denum + 1);
+			ulong new_rate_khz = (clkin_khz
+					      * ((num + 1) / (denum + 1)))
+					     / (post + 1);
+
+			/* Keep the rate below requested one. */
+			if (new_rate_khz > rate_khz)
+				continue;
+
+			if (new_rate_khz - rate_khz < delta) {
+				delta = new_rate_khz - rate_khz;
+
+				best_num = num;
+				best_denum = denum;
+				best_post = post;
+
+				if (delta == 0)
+					goto rate_calc_done;
+			}
+		}
+	}
+
+ rate_calc_done:
+
+	mpll_reg = readl(&scu->m_pll_param);
+	mpll_reg &= ~((SCU_MPLL_POST_MASK << SCU_MPLL_POST_SHIFT)
+		      | (SCU_MPLL_NUM_MASK << SCU_MPLL_NUM_SHIFT)
+		      | (SCU_MPLL_DENUM_MASK << SCU_MPLL_DENUM_SHIFT));
+	mpll_reg |= (best_post << SCU_MPLL_POST_SHIFT)
+	    | (best_num << SCU_MPLL_NUM_SHIFT)
+	    | (best_denum << SCU_MPLL_DENUM_SHIFT);
+
+	ast2500_scu_unlock(scu);
+	writel(mpll_reg, &scu->m_pll_param);
+	ast2500_scu_lock(scu);
+
+	return ast2500_get_mpll_rate(clkin, mpll_reg);
+}
+
+static ulong ast2500_clk_set_rate(struct clk *clk, ulong rate)
+{
+	struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
+
+	ulong new_rate;
+	switch (clk->id) {
+	case PLL_MPLL:
+	case MCLK_DDR:
+		new_rate = ast2500_configure_ddr(priv->scu, rate);
+		break;
+	default:
+		return -ENOENT;
+	}
+
+	return new_rate;
+}
+
+struct clk_ops ast2500_clk_ops = {
+	.get_rate = ast2500_clk_get_rate,
+	.set_rate = ast2500_clk_set_rate,
+};
+
+static int ast2500_clk_probe(struct udevice *dev)
+{
+	struct ast2500_clk_priv *priv = dev_get_priv(dev);
+	priv->scu = (struct ast2500_scu *)dev_get_addr(dev);
+
+	return 0;
+}
+
+static int ast2500_clk_bind(struct udevice *dev)
+{
+	int ret;
+
+	/* The reset driver does not have a device node, so bind it here */
+	ret = device_bind_driver(gd->dm_root, "ast_sysreset", "reset", &dev);
+	if (ret)
+		debug("Warning: No reset driver: ret=%d\n", ret);
+
+	return 0;
+}
+
+static const struct udevice_id ast2500_clk_ids[] = {
+	{ .compatible = "aspeed,ast2500-scu" },
+	{ }
+};
+
+U_BOOT_DRIVER(aspeed_ast2500_scu) = {
+	.name		= "aspeed_ast2500_scu",
+	.id		= UCLASS_CLK,
+	.of_match	= ast2500_clk_ids,
+	.priv_auto_alloc_size = sizeof(struct ast2500_clk_priv),
+	.ops		= &ast2500_clk_ops,
+	.bind		= ast2500_clk_bind,
+	.probe		= ast2500_clk_probe,
+};