linux-gpio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Andrew Lunn <andrew@lunn.ch>
To: linus.walleij@linaro.org
Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>,
	kaloz@openwrt.org,
	Gregory Clement <gregory.clement@free-electrons.com>,
	Sebastian Hesselbarth <sebastian.hesselbarth@googlemail.com>,
	linux-gpio@vger.kernel.org, linux-pwm@vger.kernel.org,
	Andrew Lunn <andrew@lunn.ch>
Subject: [PATCH 3/7] gpio: mvebu: Add limited PWM support
Date: Sat, 10 Jan 2015 00:34:49 +0100	[thread overview]
Message-ID: <1420846493-31647-4-git-send-email-andrew@lunn.ch> (raw)
In-Reply-To: <1420846493-31647-1-git-send-email-andrew@lunn.ch>

Armada 370/XP devices can 'blink' gpio lines with a configurable on
and off period. This can be modelled as a PWM.

However, there are only two sets of PWM configuration registers for
all the gpio lines. This driver simply allows a single gpio line per
gpio chip of 32 lines to be used as a PWM. Attempts to use more return
EBUSY.

Due to the interleaving of registers it is not simple to separate the
PWM driver from the gpio driver. Thus the gpio driver has been
extended with a PWM driver.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/gpio/Kconfig          |   5 ++
 drivers/gpio/Makefile         |   1 +
 drivers/gpio/gpio-mvebu-pwm.c | 202 ++++++++++++++++++++++++++++++++++++++++++
 drivers/gpio/gpio-mvebu.c     |  37 +++-----
 drivers/gpio/gpio-mvebu.h     |  79 +++++++++++++++++
 5 files changed, 299 insertions(+), 25 deletions(-)
 create mode 100644 drivers/gpio/gpio-mvebu-pwm.c
 create mode 100644 drivers/gpio/gpio-mvebu.h

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 633ec216e185..be98d531d735 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -239,6 +239,11 @@ config GPIO_MVEBU
 	select GPIO_GENERIC
 	select GENERIC_IRQ_CHIP
 
+config GPIO_MVEBU_PWM
+	def_bool y
+	depends on GPIO_MVEBU
+	depends on PWM
+
 config GPIO_MXC
 	def_bool y
 	depends on ARCH_MXC
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 81755f1305e6..be94f47c1ddb 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_GPIO_MSIC)		+= gpio-msic.o
 obj-$(CONFIG_GPIO_MSM_V1)	+= gpio-msm-v1.o
 obj-$(CONFIG_GPIO_MSM_V2)	+= gpio-msm-v2.o
 obj-$(CONFIG_GPIO_MVEBU)        += gpio-mvebu.o
+obj-$(CONFIG_GPIO_MVEBU_PWM)	+= gpio-mvebu-pwm.o
 obj-$(CONFIG_GPIO_MXC)		+= gpio-mxc.o
 obj-$(CONFIG_GPIO_MXS)		+= gpio-mxs.o
 obj-$(CONFIG_GPIO_OCTEON)	+= gpio-octeon.o
diff --git a/drivers/gpio/gpio-mvebu-pwm.c b/drivers/gpio/gpio-mvebu-pwm.c
new file mode 100644
index 000000000000..8a5af11aed67
--- /dev/null
+++ b/drivers/gpio/gpio-mvebu-pwm.c
@@ -0,0 +1,202 @@
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/pwm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include "gpio-mvebu.h"
+#include "gpiolib.h"
+
+static void __iomem *mvebu_gpioreg_blink_select(struct mvebu_gpio_chip *mvchip)
+{
+	return mvchip->membase + GPIO_BLINK_CNT_SELECT;
+}
+
+static inline struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip)
+{
+	return container_of(chip, struct mvebu_pwm, chip);
+}
+
+static inline struct mvebu_gpio_chip *to_mvchip(struct mvebu_pwm *pwm)
+{
+	return container_of(pwm, struct mvebu_gpio_chip, pwm);
+}
+
+static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwmd)
+{
+	struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
+	struct gpio_desc *desc = gpio_to_desc(pwmd->pwm);
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&pwm->lock, flags);
+	if (pwm->used) {
+		ret = -EBUSY;
+	} else {
+		if (!desc) {
+			ret = -ENODEV;
+			goto out;
+		}
+		ret = gpiod_request(desc, "mvebu-pwm");
+		if (ret)
+			goto out;
+
+		ret = gpiod_direction_output(desc, 0);
+		if (ret) {
+			gpiod_free(desc);
+			goto out;
+		}
+
+		pwm->pin = pwmd->pwm - mvchip->chip.base;
+		pwm->used = true;
+	}
+
+out:
+	spin_unlock_irqrestore(&pwm->lock, flags);
+	return ret;
+}
+
+static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwmd)
+{
+	struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
+	struct gpio_desc *desc = gpio_to_desc(pwmd->pwm);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pwm->lock, flags);
+	gpiod_free(desc);
+	pwm->used = false;
+	spin_unlock_irqrestore(&pwm->lock, flags);
+}
+
+static int mvebu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmd,
+			    int duty_ns, int period_ns)
+{
+	struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
+	unsigned int on, off;
+	unsigned long long val;
+	u32 u;
+
+	val = (unsigned long long) pwm->clk_rate * duty_ns;
+	do_div(val, NSEC_PER_SEC);
+	if (val > UINT_MAX)
+		return -EINVAL;
+	if (val)
+		on = val;
+	else
+		on = 1;
+
+	val = (unsigned long long) pwm->clk_rate * (period_ns - duty_ns);
+	do_div(val, NSEC_PER_SEC);
+	if (val > UINT_MAX)
+		return -EINVAL;
+	if (val)
+		off = val;
+	else
+		off = 1;
+
+	u = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
+	u &= ~(1 << pwm->pin);
+	u |= (pwm->id << pwm->pin);
+	writel_relaxed(u, mvebu_gpioreg_blink_select(mvchip));
+
+	writel_relaxed(on, pwm->membase + BLINK_ON_DURATION);
+	writel_relaxed(off, pwm->membase + BLINK_OFF_DURATION);
+
+	return 0;
+}
+
+static int mvebu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwmd)
+{
+	struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
+
+	mvebu_gpio_blink(&mvchip->chip, pwm->pin, 1);
+
+	return 0;
+}
+
+static void mvebu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwmd)
+{
+	struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
+
+	mvebu_gpio_blink(&mvchip->chip, pwm->pin, 0);
+}
+
+static const struct pwm_ops mvebu_pwm_ops = {
+	.request = mvebu_pwm_request,
+	.free = mvebu_pwm_free,
+	.config = mvebu_pwm_config,
+	.enable = mvebu_pwm_enable,
+	.disable = mvebu_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
+{
+	struct mvebu_pwm *pwm = &mvchip->pwm;
+
+	pwm->blink_select = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
+	pwm->blink_on_duration =
+		readl_relaxed(pwm->membase + BLINK_ON_DURATION);
+	pwm->blink_off_duration =
+		readl_relaxed(pwm->membase + BLINK_OFF_DURATION);
+}
+
+void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
+{
+	struct mvebu_pwm *pwm = &mvchip->pwm;
+
+	writel_relaxed(pwm->blink_select, mvebu_gpioreg_blink_select(mvchip));
+	writel_relaxed(pwm->blink_on_duration,
+		       pwm->membase + BLINK_ON_DURATION);
+	writel_relaxed(pwm->blink_off_duration,
+		       pwm->membase + BLINK_OFF_DURATION);
+}
+
+/*
+ * Armada 370/XP has simple PWM support for gpio lines. Other SoCs
+ * don't have this hardware. So if we don't have the necessary
+ * resource, it is not an error.
+ */
+int mvebu_pwm_probe(struct platform_device *pdev,
+		    struct mvebu_gpio_chip *mvchip,
+		    int id)
+{
+	struct device *dev = &pdev->dev;
+	struct mvebu_pwm *pwm = &mvchip->pwm;
+	struct resource *res;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
+	if (!res)
+		return 0;
+
+	mvchip->pwm.membase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(mvchip->pwm.membase))
+		return PTR_ERR(mvchip->percpu_membase);
+
+	if (id < 0 || id > 1)
+		return -EINVAL;
+	pwm->id = id;
+
+	if (IS_ERR(mvchip->clk))
+		return PTR_ERR(mvchip->clk);
+
+	pwm->clk_rate = clk_get_rate(mvchip->clk);
+	if (!pwm->clk_rate) {
+		dev_err(dev, "failed to get clock rate\n");
+		return -EINVAL;
+	}
+
+	pwm->chip.dev = dev;
+	pwm->chip.ops = &mvebu_pwm_ops;
+	pwm->chip.base = mvchip->chip.base;
+	pwm->chip.npwm = mvchip->chip.ngpio;
+	pwm->chip.can_sleep = false;
+
+	spin_lock_init(&pwm->lock);
+
+	return pwmchip_add(&pwm->chip);
+}
diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c
index d0bc123c7975..147e76cb57e4 100644
--- a/drivers/gpio/gpio-mvebu.c
+++ b/drivers/gpio/gpio-mvebu.c
@@ -42,10 +42,11 @@
 #include <linux/io.h>
 #include <linux/of_irq.h>
 #include <linux/of_device.h>
+#include <linux/pwm.h>
 #include <linux/clk.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/irqchip/chained_irq.h>
-
+#include "gpio-mvebu.h"
 /*
  * GPIO unit register offsets.
  */
@@ -75,24 +76,6 @@
 
 #define MVEBU_MAX_GPIO_PER_BANK		32
 
-struct mvebu_gpio_chip {
-	struct gpio_chip   chip;
-	spinlock_t	   lock;
-	void __iomem	  *membase;
-	void __iomem	  *percpu_membase;
-	int		   irqbase;
-	struct irq_domain *domain;
-	int		   soc_variant;
-
-	/* Used to preserve GPIO registers across suspend/resume */
-	u32                out_reg;
-	u32                io_conf_reg;
-	u32                blink_en_reg;
-	u32                in_pol_reg;
-	u32                edge_mask_regs[4];
-	u32                level_mask_regs[4];
-};
-
 /*
  * Functions returning addresses of individual registers for a given
  * GPIO controller.
@@ -228,7 +211,7 @@ static int mvebu_gpio_get(struct gpio_chip *chip, unsigned pin)
 	return (u >> pin) & 1;
 }
 
-static void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value)
+void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value)
 {
 	struct mvebu_gpio_chip *mvchip =
 		container_of(chip, struct mvebu_gpio_chip, chip);
@@ -609,6 +592,8 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
 		BUG();
 	}
 
+	mvebu_pwm_suspend(mvchip);
+
 	return 0;
 }
 
@@ -652,6 +637,8 @@ static int mvebu_gpio_resume(struct platform_device *pdev)
 		BUG();
 	}
 
+	mvebu_pwm_resume(mvchip);
+
 	return 0;
 }
 
@@ -663,7 +650,6 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 	struct resource *res;
 	struct irq_chip_generic *gc;
 	struct irq_chip_type *ct;
-	struct clk *clk;
 	unsigned int ngpios;
 	int soc_variant;
 	int i, cpu, id;
@@ -693,10 +679,10 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 		return id;
 	}
 
-	clk = devm_clk_get(&pdev->dev, NULL);
+	mvchip->clk = devm_clk_get(&pdev->dev, NULL);
 	/* Not all SoCs require a clock.*/
-	if (!IS_ERR(clk))
-		clk_prepare_enable(clk);
+	if (!IS_ERR(mvchip->clk))
+		clk_prepare_enable(mvchip->clk);
 
 	mvchip->soc_variant = soc_variant;
 	mvchip->chip.label = dev_name(&pdev->dev);
@@ -830,7 +816,8 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 		goto err_generic_chip;
 	}
 
-	return 0;
+	/* Armada 370/XP has simple PWM support for gpio lines */
+	return mvebu_pwm_probe(pdev, mvchip, id);
 
 err_generic_chip:
 	irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
diff --git a/drivers/gpio/gpio-mvebu.h b/drivers/gpio/gpio-mvebu.h
new file mode 100644
index 000000000000..7cd8c80c06da
--- /dev/null
+++ b/drivers/gpio/gpio-mvebu.h
@@ -0,0 +1,79 @@
+/*
+ * Interface between MVEBU GPIO driver and PWM driver for GPIO pins
+ *
+ * Copyright (C) 2015, Andrew Lunn <andrew@lunn.ch>
+ *
+ * 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.
+ */
+
+#ifndef MVEBU_GPIO_PWM_H
+#define MVEBU_GPIO_PWM_H
+
+#define BLINK_ON_DURATION	0x0
+#define BLINK_OFF_DURATION	0x4
+#define GPIO_BLINK_CNT_SELECT	0x0020
+
+struct mvebu_pwm {
+	void __iomem	*membase;
+	unsigned long	 clk_rate;
+	bool		 used;
+	unsigned	 pin;
+	struct pwm_chip	 chip;
+	int		 id;
+	spinlock_t	 lock;
+
+	/* Used to preserve GPIO/PWM registers across suspend /
+	 * resume */
+	u32		 blink_select;
+	u32		 blink_on_duration;
+	u32		 blink_off_duration;
+};
+
+struct mvebu_gpio_chip {
+	struct gpio_chip   chip;
+	spinlock_t	   lock;
+	void __iomem	  *membase;
+	void __iomem	  *percpu_membase;
+	int		   irqbase;
+	struct irq_domain *domain;
+	int		   soc_variant;
+	struct clk	  *clk;
+#ifdef CONFIG_PWM
+	struct mvebu_pwm pwm;
+#endif
+	/* Used to preserve GPIO registers across suspend/resume */
+	u32		   out_reg;
+	u32		   io_conf_reg;
+	u32		   blink_en_reg;
+	u32		   in_pol_reg;
+	u32		   edge_mask_regs[4];
+	u32		   level_mask_regs[4];
+};
+
+void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value);
+
+#ifdef CONFIG_PWM
+int mvebu_pwm_probe(struct platform_device *pdev,
+		    struct mvebu_gpio_chip *mvchip,
+		    int id);
+void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip);
+void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip);
+#else
+int mvebu_pwm_probe(struct platform_device *pdev,
+		    struct mvebu_gpio_chip *mvchip,
+		    int id)
+{
+	return 0;
+}
+
+void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
+{
+}
+
+void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
+{
+}
+#endif
+#endif
-- 
2.1.3


  parent reply	other threads:[~2015-01-09 23:34 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-01-09 23:34 [PATCH 0/7] Add PWM support to mvebu gpio driver Andrew Lunn
2015-01-09 23:34 ` [PATCH 1/7] gpio: mvebu: checkpatch fixes Andrew Lunn
2015-01-14 13:04   ` Linus Walleij
2015-01-09 23:34 ` [PATCH 2/7] gpio: mvebu: Fix probe cleanup on error Andrew Lunn
2015-01-14 13:05   ` Linus Walleij
2015-01-09 23:34 ` Andrew Lunn [this message]
2015-01-12 11:05   ` [PATCH 3/7] gpio: mvebu: Add limited PWM support Imre Kaloz
2015-01-12 13:12     ` Andrew Lunn
2015-01-12 14:06   ` Linus Walleij
2015-01-12 15:07     ` Andrew Lunn
2015-01-13  2:42     ` Andrew Lunn
2015-01-15  9:52       ` Linus Walleij
2015-01-17 20:04         ` Andrew Lunn
2015-01-18 10:04         ` Lee Jones
2015-01-19 12:59           ` Thierry Reding
2015-01-20 10:52             ` Lee Jones
2015-01-19 13:01         ` Thierry Reding
2015-06-12 10:38   ` Thierry Reding
2015-01-09 23:34 ` [PATCH 4/7] DT: bindings: Extend mvebu gpio documentation with PWM Andrew Lunn
2015-01-09 23:34 ` [PATCH 5/7] mvebu: xp: Add pwm properties to .dtsi files Andrew Lunn
2015-01-09 23:34 ` [PATCH 6/7] arm: mvebu: Enable PWM in defconfig Andrew Lunn
2015-01-09 23:34 ` [PATCH 7/7] mvebu: wrt1900ac: Use pwm-fan rather than gpio-fan Andrew Lunn
2015-02-23 14:09 ` [PATCH 0/7] Add PWM support to mvebu gpio driver Gregory CLEMENT
2015-02-23 15:48   ` Andrew Lunn

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=1420846493-31647-4-git-send-email-andrew@lunn.ch \
    --to=andrew@lunn.ch \
    --cc=gregory.clement@free-electrons.com \
    --cc=kaloz@openwrt.org \
    --cc=linus.walleij@linaro.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=sebastian.hesselbarth@googlemail.com \
    --cc=thomas.petazzoni@free-electrons.com \
    /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).