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 
pwm_request(int id,const char * label)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 
pwm_free(struct pwm_device * pwm)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 
pwm_config(struct pwm_device * pwm,int duty_ns,int period_ns)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 
pwm_enable(struct pwm_device * pwm)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 
pwm_disable(struct pwm_device * pwm)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 
jz4740_pwm_init(void)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);
178