pwm: Changes for v4.14-rc1
The changes for this release include a new driver for the PWM controller found on SoCs of the ZTX ZX family. Support for an old SH-Mobile SoC has been dropped and the Rockchip and MediaTek drivers gain support for more generations. Other than that there are a bunch of coding style fixes, minor bug fixes and cleanup as well as documentation patches. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlm2fWoZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zofXuD/0WP6F9ZMx3FDpc02PJe7KA H634eZXRxjRBMyJ+fWqBf+VZ9uKyA8Qh+lKzve57vzBah8cnp2fRS6MtNxCqhq9T kiu9ocsbncF01HuAqBCVZM/TwbPIa8QtDGQ7HlmQ+brdWdXyntjilWmoW+DQuoDT hsC/AL+Ih0t9LH8zrtv5wtB92+920M9NUe35up3hrc1qQgHCs+1VwRQ0PUlhlkwF xQVop+t6rAtVVHx+mEw3tBQ913Ciy2sJY/BuGkXzTCj1P3i8HrPWE/laVXrn9Kbe taTeewCr1IZSnyFY4/0+JPz2/a/kFIa36N0uQop0WGcrKV9cp1Hdjuhqa4joKgdk rPrNJ3YQ4ji6U/vsy0FYuWPXfQI8sK7i56oJiRVAOIH0OaI6CTx1nDpRr/eXvqSd fVf/zwUIVdzutREqYHVU8WwfYJO//U1zWymsJsfD1hZu/2hSZryjemhIjrmi66gh RF+8Q9qrV1KGeLFIEAvRpnZ48yM/NKQxKqf9cBUfQmvELpu3b0ujx1eYmGefPqvu 1jIQbQ96sZI39geF+qt+FE994v7VZnYiMyhWhtpR8inkw9tfcYs+oGr70Jz+SgFf 0xV/M0aC7KWsOD7Avh3vqMH+j3tUf3zfK1/a0Jsr3PYLCDlw2WEnaWr5b3mv4YQA Ta4B6NVjFz41LD9rZV+68A== =GUps -----END PGP SIGNATURE----- Merge tag 'pwm/for-4.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm updates from Thierry Reding: "The changes for this release include a new driver for the PWM controller found on SoCs of the ZTX ZX family. Support for an old SH-Mobile SoC has been dropped and the Rockchip and MediaTek drivers gain support for more generations. Other than that there are a bunch of coding style fixes, minor bug fixes and cleanup as well as documentation patches" * tag 'pwm/for-4.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (32 commits) pwm: pwm-samsung: fix suspend/resume support pwm: samsung: Remove redundant checks from pwm_samsung_config() pwm: mediatek: Disable clock on PWM configuration failure dt-bindings: pwm: Add MT2712/MT7622 information pwm: mediatek: Fix clock control issue pwm: mediatek: Fix PWM source clock selection pwm: mediatek: Fix Kconfig description pwm: tegra: Explicitly request exclusive reset control pwm: hibvt: Explicitly request exclusive reset control pwm: tiehrpwm: Set driver data before runtime PM enable pwm: tiehrpwm: Miscellaneous coding style fixups pwm: tiecap: Set driver data before runtime PM enable pwm: tiecap: Miscellaneous coding style fixups dt-bindings: pwm: tiecap: Add TI 66AK2G SoC specific compatible pwm: tiehrpwm: fix clock imbalance in probe error path pwm: tiehrpwm: Fix runtime PM imbalance at unbind pwm: Kconfig: Enable pwm-tiecap to be built for Keystone pwm: Add ZTE ZX PWM device driver dt-bindings: pwm: Add bindings doc for ZTE ZX PWM controller pwm: bcm2835: Support for polarity setting via DT ...
This commit is contained in:
commit
66c9457df3
|
@ -6,7 +6,7 @@ Required properties:
|
|||
- 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 2. See pwm.txt in this directory for a description of
|
||||
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of
|
||||
the cells format.
|
||||
|
||||
Examples:
|
||||
|
@ -15,7 +15,7 @@ pwm@2020c000 {
|
|||
compatible = "brcm,bcm2835-pwm";
|
||||
reg = <0x2020c000 0x28>;
|
||||
clocks = <&clk_pwm>;
|
||||
#pwm-cells = <2>;
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
|
||||
clocks {
|
||||
|
|
|
@ -2,6 +2,8 @@ MediaTek PWM controller
|
|||
|
||||
Required properties:
|
||||
- compatible: should be "mediatek,<name>-pwm":
|
||||
- "mediatek,mt2712-pwm": found on mt2712 SoC.
|
||||
- "mediatek,mt7622-pwm": found on mt7622 SoC.
|
||||
- "mediatek,mt7623-pwm": found on mt7623 SoC.
|
||||
- reg: physical base address and length of the controller's registers.
|
||||
- #pwm-cells: must be 2. See pwm.txt in this directory for a description of
|
||||
|
@ -10,7 +12,9 @@ Required properties:
|
|||
- clock-names: must contain the following:
|
||||
- "top": the top clock generator
|
||||
- "main": clock used by the PWM core
|
||||
- "pwm1-5": the five per PWM clocks
|
||||
- "pwm1-8": the eight per PWM clocks for mt2712
|
||||
- "pwm1-6": the six per PWM clocks for mt7622
|
||||
- "pwm1-5": the five per PWM clocks for mt7623
|
||||
- pinctrl-names: Must contain a "default" entry.
|
||||
- pinctrl-0: One property must exist for each entry in pinctrl-names.
|
||||
See pinctrl/pinctrl-bindings.txt for details of the property values.
|
||||
|
|
|
@ -3,10 +3,17 @@ Rockchip PWM controller
|
|||
Required properties:
|
||||
- compatible: should be "rockchip,<name>-pwm"
|
||||
"rockchip,rk2928-pwm": found on RK29XX,RK3066 and RK3188 SoCs
|
||||
"rockchip,rk3288-pwm": found on RK3288 SoC
|
||||
"rockchip,rk3288-pwm": found on RK3288 SOC
|
||||
"rockchip,rv1108-pwm", "rockchip,rk3288-pwm": found on RV1108 SoC
|
||||
"rockchip,vop-pwm": found integrated in VOP on RK3288 SoC
|
||||
- reg: physical base address and length of the controller's registers
|
||||
- clocks: phandle and clock specifier of the PWM reference clock
|
||||
- clocks: See ../clock/clock-bindings.txt
|
||||
- For older hardware (rk2928, rk3066, rk3188, rk3228, rk3288, rk3399):
|
||||
- There is one clock that's used both to derive the functional clock
|
||||
for the device and as the bus clock.
|
||||
- For newer hardware (rk3328 and future socs): specified by name
|
||||
- "pwm": This is used to derive the functional clock.
|
||||
- "pclk": This is the APB bus clock.
|
||||
- #pwm-cells: must be 2 (rk2928) or 3 (rk3288). See pwm.txt in this directory
|
||||
for a description of the cell format.
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ Required properties:
|
|||
for am4372 - compatible = "ti,am4372-ecap", "ti,am3352-ecap", "ti,am33xx-ecap";
|
||||
for da850 - compatible = "ti,da850-ecap", "ti,am3352-ecap", "ti,am33xx-ecap";
|
||||
for dra746 - compatible = "ti,dra746-ecap", "ti,am3352-ecap";
|
||||
for 66ak2g - compatible = "ti,k2g-ecap", "ti,am3352-ecap";
|
||||
- #pwm-cells: should be 3. See pwm.txt in this directory for a description of
|
||||
the cells format. The PWM channel index ranges from 0 to 4. The only third
|
||||
cell flag supported by this binding is PWM_POLARITY_INVERTED.
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
ZTE ZX PWM controller
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "zte,zx296718-pwm".
|
||||
- reg: Physical base address and length of the controller's registers.
|
||||
- clocks : The phandle and specifier referencing the controller's clocks.
|
||||
- clock-names: "pclk" for PCLK, "wclk" for WCLK to the PWM controller. The
|
||||
PCLK is for register access, while WCLK is the reference clock for
|
||||
calculating period and duty cycles.
|
||||
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of
|
||||
the cells format.
|
||||
|
||||
Example:
|
||||
|
||||
pwm: pwm@1439000 {
|
||||
compatible = "zte,zx296718-pwm";
|
||||
reg = <0x1439000 0x1000>;
|
||||
clocks = <&lsp1crm LSP1_PWM_PCLK>,
|
||||
<&lsp1crm LSP1_PWM_WCLK>;
|
||||
clock-names = "pclk", "wclk";
|
||||
#pwm-cells = <3>;
|
||||
};
|
|
@ -6,7 +6,6 @@ Required Properties:
|
|||
- "renesas,tpu-r8a73a4": for R8A77A4 (R-Mobile APE6) compatible PWM controller.
|
||||
- "renesas,tpu-r8a7740": for R8A7740 (R-Mobile A1) compatible PWM controller.
|
||||
- "renesas,tpu-r8a7790": for R8A7790 (R-Car H2) compatible PWM controller.
|
||||
- "renesas,tpu-sh7372": for SH7372 (SH-Mobile AP4) compatible PWM controller.
|
||||
- "renesas,tpu": for generic R-Car TPU PWM controller.
|
||||
|
||||
- reg: Base address and length of each memory resource used by the PWM
|
||||
|
|
|
@ -300,7 +300,7 @@ config PWM_MEDIATEK
|
|||
Generic PWM framework driver for Mediatek ARM SoC.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-mxs.
|
||||
will be called pwm-mediatek.
|
||||
|
||||
config PWM_MXS
|
||||
tristate "Freescale MXS PWM support"
|
||||
|
@ -456,7 +456,7 @@ config PWM_TEGRA
|
|||
|
||||
config PWM_TIECAP
|
||||
tristate "ECAP PWM support"
|
||||
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX
|
||||
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE
|
||||
help
|
||||
PWM driver support for the ECAP APWM controller found on AM33XX
|
||||
TI SOC
|
||||
|
@ -510,4 +510,13 @@ config PWM_VT8500
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-vt8500.
|
||||
|
||||
config PWM_ZX
|
||||
tristate "ZTE ZX PWM support"
|
||||
depends on ARCH_ZX
|
||||
help
|
||||
Generic PWM framework driver for ZTE ZX family SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-zx.
|
||||
|
||||
endif
|
||||
|
|
|
@ -50,3 +50,4 @@ obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o
|
|||
obj-$(CONFIG_PWM_TWL) += pwm-twl.o
|
||||
obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
|
||||
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
|
||||
obj-$(CONFIG_PWM_ZX) += pwm-zx.o
|
||||
|
|
|
@ -167,6 +167,8 @@ static int bcm2835_pwm_probe(struct platform_device *pdev)
|
|||
pc->chip.dev = &pdev->dev;
|
||||
pc->chip.ops = &bcm2835_pwm_ops;
|
||||
pc->chip.npwm = 2;
|
||||
pc->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||
pc->chip.of_pwm_n_cells = 3;
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
|
||||
|
|
|
@ -208,7 +208,7 @@ static int hibvt_pwm_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pwm_chip->rstc = devm_reset_control_get(&pdev->dev, NULL);
|
||||
pwm_chip->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
||||
if (IS_ERR(pwm_chip->rstc)) {
|
||||
clk_disable_unprepare(pwm_chip->clk);
|
||||
return PTR_ERR(pwm_chip->rstc);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Mediatek Pulse Width Modulator driver
|
||||
*
|
||||
* Copyright (C) 2015 John Crispin <blogic@openwrt.org>
|
||||
* Copyright (C) 2017 Zhi Mao <zhi.mao@mediatek.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
|
@ -29,6 +30,8 @@
|
|||
#define PWMDWIDTH 0x2c
|
||||
#define PWMTHRES 0x30
|
||||
|
||||
#define PWM_CLK_DIV_MAX 7
|
||||
|
||||
enum {
|
||||
MTK_CLK_MAIN = 0,
|
||||
MTK_CLK_TOP,
|
||||
|
@ -61,6 +64,42 @@ static inline struct mtk_pwm_chip *to_mtk_pwm_chip(struct pwm_chip *chip)
|
|||
return container_of(chip, struct mtk_pwm_chip, chip);
|
||||
}
|
||||
|
||||
static int mtk_pwm_clk_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(pc->clks[MTK_CLK_TOP]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(pc->clks[MTK_CLK_MAIN]);
|
||||
if (ret < 0)
|
||||
goto disable_clk_top;
|
||||
|
||||
ret = clk_prepare_enable(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
|
||||
if (ret < 0)
|
||||
goto disable_clk_main;
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clk_main:
|
||||
clk_disable_unprepare(pc->clks[MTK_CLK_MAIN]);
|
||||
disable_clk_top:
|
||||
clk_disable_unprepare(pc->clks[MTK_CLK_TOP]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_pwm_clk_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||
|
||||
clk_disable_unprepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
|
||||
clk_disable_unprepare(pc->clks[MTK_CLK_MAIN]);
|
||||
clk_disable_unprepare(pc->clks[MTK_CLK_TOP]);
|
||||
}
|
||||
|
||||
static inline u32 mtk_pwm_readl(struct mtk_pwm_chip *chip, unsigned int num,
|
||||
unsigned int offset)
|
||||
{
|
||||
|
@ -80,6 +119,11 @@ static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||
struct clk *clk = pc->clks[MTK_CLK_PWM1 + pwm->hwpwm];
|
||||
u32 resolution, clkdiv = 0;
|
||||
int ret;
|
||||
|
||||
ret = mtk_pwm_clk_enable(chip, pwm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
resolution = NSEC_PER_SEC / clk_get_rate(clk);
|
||||
|
||||
|
@ -88,13 +132,18 @@ static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
clkdiv++;
|
||||
}
|
||||
|
||||
if (clkdiv > 7)
|
||||
if (clkdiv > PWM_CLK_DIV_MAX) {
|
||||
mtk_pwm_clk_disable(chip, pwm);
|
||||
dev_err(chip->dev, "period %d not supported\n", period_ns);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | BIT(3) | clkdiv);
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMDWIDTH, period_ns / resolution);
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMTHRES, duty_ns / resolution);
|
||||
|
||||
mtk_pwm_clk_disable(chip, pwm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -104,7 +153,7 @@ static int mtk_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
u32 value;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
|
||||
ret = mtk_pwm_clk_enable(chip, pwm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -124,7 +173,7 @@ static void mtk_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
value &= ~BIT(pwm->hwpwm);
|
||||
writel(value, pc->regs);
|
||||
|
||||
clk_unprepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
|
||||
mtk_pwm_clk_disable(chip, pwm);
|
||||
}
|
||||
|
||||
static const struct pwm_ops mtk_pwm_ops = {
|
||||
|
@ -156,14 +205,6 @@ static int mtk_pwm_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(pc->clks[i]);
|
||||
}
|
||||
|
||||
ret = clk_prepare(pc->clks[MTK_CLK_TOP]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare(pc->clks[MTK_CLK_MAIN]);
|
||||
if (ret < 0)
|
||||
goto disable_clk_top;
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
|
||||
pc->chip.dev = &pdev->dev;
|
||||
|
@ -174,26 +215,15 @@ static int mtk_pwm_probe(struct platform_device *pdev)
|
|||
ret = pwmchip_add(&pc->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
|
||||
goto disable_clk_main;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clk_main:
|
||||
clk_unprepare(pc->clks[MTK_CLK_MAIN]);
|
||||
disable_clk_top:
|
||||
clk_unprepare(pc->clks[MTK_CLK_TOP]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_pwm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = platform_get_drvdata(pdev);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < pc->chip.npwm; i++)
|
||||
pwm_disable(&pc->chip.pwms[i]);
|
||||
|
||||
return pwmchip_remove(&pc->chip);
|
||||
}
|
||||
|
|
|
@ -441,7 +441,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson,
|
|||
for (i = 0; i < meson->chip.npwm; i++) {
|
||||
struct meson_pwm_channel *channel = &channels[i];
|
||||
|
||||
snprintf(name, sizeof(name), "%s#mux%u", np->full_name, i);
|
||||
snprintf(name, sizeof(name), "%pOF#mux%u", np, i);
|
||||
|
||||
init.name = name;
|
||||
init.ops = &clk_mux_ops;
|
||||
|
|
|
@ -241,11 +241,11 @@ static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca)
|
|||
}
|
||||
#endif
|
||||
|
||||
static void pca9685_set_sleep_mode(struct pca9685 *pca, int sleep)
|
||||
static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable)
|
||||
{
|
||||
regmap_update_bits(pca->regmap, PCA9685_MODE1,
|
||||
MODE1_SLEEP, sleep ? MODE1_SLEEP : 0);
|
||||
if (!sleep) {
|
||||
MODE1_SLEEP, enable ? MODE1_SLEEP : 0);
|
||||
if (!enable) {
|
||||
/* Wait 500us for the oscillator to be back up */
|
||||
udelay(500);
|
||||
}
|
||||
|
@ -272,13 +272,13 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
* state is guaranteed active here.
|
||||
*/
|
||||
/* Put chip into sleep mode */
|
||||
pca9685_set_sleep_mode(pca, 1);
|
||||
pca9685_set_sleep_mode(pca, true);
|
||||
|
||||
/* Change the chip-wide output frequency */
|
||||
regmap_write(pca->regmap, PCA9685_PRESCALE, prescale);
|
||||
|
||||
/* Wake the chip up */
|
||||
pca9685_set_sleep_mode(pca, 0);
|
||||
pca9685_set_sleep_mode(pca, false);
|
||||
|
||||
pca->period_ns = period_ns;
|
||||
} else {
|
||||
|
@ -534,7 +534,7 @@ static int pca9685_pwm_runtime_suspend(struct device *dev)
|
|||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct pca9685 *pca = i2c_get_clientdata(client);
|
||||
|
||||
pca9685_set_sleep_mode(pca, 1);
|
||||
pca9685_set_sleep_mode(pca, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -543,7 +543,7 @@ static int pca9685_pwm_runtime_resume(struct device *dev)
|
|||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct pca9685 *pca = i2c_get_clientdata(client);
|
||||
|
||||
pca9685_set_sleep_mode(pca, 0);
|
||||
pca9685_set_sleep_mode(pca, false);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -455,7 +455,6 @@ static const struct of_device_id tpu_of_table[] = {
|
|||
{ .compatible = "renesas,tpu-r8a73a4", },
|
||||
{ .compatible = "renesas,tpu-r8a7740", },
|
||||
{ .compatible = "renesas,tpu-r8a7790", },
|
||||
{ .compatible = "renesas,tpu-sh7372", },
|
||||
{ .compatible = "renesas,tpu", },
|
||||
{ },
|
||||
};
|
||||
|
|
|
@ -27,12 +27,15 @@
|
|||
#define PWM_DUTY_NEGATIVE (0 << 3)
|
||||
#define PWM_INACTIVE_NEGATIVE (0 << 4)
|
||||
#define PWM_INACTIVE_POSITIVE (1 << 4)
|
||||
#define PWM_POLARITY_MASK (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE)
|
||||
#define PWM_OUTPUT_LEFT (0 << 5)
|
||||
#define PWM_LOCK_EN (1 << 6)
|
||||
#define PWM_LP_DISABLE (0 << 8)
|
||||
|
||||
struct rockchip_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
struct clk *clk;
|
||||
struct clk *pclk;
|
||||
const struct rockchip_pwm_data *data;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
@ -48,13 +51,8 @@ struct rockchip_pwm_data {
|
|||
struct rockchip_pwm_regs regs;
|
||||
unsigned int prescaler;
|
||||
bool supports_polarity;
|
||||
const struct pwm_ops *ops;
|
||||
|
||||
void (*set_enable)(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm, bool enable,
|
||||
enum pwm_polarity polarity);
|
||||
void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state);
|
||||
bool supports_lock;
|
||||
u32 enable_conf;
|
||||
};
|
||||
|
||||
static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
|
||||
|
@ -62,90 +60,18 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
|
|||
return container_of(c, struct rockchip_pwm_chip, chip);
|
||||
}
|
||||
|
||||
static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm, bool enable,
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
|
||||
if (enable)
|
||||
val |= enable_conf;
|
||||
else
|
||||
val &= ~enable_conf;
|
||||
|
||||
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
|
||||
}
|
||||
|
||||
static void rockchip_pwm_get_state_v1(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
if ((val & enable_conf) == enable_conf)
|
||||
state->enabled = true;
|
||||
}
|
||||
|
||||
static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm, bool enable,
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
|
||||
PWM_CONTINUOUS;
|
||||
u32 val;
|
||||
|
||||
if (polarity == PWM_POLARITY_INVERSED)
|
||||
enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
|
||||
else
|
||||
enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
|
||||
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
|
||||
if (enable)
|
||||
val |= enable_conf;
|
||||
else
|
||||
val &= ~enable_conf;
|
||||
|
||||
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
|
||||
}
|
||||
|
||||
static void rockchip_pwm_get_state_v2(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
|
||||
PWM_CONTINUOUS;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
if ((val & enable_conf) != enable_conf)
|
||||
return;
|
||||
|
||||
state->enabled = true;
|
||||
|
||||
if (!(val & PWM_DUTY_POSITIVE))
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
}
|
||||
|
||||
static void rockchip_pwm_get_state(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u32 enable_conf = pc->data->enable_conf;
|
||||
unsigned long clk_rate;
|
||||
u64 tmp;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = clk_enable(pc->clk);
|
||||
ret = clk_enable(pc->pclk);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
|
@ -157,19 +83,31 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip,
|
|||
|
||||
tmp = readl_relaxed(pc->base + pc->data->regs.duty);
|
||||
tmp *= pc->data->prescaler * NSEC_PER_SEC;
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
|
||||
pc->data->get_state(chip, pwm, state);
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
if (pc->data->supports_polarity)
|
||||
state->enabled = ((val & enable_conf) != enable_conf) ?
|
||||
false : true;
|
||||
else
|
||||
state->enabled = ((val & enable_conf) == enable_conf) ?
|
||||
true : false;
|
||||
|
||||
clk_disable(pc->clk);
|
||||
if (pc->data->supports_polarity) {
|
||||
if (!(val & PWM_DUTY_POSITIVE))
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
}
|
||||
|
||||
clk_disable(pc->pclk);
|
||||
}
|
||||
|
||||
static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
unsigned long period, duty;
|
||||
u64 clk_rate, div;
|
||||
u32 ctrl;
|
||||
|
||||
clk_rate = clk_get_rate(pc->clk);
|
||||
|
||||
|
@ -178,26 +116,53 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
* bits, every possible input period can be obtained using the
|
||||
* default prescaler value for all practical clock rate values.
|
||||
*/
|
||||
div = clk_rate * period_ns;
|
||||
div = clk_rate * state->period;
|
||||
period = DIV_ROUND_CLOSEST_ULL(div,
|
||||
pc->data->prescaler * NSEC_PER_SEC);
|
||||
|
||||
div = clk_rate * duty_ns;
|
||||
div = clk_rate * state->duty_cycle;
|
||||
duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
|
||||
|
||||
/*
|
||||
* Lock the period and duty of previous configuration, then
|
||||
* change the duty and period, that would not be effective.
|
||||
*/
|
||||
ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
if (pc->data->supports_lock) {
|
||||
ctrl |= PWM_LOCK_EN;
|
||||
writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl);
|
||||
}
|
||||
|
||||
writel(period, pc->base + pc->data->regs.period);
|
||||
writel(duty, pc->base + pc->data->regs.duty);
|
||||
|
||||
return 0;
|
||||
if (pc->data->supports_polarity) {
|
||||
ctrl &= ~PWM_POLARITY_MASK;
|
||||
if (state->polarity == PWM_POLARITY_INVERSED)
|
||||
ctrl |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
|
||||
else
|
||||
ctrl |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlock and set polarity at the same time,
|
||||
* the configuration of duty, period and polarity
|
||||
* would be effective together at next period.
|
||||
*/
|
||||
if (pc->data->supports_lock)
|
||||
ctrl &= ~PWM_LOCK_EN;
|
||||
|
||||
writel(ctrl, pc->base + pc->data->regs.ctrl);
|
||||
}
|
||||
|
||||
static int rockchip_pwm_enable(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
bool enable,
|
||||
enum pwm_polarity polarity)
|
||||
struct pwm_device *pwm,
|
||||
bool enable)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u32 enable_conf = pc->data->enable_conf;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (enable) {
|
||||
ret = clk_enable(pc->clk);
|
||||
|
@ -205,7 +170,14 @@ static int rockchip_pwm_enable(struct pwm_chip *chip,
|
|||
return ret;
|
||||
}
|
||||
|
||||
pc->data->set_enable(chip, pwm, enable, polarity);
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
|
||||
if (enable)
|
||||
val |= enable_conf;
|
||||
else
|
||||
val &= ~enable_conf;
|
||||
|
||||
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
|
||||
|
||||
if (!enable)
|
||||
clk_disable(pc->clk);
|
||||
|
@ -219,33 +191,26 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
struct pwm_state curstate;
|
||||
bool enabled;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
ret = clk_enable(pc->pclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pwm_get_state(pwm, &curstate);
|
||||
enabled = curstate.enabled;
|
||||
|
||||
ret = clk_enable(pc->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (state->polarity != curstate.polarity && enabled) {
|
||||
ret = rockchip_pwm_enable(chip, pwm, false, state->polarity);
|
||||
if (state->polarity != curstate.polarity && enabled &&
|
||||
!pc->data->supports_lock) {
|
||||
ret = rockchip_pwm_enable(chip, pwm, false);
|
||||
if (ret)
|
||||
goto out;
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
ret = rockchip_pwm_config(chip, pwm, state->duty_cycle, state->period);
|
||||
if (ret) {
|
||||
if (enabled != curstate.enabled)
|
||||
rockchip_pwm_enable(chip, pwm, !enabled,
|
||||
state->polarity);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rockchip_pwm_config(chip, pwm, state);
|
||||
if (state->enabled != enabled) {
|
||||
ret = rockchip_pwm_enable(chip, pwm, state->enabled,
|
||||
state->polarity);
|
||||
ret = rockchip_pwm_enable(chip, pwm, state->enabled);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
@ -257,18 +222,12 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
rockchip_pwm_get_state(chip, pwm, state);
|
||||
|
||||
out:
|
||||
clk_disable(pc->clk);
|
||||
clk_disable(pc->pclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct pwm_ops rockchip_pwm_ops_v1 = {
|
||||
.get_state = rockchip_pwm_get_state,
|
||||
.apply = rockchip_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct pwm_ops rockchip_pwm_ops_v2 = {
|
||||
static const struct pwm_ops rockchip_pwm_ops = {
|
||||
.get_state = rockchip_pwm_get_state,
|
||||
.apply = rockchip_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
|
@ -282,9 +241,9 @@ static const struct rockchip_pwm_data pwm_data_v1 = {
|
|||
.ctrl = 0x0c,
|
||||
},
|
||||
.prescaler = 2,
|
||||
.ops = &rockchip_pwm_ops_v1,
|
||||
.set_enable = rockchip_pwm_set_enable_v1,
|
||||
.get_state = rockchip_pwm_get_state_v1,
|
||||
.supports_polarity = false,
|
||||
.supports_lock = false,
|
||||
.enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN,
|
||||
};
|
||||
|
||||
static const struct rockchip_pwm_data pwm_data_v2 = {
|
||||
|
@ -296,9 +255,9 @@ static const struct rockchip_pwm_data pwm_data_v2 = {
|
|||
},
|
||||
.prescaler = 1,
|
||||
.supports_polarity = true,
|
||||
.ops = &rockchip_pwm_ops_v2,
|
||||
.set_enable = rockchip_pwm_set_enable_v2,
|
||||
.get_state = rockchip_pwm_get_state_v2,
|
||||
.supports_lock = false,
|
||||
.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
|
||||
PWM_CONTINUOUS,
|
||||
};
|
||||
|
||||
static const struct rockchip_pwm_data pwm_data_vop = {
|
||||
|
@ -310,15 +269,30 @@ static const struct rockchip_pwm_data pwm_data_vop = {
|
|||
},
|
||||
.prescaler = 1,
|
||||
.supports_polarity = true,
|
||||
.ops = &rockchip_pwm_ops_v2,
|
||||
.set_enable = rockchip_pwm_set_enable_v2,
|
||||
.get_state = rockchip_pwm_get_state_v2,
|
||||
.supports_lock = false,
|
||||
.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
|
||||
PWM_CONTINUOUS,
|
||||
};
|
||||
|
||||
static const struct rockchip_pwm_data pwm_data_v3 = {
|
||||
.regs = {
|
||||
.duty = 0x08,
|
||||
.period = 0x04,
|
||||
.cntr = 0x00,
|
||||
.ctrl = 0x0c,
|
||||
},
|
||||
.prescaler = 1,
|
||||
.supports_polarity = true,
|
||||
.supports_lock = true,
|
||||
.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
|
||||
PWM_CONTINUOUS,
|
||||
};
|
||||
|
||||
static const struct of_device_id rockchip_pwm_dt_ids[] = {
|
||||
{ .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1},
|
||||
{ .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2},
|
||||
{ .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop},
|
||||
{ .compatible = "rockchip,rk3328-pwm", .data = &pwm_data_v3},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids);
|
||||
|
@ -328,7 +302,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
|
|||
const struct of_device_id *id;
|
||||
struct rockchip_pwm_chip *pc;
|
||||
struct resource *r;
|
||||
int ret;
|
||||
int ret, count;
|
||||
|
||||
id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);
|
||||
if (!id)
|
||||
|
@ -343,19 +317,49 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(pc->base))
|
||||
return PTR_ERR(pc->base);
|
||||
|
||||
pc->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(pc->clk))
|
||||
return PTR_ERR(pc->clk);
|
||||
pc->clk = devm_clk_get(&pdev->dev, "pwm");
|
||||
if (IS_ERR(pc->clk)) {
|
||||
pc->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(pc->clk)) {
|
||||
ret = PTR_ERR(pc->clk);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "Can't get bus clk: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
count = of_count_phandle_with_args(pdev->dev.of_node,
|
||||
"clocks", "#clock-cells");
|
||||
if (count == 2)
|
||||
pc->pclk = devm_clk_get(&pdev->dev, "pclk");
|
||||
else
|
||||
pc->pclk = pc->clk;
|
||||
|
||||
if (IS_ERR(pc->pclk)) {
|
||||
ret = PTR_ERR(pc->pclk);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "Can't get APB clk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(pc->clk);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare(pc->pclk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't prepare APB clk: %d\n", ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
|
||||
pc->data = id->data;
|
||||
pc->chip.dev = &pdev->dev;
|
||||
pc->chip.ops = pc->data->ops;
|
||||
pc->chip.ops = &rockchip_pwm_ops;
|
||||
pc->chip.base = -1;
|
||||
pc->chip.npwm = 1;
|
||||
|
||||
|
@ -368,12 +372,20 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
|
|||
if (ret < 0) {
|
||||
clk_unprepare(pc->clk);
|
||||
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
|
||||
goto err_pclk;
|
||||
}
|
||||
|
||||
/* Keep the PWM clk enabled if the PWM appears to be up and running. */
|
||||
if (!pwm_is_enabled(pc->chip.pwms))
|
||||
clk_disable(pc->clk);
|
||||
|
||||
return 0;
|
||||
|
||||
err_pclk:
|
||||
clk_unprepare(pc->pclk);
|
||||
err_clk:
|
||||
clk_disable_unprepare(pc->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -395,6 +407,7 @@ static int rockchip_pwm_remove(struct platform_device *pdev)
|
|||
if (pwm_is_enabled(pc->chip.pwms))
|
||||
clk_disable(pc->clk);
|
||||
|
||||
clk_unprepare(pc->pclk);
|
||||
clk_unprepare(pc->clk);
|
||||
|
||||
return pwmchip_remove(&pc->chip);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Copyright (c) 2008 Simtec Electronics
|
||||
* Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
|
||||
* Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com>
|
||||
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
|
||||
*
|
||||
* PWM driver for Samsung SoCs
|
||||
*
|
||||
|
@ -74,6 +75,7 @@ struct samsung_pwm_channel {
|
|||
* @chip: generic PWM chip
|
||||
* @variant: local copy of hardware variant data
|
||||
* @inverter_mask: inverter status for all channels - one bit per channel
|
||||
* @disabled_mask: disabled status for all channels - one bit per channel
|
||||
* @base: base address of mapped PWM registers
|
||||
* @base_clk: base clock used to drive the timers
|
||||
* @tclk0: external clock 0 (can be ERR_PTR if not present)
|
||||
|
@ -83,6 +85,7 @@ struct samsung_pwm_chip {
|
|||
struct pwm_chip chip;
|
||||
struct samsung_pwm_variant variant;
|
||||
u8 inverter_mask;
|
||||
u8 disabled_mask;
|
||||
|
||||
void __iomem *base;
|
||||
struct clk *base_clk;
|
||||
|
@ -257,6 +260,8 @@ static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan);
|
||||
writel(tcon, our_chip->base + REG_TCON);
|
||||
|
||||
our_chip->disabled_mask &= ~BIT(pwm->hwpwm);
|
||||
|
||||
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
|
||||
|
||||
return 0;
|
||||
|
@ -275,6 +280,8 @@ static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
tcon &= ~TCON_AUTORELOAD(tcon_chan);
|
||||
writel(tcon, our_chip->base + REG_TCON);
|
||||
|
||||
our_chip->disabled_mask |= BIT(pwm->hwpwm);
|
||||
|
||||
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
|
||||
}
|
||||
|
||||
|
@ -297,8 +304,8 @@ static void pwm_samsung_manual_update(struct samsung_pwm_chip *chip,
|
|||
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
|
||||
}
|
||||
|
||||
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns, bool force_period)
|
||||
{
|
||||
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
|
||||
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
|
||||
|
@ -312,9 +319,6 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
if (period_ns > NSEC_PER_SEC)
|
||||
return -ERANGE;
|
||||
|
||||
if (period_ns == chan->period_ns && duty_ns == chan->duty_ns)
|
||||
return 0;
|
||||
|
||||
tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm));
|
||||
oldtcmp = readl(our_chip->base + REG_TCMPB(pwm->hwpwm));
|
||||
|
||||
|
@ -322,7 +326,7 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
++tcnt;
|
||||
|
||||
/* Check to see if we are changing the clock rate of the PWM. */
|
||||
if (chan->period_ns != period_ns) {
|
||||
if (chan->period_ns != period_ns || force_period) {
|
||||
unsigned long tin_rate;
|
||||
u32 period;
|
||||
|
||||
|
@ -381,6 +385,12 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
return __pwm_samsung_config(chip, pwm, duty_ns, period_ns, false);
|
||||
}
|
||||
|
||||
static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
|
||||
unsigned int channel, bool invert)
|
||||
{
|
||||
|
@ -592,51 +602,41 @@ static int pwm_samsung_remove(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int pwm_samsung_suspend(struct device *dev)
|
||||
static int pwm_samsung_resume(struct device *dev)
|
||||
{
|
||||
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
|
||||
struct samsung_pwm_chip *our_chip = dev_get_drvdata(dev);
|
||||
struct pwm_chip *chip = &our_chip->chip;
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
* No one preserves these values during suspend so reset them.
|
||||
* Otherwise driver leaves PWM unconfigured if same values are
|
||||
* passed to pwm_config() next time.
|
||||
*/
|
||||
for (i = 0; i < SAMSUNG_PWM_NUM; ++i) {
|
||||
struct pwm_device *pwm = &chip->chip.pwms[i];
|
||||
for (i = 0; i < SAMSUNG_PWM_NUM; i++) {
|
||||
struct pwm_device *pwm = &chip->pwms[i];
|
||||
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
|
||||
|
||||
if (!chan)
|
||||
continue;
|
||||
|
||||
chan->period_ns = 0;
|
||||
chan->duty_ns = 0;
|
||||
}
|
||||
if (our_chip->variant.output_mask & BIT(i))
|
||||
pwm_samsung_set_invert(our_chip, i,
|
||||
our_chip->inverter_mask & BIT(i));
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (chan->period_ns) {
|
||||
__pwm_samsung_config(chip, pwm, chan->duty_ns,
|
||||
chan->period_ns, true);
|
||||
/* needed to make PWM disable work on Odroid-XU3 */
|
||||
pwm_samsung_manual_update(our_chip, pwm);
|
||||
}
|
||||
|
||||
static int pwm_samsung_resume(struct device *dev)
|
||||
{
|
||||
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
|
||||
unsigned int chan;
|
||||
|
||||
/*
|
||||
* Inverter setting must be preserved across suspend/resume
|
||||
* as nobody really seems to configure it more than once.
|
||||
*/
|
||||
for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) {
|
||||
if (chip->variant.output_mask & BIT(chan))
|
||||
pwm_samsung_set_invert(chip, chan,
|
||||
chip->inverter_mask & BIT(chan));
|
||||
if (our_chip->disabled_mask & BIT(i))
|
||||
pwm_samsung_disable(chip, pwm);
|
||||
else
|
||||
pwm_samsung_enable(chip, pwm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, pwm_samsung_suspend,
|
||||
pwm_samsung_resume);
|
||||
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, NULL, pwm_samsung_resume);
|
||||
|
||||
static struct platform_driver pwm_samsung_driver = {
|
||||
.driver = {
|
||||
|
|
|
@ -218,7 +218,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
|||
*/
|
||||
pwm->clk_rate = clk_get_rate(pwm->clk);
|
||||
|
||||
pwm->rst = devm_reset_control_get(&pdev->dev, "pwm");
|
||||
pwm->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
|
||||
if (IS_ERR(pwm->rst)) {
|
||||
ret = PTR_ERR(pwm->rst);
|
||||
dev_err(&pdev->dev, "Reset control is not found: %d\n", ret);
|
||||
|
|
|
@ -39,15 +39,15 @@
|
|||
#define ECCTL2_TSCTR_FREERUN BIT(4)
|
||||
|
||||
struct ecap_context {
|
||||
u32 cap3;
|
||||
u32 cap4;
|
||||
u16 ecctl2;
|
||||
u32 cap3;
|
||||
u32 cap4;
|
||||
u16 ecctl2;
|
||||
};
|
||||
|
||||
struct ecap_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
unsigned int clk_rate;
|
||||
void __iomem *mmio_base;
|
||||
struct pwm_chip chip;
|
||||
unsigned int clk_rate;
|
||||
void __iomem *mmio_base;
|
||||
struct ecap_context ctx;
|
||||
};
|
||||
|
||||
|
@ -64,9 +64,9 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
int duty_ns, int period_ns)
|
||||
{
|
||||
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
|
||||
u32 period_cycles, duty_cycles;
|
||||
unsigned long long c;
|
||||
unsigned long period_cycles, duty_cycles;
|
||||
unsigned int reg_val;
|
||||
u16 value;
|
||||
|
||||
if (period_ns > NSEC_PER_SEC)
|
||||
return -ERANGE;
|
||||
|
@ -74,7 +74,7 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
c = pc->clk_rate;
|
||||
c = c * period_ns;
|
||||
do_div(c, NSEC_PER_SEC);
|
||||
period_cycles = (unsigned long)c;
|
||||
period_cycles = (u32)c;
|
||||
|
||||
if (period_cycles < 1) {
|
||||
period_cycles = 1;
|
||||
|
@ -83,17 +83,17 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
c = pc->clk_rate;
|
||||
c = c * duty_ns;
|
||||
do_div(c, NSEC_PER_SEC);
|
||||
duty_cycles = (unsigned long)c;
|
||||
duty_cycles = (u32)c;
|
||||
}
|
||||
|
||||
pm_runtime_get_sync(pc->chip.dev);
|
||||
|
||||
reg_val = readw(pc->mmio_base + ECCTL2);
|
||||
value = readw(pc->mmio_base + ECCTL2);
|
||||
|
||||
/* Configure APWM mode & disable sync option */
|
||||
reg_val |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
|
||||
value |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
|
||||
|
||||
writew(reg_val, pc->mmio_base + ECCTL2);
|
||||
writew(value, pc->mmio_base + ECCTL2);
|
||||
|
||||
if (!pwm_is_enabled(pwm)) {
|
||||
/* Update active registers if not running */
|
||||
|
@ -110,40 +110,45 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
}
|
||||
|
||||
if (!pwm_is_enabled(pwm)) {
|
||||
reg_val = readw(pc->mmio_base + ECCTL2);
|
||||
value = readw(pc->mmio_base + ECCTL2);
|
||||
/* Disable APWM mode to put APWM output Low */
|
||||
reg_val &= ~ECCTL2_APWM_MODE;
|
||||
writew(reg_val, pc->mmio_base + ECCTL2);
|
||||
value &= ~ECCTL2_APWM_MODE;
|
||||
writew(value, pc->mmio_base + ECCTL2);
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(pc->chip.dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
enum pwm_polarity polarity)
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
|
||||
unsigned short reg_val;
|
||||
u16 value;
|
||||
|
||||
pm_runtime_get_sync(pc->chip.dev);
|
||||
reg_val = readw(pc->mmio_base + ECCTL2);
|
||||
|
||||
value = readw(pc->mmio_base + ECCTL2);
|
||||
|
||||
if (polarity == PWM_POLARITY_INVERSED)
|
||||
/* Duty cycle defines LOW period of PWM */
|
||||
reg_val |= ECCTL2_APWM_POL_LOW;
|
||||
value |= ECCTL2_APWM_POL_LOW;
|
||||
else
|
||||
/* Duty cycle defines HIGH period of PWM */
|
||||
reg_val &= ~ECCTL2_APWM_POL_LOW;
|
||||
value &= ~ECCTL2_APWM_POL_LOW;
|
||||
|
||||
writew(value, pc->mmio_base + ECCTL2);
|
||||
|
||||
writew(reg_val, pc->mmio_base + ECCTL2);
|
||||
pm_runtime_put_sync(pc->chip.dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
|
||||
unsigned int reg_val;
|
||||
u16 value;
|
||||
|
||||
/* Leave clock enabled on enabling PWM */
|
||||
pm_runtime_get_sync(pc->chip.dev);
|
||||
|
@ -152,24 +157,25 @@ static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
* Enable 'Free run Time stamp counter mode' to start counter
|
||||
* and 'APWM mode' to enable APWM output
|
||||
*/
|
||||
reg_val = readw(pc->mmio_base + ECCTL2);
|
||||
reg_val |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
|
||||
writew(reg_val, pc->mmio_base + ECCTL2);
|
||||
value = readw(pc->mmio_base + ECCTL2);
|
||||
value |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
|
||||
writew(value, pc->mmio_base + ECCTL2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
|
||||
unsigned int reg_val;
|
||||
u16 value;
|
||||
|
||||
/*
|
||||
* Disable 'Free run Time stamp counter mode' to stop counter
|
||||
* and 'APWM mode' to put APWM output to low
|
||||
*/
|
||||
reg_val = readw(pc->mmio_base + ECCTL2);
|
||||
reg_val &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
|
||||
writew(reg_val, pc->mmio_base + ECCTL2);
|
||||
value = readw(pc->mmio_base + ECCTL2);
|
||||
value &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
|
||||
writew(value, pc->mmio_base + ECCTL2);
|
||||
|
||||
/* Disable clock on PWM disable */
|
||||
pm_runtime_put_sync(pc->chip.dev);
|
||||
|
@ -184,12 +190,12 @@ static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
}
|
||||
|
||||
static const struct pwm_ops ecap_pwm_ops = {
|
||||
.free = ecap_pwm_free,
|
||||
.config = ecap_pwm_config,
|
||||
.set_polarity = ecap_pwm_set_polarity,
|
||||
.enable = ecap_pwm_enable,
|
||||
.disable = ecap_pwm_disable,
|
||||
.owner = THIS_MODULE,
|
||||
.free = ecap_pwm_free,
|
||||
.config = ecap_pwm_config,
|
||||
.set_polarity = ecap_pwm_set_polarity,
|
||||
.enable = ecap_pwm_enable,
|
||||
.disable = ecap_pwm_disable,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct of_device_id ecap_of_match[] = {
|
||||
|
@ -202,10 +208,10 @@ MODULE_DEVICE_TABLE(of, ecap_of_match);
|
|||
static int ecap_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int ret;
|
||||
struct ecap_pwm_chip *pc;
|
||||
struct resource *r;
|
||||
struct clk *clk;
|
||||
struct ecap_pwm_chip *pc;
|
||||
int ret;
|
||||
|
||||
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
|
||||
if (!pc)
|
||||
|
@ -248,9 +254,9 @@ static int ecap_pwm_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -259,6 +265,7 @@ static int ecap_pwm_remove(struct platform_device *pdev)
|
|||
struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return pwmchip_remove(&pc->chip);
|
||||
}
|
||||
|
||||
|
@ -311,14 +318,13 @@ static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);
|
|||
|
||||
static struct platform_driver ecap_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "ecap",
|
||||
.name = "ecap",
|
||||
.of_match_table = ecap_of_match,
|
||||
.pm = &ecap_pwm_pm_ops,
|
||||
.pm = &ecap_pwm_pm_ops,
|
||||
},
|
||||
.probe = ecap_pwm_probe,
|
||||
.remove = ecap_pwm_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ecap_pwm_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ECAP PWM driver");
|
||||
|
|
|
@ -122,12 +122,12 @@ struct ehrpwm_context {
|
|||
};
|
||||
|
||||
struct ehrpwm_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
unsigned int clk_rate;
|
||||
void __iomem *mmio_base;
|
||||
struct pwm_chip chip;
|
||||
unsigned long clk_rate;
|
||||
void __iomem *mmio_base;
|
||||
unsigned long period_cycles[NUM_PWM_CHANNEL];
|
||||
enum pwm_polarity polarity[NUM_PWM_CHANNEL];
|
||||
struct clk *tbclk;
|
||||
struct clk *tbclk;
|
||||
struct ehrpwm_context ctx;
|
||||
};
|
||||
|
||||
|
@ -136,25 +136,26 @@ static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
|
|||
return container_of(chip, struct ehrpwm_pwm_chip, chip);
|
||||
}
|
||||
|
||||
static inline u16 ehrpwm_read(void __iomem *base, int offset)
|
||||
static inline u16 ehrpwm_read(void __iomem *base, unsigned int offset)
|
||||
{
|
||||
return readw(base + offset);
|
||||
}
|
||||
|
||||
static inline void ehrpwm_write(void __iomem *base, int offset, unsigned int val)
|
||||
static inline void ehrpwm_write(void __iomem *base, unsigned int offset,
|
||||
u16 value)
|
||||
{
|
||||
writew(val & 0xFFFF, base + offset);
|
||||
writew(value, base + offset);
|
||||
}
|
||||
|
||||
static void ehrpwm_modify(void __iomem *base, int offset,
|
||||
unsigned short mask, unsigned short val)
|
||||
static void ehrpwm_modify(void __iomem *base, unsigned int offset, u16 mask,
|
||||
u16 value)
|
||||
{
|
||||
unsigned short regval;
|
||||
unsigned short val;
|
||||
|
||||
regval = readw(base + offset);
|
||||
regval &= ~mask;
|
||||
regval |= val & mask;
|
||||
writew(regval, base + offset);
|
||||
val = readw(base + offset);
|
||||
val &= ~mask;
|
||||
val |= value & mask;
|
||||
writew(val, base + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,14 +164,13 @@ static void ehrpwm_modify(void __iomem *base, int offset,
|
|||
* @prescale_div: prescaler value set
|
||||
* @tb_clk_div: Time Base Control prescaler bits
|
||||
*/
|
||||
static int set_prescale_div(unsigned long rqst_prescaler,
|
||||
unsigned short *prescale_div, unsigned short *tb_clk_div)
|
||||
static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
|
||||
u16 *tb_clk_div)
|
||||
{
|
||||
unsigned int clkdiv, hspclkdiv;
|
||||
|
||||
for (clkdiv = 0; clkdiv <= CLKDIV_MAX; clkdiv++) {
|
||||
for (hspclkdiv = 0; hspclkdiv <= HSPCLKDIV_MAX; hspclkdiv++) {
|
||||
|
||||
/*
|
||||
* calculations for prescaler value :
|
||||
* prescale_div = HSPCLKDIVIDER * CLKDIVIDER.
|
||||
|
@ -191,13 +191,14 @@ static int set_prescale_div(unsigned long rqst_prescaler,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan)
|
||||
{
|
||||
int aqctl_reg;
|
||||
unsigned short aqctl_val, aqctl_mask;
|
||||
u16 aqctl_val, aqctl_mask;
|
||||
unsigned int aqctl_reg;
|
||||
|
||||
/*
|
||||
* Configure PWM output to HIGH/LOW level on counter
|
||||
|
@ -232,13 +233,13 @@ static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan)
|
|||
* duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE
|
||||
*/
|
||||
static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
|
||||
u32 period_cycles, duty_cycles;
|
||||
u16 ps_divval, tb_divval;
|
||||
unsigned int i, cmp_reg;
|
||||
unsigned long long c;
|
||||
unsigned long period_cycles, duty_cycles;
|
||||
unsigned short ps_divval, tb_divval;
|
||||
int i, cmp_reg;
|
||||
|
||||
if (period_ns > NSEC_PER_SEC)
|
||||
return -ERANGE;
|
||||
|
@ -272,8 +273,9 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
if (i == pwm->hwpwm)
|
||||
continue;
|
||||
|
||||
dev_err(chip->dev, "Period value conflicts with channel %d\n",
|
||||
i);
|
||||
dev_err(chip->dev,
|
||||
"period value conflicts with channel %u\n",
|
||||
i);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +284,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
/* Configure clock prescaler to support Low frequency PWM wave */
|
||||
if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval,
|
||||
&tb_divval)) {
|
||||
&tb_divval)) {
|
||||
dev_err(chip->dev, "Unsupported values\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -303,7 +305,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
/* Configure ehrpwm counter for up-count mode */
|
||||
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK,
|
||||
TBCTL_CTRMODE_UP);
|
||||
TBCTL_CTRMODE_UP);
|
||||
|
||||
if (pwm->hwpwm == 1)
|
||||
/* Channel 1 configured with compare B register */
|
||||
|
@ -315,23 +317,26 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles);
|
||||
|
||||
pm_runtime_put_sync(chip->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ehrpwm_pwm_set_polarity(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm, enum pwm_polarity polarity)
|
||||
struct pwm_device *pwm,
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
|
||||
|
||||
/* Configuration of polarity in hardware delayed, do at enable */
|
||||
pc->polarity[pwm->hwpwm] = polarity;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
|
||||
unsigned short aqcsfrc_val, aqcsfrc_mask;
|
||||
u16 aqcsfrc_val, aqcsfrc_mask;
|
||||
int ret;
|
||||
|
||||
/* Leave clock enabled on enabling PWM */
|
||||
|
@ -348,7 +353,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
|
||||
/* Changes to shadow mode */
|
||||
ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
|
||||
AQSFRC_RLDCSF_ZRO);
|
||||
AQSFRC_RLDCSF_ZRO);
|
||||
|
||||
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
|
||||
|
||||
|
@ -358,20 +363,21 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
/* Enable TBCLK before enabling PWM device */
|
||||
ret = clk_enable(pc->tbclk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Failed to enable TBCLK for %s\n",
|
||||
dev_name(pc->chip.dev));
|
||||
dev_err(chip->dev, "Failed to enable TBCLK for %s: %d\n",
|
||||
dev_name(pc->chip.dev), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable time counter for free_run */
|
||||
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
|
||||
unsigned short aqcsfrc_val, aqcsfrc_mask;
|
||||
u16 aqcsfrc_val, aqcsfrc_mask;
|
||||
|
||||
/* Action Qualifier puts PWM output low forcefully */
|
||||
if (pwm->hwpwm) {
|
||||
|
@ -387,7 +393,7 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
* Action Qualifier control on PWM output from next TBCLK
|
||||
*/
|
||||
ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
|
||||
AQSFRC_RLDCSF_IMDT);
|
||||
AQSFRC_RLDCSF_IMDT);
|
||||
|
||||
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
|
||||
|
||||
|
@ -415,17 +421,17 @@ static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
}
|
||||
|
||||
static const struct pwm_ops ehrpwm_pwm_ops = {
|
||||
.free = ehrpwm_pwm_free,
|
||||
.config = ehrpwm_pwm_config,
|
||||
.set_polarity = ehrpwm_pwm_set_polarity,
|
||||
.enable = ehrpwm_pwm_enable,
|
||||
.disable = ehrpwm_pwm_disable,
|
||||
.owner = THIS_MODULE,
|
||||
.free = ehrpwm_pwm_free,
|
||||
.config = ehrpwm_pwm_config,
|
||||
.set_polarity = ehrpwm_pwm_set_polarity,
|
||||
.enable = ehrpwm_pwm_enable,
|
||||
.disable = ehrpwm_pwm_disable,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct of_device_id ehrpwm_of_match[] = {
|
||||
{ .compatible = "ti,am3352-ehrpwm" },
|
||||
{ .compatible = "ti,am33xx-ehrpwm" },
|
||||
{ .compatible = "ti,am3352-ehrpwm" },
|
||||
{ .compatible = "ti,am33xx-ehrpwm" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
|
||||
|
@ -433,10 +439,10 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
|
|||
static int ehrpwm_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int ret;
|
||||
struct ehrpwm_pwm_chip *pc;
|
||||
struct resource *r;
|
||||
struct clk *clk;
|
||||
struct ehrpwm_pwm_chip *pc;
|
||||
int ret;
|
||||
|
||||
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
|
||||
if (!pc)
|
||||
|
@ -489,13 +495,18 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
|
|||
ret = pwmchip_add(&pc->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
|
||||
return ret;
|
||||
goto err_clk_unprepare;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
return 0;
|
||||
|
||||
err_clk_unprepare:
|
||||
clk_unprepare(pc->tbclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ehrpwm_pwm_remove(struct platform_device *pdev)
|
||||
|
@ -504,8 +515,8 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
|
|||
|
||||
clk_unprepare(pc->tbclk);
|
||||
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return pwmchip_remove(&pc->chip);
|
||||
}
|
||||
|
||||
|
@ -513,6 +524,7 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
|
|||
static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
|
||||
{
|
||||
pm_runtime_get_sync(pc->chip.dev);
|
||||
|
||||
pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL);
|
||||
pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD);
|
||||
pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA);
|
||||
|
@ -521,6 +533,7 @@ static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
|
|||
pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB);
|
||||
pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC);
|
||||
pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC);
|
||||
|
||||
pm_runtime_put_sync(pc->chip.dev);
|
||||
}
|
||||
|
||||
|
@ -539,9 +552,10 @@ static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc)
|
|||
static int ehrpwm_pwm_suspend(struct device *dev)
|
||||
{
|
||||
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
ehrpwm_pwm_save_context(pc);
|
||||
|
||||
for (i = 0; i < pc->chip.npwm; i++) {
|
||||
struct pwm_device *pwm = &pc->chip.pwms[i];
|
||||
|
||||
|
@ -551,13 +565,14 @@ static int ehrpwm_pwm_suspend(struct device *dev)
|
|||
/* Disable explicitly if PWM is running */
|
||||
pm_runtime_put_sync(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ehrpwm_pwm_resume(struct device *dev)
|
||||
{
|
||||
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < pc->chip.npwm; i++) {
|
||||
struct pwm_device *pwm = &pc->chip.pwms[i];
|
||||
|
@ -568,24 +583,25 @@ static int ehrpwm_pwm_resume(struct device *dev)
|
|||
/* Enable explicitly if PWM was running */
|
||||
pm_runtime_get_sync(dev);
|
||||
}
|
||||
|
||||
ehrpwm_pwm_restore_context(pc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend,
|
||||
ehrpwm_pwm_resume);
|
||||
ehrpwm_pwm_resume);
|
||||
|
||||
static struct platform_driver ehrpwm_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "ehrpwm",
|
||||
.name = "ehrpwm",
|
||||
.of_match_table = ehrpwm_of_match,
|
||||
.pm = &ehrpwm_pwm_pm_ops,
|
||||
.pm = &ehrpwm_pwm_pm_ops,
|
||||
},
|
||||
.probe = ehrpwm_pwm_probe,
|
||||
.remove = ehrpwm_pwm_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ehrpwm_pwm_driver);
|
||||
|
||||
MODULE_DESCRIPTION("EHRPWM PWM driver");
|
||||
|
|
|
@ -241,6 +241,7 @@ static int vt8500_pwm_probe(struct platform_device *pdev)
|
|||
ret = pwmchip_add(&chip->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to add PWM chip\n");
|
||||
clk_unprepare(chip->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Sanechips Technology Co., Ltd.
|
||||
* Copyright 2017 Linaro Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define ZX_PWM_MODE 0x0
|
||||
#define ZX_PWM_CLKDIV_SHIFT 2
|
||||
#define ZX_PWM_CLKDIV_MASK GENMASK(11, 2)
|
||||
#define ZX_PWM_CLKDIV(x) (((x) << ZX_PWM_CLKDIV_SHIFT) & \
|
||||
ZX_PWM_CLKDIV_MASK)
|
||||
#define ZX_PWM_POLAR BIT(1)
|
||||
#define ZX_PWM_EN BIT(0)
|
||||
#define ZX_PWM_PERIOD 0x4
|
||||
#define ZX_PWM_DUTY 0x8
|
||||
|
||||
#define ZX_PWM_CLKDIV_MAX 1023
|
||||
#define ZX_PWM_PERIOD_MAX 65535
|
||||
|
||||
struct zx_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
struct clk *pclk;
|
||||
struct clk *wclk;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static inline struct zx_pwm_chip *to_zx_pwm_chip(struct pwm_chip *chip)
|
||||
{
|
||||
return container_of(chip, struct zx_pwm_chip, chip);
|
||||
}
|
||||
|
||||
static inline u32 zx_pwm_readl(struct zx_pwm_chip *zpc, unsigned int hwpwm,
|
||||
unsigned int offset)
|
||||
{
|
||||
return readl(zpc->base + (hwpwm + 1) * 0x10 + offset);
|
||||
}
|
||||
|
||||
static inline void zx_pwm_writel(struct zx_pwm_chip *zpc, unsigned int hwpwm,
|
||||
unsigned int offset, u32 value)
|
||||
{
|
||||
writel(value, zpc->base + (hwpwm + 1) * 0x10 + offset);
|
||||
}
|
||||
|
||||
static void zx_pwm_set_mask(struct zx_pwm_chip *zpc, unsigned int hwpwm,
|
||||
unsigned int offset, u32 mask, u32 value)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = zx_pwm_readl(zpc, hwpwm, offset);
|
||||
data &= ~mask;
|
||||
data |= value & mask;
|
||||
zx_pwm_writel(zpc, hwpwm, offset, data);
|
||||
}
|
||||
|
||||
static void zx_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip);
|
||||
unsigned long rate;
|
||||
unsigned int div;
|
||||
u32 value;
|
||||
u64 tmp;
|
||||
|
||||
value = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_MODE);
|
||||
|
||||
if (value & ZX_PWM_POLAR)
|
||||
state->polarity = PWM_POLARITY_NORMAL;
|
||||
else
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
|
||||
if (value & ZX_PWM_EN)
|
||||
state->enabled = true;
|
||||
else
|
||||
state->enabled = false;
|
||||
|
||||
div = (value & ZX_PWM_CLKDIV_MASK) >> ZX_PWM_CLKDIV_SHIFT;
|
||||
rate = clk_get_rate(zpc->wclk);
|
||||
|
||||
tmp = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_PERIOD);
|
||||
tmp *= div * NSEC_PER_SEC;
|
||||
state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
|
||||
|
||||
tmp = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_DUTY);
|
||||
tmp *= div * NSEC_PER_SEC;
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
|
||||
}
|
||||
|
||||
static int zx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
unsigned int duty_ns, unsigned int period_ns)
|
||||
{
|
||||
struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip);
|
||||
unsigned int period_cycles, duty_cycles;
|
||||
unsigned long long c;
|
||||
unsigned int div = 1;
|
||||
unsigned long rate;
|
||||
|
||||
/* Find out the best divider */
|
||||
rate = clk_get_rate(zpc->wclk);
|
||||
|
||||
while (1) {
|
||||
c = rate / div;
|
||||
c = c * period_ns;
|
||||
do_div(c, NSEC_PER_SEC);
|
||||
|
||||
if (c < ZX_PWM_PERIOD_MAX)
|
||||
break;
|
||||
|
||||
div++;
|
||||
|
||||
if (div > ZX_PWM_CLKDIV_MAX)
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
/* Calculate duty cycles */
|
||||
period_cycles = c;
|
||||
c *= duty_ns;
|
||||
do_div(c, period_ns);
|
||||
duty_cycles = c;
|
||||
|
||||
/*
|
||||
* If the PWM is being enabled, we have to temporarily disable it
|
||||
* before configuring the registers.
|
||||
*/
|
||||
if (pwm_is_enabled(pwm))
|
||||
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_EN, 0);
|
||||
|
||||
/* Set up registers */
|
||||
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_CLKDIV_MASK,
|
||||
ZX_PWM_CLKDIV(div));
|
||||
zx_pwm_writel(zpc, pwm->hwpwm, ZX_PWM_PERIOD, period_cycles);
|
||||
zx_pwm_writel(zpc, pwm->hwpwm, ZX_PWM_DUTY, duty_cycles);
|
||||
|
||||
/* Re-enable the PWM if needed */
|
||||
if (pwm_is_enabled(pwm))
|
||||
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE,
|
||||
ZX_PWM_EN, ZX_PWM_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip);
|
||||
struct pwm_state cstate;
|
||||
int ret;
|
||||
|
||||
pwm_get_state(pwm, &cstate);
|
||||
|
||||
if (state->polarity != cstate.polarity)
|
||||
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_POLAR,
|
||||
(state->polarity == PWM_POLARITY_INVERSED) ?
|
||||
0 : ZX_PWM_POLAR);
|
||||
|
||||
if (state->period != cstate.period ||
|
||||
state->duty_cycle != cstate.duty_cycle) {
|
||||
ret = zx_pwm_config(chip, pwm, state->duty_cycle,
|
||||
state->period);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (state->enabled != cstate.enabled) {
|
||||
if (state->enabled) {
|
||||
ret = clk_prepare_enable(zpc->wclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE,
|
||||
ZX_PWM_EN, ZX_PWM_EN);
|
||||
} else {
|
||||
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE,
|
||||
ZX_PWM_EN, 0);
|
||||
clk_disable_unprepare(zpc->wclk);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops zx_pwm_ops = {
|
||||
.apply = zx_pwm_apply,
|
||||
.get_state = zx_pwm_get_state,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int zx_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct zx_pwm_chip *zpc;
|
||||
struct resource *res;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
zpc = devm_kzalloc(&pdev->dev, sizeof(*zpc), GFP_KERNEL);
|
||||
if (!zpc)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
zpc->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(zpc->base))
|
||||
return PTR_ERR(zpc->base);
|
||||
|
||||
zpc->pclk = devm_clk_get(&pdev->dev, "pclk");
|
||||
if (IS_ERR(zpc->pclk))
|
||||
return PTR_ERR(zpc->pclk);
|
||||
|
||||
zpc->wclk = devm_clk_get(&pdev->dev, "wclk");
|
||||
if (IS_ERR(zpc->wclk))
|
||||
return PTR_ERR(zpc->wclk);
|
||||
|
||||
ret = clk_prepare_enable(zpc->pclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
zpc->chip.dev = &pdev->dev;
|
||||
zpc->chip.ops = &zx_pwm_ops;
|
||||
zpc->chip.base = -1;
|
||||
zpc->chip.npwm = 4;
|
||||
zpc->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||
zpc->chip.of_pwm_n_cells = 3;
|
||||
|
||||
/*
|
||||
* PWM devices may be enabled by firmware, and let's disable all of
|
||||
* them initially to save power.
|
||||
*/
|
||||
for (i = 0; i < zpc->chip.npwm; i++)
|
||||
zx_pwm_set_mask(zpc, i, ZX_PWM_MODE, ZX_PWM_EN, 0);
|
||||
|
||||
ret = pwmchip_add(&zpc->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, zpc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zx_pwm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct zx_pwm_chip *zpc = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = pwmchip_remove(&zpc->chip);
|
||||
clk_disable_unprepare(zpc->pclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id zx_pwm_dt_ids[] = {
|
||||
{ .compatible = "zte,zx296718-pwm", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, zx_pwm_dt_ids);
|
||||
|
||||
static struct platform_driver zx_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "zx-pwm",
|
||||
.of_match_table = zx_pwm_dt_ids,
|
||||
},
|
||||
.probe = zx_pwm_probe,
|
||||
.remove = zx_pwm_remove,
|
||||
};
|
||||
module_platform_driver(zx_pwm_driver);
|
||||
|
||||
MODULE_ALIAS("platform:zx-pwm");
|
||||
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
|
||||
MODULE_DESCRIPTION("ZTE ZX PWM Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue