pwm: Changes for v6.5-rc1
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. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAmSlRkwZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zof9ZD/9PtA1GNgkM/FCJiIcIBXpx FVFf6y+gHx50g+O7JDbfcNxS3b5rcbJQOwhxtTNR+9orkYUWuOkiUqWf8bUZuXCt tjw7xW1LzDdLnxrarCD37T0aihYtKnKLxIQwdIEwS4nntaqJIdDdF62VDX+jdsbN YvXN1WrMbGFq77EgjbWlHv8Bsm/T1mWPi3CNsKi33LvS4flCC1Mq28YalGUmwpZo ZCJmUhPFwRbmaQ74yjUOa/UST18NwjQ/P6X25jiQ5Fjg8HZZH7RDJMduWQsT9520 U7I7UtHyZiJSGXonHTTvyhUK55wrrTVJHaXxufqvQQPTpEMG1rtlgGnBjvi/4fxt vPIjWi46QCFtKzRFIaazHBiIEmbS+ahs8uSERmoBhlgynHyqe6/cZBdKwRp0kjSY jLhSiE38ODENdbQmyEWuCQqv1yWQfRwwm1/Y5B4/GEX9TRNMFfUDPTEIYKEPxPM2 XI/FjMM+bP52t/rEPgCHL9E26tWv3SFPNYrafacLLfpol2iLdlAzp+ni3uz6Zj3H f3d6LyKjVAPc1R3ICdrEu+YHRA7K10wlOQwWwXsg57dJmLtKY7gbIKJjmGVYZX63 od8/UUPzpF+eNHbrHFR1bAlwNO1XvPqHhPaQrqTQAV6zf3+VEovljLEGQmKz7yQP ROWkedAsD7rwkFKzz9zulg== =gWkY -----END PGP SIGNATURE----- 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 ...
This commit is contained in:
commit
ace1ba1c90
|
@ -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
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
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>;
|
||||
};
|
||||
....
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
# 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,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt8365_pwm_data = {
|
||||
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 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 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);
|
||||
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), "%s#gate%u", dev_name(dev), i);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "clkin%u", i);
|
||||
|
||||
channel->clk_parent = devm_clk_get_optional(dev, name);
|
||||
if (IS_ERR(channel->clk_parent))
|
||||
return PTR_ERR(channel->clk_parent);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -0,0 +1,507 @@
|
|||
// 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, ®s);
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,551 @@
|
|||
// 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 @@ disable_clk:
|
|||
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) {
|
||||
|
|
Loading…
Reference in New Issue