diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-10-10 20:15:24 +0900 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-10-10 20:15:24 +0900 |
commit | 2474542f64432398f503373f53bdf620491bcfa8 (patch) | |
tree | 3c9744b138c2158757530814b35c23eed31cf6ce /drivers/pwm | |
parent | c7a6ced9d8e8411bdafe83998474d185a79badc3 (diff) | |
parent | 85f8879ca4f3d26a7f473522101fb74a79bda3f2 (diff) |
Merge tag 'for-3.7-rc1' of git://gitorious.org/linux-pwm/linux-pwm
Pull pwm changes from Thierry Reding:
"All legacy PWM providers have now been moved to the PWM subsystem.
The plan for 3.8 is to adapt all board files to provide a lookup table
for PWM devices in order to get rid of the global namespace.
Subsequently, users of the legacy pwm_request() and pwm_free()
functions can be migrated to the new pwm_get() and pwm_put()
functions. Once this has been completed, the legacy API and the
compatibility code in the core can be removed.
In addition to the above, these changes also add support for
configuring the polarity of a PWM signal (currently only supported on
ECAP and EHRPWM) and include a much needed rework of the i.MX driver.
Managed functions to obtain and release a PWM device (devm_pwm_get()
and devm_pwm_put()) have been added and the pwm-backlight driver has
been updated to use them. If the PWM subsystem hasn't been enabled,
dummy functions are provided that allow the subsystem to safely
compile out.
Some common checks on input parameters have been moved to the core and
removed from the drivers. Finally, a small fix corrects the
description of the PWM specifier's second cell in the device tree
representation."
* tag 'for-3.7-rc1' of git://gitorious.org/linux-pwm/linux-pwm: (23 commits)
pwm: dt: Fix description of second PWM cell
pwm: Check for negative duty-cycle and period
pwm: Add Ingenic JZ4740 support
MIPS: JZ4740: Export timer API
pwm: Move PUV3 PWM driver to PWM framework
unicore32: pwm: Use managed resource allocations
unicore32: pwm: Remove unnecessary indirection
unicore32: pwm: Use module_platform_driver()
unicore32: pwm: Properly remap memory-mapped registers
pwm-backlight: Use devm_pwm_get() instead of pwm_get()
pwm: Move AB8500 PWM driver to PWM framework
pwm: Fix compilation error when CONFIG_PWM is not defined
pwm: i.MX: fix clock lookup
pwm: i.MX: use per clock unconditionally
pwm: i.MX: add devicetree support
pwm: i.MX: Use module_platform_driver
pwm: i.MX: add functions to enable/disable pwm.
pwm: i.MX: remove unnecessary if in pwm_[en|dis]able
pwm: i.MX: factor out SoC specific functions
pwm: pwm-tiehrpwm: Add support for configuring polarity of PWM
...
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 29 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 3 | ||||
-rw-r--r-- | drivers/pwm/core.c | 82 | ||||
-rw-r--r-- | drivers/pwm/pwm-ab8500.c | 153 | ||||
-rw-r--r-- | drivers/pwm/pwm-bfin.c | 3 | ||||
-rw-r--r-- | drivers/pwm/pwm-imx.c | 278 | ||||
-rw-r--r-- | drivers/pwm/pwm-jz4740.c | 221 | ||||
-rw-r--r-- | drivers/pwm/pwm-puv3.c | 161 | ||||
-rw-r--r-- | drivers/pwm/pwm-pxa.c | 3 | ||||
-rw-r--r-- | drivers/pwm/pwm-samsung.c | 3 | ||||
-rw-r--r-- | drivers/pwm/pwm-tiecap.c | 24 | ||||
-rw-r--r-- | drivers/pwm/pwm-tiehrpwm.c | 75 |
12 files changed, 904 insertions, 131 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d7c6b83097c1..ed81720e7b2b 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -1,6 +1,5 @@ menuconfig PWM bool "Pulse-Width Modulation (PWM) Support" - depends on !MACH_JZ4740 && !PUV3_PWM help Generic Pulse-Width Modulation (PWM) support. @@ -29,6 +28,15 @@ menuconfig PWM if PWM +config PWM_AB8500 + tristate "AB8500 PWM support" + depends on AB8500_CORE && ARCH_U8500 + help + Generic PWM framework driver for Analog Baseband AB8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-ab8500. + config PWM_BFIN tristate "Blackfin PWM support" depends on BFIN_GPTIMERS @@ -47,6 +55,16 @@ config PWM_IMX To compile this driver as a module, choose M here: the module will be called pwm-imx. +config PWM_JZ4740 + tristate "Ingenic JZ4740 PWM support" + depends on MACH_JZ4740 + help + Generic PWM framework driver for Ingenic JZ4740 based + machines. + + To compile this driver as a module, choose M here: the module + will be called pwm-jz4740. + config PWM_LPC32XX tristate "LPC32XX PWM support" depends on ARCH_LPC32XX @@ -67,6 +85,15 @@ config PWM_MXS To compile this driver as a module, choose M here: the module will be called pwm-mxs. +config PWM_PUV3 + tristate "PKUnity NetBook-0916 PWM support" + depends on ARCH_PUV3 + help + Generic PWM framework driver for PKUnity NetBook-0916. + + To compile this driver as a module, choose M here: the module + will be called pwm-puv3. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 78f123dca30d..acfe4821c58b 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,8 +1,11 @@ obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o +obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o +obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index c6e05078d3ad..f5acdaa52707 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -371,7 +371,7 @@ EXPORT_SYMBOL_GPL(pwm_free); */ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) { - if (!pwm || period_ns == 0 || duty_ns > period_ns) + if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns) return -EINVAL; return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); @@ -379,6 +379,28 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) EXPORT_SYMBOL_GPL(pwm_config); /** + * pwm_set_polarity() - configure the polarity of a PWM signal + * @pwm: PWM device + * @polarity: new polarity of the PWM signal + * + * Note that the polarity cannot be configured while the PWM device is enabled + */ +int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) +{ + if (!pwm || !pwm->chip->ops) + return -EINVAL; + + if (!pwm->chip->ops->set_polarity) + return -ENOSYS; + + if (test_bit(PWMF_ENABLED, &pwm->flags)) + return -EBUSY; + + return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); +} +EXPORT_SYMBOL_GPL(pwm_set_polarity); + +/** * pwm_enable() - start a PWM output toggling * @pwm: PWM device */ @@ -624,6 +646,64 @@ out: } EXPORT_SYMBOL_GPL(pwm_put); +static void devm_pwm_release(struct device *dev, void *res) +{ + pwm_put(*(struct pwm_device **)res); +} + +/** + * devm_pwm_get() - resource managed pwm_get() + * @dev: device for PWM consumer + * @con_id: consumer name + * + * This function performs like pwm_get() but the acquired PWM device will + * automatically be released on driver detach. + */ +struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id) +{ + struct pwm_device **ptr, *pwm; + + ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + pwm = pwm_get(dev, con_id); + if (!IS_ERR(pwm)) { + *ptr = pwm; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return pwm; +} +EXPORT_SYMBOL_GPL(devm_pwm_get); + +static int devm_pwm_match(struct device *dev, void *res, void *data) +{ + struct pwm_device **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +/** + * devm_pwm_put() - resource managed pwm_put() + * @dev: device for PWM consumer + * @pwm: PWM device + * + * Release a PWM previously allocated using devm_pwm_get(). Calling this + * function is usually not needed because devm-allocated resources are + * automatically released on driver detach. + */ +void devm_pwm_put(struct device *dev, struct pwm_device *pwm) +{ + WARN_ON(devres_release(dev, devm_pwm_release, devm_pwm_match, pwm)); +} +EXPORT_SYMBOL_GPL(devm_pwm_put); + #ifdef CONFIG_DEBUG_FS static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) { diff --git a/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c new file mode 100644 index 000000000000..cfb72ca873d1 --- /dev/null +++ b/drivers/pwm/pwm-ab8500.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Arun R Murthy <arun.murthy@stericsson.com> + * License terms: GNU General Public License (GPL) version 2 + */ +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/pwm.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/module.h> + +/* + * PWM Out generators + * Bank: 0x10 + */ +#define AB8500_PWM_OUT_CTRL1_REG 0x60 +#define AB8500_PWM_OUT_CTRL2_REG 0x61 +#define AB8500_PWM_OUT_CTRL7_REG 0x66 + +/* backlight driver constants */ +#define ENABLE_PWM 1 +#define DISABLE_PWM 0 + +struct ab8500_pwm_chip { + struct pwm_chip chip; +}; + +static int ab8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + int ret = 0; + unsigned int higher_val, lower_val; + u8 reg; + + /* + * get the first 8 bits that are be written to + * AB8500_PWM_OUT_CTRL1_REG[0:7] + */ + lower_val = duty_ns & 0x00FF; + /* + * get bits [9:10] that are to be written to + * AB8500_PWM_OUT_CTRL2_REG[0:1] + */ + higher_val = ((duty_ns & 0x0300) >> 8); + + reg = AB8500_PWM_OUT_CTRL1_REG + ((chip->base - 1) * 2); + + ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, + reg, (u8)lower_val); + if (ret < 0) + return ret; + ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, + (reg + 1), (u8)higher_val); + + return ret; +} + +static int ab8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible(chip->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (chip->base - 1), ENABLE_PWM); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); + return ret; +} + +static void ab8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible(chip->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (chip->base - 1), DISABLE_PWM); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); + return; +} + +static const struct pwm_ops ab8500_pwm_ops = { + .config = ab8500_pwm_config, + .enable = ab8500_pwm_enable, + .disable = ab8500_pwm_disable, +}; + +static int __devinit ab8500_pwm_probe(struct platform_device *pdev) +{ + struct ab8500_pwm_chip *ab8500; + int err; + + /* + * Nothing to be done in probe, this is required to get the + * device which is required for ab8500 read and write + */ + ab8500 = kzalloc(sizeof(*ab8500), GFP_KERNEL); + if (ab8500 == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + ab8500->chip.dev = &pdev->dev; + ab8500->chip.ops = &ab8500_pwm_ops; + ab8500->chip.base = pdev->id; + ab8500->chip.npwm = 1; + + err = pwmchip_add(&ab8500->chip); + if (err < 0) { + kfree(ab8500); + return err; + } + + dev_dbg(&pdev->dev, "pwm probe successful\n"); + platform_set_drvdata(pdev, ab8500); + + return 0; +} + +static int __devexit ab8500_pwm_remove(struct platform_device *pdev) +{ + struct ab8500_pwm_chip *ab8500 = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&ab8500->chip); + if (err < 0) + return err; + + dev_dbg(&pdev->dev, "pwm driver removed\n"); + kfree(ab8500); + + return 0; +} + +static struct platform_driver ab8500_pwm_driver = { + .driver = { + .name = "ab8500-pwm", + .owner = THIS_MODULE, + }, + .probe = ab8500_pwm_probe, + .remove = __devexit_p(ab8500_pwm_remove), +}; +module_platform_driver(ab8500_pwm_driver); + +MODULE_AUTHOR("Arun MURTHY <arun.murthy@stericsson.com>"); +MODULE_DESCRIPTION("AB8500 Pulse Width Modulation Driver"); +MODULE_ALIAS("platform:ab8500-pwm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c index d53c4e7941ef..5da8e185e838 100644 --- a/drivers/pwm/pwm-bfin.c +++ b/drivers/pwm/pwm-bfin.c @@ -69,9 +69,6 @@ static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long period, duty; unsigned long long val; - if (duty_ns < 0 || duty_ns > period_ns) - return -EINVAL; - val = (unsigned long long)get_sclk() * period_ns; do_div(val, NSEC_PER_SEC); period = val; diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 2a0b35333972..8a5d3ae2946a 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -16,8 +16,7 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/pwm.h> -#include <mach/hardware.h> - +#include <linux/of_device.h> /* i.MX1 and i.MX21 share the same PWM function block: */ @@ -25,6 +24,7 @@ #define MX1_PWMS 0x04 /* PWM Sample Register */ #define MX1_PWMP 0x08 /* PWM Period Register */ +#define MX1_PWMC_EN (1 << 4) /* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ @@ -40,110 +40,165 @@ #define MX3_PWMCR_EN (1 << 0) struct imx_chip { - struct clk *clk; + struct clk *clk_per; + struct clk *clk_ipg; - int clk_enabled; + int enabled; void __iomem *mmio_base; struct pwm_chip chip; + + int (*config)(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns); + void (*set_enable)(struct pwm_chip *chip, bool enable); }; #define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) -static int imx_pwm_config(struct pwm_chip *chip, +static int imx_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct imx_chip *imx = to_imx_chip(chip); - if (!(cpu_is_mx1() || cpu_is_mx21())) { - unsigned long long c; - unsigned long period_cycles, duty_cycles, prescale; - u32 cr; - - c = clk_get_rate(imx->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - prescale = period_cycles / 0x10000 + 1; - - period_cycles /= prescale; - c = (unsigned long long)period_cycles * duty_ns; - do_div(c, period_ns); - duty_cycles = c; - - /* - * according to imx pwm RM, the real period value should be - * PERIOD value in PWMPR plus 2. - */ - if (period_cycles > 2) - period_cycles -= 2; - else - period_cycles = 0; - - writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); - writel(period_cycles, imx->mmio_base + MX3_PWMPR); - - cr = MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_EN; - - if (cpu_is_mx25()) - cr |= MX3_PWMCR_CLKSRC_IPG; - else - cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; - - writel(cr, imx->mmio_base + MX3_PWMCR); - } else if (cpu_is_mx1() || cpu_is_mx21()) { - /* The PWM subsystem allows for exact frequencies. However, - * I cannot connect a scope on my device to the PWM line and - * thus cannot provide the program the PWM controller - * exactly. Instead, I'm relying on the fact that the - * Bootloader (u-boot or WinCE+haret) has programmed the PWM - * function group already. So I'll just modify the PWM sample - * register to follow the ratio of duty_ns vs. period_ns - * accordingly. - * - * This is good enough for programming the brightness of - * the LCD backlight. - * - * The real implementation would divide PERCLK[0] first by - * both the prescaler (/1 .. /128) and then by CLKSEL - * (/2 .. /16). - */ - u32 max = readl(imx->mmio_base + MX1_PWMP); - u32 p = max * duty_ns / period_ns; - writel(max - p, imx->mmio_base + MX1_PWMS); - } else { - BUG(); - } + /* + * The PWM subsystem allows for exact frequencies. However, + * I cannot connect a scope on my device to the PWM line and + * thus cannot provide the program the PWM controller + * exactly. Instead, I'm relying on the fact that the + * Bootloader (u-boot or WinCE+haret) has programmed the PWM + * function group already. So I'll just modify the PWM sample + * register to follow the ratio of duty_ns vs. period_ns + * accordingly. + * + * This is good enough for programming the brightness of + * the LCD backlight. + * + * The real implementation would divide PERCLK[0] first by + * both the prescaler (/1 .. /128) and then by CLKSEL + * (/2 .. /16). + */ + u32 max = readl(imx->mmio_base + MX1_PWMP); + u32 p = max * duty_ns / period_ns; + writel(max - p, imx->mmio_base + MX1_PWMS); return 0; } +static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) +{ + struct imx_chip *imx = to_imx_chip(chip); + u32 val; + + val = readl(imx->mmio_base + MX1_PWMC); + + if (enable) + val |= MX1_PWMC_EN; + else + val &= ~MX1_PWMC_EN; + + writel(val, imx->mmio_base + MX1_PWMC); +} + +static int imx_pwm_config_v2(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct imx_chip *imx = to_imx_chip(chip); + unsigned long long c; + unsigned long period_cycles, duty_cycles, prescale; + u32 cr; + + c = clk_get_rate(imx->clk_per); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + prescale = period_cycles / 0x10000 + 1; + + period_cycles /= prescale; + c = (unsigned long long)period_cycles * duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * according to imx pwm RM, the real period value should be + * PERIOD value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + cr = MX3_PWMCR_PRESCALER(prescale) | + MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | + MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH; + + if (imx->enabled) + cr |= MX3_PWMCR_EN; + + writel(cr, imx->mmio_base + MX3_PWMCR); + + return 0; +} + +static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) +{ + struct imx_chip *imx = to_imx_chip(chip); + u32 val; + + val = readl(imx->mmio_base + MX3_PWMCR); + + if (enable) + val |= MX3_PWMCR_EN; + else + val &= ~MX3_PWMCR_EN; + + writel(val, imx->mmio_base + MX3_PWMCR); +} + +static int imx_pwm_config(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct imx_chip *imx = to_imx_chip(chip); + int ret; + + ret = clk_prepare_enable(imx->clk_ipg); + if (ret) + return ret; + + ret = imx->config(chip, pwm, duty_ns, period_ns); + + clk_disable_unprepare(imx->clk_ipg); + + return ret; +} + static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); - int rc = 0; + int ret; - if (!imx->clk_enabled) { - rc = clk_prepare_enable(imx->clk); - if (!rc) - imx->clk_enabled = 1; - } - return rc; + ret = clk_prepare_enable(imx->clk_per); + if (ret) + return ret; + + imx->set_enable(chip, true); + + imx->enabled = 1; + + return 0; } static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); - writel(0, imx->mmio_base + MX3_PWMCR); + imx->set_enable(chip, false); - if (imx->clk_enabled) { - clk_disable_unprepare(imx->clk); - imx->clk_enabled = 0; - } + clk_disable_unprepare(imx->clk_per); + imx->enabled = 0; } static struct pwm_ops imx_pwm_ops = { @@ -153,30 +208,66 @@ static struct pwm_ops imx_pwm_ops = { .owner = THIS_MODULE, }; +struct imx_pwm_data { + int (*config)(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns); + void (*set_enable)(struct pwm_chip *chip, bool enable); +}; + +static struct imx_pwm_data imx_pwm_data_v1 = { + .config = imx_pwm_config_v1, + .set_enable = imx_pwm_set_enable_v1, +}; + +static struct imx_pwm_data imx_pwm_data_v2 = { + .config = imx_pwm_config_v2, + .set_enable = imx_pwm_set_enable_v2, +}; + +static const struct of_device_id imx_pwm_dt_ids[] = { + { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, }, + { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids); + static int __devinit imx_pwm_probe(struct platform_device *pdev) { + const struct of_device_id *of_id = + of_match_device(imx_pwm_dt_ids, &pdev->dev); + struct imx_pwm_data *data; struct imx_chip *imx; struct resource *r; int ret = 0; + if (!of_id) + return -ENODEV; + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); if (imx == NULL) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } - imx->clk = devm_clk_get(&pdev->dev, "pwm"); + imx->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(imx->clk_per)) { + dev_err(&pdev->dev, "getting per clock failed with %ld\n", + PTR_ERR(imx->clk_per)); + return PTR_ERR(imx->clk_per); + } - if (IS_ERR(imx->clk)) - return PTR_ERR(imx->clk); + imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(imx->clk_ipg)) { + dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", + PTR_ERR(imx->clk_ipg)); + return PTR_ERR(imx->clk_ipg); + } imx->chip.ops = &imx_pwm_ops; imx->chip.dev = &pdev->dev; imx->chip.base = -1; imx->chip.npwm = 1; - imx->clk_enabled = 0; - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (r == NULL) { dev_err(&pdev->dev, "no memory resource defined\n"); @@ -187,6 +278,10 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) if (imx->mmio_base == NULL) return -EADDRNOTAVAIL; + data = of_id->data; + imx->config = data->config; + imx->set_enable = data->set_enable; + ret = pwmchip_add(&imx->chip); if (ret < 0) return ret; @@ -208,23 +303,14 @@ static int __devexit imx_pwm_remove(struct platform_device *pdev) static struct platform_driver imx_pwm_driver = { .driver = { - .name = "mxc_pwm", + .name = "imx-pwm", + .of_match_table = of_match_ptr(imx_pwm_dt_ids), }, .probe = imx_pwm_probe, .remove = __devexit_p(imx_pwm_remove), }; -static int __init imx_pwm_init(void) -{ - return platform_driver_register(&imx_pwm_driver); -} -arch_initcall(imx_pwm_init); - -static void __exit imx_pwm_exit(void) -{ - platform_driver_unregister(&imx_pwm_driver); -} -module_exit(imx_pwm_exit); +module_platform_driver(imx_pwm_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c new file mode 100644 index 000000000000..10250fcefb98 --- /dev/null +++ b/drivers/pwm/pwm-jz4740.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 platform PWM support + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#include <asm/mach-jz4740/gpio.h> +#include <asm/mach-jz4740/timer.h> + +#define NUM_PWM 8 + +static const unsigned int jz4740_pwm_gpio_list[NUM_PWM] = { + JZ_GPIO_PWM0, + JZ_GPIO_PWM1, + JZ_GPIO_PWM2, + JZ_GPIO_PWM3, + JZ_GPIO_PWM4, + JZ_GPIO_PWM5, + JZ_GPIO_PWM6, + JZ_GPIO_PWM7, +}; + +struct jz4740_pwm_chip { + struct pwm_chip chip; + struct clk *clk; +}; + +static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) +{ + return container_of(chip, struct jz4740_pwm_chip, chip); +} + +static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm]; + int ret; + + /* + * Timers 0 and 1 are used for system tasks, so they are unavailable + * for use as PWMs. + */ + if (pwm->hwpwm < 2) + return -EBUSY; + + ret = gpio_request(gpio, pwm->label); + if (ret) { + dev_err(chip->dev, "Failed to request GPIO#%u for PWM: %d\n", + gpio, ret); + return ret; + } + + jz_gpio_set_function(gpio, JZ_GPIO_FUNC_PWM); + + jz4740_timer_start(pwm->hwpwm); + + return 0; +} + +static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm]; + + jz4740_timer_set_ctrl(pwm->hwpwm, 0); + + jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE); + gpio_free(gpio); + + jz4740_timer_stop(pwm->hwpwm); +} + +static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm); + + ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; + jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + jz4740_timer_enable(pwm->hwpwm); + + return 0; +} + +static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm); + + ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; + jz4740_timer_disable(pwm->hwpwm); + jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); +} + +static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); + unsigned long long tmp; + unsigned long period, duty; + unsigned int prescaler = 0; + uint16_t ctrl; + bool is_enabled; + + tmp = (unsigned long long)clk_get_rate(jz4740->clk) * period_ns; + do_div(tmp, 1000000000); + period = tmp; + + while (period > 0xffff && prescaler < 6) { + period >>= 2; + ++prescaler; + } + + if (prescaler == 6) + return -EINVAL; + + tmp = (unsigned long long)period * duty_ns; + do_div(tmp, period_ns); + duty = period - tmp; + + if (duty >= period) + duty = period - 1; + + is_enabled = jz4740_timer_is_enabled(pwm->hwpwm); + if (is_enabled) + jz4740_pwm_disable(chip, pwm); + + jz4740_timer_set_count(pwm->hwpwm, 0); + jz4740_timer_set_duty(pwm->hwpwm, duty); + jz4740_timer_set_period(pwm->hwpwm, period); + + ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT | + JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN; + + jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + + if (is_enabled) + jz4740_pwm_enable(chip, pwm); + + return 0; +} + +static const struct pwm_ops jz4740_pwm_ops = { + .request = jz4740_pwm_request, + .free = jz4740_pwm_free, + .config = jz4740_pwm_config, + .enable = jz4740_pwm_enable, + .disable = jz4740_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit jz4740_pwm_probe(struct platform_device *pdev) +{ + struct jz4740_pwm_chip *jz4740; + int ret; + + jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL); + if (!jz4740) + return -ENOMEM; + + jz4740->clk = clk_get(NULL, "ext"); + if (IS_ERR(jz4740->clk)) + return PTR_ERR(jz4740->clk); + + jz4740->chip.dev = &pdev->dev; + jz4740->chip.ops = &jz4740_pwm_ops; + jz4740->chip.npwm = NUM_PWM; + jz4740->chip.base = -1; + + ret = pwmchip_add(&jz4740->chip); + if (ret < 0) { + clk_put(jz4740->clk); + return ret; + } + + platform_set_drvdata(pdev, jz4740); + + return 0; +} + +static int __devexit jz4740_pwm_remove(struct platform_device *pdev) +{ + struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&jz4740->chip); + if (ret < 0) + return ret; + + clk_put(jz4740->clk); + + return 0; +} + +static struct platform_driver jz4740_pwm_driver = { + .driver = { + .name = "jz4740-pwm", + .owner = THIS_MODULE, + }, + .probe = jz4740_pwm_probe, + .remove = __devexit_p(jz4740_pwm_remove), +}; +module_platform_driver(jz4740_pwm_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); +MODULE_ALIAS("platform:jz4740-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-puv3.c b/drivers/pwm/pwm-puv3.c new file mode 100644 index 000000000000..2a93f37c46ad --- /dev/null +++ b/drivers/pwm/pwm-puv3.c @@ -0,0 +1,161 @@ +/* + * linux/arch/unicore32/kernel/pwm.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> + +#include <asm/div64.h> +#include <mach/hardware.h> + +struct puv3_pwm_chip { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; + bool enabled; +}; + +static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip) +{ + return container_of(chip, struct puv3_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + */ +static int puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + unsigned long period_cycles, prescale, pv, dc; + struct puv3_pwm_chip *puv3 = to_puv3(chip); + unsigned long long c; + + c = clk_get_rate(puv3->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + + prescale = (period_cycles - 1) / 1024; + pv = period_cycles / (prescale + 1) - 1; + + if (prescale > 63) + return -EINVAL; + + if (duty_ns == period_ns) + dc = OST_PWMDCCR_FDCYCLE; + else + dc = (pv + 1) * duty_ns / period_ns; + + /* + * NOTE: the clock to PWM has to be enabled first + * before writing to the registers + */ + clk_prepare_enable(puv3->clk); + + writel(prescale, puv3->base + OST_PWM_PWCR); + writel(pv - dc, puv3->base + OST_PWM_DCCR); + writel(pv, puv3->base + OST_PWM_PCR); + + clk_disable_unprepare(puv3->clk); + + return 0; +} + +static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct puv3_pwm_chip *puv3 = to_puv3(chip); + + return clk_prepare_enable(puv3->clk); +} + +static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct puv3_pwm_chip *puv3 = to_puv3(chip); + + clk_disable_unprepare(puv3->clk); +} + +static const struct pwm_ops puv3_pwm_ops = { + .config = puv3_pwm_config, + .enable = puv3_pwm_enable, + .disable = puv3_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit pwm_probe(struct platform_device *pdev) +{ + struct puv3_pwm_chip *puv3; + struct resource *r; + int ret; + + puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL); + if (puv3 == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK"); + if (IS_ERR(puv3->clk)) + return PTR_ERR(puv3->clk); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + puv3->base = devm_request_and_ioremap(&pdev->dev, r); + if (puv3->base == NULL) + return -EADDRNOTAVAIL; + + puv3->chip.dev = &pdev->dev; + puv3->chip.ops = &puv3_pwm_ops; + puv3->chip.base = -1; + puv3->chip.npwm = 1; + + ret = pwmchip_add(&puv3->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, puv3); + return 0; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev); + + return pwmchip_remove(&puv3->chip); +} + +static struct platform_driver puv3_pwm_driver = { + .driver = { + .name = "PKUnity-v3-PWM", + }, + .probe = pwm_probe, + .remove = __devexit_p(pwm_remove), +}; +module_platform_driver(puv3_pwm_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index bd5867a1c700..260c3a88564d 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -70,9 +70,6 @@ static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long offset; int rc; - if (period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - offset = pwm->hwpwm ? 0x10 : 0; c = clk_get_rate(pc->clk); diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index e5187c0ade9f..023a3bee76e7 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -126,9 +126,6 @@ static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) return -ERANGE; - if (duty_ns > period_ns) - return -EINVAL; - if (period_ns == s3c->period_ns && duty_ns == s3c->duty_ns) return 0; diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 4b6688909fee..d6d4cf05565e 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -32,6 +32,7 @@ #define CAP3 0x10 #define CAP4 0x14 #define ECCTL2 0x2A +#define ECCTL2_APWM_POL_LOW BIT(10) #define ECCTL2_APWM_MODE BIT(9) #define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) #define ECCTL2_TSCTR_FREERUN BIT(4) @@ -59,7 +60,7 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long period_cycles, duty_cycles; unsigned int reg_val; - if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + if (period_ns > NSEC_PER_SEC) return -ERANGE; c = pc->clk_rate; @@ -111,6 +112,26 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } +static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned short reg_val; + + pm_runtime_get_sync(pc->chip.dev); + reg_val = readw(pc->mmio_base + ECCTL2); + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + reg_val |= ECCTL2_APWM_POL_LOW; + else + /* Duty cycle defines HIGH period of PWM */ + reg_val &= ~ECCTL2_APWM_POL_LOW; + + 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); @@ -157,6 +178,7 @@ 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, diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index b1996bcd5b78..d3c1dff0a0dc 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -81,6 +81,15 @@ #define AQCTL_ZRO_FRCHIGH BIT(1) #define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0)) +#define AQCTL_CHANA_POLNORMAL (AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \ + AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANA_POLINVERSED (AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \ + AQCTL_ZRO_FRCLOW) +#define AQCTL_CHANB_POLNORMAL (AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \ + AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANB_POLINVERSED (AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \ + AQCTL_ZRO_FRCLOW) + #define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6)) #define AQSFRC_RLDCSF_ZRO 0 #define AQSFRC_RLDCSF_PRD BIT(6) @@ -105,6 +114,7 @@ struct ehrpwm_pwm_chip { unsigned int clk_rate; void __iomem *mmio_base; unsigned long period_cycles[NUM_PWM_CHANNEL]; + enum pwm_polarity polarity[NUM_PWM_CHANNEL]; }; static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) @@ -165,39 +175,37 @@ static int set_prescale_div(unsigned long rqst_prescaler, return 1; } -static void configure_chans(struct ehrpwm_pwm_chip *pc, int chan, - unsigned long duty_cycles) +static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan) { - int cmp_reg, aqctl_reg; + int aqctl_reg; unsigned short aqctl_val, aqctl_mask; /* - * Channels can be configured from action qualifier module. - * Channel 0 configured with compare A register and for - * up-counter mode. - * Channel 1 configured with compare B register and for - * up-counter mode. + * Configure PWM output to HIGH/LOW level on counter + * reaches compare register value and LOW/HIGH level + * on counter value reaches period register value and + * zero value on counter */ if (chan == 1) { aqctl_reg = AQCTLB; - cmp_reg = CMPB; - /* Configure PWM Low from compare B value */ - aqctl_val = AQCTL_CBU_FRCLOW; aqctl_mask = AQCTL_CBU_MASK; + + if (pc->polarity[chan] == PWM_POLARITY_INVERSED) + aqctl_val = AQCTL_CHANB_POLINVERSED; + else + aqctl_val = AQCTL_CHANB_POLNORMAL; } else { - cmp_reg = CMPA; aqctl_reg = AQCTLA; - /* Configure PWM Low from compare A value*/ - aqctl_val = AQCTL_CAU_FRCLOW; aqctl_mask = AQCTL_CAU_MASK; + + if (pc->polarity[chan] == PWM_POLARITY_INVERSED) + aqctl_val = AQCTL_CHANA_POLINVERSED; + else + aqctl_val = AQCTL_CHANA_POLNORMAL; } - /* Configure PWM High from period value and zero value */ - aqctl_val |= AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH; aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK; - ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); - - ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); + ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); } /* @@ -211,9 +219,9 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long c; unsigned long period_cycles, duty_cycles; unsigned short ps_divval, tb_divval; - int i; + int i, cmp_reg; - if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + if (period_ns > NSEC_PER_SEC) return -ERANGE; c = pc->clk_rate; @@ -278,12 +286,29 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK, TBCTL_CTRMODE_UP); - /* Configure the channel for duty cycle */ - configure_chans(pc, pwm->hwpwm, duty_cycles); + if (pwm->hwpwm == 1) + /* Channel 1 configured with compare B register */ + cmp_reg = CMPB; + else + /* Channel 0 configured with compare A register */ + cmp_reg = CMPA; + + 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 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); @@ -307,6 +332,9 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + /* Channels polarity can be configured from action qualifier module */ + configure_polarity(pc, pwm->hwpwm); + /* Enable time counter for free_run */ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN); return 0; @@ -358,6 +386,7 @@ 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, |