Commit b00d2ed3 authored by Anjelique Melendez's avatar Anjelique Melendez Committed by Lee Jones

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: default avatarAnjelique Melendez <quic_amelende@quicinc.com>
Acked-by: default avatarPavel Machek <pavel@ucw.cz>
Signed-off-by: default avatarLee Jones <lee@kernel.org>
Link: https://lore.kernel.org/r/20230407223849.17623-3-quic_amelende@quicinc.com
parent 03a85ab3
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* /*
* Copyright (c) 2017-2022 Linaro Ltd * Copyright (c) 2017-2022 Linaro Ltd
* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
* Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
*/ */
#include <linux/bits.h> #include <linux/bits.h>
#include <linux/bitfield.h> #include <linux/bitfield.h>
...@@ -17,10 +18,13 @@ ...@@ -17,10 +18,13 @@
#define LPG_SUBTYPE_REG 0x05 #define LPG_SUBTYPE_REG 0x05
#define LPG_SUBTYPE_LPG 0x2 #define LPG_SUBTYPE_LPG 0x2
#define LPG_SUBTYPE_PWM 0xb #define LPG_SUBTYPE_PWM 0xb
#define LPG_SUBTYPE_HI_RES_PWM 0xc
#define LPG_SUBTYPE_LPG_LITE 0x11 #define LPG_SUBTYPE_LPG_LITE 0x11
#define LPG_PATTERN_CONFIG_REG 0x40 #define LPG_PATTERN_CONFIG_REG 0x40
#define LPG_SIZE_CLK_REG 0x41 #define LPG_SIZE_CLK_REG 0x41
#define PWM_CLK_SELECT_MASK GENMASK(1, 0) #define PWM_CLK_SELECT_MASK GENMASK(1, 0)
#define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0)
#define PWM_SIZE_HI_RES_MASK GENMASK(6, 4)
#define LPG_PREDIV_CLK_REG 0x42 #define LPG_PREDIV_CLK_REG 0x42
#define PWM_FREQ_PRE_DIV_MASK GENMASK(6, 5) #define PWM_FREQ_PRE_DIV_MASK GENMASK(6, 5)
#define PWM_FREQ_EXP_MASK GENMASK(2, 0) #define PWM_FREQ_EXP_MASK GENMASK(2, 0)
...@@ -43,8 +47,10 @@ ...@@ -43,8 +47,10 @@
#define LPG_LUT_REG(x) (0x40 + (x) * 2) #define LPG_LUT_REG(x) (0x40 + (x) * 2)
#define RAMP_CONTROL_REG 0xc8 #define RAMP_CONTROL_REG 0xc8
#define LPG_RESOLUTION 512 #define LPG_RESOLUTION_9BIT BIT(9)
#define LPG_RESOLUTION_15BIT BIT(15)
#define LPG_MAX_M 7 #define LPG_MAX_M 7
#define LPG_MAX_PREDIV 6
struct lpg_channel; struct lpg_channel;
struct lpg_data; struct lpg_data;
...@@ -106,6 +112,7 @@ struct lpg { ...@@ -106,6 +112,7 @@ struct lpg {
* @clk_sel: reference clock frequency selector * @clk_sel: reference clock frequency selector
* @pre_div_sel: divider selector of the reference clock * @pre_div_sel: divider selector of the reference clock
* @pre_div_exp: exponential divider of the reference clock * @pre_div_exp: exponential divider of the reference clock
* @pwm_resolution_sel: pwm resolution selector
* @ramp_enabled: duty cycle is driven by iterating over lookup table * @ramp_enabled: duty cycle is driven by iterating over lookup table
* @ramp_ping_pong: reverse through pattern, rather than wrapping to start * @ramp_ping_pong: reverse through pattern, rather than wrapping to start
* @ramp_oneshot: perform only a single pass over the pattern * @ramp_oneshot: perform only a single pass over the pattern
...@@ -138,6 +145,7 @@ struct lpg_channel { ...@@ -138,6 +145,7 @@ struct lpg_channel {
unsigned int clk_sel; unsigned int clk_sel;
unsigned int pre_div_sel; unsigned int pre_div_sel;
unsigned int pre_div_exp; unsigned int pre_div_exp;
unsigned int pwm_resolution_sel;
bool ramp_enabled; bool ramp_enabled;
bool ramp_ping_pong; bool ramp_ping_pong;
...@@ -253,17 +261,24 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask) ...@@ -253,17 +261,24 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
} }
static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000}; static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6}; static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
static const unsigned int lpg_pwm_resolution[] = {9};
static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
{ {
unsigned int clk_sel, best_clk = 0; unsigned int i, pwm_resolution_count, best_pwm_resolution_sel = 0;
const unsigned int *clk_rate_arr, *pwm_resolution_arr;
unsigned int clk_sel, clk_len, best_clk = 0;
unsigned int div, best_div = 0; unsigned int div, best_div = 0;
unsigned int m, best_m = 0; unsigned int m, best_m = 0;
unsigned int resolution;
unsigned int error; unsigned int error;
unsigned int best_err = UINT_MAX; unsigned int best_err = UINT_MAX;
u64 max_period, min_period;
u64 best_period = 0; u64 best_period = 0;
u64 max_period; u64 max_res;
/* /*
* The PWM period is determined by: * The PWM period is determined by:
...@@ -272,73 +287,107 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) ...@@ -272,73 +287,107 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
* period = -------------------------- * period = --------------------------
* refclk * refclk
* *
* With resolution fixed at 2^9 bits, pre_div = {1, 3, 5, 6} and * Resolution = 2^9 bits for PWM or
* 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
* pre_div = {1, 3, 5, 6} and
* M = [0..7]. * M = [0..7].
* *
* This allows for periods between 27uS and 384s, as the PWM framework * This allows for periods between 27uS and 384s for PWM channels and periods between
* wants a period of equal or lower length than requested, reject * 3uS and 24576s for high resolution PWMs.
* anything below 27uS. * The PWM framework wants a period of equal or lower length than requested,
* reject anything below minimum period.
*/ */
if (period <= (u64)NSEC_PER_SEC * LPG_RESOLUTION / 19200000)
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
clk_rate_arr = lpg_clk_rates_hi_res;
clk_len = ARRAY_SIZE(lpg_clk_rates_hi_res);
pwm_resolution_arr = lpg_pwm_resolution_hi_res;
pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution_hi_res);
max_res = LPG_RESOLUTION_15BIT;
} else {
clk_rate_arr = lpg_clk_rates;
clk_len = ARRAY_SIZE(lpg_clk_rates);
pwm_resolution_arr = lpg_pwm_resolution;
pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution);
max_res = LPG_RESOLUTION_9BIT;
}
min_period = (u64)NSEC_PER_SEC *
div64_u64((1 << pwm_resolution_arr[0]), clk_rate_arr[clk_len - 1]);
if (period <= min_period)
return -EINVAL; return -EINVAL;
/* Limit period to largest possible value, to avoid overflows */ /* Limit period to largest possible value, to avoid overflows */
max_period = (u64)NSEC_PER_SEC * LPG_RESOLUTION * 6 * (1 << LPG_MAX_M) / 1024; max_period = (u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV *
div64_u64((1 << LPG_MAX_M), 1024);
if (period > max_period) if (period > max_period)
period = max_period; period = max_period;
/* /*
* Search for the pre_div, refclk and M by solving the rewritten formula * Search for the pre_div, refclk, resolution and M by solving the rewritten formula
* for each refclk and pre_div value: * for each refclk, resolution and pre_div value:
* *
* period * refclk * period * refclk
* M = log2 ------------------------------------- * M = log2 -------------------------------------
* NSEC_PER_SEC * pre_div * resolution * NSEC_PER_SEC * pre_div * resolution
*/ */
for (clk_sel = 1; clk_sel < ARRAY_SIZE(lpg_clk_rates); clk_sel++) {
u64 numerator = period * lpg_clk_rates[clk_sel];
for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] * LPG_RESOLUTION;
u64 actual;
u64 ratio;
if (numerator < denominator)
continue;
ratio = div64_u64(numerator, denominator);
m = ilog2(ratio);
if (m > LPG_MAX_M)
m = LPG_MAX_M;
actual = DIV_ROUND_UP_ULL(denominator * (1 << m), lpg_clk_rates[clk_sel]);
error = period - actual;
if (error < best_err) {
best_err = error;
best_div = div; for (i = 0; i < pwm_resolution_count; i++) {
best_m = m; resolution = 1 << pwm_resolution_arr[i];
best_clk = clk_sel; for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
best_period = actual; u64 numerator = period * clk_rate_arr[clk_sel];
for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] *
resolution;
u64 actual;
u64 ratio;
if (numerator < denominator)
continue;
ratio = div64_u64(numerator, denominator);
m = ilog2(ratio);
if (m > LPG_MAX_M)
m = LPG_MAX_M;
actual = DIV_ROUND_UP_ULL(denominator * (1 << m),
clk_rate_arr[clk_sel]);
error = period - actual;
if (error < best_err) {
best_err = error;
best_div = div;
best_m = m;
best_clk = clk_sel;
best_period = actual;
best_pwm_resolution_sel = i;
}
} }
} }
} }
chan->clk_sel = best_clk; chan->clk_sel = best_clk;
chan->pre_div_sel = best_div; chan->pre_div_sel = best_div;
chan->pre_div_exp = best_m; chan->pre_div_exp = best_m;
chan->period = best_period; chan->period = best_period;
chan->pwm_resolution_sel = best_pwm_resolution_sel;
return 0; return 0;
} }
static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty) static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
{ {
unsigned int max = LPG_RESOLUTION - 1; unsigned int max;
unsigned int val; unsigned int val;
unsigned int clk_rate;
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
max = LPG_RESOLUTION_15BIT - 1;
clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
} else {
max = LPG_RESOLUTION_9BIT - 1;
clk_rate = lpg_clk_rates[chan->clk_sel];
}
val = div64_u64(duty * lpg_clk_rates[chan->clk_sel], val = div64_u64(duty * clk_rate,
(u64)NSEC_PER_SEC * lpg_pre_divs[chan->pre_div_sel] * (1 << chan->pre_div_exp)); (u64)NSEC_PER_SEC * lpg_pre_divs[chan->pre_div_sel] * (1 << chan->pre_div_exp));
chan->pwm_value = min(val, max); chan->pwm_value = min(val, max);
...@@ -354,7 +403,7 @@ static void lpg_apply_freq(struct lpg_channel *chan) ...@@ -354,7 +403,7 @@ static void lpg_apply_freq(struct lpg_channel *chan)
val = chan->clk_sel; val = chan->clk_sel;
/* Specify 9bit resolution, based on the subtype of the channel */ /* Specify resolution, based on the subtype of the channel */
switch (chan->subtype) { switch (chan->subtype) {
case LPG_SUBTYPE_LPG: case LPG_SUBTYPE_LPG:
val |= GENMASK(5, 4); val |= GENMASK(5, 4);
...@@ -362,6 +411,9 @@ static void lpg_apply_freq(struct lpg_channel *chan) ...@@ -362,6 +411,9 @@ static void lpg_apply_freq(struct lpg_channel *chan)
case LPG_SUBTYPE_PWM: case LPG_SUBTYPE_PWM:
val |= BIT(2); val |= BIT(2);
break; break;
case LPG_SUBTYPE_HI_RES_PWM:
val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
break;
case LPG_SUBTYPE_LPG_LITE: case LPG_SUBTYPE_LPG_LITE:
default: default:
val |= BIT(4); val |= BIT(4);
...@@ -670,7 +722,7 @@ static int lpg_blink_set(struct lpg_led *led, ...@@ -670,7 +722,7 @@ static int lpg_blink_set(struct lpg_led *led,
triled_set(lpg, triled_mask, triled_mask); triled_set(lpg, triled_mask, triled_mask);
chan = led->channels[0]; chan = led->channels[0];
duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION); duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION_9BIT);
*delay_on = div_u64(duty, NSEC_PER_MSEC); *delay_on = div_u64(duty, NSEC_PER_MSEC);
*delay_off = div_u64(chan->period - duty, NSEC_PER_MSEC); *delay_off = div_u64(chan->period - duty, NSEC_PER_MSEC);
...@@ -977,6 +1029,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -977,6 +1029,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
{ {
struct lpg *lpg = container_of(chip, struct lpg, pwm); struct lpg *lpg = container_of(chip, struct lpg, pwm);
struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
unsigned int resolution;
unsigned int pre_div; unsigned int pre_div;
unsigned int refclk; unsigned int refclk;
unsigned int val; unsigned int val;
...@@ -988,7 +1041,14 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -988,7 +1041,14 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
if (ret) if (ret)
return ret; return ret;
refclk = lpg_clk_rates[val & PWM_CLK_SELECT_MASK]; if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
refclk = lpg_clk_rates_hi_res[FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val)];
resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
} else {
refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
resolution = 9;
}
if (refclk) { if (refclk) {
ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val); ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val);
if (ret) if (ret)
...@@ -1001,7 +1061,8 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -1001,7 +1061,8 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
if (ret) if (ret)
return ret; return ret;
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * LPG_RESOLUTION * pre_div * (1 << m), refclk); state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) *
pre_div * (1 << m), refclk);
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk); state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
} else { } else {
state->period = 0; state->period = 0;
...@@ -1149,7 +1210,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) ...@@ -1149,7 +1210,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
} }
cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL); cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
cdev->max_brightness = LPG_RESOLUTION - 1; cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
if (!of_property_read_string(np, "default-state", &state) && if (!of_property_read_string(np, "default-state", &state) &&
!strcmp(state, "on")) !strcmp(state, "on"))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment