pwm: pwm-qti-lpg: Add Support for high resolution PWM

Currently, PMK8550 PMIC has a high resolution PWM module that is
unused. This PWM module can support from 8-bit - 15-bit PWM.
Implement PWM code so that PMK8550's PWM can be used.

Change-Id: Ic0184528e8195eee38b482f0778e63fc63e0c2ed
Signed-off-by: Anjelique Melendez <quic_amelende@quicinc.com>
This commit is contained in:
Anjelique Melendez 2022-06-08 13:51:35 -07:00
parent 27d05118d5
commit d71c7b2e12

View File

@ -62,14 +62,18 @@
/* REG_LPG_PERPH_SUBTYPE */
#define SUBTYPE_PWM 0x0b
#define SUBTYPE_HI_RES_PWM 0x0c
#define SUBTYPE_LPG_LITE 0x11
/* REG_LPG_PWM_SIZE_CLK */
#define LPG_PWM_SIZE_LPG_MASK BIT(4)
#define LPG_PWM_SIZE_PWM_MASK BIT(2)
#define LPG_PWM_SIZE_PWM_HI_RES_MASK GENMASK(6, 4)
#define LPG_PWM_SIZE_LPG_SHIFT 4
#define LPG_PWM_SIZE_PWM_SHIFT 2
#define LPG_PWM_SIZE_PWM_HI_RES_SHIFT 4
#define LPG_PWM_CLK_FREQ_SEL_MASK GENMASK(1, 0)
#define LPG_PWM_HI_RES_CLK_FREQ_SEL_MASK GENMASK(2, 0)
/* REG_LPG_PWM_FREQ_PREDIV_CLK */
#define LPG_PWM_FREQ_PREDIV_MASK GENMASK(6, 5)
@ -84,6 +88,7 @@
/* REG_LPG_PWM_VALUE_MSB */
#define LPG_PWM_VALUE_MSB_MASK BIT(0)
#define LPG_PWM_HI_RES_VALUE_MSB_MASK GENMASK(7, 0)
/* REG_LPG_ENABLE_CONTROL */
#define LPG_EN_LPG_OUT_BIT BIT(7)
@ -97,7 +102,9 @@
#define LPG_PWM_VALUE_SYNC BIT(0)
#define NUM_PWM_SIZE 2
#define NUM_PWM_HI_RES_SIZE 8
#define NUM_PWM_CLK 3
#define NUM_PWM_HI_RES_CLK 4
#define NUM_CLK_PREDIV 4
#define NUM_PWM_EXP 8
@ -147,9 +154,12 @@ enum ppg_num_nvmems {
};
static const int pwm_size[NUM_PWM_SIZE] = {6, 9};
static const int pwm_hi_res_size[NUM_PWM_HI_RES_SIZE] = {8, 9, 10, 11, 12, 13, 14, 15};
static const int clk_freq_hz[NUM_PWM_CLK] = {1024, 32768, 19200000};
static const int clk_freq_hz_hi_res[NUM_PWM_HI_RES_CLK] = {1024, 32768, 19200000, 76800000};
/* clk_period_ns = NSEC_PER_SEC / clk_freq_hz */
static const int clk_period_ns[NUM_PWM_CLK] = {976562, 30517, 52};
static const int clk_period_ns_hi_res[NUM_PWM_HI_RES_CLK] = {976562, 30517, 52, 13};
static const int clk_prediv[NUM_CLK_PREDIV] = {1, 3, 5, 6};
static const int pwm_exponent[NUM_PWM_EXP] = {0, 1, 2, 3, 4, 5, 6, 7};
@ -428,10 +438,18 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg)
u8 val, mask, shift;
int pwm_size_idx, pwm_clk_idx, prediv_idx, clk_exp_idx;
pwm_size_idx = __find_index_in_array(lpg->pwm_config.pwm_size,
pwm_size, ARRAY_SIZE(pwm_size));
pwm_clk_idx = __find_index_in_array(lpg->pwm_config.pwm_clk,
clk_freq_hz, ARRAY_SIZE(clk_freq_hz));
if (lpg->subtype == SUBTYPE_HI_RES_PWM) {
pwm_size_idx = __find_index_in_array(lpg->pwm_config.pwm_size,
pwm_hi_res_size, ARRAY_SIZE(pwm_hi_res_size));
pwm_clk_idx = __find_index_in_array(lpg->pwm_config.pwm_clk,
clk_freq_hz_hi_res, ARRAY_SIZE(clk_freq_hz_hi_res));
} else {
pwm_size_idx = __find_index_in_array(lpg->pwm_config.pwm_size,
pwm_size, ARRAY_SIZE(pwm_size));
pwm_clk_idx = __find_index_in_array(lpg->pwm_config.pwm_clk,
clk_freq_hz, ARRAY_SIZE(clk_freq_hz));
}
prediv_idx = __find_index_in_array(lpg->pwm_config.prediv,
clk_prediv, ARRAY_SIZE(clk_prediv));
clk_exp_idx = __find_index_in_array(lpg->pwm_config.clk_exp,
@ -445,14 +463,16 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg)
pwm_clk_idx += 1;
if (lpg->subtype == SUBTYPE_PWM) {
shift = LPG_PWM_SIZE_PWM_SHIFT;
mask = LPG_PWM_SIZE_PWM_MASK;
mask = LPG_PWM_SIZE_PWM_MASK | LPG_PWM_CLK_FREQ_SEL_MASK;
} else if (lpg->subtype == SUBTYPE_HI_RES_PWM) {
shift = LPG_PWM_SIZE_PWM_HI_RES_SHIFT;
mask = LPG_PWM_SIZE_PWM_HI_RES_MASK | LPG_PWM_HI_RES_CLK_FREQ_SEL_MASK;
} else {
shift = LPG_PWM_SIZE_LPG_SHIFT;
mask = LPG_PWM_SIZE_LPG_MASK;
mask = LPG_PWM_SIZE_LPG_MASK | LPG_PWM_CLK_FREQ_SEL_MASK;
}
val = pwm_size_idx << shift | pwm_clk_idx;
mask |= LPG_PWM_CLK_FREQ_SEL_MASK;
rc = qpnp_lpg_masked_write(lpg, REG_LPG_PWM_SIZE_CLK, mask, val);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_PWM_SIZE_CLK failed, rc=%d\n",
@ -473,7 +493,10 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg)
return 0;
val = lpg->pwm_config.pwm_value >> 8;
mask = LPG_PWM_VALUE_MSB_MASK;
if (lpg->subtype == SUBTYPE_HI_RES_PWM)
mask = LPG_PWM_HI_RES_VALUE_MSB_MASK;
else
mask = LPG_PWM_VALUE_MSB_MASK;
rc = qpnp_lpg_masked_write(lpg, REG_LPG_PWM_VALUE_MSB, mask, val);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_PWM_VALUE_MSB failed, rc=%d\n",
@ -506,8 +529,13 @@ static int qpnp_lpg_set_pfm_config(struct qpnp_lpg_channel *lpg)
u8 val, mask;
int pwm_clk_idx, clk_exp_idx;
pwm_clk_idx = __find_index_in_array(lpg->pwm_config.pwm_clk,
clk_freq_hz, ARRAY_SIZE(clk_freq_hz));
if (lpg->subtype == SUBTYPE_HI_RES_PWM)
pwm_clk_idx = __find_index_in_array(lpg->pwm_config.pwm_clk,
clk_freq_hz_hi_res, ARRAY_SIZE(clk_freq_hz_hi_res));
else
pwm_clk_idx = __find_index_in_array(lpg->pwm_config.pwm_clk,
clk_freq_hz, ARRAY_SIZE(clk_freq_hz));
clk_exp_idx = __find_index_in_array(lpg->pwm_config.clk_exp,
pwm_exponent, ARRAY_SIZE(pwm_exponent));
@ -518,7 +546,11 @@ static int qpnp_lpg_set_pfm_config(struct qpnp_lpg_channel *lpg)
pwm_clk_idx += 1;
val = pwm_clk_idx;
mask = LPG_PWM_CLK_FREQ_SEL_MASK;
if (lpg->subtype == SUBTYPE_HI_RES_PWM)
mask = LPG_PWM_HI_RES_CLK_FREQ_SEL_MASK;
else
mask = LPG_PWM_CLK_FREQ_SEL_MASK;
rc = qpnp_lpg_masked_write(lpg, REG_LPG_PWM_SIZE_CLK, mask, val);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_PWM_SIZE_CLK failed, rc=%d\n",
@ -837,13 +869,14 @@ static int __qpnp_lpg_calc_pwm_period(u64 period_ns,
{
struct qpnp_lpg_channel *lpg = container_of(pwm_config,
struct qpnp_lpg_channel, pwm_config);
struct lpg_pwm_config configs[NUM_PWM_SIZE];
struct lpg_pwm_config configs[NUM_PWM_HI_RES_SIZE];
int i, j, m, n;
u64 tmp1, tmp2;
u64 clk_period_ns = 0, pwm_clk_period_ns;
u64 clk_delta_ns = U64_MAX, min_clk_delta_ns = U64_MAX;
u64 pwm_period_delta = U64_MAX, min_pwm_period_delta = U64_MAX;
int pwm_size_step;
int pwm_size_step, clk_len, pwm_size_len;
const int *pwm_size_arr, *clk_freq_hz_arr;
/*
* (2^pwm_size) * (2^pwm_exp) * prediv * NSEC_PER_SEC
@ -852,20 +885,34 @@ static int __qpnp_lpg_calc_pwm_period(u64 period_ns,
*
* Searching the closest settings for the requested PWM period.
*/
if (lpg->chip->use_sdam)
if (lpg->subtype == SUBTYPE_HI_RES_PWM) {
pwm_size_arr = pwm_hi_res_size;
pwm_size_len = NUM_PWM_HI_RES_SIZE;
clk_freq_hz_arr = clk_freq_hz_hi_res;
clk_len = NUM_PWM_HI_RES_CLK;
} else {
pwm_size_arr = pwm_size;
pwm_size_len = NUM_PWM_SIZE;
clk_freq_hz_arr = clk_freq_hz;
clk_len = NUM_PWM_CLK;
}
if (lpg->chip->use_sdam) {
/* SDAM pattern control can only use 9 bit resolution */
n = 1;
pwm_size_len = 2;
}
else
n = 0;
for (; n < ARRAY_SIZE(pwm_size); n++) {
pwm_clk_period_ns = period_ns >> pwm_size[n];
for (i = ARRAY_SIZE(clk_freq_hz) - 1; i >= 0; i--) {
for (j = 0; j < ARRAY_SIZE(clk_prediv); j++) {
for (; n < pwm_size_len; n++) {
pwm_clk_period_ns = period_ns >> pwm_size_arr[n];
for (i = clk_len - 1; i >= 0; i--) {
for (j = 0; j < clk_len; j++) {
for (m = 0; m < ARRAY_SIZE(pwm_exponent); m++) {
tmp1 = 1 << pwm_exponent[m];
tmp1 *= clk_prediv[j];
tmp2 = NSEC_PER_SEC;
do_div(tmp2, clk_freq_hz[i]);
do_div(tmp2, clk_freq_hz_arr[i]);
clk_period_ns = tmp1 * tmp2;
@ -879,13 +926,13 @@ static int __qpnp_lpg_calc_pwm_period(u64 period_ns,
min_clk_delta_ns
= clk_delta_ns;
configs[n].pwm_clk
= clk_freq_hz[i];
= clk_freq_hz_arr[i];
configs[n].prediv
= clk_prediv[j];
configs[n].clk_exp
= pwm_exponent[m];
configs[n].pwm_size
= pwm_size[n];
= pwm_size_arr[n];
configs[n].best_period_ns
= clk_period_ns;
}
@ -893,9 +940,9 @@ static int __qpnp_lpg_calc_pwm_period(u64 period_ns,
}
}
configs[n].best_period_ns *= 1 << pwm_size[n];
configs[n].best_period_ns *= 1 << pwm_size_arr[n];
/* Find the closest setting for PWM period */
pwm_period_delta = min_clk_delta_ns << pwm_size[n];
pwm_period_delta = min_clk_delta_ns << pwm_size_arr[n];
if (pwm_period_delta < min_pwm_period_delta) {
min_pwm_period_delta = pwm_period_delta;
memcpy(pwm_config, &configs[n],
@ -904,12 +951,12 @@ static int __qpnp_lpg_calc_pwm_period(u64 period_ns,
}
/* Larger PWM size can achieve better resolution for PWM duty */
for (n = ARRAY_SIZE(pwm_size) - 1; n > 0; n--) {
if (pwm_config->pwm_size >= pwm_size[n])
for (n = pwm_size_len - 1; n > 0; n--) {
if (pwm_config->pwm_size >= pwm_size_arr[n])
break;
pwm_size_step = pwm_size[n] - pwm_config->pwm_size;
pwm_size_step = pwm_size_arr[n] - pwm_config->pwm_size;
if (pwm_config->clk_exp >= pwm_size_step) {
pwm_config->pwm_size = pwm_size[n];
pwm_config->pwm_size = pwm_size_arr[n];
pwm_config->clk_exp -= pwm_size_step;
}
}
@ -939,10 +986,13 @@ static void __qpnp_lpg_calc_pwm_duty(u64 period_ns, u64 duty_ns,
static int __qpnp_lpg_calc_pfm_period(u64 period_ns,
struct lpg_pwm_config *pwm_config)
{
int clk, exp, lsb;
struct qpnp_lpg_channel *lpg = container_of(pwm_config,
struct qpnp_lpg_channel, pwm_config);
int clk, exp, lsb, clk_len;
int best_clk = 0, best_exp = 0, best_lsb = -EINVAL;
u64 lsb_tmp, period_tmp, curr_p_err, last_p_err = 0;
u64 min_p_err = U64_MAX;
const int *clk_freq_hz_arr, *clk_period_ns_arr;
/*
* 2 * (pwm_value_lsb + 1) * (2^pwm_exp) * NSEC_PER_SEC
@ -954,16 +1004,27 @@ static int __qpnp_lpg_calc_pfm_period(u64 period_ns,
* period. Store the triplet values that yield the closest value to
* desired period.
*/
for (clk = 0; clk < NUM_PWM_CLK; clk++) {
if (lpg->subtype == SUBTYPE_HI_RES_PWM) {
clk_freq_hz_arr = clk_freq_hz_hi_res;
clk_period_ns_arr = clk_period_ns_hi_res;
clk_len = NUM_PWM_HI_RES_CLK;
} else {
clk_freq_hz_arr = clk_freq_hz;
clk_period_ns_arr = clk_period_ns;
clk_len = NUM_PWM_CLK;
}
for (clk = 0; clk < clk_len; clk++) {
last_p_err = U64_MAX;
for (exp = 0; exp < NUM_PWM_EXP; exp++) {
lsb_tmp = div_u64(period_ns, clk_period_ns[clk]);
lsb_tmp = div_u64(period_ns, clk_period_ns_arr[clk]);
lsb_tmp >>= (exp + 1);
lsb = lsb_tmp - 1;
if (lsb >= 0 && lsb <= U8_MAX) {
period_tmp = (lsb + 1);
period_tmp <<= (exp + 1);
period_tmp *= clk_period_ns[clk];
period_tmp *= clk_period_ns_arr[clk];
curr_p_err = abs(period_ns - period_tmp);
if (curr_p_err < min_p_err) {
@ -994,7 +1055,7 @@ static int __qpnp_lpg_calc_pfm_period(u64 period_ns,
return -EINVAL;
}
pwm_config->pwm_clk = clk_freq_hz[best_clk];
pwm_config->pwm_clk = clk_freq_hz_arr[best_clk];
pwm_config->clk_exp = pwm_exponent[best_exp];
pwm_config->pwm_value = best_lsb;
@ -1794,7 +1855,8 @@ static int qpnp_lpg_parse_pfm_support(struct qpnp_lpg_chip *chip)
chan_idx--; /* zero-based indexing */
if (chip->lpgs[chan_idx].subtype == SUBTYPE_PWM) {
if (chip->lpgs[chan_idx].subtype == SUBTYPE_PWM ||
chip->lpgs[chan_idx].subtype == SUBTYPE_HI_RES_PWM) {
rc = qpnp_lpg_read(&chip->lpgs[chan_idx],
REG_PWM_STATUS1, &val);
if (rc < 0) {