blob: 93d89f0e7553d06323e2a01e269cb517f1bfe512 [file] [log] [blame]
Willie Ruan0d9acd92011-07-04 21:31:30 -07001/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 */
13/*
14 * Qualcomm PMIC8058 PWM driver
15 *
16 */
17
Willie Ruan0d9acd92011-07-04 21:31:30 -070018#define pr_fmt(fmt) "%s: " fmt, __func__
19
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070020#include <linux/module.h>
21#include <linux/platform_device.h>
22#include <linux/err.h>
23#include <linux/pwm.h>
24#include <linux/mfd/pmic8058.h>
25#include <linux/pmic8058-pwm.h>
26#include <linux/slab.h>
27
28#define PM8058_LPG_BANKS 8
29#define PM8058_PWM_CHANNELS PM8058_LPG_BANKS /* MAX=8 */
30
31#define PM8058_LPG_CTL_REGS 7
32
33/* PMIC8058 LPG/PWM */
34#define SSBI_REG_ADDR_LPG_CTL_BASE 0x13C
35#define SSBI_REG_ADDR_LPG_CTL(n) (SSBI_REG_ADDR_LPG_CTL_BASE + (n))
36#define SSBI_REG_ADDR_LPG_BANK_SEL 0x143
37#define SSBI_REG_ADDR_LPG_BANK_EN 0x144
38#define SSBI_REG_ADDR_LPG_LUT_CFG0 0x145
39#define SSBI_REG_ADDR_LPG_LUT_CFG1 0x146
40#define SSBI_REG_ADDR_LPG_TEST 0x147
41
42/* Control 0 */
43#define PM8058_PWM_1KHZ_COUNT_MASK 0xF0
44#define PM8058_PWM_1KHZ_COUNT_SHIFT 4
45
46#define PM8058_PWM_1KHZ_COUNT_MAX 15
47
48#define PM8058_PWM_OUTPUT_EN 0x08
49#define PM8058_PWM_PWM_EN 0x04
50#define PM8058_PWM_RAMP_GEN_EN 0x02
51#define PM8058_PWM_RAMP_START 0x01
52
53#define PM8058_PWM_PWM_START (PM8058_PWM_OUTPUT_EN \
54 | PM8058_PWM_PWM_EN)
55#define PM8058_PWM_RAMP_GEN_START (PM8058_PWM_RAMP_GEN_EN \
56 | PM8058_PWM_RAMP_START)
57
58/* Control 1 */
59#define PM8058_PWM_REVERSE_EN 0x80
60#define PM8058_PWM_BYPASS_LUT 0x40
61#define PM8058_PWM_HIGH_INDEX_MASK 0x3F
62
63/* Control 2 */
64#define PM8058_PWM_LOOP_EN 0x80
65#define PM8058_PWM_RAMP_UP 0x40
66#define PM8058_PWM_LOW_INDEX_MASK 0x3F
67
68/* Control 3 */
69#define PM8058_PWM_VALUE_BIT7_0 0xFF
70#define PM8058_PWM_VALUE_BIT5_0 0x3F
71
72/* Control 4 */
73#define PM8058_PWM_VALUE_BIT8 0x80
74
75#define PM8058_PWM_CLK_SEL_MASK 0x60
76#define PM8058_PWM_CLK_SEL_SHIFT 5
77
78#define PM8058_PWM_CLK_SEL_NO 0
79#define PM8058_PWM_CLK_SEL_1KHZ 1
80#define PM8058_PWM_CLK_SEL_32KHZ 2
81#define PM8058_PWM_CLK_SEL_19P2MHZ 3
82
83#define PM8058_PWM_PREDIVIDE_MASK 0x18
84#define PM8058_PWM_PREDIVIDE_SHIFT 3
85
86#define PM8058_PWM_PREDIVIDE_2 0
87#define PM8058_PWM_PREDIVIDE_3 1
88#define PM8058_PWM_PREDIVIDE_5 2
89#define PM8058_PWM_PREDIVIDE_6 3
90
91#define PM8058_PWM_M_MASK 0x07
92#define PM8058_PWM_M_MIN 0
93#define PM8058_PWM_M_MAX 7
94
95/* Control 5 */
96#define PM8058_PWM_PAUSE_COUNT_HI_MASK 0xFC
97#define PM8058_PWM_PAUSE_COUNT_HI_SHIFT 2
98
99#define PM8058_PWM_PAUSE_ENABLE_HIGH 0x02
100#define PM8058_PWM_SIZE_9_BIT 0x01
101
102/* Control 6 */
103#define PM8058_PWM_PAUSE_COUNT_LO_MASK 0xFC
104#define PM8058_PWM_PAUSE_COUNT_LO_SHIFT 2
105
106#define PM8058_PWM_PAUSE_ENABLE_LOW 0x02
107#define PM8058_PWM_RESERVED 0x01
108
109#define PM8058_PWM_PAUSE_COUNT_MAX 56 /* < 2^6 = 64*/
110
111/* LUT_CFG1 */
112#define PM8058_PWM_LUT_READ 0x40
113
114/* TEST */
115#define PM8058_PWM_DTEST_MASK 0x38
116#define PM8058_PWM_DTEST_SHIFT 3
117
118#define PM8058_PWM_DTEST_BANK_MASK 0x07
119
120/* PWM frequency support
121 *
122 * PWM Frequency = Clock Frequency / (N * T)
123 * or
124 * PWM Period = Clock Period * (N * T)
125 * where
126 * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size
127 * T = Pre-divide * 2^m, m = 0..7 (exponent)
128 *
129 * We use this formula to figure out m for the best pre-divide and clock:
130 * (PWM Period / N) / 2^m = (Pre-divide * Clock Period)
131*/
132#define NUM_CLOCKS 3
133
134#define NSEC_1000HZ (NSEC_PER_SEC / 1000)
135#define NSEC_32768HZ (NSEC_PER_SEC / 32768)
136#define NSEC_19P2MHZ (NSEC_PER_SEC / 19200000)
137
138#define CLK_PERIOD_MIN NSEC_19P2MHZ
139#define CLK_PERIOD_MAX NSEC_1000HZ
140
141#define NUM_PRE_DIVIDE 3 /* No default support for pre-divide = 6 */
142
143#define PRE_DIVIDE_0 2
144#define PRE_DIVIDE_1 3
145#define PRE_DIVIDE_2 5
146
147#define PRE_DIVIDE_MIN PRE_DIVIDE_0
148#define PRE_DIVIDE_MAX PRE_DIVIDE_2
149
150static char *clks[NUM_CLOCKS] = {
151 "1K", "32768", "19.2M"
152};
153
154static unsigned pre_div[NUM_PRE_DIVIDE] = {
155 PRE_DIVIDE_0, PRE_DIVIDE_1, PRE_DIVIDE_2
156};
157
158static unsigned int pt_t[NUM_PRE_DIVIDE][NUM_CLOCKS] = {
159 { PRE_DIVIDE_0 * NSEC_1000HZ,
160 PRE_DIVIDE_0 * NSEC_32768HZ,
161 PRE_DIVIDE_0 * NSEC_19P2MHZ,
162 },
163 { PRE_DIVIDE_1 * NSEC_1000HZ,
164 PRE_DIVIDE_1 * NSEC_32768HZ,
165 PRE_DIVIDE_1 * NSEC_19P2MHZ,
166 },
167 { PRE_DIVIDE_2 * NSEC_1000HZ,
168 PRE_DIVIDE_2 * NSEC_32768HZ,
169 PRE_DIVIDE_2 * NSEC_19P2MHZ,
170 },
171};
172
173#define MIN_MPT ((PRE_DIVIDE_MIN * CLK_PERIOD_MIN) << PM8058_PWM_M_MIN)
174#define MAX_MPT ((PRE_DIVIDE_MAX * CLK_PERIOD_MAX) << PM8058_PWM_M_MAX)
175
Willie Ruan368db792011-07-05 08:09:58 -0700176#define CHAN_LUT_SIZE (PM_PWM_LUT_SIZE / PM8058_PWM_CHANNELS)
177
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700178/* Private data */
179struct pm8058_pwm_chip;
180
181struct pwm_device {
182 int pwm_id; /* = bank/channel id */
183 int in_use;
184 const char *label;
Willie Ruand3337ed2011-07-04 23:16:22 -0700185 struct pm8058_pwm_period period;
186 int pwm_value;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700187 int pwm_period;
Willie Ruan368db792011-07-05 08:09:58 -0700188 int use_lut; /* Use LUT to output PWM */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700189 u8 pwm_ctl[PM8058_LPG_CTL_REGS];
190 int irq;
191 struct pm8058_pwm_chip *chip;
192};
193
194struct pm8058_pwm_chip {
195 struct pwm_device pwm_dev[PM8058_PWM_CHANNELS];
196 u8 bank_mask;
197 struct mutex pwm_mutex;
198 struct pm8058_chip *pm_chip;
199 struct pm8058_pwm_pdata *pdata;
200};
201
202static struct pm8058_pwm_chip *pwm_chip;
203
Willie Ruand3337ed2011-07-04 23:16:22 -0700204struct pm8058_pwm_lut {
205 /* LUT parameters */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700206 int lut_duty_ms;
207 int lut_lo_index;
208 int lut_hi_index;
209 int lut_pause_hi;
210 int lut_pause_lo;
211 int flags;
212};
213
214static u16 duty_msec[PM8058_PWM_1KHZ_COUNT_MAX + 1] = {
215 0, 1, 2, 3, 4, 6, 8, 16, 18, 24, 32, 36, 64, 128, 256, 512
216};
217
218static u16 pause_count[PM8058_PWM_PAUSE_COUNT_MAX + 1] = {
219 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
220 23, 28, 31, 42, 47, 56, 63, 83, 94, 111, 125, 167, 188, 222, 250, 333,
221 375, 500, 667, 750, 800, 900, 1000, 1100,
222 1200, 1300, 1400, 1500, 1600, 1800, 2000, 2500,
223 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500,
224 7000
225};
226
227/* Internal functions */
Willie Ruanefc2fa42011-07-05 00:12:41 -0700228static void pm8058_pwm_save(u8 *u8p, u8 mask, u8 val)
229{
230 *u8p &= ~mask;
231 *u8p |= val & mask;
232}
233
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700234static int pm8058_pwm_bank_enable(struct pwm_device *pwm, int enable)
235{
236 int rc;
237 u8 reg;
238 struct pm8058_pwm_chip *chip;
239
240 chip = pwm->chip;
241
242 if (enable)
243 reg = chip->bank_mask | (1 << pwm->pwm_id);
244 else
245 reg = chip->bank_mask & ~(1 << pwm->pwm_id);
246
247 rc = pm8058_write(chip->pm_chip, SSBI_REG_ADDR_LPG_BANK_EN, &reg, 1);
248 if (rc) {
Willie Ruan0d9acd92011-07-04 21:31:30 -0700249 pr_err("pm8058_write(): rc=%d (Enable LPG Bank)\n", rc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700250 goto bail_out;
251 }
252 chip->bank_mask = reg;
253
254bail_out:
255 return rc;
256}
257
258static int pm8058_pwm_bank_sel(struct pwm_device *pwm)
259{
260 int rc;
261 u8 reg;
262
263 reg = pwm->pwm_id;
264 rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_BANK_SEL,
265 &reg, 1);
266 if (rc)
Willie Ruan0d9acd92011-07-04 21:31:30 -0700267 pr_err("pm8058_write(): rc=%d (Select PWM Bank)\n", rc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700268 return rc;
269}
270
271static int pm8058_pwm_start(struct pwm_device *pwm, int start, int ramp_start)
272{
273 int rc;
274 u8 reg;
275
276 if (start) {
277 reg = pwm->pwm_ctl[0] | PM8058_PWM_PWM_START;
278 if (ramp_start)
279 reg |= PM8058_PWM_RAMP_GEN_START;
280 else
281 reg &= ~PM8058_PWM_RAMP_GEN_START;
282 } else {
283 reg = pwm->pwm_ctl[0] & ~PM8058_PWM_PWM_START;
284 reg &= ~PM8058_PWM_RAMP_GEN_START;
285 }
286
287 rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_CTL(0),
288 &reg, 1);
289 if (rc)
Willie Ruan0d9acd92011-07-04 21:31:30 -0700290 pr_err("pm8058_write(): rc=%d (Enable PWM Ctl 0)\n", rc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700291 else
292 pwm->pwm_ctl[0] = reg;
293 return rc;
294}
295
296static void pm8058_pwm_calc_period(unsigned int period_us,
Willie Ruand3337ed2011-07-04 23:16:22 -0700297 struct pm8058_pwm_period *period)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700298{
299 int n, m, clk, div;
300 int best_m, best_div, best_clk;
301 int last_err, cur_err, better_err, better_m;
302 unsigned int tmp_p, last_p, min_err, period_n;
303
304 /* PWM Period / N : handle underflow or overflow */
305 if (period_us < (PM_PWM_PERIOD_MAX / NSEC_PER_USEC))
306 period_n = (period_us * NSEC_PER_USEC) >> 6;
307 else
308 period_n = (period_us >> 6) * NSEC_PER_USEC;
309 if (period_n >= MAX_MPT) {
310 n = 9;
311 period_n >>= 3;
312 } else
313 n = 6;
314
315 min_err = MAX_MPT;
316 best_m = 0;
317 best_clk = 0;
318 best_div = 0;
319 for (clk = 0; clk < NUM_CLOCKS; clk++) {
320 for (div = 0; div < NUM_PRE_DIVIDE; div++) {
321 tmp_p = period_n;
322 last_p = tmp_p;
323 for (m = 0; m <= PM8058_PWM_M_MAX; m++) {
324 if (tmp_p <= pt_t[div][clk]) {
325 /* Found local best */
326 if (!m) {
327 better_err = pt_t[div][clk] -
328 tmp_p;
329 better_m = m;
330 } else {
331 last_err = last_p -
332 pt_t[div][clk];
333 cur_err = pt_t[div][clk] -
334 tmp_p;
335
336 if (cur_err < last_err) {
337 better_err = cur_err;
338 better_m = m;
339 } else {
340 better_err = last_err;
341 better_m = m - 1;
342 }
343 }
344
345 if (better_err < min_err) {
346 min_err = better_err;
347 best_m = better_m;
348 best_clk = clk;
349 best_div = div;
350 }
351 break;
352 } else {
353 last_p = tmp_p;
354 tmp_p >>= 1;
355 }
356 }
357 }
358 }
359
Willie Ruand3337ed2011-07-04 23:16:22 -0700360 period->pwm_size = n;
361 period->clk = best_clk;
362 period->pre_div = best_div;
363 period->pre_div_exp = best_m;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700364
Willie Ruan0d9acd92011-07-04 21:31:30 -0700365 pr_debug("period=%u: n=%d, m=%d, clk[%d]=%s, div[%d]=%d\n",
366 (unsigned)period_us, n, best_m,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700367 best_clk, clks[best_clk], best_div, pre_div[best_div]);
368}
369
Willie Ruanefc2fa42011-07-05 00:12:41 -0700370static void pm8058_pwm_calc_pwm_value(struct pwm_device *pwm,
371 unsigned int period_us,
372 unsigned int duty_us)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700373{
Willie Ruanefc2fa42011-07-05 00:12:41 -0700374 unsigned int max_pwm_value, tmp;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700375
Willie Ruanefc2fa42011-07-05 00:12:41 -0700376 /* Figure out pwm_value with overflow handling */
377 tmp = 1 << (sizeof(tmp) * 8 - pwm->period.pwm_size);
378 if (duty_us < tmp) {
379 tmp = duty_us << pwm->period.pwm_size;
380 pwm->pwm_value = tmp / period_us;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700381 } else {
Willie Ruanefc2fa42011-07-05 00:12:41 -0700382 tmp = period_us >> pwm->period.pwm_size;
383 pwm->pwm_value = duty_us / tmp;
384 }
385 max_pwm_value = (1 << pwm->period.pwm_size) - 1;
386 if (pwm->pwm_value > max_pwm_value)
387 pwm->pwm_value = max_pwm_value;
388}
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700389
Willie Ruanefc2fa42011-07-05 00:12:41 -0700390static int pm8058_pwm_change_table(struct pwm_device *pwm, int duty_pct[],
391 int start_idx, int len, int raw_value)
392{
393 unsigned int pwm_value, max_pwm_value;
394 u8 cfg0, cfg1;
395 int i, pwm_size;
396 int rc = 0;
397
398 pwm_size = (pwm->pwm_ctl[5] & PM8058_PWM_SIZE_9_BIT) ? 9 : 6;
399 max_pwm_value = (1 << pwm_size) - 1;
400 for (i = 0; i < len; i++) {
401 if (raw_value)
402 pwm_value = duty_pct[i];
403 else
404 pwm_value = (duty_pct[i] << pwm_size) / 100;
405
406 if (pwm_value > max_pwm_value)
407 pwm_value = max_pwm_value;
408 cfg0 = pwm_value;
409 cfg1 = (pwm_value >> 1) & 0x80;
410 cfg1 |= start_idx + i;
411
412 rc = pm8058_write(pwm->chip->pm_chip,
413 SSBI_REG_ADDR_LPG_LUT_CFG0,
414 &cfg0, 1);
415 if (rc)
416 break;
417
418 rc = pm8058_write(pwm->chip->pm_chip,
419 SSBI_REG_ADDR_LPG_LUT_CFG1,
420 &cfg1, 1);
421 if (rc)
422 break;
423 }
424 return rc;
425}
426
427static void pm8058_pwm_save_index(struct pwm_device *pwm,
428 int low_idx, int high_idx, int flags)
429{
430 pwm->pwm_ctl[1] = high_idx & PM8058_PWM_HIGH_INDEX_MASK;
431 pwm->pwm_ctl[2] = low_idx & PM8058_PWM_LOW_INDEX_MASK;
432
433 if (flags & PM_PWM_LUT_REVERSE)
434 pwm->pwm_ctl[1] |= PM8058_PWM_REVERSE_EN;
435 if (flags & PM_PWM_LUT_RAMP_UP)
436 pwm->pwm_ctl[2] |= PM8058_PWM_RAMP_UP;
437 if (flags & PM_PWM_LUT_LOOP)
438 pwm->pwm_ctl[2] |= PM8058_PWM_LOOP_EN;
439}
440
441static void pm8058_pwm_save_period(struct pwm_device *pwm)
442{
443 u8 mask, val;
444
445 val = ((pwm->period.clk + 1) << PM8058_PWM_CLK_SEL_SHIFT)
446 & PM8058_PWM_CLK_SEL_MASK;
447 val |= (pwm->period.pre_div << PM8058_PWM_PREDIVIDE_SHIFT)
448 & PM8058_PWM_PREDIVIDE_MASK;
449 val |= pwm->period.pre_div_exp & PM8058_PWM_M_MASK;
450 mask = PM8058_PWM_CLK_SEL_MASK | PM8058_PWM_PREDIVIDE_MASK |
451 PM8058_PWM_M_MASK;
452 pm8058_pwm_save(&pwm->pwm_ctl[4], mask, val);
453
454 val = (pwm->period.pwm_size > 6) ? PM8058_PWM_SIZE_9_BIT : 0;
455 mask = PM8058_PWM_SIZE_9_BIT;
456 pm8058_pwm_save(&pwm->pwm_ctl[5], mask, val);
457}
458
459static void pm8058_pwm_save_pwm_value(struct pwm_device *pwm)
460{
461 u8 mask, val;
462
463 pwm->pwm_ctl[3] = pwm->pwm_value;
464
465 val = (pwm->period.pwm_size > 6) ? (pwm->pwm_value >> 1) : 0;
466 mask = PM8058_PWM_VALUE_BIT8;
467 pm8058_pwm_save(&pwm->pwm_ctl[4], mask, val);
468}
469
470static void pm8058_pwm_save_duty_time(struct pwm_device *pwm,
471 struct pm8058_pwm_lut *lut)
472{
473 int i;
474 u8 mask, val;
475
476 /* Linear search for duty time */
477 for (i = 0; i < PM8058_PWM_1KHZ_COUNT_MAX; i++) {
478 if (duty_msec[i] >= lut->lut_duty_ms)
479 break;
480 }
481 val = i << PM8058_PWM_1KHZ_COUNT_SHIFT;
482
483 mask = PM8058_PWM_1KHZ_COUNT_MASK;
484 pm8058_pwm_save(&pwm->pwm_ctl[0], mask, val);
485}
486
487static void pm8058_pwm_save_pause(struct pwm_device *pwm,
488 struct pm8058_pwm_lut *lut)
489{
490 int i, pause_cnt, time_cnt;
491 u8 mask, val;
492
493 time_cnt = (pwm->pwm_ctl[0] & PM8058_PWM_1KHZ_COUNT_MASK)
494 >> PM8058_PWM_1KHZ_COUNT_SHIFT;
495 if (lut->flags & PM_PWM_LUT_PAUSE_HI_EN) {
496 pause_cnt = (lut->lut_pause_hi + duty_msec[time_cnt] / 2)
497 / duty_msec[time_cnt];
498 /* Linear search for pause time */
499 for (i = 0; i < PM8058_PWM_PAUSE_COUNT_MAX; i++) {
500 if (pause_count[i] >= pause_cnt)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700501 break;
502 }
Willie Ruanefc2fa42011-07-05 00:12:41 -0700503 val = (i << PM8058_PWM_PAUSE_COUNT_HI_SHIFT) &
504 PM8058_PWM_PAUSE_COUNT_HI_MASK;
505 val |= PM8058_PWM_PAUSE_ENABLE_HIGH;
506 } else
507 val = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700508
Willie Ruanefc2fa42011-07-05 00:12:41 -0700509 mask = PM8058_PWM_PAUSE_COUNT_HI_MASK | PM8058_PWM_PAUSE_ENABLE_HIGH;
510 pm8058_pwm_save(&pwm->pwm_ctl[5], mask, val);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700511
Willie Ruanefc2fa42011-07-05 00:12:41 -0700512 if (lut->flags & PM_PWM_LUT_PAUSE_LO_EN) {
513 /* Linear search for pause time */
514 pause_cnt = (lut->lut_pause_lo + duty_msec[time_cnt] / 2)
515 / duty_msec[time_cnt];
516 for (i = 0; i < PM8058_PWM_PAUSE_COUNT_MAX; i++) {
517 if (pause_count[i] >= pause_cnt)
518 break;
519 }
520 val = (i << PM8058_PWM_PAUSE_COUNT_LO_SHIFT) &
521 PM8058_PWM_PAUSE_COUNT_LO_MASK;
522 val |= PM8058_PWM_PAUSE_ENABLE_LOW;
523 } else
524 val = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700525
Willie Ruanefc2fa42011-07-05 00:12:41 -0700526 mask = PM8058_PWM_PAUSE_COUNT_LO_MASK | PM8058_PWM_PAUSE_ENABLE_LOW;
527 pm8058_pwm_save(&pwm->pwm_ctl[6], mask, val);
528}
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700529
Willie Ruanefc2fa42011-07-05 00:12:41 -0700530static int pm8058_pwm_write(struct pwm_device *pwm, int start, int end)
531{
532 int i, rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700533
Willie Ruanefc2fa42011-07-05 00:12:41 -0700534 /* Write in reverse way so 0 would be the last */
535 for (i = end - 1; i >= start; i--) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700536 rc = pm8058_write(pwm->chip->pm_chip,
537 SSBI_REG_ADDR_LPG_CTL(i),
538 &pwm->pwm_ctl[i], 1);
539 if (rc) {
Willie Ruan0d9acd92011-07-04 21:31:30 -0700540 pr_err("pm8058_write(): rc=%d (PWM Ctl[%d])\n", rc, i);
Willie Ruanefc2fa42011-07-05 00:12:41 -0700541 return rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700542 }
543 }
544
Willie Ruanefc2fa42011-07-05 00:12:41 -0700545 return 0;
546}
547
548static int pm8058_pwm_change_lut(struct pwm_device *pwm,
549 struct pm8058_pwm_lut *lut)
550{
551 int rc;
552
553 pm8058_pwm_save_index(pwm, lut->lut_lo_index,
554 lut->lut_hi_index, lut->flags);
555 pm8058_pwm_save_duty_time(pwm, lut);
556 pm8058_pwm_save_pause(pwm, lut);
557 pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0);
558
559 pm8058_pwm_bank_sel(pwm);
560 rc = pm8058_pwm_write(pwm, 0, 7);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700561
562 return rc;
563}
564
565/* APIs */
566/*
567 * pwm_request - request a PWM device
568 */
569struct pwm_device *pwm_request(int pwm_id, const char *label)
570{
571 struct pwm_device *pwm;
572
573 if (pwm_id > PM8058_PWM_CHANNELS || pwm_id < 0)
574 return ERR_PTR(-EINVAL);
575 if (pwm_chip == NULL)
576 return ERR_PTR(-ENODEV);
577
578 mutex_lock(&pwm_chip->pwm_mutex);
579 pwm = &pwm_chip->pwm_dev[pwm_id];
580 if (!pwm->in_use) {
581 pwm->in_use = 1;
582 pwm->label = label;
Willie Ruan368db792011-07-05 08:09:58 -0700583 pwm->use_lut = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700584
585 if (pwm_chip->pdata && pwm_chip->pdata->config)
586 pwm_chip->pdata->config(pwm, pwm_id, 1);
587 } else
588 pwm = ERR_PTR(-EBUSY);
589 mutex_unlock(&pwm_chip->pwm_mutex);
590
591 return pwm;
592}
593EXPORT_SYMBOL(pwm_request);
594
595/*
596 * pwm_free - free a PWM device
597 */
598void pwm_free(struct pwm_device *pwm)
599{
600 if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL)
601 return;
602
603 mutex_lock(&pwm->chip->pwm_mutex);
604 if (pwm->in_use) {
605 pm8058_pwm_bank_sel(pwm);
606 pm8058_pwm_start(pwm, 0, 0);
607
608 if (pwm->chip->pdata && pwm->chip->pdata->config)
609 pwm->chip->pdata->config(pwm, pwm->pwm_id, 0);
610
611 pwm->in_use = 0;
612 pwm->label = NULL;
613 }
614 pm8058_pwm_bank_enable(pwm, 0);
615 mutex_unlock(&pwm->chip->pwm_mutex);
616}
617EXPORT_SYMBOL(pwm_free);
618
619/*
620 * pwm_config - change a PWM device configuration
621 *
622 * @pwm: the PWM device
623 * @period_us: period in micro second
624 * @duty_us: duty cycle in micro second
625 */
626int pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
627{
Willie Ruand3337ed2011-07-04 23:16:22 -0700628 int rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700629
630 if (pwm == NULL || IS_ERR(pwm) ||
Willie Ruan8a08b962011-07-05 00:43:37 -0700631 duty_us > period_us ||
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700632 (unsigned)period_us > PM_PWM_PERIOD_MAX ||
633 (unsigned)period_us < PM_PWM_PERIOD_MIN)
634 return -EINVAL;
635 if (pwm->chip == NULL)
636 return -ENODEV;
637
638 mutex_lock(&pwm->chip->pwm_mutex);
639
640 if (!pwm->in_use) {
641 rc = -EINVAL;
642 goto out_unlock;
643 }
644
Willie Ruan8a08b962011-07-05 00:43:37 -0700645 if (pwm->pwm_period != period_us) {
646 pm8058_pwm_calc_period(period_us, &pwm->period);
647 pm8058_pwm_save_period(pwm);
648 pwm->pwm_period = period_us;
649 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700650
Willie Ruanefc2fa42011-07-05 00:12:41 -0700651 pm8058_pwm_calc_pwm_value(pwm, period_us, duty_us);
652 pm8058_pwm_save_pwm_value(pwm);
653 pm8058_pwm_save(&pwm->pwm_ctl[1],
654 PM8058_PWM_BYPASS_LUT, PM8058_PWM_BYPASS_LUT);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700655
Willie Ruanefc2fa42011-07-05 00:12:41 -0700656 pm8058_pwm_bank_sel(pwm);
657 rc = pm8058_pwm_write(pwm, 1, 6);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700658
Willie Ruan0d9acd92011-07-04 21:31:30 -0700659 pr_debug("duty/period=%u/%u usec: pwm_value=%d (of %d)\n",
660 (unsigned)duty_us, (unsigned)period_us,
Willie Ruand3337ed2011-07-04 23:16:22 -0700661 pwm->pwm_value, 1 << pwm->period.pwm_size);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700662
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700663out_unlock:
664 mutex_unlock(&pwm->chip->pwm_mutex);
665 return rc;
666}
667EXPORT_SYMBOL(pwm_config);
668
669/*
670 * pwm_enable - start a PWM output toggling
671 */
672int pwm_enable(struct pwm_device *pwm)
673{
674 int rc;
675
676 if (pwm == NULL || IS_ERR(pwm))
677 return -EINVAL;
678 if (pwm->chip == NULL)
679 return -ENODEV;
680
681 mutex_lock(&pwm->chip->pwm_mutex);
682 if (!pwm->in_use)
683 rc = -EINVAL;
684 else {
685 if (pwm->chip->pdata && pwm->chip->pdata->enable)
686 pwm->chip->pdata->enable(pwm, pwm->pwm_id, 1);
687
688 rc = pm8058_pwm_bank_enable(pwm, 1);
689
690 pm8058_pwm_bank_sel(pwm);
691 pm8058_pwm_start(pwm, 1, 0);
692 }
693 mutex_unlock(&pwm->chip->pwm_mutex);
694 return rc;
695}
696EXPORT_SYMBOL(pwm_enable);
697
698/*
699 * pwm_disable - stop a PWM output toggling
700 */
701void pwm_disable(struct pwm_device *pwm)
702{
703 if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL)
704 return;
705
706 mutex_lock(&pwm->chip->pwm_mutex);
707 if (pwm->in_use) {
708 pm8058_pwm_bank_sel(pwm);
709 pm8058_pwm_start(pwm, 0, 0);
710
711 pm8058_pwm_bank_enable(pwm, 0);
712
713 if (pwm->chip->pdata && pwm->chip->pdata->enable)
714 pwm->chip->pdata->enable(pwm, pwm->pwm_id, 0);
715 }
716 mutex_unlock(&pwm->chip->pwm_mutex);
717}
718EXPORT_SYMBOL(pwm_disable);
719
Willie Ruan368db792011-07-05 08:09:58 -0700720/**
721 * pm8058_pwm_config_period - change PWM period
722 *
723 * @pwm: the PWM device
724 * @pwm_p: period in struct pm8058_pwm_period
725 */
726int pm8058_pwm_config_period(struct pwm_device *pwm,
727 struct pm8058_pwm_period *period)
728{
729 int rc;
730
731 if (pwm == NULL || IS_ERR(pwm) || period == NULL)
732 return -EINVAL;
733 if (pwm->chip == NULL)
734 return -ENODEV;
735
736 mutex_lock(&pwm->chip->pwm_mutex);
737
738 if (!pwm->in_use) {
739 rc = -EINVAL;
740 goto out_unlock;
741 }
742
743 pwm->period.pwm_size = period->pwm_size;
744 pwm->period.clk = period->clk;
745 pwm->period.pre_div = period->pre_div;
746 pwm->period.pre_div_exp = period->pre_div_exp;
747
748 pm8058_pwm_save_period(pwm);
749 pm8058_pwm_bank_sel(pwm);
750 rc = pm8058_pwm_write(pwm, 4, 6);
751
752out_unlock:
753 mutex_unlock(&pwm->chip->pwm_mutex);
754 return rc;
755}
756EXPORT_SYMBOL(pm8058_pwm_config_period);
757
758/**
759 * pm8058_pwm_config_duty_cycle - change PWM duty cycle
760 *
761 * @pwm: the PWM device
762 * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size)
763 */
764int pm8058_pwm_config_duty_cycle(struct pwm_device *pwm, int pwm_value)
765{
766 struct pm8058_pwm_lut lut;
767 int flags, start_idx;
768 int rc = 0;
769
770 if (pwm == NULL || IS_ERR(pwm))
771 return -EINVAL;
772 if (pwm->chip == NULL)
773 return -ENODEV;
774
775 mutex_lock(&pwm->chip->pwm_mutex);
776
777 if (!pwm->in_use || !pwm->pwm_period) {
778 rc = -EINVAL;
779 goto out_unlock;
780 }
781
782 if (pwm->pwm_value == pwm_value)
783 goto out_unlock;
784
785 pwm->pwm_value = pwm_value;
786 flags = PM_PWM_LUT_RAMP_UP;
787
788 start_idx = pwm->pwm_id * CHAN_LUT_SIZE;
789 pm8058_pwm_change_table(pwm, &pwm_value, start_idx, 1, 1);
790
791 if (!pwm->use_lut) {
792 pwm->use_lut = 1;
793
794 lut.lut_duty_ms = 1;
795 lut.lut_lo_index = start_idx;
796 lut.lut_hi_index = start_idx;
797 lut.lut_pause_lo = 0;
798 lut.lut_pause_hi = 0;
799 lut.flags = flags;
800
801 rc = pm8058_pwm_change_lut(pwm, &lut);
802 } else {
803 pm8058_pwm_save_index(pwm, start_idx, start_idx, flags);
804 pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0);
805
806 pm8058_pwm_bank_sel(pwm);
807 rc = pm8058_pwm_write(pwm, 0, 3);
808 }
809
810 if (rc)
811 pr_err("[%d]: pm8058_pwm_write: rc=%d\n", pwm->pwm_id, rc);
812
813out_unlock:
814 mutex_unlock(&pwm->chip->pwm_mutex);
815 return rc;
816}
817EXPORT_SYMBOL(pm8058_pwm_config_duty_cycle);
818
819/**
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700820 * pm8058_pwm_lut_config - change a PWM device configuration to use LUT
821 *
822 * @pwm: the PWM device
823 * @period_us: period in micro second
824 * @duty_pct: arrary of duty cycles in percent, like 20, 50.
825 * @duty_time_ms: time for each duty cycle in millisecond
826 * @start_idx: start index in lookup table from 0 to MAX-1
827 * @idx_len: number of index
828 * @pause_lo: pause time in millisecond at low index
829 * @pause_hi: pause time in millisecond at high index
830 * @flags: control flags
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700831 */
832int pm8058_pwm_lut_config(struct pwm_device *pwm, int period_us,
833 int duty_pct[], int duty_time_ms, int start_idx,
834 int idx_len, int pause_lo, int pause_hi, int flags)
835{
Willie Ruanefc2fa42011-07-05 00:12:41 -0700836 struct pm8058_pwm_lut pwm_conf;
837 int len;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700838 int rc;
839
840 if (pwm == NULL || IS_ERR(pwm) || !idx_len)
841 return -EINVAL;
842 if (duty_pct == NULL && !(flags & PM_PWM_LUT_NO_TABLE))
843 return -EINVAL;
844 if (pwm->chip == NULL)
845 return -ENODEV;
846 if (idx_len >= PM_PWM_LUT_SIZE && start_idx)
847 return -EINVAL;
848 if ((start_idx + idx_len) > PM_PWM_LUT_SIZE)
849 return -EINVAL;
850 if ((unsigned)period_us > PM_PWM_PERIOD_MAX ||
851 (unsigned)period_us < PM_PWM_PERIOD_MIN)
852 return -EINVAL;
853
854 mutex_lock(&pwm->chip->pwm_mutex);
855
856 if (!pwm->in_use) {
857 rc = -EINVAL;
858 goto out_unlock;
859 }
860
Willie Ruan8a08b962011-07-05 00:43:37 -0700861 if (pwm->pwm_period != period_us) {
862 pm8058_pwm_calc_period(period_us, &pwm->period);
863 pm8058_pwm_save_period(pwm);
864 pwm->pwm_period = period_us;
865 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700866
867 len = (idx_len > PM_PWM_LUT_SIZE) ? PM_PWM_LUT_SIZE : idx_len;
868
869 if (flags & PM_PWM_LUT_NO_TABLE)
870 goto after_table_write;
871
Willie Ruanefc2fa42011-07-05 00:12:41 -0700872 rc = pm8058_pwm_change_table(pwm, duty_pct, start_idx, len, 0);
873 if (rc) {
874 pr_err("pm8058_pwm_change_table: rc=%d\n", rc);
875 goto out_unlock;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700876 }
877
878after_table_write:
879 pwm_conf.lut_duty_ms = duty_time_ms;
880 pwm_conf.lut_lo_index = start_idx;
881 pwm_conf.lut_hi_index = start_idx + len - 1;
882 pwm_conf.lut_pause_lo = pause_lo;
883 pwm_conf.lut_pause_hi = pause_hi;
884 pwm_conf.flags = flags;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700885
Willie Ruanefc2fa42011-07-05 00:12:41 -0700886 rc = pm8058_pwm_change_lut(pwm, &pwm_conf);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700887
888out_unlock:
889 mutex_unlock(&pwm->chip->pwm_mutex);
890 return rc;
891}
892EXPORT_SYMBOL(pm8058_pwm_lut_config);
893
Willie Ruan368db792011-07-05 08:09:58 -0700894/**
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700895 * pm8058_pwm_lut_enable - control a PWM device to start/stop LUT ramp
896 *
897 * @pwm: the PWM device
898 * @start: to start (1), or stop (0)
899 */
900int pm8058_pwm_lut_enable(struct pwm_device *pwm, int start)
901{
902 if (pwm == NULL || IS_ERR(pwm))
903 return -EINVAL;
904 if (pwm->chip == NULL)
905 return -ENODEV;
906
907 mutex_lock(&pwm->chip->pwm_mutex);
908 if (start) {
909 pm8058_pwm_bank_enable(pwm, 1);
910
911 pm8058_pwm_bank_sel(pwm);
912 pm8058_pwm_start(pwm, 1, 1);
913 } else {
914 pm8058_pwm_bank_sel(pwm);
915 pm8058_pwm_start(pwm, 0, 0);
916
917 pm8058_pwm_bank_enable(pwm, 0);
918 }
919 mutex_unlock(&pwm->chip->pwm_mutex);
920 return 0;
921}
922EXPORT_SYMBOL(pm8058_pwm_lut_enable);
923
924#define SSBI_REG_ADDR_LED_BASE 0x131
925#define SSBI_REG_ADDR_LED(n) (SSBI_REG_ADDR_LED_BASE + (n))
926#define SSBI_REG_ADDR_FLASH_BASE 0x48
927#define SSBI_REG_ADDR_FLASH_DRV_1 0xFB
928#define SSBI_REG_ADDR_FLASH(n) (((n) < 2 ? \
929 SSBI_REG_ADDR_FLASH_BASE + (n) : \
930 SSBI_REG_ADDR_FLASH_DRV_1))
931
932#define PM8058_LED_CURRENT_SHIFT 3
933#define PM8058_LED_MODE_MASK 0x07
934
935#define PM8058_FLASH_CURRENT_SHIFT 4
936#define PM8058_FLASH_MODE_MASK 0x03
937#define PM8058_FLASH_MODE_NONE 0
938#define PM8058_FLASH_MODE_DTEST1 1
939#define PM8058_FLASH_MODE_DTEST2 2
940#define PM8058_FLASH_MODE_PWM 3
941
942int pm8058_pwm_config_led(struct pwm_device *pwm, int id,
943 int mode, int max_current)
944{
945 int rc;
946 u8 conf;
947
948 switch (id) {
949 case PM_PWM_LED_0:
950 case PM_PWM_LED_1:
951 case PM_PWM_LED_2:
952 conf = mode & PM8058_LED_MODE_MASK;
953 conf |= (max_current / 2) << PM8058_LED_CURRENT_SHIFT;
954 rc = pm8058_write(pwm->chip->pm_chip,
955 SSBI_REG_ADDR_LED(id), &conf, 1);
956 break;
957
958 case PM_PWM_LED_KPD:
959 case PM_PWM_LED_FLASH:
960 case PM_PWM_LED_FLASH1:
961 switch (mode) {
962 case PM_PWM_CONF_PWM1:
963 case PM_PWM_CONF_PWM2:
964 case PM_PWM_CONF_PWM3:
965 conf = PM8058_FLASH_MODE_PWM;
966 break;
967 case PM_PWM_CONF_DTEST1:
968 conf = PM8058_FLASH_MODE_DTEST1;
969 break;
970 case PM_PWM_CONF_DTEST2:
971 conf = PM8058_FLASH_MODE_DTEST2;
972 break;
973 default:
974 conf = PM8058_FLASH_MODE_NONE;
975 break;
976 }
977 conf |= (max_current / 20) << PM8058_FLASH_CURRENT_SHIFT;
978 id -= PM_PWM_LED_KPD;
979 rc = pm8058_write(pwm->chip->pm_chip,
980 SSBI_REG_ADDR_FLASH(id), &conf, 1);
981 break;
982 default:
983 rc = -EINVAL;
984 break;
985 }
986
987 return rc;
988}
989EXPORT_SYMBOL(pm8058_pwm_config_led);
990
991int pm8058_pwm_set_dtest(struct pwm_device *pwm, int enable)
992{
993 int rc;
994 u8 reg;
995
996 if (pwm == NULL || IS_ERR(pwm))
997 return -EINVAL;
998 if (pwm->chip == NULL)
999 return -ENODEV;
1000
1001 if (!pwm->in_use)
1002 rc = -EINVAL;
1003 else {
1004 reg = pwm->pwm_id & PM8058_PWM_DTEST_BANK_MASK;
1005 if (enable)
1006 /* Only Test 1 available */
1007 reg |= (1 << PM8058_PWM_DTEST_SHIFT) &
1008 PM8058_PWM_DTEST_MASK;
1009 rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_TEST,
1010 &reg, 1);
1011 if (rc)
Willie Ruan0d9acd92011-07-04 21:31:30 -07001012 pr_err("pm8058_write(DTEST=0x%x): rc=%d\n", reg, rc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001013
1014 }
1015 return rc;
1016}
1017EXPORT_SYMBOL(pm8058_pwm_set_dtest);
1018
1019static int __devinit pmic8058_pwm_probe(struct platform_device *pdev)
1020{
1021 struct pm8058_chip *pm_chip;
1022 struct pm8058_pwm_chip *chip;
1023 int i;
1024
1025 pm_chip = dev_get_drvdata(pdev->dev.parent);
1026 if (pm_chip == NULL) {
Willie Ruan0d9acd92011-07-04 21:31:30 -07001027 pr_err("no parent data passed in.\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001028 return -EFAULT;
1029 }
1030
1031 chip = kzalloc(sizeof *chip, GFP_KERNEL);
1032 if (chip == NULL) {
Willie Ruan0d9acd92011-07-04 21:31:30 -07001033 pr_err("kzalloc() failed.\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001034 return -ENOMEM;
1035 }
1036
1037 for (i = 0; i < PM8058_PWM_CHANNELS; i++) {
1038 chip->pwm_dev[i].pwm_id = i;
1039 chip->pwm_dev[i].chip = chip;
1040 }
1041
1042 mutex_init(&chip->pwm_mutex);
1043
1044 chip->pdata = pdev->dev.platform_data;
1045 chip->pm_chip = pm_chip;
1046 pwm_chip = chip;
1047 platform_set_drvdata(pdev, chip);
1048
Willie Ruan0d9acd92011-07-04 21:31:30 -07001049 pr_notice("OK\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001050 return 0;
1051}
1052
1053static int __devexit pmic8058_pwm_remove(struct platform_device *pdev)
1054{
1055 struct pm8058_pwm_chip *chip = platform_get_drvdata(pdev);
1056
1057 platform_set_drvdata(pdev, NULL);
1058 kfree(chip);
1059 return 0;
1060}
1061
1062static struct platform_driver pmic8058_pwm_driver = {
1063 .probe = pmic8058_pwm_probe,
1064 .remove = __devexit_p(pmic8058_pwm_remove),
1065 .driver = {
1066 .name = "pm8058-pwm",
1067 .owner = THIS_MODULE,
1068 },
1069};
1070
1071static int __init pm8058_pwm_init(void)
1072{
1073 return platform_driver_register(&pmic8058_pwm_driver);
1074}
1075
1076static void __exit pm8058_pwm_exit(void)
1077{
1078 platform_driver_unregister(&pmic8058_pwm_driver);
1079}
1080
1081subsys_initcall(pm8058_pwm_init);
1082module_exit(pm8058_pwm_exit);
1083
1084MODULE_LICENSE("GPL v2");
1085MODULE_DESCRIPTION("PMIC8058 PWM driver");
1086MODULE_VERSION("1.0");
1087MODULE_ALIAS("platform:pmic8058_pwm");