pwm.c 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * Copyright (C) 2011 Samsung Electronics
  4. *
  5. * Donghwa Lee <dh09.lee@samsung.com>
  6. */
  7. #include <common.h>
  8. #include <errno.h>
  9. #include <pwm.h>
  10. #include <asm/io.h>
  11. #include <asm/arch/pwm.h>
  12. #include <asm/arch/clk.h>
  13. int pwm_enable(int pwm_id)
  14. {
  15. const struct s5p_timer *pwm =
  16. (struct s5p_timer *)samsung_get_base_timer();
  17. unsigned long tcon;
  18. tcon = readl(&pwm->tcon);
  19. tcon |= TCON_START(pwm_id);
  20. writel(tcon, &pwm->tcon);
  21. return 0;
  22. }
  23. void pwm_disable(int pwm_id)
  24. {
  25. const struct s5p_timer *pwm =
  26. (struct s5p_timer *)samsung_get_base_timer();
  27. unsigned long tcon;
  28. tcon = readl(&pwm->tcon);
  29. tcon &= ~TCON_START(pwm_id);
  30. writel(tcon, &pwm->tcon);
  31. }
  32. static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
  33. {
  34. unsigned long tin_parent_rate;
  35. unsigned int div;
  36. tin_parent_rate = get_pwm_clk();
  37. for (div = 2; div <= 16; div *= 2) {
  38. if ((tin_parent_rate / (div << 16)) < freq)
  39. return tin_parent_rate / div;
  40. }
  41. return tin_parent_rate / 16;
  42. }
  43. #define NS_IN_SEC 1000000000UL
  44. int pwm_config(int pwm_id, int duty_ns, int period_ns)
  45. {
  46. const struct s5p_timer *pwm =
  47. (struct s5p_timer *)samsung_get_base_timer();
  48. unsigned int offset;
  49. unsigned long tin_rate;
  50. unsigned long tin_ns;
  51. unsigned long frequency;
  52. unsigned long tcon;
  53. unsigned long tcnt;
  54. unsigned long tcmp;
  55. /*
  56. * We currently avoid using 64bit arithmetic by using the
  57. * fact that anything faster than 1GHz is easily representable
  58. * by 32bits.
  59. */
  60. if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
  61. return -ERANGE;
  62. if (duty_ns > period_ns)
  63. return -EINVAL;
  64. frequency = NS_IN_SEC / period_ns;
  65. /* Check to see if we are changing the clock rate of the PWM */
  66. tin_rate = pwm_calc_tin(pwm_id, frequency);
  67. tin_ns = NS_IN_SEC / tin_rate;
  68. tcnt = period_ns / tin_ns;
  69. /* Note, counters count down */
  70. tcmp = duty_ns / tin_ns;
  71. tcmp = tcnt - tcmp;
  72. /* Update the PWM register block. */
  73. offset = pwm_id * 3;
  74. if (pwm_id < 4) {
  75. writel(tcnt, &pwm->tcntb0 + offset);
  76. writel(tcmp, &pwm->tcmpb0 + offset);
  77. }
  78. tcon = readl(&pwm->tcon);
  79. tcon |= TCON_UPDATE(pwm_id);
  80. if (pwm_id < 4)
  81. tcon |= TCON_AUTO_RELOAD(pwm_id);
  82. else
  83. tcon |= TCON4_AUTO_RELOAD;
  84. writel(tcon, &pwm->tcon);
  85. tcon &= ~TCON_UPDATE(pwm_id);
  86. writel(tcon, &pwm->tcon);
  87. return 0;
  88. }
  89. int pwm_init(int pwm_id, int div, int invert)
  90. {
  91. u32 val;
  92. const struct s5p_timer *pwm =
  93. (struct s5p_timer *)samsung_get_base_timer();
  94. unsigned long ticks_per_period;
  95. unsigned int offset, prescaler;
  96. /*
  97. * Timer Freq(HZ) =
  98. * PWM_CLK / { (prescaler_value + 1) * (divider_value) }
  99. */
  100. val = readl(&pwm->tcfg0);
  101. if (pwm_id < 2) {
  102. prescaler = PRESCALER_0;
  103. val &= ~0xff;
  104. val |= (prescaler & 0xff);
  105. } else {
  106. prescaler = PRESCALER_1;
  107. val &= ~(0xff << 8);
  108. val |= (prescaler & 0xff) << 8;
  109. }
  110. writel(val, &pwm->tcfg0);
  111. val = readl(&pwm->tcfg1);
  112. val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
  113. val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
  114. writel(val, &pwm->tcfg1);
  115. if (pwm_id == 4) {
  116. /*
  117. * TODO(sjg): Use this as a countdown timer for now. We count
  118. * down from the maximum value to 0, then reset.
  119. */
  120. ticks_per_period = -1UL;
  121. } else {
  122. const unsigned long pwm_hz = 1000;
  123. unsigned long timer_rate_hz = get_pwm_clk() /
  124. ((prescaler + 1) * (1 << div));
  125. ticks_per_period = timer_rate_hz / pwm_hz;
  126. }
  127. /* set count value */
  128. offset = pwm_id * 3;
  129. writel(ticks_per_period, &pwm->tcntb0 + offset);
  130. val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
  131. if (invert && (pwm_id < 4))
  132. val |= TCON_INVERTER(pwm_id);
  133. writel(val, &pwm->tcon);
  134. pwm_enable(pwm_id);
  135. return 0;
  136. }