blob: 19ccbb0bc5d23c067c94996dc878bcafe6b55177 [file] [log] [blame]
Xu Kai7bc85632013-12-04 23:15:43 +08001/*
2 * * Copyright (c) 2013, The Linux Foundation. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above
10 * copyright notice, this list of conditions and the following
11 * disclaimer in the documentation and/or other materials provided
12 * with the distribution.
13 * * Neither the name of The Linux Foundation nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <debug.h>
31#include <platform/pm_pwm.h>
32#include <pm8x41_hw.h>
33
34#define NSEC_PER_USEC 1000L
35#define USEC_PER_SEC 1000000L
36#define NSEC_PER_SEC 1000000000L
37
38#define NUM_REF_CLOCKS 3
39#define NSEC_1024HZ (NSEC_PER_SEC / 1024)
40#define NSEC_32768HZ (NSEC_PER_SEC / 32768)
41#define NSEC_19P2MHZ (NSEC_PER_SEC / 19200000)
42
43#define NUM_PRE_DIVIDE 4
44#define PRE_DIVIDE_1 1
45#define PRE_DIVIDE_3 3
46#define PRE_DIVIDE_5 5
47#define PRE_DIVIDE_6 6
48static unsigned int pt_t[NUM_PRE_DIVIDE][NUM_REF_CLOCKS] = {
49 { PRE_DIVIDE_1 * NSEC_1024HZ,
50 PRE_DIVIDE_1 * NSEC_32768HZ,
51 PRE_DIVIDE_1 * NSEC_19P2MHZ,
52 },
53 { PRE_DIVIDE_3 * NSEC_1024HZ,
54 PRE_DIVIDE_3 * NSEC_32768HZ,
55 PRE_DIVIDE_3 * NSEC_19P2MHZ,
56 },
57 { PRE_DIVIDE_5 * NSEC_1024HZ,
58 PRE_DIVIDE_5 * NSEC_32768HZ,
59 PRE_DIVIDE_5 * NSEC_19P2MHZ,
60 },
61 { PRE_DIVIDE_6 * NSEC_1024HZ,
62 PRE_DIVIDE_6 * NSEC_32768HZ,
63 PRE_DIVIDE_6 * NSEC_19P2MHZ,
64 },
65};
66
67enum pwm_ctl_reg {
68 SIZE_CLK,
69 FREQ_PREDIV_CLK,
70 TYPE_CONFIG,
71 VALUE_LSB,
72 VALUE_MSB,
73};
74
75#define NUM_PWM_CTL_REGS 5
76struct pm_pwm_config {
77 int pwm_size; /* round up to 6 or 9 for 6/9-bit PWM SIZE */
78 int clk;
79 int pre_div;
80 int pre_div_exp;
81 int pwm_value;
82 uint8_t pwm_ctl[NUM_PWM_CTL_REGS];
83};
84
85static void pm_pwm_reg_write(uint8_t off, uint8_t val)
86{
87 REG_WRITE(PM_PWM_BASE(off), val);
88}
89
90/*
91 * PWM Frequency = Clock Frequency / (N * T)
92 * or
93 * PWM Period = Clock Period * (N * T)
94 * where
95 * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size
96 * T = Pre-divide * 2^m, where m = 0..7 (exponent)
97 *
98 * This is the formula to figure out m for the best pre-divide and clock:
99 * (PWM Period / N) = (Pre-divide * Clock Period) * 2^m
100 */
101
102#define PRE_DIVIDE_MAX 6
103#define CLK_PERIOD_MAX NSEC_1024HZ
104#define PM_PWM_M_MAX 7
105#define MAX_MPT ((PRE_DIVIDE_MAX * CLK_PERIOD_MAX) << PM_PWM_M_MAX)
106static void pm_pwm_calc_period(unsigned int period_us,
107 struct pm_pwm_config *pwm_config)
108{
109 int n, m, clk, div;
110 int best_m, best_div, best_clk;
111 unsigned int last_err, cur_err, min_err;
112 unsigned int tmp_p, period_n;
113
114 n = 6;
115
116 if (period_us < ((unsigned)(-1) / NSEC_PER_USEC))
117 period_n = (period_us * NSEC_PER_USEC) >> n;
118 else
119 period_n = (period_us >> n) * NSEC_PER_USEC;
120
121 if (period_n >= MAX_MPT) {
122 n = 9;
123 period_n >>= 3;
124 }
125
126 min_err = last_err = (unsigned)(-1);
127 best_m = 0;
128 best_clk = 0;
129 best_div = 0;
130 for (clk = 0; clk < NUM_REF_CLOCKS; clk++) {
131 for (div = 0; div < NUM_PRE_DIVIDE; div++) {
132 /* period_n = (PWM Period / N) */
133 /* tmp_p = (Pre-divide * Clock Period) * 2^m */
134 tmp_p = pt_t[div][clk];
135 for (m = 0; m <= PM_PWM_M_MAX; m++) {
136 if (period_n > tmp_p)
137 cur_err = period_n - tmp_p;
138 else
139 cur_err = tmp_p - period_n;
140
141 if (cur_err < min_err) {
142 min_err = cur_err;
143 best_m = m;
144 best_clk = clk;
145 best_div = div;
146 }
147
148 if (m && cur_err > last_err)
149 /* Break for bigger cur_err */
150 break;
151
152 last_err = cur_err;
153 tmp_p <<= 1;
154 }
155 }
156 }
157
158 pwm_config->pwm_size = n;
159 pwm_config->clk = best_clk;
160 pwm_config->pre_div = best_div;
161 pwm_config->pre_div_exp = best_m;
162}
163
164static void pm_pwm_calc_pwm_value(struct pm_pwm_config *pwm_config,
165 unsigned int period_us,
166 unsigned int duty_us)
167{
168 unsigned int max_pwm_value, tmp;
169
170 /* Figure out pwm_value with overflow handling */
171 tmp = 1 << (sizeof(tmp) * 8 - pwm_config->pwm_size);
172 if (duty_us < tmp) {
173 tmp = duty_us << pwm_config->pwm_size;
174 pwm_config->pwm_value = tmp / period_us;
175 } else {
176 tmp = period_us >> pwm_config->pwm_size;
177 pwm_config->pwm_value = duty_us / tmp;
178 }
179 max_pwm_value = (1 << pwm_config->pwm_size) - 1;
180 if (pwm_config->pwm_value > max_pwm_value)
181 pwm_config->pwm_value = max_pwm_value;
182}
183
184#define PM_PWM_SIZE_9_BIT 1
185#define PM_PWM_SIZE_6_BIT 0
186static void pm_pwm_config_regs(struct pm_pwm_config *pwm_config)
187{
188 int i;
189 uint8_t reg;
190
191 reg = ((pwm_config->pwm_size > 6 ? PM_PWM_SIZE_9_BIT : PM_PWM_SIZE_6_BIT)
192 << PM_PWM_SIZE_SEL_SHIFT)
193 & PM_PWM_SIZE_SEL_MASK;
194 reg |= (pwm_config->clk + 1) & PM_PWM_CLK_SEL_MASK;
195 pwm_config->pwm_ctl[SIZE_CLK] = reg;
196
197 reg = (pwm_config->pre_div << PM_PWM_PREDIVIDE_SHIFT)
198 & PM_PWM_PREDIVIDE_MASK;
199 reg |= pwm_config->pre_div_exp & PM_PWM_M_MASK;
200 pwm_config->pwm_ctl[FREQ_PREDIV_CLK] = reg;
201
202 /* Enable glitch removal by default */
203 reg = 1 << PM_PWM_EN_GLITCH_REMOVAL_SHIFT
204 & PM_PWM_EN_GLITCH_REMOVAL_MASK;
205 pwm_config->pwm_ctl[TYPE_CONFIG] = reg;
206
207 if (pwm_config->pwm_size > 6) {
208 pwm_config->pwm_ctl[VALUE_LSB] = pwm_config->pwm_value
209 & PM_PWM_VALUE_BIT7_0;
210 pwm_config->pwm_ctl[VALUE_MSB] = (pwm_config->pwm_value >> 8)
211 & PM_PWM_VALUE_BIT8;
212 } else
213 pwm_config->pwm_ctl[VALUE_LSB] = pwm_config->pwm_value
214 & PM_PWM_VALUE_BIT5_0;
215
216 for (i = 0; i < NUM_PWM_CTL_REGS; i++)
217 pm_pwm_reg_write(PM_PWM_CTL_REG_OFFSET + i, pwm_config->pwm_ctl[i]);
218
219 reg = 1 & PM_PWM_SYNC_MASK;
220 pm_pwm_reg_write(PM_PWM_SYNC_REG_OFFSET, reg);
221}
222
223/* usec: 19.2M, n=6, m=0, pre=2 */
224#define PM_PWM_PERIOD_MIN 7
225/* 1K, n=9, m=7, pre=6 */
226#define PM_PWM_PERIOD_MAX (384 * USEC_PER_SEC)
227int pm_pwm_config(unsigned int duty_us, unsigned int period_us)
228{
229 struct pm_pwm_config pwm_config;
230
231 if ((duty_us > period_us) || (period_us > PM_PWM_PERIOD_MAX) ||
232 (period_us < PM_PWM_PERIOD_MIN)) {
233 dprintf(CRITICAL, "Error in duty cycle and period\n");
234 return -1;
235 }
236
237 pm_pwm_calc_period(period_us, &pwm_config);
238 pm_pwm_calc_pwm_value(&pwm_config, period_us, duty_us);
239
240 dprintf(SPEW, "duty/period=%u/%u usec: pwm_value=%d (of %d)\n",
241 duty_us, period_us, pwm_config.pwm_value, 1 << pwm_config.pwm_size);
242
243 pm_pwm_config_regs(&pwm_config);
244
245 return 0;
246}
247
248void pm_pwm_enable(bool enable)
249{
250 uint8_t reg;
251
252 reg = enable << PM_PWM_ENABLE_CTL_SHIFT
253 & PM_PWM_ENABLE_CTL_MASK;
254
255 pm_pwm_reg_write(PM_PWM_ENABLE_CTL_REG_OFFSET, reg);
256}