1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2018 Emmanuel Vadot <manu@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 #include "opt_platform.h" 30 31 #include <sys/param.h> 32 #include <sys/systm.h> 33 #include <sys/bus.h> 34 #include <sys/conf.h> 35 #include <sys/kernel.h> 36 #include <sys/module.h> 37 #include <sys/time.h> 38 39 #include <dev/pwm/pwmbus.h> 40 #include <dev/pwm/pwmc.h> 41 42 #include "pwmbus_if.h" 43 44 #ifdef FDT 45 #include <dev/ofw/openfirm.h> 46 #include <dev/ofw/ofw_bus.h> 47 #include <dev/ofw/ofw_bus_subr.h> 48 49 static struct ofw_compat_data compat_data[] = { 50 {"freebsd,pwmc", true}, 51 {NULL, false}, 52 }; 53 54 PWMBUS_FDT_PNP_INFO(compat_data); 55 56 #endif 57 58 struct pwmc_softc { 59 device_t dev; 60 struct cdev *cdev; 61 u_int chan; 62 }; 63 64 static int 65 pwm_ioctl(struct cdev *dev, u_long cmd, caddr_t data, 66 int fflag, struct thread *td) 67 { 68 struct pwmc_softc *sc; 69 struct pwm_state state; 70 device_t bus; 71 int rv = 0; 72 73 sc = dev->si_drv1; 74 bus = device_get_parent(sc->dev); 75 76 switch (cmd) { 77 case PWMSETSTATE: 78 bcopy(data, &state, sizeof(state)); 79 rv = PWMBUS_CHANNEL_CONFIG(bus, sc->chan, 80 state.period, state.duty); 81 if (rv != 0) 82 return (rv); 83 84 rv = PWMBUS_CHANNEL_SET_FLAGS(bus, 85 sc->chan, state.flags); 86 if (rv != 0 && rv != EOPNOTSUPP) 87 return (rv); 88 89 rv = PWMBUS_CHANNEL_ENABLE(bus, sc->chan, 90 state.enable); 91 break; 92 case PWMGETSTATE: 93 bcopy(data, &state, sizeof(state)); 94 rv = PWMBUS_CHANNEL_GET_CONFIG(bus, sc->chan, 95 &state.period, &state.duty); 96 if (rv != 0) 97 return (rv); 98 99 rv = PWMBUS_CHANNEL_GET_FLAGS(bus, sc->chan, 100 &state.flags); 101 if (rv != 0) 102 return (rv); 103 104 rv = PWMBUS_CHANNEL_IS_ENABLED(bus, sc->chan, 105 &state.enable); 106 if (rv != 0) 107 return (rv); 108 bcopy(&state, data, sizeof(state)); 109 break; 110 } 111 112 return (rv); 113 } 114 115 static struct cdevsw pwm_cdevsw = { 116 .d_version = D_VERSION, 117 .d_name = "pwmc", 118 .d_ioctl = pwm_ioctl 119 }; 120 121 static void 122 pwmc_setup_label(struct pwmc_softc *sc) 123 { 124 const char *hintlabel; 125 #ifdef FDT 126 void *label; 127 128 if (OF_getprop_alloc(ofw_bus_get_node(sc->dev), "label", &label) > 0) { 129 make_dev_alias(sc->cdev, "pwm/%s", (char *)label); 130 OF_prop_free(label); 131 } 132 #endif 133 134 if (resource_string_value(device_get_name(sc->dev), 135 device_get_unit(sc->dev), "label", &hintlabel) == 0) { 136 make_dev_alias(sc->cdev, "pwm/%s", hintlabel); 137 } 138 } 139 140 static int 141 pwmc_probe(device_t dev) 142 { 143 int rv; 144 145 rv = BUS_PROBE_NOWILDCARD; 146 147 #ifdef FDT 148 if (!ofw_bus_status_okay(dev)) 149 return (ENXIO); 150 151 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { 152 rv = BUS_PROBE_DEFAULT; 153 } 154 #endif 155 156 device_set_desc(dev, "PWM Control"); 157 return (rv); 158 } 159 160 static int 161 pwmc_attach(device_t dev) 162 { 163 struct pwmc_softc *sc; 164 struct make_dev_args args; 165 int error; 166 167 sc = device_get_softc(dev); 168 sc->dev = dev; 169 170 if ((error = pwmbus_get_channel(dev, &sc->chan)) != 0) 171 return (error); 172 173 make_dev_args_init(&args); 174 args.mda_flags = MAKEDEV_CHECKNAME | MAKEDEV_WAITOK; 175 args.mda_devsw = &pwm_cdevsw; 176 args.mda_uid = UID_ROOT; 177 args.mda_gid = GID_OPERATOR; 178 args.mda_mode = 0660; 179 args.mda_si_drv1 = sc; 180 error = make_dev_s(&args, &sc->cdev, "pwm/pwmc%d.%d", 181 device_get_unit(device_get_parent(dev)), sc->chan); 182 if (error != 0) { 183 device_printf(dev, "Failed to make PWM device\n"); 184 return (error); 185 } 186 187 pwmc_setup_label(sc); 188 189 return (0); 190 } 191 192 static int 193 pwmc_detach(device_t dev) 194 { 195 struct pwmc_softc *sc; 196 197 sc = device_get_softc(dev); 198 destroy_dev(sc->cdev); 199 200 return (0); 201 } 202 203 static device_method_t pwmc_methods[] = { 204 /* device_if */ 205 DEVMETHOD(device_probe, pwmc_probe), 206 DEVMETHOD(device_attach, pwmc_attach), 207 DEVMETHOD(device_detach, pwmc_detach), 208 209 DEVMETHOD_END 210 }; 211 212 static driver_t pwmc_driver = { 213 "pwmc", 214 pwmc_methods, 215 sizeof(struct pwmc_softc), 216 }; 217 218 DRIVER_MODULE(pwmc, pwmbus, pwmc_driver, 0, 0); 219 MODULE_DEPEND(pwmc, pwmbus, 1, 1, 1); 220 MODULE_VERSION(pwmc, 1); 221