@@ -1,4 +1,4 @@
obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o
obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \
- clk-mux.o clk-divider.o
+ clk-mux.o clk-divider.o clk-test.o
new file mode 100644
@@ -0,0 +1,218 @@
+#include <linux/spinlock.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+
+/*
+ * We build a fixed rate source clock with two cascaded 2bit dividers:
+ *
+ * f1 -> div1 -> div2
+ *
+ * and try set the rate of div2 from 0Hz to f1 + 1 Hz.
+ *
+ * Note that we expect the core to divide the dividers one after
+ * another:
+ *
+ * fout = (f1 / div1) / div
+ *
+ * and not like this:
+ *
+ * fout = f1 / (div1 * div2)
+ * This has in some cases different results due to integer maths
+ */
+
+static unsigned long div1_reg, div2_reg;
+static int div1_width = 3;
+static int div2_width = 3;
+
+#define MAX_DIV(x) (1 << (x))
+
+static struct clk *f1, *div1, *div2;
+
+static DEFINE_SPINLOCK(clk_test_lock);
+
+#define FIXED_RATE 1000
+
+static int check_rounded_rate_div2(unsigned long want, unsigned long rounded)
+{
+ int i, j;
+ unsigned long best = 0, now;
+
+ if (want > FIXED_RATE) {
+ best = FIXED_RATE;
+ goto found;
+ }
+
+ for (i = 1; i <= MAX_DIV(div1_width); i++) {
+ for (j = 1; j <= MAX_DIV(div2_width); j++) {
+ now = FIXED_RATE / i / j;
+ if (now <= want && now > best)
+ best = now;
+ }
+ }
+
+ if (!best)
+ best = (FIXED_RATE / MAX_DIV(div1_width)) / MAX_DIV(div2_width);
+found:
+ if (rounded != best) {
+ printk("clk-test: wanted rate %ld, best result would be %ld,"
+ " but we have %ld\n",
+ want, best, rounded);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int check_real_rate_div2(unsigned long rate)
+{
+ unsigned long realrate;
+
+ realrate = FIXED_RATE / (div1_reg + 1) / (div2_reg + 1);
+
+ if (rate != realrate) {
+ printk("clk-test: divider returns rate %ld, but instead has %ld\n",
+ rate, realrate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int set_rate_test_div2(void)
+{
+ unsigned long i, rate, rounded;
+ int ret, errors = 0;
+
+ for (i = 0; i < FIXED_RATE + 1; i++) {
+ rounded = clk_round_rate(div2, i);
+ ret = check_rounded_rate_div2(i, rounded);
+ if (ret)
+ errors++;
+ ret = clk_set_rate(div2, i);
+ if (ret) {
+ printk("%s: setting rate of div2 to %ld failed with %d\n",
+ __func__, i, ret);
+ errors++;
+ }
+
+ rate = clk_get_rate(div2);
+
+ if (rounded != rate) {
+ printk("clk_test: wanted %ld, core rounded to %ld, have now: %ld\n",
+ i, rounded, rate);
+ errors++;
+ }
+
+ ret = check_real_rate_div2(rate);
+ if (ret)
+ errors++;
+ }
+
+ return errors;
+}
+
+static int check_rounded_rate_div1(unsigned long want, unsigned long rounded)
+{
+ unsigned long best;
+ int i;
+
+ for (i = 1; i <= MAX_DIV(div1_width); i++)
+ if (FIXED_RATE / i <= want) {
+ best = FIXED_RATE / i;
+ goto found;
+ }
+
+ best = FIXED_RATE / MAX_DIV(div1_width);
+found:
+ if (rounded != best) {
+ printk("clk-test: wanted rate %ld, best result would be %ld,"
+ " but we have %ld\n",
+ want, best, rounded);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int check_real_rate_div1(unsigned long rate)
+{
+ unsigned long realrate;
+
+ realrate = FIXED_RATE / (div1_reg + 1);
+
+ if (rate != realrate) {
+ printk("clk-test: divider returns rate %ld, but instead has %ld\n",
+ rate, realrate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int set_rate_test_div1(void)
+{
+ unsigned long i, rate, rounded;
+ int ret, errors = 0;
+
+ for (i = 0; i < FIXED_RATE + 1; i++) {
+ rounded = clk_round_rate(div1, i);
+ ret = check_rounded_rate_div1(i, rounded);
+ if (ret)
+ errors++;
+ ret = clk_set_rate(div1, i);
+ if (ret) {
+ printk("%s: setting rate of div2 to %ld failed with %d\n",
+ __func__, i, ret);
+ errors++;
+ }
+
+ rate = clk_get_rate(div1);
+
+ if (rounded != rate) {
+ printk("clk_test: wanted %ld, core rounded to %ld, have now: %ld\n",
+ i, rounded, rate);
+ errors++;
+ }
+
+ ret = check_real_rate_div1(rate);
+ if (ret)
+ errors++;
+ }
+
+ return errors;
+}
+
+/* The core does not like this flag when the parent is not adjustable */
+#define DIV1_FLAGS CLK_SET_RATE_PARENT
+
+static int clk_test_init(void)
+{
+ int errors = 0;
+
+ f1 = clk_register_fixed_rate(NULL, "f1", NULL, CLK_IS_ROOT, FIXED_RATE);
+ div1 = clk_register_divider(NULL, "div1", "f1", DIV1_FLAGS,
+ &div1_reg, 0, div1_width, 0, &clk_test_lock);
+ div2 = clk_register_divider(NULL, "div2", "div1", CLK_SET_RATE_PARENT,
+ &div2_reg, 0, div2_width, 0, &clk_test_lock);
+
+ if (!f1 || !div1 || !div2) {
+ printk("clk-test: failed to register clocks\n");
+ return -EINVAL;
+ }
+
+ /* First pass: no cascaded divider, only f1 -> div1 */
+ errors = set_rate_test_div1();
+ /* Second pass: cascaded divider, f1 -> div1 -> div2 */
+ errors += set_rate_test_div2();
+
+ printk("clk-test: finished with %d errors\n", errors);
+#if 0
+ /* we should be able to unregister clocks */
+ clk_unregister(f1);
+ clk_unregister(div1);
+ clk_unregister(div2);
+#endif
+ return -EINVAL;
+}
+subsys_initcall(clk_test_init);