/* * Copyright (C) 2010, Lars-Peter Clausen * 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 #include #include #include #include #include #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);