pwm: pwm-qti-lpg: Add LUT mode to support modulated PWM output

LPG module supports outputting the pattern programmed in Look-up table
(LUT) in addition to supporting PWM output. Add support for configuring
LPG to operate in LUT mode and configure LUT patterns per LPG channel.

Change-Id: I6352af136c4916f8b6bc17f1b8374ce1d421c10b
Signed-off-by: Fenglin Wu <fenglinw@codeaurora.org>
This commit is contained in:
Fenglin Wu 2018-04-03 08:51:39 +08:00 committed by Gerrit - the friendly Code Review server
parent 51d5b4b87d
commit f1006f3251
2 changed files with 734 additions and 37 deletions

View File

@ -11,26 +11,137 @@ device module in Qualcomm Technologies, Inc. PMIC chips.
- reg: - reg:
Usage: required Usage: required
Value type: <prop-encoded-array> Value type: <prop-encoded-array>
Definition: Register base and length for LPG modules. The length Definition: Register base and length for LPG and LUT modules. LPG size
varies based on the number of channels available in or length available per channel varies depending on the
the PMIC chips. number of channels in PMIC.
- reg-names: - reg-names:
Usage: required Usage: required
Value type: <string> Value type: <string>
Definition: The name of the register defined in the reg property. Definition: The name of the register defined in the reg property.
It must be "lpg-base". It must have "lpg-base", "lut-base" is optional but
it's required if any LPG channels support LUT mode.
- #pwm-cells: - #pwm-cells:
Usage: required Usage: required
Value type: <u32> Value type: <u32>
Definition: See Documentation/devicetree/bindings/pwm/pwm.txt; Definition: The number of cells in "pwms" property specified in
PWM user nodes. It should be 2. The first cell is
the PWM channel ID indexed from 0, and the second
cell is the PWM default period in nanoseconds.
- qcom,lut-patterns:
Usage: optional
Value type: <prop-encoded-array>
Definition: Duty ratios in percentages for LPG working at LUT mode.
These duty ratios will be translated into PWM values
and stored in LUT module. The LUT module has resource
to store 47 PWM values at max and shared for all LPG
channels. This property is required if any LPG channels
support LUT mode.
Subnode is optional if LUT mode is not required, it's required if any LPG
channels expected to be supported in LUT mode.
Subnode properties:
Subnodes for each LPG channel (lpg@X) can be defined if any of the following
parameters needs to be configured for that channel.
- qcom,lpg-chan-id:
Usage: required
Value type: <u32>
Definition: The LPG channel's hardware ID indexed from 1. Allowed
range is 1 - 8. Maximum value depends on the number of
channels supported on PMIC.
- qcom,ramp-step-ms:
Usage: required
Value type: <u32>
Definition: The step duration in milliseconds for LPG staying at each
duty specified in the LUT pattern. Allowed range is
1 - 511.
- qcom,ramp-high-index:
Usage: required
Value type: <u32>
Definition: The high index of the LUT pattern where LPG ends up
ramping to. Allowed range is 1 - 47.
- qcom,ramp-low-index:
Usage: required
Value type: <u32>
Definition: The low index of the LUT pattern from where LPG begins
ramping from. Allowed range is 0 - 46.
- qcom,ramp-from-low-to-high:
Usage: optional
Value type: <empty>
Definition: The flag to specify the LPG ramping direction. The ramping
direction is from low index to high index of the LUT
pattern if it's specified.
- qcom,ramp-pattern-repeat:
Usage: optional
Value type: <empty>
Definition: The flag to specify if LPG would be ramping with the LUT
pattern repeatedly.
- qcom,ramp-toggle:
Usage: optional
Value type: <empty>
Definition: The flag to specify if LPG would toggle the LUT pattern
in ramping. If toggling enabled, LPG would return to the
low index when high index is reached, or return to the high
index when low index is reached.
- qcom,ramp-pause-hi-count:
Usage: optional
Value type: <u32>
Definition: The step count that LPG stop the output when it ramped up
to the high index of the LUT.
- qcom,ramp-pause-lo-count:
Usage: optional
Value type: <u32>
Definition: The step count that LPG stop the output when it ramped up
to the low index of the LUT.
Example: Example:
pmi8998_lpg: lpg@b100 { pmi8998_lpg: lpg@b100 {
compatible = "qcom,pwm-lpg"; compatible = "qcom,pwm-lpg";
reg = <0xb100 0x600>; reg = <0xb100 0x600>, <0xb000 0x100>;
reg-names = "lpg-base"; reg-names = "lpg-base", "lut-base";
#pwm-cells = <2>; #pwm-cells = <2>;
qcom,lut-patterns = <0 14 28 42 56 70 84 100
100 84 70 56 42 28 14 0>;
lpg@3 {
qcom,lpg-chan-id = <3>;
qcom,ramp-step-ms = <200>;
qcom,ramp-pause-hi-count = <10>;
qcom,ramp-pause-lo-count = <10>;
qcom,ramp-low-index = <0>;
qcom,ramp-high-index = <15>;
qcom,ramp-from-low-to-high;
qcom,ramp-pattern-repeat;
};
lpg@4 {
qcom,lpg-chan-id = <4>;
qcom,ramp-step-ms = <200>;
qcom,ramp-pause-hi-count = <10>;
qcom,ramp-pause-lo-count = <10>;
qcom,ramp-low-index = <0>;
qcom,ramp-high-index = <15>;
qcom,ramp-from-low-to-high;
qcom,ramp-pattern-repeat;
};
lpg@5 {
qcom,lpg-chan-id = <5>;
qcom,ramp-step-ms = <200>;
qcom,ramp-pause-hi-count = <10>;
qcom,ramp-pause-lo-count = <10>;
qcom,ramp-low-index = <0>;
qcom,ramp-high-index = <15>;
qcom,ramp-from-low-to-high;
qcom,ramp-pattern-repeat;
};
}; };

View File

@ -24,11 +24,16 @@
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pwm.h> #include <linux/pwm.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/types.h> #include <linux/types.h>
#define REG_SIZE_PER_LPG 0x100 #define REG_SIZE_PER_LPG 0x100
#define LPG_BASE "lpg-base"
#define LUT_BASE "lut-base"
/* LPG module registers */
#define REG_LPG_PERPH_SUBTYPE 0x05 #define REG_LPG_PERPH_SUBTYPE 0x05
#define REG_LPG_PATTERN_CONFIG 0x40
#define REG_LPG_PWM_SIZE_CLK 0x41 #define REG_LPG_PWM_SIZE_CLK 0x41
#define REG_LPG_PWM_FREQ_PREDIV_CLK 0x42 #define REG_LPG_PWM_FREQ_PREDIV_CLK 0x42
#define REG_LPG_PWM_TYPE_CONFIG 0x43 #define REG_LPG_PWM_TYPE_CONFIG 0x43
@ -36,16 +41,29 @@
#define REG_LPG_PWM_VALUE_MSB 0x45 #define REG_LPG_PWM_VALUE_MSB 0x45
#define REG_LPG_ENABLE_CONTROL 0x46 #define REG_LPG_ENABLE_CONTROL 0x46
#define REG_LPG_PWM_SYNC 0x47 #define REG_LPG_PWM_SYNC 0x47
#define REG_LPG_RAMP_STEP_DURATION_LSB 0x50
#define REG_LPG_RAMP_STEP_DURATION_MSB 0x51
#define REG_LPG_PAUSE_HI_MULTIPLIER 0x52
#define REG_LPG_PAUSE_LO_MULTIPLIER 0x54
#define REG_LPG_HI_INDEX 0x56
#define REG_LPG_LO_INDEX 0x57
/* REG_LPG_PATTERN_CONFIG */
#define LPG_PATTERN_EN_PAUSE_LO BIT(0)
#define LPG_PATTERN_EN_PAUSE_HI BIT(1)
#define LPG_PATTERN_RAMP_TOGGLE BIT(2)
#define LPG_PATTERN_REPEAT BIT(3)
#define LPG_PATTERN_RAMP_LO_TO_HI BIT(4)
/* REG_LPG_PERPH_SUBTYPE */ /* REG_LPG_PERPH_SUBTYPE */
#define SUBTYPE_PWM 0x0b #define SUBTYPE_PWM 0x0b
#define SUBTYPE_LPG_LITE 0x11 #define SUBTYPE_LPG_LITE 0x11
/* REG_LPG_PWM_SIZE_CLK */ /* REG_LPG_PWM_SIZE_CLK */
#define LPG_PWM_SIZE_MASK_LPG BIT(4) #define LPG_PWM_SIZE_LPG_MASK BIT(4)
#define LPG_PWM_SIZE_MASK_PWM BIT(2) #define LPG_PWM_SIZE_PWM_MASK BIT(2)
#define LPG_PWM_SIZE_SHIFT_LPG 4 #define LPG_PWM_SIZE_LPG_SHIFT 4
#define LPG_PWM_SIZE_SHIFT_PWM 2 #define LPG_PWM_SIZE_PWM_SHIFT 2
#define LPG_PWM_CLK_FREQ_SEL_MASK GENMASK(1, 0) #define LPG_PWM_CLK_FREQ_SEL_MASK GENMASK(1, 0)
/* REG_LPG_PWM_FREQ_PREDIV_CLK */ /* REG_LPG_PWM_FREQ_PREDIV_CLK */
@ -64,6 +82,7 @@
/* REG_LPG_ENABLE_CONTROL */ /* REG_LPG_ENABLE_CONTROL */
#define LPG_EN_LPG_OUT_BIT BIT(7) #define LPG_EN_LPG_OUT_BIT BIT(7)
#define LPG_EN_LPG_OUT_SHIFT 7
#define LPG_PWM_SRC_SELECT_MASK BIT(2) #define LPG_PWM_SRC_SELECT_MASK BIT(2)
#define LPG_PWM_SRC_SELECT_SHIFT 2 #define LPG_PWM_SRC_SELECT_SHIFT 2
#define LPG_EN_RAMP_GEN_MASK BIT(1) #define LPG_EN_RAMP_GEN_MASK BIT(1)
@ -77,9 +96,18 @@
#define NUM_CLK_PREDIV 4 #define NUM_CLK_PREDIV 4
#define NUM_PWM_EXP 8 #define NUM_PWM_EXP 8
enum { #define LPG_HI_LO_IDX_MASK GENMASK(5, 0)
/* LUT module registers */
#define REG_LPG_LUT_1_LSB 0x42
#define REG_LPG_LUT_RAMP_CONTROL 0xc8
#define LPG_LUT_VALUE_MSB_MASK BIT(0)
#define LPG_LUT_COUNT_MAX 47
enum lpg_src {
LUT_PATTERN = 0, LUT_PATTERN = 0,
PWM_OUTPUT, PWM_VALUE,
}; };
static const int pwm_size[NUM_PWM_SIZE] = {6, 9}; static const int pwm_size[NUM_PWM_SIZE] = {6, 9};
@ -87,6 +115,19 @@ static const int clk_freq_hz[NUM_PWM_CLK] = {1024, 32768, 19200000};
static const int clk_prediv[NUM_CLK_PREDIV] = {1, 3, 5, 6}; 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}; static const int pwm_exponent[NUM_PWM_EXP] = {0, 1, 2, 3, 4, 5, 6, 7};
struct lpg_ramp_config {
u16 step_ms;
u8 pause_hi_count;
u8 pause_lo_count;
u8 hi_idx;
u8 lo_idx;
bool ramp_dir_low_to_hi;
bool pattern_repeat;
bool toggle;
u32 *pattern;
u32 pattern_length;
};
struct lpg_pwm_config { struct lpg_pwm_config {
u32 pwm_size; u32 pwm_size;
u32 pwm_clk; u32 pwm_clk;
@ -96,13 +137,23 @@ struct lpg_pwm_config {
u32 best_period_ns; u32 best_period_ns;
}; };
struct qpnp_lpg_lut {
struct qpnp_lpg_chip *chip;
struct mutex lock;
u32 reg_base;
u32 *pattern; /* patterns in percentage */
};
struct qpnp_lpg_channel { struct qpnp_lpg_channel {
struct qpnp_lpg_chip *chip; struct qpnp_lpg_chip *chip;
struct lpg_pwm_config pwm_config; struct lpg_pwm_config pwm_config;
struct lpg_ramp_config ramp_config;
u32 lpg_idx; u32 lpg_idx;
u32 reg_base; u32 reg_base;
u32 max_pattern_length;
u8 src_sel; u8 src_sel;
u8 subtype; u8 subtype;
bool lut_written;
int current_period_ns; int current_period_ns;
int current_duty_ns; int current_duty_ns;
}; };
@ -112,6 +163,7 @@ struct qpnp_lpg_chip {
struct regmap *regmap; struct regmap *regmap;
struct device *dev; struct device *dev;
struct qpnp_lpg_channel *lpgs; struct qpnp_lpg_channel *lpgs;
struct qpnp_lpg_lut *lut;
struct mutex bus_lock; struct mutex bus_lock;
u32 num_lpgs; u32 num_lpgs;
}; };
@ -163,6 +215,36 @@ static int qpnp_lpg_masked_write(struct qpnp_lpg_channel *lpg,
return rc; return rc;
} }
static int qpnp_lut_write(struct qpnp_lpg_lut *lut, u16 addr, u8 val)
{
int rc;
mutex_lock(&lut->chip->bus_lock);
rc = regmap_write(lut->chip->regmap, lut->reg_base + addr, val);
if (rc < 0)
dev_err(lut->chip->dev, "Write addr 0x%x with value %d failed, rc=%d\n",
lut->reg_base + addr, val, rc);
mutex_unlock(&lut->chip->bus_lock);
return rc;
}
static int qpnp_lut_masked_write(struct qpnp_lpg_lut *lut,
u16 addr, u8 mask, u8 val)
{
int rc;
mutex_lock(&lut->chip->bus_lock);
rc = regmap_update_bits(lut->chip->regmap, lut->reg_base + addr,
mask, val);
if (rc < 0)
dev_err(lut->chip->dev, "Update addr 0x%x to val 0x%x with mask 0x%x failed, rc=%d\n",
lut->reg_base + addr, val, mask, rc);
mutex_unlock(&lut->chip->bus_lock);
return rc;
}
static struct qpnp_lpg_channel *pwm_dev_to_qpnp_lpg(struct pwm_chip *pwm_chip, static struct qpnp_lpg_channel *pwm_dev_to_qpnp_lpg(struct pwm_chip *pwm_chip,
struct pwm_device *pwm) struct pwm_device *pwm)
{ {
@ -228,11 +310,11 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg)
/* pwm_clk_idx is 1 bit lower than the register value */ /* pwm_clk_idx is 1 bit lower than the register value */
pwm_clk_idx += 1; pwm_clk_idx += 1;
if (lpg->subtype == SUBTYPE_PWM) { if (lpg->subtype == SUBTYPE_PWM) {
shift = LPG_PWM_SIZE_SHIFT_PWM; shift = LPG_PWM_SIZE_PWM_SHIFT;
mask = LPG_PWM_SIZE_MASK_PWM; mask = LPG_PWM_SIZE_PWM_MASK;
} else { } else {
shift = LPG_PWM_SIZE_SHIFT_LPG; shift = LPG_PWM_SIZE_LPG_SHIFT;
mask = LPG_PWM_SIZE_MASK_LPG; mask = LPG_PWM_SIZE_LPG_MASK;
} }
val = pwm_size_idx << shift | pwm_clk_idx; val = pwm_size_idx << shift | pwm_clk_idx;
@ -253,6 +335,9 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg)
return rc; return rc;
} }
if (lpg->src_sel == LUT_PATTERN)
return 0;
val = lpg->pwm_config.pwm_value & LPG_PWM_VALUE_LSB_MASK; val = lpg->pwm_config.pwm_value & LPG_PWM_VALUE_LSB_MASK;
rc = qpnp_lpg_write(lpg, REG_LPG_PWM_VALUE_LSB, val); rc = qpnp_lpg_write(lpg, REG_LPG_PWM_VALUE_LSB, val);
if (rc < 0) { if (rc < 0) {
@ -281,6 +366,145 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg)
return rc; return rc;
} }
static int qpnp_lpg_set_lut_pattern(struct qpnp_lpg_channel *lpg,
unsigned int *pattern, unsigned int length)
{
struct qpnp_lpg_lut *lut = lpg->chip->lut;
int i, rc = 0;
u16 full_duty_value, pwm_values[LPG_LUT_COUNT_MAX + 1] = {0};
u8 lsb, msb, addr;
if (length > lpg->max_pattern_length) {
dev_err(lpg->chip->dev, "new pattern length (%d) larger than predefined (%d)\n",
length, lpg->max_pattern_length);
return -EINVAL;
}
/* Program LUT pattern */
mutex_lock(&lut->lock);
addr = REG_LPG_LUT_1_LSB + lpg->ramp_config.lo_idx * 2;
for (i = 0; i < length; i++) {
full_duty_value = 1 << lpg->pwm_config.pwm_size;
pwm_values[i] = pattern[i] * full_duty_value / 100;
if (unlikely(pwm_values[i] > full_duty_value)) {
dev_err(lpg->chip->dev, "PWM value %d exceed the max %d\n",
pwm_values[i], full_duty_value);
rc = -EINVAL;
goto unlock;
}
if (pwm_values[i] == full_duty_value)
pwm_values[i] = full_duty_value - 1;
lsb = pwm_values[i] & 0xff;
msb = pwm_values[i] >> 8;
rc = qpnp_lut_write(lut, addr++, lsb);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write NO.%d LUT pattern LSB (%d) failed, rc=%d",
i, lsb, rc);
goto unlock;
}
rc = qpnp_lut_masked_write(lut, addr++,
LPG_LUT_VALUE_MSB_MASK, msb);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write NO.%d LUT pattern MSB (%d) failed, rc=%d",
i, msb, rc);
goto unlock;
}
}
lpg->ramp_config.pattern_length = length;
unlock:
mutex_unlock(&lut->lock);
return rc;
}
static int qpnp_lpg_set_ramp_config(struct qpnp_lpg_channel *lpg)
{
struct lpg_ramp_config *ramp = &lpg->ramp_config;
u8 lsb, msb, addr, mask, val;
int rc = 0;
/* Set ramp step duration */
lsb = ramp->step_ms & 0xff;
msb = ramp->step_ms >> 8;
addr = REG_LPG_RAMP_STEP_DURATION_LSB;
rc = qpnp_lpg_write(lpg, addr, lsb);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write RAMP_STEP_DURATION_LSB failed, rc=%d\n",
rc);
return rc;
}
rc = qpnp_lpg_write(lpg, addr + 1, msb);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write RAMP_STEP_DURATION_MSB failed, rc=%d\n",
rc);
return rc;
}
/* Set hi_idx and lo_idx */
rc = qpnp_lpg_masked_write(lpg, REG_LPG_HI_INDEX,
LPG_HI_LO_IDX_MASK, ramp->hi_idx);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_HI_IDX failed, rc=%d\n",
rc);
return rc;
}
rc = qpnp_lpg_masked_write(lpg, REG_LPG_LO_INDEX,
LPG_HI_LO_IDX_MASK, ramp->lo_idx);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_LO_IDX failed, rc=%d\n",
rc);
return rc;
}
/* Set pause_hi/lo_count */
rc = qpnp_lpg_write(lpg, REG_LPG_PAUSE_HI_MULTIPLIER,
ramp->pause_hi_count);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_PAUSE_HI_MULTIPLIER failed, rc=%d\n",
rc);
return rc;
}
rc = qpnp_lpg_write(lpg, REG_LPG_PAUSE_LO_MULTIPLIER,
ramp->pause_lo_count);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_PAUSE_LO_MULTIPLIER failed, rc=%d\n",
rc);
return rc;
}
/* Set LPG_PATTERN_CONFIG */
addr = REG_LPG_PATTERN_CONFIG;
mask = LPG_PATTERN_EN_PAUSE_LO | LPG_PATTERN_EN_PAUSE_HI
| LPG_PATTERN_RAMP_TOGGLE | LPG_PATTERN_REPEAT
| LPG_PATTERN_RAMP_LO_TO_HI;
val = 0;
if (ramp->pause_lo_count != 0)
val |= LPG_PATTERN_EN_PAUSE_LO;
if (ramp->pause_hi_count != 0)
val |= LPG_PATTERN_EN_PAUSE_HI;
if (ramp->ramp_dir_low_to_hi)
val |= LPG_PATTERN_RAMP_LO_TO_HI;
if (ramp->pattern_repeat)
val |= LPG_PATTERN_REPEAT;
if (ramp->toggle)
val |= LPG_PATTERN_RAMP_TOGGLE;
rc = qpnp_lpg_masked_write(lpg, addr, mask, val);
if (rc < 0) {
dev_err(lpg->chip->dev, "Write LPG_PATTERN_CONFIG failed, rc=%d\n",
rc);
return rc;
}
return rc;
}
static void __qpnp_lpg_calc_pwm_period(int period_ns, static void __qpnp_lpg_calc_pwm_period(int period_ns,
struct lpg_pwm_config *pwm_config) struct lpg_pwm_config *pwm_config)
{ {
@ -397,17 +621,202 @@ static int qpnp_lpg_pwm_config(struct pwm_chip *pwm_chip,
return -EINVAL; return -EINVAL;
} }
if (period_ns != lpg->current_period_ns) if (period_ns != lpg->current_period_ns) {
__qpnp_lpg_calc_pwm_period(period_ns, &lpg->pwm_config); __qpnp_lpg_calc_pwm_period(period_ns, &lpg->pwm_config);
/* program LUT if PWM period is changed */
if (lpg->src_sel == LUT_PATTERN) {
rc = qpnp_lpg_set_lut_pattern(lpg,
lpg->ramp_config.pattern,
lpg->ramp_config.pattern_length);
if (rc < 0) {
dev_err(pwm_chip->dev, "set LUT pattern failed for LPG%d, rc=%d\n",
lpg->lpg_idx, rc);
return rc;
}
lpg->lut_written = true;
}
}
if (period_ns != lpg->current_period_ns || if (period_ns != lpg->current_period_ns ||
duty_ns != lpg->current_duty_ns) duty_ns != lpg->current_duty_ns)
__qpnp_lpg_calc_pwm_duty(period_ns, duty_ns, &lpg->pwm_config); __qpnp_lpg_calc_pwm_duty(period_ns, duty_ns, &lpg->pwm_config);
rc = qpnp_lpg_set_pwm_config(lpg); rc = qpnp_lpg_set_pwm_config(lpg);
if (rc < 0) if (rc < 0) {
dev_err(pwm_chip->dev, "Config PWM failed for channel %d, rc=%d\n", dev_err(pwm_chip->dev, "Config PWM failed for channel %d, rc=%d\n",
lpg->lpg_idx, rc); lpg->lpg_idx, rc);
return rc;
}
lpg->current_period_ns = period_ns;
lpg->current_duty_ns = duty_ns;
return rc;
}
static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en)
{
struct qpnp_lpg_chip *chip = lpg->chip;
struct qpnp_lpg_lut *lut = chip->lut;
u8 mask, val;
int rc;
mask = LPG_PWM_SRC_SELECT_MASK | LPG_EN_LPG_OUT_BIT |
LPG_EN_RAMP_GEN_MASK;
val = lpg->src_sel << LPG_PWM_SRC_SELECT_SHIFT;
if (lpg->src_sel == LUT_PATTERN)
val |= 1 << LPG_EN_RAMP_GEN_SHIFT;
if (en)
val |= 1 << LPG_EN_LPG_OUT_SHIFT;
rc = qpnp_lpg_masked_write(lpg, REG_LPG_ENABLE_CONTROL, mask, val);
if (rc < 0) {
dev_err(chip->dev, "Write LPG_ENABLE_CONTROL failed, rc=%d\n",
rc);
return rc;
}
if (lpg->src_sel == LUT_PATTERN && en) {
mutex_lock(&lut->lock);
val = 1 << lpg->lpg_idx;
rc = qpnp_lut_write(lut, REG_LPG_LUT_RAMP_CONTROL, val);
if (rc < 0)
dev_err(chip->dev, "Write LPG_LUT_RAMP_CONTROL failed, rc=%d\n",
rc);
mutex_unlock(&lut->lock);
}
return rc;
}
static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip,
struct pwm_device *pwm, enum pwm_output_type output_type)
{
struct qpnp_lpg_channel *lpg;
enum lpg_src src_sel;
int rc;
lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm);
if (lpg == NULL) {
dev_err(pwm_chip->dev, "lpg not found\n");
return -ENODEV;
}
if (lpg->chip->lut == NULL) {
pr_debug("lpg%d only support PWM mode\n", lpg->lpg_idx);
return 0;
}
src_sel = (output_type == PWM_OUTPUT_MODULATED) ?
LUT_PATTERN : PWM_VALUE;
if (src_sel == lpg->src_sel)
return 0;
if (src_sel == LUT_PATTERN) {
/* program LUT if it's never been programmed */
if (!lpg->lut_written) {
rc = qpnp_lpg_set_lut_pattern(lpg,
lpg->ramp_config.pattern,
lpg->ramp_config.pattern_length);
if (rc < 0) {
dev_err(pwm_chip->dev, "set LUT pattern failed for LPG%d, rc=%d\n",
lpg->lpg_idx, rc);
return rc;
}
lpg->lut_written = true;
}
rc = qpnp_lpg_set_ramp_config(lpg);
if (rc < 0) {
dev_err(pwm_chip->dev, "Config LPG%d ramping failed, rc=%d\n",
lpg->lpg_idx, rc);
return rc;
}
}
lpg->src_sel = src_sel;
if (pwm_is_enabled(pwm)) {
rc = qpnp_lpg_pwm_src_enable(lpg, true);
if (rc < 0) {
dev_err(pwm_chip->dev, "Enable PWM output failed for channel %d, rc=%d\n",
lpg->lpg_idx, rc);
return rc;
}
}
return 0;
}
static int qpnp_lpg_pwm_set_output_pattern(struct pwm_chip *pwm_chip,
struct pwm_device *pwm, struct pwm_output_pattern *output_pattern)
{
struct qpnp_lpg_channel *lpg;
int rc = 0, i, period_ns, duty_ns;
u32 *percentages;
lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm);
if (lpg == NULL) {
dev_err(pwm_chip->dev, "lpg not found\n");
return -ENODEV;
}
if (output_pattern->num_entries > lpg->max_pattern_length) {
dev_err(lpg->chip->dev, "pattern length %d shouldn't exceed %d\n",
output_pattern->num_entries,
lpg->max_pattern_length);
return -EINVAL;
}
percentages = kcalloc(output_pattern->num_entries,
sizeof(u32), GFP_KERNEL);
if (!percentages)
return -ENOMEM;
period_ns = pwm_get_period(pwm);
for (i = 0; i < output_pattern->num_entries; i++) {
duty_ns = output_pattern->duty_pattern[i];
if (duty_ns > period_ns) {
dev_err(lpg->chip->dev, "duty %dns is larger than period %dns\n",
duty_ns, period_ns);
goto err;
}
/* Translate the pattern in duty_ns to percentage */
if ((INT_MAX / duty_ns) < 100)
percentages[i] = duty_ns / (period_ns / 100);
else
percentages[i] = (duty_ns * 100) / period_ns;
}
rc = qpnp_lpg_set_lut_pattern(lpg, percentages,
output_pattern->num_entries);
if (rc < 0) {
dev_err(lpg->chip->dev, "Set LUT pattern failed for LPG%d, rc=%d\n",
lpg->lpg_idx, rc);
goto err;
}
lpg->lut_written = true;
memcpy(lpg->ramp_config.pattern, percentages,
output_pattern->num_entries);
lpg->ramp_config.hi_idx = lpg->ramp_config.lo_idx +
output_pattern->num_entries - 1;
if ((INT_MAX / period_ns) > output_pattern->cycles_per_duty)
lpg->ramp_config.step_ms = output_pattern->cycles_per_duty *
period_ns / NSEC_PER_MSEC;
else
lpg->ramp_config.step_ms = (period_ns / NSEC_PER_MSEC) *
output_pattern->cycles_per_duty;
rc = qpnp_lpg_set_ramp_config(lpg);
if (rc < 0)
dev_err(pwm_chip->dev, "Config LPG%d ramping failed, rc=%d\n",
lpg->lpg_idx, rc);
err:
kfree(percentages);
return rc; return rc;
} }
@ -417,7 +826,6 @@ static int qpnp_lpg_pwm_enable(struct pwm_chip *pwm_chip,
{ {
struct qpnp_lpg_channel *lpg; struct qpnp_lpg_channel *lpg;
int rc = 0; int rc = 0;
u8 mask, val;
lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm); lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm);
if (lpg == NULL) { if (lpg == NULL) {
@ -432,10 +840,7 @@ static int qpnp_lpg_pwm_enable(struct pwm_chip *pwm_chip,
return rc; return rc;
} }
mask = LPG_PWM_SRC_SELECT_MASK | LPG_EN_LPG_OUT_BIT; rc = qpnp_lpg_pwm_src_enable(lpg, true);
val = lpg->src_sel << LPG_PWM_SRC_SELECT_SHIFT | LPG_EN_LPG_OUT_BIT;
rc = qpnp_lpg_masked_write(lpg, REG_LPG_ENABLE_CONTROL, mask, val);
if (rc < 0) if (rc < 0)
dev_err(pwm_chip->dev, "Enable PWM output failed for channel %d, rc=%d\n", dev_err(pwm_chip->dev, "Enable PWM output failed for channel %d, rc=%d\n",
lpg->lpg_idx, rc); lpg->lpg_idx, rc);
@ -448,7 +853,6 @@ static void qpnp_lpg_pwm_disable(struct pwm_chip *pwm_chip,
{ {
struct qpnp_lpg_channel *lpg; struct qpnp_lpg_channel *lpg;
int rc; int rc;
u8 mask, val;
lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm); lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm);
if (lpg == NULL) { if (lpg == NULL) {
@ -456,10 +860,7 @@ static void qpnp_lpg_pwm_disable(struct pwm_chip *pwm_chip,
return; return;
} }
mask = LPG_PWM_SRC_SELECT_MASK | LPG_EN_LPG_OUT_BIT; rc = qpnp_lpg_pwm_src_enable(lpg, false);
val = lpg->src_sel << LPG_PWM_SRC_SELECT_SHIFT;
rc = qpnp_lpg_masked_write(lpg, REG_LPG_ENABLE_CONTROL, mask, val);
if (rc < 0) { if (rc < 0) {
dev_err(pwm_chip->dev, "Disable PWM output failed for channel %d, rc=%d\n", dev_err(pwm_chip->dev, "Disable PWM output failed for channel %d, rc=%d\n",
lpg->lpg_idx, rc); lpg->lpg_idx, rc);
@ -472,13 +873,32 @@ static void qpnp_lpg_pwm_disable(struct pwm_chip *pwm_chip,
rc); rc);
} }
static int qpnp_lpg_pwm_output_types_supported(struct pwm_chip *pwm_chip,
struct pwm_device *pwm)
{
enum pwm_output_type type = PWM_OUTPUT_FIXED;
struct qpnp_lpg_channel *lpg;
lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm);
if (lpg == NULL) {
dev_err(pwm_chip->dev, "lpg not found\n");
return type;
}
if (lpg->chip->lut != NULL)
type |= PWM_OUTPUT_MODULATED;
return type;
}
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
static void qpnp_lpg_pwm_dbg_show(struct pwm_chip *pwm_chip, struct seq_file *s) static void qpnp_lpg_pwm_dbg_show(struct pwm_chip *pwm_chip, struct seq_file *s)
{ {
struct qpnp_lpg_channel *lpg; struct qpnp_lpg_channel *lpg;
struct lpg_pwm_config *cfg; struct lpg_pwm_config *cfg;
struct lpg_ramp_config *ramp;
struct pwm_device *pwm; struct pwm_device *pwm;
int i; int i, j;
for (i = 0; i < pwm_chip->npwm; i++) { for (i = 0; i < pwm_chip->npwm; i++) {
pwm = &pwm_chip->pwms[i]; pwm = &pwm_chip->pwms[i];
@ -513,12 +933,39 @@ static void qpnp_lpg_pwm_dbg_show(struct pwm_chip *pwm_chip, struct seq_file *s)
seq_printf(s, " pwm_value = %d\n", cfg->pwm_value); seq_printf(s, " pwm_value = %d\n", cfg->pwm_value);
seq_printf(s, " Requested period: %dns, best period = %dns\n", seq_printf(s, " Requested period: %dns, best period = %dns\n",
pwm_get_period(pwm), cfg->best_period_ns); pwm_get_period(pwm), cfg->best_period_ns);
ramp = &lpg->ramp_config;
if (pwm_get_output_type(pwm) == PWM_OUTPUT_MODULATED) {
seq_puts(s, " ramping duty percentages:");
for (j = 0; j < ramp->pattern_length; j++)
seq_printf(s, " %d", ramp->pattern[j]);
seq_puts(s, "\n");
seq_printf(s, " ramping time per step: %dms\n",
ramp->step_ms);
seq_printf(s, " ramping low index: %d\n",
ramp->lo_idx);
seq_printf(s, " ramping high index: %d\n",
ramp->hi_idx);
seq_printf(s, " ramping from low to high: %d\n",
ramp->ramp_dir_low_to_hi);
seq_printf(s, " ramping pattern repeat: %d\n",
ramp->pattern_repeat);
seq_printf(s, " ramping toggle: %d\n",
ramp->toggle);
seq_printf(s, " ramping pause count at low index: %d\n",
ramp->pause_lo_count);
seq_printf(s, " ramping pause count at high index: %d\n",
ramp->pause_hi_count);
}
} }
} }
#endif #endif
static const struct pwm_ops qpnp_lpg_pwm_ops = { static const struct pwm_ops qpnp_lpg_pwm_ops = {
.config = qpnp_lpg_pwm_config, .config = qpnp_lpg_pwm_config,
.get_output_type_supported = qpnp_lpg_pwm_output_types_supported,
.set_output_type = qpnp_lpg_pwm_set_output_type,
.set_output_pattern = qpnp_lpg_pwm_set_output_pattern,
.enable = qpnp_lpg_pwm_enable, .enable = qpnp_lpg_pwm_enable,
.disable = qpnp_lpg_pwm_disable, .disable = qpnp_lpg_pwm_disable,
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
@ -529,15 +976,19 @@ static const struct pwm_ops qpnp_lpg_pwm_ops = {
static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip)
{ {
struct device_node *child;
struct qpnp_lpg_channel *lpg;
struct lpg_ramp_config *ramp;
int rc = 0, i; int rc = 0, i;
u64 base, length; u32 base, length, lpg_chan_id, tmp;
const __be32 *addr; const __be32 *addr;
addr = of_get_address(chip->dev->of_node, 0, NULL, NULL); addr = of_get_address(chip->dev->of_node, 0, NULL, NULL);
if (!addr) { if (!addr) {
dev_err(chip->dev, "Getting address failed\n"); dev_err(chip->dev, "Get %s address failed\n", LPG_BASE);
return -EINVAL; return -EINVAL;
} }
base = be32_to_cpu(addr[0]); base = be32_to_cpu(addr[0]);
length = be32_to_cpu(addr[1]); length = be32_to_cpu(addr[1]);
@ -551,7 +1002,7 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip)
chip->lpgs[i].chip = chip; chip->lpgs[i].chip = chip;
chip->lpgs[i].lpg_idx = i; chip->lpgs[i].lpg_idx = i;
chip->lpgs[i].reg_base = base + i * REG_SIZE_PER_LPG; chip->lpgs[i].reg_base = base + i * REG_SIZE_PER_LPG;
chip->lpgs[i].src_sel = PWM_OUTPUT; chip->lpgs[i].src_sel = PWM_VALUE;
rc = qpnp_lpg_read(&chip->lpgs[i], REG_LPG_PERPH_SUBTYPE, rc = qpnp_lpg_read(&chip->lpgs[i], REG_LPG_PERPH_SUBTYPE,
&chip->lpgs[i].subtype); &chip->lpgs[i].subtype);
if (rc < 0) { if (rc < 0) {
@ -560,7 +1011,142 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip)
} }
} }
return rc; addr = of_get_address(chip->dev->of_node, 1, NULL, NULL);
if (!addr) {
pr_debug("NO LUT address assigned\n");
return 0;
}
chip->lut = devm_kmalloc(chip->dev, sizeof(*chip->lut), GFP_KERNEL);
if (!chip->lut)
return -ENOMEM;
chip->lut->chip = chip;
chip->lut->reg_base = be32_to_cpu(*addr);
mutex_init(&chip->lut->lock);
rc = of_property_count_elems_of_size(chip->dev->of_node,
"qcom,lut-patterns", sizeof(u32));
if (rc < 0) {
dev_err(chip->dev, "Read qcom,lut-patterns failed, rc=%d\n",
rc);
return rc;
}
length = rc;
if (length > LPG_LUT_COUNT_MAX) {
dev_err(chip->dev, "qcom,lut-patterns length %d exceed max %d\n",
length, LPG_LUT_COUNT_MAX);
return -EINVAL;
}
chip->lut->pattern = devm_kcalloc(chip->dev, LPG_LUT_COUNT_MAX,
sizeof(*chip->lut->pattern), GFP_KERNEL);
if (!chip->lut->pattern)
return -ENOMEM;
rc = of_property_read_u32_array(chip->dev->of_node, "qcom,lut-patterns",
chip->lut->pattern, length);
if (rc < 0) {
dev_err(chip->dev, "Get qcom,lut-patterns failed, rc=%d\n",
rc);
return rc;
}
if (of_get_available_child_count(chip->dev->of_node) == 0) {
dev_err(chip->dev, "No ramp configuration for any LPG\n");
return -EINVAL;
}
for_each_available_child_of_node(chip->dev->of_node, child) {
rc = of_property_read_u32(child, "qcom,lpg-chan-id",
&lpg_chan_id);
if (rc < 0) {
dev_err(chip->dev, "Get qcom,lpg-chan-id failed for node %s, rc=%d\n",
child->name, rc);
return rc;
}
if (lpg_chan_id > chip->num_lpgs) {
dev_err(chip->dev, "lpg-chann-id %d is out of range 1~%d\n",
lpg_chan_id, chip->num_lpgs);
return -EINVAL;
}
/* lpg channel id is indexed from 1 in hardware */
lpg = &chip->lpgs[lpg_chan_id - 1];
ramp = &lpg->ramp_config;
rc = of_property_read_u32(child, "qcom,ramp-step-ms", &tmp);
if (rc < 0) {
dev_err(chip->dev, "get qcom,ramp-step-ms failed for lpg%d, rc=%d\n",
lpg_chan_id, rc);
return rc;
}
ramp->step_ms = (u16)tmp;
rc = of_property_read_u32(child, "qcom,ramp-low-index", &tmp);
if (rc < 0) {
dev_err(chip->dev, "get qcom,ramp-low-index failed for lpg%d, rc=%d\n",
lpg_chan_id, rc);
return rc;
}
ramp->lo_idx = (u8)tmp;
if (ramp->lo_idx >= LPG_LUT_COUNT_MAX) {
dev_err(chip->dev, "qcom,ramp-low-index should less than max %d\n",
LPG_LUT_COUNT_MAX);
return -EINVAL;
}
rc = of_property_read_u32(child, "qcom,ramp-high-index", &tmp);
if (rc < 0) {
dev_err(chip->dev, "get qcom,ramp-high-index failed for lpg%d, rc=%d\n",
lpg_chan_id, rc);
return rc;
}
ramp->hi_idx = (u8)tmp;
if (ramp->hi_idx > LPG_LUT_COUNT_MAX) {
dev_err(chip->dev, "qcom,ramp-high-index shouldn't exceed max %d\n",
LPG_LUT_COUNT_MAX);
return -EINVAL;
}
if (ramp->hi_idx <= ramp->lo_idx) {
dev_err(chip->dev, "high-index(%d) should be larger than low-index(%d)\n",
ramp->hi_idx, ramp->lo_idx);
return -EINVAL;
}
ramp->pattern_length = ramp->hi_idx - ramp->lo_idx + 1;
ramp->pattern = &chip->lut->pattern[ramp->lo_idx];
lpg->max_pattern_length = ramp->pattern_length;
rc = of_property_read_u32(child,
"qcom,ramp-pause-hi-count", &tmp);
if (rc < 0)
ramp->pause_hi_count = 0;
else
ramp->pause_hi_count = (u8)tmp;
rc = of_property_read_u32(child,
"qcom,ramp-pause-lo-count", &tmp);
if (rc < 0)
ramp->pause_lo_count = 0;
else
ramp->pause_lo_count = (u8)tmp;
ramp->ramp_dir_low_to_hi = of_property_read_bool(child,
"qcom,ramp-from-low-to-high");
ramp->pattern_repeat = of_property_read_bool(child,
"qcom,ramp-pattern-repeat");
ramp->toggle = of_property_read_bool(child,
"qcom,ramp-toggle");
}
return 0;
} }
static int qpnp_lpg_probe(struct platform_device *pdev) static int qpnp_lpg_probe(struct platform_device *pdev)
@ -584,7 +1170,7 @@ static int qpnp_lpg_probe(struct platform_device *pdev)
if (rc < 0) { if (rc < 0) {
dev_err(chip->dev, "Devicetree properties parsing failed, rc=%d\n", dev_err(chip->dev, "Devicetree properties parsing failed, rc=%d\n",
rc); rc);
goto destroy; goto err_out;
} }
dev_set_drvdata(chip->dev, chip); dev_set_drvdata(chip->dev, chip);
@ -596,11 +1182,11 @@ static int qpnp_lpg_probe(struct platform_device *pdev)
rc = pwmchip_add(&chip->pwm_chip); rc = pwmchip_add(&chip->pwm_chip);
if (rc < 0) { if (rc < 0) {
dev_err(chip->dev, "Add pwmchip failed, rc=%d\n", rc); dev_err(chip->dev, "Add pwmchip failed, rc=%d\n", rc);
goto destroy; goto err_out;
} }
return 0; return 0;
destroy: err_out:
mutex_destroy(&chip->bus_lock); mutex_destroy(&chip->bus_lock);
return rc; return rc;
} }