meson-mx-sdhc-clkc.c 4.1 KB

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