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