1 /* 2 * OMAP APLL clock support 3 * 4 * Copyright (C) 2013 Texas Instruments, Inc. 5 * 6 * J Keerthy <j-keerthy@ti.com> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 * 12 * This program is distributed "as is" WITHOUT ANY WARRANTY of any 13 * kind, whether express or implied; without even the implied warranty 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 */ 17 18 #include <linux/clk-provider.h> 19 #include <linux/module.h> 20 #include <linux/slab.h> 21 #include <linux/io.h> 22 #include <linux/err.h> 23 #include <linux/string.h> 24 #include <linux/log2.h> 25 #include <linux/of.h> 26 #include <linux/of_address.h> 27 #include <linux/clk/ti.h> 28 #include <linux/delay.h> 29 30 #define APLL_FORCE_LOCK 0x1 31 #define APLL_AUTO_IDLE 0x2 32 #define MAX_APLL_WAIT_TRIES 1000000 33 34 #undef pr_fmt 35 #define pr_fmt(fmt) "%s: " fmt, __func__ 36 37 static int dra7_apll_enable(struct clk_hw *hw) 38 { 39 struct clk_hw_omap *clk = to_clk_hw_omap(hw); 40 int r = 0, i = 0; 41 struct dpll_data *ad; 42 const char *clk_name; 43 u8 state = 1; 44 u32 v; 45 46 ad = clk->dpll_data; 47 if (!ad) 48 return -EINVAL; 49 50 clk_name = __clk_get_name(clk->hw.clk); 51 52 state <<= __ffs(ad->idlest_mask); 53 54 /* Check is already locked */ 55 v = ti_clk_ll_ops->clk_readl(ad->idlest_reg); 56 57 if ((v & ad->idlest_mask) == state) 58 return r; 59 60 v = ti_clk_ll_ops->clk_readl(ad->control_reg); 61 v &= ~ad->enable_mask; 62 v |= APLL_FORCE_LOCK << __ffs(ad->enable_mask); 63 ti_clk_ll_ops->clk_writel(v, ad->control_reg); 64 65 state <<= __ffs(ad->idlest_mask); 66 67 while (1) { 68 v = ti_clk_ll_ops->clk_readl(ad->idlest_reg); 69 if ((v & ad->idlest_mask) == state) 70 break; 71 if (i > MAX_APLL_WAIT_TRIES) 72 break; 73 i++; 74 udelay(1); 75 } 76 77 if (i == MAX_APLL_WAIT_TRIES) { 78 pr_warn("clock: %s failed transition to '%s'\n", 79 clk_name, (state) ? "locked" : "bypassed"); 80 } else { 81 pr_debug("clock: %s transition to '%s' in %d loops\n", 82 clk_name, (state) ? "locked" : "bypassed", i); 83 84 r = 0; 85 } 86 87 return r; 88 } 89 90 static void dra7_apll_disable(struct clk_hw *hw) 91 { 92 struct clk_hw_omap *clk = to_clk_hw_omap(hw); 93 struct dpll_data *ad; 94 u8 state = 1; 95 u32 v; 96 97 ad = clk->dpll_data; 98 99 state <<= __ffs(ad->idlest_mask); 100 101 v = ti_clk_ll_ops->clk_readl(ad->control_reg); 102 v &= ~ad->enable_mask; 103 v |= APLL_AUTO_IDLE << __ffs(ad->enable_mask); 104 ti_clk_ll_ops->clk_writel(v, ad->control_reg); 105 } 106 107 static int dra7_apll_is_enabled(struct clk_hw *hw) 108 { 109 struct clk_hw_omap *clk = to_clk_hw_omap(hw); 110 struct dpll_data *ad; 111 u32 v; 112 113 ad = clk->dpll_data; 114 115 v = ti_clk_ll_ops->clk_readl(ad->control_reg); 116 v &= ad->enable_mask; 117 118 v >>= __ffs(ad->enable_mask); 119 120 return v == APLL_AUTO_IDLE ? 0 : 1; 121 } 122 123 static u8 dra7_init_apll_parent(struct clk_hw *hw) 124 { 125 return 0; 126 } 127 128 static const struct clk_ops apll_ck_ops = { 129 .enable = &dra7_apll_enable, 130 .disable = &dra7_apll_disable, 131 .is_enabled = &dra7_apll_is_enabled, 132 .get_parent = &dra7_init_apll_parent, 133 }; 134 135 static void __init omap_clk_register_apll(struct clk_hw *hw, 136 struct device_node *node) 137 { 138 struct clk_hw_omap *clk_hw = to_clk_hw_omap(hw); 139 struct dpll_data *ad = clk_hw->dpll_data; 140 struct clk *clk; 141 142 ad->clk_ref = of_clk_get(node, 0); 143 ad->clk_bypass = of_clk_get(node, 1); 144 145 if (IS_ERR(ad->clk_ref) || IS_ERR(ad->clk_bypass)) { 146 pr_debug("clk-ref or clk-bypass for %s not ready, retry\n", 147 node->name); 148 if (!ti_clk_retry_init(node, hw, omap_clk_register_apll)) 149 return; 150 151 goto cleanup; 152 } 153 154 clk = clk_register(NULL, &clk_hw->hw); 155 if (!IS_ERR(clk)) { 156 of_clk_add_provider(node, of_clk_src_simple_get, clk); 157 kfree(clk_hw->hw.init->parent_names); 158 kfree(clk_hw->hw.init); 159 return; 160 } 161 162 cleanup: 163 kfree(clk_hw->dpll_data); 164 kfree(clk_hw->hw.init->parent_names); 165 kfree(clk_hw->hw.init); 166 kfree(clk_hw); 167 } 168 169 static void __init of_dra7_apll_setup(struct device_node *node) 170 { 171 struct dpll_data *ad = NULL; 172 struct clk_hw_omap *clk_hw = NULL; 173 struct clk_init_data *init = NULL; 174 const char **parent_names = NULL; 175 int i; 176 177 ad = kzalloc(sizeof(*ad), GFP_KERNEL); 178 clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); 179 init = kzalloc(sizeof(*init), GFP_KERNEL); 180 if (!ad || !clk_hw || !init) 181 goto cleanup; 182 183 clk_hw->dpll_data = ad; 184 clk_hw->hw.init = init; 185 clk_hw->flags = MEMMAP_ADDRESSING; 186 187 init->name = node->name; 188 init->ops = &apll_ck_ops; 189 190 init->num_parents = of_clk_get_parent_count(node); 191 if (init->num_parents < 1) { 192 pr_err("dra7 apll %s must have parent(s)\n", node->name); 193 goto cleanup; 194 } 195 196 parent_names = kzalloc(sizeof(char *) * init->num_parents, GFP_KERNEL); 197 if (!parent_names) 198 goto cleanup; 199 200 for (i = 0; i < init->num_parents; i++) 201 parent_names[i] = of_clk_get_parent_name(node, i); 202 203 init->parent_names = parent_names; 204 205 ad->control_reg = ti_clk_get_reg_addr(node, 0); 206 ad->idlest_reg = ti_clk_get_reg_addr(node, 1); 207 208 if (!ad->control_reg || !ad->idlest_reg) 209 goto cleanup; 210 211 ad->idlest_mask = 0x1; 212 ad->enable_mask = 0x3; 213 214 omap_clk_register_apll(&clk_hw->hw, node); 215 return; 216 217 cleanup: 218 kfree(parent_names); 219 kfree(ad); 220 kfree(clk_hw); 221 kfree(init); 222 } 223 CLK_OF_DECLARE(dra7_apll_clock, "ti,dra7-apll-clock", of_dra7_apll_setup); 224