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