Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

MIPS: JZ4740: Add PWM support

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>

authored by

Lars-Peter Clausen and committed by
Ralf Baechle
eda0347b 68fcfe7f

+177
+177
arch/mips/jz4740/pwm.c
··· 1 + /* 2 + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> 3 + * JZ4740 platform PWM support 4 + * 5 + * This program is free software; you can redistribute it and/or modify it 6 + * under the terms of the GNU General Public License as published by the 7 + * Free Software Foundation; either version 2 of the License, or (at your 8 + * option) any later version. 9 + * 10 + * You should have received a copy of the GNU General Public License along 11 + * with this program; if not, write to the Free Software Foundation, Inc., 12 + * 675 Mass Ave, Cambridge, MA 02139, USA. 13 + * 14 + */ 15 + 16 + #include <linux/kernel.h> 17 + 18 + #include <linux/clk.h> 19 + #include <linux/err.h> 20 + #include <linux/pwm.h> 21 + #include <linux/gpio.h> 22 + 23 + #include <asm/mach-jz4740/gpio.h> 24 + #include "timer.h" 25 + 26 + static struct clk *jz4740_pwm_clk; 27 + 28 + DEFINE_MUTEX(jz4740_pwm_mutex); 29 + 30 + struct pwm_device { 31 + unsigned int id; 32 + unsigned int gpio; 33 + bool used; 34 + }; 35 + 36 + static struct pwm_device jz4740_pwm_list[] = { 37 + { 2, JZ_GPIO_PWM2, false }, 38 + { 3, JZ_GPIO_PWM3, false }, 39 + { 4, JZ_GPIO_PWM4, false }, 40 + { 5, JZ_GPIO_PWM5, false }, 41 + { 6, JZ_GPIO_PWM6, false }, 42 + { 7, JZ_GPIO_PWM7, false }, 43 + }; 44 + 45 + struct pwm_device *pwm_request(int id, const char *label) 46 + { 47 + int ret = 0; 48 + struct pwm_device *pwm; 49 + 50 + if (id < 2 || id > 7 || !jz4740_pwm_clk) 51 + return ERR_PTR(-ENODEV); 52 + 53 + mutex_lock(&jz4740_pwm_mutex); 54 + 55 + pwm = &jz4740_pwm_list[id - 2]; 56 + if (pwm->used) 57 + ret = -EBUSY; 58 + else 59 + pwm->used = true; 60 + 61 + mutex_unlock(&jz4740_pwm_mutex); 62 + 63 + if (ret) 64 + return ERR_PTR(ret); 65 + 66 + ret = gpio_request(pwm->gpio, label); 67 + 68 + if (ret) { 69 + printk(KERN_ERR "Failed to request pwm gpio: %d\n", ret); 70 + pwm->used = false; 71 + return ERR_PTR(ret); 72 + } 73 + 74 + jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_PWM); 75 + 76 + jz4740_timer_start(id); 77 + 78 + return pwm; 79 + } 80 + 81 + void pwm_free(struct pwm_device *pwm) 82 + { 83 + pwm_disable(pwm); 84 + jz4740_timer_set_ctrl(pwm->id, 0); 85 + 86 + jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_NONE); 87 + gpio_free(pwm->gpio); 88 + 89 + jz4740_timer_stop(pwm->id); 90 + 91 + pwm->used = false; 92 + } 93 + 94 + int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) 95 + { 96 + unsigned long long tmp; 97 + unsigned long period, duty; 98 + unsigned int prescaler = 0; 99 + unsigned int id = pwm->id; 100 + uint16_t ctrl; 101 + bool is_enabled; 102 + 103 + if (duty_ns < 0 || duty_ns > period_ns) 104 + return -EINVAL; 105 + 106 + tmp = (unsigned long long)clk_get_rate(jz4740_pwm_clk) * period_ns; 107 + do_div(tmp, 1000000000); 108 + period = tmp; 109 + 110 + while (period > 0xffff && prescaler < 6) { 111 + period >>= 2; 112 + ++prescaler; 113 + } 114 + 115 + if (prescaler == 6) 116 + return -EINVAL; 117 + 118 + tmp = (unsigned long long)period * duty_ns; 119 + do_div(tmp, period_ns); 120 + duty = period - tmp; 121 + 122 + if (duty >= period) 123 + duty = period - 1; 124 + 125 + is_enabled = jz4740_timer_is_enabled(id); 126 + if (is_enabled) 127 + pwm_disable(pwm); 128 + 129 + jz4740_timer_set_count(id, 0); 130 + jz4740_timer_set_duty(id, duty); 131 + jz4740_timer_set_period(id, period); 132 + 133 + ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT | 134 + JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN; 135 + 136 + jz4740_timer_set_ctrl(id, ctrl); 137 + 138 + if (is_enabled) 139 + pwm_enable(pwm); 140 + 141 + return 0; 142 + } 143 + 144 + int pwm_enable(struct pwm_device *pwm) 145 + { 146 + uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id); 147 + 148 + ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; 149 + jz4740_timer_set_ctrl(pwm->id, ctrl); 150 + jz4740_timer_enable(pwm->id); 151 + 152 + return 0; 153 + } 154 + 155 + void pwm_disable(struct pwm_device *pwm) 156 + { 157 + uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id); 158 + 159 + ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; 160 + jz4740_timer_disable(pwm->id); 161 + jz4740_timer_set_ctrl(pwm->id, ctrl); 162 + } 163 + 164 + static int __init jz4740_pwm_init(void) 165 + { 166 + int ret = 0; 167 + 168 + jz4740_pwm_clk = clk_get(NULL, "ext"); 169 + 170 + if (IS_ERR(jz4740_pwm_clk)) { 171 + ret = PTR_ERR(jz4740_pwm_clk); 172 + jz4740_pwm_clk = NULL; 173 + } 174 + 175 + return ret; 176 + } 177 + subsys_initcall(jz4740_pwm_init);