config CLK_AT91
        bool "AT91 clock drivers"
        depends on CLK
+       select MISC
        help
          This option is used to enable the AT91 clock driver.
          The driver supports the AT91 clock generator, including
 
 #define GENERATED_SOURCE_MAX   6
 #define GENERATED_MAX_DIV      255
 
-struct generated_clk_priv {
+/**
+ * generated_clk_bind() - for the generated clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+static int generated_clk_bind(struct udevice *dev)
+{
+       return at91_clk_sub_device_bind(dev, "generic-clk");
+}
+
+static const struct udevice_id generated_clk_match[] = {
+       { .compatible = "atmel,sama5d2-clk-generated" },
+       {}
+};
+
+U_BOOT_DRIVER(generated_clk) = {
+       .name = "generated-clk",
+       .id = UCLASS_MISC,
+       .of_match = generated_clk_match,
+       .bind = generated_clk_bind,
+};
+
+/*-------------------------------------------------------------*/
+
+struct generic_clk_priv {
        u32 num_parents;
 };
 
-static ulong generated_clk_get_rate(struct clk *clk)
+static ulong generic_clk_get_rate(struct clk *clk)
 {
        struct pmc_platdata *plat = dev_get_platdata(clk->dev);
        struct at91_pmc *pmc = plat->reg_base;
        struct clk parent;
+       ulong clk_rate;
        u32 tmp, gckdiv;
        u8 parent_id;
        int ret;
                    AT91_PMC_PCR_GCKCSS_MASK;
        gckdiv = (tmp >> AT91_PMC_PCR_GCKDIV_OFFSET) & AT91_PMC_PCR_GCKDIV_MASK;
 
-       ret = clk_get_by_index(clk->dev, parent_id, &parent);
+       ret = clk_get_by_index(dev_get_parent(clk->dev), parent_id, &parent);
        if (ret)
                return 0;
 
-       return clk_get_rate(&parent) / (gckdiv + 1);
+       clk_rate = clk_get_rate(&parent) / (gckdiv + 1);
+
+       clk_free(&parent);
+
+       return clk_rate;
 }
 
-static ulong generated_clk_set_rate(struct clk *clk, ulong rate)
+static ulong generic_clk_set_rate(struct clk *clk, ulong rate)
 {
        struct pmc_platdata *plat = dev_get_platdata(clk->dev);
        struct at91_pmc *pmc = plat->reg_base;
-       struct generated_clk_priv *priv = dev_get_priv(clk->dev);
+       struct generic_clk_priv *priv = dev_get_priv(clk->dev);
        struct clk parent, best_parent;
        ulong tmp_rate, best_rate = rate, parent_rate;
        int tmp_diff, best_diff = -1;
        int ret;
 
        for (i = 0; i < priv->num_parents; i++) {
-               ret = clk_get_by_index(clk->dev, i, &parent);
+               ret = clk_get_by_index(dev_get_parent(clk->dev), i, &parent);
                if (ret)
                        return ret;
 
        return 0;
 }
 
-static struct clk_ops generated_clk_ops = {
-       .get_rate = generated_clk_get_rate,
-       .set_rate = generated_clk_set_rate,
+static struct clk_ops generic_clk_ops = {
+       .of_xlate = at91_clk_of_xlate,
+       .get_rate = generic_clk_get_rate,
+       .set_rate = generic_clk_set_rate,
 };
 
-static int generated_clk_ofdata_to_platdata(struct udevice *dev)
+static int generic_clk_ofdata_to_platdata(struct udevice *dev)
 {
-       struct generated_clk_priv *priv = dev_get_priv(dev);
+       struct generic_clk_priv *priv = dev_get_priv(dev);
        u32 cells[GENERATED_SOURCE_MAX];
        u32 num_parents;
 
-       num_parents = fdtdec_get_int_array_count(gd->fdt_blob, dev->of_offset,
+       num_parents = fdtdec_get_int_array_count(gd->fdt_blob,
+                                                dev_get_parent(dev)->of_offset,
                                                 "clocks", cells,
                                                 GENERATED_SOURCE_MAX);
 
        return 0;
 }
 
-static int generated_clk_bind(struct udevice *dev)
-{
-       return at91_pmc_clk_node_bind(dev);
-}
-
-static int generated_clk_probe(struct udevice *dev)
-{
-       return at91_pmc_core_probe(dev);
-}
-
-static const struct udevice_id generated_clk_match[] = {
-       { .compatible = "atmel,sama5d2-clk-generated" },
-       {}
-};
-
-U_BOOT_DRIVER(generated_clk) = {
-       .name = "generated-clk",
+U_BOOT_DRIVER(generic_clk) = {
+       .name = "generic-clk",
        .id = UCLASS_CLK,
-       .of_match = generated_clk_match,
-       .bind = generated_clk_bind,
-       .probe = generated_clk_probe,
-       .ofdata_to_platdata = generated_clk_ofdata_to_platdata,
-       .priv_auto_alloc_size = sizeof(struct generated_clk_priv),
+       .probe = at91_clk_probe,
+       .ofdata_to_platdata = generic_clk_ofdata_to_platdata,
+       .priv_auto_alloc_size = sizeof(struct generic_clk_priv),
        .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
-       .ops = &generated_clk_ops,
+       .ops = &generic_clk_ops,
 };
 
 #define PERIPHERAL_ID_MAX      31
 #define PERIPHERAL_MASK(id)    (1 << ((id) & PERIPHERAL_ID_MAX))
 
-static int sam9x5_periph_clk_enable(struct clk *clk)
+/**
+ * sam9x5_periph_clk_bind() - for the periph clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+static int sam9x5_periph_clk_bind(struct udevice *dev)
+{
+       return at91_clk_sub_device_bind(dev, "periph-clk");
+}
+
+static const struct udevice_id sam9x5_periph_clk_match[] = {
+       { .compatible = "atmel,at91sam9x5-clk-peripheral" },
+       {}
+};
+
+U_BOOT_DRIVER(sam9x5_periph_clk) = {
+       .name = "sam9x5-periph-clk",
+       .id = UCLASS_MISC,
+       .of_match = sam9x5_periph_clk_match,
+       .bind = sam9x5_periph_clk_bind,
+};
+
+/*---------------------------------------------------------*/
+
+static int periph_clk_enable(struct clk *clk)
 {
        struct pmc_platdata *plat = dev_get_platdata(clk->dev);
        struct at91_pmc *pmc = plat->reg_base;
        return 0;
 }
 
-static struct clk_ops sam9x5_periph_clk_ops = {
-       .enable = sam9x5_periph_clk_enable,
-};
-
-static int sam9x5_periph_clk_bind(struct udevice *dev)
+static ulong periph_get_rate(struct clk *clk)
 {
-       return at91_pmc_clk_node_bind(dev);
-}
+       struct udevice *dev;
+       struct clk clk_dev;
+       ulong clk_rate;
+       int ret;
 
-static int sam9x5_periph_clk_probe(struct udevice *dev)
-{
-       return at91_pmc_core_probe(dev);
+       dev = dev_get_parent(clk->dev);
+
+       ret = clk_get_by_index(dev, 0, &clk_dev);
+       if (ret)
+               return ret;
+
+       clk_rate = clk_get_rate(&clk_dev);
+
+       clk_free(&clk_dev);
+
+       return clk_rate;
 }
 
-static const struct udevice_id sam9x5_periph_clk_match[] = {
-       { .compatible = "atmel,at91sam9x5-clk-peripheral" },
-       {}
+static struct clk_ops periph_clk_ops = {
+       .of_xlate = at91_clk_of_xlate,
+       .enable = periph_clk_enable,
+       .get_rate = periph_get_rate,
 };
 
-U_BOOT_DRIVER(sam9x5_periph_clk) = {
-       .name = "sam9x5-periph-clk",
-       .id = UCLASS_CLK,
-       .of_match = sam9x5_periph_clk_match,
-       .bind = sam9x5_periph_clk_bind,
-       .probe = sam9x5_periph_clk_probe,
+U_BOOT_DRIVER(clk_periph) = {
+       .name   = "periph-clk",
+       .id     = UCLASS_CLK,
        .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
-       .ops = &sam9x5_periph_clk_ops,
+       .probe = at91_clk_probe,
+       .ops    = &periph_clk_ops,
 };
 
 
 #define SYSTEM_MAX_ID          31
 
+/**
+ * at91_system_clk_bind() - for the system clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+static int at91_system_clk_bind(struct udevice *dev)
+{
+       return at91_clk_sub_device_bind(dev, "system-clk");
+}
+
+static const struct udevice_id at91_system_clk_match[] = {
+       { .compatible = "atmel,at91rm9200-clk-system" },
+       {}
+};
+
+U_BOOT_DRIVER(at91_system_clk) = {
+       .name = "at91-system-clk",
+       .id = UCLASS_MISC,
+       .of_match = at91_system_clk_match,
+       .bind = at91_system_clk_bind,
+};
+
+/*----------------------------------------------------------*/
+
 static inline int is_pck(int id)
 {
        return (id >= 8) && (id <= 15);
 }
 
-static int at91_system_clk_enable(struct clk *clk)
+static int system_clk_enable(struct clk *clk)
 {
        struct pmc_platdata *plat = dev_get_platdata(clk->dev);
        struct at91_pmc *pmc = plat->reg_base;
        return 0;
 }
 
-static struct clk_ops at91_system_clk_ops = {
-       .enable = at91_system_clk_enable,
+static struct clk_ops system_clk_ops = {
+       .of_xlate = at91_clk_of_xlate,
+       .enable = system_clk_enable,
 };
 
-static int at91_system_clk_bind(struct udevice *dev)
-{
-       return at91_pmc_clk_node_bind(dev);
-}
-
-static int at91_system_clk_probe(struct udevice *dev)
-{
-       return at91_pmc_core_probe(dev);
-}
-
-static const struct udevice_id at91_system_clk_match[] = {
-       { .compatible = "atmel,at91rm9200-clk-system" },
-       {}
-};
-
-U_BOOT_DRIVER(at91_system_clk) = {
-       .name = "at91-system-clk",
+U_BOOT_DRIVER(system_clk) = {
+       .name = "system-clk",
        .id = UCLASS_CLK,
-       .of_match = at91_system_clk_match,
-       .bind = at91_system_clk_bind,
-       .probe = at91_system_clk_probe,
+       .probe = at91_clk_probe,
        .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
-       .ops = &at91_system_clk_ops,
+       .ops = &system_clk_ops,
 };
 
        .of_match = at91_pmc_match,
 };
 
+/*---------------------------------------------------------*/
+
 int at91_pmc_core_probe(struct udevice *dev)
 {
        struct pmc_platdata *plat = dev_get_platdata(dev);
        return 0;
 }
 
-int at91_pmc_clk_node_bind(struct udevice *dev)
+/**
+ * at91_clk_sub_device_bind() - for the at91 clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+int at91_clk_sub_device_bind(struct udevice *dev, const char *drv_name)
 {
        const void *fdt = gd->fdt_blob;
        int offset = dev->of_offset;
+       bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
        const char *name;
        int ret;
 
        for (offset = fdt_first_subnode(fdt, offset);
             offset > 0;
             offset = fdt_next_subnode(fdt, offset)) {
+               if (pre_reloc_only &&
+                   !fdt_getprop(fdt, offset, "u-boot,dm-pre-reloc", NULL))
+                       continue;
+               /*
+                * If this node has "compatible" property, this is not
+                * a clock sub-node, but a normal device. skip.
+                */
+               fdt_get_property(fdt, offset, "compatible", &ret);
+               if (ret >= 0)
+                       continue;
+
+               if (ret != -FDT_ERR_NOTFOUND)
+                       return ret;
+
                name = fdt_get_name(fdt, offset, NULL);
                if (!name)
                        return -EINVAL;
-
-               ret = device_bind_driver_to_node(dev, "clk", name,
+               ret = device_bind_driver_to_node(dev, drv_name, name,
                                                 offset, NULL);
                if (ret)
                        return ret;
        return 0;
 }
 
-U_BOOT_DRIVER(clk_generic) = {
-       .id     = UCLASS_CLK,
-       .name   = "clk",
-};
+int at91_clk_of_xlate(struct clk *clk, struct fdtdec_phandle_args *args)
+{
+       int periph;
+
+       if (args->args_count) {
+               debug("Invalid args_count: %d\n", args->args_count);
+               return -EINVAL;
+       }
+
+       periph = fdtdec_get_uint(gd->fdt_blob, clk->dev->of_offset, "reg", -1);
+       if (periph < 0)
+               return -EINVAL;
+
+       clk->id = periph;
+
+       return 0;
+}
+
+int at91_clk_probe(struct udevice *dev)
+{
+       struct udevice *dev_periph_container, *dev_pmc;
+       struct pmc_platdata *plat = dev_get_platdata(dev);
+
+       dev_periph_container = dev_get_parent(dev);
+       dev_pmc = dev_get_parent(dev_periph_container);
+
+       plat->reg_base = (struct at91_pmc *)dev_get_addr_ptr(dev_pmc);
+
+       return 0;
+}
 
 };
 
 int at91_pmc_core_probe(struct udevice *dev);
-int at91_pmc_clk_node_bind(struct udevice *dev);
+int at91_clk_sub_device_bind(struct udevice *dev, const char *drv_name);
+
+int at91_clk_of_xlate(struct clk *clk, struct fdtdec_phandle_args *args);
+int at91_clk_probe(struct udevice *dev);
 
 #endif