MIPS: JZ4740: Add PWM support
authorLars-Peter Clausen <lars@metafoo.de>
Sat, 17 Jul 2010 11:12:20 +0000 (11:12 +0000)
committerRalf Baechle <ralf@linux-mips.org>
Thu, 5 Aug 2010 12:26:16 +0000 (13:26 +0100)
Add support for the PWM part of the timer unit on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/1468/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/jz4740/pwm.c [new file with mode: 0644]

diff --git a/arch/mips/jz4740/pwm.c b/arch/mips/jz4740/pwm.c
new file mode 100644 (file)
index 0000000..a26a6fa
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *  JZ4740 platform PWM support
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under  the terms of the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-jz4740/gpio.h>
+#include "timer.h"
+
+static struct clk *jz4740_pwm_clk;
+
+DEFINE_MUTEX(jz4740_pwm_mutex);
+
+struct pwm_device {
+       unsigned int id;
+       unsigned int gpio;
+       bool used;
+};
+
+static struct pwm_device jz4740_pwm_list[] = {
+       { 2, JZ_GPIO_PWM2, false },
+       { 3, JZ_GPIO_PWM3, false },
+       { 4, JZ_GPIO_PWM4, false },
+       { 5, JZ_GPIO_PWM5, false },
+       { 6, JZ_GPIO_PWM6, false },
+       { 7, JZ_GPIO_PWM7, false },
+};
+
+struct pwm_device *pwm_request(int id, const char *label)
+{
+       int ret = 0;
+       struct pwm_device *pwm;
+
+       if (id < 2 || id > 7 || !jz4740_pwm_clk)
+               return ERR_PTR(-ENODEV);
+
+       mutex_lock(&jz4740_pwm_mutex);
+
+       pwm = &jz4740_pwm_list[id - 2];
+       if (pwm->used)
+               ret = -EBUSY;
+       else
+               pwm->used = true;
+
+       mutex_unlock(&jz4740_pwm_mutex);
+
+       if (ret)
+               return ERR_PTR(ret);
+
+       ret = gpio_request(pwm->gpio, label);
+
+       if (ret) {
+               printk(KERN_ERR "Failed to request pwm gpio: %d\n", ret);
+               pwm->used = false;
+               return ERR_PTR(ret);
+       }
+
+       jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_PWM);
+
+       jz4740_timer_start(id);
+
+       return pwm;
+}
+
+void pwm_free(struct pwm_device *pwm)
+{
+       pwm_disable(pwm);
+       jz4740_timer_set_ctrl(pwm->id, 0);
+
+       jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_NONE);
+       gpio_free(pwm->gpio);
+
+       jz4740_timer_stop(pwm->id);
+
+       pwm->used = false;
+}
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+       unsigned long long tmp;
+       unsigned long period, duty;
+       unsigned int prescaler = 0;
+       unsigned int id = pwm->id;
+       uint16_t ctrl;
+       bool is_enabled;
+
+       if (duty_ns < 0 || duty_ns > period_ns)
+               return -EINVAL;
+
+       tmp = (unsigned long long)clk_get_rate(jz4740_pwm_clk) * period_ns;
+       do_div(tmp, 1000000000);
+       period = tmp;
+
+       while (period > 0xffff && prescaler < 6) {
+               period >>= 2;
+               ++prescaler;
+       }
+
+       if (prescaler == 6)
+               return -EINVAL;
+
+       tmp = (unsigned long long)period * duty_ns;
+       do_div(tmp, period_ns);
+       duty = period - tmp;
+
+       if (duty >= period)
+               duty = period - 1;
+
+       is_enabled = jz4740_timer_is_enabled(id);
+       if (is_enabled)
+               pwm_disable(pwm);
+
+       jz4740_timer_set_count(id, 0);
+       jz4740_timer_set_duty(id, duty);
+       jz4740_timer_set_period(id, period);
+
+       ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
+               JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
+
+       jz4740_timer_set_ctrl(id, ctrl);
+
+       if (is_enabled)
+               pwm_enable(pwm);
+
+       return 0;
+}
+
+int pwm_enable(struct pwm_device *pwm)
+{
+       uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+       ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
+       jz4740_timer_set_ctrl(pwm->id, ctrl);
+       jz4740_timer_enable(pwm->id);
+
+       return 0;
+}
+
+void pwm_disable(struct pwm_device *pwm)
+{
+       uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+       ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
+       jz4740_timer_disable(pwm->id);
+       jz4740_timer_set_ctrl(pwm->id, ctrl);
+}
+
+static int __init jz4740_pwm_init(void)
+{
+       int ret = 0;
+
+       jz4740_pwm_clk = clk_get(NULL, "ext");
+
+       if (IS_ERR(jz4740_pwm_clk)) {
+               ret = PTR_ERR(jz4740_pwm_clk);
+               jz4740_pwm_clk = NULL;
+       }
+
+       return ret;
+}
+subsys_initcall(jz4740_pwm_init);