All of lore.kernel.org
 help / color / mirror / Atom feed
From: Johannes Thumshirn <morbidrsa@gmail.com>
To: Thierry Reding <thierry.reding@gmail.com>,
	Stephen Warren <swarren@wwwdotorg.org>
Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
	linux-rpi-kernel@lists.infradead.org,
	Johannes Thumshirn <morbidrsa@gmail.com>
Subject: [RFC] PWM: Add support for pwm-bcm2835
Date: Sat, 21 Sep 2013 12:09:02 +0200	[thread overview]
Message-ID: <1379758142-7302-1-git-send-email-morbidrsa@gmail.com> (raw)

Add support for the PWM controller of the BCM2835 SoC found on Raspberry PI

The driver isn't as much tested as I wanted it to be and devicetree
support is still missing, but I thought it would be nice to have some
comments if I'm in the right direction.

Signed-off-by: Johannes Thumshirn <morbidrsa@gmail.com>
---
 drivers/pwm/Kconfig       |   8 ++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-bcm2835.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 284 insertions(+)
 create mode 100644 drivers/pwm/pwm-bcm2835.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 75840b5..5417159 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -224,4 +224,12 @@ config PWM_VT8500
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-vt8500.
 
+config PWM_BCM2835
+       tristate "BCM2835 PWM support"
+       help
+        Generic PWM framework driver for bcm2835 found on the Rasperry PI
+
+	To compile this driver as a module, choose M here: the module will be
+	called pwm-bcm2835
+
 endif
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 77a8c18..0cf8d4d 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_PWM_TIPWMSS)	+= pwm-tipwmss.o
 obj-$(CONFIG_PWM_TWL)		+= pwm-twl.o
 obj-$(CONFIG_PWM_TWL_LED)	+= pwm-twl-led.o
 obj-$(CONFIG_PWM_VT8500)	+= pwm-vt8500.o
+obj-$(CONFIG_PWM_BCM2835)	+= pwm-bcm2835.o
diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c
new file mode 100644
index 0000000..8a64666
--- /dev/null
+++ b/drivers/pwm/pwm-bcm2835.c
@@ -0,0 +1,275 @@
+/*
+ * BCM2835 PWM driver
+ *
+ * Derived from the Tegra PWM driver by NVIDIA Corporation.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+
+#define NPWM 2
+
+/* Address Mappings (offsets) from Broadcom BCM2835 ARM Periperals Manual
+ * Section 9.6 Page 141ff
+ */
+#define BCM2835_PWM_CTL		0x00 /* Control register */
+#define BCM2835_PWM_STA		0x04 /* Status register */
+#define BCM2835_PWM_DMAC	0x08 /* PWM DMA Configuration */
+#define BCM2835_PWM_RNG1	0x10 /* PWM Channel 1 Range */
+#define BCM2835_PWM_DAT1	0x14 /* PWM Channel 1 Data */
+#define BCM2835_PWM_FIF1	0x18 /* PWM FIFO Input */
+#define BCM2835_PWM_RNG2	0x20 /* PWM Channel 2 Range */
+#define BCM2835_PWM_DAT2	0x24 /* PWM Channel 2 Data */
+
+/* Control registers 0 and 1 are mirrored on a distance of 4 bits */
+#define HWPWM(x) ((x) << 4)
+
+/* TODO: We only need this register set once and use HWPWM macro to
+ * access 2nd output
+ */
+/* Control Register Bits */
+#define BCM2835_PWM_CTL_PWEN1	BIT(0)	/* Channel 1 enable (RW) */
+#define BCM2835_PWM_CTL_MODE1	BIT(1)	/* Channel 1 mode (RW) */
+#define BCM2835_PWM_CTL_RPTL1	BIT(2)	/* Channel 1 repeat last data (RW) */
+#define BCM2835_PWM_CTL_SBIT1	BIT(3)	/* Channel 1 silence bit (RW) */
+#define BCM2835_PWM_CTL_POLA1	BIT(4)	/* Channel 1 polarity (RW) */
+#define BCM2835_PWM_CTL_USEF1	BIT(5)	/* Channel 1 use FIFO (RW) */
+#define BCM2835_PWM_CTL_CLRF1	BIT(6)	/* Channel 1 clear FIFO (RO) */
+#define BCM2835_PWM_CTL_MSEN1	BIT(7)	/* Channel 1 M/S enable (RW) */
+#define BCM2835_PWM_CTL_PWEN2	BIT(8)	/* Channel 2 enable (RW) */
+#define BCM2835_PWM_CTL_MODE2	BIT(9)	/* Channel 2 mode (RW) */
+#define BCM2835_PWM_CTL_RPTL2	BIT(10)	/* Channel 2 repeat last data (RW) */
+#define BCM2835_PWM_CTL_SBIT2	BIT(11)	/* Channel 2 silence bit (RW) */
+#define BCM2835_PWM_CTL_POLA2	BIT(12)	/* Channel 2 polarity (RW) */
+#define BCM2835_PWM_CTL_USEF2	BIT(13)	/* Channel 2 use FIFO (RW) */
+/* Bit 14 is reserved */
+#define BCM2835_PWM_MSEN2	BIT(15)	/* Channel 2 M/S enable (RW) */
+/* Bits 16 - 31 are reserved */
+
+/* Status Register Bits */
+#define BCM2835_PWM_STA_FULL1	BIT(0)	/* FIFO full flag (RW) */
+#define BCM2835_PWM_STA_EMPT1	BIT(1)	/* FIFO empty flag (RW) */
+#define BCM2835_PWM_STA_WERR1	BIT(2)	/* FIFO write error flag (RW) */
+#define BCM2835_PWM_STA_RERR1	BIT(3)	/* FIFO read error flag (RW) */
+#define BCM2835_PWM_STA_GAPO1	BIT(4)	/* Channel 1 gap occured (RW) */
+#define BCM2835_PWM_STA_GAPO2	BIT(5)	/* Channel 2 gap occured (RW) */
+#define BCM2835_PWM_STA_GAPO3	BIT(6)	/* Channel 3 gap occured (RW) */
+#define BCM2835_PWM_STA_GAPO4	BIT(7)	/* Channel 4 gap occured (RW) */
+#define BCM2835_PWM_STA_BERR	BIT(8)	/* Bus error flag (RW) */
+#define BCM2835_PWM_STA_STA1	BIT(9)	/* Channel 1 state (RW) */
+#define BCM2835_PWM_STA_STA2	BIT(10)	/* Channel 1 state (RW) */
+#define BCM2835_PWM_STA_STA3	BIT(11)	/* Channel 1 state (RW) */
+#define BCM2835_PWM_STA_STA4	BIT(12)	/* Channel 1 state (RW) */
+/* Bits 13 - 31 are reserved */
+
+struct bcm2835_pwm_dev {
+	struct pwm_chip chip;
+	struct device *dev;
+	struct clk *clk;
+	void __iomem *mmio_base;
+};
+
+static inline struct bcm2835_pwm_dev *to_bcm(struct pwm_chip *chip)
+{
+	return container_of(chip, struct bcm2835_pwm_dev, chip);
+}
+
+static inline u32 bcm2835_readl(struct bcm2835_pwm_dev *chip,
+				unsigned int hwpwm, unsigned int num)
+{
+	num <<= HWPWM(hwpwm);
+	return readl(chip->mmio_base + num);
+}
+
+static inline void bcm2835_writel(struct bcm2835_pwm_dev *chip,
+				  unsigned int hwpwm, unsigned int num,
+				  unsigned long val)
+{
+	num <<= HWPWM(hwpwm);
+	writel(val, chip->mmio_base + num);
+}
+
+static int bcm2835_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+			      int duty_ns, int period_ns)
+{
+	struct bcm2835_pwm_dev *bcm = to_bcm(chip);
+
+	if (WARN_ON(!bcm))
+		return -ENODEV;
+
+	if (duty_ns < 1) {
+		dev_err(bcm->dev, "duty is out of range: %d < 1\n", duty_ns);
+		return -ERANGE;
+	}
+
+	if (period_ns < 1) {
+		dev_err(bcm->dev, "period is out of range: %d < 1\n",
+			period_ns);
+		return -ERANGE;
+	}
+
+	/* disable PWM */
+	bcm2835_writel(bcm, pwm->hwpwm, BCM2835_PWM_CTL, 0);
+
+	/* write period */
+	bcm2835_writel(bcm, pwm->hwpwm, BCM2835_PWM_RNG1, period_ns);
+
+	/* write duty */
+	bcm2835_writel(bcm, pwm->hwpwm, BCM2835_PWM_DAT1, duty_ns);
+
+	/* start PWM */
+	bcm2835_writel(bcm, pwm->hwpwm, BCM2835_PWM_CTL, BCM2835_PWM_CTL_PWEN1);
+
+	return 0;
+}
+
+static int bcm2835_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct bcm2835_pwm_dev *bcm = to_bcm(chip);
+	int rc = 0;
+	u32 val;
+
+	if (WARN_ON(!bcm))
+		return -ENODEV;
+
+	rc = clk_prepare_enable(bcm->clk);
+	if (rc < 0)
+		return rc;
+
+	val = bcm2835_readl(bcm, pwm->hwpwm, BCM2835_PWM_CTL);
+	val |= BCM2835_PWM_CTL_PWEN1;
+	bcm2835_writel(bcm, pwm->hwpwm, BCM2835_PWM_CTL, val);
+
+	return 0;
+}
+
+static void bcm2835_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct bcm2835_pwm_dev *bcm = to_bcm(chip);
+	u32 val;
+
+	if (WARN_ON(!bcm))
+		return;
+
+	val = bcm2835_readl(bcm, pwm->hwpwm, BCM2835_PWM_CTL);
+	val &= ~BCM2835_PWM_CTL_PWEN1;
+	bcm2835_writel(bcm, pwm->hwpwm, BCM2835_PWM_CTL, val);
+
+	clk_disable_unprepare(bcm->clk);
+}
+
+static int bcm2835_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
+				enum pwm_polarity polarity)
+{
+	struct bcm2835_pwm_dev *bcm = to_bcm(chip);
+	u32 val;
+
+	if (WARN_ON(!bcm))
+		return -ENODEV;
+
+	val = bcm2835_readl(bcm, pwm->hwpwm, BCM2835_PWM_CTL);
+
+	if (polarity == PWM_POLARITY_NORMAL)
+		val |= BCM2835_PWM_CTL_POLA1;
+	else
+		val &= ~BCM2835_PWM_CTL_POLA1;
+
+	return 0;
+}
+
+static const struct pwm_ops bcm2835_pwm_ops = {
+	.config  = bcm2835_pwm_config,
+	.enable  = bcm2835_pwm_enable,
+	.disable = bcm2835_pwm_disable,
+	.set_polarity = bcm2835_set_polarity,
+};
+
+static int bcm2835_pwm_probe(struct platform_device *pdev)
+{
+	struct bcm2835_pwm_dev *bcm;
+	struct resource *r;
+	int ret;
+
+	bcm = devm_kzalloc(&pdev->dev, sizeof(*bcm), GFP_KERNEL);
+	if (!bcm)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		dev_err(&pdev->dev, "no memory resource defined\n");
+		return -ENODEV;
+	}
+
+	bcm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
+	if (IS_ERR(bcm->mmio_base))
+		return PTR_ERR(bcm->mmio_base);
+
+	bcm->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(bcm->clk))
+		return PTR_ERR(bcm->clk);
+
+	clk_prepare_enable(bcm->clk);
+
+	bcm->chip.ops = &bcm2835_pwm_ops;
+	bcm->chip.dev = &pdev->dev;
+	bcm->chip.base = -1;
+	bcm->chip.npwm = NPWM;
+
+	ret = pwmchip_add(&bcm->chip);
+	if (ret < 0)
+		return ret;
+
+	platform_set_drvdata(pdev, bcm);
+
+	return 0;
+}
+
+static int bcm2835_pwm_remove(struct platform_device *pdev)
+{
+	struct bcm2835_pwm_dev *bcm = platform_get_drvdata(pdev);
+
+	if (WARN_ON(!bcm))
+		return -ENODEV;
+
+	clk_disable_unprepare(bcm->clk);
+
+	return pwmchip_remove(&bcm->chip);
+}
+
+static const struct of_device_id bcm2835_pwm_of_match[] = {
+	{ .compatible = "brcm,bcm2835-pwm" },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_pwm_of_match);
+
+static struct platform_driver bcm2835_pwm_driver = {
+	.probe		= bcm2835_pwm_probe,
+	.remove		= bcm2835_pwm_remove,
+	.driver	= {
+		.name	= "pwm-bcm2835",
+		.owner	= THIS_MODULE,
+		.of_match_table = bcm2835_pwm_of_match,
+	},
+};
+
+module_platform_driver(bcm2835_pwm_driver);
+
+MODULE_AUTHOR("Johannes Thumshirn <morbidrsa@gmail.com>");
+MODULE_DESCRIPTION("BCM2835 PWM driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pwm-bcm2835");
-- 
1.8.4

             reply	other threads:[~2013-09-21 10:09 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-09-21 10:09 Johannes Thumshirn [this message]
2013-09-23  9:45 ` [RFC] PWM: Add support for pwm-bcm2835 Thierry Reding
2013-09-24 18:05   ` Johannes Thumshirn
2013-09-24  3:56 ` Stephen Warren

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=1379758142-7302-1-git-send-email-morbidrsa@gmail.com \
    --to=morbidrsa@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=linux-rpi-kernel@lists.infradead.org \
    --cc=swarren@wwwdotorg.org \
    --cc=thierry.reding@gmail.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.