Allow iic bridges to support a generalized transfer, rather than

forcing all transfers to do the start read/write stop by hand.  Some
smart bridges prefer this sort of operation, and this allows us to
support their features more easily.  When bridges don't support it, we
fall back to using the old-style opertaions.  Expand the ioctl
interface to expose this function.  Unlike the old-style interface,
this interface is thread safe, even on old bridges.
This commit is contained in:
Warner Losh 2006-07-14 23:15:06 +00:00
parent a9a5ae2d62
commit d7fac9732b
7 changed files with 117 additions and 12 deletions

View file

@ -240,8 +240,11 @@ iicioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *t
struct iic_softc *sc = IIC_SOFTC(minor(dev));
device_t parent = device_get_parent(iicdev);
struct iiccmd *s = (struct iiccmd *)data;
int error, count;
struct iic_rdwr_data *d = (struct iic_rdwr_data *)data;
struct iic_msg *m;
int error, count, i;
char *buf = NULL;
void **usrbufs = NULL;
if (!sc)
return (EINVAL);
@ -297,6 +300,30 @@ iicioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *t
error = copyout(buf, s->buf, s->count);
break;
case I2CRDWR:
buf = malloc(sizeof(*d->msgs) * d->nmsgs, M_TEMP, M_WAITOK);
usrbufs = malloc(sizeof(void *) * d->nmsgs, M_TEMP, M_ZERO | M_WAITOK);
error = copyin(d->msgs, buf, sizeof(*d->msgs) * d->nmsgs);
if (error)
break;
/* Allocate kernel buffers for userland data, copyin write data */
for (i = 0; i < d->nmsgs; i++) {
m = &((struct iic_msg *)buf)[i];
usrbufs[i] = m->buf;
m->buf = malloc(m->len, M_TEMP, M_WAITOK);
if (!(m->flags & IIC_M_RD))
copyin(usrbufs[i], m->buf, m->len);
}
error = iicbus_transfer(parent, (struct iic_msg *)buf, d->nmsgs);
/* Copyout all read segments, free up kernel buffers */
for (i = 0; i < d->nmsgs; i++) {
m = &((struct iic_msg *)buf)[i];
if (!(m->flags & IIC_M_RD))
copyout(m->buf, usrbufs[i], m->len);
free(m->buf, M_TEMP);
}
free(usrbufs, M_TEMP);
break;
default:
error = ENOTTY;
}

View file

@ -31,6 +31,16 @@
#include <sys/ioccom.h>
/* Designed to be compatible with linux's struct i2c_msg */
struct iic_msg
{
uint16_t slave;
uint16_t flags;
#define IIC_M_RD 0x0001 /* read vs write */
uint16_t len; /* msg legnth */
uint8_t * buf;
};
struct iiccmd {
u_char slave;
int count;
@ -38,10 +48,16 @@ struct iiccmd {
char *buf;
};
struct iic_rdwr_data {
struct iic_msg *msgs;
uint32_t nmsgs;
};
#define I2CSTART _IOW('i', 1, struct iiccmd) /* start condition */
#define I2CSTOP _IO('i', 2) /* stop condition */
#define I2CRSTCARD _IOW('i', 3, struct iiccmd) /* reset the card */
#define I2CWRITE _IOW('i', 4, struct iiccmd) /* send data */
#define I2CREAD _IOW('i', 5, struct iiccmd) /* receive data */
#define I2CRDWR _IOW('i', 6, struct iic_rdwr_data) /* General read/write interface */
#endif

View file

@ -45,7 +45,7 @@ __FBSDID("$FreeBSD$");
#define DEVTOIICBUS(dev) ((struct iicbus_device*)device_get_ivars(dev))
static devclass_t iicbus_devclass;
devclass_t iicbus_devclass;
/* See comments below for why auto-scanning is a bad idea. */
#define SCAN_IICBUS 0
@ -72,7 +72,7 @@ static device_method_t iicbus_methods[] = {
{ 0, 0 }
};
static driver_t iicbus_driver = {
driver_t iicbus_driver = {
"iicbus",
iicbus_methods,
sizeof(struct iicbus_softc),
@ -81,8 +81,8 @@ static driver_t iicbus_driver = {
static int
iicbus_probe(device_t dev)
{
device_set_desc(dev, "Philips I2C bus");
device_set_desc(dev, "Philips I2C bus");
return (0);
}
@ -139,51 +139,52 @@ iicbus_attach(device_t dev)
printf("\n");
#endif
/* attach any known device */
device_add_child(dev, "ic", -1);
device_add_child(dev, "iic", -1);
device_add_child(dev, "iicsmb", -1);
#if 0
/* attach any known device */
device_add_child(dev, "iic", -1);
#endif
bus_generic_attach(dev);
return (0);
}
static int
iicbus_detach(device_t dev)
{
iicbus_reset(dev, IIC_FASTEST, 0, NULL);
bus_generic_detach(dev);
return (0);
}
static int
iicbus_add_child(device_t dev, int order, const char *name, int unit)
{
device_add_child_ordered(dev, order, name, unit);
bus_generic_attach(dev);
return (0);
}
int
iicbus_generic_intr(device_t dev, int event, char *buf)
{
return (0);
}
int
iicbus_null_callback(device_t dev, int index, caddr_t data)
{
return (0);
}
int
iicbus_null_repeated_start(device_t dev, u_char addr)
{
return (IIC_ENOTSUPP);
}

View file

@ -38,4 +38,7 @@ struct iicbus_softc {
extern int iicbus_generic_intr(device_t dev, int event, char *buf);
extern driver_t iicbus_driver;
extern devclass_t iicbus_devclass;
#endif

View file

@ -27,6 +27,7 @@
#
#include <sys/bus.h>
#include <dev/iicbus/iic.h>
INTERFACE iicbus;
@ -105,3 +106,12 @@ METHOD int reset {
u_char addr;
u_char *oldaddr;
};
#
# Generalized Read/Write interface
#
METHOD int transfer {
device_t dev;
struct iic_msg *msgs;
uint32_t nmsgs;
};

View file

@ -331,3 +331,45 @@ iicbus_block_read(device_t bus, u_char slave, char *buf, int len, int *read)
return (error);
}
/*
* iicbus_trasnfer()
*
* Do an aribtrary number of transfers on the iicbus. We pass these
* raw requests to the bridge driver. If the bridge driver supports
* them directly, then it manages all the details. If not, it can use
* the helper function iicbus_transfer_gen() which will do the
* transfers at a low level.
*
* Pointers passed in as part of iic_msg must be kernel pointers.
* Callers that have user addresses to manage must do so on their own.
*/
int
iicbus_transfer(device_t bus, struct iic_msg *msgs, uint32_t nmsgs)
{
return (IICBUS_TRANSFER(device_get_parent(bus), msgs, nmsgs));
}
/*
* Generic version of iicbus_transfer that calls the appropriate
* routines to accomplish this. See note above about acceptable
* buffer addresses.
*/
int
iicbus_transfer_gen(device_t bus, struct iic_msg *msgs, uint32_t nmsgs)
{
int i, error, max, lenread, lenwrote;
for (i = 0, max = 0; i < nmsgs; i++)
if (max < msgs[i].len)
max = msgs[i].len;
for (i = 0, error = 0; i < nmsgs && error == 0; i++) {
if (msgs[i].flags & IIC_M_RD)
error = iicbus_block_read(bus, msgs[i].slave,
msgs[i].buf, msgs[i].len, &lenread);
else
error = iicbus_block_write(bus, msgs[i].slave,
msgs[i].buf, msgs[i].len, &lenwrote);
}
return (error);
}

View file

@ -29,6 +29,8 @@
#define __IICONF_H
#include <sys/queue.h>
#include <dev/iicbus/iic.h>
#define IICPRI (PZERO+8) /* XXX sleep/wakeup queue priority */
@ -127,6 +129,10 @@ extern int iicbus_block_read(device_t, u_char, char *, int, int *);
extern u_char iicbus_get_addr(device_t);
/* vectors of iic operations to pass to bridge */
int iicbus_transfer(device_t bus, struct iic_msg *msgs, uint32_t nmsgs);
int iicbus_transfer_gen(device_t bus, struct iic_msg *msgs, uint32_t nmsgs);
#define IICBUS_MODVER 1
#define IICBUS_MINVER 1
#define IICBUS_MAXVER 1