1/*
2 * linux/arch/unicore32/kernel/pwm.c
3 *
4 * Code specific to PKUnity SoC and UniCore ISA
5 *
6 *	Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn>
7 *	Copyright (C) 2001-2010 Guan Xuetao
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 */
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/platform_device.h>
17#include <linux/slab.h>
18#include <linux/err.h>
19#include <linux/clk.h>
20#include <linux/io.h>
21#include <linux/pwm.h>
22
23#include <asm/div64.h>
24#include <mach/hardware.h>
25
26struct pwm_device {
27	struct list_head	node;
28	struct platform_device *pdev;
29
30	const char	*label;
31	struct clk	*clk;
32	int		clk_enabled;
33
34	unsigned int	use_count;
35	unsigned int	pwm_id;
36};
37
38/*
39 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
40 * duty_ns   = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
41 */
42int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
43{
44	unsigned long long c;
45	unsigned long period_cycles, prescale, pv, dc;
46
47	if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
48		return -EINVAL;
49
50	c = clk_get_rate(pwm->clk);
51	c = c * period_ns;
52	do_div(c, 1000000000);
53	period_cycles = c;
54
55	if (period_cycles < 1)
56		period_cycles = 1;
57	prescale = (period_cycles - 1) / 1024;
58	pv = period_cycles / (prescale + 1) - 1;
59
60	if (prescale > 63)
61		return -EINVAL;
62
63	if (duty_ns == period_ns)
64		dc = OST_PWMDCCR_FDCYCLE;
65	else
66		dc = (pv + 1) * duty_ns / period_ns;
67
68	/* NOTE: the clock to PWM has to be enabled first
69	 * before writing to the registers
70	 */
71	clk_enable(pwm->clk);
72	OST_PWMPWCR = prescale;
73	OST_PWMDCCR = pv - dc;
74	OST_PWMPCR  = pv;
75	clk_disable(pwm->clk);
76
77	return 0;
78}
79EXPORT_SYMBOL(pwm_config);
80
81int pwm_enable(struct pwm_device *pwm)
82{
83	int rc = 0;
84
85	if (!pwm->clk_enabled) {
86		rc = clk_enable(pwm->clk);
87		if (!rc)
88			pwm->clk_enabled = 1;
89	}
90	return rc;
91}
92EXPORT_SYMBOL(pwm_enable);
93
94void pwm_disable(struct pwm_device *pwm)
95{
96	if (pwm->clk_enabled) {
97		clk_disable(pwm->clk);
98		pwm->clk_enabled = 0;
99	}
100}
101EXPORT_SYMBOL(pwm_disable);
102
103static DEFINE_MUTEX(pwm_lock);
104static LIST_HEAD(pwm_list);
105
106struct pwm_device *pwm_request(int pwm_id, const char *label)
107{
108	struct pwm_device *pwm;
109	int found = 0;
110
111	mutex_lock(&pwm_lock);
112
113	list_for_each_entry(pwm, &pwm_list, node) {
114		if (pwm->pwm_id == pwm_id) {
115			found = 1;
116			break;
117		}
118	}
119
120	if (found) {
121		if (pwm->use_count == 0) {
122			pwm->use_count++;
123			pwm->label = label;
124		} else
125			pwm = ERR_PTR(-EBUSY);
126	} else
127		pwm = ERR_PTR(-ENOENT);
128
129	mutex_unlock(&pwm_lock);
130	return pwm;
131}
132EXPORT_SYMBOL(pwm_request);
133
134void pwm_free(struct pwm_device *pwm)
135{
136	mutex_lock(&pwm_lock);
137
138	if (pwm->use_count) {
139		pwm->use_count--;
140		pwm->label = NULL;
141	} else
142		pr_warning("PWM device already freed\n");
143
144	mutex_unlock(&pwm_lock);
145}
146EXPORT_SYMBOL(pwm_free);
147
148static inline void __add_pwm(struct pwm_device *pwm)
149{
150	mutex_lock(&pwm_lock);
151	list_add_tail(&pwm->node, &pwm_list);
152	mutex_unlock(&pwm_lock);
153}
154
155static struct pwm_device *pwm_probe(struct platform_device *pdev,
156		unsigned int pwm_id, struct pwm_device *parent_pwm)
157{
158	struct pwm_device *pwm;
159	struct resource *r;
160	int ret = 0;
161
162	pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
163	if (pwm == NULL) {
164		dev_err(&pdev->dev, "failed to allocate memory\n");
165		return ERR_PTR(-ENOMEM);
166	}
167
168	pwm->clk = clk_get(NULL, "OST_CLK");
169	if (IS_ERR(pwm->clk)) {
170		ret = PTR_ERR(pwm->clk);
171		goto err_free;
172	}
173	pwm->clk_enabled = 0;
174
175	pwm->use_count = 0;
176	pwm->pwm_id = pwm_id;
177	pwm->pdev = pdev;
178
179	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
180	if (r == NULL) {
181		dev_err(&pdev->dev, "no memory resource defined\n");
182		ret = -ENODEV;
183		goto err_free_clk;
184	}
185
186	r = request_mem_region(r->start, resource_size(r), pdev->name);
187	if (r == NULL) {
188		dev_err(&pdev->dev, "failed to request memory resource\n");
189		ret = -EBUSY;
190		goto err_free_clk;
191	}
192
193	__add_pwm(pwm);
194	platform_set_drvdata(pdev, pwm);
195	return pwm;
196
197err_free_clk:
198	clk_put(pwm->clk);
199err_free:
200	kfree(pwm);
201	return ERR_PTR(ret);
202}
203
204static int __devinit puv3_pwm_probe(struct platform_device *pdev)
205{
206	struct pwm_device *pwm = pwm_probe(pdev, pdev->id, NULL);
207
208	if (IS_ERR(pwm))
209		return PTR_ERR(pwm);
210
211	return 0;
212}
213
214static int __devexit pwm_remove(struct platform_device *pdev)
215{
216	struct pwm_device *pwm;
217	struct resource *r;
218
219	pwm = platform_get_drvdata(pdev);
220	if (pwm == NULL)
221		return -ENODEV;
222
223	mutex_lock(&pwm_lock);
224	list_del(&pwm->node);
225	mutex_unlock(&pwm_lock);
226
227	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
228	release_mem_region(r->start, resource_size(r));
229
230	clk_put(pwm->clk);
231	kfree(pwm);
232	return 0;
233}
234
235static struct platform_driver puv3_pwm_driver = {
236	.driver		= {
237		.name	= "PKUnity-v3-PWM",
238	},
239	.probe		= puv3_pwm_probe,
240	.remove		= __devexit_p(pwm_remove),
241};
242
243static int __init pwm_init(void)
244{
245	int ret = 0;
246
247	ret = platform_driver_register(&puv3_pwm_driver);
248	if (ret) {
249		printk(KERN_ERR "failed to register puv3_pwm_driver\n");
250		return ret;
251	}
252
253	return ret;
254}
255arch_initcall(pwm_init);
256
257static void __exit pwm_exit(void)
258{
259	platform_driver_unregister(&puv3_pwm_driver);
260}
261module_exit(pwm_exit);
262
263MODULE_LICENSE("GPL v2");
264