clk-dualdiv.c 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Copyright (c) 2017 BayLibre, SAS
  4. * Author: Neil Armstrong <[email protected]>
  5. * Author: Jerome Brunet <[email protected]>
  6. */
  7. /*
  8. * The AO Domain embeds a dual/divider to generate a more precise
  9. * 32,768KHz clock for low-power suspend mode and CEC.
  10. * ______ ______
  11. * | | | |
  12. * | Div1 |-| Cnt1 |
  13. * /|______| |______|\
  14. * -| ______ ______ X--> Out
  15. * \| | | |/
  16. * | Div2 |-| Cnt2 |
  17. * |______| |______|
  18. *
  19. * The dividing can be switched to single or dual, with a counter
  20. * for each divider to set when the switching is done.
  21. */
  22. #include <linux/clk-provider.h>
  23. #include <linux/module.h>
  24. #include "clk-regmap.h"
  25. #include "clk-dualdiv.h"
  26. static inline struct meson_clk_dualdiv_data *
  27. meson_clk_dualdiv_data(struct clk_regmap *clk)
  28. {
  29. return (struct meson_clk_dualdiv_data *)clk->data;
  30. }
  31. static unsigned long
  32. __dualdiv_param_to_rate(unsigned long parent_rate,
  33. const struct meson_clk_dualdiv_param *p)
  34. {
  35. if (!p->dual)
  36. return DIV_ROUND_CLOSEST(parent_rate, p->n1);
  37. return DIV_ROUND_CLOSEST(parent_rate * (p->m1 + p->m2),
  38. p->n1 * p->m1 + p->n2 * p->m2);
  39. }
  40. static unsigned long meson_clk_dualdiv_recalc_rate(struct clk_hw *hw,
  41. unsigned long parent_rate)
  42. {
  43. struct clk_regmap *clk = to_clk_regmap(hw);
  44. struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
  45. struct meson_clk_dualdiv_param setting;
  46. setting.dual = meson_parm_read(clk->map, &dualdiv->dual);
  47. setting.n1 = meson_parm_read(clk->map, &dualdiv->n1) + 1;
  48. setting.m1 = meson_parm_read(clk->map, &dualdiv->m1) + 1;
  49. setting.n2 = meson_parm_read(clk->map, &dualdiv->n2) + 1;
  50. setting.m2 = meson_parm_read(clk->map, &dualdiv->m2) + 1;
  51. return __dualdiv_param_to_rate(parent_rate, &setting);
  52. }
  53. static const struct meson_clk_dualdiv_param *
  54. __dualdiv_get_setting(unsigned long rate, unsigned long parent_rate,
  55. struct meson_clk_dualdiv_data *dualdiv)
  56. {
  57. const struct meson_clk_dualdiv_param *table = dualdiv->table;
  58. unsigned long best = 0, now = 0;
  59. unsigned int i, best_i = 0;
  60. if (!table)
  61. return NULL;
  62. for (i = 0; table[i].n1; i++) {
  63. now = __dualdiv_param_to_rate(parent_rate, &table[i]);
  64. /* If we get an exact match, don't bother any further */
  65. if (now == rate) {
  66. return &table[i];
  67. } else if (abs(now - rate) < abs(best - rate)) {
  68. best = now;
  69. best_i = i;
  70. }
  71. }
  72. return (struct meson_clk_dualdiv_param *)&table[best_i];
  73. }
  74. static long meson_clk_dualdiv_round_rate(struct clk_hw *hw, unsigned long rate,
  75. unsigned long *parent_rate)
  76. {
  77. struct clk_regmap *clk = to_clk_regmap(hw);
  78. struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
  79. const struct meson_clk_dualdiv_param *setting =
  80. __dualdiv_get_setting(rate, *parent_rate, dualdiv);
  81. if (!setting)
  82. return meson_clk_dualdiv_recalc_rate(hw, *parent_rate);
  83. return __dualdiv_param_to_rate(*parent_rate, setting);
  84. }
  85. static int meson_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate,
  86. unsigned long parent_rate)
  87. {
  88. struct clk_regmap *clk = to_clk_regmap(hw);
  89. struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
  90. const struct meson_clk_dualdiv_param *setting =
  91. __dualdiv_get_setting(rate, parent_rate, dualdiv);
  92. if (!setting)
  93. return -EINVAL;
  94. meson_parm_write(clk->map, &dualdiv->dual, setting->dual);
  95. meson_parm_write(clk->map, &dualdiv->n1, setting->n1 - 1);
  96. meson_parm_write(clk->map, &dualdiv->m1, setting->m1 - 1);
  97. meson_parm_write(clk->map, &dualdiv->n2, setting->n2 - 1);
  98. meson_parm_write(clk->map, &dualdiv->m2, setting->m2 - 1);
  99. return 0;
  100. }
  101. const struct clk_ops meson_clk_dualdiv_ops = {
  102. .recalc_rate = meson_clk_dualdiv_recalc_rate,
  103. .round_rate = meson_clk_dualdiv_round_rate,
  104. .set_rate = meson_clk_dualdiv_set_rate,
  105. };
  106. EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops);
  107. const struct clk_ops meson_clk_dualdiv_ro_ops = {
  108. .recalc_rate = meson_clk_dualdiv_recalc_rate,
  109. };
  110. EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops);
  111. MODULE_DESCRIPTION("Amlogic dual divider driver");
  112. MODULE_AUTHOR("Neil Armstrong <[email protected]>");
  113. MODULE_AUTHOR("Jerome Brunet <[email protected]>");
  114. MODULE_LICENSE("GPL v2");