mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 03:27:03 +02:00
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:
parent
27d05118d5
commit
d71c7b2e12
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user