summaryrefslogtreecommitdiffstats
path: root/drivers/pwm/pwm-ep93xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pwm/pwm-ep93xx.c')
-rw-r--r--drivers/pwm/pwm-ep93xx.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-ep93xx.c b/drivers/pwm/pwm-ep93xx.c
new file mode 100644
index 000000000..c45a75e65
--- /dev/null
+++ b/drivers/pwm/pwm-ep93xx.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PWM framework driver for Cirrus Logic EP93xx
+ *
+ * Copyright (c) 2009 Matthieu Crapet <mcrapet@gmail.com>
+ * Copyright (c) 2009, 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * EP9301/02 have only one channel:
+ * platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14)
+ *
+ * EP9307 has only one channel:
+ * platform device ep93xx-pwm.0 - PWMOUT
+ *
+ * EP9312/15 have two channels:
+ * platform device ep93xx-pwm.0 - PWMOUT
+ * platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14)
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/pwm.h>
+
+#include <asm/div64.h>
+
+#include <linux/soc/cirrus/ep93xx.h> /* for ep93xx_pwm_{acquire,release}_gpio() */
+
+#define EP93XX_PWMx_TERM_COUNT 0x00
+#define EP93XX_PWMx_DUTY_CYCLE 0x04
+#define EP93XX_PWMx_ENABLE 0x08
+#define EP93XX_PWMx_INVERT 0x0c
+
+struct ep93xx_pwm {
+ void __iomem *base;
+ struct clk *clk;
+ struct pwm_chip chip;
+};
+
+static inline struct ep93xx_pwm *to_ep93xx_pwm(struct pwm_chip *chip)
+{
+ return container_of(chip, struct ep93xx_pwm, chip);
+}
+
+static int ep93xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct platform_device *pdev = to_platform_device(chip->dev);
+
+ return ep93xx_pwm_acquire_gpio(pdev);
+}
+
+static void ep93xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct platform_device *pdev = to_platform_device(chip->dev);
+
+ ep93xx_pwm_release_gpio(pdev);
+}
+
+static int ep93xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int ret;
+ struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip);
+ bool enabled = state->enabled;
+ void __iomem *base = ep93xx_pwm->base;
+ unsigned long long c;
+ unsigned long period_cycles;
+ unsigned long duty_cycles;
+ unsigned long term;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
+ clk_disable_unprepare(ep93xx_pwm->clk);
+ enabled = false;
+ }
+
+ /*
+ * The clock needs to be enabled to access the PWM registers.
+ * Polarity can only be changed when the PWM is disabled.
+ */
+ ret = clk_prepare_enable(ep93xx_pwm->clk);
+ if (ret)
+ return ret;
+
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_INVERT);
+ else
+ writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_INVERT);
+
+ clk_disable_unprepare(ep93xx_pwm->clk);
+ }
+
+ if (!state->enabled) {
+ if (enabled) {
+ writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
+ clk_disable_unprepare(ep93xx_pwm->clk);
+ }
+
+ return 0;
+ }
+
+ /*
+ * The clock needs to be enabled to access the PWM registers.
+ * Configuration can be changed at any time.
+ */
+ if (!pwm_is_enabled(pwm)) {
+ ret = clk_prepare_enable(ep93xx_pwm->clk);
+ if (ret)
+ return ret;
+ }
+
+ c = clk_get_rate(ep93xx_pwm->clk);
+ c *= state->period;
+ do_div(c, 1000000000);
+ period_cycles = c;
+
+ c = period_cycles;
+ c *= state->duty_cycle;
+ do_div(c, state->period);
+ duty_cycles = c;
+
+ if (period_cycles < 0x10000 && duty_cycles < 0x10000) {
+ term = readw(base + EP93XX_PWMx_TERM_COUNT);
+
+ /* Order is important if PWM is running */
+ if (period_cycles > term) {
+ writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT);
+ writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE);
+ } else {
+ writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE);
+ writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT);
+ }
+ ret = 0;
+ } else {
+ ret = -EINVAL;
+ }
+
+ if (!pwm_is_enabled(pwm))
+ clk_disable_unprepare(ep93xx_pwm->clk);
+
+ if (ret)
+ return ret;
+
+ if (!enabled) {
+ ret = clk_prepare_enable(ep93xx_pwm->clk);
+ if (ret)
+ return ret;
+
+ writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops ep93xx_pwm_ops = {
+ .request = ep93xx_pwm_request,
+ .free = ep93xx_pwm_free,
+ .apply = ep93xx_pwm_apply,
+ .owner = THIS_MODULE,
+};
+
+static int ep93xx_pwm_probe(struct platform_device *pdev)
+{
+ struct ep93xx_pwm *ep93xx_pwm;
+ int ret;
+
+ ep93xx_pwm = devm_kzalloc(&pdev->dev, sizeof(*ep93xx_pwm), GFP_KERNEL);
+ if (!ep93xx_pwm)
+ return -ENOMEM;
+
+ ep93xx_pwm->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ep93xx_pwm->base))
+ return PTR_ERR(ep93xx_pwm->base);
+
+ ep93xx_pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk");
+ if (IS_ERR(ep93xx_pwm->clk))
+ return PTR_ERR(ep93xx_pwm->clk);
+
+ ep93xx_pwm->chip.dev = &pdev->dev;
+ ep93xx_pwm->chip.ops = &ep93xx_pwm_ops;
+ ep93xx_pwm->chip.npwm = 1;
+
+ ret = devm_pwmchip_add(&pdev->dev, &ep93xx_pwm->chip);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct platform_driver ep93xx_pwm_driver = {
+ .driver = {
+ .name = "ep93xx-pwm",
+ },
+ .probe = ep93xx_pwm_probe,
+};
+module_platform_driver(ep93xx_pwm_driver);
+
+MODULE_DESCRIPTION("Cirrus Logic EP93xx PWM driver");
+MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>");
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_ALIAS("platform:ep93xx-pwm");
+MODULE_LICENSE("GPL");