meson-mx-sdhc-clkc.c 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * Amlogic Meson SDHC clock controller
  4. *
  5. * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
  6. */
  7. #include <linux/clk.h>
  8. #include <linux/clk-provider.h>
  9. #include <linux/device.h>
  10. #include <linux/platform_device.h>
  11. #include "meson-mx-sdhc.h"
  12. #define MESON_SDHC_NUM_BUILTIN_CLKS 6
  13. struct meson_mx_sdhc_clkc {
  14. struct clk_mux src_sel;
  15. struct clk_divider div;
  16. struct clk_gate mod_clk_en;
  17. struct clk_gate tx_clk_en;
  18. struct clk_gate rx_clk_en;
  19. struct clk_gate sd_clk_en;
  20. };
  21. static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = {
  22. { .fw_name = "clkin0" },
  23. { .fw_name = "clkin1" },
  24. { .fw_name = "clkin2" },
  25. { .fw_name = "clkin3" },
  26. };
  27. static const struct clk_div_table meson_mx_sdhc_div_table[] = {
  28. { .div = 6, .val = 5, },
  29. { .div = 8, .val = 7, },
  30. { .div = 9, .val = 8, },
  31. { .div = 10, .val = 9, },
  32. { .div = 12, .val = 11, },
  33. { .div = 16, .val = 15, },
  34. { .div = 18, .val = 17, },
  35. { .div = 34, .val = 33, },
  36. { .div = 142, .val = 141, },
  37. { .div = 850, .val = 849, },
  38. { .div = 2126, .val = 2125, },
  39. { .div = 4096, .val = 4095, },
  40. { /* sentinel */ }
  41. };
  42. static int meson_mx_sdhc_clk_hw_register(struct device *dev,
  43. const char *name_suffix,
  44. const struct clk_parent_data *parents,
  45. unsigned int num_parents,
  46. const struct clk_ops *ops,
  47. struct clk_hw *hw)
  48. {
  49. struct clk_init_data init = { };
  50. char clk_name[32];
  51. snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dev),
  52. name_suffix);
  53. init.name = clk_name;
  54. init.ops = ops;
  55. init.flags = CLK_SET_RATE_PARENT;
  56. init.parent_data = parents;
  57. init.num_parents = num_parents;
  58. hw->init = &init;
  59. return devm_clk_hw_register(dev, hw);
  60. }
  61. static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev,
  62. const char *name_suffix,
  63. struct clk_hw *parent,
  64. struct clk_hw *hw)
  65. {
  66. struct clk_parent_data parent_data = { .hw = parent };
  67. return meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1,
  68. &clk_gate_ops, hw);
  69. }
  70. int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
  71. struct clk_bulk_data *clk_bulk_data)
  72. {
  73. struct clk_parent_data div_parent = { };
  74. struct meson_mx_sdhc_clkc *clkc_data;
  75. int ret;
  76. clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL);
  77. if (!clkc_data)
  78. return -ENOMEM;
  79. clkc_data->src_sel.reg = base + MESON_SDHC_CLKC;
  80. clkc_data->src_sel.mask = 0x3;
  81. clkc_data->src_sel.shift = 16;
  82. ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel",
  83. meson_mx_sdhc_src_sel_parents, 4,
  84. &clk_mux_ops,
  85. &clkc_data->src_sel.hw);
  86. if (ret)
  87. return ret;
  88. clkc_data->div.reg = base + MESON_SDHC_CLKC;
  89. clkc_data->div.shift = 0;
  90. clkc_data->div.width = 12;
  91. clkc_data->div.table = meson_mx_sdhc_div_table;
  92. div_parent.hw = &clkc_data->src_sel.hw;
  93. ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1,
  94. &clk_divider_ops,
  95. &clkc_data->div.hw);
  96. if (ret)
  97. return ret;
  98. clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC;
  99. clkc_data->mod_clk_en.bit_idx = 15;
  100. ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on",
  101. &clkc_data->div.hw,
  102. &clkc_data->mod_clk_en.hw);
  103. if (ret)
  104. return ret;
  105. clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC;
  106. clkc_data->tx_clk_en.bit_idx = 14;
  107. ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on",
  108. &clkc_data->div.hw,
  109. &clkc_data->tx_clk_en.hw);
  110. if (ret)
  111. return ret;
  112. clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC;
  113. clkc_data->rx_clk_en.bit_idx = 13;
  114. ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on",
  115. &clkc_data->div.hw,
  116. &clkc_data->rx_clk_en.hw);
  117. if (ret)
  118. return ret;
  119. clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC;
  120. clkc_data->sd_clk_en.bit_idx = 12;
  121. ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on",
  122. &clkc_data->div.hw,
  123. &clkc_data->sd_clk_en.hw);
  124. if (ret)
  125. return ret;
  126. /*
  127. * TODO: Replace clk_hw.clk with devm_clk_hw_get_clk() once that is
  128. * available.
  129. */
  130. clk_bulk_data[0].clk = clkc_data->mod_clk_en.hw.clk;
  131. clk_bulk_data[1].clk = clkc_data->sd_clk_en.hw.clk;
  132. clk_bulk_data[2].clk = clkc_data->tx_clk_en.hw.clk;
  133. clk_bulk_data[3].clk = clkc_data->rx_clk_en.hw.clk;
  134. return 0;
  135. }