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
aplpwm_match(struct device * parent,void * match,void * aux)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
aplpwm_attach(struct device * parent,struct device * self,void * aux)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
aplpwm_get_state(void * cookie,uint32_t * cells,struct pwm_state * ps)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
aplpwm_set_state(void * cookie,uint32_t * cells,struct pwm_state * ps)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