@@ -191,7 +191,7 @@ static void __init hi6220_clk_sys_init(struct device_node *np)
hisi_clk_register_mux(hi6220_mux_clks_sys,
ARRAY_SIZE(hi6220_mux_clks_sys), clk_data);
- hi6220_clk_register_divider(hi6220_div_clks_sys,
+ hi6220_clk_register_divider(NULL, hi6220_div_clks_sys,
ARRAY_SIZE(hi6220_div_clks_sys), clk_data);
}
CLK_OF_DECLARE_DRIVER(hi6220_clk_sys, "hisilicon,hi6220-sysctrl", hi6220_clk_sys_init);
@@ -248,7 +248,7 @@ static void __init hi6220_clk_media_init(struct device_node *np)
hisi_clk_register_mux(hi6220_mux_clks_media,
ARRAY_SIZE(hi6220_mux_clks_media), clk_data);
- hi6220_clk_register_divider(hi6220_div_clks_media,
+ hi6220_clk_register_divider(NULL, hi6220_div_clks_media,
ARRAY_SIZE(hi6220_div_clks_media), clk_data);
}
CLK_OF_DECLARE_DRIVER(hi6220_clk_media, "hisilicon,hi6220-mediactrl", hi6220_clk_media_init);
@@ -279,7 +279,7 @@ static void __init hi6220_clk_power_init(struct device_node *np)
hisi_clk_register_gate(hi6220_gate_clks_power,
ARRAY_SIZE(hi6220_gate_clks_power), clk_data);
- hi6220_clk_register_divider(hi6220_div_clks_power,
+ hi6220_clk_register_divider(NULL, hi6220_div_clks_power,
ARRAY_SIZE(hi6220_div_clks_power), clk_data);
}
CLK_OF_DECLARE(hi6220_clk_power, "hisilicon,hi6220-pmctrl", hi6220_clk_power_init);
@@ -58,6 +58,13 @@ EXPORT_SYMBOL_GPL(hisi_clk_init);
void hisi_clk_free(struct device_node *np, struct hisi_clock_data *data)
{
+ if (data->clks) {
+ if (data->clks->fixed_rate_clks_num)
+ hisi_clk_unregister_fixed_rate(data);
+ if (data->clks->fixed_factor_clks_num)
+ hisi_clk_unregister_fixed_factor(data);
+ }
+
of_clk_del_provider(np);
kfree(data->clk_data.clks);
kfree(data);
@@ -260,7 +267,7 @@ int hisi_clk_register_gate(const struct hisi_gate_clock *clks,
}
EXPORT_SYMBOL_GPL(hisi_clk_register_gate);
-void hisi_clk_register_gate_sep(const struct hisi_gate_clock *clks,
+int hisi_clk_register_gate_sep(const struct hisi_gate_clock *clks,
int nums, struct hisi_clock_data *data)
{
struct clk *clk;
@@ -286,11 +293,14 @@ void hisi_clk_register_gate_sep(const struct hisi_gate_clock *clks,
data->clk_data.clks[clks[i].id] = clk;
}
+
+ return 0;
}
EXPORT_SYMBOL_GPL(hisi_clk_register_gate_sep);
-void __init hi6220_clk_register_divider(const struct hi6220_divider_clock *clks,
- int nums, struct hisi_clock_data *data)
+int hi6220_clk_register_divider(struct device *dev,
+ const struct hi6220_divider_clock *clks,
+ int nums, struct hisi_clock_data *data)
{
struct clk *clk;
void __iomem *base = data->base;
@@ -308,7 +318,7 @@ void __init hi6220_clk_register_divider(const struct hi6220_divider_clock *clks,
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n",
__func__, clks[i].name);
- continue;
+ return PTR_ERR(clk);
}
if (clks[i].alias)
@@ -316,4 +326,118 @@ void __init hi6220_clk_register_divider(const struct hi6220_divider_clock *clks,
data->clk_data.clks[clks[i].id] = clk;
}
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hi6220_clk_register_divider);
+
+int hisi_clk_register(struct device *dev, const struct hisi_clocks *clks,
+ struct hisi_clock_data *data)
+{
+ int ret;
+
+#define do_hisi_clk_register(type) do { \
+ if (clks->type##_clks_num) { \
+ ret = hisi_clk_register_##type(dev, clks->type##_clks, \
+ clks->type##_clks_num, data); \
+ if (ret) \
+ return ret; \
+ } \
+} while (0)
+
+ do_hisi_clk_register(mux);
+ do_hisi_clk_register(phase);
+ do_hisi_clk_register(divider);
+ do_hisi_clk_register(gate);
+ do_hisi_clk_register(gate_sep);
+
+ if (clks->clk_register_customized && clks->customized_clks_num) {
+ ret = clks->clk_register_customized(dev, clks->customized_clks,
+ clks->customized_clks_num, data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hisi_clk_register);
+
+int hisi_clk_early_init(struct device_node *np, const struct hisi_clocks *clks)
+{
+ struct hisi_clock_data *data;
+ int ret;
+
+ data = hisi_clk_init(np, clks->nr);
+ if (!data)
+ return -ENOMEM;
+ data->clks = clks;
+
+ ret = hisi_clk_register_fixed_rate(clks->fixed_rate_clks,
+ clks->fixed_rate_clks_num, data);
+ if (ret)
+ goto err;
+
+ ret = hisi_clk_register_fixed_factor(clks->fixed_factor_clks,
+ clks->fixed_factor_clks_num, data);
+ if (ret)
+ goto err;
+
+ np->data = data;
+ return 0;
+
+err:
+ hisi_clk_free(np, data);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(hisi_clk_early_init);
+
+int hisi_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct hisi_clocks *clks;
+ struct hisi_clock_data *data;
+ int ret;
+
+ clks = of_device_get_match_data(dev);
+ if (!clks)
+ return -ENOENT;
+
+ if (!np->data) {
+ ret = hisi_clk_early_init(np, clks);
+ if (ret)
+ return ret;
+ }
+
+ data = np->data;
+ np->data = NULL;
+
+ if (clks->prologue) {
+ ret = clks->prologue(dev, data);
+ if (ret)
+ goto err;
+ }
+
+ ret = hisi_clk_register(dev, clks, data);
+ if (ret)
+ goto err;
+
+ platform_set_drvdata(pdev, data);
+ return 0;
+
+err:
+ hisi_clk_free(np, data);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(hisi_clk_probe);
+
+int hisi_clk_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct hisi_clock_data *data = platform_get_drvdata(pdev);
+
+ hisi_clk_free(np, data);
+ return 0;
}
+EXPORT_SYMBOL_GPL(hisi_clk_remove);
@@ -17,10 +17,22 @@
#include <linux/spinlock.h>
struct platform_device;
+struct hisi_clocks;
+/*
+ * (Virtual) fixed clocks, often depended by crucial peripherals, require
+ * early initialization before device probing, thus cannot use devm APIs.
+ * Otherwise, kernel will defer those peripherals, causing boot failure.
+ *
+ * fixed_rate and fixed_factor clocks are driver-managed. They are freed by
+ * `hisi_clk_free` altogether.
+ *
+ * Other clocks are devm-managed.
+ */
struct hisi_clock_data {
struct clk_onecell_data clk_data;
void __iomem *base;
+ const struct hisi_clocks *clks;
};
struct hisi_fixed_rate_clock {
@@ -103,6 +115,38 @@ struct hisi_gate_clock {
const char *alias;
};
+struct hisi_clocks {
+ int nr;
+
+ int (*prologue)(struct device *dev, struct hisi_clock_data *data);
+
+ const struct hisi_fixed_rate_clock *fixed_rate_clks;
+ int fixed_rate_clks_num;
+
+ const struct hisi_fixed_factor_clock *fixed_factor_clks;
+ int fixed_factor_clks_num;
+
+ const struct hisi_mux_clock *mux_clks;
+ int mux_clks_num;
+
+ const struct hisi_phase_clock *phase_clks;
+ int phase_clks_num;
+
+ const struct hisi_divider_clock *divider_clks;
+ int divider_clks_num;
+
+ const struct hisi_gate_clock *gate_clks;
+ int gate_clks_num;
+
+ const struct hisi_gate_clock *gate_sep_clks;
+ int gate_sep_clks_num;
+
+ const void *customized_clks;
+ int customized_clks_num;
+ int (*clk_register_customized)(struct device *dev, const void *clks,
+ int num, struct hisi_clock_data *data);
+};
+
struct clk *hisi_register_clkgate_sep(struct device *, const char *,
const char *, unsigned long,
void __iomem *, u8,
@@ -118,6 +162,7 @@ int hisi_clk_register_fixed_rate(const struct hisi_fixed_rate_clock *,
int, struct hisi_clock_data *);
int hisi_clk_register_fixed_factor(const struct hisi_fixed_factor_clock *,
int, struct hisi_clock_data *);
+
int hisi_clk_register_mux(const struct hisi_mux_clock *, int,
struct hisi_clock_data *);
struct clk *clk_register_hisi_phase(struct device *dev,
@@ -130,12 +175,22 @@ int hisi_clk_register_divider(const struct hisi_divider_clock *,
int, struct hisi_clock_data *);
int hisi_clk_register_gate(const struct hisi_gate_clock *,
int, struct hisi_clock_data *);
-void hisi_clk_register_gate_sep(const struct hisi_gate_clock *,
- int, struct hisi_clock_data *);
-void hi6220_clk_register_divider(const struct hi6220_divider_clock *,
- int, struct hisi_clock_data *);
+int hisi_clk_register_gate_sep(const struct hisi_gate_clock *clks,
+ int nums, struct hisi_clock_data *data);
+int hi6220_clk_register_divider(struct device *dev,
+ const struct hi6220_divider_clock *clks,
+ int nums, struct hisi_clock_data *data);
+
+int hisi_clk_register(struct device *dev, const struct hisi_clocks *clks,
+ struct hisi_clock_data *data);
+
+/* helper functions for platform driver */
+
+int hisi_clk_early_init(struct device_node *np, const struct hisi_clocks *clks);
+int hisi_clk_probe(struct platform_device *pdev);
+int hisi_clk_remove(struct platform_device *pdev);
-#define hisi_clk_unregister(type) \
+#define hisi_clk_unregister_fn(type) \
static inline \
void hisi_clk_unregister_##type(const struct hisi_##type##_clock *clks, \
int nums, struct hisi_clock_data *data) \
@@ -149,10 +204,10 @@ void hisi_clk_unregister_##type(const struct hisi_##type##_clock *clks, \
} \
}
-hisi_clk_unregister(fixed_rate)
-hisi_clk_unregister(fixed_factor)
-hisi_clk_unregister(mux)
-hisi_clk_unregister(divider)
-hisi_clk_unregister(gate)
+hisi_clk_unregister_fn(fixed_rate)
+hisi_clk_unregister_fn(fixed_factor)
+hisi_clk_unregister_fn(mux)
+hisi_clk_unregister_fn(divider)
+hisi_clk_unregister_fn(gate)
#endif /* __HISI_CLK_H */
@@ -22,4 +22,9 @@ struct hisi_crg_dev {
const struct hisi_crg_funcs *funcs;
};
+/* helper functions for platform driver */
+
+int hisi_crg_probe(struct platform_device *pdev);
+int hisi_crg_remove(struct platform_device *pdev);
+
#endif /* __HISI_CRG_H */
@@ -5,12 +5,16 @@
* Copyright (c) 2015-2016 HiSilicon Technologies Co., Ltd.
*/
+#include <linux/device.h>
#include <linux/io.h>
+#include <linux/kernel.h>
#include <linux/of_address.h>
-#include <linux/platform_device.h>
+#include <linux/of_device.h>
#include <linux/reset-controller.h>
-#include <linux/slab.h>
#include <linux/spinlock.h>
+
+#include "clk.h"
+#include "crg.h"
#include "reset.h"
#define HISI_RESET_BIT_MASK 0x1f
@@ -116,3 +120,60 @@ void hisi_reset_exit(struct hisi_reset_controller *rstc)
reset_controller_unregister(&rstc->rcdev);
}
EXPORT_SYMBOL_GPL(hisi_reset_exit);
+
+int hisi_crg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct hisi_clocks *clks;
+ struct hisi_crg_dev *crg;
+ int ret;
+
+ clks = of_device_get_match_data(dev);
+ if (!clks)
+ return -ENOENT;
+
+ crg = devm_kmalloc(dev, sizeof(*crg), GFP_KERNEL);
+ if (!crg)
+ return -ENOMEM;
+
+ if (!np->data) {
+ ret = hisi_clk_early_init(np, clks);
+ if (ret)
+ return ret;
+ }
+
+ crg->clk_data = np->data;
+ np->data = NULL;
+
+ ret = hisi_clk_register(dev, clks, crg->clk_data);
+ if (ret)
+ goto err;
+
+ crg->rstc = hisi_reset_init(pdev);
+ if (!crg->rstc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, crg);
+ return 0;
+
+err:
+ hisi_clk_free(np, crg->clk_data);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(hisi_crg_probe);
+
+int hisi_crg_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct hisi_crg_dev *crg = platform_get_drvdata(pdev);
+
+ hisi_reset_exit(crg->rstc);
+ crg->funcs->unregister_clks(pdev);
+ hisi_clk_free(np, crg->clk_data);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hisi_crg_remove);