aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--arch/arm/plat-omap/Kconfig9
-rw-r--r--arch/arm/plat-omap/Makefile1
-rw-r--r--arch/arm/plat-omap/include/plat/pwm.h29
-rw-r--r--arch/arm/plat-omap/pwm.c450
4 files changed, 489 insertions, 0 deletions
diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig
index 49a4c75..11396da 100644
--- a/arch/arm/plat-omap/Kconfig
+++ b/arch/arm/plat-omap/Kconfig
@@ -197,6 +197,15 @@ config OMAP_DM_TIMER
help
Select this option if you want to use OMAP Dual-Mode timers.
+config HAVE_PWM
+ bool "Use PWM timers"
+ depends on OMAP_DM_TIMER
+ help
+ Select this option if you want to be able to request and use
+ one or more of the OMAP dual-mode timers as a generic PWM device
+ compatible with other generic PWM drivers such as the backlight or
+ beeper driver.
+
config OMAP_SERIAL_WAKE
bool "Enable wake-up events for serial ports"
depends on ARCH_OMAP1 && OMAP_MUX
diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile
index f0233e6..779d761 100644
--- a/arch/arm/plat-omap/Makefile
+++ b/arch/arm/plat-omap/Makefile
@@ -32,3 +32,4 @@ obj-y += $(i2c-omap-m) $(i2c-omap-y)
obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox.o
obj-$(CONFIG_OMAP_PM_NOOP) += omap-pm-noop.o
+obj-$(CONFIG_HAVE_PWM) += pwm.o
diff --git a/arch/arm/plat-omap/include/plat/pwm.h b/arch/arm/plat-omap/include/plat/pwm.h
new file mode 100644
index 0000000..04030cd
--- /dev/null
+++ b/arch/arm/plat-omap/include/plat/pwm.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010 Grant Erickson <marathon96@gmail.com>
+ *
+ * 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.
+ *
+ * Description:
+ * This file is defines platform-specific configuration data for
+ * the OMAP generic PWM platform driver.
+ */
+
+#ifndef _OMAP2_PWM_H
+#define _OMAP2_PWM_H
+
+/**
+ * struct omap2_pwm_platform_config - OMAP platform-specific data for PWMs
+ * @timer_id: the OMAP dual-mode timer ID.
+ * @polarity: the polarity (active-high or -low) of the PWM.
+ *
+ * This identifies the OMAP dual-mode timer (dmtimer) that will be bound
+ * to the PWM.
+ */
+struct omap2_pwm_platform_config {
+ int timer_id;
+ bool polarity;
+};
+
+#endif /* _OMAP2_PWM_H */
diff --git a/arch/arm/plat-omap/pwm.c b/arch/arm/plat-omap/pwm.c
new file mode 100644
index 0000000..c6d103d
--- /dev/null
+++ b/arch/arm/plat-omap/pwm.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2010 Grant Erickson <marathon96@gmail.com>
+ *
+ * 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.
+ *
+ * Description:
+ * This file is the core OMAP2/3 support for the generic, Linux
+ * PWM driver / controller, using the OMAP's dual-mode timers.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/pwm.h>
+#include <mach/hardware.h>
+#include <plat/dmtimer.h>
+#include <plat/pwm.h>
+
+/* Preprocessor Definitions */
+
+#undef OMAP_PWM_DEBUG
+
+#if defined(OMAP_PWM_DEBUG)
+#define DBG(args...) \
+ do { \
+ pr_info(args); \
+ } while (0)
+#define DEV_DBG(dev, args...) \
+ do { \
+ dev_info(dev, args); \
+ } while (0)
+#else
+#define DBG(args...) \
+ do { } while (0)
+#define DEV_DBG(dev, args...) \
+ do { } while (0)
+#endif /* defined(OMAP_PWM_DEBUG) */
+
+#define DM_TIMER_LOAD_MIN 0xFFFFFFFE
+
+/* Type Definitions */
+
+/**
+ * struct pwm_device - opaque internal PWM device instance state
+ * @head: list head for all PWMs managed by this driver.
+ * @pdev: corresponding platform device associated with this device instance.
+ * @dm_timer: corresponding dual-mode timer associated with this device
+ * instance.
+ * @config: platform-specific configuration data.
+ * @label: description label.
+ * @use_count: use count.
+ * @pwm_id: generic PWM ID requested for this device instance.
+ *
+ * As far as clients of the PWM driver are concerned, PWM devices are
+ * opaque abstract objects. Consequently, this structure is used for
+ * tracking internal device instance state but is otherwise just a
+ * instance reference externally.
+ */
+
+struct pwm_device {
+ struct list_head head;
+ struct platform_device *pdev;
+ struct omap_dm_timer *dm_timer;
+ struct omap2_pwm_platform_config config;
+ const char *label;
+ unsigned int use_count;
+ unsigned int pwm_id;
+};
+
+/* Function Prototypes */
+
+static int __devinit omap_pwm_probe(struct platform_device *pdev);
+static int __devexit omap_pwm_remove(struct platform_device *pdev);
+
+/* Global Variables */
+
+static struct platform_driver omap_pwm_driver = {
+ .driver = {
+ .name = "omap-pwm",
+ .owner = THIS_MODULE,
+ },
+ .probe = omap_pwm_probe,
+ .remove = __devexit_p(omap_pwm_remove)
+};
+
+/* List and associated lock for managing generic PWM devices bound to
+ * this driver.
+ */
+
+static DEFINE_MUTEX(pwm_lock);
+static LIST_HEAD(pwm_list);
+
+/**
+ * pwm_request - request and allocate the specified generic PWM device.
+ * @pwm_id: The identifier associated with the desired generic PWM device.
+ * @label: An optional pointer to a C string describing the usage of the
+ * requested generic PWM device.
+ *
+ * Returns a pointer to the requested generic PWM device on success;
+ * otherwise, NULL on error.
+ */
+struct pwm_device *pwm_request(int pwm_id, const char *label)
+{
+ struct pwm_device *pwm = NULL;
+ bool found = false;
+
+ mutex_lock(&pwm_lock);
+
+ /* Walk the list of available PWMs and attempt to find a matching
+ * ID, regardless of whether it is in use or not.
+ */
+
+ list_for_each_entry(pwm, &pwm_list, head) {
+ if (pwm->pwm_id == pwm_id) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ if (pwm->use_count == 0) {
+ pwm->use_count++;
+ pwm->label = label;
+ } else {
+ pwm = ERR_PTR(-EBUSY);
+ }
+ } else {
+ pwm = ERR_PTR(-ENOENT);
+ }
+
+ mutex_unlock(&pwm_lock);
+
+ return pwm;
+}
+EXPORT_SYMBOL(pwm_request);
+
+/**
+ * pwm_free - deallocate/release a previously-requested generic PWM device.
+ * @pwm: A pointer to the generic PWM device to release.
+ */
+void pwm_free(struct pwm_device *pwm)
+{
+ mutex_lock(&pwm_lock);
+
+ if (pwm->use_count) {
+ pwm->use_count--;
+ pwm->label = NULL;
+ } else {
+ pr_err("PWM%d has already been freed.\n", pwm->pwm_id);
+ }
+
+ mutex_unlock(&pwm_lock);
+}
+EXPORT_SYMBOL(pwm_free);
+
+/**
+ * pwm_calc_value - determines the counter value for a clock rate and period.
+ * @clk_rate: The clock rate, in Hz, of the PWM's clock source to compute the
+ * counter value for.
+ * @ns: The period, in nanoseconds, to computer the counter value for.
+ *
+ * Returns the PWM counter value for the specified clock rate and period.
+ */
+static inline int pwm_calc_value(unsigned long clk_rate, int ns)
+{
+ const unsigned long nanoseconds_per_second = 1000000000;
+ int cycles;
+ __u64 c;
+
+ c = (__u64)clk_rate * ns;
+ do_div(c, nanoseconds_per_second);
+ cycles = c;
+
+ return DM_TIMER_LOAD_MIN - cycles;
+}
+
+/**
+ * pwm_config - configures the generic PWM device to the specified parameters.
+ * @pwm: A pointer to the PWM device to configure.
+ * @duty_ns: The duty period of the PWM, in nanoseconds.
+ * @period_ns: The overall period of the PWM, in nanoseconds.
+ *
+ * Returns 0 if the generic PWM device was successfully configured;
+ * otherwise, < 0 on error.
+ */
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ int status = 0;
+ const bool enable = true;
+ const bool autoreload = true;
+ const bool toggle = true;
+ const int trigger = OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE;
+ int load_value, match_value;
+ unsigned long clk_rate;
+
+ DEV_DBG(&pwm->pdev->dev,
+ "duty cycle: %d, period %d\n",
+ duty_ns, period_ns);
+
+ clk_rate = clk_get_rate(omap_dm_timer_get_fclk(pwm->dm_timer));
+
+ /* Calculate the appropriate load and match values based on the
+ * specified period and duty cycle. The load value determines the
+ * cycle time and the match value determines the duty cycle.
+ */
+
+ load_value = pwm_calc_value(clk_rate, period_ns);
+ match_value = pwm_calc_value(clk_rate, period_ns - duty_ns);
+
+ /* We MUST enable yet stop the associated dual-mode timer before
+ * attempting to write its registers.
+ */
+
+ omap_dm_timer_enable(pwm->dm_timer);
+ omap_dm_timer_stop(pwm->dm_timer);
+
+ omap_dm_timer_set_load(pwm->dm_timer, autoreload, load_value);
+ omap_dm_timer_set_match(pwm->dm_timer, enable, match_value);
+
+ DEV_DBG(&pwm->pdev->dev,
+ "load value: %#08x (%d), "
+ "match value: %#08x (%d)\n",
+ load_value, load_value,
+ match_value, match_value);
+
+ omap_dm_timer_set_pwm(pwm->dm_timer,
+ !pwm->config.polarity,
+ toggle,
+ trigger);
+
+ /* Set the counter to generate an overflow event immediately. */
+
+ omap_dm_timer_write_counter(pwm->dm_timer, DM_TIMER_LOAD_MIN);
+
+ /* Now that we're done configuring the dual-mode timer, disable it
+ * again. We'll enable and start it later, when requested.
+ */
+
+ omap_dm_timer_disable(pwm->dm_timer);
+
+ return status;
+}
+EXPORT_SYMBOL(pwm_config);
+
+/**
+ * pwm_enable - enable the generic PWM device.
+ * @pwm: A pointer to the generic PWM device to enable.
+ *
+ * Returns 0 if the generic PWM device was successfully enabled;
+ * otherwise, < 0 on error.
+ */
+int pwm_enable(struct pwm_device *pwm)
+{
+ int status = 0;
+
+ /* Enable the counter--always--before attempting to write its
+ * registers and then set the timer to its minimum load value to
+ * ensure we get an overflow event right away once we start it.
+ */
+
+ omap_dm_timer_enable(pwm->dm_timer);
+ omap_dm_timer_write_counter(pwm->dm_timer, DM_TIMER_LOAD_MIN);
+ omap_dm_timer_start(pwm->dm_timer);
+
+ return status;
+}
+EXPORT_SYMBOL(pwm_enable);
+
+/**
+ * pwm_disable - disable the generic PWM device.
+ * @pwm: A pointer to the generic PWM device to disable.
+ */
+void pwm_disable(struct pwm_device *pwm)
+{
+ omap_dm_timer_enable(pwm->dm_timer);
+ omap_dm_timer_stop(pwm->dm_timer);
+ omap_dm_timer_disable(pwm->dm_timer);
+}
+EXPORT_SYMBOL(pwm_disable);
+
+/**
+ * omap_pwm_probe - check for the PWM and bind it to the driver.
+ * @pdev: A pointer to the platform device node associated with the
+ * PWM instance to be probed for driver binding.
+ *
+ * Returns 0 if the PWM instance was successfully bound to the driver;
+ * otherwise, < 0 on error.
+ */
+static int __devinit omap_pwm_probe(struct platform_device *pdev)
+{
+ struct pwm_device *pwm = NULL;
+ struct omap2_pwm_platform_config *pdata = NULL;
+ int status = 0;
+
+ pdata = ((struct omap2_pwm_platform_config *)(pdev->dev.platform_data));
+
+ BUG_ON(pdata == NULL);
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "Could not find required platform data.\n");
+ status = -ENOENT;
+ goto done;
+ }
+
+ /* Allocate memory for the driver-private PWM data and state */
+
+ pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
+
+ if (pwm == NULL) {
+ dev_err(&pdev->dev, "Could not allocate memory.\n");
+ status = -ENOMEM;
+ goto done;
+ }
+
+ /* Request the OMAP dual-mode timer that will be bound to and
+ * associated with this generic PWM.
+ */
+
+ pwm->dm_timer = omap_dm_timer_request_specific(pdata->timer_id);
+
+ if (pwm->dm_timer == NULL) {
+ status = -ENOENT;
+ goto err_free;
+ }
+
+ /* Configure the source for the dual-mode timer backing this
+ * generic PWM device. The clock source will ultimately determine
+ * how small or large the PWM frequency can be.
+ *
+ * At some point, it's probably worth revisiting moving this to
+ * the configure method and choosing either the slow- or
+ * system-clock source as appropriate for the desired PWM period.
+ */
+
+ omap_dm_timer_set_source(pwm->dm_timer, OMAP_TIMER_SRC_SYS_CLK);
+
+ /* Cache away other miscellaneous driver-private data and state
+ * information and add the driver-private data to the platform
+ * device.
+ */
+
+ pwm->pdev = pdev;
+ pwm->pwm_id = pdev->id;
+ pwm->config = *pdata;
+
+ platform_set_drvdata(pdev, pwm);
+
+ /* Finally, push the added generic PWM device to the end of the
+ * list of available generic PWM devices.
+ */
+
+ mutex_lock(&pwm_lock);
+ list_add_tail(&pwm->head, &pwm_list);
+ mutex_unlock(&pwm_lock);
+
+ status = 0;
+ goto done;
+
+ err_free:
+ kfree(pwm);
+
+ done:
+ return status;
+}
+
+/**
+ * omap_pwm_remove - unbind the specified PWM platform device from the driver.
+ * @pdev: A pointer to the platform device node associated with the
+ * PWM instance to be unbound/removed.
+ *
+ * Returns 0 if the PWM was successfully removed as a platform device;
+ * otherwise, < 0 on error.
+ */
+static int __devexit omap_pwm_remove(struct platform_device *pdev)
+{
+ struct pwm_device *pwm = NULL;
+ int status = 0;
+
+ /* Attempt to get the driver-private data from the platform device
+ * node.
+ */
+
+ pwm = platform_get_drvdata(pdev);
+
+ if (pwm == NULL) {
+ status = -ENODEV;
+ goto done;
+ }
+
+ /* Remove the generic PWM device from the list of available
+ * generic PWM devices.
+ */
+
+ mutex_lock(&pwm_lock);
+ list_del(&pwm->head);
+ mutex_unlock(&pwm_lock);
+
+ /* Unbind the OMAP dual-mode timer associated with the generic PWM
+ * device.
+ */
+
+ omap_dm_timer_free(pwm->dm_timer);
+
+ /* Finally, release the memory associated with the driver-private
+ * data and state.
+ */
+
+ kfree(pwm);
+
+ done:
+ return status;
+}
+
+/**
+ * omap_pwm_init - driver/module insertion entry point
+ *
+ * This routine is the driver/module insertion entry point. It
+ * registers the driver as a platform driver.
+ *
+ * Returns 0 if the driver/module was successfully registered as a
+ * platform driver driver; otherwise, < 0 on error.
+ */
+static int __init omap_pwm_init(void)
+{
+ return platform_driver_register(&omap_pwm_driver);
+}
+
+/**
+ * omap_pwm_exit - driver/module removal entry point
+ *
+ * This routine is the driver/module removal entry point. It
+ * unregisters the driver as a platform driver.
+ */
+static void __exit omap_pwm_exit(void)
+{
+ platform_driver_unregister(&omap_pwm_driver);
+}
+
+arch_initcall(omap_pwm_init);
+module_exit(omap_pwm_exit);
+
+MODULE_AUTHOR("Grant Erickson <marathon96@gmail.com>");
+MODULE_LICENSE("GPLv2");
+MODULE_VERSION("2010-11-09");