From 3a3d1a4e32ab47323d7b8c8b7631a8d36a3098b2 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 8 Jun 2016 10:21:23 +0100 Subject: pwm: Add PWM capture support Supply a PWM capture callback op in order to pass back information obtained by running analysis on a PWM signal. This would normally (at least during testing) be called from the sysfs routines with a view to printing out PWM capture data which has been encoded into a string. Signed-off-by: Lee Jones [thierry.reding@gmail.com: make capture data unsigned int for symmetry] Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 27 +++++++++++++++++++++++++++ include/linux/pwm.h | 24 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index dba3843c53b8..8f40604046d6 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -524,6 +524,33 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state) } EXPORT_SYMBOL_GPL(pwm_apply_state); +/** + * pwm_capture() - capture and report a PWM signal + * @pwm: PWM device + * @result: structure to fill with capture result + * @timeout: time to wait, in milliseconds, before giving up on capture + * + * Returns: 0 on success or a negative error code on failure. + */ +int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, + unsigned long timeout) +{ + int err; + + if (!pwm || !pwm->chip->ops) + return -EINVAL; + + if (!pwm->chip->ops->capture) + return -ENOSYS; + + mutex_lock(&pwm_lock); + err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); + mutex_unlock(&pwm_lock); + + return err; +} +EXPORT_SYMBOL_GPL(pwm_capture); + /** * pwm_adjust_config() - adjust the current PWM config to the PWM arguments * @pwm: PWM device diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 17018f3c066e..8402b5dd3e06 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -5,7 +5,9 @@ #include #include +struct pwm_capture; struct seq_file; + struct pwm_chip; /** @@ -153,6 +155,7 @@ static inline void pwm_get_args(const struct pwm_device *pwm, * @free: optional hook for freeing a PWM * @config: configure duty cycles and period length for this PWM * @set_polarity: configure the polarity of this PWM + * @capture: capture and report PWM signal * @enable: enable PWM output toggling * @disable: disable PWM output toggling * @apply: atomically apply a new PWM config. The state argument @@ -172,6 +175,8 @@ struct pwm_ops { int duty_ns, int period_ns); int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity); + int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_capture *result, unsigned long timeout); int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm, @@ -212,6 +217,16 @@ struct pwm_chip { bool can_sleep; }; +/** + * struct pwm_capture - PWM capture data + * @period: period of the PWM signal (in nanoseconds) + * @duty_cycle: duty cycle of the PWM signal (in nanoseconds) + */ +struct pwm_capture { + unsigned int period; + unsigned int duty_cycle; +}; + #if IS_ENABLED(CONFIG_PWM) /* PWM user APIs */ struct pwm_device *pwm_request(int pwm_id, const char *label); @@ -322,6 +337,8 @@ static inline void pwm_disable(struct pwm_device *pwm) /* PWM provider APIs */ +int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, + unsigned long timeout); int pwm_set_chip_data(struct pwm_device *pwm, void *data); void *pwm_get_chip_data(struct pwm_device *pwm); @@ -373,6 +390,13 @@ static inline int pwm_config(struct pwm_device *pwm, int duty_ns, return -EINVAL; } +static inline int pwm_capture(struct pwm_device *pwm, + struct pwm_capture *result, + unsigned long timeout) +{ + return -EINVAL; +} + static inline int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) { -- cgit v1.2.3 From 1a366fe9153f445e950a7a344932b7419aa83094 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 8 Jun 2016 10:21:25 +0100 Subject: pwm: sysfs: Add PWM capture support Allow a user to read PWM capture results from sysfs. To start a capture and read the result, simply read the file: $ cat $PWMCHIP/capture The output format is " ". Signed-off-by: Lee Jones Signed-off-by: Thierry Reding --- Documentation/ABI/testing/sysfs-class-pwm | 9 +++++++++ drivers/pwm/sysfs.c | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-pwm b/Documentation/ABI/testing/sysfs-class-pwm index c479d77b67c5..c20e61354561 100644 --- a/Documentation/ABI/testing/sysfs-class-pwm +++ b/Documentation/ABI/testing/sysfs-class-pwm @@ -77,3 +77,12 @@ Description: Enable/disable the PWM signal. 0 is disabled 1 is enabled + +What: /sys/class/pwm/pwmchipN/pwmX/capture +Date: June 2016 +KernelVersion: 4.8 +Contact: Lee Jones +Description: + Capture information about a PWM signal. The output format is a + pair unsigned integers (period and duty cycle), separated by a + single space. diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c index d98599249a05..c3b1b563480e 100644 --- a/drivers/pwm/sysfs.c +++ b/drivers/pwm/sysfs.c @@ -208,16 +208,33 @@ static ssize_t polarity_store(struct device *child, return ret ? : size; } +static ssize_t capture_show(struct device *child, + struct device_attribute *attr, + char *buf) +{ + struct pwm_device *pwm = child_to_pwm_device(child); + struct pwm_capture result; + int ret; + + ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ)); + if (ret) + return ret; + + return sprintf(buf, "%u %u\n", result.period, result.duty_cycle); +} + static DEVICE_ATTR_RW(period); static DEVICE_ATTR_RW(duty_cycle); static DEVICE_ATTR_RW(enable); static DEVICE_ATTR_RW(polarity); +static DEVICE_ATTR_RO(capture); static struct attribute *pwm_attrs[] = { &dev_attr_period.attr, &dev_attr_duty_cycle.attr, &dev_attr_enable.attr, &dev_attr_polarity.attr, + &dev_attr_capture.attr, NULL }; ATTRIBUTE_GROUPS(pwm); -- cgit v1.2.3 From a6a0dbbcfa469cf3e5c4d9522106c0b7b9e9e373 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:09 +0200 Subject: pwm: Add a helper to prepare a new PWM state The pwm_init_state() helper prepares a new state object containing the current PWM state except for the polarity and period fields which are set to the reference values (those in struct pwm_args). This is particularly useful for PWM users who want to apply a new duty- cycle expressed relatively to the reference period without changing the enable state. Signed-off-by: Boris Brezillon Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- include/linux/pwm.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 17018f3c066e..a100f6e80738 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -147,6 +147,39 @@ static inline void pwm_get_args(const struct pwm_device *pwm, *args = pwm->args; } +/** + * pwm_init_state() - prepare a new state to be applied with pwm_apply_state() + * @pwm: PWM device + * @state: state to fill with the prepared PWM state + * + * This functions prepares a state that can later be tweaked and applied + * to the PWM device with pwm_apply_state(). This is a convenient function + * that first retrieves the current PWM state and the replaces the period + * and polarity fields with the reference values defined in pwm->args. + * Once the function returns, you can adjust the ->enabled and ->duty_cycle + * fields according to your needs before calling pwm_apply_state(). + * + * ->duty_cycle is initially set to zero to avoid cases where the current + * ->duty_cycle value exceed the pwm_args->period one, which would trigger + * an error if the user calls pwm_apply_state() without adjusting ->duty_cycle + * first. + */ +static inline void pwm_init_state(const struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_args args; + + /* First get the current state. */ + pwm_get_state(pwm, state); + + /* Then fill it with the reference config */ + pwm_get_args(pwm, &args); + + state->period = args.period; + state->polarity = args.polarity; + state->duty_cycle = 0; +} + /** * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM -- cgit v1.2.3 From f6f3bddf7b2b994a927808fcc5a3d07069c35956 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:10 +0200 Subject: pwm: Add relative duty cycle manipulation helpers The PWM framework expects PWM users to configure the duty cycle in nano- seconds, but many users want to express the duty cycle relatively to the period value (i.e. duty_cycle = 33% of the period). Add the pwm_{get,set}_relative_duty_cycle() helpers to ease this kind of conversion. Signed-off-by: Boris Brezillon Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- include/linux/pwm.h | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/include/linux/pwm.h b/include/linux/pwm.h index a100f6e80738..fd1092729ed6 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -180,6 +180,61 @@ static inline void pwm_init_state(const struct pwm_device *pwm, state->duty_cycle = 0; } +/** + * pwm_get_relative_duty_cycle() - Get a relative duty cycle value + * @state: PWM state to extract the duty cycle from + * @scale: target scale of the relative duty cycle + * + * This functions converts the absolute duty cycle stored in @state (expressed + * in nanosecond) into a value relative to the period. + * + * For example if you want to get the duty_cycle expressed in percent, call: + * + * pwm_get_state(pwm, &state); + * duty = pwm_get_relative_duty_cycle(&state, 100); + */ +static inline unsigned int +pwm_get_relative_duty_cycle(const struct pwm_state *state, unsigned int scale) +{ + if (!state->period) + return 0; + + return DIV_ROUND_CLOSEST_ULL((u64)state->duty_cycle * scale, + state->period); +} + +/** + * pwm_set_relative_duty_cycle() - Set a relative duty cycle value + * @state: PWM state to fill + * @duty_cycle: relative duty cycle value + * @scale: scale in which @duty_cycle is expressed + * + * This functions converts a relative into an absolute duty cycle (expressed + * in nanoseconds), and puts the result in state->duty_cycle. + * + * For example if you want to configure a 50% duty cycle, call: + * + * pwm_init_state(pwm, &state); + * pwm_set_relative_duty_cycle(&state, 50, 100); + * pwm_apply_state(pwm, &state); + * + * This functions returns -EINVAL if @duty_cycle and/or @scale are + * inconsistent (@scale == 0 or @duty_cycle > @scale). + */ +static inline int +pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, + unsigned int scale) +{ + if (!scale || duty_cycle > scale) + return -EINVAL; + + state->duty_cycle = DIV_ROUND_CLOSEST_ULL((u64)duty_cycle * + state->period, + scale); + + return 0; +} + /** * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM -- cgit v1.2.3 From e1aaf89a5415ab6514cb6798148c7fe04c0ee138 Mon Sep 17 00:00:00 2001 From: "Cooper Jr., Franklin" Date: Thu, 17 Mar 2016 20:15:23 -0500 Subject: pwm: pwm-tiehrpwm: Update DT binding document to use generic node name Now that the node name has been changed from ehrpwm to pwm the document should show this proper usage. Also change the unit address in the example from 0 to the proper physical address value that should be used. Signed-off-by: Franklin S Cooper Jr Acked-by: Rob Herring Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt b/Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt index 9c100b2c5b23..0f9ba509ebd6 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt +++ b/Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt @@ -15,14 +15,14 @@ Optional properties: Example: -ehrpwm0: ehrpwm@0 { /* EHRPWM on am33xx */ +ehrpwm0: pwm@48300200 { /* EHRPWM on am33xx */ compatible = "ti,am33xx-ehrpwm"; #pwm-cells = <3>; reg = <0x48300200 0x100>; ti,hwmods = "ehrpwm0"; }; -ehrpwm0: ehrpwm@0 { /* EHRPWM on da850 */ +ehrpwm0: pwm@300000 { /* EHRPWM on da850 */ compatible = "ti,da850-ehrpwm", "ti,am33xx-ehrpwm"; #pwm-cells = <3>; reg = <0x300000 0x2000>; -- cgit v1.2.3 From ae5200d2271691b28aff8d0409fbefd4644ff507 Mon Sep 17 00:00:00 2001 From: "Cooper Jr., Franklin" Date: Tue, 3 May 2016 10:56:52 -0500 Subject: pwm: pwm-ti*: Get the clock from the PWMSS parent when using old bindings When using the old eCAP and ePWM bindings for AM335x and AM437x the clock can be retrieved from the PWMSS parent. Newer bindings will insure that this clock is provided via device tree. Therefore, update this driver to support the newer and older bindings. In the case of the older binding being used give a warning. Signed-off-by: Franklin S Cooper Jr [thierry.reding@gmail.com: rewrite slightly for readability] Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tiecap.c | 9 +++++++++ drivers/pwm/pwm-tiehrpwm.c | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 616af764a276..0dc5b97a7524 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -195,6 +195,7 @@ static const struct pwm_ops ecap_pwm_ops = { }; static const struct of_device_id ecap_of_match[] = { + { .compatible = "ti,am3352-ecap" }, { .compatible = "ti,am33xx-ecap" }, {}, }; @@ -202,6 +203,7 @@ MODULE_DEVICE_TABLE(of, ecap_of_match); static int ecap_pwm_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; int ret; struct resource *r; struct clk *clk; @@ -213,6 +215,13 @@ static int ecap_pwm_probe(struct platform_device *pdev) return -ENOMEM; clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + if (of_device_is_compatible(np, "ti,am33xx-ecap")) { + dev_warn(&pdev->dev, "Binding is obsolete.\n"); + clk = devm_clk_get(pdev->dev.parent, "fck"); + } + } + if (IS_ERR(clk)) { dev_err(&pdev->dev, "failed to get clock\n"); return PTR_ERR(clk); diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index 6a41e66015b6..c791720d27b0 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -426,6 +426,7 @@ static const struct pwm_ops ehrpwm_pwm_ops = { }; static const struct of_device_id ehrpwm_of_match[] = { + { .compatible = "ti,am3352-ehrpwm" }, { .compatible = "ti,am33xx-ehrpwm" }, {}, }; @@ -433,6 +434,7 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match); static int ehrpwm_pwm_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; int ret; struct resource *r; struct clk *clk; @@ -444,6 +446,13 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) return -ENOMEM; clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + if (of_device_is_compatible(np, "ti,am33xx-ecap")) { + dev_warn(&pdev->dev, "Binding is obsolete.\n"); + clk = devm_clk_get(pdev->dev.parent, "fck"); + } + } + if (IS_ERR(clk)) { dev_err(&pdev->dev, "failed to get clock\n"); return PTR_ERR(clk); -- cgit v1.2.3 From 2b77487f2e8ff7e6496a7f5a08839de9bbb39ab3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 10 Jun 2016 15:49:53 +0200 Subject: pwm: Remove gratuitous blank line Commit 5ec803edcb70 ("pwm: Add core infrastructure to allow atomic updates") introduced this double blank line by mistake. Signed-off-by: Thierry Reding --- include/linux/pwm.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/pwm.h b/include/linux/pwm.h index fd1092729ed6..83d8bcb7e1de 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -408,7 +408,6 @@ static inline void pwm_disable(struct pwm_device *pwm) pwm_apply_state(pwm, &state); } - /* PWM provider APIs */ int pwm_set_chip_data(struct pwm_device *pwm, void *data); void *pwm_get_chip_data(struct pwm_device *pwm); -- cgit v1.2.3 From fd4f99c4c3ce8ccd9b8ea751afc614a7624ecef2 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:17 +0200 Subject: regulator: pwm: Adjust PWM config at probe time The PWM attached to a PWM regulator device might have been previously configured by the bootloader. Make sure the bootloader and linux config are in sync, and adjust the PWM config if that's not the case. Signed-off-by: Boris Brezillon Acked-by: Mark Brown Acked-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 666bc3bb52ef..cb2f22c02469 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -316,11 +316,9 @@ static int pwm_regulator_probe(struct platform_device *pdev) return ret; } - /* - * FIXME: pwm_apply_args() should be removed when switching to the - * atomic PWM API. - */ - pwm_apply_args(drvdata->pwm); + ret = pwm_adjust_config(drvdata->pwm); + if (ret) + return ret; regulator = devm_regulator_register(&pdev->dev, &drvdata->desc, &config); -- cgit v1.2.3 From 3f4eb39be9b1402ea01a5c67441d0b0bcb74b4b2 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:18 +0200 Subject: regulator: pwm: Switch to the atomic PWM API Use the atomic API wherever appropriate and get rid of pwm_apply_args() call (the reference period and polarity are now explicitly set when calling pwm_apply_state()). We also make use of the pwm_set_relative_duty_cycle() helper to ease relative to absolute duty_cycle conversion. Note that changes introduced by commit fd786fb0276a ("regulator: pwm: Try to avoid voltage error in duty cycle calculation") are no longer needed because pwm_set_relative_duty_cycle() takes care of all rounding approximation for us. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Acked-by: Laxman Dewangan Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index cb2f22c02469..7920411057af 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -63,16 +63,14 @@ static int pwm_regulator_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); - struct pwm_args pargs; - int dutycycle; + struct pwm_state pstate; int ret; - pwm_get_args(drvdata->pwm, &pargs); + pwm_init_state(drvdata->pwm, &pstate); + pwm_set_relative_duty_cycle(&pstate, + drvdata->duty_cycle_table[selector].dutycycle, 100); - dutycycle = (pargs.period * - drvdata->duty_cycle_table[selector].dutycycle) / 100; - - ret = pwm_config(drvdata->pwm, dutycycle, pargs.period); + ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); return ret; @@ -139,35 +137,19 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev, { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); unsigned int ramp_delay = rdev->constraints->ramp_delay; - struct pwm_args pargs; unsigned int req_diff = min_uV - rdev->constraints->min_uV; + struct pwm_state pstate; unsigned int diff; - unsigned int duty_pulse; - u64 req_period; - u32 rem; int old_uV = pwm_regulator_get_voltage(rdev); int ret; - pwm_get_args(drvdata->pwm, &pargs); + pwm_init_state(drvdata->pwm, &pstate); diff = rdev->constraints->max_uV - rdev->constraints->min_uV; - /* First try to find out if we get the iduty cycle time which is - * factor of PWM period time. If (request_diff_to_min * pwm_period) - * is perfect divided by voltage_range_diff then it is possible to - * get duty cycle time which is factor of PWM period. This will help - * to get output voltage nearer to requested value as there is no - * calculation loss. - */ - req_period = req_diff * pargs.period; - div_u64_rem(req_period, diff, &rem); - if (!rem) { - do_div(req_period, diff); - duty_pulse = (unsigned int)req_period; - } else { - duty_pulse = (pargs.period / 100) * ((req_diff * 100) / diff); - } + /* We pass diff as the scale to get a uV precision. */ + pwm_set_relative_duty_cycle(&pstate, req_diff, diff); - ret = pwm_config(drvdata->pwm, duty_pulse, pargs.period); + ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); return ret; -- cgit v1.2.3 From 87248991a1de28e73dc30057e82d831bc11cdd44 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:19 +0200 Subject: regulator: pwm: Properly initialize the ->state field The ->state field is currently initialized to 0, thus referencing the voltage selector at index 0, which might not reflect the current voltage value. If possible, retrieve the current voltage selector from the PWM state, else return -EINVAL. Signed-off-by: Boris Brezillon Tested-by: Brian Norris Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 7920411057af..7d26d3b0eed6 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -52,10 +52,31 @@ struct pwm_voltages { /** * Voltage table call-backs */ +static void pwm_regulator_init_state(struct regulator_dev *rdev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + struct pwm_state pwm_state; + unsigned int dutycycle; + int i; + + pwm_get_state(drvdata->pwm, &pwm_state); + dutycycle = pwm_get_relative_duty_cycle(&pwm_state, 100); + + for (i = 0; i < rdev->desc->n_voltages; i++) { + if (dutycycle == drvdata->duty_cycle_table[i].dutycycle) { + drvdata->state = i; + return; + } + } +} + static int pwm_regulator_get_voltage_sel(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + if (drvdata->state < 0) + pwm_regulator_init_state(rdev); + return drvdata->state; } @@ -221,6 +242,7 @@ static int pwm_regulator_init_table(struct platform_device *pdev, return ret; } + drvdata->state = -EINVAL; drvdata->duty_cycle_table = duty_cycle_table; memcpy(&drvdata->ops, &pwm_regulator_voltage_table_ops, sizeof(drvdata->ops)); -- cgit v1.2.3 From d9070fdbe40a04b61262bac0f7ff0c7c29a68015 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:20 +0200 Subject: regulator: pwm: Retrieve correct voltage The continuous PWM voltage regulator is caching the voltage value in the ->volt_uV field. While most of the time this value should reflect the real voltage, sometime it can be sightly different if the PWM device rounded the set_duty_cycle request. Moreover, this value is not valid until someone has modified the regulator output. Remove the ->volt_uV field and always rely on the PWM state to calculate the regulator output. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 7d26d3b0eed6..ae0bebbfda9d 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -37,9 +37,6 @@ struct pwm_regulator_data { int state; - /* Continuous voltage */ - int volt_uV; - /* Enable GPIO */ struct gpio_desc *enb_gpio; }; @@ -148,8 +145,13 @@ static int pwm_regulator_is_enabled(struct regulator_dev *dev) static int pwm_regulator_get_voltage(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + int min_uV = rdev->constraints->min_uV; + int diff = rdev->constraints->max_uV - min_uV; + struct pwm_state pstate; - return drvdata->volt_uV; + pwm_get_state(drvdata->pwm, &pstate); + + return min_uV + pwm_get_relative_duty_cycle(&pstate, diff); } static int pwm_regulator_set_voltage(struct regulator_dev *rdev, @@ -176,8 +178,6 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev, return ret; } - drvdata->volt_uV = min_uV; - if ((ramp_delay == 0) || !pwm_regulator_is_enabled(rdev)) return 0; -- cgit v1.2.3 From cc37655e6bbf83ded1e4d1d7ffd977786a845a67 Mon Sep 17 00:00:00 2001 From: "Cooper Jr., Franklin" Date: Mon, 7 Mar 2016 13:33:56 -0600 Subject: pwm: pwm-ti*: Remove support for local clock gating The PWMSS local clock gating registers have no real purpose on OMAP ARM devices. These registers were left over registers from DSP IP where the PRCM doesn't exist. There is a silicon bug where gating and ungating clocks don't function properly. TRMs will be update to indicate that these registers shouldn't be touched. Therefore, all code that accesses the PWMSS_CLKCONFIG or PWMSS_CLKSTATUS will be removed by this patch with zero loss of functionality by the ECAP and EPWM drivers. Signed-off-by: Franklin S Cooper Jr Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tiecap.c | 28 -------------------------- drivers/pwm/pwm-tiehrpwm.c | 29 --------------------------- drivers/pwm/pwm-tipwmss.c | 49 ---------------------------------------------- drivers/pwm/pwm-tipwmss.h | 39 ------------------------------------ 4 files changed, 145 deletions(-) delete mode 100644 drivers/pwm/pwm-tipwmss.h diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 0dc5b97a7524..6ec342dd3eea 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -27,8 +27,6 @@ #include #include -#include "pwm-tipwmss.h" - /* ECAP registers and bits definitions */ #define CAP1 0x08 #define CAP2 0x0C @@ -208,7 +206,6 @@ static int ecap_pwm_probe(struct platform_device *pdev) struct resource *r; struct clk *clk; struct ecap_pwm_chip *pc; - u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); if (!pc) @@ -252,40 +249,15 @@ static int ecap_pwm_probe(struct platform_device *pdev) } pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - - status = pwmss_submodule_state_change(pdev->dev.parent, - PWMSS_ECAPCLK_EN); - if (!(status & PWMSS_ECAPCLK_EN_ACK)) { - dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); - ret = -EINVAL; - goto pwmss_clk_failure; - } - - pm_runtime_put_sync(&pdev->dev); platform_set_drvdata(pdev, pc); return 0; - -pwmss_clk_failure: - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pwmchip_remove(&pc->chip); - return ret; } static int ecap_pwm_remove(struct platform_device *pdev) { struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); - pm_runtime_get_sync(&pdev->dev); - /* - * Due to hardware misbehaviour, acknowledge of the stop_req - * is missing. Hence checking of the status bit skipped. - */ - pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); return pwmchip_remove(&pc->chip); } diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index c791720d27b0..b5c6b0636893 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -27,8 +27,6 @@ #include #include -#include "pwm-tipwmss.h" - /* EHRPWM registers and bits definitions */ /* Time base module registers */ @@ -439,7 +437,6 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) struct resource *r; struct clk *clk; struct ehrpwm_pwm_chip *pc; - u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); if (!pc) @@ -496,27 +493,9 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) } pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - - status = pwmss_submodule_state_change(pdev->dev.parent, - PWMSS_EPWMCLK_EN); - if (!(status & PWMSS_EPWMCLK_EN_ACK)) { - dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); - ret = -EINVAL; - goto pwmss_clk_failure; - } - - pm_runtime_put_sync(&pdev->dev); platform_set_drvdata(pdev, pc); return 0; - -pwmss_clk_failure: - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pwmchip_remove(&pc->chip); - clk_unprepare(pc->tbclk); - return ret; } static int ehrpwm_pwm_remove(struct platform_device *pdev) @@ -525,14 +504,6 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev) clk_unprepare(pc->tbclk); - pm_runtime_get_sync(&pdev->dev); - /* - * Due to hardware misbehaviour, acknowledge of the stop_req - * is missing. Hence checking of the status bit skipped. - */ - pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return pwmchip_remove(&pc->chip); diff --git a/drivers/pwm/pwm-tipwmss.c b/drivers/pwm/pwm-tipwmss.c index 5cf65a15d021..829f4991c96f 100644 --- a/drivers/pwm/pwm-tipwmss.c +++ b/drivers/pwm/pwm-tipwmss.c @@ -22,32 +22,6 @@ #include #include -#include "pwm-tipwmss.h" - -#define PWMSS_CLKCONFIG 0x8 /* Clock gating reg */ -#define PWMSS_CLKSTATUS 0xc /* Clock gating status reg */ - -struct pwmss_info { - void __iomem *mmio_base; - struct mutex pwmss_lock; - u16 pwmss_clkconfig; -}; - -u16 pwmss_submodule_state_change(struct device *dev, int set) -{ - struct pwmss_info *info = dev_get_drvdata(dev); - u16 val; - - mutex_lock(&info->pwmss_lock); - val = readw(info->mmio_base + PWMSS_CLKCONFIG); - val |= set; - writew(val , info->mmio_base + PWMSS_CLKCONFIG); - mutex_unlock(&info->pwmss_lock); - - return readw(info->mmio_base + PWMSS_CLKSTATUS); -} -EXPORT_SYMBOL(pwmss_submodule_state_change); - static const struct of_device_id pwmss_of_match[] = { { .compatible = "ti,am33xx-pwmss" }, {}, @@ -57,24 +31,10 @@ MODULE_DEVICE_TABLE(of, pwmss_of_match); static int pwmss_probe(struct platform_device *pdev) { int ret; - struct resource *r; - struct pwmss_info *info; struct device_node *node = pdev->dev.of_node; - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - mutex_init(&info->pwmss_lock); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - info->mmio_base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(info->mmio_base)) - return PTR_ERR(info->mmio_base); - pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); - platform_set_drvdata(pdev, info); /* Populate all the child nodes here... */ ret = of_platform_populate(node, NULL, NULL, &pdev->dev); @@ -86,30 +46,21 @@ static int pwmss_probe(struct platform_device *pdev) static int pwmss_remove(struct platform_device *pdev) { - struct pwmss_info *info = platform_get_drvdata(pdev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); - mutex_destroy(&info->pwmss_lock); return 0; } #ifdef CONFIG_PM_SLEEP static int pwmss_suspend(struct device *dev) { - struct pwmss_info *info = dev_get_drvdata(dev); - - info->pwmss_clkconfig = readw(info->mmio_base + PWMSS_CLKCONFIG); pm_runtime_put_sync(dev); return 0; } static int pwmss_resume(struct device *dev) { - struct pwmss_info *info = dev_get_drvdata(dev); - pm_runtime_get_sync(dev); - writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG); return 0; } #endif diff --git a/drivers/pwm/pwm-tipwmss.h b/drivers/pwm/pwm-tipwmss.h deleted file mode 100644 index 10ad8040408b..000000000000 --- a/drivers/pwm/pwm-tipwmss.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * TI PWM Subsystem driver - * - * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __TIPWMSS_H -#define __TIPWMSS_H - -/* PWM substem clock gating */ -#define PWMSS_ECAPCLK_EN BIT(0) -#define PWMSS_ECAPCLK_STOP_REQ BIT(1) -#define PWMSS_EPWMCLK_EN BIT(8) -#define PWMSS_EPWMCLK_STOP_REQ BIT(9) - -#define PWMSS_ECAPCLK_EN_ACK BIT(0) -#define PWMSS_EPWMCLK_EN_ACK BIT(8) - -#ifdef CONFIG_PWM_TIPWMSS -extern u16 pwmss_submodule_state_change(struct device *dev, int set); -#else -static inline u16 pwmss_submodule_state_change(struct device *dev, int set) -{ - /* return success status value */ - return 0xFFFF; -} -#endif -#endif /* __TIPWMSS_H */ -- cgit v1.2.3 From e5ca42458b6278b7d5866e08dae7c45349af2157 Mon Sep 17 00:00:00 2001 From: Dan O'Donovan Date: Wed, 1 Jun 2016 15:31:12 +0100 Subject: pwm: lpss: Fix base_unit calculation for PWM frequency The base_unit calculation applies an offset of 0x2 which adds significant error for lower frequencies and doesn't appear to be warranted - rounding the division result gives a correct value. Also, the upper limit check for base_unit is off-by-one; the upper nibble of base_unit is invalid if >=128 according to the Table 88 in the Z8000 Processor Series Datasheet Volume 1 (Rev. 2). Verified on UP Board (Cherry Trail) and Minnowboard Max (Bay Trail). Signed-off-by: Dan O'Donovan Acked-by: Mika Westerberg Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 295b963dbddb..98dc8b80b79d 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -27,7 +27,6 @@ #define PWM_SW_UPDATE BIT(30) #define PWM_BASE_UNIT_SHIFT 8 #define PWM_ON_TIME_DIV_MASK 0x000000ff -#define PWM_DIVISION_CORRECTION 0x2 /* Size of each PWM register space if multiple */ #define PWM_SIZE 0x400 @@ -101,17 +100,16 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, /* * The equation is: - * base_unit = ((freq / c) * base_unit_range) + correction + * base_unit = round(base_unit_range * freq / c) */ base_unit_range = BIT(lpwm->info->base_unit_bits); - base_unit = freq * base_unit_range; + freq *= base_unit_range; c = lpwm->info->clk_rate; if (!c) return -EINVAL; - do_div(base_unit, c); - base_unit += PWM_DIVISION_CORRECTION; + base_unit = DIV_ROUND_CLOSEST_ULL(freq, c); if (duty_ns <= 0) duty_ns = 1; -- cgit v1.2.3 From ab248b603960a4b6effaa9e16fc1ea84a33210c7 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 10 Jun 2016 15:43:21 +0300 Subject: pwm: lpss: Prevent on_time_div overflow on lower frequencies If duty_ns is large enough multiplying it by 255 overflows and results wrong duty cycle value being programmed. For example with 10ms duty when period is 20ms (50%) we get 255 * 10000000 / 20000000 = -87 because 255 * 10000000 overlows int. Whereas correct value should be 255 * 10000000 / 20000000 = 127 Fix this by using unsigned long long as type for on_time_div and changing integer literals to use proper type annotation. Signed-off-by: Mika Westerberg Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 98dc8b80b79d..be4658b42882 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -91,7 +91,7 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct pwm_lpss_chip *lpwm = to_lpwm(chip); - u8 on_time_div; + unsigned long long on_time_div; unsigned long c, base_unit_range; unsigned long long base_unit, freq = NSEC_PER_SEC; u32 ctrl; @@ -113,7 +113,9 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, if (duty_ns <= 0) duty_ns = 1; - on_time_div = 255 - (255 * duty_ns / period_ns); + on_time_div = 255ULL * duty_ns; + do_div(on_time_div, period_ns); + on_time_div = 255ULL - on_time_div; pm_runtime_get_sync(chip->dev); -- cgit v1.2.3 From b89b4b7a3d0adab8de1062579e4305d05a028c31 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 13 Jun 2016 21:52:19 +0300 Subject: pwm: lpss: pci: Enable PWM module on Intel Edison Intel Edison has 4 PWM channels on the die with the same IP as in Broxton. Enable it. Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss-pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index 7160e8ab38a4..3622f093490e 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -76,6 +76,7 @@ static const struct pci_device_id pwm_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, + { PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, -- cgit v1.2.3 From d9cd4a73693bc7153766d22079e0fc90c0fc1107 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 4 Jul 2016 18:36:27 +0300 Subject: pwm: lpss: Move clk_rate check to ->probe() There is no need to check each time if the clk_rate defined or not when we call pwm_lpss_config(). Move the check to ->probe() instead. Signed-off-by: Andy Shevchenko Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpss.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index be4658b42882..72c0bce5a75c 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -92,7 +92,7 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, { struct pwm_lpss_chip *lpwm = to_lpwm(chip); unsigned long long on_time_div; - unsigned long c, base_unit_range; + unsigned long c = lpwm->info->clk_rate, base_unit_range; unsigned long long base_unit, freq = NSEC_PER_SEC; u32 ctrl; @@ -105,10 +105,6 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, base_unit_range = BIT(lpwm->info->base_unit_bits); freq *= base_unit_range; - c = lpwm->info->clk_rate; - if (!c) - return -EINVAL; - base_unit = DIV_ROUND_CLOSEST_ULL(freq, c); if (duty_ns <= 0) @@ -169,6 +165,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, const struct pwm_lpss_boardinfo *info) { struct pwm_lpss_chip *lpwm; + unsigned long c; int ret; lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); @@ -180,6 +177,11 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, return ERR_CAST(lpwm->regs); lpwm->info = info; + + c = lpwm->info->clk_rate; + if (!c) + return ERR_PTR(-EINVAL); + lpwm->chip.dev = dev; lpwm->chip.ops = &pwm_lpss_ops; lpwm->chip.base = -1; -- cgit v1.2.3 From f734f6ddbc12364021603d4bc8ba031b782508a5 Mon Sep 17 00:00:00 2001 From: Yendapally Reddy Dhananjaya Reddy Date: Tue, 5 Jul 2016 02:00:24 -0400 Subject: dt-bindings: pwm: Add Broadcom iProc PWM controller binding Add a device tree binding for Broadcom iProc PWM controller. Signed-off-by: Yendapally Reddy Dhananjaya Reddy Acked-by: Rob Herring Signed-off-by: Thierry Reding --- .../devicetree/bindings/pwm/brcm,iproc-pwm.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt b/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt new file mode 100644 index 000000000000..21f75bbd6dae --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt @@ -0,0 +1,21 @@ +Broadcom iProc PWM controller device tree bindings + +This controller has 4 channels. + +Required Properties : +- compatible: must be "brcm,iproc-pwm" +- reg: physical base address and length of the controller's registers +- clocks: phandle + clock specifier pair for the external clock +- #pwm-cells: Should be 3. See pwm.txt in this directory for a + description of the cells format. + +Refer to clocks/clock-bindings.txt for generic clock consumer properties. + +Example: + +pwm: pwm@18031000 { + compatible = "brcm,iproc-pwm"; + reg = <0x18031000 0x28>; + clocks = <&osc>; + #pwm-cells = <3>; +}; -- cgit v1.2.3 From daa5abc41c80e32ebaf069bd482b7561e0ada71d Mon Sep 17 00:00:00 2001 From: Yendapally Reddy Dhananjaya Reddy Date: Tue, 5 Jul 2016 02:00:25 -0400 Subject: pwm: Add support for Broadcom iProc PWM controller Add support for the PWM controller present in Broadcom's iProc family of SoCs. It has been tested on the Northstar+ bcm958625HR board. Signed-off-by: Yendapally Reddy Dhananjaya Reddy [thierry.reding@gmail.com: bunch of coding style fixes, cleanups] Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-bcm-iproc.c | 277 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 drivers/pwm/pwm-bcm-iproc.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c182efc62c7b..d298ebdd872d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -74,6 +74,16 @@ config PWM_ATMEL_TCB To compile this driver as a module, choose M here: the module will be called pwm-atmel-tcb. +config PWM_BCM_IPROC + tristate "iProc PWM support" + depends on ARCH_BCM_IPROC + help + Generic PWM framework driver for Broadcom iProc PWM block. This + block is used in Broadcom iProc SoC's. + + To compile this driver as a module, choose M here: the module + will be called pwm-bcm-iproc. + config PWM_BCM_KONA tristate "Kona PWM support" depends on ARCH_BCM_MOBILE diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index dd35bc121a18..a196d79fbd3f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o +obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c new file mode 100644 index 000000000000..d961a8207b1c --- /dev/null +++ b/drivers/pwm/pwm-bcm-iproc.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2016 Broadcom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPROC_PWM_CTRL_OFFSET 0x00 +#define IPROC_PWM_CTRL_TYPE_SHIFT(ch) (15 + (ch)) +#define IPROC_PWM_CTRL_POLARITY_SHIFT(ch) (8 + (ch)) +#define IPROC_PWM_CTRL_EN_SHIFT(ch) (ch) + +#define IPROC_PWM_PERIOD_OFFSET(ch) (0x04 + ((ch) << 3)) +#define IPROC_PWM_PERIOD_MIN 0x02 +#define IPROC_PWM_PERIOD_MAX 0xffff + +#define IPROC_PWM_DUTY_CYCLE_OFFSET(ch) (0x08 + ((ch) << 3)) +#define IPROC_PWM_DUTY_CYCLE_MIN 0x00 +#define IPROC_PWM_DUTY_CYCLE_MAX 0xffff + +#define IPROC_PWM_PRESCALE_OFFSET 0x24 +#define IPROC_PWM_PRESCALE_BITS 0x06 +#define IPROC_PWM_PRESCALE_SHIFT(ch) ((3 - (ch)) * \ + IPROC_PWM_PRESCALE_BITS) +#define IPROC_PWM_PRESCALE_MASK(ch) (IPROC_PWM_PRESCALE_MAX << \ + IPROC_PWM_PRESCALE_SHIFT(ch)) +#define IPROC_PWM_PRESCALE_MIN 0x00 +#define IPROC_PWM_PRESCALE_MAX 0x3f + +struct iproc_pwmc { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +static inline struct iproc_pwmc *to_iproc_pwmc(struct pwm_chip *chip) +{ + return container_of(chip, struct iproc_pwmc, chip); +} + +static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel) +{ + u32 value; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + value |= 1 << IPROC_PWM_CTRL_EN_SHIFT(channel); + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + /* must be a 400 ns delay between clearing and setting enable bit */ + ndelay(400); +} + +static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel) +{ + u32 value; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + value &= ~(1 << IPROC_PWM_CTRL_EN_SHIFT(channel)); + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + /* must be a 400 ns delay between clearing and setting enable bit */ + ndelay(400); +} + +static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iproc_pwmc *ip = to_iproc_pwmc(chip); + u64 tmp, multi, rate; + u32 value, prescale; + + rate = clk_get_rate(ip->clk); + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm))) + state->enabled = true; + else + state->enabled = false; + + if (value & BIT(IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm))) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); + prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); + prescale &= IPROC_PWM_PRESCALE_MAX; + + multi = NSEC_PER_SEC * (prescale + 1); + + value = readl(ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); + tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; + state->period = div64_u64(tmp, rate); + + value = readl(ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); + tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; + state->duty_cycle = div64_u64(tmp, rate); +} + +static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + unsigned long prescale = IPROC_PWM_PRESCALE_MIN; + struct iproc_pwmc *ip = to_iproc_pwmc(chip); + u32 value, period, duty; + u64 rate; + + rate = clk_get_rate(ip->clk); + + /* + * Find period count, duty count and prescale to suit duty_cycle and + * period. This is done according to formulas described below: + * + * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + * + * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) + * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) + */ + while (1) { + u64 value, div; + + div = NSEC_PER_SEC * (prescale + 1); + value = rate * state->period; + period = div64_u64(value, div); + value = rate * state->duty_cycle; + duty = div64_u64(value, div); + + if (period < IPROC_PWM_PERIOD_MIN || + duty < IPROC_PWM_DUTY_CYCLE_MIN) + return -EINVAL; + + if (period <= IPROC_PWM_PERIOD_MAX && + duty <= IPROC_PWM_DUTY_CYCLE_MAX) + break; + + /* Otherwise, increase prescale and recalculate counts */ + if (++prescale > IPROC_PWM_PRESCALE_MAX) + return -EINVAL; + } + + iproc_pwmc_disable(ip, pwm->hwpwm); + + /* Set prescale */ + value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); + value &= ~IPROC_PWM_PRESCALE_MASK(pwm->hwpwm); + value |= prescale << IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); + writel(value, ip->base + IPROC_PWM_PRESCALE_OFFSET); + + /* set period and duty cycle */ + writel(period, ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); + writel(duty, ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); + + /* set polarity */ + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + if (state->polarity == PWM_POLARITY_NORMAL) + value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm); + else + value &= ~(1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm)); + + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + if (state->enabled) + iproc_pwmc_enable(ip, pwm->hwpwm); + + return 0; +} + +static const struct pwm_ops iproc_pwm_ops = { + .apply = iproc_pwmc_apply, + .get_state = iproc_pwmc_get_state, +}; + +static int iproc_pwmc_probe(struct platform_device *pdev) +{ + struct iproc_pwmc *ip; + struct resource *res; + unsigned int i; + u32 value; + int ret; + + ip = devm_kzalloc(&pdev->dev, sizeof(*ip), GFP_KERNEL); + if (!ip) + return -ENOMEM; + + platform_set_drvdata(pdev, ip); + + ip->chip.dev = &pdev->dev; + ip->chip.ops = &iproc_pwm_ops; + ip->chip.base = -1; + ip->chip.npwm = 4; + ip->chip.of_xlate = of_pwm_xlate_with_flags; + ip->chip.of_pwm_n_cells = 3; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ip->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ip->base)) + return PTR_ERR(ip->base); + + ip->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ip->clk)) { + dev_err(&pdev->dev, "failed to get clock: %ld\n", + PTR_ERR(ip->clk)); + return PTR_ERR(ip->clk); + } + + ret = clk_prepare_enable(ip->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Set full drive and normal polarity for all channels */ + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + for (i = 0; i < ip->chip.npwm; i++) { + value &= ~(1 << IPROC_PWM_CTRL_TYPE_SHIFT(i)); + value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(i); + } + + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + ret = pwmchip_add(&ip->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + clk_disable_unprepare(ip->clk); + } + + return ret; +} + +static int iproc_pwmc_remove(struct platform_device *pdev) +{ + struct iproc_pwmc *ip = platform_get_drvdata(pdev); + + clk_disable_unprepare(ip->clk); + + return pwmchip_remove(&ip->chip); +} + +static const struct of_device_id bcm_iproc_pwmc_dt[] = { + { .compatible = "brcm,iproc-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_iproc_pwmc_dt); + +static struct platform_driver iproc_pwmc_driver = { + .driver = { + .name = "bcm-iproc-pwm", + .of_match_table = bcm_iproc_pwmc_dt, + }, + .probe = iproc_pwmc_probe, + .remove = iproc_pwmc_remove, +}; +module_platform_driver(iproc_pwmc_driver); + +MODULE_AUTHOR("Yendapally Reddy Dhananjaya Reddy "); +MODULE_DESCRIPTION("Broadcom iProc PWM driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 12f9ce4a519845070d338253ab9528b5d7e2df34 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:11 +0200 Subject: pwm: rockchip: Fix period and duty cycle approximation The current implementation always round down the duty and period values, while it would be better to round them to the closest integer. These changes are needed in preparation of atomic update support to prevent a period/duty cycle drift when executing several times the 'pwm_get_state() / modify / pwm_apply_state()' sequence. Say you have an expected period of 3.333 us and a clk rate of 112.666667 MHz -- the clock frequency doesn't divide evenly, so the period (stashed in nanoseconds) shrinks when we convert to the register value and back, as follows: pwm_apply_state(): register = period * 112666667 / 1000000000; pwm_get_state(): period = register * 1000000000 / 112666667; or in other words: period = period * 112666667 / 1000000000 * 1000000000 / 112666667; which yields a sequence like: 3333 -> 3328 3328 -> 3319 3319 -> 3310 3310 -> 3301 3301 -> 3292 3292 -> ... (etc) ... With this patch, we'd see instead: period = div_round_closest(period * 112666667, 1000000000) * 1000000000 / 112666667; which yields a stable sequence: 3333 -> 3337 3337 -> 3337 3337 -> ... (etc) ... Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- drivers/pwm/pwm-rockchip.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 7d9cc9049522..68d72ce8fba5 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -114,12 +114,11 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * default prescaler value for all practical clock rate values. */ div = clk_rate * period_ns; - do_div(div, pc->data->prescaler * NSEC_PER_SEC); - period = div; + period = DIV_ROUND_CLOSEST_ULL(div, + pc->data->prescaler * NSEC_PER_SEC); div = clk_rate * duty_ns; - do_div(div, pc->data->prescaler * NSEC_PER_SEC); - duty = div; + duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); ret = clk_enable(pc->clk); if (ret) -- cgit v1.2.3 From 1ebb74cf3537135f157beddf1a4366070155edda Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:12 +0200 Subject: pwm: rockchip: Add support for hardware readout Implement the ->get_state() function to expose initial state. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- drivers/pwm/pwm-rockchip.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 68d72ce8fba5..c72b4192c7bf 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -51,6 +51,8 @@ struct rockchip_pwm_data { void (*set_enable)(struct pwm_chip *chip, struct pwm_device *pwm, bool enable); + void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state); }; static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) @@ -75,6 +77,19 @@ static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, writel_relaxed(val, pc->base + pc->data->regs.ctrl); } +static void rockchip_pwm_get_state_v1(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + if ((val & enable_conf) == enable_conf) + state->enabled = true; +} + static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, struct pwm_device *pwm, bool enable) { @@ -98,6 +113,53 @@ static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, writel_relaxed(val, pc->base + pc->data->regs.ctrl); } +static void rockchip_pwm_get_state_v2(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + if ((val & enable_conf) != enable_conf) + return; + + state->enabled = true; + + if (!(val & PWM_DUTY_POSITIVE)) + state->polarity = PWM_POLARITY_INVERSED; +} + +static void rockchip_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + unsigned long clk_rate; + u64 tmp; + int ret; + + ret = clk_enable(pc->clk); + if (ret) + return; + + clk_rate = clk_get_rate(pc->clk); + + tmp = readl_relaxed(pc->base + pc->data->regs.period); + tmp *= pc->data->prescaler * NSEC_PER_SEC; + state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + tmp = readl_relaxed(pc->base + pc->data->regs.duty); + tmp *= pc->data->prescaler * NSEC_PER_SEC; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + pc->data->get_state(chip, pwm, state); + + clk_disable(pc->clk); +} + static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -170,6 +232,7 @@ static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) } static const struct pwm_ops rockchip_pwm_ops_v1 = { + .get_state = rockchip_pwm_get_state, .config = rockchip_pwm_config, .enable = rockchip_pwm_enable, .disable = rockchip_pwm_disable, @@ -177,6 +240,7 @@ static const struct pwm_ops rockchip_pwm_ops_v1 = { }; static const struct pwm_ops rockchip_pwm_ops_v2 = { + .get_state = rockchip_pwm_get_state, .config = rockchip_pwm_config, .set_polarity = rockchip_pwm_set_polarity, .enable = rockchip_pwm_enable, @@ -194,6 +258,7 @@ static const struct rockchip_pwm_data pwm_data_v1 = { .prescaler = 2, .ops = &rockchip_pwm_ops_v1, .set_enable = rockchip_pwm_set_enable_v1, + .get_state = rockchip_pwm_get_state_v1, }; static const struct rockchip_pwm_data pwm_data_v2 = { @@ -206,6 +271,7 @@ static const struct rockchip_pwm_data pwm_data_v2 = { .prescaler = 1, .ops = &rockchip_pwm_ops_v2, .set_enable = rockchip_pwm_set_enable_v2, + .get_state = rockchip_pwm_get_state_v2, }; static const struct rockchip_pwm_data pwm_data_vop = { @@ -218,6 +284,7 @@ static const struct rockchip_pwm_data pwm_data_vop = { .prescaler = 1, .ops = &rockchip_pwm_ops_v2, .set_enable = rockchip_pwm_set_enable_v2, + .get_state = rockchip_pwm_get_state_v2, }; static const struct of_device_id rockchip_pwm_dt_ids[] = { -- cgit v1.2.3 From 48cf973cae33488f84d7ab79a0f613383cff4de4 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:13 +0200 Subject: pwm: rockchip: Avoid glitches on already running PWMs The current logic will disable the PWM clk even if the PWM was left enabled by the bootloader (because it's controlling a critical device like a regulator for example). Keep the PWM clk enabled if the PWM is enabled to avoid any glitches. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- drivers/pwm/pwm-rockchip.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index c72b4192c7bf..dd8ca8663c24 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -319,7 +319,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) if (IS_ERR(pc->clk)) return PTR_ERR(pc->clk); - ret = clk_prepare(pc->clk); + ret = clk_prepare_enable(pc->clk); if (ret) return ret; @@ -342,6 +342,10 @@ static int rockchip_pwm_probe(struct platform_device *pdev) dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); } + /* Keep the PWM clk enabled if the PWM appears to be up and running. */ + if (!pwm_is_enabled(pc->chip.pwms)) + clk_disable(pc->clk); + return ret; } @@ -349,6 +353,20 @@ static int rockchip_pwm_remove(struct platform_device *pdev) { struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); + /* + * Disable the PWM clk before unpreparing it if the PWM device is still + * running. This should only happen when the last PWM user left it + * enabled, or when nobody requested a PWM that was previously enabled + * by the bootloader. + * + * FIXME: Maybe the core should disable all PWM devices in + * pwmchip_remove(). In this case we'd only have to call + * clk_unprepare() after pwmchip_remove(). + * + */ + if (pwm_is_enabled(pc->chip.pwms)) + clk_disable(pc->clk); + clk_unprepare(pc->clk); return pwmchip_remove(&pc->chip); -- cgit v1.2.3 From 2bf1c98aa5a41651f5e6455117ff06f66ff3cc50 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:14 +0200 Subject: pwm: rockchip: Add support for atomic update Implement the ->apply() function to add support for atomic update. Signed-off-by: Boris Brezillon Tested-by: Heiko Stuebner Reviewed-by: Brian Norris Tested-by: Brian Norris Signed-off-by: Thierry Reding --- drivers/pwm/pwm-rockchip.c | 84 ++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index dd8ca8663c24..ef89df1f7336 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -47,10 +47,12 @@ struct rockchip_pwm_regs { struct rockchip_pwm_data { struct rockchip_pwm_regs regs; unsigned int prescaler; + bool supports_polarity; const struct pwm_ops *ops; void (*set_enable)(struct pwm_chip *chip, - struct pwm_device *pwm, bool enable); + struct pwm_device *pwm, bool enable, + enum pwm_polarity polarity); void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state); }; @@ -61,7 +63,8 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) } static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, - struct pwm_device *pwm, bool enable) + struct pwm_device *pwm, bool enable, + enum pwm_polarity polarity) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; @@ -91,14 +94,15 @@ static void rockchip_pwm_get_state_v1(struct pwm_chip *chip, } static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, - struct pwm_device *pwm, bool enable) + struct pwm_device *pwm, bool enable, + enum pwm_polarity polarity) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | PWM_CONTINUOUS; u32 val; - if (pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED) + if (polarity == PWM_POLARITY_INVERSED) enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE; else enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE; @@ -166,7 +170,6 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); unsigned long period, duty; u64 clk_rate, div; - int ret; clk_rate = clk_get_rate(pc->clk); @@ -182,69 +185,66 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, div = clk_rate * duty_ns; duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); - ret = clk_enable(pc->clk); - if (ret) - return ret; - writel(period, pc->base + pc->data->regs.period); writel(duty, pc->base + pc->data->regs.duty); - writel(0, pc->base + pc->data->regs.cntr); - - clk_disable(pc->clk); - - return 0; -} - -static int rockchip_pwm_set_polarity(struct pwm_chip *chip, - struct pwm_device *pwm, - enum pwm_polarity polarity) -{ - /* - * No action needed here because pwm->polarity will be set by the core - * and the core will only change polarity when the PWM is not enabled. - * We'll handle things in set_enable(). - */ return 0; } -static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + struct pwm_state curstate; + bool enabled; int ret; + pwm_get_state(pwm, &curstate); + enabled = curstate.enabled; + ret = clk_enable(pc->clk); if (ret) return ret; - pc->data->set_enable(chip, pwm, true); + if (state->polarity != curstate.polarity && enabled) { + pc->data->set_enable(chip, pwm, false, state->polarity); + enabled = false; + } - return 0; -} + ret = rockchip_pwm_config(chip, pwm, state->duty_cycle, state->period); + if (ret) { + if (enabled != curstate.enabled) + pc->data->set_enable(chip, pwm, !enabled, + state->polarity); -static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + goto out; + } + + if (state->enabled != enabled) + pc->data->set_enable(chip, pwm, state->enabled, + state->polarity); - pc->data->set_enable(chip, pwm, false); + /* + * Update the state with the real hardware, which can differ a bit + * because of period/duty_cycle approximation. + */ + rockchip_pwm_get_state(chip, pwm, state); +out: clk_disable(pc->clk); + + return ret; } static const struct pwm_ops rockchip_pwm_ops_v1 = { .get_state = rockchip_pwm_get_state, - .config = rockchip_pwm_config, - .enable = rockchip_pwm_enable, - .disable = rockchip_pwm_disable, + .apply = rockchip_pwm_apply, .owner = THIS_MODULE, }; static const struct pwm_ops rockchip_pwm_ops_v2 = { .get_state = rockchip_pwm_get_state, - .config = rockchip_pwm_config, - .set_polarity = rockchip_pwm_set_polarity, - .enable = rockchip_pwm_enable, - .disable = rockchip_pwm_disable, + .apply = rockchip_pwm_apply, .owner = THIS_MODULE, }; @@ -269,6 +269,7 @@ static const struct rockchip_pwm_data pwm_data_v2 = { .ctrl = 0x0c, }, .prescaler = 1, + .supports_polarity = true, .ops = &rockchip_pwm_ops_v2, .set_enable = rockchip_pwm_set_enable_v2, .get_state = rockchip_pwm_get_state_v2, @@ -282,6 +283,7 @@ static const struct rockchip_pwm_data pwm_data_vop = { .ctrl = 0x00, }, .prescaler = 1, + .supports_polarity = true, .ops = &rockchip_pwm_ops_v2, .set_enable = rockchip_pwm_set_enable_v2, .get_state = rockchip_pwm_get_state_v2, @@ -331,7 +333,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) pc->chip.base = -1; pc->chip.npwm = 1; - if (pc->data->ops->set_polarity) { + if (pc->data->supports_polarity) { pc->chip.of_xlate = of_pwm_xlate_with_flags; pc->chip.of_pwm_n_cells = 3; } -- cgit v1.2.3 From 27e8c8f3512371772b1fdd099466a8c198d05897 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 5 Apr 2016 23:22:29 +0200 Subject: dt-bindings: pwm: Add DT bindings for STMPE PWM This adds fairly standard device tree bindings for the STMPE PWM found in STMPE24xx multi-purpose expanders. Cc: devicetree@vger.kernel.org Acked-by: Rob Herring Signed-off-by: Linus Walleij Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt new file mode 100644 index 000000000000..cb209646bf13 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt @@ -0,0 +1,18 @@ +== ST STMPE PWM controller == + +This is a PWM block embedded in the ST Microelectronics STMPE +(ST Multi-Purpose Expander) chips. The PWM is registered as a +subdevices of the STMPE MFD device. + +Required properties: +- compatible: should be: + - "st,stmpe-pwm" +- #pwm-cells: should be 2. See pwm.txt in this directory for a description of + the cells format. + +Example: + +pwm0: pwm { + compatible = "st,stmpe-pwm"; + #pwm-cells = <2>; +}; -- cgit v1.2.3 From ef1f09eca74a42d39ce81adec444743a6ff018aa Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 5 Apr 2016 23:22:37 +0200 Subject: pwm: Add a driver for the STMPE PWM This adds a driver for the PWM block found in chips of the STMPE 24xx series of multi-purpose I2C expanders. (I think STMPE means ST Microelectronics Multi-Purpose Expander.) This PWM was designed in accordance with Nokia specifications and is kind of weird and usually just switched between max and zero duty cycle. However it is indeed a PWM so it needs to live in the PWM subsystem. This PWM is mostly used for white LED backlight. Cc: Lee Jones Signed-off-by: Linus Walleij Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 7 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-stmpe.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 drivers/pwm/pwm-stmpe.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d298ebdd872d..59124eb4d592 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -372,6 +372,13 @@ config PWM_STI To compile this driver as a module, choose M here: the module will be called pwm-sti. +config PWM_STMPE + bool "STMPE expander PWM export" + depends on MFD_STMPE + help + This enables support for the PWMs found in the STMPE I/O + expanders. + config PWM_SUN4I tristate "Allwinner PWM support" depends on ARCH_SUNXI || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index a196d79fbd3f..98ae3cb4e3e7 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_STI) += pwm-sti.o +obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c new file mode 100644 index 000000000000..e464582a390a --- /dev/null +++ b/drivers/pwm/pwm-stmpe.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2016 Linaro Ltd. + * + * Author: Linus Walleij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STMPE24XX_PWMCS 0x30 +#define PWMCS_EN_PWM0 BIT(0) +#define PWMCS_EN_PWM1 BIT(1) +#define PWMCS_EN_PWM2 BIT(2) +#define STMPE24XX_PWMIC0 0x38 +#define STMPE24XX_PWMIC1 0x39 +#define STMPE24XX_PWMIC2 0x3a + +#define STMPE_PWM_24XX_PINBASE 21 + +struct stmpe_pwm { + struct stmpe *stmpe; + struct pwm_chip chip; + u8 last_duty; +}; + +static inline struct stmpe_pwm *to_stmpe_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct stmpe_pwm, chip); +} + +static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + u8 value; + int ret; + + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); + if (ret < 0) { + dev_err(chip->dev, "error reading PWM#%u control\n", + pwm->hwpwm); + return ret; + } + + value = ret | BIT(pwm->hwpwm); + + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); + if (ret) { + dev_err(chip->dev, "error writing PWM#%u control\n", + pwm->hwpwm); + return ret; + } + + return 0; +} + +static void stmpe_24xx_pwm_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + u8 value; + int ret; + + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); + if (ret < 0) { + dev_err(chip->dev, "error reading PWM#%u control\n", + pwm->hwpwm); + return; + } + + value = ret & ~BIT(pwm->hwpwm); + + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); + if (ret) { + dev_err(chip->dev, "error writing PWM#%u control\n", + pwm->hwpwm); + return; + } +} + +/* STMPE 24xx PWM instructions */ +#define SMAX 0x007f +#define SMIN 0x00ff +#define GTS 0x0000 +#define LOAD BIT(14) /* Only available on 2403 */ +#define RAMPUP 0x0000 +#define RAMPDOWN BIT(7) +#define PRESCALE_512 BIT(14) +#define STEPTIME_1 BIT(8) +#define BRANCH (BIT(15) | BIT(13)) + +static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + unsigned int i, pin; + u16 program[3] = { + SMAX, + GTS, + GTS, + }; + u8 offset; + int ret; + + /* Make sure we are disabled */ + if (pwm_is_enabled(pwm)) { + stmpe_24xx_pwm_disable(chip, pwm); + } else { + /* Connect the PWM to the pin */ + pin = pwm->hwpwm; + + /* On STMPE2401 and 2403 pins 21,22,23 are used */ + if (stmpe_pwm->stmpe->partnum == STMPE2401 || + stmpe_pwm->stmpe->partnum == STMPE2403) + pin += STMPE_PWM_24XX_PINBASE; + + ret = stmpe_set_altfunc(stmpe_pwm->stmpe, BIT(pin), + STMPE_BLOCK_PWM); + if (ret) { + dev_err(chip->dev, "unable to connect PWM#%u to pin\n", + pwm->hwpwm); + return ret; + } + } + + /* STMPE24XX */ + switch (pwm->hwpwm) { + case 0: + offset = STMPE24XX_PWMIC0; + break; + + case 1: + offset = STMPE24XX_PWMIC1; + break; + + case 2: + offset = STMPE24XX_PWMIC1; + break; + + default: + /* Should not happen as npwm is 3 */ + return -ENODEV; + } + + dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n", + pwm->hwpwm, duty_ns, period_ns); + + if (duty_ns == 0) { + if (stmpe_pwm->stmpe->partnum == STMPE2401) + program[0] = SMAX; /* off all the time */ + + if (stmpe_pwm->stmpe->partnum == STMPE2403) + program[0] = LOAD | 0xff; /* LOAD 0xff */ + + stmpe_pwm->last_duty = 0x00; + } else if (duty_ns == period_ns) { + if (stmpe_pwm->stmpe->partnum == STMPE2401) + program[0] = SMIN; /* on all the time */ + + if (stmpe_pwm->stmpe->partnum == STMPE2403) + program[0] = LOAD | 0x00; /* LOAD 0x00 */ + + stmpe_pwm->last_duty = 0xff; + } else { + u8 value, last = stmpe_pwm->last_duty; + unsigned long duty; + + /* + * Counter goes from 0x00 to 0xff repeatedly at 32768 Hz, + * (means a period of 30517 ns) then this is compared to the + * counter from the ramp, if this is >= PWM counter the output + * is high. With LOAD we can define how much of the cycle it + * is on. + * + * Prescale = 0 -> 2 kHz -> T = 1/f = 488281.25 ns + */ + + /* Scale to 0..0xff */ + duty = duty_ns * 256; + duty = DIV_ROUND_CLOSEST(duty, period_ns); + value = duty; + + if (value == last) { + /* Run the old program */ + if (pwm_is_enabled(pwm)) + stmpe_24xx_pwm_enable(chip, pwm); + + return 0; + } else if (stmpe_pwm->stmpe->partnum == STMPE2403) { + /* STMPE2403 can simply set the right PWM value */ + program[0] = LOAD | value; + program[1] = 0x0000; + } else if (stmpe_pwm->stmpe->partnum == STMPE2401) { + /* STMPE2401 need a complex program */ + u16 incdec = 0x0000; + + if (last < value) + /* Count up */ + incdec = RAMPUP | (value - last); + else + /* Count down */ + incdec = RAMPDOWN | (last - value); + + /* Step to desired value, smoothly */ + program[0] = PRESCALE_512 | STEPTIME_1 | incdec; + + /* Loop eternally to 0x00 */ + program[1] = BRANCH; + } + + dev_dbg(chip->dev, + "PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n", + pwm->hwpwm, value, last, program[0], program[1], + program[2]); + stmpe_pwm->last_duty = value; + } + + /* + * We can write programs of up to 64 16-bit words into this channel. + */ + for (i = 0; i < ARRAY_SIZE(program); i++) { + u8 value; + + value = (program[i] >> 8) & 0xff; + + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); + if (ret) { + dev_err(chip->dev, "error writing register %02x: %d\n", + offset, ret); + return ret; + } + + value = program[i] & 0xff; + + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); + if (ret) { + dev_err(chip->dev, "error writing register %02x: %d\n", + offset, ret); + return ret; + } + } + + /* If we were enabled, re-enable this PWM */ + if (pwm_is_enabled(pwm)) + stmpe_24xx_pwm_enable(chip, pwm); + + /* Sleep for 200ms so we're sure it will take effect */ + msleep(200); + + dev_dbg(chip->dev, "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i); + + return 0; +} + +static const struct pwm_ops stmpe_24xx_pwm_ops = { + .config = stmpe_24xx_pwm_config, + .enable = stmpe_24xx_pwm_enable, + .disable = stmpe_24xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __init stmpe_pwm_probe(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct stmpe_pwm *pwm; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->stmpe = stmpe; + pwm->chip.dev = &pdev->dev; + pwm->chip.base = -1; + + if (stmpe->partnum == STMPE2401 || stmpe->partnum == STMPE2403) { + pwm->chip.ops = &stmpe_24xx_pwm_ops; + pwm->chip.npwm = 3; + } else { + if (stmpe->partnum == STMPE1601) + dev_err(&pdev->dev, "STMPE1601 not yet supported\n"); + else + dev_err(&pdev->dev, "Unknown STMPE PWM\n"); + + return -ENODEV; + } + + ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM); + if (ret) + return ret; + + ret = pwmchip_add(&pwm->chip); + if (ret) { + stmpe_disable(stmpe, STMPE_BLOCK_PWM); + return ret; + } + + platform_set_drvdata(pdev, pwm); + + return 0; +} + +static struct platform_driver stmpe_pwm_driver = { + .driver = { + .name = "stmpe-pwm", + }, +}; +builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); -- cgit v1.2.3 From acfd92fdfb9384d54d9404101e8657e75c2372f3 Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Mon, 27 Jun 2016 09:09:55 -0400 Subject: pwm: lpc32xx: Set PWM_PIN_LEVEL bit to default value The PWM_PIN_LEVEL bit is leave unset by the kernel PWM driver. Prior to commit 08ee77b5a5de27ad63c92262ebcb4efe0da93b58, the PWM_PIN_LEVEL bit was always clear when the PWM was disable and a 0 logic level was apply to the output. According to the LPC32x0 User Manual [1], the default value for bit 30 (PWM_PIN_LEVEL) is 0. This change initialize the pin level to 0 (default value) and update the register value accordingly. [1] http://www.nxp.com/documents/user_manual/UM10326.pdf Signed-off-by: Sylvain Lemieux Signed-off-by: Thierry Reding --- drivers/pwm/pwm-lpc32xx.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c index 4d470c1a406a..a9b3cff96aac 100644 --- a/drivers/pwm/pwm-lpc32xx.c +++ b/drivers/pwm/pwm-lpc32xx.c @@ -25,6 +25,7 @@ struct lpc32xx_pwm_chip { }; #define PWM_ENABLE BIT(31) +#define PWM_PIN_LEVEL BIT(30) #define to_lpc32xx_pwm_chip(_chip) \ container_of(_chip, struct lpc32xx_pwm_chip, chip) @@ -103,6 +104,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) struct lpc32xx_pwm_chip *lpc32xx; struct resource *res; int ret; + u32 val; lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); if (!lpc32xx) @@ -128,6 +130,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) return ret; } + /* When PWM is disable, configure the output to the default value */ + val = readl(lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2)); + val &= ~PWM_PIN_LEVEL; + writel(val, lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2)); + platform_set_drvdata(pdev, lpc32xx); return 0; -- cgit v1.2.3 From c009c56475a08fc8c0e1d4d4161bdcc76f3638b7 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 11 Jul 2016 11:08:29 +0200 Subject: pwm: tegra: Drop NUM_PWM macro This macro is used to initialize the ->npwm field of the PWM chip. Use a literal instead and make all other places rely on ->npwm. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index d4de0607b502..d42b636c9695 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -36,8 +36,6 @@ #define PWM_SCALE_WIDTH 13 #define PWM_SCALE_SHIFT 0 -#define NUM_PWM 4 - struct tegra_pwm_chip { struct pwm_chip chip; struct device *dev; @@ -192,7 +190,7 @@ static int tegra_pwm_probe(struct platform_device *pdev) pwm->chip.dev = &pdev->dev; pwm->chip.ops = &tegra_pwm_ops; pwm->chip.base = -1; - pwm->chip.npwm = NUM_PWM; + pwm->chip.npwm = 4; ret = pwmchip_add(&pwm->chip); if (ret < 0) { @@ -206,12 +204,12 @@ static int tegra_pwm_probe(struct platform_device *pdev) static int tegra_pwm_remove(struct platform_device *pdev) { struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); - int i; + unsigned int i; if (WARN_ON(!pc)) return -ENODEV; - for (i = 0; i < NUM_PWM; i++) { + for (i = 0; i < pc->chip.npwm; i++) { struct pwm_device *pwm = &pc->chip.pwms[i]; if (!pwm_is_enabled(pwm)) -- cgit v1.2.3 From e17c0b2258834b88bdaa282a30e125071d9325fc Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 11 Jul 2016 11:26:52 +0200 Subject: pwm: tegra: Remove useless padding Use single spaces to separate data type from field names in structure definitions. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index d42b636c9695..ec5b79f08f17 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -37,12 +37,12 @@ #define PWM_SCALE_SHIFT 0 struct tegra_pwm_chip { - struct pwm_chip chip; - struct device *dev; + struct pwm_chip chip; + struct device *dev; - struct clk *clk; + struct clk *clk; - void __iomem *mmio_base; + void __iomem *mmio_base; }; static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) -- cgit v1.2.3 From 4f57f5a01fcb4ec9d98e274837c0cd853d447da3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 11 Jul 2016 11:27:29 +0200 Subject: pwm: tegra: Rename mmio_base to regs The former is much longer to type and is ambiguous because the value stored in the field is not the (physical) base address of the memory- mapped I/O registers, but the virtual address of those registers as mapped through the MMU. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index ec5b79f08f17..3cbb32d8064f 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -42,7 +42,7 @@ struct tegra_pwm_chip { struct clk *clk; - void __iomem *mmio_base; + void __iomem *regs; }; static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) @@ -52,13 +52,13 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num) { - return readl(chip->mmio_base + (num << 4)); + return readl(chip->regs + (num << 4)); } static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num, unsigned long val) { - writel(val, chip->mmio_base + (num << 4)); + writel(val, chip->regs + (num << 4)); } static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, @@ -177,9 +177,9 @@ static int tegra_pwm_probe(struct platform_device *pdev) pwm->dev = &pdev->dev; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(pwm->mmio_base)) - return PTR_ERR(pwm->mmio_base); + pwm->regs = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pwm->regs)) + return PTR_ERR(pwm->regs); platform_set_drvdata(pdev, pwm); -- cgit v1.2.3 From 5dfbd2bd5439f1ada5ddaa3883e9e038de5d2abe Mon Sep 17 00:00:00 2001 From: Rohith Seelaboyina Date: Wed, 22 Jun 2016 17:17:19 +0530 Subject: pwm: tegra: Add support for reset control Add reset control of the PWM controller to reset it before accessing the PWM register. Signed-off-by: Rohith Seelaboyina Signed-off-by: Laxman Dewangan Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 3cbb32d8064f..097658e0751b 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -29,6 +29,7 @@ #include #include #include +#include #define PWM_ENABLE (1 << 31) #define PWM_DUTY_WIDTH 8 @@ -41,6 +42,7 @@ struct tegra_pwm_chip { struct device *dev; struct clk *clk; + struct reset_control*rst; void __iomem *regs; }; @@ -187,6 +189,15 @@ static int tegra_pwm_probe(struct platform_device *pdev) if (IS_ERR(pwm->clk)) return PTR_ERR(pwm->clk); + pwm->rst = devm_reset_control_get(&pdev->dev, "pwm"); + if (IS_ERR(pwm->rst)) { + ret = PTR_ERR(pwm->rst); + dev_err(&pdev->dev, "Reset control is not found: %d\n", ret); + return ret; + } + + reset_control_deassert(pwm->rst); + pwm->chip.dev = &pdev->dev; pwm->chip.ops = &tegra_pwm_ops; pwm->chip.base = -1; @@ -195,6 +206,7 @@ static int tegra_pwm_probe(struct platform_device *pdev) ret = pwmchip_add(&pwm->chip); if (ret < 0) { dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + reset_control_assert(pwm->rst); return ret; } @@ -205,10 +217,15 @@ static int tegra_pwm_remove(struct platform_device *pdev) { struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); unsigned int i; + int err; if (WARN_ON(!pc)) return -ENODEV; + err = clk_prepare_enable(pc->clk); + if (err < 0) + return err; + for (i = 0; i < pc->chip.npwm; i++) { struct pwm_device *pwm = &pc->chip.pwms[i]; @@ -221,6 +238,9 @@ static int tegra_pwm_remove(struct platform_device *pdev) clk_disable_unprepare(pc->clk); } + reset_control_assert(pc->rst); + clk_disable_unprepare(pc->clk); + return pwmchip_remove(&pc->chip); } -- cgit v1.2.3 From e0ee1a75f412cc9f3b16127742a30baf975ec51f Mon Sep 17 00:00:00 2001 From: "Victor(Weiguo) Pan" Date: Wed, 22 Jun 2016 17:17:20 +0530 Subject: pwm: tegra: Allow 100 % duty cycle To get 100 % duty cycle (always high), pulse width needs to be set to 256. Signed-off-by: Victor(Weiguo) Pan Signed-off-by: Laxman Dewangan Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 097658e0751b..2026eaa932ae 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -77,7 +77,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the * nearest integer during division. */ - c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1) + period_ns / 2; + c = duty_ns * (1 << PWM_DUTY_WIDTH) + period_ns / 2; do_div(c, period_ns); val = (u32)c << PWM_DUTY_SHIFT; -- cgit v1.2.3 From b979ed531468e9848d95a3b788dd8490927417e3 Mon Sep 17 00:00:00 2001 From: Hyong Bin Kim Date: Wed, 22 Jun 2016 17:17:21 +0530 Subject: pwm: tegra: Avoid overflow when calculating duty cycle duty_ns * (1 << PWM_DUTY_WIDTH) could overflow in integer calculation when the PWM rate is low. Hence do all calculation on unsigned long long to avoid overflow. Signed-off-by: Hyong Bin Kim Signed-off-by: Laxman Dewangan Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 2026eaa932ae..247156149b44 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -67,7 +67,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); - unsigned long long c; + unsigned long long c = duty_ns; unsigned long rate, hz; u32 val = 0; int err; @@ -77,7 +77,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the * nearest integer during division. */ - c = duty_ns * (1 << PWM_DUTY_WIDTH) + period_ns / 2; + c *= (1 << PWM_DUTY_WIDTH); + c += period_ns / 2; do_div(c, period_ns); val = (u32)c << PWM_DUTY_SHIFT; -- cgit v1.2.3 From 57dfd17edaf353d0880bea494be05e8cda82ebc0 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 22 Jun 2016 17:17:22 +0530 Subject: dt-bindings: pwm: tegra: Add compatible string for Tegra186 Tegra186 has 8 different PWM controllers and each controller has only one output. Earlier SoC generations have 4 PWM outputs per controller. Add a device tree compatible string for Tegra186 to be able to differentiate between the two. Signed-off-by: Laxman Dewangan Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt index c52f03b5032f..b4e73778dda3 100644 --- a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt +++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt @@ -1,10 +1,14 @@ Tegra SoC PWFM controller Required properties: -- compatible: For Tegra20, must contain "nvidia,tegra20-pwm". For Tegra30, - must contain "nvidia,tegra30-pwm". Otherwise, must contain - "nvidia,-pwm", plus one of the above, where is tegra114, - tegra124, tegra132, or tegra210. +- compatible: Must be: + - "nvidia,tegra20-pwm": for Tegra20 + - "nvidia,tegra30-pwm", "nvidia,tegra20-pwm": for Tegra30 + - "nvidia,tegra114-pwm", "nvidia,tegra20-pwm": for Tegra114 + - "nvidia,tegra124-pwm", "nvidia,tegra20-pwm": for Tegra124 + - "nvidia,tegra132-pwm", "nvidia,tegra20-pwm": for Tegra132 + - "nvidia,tegra210-pwm", "nvidia,tegra20-pwm": for Tegra210 + - "nvidia,tegra186-pwm": for Tegra186 - reg: physical base address and length of the controller's registers - #pwm-cells: should be 2. See pwm.txt in this directory for a description of the cells format. -- cgit v1.2.3 From e9be88a2f06b89a56499c9bfe494ddc4e5f55f46 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 22 Jun 2016 17:17:23 +0530 Subject: pwm: tegra: Add support for Tegra186 Tegra186 has multiple PWM controllers with only one output instead of one controller with four outputs in earlier SoC generations. Add support for Tegra186 and detect the number of PWM outputs using device tree match data. Signed-off-by: Laxman Dewangan Reviewed-by: Alexandre Courbot Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 247156149b44..e4647840cd6e 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,10 @@ #define PWM_SCALE_WIDTH 13 #define PWM_SCALE_SHIFT 0 +struct tegra_pwm_soc { + unsigned int num_channels; +}; + struct tegra_pwm_chip { struct pwm_chip chip; struct device *dev; @@ -45,6 +50,8 @@ struct tegra_pwm_chip { struct reset_control*rst; void __iomem *regs; + + const struct tegra_pwm_soc *soc; }; static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) @@ -177,6 +184,7 @@ static int tegra_pwm_probe(struct platform_device *pdev) if (!pwm) return -ENOMEM; + pwm->soc = of_device_get_match_data(&pdev->dev); pwm->dev = &pdev->dev; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -202,7 +210,7 @@ static int tegra_pwm_probe(struct platform_device *pdev) pwm->chip.dev = &pdev->dev; pwm->chip.ops = &tegra_pwm_ops; pwm->chip.base = -1; - pwm->chip.npwm = 4; + pwm->chip.npwm = pwm->soc->num_channels; ret = pwmchip_add(&pwm->chip); if (ret < 0) { @@ -245,9 +253,17 @@ static int tegra_pwm_remove(struct platform_device *pdev) return pwmchip_remove(&pc->chip); } +static const struct tegra_pwm_soc tegra20_pwm_soc = { + .num_channels = 4, +}; + +static const struct tegra_pwm_soc tegra186_pwm_soc = { + .num_channels = 1, +}; + static const struct of_device_id tegra_pwm_of_match[] = { - { .compatible = "nvidia,tegra20-pwm" }, - { .compatible = "nvidia,tegra30-pwm" }, + { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc }, + { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc }, { } }; -- cgit v1.2.3 From 48169988cff0cb39e0e16a9fe3e445c812c12960 Mon Sep 17 00:00:00 2001 From: Ryo Kodama Date: Thu, 31 Mar 2016 13:39:11 +0200 Subject: pwm: rcar: Use ARCH_RENESAS Replace ARCH_RCAR_GEN{1,2} with ARCH_RENESAS in order to support R-Car Gen3. Signed-off-by: Ryo Kodama Signed-off-by: Harunobu Kurokawa Signed-off-by: Ulrich Hecht Acked-by: Geert Uytterhoeven Acked-by: Simon Horman Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 59124eb4d592..db32cb8c1aba 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -315,7 +315,7 @@ config PWM_PXA config PWM_RCAR tristate "Renesas R-Car PWM support" - depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST depends on HAS_IOMEM help This driver exposes the PWM Timer controller found in Renesas -- cgit v1.2.3 From 396d502c5167da69096c683c4566388ba42d2623 Mon Sep 17 00:00:00 2001 From: Ulrich Hecht Date: Thu, 31 Mar 2016 13:39:15 +0200 Subject: dt-bindings: pwm: Add R-Car H3 device tree bindings Add device tree bindings for the PWM controller found on R-Car H3 SoCs. The controller is compatible with the one found in earlier generations of R-Car SoCs. Signed-off-by: Ulrich Hecht Acked-by: Geert Uytterhoeven Acked-by: Simon Horman Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt index 0822a083fc57..d6de64335022 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt +++ b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt @@ -7,6 +7,7 @@ Required Properties: - "renesas,pwm-r8a7790": for R-Car H2 - "renesas,pwm-r8a7791": for R-Car M2-W - "renesas,pwm-r8a7794": for R-Car E2 + - "renesas,pwm-r8a7795": for R-Car H3 - reg: base address and length of the registers block for the PWM. - #pwm-cells: should be 2. See pwm.txt in this directory for a description of the cells format. -- cgit v1.2.3 From f718c54c1abab7171d375112bffd199046749953 Mon Sep 17 00:00:00 2001 From: Guillermo Rodriguez Date: Fri, 13 May 2016 13:09:37 +0200 Subject: pwm: atmel: Fix disabling of PWM channels When disabling a PWM channel, the PWM clock was being stopped immediately after writing to PWM_DIS. As a result, the disabling of the PWM channel did not complete properly, and the PWM output might be left at the wrong level. Fix this by waiting for the channel to be effectively disabled (by checking the PWM_SR register) before disabling the clock. Signed-off-by: Guillermo Rodriguez Acked-by: Alexandre Belloni Signed-off-by: Thierry Reding --- drivers/pwm/pwm-atmel.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index 0e4bd4e8e582..f3df529737f2 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -271,6 +271,16 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) mutex_unlock(&atmel_pwm->isr_lock); atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm); + /* + * Wait for the PWM channel disable operation to be effective before + * stopping the clock. + */ + timeout = jiffies + 2 * HZ; + + while ((atmel_pwm_readl(atmel_pwm, PWM_SR) & (1 << pwm->hwpwm)) && + time_before(jiffies, timeout)) + usleep_range(10, 100); + clk_disable(atmel_pwm->clk); } -- cgit v1.2.3 From 313b78efea71229e0c48d50ed5527a3c34f3e4eb Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 11 Jul 2016 12:14:34 +0200 Subject: pwm: atmel: Fix checkpatch warnings Avoid an overly long line by moving a comment around, and remove a use of else-after-return. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-atmel.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index f3df529737f2..8083015b8641 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -64,7 +64,8 @@ struct atmel_pwm_chip { void __iomem *base; unsigned int updated_pwms; - struct mutex isr_lock; /* ISR is cleared when read, ensure only one thread does that */ + /* ISR is cleared when read, ensure only one thread does that */ + struct mutex isr_lock; void (*config)(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long dty, unsigned long prd); @@ -334,6 +335,8 @@ MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); static inline const struct atmel_pwm_data * atmel_pwm_get_driver_data(struct platform_device *pdev) { + const struct platform_device_id *id; + if (pdev->dev.of_node) { const struct of_device_id *match; @@ -342,13 +345,11 @@ atmel_pwm_get_driver_data(struct platform_device *pdev) return NULL; return match->data; - } else { - const struct platform_device_id *id; + } - id = platform_get_device_id(pdev); + id = platform_get_device_id(pdev); - return (struct atmel_pwm_data *)id->driver_data; - } + return (struct atmel_pwm_data *)id->driver_data; } static int atmel_pwm_probe(struct platform_device *pdev) -- cgit v1.2.3 From 017bb04e846be82715769799aafa934feab0e443 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 11 Jul 2016 12:16:01 +0200 Subject: pwm: atmel: Use of_device_get_match_data() Use of_device_get_match_data() instead of an open-coded variant. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-atmel.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index 8083015b8641..e6b8b1b7e6ba 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -337,15 +337,8 @@ atmel_pwm_get_driver_data(struct platform_device *pdev) { const struct platform_device_id *id; - if (pdev->dev.of_node) { - const struct of_device_id *match; - - match = of_match_device(atmel_pwm_dt_ids, &pdev->dev); - if (!match) - return NULL; - - return match->data; - } + if (pdev->dev.of_node) + return of_device_get_match_data(&pdev->dev); id = platform_get_device_id(pdev); -- cgit v1.2.3 From 9798ac6d32c1a32d6d92d853ff507d2d39c4300c Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Fri, 15 Jul 2016 16:28:41 -0700 Subject: mfd: cros_ec: Add cros_ec_cmd_xfer_status() helper So that callers of cros_ec_cmd_xfer() don't have to repeat boilerplate code when checking for errors from the EC side. Signed-off-by: Tomeu Vizoso Reviewed-by: Benson Leung Signed-off-by: Brian Norris Acked-by: Lee Jones Tested-by: Enric Balletbo i Serra Signed-off-by: Thierry Reding --- drivers/platform/chrome/cros_ec_proto.c | 17 +++++++++++++++++ include/linux/mfd/cros_ec.h | 15 +++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index b6e161f71b26..6c084b266651 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -380,3 +380,20 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, return ret; } EXPORT_SYMBOL(cros_ec_cmd_xfer); + +int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + ret = cros_ec_cmd_xfer(ec_dev, msg); + if (ret < 0) { + dev_err(ec_dev->dev, "Command xfer error (err:%d)\n", ret); + } else if (msg->result != EC_RES_SUCCESS) { + dev_dbg(ec_dev->dev, "Command result (err: %d)\n", msg->result); + return -EPROTO; + } + + return ret; +} +EXPORT_SYMBOL(cros_ec_cmd_xfer_status); diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 64184d27e3cd..d641a18abacb 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -225,6 +225,21 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev, int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, struct cros_ec_command *msg); +/** + * cros_ec_cmd_xfer_status - Send a command to the ChromeOS EC + * + * This function is identical to cros_ec_cmd_xfer, except it returns success + * status only if both the command was transmitted successfully and the EC + * replied with success status. It's not necessary to check msg->result when + * using this function. + * + * @ec_dev: EC device + * @msg: Message to write + * @return: Num. of bytes transferred on success, <0 on failure + */ +int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg); + /** * cros_ec_remove - Remove a ChromeOS EC * -- cgit v1.2.3 From 2b66bd692c40034d42d905f37f7f20c3540f185e Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 15 Jul 2016 16:28:42 -0700 Subject: mfd: cros_ec: Add EC_PWM function definitions The EC_CMD_PWM_{GET,SET}_DUTY commands allow us to control a PWM that is attached to the EC, rather than the main host SoC. The API provides functionality-based (e.g., keyboard light, backlight) or index-based addressing of the PWM(s). Duty cycles are represented by a 16-bit value, where 0 maps to 0% duty cycle and U16_MAX maps to 100%. The period cannot be controlled. This command set is more generic than, e.g., EC_CMD_PWM_{GET,SET}_KEYBOARD_BACKLIGHT and could possibly used to replace it on future products. Let's update the command header to include the definitions. Signed-off-by: Brian Norris Acked-by: Lee Jones Signed-off-by: Thierry Reding --- include/linux/mfd/cros_ec_commands.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h index 13b630c10d4c..7e7a8d4b4551 100644 --- a/include/linux/mfd/cros_ec_commands.h +++ b/include/linux/mfd/cros_ec_commands.h @@ -949,6 +949,37 @@ struct ec_params_pwm_set_fan_duty { uint32_t percent; } __packed; +#define EC_CMD_PWM_SET_DUTY 0x25 +/* 16 bit duty cycle, 0xffff = 100% */ +#define EC_PWM_MAX_DUTY 0xffff + +enum ec_pwm_type { + /* All types, indexed by board-specific enum pwm_channel */ + EC_PWM_TYPE_GENERIC = 0, + /* Keyboard backlight */ + EC_PWM_TYPE_KB_LIGHT, + /* Display backlight */ + EC_PWM_TYPE_DISPLAY_LIGHT, + EC_PWM_TYPE_COUNT, +}; + +struct ec_params_pwm_set_duty { + uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */ + uint8_t pwm_type; /* ec_pwm_type */ + uint8_t index; /* Type-specific index, or 0 if unique */ +} __packed; + +#define EC_CMD_PWM_GET_DUTY 0x26 + +struct ec_params_pwm_get_duty { + uint8_t pwm_type; /* ec_pwm_type */ + uint8_t index; /* Type-specific index, or 0 if unique */ +} __packed; + +struct ec_response_pwm_get_duty { + uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */ +} __packed; + /*****************************************************************************/ /* * Lightbar commands. This looks worse than it is. Since we only use one HOST -- cgit v1.2.3 From 9e60f50b4a79ae2df791d89d08cf2b78ad7629bd Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 15 Jul 2016 16:28:43 -0700 Subject: dt-bindings: pwm: Add binding for ChromeOS EC PWM The ChromeOS Embedded Controller can support controlling its attached PWMs via its host-command interface. The number of supported PWMs varies on a per-board basis, but we can autodetect this by checking the error codes, so we don't need an extra property for this. And because the EC only allows specifying the duty cycle and not the period, we don't specify the period via pwm-cells, and instead have only support for one cell -- to specify the index. Signed-off-by: Brian Norris Acked-by: Rob Herring Signed-off-by: Thierry Reding --- .../devicetree/bindings/pwm/google,cros-ec-pwm.txt | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt new file mode 100644 index 000000000000..472bd46ab5a4 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt @@ -0,0 +1,23 @@ +* PWM controlled by ChromeOS EC + +Google's ChromeOS EC PWM is a simple PWM attached to the Embedded Controller +(EC) and controlled via a host-command interface. + +An EC PWM node should be only found as a sub-node of the EC node (see +Documentation/devicetree/bindings/mfd/cros-ec.txt). + +Required properties: +- compatible: Must contain "google,cros-ec-pwm" +- #pwm-cells: Should be 1. The cell specifies the PWM index. + +Example: + cros-ec@0 { + compatible = "google,cros-ec-spi"; + + ... + + cros_ec_pwm: ec-pwm { + compatible = "google,cros-ec-pwm"; + #pwm-cells = <1>; + }; + }; -- cgit v1.2.3 From 1f0d3bb02785f698dc273b9006a473194c32f874 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 15 Jul 2016 16:28:44 -0700 Subject: pwm: Add ChromeOS EC PWM driver Use the new ChromeOS EC EC_CMD_PWM_{GET,SET}_DUTY commands to control one or more PWMs attached to the Embedded Controller. Because the EC allows us to modify the duty cycle (as a percentage, where U16_MAX is 100%) but not the period, we assign the period a fixed value of EC_PWM_MAX_DUTY and reject all attempts to change it. This driver supports only device tree at the moment, because that provides a very flexible way of describing the relationship between PWMs and their consumer devices (e.g., backlight). On a non-DT system, we'll probably want to use the non-GENERIC addressing (i.e., we'll need to make special device instances that will use EC_PWM_TYPE_KB_LIGHT or EC_PWM_TYPE_DISPLAY_LIGHT), as well as the relatively inflexible pwm_lookup infrastructure for matching devices. Defer that work for now. Signed-off-by: Brian Norris Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 7 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-cros-ec.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 drivers/pwm/pwm-cros-ec.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index db32cb8c1aba..80a566a00d04 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -147,6 +147,13 @@ config PWM_CRC Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM control. +config PWM_CROS_EC + tristate "ChromeOS EC PWM driver" + depends on MFD_CROS_EC + help + PWM driver for exposing a PWM attached to the ChromeOS Embedded + Controller. + config PWM_EP93XX tristate "Cirrus Logic EP93xx PWM support" depends on ARCH_EP93XX diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 98ae3cb4e3e7..feef1dd29f73 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o obj-$(CONFIG_PWM_CRC) += pwm-crc.o +obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o obj-$(CONFIG_PWM_IMG) += pwm-img.o diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c new file mode 100644 index 000000000000..99b9acc1a420 --- /dev/null +++ b/drivers/pwm/pwm-cros-ec.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 Google, Inc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published by + * the Free Software Foundation. + * + * Expose a PWM controlled by the ChromeOS EC to the host processor. + */ + +#include +#include +#include +#include +#include +#include + +/** + * struct cros_ec_pwm_device - Driver data for EC PWM + * + * @dev: Device node + * @ec: Pointer to EC device + * @chip: PWM controller chip + */ +struct cros_ec_pwm_device { + struct device *dev; + struct cros_ec_device *ec; + struct pwm_chip chip; +}; + +static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *c) +{ + return container_of(c, struct cros_ec_pwm_device, chip); +} + +static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty) +{ + struct { + struct cros_ec_command msg; + struct ec_params_pwm_set_duty params; + } buf; + struct ec_params_pwm_set_duty *params = &buf.params; + struct cros_ec_command *msg = &buf.msg; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_SET_DUTY; + msg->insize = 0; + msg->outsize = sizeof(*params); + + params->duty = duty; + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + return cros_ec_cmd_xfer_status(ec, msg); +} + +static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index, + u32 *result) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_pwm_get_duty params; + struct ec_response_pwm_get_duty resp; + }; + } buf; + struct ec_params_pwm_get_duty *params = &buf.params; + struct ec_response_pwm_get_duty *resp = &buf.resp; + struct cros_ec_command *msg = &buf.msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_GET_DUTY; + msg->insize = sizeof(*params); + msg->outsize = sizeof(*resp); + + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (result) + *result = msg->result; + if (ret < 0) + return ret; + + return resp->duty; +} + +static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index) +{ + return __cros_ec_pwm_get_duty(ec, index, NULL); +} + +static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); + int duty_cycle; + + /* The EC won't let us change the period */ + if (state->period != EC_PWM_MAX_DUTY) + return -EINVAL; + + /* + * EC doesn't separate the concept of duty cycle and enabled, but + * kernel does. Translate. + */ + duty_cycle = state->enabled ? state->duty_cycle : 0; + + return cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle); +} + +static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); + int ret; + + ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm); + if (ret < 0) { + dev_err(chip->dev, "error getting initial duty: %d\n", ret); + return; + } + + state->enabled = (ret > 0); + state->period = EC_PWM_MAX_DUTY; + + /* Note that "disabled" and "duty cycle == 0" are treated the same */ + state->duty_cycle = ret; +} + +static struct pwm_device * +cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + /* The EC won't let us change the period */ + pwm->args.period = EC_PWM_MAX_DUTY; + + return pwm; +} + +static const struct pwm_ops cros_ec_pwm_ops = { + .get_state = cros_ec_pwm_get_state, + .apply = cros_ec_pwm_apply, + .owner = THIS_MODULE, +}; + +static int cros_ec_num_pwms(struct cros_ec_device *ec) +{ + int i, ret; + + /* The index field is only 8 bits */ + for (i = 0; i <= U8_MAX; i++) { + u32 result = 0; + + ret = __cros_ec_pwm_get_duty(ec, i, &result); + /* We want to parse EC protocol errors */ + if (ret < 0 && !(ret == -EPROTO && result)) + return ret; + + /* + * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM + * responses; everything else is treated as an error. + */ + if (result == EC_RES_INVALID_COMMAND) + return -ENODEV; + else if (result == EC_RES_INVALID_PARAM) + return i; + else if (result) + return -EPROTO; + } + + return U8_MAX; +} + +static int cros_ec_pwm_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct cros_ec_pwm_device *ec_pwm; + struct pwm_chip *chip; + int ret; + + if (!ec) { + dev_err(dev, "no parent EC device\n"); + return -EINVAL; + } + + ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL); + if (!ec_pwm) + return -ENOMEM; + chip = &ec_pwm->chip; + ec_pwm->ec = ec; + + /* PWM chip */ + chip->dev = dev; + chip->ops = &cros_ec_pwm_ops; + chip->of_xlate = cros_ec_pwm_xlate; + chip->of_pwm_n_cells = 1; + chip->base = -1; + ret = cros_ec_num_pwms(ec); + if (ret < 0) { + dev_err(dev, "Couldn't find PWMs: %d\n", ret); + return ret; + } + chip->npwm = ret; + dev_dbg(dev, "Probed %u PWMs\n", chip->npwm); + + ret = pwmchip_add(chip); + if (ret < 0) { + dev_err(dev, "cannot register PWM: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, ec_pwm); + + return ret; +} + +static int cros_ec_pwm_remove(struct platform_device *dev) +{ + struct cros_ec_pwm_device *ec_pwm = platform_get_drvdata(dev); + struct pwm_chip *chip = &ec_pwm->chip; + + return pwmchip_remove(chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id cros_ec_pwm_of_match[] = { + { .compatible = "google,cros-ec-pwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match); +#endif + +static struct platform_driver cros_ec_pwm_driver = { + .probe = cros_ec_pwm_probe, + .remove = cros_ec_pwm_remove, + .driver = { + .name = "cros-ec-pwm", + .of_match_table = of_match_ptr(cros_ec_pwm_of_match), + }, +}; +module_platform_driver(cros_ec_pwm_driver); + +MODULE_ALIAS("platform:cros-ec-pwm"); +MODULE_DESCRIPTION("ChromeOS EC PWM driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From ea398e28739e25651ede7ddf5aeb57cbcbc8ca7d Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:21 +0200 Subject: regulator: pwm: Support extra continuous mode cases The continuous mode allows one to declare a PWM regulator without having to declare the voltage <-> dutycycle association table. It works fine as long as your voltage(dutycycle) function is linear, but also has the following constraints: - dutycycle for min_uV = 0% - dutycycle for max_uV = 100% - dutycycle for min_uV < dutycycle for max_uV While the linearity constraint is acceptable for now, we sometimes need to restrict of the PWM range (to limit the maximum/minimum voltage for example) or have a min_uV_dutycycle > max_uV_dutycycle (this could be tweaked with PWM polarity, but not all PWMs support inverted polarity). Add the pwm-dutycycle-range and pwm-dutycycle-unit DT properties to define such constraints. If those properties are not defined, the PWM regulator use the default pwm-dutycycle-range = <0 100> and pwm-dutycycle-unit = <100> values (existing behavior). Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 92 ++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index ae0bebbfda9d..c24524242da2 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -22,6 +22,12 @@ #include #include +struct pwm_continuous_reg_data { + unsigned int min_uV_dutycycle; + unsigned int max_uV_dutycycle; + unsigned int dutycycle_unit; +}; + struct pwm_regulator_data { /* Shared */ struct pwm_device *pwm; @@ -29,6 +35,9 @@ struct pwm_regulator_data { /* Voltage table */ struct pwm_voltages *duty_cycle_table; + /* Continuous mode info */ + struct pwm_continuous_reg_data continuous; + /* regulator descriptor */ struct regulator_desc desc; @@ -145,32 +154,78 @@ static int pwm_regulator_is_enabled(struct regulator_dev *dev) static int pwm_regulator_get_voltage(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle; + unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle; + unsigned int duty_unit = drvdata->continuous.dutycycle_unit; int min_uV = rdev->constraints->min_uV; - int diff = rdev->constraints->max_uV - min_uV; + int max_uV = rdev->constraints->max_uV; + int diff_uV = max_uV - min_uV; struct pwm_state pstate; + unsigned int diff_duty; + unsigned int voltage; pwm_get_state(drvdata->pwm, &pstate); - return min_uV + pwm_get_relative_duty_cycle(&pstate, diff); + voltage = pwm_get_relative_duty_cycle(&pstate, duty_unit); + + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) { + voltage = min_uV_duty - voltage; + diff_duty = min_uV_duty - max_uV_duty; + } else { + voltage = voltage - min_uV_duty; + diff_duty = max_uV_duty - min_uV_duty; + } + + voltage = DIV_ROUND_CLOSEST_ULL((u64)voltage * diff_uV, diff_duty); + + return voltage + min_uV; } static int pwm_regulator_set_voltage(struct regulator_dev *rdev, - int min_uV, int max_uV, - unsigned *selector) + int req_min_uV, int req_max_uV, + unsigned int *selector) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle; + unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle; + unsigned int duty_unit = drvdata->continuous.dutycycle_unit; unsigned int ramp_delay = rdev->constraints->ramp_delay; - unsigned int req_diff = min_uV - rdev->constraints->min_uV; + int min_uV = rdev->constraints->min_uV; + int max_uV = rdev->constraints->max_uV; + int diff_uV = max_uV - min_uV; struct pwm_state pstate; - unsigned int diff; int old_uV = pwm_regulator_get_voltage(rdev); + unsigned int diff_duty; + unsigned int dutycycle; int ret; pwm_init_state(drvdata->pwm, &pstate); - diff = rdev->constraints->max_uV - rdev->constraints->min_uV; - /* We pass diff as the scale to get a uV precision. */ - pwm_set_relative_duty_cycle(&pstate, req_diff, diff); + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) + diff_duty = min_uV_duty - max_uV_duty; + else + diff_duty = max_uV_duty - min_uV_duty; + + dutycycle = DIV_ROUND_CLOSEST_ULL((u64)(req_min_uV - min_uV) * + diff_duty, + diff_uV); + + if (max_uV_duty < min_uV_duty) + dutycycle = min_uV_duty - dutycycle; + else + dutycycle = min_uV_duty + dutycycle; + + pwm_set_relative_duty_cycle(&pstate, dutycycle, duty_unit); ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { @@ -182,7 +237,7 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev, return 0; /* Ramp delay is in uV/uS. Adjust to uS and delay */ - ramp_delay = DIV_ROUND_UP(abs(min_uV - old_uV), ramp_delay); + ramp_delay = DIV_ROUND_UP(abs(req_min_uV - old_uV), ramp_delay); usleep_range(ramp_delay, ramp_delay + DIV_ROUND_UP(ramp_delay, 10)); return 0; @@ -255,11 +310,28 @@ static int pwm_regulator_init_table(struct platform_device *pdev, static int pwm_regulator_init_continuous(struct platform_device *pdev, struct pwm_regulator_data *drvdata) { + u32 dutycycle_range[2] = { 0, 100 }; + u32 dutycycle_unit = 100; + memcpy(&drvdata->ops, &pwm_regulator_voltage_continuous_ops, sizeof(drvdata->ops)); drvdata->desc.ops = &drvdata->ops; drvdata->desc.continuous_voltage_range = true; + of_property_read_u32_array(pdev->dev.of_node, + "pwm-dutycycle-range", + dutycycle_range, 2); + of_property_read_u32(pdev->dev.of_node, "pwm-dutycycle-unit", + &dutycycle_unit); + + if (dutycycle_range[0] > dutycycle_unit || + dutycycle_range[1] > dutycycle_unit) + return -EINVAL; + + drvdata->continuous.dutycycle_unit = dutycycle_unit; + drvdata->continuous.min_uV_dutycycle = dutycycle_range[0]; + drvdata->continuous.max_uV_dutycycle = dutycycle_range[1]; + return 0; } -- cgit v1.2.3 From 58fd822b2e344edae6b4dbc09b19bd0c4a2f8f60 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:22 +0200 Subject: regulator: pwm: Document pwm-dutycycle-unit and pwm-dutycycle-range Document the pwm-dutycycle-unit and pwm-dutycycle-range properties. Signed-off-by: Boris Brezillon Acked-by: Brian Norris Acked-by: Rob Herring Acked-by: Mark Brown Signed-off-by: Thierry Reding --- .../devicetree/bindings/regulator/pwm-regulator.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/regulator/pwm-regulator.txt b/Documentation/devicetree/bindings/regulator/pwm-regulator.txt index dd6f59cf1455..3aeba9f86ed8 100644 --- a/Documentation/devicetree/bindings/regulator/pwm-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/pwm-regulator.txt @@ -34,6 +34,18 @@ Only required for Voltage Table Mode: First cell is voltage in microvolts (uV) Second cell is duty-cycle in percent (%) +Optional properties for Continuous mode: +- pwm-dutycycle-unit: Integer value encoding the duty cycle unit. If not + defined, <100> is assumed, meaning that + pwm-dutycycle-range contains values expressed in + percent. + +- pwm-dutycycle-range: Should contain 2 entries. The first entry is encoding + the dutycycle for regulator-min-microvolt and the + second one the dutycycle for regulator-max-microvolt. + Duty cycle values are expressed in pwm-dutycycle-unit. + If not defined, <0 100> is assumed. + NB: To be clear, if voltage-table is provided, then the device will be used in Voltage Table Mode. If no voltage-table is provided, then the device will be used in Continuous Voltage Mode. @@ -53,6 +65,13 @@ Continuous Voltage With Enable GPIO Example: regulator-min-microvolt = <1016000>; regulator-max-microvolt = <1114000>; regulator-name = "vdd_logic"; + /* unit == per-mille */ + pwm-dutycycle-unit = <1000>; + /* + * Inverted PWM logic, and the duty cycle range is limited + * to 30%-70%. + */ + pwm-dutycycle-range <700 300>; /* */ }; Voltage Table Example: -- cgit v1.2.3