diff options
| author | Tero Kristo <t-kristo@ti.com> | 2014-02-24 09:06:34 -0500 |
|---|---|---|
| committer | Tero Kristo <t-kristo@ti.com> | 2014-05-28 05:30:02 -0400 |
| commit | 4d008589e271e28eae728eef7f5fb1f658f12b9f (patch) | |
| tree | 8b30eee15659aa978c5b452b65647662cc90c8c3 | |
| parent | aa76fcf473f6bfa839f37f77b6fdb71f0fb88d8f (diff) | |
CLK: TI: APLL: add support for omap2 aplls
This patch adds support for omap2 type aplls, which have gating and
autoidle functionality.
Signed-off-by: Tero Kristo <t-kristo@ti.com>
| -rw-r--r-- | Documentation/devicetree/bindings/clock/ti/apll.txt | 24 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/clock.h | 11 | ||||
| -rw-r--r-- | drivers/clk/ti/apll.c | 181 | ||||
| -rw-r--r-- | include/linux/clk/ti.h | 21 |
4 files changed, 220 insertions, 17 deletions
diff --git a/Documentation/devicetree/bindings/clock/ti/apll.txt b/Documentation/devicetree/bindings/clock/ti/apll.txt index 7faf5a68b3be..ade4dd4c30f0 100644 --- a/Documentation/devicetree/bindings/clock/ti/apll.txt +++ b/Documentation/devicetree/bindings/clock/ti/apll.txt | |||
| @@ -14,18 +14,32 @@ a subtype of a DPLL [2], although a simplified one at that. | |||
| 14 | [2] Documentation/devicetree/bindings/clock/ti/dpll.txt | 14 | [2] Documentation/devicetree/bindings/clock/ti/dpll.txt |
| 15 | 15 | ||
| 16 | Required properties: | 16 | Required properties: |
| 17 | - compatible : shall be "ti,dra7-apll-clock" | 17 | - compatible : shall be "ti,dra7-apll-clock" or "ti,omap2-apll-clock" |
| 18 | - #clock-cells : from common clock binding; shall be set to 0. | 18 | - #clock-cells : from common clock binding; shall be set to 0. |
| 19 | - clocks : link phandles of parent clocks (clk-ref and clk-bypass) | 19 | - clocks : link phandles of parent clocks (clk-ref and clk-bypass) |
| 20 | - reg : address and length of the register set for controlling the APLL. | 20 | - reg : address and length of the register set for controlling the APLL. |
| 21 | It contains the information of registers in the following order: | 21 | It contains the information of registers in the following order: |
| 22 | "control" - contains the control register base address | 22 | "control" - contains the control register offset |
| 23 | "idlest" - contains the idlest register base address | 23 | "idlest" - contains the idlest register offset |
| 24 | "autoidle" - contains the autoidle register offset (OMAP2 only) | ||
| 25 | - ti,clock-frequency : static clock frequency for the clock (OMAP2 only) | ||
| 26 | - ti,idlest-shift : bit-shift for the idlest field (OMAP2 only) | ||
| 27 | - ti,bit-shift : bit-shift for enable and autoidle fields (OMAP2 only) | ||
| 24 | 28 | ||
| 25 | Examples: | 29 | Examples: |
| 26 | apll_pcie_ck: apll_pcie_ck@4a008200 { | 30 | apll_pcie_ck: apll_pcie_ck { |
| 27 | #clock-cells = <0>; | 31 | #clock-cells = <0>; |
| 28 | clocks = <&apll_pcie_in_clk_mux>, <&dpll_pcie_ref_ck>; | 32 | clocks = <&apll_pcie_in_clk_mux>, <&dpll_pcie_ref_ck>; |
| 29 | reg = <0x4a00821c 0x4>, <0x4a008220 0x4>; | 33 | reg = <0x021c>, <0x0220>; |
| 30 | compatible = "ti,dra7-apll-clock"; | 34 | compatible = "ti,dra7-apll-clock"; |
| 31 | }; | 35 | }; |
| 36 | |||
| 37 | apll96_ck: apll96_ck { | ||
| 38 | #clock-cells = <0>; | ||
| 39 | compatible = "ti,omap2-apll-clock"; | ||
| 40 | clocks = <&sys_ck>; | ||
| 41 | ti,bit-shift = <2>; | ||
| 42 | ti,idlest-shift = <8>; | ||
| 43 | ti,clock-frequency = <96000000>; | ||
| 44 | reg = <0x0500>, <0x0530>, <0x0520>; | ||
| 45 | }; | ||
diff --git a/arch/arm/mach-omap2/clock.h b/arch/arm/mach-omap2/clock.h index f6e9904d7a75..eb441d137843 100644 --- a/arch/arm/mach-omap2/clock.h +++ b/arch/arm/mach-omap2/clock.h | |||
| @@ -178,17 +178,6 @@ struct clksel { | |||
| 178 | const struct clksel_rate *rates; | 178 | const struct clksel_rate *rates; |
| 179 | }; | 179 | }; |
| 180 | 180 | ||
| 181 | struct clk_hw_omap_ops { | ||
| 182 | void (*find_idlest)(struct clk_hw_omap *oclk, | ||
| 183 | void __iomem **idlest_reg, | ||
| 184 | u8 *idlest_bit, u8 *idlest_val); | ||
| 185 | void (*find_companion)(struct clk_hw_omap *oclk, | ||
| 186 | void __iomem **other_reg, | ||
| 187 | u8 *other_bit); | ||
| 188 | void (*allow_idle)(struct clk_hw_omap *oclk); | ||
| 189 | void (*deny_idle)(struct clk_hw_omap *oclk); | ||
| 190 | }; | ||
| 191 | |||
| 192 | unsigned long omap_fixed_divisor_recalc(struct clk_hw *hw, | 181 | unsigned long omap_fixed_divisor_recalc(struct clk_hw *hw, |
| 193 | unsigned long parent_rate); | 182 | unsigned long parent_rate); |
| 194 | 183 | ||
diff --git a/drivers/clk/ti/apll.c b/drivers/clk/ti/apll.c index b986f61f5a77..5428c9c547cd 100644 --- a/drivers/clk/ti/apll.c +++ b/drivers/clk/ti/apll.c | |||
| @@ -221,3 +221,184 @@ cleanup: | |||
| 221 | kfree(init); | 221 | kfree(init); |
| 222 | } | 222 | } |
| 223 | CLK_OF_DECLARE(dra7_apll_clock, "ti,dra7-apll-clock", of_dra7_apll_setup); | 223 | CLK_OF_DECLARE(dra7_apll_clock, "ti,dra7-apll-clock", of_dra7_apll_setup); |
| 224 | |||
| 225 | #define OMAP2_EN_APLL_LOCKED 0x3 | ||
| 226 | #define OMAP2_EN_APLL_STOPPED 0x0 | ||
| 227 | |||
| 228 | static int omap2_apll_is_enabled(struct clk_hw *hw) | ||
| 229 | { | ||
| 230 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); | ||
| 231 | struct dpll_data *ad = clk->dpll_data; | ||
| 232 | u32 v; | ||
| 233 | |||
| 234 | v = ti_clk_ll_ops->clk_readl(ad->control_reg); | ||
| 235 | v &= ad->enable_mask; | ||
| 236 | |||
| 237 | v >>= __ffs(ad->enable_mask); | ||
| 238 | |||
| 239 | return v == OMAP2_EN_APLL_LOCKED ? 1 : 0; | ||
| 240 | } | ||
| 241 | |||
| 242 | static unsigned long omap2_apll_recalc(struct clk_hw *hw, | ||
| 243 | unsigned long parent_rate) | ||
| 244 | { | ||
| 245 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); | ||
| 246 | |||
| 247 | if (omap2_apll_is_enabled(hw)) | ||
| 248 | return clk->fixed_rate; | ||
| 249 | |||
| 250 | return 0; | ||
| 251 | } | ||
| 252 | |||
| 253 | static int omap2_apll_enable(struct clk_hw *hw) | ||
| 254 | { | ||
| 255 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); | ||
| 256 | struct dpll_data *ad = clk->dpll_data; | ||
| 257 | u32 v; | ||
| 258 | int i = 0; | ||
| 259 | |||
| 260 | v = ti_clk_ll_ops->clk_readl(ad->control_reg); | ||
| 261 | v &= ~ad->enable_mask; | ||
| 262 | v |= OMAP2_EN_APLL_LOCKED << __ffs(ad->enable_mask); | ||
| 263 | ti_clk_ll_ops->clk_writel(v, ad->control_reg); | ||
| 264 | |||
| 265 | while (1) { | ||
| 266 | v = ti_clk_ll_ops->clk_readl(ad->idlest_reg); | ||
| 267 | if (v & ad->idlest_mask) | ||
| 268 | break; | ||
| 269 | if (i > MAX_APLL_WAIT_TRIES) | ||
| 270 | break; | ||
| 271 | i++; | ||
| 272 | udelay(1); | ||
| 273 | } | ||
| 274 | |||
| 275 | if (i == MAX_APLL_WAIT_TRIES) { | ||
| 276 | pr_warn("%s failed to transition to locked\n", | ||
| 277 | __clk_get_name(clk->hw.clk)); | ||
| 278 | return -EBUSY; | ||
| 279 | } | ||
| 280 | |||
| 281 | return 0; | ||
| 282 | } | ||
| 283 | |||
| 284 | static void omap2_apll_disable(struct clk_hw *hw) | ||
| 285 | { | ||
| 286 | struct clk_hw_omap *clk = to_clk_hw_omap(hw); | ||
| 287 | struct dpll_data *ad = clk->dpll_data; | ||
| 288 | u32 v; | ||
| 289 | |||
| 290 | v = ti_clk_ll_ops->clk_readl(ad->control_reg); | ||
| 291 | v &= ~ad->enable_mask; | ||
| 292 | v |= OMAP2_EN_APLL_STOPPED << __ffs(ad->enable_mask); | ||
| 293 | ti_clk_ll_ops->clk_writel(v, ad->control_reg); | ||
| 294 | } | ||
| 295 | |||
| 296 | static struct clk_ops omap2_apll_ops = { | ||
| 297 | .enable = &omap2_apll_enable, | ||
| 298 | .disable = &omap2_apll_disable, | ||
| 299 | .is_enabled = &omap2_apll_is_enabled, | ||
| 300 | .recalc_rate = &omap2_apll_recalc, | ||
| 301 | }; | ||
| 302 | |||
| 303 | static void omap2_apll_set_autoidle(struct clk_hw_omap *clk, u32 val) | ||
| 304 | { | ||
| 305 | struct dpll_data *ad = clk->dpll_data; | ||
| 306 | u32 v; | ||
| 307 | |||
| 308 | v = ti_clk_ll_ops->clk_readl(ad->autoidle_reg); | ||
| 309 | v &= ~ad->autoidle_mask; | ||
| 310 | v |= val << __ffs(ad->autoidle_mask); | ||
| 311 | ti_clk_ll_ops->clk_writel(v, ad->control_reg); | ||
| 312 | } | ||
| 313 | |||
| 314 | #define OMAP2_APLL_AUTOIDLE_LOW_POWER_STOP 0x3 | ||
| 315 | #define OMAP2_APLL_AUTOIDLE_DISABLE 0x0 | ||
| 316 | |||
| 317 | static void omap2_apll_allow_idle(struct clk_hw_omap *clk) | ||
| 318 | { | ||
| 319 | omap2_apll_set_autoidle(clk, OMAP2_APLL_AUTOIDLE_LOW_POWER_STOP); | ||
| 320 | } | ||
| 321 | |||
| 322 | static void omap2_apll_deny_idle(struct clk_hw_omap *clk) | ||
| 323 | { | ||
| 324 | omap2_apll_set_autoidle(clk, OMAP2_APLL_AUTOIDLE_DISABLE); | ||
| 325 | } | ||
| 326 | |||
| 327 | static struct clk_hw_omap_ops omap2_apll_hwops = { | ||
| 328 | .allow_idle = &omap2_apll_allow_idle, | ||
| 329 | .deny_idle = &omap2_apll_deny_idle, | ||
| 330 | }; | ||
| 331 | |||
| 332 | static void __init of_omap2_apll_setup(struct device_node *node) | ||
| 333 | { | ||
| 334 | struct dpll_data *ad = NULL; | ||
| 335 | struct clk_hw_omap *clk_hw = NULL; | ||
| 336 | struct clk_init_data *init = NULL; | ||
| 337 | struct clk *clk; | ||
| 338 | const char *parent_name; | ||
| 339 | u32 val; | ||
| 340 | |||
| 341 | ad = kzalloc(sizeof(*clk_hw), GFP_KERNEL); | ||
| 342 | clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); | ||
| 343 | init = kzalloc(sizeof(*init), GFP_KERNEL); | ||
| 344 | |||
| 345 | if (!ad || !clk_hw || !init) | ||
| 346 | goto cleanup; | ||
| 347 | |||
| 348 | clk_hw->dpll_data = ad; | ||
| 349 | clk_hw->hw.init = init; | ||
| 350 | init->ops = &omap2_apll_ops; | ||
| 351 | init->name = node->name; | ||
| 352 | clk_hw->ops = &omap2_apll_hwops; | ||
| 353 | |||
| 354 | init->num_parents = of_clk_get_parent_count(node); | ||
| 355 | if (init->num_parents != 1) { | ||
| 356 | pr_err("%s must have one parent\n", node->name); | ||
| 357 | goto cleanup; | ||
| 358 | } | ||
| 359 | |||
| 360 | parent_name = of_clk_get_parent_name(node, 0); | ||
| 361 | init->parent_names = &parent_name; | ||
| 362 | |||
| 363 | if (of_property_read_u32(node, "ti,clock-frequency", &val)) { | ||
| 364 | pr_err("%s missing clock-frequency\n", node->name); | ||
| 365 | goto cleanup; | ||
| 366 | } | ||
| 367 | clk_hw->fixed_rate = val; | ||
| 368 | |||
| 369 | if (of_property_read_u32(node, "ti,bit-shift", &val)) { | ||
| 370 | pr_err("%s missing bit-shift\n", node->name); | ||
| 371 | goto cleanup; | ||
| 372 | } | ||
| 373 | |||
| 374 | clk_hw->enable_bit = val; | ||
| 375 | ad->enable_mask = 0x3 << val; | ||
| 376 | ad->autoidle_mask = 0x3 << val; | ||
| 377 | |||
| 378 | if (of_property_read_u32(node, "ti,idlest-shift", &val)) { | ||
| 379 | pr_err("%s missing idlest-shift\n", node->name); | ||
| 380 | goto cleanup; | ||
| 381 | } | ||
| 382 | |||
| 383 | ad->idlest_mask = 1 << val; | ||
| 384 | |||
| 385 | ad->control_reg = ti_clk_get_reg_addr(node, 0); | ||
| 386 | ad->autoidle_reg = ti_clk_get_reg_addr(node, 1); | ||
| 387 | ad->idlest_reg = ti_clk_get_reg_addr(node, 2); | ||
| 388 | |||
| 389 | if (!ad->control_reg || !ad->autoidle_reg || !ad->idlest_reg) | ||
| 390 | goto cleanup; | ||
| 391 | |||
| 392 | clk = clk_register(NULL, &clk_hw->hw); | ||
| 393 | if (!IS_ERR(clk)) { | ||
| 394 | of_clk_add_provider(node, of_clk_src_simple_get, clk); | ||
| 395 | kfree(init); | ||
| 396 | return; | ||
| 397 | } | ||
| 398 | cleanup: | ||
| 399 | kfree(ad); | ||
| 400 | kfree(clk_hw); | ||
| 401 | kfree(init); | ||
| 402 | } | ||
| 403 | CLK_OF_DECLARE(omap2_apll_clock, "ti,omap2-apll-clock", | ||
| 404 | of_omap2_apll_setup); | ||
diff --git a/include/linux/clk/ti.h b/include/linux/clk/ti.h index 753878c6fa52..44bf84002a34 100644 --- a/include/linux/clk/ti.h +++ b/include/linux/clk/ti.h | |||
| @@ -94,7 +94,26 @@ struct dpll_data { | |||
| 94 | u8 flags; | 94 | u8 flags; |
| 95 | }; | 95 | }; |
| 96 | 96 | ||
| 97 | struct clk_hw_omap_ops; | 97 | struct clk_hw_omap; |
| 98 | |||
| 99 | /** | ||
| 100 | * struct clk_hw_omap_ops - OMAP clk ops | ||
| 101 | * @find_idlest: find idlest register information for a clock | ||
| 102 | * @find_companion: find companion clock register information for a clock, | ||
| 103 | * basically converts CM_ICLKEN* <-> CM_FCLKEN* | ||
| 104 | * @allow_idle: enables autoidle hardware functionality for a clock | ||
| 105 | * @deny_idle: prevent autoidle hardware functionality for a clock | ||
| 106 | */ | ||
| 107 | struct clk_hw_omap_ops { | ||
| 108 | void (*find_idlest)(struct clk_hw_omap *oclk, | ||
| 109 | void __iomem **idlest_reg, | ||
| 110 | u8 *idlest_bit, u8 *idlest_val); | ||
| 111 | void (*find_companion)(struct clk_hw_omap *oclk, | ||
| 112 | void __iomem **other_reg, | ||
| 113 | u8 *other_bit); | ||
| 114 | void (*allow_idle)(struct clk_hw_omap *oclk); | ||
| 115 | void (*deny_idle)(struct clk_hw_omap *oclk); | ||
| 116 | }; | ||
| 98 | 117 | ||
| 99 | /** | 118 | /** |
| 100 | * struct clk_hw_omap - OMAP struct clk | 119 | * struct clk_hw_omap - OMAP struct clk |
