From: Bill Gatliff <bgat@billgatliff.com>
To: linux-embedded@vger.kernel.org, linux-embedded@vger.kernel.org
Cc: Bill Gatliff <bgat@billgatliff.com>
Subject: [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only
Date: Fri, 1 Oct 2010 10:17:49 -0500 [thread overview]
Message-ID: <1285946271-17728-8-git-send-email-bgat@billgatliff.com> (raw)
In-Reply-To: <1285946271-17728-1-git-send-email-bgat@billgatliff.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/pxa-pwm.c | 322 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 322 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/pxa-pwm.c
diff --git a/drivers/pwm/pxa-pwm.c b/drivers/pwm/pxa-pwm.c
new file mode 100644
index 0000000..937ab41
--- /dev/null
+++ b/drivers/pwm/pxa-pwm.c
@@ -0,0 +1,322 @@
+/*
+ * drivers/pwm/pxa-pwm.c
+ *
+ * Driver for PXA PWM controllers
+ *
+ * Copyright (c) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (c) 2008 Eric Miao (eric.miao@marvell.com>
+ *
+ * This program is free software; you may redistribute 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/pwm.h>
+
+#include <asm/div64.h>
+
+#define HAS_SECONDARY_PWM 0x10
+
+static const struct platform_device_id pxa_pwm_id_table[] = {
+ /* PWM has_secondary_pwm? */
+ { "pxa25x-pwm", 0 },
+ { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM },
+ { "pxa168-pwm", 1 },
+ { "pxa910-pwm", 1 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, pxa_pwm_id_table);
+
+/* PWM registers and bits definitions */
+enum {
+ PWMCR = 0,
+ PWMDCR = 0x4,
+ PWMPCR = 0x8,
+
+ PWMCR_SD = (1 << 6),
+ PWMDCR_FD = (1 << 10),
+};
+
+struct pxa_pwm {
+ struct pwm_device pwm;
+ spinlock_t lock;
+
+ struct clk *clk;
+ int clk_enabled;
+ void __iomem *mmio_base;
+};
+
+static inline struct pxa_pwm *to_pxa_pwm(const struct pwm_channel *p)
+{
+ return container_of(p->pwm, struct pxa_pwm, pwm);
+}
+
+static inline int __pxa_pwm_enable(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ int ret = 0;
+
+ /* TODO: does the hardware really permit independent control
+ * of both the primary and secondary (if present) channels?
+ * According to Eric Miao's code, it doesn't--- or he didn't
+ * use it that way. Revisit, looking for ways to
+ * independently control either channel.
+ */
+
+ if (!pxa->clk_enabled) {
+ ret = clk_enable(pxa->clk);
+ if (ret)
+ return ret;
+
+ pxa->clk_enabled = 1;
+ }
+
+ return ret;
+}
+
+static inline int __pxa_pwm_disable(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+
+ /* TODO: This is how Eric Miao did it, but I'm concerned that
+ * this won't always drive the PWM output signal to its
+ * inactive state. Revisit.
+ */
+
+ if (pxa->clk_enabled) {
+ clk_disable(pxa->clk);
+ pxa->clk_enabled = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
+ * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
+ */
+static int __pxa_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ void *io;
+
+ p->duty_ticks = c->duty_ticks;
+ io = pxa->mmio_base + (p->chan * 0x10);
+
+ /* NOTE: The clock to the PWM peripheral must be running in
+ * order to write to the peripheral control registers.
+ *
+ * TODO: What does setting PWMPC == PWMDC do? What about PWMDC
+ * == 0 and/or PWMPC == 0? Investigate.
+ */
+ clk_enable(pxa->clk);
+ if (p->duty_ticks == p->period_ticks)
+ __raw_writel(PWMDCR_FD, io + PWMDCR);
+ else
+ __raw_writel(p->duty_ticks, io + PWMDCR);
+ clk_disable(pxa->clk);
+
+ return 0;
+}
+
+static int __pxa_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ void *io;
+ unsigned long prescale, pv;
+
+ prescale = (c->period_ticks - 1) / 1024;
+ if (prescale > 63)
+ return -EINVAL;
+
+ pv = c->period_ticks / (prescale + 1) - 1;
+ p->period_ticks = c->period_ticks;
+
+ io = pxa->mmio_base + (p->chan * 0x10);
+
+ /* NOTE: the clock to PWM has to be enabled first
+ * before writing to the registers.
+ *
+ * TODO: see also the TODOs in __pxa_config_duty_ticks().
+ */
+ clk_enable(pxa->clk);
+ __raw_writel(prescale, io + PWMCR);
+ if (p->period_ticks == p->duty_ticks)
+ __raw_writel(PWMDCR_FD, io + PWMDCR);
+ __raw_writel(pv, io + PWMPCR);
+ clk_disable(pxa->clk);
+
+ return 0;
+}
+
+static int pxa_pwm_request(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+
+ p->tick_hz = clk_get_rate(pxa->clk);
+ return 0;
+}
+
+
+static int pxa_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ if (!(c->config_mask & (PWM_CONFIG_STOP
+ | PWM_CONFIG_START
+ | PWM_CONFIG_DUTY_TICKS
+ | PWM_CONFIG_PERIOD_TICKS)))
+ return -EINVAL;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ if (c->config_mask & PWM_CONFIG_STOP)
+ __pxa_pwm_disable(p);
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ __pxa_config_period_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ __pxa_config_duty_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_START)
+ __pxa_pwm_enable(p);
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+static int pxa_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ return pxa_pwm_config_nosleep(p, c);
+}
+
+static int __init pxa_pwm_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct pxa_pwm *pxa;
+ struct resource *r;
+ int ret = 0;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(&pdev->dev, "error, missing mmio_base resource\n");
+ return -EINVAL;
+ }
+
+ r = request_mem_region(r->start, resource_size(r), pdev->name);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(&pdev->dev, "error, failed to request mmio_base resource\n");
+ return -EBUSY;
+ }
+
+ pxa = kzalloc(sizeof *pxa, GFP_KERNEL);
+ if (IS_ERR_OR_NULL(pxa)) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ pxa->mmio_base = ioremap(r->start, resource_size(r));
+ if (IS_ERR_OR_NULL(pxa->mmio_base)) {
+ dev_err(&pdev->dev, "error, failed to ioremap() registers\n");
+ ret = -ENODEV;
+ goto err_ioremap;
+ }
+
+ pxa->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR_OR_NULL(pxa->clk)) {
+ ret = PTR_ERR(pxa->clk);
+ if (!ret)
+ ret = -EINVAL;
+ goto err_clk_get;
+ }
+ pxa->clk_enabled = 0;
+
+ spin_lock_init(&pxa->lock);
+
+ pxa->pwm.dev = &pdev->dev;
+ pxa->pwm.bus_id = dev_name(&pdev->dev);
+ pxa->pwm.owner = THIS_MODULE;
+ pxa->pwm.request = pxa_pwm_request;
+ pxa->pwm.config_nosleep = pxa_pwm_config_nosleep;
+ pxa->pwm.config = pxa_pwm_config;
+
+ if (id->driver_data & HAS_SECONDARY_PWM)
+ pxa->pwm.nchan = 2;
+ else
+ pxa->pwm.nchan = 1;
+
+ ret = pwm_register(&pxa->pwm);
+
+ if (ret)
+ goto err_pwm_register;
+
+ platform_set_drvdata(pdev, pxa);
+ return 0;
+
+err_pwm_register:
+ clk_put(pxa->clk);
+err_clk_get:
+ iounmap(pxa->mmio_base);
+err_ioremap:
+ kfree(pxa);
+err_kzalloc:
+ release_mem_region(r->start, resource_size(r));
+ return ret;
+}
+
+static int __exit pxa_pwm_remove(struct platform_device *pdev)
+{
+ struct pxa_pwm *pxa = platform_get_drvdata(pdev);
+
+ if (IS_ERR_OR_NULL(pxa))
+ return -ENODEV;
+
+ pwm_unregister(&pxa->pwm);
+ clk_put(pxa->clk);
+ iounmap(pxa->mmio_base);
+ kfree(pxa);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver pwm_driver = {
+ .driver = {
+ .name = "pxa25x-pwm",
+ .owner = THIS_MODULE,
+ },
+ .probe = pxa_pwm_probe,
+ .remove = pxa_pwm_remove,
+ .id_table = pxa_pwm_id_table,
+};
+
+static int __init pxa_pwm_init(void)
+{
+ return platform_driver_register(&pwm_driver);
+}
+/* TODO: do we have to do this at arch_initcall? */
+module_init(pxa_pwm_init);
+
+static void __exit pxa_pwm_exit(void)
+{
+ platform_driver_unregister(&pwm_driver);
+}
+module_exit(pxa_pwm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_AUTHOR("Eric Miao (eric.miao@marvell.com>");
+MODULE_DESCRIPTION("Platform and PWM API drivers for PXA PWM peripheral");
--
1.7.1
next prev parent reply other threads:[~2010-10-01 15:17 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
2010-10-01 15:17 ` [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin Bill Gatliff
2010-10-16 6:54 ` Grant Likely
2010-10-01 15:17 ` [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API Bill Gatliff
2010-10-16 7:50 ` Grant Likely
2010-10-01 15:17 ` [PWM 04/10] Implements PWM-based LED control Bill Gatliff
2010-10-16 7:58 ` Grant Likely
2010-10-01 15:17 ` [PWM 05/10] LED "dim" trigger based on PWM control of the LED Bill Gatliff
2010-10-16 8:00 ` Grant Likely
2010-10-01 15:17 ` [PWM 06/10] Incorporate PWM API code into KBuild Bill Gatliff
2010-10-16 8:02 ` Grant Likely
2010-10-19 2:17 ` Bill Gatliff
2010-10-01 15:17 ` [PWM 07/10] PWM API driver for MPC52xx GPT peripheral Bill Gatliff
2010-10-01 15:17 ` [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only Bill Gatliff
2010-10-01 15:17 ` Bill Gatliff [this message]
2010-10-01 15:17 ` [PWM 09/10] Build pwm.o only if CONFIG_GENERIC_PWM is set Bill Gatliff
2010-10-01 15:17 ` [PWM 10/10] Expunge previous driver for PXA PWM Bill Gatliff
2010-10-01 22:00 ` [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Kevin Hilman
2010-10-02 5:13 ` Jason Kridner
2010-10-06 18:45 ` Bill Gatliff
2010-10-06 19:08 ` Kevin Hilman
2010-10-02 12:25 ` Hector Oron
2010-10-06 18:48 ` Bill Gatliff
2010-10-16 6:05 ` Grant Likely
2010-10-05 10:35 ` sugumar
2010-10-06 18:50 ` Bill Gatliff
2010-10-06 19:02 ` Grosen, Mark
2010-10-07 7:58 ` Sugumar Natarajan
2010-10-16 7:42 ` Grant Likely
2010-10-20 18:13 ` Bill Gatliff
2010-10-20 18:34 ` Grant Likely
2010-10-20 19:32 ` Bill Gatliff
2010-10-21 13:18 ` Bill Gatliff
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1285946271-17728-8-git-send-email-bgat@billgatliff.com \
--to=bgat@billgatliff.com \
--cc=linux-embedded@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).