Skip to content

Commit b00d2ed

Browse files
Anjelique Melendezlag-linaro
authored andcommitted
leds: rgb: leds-qcom-lpg: Add support for high resolution PWM
Certain PMICs like PMK8550 have a high resolution PWM module which can support from 8-bit to 15-bit PWM. Add support for it. Signed-off-by: Anjelique Melendez <quic_amelende@quicinc.com> Acked-by: Pavel Machek <pavel@ucw.cz> Signed-off-by: Lee Jones <lee@kernel.org> Link: https://lore.kernel.org/r/20230407223849.17623-3-quic_amelende@quicinc.com
1 parent 03a85ab commit b00d2ed

1 file changed

Lines changed: 106 additions & 45 deletions

File tree

drivers/leds/rgb/leds-qcom-lpg.c

Lines changed: 106 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/*
33
* Copyright (c) 2017-2022 Linaro Ltd
44
* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
5+
* Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
56
*/
67
#include <linux/bits.h>
78
#include <linux/bitfield.h>
@@ -17,10 +18,13 @@
1718
#define LPG_SUBTYPE_REG 0x05
1819
#define LPG_SUBTYPE_LPG 0x2
1920
#define LPG_SUBTYPE_PWM 0xb
21+
#define LPG_SUBTYPE_HI_RES_PWM 0xc
2022
#define LPG_SUBTYPE_LPG_LITE 0x11
2123
#define LPG_PATTERN_CONFIG_REG 0x40
2224
#define LPG_SIZE_CLK_REG 0x41
2325
#define PWM_CLK_SELECT_MASK GENMASK(1, 0)
26+
#define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0)
27+
#define PWM_SIZE_HI_RES_MASK GENMASK(6, 4)
2428
#define LPG_PREDIV_CLK_REG 0x42
2529
#define PWM_FREQ_PRE_DIV_MASK GENMASK(6, 5)
2630
#define PWM_FREQ_EXP_MASK GENMASK(2, 0)
@@ -43,8 +47,10 @@
4347
#define LPG_LUT_REG(x) (0x40 + (x) * 2)
4448
#define RAMP_CONTROL_REG 0xc8
4549

46-
#define LPG_RESOLUTION 512
50+
#define LPG_RESOLUTION_9BIT BIT(9)
51+
#define LPG_RESOLUTION_15BIT BIT(15)
4752
#define LPG_MAX_M 7
53+
#define LPG_MAX_PREDIV 6
4854

4955
struct lpg_channel;
5056
struct lpg_data;
@@ -106,6 +112,7 @@ struct lpg {
106112
* @clk_sel: reference clock frequency selector
107113
* @pre_div_sel: divider selector of the reference clock
108114
* @pre_div_exp: exponential divider of the reference clock
115+
* @pwm_resolution_sel: pwm resolution selector
109116
* @ramp_enabled: duty cycle is driven by iterating over lookup table
110117
* @ramp_ping_pong: reverse through pattern, rather than wrapping to start
111118
* @ramp_oneshot: perform only a single pass over the pattern
@@ -138,6 +145,7 @@ struct lpg_channel {
138145
unsigned int clk_sel;
139146
unsigned int pre_div_sel;
140147
unsigned int pre_div_exp;
148+
unsigned int pwm_resolution_sel;
141149

142150
bool ramp_enabled;
143151
bool ramp_ping_pong;
@@ -253,17 +261,24 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
253261
}
254262

255263
static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
264+
static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
256265
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
266+
static const unsigned int lpg_pwm_resolution[] = {9};
267+
static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
257268

258269
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
259270
{
260-
unsigned int clk_sel, best_clk = 0;
271+
unsigned int i, pwm_resolution_count, best_pwm_resolution_sel = 0;
272+
const unsigned int *clk_rate_arr, *pwm_resolution_arr;
273+
unsigned int clk_sel, clk_len, best_clk = 0;
261274
unsigned int div, best_div = 0;
262275
unsigned int m, best_m = 0;
276+
unsigned int resolution;
263277
unsigned int error;
264278
unsigned int best_err = UINT_MAX;
279+
u64 max_period, min_period;
265280
u64 best_period = 0;
266-
u64 max_period;
281+
u64 max_res;
267282

268283
/*
269284
* The PWM period is determined by:
@@ -272,73 +287,107 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
272287
* period = --------------------------
273288
* refclk
274289
*
275-
* With resolution fixed at 2^9 bits, pre_div = {1, 3, 5, 6} and
290+
* Resolution = 2^9 bits for PWM or
291+
* 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
292+
* pre_div = {1, 3, 5, 6} and
276293
* M = [0..7].
277294
*
278-
* This allows for periods between 27uS and 384s, as the PWM framework
279-
* wants a period of equal or lower length than requested, reject
280-
* anything below 27uS.
295+
* This allows for periods between 27uS and 384s for PWM channels and periods between
296+
* 3uS and 24576s for high resolution PWMs.
297+
* The PWM framework wants a period of equal or lower length than requested,
298+
* reject anything below minimum period.
281299
*/
282-
if (period <= (u64)NSEC_PER_SEC * LPG_RESOLUTION / 19200000)
300+
301+
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
302+
clk_rate_arr = lpg_clk_rates_hi_res;
303+
clk_len = ARRAY_SIZE(lpg_clk_rates_hi_res);
304+
pwm_resolution_arr = lpg_pwm_resolution_hi_res;
305+
pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution_hi_res);
306+
max_res = LPG_RESOLUTION_15BIT;
307+
} else {
308+
clk_rate_arr = lpg_clk_rates;
309+
clk_len = ARRAY_SIZE(lpg_clk_rates);
310+
pwm_resolution_arr = lpg_pwm_resolution;
311+
pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution);
312+
max_res = LPG_RESOLUTION_9BIT;
313+
}
314+
315+
min_period = (u64)NSEC_PER_SEC *
316+
div64_u64((1 << pwm_resolution_arr[0]), clk_rate_arr[clk_len - 1]);
317+
if (period <= min_period)
283318
return -EINVAL;
284319

285320
/* Limit period to largest possible value, to avoid overflows */
286-
max_period = (u64)NSEC_PER_SEC * LPG_RESOLUTION * 6 * (1 << LPG_MAX_M) / 1024;
321+
max_period = (u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV *
322+
div64_u64((1 << LPG_MAX_M), 1024);
287323
if (period > max_period)
288324
period = max_period;
289325

290326
/*
291-
* Search for the pre_div, refclk and M by solving the rewritten formula
292-
* for each refclk and pre_div value:
327+
* Search for the pre_div, refclk, resolution and M by solving the rewritten formula
328+
* for each refclk, resolution and pre_div value:
293329
*
294330
* period * refclk
295331
* M = log2 -------------------------------------
296332
* NSEC_PER_SEC * pre_div * resolution
297333
*/
298-
for (clk_sel = 1; clk_sel < ARRAY_SIZE(lpg_clk_rates); clk_sel++) {
299-
u64 numerator = period * lpg_clk_rates[clk_sel];
300-
301-
for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
302-
u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] * LPG_RESOLUTION;
303-
u64 actual;
304-
u64 ratio;
305-
306-
if (numerator < denominator)
307-
continue;
308-
309-
ratio = div64_u64(numerator, denominator);
310-
m = ilog2(ratio);
311-
if (m > LPG_MAX_M)
312-
m = LPG_MAX_M;
313-
314-
actual = DIV_ROUND_UP_ULL(denominator * (1 << m), lpg_clk_rates[clk_sel]);
315-
316-
error = period - actual;
317-
if (error < best_err) {
318-
best_err = error;
319334

320-
best_div = div;
321-
best_m = m;
322-
best_clk = clk_sel;
323-
best_period = actual;
335+
for (i = 0; i < pwm_resolution_count; i++) {
336+
resolution = 1 << pwm_resolution_arr[i];
337+
for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
338+
u64 numerator = period * clk_rate_arr[clk_sel];
339+
340+
for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
341+
u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] *
342+
resolution;
343+
u64 actual;
344+
u64 ratio;
345+
346+
if (numerator < denominator)
347+
continue;
348+
349+
ratio = div64_u64(numerator, denominator);
350+
m = ilog2(ratio);
351+
if (m > LPG_MAX_M)
352+
m = LPG_MAX_M;
353+
354+
actual = DIV_ROUND_UP_ULL(denominator * (1 << m),
355+
clk_rate_arr[clk_sel]);
356+
error = period - actual;
357+
if (error < best_err) {
358+
best_err = error;
359+
best_div = div;
360+
best_m = m;
361+
best_clk = clk_sel;
362+
best_period = actual;
363+
best_pwm_resolution_sel = i;
364+
}
324365
}
325366
}
326367
}
327-
328368
chan->clk_sel = best_clk;
329369
chan->pre_div_sel = best_div;
330370
chan->pre_div_exp = best_m;
331371
chan->period = best_period;
332-
372+
chan->pwm_resolution_sel = best_pwm_resolution_sel;
333373
return 0;
334374
}
335375

336376
static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
337377
{
338-
unsigned int max = LPG_RESOLUTION - 1;
378+
unsigned int max;
339379
unsigned int val;
380+
unsigned int clk_rate;
381+
382+
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
383+
max = LPG_RESOLUTION_15BIT - 1;
384+
clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
385+
} else {
386+
max = LPG_RESOLUTION_9BIT - 1;
387+
clk_rate = lpg_clk_rates[chan->clk_sel];
388+
}
340389

341-
val = div64_u64(duty * lpg_clk_rates[chan->clk_sel],
390+
val = div64_u64(duty * clk_rate,
342391
(u64)NSEC_PER_SEC * lpg_pre_divs[chan->pre_div_sel] * (1 << chan->pre_div_exp));
343392

344393
chan->pwm_value = min(val, max);
@@ -354,14 +403,17 @@ static void lpg_apply_freq(struct lpg_channel *chan)
354403

355404
val = chan->clk_sel;
356405

357-
/* Specify 9bit resolution, based on the subtype of the channel */
406+
/* Specify resolution, based on the subtype of the channel */
358407
switch (chan->subtype) {
359408
case LPG_SUBTYPE_LPG:
360409
val |= GENMASK(5, 4);
361410
break;
362411
case LPG_SUBTYPE_PWM:
363412
val |= BIT(2);
364413
break;
414+
case LPG_SUBTYPE_HI_RES_PWM:
415+
val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
416+
break;
365417
case LPG_SUBTYPE_LPG_LITE:
366418
default:
367419
val |= BIT(4);
@@ -670,7 +722,7 @@ static int lpg_blink_set(struct lpg_led *led,
670722
triled_set(lpg, triled_mask, triled_mask);
671723

672724
chan = led->channels[0];
673-
duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION);
725+
duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION_9BIT);
674726
*delay_on = div_u64(duty, NSEC_PER_MSEC);
675727
*delay_off = div_u64(chan->period - duty, NSEC_PER_MSEC);
676728

@@ -977,6 +1029,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
9771029
{
9781030
struct lpg *lpg = container_of(chip, struct lpg, pwm);
9791031
struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
1032+
unsigned int resolution;
9801033
unsigned int pre_div;
9811034
unsigned int refclk;
9821035
unsigned int val;
@@ -988,7 +1041,14 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
9881041
if (ret)
9891042
return ret;
9901043

991-
refclk = lpg_clk_rates[val & PWM_CLK_SELECT_MASK];
1044+
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
1045+
refclk = lpg_clk_rates_hi_res[FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val)];
1046+
resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
1047+
} else {
1048+
refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
1049+
resolution = 9;
1050+
}
1051+
9921052
if (refclk) {
9931053
ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val);
9941054
if (ret)
@@ -1001,7 +1061,8 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
10011061
if (ret)
10021062
return ret;
10031063

1004-
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * LPG_RESOLUTION * pre_div * (1 << m), refclk);
1064+
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) *
1065+
pre_div * (1 << m), refclk);
10051066
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
10061067
} else {
10071068
state->period = 0;
@@ -1149,7 +1210,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
11491210
}
11501211

11511212
cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
1152-
cdev->max_brightness = LPG_RESOLUTION - 1;
1213+
cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
11531214

11541215
if (!of_property_read_string(np, "default-state", &state) &&
11551216
!strcmp(state, "on"))

0 commit comments

Comments
 (0)