From 665d78a15cb29d8a0f4f9d00125cf15234e71990 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 26 May 2020 19:21:23 -0700 Subject: [PATCH 1/3] pwm: qti-lpg: Support two-nvmem PPG scheme On PMICs such as PM8350C, the lookup table containing the pattern data is stored in a separate nvmem device from the one where the per-channel data is stored. Add two separate nvmems to handle this case while maintaining backward compatibility for those targets that use only a single nvmem device. Differentiate between the two schemes via an enum. Change-Id: I2826a9acc8cee4d83b6cf2bf2d4d60b1ec3b67d7 Signed-off-by: Guru Das Srinagesh --- drivers/pwm/pwm-qti-lpg.c | 68 ++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/drivers/pwm/pwm-qti-lpg.c b/drivers/pwm/pwm-qti-lpg.c index cc7a629c91c9..b395695416a4 100644 --- a/drivers/pwm/pwm-qti-lpg.c +++ b/drivers/pwm/pwm-qti-lpg.c @@ -191,7 +191,8 @@ struct qpnp_lpg_chip { struct qpnp_lpg_lut *lut; struct mutex bus_lock; u32 *lpg_group; - struct nvmem_device *sdam_nvmem; + struct nvmem_device *lpg_chan_nvmem; + struct nvmem_device *lut_nvmem; struct device_node *pbs_dev_node; u32 num_lpgs; unsigned long pbs_en_bitmap; @@ -275,12 +276,13 @@ static int qpnp_lut_masked_write(struct qpnp_lpg_lut *lut, return rc; } -static int qpnp_sdam_write(struct qpnp_lpg_chip *chip, u16 addr, u8 val) +static int qpnp_lpg_chan_nvmem_write(struct qpnp_lpg_chip *chip, u16 addr, + u8 val) { int rc; mutex_lock(&chip->bus_lock); - rc = nvmem_device_write(chip->sdam_nvmem, addr, 1, &val); + rc = nvmem_device_write(chip->lpg_chan_nvmem, addr, 1, &val); if (rc < 0) dev_err(chip->dev, "write SDAM add 0x%x failed, rc=%d\n", addr, rc); @@ -296,7 +298,7 @@ static int qpnp_lpg_sdam_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 val) int rc; mutex_lock(&chip->bus_lock); - rc = nvmem_device_write(chip->sdam_nvmem, + rc = nvmem_device_write(chip->lpg_chan_nvmem, lpg->lpg_sdam_base + addr, 1, &val); if (rc < 0) dev_err(chip->dev, "write SDAM add 0x%x failed, rc=%d\n", @@ -316,7 +318,7 @@ static int qpnp_lpg_sdam_masked_write(struct qpnp_lpg_channel *lpg, mutex_lock(&chip->bus_lock); - rc = nvmem_device_read(chip->sdam_nvmem, + rc = nvmem_device_read(chip->lpg_chan_nvmem, lpg->lpg_sdam_base + addr, 1, &tmp); if (rc < 0) { dev_err(chip->dev, "Read SDAM addr %d failed, rc=%d\n", @@ -326,7 +328,7 @@ static int qpnp_lpg_sdam_masked_write(struct qpnp_lpg_channel *lpg, tmp = tmp & ~mask; tmp |= val & mask; - rc = nvmem_device_write(chip->sdam_nvmem, + rc = nvmem_device_write(chip->lpg_chan_nvmem, lpg->lpg_sdam_base + addr, 1, &tmp); if (rc < 0) dev_err(chip->dev, "write SDAM addr %d failed, rc=%d\n", @@ -348,7 +350,7 @@ static int qpnp_lut_sdam_write(struct qpnp_lpg_lut *lut, return -EINVAL; mutex_lock(&chip->bus_lock); - rc = nvmem_device_write(chip->sdam_nvmem, + rc = nvmem_device_write(chip->lut_nvmem, lut->reg_base + addr, length, val); if (rc < 0) dev_err(chip->dev, "write SDAM addr %d failed, rc=%d\n", @@ -534,7 +536,7 @@ static int qpnp_lpg_set_sdam_ramp_config(struct qpnp_lpg_channel *lpg) if (val > 0) val--; addr = SDAM_REG_RAMP_STEP_DURATION; - rc = qpnp_sdam_write(lpg->chip, addr, val); + rc = qpnp_lpg_chan_nvmem_write(lpg->chip, addr, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_RAMP_STEP_DURATION failed, rc=%d\n", rc); @@ -887,8 +889,8 @@ static int qpnp_lpg_pbs_trigger_enable(struct qpnp_lpg_channel *lpg, bool en) if (en) { if (chip->pbs_en_bitmap == 0) { - rc = qpnp_sdam_write(chip, SDAM_REG_PBS_SEQ_EN, - PBS_SW_TRG_BIT); + rc = qpnp_lpg_chan_nvmem_write(chip, + SDAM_REG_PBS_SEQ_EN, PBS_SW_TRG_BIT); if (rc < 0) { dev_err(chip->dev, "Write SDAM_REG_PBS_SEQ_EN failed, rc=%d\n", rc); @@ -907,7 +909,8 @@ static int qpnp_lpg_pbs_trigger_enable(struct qpnp_lpg_channel *lpg, bool en) } else { clear_bit(lpg->lpg_idx, &chip->pbs_en_bitmap); if (chip->pbs_en_bitmap == 0) { - rc = qpnp_sdam_write(chip, SDAM_REG_PBS_SEQ_EN, 0); + rc = qpnp_lpg_chan_nvmem_write(chip, + SDAM_REG_PBS_SEQ_EN, 0); if (rc < 0) { dev_err(chip->dev, "Write SDAM_REG_PBS_SEQ_EN failed, rc=%d\n", rc); @@ -1449,6 +1452,40 @@ static bool lut_is_defined(struct qpnp_lpg_chip *chip, const __be32 **addr) return true; } +static int qpnp_lpg_get_nvmem_dt(struct qpnp_lpg_chip *chip) +{ + int rc = 0; + struct nvmem_device *ppg_nv, *lut_nv, *lpg_nv; + + /* Ensure backward compatibility */ + ppg_nv = devm_nvmem_device_get(chip->dev, "ppg_sdam"); + if (IS_ERR(ppg_nv)) { + lut_nv = devm_nvmem_device_get(chip->dev, "lut_sdam"); + if (IS_ERR(lut_nv)) { + rc = PTR_ERR(lut_nv); + goto err; + } + + lpg_nv = devm_nvmem_device_get(chip->dev, "lpg_chan_sdam"); + if (IS_ERR(lpg_nv)) { + rc = PTR_ERR(lpg_nv); + goto err; + } + + chip->lut_nvmem = lut_nv; + chip->lpg_chan_nvmem = lpg_nv; + } else { + chip->lut_nvmem = chip->lpg_chan_nvmem = ppg_nv; + } + + return 0; +err: + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, "Failed to get nvmem device, rc=%d\n", + rc); + return rc; +} + static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) { int rc = 0, i; @@ -1483,14 +1520,9 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) if (!chip->lut) return -ENOMEM; - chip->sdam_nvmem = devm_nvmem_device_get(chip->dev, "ppg_sdam"); - if (IS_ERR_OR_NULL(chip->sdam_nvmem)) { - rc = PTR_ERR(chip->sdam_nvmem); - if (rc != -EPROBE_DEFER) - dev_err(chip->dev, "Failed to get nvmem device, rc=%d\n", - rc); + rc = qpnp_lpg_get_nvmem_dt(chip); + if (rc < 0) return rc; - } chip->use_sdam = true; chip->pbs_dev_node = of_parse_phandle(chip->dev->of_node, From 6d56178cb701a47a5b93950722b849f07ee28a46 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 26 May 2020 19:22:03 -0700 Subject: [PATCH 2/3] pwm: qti-lpg: Adapt to two-nvmem PBS triggering scheme PM8350C PWM PPG does away with the need to directly manage the PBS triggering via the PBS driver APIs by using a single register write to an SDAM register instead. A PBS sequence thus triggered should be duly cleared before next use. The existing scheme is also retained for backward compatibility with targets that use a single-nvmem scheme. The new PBS triggering scheme also expects a change in the way the high and low indices pertaining to the pattern are specified. Change-Id: I7d07dc611f98ce32cf5e8ba7de758fbbc3513f4b Signed-off-by: Guru Das Srinagesh --- drivers/pwm/pwm-qti-lpg.c | 124 +++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/drivers/pwm/pwm-qti-lpg.c b/drivers/pwm/pwm-qti-lpg.c index b395695416a4..d6df9fe46513 100644 --- a/drivers/pwm/pwm-qti-lpg.c +++ b/drivers/pwm/pwm-qti-lpg.c @@ -134,6 +134,12 @@ enum lpg_src { PWM_VALUE, }; +enum ppg_num_nvmems { + PPG_NO_NVMEMS, + PPG_NVMEMS_1, /* A single nvmem for both LUT and LPG channel config */ + PPG_NVMEMS_2, /* Two separate nvmems for LUT and LPG channel config */ +}; + static const int pwm_size[NUM_PWM_SIZE] = {6, 9}; static const int clk_freq_hz[NUM_PWM_CLK] = {1024, 32768, 19200000}; static const int clk_prediv[NUM_CLK_PREDIV] = {1, 3, 5, 6}; @@ -164,6 +170,7 @@ struct lpg_pwm_config { struct qpnp_lpg_lut { struct qpnp_lpg_chip *chip; struct mutex lock; + enum ppg_num_nvmems nvmem_count; u32 reg_base; u32 *pattern; /* patterns in percentage */ }; @@ -515,6 +522,18 @@ unlock: return rc; } +#define SDAM_START_BASE 0x40 +static u8 qpnp_lpg_get_sdam_lut_idx(struct qpnp_lpg_channel *lpg, u8 idx) +{ + struct qpnp_lpg_chip *chip = lpg->chip; + u8 val = idx; + + if (chip->lut->nvmem_count == PPG_NVMEMS_2) + val += (chip->lut->reg_base - SDAM_START_BASE); + + return val; +} + static int qpnp_lpg_set_sdam_ramp_config(struct qpnp_lpg_channel *lpg) { struct lpg_ramp_config *ramp = &lpg->ramp_config; @@ -544,15 +563,16 @@ static int qpnp_lpg_set_sdam_ramp_config(struct qpnp_lpg_channel *lpg) } /* Set hi_idx and lo_idx */ - rc = qpnp_lpg_sdam_write(lpg, SDAM_END_INDEX_OFFSET, ramp->hi_idx); + val = qpnp_lpg_get_sdam_lut_idx(lpg, ramp->hi_idx); + rc = qpnp_lpg_sdam_write(lpg, SDAM_END_INDEX_OFFSET, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_END_INDEX failed, rc=%d\n", rc); return rc; } - rc = qpnp_lpg_sdam_write(lpg, SDAM_START_INDEX_OFFSET, - ramp->lo_idx); + val = qpnp_lpg_get_sdam_lut_idx(lpg, ramp->lo_idx); + rc = qpnp_lpg_sdam_write(lpg, SDAM_START_INDEX_OFFSET, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_START_INDEX failed, rc=%d\n", rc); @@ -882,6 +902,63 @@ static int qpnp_lpg_pwm_config(struct pwm_chip *pwm_chip, return qpnp_lpg_config(lpg, duty_ns, period_ns); } +#define SDAM_PBS_TRIG_SET 0xe5 +#define SDAM_PBS_TRIG_CLR 0xe6 +static int qpnp_lpg_clear_pbs_trigger(struct qpnp_lpg_chip *chip) +{ + int rc; + + rc = qpnp_lpg_chan_nvmem_write(chip, + SDAM_REG_PBS_SEQ_EN, 0); + if (rc < 0) { + dev_err(chip->dev, "Write SDAM_REG_PBS_SEQ_EN failed, rc=%d\n", + rc); + return rc; + } + + if (chip->lut->nvmem_count == PPG_NVMEMS_2) { + rc = qpnp_lpg_chan_nvmem_write(chip, SDAM_PBS_TRIG_CLR, + PBS_SW_TRG_BIT); + if (rc < 0) { + dev_err(chip->dev, "Failed to fire PBS seq, rc=%d\n", + rc); + return rc; + } + } + + return 0; +} + +static int qpnp_lpg_set_pbs_trigger(struct qpnp_lpg_chip *chip) +{ + int rc; + + rc = qpnp_lpg_chan_nvmem_write(chip, + SDAM_REG_PBS_SEQ_EN, PBS_SW_TRG_BIT); + if (rc < 0) { + dev_err(chip->dev, "Write SDAM_REG_PBS_SEQ_EN failed, rc=%d\n", + rc); + return rc; + } + + if (chip->lut->nvmem_count == PPG_NVMEMS_1) { + if (!chip->pbs_dev_node) { + dev_err(chip->dev, "PBS device unavailable\n"); + return -ENODEV; + } + rc = qpnp_pbs_trigger_event(chip->pbs_dev_node, + PBS_SW_TRG_BIT); + } else { + rc = qpnp_lpg_chan_nvmem_write(chip, SDAM_PBS_TRIG_SET, + PBS_SW_TRG_BIT); + } + + if (rc < 0) + dev_err(chip->dev, "Failed to trigger PBS, rc=%d\n", rc); + + return rc; +} + static int qpnp_lpg_pbs_trigger_enable(struct qpnp_lpg_channel *lpg, bool en) { struct qpnp_lpg_chip *chip = lpg->chip; @@ -889,33 +966,17 @@ static int qpnp_lpg_pbs_trigger_enable(struct qpnp_lpg_channel *lpg, bool en) if (en) { if (chip->pbs_en_bitmap == 0) { - rc = qpnp_lpg_chan_nvmem_write(chip, - SDAM_REG_PBS_SEQ_EN, PBS_SW_TRG_BIT); - if (rc < 0) { - dev_err(chip->dev, "Write SDAM_REG_PBS_SEQ_EN failed, rc=%d\n", - rc); + rc = qpnp_lpg_set_pbs_trigger(chip); + if (rc < 0) return rc; - } - - rc = qpnp_pbs_trigger_event(chip->pbs_dev_node, - PBS_SW_TRG_BIT); - if (rc < 0) { - dev_err(chip->dev, "Failed to trigger PBS, rc=%d\n", - rc); - return rc; - } } set_bit(lpg->lpg_idx, &chip->pbs_en_bitmap); } else { clear_bit(lpg->lpg_idx, &chip->pbs_en_bitmap); if (chip->pbs_en_bitmap == 0) { - rc = qpnp_lpg_chan_nvmem_write(chip, - SDAM_REG_PBS_SEQ_EN, 0); - if (rc < 0) { - dev_err(chip->dev, "Write SDAM_REG_PBS_SEQ_EN failed, rc=%d\n", - rc); + rc = qpnp_lpg_clear_pbs_trigger(chip); + if (rc < 0) return rc; - } } } @@ -1474,8 +1535,10 @@ static int qpnp_lpg_get_nvmem_dt(struct qpnp_lpg_chip *chip) chip->lut_nvmem = lut_nv; chip->lpg_chan_nvmem = lpg_nv; + chip->lut->nvmem_count = PPG_NVMEMS_2; } else { chip->lut_nvmem = chip->lpg_chan_nvmem = ppg_nv; + chip->lut->nvmem_count = PPG_NVMEMS_1; } return 0; @@ -1525,11 +1588,15 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return rc; chip->use_sdam = true; - chip->pbs_dev_node = of_parse_phandle(chip->dev->of_node, - "qcom,pbs-client", 0); - if (!chip->pbs_dev_node) { - dev_err(chip->dev, "Missing qcom,pbs-client property\n"); - return -EINVAL; + if (of_find_property(chip->dev->of_node, "qcom,pbs-client", + NULL)) { + chip->pbs_dev_node = of_parse_phandle( + chip->dev->of_node, + "qcom,pbs-client", 0); + if (!chip->pbs_dev_node) { + dev_err(chip->dev, "Missing qcom,pbs-client property\n"); + return -ENODEV; + } } rc = of_property_read_u32(chip->dev->of_node, @@ -1648,6 +1715,7 @@ static int qpnp_lpg_remove(struct platform_device *pdev) struct qpnp_lpg_chip *chip = dev_get_drvdata(&pdev->dev); int rc = 0; + of_node_put(chip->pbs_dev_node); rc = pwmchip_remove(&chip->pwm_chip); if (rc < 0) dev_err(chip->dev, "Remove pwmchip failed, rc=%d\n", rc); From fe41af3d8fd990ef3b5ade2ceaeaad62b46e143a Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 26 May 2020 17:56:41 -0700 Subject: [PATCH 3/3] pwm: qti-lpg: Read tick duration via device tree Tick duration defines the tick duration (in microseconds) for the clock that PPG uses which is used to program the ramp step duration for the pattern. If unspecified, the default value is 7800 us. Change-Id: I7cd96d74a39becffbf9a008610c0092ad2462446 Signed-off-by: Guru Das Srinagesh --- drivers/pwm/pwm-qti-lpg.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-qti-lpg.c b/drivers/pwm/pwm-qti-lpg.c index d6df9fe46513..ca1ec690ee69 100644 --- a/drivers/pwm/pwm-qti-lpg.c +++ b/drivers/pwm/pwm-qti-lpg.c @@ -173,6 +173,7 @@ struct qpnp_lpg_lut { enum ppg_num_nvmems nvmem_count; u32 reg_base; u32 *pattern; /* patterns in percentage */ + u32 ramp_step_tick_us; }; struct qpnp_lpg_channel { @@ -550,8 +551,8 @@ static int qpnp_lpg_set_sdam_ramp_config(struct qpnp_lpg_channel *lpg) return rc; } - /* Set ramp step duration, one WAIT_TICK is 7.8ms */ - val = (ramp->step_ms * 1000 / 7800) & 0xff; + /* Set ramp step duration, in ticks */ + val = (ramp->step_ms * 1000 / lpg->chip->lut->ramp_step_tick_us) & 0xff; if (val > 0) val--; addr = SDAM_REG_RAMP_STEP_DURATION; @@ -1549,6 +1550,7 @@ err: return rc; } +#define DEFAULT_TICK_DURATION_US 7800 static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) { int rc = 0, i; @@ -1608,6 +1610,10 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return rc; } + chip->lut->ramp_step_tick_us = DEFAULT_TICK_DURATION_US; + of_property_read_u32(chip->dev->of_node, "qcom,tick-period-us", + &chip->lut->ramp_step_tick_us); + rc = qpnp_lpg_parse_pattern_dt(chip, SDAM_LUT_COUNT_MAX); if (rc < 0) { of_node_put(chip->pbs_dev_node);