From d71c7b2e126216c531462f56965ef87e83047c37 Mon Sep 17 00:00:00 2001 From: Anjelique Melendez Date: Wed, 8 Jun 2022 13:51:35 -0700 Subject: [PATCH] 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 --- drivers/pwm/pwm-qti-lpg.c | 128 ++++++++++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/drivers/pwm/pwm-qti-lpg.c b/drivers/pwm/pwm-qti-lpg.c index dd8a32536603..416e1a23f03f 100644 --- a/drivers/pwm/pwm-qti-lpg.c +++ b/drivers/pwm/pwm-qti-lpg.c @@ -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) {