[PWM 07/10] PWM API driver for MPC52xx GPT peripheral

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Bill Gatliff <bgat@xxxxxxxxxxxxxxx>
---
 arch/powerpc/platforms/52xx/mpc52xx_gpt.c |  196 ++++++++++++++++++++++++++++-
 1 files changed, 194 insertions(+), 2 deletions(-)

diff --git a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
index fea833e..76f8c40 100644
--- a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
+++ b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
@@ -1,6 +1,7 @@
 /*
  * MPC5200 General Purpose Timer device driver
  *
+ * Copyright (c) 2010 Bill Gatliff <bgat@xxxxxxxxxxxxxxx>
  * Copyright (c) 2009 Secret Lab Technologies Ltd.
  * Copyright (c) 2008 Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>, Pengutronix
  *
@@ -50,6 +51,12 @@
  * IO, or it can be an Open Collector (OC) output.  At the moment it is the
  * responsibility of either the bootloader or the platform setup code to set
  * the output mode.  This driver does not change the output mode setting.
+ *
+ * To use the PWM function, the following property must be added to
+ * the device tree node for the gpt device:
+ *     pwm-controller;
+ * TODO: we could set polarity, initial period and duty cycle, on/off,
+ * and whatnot inside the dts file...
  */
 
 #include <linux/device.h>
@@ -66,11 +73,12 @@
 #include <linux/watchdog.h>
 #include <linux/miscdevice.h>
 #include <linux/uaccess.h>
+#include <linux/pwm/pwm.h>
 #include <asm/div64.h>
 #include <asm/mpc52xx.h>
 
 MODULE_DESCRIPTION("Freescale MPC52xx gpt driver");
-MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht DreÃ?");
+MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht DreÃ?, Bill Gatliff");
 MODULE_LICENSE("GPL");
 
 /**
@@ -96,6 +104,9 @@ struct mpc52xx_gpt_priv {
 #if defined(CONFIG_GPIOLIB)
 	struct gpio_chip gc;
 #endif
+#if defined(CONFIG_GENERIC_PWM)
+	struct pwm_device pwm;
+#endif
 };
 
 LIST_HEAD(mpc52xx_gpt_list);
@@ -126,6 +137,10 @@ DEFINE_MUTEX(mpc52xx_gpt_list_mutex);
 
 #define MPC52xx_GPT_STATUS_IRQMASK	(0x000f)
 
+#define MPC52xx_GPT_PWM_WIDTH_MASK	(0xffff0000)
+#define MPC52xx_GPT_PWM_PWMOP		(0x100)
+#define MPC52xx_GPT_PWM_LOAD		(0x1)
+
 #define MPC52xx_GPT_CAN_WDT		(1 << 0)
 #define MPC52xx_GPT_IS_WDT		(1 << 1)
 
@@ -275,6 +290,183 @@ mpc52xx_gpt_irq_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
 
 
 /* ---------------------------------------------------------------------
+ * PWM API hooks
+ */
+#if defined(CONFIG_GENERIC_PWM)
+static inline struct mpc52xx_gpt_priv *pwm_to_mpc52xx_gpt(const struct pwm_channel *p)
+{
+	return container_of(p->pwm, struct mpc52xx_gpt_priv, pwm);
+}
+
+static int mpc52xx_gpt_pwm_request(struct pwm_channel *p)
+{
+	struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+
+	/* TODO: add hooks to prevent conflicts in use */
+	p->tick_hz = gpt->ipb_freq;
+	return 0;
+}
+
+static void mpc52xx_gpt_pwm_release(struct pwm_channel *p)
+{
+	/* TODO: add hooks to prevent conflicts in use */
+}
+
+static int __mpc52xx_gpt_pwm_config_period_ticks(struct pwm_channel *p,
+					       struct pwm_channel_config *c)
+{
+	struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+	u64 prescale, count;
+
+	prescale = (c->period_ticks >> 16) + 1;
+	count = c->period_ticks;
+	do_div(count, prescale);
+	out_be32(&gpt->regs->count, prescale << 16 | count);
+
+	p->period_ticks = count * prescale;
+	dev_dbg(p->pwm->dev, "prescale %4x count %4x period_ticks %8x\n",
+		(unsigned int)prescale, (unsigned int)count,
+		(unsigned int)p->period_ticks);
+
+	return 0;
+}
+
+static int __mpc52xx_gpt_pwm_config_duty_ticks(struct pwm_channel *p,
+					       struct pwm_channel_config *c)
+{
+	struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+	unsigned long flags;
+	u64 width = c->duty_ticks;
+	u32 prescale, count;
+
+	spin_lock_irqsave(&gpt->lock, flags);
+
+	count = in_be32(&gpt->regs->count);
+	prescale = count >> 16;
+	count &= 0xffffUL;
+
+	/* TODO: this probably isn't the best place to do a divide... */
+	do_div(width, prescale);
+
+	clrsetbits_be32(&gpt->regs->pwm, MPC52xx_GPT_PWM_WIDTH_MASK,
+			MPC52xx_GPT_PWM_WIDTH_MASK & (width << 16));
+	spin_unlock_irqrestore(&gpt->lock, flags);
+
+	p->duty_ticks = width * prescale;
+	dev_dbg(p->pwm->dev, "prescale %4x count %4x width %4x duty_ticks %8x\n",
+		(unsigned int)prescale, (unsigned int)count,
+		(unsigned int)width, (unsigned int)p->duty_ticks);
+
+	return 0;
+}
+
+static int __mpc52xx_gpt_pwm_config_polarity(struct pwm_channel *p,
+					     struct pwm_channel_config *c)
+{
+	struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpt->lock, flags);
+	if (c->polarity)
+		setbits32(&gpt->regs->pwm, MPC52xx_GPT_PWM_PWMOP);
+	else
+		clrbits32(&gpt->regs->pwm, MPC52xx_GPT_PWM_PWMOP);
+	p->active_high = c->polarity ? 1 : 0;
+	spin_unlock_irqrestore(&gpt->lock, flags);
+	return 0;
+}
+
+static int __mpc52xx_gpt_pwm_start(struct pwm_channel *p)
+{
+	struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpt->lock, flags);
+	clrsetbits_be32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK,
+		     MPC52xx_GPT_MODE_MS_PWM);
+	spin_unlock_irqrestore(&gpt->lock, flags);
+	return 0;
+}
+
+static int __mpc52xx_gpt_pwm_stop(struct pwm_channel *p)
+{
+	struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpt->lock, flags);
+	clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK);
+	spin_unlock_irqrestore(&gpt->lock, flags);
+	return 0;
+}
+
+static int mpc52xx_gpt_pwm_config_nosleep(struct pwm_channel *p,
+					  struct pwm_channel_config *c)
+{
+	int ret = -EINVAL;
+
+	if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+		if ((ret = __mpc52xx_gpt_pwm_config_period_ticks(p, c)))
+			goto done;
+
+	if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+		if ((ret = __mpc52xx_gpt_pwm_config_duty_ticks(p, c)))
+			goto done;
+
+	if (c->config_mask & PWM_CONFIG_POLARITY)
+		if ((ret = __mpc52xx_gpt_pwm_config_polarity(p, c)))
+			goto done;
+
+	if (c->config_mask & PWM_CONFIG_START)
+		if ((ret = __mpc52xx_gpt_pwm_start(p)))
+			goto done;
+
+	if (c->config_mask & PWM_CONFIG_STOP)
+		if ((ret = __mpc52xx_gpt_pwm_stop(p)))
+			goto done;
+
+done:
+	return ret;
+}
+
+static int mpc52xx_gpt_pwm_config(struct pwm_channel *p,
+				  struct pwm_channel_config *c)
+{
+	dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask);
+
+	if (!mpc52xx_gpt_pwm_config_nosleep(p, c))
+		return 0;
+
+	might_sleep();
+
+	/* TODO: add other API entry points */
+
+	return -EINVAL;
+}
+
+static void
+mpc52xx_gpt_pwm_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
+{
+	if (!of_find_property(node, "pwm-controller", NULL))
+		return;
+
+	gpt->pwm.dev = gpt->dev;
+	gpt->pwm.bus_id = dev_name(gpt->dev);
+	gpt->pwm.nchan = 1;
+	gpt->pwm.owner = THIS_MODULE;
+
+	gpt->pwm.request = mpc52xx_gpt_pwm_request;
+	gpt->pwm.release = mpc52xx_gpt_pwm_release;
+	gpt->pwm.config_nosleep = mpc52xx_gpt_pwm_config_nosleep;
+	gpt->pwm.config = mpc52xx_gpt_pwm_config;
+
+	pwm_register(&gpt->pwm);
+}
+#else
+static void
+mpc52xx_gpt_pwm_setup(struct mpc52xx_gpt_priv *p, struct device_node *np) {}
+#endif
+
+/* ---------------------------------------------------------------------
  * GPIOLIB hooks
  */
 #if defined(CONFIG_GPIOLIB)
@@ -739,9 +931,9 @@ static int __devinit mpc52xx_gpt_probe(struct platform_device *ofdev,
 	}
 
 	dev_set_drvdata(&ofdev->dev, gpt);
-
 	mpc52xx_gpt_gpio_setup(gpt, ofdev->dev.of_node);
 	mpc52xx_gpt_irq_setup(gpt, ofdev->dev.of_node);
+	mpc52xx_gpt_pwm_setup(gpt, ofdev->dev.of_node);
 
 	mutex_lock(&mpc52xx_gpt_list_mutex);
 	list_add(&gpt->list, &mpc52xx_gpt_list);
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Gstreamer Embedded]     [Linux MMC Devel]     [U-Boot V2]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux ARM Kernel]     [Linux OMAP]     [Linux SCSI]

  Powered by Linux