diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 9 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-omap.c | 249 |
3 files changed, 259 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 115b644..7cdb0b0 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -97,6 +97,15 @@ config PWM_MXS To compile this driver as a module, choose M here: the module will be called pwm-mxs. +config PWM_OMAP + tristate "OMAP PWM support" + depends on ARCH_OMAP && OMAP_DM_TIMER + help + Generic PWM framework driver for OMAP + + To compile this driver as a module, choose M here: the module + will be called pwm-omap + config PWM_PUV3 tristate "PKUnity NetBook-0916 PWM support" depends on ARCH_PUV3 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 94ba21e..6944024 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -6,6 +6,7 @@ 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_OMAP) += pwm-omap.o obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o diff --git a/drivers/pwm/pwm-omap.c b/drivers/pwm/pwm-omap.c new file mode 100644 index 0000000..bc53612 --- /dev/null +++ b/drivers/pwm/pwm-omap.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2012 NeilBrown <neilb@suse.de> + * Heavily based on earlier code which is: + * Copyright (c) 2010 Grant Erickson <marathon96@gmail.com> + * + * Also based on pwm-samsung.c + * + * 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 OMAP support for the generic, Linux + * PWM driver / controller, using the OMAP's dual-mode timers. + * + */ + +#include <linux/export.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 <linux/module.h> +#include <linux/platform_data/omap-pwm.h> + +#include <plat/dmtimer.h> + +#define DM_TIMER_LOAD_MIN 0xFFFFFFFE + +struct omap_chip { + struct omap_dm_timer *dm_timer; + enum pwm_polarity polarity; + unsigned int duty_ns, period_ns; + struct pwm_chip chip; +}; + +#define to_omap_chip(chip) container_of(chip, struct omap_chip, chip) + +/** + * pwm_calc_value - Determine 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 compute 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) +{ + u64 c; + + c = (u64)clk_rate * ns; + do_div(c, NSEC_PER_SEC); + + return DM_TIMER_LOAD_MIN - c; +} + +static int omap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct omap_chip *omap = to_omap_chip(chip); + + omap_dm_timer_start(omap->dm_timer); + + return 0; +} + +static void omap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct omap_chip *omap = to_omap_chip(chip); + + omap_dm_timer_stop(omap->dm_timer); +} + +static int omap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct omap_chip *omap = to_omap_chip(chip); + int load_value, match_value; + unsigned long clk_rate; + + dev_dbg(chip->dev, "duty cycle: %d, period %d\n", duty_ns, period_ns); + + if (omap->duty_ns == duty_ns && + omap->period_ns == period_ns) + /* No change - don't cause any transients. */ + return 0; + + clk_rate = clk_get_rate(omap_dm_timer_get_fclk(omap->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. Hopefully it is already + * disabled, but call the (idempotent) pwm_disable just in case. + */ + + pwm_disable(pwm); + + omap_dm_timer_set_load(omap->dm_timer, true, load_value); + omap_dm_timer_set_match(omap->dm_timer, true, match_value); + + dev_dbg(chip->dev, "load value: %#08x (%d), match value: %#08x (%d)\n", + load_value, load_value, match_value, match_value); + + omap_dm_timer_set_pwm(omap->dm_timer, + omap->polarity == PWM_POLARITY_INVERSED, + true, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + + omap->duty_ns = duty_ns; + omap->period_ns = period_ns; + + return 0; +} + +static int omap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct omap_chip *omap = to_omap_chip(chip); + + if (omap->polarity == polarity) + return 0; + + omap->polarity = polarity; + + omap_dm_timer_set_pwm(omap->dm_timer, + omap->polarity == PWM_POLARITY_INVERSED, + true, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + return 0; +} + +static struct pwm_ops omap_pwm_ops = { + .enable = omap_pwm_enable, + .disable = omap_pwm_disable, + .config = omap_pwm_config, + .set_polarity = omap_pwm_set_polarity, + .owner = THIS_MODULE, +}; + +static int omap_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct omap_chip *omap; + int status = 0; + struct omap_pwm_pdata *pdata = dev->platform_data; + + if (!pdata) { + dev_err(dev, "No platform data provided\n"); + return -ENODEV; + } + + omap = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); + if (omap == NULL) { + dev_err(dev, "Could not allocate memory.\n"); + return -ENOMEM; + } + + /* + * Request the OMAP dual-mode timer that will be bound to and + * associated with this generic PWM. + */ + + omap->dm_timer = omap_dm_timer_request_specific(pdata->timer_id); + if (omap->dm_timer == NULL) { + status = -EPROBE_DEFER; + 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(omap->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. + */ + + omap->chip.dev = dev; + omap->chip.ops = &omap_pwm_ops; + omap->chip.base = -1; + omap->chip.npwm = 1; + omap->polarity = PWM_POLARITY_NORMAL; + + status = pwmchip_add(&omap->chip); + if (status < 0) { + dev_err(dev, "failed to register PWM\n"); + omap_dm_timer_free(omap->dm_timer); + goto err_free; + } + + platform_set_drvdata(pdev, omap); + + return 0; + + err_free: + kfree(omap); + return status; +} + +static int omap_pwm_remove(struct platform_device *pdev) +{ + struct omap_chip *omap = platform_get_drvdata(pdev); + int status; + + omap_dm_timer_stop(omap->dm_timer); + status = pwmchip_remove(&omap->chip); + if (status < 0) + return status; + + omap_dm_timer_free(omap->dm_timer); + kfree(omap); + + return 0; +} +static struct platform_driver omap_pwm_driver = { + .driver = { + .name = "omap-pwm", + .owner = THIS_MODULE, + }, + .probe = omap_pwm_probe, + .remove = omap_pwm_remove, +}; +module_platform_driver(omap_pwm_driver); + +MODULE_AUTHOR("Grant Erickson <marathon96@gmail.com>"); +MODULE_AUTHOR("NeilBrown <neilb@suse.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("2012-12-01"); +MODULE_DESCRIPTION("OMAP PWM Driver using Dual-mode Timers"); |