pwm.c 3.5 KB

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