Commit ace1ba1c authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'pwm/for-6.5-rc1' of...

Merge tag 'pwm/for-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm

Pull pwm updates from Thierry Reding:
 "There's a little bit of everything in here: we've got various
  improvements and cleanups to drivers, some fixes across the board and
  a bit of new hardware support"

* tag 'pwm/for-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (22 commits)
  dt-bindings: pwm: convert pwm-bcm2835 bindings to YAML
  pwm: Add Renesas RZ/G2L MTU3a PWM driver
  pwm: mtk_disp: Fix the disable flow of disp_pwm
  dt-bindings: pwm: restrict node name suffixes
  pwm: pca9685: Switch i2c driver back to use .probe()
  pwm: ab8500: Fix error code in probe()
  MAINTAINERS: add pwm to PolarFire SoC entry
  pwm: add microchip soft ip corePWM driver
  pwm: sysfs: Do not apply state to already disabled PWMs
  pwm: imx-tpm: force 'real_period' to be zero in suspend
  pwm: meson: make full use of common clock framework
  pwm: meson: don't use hdmi/video clock as mux parent
  pwm: meson: switch to using struct clk_parent_data for mux parents
  pwm: meson: remove not needed check in meson_pwm_calc
  pwm: meson: fix handling of period/duty if greater than UINT_MAX
  pwm: meson: modify and simplify calculation in meson_pwm_get_state
  dt-bindings: pwm: Add R-Car V3U device tree bindings
  dt-bindings: pwm: imx: add i.MX8QXP compatible
  pwm: mediatek: Add support for MT7981
  dt-bindings: pwm: mediatek: Add mediatek,mt7981 compatible
  ...
parents b9861581 92554cdd
......@@ -43,6 +43,7 @@ properties:
- fsl,imx8mn-pwm
- fsl,imx8mp-pwm
- fsl,imx8mq-pwm
- fsl,imx8qxp-pwm
- const: fsl,imx27-pwm
reg:
......@@ -61,6 +62,9 @@ properties:
interrupts:
maxItems: 1
power-domains:
maxItems: 1
required:
- compatible
- reg
......
......@@ -22,6 +22,7 @@ properties:
- mediatek,mt7623-pwm
- mediatek,mt7628-pwm
- mediatek,mt7629-pwm
- mediatek,mt7981-pwm
- mediatek,mt7986-pwm
- mediatek,mt8183-pwm
- mediatek,mt8365-pwm
......
BCM2835 PWM controller (Raspberry Pi controller)
Required properties:
- compatible: should be "brcm,bcm2835-pwm"
- reg: physical base address and length of the controller's registers
- clocks: This clock defines the base clock frequency of the PWM hardware
system, the period and the duty_cycle of the PWM signal is a multiple of
the base period.
- #pwm-cells: Should be 3. See pwm.yaml in this directory for a description of
the cells format.
Examples:
pwm@2020c000 {
compatible = "brcm,bcm2835-pwm";
reg = <0x2020c000 0x28>;
clocks = <&clk_pwm>;
#pwm-cells = <3>;
};
clocks {
....
clk_pwm: pwm {
compatible = "fixed-clock";
reg = <3>;
#clock-cells = <0>;
clock-frequency = <9200000>;
};
....
};
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/pwm-bcm2835.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: BCM2835 PWM controller (Raspberry Pi controller)
maintainers:
- Stefan Wahren <stefan.wahren@i2se.com>
allOf:
- $ref: pwm.yaml#
properties:
compatible:
const: brcm,bcm2835-pwm
reg:
maxItems: 1
clocks:
maxItems: 1
"#pwm-cells":
const: 3
required:
- compatible
- reg
- clocks
- "#pwm-cells"
additionalProperties: false
examples:
- |
pwm@2020c000 {
compatible = "brcm,bcm2835-pwm";
reg = <0x2020c000 0x28>;
clocks = <&clk_pwm>;
#pwm-cells = <3>;
};
......@@ -13,7 +13,7 @@ select: false
properties:
$nodename:
pattern: "^pwm(@.*|-[0-9a-f])*$"
pattern: "^pwm(@.*|-([0-9]|[1-9][0-9]+))?$"
"#pwm-cells":
description:
......
......@@ -35,6 +35,7 @@ properties:
- renesas,pwm-r8a77980 # R-Car V3H
- renesas,pwm-r8a77990 # R-Car E3
- renesas,pwm-r8a77995 # R-Car D3
- renesas,pwm-r8a779a0 # R-Car V3U
- renesas,pwm-r8a779g0 # R-Car V4H
- const: renesas,pwm-rcar
......
......@@ -18337,6 +18337,7 @@ F: drivers/clk/microchip/clk-mpfs*.c
F: drivers/i2c/busses/i2c-microchip-corei2c.c
F: drivers/mailbox/mailbox-mpfs.c
F: drivers/pci/controller/pcie-microchip-host.c
F: drivers/pwm/pwm-microchip-core.c
F: drivers/reset/reset-mpfs.c
F: drivers/rtc/rtc-mpfs.c
F: drivers/soc/microchip/mpfs-sys-controller.c
......
......@@ -405,6 +405,16 @@ config PWM_MEDIATEK
To compile this driver as a module, choose M here: the module
will be called pwm-mediatek.
config PWM_MICROCHIP_CORE
tristate "Microchip corePWM PWM support"
depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST
depends on HAS_IOMEM && OF
help
PWM driver for Microchip FPGA soft IP core.
To compile this driver as a module, choose M here: the module
will be called pwm-microchip-core.
config PWM_MXS
tristate "Freescale MXS PWM support"
depends on ARCH_MXS || COMPILE_TEST
......@@ -493,6 +503,17 @@ config PWM_ROCKCHIP
Generic PWM framework driver for the PWM controller found on
Rockchip SoCs.
config PWM_RZ_MTU3
tristate "Renesas RZ/G2L MTU3a PWM Timer support"
depends on RZ_MTU3 || COMPILE_TEST
depends on HAS_IOMEM
help
This driver exposes the MTU3a PWM Timer controller found in Renesas
RZ/G2L like chips through the PWM API.
To compile this driver as a module, choose M here: the module
will be called pwm-rz-mtu3.
config PWM_SAMSUNG
tristate "Samsung PWM support"
depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
......
......@@ -35,6 +35,7 @@ obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
obj-$(CONFIG_PWM_MESON) += pwm-meson.o
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o
obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
......@@ -45,6 +46,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o
......
......@@ -190,7 +190,7 @@ static int ab8500_pwm_probe(struct platform_device *pdev)
int err;
if (pdev->id < 1 || pdev->id > 31)
return dev_err_probe(&pdev->dev, EINVAL, "Invalid device id %d\n", pdev->id);
return dev_err_probe(&pdev->dev, -EINVAL, "Invalid device id %d\n", pdev->id);
/*
* Nothing to be done in probe, this is required to get the
......
......@@ -89,7 +89,7 @@ static int pwm_clk_probe(struct platform_device *pdev)
if (!pcchip)
return -ENOMEM;
pcchip->clk = devm_clk_get(&pdev->dev, NULL);
pcchip->clk = devm_clk_get_prepared(&pdev->dev, NULL);
if (IS_ERR(pcchip->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk),
"Failed to get clock\n");
......@@ -98,15 +98,9 @@ static int pwm_clk_probe(struct platform_device *pdev)
pcchip->chip.ops = &pwm_clk_ops;
pcchip->chip.npwm = 1;
ret = clk_prepare(pcchip->clk);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n");
ret = pwmchip_add(&pcchip->chip);
if (ret < 0) {
clk_unprepare(pcchip->clk);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
}
platform_set_drvdata(pdev, pcchip);
return 0;
......@@ -120,8 +114,6 @@ static void pwm_clk_remove(struct platform_device *pdev)
if (pcchip->clk_enabled)
clk_disable(pcchip->clk);
clk_unprepare(pcchip->clk);
}
static const struct of_device_id pwm_clk_dt_ids[] = {
......
......@@ -397,6 +397,13 @@ static int __maybe_unused pwm_imx_tpm_suspend(struct device *dev)
if (tpm->enable_count > 0)
return -EBUSY;
/*
* Force 'real_period' to be zero to force period update code
* can be executed after system resume back, since suspend causes
* the period related registers to become their reset values.
*/
tpm->real_period = 0;
clk_disable_unprepare(tpm->clk);
return 0;
......
......@@ -38,6 +38,7 @@ struct pwm_mediatek_of_data {
unsigned int num_pwms;
bool pwm45_fixup;
bool has_ck_26m_sel;
const unsigned int *reg_offset;
};
/**
......@@ -59,10 +60,14 @@ struct pwm_mediatek_chip {
const struct pwm_mediatek_of_data *soc;
};
static const unsigned int pwm_mediatek_reg_offset[] = {
static const unsigned int mtk_pwm_reg_offset_v1[] = {
0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220
};
static const unsigned int mtk_pwm_reg_offset_v2[] = {
0x0080, 0x00c0, 0x0100, 0x0140, 0x0180, 0x01c0, 0x0200, 0x0240
};
static inline struct pwm_mediatek_chip *
to_pwm_mediatek_chip(struct pwm_chip *chip)
{
......@@ -111,7 +116,7 @@ static inline void pwm_mediatek_writel(struct pwm_mediatek_chip *chip,
unsigned int num, unsigned int offset,
u32 value)
{
writel(value, chip->regs + pwm_mediatek_reg_offset[num] + offset);
writel(value, chip->regs + chip->soc->reg_offset[num] + offset);
}
static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
......@@ -285,60 +290,77 @@ static const struct pwm_mediatek_of_data mt2712_pwm_data = {
.num_pwms = 8,
.pwm45_fixup = false,
.has_ck_26m_sel = false,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt6795_pwm_data = {
.num_pwms = 7,
.pwm45_fixup = false,
.has_ck_26m_sel = false,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt7622_pwm_data = {
.num_pwms = 6,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt7623_pwm_data = {
.num_pwms = 5,
.pwm45_fixup = true,
.has_ck_26m_sel = false,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt7628_pwm_data = {
.num_pwms = 4,
.pwm45_fixup = true,
.has_ck_26m_sel = false,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt7629_pwm_data = {
.num_pwms = 1,
.pwm45_fixup = false,
.has_ck_26m_sel = false,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt8183_pwm_data = {
.num_pwms = 4,
static const struct pwm_mediatek_of_data mt7981_pwm_data = {
.num_pwms = 3,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
.reg_offset = mtk_pwm_reg_offset_v2,
};
static const struct pwm_mediatek_of_data mt8365_pwm_data = {
.num_pwms = 3,
static const struct pwm_mediatek_of_data mt7986_pwm_data = {
.num_pwms = 2,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt7986_pwm_data = {
.num_pwms = 2,
static const struct pwm_mediatek_of_data mt8183_pwm_data = {
.num_pwms = 4,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt8365_pwm_data = {
.num_pwms = 3,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct pwm_mediatek_of_data mt8516_pwm_data = {
.num_pwms = 5,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
.reg_offset = mtk_pwm_reg_offset_v1,
};
static const struct of_device_id pwm_mediatek_of_match[] = {
......@@ -348,6 +370,7 @@ static const struct of_device_id pwm_mediatek_of_match[] = {
{ .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data },
{ .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data },
{ .compatible = "mediatek,mt7629-pwm", .data = &mt7629_pwm_data },
{ .compatible = "mediatek,mt7981-pwm", .data = &mt7981_pwm_data },
{ .compatible = "mediatek,mt7986-pwm", .data = &mt7986_pwm_data },
{ .compatible = "mediatek,mt8183-pwm", .data = &mt8183_pwm_data },
{ .compatible = "mediatek,mt8365-pwm", .data = &mt8365_pwm_data },
......
......@@ -49,9 +49,9 @@
#define PWM_HIGH_MASK GENMASK(31, 16)
#define REG_MISC_AB 0x8
#define MISC_B_CLK_EN BIT(23)
#define MISC_A_CLK_EN BIT(15)
#define MISC_CLK_DIV_MASK 0x7f
#define MISC_B_CLK_EN_SHIFT 23
#define MISC_A_CLK_EN_SHIFT 15
#define MISC_CLK_DIV_WIDTH 7
#define MISC_B_CLK_DIV_SHIFT 16
#define MISC_A_CLK_DIV_SHIFT 8
#define MISC_B_CLK_SEL_SHIFT 6
......@@ -61,37 +61,39 @@
#define MISC_A_EN BIT(0)
#define MESON_NUM_PWMS 2
#define MESON_MAX_MUX_PARENTS 4
static struct meson_pwm_channel_data {
u8 reg_offset;
u8 clk_sel_shift;
u8 clk_div_shift;
u32 clk_en_mask;
u8 clk_en_shift;
u32 pwm_en_mask;
} meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
{
.reg_offset = REG_PWM_A,
.clk_sel_shift = MISC_A_CLK_SEL_SHIFT,
.clk_div_shift = MISC_A_CLK_DIV_SHIFT,
.clk_en_mask = MISC_A_CLK_EN,
.clk_en_shift = MISC_A_CLK_EN_SHIFT,
.pwm_en_mask = MISC_A_EN,
},
{
.reg_offset = REG_PWM_B,
.clk_sel_shift = MISC_B_CLK_SEL_SHIFT,
.clk_div_shift = MISC_B_CLK_DIV_SHIFT,
.clk_en_mask = MISC_B_CLK_EN,
.clk_en_shift = MISC_B_CLK_EN_SHIFT,
.pwm_en_mask = MISC_B_EN,
}
};
struct meson_pwm_channel {
unsigned long rate;
unsigned int hi;
unsigned int lo;
u8 pre_div;
struct clk *clk_parent;
struct clk_mux mux;
struct clk_divider div;
struct clk_gate gate;
struct clk *clk;
};
......@@ -124,16 +126,6 @@ static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
struct device *dev = chip->dev;
int err;
if (channel->clk_parent) {
err = clk_set_parent(channel->clk, channel->clk_parent);
if (err < 0) {
dev_err(dev, "failed to set parent %s for %s: %d\n",
__clk_get_name(channel->clk_parent),
__clk_get_name(channel->clk), err);
return err;
}
}
err = clk_prepare_enable(channel->clk);
if (err < 0) {
dev_err(dev, "failed to enable clock %s: %d\n",
......@@ -156,8 +148,9 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
unsigned int duty, period, pre_div, cnt, duty_cnt;
unsigned int cnt, duty_cnt;
unsigned long fin_freq;
u64 duty, period, freq;
duty = state->duty_cycle;
period = state->period;
......@@ -171,7 +164,11 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,
if (state->polarity == PWM_POLARITY_INVERSED)
duty = period - duty;
fin_freq = clk_get_rate(channel->clk);
freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period);
if (freq > ULONG_MAX)
freq = ULONG_MAX;
fin_freq = clk_round_rate(channel->clk, freq);
if (fin_freq == 0) {
dev_err(meson->chip.dev, "invalid source clock frequency\n");
return -EINVAL;
......@@ -179,46 +176,31 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,
dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq);
pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL);
if (pre_div > MISC_CLK_DIV_MASK) {
dev_err(meson->chip.dev, "unable to get period pre_div\n");
return -EINVAL;
}
cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1));
cnt = div_u64(fin_freq * period, NSEC_PER_SEC);
if (cnt > 0xffff) {
dev_err(meson->chip.dev, "unable to get period cnt\n");
return -EINVAL;
}
dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period,
pre_div, cnt);
dev_dbg(meson->chip.dev, "period=%llu cnt=%u\n", period, cnt);
if (duty == period) {
channel->pre_div = pre_div;
channel->hi = cnt;
channel->lo = 0;
} else if (duty == 0) {
channel->pre_div = pre_div;
channel->hi = 0;
channel->lo = cnt;
} else {
/* Then check is we can have the duty with the same pre_div */
duty_cnt = div64_u64(fin_freq * (u64)duty,
NSEC_PER_SEC * (pre_div + 1));
if (duty_cnt > 0xffff) {
dev_err(meson->chip.dev, "unable to get duty cycle\n");
return -EINVAL;
}
duty_cnt = div_u64(fin_freq * duty, NSEC_PER_SEC);
dev_dbg(meson->chip.dev, "duty=%u pre_div=%u duty_cnt=%u\n",
duty, pre_div, duty_cnt);
dev_dbg(meson->chip.dev, "duty=%llu duty_cnt=%u\n", duty, duty_cnt);
channel->pre_div = pre_div;
channel->hi = duty_cnt;
channel->lo = cnt - duty_cnt;
}
channel->rate = fin_freq;
return 0;
}
......@@ -228,16 +210,15 @@ static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm)
struct meson_pwm_channel_data *channel_data;
unsigned long flags;
u32 value;
int err;
channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
spin_lock_irqsave(&meson->lock, flags);
err = clk_set_rate(channel->clk, channel->rate);
if (err)
dev_err(meson->chip.dev, "setting clock rate failed\n");
value = readl(meson->base + REG_MISC_AB);
value &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift);
value |= channel->pre_div << channel_data->clk_div_shift;
value |= channel_data->clk_en_mask;
writel(value, meson->base + REG_MISC_AB);
spin_lock_irqsave(&meson->lock, flags);
value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) |
FIELD_PREP(PWM_LOW_MASK, channel->lo);
......@@ -276,16 +257,16 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
/*
* This IP block revision doesn't have an "always high"
* setting which we can use for "inverted disabled".
* Instead we achieve this using the same settings
* that we use a pre_div of 0 (to get the shortest
* possible duration for one "count") and
* "period == duty_cycle". This results in a signal
* Instead we achieve this by setting mux parent with
* highest rate and minimum divider value, resulting
* in the shortest possible duration for one "count"
* and "period == duty_cycle". This results in a signal
* which is LOW for one "count", while being HIGH for
* the rest of the (so the signal is HIGH for slightly
* less than 100% of the period, but this is the best
* we can achieve).
*/
channel->pre_div = 0;
channel->rate = ULONG_MAX;
channel->hi = ~0;
channel->lo = 0;
......@@ -304,13 +285,12 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip,
struct pwm_device *pwm, u32 cnt)
static u64 meson_pwm_cnt_to_ns(struct pwm_chip *chip, struct pwm_device *pwm,
u32 cnt)
{
struct meson_pwm *meson = to_meson_pwm(chip);
struct meson_pwm_channel *channel;
unsigned long fin_freq;
u32 fin_ns;
/* to_meson_pwm() can only be used after .get_state() is called */
channel = &meson->channels[pwm->hwpwm];
......@@ -319,9 +299,7 @@ static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip,
if (fin_freq == 0)
return 0;
fin_ns = div_u64(NSEC_PER_SEC, fin_freq);
return cnt * fin_ns * (channel->pre_div + 1);
return div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq);
}
static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
......@@ -330,7 +308,7 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct meson_pwm *meson = to_meson_pwm(chip);
struct meson_pwm_channel_data *channel_data;
struct meson_pwm_channel *channel;
u32 value, tmp;
u32 value;
if (!state)
return 0;
......@@ -339,30 +317,14 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
value = readl(meson->base + REG_MISC_AB);
tmp = channel_data->pwm_en_mask | channel_data->clk_en_mask;
state->enabled = (value & tmp) == tmp;
tmp = value >> channel_data->clk_div_shift;
channel->pre_div = FIELD_GET(MISC_CLK_DIV_MASK, tmp);
state->enabled = value & channel_data->pwm_en_mask;
value = readl(meson->base + channel_data->reg_offset);
channel->lo = FIELD_GET(PWM_LOW_MASK, value);
channel->hi = FIELD_GET(PWM_HIGH_MASK, value);
if (channel->lo == 0) {
state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi);
state->duty_cycle = state->period;
} else if (channel->lo >= channel->hi) {
state->period = meson_pwm_cnt_to_ns(chip, pwm,
channel->lo + channel->hi);
state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm,
channel->hi);
} else {
state->period = 0;
state->duty_cycle = 0;
}
state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + channel->hi);
state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi);
state->polarity = PWM_POLARITY_NORMAL;
......@@ -378,7 +340,7 @@ static const struct pwm_ops meson_pwm_ops = {
};
static const char * const pwm_meson8b_parent_names[] = {
"xtal", "vid_pll", "fclk_div4", "fclk_div3"
"xtal", NULL, "fclk_div4", "fclk_div3"
};
static const struct meson_pwm_data pwm_meson8b_data = {
......@@ -386,15 +348,6 @@ static const struct meson_pwm_data pwm_meson8b_data = {
.num_parents = ARRAY_SIZE(pwm_meson8b_parent_names),
};
static const char * const pwm_gxbb_parent_names[] = {
"xtal", "hdmi_pll", "fclk_div4", "fclk_div3"
};
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
......@@ -444,15 +397,6 @@ static const struct meson_pwm_data pwm_g12a_ao_cd_data = {
.num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_names),
};
static const char * const pwm_g12a_ee_parent_names[] = {
"xtal", "hdmi_pll", "fclk_div4", "fclk_div3"
};
static const struct meson_pwm_data pwm_g12a_ee_data = {
.parent_names = pwm_g12a_ee_parent_names,
.num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_names),
};
static const struct of_device_id meson_pwm_matches[] = {
{
.compatible = "amlogic,meson8b-pwm",
......@@ -460,7 +404,7 @@ static const struct of_device_id meson_pwm_matches[] = {
},
{
.compatible = "amlogic,meson-gxbb-pwm",
.data = &pwm_gxbb_data
.data = &pwm_meson8b_data
},
{
.compatible = "amlogic,meson-gxbb-ao-pwm",
......@@ -476,7 +420,7 @@ static const struct of_device_id meson_pwm_matches[] = {
},
{
.compatible = "amlogic,meson-g12a-ee-pwm",
.data = &pwm_g12a_ee_data
.data = &pwm_meson8b_data
},
{
.compatible = "amlogic,meson-g12a-ao-pwm-ab",
......@@ -492,21 +436,28 @@ MODULE_DEVICE_TABLE(of, meson_pwm_matches);
static int meson_pwm_init_channels(struct meson_pwm *meson)
{
struct clk_parent_data mux_parent_data[MESON_MAX_MUX_PARENTS] = {};
struct device *dev = meson->chip.dev;
struct clk_init_data init;
unsigned int i;
char name[255];
int err;
for (i = 0; i < meson->data->num_parents; i++) {
mux_parent_data[i].index = -1;
mux_parent_data[i].name = meson->data->parent_names[i];
}
for (i = 0; i < meson->chip.npwm; i++) {
struct meson_pwm_channel *channel = &meson->channels[i];
struct clk_parent_data div_parent = {}, gate_parent = {};
struct clk_init_data init = {};
snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i);
init.name = name;
init.ops = &clk_mux_ops;
init.flags = 0;
init.parent_names = meson->data->parent_names;
init.parent_data = mux_parent_data;
init.num_parents = meson->data->num_parents;
channel->mux.reg = meson->base + REG_MISC_AB;
......@@ -518,18 +469,63 @@ static int meson_pwm_init_channels(struct meson_pwm *meson)
channel->mux.table = NULL;
channel->mux.hw.init = &init;
channel->clk = devm_clk_register(dev, &channel->mux.hw);
if (IS_ERR(channel->clk)) {
err = PTR_ERR(channel->clk);
err = devm_clk_hw_register(dev, &channel->mux.hw);
if (err) {
dev_err(dev, "failed to register %s: %d\n", name, err);
return err;
}
snprintf(name, sizeof(name), "%s#div%u", dev_name(dev), i);
init.name = name;
init.ops = &clk_divider_ops;
init.flags = CLK_SET_RATE_PARENT;
div_parent.index = -1;
div_parent.hw = &channel->mux.hw;
init.parent_data = &div_parent;
init.num_parents = 1;
channel->div.reg = meson->base + REG_MISC_AB;
channel->div.shift = meson_pwm_per_channel_data[i].clk_div_shift;
channel->div.width = MISC_CLK_DIV_WIDTH;
channel->div.hw.init = &init;
channel->div.flags = 0;
channel->div.lock = &meson->lock;
err = devm_clk_hw_register(dev, &channel->div.hw);
if (err) {
dev_err(dev, "failed to register %s: %d\n", name, err);
return err;
}
snprintf(name, sizeof(name), "clkin%u", i);
snprintf(name, sizeof(name), "%s#gate%u", dev_name(dev), i);
channel->clk_parent = devm_clk_get_optional(dev, name);
if (IS_ERR(channel->clk_parent))
return PTR_ERR(channel->clk_parent);
init.name = name;
init.ops = &clk_gate_ops;
init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;
gate_parent.index = -1;
gate_parent.hw = &channel->div.hw;
init.parent_data = &gate_parent;
init.num_parents = 1;
channel->gate.reg = meson->base + REG_MISC_AB;
channel->gate.bit_idx = meson_pwm_per_channel_data[i].clk_en_shift;
channel->gate.hw.init = &init;
channel->gate.flags = 0;
channel->gate.lock = &meson->lock;
err = devm_clk_hw_register(dev, &channel->gate.hw);
if (err) {
dev_err(dev, "failed to register %s: %d\n", name, err);
return err;
}
channel->clk = devm_clk_hw_get_clk(dev, &channel->gate.hw, NULL);
if (IS_ERR(channel->clk)) {
err = PTR_ERR(channel->clk);
dev_err(dev, "failed to register %s: %d\n", name, err);
return err;
}
}
return 0;
......
// SPDX-License-Identifier: GPL-2.0
/*
* corePWM driver for Microchip "soft" FPGA IP cores.
*
* Copyright (c) 2021-2023 Microchip Corporation. All rights reserved.
* Author: Conor Dooley <conor.dooley@microchip.com>
* Documentation:
* https://www.microsemi.com/document-portal/doc_download/1245275-corepwm-hb
*
* Limitations:
* - If the IP block is configured without "shadow registers", all register
* writes will take effect immediately, causing glitches on the output.
* If shadow registers *are* enabled, setting the "SYNC_UPDATE" register
* notifies the core that it needs to update the registers defining the
* waveform from the contents of the "shadow registers". Otherwise, changes
* will take effective immediately, even for those channels.
* As setting the period/duty cycle takes 4 register writes, there is a window
* in which this races against the start of a new period.
* - The IP block has no concept of a duty cycle, only rising/falling edges of
* the waveform. Unfortunately, if the rising & falling edges registers have
* the same value written to them the IP block will do whichever of a rising
* or a falling edge is possible. I.E. a 50% waveform at twice the requested
* period. Therefore to get a 0% waveform, the output is set the max high/low
* time depending on polarity.
* If the duty cycle is 0%, and the requested period is less than the
* available period resolution, this will manifest as a ~100% waveform (with
* some output glitches) rather than 50%.
* - The PWM period is set for the whole IP block not per channel. The driver
* will only change the period if no other PWM output is enabled.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/ktime.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define MCHPCOREPWM_PRESCALE_MAX 0xff
#define MCHPCOREPWM_PERIOD_STEPS_MAX 0xfe
#define MCHPCOREPWM_PERIOD_MAX 0xff00
#define MCHPCOREPWM_PRESCALE 0x00
#define MCHPCOREPWM_PERIOD 0x04
#define MCHPCOREPWM_EN(i) (0x08 + 0x04 * (i)) /* 0x08, 0x0c */
#define MCHPCOREPWM_POSEDGE(i) (0x10 + 0x08 * (i)) /* 0x10, 0x18, ..., 0x88 */
#define MCHPCOREPWM_NEGEDGE(i) (0x14 + 0x08 * (i)) /* 0x14, 0x1c, ..., 0x8c */
#define MCHPCOREPWM_SYNC_UPD 0xe4
#define MCHPCOREPWM_TIMEOUT_MS 100u
struct mchp_core_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
struct mutex lock; /* protects the shared period */
ktime_t update_timestamp;
u32 sync_update_mask;
u16 channel_enabled;
};
static inline struct mchp_core_pwm_chip *to_mchp_core_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct mchp_core_pwm_chip, chip);
}
static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
bool enable, u64 period)
{
struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
u8 channel_enable, reg_offset, shift;
/*
* There are two adjacent 8 bit control regs, the lower reg controls
* 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg
* and if so, offset by the bus width.
*/
reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3);
shift = pwm->hwpwm & 7;
channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset);
channel_enable &= ~(1 << shift);
channel_enable |= (enable << shift);
writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset);
mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm);
mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm;
/*
* The updated values will not appear on the bus until they have been
* applied to the waveform at the beginning of the next period.
* This is a NO-OP if the channel does not have shadow registers.
*/
if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm))
mchp_core_pwm->update_timestamp = ktime_add_ns(ktime_get(), period);
}
static void mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
unsigned int channel)
{
/*
* If a shadow register is used for this PWM channel, and iff there is
* a pending update to the waveform, we must wait for it to be applied
* before attempting to read its state. Reading the registers yields
* the currently implemented settings & the new ones are only readable
* once the current period has ended.
*/
if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
ktime_t current_time = ktime_get();
s64 remaining_ns;
u32 delay_us;
remaining_ns = ktime_to_ns(ktime_sub(mchp_core_pwm->update_timestamp,
current_time));
/*
* If the update has gone through, don't bother waiting for
* obvious reasons. Otherwise wait around for an appropriate
* amount of time for the update to go through.
*/
if (remaining_ns <= 0)
return;
delay_us = DIV_ROUND_UP_ULL(remaining_ns, NSEC_PER_USEC);
fsleep(delay_us);
}
}
static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate,
u8 prescale, u8 period_steps)
{
u64 duty_steps, tmp;
/*
* Calculate the duty cycle in multiples of the prescaled period:
* duty_steps = duty_in_ns / step_in_ns
* step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate
* The code below is rearranged slightly to only divide once.
*/
tmp = (((u64)prescale) + 1) * NSEC_PER_SEC;
duty_steps = mul_u64_u64_div_u64(state->duty_cycle, clk_rate, tmp);
return duty_steps;
}
static void mchp_core_pwm_apply_duty(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state, u64 duty_steps,
u16 period_steps)
{
struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
u8 posedge, negedge;
u8 first_edge = 0, second_edge = duty_steps;
/*
* Setting posedge == negedge doesn't yield a constant output,
* so that's an unsuitable setting to model duty_steps = 0.
* In that case set the unwanted edge to a value that never
* triggers.
*/
if (duty_steps == 0)
first_edge = period_steps + 1;
if (state->polarity == PWM_POLARITY_INVERSED) {
negedge = first_edge;
posedge = second_edge;
} else {
posedge = first_edge;
negedge = second_edge;
}
/*
* Set the sync bit which ensures that periods that already started are
* completed unaltered. At each counter reset event the values are
* updated from the shadow registers.
*/
writel_relaxed(posedge, mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
writel_relaxed(negedge, mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
}
static int mchp_core_pwm_calc_period(const struct pwm_state *state, unsigned long clk_rate,
u16 *prescale, u16 *period_steps)
{
u64 tmp;
/*
* Calculate the period cycles and prescale values.
* The registers are each 8 bits wide & multiplied to compute the period
* using the formula:
* (prescale + 1) * (period_steps + 1)
* period = -------------------------------------
* clk_rate
* so the maximum period that can be generated is 0x10000 times the
* period of the input clock.
* However, due to the design of the "hardware", it is not possible to
* attain a 100% duty cycle if the full range of period_steps is used.
* Therefore period_steps is restricted to 0xfe and the maximum multiple
* of the clock period attainable is (0xff + 1) * (0xfe + 1) = 0xff00
*
* The prescale and period_steps registers operate similarly to
* CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that
* in the register plus one.
* It's therefore not possible to set a period lower than 1/clk_rate, so
* if tmp is 0, abort. Without aborting, we will set a period that is
* greater than that requested and, more importantly, will trigger the
* neg-/pos-edge issue described in the limitations.
*/
tmp = mul_u64_u64_div_u64(state->period, clk_rate, NSEC_PER_SEC);
if (tmp >= MCHPCOREPWM_PERIOD_MAX) {
*prescale = MCHPCOREPWM_PRESCALE_MAX;
*period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX;
return 0;
}
/*
* There are multiple strategies that could be used to choose the
* prescale & period_steps values.
* Here the idea is to pick values so that the selection of duty cycles
* is as finegrain as possible, while also keeping the period less than
* that requested.
*
* A simple way to satisfy the first condition is to always set
* period_steps to its maximum value. This neatly also satisfies the
* second condition too, since using the maximum value of period_steps
* to calculate prescale actually calculates its upper bound.
* Integer division will ensure a round down, so the period will thereby
* always be less than that requested.
*
* The downside of this approach is a significant degree of inaccuracy,
* especially as tmp approaches integer multiples of
* MCHPCOREPWM_PERIOD_STEPS_MAX.
*
* As we must produce a period less than that requested, and for the
* sake of creating a simple algorithm, disallow small values of tmp
* that would need special handling.
*/
if (tmp < MCHPCOREPWM_PERIOD_STEPS_MAX + 1)
return -EINVAL;
/*
* This "optimal" value for prescale is be calculated using the maximum
* permitted value of period_steps, 0xfe.
*
* period * clk_rate
* prescale = ------------------------- - 1
* NSEC_PER_SEC * (0xfe + 1)
*
*
* period * clk_rate
* ------------------- was precomputed as `tmp`
* NSEC_PER_SEC
*/
*prescale = ((u16)tmp) / (MCHPCOREPWM_PERIOD_STEPS_MAX + 1) - 1;
/*
* period_steps can be computed from prescale:
* period * clk_rate
* period_steps = ----------------------------- - 1
* NSEC_PER_SEC * (prescale + 1)
*
* However, in this approximation, we simply use the maximum value that
* was used to compute prescale.
*/
*period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX;
return 0;
}
static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
bool period_locked;
unsigned long clk_rate;
u64 duty_steps;
u16 prescale, period_steps;
int ret;
if (!state->enabled) {
mchp_core_pwm_enable(chip, pwm, false, pwm->state.period);
return 0;
}
/*
* If clk_rate is too big, the following multiplication might overflow.
* However this is implausible, as the fabric of current FPGAs cannot
* provide clocks at a rate high enough.
*/
clk_rate = clk_get_rate(mchp_core_pwm->clk);
if (clk_rate >= NSEC_PER_SEC)
return -EINVAL;
ret = mchp_core_pwm_calc_period(state, clk_rate, &prescale, &period_steps);
if (ret)
return ret;
/*
* If the only thing that has changed is the duty cycle or the polarity,
* we can shortcut the calculations and just compute/apply the new duty
* cycle pos & neg edges
* As all the channels share the same period, do not allow it to be
* changed if any other channels are enabled.
* If the period is locked, it may not be possible to use a period
* less than that requested. In that case, we just abort.
*/
period_locked = mchp_core_pwm->channel_enabled & ~(1 << pwm->hwpwm);
if (period_locked) {
u16 hw_prescale;
u16 hw_period_steps;
hw_prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
hw_period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
if ((period_steps + 1) * (prescale + 1) <
(hw_period_steps + 1) * (hw_prescale + 1))
return -EINVAL;
/*
* It is possible that something could have set the period_steps
* register to 0xff, which would prevent us from setting a 100%
* or 0% relative duty cycle, as explained above in
* mchp_core_pwm_calc_period().
* The period is locked and we cannot change this, so we abort.
*/
if (hw_period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX)
return -EINVAL;
prescale = hw_prescale;
period_steps = hw_period_steps;
}
duty_steps = mchp_core_pwm_calc_duty(state, clk_rate, prescale, period_steps);
/*
* Because the period is not per channel, it is possible that the
* requested duty cycle is longer than the period, in which case cap it
* to the period, IOW a 100% duty cycle.
*/
if (duty_steps > period_steps)
duty_steps = period_steps + 1;
if (!period_locked) {
writel_relaxed(prescale, mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
}
mchp_core_pwm_apply_duty(chip, pwm, state, duty_steps, period_steps);
mchp_core_pwm_enable(chip, pwm, true, pwm->state.period);
return 0;
}
static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
int ret;
mutex_lock(&mchp_core_pwm->lock);
mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
ret = mchp_core_pwm_apply_locked(chip, pwm, state);
mutex_unlock(&mchp_core_pwm->lock);
return ret;
}
static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
u64 rate;
u16 prescale, period_steps;
u8 duty_steps, posedge, negedge;
mutex_lock(&mchp_core_pwm->lock);
mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm))
state->enabled = true;
else
state->enabled = false;
rate = clk_get_rate(mchp_core_pwm->clk);
/*
* Calculating the period:
* The registers are each 8 bits wide & multiplied to compute the period
* using the formula:
* (prescale + 1) * (period_steps + 1)
* period = -------------------------------------
* clk_rate
*
* Note:
* The prescale and period_steps registers operate similarly to
* CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that
* in the register plus one.
*/
prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
state->period = (period_steps + 1) * (prescale + 1);
state->period *= NSEC_PER_SEC;
state->period = DIV64_U64_ROUND_UP(state->period, rate);
posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
mutex_unlock(&mchp_core_pwm->lock);
if (negedge == posedge) {
state->duty_cycle = state->period;
state->period *= 2;
} else {
duty_steps = abs((s16)posedge - (s16)negedge);
state->duty_cycle = duty_steps * (prescale + 1) * NSEC_PER_SEC;
state->duty_cycle = DIV64_U64_ROUND_UP(state->duty_cycle, rate);
}
state->polarity = negedge < posedge ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
return 0;
}
static const struct pwm_ops mchp_core_pwm_ops = {
.apply = mchp_core_pwm_apply,
.get_state = mchp_core_pwm_get_state,
.owner = THIS_MODULE,
};
static const struct of_device_id mchp_core_of_match[] = {
{
.compatible = "microchip,corepwm-rtl-v4",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mchp_core_of_match);
static int mchp_core_pwm_probe(struct platform_device *pdev)
{
struct mchp_core_pwm_chip *mchp_core_pwm;
struct resource *regs;
int ret;
mchp_core_pwm = devm_kzalloc(&pdev->dev, sizeof(*mchp_core_pwm), GFP_KERNEL);
if (!mchp_core_pwm)
return -ENOMEM;
mchp_core_pwm->base = devm_platform_get_and_ioremap_resource(pdev, 0, &regs);
if (IS_ERR(mchp_core_pwm->base))
return PTR_ERR(mchp_core_pwm->base);
mchp_core_pwm->clk = devm_clk_get_enabled(&pdev->dev, NULL);
if (IS_ERR(mchp_core_pwm->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(mchp_core_pwm->clk),
"failed to get PWM clock\n");
if (of_property_read_u32(pdev->dev.of_node, "microchip,sync-update-mask",
&mchp_core_pwm->sync_update_mask))
mchp_core_pwm->sync_update_mask = 0;
mutex_init(&mchp_core_pwm->lock);
mchp_core_pwm->chip.dev = &pdev->dev;
mchp_core_pwm->chip.ops = &mchp_core_pwm_ops;
mchp_core_pwm->chip.npwm = 16;
mchp_core_pwm->channel_enabled = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(0));
mchp_core_pwm->channel_enabled |=
readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(1)) << 8;
/*
* Enable synchronous update mode for all channels for which shadow
* registers have been synthesised.
*/
writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
mchp_core_pwm->update_timestamp = ktime_get();
ret = devm_pwmchip_add(&pdev->dev, &mchp_core_pwm->chip);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to add pwmchip\n");
return 0;
}
static struct platform_driver mchp_core_pwm_driver = {
.driver = {
.name = "mchp-core-pwm",
.of_match_table = mchp_core_of_match,
},
.probe = mchp_core_pwm_probe,
};
module_platform_driver(mchp_core_pwm_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>");
MODULE_DESCRIPTION("corePWM driver for Microchip FPGAs");
......@@ -79,14 +79,11 @@ static int mtk_disp_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask,
0x0);
if (mdp->enabled) {
clk_disable_unprepare(mdp->clk_mm);
clk_disable_unprepare(mdp->clk_main);
}
if (!state->enabled && mdp->enabled) {
mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN,
mdp->data->enable_mask, 0x0);
clk_disable_unprepare(mdp->clk_mm);
clk_disable_unprepare(mdp->clk_main);
mdp->enabled = false;
return 0;
......
......@@ -665,7 +665,7 @@ static struct i2c_driver pca9685_i2c_driver = {
.of_match_table = of_match_ptr(pca9685_dt_ids),
.pm = &pca9685_pwm_pm,
},
.probe_new = pca9685_pwm_probe,
.probe = pca9685_pwm_probe,
.remove = pca9685_pwm_remove,
.id_table = pca9685_id,
};
......
// SPDX-License-Identifier: GPL-2.0
/*
* Renesas RZ/G2L MTU3a PWM Timer driver
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Hardware manual for this IP can be found here
* https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en
*
* Limitations:
* - When PWM is disabled, the output is driven to Hi-Z.
* - While the hardware supports both polarities, the driver (for now)
* only handles normal polarity.
* - HW uses one counter and two match components to configure duty_cycle
* and period.
* - Multi-Function Timer Pulse Unit (a.k.a MTU) has 7 HW channels for PWM
* operations. (The channels are MTU{0..4, 6, 7}.)
* - MTU{1, 2} channels have a single IO, whereas all other HW channels have
* 2 IOs.
* - Each IO is modelled as an independent PWM channel.
* - rz_mtu3_channel_io_map table is used to map the PWM channel to the
* corresponding HW channel as there are difference in number of IOs
* between HW channels.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/limits.h>
#include <linux/mfd/rz-mtu3.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
#include <linux/time.h>
#define RZ_MTU3_MAX_PWM_CHANNELS 12
#define RZ_MTU3_MAX_HW_CHANNELS 7
/**
* struct rz_mtu3_channel_io_map - MTU3 pwm channel map
*
* @base_pwm_number: First PWM of a channel
* @num: number of IOs on the HW channel.
*/
struct rz_mtu3_channel_io_map {
u8 base_pwm_number;
u8 num_channel_ios;
};
/**
* struct rz_mtu3_pwm_channel - MTU3 pwm channel data
*
* @mtu: MTU3 channel data
* @map: MTU3 pwm channel map
*/
struct rz_mtu3_pwm_channel {
struct rz_mtu3_channel *mtu;
const struct rz_mtu3_channel_io_map *map;
};
/**
* struct rz_mtu3_pwm_chip - MTU3 pwm private data
*
* @chip: MTU3 pwm chip data
* @clk: MTU3 module clock
* @lock: Lock to prevent concurrent access for usage count
* @rate: MTU3 clock rate
* @user_count: MTU3 usage count
* @enable_count: MTU3 enable count
* @prescale: MTU3 prescale
* @channel_data: MTU3 pwm channel data
*/
struct rz_mtu3_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
struct mutex lock;
unsigned long rate;
u32 user_count[RZ_MTU3_MAX_HW_CHANNELS];
u32 enable_count[RZ_MTU3_MAX_HW_CHANNELS];
u8 prescale[RZ_MTU3_MAX_HW_CHANNELS];
struct rz_mtu3_pwm_channel channel_data[RZ_MTU3_MAX_HW_CHANNELS];
};
/*
* The MTU channels are {0..4, 6, 7} and the number of IO on MTU1
* and MTU2 channel is 1 compared to 2 on others.
*/
static const struct rz_mtu3_channel_io_map channel_map[] = {
{ 0, 2 }, { 2, 1 }, { 3, 1 }, { 4, 2 }, { 6, 2 }, { 8, 2 }, { 10, 2 }
};
static inline struct rz_mtu3_pwm_chip *to_rz_mtu3_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct rz_mtu3_pwm_chip, chip);
}
static void rz_mtu3_pwm_read_tgr_registers(struct rz_mtu3_pwm_channel *priv,
u16 reg_pv_offset, u16 *pv_val,
u16 reg_dc_offset, u16 *dc_val)
{
*pv_val = rz_mtu3_16bit_ch_read(priv->mtu, reg_pv_offset);
*dc_val = rz_mtu3_16bit_ch_read(priv->mtu, reg_dc_offset);
}
static void rz_mtu3_pwm_write_tgr_registers(struct rz_mtu3_pwm_channel *priv,
u16 reg_pv_offset, u16 pv_val,
u16 reg_dc_offset, u16 dc_val)
{
rz_mtu3_16bit_ch_write(priv->mtu, reg_pv_offset, pv_val);
rz_mtu3_16bit_ch_write(priv->mtu, reg_dc_offset, dc_val);
}
static u8 rz_mtu3_pwm_calculate_prescale(struct rz_mtu3_pwm_chip *rz_mtu3,
u64 period_cycles)
{
u32 prescaled_period_cycles;
u8 prescale;
/*
* Supported prescale values are 1, 4, 16 and 64.
* TODO: Support prescale values 2, 8, 32, 256 and 1024.
*/
prescaled_period_cycles = period_cycles >> 16;
if (prescaled_period_cycles >= 16)
prescale = 3;
else
prescale = (fls(prescaled_period_cycles) + 1) / 2;
return prescale;
}
static struct rz_mtu3_pwm_channel *
rz_mtu3_get_channel(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, u32 hwpwm)
{
struct rz_mtu3_pwm_channel *priv = rz_mtu3_pwm->channel_data;
unsigned int ch;
for (ch = 0; ch < RZ_MTU3_MAX_HW_CHANNELS; ch++, priv++) {
if (priv->map->base_pwm_number + priv->map->num_channel_ios > hwpwm)
break;
}
return priv;
}
static bool rz_mtu3_pwm_is_ch_enabled(struct rz_mtu3_pwm_chip *rz_mtu3_pwm,
u32 hwpwm)
{
struct rz_mtu3_pwm_channel *priv;
bool is_channel_en;
u8 val;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, hwpwm);
is_channel_en = rz_mtu3_is_enabled(priv->mtu);
if (!is_channel_en)
return false;
if (priv->map->base_pwm_number == hwpwm)
val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TIORH);
else
val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TIORL);
return val & RZ_MTU3_TIOR_IOA;
}
static int rz_mtu3_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
struct rz_mtu3_pwm_channel *priv;
bool is_mtu3_channel_available;
u32 ch;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
ch = priv - rz_mtu3_pwm->channel_data;
mutex_lock(&rz_mtu3_pwm->lock);
/*
* Each channel must be requested only once, so if the channel
* serves two PWMs and the other is already requested, skip over
* rz_mtu3_request_channel()
*/
if (!rz_mtu3_pwm->user_count[ch]) {
is_mtu3_channel_available = rz_mtu3_request_channel(priv->mtu);
if (!is_mtu3_channel_available) {
mutex_unlock(&rz_mtu3_pwm->lock);
return -EBUSY;
}
}
rz_mtu3_pwm->user_count[ch]++;
mutex_unlock(&rz_mtu3_pwm->lock);
return 0;
}
static void rz_mtu3_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
struct rz_mtu3_pwm_channel *priv;
u32 ch;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
ch = priv - rz_mtu3_pwm->channel_data;
mutex_lock(&rz_mtu3_pwm->lock);
rz_mtu3_pwm->user_count[ch]--;
if (!rz_mtu3_pwm->user_count[ch])
rz_mtu3_release_channel(priv->mtu);
mutex_unlock(&rz_mtu3_pwm->lock);
}
static int rz_mtu3_pwm_enable(struct rz_mtu3_pwm_chip *rz_mtu3_pwm,
struct pwm_device *pwm)
{
struct rz_mtu3_pwm_channel *priv;
u32 ch;
u8 val;
int rc;
rc = pm_runtime_resume_and_get(rz_mtu3_pwm->chip.dev);
if (rc)
return rc;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
ch = priv - rz_mtu3_pwm->channel_data;
val = RZ_MTU3_TIOR_OC_IOB_TOGGLE | RZ_MTU3_TIOR_OC_IOA_H_COMP_MATCH;
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_MD_PWMMODE1);
if (priv->map->base_pwm_number == pwm->hwpwm)
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORH, val);
else
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORL, val);
mutex_lock(&rz_mtu3_pwm->lock);
if (!rz_mtu3_pwm->enable_count[ch])
rz_mtu3_enable(priv->mtu);
rz_mtu3_pwm->enable_count[ch]++;
mutex_unlock(&rz_mtu3_pwm->lock);
return 0;
}
static void rz_mtu3_pwm_disable(struct rz_mtu3_pwm_chip *rz_mtu3_pwm,
struct pwm_device *pwm)
{
struct rz_mtu3_pwm_channel *priv;
u32 ch;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
ch = priv - rz_mtu3_pwm->channel_data;
/* Disable output pins of MTU3 channel */
if (priv->map->base_pwm_number == pwm->hwpwm)
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORH, RZ_MTU3_TIOR_OC_RETAIN);
else
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORL, RZ_MTU3_TIOR_OC_RETAIN);
mutex_lock(&rz_mtu3_pwm->lock);
rz_mtu3_pwm->enable_count[ch]--;
if (!rz_mtu3_pwm->enable_count[ch])
rz_mtu3_disable(priv->mtu);
mutex_unlock(&rz_mtu3_pwm->lock);
pm_runtime_put_sync(rz_mtu3_pwm->chip.dev);
}
static int rz_mtu3_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
int rc;
rc = pm_runtime_resume_and_get(chip->dev);
if (rc)
return rc;
state->enabled = rz_mtu3_pwm_is_ch_enabled(rz_mtu3_pwm, pwm->hwpwm);
if (state->enabled) {
struct rz_mtu3_pwm_channel *priv;
u8 prescale, val;
u16 dc, pv;
u64 tmp;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
if (priv->map->base_pwm_number == pwm->hwpwm)
rz_mtu3_pwm_read_tgr_registers(priv, RZ_MTU3_TGRA, &pv,
RZ_MTU3_TGRB, &dc);
else
rz_mtu3_pwm_read_tgr_registers(priv, RZ_MTU3_TGRC, &pv,
RZ_MTU3_TGRD, &dc);
val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TCR);
prescale = FIELD_GET(RZ_MTU3_TCR_TPCS, val);
/* With prescale <= 7 and pv <= 0xffff this doesn't overflow. */
tmp = NSEC_PER_SEC * (u64)pv << (2 * prescale);
state->period = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate);
tmp = NSEC_PER_SEC * (u64)dc << (2 * prescale);
state->duty_cycle = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate);
if (state->duty_cycle > state->period)
state->duty_cycle = state->period;
}
state->polarity = PWM_POLARITY_NORMAL;
pm_runtime_put(chip->dev);
return 0;
}
static u16 rz_mtu3_pwm_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
{
return min(period_or_duty_cycle >> (2 * prescale), (u64)U16_MAX);
}
static int rz_mtu3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
struct rz_mtu3_pwm_channel *priv;
u64 period_cycles;
u64 duty_cycles;
u8 prescale;
u16 pv, dc;
u8 val;
u32 ch;
priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
ch = priv - rz_mtu3_pwm->channel_data;
period_cycles = mul_u64_u32_div(state->period, rz_mtu3_pwm->rate,
NSEC_PER_SEC);
prescale = rz_mtu3_pwm_calculate_prescale(rz_mtu3_pwm, period_cycles);
/*
* Prescalar is shared by multiple channels, so prescale can
* NOT be modified when there are multiple channels in use with
* different settings. Modify prescalar if other PWM is off or handle
* it, if current prescale value is less than the one we want to set.
*/
if (rz_mtu3_pwm->enable_count[ch] > 1) {
if (rz_mtu3_pwm->prescale[ch] > prescale)
return -EBUSY;
prescale = rz_mtu3_pwm->prescale[ch];
}
pv = rz_mtu3_pwm_calculate_pv_or_dc(period_cycles, prescale);
duty_cycles = mul_u64_u32_div(state->duty_cycle, rz_mtu3_pwm->rate,
NSEC_PER_SEC);
dc = rz_mtu3_pwm_calculate_pv_or_dc(duty_cycles, prescale);
/*
* If the PWM channel is disabled, make sure to turn on the clock
* before writing the register.
*/
if (!pwm->state.enabled) {
int rc;
rc = pm_runtime_resume_and_get(chip->dev);
if (rc)
return rc;
}
val = RZ_MTU3_TCR_CKEG_RISING | prescale;
/* Counter must be stopped while updating TCR register */
if (rz_mtu3_pwm->prescale[ch] != prescale && rz_mtu3_pwm->enable_count[ch])
rz_mtu3_disable(priv->mtu);
if (priv->map->base_pwm_number == pwm->hwpwm) {
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TCR,
RZ_MTU3_TCR_CCLR_TGRA | val);
rz_mtu3_pwm_write_tgr_registers(priv, RZ_MTU3_TGRA, pv,
RZ_MTU3_TGRB, dc);
} else {
rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TCR,
RZ_MTU3_TCR_CCLR_TGRC | val);
rz_mtu3_pwm_write_tgr_registers(priv, RZ_MTU3_TGRC, pv,
RZ_MTU3_TGRD, dc);
}
if (rz_mtu3_pwm->prescale[ch] != prescale) {
/*
* Prescalar is shared by multiple channels, we cache the
* prescalar value from first enabled channel and use the same
* value for both channels.
*/
rz_mtu3_pwm->prescale[ch] = prescale;
if (rz_mtu3_pwm->enable_count[ch])
rz_mtu3_enable(priv->mtu);
}
/* If the PWM is not enabled, turn the clock off again to save power. */
if (!pwm->state.enabled)
pm_runtime_put(chip->dev);
return 0;
}
static int rz_mtu3_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
bool enabled = pwm->state.enabled;
int ret;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (enabled)
rz_mtu3_pwm_disable(rz_mtu3_pwm, pwm);
return 0;
}
mutex_lock(&rz_mtu3_pwm->lock);
ret = rz_mtu3_pwm_config(chip, pwm, state);
mutex_unlock(&rz_mtu3_pwm->lock);
if (ret)
return ret;
if (!enabled)
ret = rz_mtu3_pwm_enable(rz_mtu3_pwm, pwm);
return ret;
}
static const struct pwm_ops rz_mtu3_pwm_ops = {
.request = rz_mtu3_pwm_request,
.free = rz_mtu3_pwm_free,
.get_state = rz_mtu3_pwm_get_state,
.apply = rz_mtu3_pwm_apply,
.owner = THIS_MODULE,
};
static int rz_mtu3_pwm_pm_runtime_suspend(struct device *dev)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = dev_get_drvdata(dev);
clk_disable_unprepare(rz_mtu3_pwm->clk);
return 0;
}
static int rz_mtu3_pwm_pm_runtime_resume(struct device *dev)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = dev_get_drvdata(dev);
return clk_prepare_enable(rz_mtu3_pwm->clk);
}
static DEFINE_RUNTIME_DEV_PM_OPS(rz_mtu3_pwm_pm_ops,
rz_mtu3_pwm_pm_runtime_suspend,
rz_mtu3_pwm_pm_runtime_resume, NULL);
static void rz_mtu3_pwm_pm_disable(void *data)
{
struct rz_mtu3_pwm_chip *rz_mtu3_pwm = data;
clk_rate_exclusive_put(rz_mtu3_pwm->clk);
pm_runtime_disable(rz_mtu3_pwm->chip.dev);
pm_runtime_set_suspended(rz_mtu3_pwm->chip.dev);
}
static int rz_mtu3_pwm_probe(struct platform_device *pdev)
{
struct rz_mtu3 *parent_ddata = dev_get_drvdata(pdev->dev.parent);
struct rz_mtu3_pwm_chip *rz_mtu3_pwm;
struct device *dev = &pdev->dev;
unsigned int i, j = 0;
int ret;
rz_mtu3_pwm = devm_kzalloc(&pdev->dev, sizeof(*rz_mtu3_pwm), GFP_KERNEL);
if (!rz_mtu3_pwm)
return -ENOMEM;
rz_mtu3_pwm->clk = parent_ddata->clk;
for (i = 0; i < RZ_MTU_NUM_CHANNELS; i++) {
if (i == RZ_MTU3_CHAN_5 || i == RZ_MTU3_CHAN_8)
continue;
rz_mtu3_pwm->channel_data[j].mtu = &parent_ddata->channels[i];
rz_mtu3_pwm->channel_data[j].mtu->dev = dev;
rz_mtu3_pwm->channel_data[j].map = &channel_map[j];
j++;
}
mutex_init(&rz_mtu3_pwm->lock);
platform_set_drvdata(pdev, rz_mtu3_pwm);
ret = clk_prepare_enable(rz_mtu3_pwm->clk);
if (ret)
return dev_err_probe(dev, ret, "Clock enable failed\n");
clk_rate_exclusive_get(rz_mtu3_pwm->clk);
rz_mtu3_pwm->rate = clk_get_rate(rz_mtu3_pwm->clk);
/*
* Refuse clk rates > 1 GHz to prevent overflow later for computing
* period and duty cycle.
*/
if (rz_mtu3_pwm->rate > NSEC_PER_SEC) {
ret = -EINVAL;
clk_rate_exclusive_put(rz_mtu3_pwm->clk);
goto disable_clock;
}
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
rz_mtu3_pwm->chip.dev = &pdev->dev;
ret = devm_add_action_or_reset(&pdev->dev, rz_mtu3_pwm_pm_disable,
rz_mtu3_pwm);
if (ret < 0)
return ret;
rz_mtu3_pwm->chip.ops = &rz_mtu3_pwm_ops;
rz_mtu3_pwm->chip.npwm = RZ_MTU3_MAX_PWM_CHANNELS;
ret = devm_pwmchip_add(&pdev->dev, &rz_mtu3_pwm->chip);
if (ret)
return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
pm_runtime_idle(&pdev->dev);
return 0;
disable_clock:
clk_disable_unprepare(rz_mtu3_pwm->clk);
return ret;
}
static struct platform_driver rz_mtu3_pwm_driver = {
.driver = {
.name = "pwm-rz-mtu3",
.pm = pm_ptr(&rz_mtu3_pwm_pm_ops),
},
.probe = rz_mtu3_pwm_probe,
};
module_platform_driver(rz_mtu3_pwm_driver);
MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
MODULE_ALIAS("platform:pwm-rz-mtu3");
MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a PWM Timer Driver");
MODULE_LICENSE("GPL");
......@@ -244,12 +244,12 @@ static int pwm_sifive_probe(struct platform_device *pdev)
if (IS_ERR(ddata->regs))
return PTR_ERR(ddata->regs);
ddata->clk = devm_clk_get(dev, NULL);
ddata->clk = devm_clk_get_prepared(dev, NULL);
if (IS_ERR(ddata->clk))
return dev_err_probe(dev, PTR_ERR(ddata->clk),
"Unable to find controller clock\n");
ret = clk_prepare_enable(ddata->clk);
ret = clk_enable(ddata->clk);
if (ret) {
dev_err(dev, "failed to enable clock for pwm: %d\n", ret);
return ret;
......@@ -308,7 +308,6 @@ static int pwm_sifive_probe(struct platform_device *pdev)
clk_disable(ddata->clk);
--enabled_clks;
}
clk_unprepare(ddata->clk);
return ret;
}
......@@ -327,8 +326,6 @@ static void pwm_sifive_remove(struct platform_device *dev)
if (pwm->state.enabled)
clk_disable(ddata->clk);
}
clk_unprepare(ddata->clk);
}
static const struct of_device_id pwm_sifive_of_match[] = {
......
......@@ -424,6 +424,13 @@ static int pwm_class_resume_npwm(struct device *parent, unsigned int npwm)
if (!export)
continue;
/* If pwmchip was not enabled before suspend, do nothing. */
if (!export->suspend.enabled) {
/* release lock taken in pwm_class_get_state */
mutex_unlock(&export->lock);
continue;
}
state.enabled = export->suspend.enabled;
ret = pwm_class_apply_state(export, pwm, &state);
if (ret < 0)
......@@ -448,7 +455,17 @@ static int pwm_class_suspend(struct device *parent)
if (!export)
continue;
/*
* If pwmchip was not enabled before suspend, save
* state for resume time and do nothing else.
*/
export->suspend = state;
if (!state.enabled) {
/* release lock taken in pwm_class_get_state */
mutex_unlock(&export->lock);
continue;
}
state.enabled = false;
ret = pwm_class_apply_state(export, pwm, &state);
if (ret < 0) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment