aboutsummaryrefslogtreecommitdiff
path: root/drivers/pwm/pwm-tiehrpwm.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-07-30 09:22:37 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2012-07-30 09:22:37 -0700
commit9ec97169e7d6afe2f8206d694d1411fb3bb49853 (patch)
tree9d24c8cd440a312f96b70db5cdaaef1136787003 /drivers/pwm/pwm-tiehrpwm.c
parenta410963ba4c0c768302f0298e258b1ee940e8316 (diff)
parent19891b20e7c275feb92d669f4b1879861f7e8c25 (diff)
Merge branch 'for-3.6' of git://gitorious.org/linux-pwm/linux-pwm
Pull PWM subsystem from Thierry Reding: "The new PWM subsystem aims at collecting all implementations of the legacy PWM API and to eventually replace it completely. The subsystem has been in development for over half a year now and many drivers have already been converted. It has been in linux-next for a couple of weeks and there have been no major issues so I think it is ready for inclusion in your tree." Arnd Bergmann <arnd@arndb.de>: "Very much Ack on the new subsystem. It uses the interface declarations as the previously separate pwm drivers, so nothing changes for now in the drivers using it, although it enables us to change those more easily in the future if we want to. This work is also one of the missing pieces that are required to eventually build ARM kernels for multiple platforms, which is currently prohibited (amongs other things) by the fact that you cannot have more than one driver exporting the pwm functions." Tested-and-acked-by: Alexandre Courbot <acourbot@nvidia.com> Acked-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Acked-by: Philip, Avinash <avinashphilip@ti.com> # TI's AM33xx platforms Acked-By: Alexandre Pereira da Silva <aletes.xgr@gmail.com> # LPC32XX Acked-by: Arnd Bergmann <arnd@arndb.de> Acked-by: Sachin Kamat <sachin.kamat@linaro.org> Fix up trivial conflicts with other cleanups and DT updates. * 'for-3.6' of git://gitorious.org/linux-pwm/linux-pwm: (36 commits) pwm: pwm-tiehrpwm: PWM driver support for EHRPWM pwm: pwm-tiecap: PWM driver support for ECAP APWM pwm: fix used-uninitialized warning in pwm_get() pwm: add lpc32xx PWM support pwm_backlight: pass correct brightness to callback pwm: Use pr_* functions in pwm-samsung.c file pwm: Convert pwm-samsung to use devm_* APIs pwm: Convert pwm-tegra to use devm_clk_get() pwm: pwm-mxs: Return proper error if pwmchip_remove() fails pwm: pwm-bfin: Return proper error if pwmchip_remove() fails pwm: pxa: Propagate pwmchip_remove() error pwm: Convert pwm-pxa to use devm_* APIs pwm: Convert pwm-vt8500 to use devm_* APIs pwm: Convert pwm-imx to use devm_* APIs pwm: Conflict with legacy PWM API pwm: pwm-mxs: add pinctrl support pwm: pwm-mxs: use devm_* managed functions pwm: pwm-mxs: use global reset function stmp_reset_block pwm: pwm-mxs: encode soc name in compatible string pwm: Take over maintainership of the PWM subsystem ...
Diffstat (limited to 'drivers/pwm/pwm-tiehrpwm.c')
-rw-r--r--drivers/pwm/pwm-tiehrpwm.c411
1 files changed, 411 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
new file mode 100644
index 000000000000..010d232cb0c8
--- /dev/null
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -0,0 +1,411 @@
+/*
+ * EHRPWM PWM driver
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/pm_runtime.h>
+
+/* EHRPWM registers and bits definitions */
+
+/* Time base module registers */
+#define TBCTL 0x00
+#define TBPRD 0x0A
+
+#define TBCTL_RUN_MASK (BIT(15) | BIT(14))
+#define TBCTL_STOP_NEXT 0
+#define TBCTL_STOP_ON_CYCLE BIT(14)
+#define TBCTL_FREE_RUN (BIT(15) | BIT(14))
+#define TBCTL_PRDLD_MASK BIT(3)
+#define TBCTL_PRDLD_SHDW 0
+#define TBCTL_PRDLD_IMDT BIT(3)
+#define TBCTL_CLKDIV_MASK (BIT(12) | BIT(11) | BIT(10) | BIT(9) | \
+ BIT(8) | BIT(7))
+#define TBCTL_CTRMODE_MASK (BIT(1) | BIT(0))
+#define TBCTL_CTRMODE_UP 0
+#define TBCTL_CTRMODE_DOWN BIT(0)
+#define TBCTL_CTRMODE_UPDOWN BIT(1)
+#define TBCTL_CTRMODE_FREEZE (BIT(1) | BIT(0))
+
+#define TBCTL_HSPCLKDIV_SHIFT 7
+#define TBCTL_CLKDIV_SHIFT 10
+
+#define CLKDIV_MAX 7
+#define HSPCLKDIV_MAX 7
+#define PERIOD_MAX 0xFFFF
+
+/* compare module registers */
+#define CMPA 0x12
+#define CMPB 0x14
+
+/* Action qualifier module registers */
+#define AQCTLA 0x16
+#define AQCTLB 0x18
+#define AQSFRC 0x1A
+#define AQCSFRC 0x1C
+
+#define AQCTL_CBU_MASK (BIT(9) | BIT(8))
+#define AQCTL_CBU_FRCLOW BIT(8)
+#define AQCTL_CBU_FRCHIGH BIT(9)
+#define AQCTL_CBU_FRCTOGGLE (BIT(9) | BIT(8))
+#define AQCTL_CAU_MASK (BIT(5) | BIT(4))
+#define AQCTL_CAU_FRCLOW BIT(4)
+#define AQCTL_CAU_FRCHIGH BIT(5)
+#define AQCTL_CAU_FRCTOGGLE (BIT(5) | BIT(4))
+#define AQCTL_PRD_MASK (BIT(3) | BIT(2))
+#define AQCTL_PRD_FRCLOW BIT(2)
+#define AQCTL_PRD_FRCHIGH BIT(3)
+#define AQCTL_PRD_FRCTOGGLE (BIT(3) | BIT(2))
+#define AQCTL_ZRO_MASK (BIT(1) | BIT(0))
+#define AQCTL_ZRO_FRCLOW BIT(0)
+#define AQCTL_ZRO_FRCHIGH BIT(1)
+#define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0))
+
+#define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6))
+#define AQSFRC_RLDCSF_ZRO 0
+#define AQSFRC_RLDCSF_PRD BIT(6)
+#define AQSFRC_RLDCSF_ZROPRD BIT(7)
+#define AQSFRC_RLDCSF_IMDT (BIT(7) | BIT(6))
+
+#define AQCSFRC_CSFB_MASK (BIT(3) | BIT(2))
+#define AQCSFRC_CSFB_FRCDIS 0
+#define AQCSFRC_CSFB_FRCLOW BIT(2)
+#define AQCSFRC_CSFB_FRCHIGH BIT(3)
+#define AQCSFRC_CSFB_DISSWFRC (BIT(3) | BIT(2))
+#define AQCSFRC_CSFA_MASK (BIT(1) | BIT(0))
+#define AQCSFRC_CSFA_FRCDIS 0
+#define AQCSFRC_CSFA_FRCLOW BIT(0)
+#define AQCSFRC_CSFA_FRCHIGH BIT(1)
+#define AQCSFRC_CSFA_DISSWFRC (BIT(1) | BIT(0))
+
+#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */
+
+struct ehrpwm_pwm_chip {
+ struct pwm_chip chip;
+ unsigned int clk_rate;
+ void __iomem *mmio_base;
+};
+
+static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
+{
+ return container_of(chip, struct ehrpwm_pwm_chip, chip);
+}
+
+static void ehrpwm_write(void *base, int offset, unsigned int val)
+{
+ writew(val & 0xFFFF, base + offset);
+}
+
+static void ehrpwm_modify(void *base, int offset,
+ unsigned short mask, unsigned short val)
+{
+ unsigned short regval;
+
+ regval = readw(base + offset);
+ regval &= ~mask;
+ regval |= val & mask;
+ writew(regval, base + offset);
+}
+
+/**
+ * set_prescale_div - Set up the prescaler divider function
+ * @rqst_prescaler: prescaler value min
+ * @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)
+{
+ 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.
+ * HSPCLKDIVIDER = 2 ** hspclkdiv
+ * CLKDIVIDER = (1), if clkdiv == 0 *OR*
+ * (2 * clkdiv), if clkdiv != 0
+ *
+ * Configure prescale_div value such that period
+ * register value is less than 65535.
+ */
+
+ *prescale_div = (1 << clkdiv) *
+ (hspclkdiv ? (hspclkdiv * 2) : 1);
+ if (*prescale_div > rqst_prescaler) {
+ *tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) |
+ (hspclkdiv << TBCTL_HSPCLKDIV_SHIFT);
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+static void configure_chans(struct ehrpwm_pwm_chip *pc, int chan,
+ unsigned long duty_cycles)
+{
+ int cmp_reg, 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.
+ */
+ 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;
+ } else {
+ cmp_reg = CMPA;
+ aqctl_reg = AQCTLA;
+ /* Configure PWM Low from compare A value*/
+ aqctl_val = AQCTL_CAU_FRCLOW;
+ aqctl_mask = AQCTL_CAU_MASK;
+ }
+
+ /* 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);
+}
+
+/*
+ * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE
+ * 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)
+{
+ struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+ unsigned long long c;
+ unsigned long period_cycles, duty_cycles;
+ unsigned short ps_divval, tb_divval;
+
+ if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC)
+ return -ERANGE;
+
+ c = pc->clk_rate;
+ c = c * period_ns;
+ do_div(c, NSEC_PER_SEC);
+ period_cycles = (unsigned long)c;
+
+ if (period_cycles < 1) {
+ period_cycles = 1;
+ duty_cycles = 1;
+ } else {
+ c = pc->clk_rate;
+ c = c * duty_ns;
+ do_div(c, NSEC_PER_SEC);
+ duty_cycles = (unsigned long)c;
+ }
+
+ /* Configure clock prescaler to support Low frequency PWM wave */
+ if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval,
+ &tb_divval)) {
+ dev_err(chip->dev, "Unsupported values\n");
+ return -EINVAL;
+ }
+
+ pm_runtime_get_sync(chip->dev);
+
+ /* Update clock prescaler values */
+ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval);
+
+ /* Update period & duty cycle with presacler division */
+ period_cycles = period_cycles / ps_divval;
+ duty_cycles = duty_cycles / ps_divval;
+
+ /* Configure shadow loading on Period register */
+ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_PRDLD_MASK, TBCTL_PRDLD_SHDW);
+
+ ehrpwm_write(pc->mmio_base, TBPRD, period_cycles);
+
+ /* Configure ehrpwm counter for up-count mode */
+ 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);
+ pm_runtime_put_sync(chip->dev);
+ 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;
+
+ /* Leave clock enabled on enabling PWM */
+ pm_runtime_get_sync(chip->dev);
+
+ /* Disabling Action Qualifier on PWM output */
+ if (pwm->hwpwm) {
+ aqcsfrc_val = AQCSFRC_CSFB_FRCDIS;
+ aqcsfrc_mask = AQCSFRC_CSFB_MASK;
+ } else {
+ aqcsfrc_val = AQCSFRC_CSFA_FRCDIS;
+ aqcsfrc_mask = AQCSFRC_CSFA_MASK;
+ }
+
+ /* Changes to shadow mode */
+ ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
+ AQSFRC_RLDCSF_ZRO);
+
+ ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
+
+ /* 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;
+
+ /* Action Qualifier puts PWM output low forcefully */
+ if (pwm->hwpwm) {
+ aqcsfrc_val = AQCSFRC_CSFB_FRCLOW;
+ aqcsfrc_mask = AQCSFRC_CSFB_MASK;
+ } else {
+ aqcsfrc_val = AQCSFRC_CSFA_FRCLOW;
+ aqcsfrc_mask = AQCSFRC_CSFA_MASK;
+ }
+
+ /*
+ * Changes to immediate action on Action Qualifier. This puts
+ * Action Qualifier control on PWM output from next TBCLK
+ */
+ ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
+ AQSFRC_RLDCSF_IMDT);
+
+ ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
+
+ /* Stop Time base counter */
+ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT);
+
+ /* Disable clock on PWM disable */
+ pm_runtime_put_sync(chip->dev);
+}
+
+static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ if (test_bit(PWMF_ENABLED, &pwm->flags)) {
+ dev_warn(chip->dev, "Removing PWM device without disabling\n");
+ pm_runtime_put_sync(chip->dev);
+ }
+}
+
+static const struct pwm_ops ehrpwm_pwm_ops = {
+ .free = ehrpwm_pwm_free,
+ .config = ehrpwm_pwm_config,
+ .enable = ehrpwm_pwm_enable,
+ .disable = ehrpwm_pwm_disable,
+ .owner = THIS_MODULE,
+};
+
+static int __devinit ehrpwm_pwm_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+ struct clk *clk;
+ struct ehrpwm_pwm_chip *pc;
+
+ pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
+ if (!pc) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ clk = devm_clk_get(&pdev->dev, "fck");
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "failed to get clock\n");
+ return PTR_ERR(clk);
+ }
+
+ pc->clk_rate = clk_get_rate(clk);
+ if (!pc->clk_rate) {
+ dev_err(&pdev->dev, "failed to get clock rate\n");
+ return -EINVAL;
+ }
+
+ pc->chip.dev = &pdev->dev;
+ pc->chip.ops = &ehrpwm_pwm_ops;
+ pc->chip.base = -1;
+ pc->chip.npwm = NUM_PWM_CHANNEL;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ dev_err(&pdev->dev, "no memory resource defined\n");
+ return -ENODEV;
+ }
+
+ pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
+ if (!pc->mmio_base) {
+ dev_err(&pdev->dev, "failed to ioremap() registers\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ ret = pwmchip_add(&pc->chip);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
+ return ret;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+ platform_set_drvdata(pdev, pc);
+ return 0;
+}
+
+static int __devexit ehrpwm_pwm_remove(struct platform_device *pdev)
+{
+ struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev);
+
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ return pwmchip_remove(&pc->chip);
+}
+
+static struct platform_driver ehrpwm_pwm_driver = {
+ .driver = {
+ .name = "ehrpwm",
+ },
+ .probe = ehrpwm_pwm_probe,
+ .remove = __devexit_p(ehrpwm_pwm_remove),
+};
+
+module_platform_driver(ehrpwm_pwm_driver);
+
+MODULE_DESCRIPTION("EHRPWM PWM driver");
+MODULE_AUTHOR("Texas Instruments");
+MODULE_LICENSE("GPL");