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 26static struct clk *jz4740_pwm_clk; 27 28DEFINE_MUTEX(jz4740_pwm_mutex); 29 30struct pwm_device { 31 unsigned int id; 32 unsigned int gpio; 33 bool used; 34}; 35 36static 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 45struct 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 81void 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 94int 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 144int 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 155void 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 164static 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} 177subsys_initcall(jz4740_pwm_init); 178