ufm.c revision 187970
1/*- 2 * Copyright (c) 2001 M. Warner Losh 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions, and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. 27 * This code includes software developed by the NetBSD Foundation, Inc. and 28 * its contributors. 29 */ 30 31#include <sys/cdefs.h> 32__FBSDID("$FreeBSD: head/sys/dev/usb2/misc/ufm2.c 187970 2009-02-01 00:51:25Z thompsa $"); 33 34 35#include <dev/usb2/include/usb2_devid.h> 36#include <dev/usb2/include/usb2_standard.h> 37#include <dev/usb2/include/usb2_mfunc.h> 38#include <dev/usb2/include/usb2_error.h> 39#include <dev/usb2/include/ufm2_ioctl.h> 40 41#define USB_DEBUG_VAR usb2_debug 42 43#include <dev/usb2/core/usb2_core.h> 44#include <dev/usb2/core/usb2_debug.h> 45#include <dev/usb2/core/usb2_process.h> 46#include <dev/usb2/core/usb2_request.h> 47#include <dev/usb2/core/usb2_lookup.h> 48#include <dev/usb2/core/usb2_util.h> 49#include <dev/usb2/core/usb2_busdma.h> 50#include <dev/usb2/core/usb2_mbuf.h> 51#include <dev/usb2/core/usb2_dev.h> 52 53#define UFM_CMD0 0x00 54#define UFM_CMD_SET_FREQ 0x01 55#define UFM_CMD2 0x02 56 57struct ufm_softc { 58 struct usb2_fifo_sc sc_fifo; 59 struct mtx sc_mtx; 60 61 struct usb2_device *sc_udev; 62 63 uint32_t sc_unit; 64 uint32_t sc_freq; 65 66 uint8_t sc_name[16]; 67}; 68 69/* prototypes */ 70 71static device_probe_t ufm_probe; 72static device_attach_t ufm_attach; 73static device_detach_t ufm_detach; 74 75static usb2_fifo_ioctl_t ufm_ioctl; 76static usb2_fifo_open_t ufm_open; 77 78static struct usb2_fifo_methods ufm_fifo_methods = { 79 .f_ioctl = &ufm_ioctl, 80 .f_open = &ufm_open, 81 .basename[0] = "ufm", 82}; 83 84static int ufm_do_req(struct ufm_softc *, uint8_t, uint16_t, uint16_t, 85 uint8_t *); 86static int ufm_set_freq(struct ufm_softc *, void *); 87static int ufm_get_freq(struct ufm_softc *, void *); 88static int ufm_start(struct ufm_softc *, void *); 89static int ufm_stop(struct ufm_softc *, void *); 90static int ufm_get_stat(struct ufm_softc *, void *); 91 92static devclass_t ufm_devclass; 93 94static device_method_t ufm_methods[] = { 95 DEVMETHOD(device_probe, ufm_probe), 96 DEVMETHOD(device_attach, ufm_attach), 97 DEVMETHOD(device_detach, ufm_detach), 98 {0, 0} 99}; 100 101static driver_t ufm_driver = { 102 .name = "ufm", 103 .methods = ufm_methods, 104 .size = sizeof(struct ufm_softc), 105}; 106 107MODULE_DEPEND(ufm, usb2_misc, 1, 1, 1); 108DRIVER_MODULE(ufm, ushub, ufm_driver, ufm_devclass, NULL, 0); 109MODULE_DEPEND(ufm, usb2_core, 1, 1, 1); 110 111static int 112ufm_probe(device_t dev) 113{ 114 struct usb2_attach_arg *uaa = device_get_ivars(dev); 115 116 if (uaa->usb2_mode != USB_MODE_HOST) { 117 return (ENXIO); 118 } 119 if ((uaa->info.idVendor == USB_VENDOR_CYPRESS) && 120 (uaa->info.idProduct == USB_PRODUCT_CYPRESS_FMRADIO)) { 121 return (0); 122 } 123 return (ENXIO); 124} 125 126static int 127ufm_attach(device_t dev) 128{ 129 struct usb2_attach_arg *uaa = device_get_ivars(dev); 130 struct ufm_softc *sc = device_get_softc(dev); 131 int error; 132 133 sc->sc_udev = uaa->device; 134 sc->sc_unit = device_get_unit(dev); 135 136 snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", 137 device_get_nameunit(dev)); 138 139 mtx_init(&sc->sc_mtx, "ufm lock", NULL, MTX_DEF | MTX_RECURSE); 140 141 device_set_usb2_desc(dev); 142 143 /* set interface permissions */ 144 usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, 145 UID_ROOT, GID_OPERATOR, 0644); 146 147 error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, 148 &ufm_fifo_methods, &sc->sc_fifo, 149 device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex); 150 if (error) { 151 goto detach; 152 } 153 return (0); /* success */ 154 155detach: 156 ufm_detach(dev); 157 return (ENXIO); 158} 159 160static int 161ufm_detach(device_t dev) 162{ 163 struct ufm_softc *sc = device_get_softc(dev); 164 165 usb2_fifo_detach(&sc->sc_fifo); 166 167 mtx_destroy(&sc->sc_mtx); 168 169 return (0); 170} 171 172static int 173ufm_open(struct usb2_fifo *dev, int fflags, struct thread *td) 174{ 175 if ((fflags & (FWRITE | FREAD)) != (FWRITE | FREAD)) { 176 return (EACCES); 177 } 178 return (0); 179} 180 181static int 182ufm_do_req(struct ufm_softc *sc, uint8_t request, 183 uint16_t value, uint16_t index, uint8_t *retbuf) 184{ 185 int error; 186 187 struct usb2_device_request req; 188 uint8_t buf[1]; 189 190 req.bmRequestType = UT_READ_VENDOR_DEVICE; 191 req.bRequest = request; 192 USETW(req.wValue, value); 193 USETW(req.wIndex, index); 194 USETW(req.wLength, 1); 195 196 error = usb2_do_request(sc->sc_udev, NULL, &req, buf); 197 198 if (retbuf) { 199 *retbuf = buf[0]; 200 } 201 if (error) { 202 return (ENXIO); 203 } 204 return (0); 205} 206 207static int 208ufm_set_freq(struct ufm_softc *sc, void *addr) 209{ 210 int freq = *(int *)addr; 211 212 /* 213 * Freq now is in Hz. We need to convert it to the frequency 214 * that the radio wants. This frequency is 10.7MHz above 215 * the actual frequency. We then need to convert to 216 * units of 12.5kHz. We add one to the IFM to make rounding 217 * easier. 218 */ 219 mtx_lock(&sc->sc_mtx); 220 sc->sc_freq = freq; 221 mtx_unlock(&sc->sc_mtx); 222 223 freq = (freq + 10700001) / 12500; 224 225 /* This appears to set the frequency */ 226 if (ufm_do_req(sc, UFM_CMD_SET_FREQ, 227 freq >> 8, freq, NULL) != 0) { 228 return (EIO); 229 } 230 /* Not sure what this does */ 231 if (ufm_do_req(sc, UFM_CMD0, 232 0x96, 0xb7, NULL) != 0) { 233 return (EIO); 234 } 235 return (0); 236} 237 238static int 239ufm_get_freq(struct ufm_softc *sc, void *addr) 240{ 241 int *valp = (int *)addr; 242 243 mtx_lock(&sc->sc_mtx); 244 *valp = sc->sc_freq; 245 mtx_unlock(&sc->sc_mtx); 246 return (0); 247} 248 249static int 250ufm_start(struct ufm_softc *sc, void *addr) 251{ 252 uint8_t ret; 253 254 if (ufm_do_req(sc, UFM_CMD0, 255 0x00, 0xc7, &ret)) { 256 return (EIO); 257 } 258 if (ufm_do_req(sc, UFM_CMD2, 259 0x01, 0x00, &ret)) { 260 return (EIO); 261 } 262 if (ret & 0x1) { 263 return (EIO); 264 } 265 return (0); 266} 267 268static int 269ufm_stop(struct ufm_softc *sc, void *addr) 270{ 271 if (ufm_do_req(sc, UFM_CMD0, 272 0x16, 0x1C, NULL)) { 273 return (EIO); 274 } 275 if (ufm_do_req(sc, UFM_CMD2, 276 0x00, 0x00, NULL)) { 277 return (EIO); 278 } 279 return (0); 280} 281 282static int 283ufm_get_stat(struct ufm_softc *sc, void *addr) 284{ 285 uint8_t ret; 286 287 /* 288 * Note, there's a 240ms settle time before the status 289 * will be valid, so sleep that amount. 290 */ 291 292 mtx_lock(&sc->sc_mtx); 293 usb2_pause_mtx(&sc->sc_mtx, USB_MS_HZ / 4); 294 mtx_unlock(&sc->sc_mtx); 295 296 if (ufm_do_req(sc, UFM_CMD0, 297 0x00, 0x24, &ret)) { 298 return (EIO); 299 } 300 *(int *)addr = ret; 301 302 return (0); 303} 304 305static int 306ufm_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, 307 int fflags, struct thread *td) 308{ 309 struct ufm_softc *sc = fifo->priv_sc0; 310 int error = 0; 311 312 switch (cmd) { 313 case FM_SET_FREQ: 314 error = ufm_set_freq(sc, addr); 315 break; 316 case FM_GET_FREQ: 317 error = ufm_get_freq(sc, addr); 318 break; 319 case FM_START: 320 error = ufm_start(sc, addr); 321 break; 322 case FM_STOP: 323 error = ufm_stop(sc, addr); 324 break; 325 case FM_GET_STAT: 326 error = ufm_get_stat(sc, addr); 327 break; 328 default: 329 error = ENOTTY; 330 break; 331 } 332 return (error); 333} 334