am335x_pwm.c revision 266152
1178354Ssam/*-
2178354Ssam * Copyright (c) 2013 Oleksandr Tymoshenko <gonzo@freebsd.org>
3178354Ssam * All rights reserved.
4178354Ssam *
5178354Ssam * Redistribution and use in source and binary forms, with or without
6178354Ssam * modification, are permitted provided that the following conditions
7178354Ssam * are met:
8178354Ssam * 1. Redistributions of source code must retain the above copyright
9178354Ssam *    notice, this list of conditions and the following disclaimer.
10178354Ssam * 2. Redistributions in binary form must reproduce the above copyright
11178354Ssam *    notice, this list of conditions and the following disclaimer in the
12178354Ssam *    documentation and/or other materials provided with the distribution.
13178354Ssam *
14178354Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15178354Ssam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16178354Ssam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17178354Ssam * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18178354Ssam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19178354Ssam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20178354Ssam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21178354Ssam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22178354Ssam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23178354Ssam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24178354Ssam * SUCH DAMAGE.
25178354Ssam */
26178354Ssam
27178354Ssam#include <sys/cdefs.h>
28178354Ssam__FBSDID("$FreeBSD: stable/10/sys/arm/ti/am335x/am335x_pwm.c 266152 2014-05-15 16:11:06Z ian $");
29178354Ssam
30178354Ssam#include <sys/param.h>
31178354Ssam#include <sys/systm.h>
32178354Ssam#include <sys/kernel.h>
33178354Ssam#include <sys/module.h>
34178354Ssam#include <sys/bus.h>
35178354Ssam#include <sys/lock.h>
36178354Ssam#include <sys/mutex.h>
37178354Ssam#include <sys/resource.h>
38178354Ssam#include <sys/rman.h>
39178354Ssam#include <sys/sysctl.h>
40178354Ssam
41178354Ssam#include <machine/bus.h>
42178354Ssam
43178354Ssam#include <dev/fdt/fdt_common.h>
44178354Ssam#include <dev/ofw/openfirm.h>
45178354Ssam#include <dev/ofw/ofw_bus.h>
46206358Srpaulo#include <dev/ofw/ofw_bus_subr.h>
47178354Ssam
48178354Ssam#include <arm/ti/ti_prcm.h>
49178354Ssam#include <arm/ti/ti_scm.h>
50178354Ssam
51178354Ssam#include "am335x_pwm.h"
52178354Ssam#include "am335x_scm.h"
53178354Ssam
54178354Ssam/* In ticks */
55178354Ssam#define	DEFAULT_PWM_PERIOD	1000
56178354Ssam
57178354Ssam#define	PWM_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
58178354Ssam#define	PWM_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
59178354Ssam#define	PWM_LOCK_INIT(_sc)	mtx_init(&(_sc)->sc_mtx, \
60178354Ssam    device_get_nameunit(_sc->sc_dev), "am335x_pwm softc", MTX_DEF)
61178354Ssam#define	PWM_LOCK_DESTROY(_sc)	mtx_destroy(&(_sc)->sc_mtx);
62178354Ssam
63178354Ssamstatic struct resource_spec am335x_pwm_mem_spec[] = {
64178354Ssam	{ SYS_RES_MEMORY, 0, RF_ACTIVE }, /* PWMSS */
65178354Ssam	{ SYS_RES_MEMORY, 1, RF_ACTIVE }, /* eCAP */
66178354Ssam	{ SYS_RES_MEMORY, 2, RF_ACTIVE }, /* eQEP */
67178354Ssam	{ SYS_RES_MEMORY, 3, RF_ACTIVE }, /*ePWM */
68178354Ssam	{ -1, 0, 0 }
69178354Ssam};
70178354Ssam
71178354Ssam#define	PWMSS_READ4(_sc, reg)	bus_read_4((_sc)->sc_mem_res[0], reg);
72#define	PWMSS_WRITE4(_sc, reg, value)	\
73    bus_write_4((_sc)->sc_mem_res[0], reg, value);
74
75#define	ECAP_READ2(_sc, reg)	bus_read_2((_sc)->sc_mem_res[1], reg);
76#define	ECAP_WRITE2(_sc, reg, value)	\
77    bus_write_2((_sc)->sc_mem_res[1], reg, value);
78#define	ECAP_READ4(_sc, reg)	bus_read_4((_sc)->sc_mem_res[1], reg);
79#define	ECAP_WRITE4(_sc, reg, value)	\
80    bus_write_4((_sc)->sc_mem_res[1], reg, value);
81
82#define	EPWM_READ2(_sc, reg)	bus_read_2((_sc)->sc_mem_res[3], reg);
83#define	EPWM_WRITE2(_sc, reg, value)	\
84    bus_write_2((_sc)->sc_mem_res[3], reg, value);
85
86#define	PWMSS_IDVER		0x00
87#define	PWMSS_SYSCONFIG		0x04
88#define	PWMSS_CLKCONFIG		0x08
89#define		CLKCONFIG_EPWMCLK_EN	(1 << 8)
90#define	PWMSS_CLKSTATUS		0x0C
91
92#define	ECAP_TSCTR		0x00
93#define	ECAP_CAP1		0x08
94#define	ECAP_CAP2		0x0C
95#define	ECAP_CAP3		0x10
96#define	ECAP_CAP4		0x14
97#define	ECAP_ECCTL2		0x2A
98#define		ECCTL2_MODE_APWM		(1 << 9)
99#define		ECCTL2_SYNCO_SEL		(3 << 6)
100#define		ECCTL2_TSCTRSTOP_FREERUN	(1 << 4)
101
102#define	EPWM_TBCTL		0x00
103#define		TBCTL_FREERUN		(2 << 14)
104#define		TBCTL_PHDIR_UP		(1 << 13)
105#define		TBCTL_PHDIR_DOWN	(0 << 13)
106#define		TBCTL_CLKDIV(x)		((x) << 10)
107#define		TBCTL_CLKDIV_MASK	(3 << 10)
108#define		TBCTL_HSPCLKDIV(x)	((x) << 7)
109#define		TBCTL_HSPCLKDIV_MASK	(3 << 7)
110#define		TBCTL_SYNCOSEL_DISABLED	(3 << 4)
111#define		TBCTL_PRDLD_SHADOW	(0 << 3)
112#define		TBCTL_PRDLD_IMMEDIATE	(0 << 3)
113#define		TBCTL_PHSEN_ENABLED	(1 << 2)
114#define		TBCTL_PHSEN_DISABLED	(0 << 2)
115#define		TBCTL_CTRMODE_MASK	(3)
116#define		TBCTL_CTRMODE_UP	(0 << 0)
117#define		TBCTL_CTRMODE_DOWN	(1 << 0)
118#define		TBCTL_CTRMODE_UPDOWN	(2 << 0)
119#define		TBCTL_CTRMODE_FREEZE	(3 << 0)
120
121#define	EPWM_TBSTS		0x02
122#define	EPWM_TBPHSHR		0x04
123#define	EPWM_TBPHS		0x06
124#define	EPWM_TBCNT		0x08
125#define	EPWM_TBPRD		0x0a
126/* Counter-compare */
127#define	EPWM_CMPCTL		0x0e
128#define		CMPCTL_SHDWBMODE_SHADOW		(1 << 6)
129#define		CMPCTL_SHDWBMODE_IMMEDIATE	(0 << 6)
130#define		CMPCTL_SHDWAMODE_SHADOW		(1 << 4)
131#define		CMPCTL_SHDWAMODE_IMMEDIATE	(0 << 4)
132#define		CMPCTL_LOADBMODE_ZERO		(0 << 2)
133#define		CMPCTL_LOADBMODE_PRD		(1 << 2)
134#define		CMPCTL_LOADBMODE_EITHER		(2 << 2)
135#define		CMPCTL_LOADBMODE_FREEZE		(3 << 2)
136#define		CMPCTL_LOADAMODE_ZERO		(0 << 0)
137#define		CMPCTL_LOADAMODE_PRD		(1 << 0)
138#define		CMPCTL_LOADAMODE_EITHER		(2 << 0)
139#define		CMPCTL_LOADAMODE_FREEZE		(3 << 0)
140#define	EPWM_CMPAHR		0x10
141#define	EPWM_CMPA		0x12
142#define	EPWM_CMPB		0x14
143/* CMPCTL_LOADAMODE_ZERO */
144#define	EPWM_AQCTLA		0x16
145#define	EPWM_AQCTLB		0x18
146#define		AQCTL_CBU_NONE		(0 << 8)
147#define		AQCTL_CBU_CLEAR		(1 << 8)
148#define		AQCTL_CBU_SET		(2 << 8)
149#define		AQCTL_CBU_TOGGLE	(3 << 8)
150#define		AQCTL_CAU_NONE		(0 << 4)
151#define		AQCTL_CAU_CLEAR		(1 << 4)
152#define		AQCTL_CAU_SET		(2 << 4)
153#define		AQCTL_CAU_TOGGLE	(3 << 4)
154#define		AQCTL_ZRO_NONE		(0 << 0)
155#define		AQCTL_ZRO_CLEAR		(1 << 0)
156#define		AQCTL_ZRO_SET		(2 << 0)
157#define		AQCTL_ZRO_TOGGLE	(3 << 0)
158#define	EPWM_AQSFRC		0x1a
159#define	EPWM_AQCSFRC		0x1c
160
161/* Trip-Zone module */
162#define	EPWM_TZCTL		0x28
163#define	EPWM_TZFLG		0x2C
164/* High-Resolution PWM */
165#define	EPWM_HRCTL		0x40
166#define		HRCTL_DELMODE_BOTH	3
167#define		HRCTL_DELMODE_FALL	2
168#define		HRCTL_DELMODE_RISE	1
169
170static device_probe_t am335x_pwm_probe;
171static device_attach_t am335x_pwm_attach;
172static device_detach_t am335x_pwm_detach;
173
174struct am335x_pwm_softc {
175	device_t		sc_dev;
176	struct mtx		sc_mtx;
177	struct resource		*sc_mem_res[4];
178	int			sc_id;
179	/* sysctl for configuration */
180	struct sysctl_oid	*sc_period_oid;
181	struct sysctl_oid	*sc_chanA_oid;
182	struct sysctl_oid	*sc_chanB_oid;
183	uint32_t		sc_pwm_period;
184	uint32_t		sc_pwm_dutyA;
185	uint32_t		sc_pwm_dutyB;
186};
187
188static device_method_t am335x_pwm_methods[] = {
189	DEVMETHOD(device_probe,		am335x_pwm_probe),
190	DEVMETHOD(device_attach,	am335x_pwm_attach),
191	DEVMETHOD(device_detach,	am335x_pwm_detach),
192
193	DEVMETHOD_END
194};
195
196static driver_t am335x_pwm_driver = {
197	"am335x_pwm",
198	am335x_pwm_methods,
199	sizeof(struct am335x_pwm_softc),
200};
201
202static devclass_t am335x_pwm_devclass;
203
204/*
205 * API function to set period/duty cycles for ECASx
206 */
207int
208am335x_pwm_config_ecas(int unit, int period, int duty)
209{
210	device_t dev;
211	struct am335x_pwm_softc *sc;
212	uint16_t reg;
213
214	dev = devclass_get_device(am335x_pwm_devclass, unit);
215	if (dev == NULL)
216		return (ENXIO);
217
218	if (duty > period)
219		return (EINVAL);
220
221	if (period == 0)
222		return (EINVAL);
223
224	sc = device_get_softc(dev);
225	PWM_LOCK(sc);
226
227	reg = ECAP_READ2(sc, ECAP_ECCTL2);
228	reg |= ECCTL2_MODE_APWM | ECCTL2_TSCTRSTOP_FREERUN | ECCTL2_SYNCO_SEL;
229	ECAP_WRITE2(sc, ECAP_ECCTL2, reg);
230
231	/* CAP3 in APWM mode is APRD shadow register */
232	ECAP_WRITE4(sc, ECAP_CAP3, period - 1);
233
234	/* CAP4 in APWM mode is ACMP shadow register */
235	ECAP_WRITE4(sc, ECAP_CAP4, duty);
236	/* Restart counter */
237	ECAP_WRITE4(sc, ECAP_TSCTR, 0);
238
239	PWM_UNLOCK(sc);
240
241	return (0);
242}
243
244static int
245am335x_pwm_sysctl_duty(SYSCTL_HANDLER_ARGS)
246{
247	struct am335x_pwm_softc *sc = (struct am335x_pwm_softc*)arg1;
248	int error;
249	uint32_t duty;
250
251	if (oidp == sc->sc_chanA_oid)
252		duty = sc->sc_pwm_dutyA;
253	else
254		duty = sc->sc_pwm_dutyB;
255	error = sysctl_handle_int(oidp, &duty, 0, req);
256
257	if (error != 0 || req->newptr == NULL)
258		return (error);
259
260	if (duty > sc->sc_pwm_period) {
261		device_printf(sc->sc_dev, "Duty cycle can't be greater then period\n");
262		return (EINVAL);
263	}
264
265	PWM_LOCK(sc);
266	if (oidp == sc->sc_chanA_oid) {
267		sc->sc_pwm_dutyA = duty;
268		EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA);
269	}
270	else {
271		sc->sc_pwm_dutyB = duty;
272		EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB);
273	}
274	PWM_UNLOCK(sc);
275
276	return (error);
277}
278
279static int
280am335x_pwm_sysctl_period(SYSCTL_HANDLER_ARGS)
281{
282	struct am335x_pwm_softc *sc = (struct am335x_pwm_softc*)arg1;
283	int error;
284	uint32_t period;
285
286	period = sc->sc_pwm_period;
287	error = sysctl_handle_int(oidp, &period, 0, req);
288
289	if (error != 0 || req->newptr == NULL)
290		return (error);
291
292	if (period < 1)
293		return (EINVAL);
294
295	if ((period < sc->sc_pwm_dutyA) || (period < sc->sc_pwm_dutyB)) {
296		device_printf(sc->sc_dev, "Period can't be less then duty cycle\n");
297		return (EINVAL);
298	}
299
300
301	PWM_LOCK(sc);
302	sc->sc_pwm_period = period;
303	EPWM_WRITE2(sc, EPWM_TBPRD, period - 1);
304	PWM_UNLOCK(sc);
305
306	return (error);
307}
308
309static int
310am335x_pwm_probe(device_t dev)
311{
312
313	if (!ofw_bus_status_okay(dev))
314		return (ENXIO);
315
316	if (!ofw_bus_is_compatible(dev, "ti,am335x-pwm"))
317		return (ENXIO);
318
319	device_set_desc(dev, "AM335x PWM");
320
321	return (BUS_PROBE_DEFAULT);
322}
323
324static int
325am335x_pwm_attach(device_t dev)
326{
327	struct am335x_pwm_softc *sc;
328	int err;
329	uint32_t reg;
330	phandle_t node;
331	pcell_t did;
332	struct sysctl_ctx_list *ctx;
333	struct sysctl_oid *tree;
334
335	sc = device_get_softc(dev);
336	sc->sc_dev = dev;
337	/* Get the PWM module id */
338	node = ofw_bus_get_node(dev);
339	if ((OF_getprop(node, "pwm-device-id", &did, sizeof(did))) <= 0) {
340		device_printf(dev, "missing pwm-device-id attribute in FDT\n");
341		return (ENXIO);
342	}
343	sc->sc_id = fdt32_to_cpu(did);
344
345	PWM_LOCK_INIT(sc);
346
347	err = bus_alloc_resources(dev, am335x_pwm_mem_spec,
348	    sc->sc_mem_res);
349	if (err) {
350		device_printf(dev, "cannot allocate memory resources\n");
351		goto fail;
352	}
353
354	ti_prcm_clk_enable(PWMSS0_CLK + sc->sc_id);
355	ti_scm_reg_read_4(SCM_PWMSS_CTRL, &reg);
356	reg |= (1 << sc->sc_id);
357	ti_scm_reg_write_4(SCM_PWMSS_CTRL, reg);
358
359	/* Init backlight interface */
360	ctx = device_get_sysctl_ctx(sc->sc_dev);
361	tree = device_get_sysctl_tree(sc->sc_dev);
362
363	sc->sc_period_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
364	    "period", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
365	    am335x_pwm_sysctl_period, "I", "PWM period");
366
367	sc->sc_chanA_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
368	    "dutyA", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
369	    am335x_pwm_sysctl_duty, "I", "Channel A duty cycles");
370
371	sc->sc_chanB_oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
372	    "dutyB", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
373	    am335x_pwm_sysctl_duty, "I", "Channel B duty cycles");
374
375
376	/* CONFIGURE EPWM1 */
377	reg = EPWM_READ2(sc, EPWM_TBCTL);
378	reg &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK);
379	EPWM_WRITE2(sc, EPWM_TBCTL, reg);
380
381	sc->sc_pwm_period = DEFAULT_PWM_PERIOD;
382	sc->sc_pwm_dutyA = 0;
383	sc->sc_pwm_dutyB = 0;
384
385	EPWM_WRITE2(sc, EPWM_TBPRD, sc->sc_pwm_period - 1);
386	EPWM_WRITE2(sc, EPWM_CMPA, sc->sc_pwm_dutyA);
387	EPWM_WRITE2(sc, EPWM_CMPB, sc->sc_pwm_dutyB);
388
389	EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_SET | AQCTL_CAU_CLEAR));
390	EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_SET | AQCTL_CBU_CLEAR));
391
392	/* START EPWM */
393	reg &= ~TBCTL_CTRMODE_MASK;
394	reg |= TBCTL_CTRMODE_UP | TBCTL_FREERUN;
395	EPWM_WRITE2(sc, EPWM_TBCTL, reg);
396
397	EPWM_WRITE2(sc, EPWM_TZCTL, 0xf);
398	reg = EPWM_READ2(sc, EPWM_TZFLG);
399
400	return (0);
401fail:
402	PWM_LOCK_DESTROY(sc);
403	if (sc->sc_mem_res[0])
404		bus_release_resources(dev, am335x_pwm_mem_spec,
405		    sc->sc_mem_res);
406
407	return(ENXIO);
408}
409
410static int
411am335x_pwm_detach(device_t dev)
412{
413	struct am335x_pwm_softc *sc;
414
415	sc = device_get_softc(dev);
416
417	PWM_LOCK(sc);
418	if (sc->sc_mem_res[0])
419		bus_release_resources(dev, am335x_pwm_mem_spec,
420		    sc->sc_mem_res);
421	PWM_UNLOCK(sc);
422
423	PWM_LOCK_DESTROY(sc);
424
425	return (0);
426}
427
428DRIVER_MODULE(am335x_pwm, simplebus, am335x_pwm_driver, am335x_pwm_devclass, 0, 0);
429MODULE_VERSION(am335x_pwm, 1);
430MODULE_DEPEND(am335x_pwm, simplebus, 1, 1, 1);
431