clk-bcm53573-ilp.c 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /*
  2. * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License version 2 as
  6. * published by the Free Software Foundation.
  7. */
  8. #include <linux/clk-provider.h>
  9. #include <linux/err.h>
  10. #include <linux/io.h>
  11. #include <linux/mfd/syscon.h>
  12. #include <linux/of.h>
  13. #include <linux/of_address.h>
  14. #include <linux/regmap.h>
  15. #include <linux/slab.h>
  16. #define PMU_XTAL_FREQ_RATIO 0x66c
  17. #define XTAL_ALP_PER_4ILP 0x00001fff
  18. #define XTAL_CTL_EN 0x80000000
  19. #define PMU_SLOW_CLK_PERIOD 0x6dc
  20. struct bcm53573_ilp {
  21. struct clk_hw hw;
  22. struct regmap *regmap;
  23. };
  24. static int bcm53573_ilp_enable(struct clk_hw *hw)
  25. {
  26. struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
  27. regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0x10199);
  28. regmap_write(ilp->regmap, 0x674, 0x10000);
  29. return 0;
  30. }
  31. static void bcm53573_ilp_disable(struct clk_hw *hw)
  32. {
  33. struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
  34. regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0);
  35. regmap_write(ilp->regmap, 0x674, 0);
  36. }
  37. static unsigned long bcm53573_ilp_recalc_rate(struct clk_hw *hw,
  38. unsigned long parent_rate)
  39. {
  40. struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
  41. struct regmap *regmap = ilp->regmap;
  42. u32 last_val, cur_val;
  43. int sum = 0, num = 0, loop_num = 0;
  44. int avg;
  45. /* Enable measurement */
  46. regmap_write(regmap, PMU_XTAL_FREQ_RATIO, XTAL_CTL_EN);
  47. /* Read initial value */
  48. regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &last_val);
  49. last_val &= XTAL_ALP_PER_4ILP;
  50. /*
  51. * At minimum we should loop for a bit to let hardware do the
  52. * measurement. This isn't very accurate however, so for a better
  53. * precision lets try getting 20 different values for and use average.
  54. */
  55. while (num < 20) {
  56. regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &cur_val);
  57. cur_val &= XTAL_ALP_PER_4ILP;
  58. if (cur_val != last_val) {
  59. /* Got different value, use it */
  60. sum += cur_val;
  61. num++;
  62. loop_num = 0;
  63. last_val = cur_val;
  64. } else if (++loop_num > 5000) {
  65. /* Same value over and over, give up */
  66. sum += cur_val;
  67. num++;
  68. break;
  69. }
  70. cpu_relax();
  71. }
  72. /* Disable measurement to save power */
  73. regmap_write(regmap, PMU_XTAL_FREQ_RATIO, 0x0);
  74. avg = sum / num;
  75. return parent_rate * 4 / avg;
  76. }
  77. static const struct clk_ops bcm53573_ilp_clk_ops = {
  78. .enable = bcm53573_ilp_enable,
  79. .disable = bcm53573_ilp_disable,
  80. .recalc_rate = bcm53573_ilp_recalc_rate,
  81. };
  82. static void bcm53573_ilp_init(struct device_node *np)
  83. {
  84. struct bcm53573_ilp *ilp;
  85. struct clk_init_data init = { };
  86. const char *parent_name;
  87. int err;
  88. ilp = kzalloc(sizeof(*ilp), GFP_KERNEL);
  89. if (!ilp)
  90. return;
  91. parent_name = of_clk_get_parent_name(np, 0);
  92. if (!parent_name) {
  93. err = -ENOENT;
  94. goto err_free_ilp;
  95. }
  96. ilp->regmap = syscon_node_to_regmap(of_get_parent(np));
  97. if (IS_ERR(ilp->regmap)) {
  98. err = PTR_ERR(ilp->regmap);
  99. goto err_free_ilp;
  100. }
  101. init.name = np->name;
  102. init.ops = &bcm53573_ilp_clk_ops;
  103. init.parent_names = &parent_name;
  104. init.num_parents = 1;
  105. ilp->hw.init = &init;
  106. err = clk_hw_register(NULL, &ilp->hw);
  107. if (err)
  108. goto err_free_ilp;
  109. err = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &ilp->hw);
  110. if (err)
  111. goto err_clk_hw_unregister;
  112. return;
  113. err_clk_hw_unregister:
  114. clk_hw_unregister(&ilp->hw);
  115. err_free_ilp:
  116. kfree(ilp);
  117. pr_err("Failed to init ILP clock: %d\n", err);
  118. }
  119. /* We need it very early for arch code, before device model gets ready */
  120. CLK_OF_DECLARE(bcm53573_ilp_clk, "brcm,bcm53573-ilp", bcm53573_ilp_init);