Certain PMICs have a high reolution PWM module which can support from 8-bit
to 15-bit PWM. This change series adds support for those PMICs. Thanks!
Anjelique Melendez (3):
dt-bindings: leds-qcom-lpg: Add qcom,pmk8550-pwm compatible string
leds: rgb: leds-qcom-lpg: Add support for high resolution PWM
leds: rgb: leds-qcom-lpg: Add support for PMK8550 PWM
.../bindings/leds/leds-qcom-lpg.yaml | 1 +
drivers/leds/rgb/leds-qcom-lpg.c | 151 +++++++++++++-----
2 files changed, 110 insertions(+), 42 deletions(-)
--
2.39.0
Add support for pmk8550 compatible and lpg_data.
Signed-off-by: Anjelique Melendez <[email protected]>
---
drivers/leds/rgb/leds-qcom-lpg.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index 534ca4c0dea4..0fe51fcb42b0 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -1487,6 +1487,14 @@ static const struct lpg_data pm8350c_pwm_data = {
},
};
+static const struct lpg_data pmk8550_pwm_data = {
+ .num_channels = 2,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xe800 },
+ { .base = 0xe900 },
+ },
+};
+
static const struct of_device_id lpg_of_table[] = {
{ .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data },
{ .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data },
@@ -1497,6 +1505,7 @@ static const struct of_device_id lpg_of_table[] = {
{ .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
{ .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
{ .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data },
+ { .compatible = "qcom,pmk8550-pwm", .data = &pmk8550_pwm_data },
{}
};
MODULE_DEVICE_TABLE(of, lpg_of_table);
--
2.39.0
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 <[email protected]>
---
drivers/leds/rgb/leds-qcom-lpg.c | 142 ++++++++++++++++++++++---------
1 file changed, 100 insertions(+), 42 deletions(-)
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index 67f48f222109..534ca4c0dea4 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -2,6 +2,7 @@
/*
* Copyright (c) 2017-2022 Linaro Ltd
* 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/bitfield.h>
@@ -17,10 +18,13 @@
#define LPG_SUBTYPE_REG 0x05
#define LPG_SUBTYPE_LPG 0x2
#define LPG_SUBTYPE_PWM 0xb
+#define LPG_SUBTYPE_HI_RES_PWM 0xc
#define LPG_SUBTYPE_LPG_LITE 0x11
#define LPG_PATTERN_CONFIG_REG 0x40
#define LPG_SIZE_CLK_REG 0x41
#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 PWM_FREQ_PRE_DIV_MASK GENMASK(6, 5)
#define PWM_FREQ_EXP_MASK GENMASK(2, 0)
@@ -43,8 +47,10 @@
#define LPG_LUT_REG(x) (0x40 + (x) * 2)
#define RAMP_CONTROL_REG 0xc8
-#define LPG_RESOLUTION 512
+#define LPG_RESOLUTION BIT(9)
+#define LPG_RESOLUTION_HI_RES BIT(15)
#define LPG_MAX_M 7
+#define LPG_MAX_PREDIV 6
struct lpg_channel;
struct lpg_data;
@@ -106,6 +112,7 @@ struct lpg {
* @clk_sel: reference clock frequency selector
* @pre_div_sel: divider selector of the reference clock
* @pre_div_exp: exponential divider of the reference clock
+ * @pwm_size_sel: pwm size selector
* @ramp_enabled: duty cycle is driven by iterating over lookup table
* @ramp_ping_pong: reverse through pattern, rather than wrapping to start
* @ramp_oneshot: perform only a single pass over the pattern
@@ -138,6 +145,7 @@ struct lpg_channel {
unsigned int clk_sel;
unsigned int pre_div_sel;
unsigned int pre_div_exp;
+ unsigned int pwm_size_sel;
bool ramp_enabled;
bool ramp_ping_pong;
@@ -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_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
+static const unsigned int lpg_pwm_size[] = {9};
+static const unsigned int lpg_pwm_size_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
{
- unsigned int clk_sel, best_clk = 0;
+ unsigned int i, pwm_size_len, best_pwm_size_sel = 0;
+ const unsigned int *clk_rate_arr, *pwm_size_arr;
+ unsigned int clk_sel, clk_len, best_clk = 0;
unsigned int div, best_div = 0;
unsigned int m, best_m = 0;
+ unsigned int resolution;
unsigned int error;
unsigned int best_err = UINT_MAX;
u64 best_period = 0;
u64 max_period;
+ u64 max_res;
/*
* The PWM period is determined by:
@@ -272,73 +287,104 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
* period = --------------------------
* 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].
*
- * This allows for periods between 27uS and 384s, as the PWM framework
- * wants a period of equal or lower length than requested, reject
- * anything below 27uS.
+ * This allows for periods between 27uS and 384s for PWM channels and periods between
+ * 3uS and 24576s for high resolution PWMs.
+ * 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_size_arr = lpg_pwm_size_hi_res;
+ pwm_size_len = ARRAY_SIZE(lpg_pwm_size_hi_res);
+ max_res = LPG_RESOLUTION_HI_RES;
+ } else {
+ clk_rate_arr = lpg_clk_rates;
+ clk_len = ARRAY_SIZE(lpg_clk_rates);
+ pwm_size_arr = lpg_pwm_size;
+ pwm_size_len = ARRAY_SIZE(lpg_pwm_size);
+ max_res = LPG_RESOLUTION;
+ }
+
+ if (period <= (u64)NSEC_PER_SEC * (1 << pwm_size_arr[0]) / clk_rate_arr[clk_len - 1])
return -EINVAL;
/* 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 * (1 << LPG_MAX_M) / 1024;
if (period > max_period)
period = max_period;
/*
- * Search for the pre_div, refclk and M by solving the rewritten formula
- * for each refclk and pre_div value:
+ * Search for the pre_div, refclk, resolution and M by solving the rewritten formula
+ * for each refclk, resolution and pre_div value:
*
* period * refclk
* M = log2 -------------------------------------
* 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;
- best_m = m;
- best_clk = clk_sel;
- best_period = actual;
+ for (i = 0; i < pwm_size_len; i++) {
+ resolution = 1 << pwm_size_arr[i];
+ for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
+ 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_size_sel = i;
+ }
}
}
}
-
chan->clk_sel = best_clk;
chan->pre_div_sel = best_div;
chan->pre_div_exp = best_m;
chan->period = best_period;
-
+ chan->pwm_size_sel = best_pwm_size_sel;
return 0;
}
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 clk_rate;
+
+ if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
+ max = LPG_RESOLUTION_HI_RES - 1;
+ clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
+ } else {
+ max = LPG_RESOLUTION - 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));
chan->pwm_value = min(val, max);
@@ -354,7 +400,7 @@ static void lpg_apply_freq(struct lpg_channel *chan)
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) {
case LPG_SUBTYPE_LPG:
val |= GENMASK(5, 4);
@@ -362,6 +408,9 @@ static void lpg_apply_freq(struct lpg_channel *chan)
case LPG_SUBTYPE_PWM:
val |= BIT(2);
break;
+ case LPG_SUBTYPE_HI_RES_PWM:
+ val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_size_sel);
+ break;
case LPG_SUBTYPE_LPG_LITE:
default:
val |= BIT(4);
@@ -977,6 +1026,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_channel *chan = &lpg->channels[pwm->hwpwm];
+ unsigned int pwm_size;
unsigned int pre_div;
unsigned int refclk;
unsigned int val;
@@ -988,7 +1038,14 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
if (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)];
+ pwm_size = lpg_pwm_size_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
+ } else {
+ refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
+ pwm_size = 9;
+ }
+
if (refclk) {
ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val);
if (ret)
@@ -1001,7 +1058,8 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
if (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 << pwm_size) *
+ pre_div * (1 << m), refclk);
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
} else {
state->period = 0;
--
2.39.0
Hi Anjelique,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on lee-leds/for-leds-next]
[also build test ERROR on robh/for-next pavel-leds/for-next linus/master v6.3-rc2 next-20230317]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Anjelique-Melendez/dt-bindings-leds-qcom-lpg-Add-qcom-pmk8550-pwm-compatible-string/20230317-032340
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git for-leds-next
patch link: https://lore.kernel.org/r/20230316192134.26436-3-quic_amelende%40quicinc.com
patch subject: [PATCH 2/3] leds: rgb: leds-qcom-lpg: Add support for high resolution PWM
config: parisc-randconfig-r014-20230312 (https://download.01.org/0day-ci/archive/20230317/[email protected]/config)
compiler: hppa-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/916dd0b271b3f035efd07efdaa696e7c815f7e6c
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Anjelique-Melendez/dt-bindings-leds-qcom-lpg-Add-qcom-pmk8550-pwm-compatible-string/20230317-032340
git checkout 916dd0b271b3f035efd07efdaa696e7c815f7e6c
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=parisc olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=parisc SHELL=/bin/bash
If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: "__udivdi3" [drivers/leds/rgb/leds-qcom-lpg.ko] undefined!
>> ERROR: modpost: "__divdi3" [drivers/leds/rgb/leds-qcom-lpg.ko] undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests
Hi Anjelique,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on lee-leds/for-leds-next]
[also build test ERROR on robh/for-next pavel-leds/for-next linus/master v6.3-rc2 next-20230317]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Anjelique-Melendez/dt-bindings-leds-qcom-lpg-Add-qcom-pmk8550-pwm-compatible-string/20230317-032340
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git for-leds-next
patch link: https://lore.kernel.org/r/20230316192134.26436-3-quic_amelende%40quicinc.com
patch subject: [PATCH 2/3] leds: rgb: leds-qcom-lpg: Add support for high resolution PWM
config: arm-allmodconfig (https://download.01.org/0day-ci/archive/20230317/[email protected]/config)
compiler: arm-linux-gnueabi-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/916dd0b271b3f035efd07efdaa696e7c815f7e6c
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Anjelique-Melendez/dt-bindings-leds-qcom-lpg-Add-qcom-pmk8550-pwm-compatible-string/20230317-032340
git checkout 916dd0b271b3f035efd07efdaa696e7c815f7e6c
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arm olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arm SHELL=/bin/bash
If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: "__aeabi_uldivmod" [drivers/leds/rgb/leds-qcom-lpg.ko] undefined!
>> ERROR: modpost: "__aeabi_ldivmod" [drivers/leds/rgb/leds-qcom-lpg.ko] undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests
On Thu 2023-03-16 12:21:33, Anjelique Melendez wrote:
> 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 <[email protected]>
Provided build problems are fixed:
Acked-by: Pavel Machek <[email protected]>
Pavel
--
People of Russia, stop Putin before his war on Ukraine escalates.
On Thu, 16 Mar 2023, Anjelique Melendez wrote:
> Certain PMICs have a high reolution PWM module which can support from 8-bit
> to 15-bit PWM. This change series adds support for those PMICs. Thanks!
>
> Anjelique Melendez (3):
> dt-bindings: leds-qcom-lpg: Add qcom,pmk8550-pwm compatible string
> leds: rgb: leds-qcom-lpg: Add support for high resolution PWM
> leds: rgb: leds-qcom-lpg: Add support for PMK8550 PWM
>
> .../bindings/leds/leds-qcom-lpg.yaml | 1 +
> drivers/leds/rgb/leds-qcom-lpg.c | 151 +++++++++++++-----
> 2 files changed, 110 insertions(+), 42 deletions(-)
I guess you'll be fixing the build issues and resubmitting?
Please retrain the Acks you've collected.
--
Lee Jones [李琼斯]