Skip to content

Commit

Permalink
Merge tag 'pwm/for-4.21-rc1' of git://git.kernel.org/pub/scm/linux/ke…
Browse files Browse the repository at this point in the history
…rnel/git/thierry.reding/linux-pwm

Pull pwm updates from Thierry Reding:
 "Not a lot going on this cycle.

  There's some more cleanup going on and new driver support that was not
  quite ready in time for v4.21-rc1, but here are a few fixes and
  improvements that are good to go.

  The Kona PWM driver can now be built on the Cygnus architecture and
  the i.MX driver gained support for hardware readback. Some small fixes
  are provided for the clks711x and lpc18xx-sct drivers.

  Finally, to round things off some drivers are switched to SPDX license
  identifiers"

* tag 'pwm/for-4.21-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm:
  dt-bindings: pwm: rcar: Add r8a774c0 support
  pwm: imx: Add ipg clock operation
  pwm: clps711x: Switch to SPDX identifier
  pwm: clps711x: Fix period calculation
  pwm: bcm2835: Switch to SPDX identifier
  pwm: Enable Kona PWM to be built for the Cygnus architecture
  pwm: Drop legacy wrapper for changing polarity
  pwm: imx: Implement get_state() function for hardware readout
  pwm: imx: Use bitops and bitfield macros to define register values
  pwm: imx: Sort include files
  pwm: lpc18xx-sct: Don't reconfigure PWM in .request and .free
  • Loading branch information
torvalds committed Dec 25, 2018
2 parents e6d1315 + e94b815 commit d923fd6
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 89 deletions.
1 change: 1 addition & 0 deletions Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Required Properties:
- "renesas,pwm-r8a7744": for RZ/G1N
- "renesas,pwm-r8a7745": for RZ/G1E
- "renesas,pwm-r8a774a1": for RZ/G2M
- "renesas,pwm-r8a774c0": for RZ/G2E
- "renesas,pwm-r8a7778": for R-Car M1A
- "renesas,pwm-r8a7779": for R-Car H1
- "renesas,pwm-r8a7790": for R-Car H2
Expand Down
4 changes: 3 additions & 1 deletion drivers/pwm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ config PWM_BCM_IPROC

config PWM_BCM_KONA
tristate "Kona PWM support"
depends on ARCH_BCM_MOBILE
depends on ARCH_BCM_MOBILE || ARCH_BCM_CYGNUS || COMPILE_TEST
depends on HAVE_CLK && HAS_IOMEM
default ARCH_BCM_MOBILE || ARCH_BCM_CYGNUS
help
Generic PWM framework driver for Broadcom Kona PWM block.

Expand Down
5 changes: 1 addition & 4 deletions drivers/pwm/pwm-bcm2835.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2014 Bart Tanghe <[email protected]>
*
* 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.
*/

#include <linux/clk.h>
Expand Down
13 changes: 4 additions & 9 deletions drivers/pwm/pwm-clps711x.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Cirrus Logic CLPS711X PWM driver
*
* Copyright (C) 2014 Alexander Shiyan <[email protected]>
*
* 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.
* Author: Alexander Shiyan <[email protected]>
*/

#include <linux/clk.h>
Expand Down Expand Up @@ -48,7 +43,7 @@ static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v)
static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v)
{
/* Duty cycle 0..15 max */
return DIV_ROUND_CLOSEST(v * 0xf, pwm_get_period(pwm));
return DIV_ROUND_CLOSEST(v * 0xf, pwm->args.period);
}

static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
Expand All @@ -71,7 +66,7 @@ static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;

if (period_ns != pwm_get_period(pwm))
if (period_ns != pwm->args.period)
return -EINVAL;

duty = clps711x_get_duty(pwm, duty_ns);
Expand Down
194 changes: 164 additions & 30 deletions drivers/pwm/pwm-imx.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,90 @@
* Derived from pxa PWM driver by eric miao <[email protected]>
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>

/* i.MX1 and i.MX21 share the same PWM function block: */

#define MX1_PWMC 0x00 /* PWM Control Register */
#define MX1_PWMS 0x04 /* PWM Sample Register */
#define MX1_PWMP 0x08 /* PWM Period Register */

#define MX1_PWMC_EN (1 << 4)
#define MX1_PWMC_EN BIT(4)

/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */

#define MX3_PWMCR 0x00 /* PWM Control Register */
#define MX3_PWMSR 0x04 /* PWM Status Register */
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
#define MX3_PWMPR 0x10 /* PWM Period Register */
#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4)
#define MX3_PWMCR_STOPEN (1 << 25)
#define MX3_PWMCR_DOZEEN (1 << 24)
#define MX3_PWMCR_WAITEN (1 << 23)
#define MX3_PWMCR_DBGEN (1 << 22)
#define MX3_PWMCR_POUTC (1 << 18)
#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16)
#define MX3_PWMCR_CLKSRC_IPG (1 << 16)
#define MX3_PWMCR_SWR (1 << 3)
#define MX3_PWMCR_EN (1 << 0)
#define MX3_PWMSR_FIFOAV_4WORDS 0x4
#define MX3_PWMSR_FIFOAV_MASK 0x7

#define MX3_PWMCR_FWM GENMASK(27, 26)
#define MX3_PWMCR_STOPEN BIT(25)
#define MX3_PWMCR_DOZEN BIT(24)
#define MX3_PWMCR_WAITEN BIT(23)
#define MX3_PWMCR_DBGEN BIT(22)
#define MX3_PWMCR_BCTR BIT(21)
#define MX3_PWMCR_HCTR BIT(20)

#define MX3_PWMCR_POUTC GENMASK(19, 18)
#define MX3_PWMCR_POUTC_NORMAL 0
#define MX3_PWMCR_POUTC_INVERTED 1
#define MX3_PWMCR_POUTC_OFF 2

#define MX3_PWMCR_CLKSRC GENMASK(17, 16)
#define MX3_PWMCR_CLKSRC_OFF 0
#define MX3_PWMCR_CLKSRC_IPG 1
#define MX3_PWMCR_CLKSRC_IPG_HIGH 2
#define MX3_PWMCR_CLKSRC_IPG_32K 3

#define MX3_PWMCR_PRESCALER GENMASK(15, 4)

#define MX3_PWMCR_SWR BIT(3)

#define MX3_PWMCR_REPEAT GENMASK(2, 1)
#define MX3_PWMCR_REPEAT_1X 0
#define MX3_PWMCR_REPEAT_2X 1
#define MX3_PWMCR_REPEAT_4X 2
#define MX3_PWMCR_REPEAT_8X 3

#define MX3_PWMCR_EN BIT(0)

#define MX3_PWMSR_FWE BIT(6)
#define MX3_PWMSR_CMP BIT(5)
#define MX3_PWMSR_ROV BIT(4)
#define MX3_PWMSR_FE BIT(3)

#define MX3_PWMSR_FIFOAV GENMASK(2, 0)
#define MX3_PWMSR_FIFOAV_EMPTY 0
#define MX3_PWMSR_FIFOAV_1WORD 1
#define MX3_PWMSR_FIFOAV_2WORDS 2
#define MX3_PWMSR_FIFOAV_3WORDS 3
#define MX3_PWMSR_FIFOAV_4WORDS 4

#define MX3_PWMCR_PRESCALER_SET(x) FIELD_PREP(MX3_PWMCR_PRESCALER, (x) - 1)
#define MX3_PWMCR_PRESCALER_GET(x) (FIELD_GET(MX3_PWMCR_PRESCALER, \
(x)) + 1)

#define MX3_PWM_SWR_LOOP 5

/* PWMPR register value of 0xffff has the same effect as 0xfffe */
#define MX3_PWMPR_MAX 0xfffe

struct imx_chip {
struct clk *clk_ipg;

struct clk *clk_per;

void __iomem *mmio_base;
Expand All @@ -56,6 +98,87 @@ struct imx_chip {

#define to_imx_chip(chip) container_of(chip, struct imx_chip, chip)

static int imx_pwm_clk_prepare_enable(struct pwm_chip *chip)
{
struct imx_chip *imx = to_imx_chip(chip);
int ret;

ret = clk_prepare_enable(imx->clk_ipg);
if (ret)
return ret;

ret = clk_prepare_enable(imx->clk_per);
if (ret) {
clk_disable_unprepare(imx->clk_ipg);
return ret;
}

return 0;
}

static void imx_pwm_clk_disable_unprepare(struct pwm_chip *chip)
{
struct imx_chip *imx = to_imx_chip(chip);

clk_disable_unprepare(imx->clk_per);
clk_disable_unprepare(imx->clk_ipg);
}

static void imx_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm, struct pwm_state *state)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 period, prescaler, pwm_clk, ret, val;
u64 tmp;

ret = imx_pwm_clk_prepare_enable(chip);
if (ret < 0)
return;

val = readl(imx->mmio_base + MX3_PWMCR);

if (val & MX3_PWMCR_EN) {
state->enabled = true;
ret = imx_pwm_clk_prepare_enable(chip);
if (ret)
return;
} else {
state->enabled = false;
}

switch (FIELD_GET(MX3_PWMCR_POUTC, val)) {
case MX3_PWMCR_POUTC_NORMAL:
state->polarity = PWM_POLARITY_NORMAL;
break;
case MX3_PWMCR_POUTC_INVERTED:
state->polarity = PWM_POLARITY_INVERSED;
break;
default:
dev_warn(chip->dev, "can't set polarity, output disconnected");
}

prescaler = MX3_PWMCR_PRESCALER_GET(val);
pwm_clk = clk_get_rate(imx->clk_per);
pwm_clk = DIV_ROUND_CLOSEST_ULL(pwm_clk, prescaler);
val = readl(imx->mmio_base + MX3_PWMPR);
period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;

/* PWMOUT (Hz) = PWMCLK / (PWMPR + 2) */
tmp = NSEC_PER_SEC * (u64)(period + 2);
state->period = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk);

/* PWMSAR can be read only if PWM is enabled */
if (state->enabled) {
val = readl(imx->mmio_base + MX3_PWMSAR);
tmp = NSEC_PER_SEC * (u64)(val);
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk);
} else {
state->duty_cycle = 0;
}

imx_pwm_clk_disable_unprepare(chip);
}

static int imx_pwm_config_v1(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns)
{
Expand Down Expand Up @@ -91,7 +214,7 @@ static int imx_pwm_enable_v1(struct pwm_chip *chip, struct pwm_device *pwm)
u32 val;
int ret;

ret = clk_prepare_enable(imx->clk_per);
ret = imx_pwm_clk_prepare_enable(chip);
if (ret < 0)
return ret;

Expand All @@ -111,7 +234,7 @@ static void imx_pwm_disable_v1(struct pwm_chip *chip, struct pwm_device *pwm)
val &= ~MX1_PWMC_EN;
writel(val, imx->mmio_base + MX1_PWMC);

clk_disable_unprepare(imx->clk_per);
imx_pwm_clk_disable_unprepare(chip);
}

static void imx_pwm_sw_reset(struct pwm_chip *chip)
Expand Down Expand Up @@ -142,14 +265,14 @@ static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip,
u32 sr;

sr = readl(imx->mmio_base + MX3_PWMSR);
fifoav = sr & MX3_PWMSR_FIFOAV_MASK;
fifoav = FIELD_GET(MX3_PWMSR_FIFOAV, sr);
if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) {
period_ms = DIV_ROUND_UP(pwm_get_period(pwm),
NSEC_PER_MSEC);
msleep(period_ms);

sr = readl(imx->mmio_base + MX3_PWMSR);
if (fifoav == (sr & MX3_PWMSR_FIFOAV_MASK))
if (fifoav == FIELD_GET(MX3_PWMSR_FIFOAV, sr))
dev_warn(dev, "there is no free FIFO slot\n");
}
}
Expand Down Expand Up @@ -197,7 +320,7 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
if (cstate.enabled) {
imx_pwm_wait_fifo_slot(chip, pwm);
} else {
ret = clk_prepare_enable(imx->clk_per);
ret = imx_pwm_clk_prepare_enable(chip);
if (ret)
return ret;

Expand All @@ -207,19 +330,20 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
writel(period_cycles, imx->mmio_base + MX3_PWMPR);

cr = MX3_PWMCR_PRESCALER(prescale) |
MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH |
MX3_PWMCR_EN;
cr = MX3_PWMCR_PRESCALER_SET(prescale) |
MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEN | MX3_PWMCR_WAITEN |
FIELD_PREP(MX3_PWMCR_CLKSRC, MX3_PWMCR_CLKSRC_IPG_HIGH) |
MX3_PWMCR_DBGEN | MX3_PWMCR_EN;

if (state->polarity == PWM_POLARITY_INVERSED)
cr |= MX3_PWMCR_POUTC;
cr |= FIELD_PREP(MX3_PWMCR_POUTC,
MX3_PWMCR_POUTC_INVERTED);

writel(cr, imx->mmio_base + MX3_PWMCR);
} else if (cstate.enabled) {
writel(0, imx->mmio_base + MX3_PWMCR);

clk_disable_unprepare(imx->clk_per);
imx_pwm_clk_disable_unprepare(chip);
}

return 0;
Expand All @@ -234,6 +358,7 @@ static const struct pwm_ops imx_pwm_ops_v1 = {

static const struct pwm_ops imx_pwm_ops_v2 = {
.apply = imx_pwm_apply_v2,
.get_state = imx_pwm_get_state,
.owner = THIS_MODULE,
};

Expand Down Expand Up @@ -276,6 +401,13 @@ static int imx_pwm_probe(struct platform_device *pdev)
if (imx == NULL)
return -ENOMEM;

imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(imx->clk_ipg)) {
dev_err(&pdev->dev, "getting ipg clock failed with %ld\n",
PTR_ERR(imx->clk_ipg));
return PTR_ERR(imx->clk_ipg);
}

imx->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(imx->clk_per)) {
dev_err(&pdev->dev, "getting per clock failed with %ld\n",
Expand Down Expand Up @@ -315,6 +447,8 @@ static int imx_pwm_remove(struct platform_device *pdev)
if (imx == NULL)
return -ENODEV;

imx_pwm_clk_disable_unprepare(&imx->chip);

return pwmchip_remove(&imx->chip);
}

Expand Down
3 changes: 0 additions & 3 deletions drivers/pwm/pwm-lpc18xx-sct.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,6 @@ static int lpc18xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)

set_bit(event, &lpc18xx_pwm->event_map);
lpc18xx_data->duty_event = event;
lpc18xx_pwm_config_duty(chip, pwm, pwm_get_duty_cycle(pwm));

return 0;
}
Expand All @@ -306,8 +305,6 @@ static void lpc18xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm);

pwm_disable(pwm);
pwm_set_duty_cycle(pwm, 0);
clear_bit(lpc18xx_data->duty_event, &lpc18xx_pwm->event_map);
}

Expand Down
Loading

0 comments on commit d923fd6

Please sign in to comment.