Loading Documentation/devicetree/bindings/pwm/pwm-qti-lpg.txt +117 −27 Original line number Diff line number Diff line Loading @@ -18,7 +18,8 @@ device module in Qualcomm Technologies, Inc. PMIC chips. Value type: <string> Definition: The name of the register defined in the reg property. It must have "lpg-base", "lut-base" is optional but it's required if any LPG channels support LUT mode. it's required if any LPG channels support LUT mode with a LUT module. - #pwm-cells: Usage: required Loading @@ -33,14 +34,47 @@ device module in Qualcomm Technologies, Inc. PMIC chips. Value type: <u32> Definition: The number of the consecutive LPG/PWM channels in the chip. - nvmem-names: Usage: optional Value type: <string> Definition: The nvmem device name for the SDAM module where the LUT pattern is stored. It must be "ppg_sdam". This property is required only when LUT mode is supported with a SDAM module instead of a LUT module. - nvmem: Usage: optional Value type: <phandle> Definition: Phandle of the nvmem device to access the LUT stored in the SDAM module. This property is required only when LUT mode is supported and the LUT pattern is stored in a SDAM module instead of a LUT module. - qcom,pbs-client Usage: optional Value type: <phandle> Definition: Phandle of the PBS client used for sending the PBS trigger. This property is required when LUT mode is supported and the LUT pattern is stored in a SDAM module instead of a LUT module. - qcom,lut-sdam-base: Usage: optional Value type: <u32> Definition: The register base of the LUT entries stored in SDAM. This property is required only when LUT mode is supported and the LUT pattern is stored in a SDAM module instead of a LUT module. - 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 and stored in LUT or SDAM module shared for all LPG channels. The LUT module has resource to store 47 PWM values at max while SDAM module can store upto 64 PWM values. This property is required if any LPG channels support LUT mode. - qcom,sync-channel-ids: Loading Loading @@ -68,31 +102,37 @@ parameters needs to be configured for that channel. range is 1 - 8. Maximum value depends on the number of channels supported on PMIC. - qcom,lpg-sdam-base: Usage: optional Value type: <u32> Definition: Register base address for LPG configuration in SDAM for the LPG channel specified under "qcom,lpg-chan-id". This property is required if LUT mode is supported with a SDAM module. - 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. duty specified in the LUT pattern. Allowed range: 1 - 511 when LUT module is used, and 8 - 2000 when SDAM is used. - 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. ramping to. Allowed range: 1 - 47 when LUT module is used, and 1 - 64 when SDAM module is used. - 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. ramping from. The ramp-low-index should be always less than ramp-high-index when SDAM module is used. Allowed range: 0 - 46 when LUT module is used, and 0 - 63 when SDAM module is used. - qcom,ramp-pattern-repeat: Usage: optional Loading @@ -100,28 +140,40 @@ parameters needs to be configured for that channel. Definition: The flag to specify if LPG would be ramping with the LUT pattern repeatedly. - 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. This property is not required when SDAM module is used. - 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. index when low index is reached. This property is not required when SDAM module is used. - 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. to the high index of the LUT. This property is not required when SDAM module is used. - 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: to the low index of the LUT. This property is not required when SDAM module is used. Example when LUT pattern is stored in a LUT module: pmi8998_lpg: lpg@b100 { pm8150l_lpg: lpg@b100 { compatible = "qcom,pwm-lpg"; reg = <0xb100 0x600>, <0xb000 0x100>; reg-names = "lpg-base", "lut-base"; Loading @@ -129,9 +181,8 @@ Example: #pwm-cells = <2>; qcom,lut-patterns = <0 14 28 42 56 70 84 100 100 84 70 56 42 28 14 0>; qcom,sync-channel-ids = <3 4 5>; lpg@3 { qcom,lpg-chan-id = <3>; lpg@1 { qcom,lpg-chan-id = <1>; qcom,ramp-step-ms = <200>; qcom,ramp-pause-hi-count = <10>; qcom,ramp-pause-lo-count = <10>; Loading @@ -140,8 +191,8 @@ Example: qcom,ramp-from-low-to-high; qcom,ramp-pattern-repeat; }; lpg@4 { qcom,lpg-chan-id = <4>; lpg@2 { qcom,lpg-chan-id = <2>; qcom,ramp-step-ms = <200>; qcom,ramp-pause-hi-count = <10>; qcom,ramp-pause-lo-count = <10>; Loading @@ -150,8 +201,8 @@ Example: qcom,ramp-from-low-to-high; qcom,ramp-pattern-repeat; }; lpg@5 { qcom,lpg-chan-id = <5>; lpg@3 { qcom,lpg-chan-id = <3>; qcom,ramp-step-ms = <200>; qcom,ramp-pause-hi-count = <10>; qcom,ramp-pause-lo-count = <10>; Loading @@ -161,3 +212,42 @@ Example: qcom,ramp-pattern-repeat; }; }; Example when LUT pattern is stored in a SDAM module: pmi632_lpg: lpg@b100 { compatible = "qcom,pwm-lpg"; reg = <0xb100 0x600>; reg-names = "lpg-base"; #pwm-cells = <2>; nvmem-names = "ppg_sdam"; nvmem = <&sdam7>; qcom,pbs-client = <&pbs_client_3>; qcom,lut-sdam-base = <0x80>; qcom,lut-patterns = <0 14 28 42 56 70 84 100 100 84 70 56 42 28 14 0>; lpg@1 { qcom,lpg-chan-id = <1>; qcom,ramp-step-ms = <200>; qcom,ramp-low-index = <0>; qcom,ramp-high-index = <15>; qcom,ramp-pattern-repeat; qcom,lpg-sdam-base = <0x48>: }; lpg@2 { qcom,lpg-chan-id = <2>; qcom,ramp-step-ms = <200>; qcom,ramp-low-index = <0>; qcom,ramp-high-index = <15>; qcom,ramp-pattern-repeat; qcom,lpg-sdam-base = <0x56>; }; lpg@3 { qcom,lpg-chan-id = <3>; qcom,ramp-step-ms = <200>; qcom,ramp-low-index = <0>; qcom,ramp-high-index = <15>; qcom,ramp-pattern-repeat; qcom,lpg-sdam-base = <0x64>; }; }; drivers/pwm/pwm-qti-lpg.c +418 −26 Original line number Diff line number Diff line Loading @@ -19,10 +19,12 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/nvmem-consumer.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/qpnp/qpnp-pbs.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/types.h> Loading Loading @@ -105,6 +107,34 @@ #define LPG_LUT_VALUE_MSB_MASK BIT(0) #define LPG_LUT_COUNT_MAX 47 /* LPG config settings in SDAM */ #define SDAM_REG_PBS_SEQ_EN 0x42 #define PBS_SW_TRG_BIT BIT(0) #define SDAM_REG_RAMP_STEP_DURATION 0x47 #define SDAM_LUT_EN_OFFSET 0x0 #define SDAM_PATTERN_CONFIG_OFFSET 0x1 #define SDAM_END_INDEX_OFFSET 0x3 #define SDAM_START_INDEX_OFFSET 0x4 #define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6 /* SDAM_REG_LUT_EN */ #define SDAM_LUT_EN_BIT BIT(0) /* SDAM_REG_PATTERN_CONFIG */ #define SDAM_PATTERN_LOOP_ENABLE BIT(3) #define SDAM_PATTERN_RAMP_TOGGLE BIT(2) #define SDAM_PATTERN_EN_PAUSE_END BIT(1) #define SDAM_PATTERN_EN_PAUSE_START BIT(0) /* SDAM_REG_PAUSE_MULTIPLIER */ #define SDAM_PAUSE_START_SHIFT 4 #define SDAM_PAUSE_START_MASK GENMASK(7, 4) #define SDAM_PAUSE_END_MASK GENMASK(3, 0) #define SDAM_LUT_COUNT_MAX 64 enum lpg_src { LUT_PATTERN = 0, PWM_VALUE, Loading Loading @@ -151,6 +181,7 @@ struct qpnp_lpg_channel { u32 lpg_idx; u32 reg_base; u32 max_pattern_length; u32 lpg_sdam_base; u8 src_sel; u8 subtype; bool lut_written; Loading @@ -166,7 +197,11 @@ struct qpnp_lpg_chip { struct qpnp_lpg_lut *lut; struct mutex bus_lock; u32 *lpg_group; struct nvmem_device *sdam_nvmem; struct device_node *pbs_dev_node; u32 num_lpgs; unsigned long pbs_en_bitmap; bool use_sdam; }; static int qpnp_lpg_read(struct qpnp_lpg_channel *lpg, u16 addr, u8 *val) Loading @@ -193,7 +228,7 @@ static int qpnp_lpg_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 val) mutex_lock(&lpg->chip->bus_lock); rc = regmap_write(lpg->chip->regmap, lpg->reg_base + addr, val); if (rc < 0) dev_err(lpg->chip->dev, "Write addr 0x%x with value %d failed, rc=%d\n", dev_err(lpg->chip->dev, "Write addr 0x%x with value 0x%x failed, rc=%d\n", lpg->reg_base + addr, val, rc); mutex_unlock(&lpg->chip->bus_lock); Loading Loading @@ -246,6 +281,90 @@ 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) { int rc; mutex_lock(&chip->bus_lock); rc = nvmem_device_write(chip->sdam_nvmem, addr, 1, &val); if (rc < 0) dev_err(chip->dev, "write SDAM add 0x%x failed, rc=%d\n", addr, rc); mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static int qpnp_lpg_sdam_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 val) { struct qpnp_lpg_chip *chip = lpg->chip; int rc; mutex_lock(&chip->bus_lock); rc = nvmem_device_write(chip->sdam_nvmem, lpg->lpg_sdam_base + addr, 1, &val); if (rc < 0) dev_err(chip->dev, "write SDAM add 0x%x failed, rc=%d\n", lpg->lpg_sdam_base + addr, rc); mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static int qpnp_lpg_sdam_masked_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 mask, u8 val) { int rc; u8 tmp; struct qpnp_lpg_chip *chip = lpg->chip; mutex_lock(&chip->bus_lock); rc = nvmem_device_read(chip->sdam_nvmem, lpg->lpg_sdam_base + addr, 1, &tmp); if (rc < 0) { dev_err(chip->dev, "Read SDAM addr %d failed, rc=%d\n", lpg->lpg_sdam_base + addr, rc); goto unlock; } tmp = tmp & ~mask; tmp |= val & mask; rc = nvmem_device_write(chip->sdam_nvmem, lpg->lpg_sdam_base + addr, 1, &tmp); if (rc < 0) dev_err(chip->dev, "write SDAM addr %d failed, rc=%d\n", lpg->lpg_sdam_base + addr, rc); unlock: mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static int qpnp_lut_sdam_write(struct qpnp_lpg_lut *lut, u16 addr, u8 *val, size_t length) { struct qpnp_lpg_chip *chip = lut->chip; int rc; if (addr >= SDAM_LUT_COUNT_MAX) return -EINVAL; mutex_lock(&chip->bus_lock); rc = nvmem_device_write(chip->sdam_nvmem, lut->reg_base + addr, length, val); if (rc < 0) dev_err(chip->dev, "write SDAM addr %d failed, rc=%d\n", lut->reg_base + addr, rc); mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static struct qpnp_lpg_channel *pwm_dev_to_qpnp_lpg(struct pwm_chip *pwm_chip, struct pwm_device *pwm) { Loading Loading @@ -367,14 +486,111 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg) return rc; } static int qpnp_lpg_set_sdam_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; u8 val[SDAM_LUT_COUNT_MAX + 1], 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 = lpg->ramp_config.lo_idx; for (i = 0; i < length; i++) val[i] = pattern[i] * 255 / 100; rc = qpnp_lut_sdam_write(lut, addr, val, length); if (rc < 0) { dev_err(lpg->chip->dev, "Write pattern in SDAM failed, rc=%d", rc); goto unlock; } lpg->ramp_config.pattern_length = length; unlock: mutex_unlock(&lut->lock); return rc; } static int qpnp_lpg_set_sdam_ramp_config(struct qpnp_lpg_channel *lpg) { struct lpg_ramp_config *ramp = &lpg->ramp_config; u8 addr, mask, val; int rc = 0; /* clear PBS scatchpad register */ val = 0; rc = qpnp_lpg_sdam_write(lpg, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET failed, rc=%d\n", rc); return rc; } /* Set ramp step duration, one WAIT_TICK is 7.8ms */ val = (ramp->step_ms * 1000 / 7800) & 0xff; if (val > 0) val--; addr = SDAM_REG_RAMP_STEP_DURATION; rc = qpnp_sdam_write(lpg->chip, addr, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_RAMP_STEP_DURATION failed, rc=%d\n", rc); return rc; } /* Set hi_idx and lo_idx */ rc = qpnp_lpg_sdam_write(lpg, SDAM_END_INDEX_OFFSET, ramp->hi_idx); 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); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_START_INDEX failed, rc=%d\n", rc); return rc; } /* Set LPG_PATTERN_CONFIG */ addr = SDAM_PATTERN_CONFIG_OFFSET; mask = SDAM_PATTERN_LOOP_ENABLE; val = 0; if (ramp->pattern_repeat) val |= SDAM_PATTERN_LOOP_ENABLE; rc = qpnp_lpg_sdam_masked_write(lpg, addr, mask, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_PATTERN_CONFIG failed, rc=%d\n", rc); 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; u16 full_duty_value, pwm_values[SDAM_LUT_COUNT_MAX + 1] = {0}; int i, rc = 0; u16 full_duty_value, pwm_values[LPG_LUT_COUNT_MAX + 1] = {0}; u8 lsb, msb, addr; if (lpg->chip->use_sdam) return qpnp_lpg_set_sdam_lut_pattern(lpg, pattern, length); 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); Loading Loading @@ -428,6 +644,9 @@ static int qpnp_lpg_set_ramp_config(struct qpnp_lpg_channel *lpg) u8 lsb, msb, addr, mask, val; int rc = 0; if (lpg->chip->use_sdam) return qpnp_lpg_set_sdam_ramp_config(lpg); /* Set ramp step duration */ lsb = ramp->step_ms & 0xff; msb = ramp->step_ms >> 8; Loading Loading @@ -509,6 +728,8 @@ static int qpnp_lpg_set_ramp_config(struct qpnp_lpg_channel *lpg) static void __qpnp_lpg_calc_pwm_period(u64 period_ns, struct lpg_pwm_config *pwm_config) { struct qpnp_lpg_channel *lpg = container_of(pwm_config, struct qpnp_lpg_channel, pwm_config); struct lpg_pwm_config configs[NUM_PWM_SIZE]; int i, j, m, n; u64 tmp1, tmp2; Loading @@ -524,7 +745,12 @@ static void __qpnp_lpg_calc_pwm_period(u64 period_ns, * * Searching the closest settings for the requested PWM period. */ for (n = 0; n < ARRAY_SIZE(pwm_size); n++) { if (lpg->chip->use_sdam) /* SDAM pattern control can only use 9 bit resolution */ n = 1; 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++) { Loading Loading @@ -672,6 +898,45 @@ static int qpnp_lpg_pwm_config_extend(struct pwm_chip *pwm_chip, } return qpnp_lpg_config(lpg, duty_ns, period_ns); }; static int qpnp_lpg_pbs_trigger_enable(struct qpnp_lpg_channel *lpg, bool en) { struct qpnp_lpg_chip *chip = lpg->chip; int rc = 0; if (en) { if (chip->pbs_en_bitmap == 0) { rc = qpnp_sdam_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; } 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_sdam_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; } } } return rc; } static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en) Loading @@ -686,7 +951,7 @@ static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en) LPG_EN_RAMP_GEN_MASK; val = lpg->src_sel << LPG_PWM_SRC_SELECT_SHIFT; if (lpg->src_sel == LUT_PATTERN) if (lpg->src_sel == LUT_PATTERN && !chip->use_sdam) val |= 1 << LPG_EN_RAMP_GEN_SHIFT; if (en) Loading @@ -699,6 +964,27 @@ static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en) return rc; } if (chip->use_sdam) { if (lpg->src_sel == LUT_PATTERN && en) { val = SDAM_LUT_EN_BIT; en = true; } else { val = 0; en = false; } rc = qpnp_lpg_sdam_write(lpg, SDAM_LUT_EN_OFFSET, val); if (rc < 0) { dev_err(chip->dev, "Write SDAM_REG_LUT_EN failed, rc=%d\n", rc); return rc; } qpnp_lpg_pbs_trigger_enable(lpg, en); return rc; } if (lpg->src_sel == LUT_PATTERN && en) { val = 1 << lpg->lpg_idx; for (i = 0; i < chip->num_lpgs; i++) { Loading Loading @@ -741,6 +1027,7 @@ static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip, struct qpnp_lpg_channel *lpg; enum lpg_src src_sel; int rc; bool is_enabled; lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm); if (lpg == NULL) { Loading @@ -758,6 +1045,23 @@ static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip, if (src_sel == lpg->src_sel) return 0; is_enabled = pwm_is_enabled(pwm); if (is_enabled) { /* * Disable the channel first then enable it later to make * sure the output type is changed successfully. This is * especially useful in SDAM use case to stop the PBS * sequence when changing the PWM output type from * MODULATED to FIXED. */ rc = qpnp_lpg_pwm_src_enable(lpg, false); if (rc < 0) { dev_err(pwm_chip->dev, "Enable PWM output failed for channel %d, rc=%d\n", lpg->lpg_idx, rc); return rc; } } if (src_sel == LUT_PATTERN) { /* program LUT if it's never been programmed */ if (!lpg->lut_written) { Loading @@ -782,7 +1086,14 @@ static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip, lpg->src_sel = src_sel; if (pwm_is_enabled(pwm)) { if (is_enabled) { rc = qpnp_lpg_set_pwm_config(lpg); if (rc < 0) { dev_err(pwm_chip->dev, "Config PWM failed for channel %d, rc=%d\n", lpg->lpg_idx, rc); return rc; } 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", Loading Loading @@ -1034,7 +1345,7 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) struct qpnp_lpg_channel *lpg; struct lpg_ramp_config *ramp; int rc = 0, i; u32 base, length, lpg_chan_id, tmp; u32 base, length, lpg_chan_id, tmp, max_count; const __be32 *addr; addr = of_get_address(chip->dev->of_node, 0, NULL, NULL); Loading Loading @@ -1075,18 +1386,47 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) } } chip->lut = devm_kmalloc(chip->dev, sizeof(*chip->lut), GFP_KERNEL); if (!chip->lut) return -ENOMEM; chip->sdam_nvmem = devm_nvmem_device_get(chip->dev, "ppg_sdam"); if (IS_ERR_OR_NULL(chip->sdam_nvmem)) { if (PTR_ERR(chip->sdam_nvmem) == -EPROBE_DEFER) return -EPROBE_DEFER; addr = of_get_address(chip->dev->of_node, 1, NULL, NULL); if (!addr) { pr_debug("NO LUT address assigned\n"); devm_kfree(chip->dev, chip->lut); chip->lut = NULL; return 0; } chip->lut = devm_kmalloc(chip->dev, sizeof(*chip->lut), GFP_KERNEL); if (!chip->lut) return -ENOMEM; chip->lut->reg_base = be32_to_cpu(*addr); max_count = LPG_LUT_COUNT_MAX; } else { 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; } rc = of_property_read_u32(chip->dev->of_node, "qcom,lut-sdam-base", &chip->lut->reg_base); if (rc < 0) { dev_err(chip->dev, "Read qcom,lut-sdam-base failed, rc=%d\n", rc); return rc; } max_count = SDAM_LUT_COUNT_MAX; } 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, Loading @@ -1098,13 +1438,13 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) } length = rc; if (length > LPG_LUT_COUNT_MAX) { if (length > max_count) { dev_err(chip->dev, "qcom,lut-patterns length %d exceed max %d\n", length, LPG_LUT_COUNT_MAX); length, max_count); return -EINVAL; } chip->lut->pattern = devm_kcalloc(chip->dev, LPG_LUT_COUNT_MAX, chip->lut->pattern = devm_kcalloc(chip->dev, max_count, sizeof(*chip->lut->pattern), GFP_KERNEL); if (!chip->lut->pattern) return -ENOMEM; Loading @@ -1131,12 +1471,24 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return rc; } if (lpg_chan_id > chip->num_lpgs) { if (lpg_chan_id < 1 || 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; } if (chip->use_sdam) { rc = of_property_read_u32(child, "qcom,lpg-sdam-base", &tmp); if (rc < 0) { dev_err(chip->dev, "get qcom,lpg-sdam-base failed for lpg%d, rc=%d\n", lpg_chan_id, rc); return rc; } chip->lpgs[lpg_chan_id - 1].lpg_sdam_base = tmp; } /* lpg channel id is indexed from 1 in hardware */ lpg = &chip->lpgs[lpg_chan_id - 1]; ramp = &lpg->ramp_config; Loading @@ -1156,9 +1508,9 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return rc; } ramp->lo_idx = (u8)tmp; if (ramp->lo_idx >= LPG_LUT_COUNT_MAX) { if (ramp->lo_idx >= max_count) { dev_err(chip->dev, "qcom,ramp-low-index should less than max %d\n", LPG_LUT_COUNT_MAX); max_count); return -EINVAL; } Loading @@ -1170,14 +1522,14 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) } ramp->hi_idx = (u8)tmp; if (ramp->hi_idx > LPG_LUT_COUNT_MAX) { if (ramp->hi_idx > max_count) { dev_err(chip->dev, "qcom,ramp-high-index shouldn't exceed max %d\n", LPG_LUT_COUNT_MAX); max_count); return -EINVAL; } if (ramp->hi_idx <= ramp->lo_idx) { dev_err(chip->dev, "high-index(%d) should be larger than low-index(%d)\n", if (chip->use_sdam && ramp->hi_idx <= ramp->lo_idx) { dev_err(chip->dev, "high-index(%d) should be larger than low-index(%d) when SDAM used\n", ramp->hi_idx, ramp->lo_idx); return -EINVAL; } Loading @@ -1186,6 +1538,12 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) ramp->pattern = &chip->lut->pattern[ramp->lo_idx]; lpg->max_pattern_length = ramp->pattern_length; ramp->pattern_repeat = of_property_read_bool(child, "qcom,ramp-pattern-repeat"); if (chip->use_sdam) continue; rc = of_property_read_u32(child, "qcom,ramp-pause-hi-count", &tmp); if (rc < 0) Loading @@ -1203,9 +1561,6 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) 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"); } Loading Loading @@ -1260,6 +1615,36 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return 0; } static int qpnp_lpg_sdam_hw_init(struct qpnp_lpg_chip *chip) { struct qpnp_lpg_channel *lpg; int i, rc = 0; if (!chip->use_sdam) return 0; for (i = 0; i < chip->num_lpgs; i++) { lpg = &chip->lpgs[i]; if (lpg->lpg_sdam_base != 0) { rc = qpnp_lpg_sdam_write(lpg, SDAM_LUT_EN_OFFSET, 0); if (rc < 0) { dev_err(chip->dev, "Write SDAM_REG_LUT_EN failed, rc=%d\n", rc); return rc; } rc = qpnp_lpg_sdam_write(lpg, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET, 0); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_PBS_SCRATCH_LUT_COUNTER failed, rc=%d\n", rc); return rc; } } } return rc; } static int qpnp_lpg_probe(struct platform_device *pdev) { int rc; Loading @@ -1284,6 +1669,13 @@ static int qpnp_lpg_probe(struct platform_device *pdev) goto err_out; } rc = qpnp_lpg_sdam_hw_init(chip); if (rc < 0) { dev_err(chip->dev, "SDAM HW init failed, rc=%d\n", rc); goto err_out; } dev_set_drvdata(chip->dev, chip); chip->pwm_chip.dev = chip->dev; chip->pwm_chip.base = -1; Loading Loading
Documentation/devicetree/bindings/pwm/pwm-qti-lpg.txt +117 −27 Original line number Diff line number Diff line Loading @@ -18,7 +18,8 @@ device module in Qualcomm Technologies, Inc. PMIC chips. Value type: <string> Definition: The name of the register defined in the reg property. It must have "lpg-base", "lut-base" is optional but it's required if any LPG channels support LUT mode. it's required if any LPG channels support LUT mode with a LUT module. - #pwm-cells: Usage: required Loading @@ -33,14 +34,47 @@ device module in Qualcomm Technologies, Inc. PMIC chips. Value type: <u32> Definition: The number of the consecutive LPG/PWM channels in the chip. - nvmem-names: Usage: optional Value type: <string> Definition: The nvmem device name for the SDAM module where the LUT pattern is stored. It must be "ppg_sdam". This property is required only when LUT mode is supported with a SDAM module instead of a LUT module. - nvmem: Usage: optional Value type: <phandle> Definition: Phandle of the nvmem device to access the LUT stored in the SDAM module. This property is required only when LUT mode is supported and the LUT pattern is stored in a SDAM module instead of a LUT module. - qcom,pbs-client Usage: optional Value type: <phandle> Definition: Phandle of the PBS client used for sending the PBS trigger. This property is required when LUT mode is supported and the LUT pattern is stored in a SDAM module instead of a LUT module. - qcom,lut-sdam-base: Usage: optional Value type: <u32> Definition: The register base of the LUT entries stored in SDAM. This property is required only when LUT mode is supported and the LUT pattern is stored in a SDAM module instead of a LUT module. - 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 and stored in LUT or SDAM module shared for all LPG channels. The LUT module has resource to store 47 PWM values at max while SDAM module can store upto 64 PWM values. This property is required if any LPG channels support LUT mode. - qcom,sync-channel-ids: Loading Loading @@ -68,31 +102,37 @@ parameters needs to be configured for that channel. range is 1 - 8. Maximum value depends on the number of channels supported on PMIC. - qcom,lpg-sdam-base: Usage: optional Value type: <u32> Definition: Register base address for LPG configuration in SDAM for the LPG channel specified under "qcom,lpg-chan-id". This property is required if LUT mode is supported with a SDAM module. - 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. duty specified in the LUT pattern. Allowed range: 1 - 511 when LUT module is used, and 8 - 2000 when SDAM is used. - 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. ramping to. Allowed range: 1 - 47 when LUT module is used, and 1 - 64 when SDAM module is used. - 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. ramping from. The ramp-low-index should be always less than ramp-high-index when SDAM module is used. Allowed range: 0 - 46 when LUT module is used, and 0 - 63 when SDAM module is used. - qcom,ramp-pattern-repeat: Usage: optional Loading @@ -100,28 +140,40 @@ parameters needs to be configured for that channel. Definition: The flag to specify if LPG would be ramping with the LUT pattern repeatedly. - 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. This property is not required when SDAM module is used. - 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. index when low index is reached. This property is not required when SDAM module is used. - 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. to the high index of the LUT. This property is not required when SDAM module is used. - 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: to the low index of the LUT. This property is not required when SDAM module is used. Example when LUT pattern is stored in a LUT module: pmi8998_lpg: lpg@b100 { pm8150l_lpg: lpg@b100 { compatible = "qcom,pwm-lpg"; reg = <0xb100 0x600>, <0xb000 0x100>; reg-names = "lpg-base", "lut-base"; Loading @@ -129,9 +181,8 @@ Example: #pwm-cells = <2>; qcom,lut-patterns = <0 14 28 42 56 70 84 100 100 84 70 56 42 28 14 0>; qcom,sync-channel-ids = <3 4 5>; lpg@3 { qcom,lpg-chan-id = <3>; lpg@1 { qcom,lpg-chan-id = <1>; qcom,ramp-step-ms = <200>; qcom,ramp-pause-hi-count = <10>; qcom,ramp-pause-lo-count = <10>; Loading @@ -140,8 +191,8 @@ Example: qcom,ramp-from-low-to-high; qcom,ramp-pattern-repeat; }; lpg@4 { qcom,lpg-chan-id = <4>; lpg@2 { qcom,lpg-chan-id = <2>; qcom,ramp-step-ms = <200>; qcom,ramp-pause-hi-count = <10>; qcom,ramp-pause-lo-count = <10>; Loading @@ -150,8 +201,8 @@ Example: qcom,ramp-from-low-to-high; qcom,ramp-pattern-repeat; }; lpg@5 { qcom,lpg-chan-id = <5>; lpg@3 { qcom,lpg-chan-id = <3>; qcom,ramp-step-ms = <200>; qcom,ramp-pause-hi-count = <10>; qcom,ramp-pause-lo-count = <10>; Loading @@ -161,3 +212,42 @@ Example: qcom,ramp-pattern-repeat; }; }; Example when LUT pattern is stored in a SDAM module: pmi632_lpg: lpg@b100 { compatible = "qcom,pwm-lpg"; reg = <0xb100 0x600>; reg-names = "lpg-base"; #pwm-cells = <2>; nvmem-names = "ppg_sdam"; nvmem = <&sdam7>; qcom,pbs-client = <&pbs_client_3>; qcom,lut-sdam-base = <0x80>; qcom,lut-patterns = <0 14 28 42 56 70 84 100 100 84 70 56 42 28 14 0>; lpg@1 { qcom,lpg-chan-id = <1>; qcom,ramp-step-ms = <200>; qcom,ramp-low-index = <0>; qcom,ramp-high-index = <15>; qcom,ramp-pattern-repeat; qcom,lpg-sdam-base = <0x48>: }; lpg@2 { qcom,lpg-chan-id = <2>; qcom,ramp-step-ms = <200>; qcom,ramp-low-index = <0>; qcom,ramp-high-index = <15>; qcom,ramp-pattern-repeat; qcom,lpg-sdam-base = <0x56>; }; lpg@3 { qcom,lpg-chan-id = <3>; qcom,ramp-step-ms = <200>; qcom,ramp-low-index = <0>; qcom,ramp-high-index = <15>; qcom,ramp-pattern-repeat; qcom,lpg-sdam-base = <0x64>; }; };
drivers/pwm/pwm-qti-lpg.c +418 −26 Original line number Diff line number Diff line Loading @@ -19,10 +19,12 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/nvmem-consumer.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/qpnp/qpnp-pbs.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/types.h> Loading Loading @@ -105,6 +107,34 @@ #define LPG_LUT_VALUE_MSB_MASK BIT(0) #define LPG_LUT_COUNT_MAX 47 /* LPG config settings in SDAM */ #define SDAM_REG_PBS_SEQ_EN 0x42 #define PBS_SW_TRG_BIT BIT(0) #define SDAM_REG_RAMP_STEP_DURATION 0x47 #define SDAM_LUT_EN_OFFSET 0x0 #define SDAM_PATTERN_CONFIG_OFFSET 0x1 #define SDAM_END_INDEX_OFFSET 0x3 #define SDAM_START_INDEX_OFFSET 0x4 #define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6 /* SDAM_REG_LUT_EN */ #define SDAM_LUT_EN_BIT BIT(0) /* SDAM_REG_PATTERN_CONFIG */ #define SDAM_PATTERN_LOOP_ENABLE BIT(3) #define SDAM_PATTERN_RAMP_TOGGLE BIT(2) #define SDAM_PATTERN_EN_PAUSE_END BIT(1) #define SDAM_PATTERN_EN_PAUSE_START BIT(0) /* SDAM_REG_PAUSE_MULTIPLIER */ #define SDAM_PAUSE_START_SHIFT 4 #define SDAM_PAUSE_START_MASK GENMASK(7, 4) #define SDAM_PAUSE_END_MASK GENMASK(3, 0) #define SDAM_LUT_COUNT_MAX 64 enum lpg_src { LUT_PATTERN = 0, PWM_VALUE, Loading Loading @@ -151,6 +181,7 @@ struct qpnp_lpg_channel { u32 lpg_idx; u32 reg_base; u32 max_pattern_length; u32 lpg_sdam_base; u8 src_sel; u8 subtype; bool lut_written; Loading @@ -166,7 +197,11 @@ struct qpnp_lpg_chip { struct qpnp_lpg_lut *lut; struct mutex bus_lock; u32 *lpg_group; struct nvmem_device *sdam_nvmem; struct device_node *pbs_dev_node; u32 num_lpgs; unsigned long pbs_en_bitmap; bool use_sdam; }; static int qpnp_lpg_read(struct qpnp_lpg_channel *lpg, u16 addr, u8 *val) Loading @@ -193,7 +228,7 @@ static int qpnp_lpg_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 val) mutex_lock(&lpg->chip->bus_lock); rc = regmap_write(lpg->chip->regmap, lpg->reg_base + addr, val); if (rc < 0) dev_err(lpg->chip->dev, "Write addr 0x%x with value %d failed, rc=%d\n", dev_err(lpg->chip->dev, "Write addr 0x%x with value 0x%x failed, rc=%d\n", lpg->reg_base + addr, val, rc); mutex_unlock(&lpg->chip->bus_lock); Loading Loading @@ -246,6 +281,90 @@ 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) { int rc; mutex_lock(&chip->bus_lock); rc = nvmem_device_write(chip->sdam_nvmem, addr, 1, &val); if (rc < 0) dev_err(chip->dev, "write SDAM add 0x%x failed, rc=%d\n", addr, rc); mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static int qpnp_lpg_sdam_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 val) { struct qpnp_lpg_chip *chip = lpg->chip; int rc; mutex_lock(&chip->bus_lock); rc = nvmem_device_write(chip->sdam_nvmem, lpg->lpg_sdam_base + addr, 1, &val); if (rc < 0) dev_err(chip->dev, "write SDAM add 0x%x failed, rc=%d\n", lpg->lpg_sdam_base + addr, rc); mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static int qpnp_lpg_sdam_masked_write(struct qpnp_lpg_channel *lpg, u16 addr, u8 mask, u8 val) { int rc; u8 tmp; struct qpnp_lpg_chip *chip = lpg->chip; mutex_lock(&chip->bus_lock); rc = nvmem_device_read(chip->sdam_nvmem, lpg->lpg_sdam_base + addr, 1, &tmp); if (rc < 0) { dev_err(chip->dev, "Read SDAM addr %d failed, rc=%d\n", lpg->lpg_sdam_base + addr, rc); goto unlock; } tmp = tmp & ~mask; tmp |= val & mask; rc = nvmem_device_write(chip->sdam_nvmem, lpg->lpg_sdam_base + addr, 1, &tmp); if (rc < 0) dev_err(chip->dev, "write SDAM addr %d failed, rc=%d\n", lpg->lpg_sdam_base + addr, rc); unlock: mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static int qpnp_lut_sdam_write(struct qpnp_lpg_lut *lut, u16 addr, u8 *val, size_t length) { struct qpnp_lpg_chip *chip = lut->chip; int rc; if (addr >= SDAM_LUT_COUNT_MAX) return -EINVAL; mutex_lock(&chip->bus_lock); rc = nvmem_device_write(chip->sdam_nvmem, lut->reg_base + addr, length, val); if (rc < 0) dev_err(chip->dev, "write SDAM addr %d failed, rc=%d\n", lut->reg_base + addr, rc); mutex_unlock(&chip->bus_lock); return rc > 0 ? 0 : rc; } static struct qpnp_lpg_channel *pwm_dev_to_qpnp_lpg(struct pwm_chip *pwm_chip, struct pwm_device *pwm) { Loading Loading @@ -367,14 +486,111 @@ static int qpnp_lpg_set_pwm_config(struct qpnp_lpg_channel *lpg) return rc; } static int qpnp_lpg_set_sdam_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; u8 val[SDAM_LUT_COUNT_MAX + 1], 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 = lpg->ramp_config.lo_idx; for (i = 0; i < length; i++) val[i] = pattern[i] * 255 / 100; rc = qpnp_lut_sdam_write(lut, addr, val, length); if (rc < 0) { dev_err(lpg->chip->dev, "Write pattern in SDAM failed, rc=%d", rc); goto unlock; } lpg->ramp_config.pattern_length = length; unlock: mutex_unlock(&lut->lock); return rc; } static int qpnp_lpg_set_sdam_ramp_config(struct qpnp_lpg_channel *lpg) { struct lpg_ramp_config *ramp = &lpg->ramp_config; u8 addr, mask, val; int rc = 0; /* clear PBS scatchpad register */ val = 0; rc = qpnp_lpg_sdam_write(lpg, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET failed, rc=%d\n", rc); return rc; } /* Set ramp step duration, one WAIT_TICK is 7.8ms */ val = (ramp->step_ms * 1000 / 7800) & 0xff; if (val > 0) val--; addr = SDAM_REG_RAMP_STEP_DURATION; rc = qpnp_sdam_write(lpg->chip, addr, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_RAMP_STEP_DURATION failed, rc=%d\n", rc); return rc; } /* Set hi_idx and lo_idx */ rc = qpnp_lpg_sdam_write(lpg, SDAM_END_INDEX_OFFSET, ramp->hi_idx); 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); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_START_INDEX failed, rc=%d\n", rc); return rc; } /* Set LPG_PATTERN_CONFIG */ addr = SDAM_PATTERN_CONFIG_OFFSET; mask = SDAM_PATTERN_LOOP_ENABLE; val = 0; if (ramp->pattern_repeat) val |= SDAM_PATTERN_LOOP_ENABLE; rc = qpnp_lpg_sdam_masked_write(lpg, addr, mask, val); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_PATTERN_CONFIG failed, rc=%d\n", rc); 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; u16 full_duty_value, pwm_values[SDAM_LUT_COUNT_MAX + 1] = {0}; int i, rc = 0; u16 full_duty_value, pwm_values[LPG_LUT_COUNT_MAX + 1] = {0}; u8 lsb, msb, addr; if (lpg->chip->use_sdam) return qpnp_lpg_set_sdam_lut_pattern(lpg, pattern, length); 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); Loading Loading @@ -428,6 +644,9 @@ static int qpnp_lpg_set_ramp_config(struct qpnp_lpg_channel *lpg) u8 lsb, msb, addr, mask, val; int rc = 0; if (lpg->chip->use_sdam) return qpnp_lpg_set_sdam_ramp_config(lpg); /* Set ramp step duration */ lsb = ramp->step_ms & 0xff; msb = ramp->step_ms >> 8; Loading Loading @@ -509,6 +728,8 @@ static int qpnp_lpg_set_ramp_config(struct qpnp_lpg_channel *lpg) static void __qpnp_lpg_calc_pwm_period(u64 period_ns, struct lpg_pwm_config *pwm_config) { struct qpnp_lpg_channel *lpg = container_of(pwm_config, struct qpnp_lpg_channel, pwm_config); struct lpg_pwm_config configs[NUM_PWM_SIZE]; int i, j, m, n; u64 tmp1, tmp2; Loading @@ -524,7 +745,12 @@ static void __qpnp_lpg_calc_pwm_period(u64 period_ns, * * Searching the closest settings for the requested PWM period. */ for (n = 0; n < ARRAY_SIZE(pwm_size); n++) { if (lpg->chip->use_sdam) /* SDAM pattern control can only use 9 bit resolution */ n = 1; 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++) { Loading Loading @@ -672,6 +898,45 @@ static int qpnp_lpg_pwm_config_extend(struct pwm_chip *pwm_chip, } return qpnp_lpg_config(lpg, duty_ns, period_ns); }; static int qpnp_lpg_pbs_trigger_enable(struct qpnp_lpg_channel *lpg, bool en) { struct qpnp_lpg_chip *chip = lpg->chip; int rc = 0; if (en) { if (chip->pbs_en_bitmap == 0) { rc = qpnp_sdam_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; } 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_sdam_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; } } } return rc; } static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en) Loading @@ -686,7 +951,7 @@ static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en) LPG_EN_RAMP_GEN_MASK; val = lpg->src_sel << LPG_PWM_SRC_SELECT_SHIFT; if (lpg->src_sel == LUT_PATTERN) if (lpg->src_sel == LUT_PATTERN && !chip->use_sdam) val |= 1 << LPG_EN_RAMP_GEN_SHIFT; if (en) Loading @@ -699,6 +964,27 @@ static int qpnp_lpg_pwm_src_enable(struct qpnp_lpg_channel *lpg, bool en) return rc; } if (chip->use_sdam) { if (lpg->src_sel == LUT_PATTERN && en) { val = SDAM_LUT_EN_BIT; en = true; } else { val = 0; en = false; } rc = qpnp_lpg_sdam_write(lpg, SDAM_LUT_EN_OFFSET, val); if (rc < 0) { dev_err(chip->dev, "Write SDAM_REG_LUT_EN failed, rc=%d\n", rc); return rc; } qpnp_lpg_pbs_trigger_enable(lpg, en); return rc; } if (lpg->src_sel == LUT_PATTERN && en) { val = 1 << lpg->lpg_idx; for (i = 0; i < chip->num_lpgs; i++) { Loading Loading @@ -741,6 +1027,7 @@ static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip, struct qpnp_lpg_channel *lpg; enum lpg_src src_sel; int rc; bool is_enabled; lpg = pwm_dev_to_qpnp_lpg(pwm_chip, pwm); if (lpg == NULL) { Loading @@ -758,6 +1045,23 @@ static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip, if (src_sel == lpg->src_sel) return 0; is_enabled = pwm_is_enabled(pwm); if (is_enabled) { /* * Disable the channel first then enable it later to make * sure the output type is changed successfully. This is * especially useful in SDAM use case to stop the PBS * sequence when changing the PWM output type from * MODULATED to FIXED. */ rc = qpnp_lpg_pwm_src_enable(lpg, false); if (rc < 0) { dev_err(pwm_chip->dev, "Enable PWM output failed for channel %d, rc=%d\n", lpg->lpg_idx, rc); return rc; } } if (src_sel == LUT_PATTERN) { /* program LUT if it's never been programmed */ if (!lpg->lut_written) { Loading @@ -782,7 +1086,14 @@ static int qpnp_lpg_pwm_set_output_type(struct pwm_chip *pwm_chip, lpg->src_sel = src_sel; if (pwm_is_enabled(pwm)) { if (is_enabled) { rc = qpnp_lpg_set_pwm_config(lpg); if (rc < 0) { dev_err(pwm_chip->dev, "Config PWM failed for channel %d, rc=%d\n", lpg->lpg_idx, rc); return rc; } 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", Loading Loading @@ -1034,7 +1345,7 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) struct qpnp_lpg_channel *lpg; struct lpg_ramp_config *ramp; int rc = 0, i; u32 base, length, lpg_chan_id, tmp; u32 base, length, lpg_chan_id, tmp, max_count; const __be32 *addr; addr = of_get_address(chip->dev->of_node, 0, NULL, NULL); Loading Loading @@ -1075,18 +1386,47 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) } } chip->lut = devm_kmalloc(chip->dev, sizeof(*chip->lut), GFP_KERNEL); if (!chip->lut) return -ENOMEM; chip->sdam_nvmem = devm_nvmem_device_get(chip->dev, "ppg_sdam"); if (IS_ERR_OR_NULL(chip->sdam_nvmem)) { if (PTR_ERR(chip->sdam_nvmem) == -EPROBE_DEFER) return -EPROBE_DEFER; addr = of_get_address(chip->dev->of_node, 1, NULL, NULL); if (!addr) { pr_debug("NO LUT address assigned\n"); devm_kfree(chip->dev, chip->lut); chip->lut = NULL; return 0; } chip->lut = devm_kmalloc(chip->dev, sizeof(*chip->lut), GFP_KERNEL); if (!chip->lut) return -ENOMEM; chip->lut->reg_base = be32_to_cpu(*addr); max_count = LPG_LUT_COUNT_MAX; } else { 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; } rc = of_property_read_u32(chip->dev->of_node, "qcom,lut-sdam-base", &chip->lut->reg_base); if (rc < 0) { dev_err(chip->dev, "Read qcom,lut-sdam-base failed, rc=%d\n", rc); return rc; } max_count = SDAM_LUT_COUNT_MAX; } 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, Loading @@ -1098,13 +1438,13 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) } length = rc; if (length > LPG_LUT_COUNT_MAX) { if (length > max_count) { dev_err(chip->dev, "qcom,lut-patterns length %d exceed max %d\n", length, LPG_LUT_COUNT_MAX); length, max_count); return -EINVAL; } chip->lut->pattern = devm_kcalloc(chip->dev, LPG_LUT_COUNT_MAX, chip->lut->pattern = devm_kcalloc(chip->dev, max_count, sizeof(*chip->lut->pattern), GFP_KERNEL); if (!chip->lut->pattern) return -ENOMEM; Loading @@ -1131,12 +1471,24 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return rc; } if (lpg_chan_id > chip->num_lpgs) { if (lpg_chan_id < 1 || 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; } if (chip->use_sdam) { rc = of_property_read_u32(child, "qcom,lpg-sdam-base", &tmp); if (rc < 0) { dev_err(chip->dev, "get qcom,lpg-sdam-base failed for lpg%d, rc=%d\n", lpg_chan_id, rc); return rc; } chip->lpgs[lpg_chan_id - 1].lpg_sdam_base = tmp; } /* lpg channel id is indexed from 1 in hardware */ lpg = &chip->lpgs[lpg_chan_id - 1]; ramp = &lpg->ramp_config; Loading @@ -1156,9 +1508,9 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return rc; } ramp->lo_idx = (u8)tmp; if (ramp->lo_idx >= LPG_LUT_COUNT_MAX) { if (ramp->lo_idx >= max_count) { dev_err(chip->dev, "qcom,ramp-low-index should less than max %d\n", LPG_LUT_COUNT_MAX); max_count); return -EINVAL; } Loading @@ -1170,14 +1522,14 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) } ramp->hi_idx = (u8)tmp; if (ramp->hi_idx > LPG_LUT_COUNT_MAX) { if (ramp->hi_idx > max_count) { dev_err(chip->dev, "qcom,ramp-high-index shouldn't exceed max %d\n", LPG_LUT_COUNT_MAX); max_count); return -EINVAL; } if (ramp->hi_idx <= ramp->lo_idx) { dev_err(chip->dev, "high-index(%d) should be larger than low-index(%d)\n", if (chip->use_sdam && ramp->hi_idx <= ramp->lo_idx) { dev_err(chip->dev, "high-index(%d) should be larger than low-index(%d) when SDAM used\n", ramp->hi_idx, ramp->lo_idx); return -EINVAL; } Loading @@ -1186,6 +1538,12 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) ramp->pattern = &chip->lut->pattern[ramp->lo_idx]; lpg->max_pattern_length = ramp->pattern_length; ramp->pattern_repeat = of_property_read_bool(child, "qcom,ramp-pattern-repeat"); if (chip->use_sdam) continue; rc = of_property_read_u32(child, "qcom,ramp-pause-hi-count", &tmp); if (rc < 0) Loading @@ -1203,9 +1561,6 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) 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"); } Loading Loading @@ -1260,6 +1615,36 @@ static int qpnp_lpg_parse_dt(struct qpnp_lpg_chip *chip) return 0; } static int qpnp_lpg_sdam_hw_init(struct qpnp_lpg_chip *chip) { struct qpnp_lpg_channel *lpg; int i, rc = 0; if (!chip->use_sdam) return 0; for (i = 0; i < chip->num_lpgs; i++) { lpg = &chip->lpgs[i]; if (lpg->lpg_sdam_base != 0) { rc = qpnp_lpg_sdam_write(lpg, SDAM_LUT_EN_OFFSET, 0); if (rc < 0) { dev_err(chip->dev, "Write SDAM_REG_LUT_EN failed, rc=%d\n", rc); return rc; } rc = qpnp_lpg_sdam_write(lpg, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET, 0); if (rc < 0) { dev_err(lpg->chip->dev, "Write SDAM_REG_PBS_SCRATCH_LUT_COUNTER failed, rc=%d\n", rc); return rc; } } } return rc; } static int qpnp_lpg_probe(struct platform_device *pdev) { int rc; Loading @@ -1284,6 +1669,13 @@ static int qpnp_lpg_probe(struct platform_device *pdev) goto err_out; } rc = qpnp_lpg_sdam_hw_init(chip); if (rc < 0) { dev_err(chip->dev, "SDAM HW init failed, rc=%d\n", rc); goto err_out; } dev_set_drvdata(chip->dev, chip); chip->pwm_chip.dev = chip->dev; chip->pwm_chip.base = -1; Loading