blob: ad03b886f74c49a3cdba648fad363f653aa44de9 [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 Ruan7bd18192011-07-05 14:11:17 -0700360 /* Use higher resolution */
361 if (best_m >= 3 && n == 6) {
362 n += 3;
363 best_m -= 3;
364 }
365
Willie Ruand3337ed2011-07-04 23:16:22 -0700366 period->pwm_size = n;
367 period->clk = best_clk;
368 period->pre_div = best_div;
369 period->pre_div_exp = best_m;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700370
Willie Ruan0d9acd92011-07-04 21:31:30 -0700371 pr_debug("period=%u: n=%d, m=%d, clk[%d]=%s, div[%d]=%d\n",
372 (unsigned)period_us, n, best_m,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700373 best_clk, clks[best_clk], best_div, pre_div[best_div]);
374}
375
Willie Ruanefc2fa42011-07-05 00:12:41 -0700376static void pm8058_pwm_calc_pwm_value(struct pwm_device *pwm,
377 unsigned int period_us,
378 unsigned int duty_us)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700379{
Willie Ruanefc2fa42011-07-05 00:12:41 -0700380 unsigned int max_pwm_value, tmp;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700381
Willie Ruanefc2fa42011-07-05 00:12:41 -0700382 /* Figure out pwm_value with overflow handling */
383 tmp = 1 << (sizeof(tmp) * 8 - pwm->period.pwm_size);
384 if (duty_us < tmp) {
385 tmp = duty_us << pwm->period.pwm_size;
386 pwm->pwm_value = tmp / period_us;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700387 } else {
Willie Ruanefc2fa42011-07-05 00:12:41 -0700388 tmp = period_us >> pwm->period.pwm_size;
389 pwm->pwm_value = duty_us / tmp;
390 }
391 max_pwm_value = (1 << pwm->period.pwm_size) - 1;
392 if (pwm->pwm_value > max_pwm_value)
393 pwm->pwm_value = max_pwm_value;
394}
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700395
Willie Ruanefc2fa42011-07-05 00:12:41 -0700396static int pm8058_pwm_change_table(struct pwm_device *pwm, int duty_pct[],
397 int start_idx, int len, int raw_value)
398{
399 unsigned int pwm_value, max_pwm_value;
400 u8 cfg0, cfg1;
401 int i, pwm_size;
402 int rc = 0;
403
404 pwm_size = (pwm->pwm_ctl[5] & PM8058_PWM_SIZE_9_BIT) ? 9 : 6;
405 max_pwm_value = (1 << pwm_size) - 1;
406 for (i = 0; i < len; i++) {
407 if (raw_value)
408 pwm_value = duty_pct[i];
409 else
410 pwm_value = (duty_pct[i] << pwm_size) / 100;
411
412 if (pwm_value > max_pwm_value)
413 pwm_value = max_pwm_value;
414 cfg0 = pwm_value;
415 cfg1 = (pwm_value >> 1) & 0x80;
416 cfg1 |= start_idx + i;
417
418 rc = pm8058_write(pwm->chip->pm_chip,
419 SSBI_REG_ADDR_LPG_LUT_CFG0,
420 &cfg0, 1);
421 if (rc)
422 break;
423
424 rc = pm8058_write(pwm->chip->pm_chip,
425 SSBI_REG_ADDR_LPG_LUT_CFG1,
426 &cfg1, 1);
427 if (rc)
428 break;
429 }
430 return rc;
431}
432
433static void pm8058_pwm_save_index(struct pwm_device *pwm,
434 int low_idx, int high_idx, int flags)
435{
436 pwm->pwm_ctl[1] = high_idx & PM8058_PWM_HIGH_INDEX_MASK;
437 pwm->pwm_ctl[2] = low_idx & PM8058_PWM_LOW_INDEX_MASK;
438
439 if (flags & PM_PWM_LUT_REVERSE)
440 pwm->pwm_ctl[1] |= PM8058_PWM_REVERSE_EN;
441 if (flags & PM_PWM_LUT_RAMP_UP)
442 pwm->pwm_ctl[2] |= PM8058_PWM_RAMP_UP;
443 if (flags & PM_PWM_LUT_LOOP)
444 pwm->pwm_ctl[2] |= PM8058_PWM_LOOP_EN;
445}
446
447static void pm8058_pwm_save_period(struct pwm_device *pwm)
448{
449 u8 mask, val;
450
451 val = ((pwm->period.clk + 1) << PM8058_PWM_CLK_SEL_SHIFT)
452 & PM8058_PWM_CLK_SEL_MASK;
453 val |= (pwm->period.pre_div << PM8058_PWM_PREDIVIDE_SHIFT)
454 & PM8058_PWM_PREDIVIDE_MASK;
455 val |= pwm->period.pre_div_exp & PM8058_PWM_M_MASK;
456 mask = PM8058_PWM_CLK_SEL_MASK | PM8058_PWM_PREDIVIDE_MASK |
457 PM8058_PWM_M_MASK;
458 pm8058_pwm_save(&pwm->pwm_ctl[4], mask, val);
459
460 val = (pwm->period.pwm_size > 6) ? PM8058_PWM_SIZE_9_BIT : 0;
461 mask = PM8058_PWM_SIZE_9_BIT;
462 pm8058_pwm_save(&pwm->pwm_ctl[5], mask, val);
463}
464
465static void pm8058_pwm_save_pwm_value(struct pwm_device *pwm)
466{
467 u8 mask, val;
468
469 pwm->pwm_ctl[3] = pwm->pwm_value;
470
471 val = (pwm->period.pwm_size > 6) ? (pwm->pwm_value >> 1) : 0;
472 mask = PM8058_PWM_VALUE_BIT8;
473 pm8058_pwm_save(&pwm->pwm_ctl[4], mask, val);
474}
475
476static void pm8058_pwm_save_duty_time(struct pwm_device *pwm,
477 struct pm8058_pwm_lut *lut)
478{
479 int i;
480 u8 mask, val;
481
482 /* Linear search for duty time */
483 for (i = 0; i < PM8058_PWM_1KHZ_COUNT_MAX; i++) {
484 if (duty_msec[i] >= lut->lut_duty_ms)
485 break;
486 }
487 val = i << PM8058_PWM_1KHZ_COUNT_SHIFT;
488
489 mask = PM8058_PWM_1KHZ_COUNT_MASK;
490 pm8058_pwm_save(&pwm->pwm_ctl[0], mask, val);
491}
492
493static void pm8058_pwm_save_pause(struct pwm_device *pwm,
494 struct pm8058_pwm_lut *lut)
495{
496 int i, pause_cnt, time_cnt;
497 u8 mask, val;
498
499 time_cnt = (pwm->pwm_ctl[0] & PM8058_PWM_1KHZ_COUNT_MASK)
500 >> PM8058_PWM_1KHZ_COUNT_SHIFT;
501 if (lut->flags & PM_PWM_LUT_PAUSE_HI_EN) {
502 pause_cnt = (lut->lut_pause_hi + duty_msec[time_cnt] / 2)
503 / duty_msec[time_cnt];
504 /* Linear search for pause time */
505 for (i = 0; i < PM8058_PWM_PAUSE_COUNT_MAX; i++) {
506 if (pause_count[i] >= pause_cnt)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700507 break;
508 }
Willie Ruanefc2fa42011-07-05 00:12:41 -0700509 val = (i << PM8058_PWM_PAUSE_COUNT_HI_SHIFT) &
510 PM8058_PWM_PAUSE_COUNT_HI_MASK;
511 val |= PM8058_PWM_PAUSE_ENABLE_HIGH;
512 } else
513 val = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700514
Willie Ruanefc2fa42011-07-05 00:12:41 -0700515 mask = PM8058_PWM_PAUSE_COUNT_HI_MASK | PM8058_PWM_PAUSE_ENABLE_HIGH;
516 pm8058_pwm_save(&pwm->pwm_ctl[5], mask, val);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700517
Willie Ruanefc2fa42011-07-05 00:12:41 -0700518 if (lut->flags & PM_PWM_LUT_PAUSE_LO_EN) {
519 /* Linear search for pause time */
520 pause_cnt = (lut->lut_pause_lo + duty_msec[time_cnt] / 2)
521 / duty_msec[time_cnt];
522 for (i = 0; i < PM8058_PWM_PAUSE_COUNT_MAX; i++) {
523 if (pause_count[i] >= pause_cnt)
524 break;
525 }
526 val = (i << PM8058_PWM_PAUSE_COUNT_LO_SHIFT) &
527 PM8058_PWM_PAUSE_COUNT_LO_MASK;
528 val |= PM8058_PWM_PAUSE_ENABLE_LOW;
529 } else
530 val = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700531
Willie Ruanefc2fa42011-07-05 00:12:41 -0700532 mask = PM8058_PWM_PAUSE_COUNT_LO_MASK | PM8058_PWM_PAUSE_ENABLE_LOW;
533 pm8058_pwm_save(&pwm->pwm_ctl[6], mask, val);
534}
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700535
Willie Ruanefc2fa42011-07-05 00:12:41 -0700536static int pm8058_pwm_write(struct pwm_device *pwm, int start, int end)
537{
538 int i, rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700539
Willie Ruanefc2fa42011-07-05 00:12:41 -0700540 /* Write in reverse way so 0 would be the last */
541 for (i = end - 1; i >= start; i--) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700542 rc = pm8058_write(pwm->chip->pm_chip,
543 SSBI_REG_ADDR_LPG_CTL(i),
544 &pwm->pwm_ctl[i], 1);
545 if (rc) {
Willie Ruan0d9acd92011-07-04 21:31:30 -0700546 pr_err("pm8058_write(): rc=%d (PWM Ctl[%d])\n", rc, i);
Willie Ruanefc2fa42011-07-05 00:12:41 -0700547 return rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700548 }
549 }
550
Willie Ruanefc2fa42011-07-05 00:12:41 -0700551 return 0;
552}
553
554static int pm8058_pwm_change_lut(struct pwm_device *pwm,
555 struct pm8058_pwm_lut *lut)
556{
557 int rc;
558
559 pm8058_pwm_save_index(pwm, lut->lut_lo_index,
560 lut->lut_hi_index, lut->flags);
561 pm8058_pwm_save_duty_time(pwm, lut);
562 pm8058_pwm_save_pause(pwm, lut);
563 pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0);
564
565 pm8058_pwm_bank_sel(pwm);
566 rc = pm8058_pwm_write(pwm, 0, 7);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700567
568 return rc;
569}
570
571/* APIs */
572/*
573 * pwm_request - request a PWM device
574 */
575struct pwm_device *pwm_request(int pwm_id, const char *label)
576{
577 struct pwm_device *pwm;
578
579 if (pwm_id > PM8058_PWM_CHANNELS || pwm_id < 0)
580 return ERR_PTR(-EINVAL);
581 if (pwm_chip == NULL)
582 return ERR_PTR(-ENODEV);
583
584 mutex_lock(&pwm_chip->pwm_mutex);
585 pwm = &pwm_chip->pwm_dev[pwm_id];
586 if (!pwm->in_use) {
587 pwm->in_use = 1;
588 pwm->label = label;
Willie Ruan368db792011-07-05 08:09:58 -0700589 pwm->use_lut = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700590
591 if (pwm_chip->pdata && pwm_chip->pdata->config)
592 pwm_chip->pdata->config(pwm, pwm_id, 1);
593 } else
594 pwm = ERR_PTR(-EBUSY);
595 mutex_unlock(&pwm_chip->pwm_mutex);
596
597 return pwm;
598}
599EXPORT_SYMBOL(pwm_request);
600
601/*
602 * pwm_free - free a PWM device
603 */
604void pwm_free(struct pwm_device *pwm)
605{
606 if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL)
607 return;
608
609 mutex_lock(&pwm->chip->pwm_mutex);
610 if (pwm->in_use) {
611 pm8058_pwm_bank_sel(pwm);
612 pm8058_pwm_start(pwm, 0, 0);
613
614 if (pwm->chip->pdata && pwm->chip->pdata->config)
615 pwm->chip->pdata->config(pwm, pwm->pwm_id, 0);
616
617 pwm->in_use = 0;
618 pwm->label = NULL;
619 }
620 pm8058_pwm_bank_enable(pwm, 0);
621 mutex_unlock(&pwm->chip->pwm_mutex);
622}
623EXPORT_SYMBOL(pwm_free);
624
625/*
626 * pwm_config - change a PWM device configuration
627 *
628 * @pwm: the PWM device
629 * @period_us: period in micro second
630 * @duty_us: duty cycle in micro second
631 */
632int pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
633{
Willie Ruand3337ed2011-07-04 23:16:22 -0700634 int rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700635
636 if (pwm == NULL || IS_ERR(pwm) ||
Willie Ruan8a08b962011-07-05 00:43:37 -0700637 duty_us > period_us ||
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700638 (unsigned)period_us > PM_PWM_PERIOD_MAX ||
639 (unsigned)period_us < PM_PWM_PERIOD_MIN)
640 return -EINVAL;
641 if (pwm->chip == NULL)
642 return -ENODEV;
643
644 mutex_lock(&pwm->chip->pwm_mutex);
645
646 if (!pwm->in_use) {
647 rc = -EINVAL;
648 goto out_unlock;
649 }
650
Willie Ruan8a08b962011-07-05 00:43:37 -0700651 if (pwm->pwm_period != period_us) {
652 pm8058_pwm_calc_period(period_us, &pwm->period);
653 pm8058_pwm_save_period(pwm);
654 pwm->pwm_period = period_us;
655 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700656
Willie Ruanefc2fa42011-07-05 00:12:41 -0700657 pm8058_pwm_calc_pwm_value(pwm, period_us, duty_us);
658 pm8058_pwm_save_pwm_value(pwm);
659 pm8058_pwm_save(&pwm->pwm_ctl[1],
660 PM8058_PWM_BYPASS_LUT, PM8058_PWM_BYPASS_LUT);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700661
Willie Ruanefc2fa42011-07-05 00:12:41 -0700662 pm8058_pwm_bank_sel(pwm);
663 rc = pm8058_pwm_write(pwm, 1, 6);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700664
Willie Ruan0d9acd92011-07-04 21:31:30 -0700665 pr_debug("duty/period=%u/%u usec: pwm_value=%d (of %d)\n",
666 (unsigned)duty_us, (unsigned)period_us,
Willie Ruand3337ed2011-07-04 23:16:22 -0700667 pwm->pwm_value, 1 << pwm->period.pwm_size);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700668
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700669out_unlock:
670 mutex_unlock(&pwm->chip->pwm_mutex);
671 return rc;
672}
673EXPORT_SYMBOL(pwm_config);
674
675/*
676 * pwm_enable - start a PWM output toggling
677 */
678int pwm_enable(struct pwm_device *pwm)
679{
680 int rc;
681
682 if (pwm == NULL || IS_ERR(pwm))
683 return -EINVAL;
684 if (pwm->chip == NULL)
685 return -ENODEV;
686
687 mutex_lock(&pwm->chip->pwm_mutex);
688 if (!pwm->in_use)
689 rc = -EINVAL;
690 else {
691 if (pwm->chip->pdata && pwm->chip->pdata->enable)
692 pwm->chip->pdata->enable(pwm, pwm->pwm_id, 1);
693
694 rc = pm8058_pwm_bank_enable(pwm, 1);
695
696 pm8058_pwm_bank_sel(pwm);
697 pm8058_pwm_start(pwm, 1, 0);
698 }
699 mutex_unlock(&pwm->chip->pwm_mutex);
700 return rc;
701}
702EXPORT_SYMBOL(pwm_enable);
703
704/*
705 * pwm_disable - stop a PWM output toggling
706 */
707void pwm_disable(struct pwm_device *pwm)
708{
709 if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL)
710 return;
711
712 mutex_lock(&pwm->chip->pwm_mutex);
713 if (pwm->in_use) {
714 pm8058_pwm_bank_sel(pwm);
715 pm8058_pwm_start(pwm, 0, 0);
716
717 pm8058_pwm_bank_enable(pwm, 0);
718
719 if (pwm->chip->pdata && pwm->chip->pdata->enable)
720 pwm->chip->pdata->enable(pwm, pwm->pwm_id, 0);
721 }
722 mutex_unlock(&pwm->chip->pwm_mutex);
723}
724EXPORT_SYMBOL(pwm_disable);
725
Willie Ruan368db792011-07-05 08:09:58 -0700726/**
727 * pm8058_pwm_config_period - change PWM period
728 *
729 * @pwm: the PWM device
730 * @pwm_p: period in struct pm8058_pwm_period
731 */
732int pm8058_pwm_config_period(struct pwm_device *pwm,
733 struct pm8058_pwm_period *period)
734{
735 int rc;
736
737 if (pwm == NULL || IS_ERR(pwm) || period == NULL)
738 return -EINVAL;
739 if (pwm->chip == NULL)
740 return -ENODEV;
741
742 mutex_lock(&pwm->chip->pwm_mutex);
743
744 if (!pwm->in_use) {
745 rc = -EINVAL;
746 goto out_unlock;
747 }
748
749 pwm->period.pwm_size = period->pwm_size;
750 pwm->period.clk = period->clk;
751 pwm->period.pre_div = period->pre_div;
752 pwm->period.pre_div_exp = period->pre_div_exp;
753
754 pm8058_pwm_save_period(pwm);
755 pm8058_pwm_bank_sel(pwm);
756 rc = pm8058_pwm_write(pwm, 4, 6);
757
758out_unlock:
759 mutex_unlock(&pwm->chip->pwm_mutex);
760 return rc;
761}
762EXPORT_SYMBOL(pm8058_pwm_config_period);
763
764/**
765 * pm8058_pwm_config_duty_cycle - change PWM duty cycle
766 *
767 * @pwm: the PWM device
768 * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size)
769 */
770int pm8058_pwm_config_duty_cycle(struct pwm_device *pwm, int pwm_value)
771{
772 struct pm8058_pwm_lut lut;
773 int flags, start_idx;
774 int rc = 0;
775
776 if (pwm == NULL || IS_ERR(pwm))
777 return -EINVAL;
778 if (pwm->chip == NULL)
779 return -ENODEV;
780
781 mutex_lock(&pwm->chip->pwm_mutex);
782
783 if (!pwm->in_use || !pwm->pwm_period) {
784 rc = -EINVAL;
785 goto out_unlock;
786 }
787
788 if (pwm->pwm_value == pwm_value)
789 goto out_unlock;
790
791 pwm->pwm_value = pwm_value;
792 flags = PM_PWM_LUT_RAMP_UP;
793
794 start_idx = pwm->pwm_id * CHAN_LUT_SIZE;
795 pm8058_pwm_change_table(pwm, &pwm_value, start_idx, 1, 1);
796
797 if (!pwm->use_lut) {
798 pwm->use_lut = 1;
799
800 lut.lut_duty_ms = 1;
801 lut.lut_lo_index = start_idx;
802 lut.lut_hi_index = start_idx;
803 lut.lut_pause_lo = 0;
804 lut.lut_pause_hi = 0;
805 lut.flags = flags;
806
807 rc = pm8058_pwm_change_lut(pwm, &lut);
808 } else {
809 pm8058_pwm_save_index(pwm, start_idx, start_idx, flags);
810 pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0);
811
812 pm8058_pwm_bank_sel(pwm);
813 rc = pm8058_pwm_write(pwm, 0, 3);
814 }
815
816 if (rc)
817 pr_err("[%d]: pm8058_pwm_write: rc=%d\n", pwm->pwm_id, rc);
818
819out_unlock:
820 mutex_unlock(&pwm->chip->pwm_mutex);
821 return rc;
822}
823EXPORT_SYMBOL(pm8058_pwm_config_duty_cycle);
824
825/**
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700826 * pm8058_pwm_lut_config - change a PWM device configuration to use LUT
827 *
828 * @pwm: the PWM device
829 * @period_us: period in micro second
830 * @duty_pct: arrary of duty cycles in percent, like 20, 50.
831 * @duty_time_ms: time for each duty cycle in millisecond
832 * @start_idx: start index in lookup table from 0 to MAX-1
833 * @idx_len: number of index
834 * @pause_lo: pause time in millisecond at low index
835 * @pause_hi: pause time in millisecond at high index
836 * @flags: control flags
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700837 */
838int pm8058_pwm_lut_config(struct pwm_device *pwm, int period_us,
839 int duty_pct[], int duty_time_ms, int start_idx,
840 int idx_len, int pause_lo, int pause_hi, int flags)
841{
Willie Ruan965072e2011-07-05 14:03:55 -0700842 struct pm8058_pwm_lut lut;
Willie Ruanefc2fa42011-07-05 00:12:41 -0700843 int len;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700844 int rc;
845
846 if (pwm == NULL || IS_ERR(pwm) || !idx_len)
847 return -EINVAL;
848 if (duty_pct == NULL && !(flags & PM_PWM_LUT_NO_TABLE))
849 return -EINVAL;
850 if (pwm->chip == NULL)
851 return -ENODEV;
852 if (idx_len >= PM_PWM_LUT_SIZE && start_idx)
853 return -EINVAL;
854 if ((start_idx + idx_len) > PM_PWM_LUT_SIZE)
855 return -EINVAL;
856 if ((unsigned)period_us > PM_PWM_PERIOD_MAX ||
857 (unsigned)period_us < PM_PWM_PERIOD_MIN)
858 return -EINVAL;
859
860 mutex_lock(&pwm->chip->pwm_mutex);
861
862 if (!pwm->in_use) {
863 rc = -EINVAL;
864 goto out_unlock;
865 }
866
Willie Ruan8a08b962011-07-05 00:43:37 -0700867 if (pwm->pwm_period != period_us) {
868 pm8058_pwm_calc_period(period_us, &pwm->period);
869 pm8058_pwm_save_period(pwm);
870 pwm->pwm_period = period_us;
871 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700872
873 len = (idx_len > PM_PWM_LUT_SIZE) ? PM_PWM_LUT_SIZE : idx_len;
874
875 if (flags & PM_PWM_LUT_NO_TABLE)
876 goto after_table_write;
877
Willie Ruanefc2fa42011-07-05 00:12:41 -0700878 rc = pm8058_pwm_change_table(pwm, duty_pct, start_idx, len, 0);
879 if (rc) {
880 pr_err("pm8058_pwm_change_table: rc=%d\n", rc);
881 goto out_unlock;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700882 }
883
884after_table_write:
Willie Ruan965072e2011-07-05 14:03:55 -0700885 lut.lut_duty_ms = duty_time_ms;
886 lut.lut_lo_index = start_idx;
887 lut.lut_hi_index = start_idx + len - 1;
888 lut.lut_pause_lo = pause_lo;
889 lut.lut_pause_hi = pause_hi;
890 lut.flags = flags;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700891
Willie Ruan965072e2011-07-05 14:03:55 -0700892 rc = pm8058_pwm_change_lut(pwm, &lut);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700893
894out_unlock:
895 mutex_unlock(&pwm->chip->pwm_mutex);
896 return rc;
897}
898EXPORT_SYMBOL(pm8058_pwm_lut_config);
899
Willie Ruan368db792011-07-05 08:09:58 -0700900/**
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700901 * pm8058_pwm_lut_enable - control a PWM device to start/stop LUT ramp
902 *
903 * @pwm: the PWM device
904 * @start: to start (1), or stop (0)
905 */
906int pm8058_pwm_lut_enable(struct pwm_device *pwm, int start)
907{
908 if (pwm == NULL || IS_ERR(pwm))
909 return -EINVAL;
910 if (pwm->chip == NULL)
911 return -ENODEV;
912
913 mutex_lock(&pwm->chip->pwm_mutex);
914 if (start) {
915 pm8058_pwm_bank_enable(pwm, 1);
916
917 pm8058_pwm_bank_sel(pwm);
918 pm8058_pwm_start(pwm, 1, 1);
919 } else {
920 pm8058_pwm_bank_sel(pwm);
921 pm8058_pwm_start(pwm, 0, 0);
922
923 pm8058_pwm_bank_enable(pwm, 0);
924 }
925 mutex_unlock(&pwm->chip->pwm_mutex);
926 return 0;
927}
928EXPORT_SYMBOL(pm8058_pwm_lut_enable);
929
930#define SSBI_REG_ADDR_LED_BASE 0x131
931#define SSBI_REG_ADDR_LED(n) (SSBI_REG_ADDR_LED_BASE + (n))
932#define SSBI_REG_ADDR_FLASH_BASE 0x48
933#define SSBI_REG_ADDR_FLASH_DRV_1 0xFB
934#define SSBI_REG_ADDR_FLASH(n) (((n) < 2 ? \
935 SSBI_REG_ADDR_FLASH_BASE + (n) : \
936 SSBI_REG_ADDR_FLASH_DRV_1))
937
938#define PM8058_LED_CURRENT_SHIFT 3
939#define PM8058_LED_MODE_MASK 0x07
940
941#define PM8058_FLASH_CURRENT_SHIFT 4
942#define PM8058_FLASH_MODE_MASK 0x03
943#define PM8058_FLASH_MODE_NONE 0
944#define PM8058_FLASH_MODE_DTEST1 1
945#define PM8058_FLASH_MODE_DTEST2 2
946#define PM8058_FLASH_MODE_PWM 3
947
948int pm8058_pwm_config_led(struct pwm_device *pwm, int id,
949 int mode, int max_current)
950{
951 int rc;
952 u8 conf;
953
954 switch (id) {
955 case PM_PWM_LED_0:
956 case PM_PWM_LED_1:
957 case PM_PWM_LED_2:
958 conf = mode & PM8058_LED_MODE_MASK;
959 conf |= (max_current / 2) << PM8058_LED_CURRENT_SHIFT;
960 rc = pm8058_write(pwm->chip->pm_chip,
961 SSBI_REG_ADDR_LED(id), &conf, 1);
962 break;
963
964 case PM_PWM_LED_KPD:
965 case PM_PWM_LED_FLASH:
966 case PM_PWM_LED_FLASH1:
967 switch (mode) {
968 case PM_PWM_CONF_PWM1:
969 case PM_PWM_CONF_PWM2:
970 case PM_PWM_CONF_PWM3:
971 conf = PM8058_FLASH_MODE_PWM;
972 break;
973 case PM_PWM_CONF_DTEST1:
974 conf = PM8058_FLASH_MODE_DTEST1;
975 break;
976 case PM_PWM_CONF_DTEST2:
977 conf = PM8058_FLASH_MODE_DTEST2;
978 break;
979 default:
980 conf = PM8058_FLASH_MODE_NONE;
981 break;
982 }
983 conf |= (max_current / 20) << PM8058_FLASH_CURRENT_SHIFT;
984 id -= PM_PWM_LED_KPD;
985 rc = pm8058_write(pwm->chip->pm_chip,
986 SSBI_REG_ADDR_FLASH(id), &conf, 1);
987 break;
988 default:
989 rc = -EINVAL;
990 break;
991 }
992
993 return rc;
994}
995EXPORT_SYMBOL(pm8058_pwm_config_led);
996
997int pm8058_pwm_set_dtest(struct pwm_device *pwm, int enable)
998{
999 int rc;
1000 u8 reg;
1001
1002 if (pwm == NULL || IS_ERR(pwm))
1003 return -EINVAL;
1004 if (pwm->chip == NULL)
1005 return -ENODEV;
1006
1007 if (!pwm->in_use)
1008 rc = -EINVAL;
1009 else {
1010 reg = pwm->pwm_id & PM8058_PWM_DTEST_BANK_MASK;
1011 if (enable)
1012 /* Only Test 1 available */
1013 reg |= (1 << PM8058_PWM_DTEST_SHIFT) &
1014 PM8058_PWM_DTEST_MASK;
1015 rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_TEST,
1016 &reg, 1);
1017 if (rc)
Willie Ruan0d9acd92011-07-04 21:31:30 -07001018 pr_err("pm8058_write(DTEST=0x%x): rc=%d\n", reg, rc);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001019
1020 }
1021 return rc;
1022}
1023EXPORT_SYMBOL(pm8058_pwm_set_dtest);
1024
1025static int __devinit pmic8058_pwm_probe(struct platform_device *pdev)
1026{
1027 struct pm8058_chip *pm_chip;
1028 struct pm8058_pwm_chip *chip;
1029 int i;
1030
1031 pm_chip = dev_get_drvdata(pdev->dev.parent);
1032 if (pm_chip == NULL) {
Willie Ruan0d9acd92011-07-04 21:31:30 -07001033 pr_err("no parent data passed in.\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001034 return -EFAULT;
1035 }
1036
1037 chip = kzalloc(sizeof *chip, GFP_KERNEL);
1038 if (chip == NULL) {
Willie Ruan0d9acd92011-07-04 21:31:30 -07001039 pr_err("kzalloc() failed.\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001040 return -ENOMEM;
1041 }
1042
1043 for (i = 0; i < PM8058_PWM_CHANNELS; i++) {
1044 chip->pwm_dev[i].pwm_id = i;
1045 chip->pwm_dev[i].chip = chip;
1046 }
1047
1048 mutex_init(&chip->pwm_mutex);
1049
1050 chip->pdata = pdev->dev.platform_data;
1051 chip->pm_chip = pm_chip;
1052 pwm_chip = chip;
1053 platform_set_drvdata(pdev, chip);
1054
Willie Ruan0d9acd92011-07-04 21:31:30 -07001055 pr_notice("OK\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001056 return 0;
1057}
1058
1059static int __devexit pmic8058_pwm_remove(struct platform_device *pdev)
1060{
1061 struct pm8058_pwm_chip *chip = platform_get_drvdata(pdev);
1062
1063 platform_set_drvdata(pdev, NULL);
1064 kfree(chip);
1065 return 0;
1066}
1067
1068static struct platform_driver pmic8058_pwm_driver = {
1069 .probe = pmic8058_pwm_probe,
1070 .remove = __devexit_p(pmic8058_pwm_remove),
1071 .driver = {
1072 .name = "pm8058-pwm",
1073 .owner = THIS_MODULE,
1074 },
1075};
1076
1077static int __init pm8058_pwm_init(void)
1078{
1079 return platform_driver_register(&pmic8058_pwm_driver);
1080}
1081
1082static void __exit pm8058_pwm_exit(void)
1083{
1084 platform_driver_unregister(&pmic8058_pwm_driver);
1085}
1086
1087subsys_initcall(pm8058_pwm_init);
1088module_exit(pm8058_pwm_exit);
1089
1090MODULE_LICENSE("GPL v2");
1091MODULE_DESCRIPTION("PMIC8058 PWM driver");
1092MODULE_VERSION("1.0");
1093MODULE_ALIAS("platform:pmic8058_pwm");