1d70424edSNicolas Souchu /*- 2c17d4340SNicolas Souchu * Copyright (c) 1998, 2001 Nicolas Souchu 3d70424edSNicolas Souchu * All rights reserved. 4d70424edSNicolas Souchu * 5d70424edSNicolas Souchu * Redistribution and use in source and binary forms, with or without 6d70424edSNicolas Souchu * modification, are permitted provided that the following conditions 7d70424edSNicolas Souchu * are met: 8d70424edSNicolas Souchu * 1. Redistributions of source code must retain the above copyright 9d70424edSNicolas Souchu * notice, this list of conditions and the following disclaimer. 10d70424edSNicolas Souchu * 2. Redistributions in binary form must reproduce the above copyright 11d70424edSNicolas Souchu * notice, this list of conditions and the following disclaimer in the 12d70424edSNicolas Souchu * documentation and/or other materials provided with the distribution. 13d70424edSNicolas Souchu * 14d70424edSNicolas Souchu * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15d70424edSNicolas Souchu * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16d70424edSNicolas Souchu * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17d70424edSNicolas Souchu * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18d70424edSNicolas Souchu * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19d70424edSNicolas Souchu * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20d70424edSNicolas Souchu * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21d70424edSNicolas Souchu * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22d70424edSNicolas Souchu * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23d70424edSNicolas Souchu * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24d70424edSNicolas Souchu * SUCH DAMAGE. 25d70424edSNicolas Souchu * 26c3aac50fSPeter Wemm * $FreeBSD$ 27d70424edSNicolas Souchu */ 28b3dd2b7bSJoerg Wunsch 29d70424edSNicolas Souchu #include <sys/param.h> 30d70424edSNicolas Souchu #include <sys/kernel.h> 31d70424edSNicolas Souchu #include <sys/systm.h> 32d70424edSNicolas Souchu #include <sys/module.h> 33d70424edSNicolas Souchu #include <sys/bus.h> 34d70424edSNicolas Souchu #include <sys/conf.h> 35d70424edSNicolas Souchu #include <sys/uio.h> 36ba81c311SNicolas Souchu #include <sys/fcntl.h> 37d70424edSNicolas Souchu 38d70424edSNicolas Souchu #include <dev/smbus/smbconf.h> 39d70424edSNicolas Souchu #include <dev/smbus/smbus.h> 40531facbaSPeter Wemm #include <dev/smbus/smb.h> 41d70424edSNicolas Souchu 42d70424edSNicolas Souchu #include "smbus_if.h" 43d70424edSNicolas Souchu 44d70424edSNicolas Souchu #define BUFSIZE 1024 45d70424edSNicolas Souchu 46d70424edSNicolas Souchu struct smb_softc { 47697218c9SJohn Baldwin device_t sc_dev; 48d70424edSNicolas Souchu int sc_count; /* >0 if device opened */ 4989c9c53dSPoul-Henning Kamp struct cdev *sc_devnode; 50697218c9SJohn Baldwin struct mtx sc_lock; 51d70424edSNicolas Souchu }; 52d70424edSNicolas Souchu 537048a99cSJohn Baldwin static void smb_identify(driver_t *driver, device_t parent); 54d70424edSNicolas Souchu static int smb_probe(device_t); 55d70424edSNicolas Souchu static int smb_attach(device_t); 56c17d4340SNicolas Souchu static int smb_detach(device_t); 57d70424edSNicolas Souchu 58d70424edSNicolas Souchu static devclass_t smb_devclass; 59d70424edSNicolas Souchu 60d70424edSNicolas Souchu static device_method_t smb_methods[] = { 61d70424edSNicolas Souchu /* device interface */ 627048a99cSJohn Baldwin DEVMETHOD(device_identify, smb_identify), 63d70424edSNicolas Souchu DEVMETHOD(device_probe, smb_probe), 64d70424edSNicolas Souchu DEVMETHOD(device_attach, smb_attach), 65c17d4340SNicolas Souchu DEVMETHOD(device_detach, smb_detach), 66d70424edSNicolas Souchu 67d70424edSNicolas Souchu /* smbus interface */ 68d70424edSNicolas Souchu DEVMETHOD(smbus_intr, smbus_generic_intr), 69d70424edSNicolas Souchu 70d70424edSNicolas Souchu { 0, 0 } 71d70424edSNicolas Souchu }; 72d70424edSNicolas Souchu 73d70424edSNicolas Souchu static driver_t smb_driver = { 74d70424edSNicolas Souchu "smb", 75d70424edSNicolas Souchu smb_methods, 76d70424edSNicolas Souchu sizeof(struct smb_softc), 77d70424edSNicolas Souchu }; 78d70424edSNicolas Souchu 79d70424edSNicolas Souchu static d_open_t smbopen; 80d70424edSNicolas Souchu static d_close_t smbclose; 81d70424edSNicolas Souchu static d_ioctl_t smbioctl; 82d70424edSNicolas Souchu 834e2f199eSPoul-Henning Kamp static struct cdevsw smb_cdevsw = { 84dc08ffecSPoul-Henning Kamp .d_version = D_VERSION, 85697218c9SJohn Baldwin .d_flags = D_TRACKCLOSE, 867ac40f5fSPoul-Henning Kamp .d_open = smbopen, 877ac40f5fSPoul-Henning Kamp .d_close = smbclose, 887ac40f5fSPoul-Henning Kamp .d_ioctl = smbioctl, 897ac40f5fSPoul-Henning Kamp .d_name = "smb", 904e2f199eSPoul-Henning Kamp }; 91d70424edSNicolas Souchu 927048a99cSJohn Baldwin static void 937048a99cSJohn Baldwin smb_identify(driver_t *driver, device_t parent) 947048a99cSJohn Baldwin { 957048a99cSJohn Baldwin 967048a99cSJohn Baldwin if (device_find_child(parent, "smb", -1) == NULL) 977048a99cSJohn Baldwin BUS_ADD_CHILD(parent, 0, "smb", -1); 987048a99cSJohn Baldwin } 997048a99cSJohn Baldwin 100d70424edSNicolas Souchu static int 101d70424edSNicolas Souchu smb_probe(device_t dev) 102d70424edSNicolas Souchu { 103*202379afSMichael Gmelin if (smbus_get_addr(dev) != -1) 104*202379afSMichael Gmelin return (ENXIO); 105d70424edSNicolas Souchu 106*202379afSMichael Gmelin device_set_desc(dev, "SMBus generic I/O"); 107*202379afSMichael Gmelin return (BUS_PROBE_NOWILDCARD); 108d70424edSNicolas Souchu } 109d70424edSNicolas Souchu 110d70424edSNicolas Souchu static int 111d70424edSNicolas Souchu smb_attach(device_t dev) 112d70424edSNicolas Souchu { 113697218c9SJohn Baldwin struct smb_softc *sc = device_get_softc(dev); 114*202379afSMichael Gmelin int unit; 115c17d4340SNicolas Souchu 116*202379afSMichael Gmelin unit = device_get_unit(dev); 117697218c9SJohn Baldwin sc->sc_dev = dev; 118*202379afSMichael Gmelin 119*202379afSMichael Gmelin sc->sc_devnode = make_dev(&smb_cdevsw, unit, UID_ROOT, GID_WHEEL, 120*202379afSMichael Gmelin 0600, "smb%d", unit); 121697218c9SJohn Baldwin sc->sc_devnode->si_drv1 = sc; 122697218c9SJohn Baldwin mtx_init(&sc->sc_lock, device_get_nameunit(dev), NULL, MTX_DEF); 123c17d4340SNicolas Souchu 124c17d4340SNicolas Souchu return (0); 125c17d4340SNicolas Souchu } 126c17d4340SNicolas Souchu 127c17d4340SNicolas Souchu static int 128c17d4340SNicolas Souchu smb_detach(device_t dev) 129c17d4340SNicolas Souchu { 130c17d4340SNicolas Souchu struct smb_softc *sc = (struct smb_softc *)device_get_softc(dev); 131c17d4340SNicolas Souchu 132c17d4340SNicolas Souchu if (sc->sc_devnode) 133c17d4340SNicolas Souchu destroy_dev(sc->sc_devnode); 134697218c9SJohn Baldwin mtx_destroy(&sc->sc_lock); 135c17d4340SNicolas Souchu 136d70424edSNicolas Souchu return (0); 137d70424edSNicolas Souchu } 138d70424edSNicolas Souchu 139d70424edSNicolas Souchu static int 14089c9c53dSPoul-Henning Kamp smbopen(struct cdev *dev, int flags, int fmt, struct thread *td) 141d70424edSNicolas Souchu { 142697218c9SJohn Baldwin struct smb_softc *sc = dev->si_drv1; 143d70424edSNicolas Souchu 144697218c9SJohn Baldwin mtx_lock(&sc->sc_lock); 145697218c9SJohn Baldwin if (sc->sc_count != 0) { 146697218c9SJohn Baldwin mtx_unlock(&sc->sc_lock); 147d70424edSNicolas Souchu return (EBUSY); 148697218c9SJohn Baldwin } 149d70424edSNicolas Souchu 150d70424edSNicolas Souchu sc->sc_count++; 151697218c9SJohn Baldwin mtx_unlock(&sc->sc_lock); 152d70424edSNicolas Souchu 153d70424edSNicolas Souchu return (0); 154d70424edSNicolas Souchu } 155d70424edSNicolas Souchu 156d70424edSNicolas Souchu static int 15789c9c53dSPoul-Henning Kamp smbclose(struct cdev *dev, int flags, int fmt, struct thread *td) 158d70424edSNicolas Souchu { 159697218c9SJohn Baldwin struct smb_softc *sc = dev->si_drv1; 160d70424edSNicolas Souchu 161697218c9SJohn Baldwin mtx_lock(&sc->sc_lock); 162697218c9SJohn Baldwin KASSERT(sc->sc_count == 1, ("device not busy")); 163d70424edSNicolas Souchu sc->sc_count--; 164697218c9SJohn Baldwin mtx_unlock(&sc->sc_lock); 165d70424edSNicolas Souchu 166d70424edSNicolas Souchu return (0); 167d70424edSNicolas Souchu } 168d70424edSNicolas Souchu 169d70424edSNicolas Souchu static int 17089c9c53dSPoul-Henning Kamp smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) 171d70424edSNicolas Souchu { 172add37e1eSJoerg Wunsch char buf[SMB_MAXBLOCKSIZE]; 173b3dd2b7bSJoerg Wunsch device_t parent; 174d70424edSNicolas Souchu struct smbcmd *s = (struct smbcmd *)data; 175697218c9SJohn Baldwin struct smb_softc *sc = dev->si_drv1; 176697218c9SJohn Baldwin device_t smbdev = sc->sc_dev; 177b3dd2b7bSJoerg Wunsch int error; 178*202379afSMichael Gmelin int unit; 179*202379afSMichael Gmelin u_char bcount; 180*202379afSMichael Gmelin 181*202379afSMichael Gmelin /* 182*202379afSMichael Gmelin * If a specific slave device is being used, override any passed-in 183*202379afSMichael Gmelin * slave. 184*202379afSMichael Gmelin */ 185*202379afSMichael Gmelin unit = dev2unit(dev); 186*202379afSMichael Gmelin if (unit & 0x0400) 187*202379afSMichael Gmelin s->slave = unit & 0x03ff; 188d70424edSNicolas Souchu 189b3dd2b7bSJoerg Wunsch parent = device_get_parent(smbdev); 190b3dd2b7bSJoerg Wunsch 191bb6bb7feSJohn Baldwin /* Make sure that LSB bit is cleared. */ 192bb6bb7feSJohn Baldwin if (s->slave & 0x1) 193bb6bb7feSJohn Baldwin return (EINVAL); 194bb6bb7feSJohn Baldwin 195b3dd2b7bSJoerg Wunsch /* Allocate the bus. */ 196ba81c311SNicolas Souchu if ((error = smbus_request_bus(parent, smbdev, 197ba81c311SNicolas Souchu (flags & O_NONBLOCK) ? SMB_DONTWAIT : (SMB_WAIT | SMB_INTR)))) 198ba81c311SNicolas Souchu return (error); 199ba81c311SNicolas Souchu 200d70424edSNicolas Souchu switch (cmd) { 201d70424edSNicolas Souchu case SMB_QUICK_WRITE: 2024012f363SNicolas Souchu error = smbus_error(smbus_quick(parent, s->slave, SMB_QWRITE)); 203ba81c311SNicolas Souchu break; 204d70424edSNicolas Souchu 205d70424edSNicolas Souchu case SMB_QUICK_READ: 2064012f363SNicolas Souchu error = smbus_error(smbus_quick(parent, s->slave, SMB_QREAD)); 207ba81c311SNicolas Souchu break; 208d70424edSNicolas Souchu 209d70424edSNicolas Souchu case SMB_SENDB: 2104012f363SNicolas Souchu error = smbus_error(smbus_sendb(parent, s->slave, s->cmd)); 211d70424edSNicolas Souchu break; 212d70424edSNicolas Souchu 213d70424edSNicolas Souchu case SMB_RECVB: 2144012f363SNicolas Souchu error = smbus_error(smbus_recvb(parent, s->slave, &s->cmd)); 215d70424edSNicolas Souchu break; 216d70424edSNicolas Souchu 217d70424edSNicolas Souchu case SMB_WRITEB: 2184012f363SNicolas Souchu error = smbus_error(smbus_writeb(parent, s->slave, s->cmd, 219*202379afSMichael Gmelin s->wdata.byte)); 220d70424edSNicolas Souchu break; 221d70424edSNicolas Souchu 222d70424edSNicolas Souchu case SMB_WRITEW: 2234012f363SNicolas Souchu error = smbus_error(smbus_writew(parent, s->slave, 224*202379afSMichael Gmelin s->cmd, s->wdata.word)); 225d70424edSNicolas Souchu break; 226d70424edSNicolas Souchu 227d70424edSNicolas Souchu case SMB_READB: 228*202379afSMichael Gmelin error = smbus_error(smbus_readb(parent, s->slave, s->cmd, 229*202379afSMichael Gmelin &s->rdata.byte)); 230add37e1eSJoerg Wunsch if (error) 231add37e1eSJoerg Wunsch break; 232*202379afSMichael Gmelin if (s->rbuf && s->rcount >= 1) { 233*202379afSMichael Gmelin error = copyout(&s->rdata.byte, s->rbuf, 1); 234*202379afSMichael Gmelin s->rcount = 1; 235add37e1eSJoerg Wunsch } 236d70424edSNicolas Souchu break; 237d70424edSNicolas Souchu 238d70424edSNicolas Souchu case SMB_READW: 239*202379afSMichael Gmelin error = smbus_error(smbus_readw(parent, s->slave, s->cmd, 240*202379afSMichael Gmelin &s->rdata.word)); 241*202379afSMichael Gmelin if (error) 242*202379afSMichael Gmelin break; 243*202379afSMichael Gmelin if (s->rbuf && s->rcount >= 2) { 244*202379afSMichael Gmelin buf[0] = (u_char)s->rdata.word; 245*202379afSMichael Gmelin buf[1] = (u_char)(s->rdata.word >> 8); 246*202379afSMichael Gmelin error = copyout(buf, s->rbuf, 2); 247*202379afSMichael Gmelin s->rcount = 2; 248add37e1eSJoerg Wunsch } 249d70424edSNicolas Souchu break; 250d70424edSNicolas Souchu 251d70424edSNicolas Souchu case SMB_PCALL: 2524012f363SNicolas Souchu error = smbus_error(smbus_pcall(parent, s->slave, s->cmd, 253*202379afSMichael Gmelin s->wdata.word, &s->rdata.word)); 254add37e1eSJoerg Wunsch if (error) 255add37e1eSJoerg Wunsch break; 256*202379afSMichael Gmelin if (s->rbuf && s->rcount >= 2) { 257*202379afSMichael Gmelin buf[0] = (u_char)s->rdata.word; 258*202379afSMichael Gmelin buf[1] = (u_char)(s->rdata.word >> 8); 259*202379afSMichael Gmelin error = copyout(buf, s->rbuf, 2); 260*202379afSMichael Gmelin s->rcount = 2; 261add37e1eSJoerg Wunsch } 262add37e1eSJoerg Wunsch 263d70424edSNicolas Souchu break; 264d70424edSNicolas Souchu 265d70424edSNicolas Souchu case SMB_BWRITE: 266*202379afSMichael Gmelin if (s->wcount < 0) { 267*202379afSMichael Gmelin error = EINVAL; 268*202379afSMichael Gmelin break; 269*202379afSMichael Gmelin } 270*202379afSMichael Gmelin if (s->wcount > SMB_MAXBLOCKSIZE) 271*202379afSMichael Gmelin s->wcount = SMB_MAXBLOCKSIZE; 272*202379afSMichael Gmelin if (s->wcount) 273*202379afSMichael Gmelin error = copyin(s->wbuf, buf, s->wcount); 274add37e1eSJoerg Wunsch if (error) 275add37e1eSJoerg Wunsch break; 276*202379afSMichael Gmelin error = smbus_error(smbus_bwrite(parent, s->slave, s->cmd, 277*202379afSMichael Gmelin s->wcount, buf)); 278d70424edSNicolas Souchu break; 279d70424edSNicolas Souchu 280d70424edSNicolas Souchu case SMB_BREAD: 281*202379afSMichael Gmelin if (s->rcount < 0) { 282*202379afSMichael Gmelin error = EINVAL; 283*202379afSMichael Gmelin break; 284*202379afSMichael Gmelin } 285*202379afSMichael Gmelin if (s->rcount > SMB_MAXBLOCKSIZE) 286*202379afSMichael Gmelin s->rcount = SMB_MAXBLOCKSIZE; 287*202379afSMichael Gmelin error = smbus_error(smbus_bread(parent, s->slave, s->cmd, 288*202379afSMichael Gmelin &bcount, buf)); 289add37e1eSJoerg Wunsch if (error) 290add37e1eSJoerg Wunsch break; 291*202379afSMichael Gmelin if (s->rcount > bcount) 292*202379afSMichael Gmelin s->rcount = bcount; 293*202379afSMichael Gmelin error = copyout(buf, s->rbuf, s->rcount); 294d70424edSNicolas Souchu break; 295d70424edSNicolas Souchu 296*202379afSMichael Gmelin case SMB_TRANS: 297*202379afSMichael Gmelin if (s->rcount < 0 || s->wcount < 0) { 298*202379afSMichael Gmelin error = EINVAL; 299*202379afSMichael Gmelin break; 300*202379afSMichael Gmelin } 301*202379afSMichael Gmelin if (s->rcount > SMB_MAXBLOCKSIZE) 302*202379afSMichael Gmelin s->rcount = SMB_MAXBLOCKSIZE; 303*202379afSMichael Gmelin if (s->wcount > SMB_MAXBLOCKSIZE) 304*202379afSMichael Gmelin s->wcount = SMB_MAXBLOCKSIZE; 305*202379afSMichael Gmelin if (s->wcount) 306*202379afSMichael Gmelin error = copyin(s->wbuf, buf, s->wcount); 307*202379afSMichael Gmelin if (error) 308*202379afSMichael Gmelin break; 309*202379afSMichael Gmelin error = smbus_error(smbus_trans(parent, s->slave, s->cmd, 310*202379afSMichael Gmelin s->op, buf, s->wcount, buf, s->rcount, &s->rcount)); 311*202379afSMichael Gmelin if (error == 0) 312*202379afSMichael Gmelin error = copyout(buf, s->rbuf, s->rcount); 313*202379afSMichael Gmelin break; 314d70424edSNicolas Souchu default: 315add37e1eSJoerg Wunsch error = ENOTTY; 316d70424edSNicolas Souchu } 317d70424edSNicolas Souchu 318ba81c311SNicolas Souchu smbus_release_bus(parent, smbdev); 319ba81c311SNicolas Souchu 320d70424edSNicolas Souchu return (error); 321d70424edSNicolas Souchu } 322d70424edSNicolas Souchu 323c6b92decSPeter Wemm DRIVER_MODULE(smb, smbus, smb_driver, smb_devclass, 0, 0); 324c17d4340SNicolas Souchu MODULE_DEPEND(smb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); 325c17d4340SNicolas Souchu MODULE_VERSION(smb, 1); 326