mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] pwm: add Exynos PWM driver
@ 2012-12-13 10:16 Joonyoung Shim
  2013-01-09  8:16 ` Thierry Reding
  0 siblings, 1 reply; 2+ messages in thread
From: Joonyoung Shim @ 2012-12-13 10:16 UTC (permalink / raw)
  To: thierry.reding; +Cc: linux-samsung-soc, linux-kernel, kgene.kim, kyungmin.park

This is PWM driver to support 4 pwm for Exynos SoCs. Also this supports
device tree node.

The existing s3c24xx-pwm driver has many dependence with arch specific
codes and it is difficult to support device tree by static mapping of
PMW memory area. Also it can't support multi pwm to one device and can't
make to module.

Signed-off-by: Joonyoung Shim <jy0922.shim@samsung.com>
---
This is based on for-next branch of git://gitorious.org/linux-pwm/linux-pwm.git

 drivers/pwm/Kconfig      |    9 ++
 drivers/pwm/Makefile     |    1 +
 drivers/pwm/pwm-exynos.c |  234 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 244 insertions(+)
 create mode 100644 drivers/pwm/pwm-exynos.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index ed81720..1632ace 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -46,6 +46,15 @@ config PWM_BFIN
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-bfin.
 
+config PWM_EXYNOS
+	tristate "Exynos pwm support"
+	depends on ARCH_EXYNOS
+	help
+	  Generic PWM framework driver for Exynos.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-exynos.
+
 config PWM_IMX
 	tristate "i.MX pwm support"
 	depends on ARCH_MXC
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index acfe482..423a251 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -1,6 +1,7 @@
 obj-$(CONFIG_PWM)		+= core.o
 obj-$(CONFIG_PWM_AB8500)	+= pwm-ab8500.o
 obj-$(CONFIG_PWM_BFIN)		+= pwm-bfin.o
+obj-$(CONFIG_PWM_EXYNOS)	+= pwm-exynos.o
 obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_LPC32XX)	+= pwm-lpc32xx.o
diff --git a/drivers/pwm/pwm-exynos.c b/drivers/pwm/pwm-exynos.c
new file mode 100644
index 0000000..5a411b6
--- /dev/null
+++ b/drivers/pwm/pwm-exynos.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.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.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define PWM_NUM			4
+
+#define PWM_TCFG0		0x00
+#define PWM_TCFG1		0x04
+#define PWM_TCON		0x08
+#define PWM_TCNTB(hw)		(0x0c + (hw) * 0x0c)
+#define PWM_TCMPB(hw)		(0x10 + (hw) * 0x0c)
+#define PWM_TCNTO(hw)		(0x14 + (hw) * 0x0c)
+
+#define PWM_TCFG0_RST_VAL	0x101
+#define PWM_TCFG1_RST_VAL	0x0
+#define PWM_TCON_RST_VAL	0x44404		/* inverter on */
+
+#define PWM_TCFG0_PRESCALER0_MASK	0xff
+#define PWM_TCFG0_PRESCALER1_MASK	0xff00
+#define PWM_TCFG0_PRESCALER1_SHIFT	8
+
+#define PWM_TCFG1_MUX_MASK		0xf
+#define PWM_TCFG1_MUX_SHIFT		4
+
+#define PWM_TCON_BASE(hw)		((hw) ? ((hw) + 1) * 4 : 0)
+#define PWM_TCON_START(hw)		(1 << (PWM_TCON_BASE(hw) + 0))
+#define PWM_TCON_MANUALUPDATE(hw)	(1 << (PWM_TCON_BASE(hw) + 1))
+#define PWM_TCON_INVERTER(hw)		(1 << (PWM_TCON_BASE(hw) + 2))
+#define PWM_TCON_AUTORELOAD(hw)		(1 << (PWM_TCON_BASE(hw) + 3))
+
+struct exynos_pwm {
+	struct pwm_chip		chip;
+	struct device		*dev;
+	struct clk		*clk;
+	struct mutex		mutex;
+	void __iomem		*base;
+};
+
+static int exynos_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+			     int duty_ns, int period_ns)
+{
+	struct exynos_pwm *exynos = container_of(chip, struct exynos_pwm, chip);
+	unsigned long long c;
+	unsigned long period_cycles, duty_cycles;
+	unsigned long clk_rate;
+	unsigned int prescaler;
+	unsigned int divider;
+	unsigned int hw = pwm->hwpwm;
+	u32 val;
+
+	if (period_ns > NSEC_PER_SEC)
+		return -ERANGE;
+
+	val = readl(exynos->base + PWM_TCFG0);
+	if (hw == 0 || hw == 1)
+		prescaler = val & PWM_TCFG0_PRESCALER0_MASK;
+	else
+		prescaler = (val & PWM_TCFG0_PRESCALER1_MASK) >>
+						PWM_TCFG0_PRESCALER1_SHIFT;
+
+	val = readl(exynos->base + PWM_TCFG1);
+	divider = val & (PWM_TCFG1_MUX_MASK << (hw * PWM_TCFG1_MUX_SHIFT));
+	divider = 1 << divider;
+
+	/* Clock Frequency = PCLK / (prescaler + 1) / divider */
+	clk_rate = clk_get_rate(exynos->clk);
+	clk_rate = clk_rate / (prescaler + 1) / divider;
+
+	c = (unsigned long long)clk_rate * period_ns;
+	do_div(c, NSEC_PER_SEC);
+	period_cycles = (unsigned long)c;
+
+	c = (unsigned long long)period_cycles * duty_ns;
+	do_div(c, period_ns);
+	duty_cycles = (unsigned long)c;
+
+	/* because inverter is on and count down */
+	duty_cycles = period_cycles - duty_cycles;
+
+	writel(period_cycles, exynos->base + PWM_TCNTB(hw));
+	writel(duty_cycles, exynos->base + PWM_TCMPB(hw));
+
+	mutex_lock(&exynos->mutex);
+	val = readl(exynos->base + PWM_TCON);
+	val |= PWM_TCON_MANUALUPDATE(hw);
+	val |= PWM_TCON_AUTORELOAD(hw);
+	writel(val, exynos->base + PWM_TCON);
+
+	val &= ~PWM_TCON_MANUALUPDATE(hw);
+	writel(val, exynos->base + PWM_TCON);
+	mutex_unlock(&exynos->mutex);
+
+	return 0;
+}
+
+static int exynos_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct exynos_pwm *exynos = container_of(chip, struct exynos_pwm, chip);
+	unsigned int hw = pwm->hwpwm;
+	u32 val;
+
+	mutex_lock(&exynos->mutex);
+	val = readl(exynos->base + PWM_TCON);
+	val |= PWM_TCON_START(hw);
+	writel(val, exynos->base + PWM_TCON);
+	mutex_unlock(&exynos->mutex);
+
+	return 0;
+}
+
+static void exynos_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct exynos_pwm *exynos = container_of(chip, struct exynos_pwm, chip);
+	unsigned int hw = pwm->hwpwm;
+	u32 val;
+
+	mutex_lock(&exynos->mutex);
+	val = readl(exynos->base + PWM_TCON);
+	val |= PWM_TCON_START(hw);
+	writel(val, exynos->base + PWM_TCON);
+	mutex_unlock(&exynos->mutex);
+}
+
+static const struct pwm_ops exynos_pwm_ops = {
+	.config = exynos_pwm_config,
+	.enable = exynos_pwm_enable,
+	.disable = exynos_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+static int exynos_pwm_probe(struct platform_device *pdev)
+{
+	struct exynos_pwm *exynos;
+	struct resource *res;
+	int ret;
+
+	exynos = devm_kzalloc(&pdev->dev, sizeof(*exynos), GFP_KERNEL);
+	if (!exynos) {
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	exynos->dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "no memory resources defined\n");
+		return -ENODEV;
+	}
+
+	exynos->base = devm_request_and_ioremap(&pdev->dev, res);
+	if (!exynos->base)
+		return -EADDRNOTAVAIL;
+
+	exynos->clk = devm_clk_get(&pdev->dev, "timers");
+	if (IS_ERR(exynos->clk)) {
+		dev_err(&pdev->dev, "failed to get timer clock with %ld\n",
+				PTR_ERR(exynos->clk));
+		return PTR_ERR(exynos->clk);
+	}
+
+	mutex_init(&exynos->mutex);
+
+	exynos->chip.dev = &pdev->dev;
+	exynos->chip.ops = &exynos_pwm_ops;
+	exynos->chip.base = -1;
+	exynos->chip.npwm = PWM_NUM;
+
+	ret = clk_prepare_enable(exynos->clk);
+	if (ret)
+		return ret;
+
+	/* Reset registers related with PWM clock and control */
+	writel(PWM_TCFG0_RST_VAL, exynos->base + PWM_TCFG0);
+	writel(PWM_TCFG1_RST_VAL, exynos->base + PWM_TCFG1);
+	writel(PWM_TCON_RST_VAL, exynos->base + PWM_TCON);
+
+	ret = pwmchip_add(&exynos->chip);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
+		clk_disable_unprepare(exynos->clk);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, exynos);
+
+	return 0;
+}
+
+static int __devexit exynos_pwm_remove(struct platform_device *pdev)
+{
+	struct exynos_pwm *exynos = platform_get_drvdata(pdev);
+	if (!exynos)
+		return -ENODEV;
+
+	clk_disable_unprepare(exynos->clk);
+
+	return pwmchip_remove(&exynos->chip);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id exynos_pwm_dt_match[] = {
+	{ .compatible = "samsung,exynos-pwm" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos_pwm_dt_match);
+#endif
+
+static struct platform_driver exynos_pwm_driver = {
+	.probe		= exynos_pwm_probe,
+	.remove		= __devexit_p(exynos_pwm_remove),
+	.driver		= {
+		.name	= "exynos-pwm",
+		.owner	= THIS_MODULE,
+		.of_match_table	= of_match_ptr(exynos_pwm_dt_match),
+	},
+};
+module_platform_driver(exynos_pwm_driver);
+
+MODULE_DESCRIPTION("Exynos PWM driver");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2013-01-09  8:16 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-12-13 10:16 [PATCH] pwm: add Exynos PWM driver Joonyoung Shim
2013-01-09  8:16 ` Thierry Reding

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®