Expand SMBUS API to add smbus_trans() function.

Differential Revision:	https://reviews.freebsd.org/D1955
Reviewed by:	adrian, jhb, wblock
Approved by:	adrian, jhb
This commit is contained in:
grembo 2015-04-25 16:15:01 +00:00
parent c60c12803e
commit 9da00f2a97
8 changed files with 333 additions and 116 deletions

View File

@ -1,5 +1,6 @@
.\" Copyright (c) 1998, Nicolas Souchu
.\" Copyright (c) 2004, Joerg Wunsch
.\" Copyright (c) 2015, Michael Gmelin <freebsd@grem.de>
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
@ -25,7 +26,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd February 6, 2009
.Dd April 25, 2015
.Dt SMB 4
.Os
.Sh NAME
@ -49,21 +50,24 @@ as its argument.
#include <sys/types.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;
};
.Ed
.Pp
@ -107,14 +111,14 @@ The
command first sends the byte from the
.Fa cmd
field to the device, followed by the byte given in
.Fa data.byte .
.Fa wdata.byte .
.It Dv SMB_WRITEW Ta
The
.Em WriteWord
command first sends the byte from the
.Fa cmd
field to the device, followed by the word given in
.Fa data.word .
.Fa wdata.word .
Note that the SMBus byte-order is little-endian by definition.
.It Dv SMB_READB Ta
The
@ -123,8 +127,8 @@ command first sends the byte from the
.Fa cmd
field to the device, and then reads one byte of data from
the device.
The returned data will be stored in the location pointed to by
.Fa data.byte_ptr .
The returned data will be stored in
.Fa rdata.byte .
.It Dv SMB_READW Ta
The
.Em ReadWord
@ -132,29 +136,33 @@ command first sends the byte from the
.Fa cmd
field to the device, and then reads one word of data from
the device.
The returned data will be stored in the location pointed to by
.Fa data.word_ptr .
The returned data will be stored in
.Fa rdata.word .
.It Dv SMB_PCALL Ta
The
.Em ProcedureCall
command first sends the byte from the
.Fa cmd
field to the device, followed by the word provided in
.Fa data.process.sdata .
.Fa wdata.word .
It then reads one word of data from the device, and returns it
in the location pointed to by
.Fa data.process.rdata .
in
.Fa rdata.word .
.It Dv SMB_BWRITE Ta
The
.Em BlockWrite
command first sends the byte from the
.Fa cmd
field to the device, followed by
.Fa count
.Fa wcount
bytes of data that are taken from the buffer pointed to by
.Fa data.byte_ptr .
.Fa wbuf .
The SMBus specification mandates that no more than 32 bytes of
data can be transferred in a single block read or write command.
data can be transferred in a single block read or write command,
but since
.Xr smbus 4
is also used to access I2C devices, the limit has been increased
to 1024.
This value is available in the constant
.Dv SMB_MAXBLOCKSIZE .
.It Dv SMB_BREAD Ta
@ -163,10 +171,38 @@ The
command first sends the byte from the
.Fa cmd
field to the device, and then reads
.Fa count
.Fa rcount
bytes of data that from the device.
These data will be returned in the buffer pointed to by
.Fa data.byte_ptr .
.Fa rbuf .
.It Dv SMB_TRANS Ta
The
.Em Trans
command sends an SMB roll-up transaction with flags that also allow it to
be used for (mostly) I2C pass-through and with with 10-bit addresses.
This function can be used to roll up all of the above functions.
It first sends the byte from the
.Fa cmd
field to the device, followed by
.Fa wcount
bytes of data that are taken from the buffer pointed to by
.Fa wbuf ,
then reads
.Fa rcount
bytes of data that from the device.
These data will be returned in the buffer pointed to by
.Fa rbuf .
.Pp
The following flags are allowed in
.Fa op :
.Pp
.Bd -literal -compact
SMB_TRANS_NOSTOP Do not send STOP at end
SMB_TRANS_NOCMD Ignore cmd field (do not tx)
SMB_TRANS_NOCNT Do not tx or rx count field
SMB_TRANS_7BIT Change address mode to 7-bit
SMB_TRANS_10BIT Change address mode to 10-bit
.Ed
.El
.Pp
The
@ -201,4 +237,7 @@ manual page first appeared in
.Sh AUTHORS
This
manual page was written by
.An Nicolas Souchu .
.An Nicolas Souchu
and extended by
.An Michael Gmelin Aq freebsd@grem.de
.

View File

@ -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;
}

View File

@ -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

View File

@ -67,10 +67,31 @@
#define SMB_QWRITE 0x0
#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

View File

@ -38,6 +38,10 @@ __FBSDID("$FreeBSD$");
#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);

View File

@ -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);

View File

@ -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;
};

View File

@ -163,7 +163,8 @@ do_io(void)
}
if (iflag == 1 && oflag == -1) {
/* command + 1 byte input: read byte op. */
c.data.byte_ptr = ibuf;
c.rbuf = ibuf;
c.rcount = iflag;
if (ioctl(fd, SMB_READB, &c) == -1)
return (-1);
printf(fmt, (int)(unsigned char)ibuf[0]);
@ -171,11 +172,12 @@ do_io(void)
return (0);
} else if (iflag == -1 && oflag == 1) {
/* command + 1 byte output: write byte op. */
c.data.byte = obuf[0];
c.wdata.byte = obuf[0];
return (ioctl(fd, SMB_WRITEB, &c));
} else if (wflag && iflag == 2 && oflag == -1) {
/* command + 2 bytes input: read word op. */
c.data.word_ptr = &iword;
c.rbuf = (char*) &iword;
c.rcount = iflag;
if (ioctl(fd, SMB_READW, &c) == -1)
return (-1);
printf(fmt, (int)(unsigned short)iword);
@ -183,15 +185,16 @@ do_io(void)
return (0);
} else if (wflag && iflag == -1 && oflag == 2) {
/* command + 2 bytes output: write word op. */
c.data.word = oword;
c.wdata.word = oword;
return (ioctl(fd, SMB_WRITEW, &c));
} else if (wflag && iflag == 2 && oflag == 2) {
/*
* command + 2 bytes output + 2 bytes input:
* "process call" op.
*/
c.data.process.sdata = oword;
c.data.process.rdata = &iword;
c.wdata.word = oword;
c.rbuf = (char*) &iword;
c.rcount = iflag;
if (ioctl(fd, SMB_PCALL, &c) == -1)
return (-1);
printf(fmt, (int)(unsigned short)iword);
@ -199,8 +202,8 @@ do_io(void)
return (0);
} else if (iflag > 1 && oflag == -1) {
/* command + > 1 bytes of input: block read */
c.data.byte_ptr = ibuf;
c.count = iflag;
c.rbuf = ibuf;
c.rcount = iflag;
if (ioctl(fd, SMB_BREAD, &c) == -1)
return (-1);
for (i = 0; i < iflag; i++) {
@ -212,8 +215,8 @@ do_io(void)
return (0);
} else if (iflag == -1 && oflag > 1) {
/* command + > 1 bytes of output: block write */
c.data.byte_ptr = obuf;
c.count = oflag;
c.wbuf = obuf;
c.wcount = oflag;
return (ioctl(fd, SMB_BWRITE, &c));
}