From 0527eb372301bfd9b84160adb6c132b443ae3013 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Tue, 2 May 2017 19:35:37 +0530 Subject: [PATCH 01/14] pwm: tegra: Set maximum pwm clock source per SoC tapeout The PWM hardware IP is taped-out with different maximum frequency on different SoCs. From HW team: Before Tegra186, it is 48 MHz. In Tegra186, it is 102 MHz. Add support to limit the clock source frequency to the maximum IP supported frequency. Provide these values via SoC chipdata. Signed-off-by: Laxman Dewangan Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 8c6ed556db28..e9b33f09ff09 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -41,6 +41,9 @@ struct tegra_pwm_soc { unsigned int num_channels; + + /* Maximum IP frequency for given SoCs */ + unsigned long max_frequency; }; struct tegra_pwm_chip { @@ -201,7 +204,18 @@ static int tegra_pwm_probe(struct platform_device *pdev) if (IS_ERR(pwm->clk)) return PTR_ERR(pwm->clk); - /* Read PWM clock rate from source */ + /* Set maximum frequency of the IP */ + ret = clk_set_rate(pwm->clk, pwm->soc->max_frequency); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret); + return ret; + } + + /* + * The requested and configured frequency may differ due to + * clock register resolutions. Get the configured frequency + * so that PWM period can be calculated more accurately. + */ pwm->clk_rate = clk_get_rate(pwm->clk); pwm->rst = devm_reset_control_get(&pdev->dev, "pwm"); @@ -273,10 +287,12 @@ static int tegra_pwm_resume(struct device *dev) static const struct tegra_pwm_soc tegra20_pwm_soc = { .num_channels = 4, + .max_frequency = 48000000UL, }; static const struct tegra_pwm_soc tegra186_pwm_soc = { .num_channels = 1, + .max_frequency = 102000000UL, }; static const struct of_device_id tegra_pwm_of_match[] = { From df8f4c6c0275216498340a64c7d2674377bd3e78 Mon Sep 17 00:00:00 2001 From: Ulrich Hecht Date: Thu, 27 Apr 2017 16:37:43 +0200 Subject: [PATCH 02/14] dt-bindings: pwm: Add R-Car M3-W device tree bindings Add device tree bindings for the PWM controller found on R-Car M3-W SoCs. Signed-off-by: Ulrich Hecht Reviewed-by: Geert Uytterhoeven Reviewed-by: Simon Horman Acked-by: Rob Herring 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 d6de64335022..7e94b802395d 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt +++ b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt @@ -8,6 +8,7 @@ Required Properties: - "renesas,pwm-r8a7791": for R-Car M2-W - "renesas,pwm-r8a7794": for R-Car E2 - "renesas,pwm-r8a7795": for R-Car H3 + - "renesas,pwm-r8a7796": for R-Car M3-W - 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. From d7f673d8a0776f3f791fd795b409060ba808b62a Mon Sep 17 00:00:00 2001 From: Fabrice GASNIER Date: Wed, 14 Jun 2017 17:13:16 +0200 Subject: [PATCH 03/14] dt-bindings: pwm: Update STM32 timers clock names Clock name has been updated during driver/DT binding review: https://lkml.org/lkml/2016/12/13/718 Update DT binding doc to reflect this. Fixes: cd9a99c2f8e8 (dt-bindings: pwm: Add STM32 bindings) Signed-off-by: Fabrice Gasnier Acked-by: Rob Herring Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/pwm-stm32.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pwm/pwm-stm32.txt b/Documentation/devicetree/bindings/pwm/pwm-stm32.txt index 6dd040363e5e..3e6d55018d7a 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-stm32.txt +++ b/Documentation/devicetree/bindings/pwm/pwm-stm32.txt @@ -24,7 +24,7 @@ Example: compatible = "st,stm32-timers"; reg = <0x40010000 0x400>; clocks = <&rcc 0 160>; - clock-names = "clk_int"; + clock-names = "int"; pwm { compatible = "st,stm32-pwm"; From 0e1921dcd825004551bd4dd2df99697641434a52 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 16 May 2017 11:40:09 +0200 Subject: [PATCH 04/14] pwm: bfin: Remove unneeded error message Omit an extra message for a memory allocation failure in this function. This issue was detected by using the Coccinelle software. Link: http://events.linuxfoundation.org/sites/events/files/slides/LCJ16-Refactor_Strings-WSang_0.pdf Signed-off-by: Markus Elfring Signed-off-by: Thierry Reding --- drivers/pwm/pwm-bfin.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c index d2ed0a2a18e8..a9a88137f2cb 100644 --- a/drivers/pwm/pwm-bfin.c +++ b/drivers/pwm/pwm-bfin.c @@ -118,10 +118,8 @@ static int bfin_pwm_probe(struct platform_device *pdev) int ret; pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); - if (!pwm) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!pwm) return -ENOMEM; - } platform_set_drvdata(pdev, pwm); From c571123c8a94cfbc88e70be4e8883529181417ce Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Sat, 17 Jun 2017 12:26:44 -0300 Subject: [PATCH 05/14] pwm: Standardize document format Each text file under Documentation follows a different format. Some don't even have titles! Change its representation to follow the adopted standard, using ReST markup for it to be parseable by Sphinx: - mark document title; - mark literal blocks; - better format the parameters. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Thierry Reding --- Documentation/pwm.txt | 46 ++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt index 789b27c6ec99..8fbf0aa3ba2d 100644 --- a/Documentation/pwm.txt +++ b/Documentation/pwm.txt @@ -1,4 +1,6 @@ +====================================== Pulse Width Modulation (PWM) interface +====================================== This provides an overview about the Linux PWM interface @@ -16,7 +18,7 @@ Users of the legacy PWM API use unique IDs to refer to PWM devices. Instead of referring to a PWM device via its unique ID, board setup code should instead register a static mapping that can be used to match PWM -consumers to providers, as given in the following example: +consumers to providers, as given in the following example:: static struct pwm_lookup board_pwm_lookup[] = { PWM_LOOKUP("tegra-pwm", 0, "pwm-backlight", NULL, @@ -40,9 +42,9 @@ New users should use the pwm_get() function and pass to it the consumer device or a consumer name. pwm_put() is used to free the PWM device. Managed variants of these functions, devm_pwm_get() and devm_pwm_put(), also exist. -After being requested, a PWM has to be configured using: +After being requested, a PWM has to be configured using:: -int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state); + int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state); This API controls both the PWM period/duty_cycle config and the enable/disable state. @@ -72,11 +74,14 @@ interface is provided to use the PWMs from userspace. It is exposed at pwmchipN, where N is the base of the PWM chip. Inside the directory you will find: -npwm - The number of PWM channels this chip supports (read-only). + npwm + The number of PWM channels this chip supports (read-only). -export - Exports a PWM channel for use with sysfs (write-only). + export + Exports a PWM channel for use with sysfs (write-only). -unexport - Unexports a PWM channel from sysfs (write-only). + unexport + Unexports a PWM channel from sysfs (write-only). The PWM channels are numbered using a per-chip index from 0 to npwm-1. @@ -84,21 +89,26 @@ When a PWM channel is exported a pwmX directory will be created in the pwmchipN directory it is associated with, where X is the number of the channel that was exported. The following properties will then be available: -period - The total period of the PWM signal (read/write). - Value is in nanoseconds and is the sum of the active and inactive - time of the PWM. + period + The total period of the PWM signal (read/write). + Value is in nanoseconds and is the sum of the active and inactive + time of the PWM. -duty_cycle - The active time of the PWM signal (read/write). - Value is in nanoseconds and must be less than the period. + duty_cycle + The active time of the PWM signal (read/write). + Value is in nanoseconds and must be less than the period. -polarity - Changes the polarity of the PWM signal (read/write). - Writes to this property only work if the PWM chip supports changing - the polarity. The polarity can only be changed if the PWM is not - enabled. Value is the string "normal" or "inversed". + polarity + Changes the polarity of the PWM signal (read/write). + Writes to this property only work if the PWM chip supports changing + the polarity. The polarity can only be changed if the PWM is not + enabled. Value is the string "normal" or "inversed". -enable - Enable/disable the PWM signal (read/write). - 0 - disabled - 1 - enabled + enable + Enable/disable the PWM signal (read/write). + + - 0 - disabled + - 1 - enabled Implementing a PWM driver ------------------------- From 93c292ef3cbb46db78853763fc4d9a5b9dfb97d5 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Tue, 23 May 2017 18:05:03 +0200 Subject: [PATCH 06/14] pwm: Silently error out on EPROBE_DEFER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In of_pwm_get(), if we fail to get the PWM chip due to probe deferal, we shouldn't print an error message. Just be silent in this case. Signed-off-by: Jerome Brunet Reviewed-by: Andreas Färber Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index a0860b30bd93..1581f6ab1b1f 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -678,7 +678,9 @@ struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id) pc = of_node_to_pwmchip(args.np); if (IS_ERR(pc)) { - pr_err("%s(): PWM chip not found\n", __func__); + if (PTR_ERR(pc) != -EPROBE_DEFER) + pr_err("%s(): PWM chip not found\n", __func__); + pwm = ERR_CAST(pc); goto put; } From c034a6fda0179a85cd571c386ed6748188b0a79c Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 13 Jun 2017 15:26:41 +0530 Subject: [PATCH 07/14] pwm: hibvt: Constify hibvt_pwm_ops File size before: text data bss dec hex filename 1510 296 0 1806 70e drivers/pwm/pwm-hibvt.o File size After adding 'const': text data bss dec hex filename 1606 192 0 1798 706 drivers/pwm/pwm-hibvt.o Signed-off-by: Arvind Yadav Signed-off-by: Thierry Reding --- drivers/pwm/pwm-hibvt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c index d0e8f8542626..8dadc58d6cdf 100644 --- a/drivers/pwm/pwm-hibvt.c +++ b/drivers/pwm/pwm-hibvt.c @@ -165,7 +165,7 @@ static int hibvt_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -static struct pwm_ops hibvt_pwm_ops = { +static const struct pwm_ops hibvt_pwm_ops = { .get_state = hibvt_pwm_get_state, .apply = hibvt_pwm_apply, From 93e0dfb2c52f9cb7a7899156475b4e7b0bee7de3 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 30 May 2017 21:32:07 +0200 Subject: [PATCH 08/14] pwm: sun4i: Improve hardware read out Implement .get_state instead of only reading the polarity at probe time. This allows to get the proper state, period and duty cycle. Signed-off-by: Alexandre Belloni Reviewed-by: Chen-Yu Tsai Acked-by: Maxime Ripard Signed-off-by: Thierry Reding --- drivers/pwm/pwm-sun4i.c | 65 +++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index 1284ffa05921..175a69245a8a 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -44,6 +44,10 @@ #define PWM_DTY_MASK GENMASK(15, 0) +#define PWM_REG_PRD(reg) ((((reg) >> 16) & PWM_PRD_MASK) + 1) +#define PWM_REG_DTY(reg) ((reg) & PWM_DTY_MASK) +#define PWM_REG_PRESCAL(reg, chan) (((reg) >> ((chan) * PWMCH_OFFSET)) & PWM_PRESCAL_MASK) + #define BIT_CH(bit, chan) ((bit) << ((chan) * PWMCH_OFFSET)) static const u32 prescaler_table[] = { @@ -96,6 +100,46 @@ static inline void sun4i_pwm_writel(struct sun4i_pwm_chip *chip, writel(val, chip->base + offset); } +static void sun4i_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + u64 clk_rate, tmp; + u32 val; + unsigned int prescaler; + + clk_rate = clk_get_rate(sun4i_pwm->clk); + + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + + if ((val == PWM_PRESCAL_MASK) && sun4i_pwm->data->has_prescaler_bypass) + prescaler = 1; + else + prescaler = prescaler_table[PWM_REG_PRESCAL(val, pwm->hwpwm)]; + + if (prescaler == 0) + return; + + if (val & BIT_CH(PWM_ACT_STATE, pwm->hwpwm)) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + if (val & BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm)) + state->enabled = true; + else + state->enabled = false; + + val = sun4i_pwm_readl(sun4i_pwm, PWM_CH_PRD(pwm->hwpwm)); + + tmp = prescaler * NSEC_PER_SEC * PWM_REG_DTY(val); + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + tmp = prescaler * NSEC_PER_SEC * PWM_REG_PRD(val); + state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); +} + static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -257,6 +301,7 @@ static const struct pwm_ops sun4i_pwm_ops = { .set_polarity = sun4i_pwm_set_polarity, .enable = sun4i_pwm_enable, .disable = sun4i_pwm_disable, + .get_state = sun4i_pwm_get_state, .owner = THIS_MODULE, }; @@ -316,8 +361,7 @@ static int sun4i_pwm_probe(struct platform_device *pdev) { struct sun4i_pwm_chip *pwm; struct resource *res; - u32 val; - int i, ret; + int ret; const struct of_device_id *match; match = of_match_device(sun4i_pwm_dt_ids, &pdev->dev); @@ -353,24 +397,7 @@ static int sun4i_pwm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pwm); - ret = clk_prepare_enable(pwm->clk); - if (ret) { - dev_err(&pdev->dev, "failed to enable PWM clock\n"); - goto clk_error; - } - - val = sun4i_pwm_readl(pwm, PWM_CTRL_REG); - for (i = 0; i < pwm->chip.npwm; i++) - if (!(val & BIT_CH(PWM_ACT_STATE, i))) - pwm_set_polarity(&pwm->chip.pwms[i], - PWM_POLARITY_INVERSED); - clk_disable_unprepare(pwm->clk); - return 0; - -clk_error: - pwmchip_remove(&pwm->chip); - return ret; } static int sun4i_pwm_remove(struct platform_device *pdev) From c32c5c50d4fe156da1ecd32f2a16c1b8fcfba392 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 30 May 2017 21:32:08 +0200 Subject: [PATCH 09/14] pwm: sun4i: Switch to atomic PWM Switch the driver to atomic PWM. This makes it easier to wait a proper amount of time when changing the duty cycle before disabling the channel (main use case is switching the duty cycle to 0 before disabling). Signed-off-by: Alexandre Belloni Acked-by: Maxime Ripard Signed-off-by: Thierry Reding --- drivers/pwm/pwm-sun4i.c | 166 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index 175a69245a8a..cd8737d0804f 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -8,8 +8,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -81,6 +83,8 @@ struct sun4i_pwm_chip { void __iomem *base; spinlock_t ctrl_lock; const struct sun4i_pwm_data *data; + unsigned long next_period[2]; + bool needs_delay[2]; }; static inline struct sun4i_pwm_chip *to_sun4i_pwm_chip(struct pwm_chip *chip) @@ -140,6 +144,167 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip, state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); } +static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm, + struct pwm_state *state, + u32 *dty, u32 *prd, unsigned int *prsclr) +{ + u64 clk_rate, div = 0; + unsigned int pval, prescaler = 0; + + clk_rate = clk_get_rate(sun4i_pwm->clk); + + if (sun4i_pwm->data->has_prescaler_bypass) { + /* First, test without any prescaler when available */ + prescaler = PWM_PRESCAL_MASK; + pval = 1; + /* + * When not using any prescaler, the clock period in nanoseconds + * is not an integer so round it half up instead of + * truncating to get less surprising values. + */ + div = clk_rate * state->period + NSEC_PER_SEC / 2; + do_div(div, NSEC_PER_SEC); + if (div - 1 > PWM_PRD_MASK) + prescaler = 0; + } + + if (prescaler == 0) { + /* Go up from the first divider */ + for (prescaler = 0; prescaler < PWM_PRESCAL_MASK; prescaler++) { + if (!prescaler_table[prescaler]) + continue; + pval = prescaler_table[prescaler]; + div = clk_rate; + do_div(div, pval); + div = div * state->period; + do_div(div, NSEC_PER_SEC); + if (div - 1 <= PWM_PRD_MASK) + break; + } + + if (div - 1 > PWM_PRD_MASK) + return -EINVAL; + } + + *prd = div; + div *= state->duty_cycle; + do_div(div, state->period); + *dty = div; + *prsclr = prescaler; + + div = (u64)pval * NSEC_PER_SEC * *prd; + state->period = DIV_ROUND_CLOSEST_ULL(div, clk_rate); + + div = (u64)pval * NSEC_PER_SEC * *dty; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(div, clk_rate); + + return 0; +} + +static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + struct pwm_state cstate; + u32 ctrl; + int ret; + unsigned int delay_us; + unsigned long now; + + pwm_get_state(pwm, &cstate); + + if (!cstate.enabled) { + ret = clk_prepare_enable(sun4i_pwm->clk); + if (ret) { + dev_err(chip->dev, "failed to enable PWM clock\n"); + return ret; + } + } + + spin_lock(&sun4i_pwm->ctrl_lock); + ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + + if ((cstate.period != state->period) || + (cstate.duty_cycle != state->duty_cycle)) { + u32 period, duty, val; + unsigned int prescaler; + + ret = sun4i_pwm_calculate(sun4i_pwm, state, + &duty, &period, &prescaler); + if (ret) { + dev_err(chip->dev, "period exceeds the maximum value\n"); + spin_unlock(&sun4i_pwm->ctrl_lock); + if (!cstate.enabled) + clk_disable_unprepare(sun4i_pwm->clk); + return ret; + } + + if (PWM_REG_PRESCAL(ctrl, pwm->hwpwm) != prescaler) { + /* Prescaler changed, the clock has to be gated */ + ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + + ctrl &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm); + ctrl |= BIT_CH(prescaler, pwm->hwpwm); + } + + val = (duty & PWM_DTY_MASK) | PWM_PRD(period); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm)); + sun4i_pwm->next_period[pwm->hwpwm] = jiffies + + usecs_to_jiffies(cstate.period / 1000 + 1); + sun4i_pwm->needs_delay[pwm->hwpwm] = true; + } + + if (state->polarity != PWM_POLARITY_NORMAL) + ctrl &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm); + else + ctrl |= BIT_CH(PWM_ACT_STATE, pwm->hwpwm); + + ctrl |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + if (state->enabled) { + ctrl |= BIT_CH(PWM_EN, pwm->hwpwm); + } else if (!sun4i_pwm->needs_delay[pwm->hwpwm]) { + ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm); + ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + } + + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + + spin_unlock(&sun4i_pwm->ctrl_lock); + + if (state->enabled) + return 0; + + if (!sun4i_pwm->needs_delay[pwm->hwpwm]) { + clk_disable_unprepare(sun4i_pwm->clk); + return 0; + } + + /* We need a full period to elapse before disabling the channel. */ + now = jiffies; + if (sun4i_pwm->needs_delay[pwm->hwpwm] && + time_before(now, sun4i_pwm->next_period[pwm->hwpwm])) { + delay_us = jiffies_to_usecs(sun4i_pwm->next_period[pwm->hwpwm] - + now); + if ((delay_us / 500) > MAX_UDELAY_MS) + msleep(delay_us / 1000 + 1); + else + usleep_range(delay_us, delay_us * 2); + } + sun4i_pwm->needs_delay[pwm->hwpwm] = false; + + spin_lock(&sun4i_pwm->ctrl_lock); + ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + spin_unlock(&sun4i_pwm->ctrl_lock); + + clk_disable_unprepare(sun4i_pwm->clk); + + return 0; +} + static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -301,6 +466,7 @@ static const struct pwm_ops sun4i_pwm_ops = { .set_polarity = sun4i_pwm_set_polarity, .enable = sun4i_pwm_enable, .disable = sun4i_pwm_disable, + .apply = sun4i_pwm_apply, .get_state = sun4i_pwm_get_state, .owner = THIS_MODULE, }; From a054c4d68408cdbb260ba9384415fb53edb29d7a Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Tue, 30 May 2017 21:32:09 +0200 Subject: [PATCH 10/14] pwm: sun4i: Drop legacy callbacks Remove the legacy callbacks .enable(), .disable(), .set_polarity() and .config(). Signed-off-by: Alexandre Belloni Acked-by: Chen-Yu Tsai Acked-by: Maxime Ripard Signed-off-by: Thierry Reding --- drivers/pwm/pwm-sun4i.c | 160 ---------------------------------------- 1 file changed, 160 deletions(-) diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index cd8737d0804f..6d23f1d1c9b7 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -305,167 +305,7 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) -{ - struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); - u32 prd, dty, val, clk_gate; - u64 clk_rate, div = 0; - unsigned int prescaler = 0; - int err; - - clk_rate = clk_get_rate(sun4i_pwm->clk); - - if (sun4i_pwm->data->has_prescaler_bypass) { - /* First, test without any prescaler when available */ - prescaler = PWM_PRESCAL_MASK; - /* - * When not using any prescaler, the clock period in nanoseconds - * is not an integer so round it half up instead of - * truncating to get less surprising values. - */ - div = clk_rate * period_ns + NSEC_PER_SEC / 2; - do_div(div, NSEC_PER_SEC); - if (div - 1 > PWM_PRD_MASK) - prescaler = 0; - } - - if (prescaler == 0) { - /* Go up from the first divider */ - for (prescaler = 0; prescaler < PWM_PRESCAL_MASK; prescaler++) { - if (!prescaler_table[prescaler]) - continue; - div = clk_rate; - do_div(div, prescaler_table[prescaler]); - div = div * period_ns; - do_div(div, NSEC_PER_SEC); - if (div - 1 <= PWM_PRD_MASK) - break; - } - - if (div - 1 > PWM_PRD_MASK) { - dev_err(chip->dev, "period exceeds the maximum value\n"); - return -EINVAL; - } - } - - prd = div; - div *= duty_ns; - do_div(div, period_ns); - dty = div; - - err = clk_prepare_enable(sun4i_pwm->clk); - if (err) { - dev_err(chip->dev, "failed to enable PWM clock\n"); - return err; - } - - spin_lock(&sun4i_pwm->ctrl_lock); - val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); - - if (sun4i_pwm->data->has_rdy && (val & PWM_RDY(pwm->hwpwm))) { - spin_unlock(&sun4i_pwm->ctrl_lock); - clk_disable_unprepare(sun4i_pwm->clk); - return -EBUSY; - } - - clk_gate = val & BIT_CH(PWM_CLK_GATING, pwm->hwpwm); - if (clk_gate) { - val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); - sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); - } - - val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); - val &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm); - val |= BIT_CH(prescaler, pwm->hwpwm); - sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); - - val = (dty & PWM_DTY_MASK) | PWM_PRD(prd); - sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm)); - - if (clk_gate) { - val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); - val |= clk_gate; - sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); - } - - spin_unlock(&sun4i_pwm->ctrl_lock); - clk_disable_unprepare(sun4i_pwm->clk); - - return 0; -} - -static int sun4i_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, - enum pwm_polarity polarity) -{ - struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); - u32 val; - int ret; - - ret = clk_prepare_enable(sun4i_pwm->clk); - if (ret) { - dev_err(chip->dev, "failed to enable PWM clock\n"); - return ret; - } - - spin_lock(&sun4i_pwm->ctrl_lock); - val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); - - if (polarity != PWM_POLARITY_NORMAL) - val &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm); - else - val |= BIT_CH(PWM_ACT_STATE, pwm->hwpwm); - - sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); - - spin_unlock(&sun4i_pwm->ctrl_lock); - clk_disable_unprepare(sun4i_pwm->clk); - - return 0; -} - -static int sun4i_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); - u32 val; - int ret; - - ret = clk_prepare_enable(sun4i_pwm->clk); - if (ret) { - dev_err(chip->dev, "failed to enable PWM clock\n"); - return ret; - } - - spin_lock(&sun4i_pwm->ctrl_lock); - val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); - val |= BIT_CH(PWM_EN, pwm->hwpwm); - val |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm); - sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); - spin_unlock(&sun4i_pwm->ctrl_lock); - - return 0; -} - -static void sun4i_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); - u32 val; - - spin_lock(&sun4i_pwm->ctrl_lock); - val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); - val &= ~BIT_CH(PWM_EN, pwm->hwpwm); - val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); - sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); - spin_unlock(&sun4i_pwm->ctrl_lock); - - clk_disable_unprepare(sun4i_pwm->clk); -} - static const struct pwm_ops sun4i_pwm_ops = { - .config = sun4i_pwm_config, - .set_polarity = sun4i_pwm_set_polarity, - .enable = sun4i_pwm_enable, - .disable = sun4i_pwm_disable, .apply = sun4i_pwm_apply, .get_state = sun4i_pwm_get_state, .owner = THIS_MODULE, From d396b20a1e88ca2cabf7ec99c6c2138902aff1f3 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 8 Jun 2017 14:24:15 +0200 Subject: [PATCH 11/14] pwm: meson: Add compatible for the gxbb ao PWMs On the gxbb (and gxl) family, the PWMs of the AO domain require a specific compatible because the possible input clocks are different from the EE PWMs input clocks. Since the number of possible input clocks is also different, the 'num_parents' field is added to all the Meson PWM data. Acked-by: Neil Armstrong Signed-off-by: Jerome Brunet Reviewed-by: Kevin Hilman Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 045ef9fa6fe3..defc27d880f3 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -103,6 +103,7 @@ struct meson_pwm_channel { struct meson_pwm_data { const char * const *parent_names; + unsigned int num_parents; }; struct meson_pwm { @@ -381,6 +382,7 @@ static const char * const pwm_meson8b_parent_names[] = { static const struct meson_pwm_data pwm_meson8b_data = { .parent_names = pwm_meson8b_parent_names, + .num_parents = ARRAY_SIZE(pwm_meson8b_parent_names), }; static const char * const pwm_gxbb_parent_names[] = { @@ -389,11 +391,35 @@ static const char * const pwm_gxbb_parent_names[] = { static const struct meson_pwm_data pwm_gxbb_data = { .parent_names = pwm_gxbb_parent_names, + .num_parents = ARRAY_SIZE(pwm_gxbb_parent_names), +}; + +/* + * Only the 2 first inputs of the GXBB AO PWMs are valid + * The last 2 are grounded + */ +static const char * const pwm_gxbb_ao_parent_names[] = { + "xtal", "clk81" +}; + +static const struct meson_pwm_data pwm_gxbb_ao_data = { + .parent_names = pwm_gxbb_ao_parent_names, + .num_parents = ARRAY_SIZE(pwm_gxbb_ao_parent_names), }; static const struct of_device_id meson_pwm_matches[] = { - { .compatible = "amlogic,meson8b-pwm", .data = &pwm_meson8b_data }, - { .compatible = "amlogic,meson-gxbb-pwm", .data = &pwm_gxbb_data }, + { + .compatible = "amlogic,meson8b-pwm", + .data = &pwm_meson8b_data + }, + { + .compatible = "amlogic,meson-gxbb-pwm", + .data = &pwm_gxbb_data + }, + { + .compatible = "amlogic,meson-gxbb-ao-pwm", + .data = &pwm_gxbb_ao_data + }, {}, }; MODULE_DEVICE_TABLE(of, meson_pwm_matches); @@ -417,7 +443,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, init.ops = &clk_mux_ops; init.flags = CLK_IS_BASIC; init.parent_names = meson->data->parent_names; - init.num_parents = 1 << MISC_CLK_SEL_WIDTH; + init.num_parents = meson->data->num_parents; channel->mux.reg = meson->base + REG_MISC_AB; channel->mux.shift = mux_reg_shifts[i]; From 8517bb1f19679bf2bf6c29a98b7a4f3a78629554 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 8 Jun 2017 14:24:14 +0200 Subject: [PATCH 12/14] dt-bindings: pwm: meson: Add compatible for gxbb ao PWMs Add compatible string to properly handle the PWMs found in the AO domain of the gxbb (and gxl) family. Acked-by: Neil Armstrong Signed-off-by: Jerome Brunet Acked-by: Rob Herring Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/pwm-meson.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pwm/pwm-meson.txt b/Documentation/devicetree/bindings/pwm/pwm-meson.txt index 5376a4468cb6..5b07bebbf6f7 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-meson.txt +++ b/Documentation/devicetree/bindings/pwm/pwm-meson.txt @@ -2,7 +2,9 @@ Amlogic Meson PWM Controller ============================ Required properties: -- compatible: Shall contain "amlogic,meson8b-pwm" or "amlogic,meson-gxbb-pwm". +- compatible: Shall contain "amlogic,meson8b-pwm" + or "amlogic,meson-gxbb-pwm" + or "amlogic,meson-gxbb-ao-pwm" - #pwm-cells: Should be 3. See pwm.txt in this directory for a description of the cells format. From fd7b2be8cbcf6cd6d9c9e843ffff36fb91388e51 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 8 Jun 2017 14:24:16 +0200 Subject: [PATCH 13/14] pwm: meson: Improve PWM calculation precision When using input clocks with high rates, such as clk81 (166MHz), the fin_ns = NSEC_PER_SEC / fin_freq can introduce a significant error. Ex: fin_freq = 166666667, NSEC_PER_SEC = 1000000000 fin_ns = 5,9999999 which is, of course, rounded down to 5. This introduces an error of ~20% on the period requested from the PWM. This patch uses ps instead of ns (and 64 bit integers) to perform the calculation. This should give a good enough precision. Fixes: 211ed630753d ("pwm: Add support for Meson PWM Controller") Signed-off-by: Jerome Brunet Acked-by: Neil Armstrong Signed-off-by: Thierry Reding squash! pwm: meson: Improve pwm calculation precision --- drivers/pwm/pwm-meson.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index defc27d880f3..cb845edfe2b4 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -163,7 +163,8 @@ static int meson_pwm_calc(struct meson_pwm *meson, unsigned int duty, unsigned int period) { unsigned int pre_div, cnt, duty_cnt; - unsigned long fin_freq = -1, fin_ns; + unsigned long fin_freq = -1; + u64 fin_ps; if (~(meson->inverter_mask >> id) & 0x1) duty = period - duty; @@ -179,13 +180,15 @@ static int meson_pwm_calc(struct meson_pwm *meson, } dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); - fin_ns = NSEC_PER_SEC / fin_freq; + fin_ps = (u64)NSEC_PER_SEC * 1000; + do_div(fin_ps, fin_freq); /* Calc pre_div with the period */ for (pre_div = 0; pre_div < MISC_CLK_DIV_MASK; pre_div++) { - cnt = DIV_ROUND_CLOSEST(period, fin_ns * (pre_div + 1)); - dev_dbg(meson->chip.dev, "fin_ns=%lu pre_div=%u cnt=%u\n", - fin_ns, pre_div, cnt); + cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000, + fin_ps * (pre_div + 1)); + dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n", + fin_ps, pre_div, cnt); if (cnt <= 0xffff) break; } @@ -208,7 +211,8 @@ static int meson_pwm_calc(struct meson_pwm *meson, channel->lo = cnt; } else { /* Then check is we can have the duty with the same pre_div */ - duty_cnt = DIV_ROUND_CLOSEST(duty, fin_ns * (pre_div + 1)); + duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000, + fin_ps * (pre_div + 1)); if (duty_cnt > 0xffff) { dev_err(meson->chip.dev, "unable to get duty cycle\n"); return -EINVAL; From e47866a177cf0baba1d714fa93cb762f25bd6cef Mon Sep 17 00:00:00 2001 From: Nick Vaccaro Date: Fri, 23 Jun 2017 14:52:47 -0700 Subject: [PATCH 14/14] pwm: cros-ec: Fix transposed param settings The __cros_ec_pwm_get_duty() routine was transposing the insize and outsize fields when calling cros_ec_cmd_xfer_status(). The original code worked without error due to size of the two particular parameter blocks passed to cros_ec_cmd_xfer_status(), so this change is not fixing an actual runtime problem, just correcting the calling usage. Signed-off-by: Nick Vaccaro Reviewed-by: Brian Norris Signed-off-by: Thierry Reding --- drivers/pwm/pwm-cros-ec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c index f6ca4e8c6253..9c13694eaa24 100644 --- a/drivers/pwm/pwm-cros-ec.c +++ b/drivers/pwm/pwm-cros-ec.c @@ -75,8 +75,8 @@ static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index, msg->version = 0; msg->command = EC_CMD_PWM_GET_DUTY; - msg->insize = sizeof(*params); - msg->outsize = sizeof(*resp); + msg->insize = sizeof(*resp); + msg->outsize = sizeof(*params); params->pwm_type = EC_PWM_TYPE_GENERIC; params->index = index;