pwm-clk.c 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Clock based PWM controller
  4. *
  5. * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
  6. *
  7. * This is an "adapter" driver that allows PWM consumers to use
  8. * system clocks with duty cycle control as PWM outputs.
  9. *
  10. * Limitations:
  11. * - Due to the fact that exact behavior depends on the underlying
  12. * clock driver, various limitations are possible.
  13. * - Underlying clock may not be able to give 0% or 100% duty cycle
  14. * (constant off or on), exact behavior will depend on the clock.
  15. * - When the PWM is disabled, the clock will be disabled as well,
  16. * line state will depend on the clock.
  17. * - The clk API doesn't expose the necessary calls to implement
  18. * .get_state().
  19. */
  20. #include <linux/kernel.h>
  21. #include <linux/math64.h>
  22. #include <linux/err.h>
  23. #include <linux/module.h>
  24. #include <linux/of.h>
  25. #include <linux/platform_device.h>
  26. #include <linux/clk.h>
  27. #include <linux/pwm.h>
  28. struct pwm_clk_chip {
  29. struct clk *clk;
  30. bool clk_enabled;
  31. };
  32. static inline struct pwm_clk_chip *to_pwm_clk_chip(struct pwm_chip *chip)
  33. {
  34. return pwmchip_get_drvdata(chip);
  35. }
  36. static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm,
  37. const struct pwm_state *state)
  38. {
  39. struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
  40. int ret;
  41. u32 rate;
  42. u64 period = state->period;
  43. u64 duty_cycle = state->duty_cycle;
  44. if (!state->enabled) {
  45. if (pwm->state.enabled) {
  46. clk_disable(pcchip->clk);
  47. pcchip->clk_enabled = false;
  48. }
  49. return 0;
  50. } else if (!pwm->state.enabled) {
  51. ret = clk_enable(pcchip->clk);
  52. if (ret)
  53. return ret;
  54. pcchip->clk_enabled = true;
  55. }
  56. /*
  57. * We have to enable the clk before setting the rate and duty_cycle,
  58. * that however results in a window where the clk is on with a
  59. * (potentially) different setting. Also setting period and duty_cycle
  60. * are two separate calls, so that probably isn't atomic either.
  61. */
  62. rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period);
  63. ret = clk_set_rate(pcchip->clk, rate);
  64. if (ret)
  65. return ret;
  66. if (state->polarity == PWM_POLARITY_INVERSED)
  67. duty_cycle = period - duty_cycle;
  68. return clk_set_duty_cycle(pcchip->clk, duty_cycle, period);
  69. }
  70. static const struct pwm_ops pwm_clk_ops = {
  71. .apply = pwm_clk_apply,
  72. };
  73. static int pwm_clk_probe(struct platform_device *pdev)
  74. {
  75. struct pwm_chip *chip;
  76. struct pwm_clk_chip *pcchip;
  77. int ret;
  78. chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pcchip));
  79. if (IS_ERR(chip))
  80. return PTR_ERR(chip);
  81. pcchip = to_pwm_clk_chip(chip);
  82. pcchip->clk = devm_clk_get_prepared(&pdev->dev, NULL);
  83. if (IS_ERR(pcchip->clk))
  84. return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk),
  85. "Failed to get clock\n");
  86. chip->ops = &pwm_clk_ops;
  87. ret = pwmchip_add(chip);
  88. if (ret < 0)
  89. return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
  90. platform_set_drvdata(pdev, chip);
  91. return 0;
  92. }
  93. static void pwm_clk_remove(struct platform_device *pdev)
  94. {
  95. struct pwm_chip *chip = platform_get_drvdata(pdev);
  96. struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
  97. pwmchip_remove(chip);
  98. if (pcchip->clk_enabled)
  99. clk_disable(pcchip->clk);
  100. }
  101. static const struct of_device_id pwm_clk_dt_ids[] = {
  102. { .compatible = "clk-pwm", },
  103. { /* sentinel */ }
  104. };
  105. MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids);
  106. static struct platform_driver pwm_clk_driver = {
  107. .driver = {
  108. .name = "pwm-clk",
  109. .of_match_table = pwm_clk_dt_ids,
  110. },
  111. .probe = pwm_clk_probe,
  112. .remove = pwm_clk_remove,
  113. };
  114. module_platform_driver(pwm_clk_driver);
  115. MODULE_ALIAS("platform:pwm-clk");
  116. MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
  117. MODULE_DESCRIPTION("Clock based PWM driver");
  118. MODULE_LICENSE("GPL");