aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/pwm-omap.c
blob: 344072c998a1c1cc6e592be371ec8e2b7875da7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/*
 *    Copyright (c) 2012 NeilBrown <neilb@suse.de>
 *    Heavily based on earlier code which is:
 *    Copyright (c) 2010 Grant Erickson <marathon96@gmail.com>
 *
 *    Also based on pwm-samsung.c
 *
 *    This program is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU General Public License
 *    version 2 as published by the Free Software Foundation.
 *
 *    Description:
 *      This file is the core OMAP support for the generic, Linux
 *      PWM driver / controller, using the OMAP's dual-mode timers.
 *
 */

#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/module.h>
#include <linux/platform_data/omap-pwm.h>

#include <plat/dmtimer.h>

#define DM_TIMER_LOAD_MIN		0xFFFFFFFE

struct omap_chip {
	struct omap_dm_timer	*dm_timer;
	enum pwm_polarity	polarity;
	unsigned int		duty_ns, period_ns;
	struct pwm_chip		chip;
};

#define to_omap_chip(chip)	container_of(chip, struct omap_chip, chip)

/**
 * pwm_calc_value - Determine the counter value for a clock rate and period.
 * @clk_rate: The clock rate, in Hz, of the PWM's clock source to compute the
 *            counter value for.
 * @ns: The period, in nanoseconds, to compute the counter value for.
 *
 * Returns the PWM counter value for the specified clock rate and period.
 */
static inline int pwm_calc_value(unsigned long clk_rate, int ns)
{
	u64 c;

	c = (u64)clk_rate * ns;
	do_div(c, NSEC_PER_SEC);

	return DM_TIMER_LOAD_MIN - c;
}

static int omap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct omap_chip *omap = to_omap_chip(chip);

	omap_dm_timer_start(omap->dm_timer);

	return 0;
}

static void omap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct omap_chip *omap = to_omap_chip(chip);

	omap_dm_timer_stop(omap->dm_timer);
}

static int omap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
			   int duty_ns, int period_ns)
{
	struct omap_chip *omap = to_omap_chip(chip);
	int load_value, match_value;
	unsigned long clk_rate;

	dev_dbg(chip->dev, "duty cycle: %d, period %d\n", duty_ns, period_ns);

	if (omap->duty_ns == duty_ns &&
	    omap->period_ns == period_ns)
		/* No change - don't cause any transients. */
		return 0;

	clk_rate = clk_get_rate(omap_dm_timer_get_fclk(omap->dm_timer));

	/*
	 * Calculate the appropriate load and match values based on the
	 * specified period and duty cycle. The load value determines the
	 * cycle time and the match value determines the duty cycle.
	 */

	load_value = pwm_calc_value(clk_rate, period_ns);
	match_value = pwm_calc_value(clk_rate, period_ns - duty_ns);

	/*
	 * We MUST enable yet stop the associated dual-mode timer before
	 * attempting to write its registers.  Hopefully it is already
	 * disabled, but call the (idempotent) pwm_disable just in case.
	 */

	pwm_disable(pwm);

	omap_dm_timer_set_load(omap->dm_timer, true, load_value);
	omap_dm_timer_set_match(omap->dm_timer, true, match_value);

	dev_dbg(chip->dev, "load value: %#08x (%d), match value: %#08x (%d)\n",
		load_value, load_value,	match_value, match_value);

	omap_dm_timer_set_pwm(omap->dm_timer,
			      omap->polarity == PWM_POLARITY_INVERSED,
			      true,
			      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);

	omap->duty_ns = duty_ns;
	omap->period_ns = period_ns;

	return 0;
}

static int omap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
				 enum pwm_polarity polarity)
{
	struct omap_chip *omap = to_omap_chip(chip);

	if (omap->polarity == polarity)
		return 0;

	omap->polarity = polarity;

	omap_dm_timer_set_pwm(omap->dm_timer,
			      omap->polarity == PWM_POLARITY_INVERSED,
			      true,
			      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
	return 0;
}

static struct pwm_ops omap_pwm_ops = {
	.enable		= omap_pwm_enable,
	.disable	= omap_pwm_disable,
	.config		= omap_pwm_config,
	.set_polarity	= omap_pwm_set_polarity,
	.owner		= THIS_MODULE,
};

static int omap_pwm_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct omap_chip *omap;
	int status = 0;
	struct omap_pwm_pdata *pdata = dev->platform_data;

	if (!pdata) {
		dev_err(dev, "No platform data provided\n");
		return -ENODEV;
	}

	omap = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
	if (omap == NULL) {
		dev_err(dev, "Could not allocate memory.\n");
		return -ENOMEM;
	}

	/*
	 * Request the OMAP dual-mode timer that will be bound to and
	 * associated with this generic PWM.
	 */

	omap->dm_timer = omap_dm_timer_request_specific(pdata->timer_id);
	if (omap->dm_timer == NULL) {
		status = -EPROBE_DEFER;
		goto err_free;
	}

	/*
	 * Configure the source for the dual-mode timer backing this
	 * generic PWM device. The clock source will ultimately determine
	 * how small or large the PWM frequency can be.
	 *
	 * At some point, it's probably worth revisiting moving this to
	 * the configure method and choosing either the slow- or
	 * system-clock source as appropriate for the desired PWM period.
	 */

	omap_dm_timer_set_source(omap->dm_timer, OMAP_TIMER_SRC_SYS_CLK);

	/*
	 * Cache away other miscellaneous driver-private data and state
	 * information and add the driver-private data to the platform
	 * device.
	 */

	omap->chip.dev = dev;
	omap->chip.ops = &omap_pwm_ops;
	omap->chip.base = -1;
	omap->chip.npwm = 1;
	omap->polarity = PWM_POLARITY_NORMAL;

	status = pwmchip_add(&omap->chip);
	if (status < 0) {
		dev_err(dev, "failed to register PWM\n");
		omap_dm_timer_free(omap->dm_timer);
		goto err_free;
	}

	platform_set_drvdata(pdev, omap);

	return 0;

 err_free:
	kfree(omap);
	return status;
}

static int omap_pwm_remove(struct platform_device *pdev)
{
	struct omap_chip *omap = platform_get_drvdata(pdev);
	int status;

	omap_dm_timer_stop(omap->dm_timer);
	status = pwmchip_remove(&omap->chip);
	if (status < 0)
		return status;

	omap_dm_timer_free(omap->dm_timer);
	kfree(omap);

	return 0;
}

#if CONFIG_PM
static int omap_pwm_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct omap_chip *omap = platform_get_drvdata(pdev);
	/*
	 * No one preserve these values during suspend so reset them,
	 * otherwise driver leaves PWM unconfigured if same values
	 * passed to pwm_config.
	 */
	omap->period_ns = 0;
	omap->duty_ns = 0;

	return 0;
}
#else
#define omap_pwm_suspend	NULL
#endif

static SIMPLE_DEV_PM_OPS(omap_pwm_pm, omap_pwm_suspend, NULL);
static struct platform_driver omap_pwm_driver = {
	.driver = {
		.name	= "omap-pwm",
		.owner	= THIS_MODULE,
		.pm	= &omap_pwm_pm,
	},
	.probe		= omap_pwm_probe,
	.remove		= omap_pwm_remove,
};
module_platform_driver(omap_pwm_driver);

MODULE_AUTHOR("Grant Erickson <marathon96@gmail.com>");
MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("2012-12-01");
MODULE_DESCRIPTION("OMAP PWM Driver using Dual-mode Timers");