xref: /openbsd/sys/arch/arm64/dev/aplpwm.c (revision d415bd75)
1 /*	$OpenBSD: aplpwm.c,v 1.1 2022/11/21 21:48:06 kettenis Exp $	*/
2 /*
3  * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21 
22 #include <machine/bus.h>
23 #include <machine/fdt.h>
24 
25 #include <dev/ofw/openfirm.h>
26 #include <dev/ofw/ofw_clock.h>
27 #include <dev/ofw/ofw_misc.h>
28 #include <dev/ofw/ofw_power.h>
29 #include <dev/ofw/fdt.h>
30 
31 #define PWM_CTRL		0x0000
32 #define  PWM_CTRL_EN		(1 << 0)
33 #define  PWM_CTRL_UPDATE	(1 << 5)
34 #define  PWM_CTRL_OUTPUT_EN	(1 << 14)
35 #define PWM_OFF_CYCLES		0x0018
36 #define PWM_ON_CYCLES		0x001c
37 
38 #define NS_PER_S		1000000000
39 
40 #define HREAD4(sc, reg)							\
41 	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
42 #define HWRITE4(sc, reg, val)						\
43 	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
44 
45 struct aplpwm_softc {
46 	struct device		sc_dev;
47 	bus_space_tag_t		sc_iot;
48 	bus_space_handle_t	sc_ioh;
49 
50 	uint64_t		sc_clkin;
51 	struct pwm_device	sc_pd;
52 };
53 
54 int	aplpwm_match(struct device *, void *, void *);
55 void	aplpwm_attach(struct device *, struct device *, void *);
56 
57 const struct cfattach aplpwm_ca = {
58 	sizeof (struct aplpwm_softc), aplpwm_match, aplpwm_attach
59 };
60 
61 struct cfdriver aplpwm_cd = {
62 	NULL, "aplpwm", DV_DULL
63 };
64 
65 int	aplpwm_get_state(void *, uint32_t *, struct pwm_state *);
66 int	aplpwm_set_state(void *, uint32_t *, struct pwm_state *);
67 
68 int
69 aplpwm_match(struct device *parent, void *match, void *aux)
70 {
71 	struct fdt_attach_args *faa = aux;
72 
73 	return OF_is_compatible(faa->fa_node, "apple,s5l-fpwm");
74 }
75 
76 void
77 aplpwm_attach(struct device *parent, struct device *self, void *aux)
78 {
79 	struct aplpwm_softc *sc = (struct aplpwm_softc *)self;
80 	struct fdt_attach_args *faa = aux;
81 
82 	if (faa->fa_nreg < 1) {
83 		printf(": no registers\n");
84 		return;
85 	}
86 
87 	sc->sc_iot = faa->fa_iot;
88 	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
89 	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
90 		printf(": can't map registers\n");
91 		return;
92 	}
93 
94 	sc->sc_clkin = clock_get_frequency(faa->fa_node, NULL);
95 	if (sc->sc_clkin == 0) {
96 		printf(": no clock\n");
97 		return;
98 	}
99 
100 	printf("\n");
101 
102 	power_domain_enable(faa->fa_node);
103 
104 	sc->sc_pd.pd_node = faa->fa_node;
105 	sc->sc_pd.pd_cookie = sc;
106 	sc->sc_pd.pd_get_state = aplpwm_get_state;
107 	sc->sc_pd.pd_set_state = aplpwm_set_state;
108 	pwm_register(&sc->sc_pd);
109 }
110 
111 int
112 aplpwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
113 {
114 	struct aplpwm_softc *sc = cookie;
115 	uint64_t on_cycles, off_cycles;
116 	uint32_t ctrl;
117 
118 	ctrl = HREAD4(sc, PWM_CTRL);
119 	on_cycles = HREAD4(sc, PWM_ON_CYCLES);
120 	off_cycles = HREAD4(sc, PWM_OFF_CYCLES);
121 
122 	memset(ps, 0, sizeof(struct pwm_state));
123 	ps->ps_period = ((on_cycles + off_cycles) * NS_PER_S) / sc->sc_clkin;
124 	ps->ps_pulse_width = (on_cycles * NS_PER_S) / sc->sc_clkin;
125 	if ((ctrl & PWM_CTRL_EN) && (ctrl & PWM_CTRL_OUTPUT_EN))
126 		ps->ps_enabled = 1;
127 
128 	return 0;
129 }
130 
131 int
132 aplpwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
133 {
134 	struct aplpwm_softc *sc = cookie;
135 	uint64_t cycles, on_cycles, off_cycles;
136 	uint32_t ctrl;
137 
138 	if (ps->ps_pulse_width > ps->ps_period)
139 		return EINVAL;
140 
141 	cycles = (ps->ps_period * sc->sc_clkin) / NS_PER_S;
142 	on_cycles = (ps->ps_pulse_width * sc->sc_clkin) / NS_PER_S;
143 	off_cycles = cycles - on_cycles;
144 	if (on_cycles > UINT32_MAX || off_cycles > UINT32_MAX)
145 		return EINVAL;
146 
147 	if (ps->ps_enabled)
148 		ctrl = PWM_CTRL_EN | PWM_CTRL_OUTPUT_EN | PWM_CTRL_UPDATE;
149 	else
150 		ctrl = 0;
151 
152 	HWRITE4(sc, PWM_ON_CYCLES, on_cycles);
153 	HWRITE4(sc, PWM_OFF_CYCLES, off_cycles);
154 	HWRITE4(sc, PWM_CTRL, ctrl);
155 
156 	return 0;
157 }
158