summaryrefslogtreecommitdiff
path: root/drivers/clk/ux500/u8500_of_clk.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/ux500/u8500_of_clk.c')
-rw-r--r--drivers/clk/ux500/u8500_of_clk.c87
1 files changed, 80 insertions, 7 deletions
diff --git a/drivers/clk/ux500/u8500_of_clk.c b/drivers/clk/ux500/u8500_of_clk.c
index 6aa89645f5fa..8e2f6c65db2a 100644
--- a/drivers/clk/ux500/u8500_of_clk.c
+++ b/drivers/clk/ux500/u8500_of_clk.c
@@ -17,6 +17,7 @@
static struct clk *prcc_pclk[(PRCC_NUM_PERIPH_CLUSTERS + 1) * PRCC_PERIPHS_PER_CLUSTER];
static struct clk *prcc_kclk[(PRCC_NUM_PERIPH_CLUSTERS + 1) * PRCC_PERIPHS_PER_CLUSTER];
+static struct clk_hw *clkout_clk[2];
#define PRCC_SHOW(clk, base, bit) \
clk[(base * PRCC_PERIPHS_PER_CLUSTER) + bit]
@@ -56,6 +57,71 @@ static struct clk_hw_onecell_data u8500_prcmu_hw_clks = {
.num = PRCMU_NUM_CLKS,
};
+/* Essentially names for the first PRCMU_CLKSRC_* defines */
+static const char * const u8500_clkout_parents[] = {
+ "clk38m_to_clkgen",
+ "aclk",
+ /* Just called "sysclk" in documentation */
+ "ab8500_sysclk",
+ "lcdclk",
+ "sdmmcclk",
+ "tvclk",
+ "timclk",
+ /* CLK009 is not implemented, add it if you need it */
+ "clk009",
+};
+
+static struct clk_hw *ux500_clkout_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ u32 id, source, divider;
+ struct clk_hw *clkout;
+
+ if (clkspec->args_count != 3)
+ return ERR_PTR(-EINVAL);
+
+ id = clkspec->args[0];
+ source = clkspec->args[1];
+ divider = clkspec->args[2];
+
+ if (id > 1) {
+ pr_err("%s: invalid clkout ID %d\n", __func__, id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (clkout_clk[id]) {
+ pr_info("%s: clkout%d already registered, not reconfiguring\n",
+ __func__, id + 1);
+ return clkout_clk[id];
+ }
+
+ if (source > 7) {
+ pr_err("%s: invalid source ID %d\n", __func__, source);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (divider == 0 || divider > 63) {
+ pr_err("%s: invalid divider %d\n", __func__, divider);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pr_debug("registering clkout%d with source %d and divider %d\n",
+ id + 1, source, divider);
+
+ clkout = clk_reg_prcmu_clkout(id ? "clkout2" : "clkout1",
+ u8500_clkout_parents,
+ ARRAY_SIZE(u8500_clkout_parents),
+ source, divider);
+ if (IS_ERR(clkout)) {
+ pr_err("failed to register clkout%d\n", id + 1);
+ return ERR_CAST(clkout);
+ }
+
+ clkout_clk[id] = clkout;
+
+ return clkout;
+}
+
static void u8500_clk_init(struct device_node *np)
{
struct prcmu_fw_version *fw_version;
@@ -99,7 +165,17 @@ static void u8500_clk_init(struct device_node *np)
clk_reg_prcmu_gate("ddr_pll", NULL, PRCMU_PLLDDR,
CLK_IGNORE_UNUSED);
- /* FIXME: Add sys, ulp and int clocks here. */
+ /*
+ * Read-only clocks that only return their current rate, only used
+ * as parents to other clocks and not visible in the device tree.
+ * clk38m_to_clkgen is the same as the SYSCLK, i.e. the root clock.
+ */
+ clk_reg_prcmu_rate("clk38m_to_clkgen", NULL, PRCMU_SYSCLK,
+ CLK_IGNORE_UNUSED);
+ clk_reg_prcmu_rate("aclk", NULL, PRCMU_ACLK,
+ CLK_IGNORE_UNUSED);
+
+ /* TODO: add CLK009 if needed */
rtc_clk = clk_register_fixed_rate(NULL, "rtc32k", "NULL",
CLK_IGNORE_UNUSED,
@@ -223,12 +299,6 @@ static void u8500_clk_init(struct device_node *np)
twd_clk = clk_register_fixed_factor(NULL, "smp_twd", "armss",
CLK_IGNORE_UNUSED, 1, 2);
- /*
- * FIXME: Add special handled PRCMU clocks here:
- * 1. clkout0yuv, use PRCMU as parent + need regulator + pinctrl.
- * 2. ab9540_clkout1yuv, see clkout0yuv
- */
-
/* PRCC P-clocks */
clk = clk_reg_prcc_pclk("p1_pclk0", "per1clk", bases[CLKRST1_INDEX],
BIT(0), 0);
@@ -526,6 +596,9 @@ static void u8500_clk_init(struct device_node *np)
of_clk_add_hw_provider(child, of_clk_hw_onecell_get,
&u8500_prcmu_hw_clks);
+ if (of_node_name_eq(child, "clkout-clock"))
+ of_clk_add_hw_provider(child, ux500_clkout_get, NULL);
+
if (of_node_name_eq(child, "prcc-periph-clock"))
of_clk_add_provider(child, ux500_twocell_get, prcc_pclk);