diff options
author | grembo <grembo@FreeBSD.org> | 2015-04-25 16:15:01 +0000 |
---|---|---|
committer | grembo <grembo@FreeBSD.org> | 2015-04-25 16:15:01 +0000 |
commit | 9da00f2a97fc238a88291c3bb9b68c9d911c202f (patch) | |
tree | ba84fdf08fc09b66a10a6a8cbe9cb4d12560861d /sys/dev/smbus | |
parent | c60c12803ebf064604c76f5070edeef1202a507a (diff) | |
download | FreeBSD-src-9da00f2a97fc238a88291c3bb9b68c9d911c202f.zip FreeBSD-src-9da00f2a97fc238a88291c3bb9b68c9d911c202f.tar.gz |
Expand SMBUS API to add smbus_trans() function.
Differential Revision: https://reviews.freebsd.org/D1955
Reviewed by: adrian, jhb, wblock
Approved by: adrian, jhb
Diffstat (limited to 'sys/dev/smbus')
-rw-r--r-- | sys/dev/smbus/smb.c | 148 | ||||
-rw-r--r-- | sys/dev/smbus/smb.h | 37 | ||||
-rw-r--r-- | sys/dev/smbus/smbconf.h | 33 | ||||
-rw-r--r-- | sys/dev/smbus/smbus.c | 88 | ||||
-rw-r--r-- | sys/dev/smbus/smbus.h | 4 | ||||
-rw-r--r-- | sys/dev/smbus/smbus_if.m | 17 |
6 files changed, 251 insertions, 76 deletions
diff --git a/sys/dev/smbus/smb.c b/sys/dev/smbus/smb.c index 579204d..e842441 100644 --- a/sys/dev/smbus/smb.c +++ b/sys/dev/smbus/smb.c @@ -26,10 +26,6 @@ * $FreeBSD$ */ -#ifdef HAVE_KERNEL_OPTION_HEADERS -#include "opt_compat.h" -#endif - #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> @@ -104,19 +100,24 @@ smb_identify(driver_t *driver, device_t parent) static int smb_probe(device_t dev) { - device_set_desc(dev, "SMBus generic I/O"); + if (smbus_get_addr(dev) != -1) + return (ENXIO); - return (0); + device_set_desc(dev, "SMBus generic I/O"); + return (BUS_PROBE_NOWILDCARD); } static int smb_attach(device_t dev) { struct smb_softc *sc = device_get_softc(dev); - + int unit; + + unit = device_get_unit(dev); sc->sc_dev = dev; - sc->sc_devnode = make_dev(&smb_cdevsw, device_get_unit(dev), - UID_ROOT, GID_WHEEL, 0600, "smb%d", device_get_unit(dev)); + + sc->sc_devnode = make_dev(&smb_cdevsw, unit, UID_ROOT, GID_WHEEL, + 0600, "smb%d", unit); sc->sc_devnode->si_drv1 = sc; mtx_init(&sc->sc_lock, device_get_nameunit(dev), NULL, MTX_DEF); @@ -174,9 +175,16 @@ smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *t struct smb_softc *sc = dev->si_drv1; device_t smbdev = sc->sc_dev; int error; - short w; - u_char count; - char c; + int unit; + u_char bcount; + + /* + * If a specific slave device is being used, override any passed-in + * slave. + */ + unit = dev2unit(dev); + if (unit & 0x0400) + s->slave = unit & 0x03ff; parent = device_get_parent(smbdev); @@ -208,77 +216,101 @@ smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *t case SMB_WRITEB: error = smbus_error(smbus_writeb(parent, s->slave, s->cmd, - s->data.byte)); + s->wdata.byte)); break; case SMB_WRITEW: error = smbus_error(smbus_writew(parent, s->slave, - s->cmd, s->data.word)); + s->cmd, s->wdata.word)); break; case SMB_READB: - if (s->data.byte_ptr) { - error = smbus_error(smbus_readb(parent, s->slave, - s->cmd, &c)); - if (error) - break; - error = copyout(&c, s->data.byte_ptr, - sizeof(*(s->data.byte_ptr))); + error = smbus_error(smbus_readb(parent, s->slave, s->cmd, + &s->rdata.byte)); + if (error) + break; + if (s->rbuf && s->rcount >= 1) { + error = copyout(&s->rdata.byte, s->rbuf, 1); + s->rcount = 1; } break; case SMB_READW: - if (s->data.word_ptr) { - error = smbus_error(smbus_readw(parent, s->slave, - s->cmd, &w)); - if (error == 0) { - error = copyout(&w, s->data.word_ptr, - sizeof(*(s->data.word_ptr))); - } + error = smbus_error(smbus_readw(parent, s->slave, s->cmd, + &s->rdata.word)); + if (error) + break; + if (s->rbuf && s->rcount >= 2) { + buf[0] = (u_char)s->rdata.word; + buf[1] = (u_char)(s->rdata.word >> 8); + error = copyout(buf, s->rbuf, 2); + s->rcount = 2; } break; case SMB_PCALL: - if (s->data.process.rdata) { - - error = smbus_error(smbus_pcall(parent, s->slave, s->cmd, - s->data.process.sdata, &w)); - if (error) - break; - error = copyout(&w, s->data.process.rdata, - sizeof(*(s->data.process.rdata))); + error = smbus_error(smbus_pcall(parent, s->slave, s->cmd, + s->wdata.word, &s->rdata.word)); + if (error) + break; + if (s->rbuf && s->rcount >= 2) { + buf[0] = (u_char)s->rdata.word; + buf[1] = (u_char)(s->rdata.word >> 8); + error = copyout(buf, s->rbuf, 2); + s->rcount = 2; } - + break; case SMB_BWRITE: - if (s->count && s->data.byte_ptr) { - if (s->count > SMB_MAXBLOCKSIZE) - s->count = SMB_MAXBLOCKSIZE; - error = copyin(s->data.byte_ptr, buf, s->count); - if (error) - break; - error = smbus_error(smbus_bwrite(parent, s->slave, - s->cmd, s->count, buf)); + if (s->wcount < 0) { + error = EINVAL; + break; } + if (s->wcount > SMB_MAXBLOCKSIZE) + s->wcount = SMB_MAXBLOCKSIZE; + if (s->wcount) + error = copyin(s->wbuf, buf, s->wcount); + if (error) + break; + error = smbus_error(smbus_bwrite(parent, s->slave, s->cmd, + s->wcount, buf)); break; -#if defined(COMPAT_FREEBSD4) || defined(COMPAT_FREEBSD5) || defined(COMPAT_FREEBSD6) - case SMB_OLD_BREAD: -#endif case SMB_BREAD: - if (s->count && s->data.byte_ptr) { - count = min(s->count, SMB_MAXBLOCKSIZE); - error = smbus_error(smbus_bread(parent, s->slave, - s->cmd, &count, buf)); - if (error) - break; - error = copyout(buf, s->data.byte_ptr, - min(count, s->count)); - s->count = count; + if (s->rcount < 0) { + error = EINVAL; + break; + } + if (s->rcount > SMB_MAXBLOCKSIZE) + s->rcount = SMB_MAXBLOCKSIZE; + error = smbus_error(smbus_bread(parent, s->slave, s->cmd, + &bcount, buf)); + if (error) + break; + if (s->rcount > bcount) + s->rcount = bcount; + error = copyout(buf, s->rbuf, s->rcount); + break; + + case SMB_TRANS: + if (s->rcount < 0 || s->wcount < 0) { + error = EINVAL; + break; } + if (s->rcount > SMB_MAXBLOCKSIZE) + s->rcount = SMB_MAXBLOCKSIZE; + if (s->wcount > SMB_MAXBLOCKSIZE) + s->wcount = SMB_MAXBLOCKSIZE; + if (s->wcount) + error = copyin(s->wbuf, buf, s->wcount); + if (error) + break; + error = smbus_error(smbus_trans(parent, s->slave, s->cmd, + s->op, buf, s->wcount, buf, s->rcount, &s->rcount)); + if (error == 0) + error = copyout(buf, s->rbuf, s->rcount); break; - default: error = ENOTTY; } diff --git a/sys/dev/smbus/smb.h b/sys/dev/smbus/smb.h index 32347f8..8007653 100644 --- a/sys/dev/smbus/smb.h +++ b/sys/dev/smbus/smb.h @@ -32,27 +32,33 @@ #include <sys/ioccom.h> struct smbcmd { - char cmd; - int count; - u_char slave; + u_char cmd; + u_char reserved; + u_short op; union { - char byte; - short word; - - char *byte_ptr; - short *word_ptr; - - struct { - short sdata; - short *rdata; - } process; - } data; + char byte; + char buf[2]; + short word; + } wdata; + union { + char byte; + char buf[2]; + short word; + } rdata; + int slave; + char *wbuf; /* use wdata if NULL */ + int wcount; + char *rbuf; /* use rdata if NULL */ + int rcount; }; /* * SMBus spec 2.0 says block transfers may be at most 32 bytes. + * We use SMBus for i2c as well, make the size limit something more + * reasonable. Keep in mind that a char buf array is declared on the + * kernel stack. */ -#define SMB_MAXBLOCKSIZE 32 +#define SMB_MAXBLOCKSIZE 1024 #define SMB_QUICK_WRITE _IOW('i', 1, struct smbcmd) #define SMB_QUICK_READ _IOW('i', 2, struct smbcmd) @@ -66,5 +72,6 @@ struct smbcmd { #define SMB_BWRITE _IOW('i', 10, struct smbcmd) #define SMB_OLD_BREAD _IOW('i', 11, struct smbcmd) #define SMB_BREAD _IOWR('i', 11, struct smbcmd) +#define SMB_TRANS _IOWR('i', 12, struct smbcmd) #endif diff --git a/sys/dev/smbus/smbconf.h b/sys/dev/smbus/smbconf.h index a3d403d..f39c442 100644 --- a/sys/dev/smbus/smbconf.h +++ b/sys/dev/smbus/smbconf.h @@ -68,9 +68,30 @@ #define SMB_QREAD 0x1 /* + * smbus transction op with pass-thru capabilities + * + * This smbus function is capable of doing a smbus command transaction + * (read or write), and can be flagged to not issue the 'cmd' and/or + * issue or expect a count field as well as flagged for chaining (no STOP), + * which gives it an i2c pass-through capability. + * + * NOSTOP- Caller chaining transactions, do not issue STOP + * NOCMD- Do not transmit the command field + * NOCNT- Do not transmit (wr) or expect (rd) the count field + */ +#define SMB_TRANS_NOSTOP 0x0001 /* do not send STOP at end */ +#define SMB_TRANS_NOCMD 0x0002 /* ignore cmd field (do not tx) */ +#define SMB_TRANS_NOCNT 0x0004 /* do not tx or rx count field */ +#define SMB_TRANS_7BIT 0x0008 /* change address mode to 7-bit */ +#define SMB_TRANS_10BIT 0x0010 /* change address mode to 10-bit */ +#define SMB_TRANS_NOREPORT 0x0020 /* do not report errors */ + +/* * ivars codes */ -#define SMBUS_IVAR_ADDR 0x1 /* slave address of the device */ +enum smbus_ivars { + SMBUS_IVAR_ADDR, /* slave address of the device */ +}; int smbus_request_bus(device_t, device_t, int); int smbus_release_bus(device_t, device_t); @@ -79,7 +100,12 @@ int smbus_error(int error); void smbus_intr(device_t, u_char, char low, char high, int error); -u_char smbus_get_addr(device_t); +#define SMBUS_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(smbus, var, SMBUS, ivar, type) + +SMBUS_ACCESSOR(addr, ADDR, int) + +#undef SMBUS_ACCESSOR extern driver_t smbus_driver; extern devclass_t smbus_devclass; @@ -104,6 +130,9 @@ extern devclass_t smbus_devclass; (SMBUS_BWRITE(device_get_parent(bus), slave, cmd, count, buf)) #define smbus_bread(bus,slave,cmd,count,buf) \ (SMBUS_BREAD(device_get_parent(bus), slave, cmd, count, buf)) +#define smbus_trans(bus,slave,cmd,op,wbuf,wcount,rbuf,rcount,actualp) \ + (SMBUS_TRANS(device_get_parent(bus), slave, cmd, op, \ + wbuf, wcount, rbuf, rcount, actualp)) #define SMBUS_MODVER 1 #define SMBUS_MINVER 1 diff --git a/sys/dev/smbus/smbus.c b/sys/dev/smbus/smbus.c index a111332..389efac 100644 --- a/sys/dev/smbus/smbus.c +++ b/sys/dev/smbus/smbus.c @@ -33,11 +33,15 @@ __FBSDID("$FreeBSD$"); #include <sys/lock.h> #include <sys/module.h> #include <sys/mutex.h> -#include <sys/bus.h> +#include <sys/bus.h> #include <dev/smbus/smbconf.h> #include <dev/smbus/smbus.h> +#include "smbus_if.h" +#include "bus_if.h" + + /* * Autoconfiguration and support routines for System Management bus */ @@ -49,6 +53,13 @@ static int smbus_probe(device_t); static int smbus_attach(device_t); static int smbus_detach(device_t); +static int smbus_child_location_str(device_t parent, device_t child, + char *buf, size_t buflen); +static int smbus_print_child(device_t parent, device_t child); +static void smbus_probe_device(device_t dev, u_char* addr); +static int smbus_read_ivar(device_t parent, device_t child, int which, + uintptr_t *result); + static device_method_t smbus_methods[] = { /* device interface */ DEVMETHOD(device_probe, smbus_probe), @@ -57,6 +68,10 @@ static device_method_t smbus_methods[] = { /* bus interface */ DEVMETHOD(bus_add_child, bus_generic_add_child), + DEVMETHOD(bus_child_location_str, smbus_child_location_str), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + DEVMETHOD(bus_print_child, smbus_print_child), + DEVMETHOD(bus_read_ivar, smbus_read_ivar), DEVMETHOD_END }; @@ -87,9 +102,14 @@ static int smbus_attach(device_t dev) { struct smbus_softc *sc = device_get_softc(dev); + unsigned char addr; mtx_init(&sc->lock, device_get_nameunit(dev), "smbus", MTX_DEF); bus_generic_probe(dev); + for (addr = SMBUS_ADDR_MIN; addr < SMBUS_ADDR_MAX; ++addr) { + sc->addrs[addr] = addr; + smbus_probe_device(dev, &sc->addrs[addr]); + } bus_generic_attach(dev); return (0); @@ -114,4 +134,70 @@ smbus_generic_intr(device_t dev, u_char devaddr, char low, char high, int err) { } +static void +smbus_probe_device(device_t dev, u_char* addr) +{ + device_t child; + int error; + u_char cmd; + u_char buf[2]; + + cmd = 0x01; + error = smbus_trans(dev, *addr, cmd, + SMB_TRANS_NOCNT | SMB_TRANS_NOREPORT, + NULL, 0, buf, 1, NULL); + if (error == 0) { + if (bootverbose) + device_printf(dev, "Probed address 0x%02x\n", *addr); + child = device_add_child(dev, NULL, -1); + device_set_ivars(child, addr); + } +} + +static int +smbus_child_location_str(device_t parent, device_t child, char *buf, + size_t buflen) +{ + unsigned char *addr; + + addr = device_get_ivars(child); + if (addr) + snprintf(buf, buflen, "addr=0x%x", *addr); + else if (buflen) + buf[0] = 0; + return (0); +} + +static int +smbus_print_child(device_t parent, device_t child) +{ + unsigned char *addr; + int retval; + + addr = device_get_ivars(child); + retval = bus_print_child_header(parent, child); + if (addr) + retval += printf(" at addr 0x%x", *addr); + retval += bus_print_child_footer(parent, child); + + return (retval); +} + +static int +smbus_read_ivar(device_t parent, device_t child, int which, + uintptr_t *result) +{ + unsigned char *addr; + + addr = device_get_ivars(child); + switch (which) { + case SMBUS_IVAR_ADDR: + *result = (addr == NULL) ? -1 : *addr; + break; + default: + return (ENOENT); + } + return (0); +} + MODULE_VERSION(smbus, SMBUS_MODVER); diff --git a/sys/dev/smbus/smbus.h b/sys/dev/smbus/smbus.h index 5626835..2093aa0 100644 --- a/sys/dev/smbus/smbus.h +++ b/sys/dev/smbus/smbus.h @@ -29,9 +29,13 @@ #ifndef __SMBUS_H #define __SMBUS_H +#define SMBUS_ADDR_MIN 0x10 +#define SMBUS_ADDR_MAX 0x70 + struct smbus_softc { device_t owner; /* smbus owner device structure */ struct mtx lock; + unsigned char addrs[SMBUS_ADDR_MAX]; }; void smbus_generic_intr(device_t dev, u_char devaddr, char low, char high, int err); diff --git a/sys/dev/smbus/smbus_if.m b/sys/dev/smbus/smbus_if.m index d969e25..6a5acf5 100644 --- a/sys/dev/smbus/smbus_if.m +++ b/sys/dev/smbus/smbus_if.m @@ -149,3 +149,20 @@ METHOD int bread { u_char *count; char *buf; }; + +# +# SMB roll-up transaction with flags that also allow it to be +# used for (mostly) i2c pass-through and with 10-bit addresses. +# This function can be used to roll-up all of the above functions. +# +METHOD int trans { + device_t dev; + int slave; + char cmd; + int op; + char *wbuf; + int wcount; + char *rbuf; + int rcount; + int *actualp; +}; |