new xfer stuff in kernel module, i2c not working yet

This commit is contained in:
Triss 2021-07-14 04:45:35 +02:00
parent 595c768b88
commit a8ad3f6e04
5 changed files with 306 additions and 302 deletions

View File

@ -6,6 +6,7 @@ f = os.open(sys.argv[1], os.O_RDWR | os.O_CLOEXEC) # TODO: windows: os.O_BINARY
try:
os.write(f, b'\x00') # get version
resp = os.read(f, 4) # response: status, paylaod len (should be 2), payload
print("resp=%s"%repr(resp))
print("stat=%d plen=%d ver=%04x" % (resp[0], resp[1], struct.unpack('<H', resp[2:])[0]))
finally:
os.close(f)

View File

@ -29,17 +29,11 @@
#define DEVICE_NAME "dmj-char"
#define CLASS_NAME "dmj"
#define DMJ_READ_BUFSIZE 64
struct dmj_char_dev {
struct cdev cdev;
struct device *dev;
struct platform_device *pdev;
int minor;
spinlock_t devopen_lock;
size_t bufpos;
uint8_t rdbuf[DMJ_READ_BUFSIZE];
};
static int n_cdevs = 0;
@ -50,120 +44,56 @@ static struct class *dmj_char_class;
static ssize_t dmj_char_read(struct file *file, char *buf, size_t len, loff_t *loff)
{
int res, bsize;
struct dmj_char_dev *dmjch;
size_t bpos, todo, done = 0;
uint8_t *kbuf;
struct device *dev;
int res, ilen;
unsigned long ret;
struct dmj_char_dev *dmjch = file->private_data;
void *kbuf = NULL;
struct device *dev = dmjch->dev;
kbuf = kmalloc(len + DMJ_READ_BUFSIZE, GFP_KERNEL);
if (!kbuf) return -ENOMEM;
if (len > INT_MAX) return -EINVAL;
ilen = (int)len;
dmjch = file->private_data;
dev = dmjch->dev;
todo = len;
spin_lock(&dmjch->devopen_lock);
bpos = dmjch->bufpos;
/* TODO: ioctl to control this buffering? */
if (bpos) { /* data in buffer */
if (len <= DMJ_READ_BUFSIZE - bpos) {
/* doable in a single copy, no USB xfers needed */
memcpy(kbuf, &dmjch->rdbuf[bpos], len);
bpos += len;
if (bpos == DMJ_READ_BUFSIZE) bpos = 0;
done += len;
todo -= len;
} else {
/* initial copy stuff */
memcpy(kbuf, &dmjch->rdbuf[bpos], DMJ_READ_BUFSIZE - bpos);
todo -= DMJ_READ_BUFSIZE - bpos;
done += DMJ_READ_BUFSIZE - bpos;
bpos = 0;
}
res = dmj_transfer(dmjch->pdev, -1, DMJ_XFER_FLAGS_FILL_RECVBUF, NULL, 0, &kbuf, &ilen);
if (res < 0 || ilen < 0 || !kbuf) {
if (kbuf) kfree(kbuf);
return (res >= 0) ? -EIO : ret;
}
if /*while*/ (todo) { /* TODO: do we want a while here? */
bsize = DMJ_READ_BUFSIZE;
ret = copy_to_user(buf, kbuf, (size_t)ilen);
kfree(kbuf);
res = dmj_read(dmjch->pdev, 0, &kbuf[done], &bsize);
if (res < 0 || bsize < 0) {
/* ah snap */
spin_unlock(&dmjch->devopen_lock);
return res;
}
if (ret) return -EFAULT;
if ((size_t)bsize > todo) {
if ((size_t)bsize > todo + DMJ_READ_BUFSIZE) {
/* can't hold all this data, time to bail out... */
dev_err(dev, "too much data (%zu B excess), can't buffer, AAAAAA",
(size_t)bsize - (todo + DMJ_READ_BUFSIZE));
spin_unlock(&dmjch->devopen_lock);
BUG(); /* some stuff somewhere will have been corrupted, so, get out while we can */
}
/* stuff for next call */
done = todo;
bpos = DMJ_READ_BUFSIZE - ((size_t)bsize - todo);
memcpy(&dmjch->rdbuf[bpos], &kbuf[done], (size_t)bsize - todo);
todo = 0;
} else {
todo -= (size_t)bsize;
done += (size_t)bsize;
}
}
dmjch->bufpos = bpos;
spin_unlock(&dmjch->devopen_lock);
res = copy_to_user(buf, kbuf, len);
if (res) return (res < 0) ? res : -EFAULT;
return done;
return (ssize_t)ilen;
}
static ssize_t dmj_char_write(struct file *file, const char *buf, size_t len, loff_t *off)
{
unsigned long ret;
struct dmj_char_dev *dmjch;
int res;
struct dmj_char_dev *dmjch = file->private_data;
void *kbuf;
dmjch = file->private_data;
kbuf = kmalloc(len, GFP_KERNEL);
if (!kbuf) return -ENOMEM;
ret = copy_from_user(kbuf, buf, len);
if (ret) {
kfree(kbuf);
return (ret < 0) ? ret : -EFAULT;
return -EFAULT;
}
ret = dmj_transfer(dmjch->pdev, -1, 0, kbuf, len, NULL, NULL);
if (ret < 0) {
kfree(kbuf);
return ret;
}
res = dmj_transfer(dmjch->pdev, -1, 0, kbuf, len, NULL, NULL);
kfree(kbuf);
return len;
return (res < 0) ? res : len;
}
static int dmj_char_open(struct inode *inode, struct file *file)
{
struct dmj_char_dev *dmjch;
int ret;
dmjch = container_of(inode->i_cdev, struct dmj_char_dev, cdev);
spin_lock(&dmjch->devopen_lock);
ret = dmjch->bufpos;
if (~ret == 0) dmjch->bufpos = 0;
spin_unlock(&dmjch->devopen_lock);
if (~ret != 0) return -ETXTBSY; // already open
file->private_data = dmjch;
return 0;
@ -174,10 +104,6 @@ static int dmj_char_release(struct inode *inode, struct file *file)
dmjch = container_of(inode->i_cdev, struct dmj_char_dev, cdev);
spin_lock(&dmjch->devopen_lock);
dmjch->bufpos = ~(size_t)0;
spin_unlock(&dmjch->devopen_lock);
return 0;
}
@ -231,9 +157,6 @@ static int dmj_char_probe(struct platform_device *pdev)
dmjch->dev = device;
dmjch->minor = minor;
dmjch->pdev = pdev;
dmjch->bufpos = ~(size_t)0;
spin_lock_init(&dmjch->devopen_lock);
return 0;
}

View File

@ -96,39 +96,15 @@ static int dmj_send_wait(struct dmj_dev *dmj, int cmd, const void *wbuf, int wbu
return ret;
}
static int dmj_recv_wait(struct dmj_dev *dmj, void **kbuf, int rbufsize, bool parse_hdr)
{
int len, actual;
int ret;
void *buf;
*kbuf = NULL;
if (rbufsize <= 0) len = 0;
else if (parse_hdr) len = rbufsize + DMJ_RESP_HDR_SIZE;
else len = rbufsize;
buf = kmalloc(len, GFP_KERNEL);
if (!buf) return -ENOMEM;
ret = usb_bulk_msg(dmj->usb_dev, usb_rcvbulkpipe(dmj->usb_dev, dmj->ep_in),
buf, len, &actual, DMJ_USB_TIMEOUT);
if (ret < 0) kfree(buf);
*kbuf = buf;
return actual;
}
int dmj_xfer_internal(struct dmj_dev *dmj, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize)
const void *wbuf, int wbufsize, void **rbuf, int *rbufsize)
{
int ret = 0, actual;
int ret = 0, actual, pl_off, todo;
struct device *dev = &dmj->interface->dev;
int respstat, total_len, bytes_read;
uint32_t pl_len;
void *buf;
uint8_t *dbuf;
ptrdiff_t pl_off;
void *tmpbuf = NULL, *longbuf = NULL;
uint8_t *bbuf;
uint8_t respstat;
spin_lock(&dmj->disconnect_lock);
if (dmj->disconnect) ret = -ENODEV;
@ -144,125 +120,172 @@ int dmj_xfer_internal(struct dmj_dev *dmj, int cmd, int recvflags,
}
}
if ((recvflags & DMJ_XFER_FLAGS_PARSE_RESP) == 0
&& !(rbufsize && *rbufsize > 0 && rbuf)) {
/* don't want any type of response? then dont do the urb stuff either */
return 0;
}
/* first recv buffer, with optional response header parsing */
ret = dmj_recv_wait(dmj, &buf, (rbufsize && *rbufsize > 0) ? *rbufsize : 0,
(recvflags & DMJ_XFER_FLAGS_PARSE_RESP) != 0);
if (ret < 0 || !buf) {
dev_err(dev, "USB read failed: %d\n", ret);
return ret;
}
actual = ret;
if (rbuf) *rbuf = NULL;
if (recvflags & DMJ_XFER_FLAGS_PARSE_RESP) {
dbuf = buf;
/*
* if we do want to parse the response, we'll split the reads into
* blocks of 64 bytes, first to read the response header, and then
* allocate a big reponse buffer, and then keep doing 64b reads
* to fill that buffer. result length will be put in rbufsize, its
* value when passed to the function will not matter
*/
/* decode payload length */
if (dbuf[1] & 0x80) {
if (*rbufsize) *rbufsize = -1;
tmpbuf = kmalloc(64, GFP_KERNEL);
if (!tmpbuf) return -ENOMEM;
/* first read: 64b, with header data to parse */
ret = usb_bulk_msg(dmj->usb_dev, usb_rcvbulkpipe(dmj->usb_dev, dmj->ep_in),
tmpbuf, 64, &actual, DMJ_USB_TIMEOUT);
if (ret < 0) goto err_freetmp;
if (actual < 0) { ret = -EREMOTEIO; goto err_freetmp; }
bbuf = tmpbuf;
if (bbuf[1] & 0x80) {
if (actual < 3) {
dev_err(dev, "short response (%d, expected at least 3)\n", actual);
kfree(buf);
return -EREMOTEIO;
ret = -EREMOTEIO;
goto err_freetmp;
}
pl_len = (uint32_t)(dbuf[1] & 0x7f);
pl_len = (uint32_t)(bbuf[1] & 0x7f);
if (dbuf[2] & 0x80) {
if (bbuf[2] & 0x80) {
if (actual < 4) {
dev_err(dev, "short response (%d, expected at least 4)\n", actual);
kfree(buf);
return -EREMOTEIO;
ret = -EREMOTEIO;
goto err_freetmp;
}
pl_len |= (uint32_t)(dbuf[2] & 0x7f) << 7;
pl_len |= (uint32_t)dbuf[3] << 14;
pl_len |= (uint32_t)(bbuf[2] & 0x7f) << 7;
pl_len |= (uint32_t)bbuf[3] << 14;
pl_off = 4;
} else {
pl_len |= (uint32_t)dbuf[2] << 7;
pl_len |= (uint32_t)bbuf[2] << 7;
pl_off = 3;
}
} else {
if (actual < 2) {
dev_err(dev, "short response (%d, expected at least 2)\n", actual);
kfree(buf);
return -EREMOTEIO;
ret = -EREMOTEIO;
goto err_freetmp;
}
pl_len = (uint32_t)dbuf[1];
pl_len = (uint32_t)bbuf[1];
pl_off = 2;
}
respstat = dbuf[0];
total_len = pl_len;
actual -= pl_off;
respstat = bbuf[0];
/*dev_dbg(dev, "pl_len=%d,off=%ld,resp=%d\n", pl_len, pl_off, respstat);*/
} else {
pl_off = 0;
if (rbufsize && *rbufsize > 0) total_len = *rbufsize;
else total_len = actual;
respstat = 0;
}
dev_dbg(dev, "got packet hdr: status %02x, payload len 0x%x, off %u\n",
respstat, pl_len, pl_off);
if (rbuf && rbufsize && *rbufsize > 0) {
if (*rbufsize < total_len) total_len = *rbufsize;
/* now that the header has been parsed, we can start filling in the
* actual response buffer */
longbuf = kmalloc(pl_len, GFP_KERNEL);
todo = (int)pl_len;
/* rest of the data of the 1st read */
memcpy(longbuf, tmpbuf + pl_off, actual - pl_off);
todo -= (actual - pl_off);
pl_off = actual - pl_off;
if (actual > total_len) {
/*if (recvflags & DMJ_XFER_FLAGS_FILL_RECVBUF)*/
{
dev_err(dev, "aaa msgsize %d > %d\n", actual, total_len);
kfree(buf);
return -EMSGSIZE;
} /*else {
actual = total_len;
}*/
while (todo) {
actual = 64;
if (todo < actual) actual = todo;
ret = usb_bulk_msg(dmj->usb_dev, usb_rcvbulkpipe(dmj->usb_dev, dmj->ep_in),
tmpbuf, actual, &actual, DMJ_USB_TIMEOUT);
if (ret < 0) goto err_freelong;
if (actual < 0) { ret = -EREMOTEIO; goto err_freelong; }
if (actual > todo) {
dev_err(dev, "USB protocol violation! bulk reply longer than payload header!\n");
ret = -EMSGSIZE;
goto err_freelong;
}
memcpy(longbuf + pl_off, tmpbuf, actual);
todo -= actual;
pl_off += actual;
}
memcpy(rbuf + bytes_read, buf + pl_off, actual);
kfree(buf);
pl_off = -1;
buf = NULL;
/* we're done! */
bytes_read = actual;
/* response maybe not always wanted in this case, maybe was just called
* to check the status */
if (rbuf) *rbuf = longbuf;
else kfree(longbuf);
if (rbufsize) *rbufsize = (int)pl_len;
while (bytes_read < total_len && (recvflags & DMJ_XFER_FLAGS_FILL_RECVBUF) != 0) {
ret = dmj_recv_wait(dmj, &buf, total_len - bytes_read, false);
if (ret < 0 || !buf) {
dev_err(dev, "USB read failed: %d\n", ret);
return ret;
}
actual = ret;
if (bytes_read + actual > total_len) {
/*actual = total_len - bytes_read;*/
dev_err(dev, "aaa2 msgsize %d > %d\n", actual+bytes_read, total_len);
kfree(buf);
return -EMSGSIZE;
}
memcpy(rbuf + bytes_read, buf, actual);
kfree(buf);
buf = NULL;
bytes_read += actual;
}
ret = respstat;
} else {
bytes_read = 0;
kfree(buf);
/*
* otherwise, read max. rbufsize bytes (if using
* DMJ_XFER_FLAGS_FILL_RECVBUF, will try to fill it exactly, but it
* will error when going beyond!). also done in 64b chunks
*/
if (!rbuf || !rbufsize || *rbufsize <= 0) return 0;
if (recvflags & DMJ_XFER_FLAGS_FILL_RECVBUF) {
tmpbuf = kmalloc(64, GFP_KERNEL);
if (!tmpbuf) return -ENOMEM;
longbuf = kmalloc(*rbufsize, GFP_KERNEL);
if (!longbuf) { ret = -ENOMEM; goto err_freetmp; }
todo = *rbufsize;
pl_off = 0;
while (todo) {
actual = 64;
if (todo < actual) actual = todo;
ret = usb_bulk_msg(dmj->usb_dev, usb_rcvbulkpipe(dmj->usb_dev, dmj->ep_in),
tmpbuf, actual, &actual, DMJ_USB_TIMEOUT);
if (ret < 0) goto err_freelong;
if (actual < 0) { ret = -EREMOTEIO; goto err_freelong; }
if (actual > todo) { ret = -EMSGSIZE; goto err_freelong; }
memcpy(longbuf + pl_off, tmpbuf, actual);
todo -= actual;
pl_off += actual;
}
ret = 0;
*rbuf = longbuf;
*rbufsize = pl_off;
} else {
/* just try it at once & see what happens */
tmpbuf = NULL;
longbuf = kmalloc(*rbufsize, GFP_KERNEL);
if (!longbuf) return -ENOMEM;
ret = usb_bulk_msg(dmj->usb_dev, usb_rcvbulkpipe(dmj->usb_dev, dmj->ep_in),
longbuf, *rbufsize, rbufsize, DMJ_USB_TIMEOUT);
if (ret < 0) goto err_freelong;
if (actual < 0) { ret = -EREMOTEIO; goto err_freelong; }
ret = 0;
*rbuf = longbuf;
}
}
*rbufsize = bytes_read;
if (tmpbuf) kfree(tmpbuf);
return ret;
/*dev_dbg(dev, "all good! resp=%02x\n", respstat);*/
return respstat;
err_freelong:
if (longbuf) kfree(longbuf);
if (rbuf) *rbuf = NULL;
err_freetmp:
if (tmpbuf) kfree(tmpbuf);
return ret;
}
int dmj_transfer(struct platform_device *pdev, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize)
const void *wbuf, int wbufsize, void **rbuf, int *rbufsize)
{
struct dmj_platform_data *dmj_pdata;
struct dmj_dev *dmj;
@ -280,57 +303,66 @@ static int dmj_check_hw(struct dmj_dev *dmj)
{
struct device *dev = &dmj->interface->dev;
int ret;
__le16 protover;
int len = sizeof(protover);
int ret, len;
uint16_t protover;
uint8_t *buf = NULL;
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_VERSION,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &protover, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "version check", true, sizeof(protover), sizeof(protover));
if (ret < 0) return ret;
if (le16_to_cpu(protover) != DMJ_USB_CFG_PROTO_VER) {
dev_err(&dmj->interface->dev, HARDWARE_NAME " config protocol version 0x%04x too %s\n",
le16_to_cpu(protover), (le16_to_cpu(protover) > DMJ_USB_CFG_PROTO_VER) ? "new" : "old");
return -ENODEV;
}
if (ret < 0 || !buf) goto out;
return 0;
protover = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
if (protover != DMJ_USB_CFG_PROTO_VER) {
dev_err(&dmj->interface->dev, HARDWARE_NAME " config protocol version 0x%04x too %s\n",
protover, (protover > DMJ_USB_CFG_PROTO_VER) ? "new" : "old");
ret = -ENODEV;
} else
ret = 0;
out:
if (buf) kfree(buf);
return ret;
}
static int dmj_print_info(struct dmj_dev *dmj)
{
int ret, i, j, len;
__le16 modes, mversion;
uint16_t modes, mversion;
uint8_t curmode, features;
struct device *dev = &dmj->interface->dev;
char strinfo[65];
uint8_t *buf;
char modeinfo[16];
char *strinfo;
/* info string */
len = sizeof(strinfo)-1;
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_INFOSTR,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, strinfo, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, -1, sizeof(strinfo));
if (ret < 0) return ret;
strinfo[len] = 0; /*strinfo[64] = 0;*/
dev_info(dev, HARDWARE_NAME " '%s'\n", strinfo);
if (ret < 0 || !buf) goto out;
buf[len] = 0;
dev_info(dev, HARDWARE_NAME " '%s'\n", buf);
kfree(buf); buf = NULL;
/* cur mode */
len = sizeof(curmode);
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_CUR_MODE,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &curmode, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, sizeof(curmode), sizeof(curmode));
if (ret < 0) return ret;
dmj->dmj_mode = curmode;
if (ret < 0 || !buf) goto out;
dmj->dmj_mode = curmode = buf[0];
kfree(buf); buf = NULL;
/* map of available modes */
len = sizeof(modes);
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_MODES,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &modes, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, sizeof(modes), sizeof(modes));
if (ret < 0) return ret;
if (ret < 0 || !buf) goto out;
modes = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
kfree(buf); buf = NULL;
for (i = 1; i < 16; ++i) { /* build the string, uglily */
if (le16_to_cpu(modes) & (1<<i)) {
if (modes & (1<<i)) {
if (i < 0xa) modeinfo[i - 1] = '0'+i-0;
else modeinfo[i - 1] = 'a'+i-0xa;
} else modeinfo[i - 1] = '-';
@ -340,30 +372,31 @@ static int dmj_print_info(struct dmj_dev *dmj)
/* for each available mode print name, version and features */
for (i = 1; i < 16; ++i) {
if (!(le16_to_cpu(modes) & (1<<i))) continue; /* not available */
if (!(modes & (1<<i))) continue; /* not available */
/* name */
len = sizeof(strinfo)-1;
ret = dmj_xfer_internal(dmj, (i<<4) | DMJ_CMD_MODE_GET_NAME,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, strinfo, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, -1, sizeof(strinfo));
if (ret < 0) return ret;
if (len >= sizeof(strinfo)) return -EMSGSIZE;
strinfo[len] = 0; /*strinfo[64] = 0;*/
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, -1, -1);
if (ret < 0 || !buf) goto out;
buf[len] = 0;
strinfo = buf; buf = NULL;
/* version */
len = sizeof(mversion);
ret = dmj_xfer_internal(dmj, (i<<4) | DMJ_CMD_MODE_GET_VERSION,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &mversion, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, sizeof(mversion), sizeof(mversion));
if (ret < 0) return ret;
if (ret < 0 || !buf) { kfree(strinfo); goto out; }
mversion = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
kfree(buf); buf = NULL;
/* features */
len = sizeof(features);
ret = dmj_xfer_internal(dmj, (i<<4) | DMJ_CMD_MODE_GET_FEATURES,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &features, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "get info", true, sizeof(features), sizeof(features));
if (ret < 0) return ret;
if (ret < 0 || !buf) { kfree(strinfo); goto out; }
features = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
kfree(buf); buf = NULL;
if (i == 1) dmj->dmj_m1feature = features;
@ -375,9 +408,14 @@ static int dmj_print_info(struct dmj_dev *dmj)
dev_dbg(dev, "Mode %d: '%s' version 0x%04x, features: %s\n",
i, strinfo, mversion, modeinfo);
kfree(strinfo);
}
return 0;
out:
if (buf) kfree(buf);
return ret;
}
static int dmj_hw_init(struct dmj_dev *dmj)
{
@ -461,8 +499,6 @@ static int dmj_probe(struct usb_interface *itf, const struct usb_device_id *usb_
if (ret) {
dev_err(dev, "failed to add MFD I2C devices\n");
goto out_free;
} else {
dev_warn(dev, "added i2c mfd\n");
}
}
if (dmj->dmj_m1feature & DMJ_FEATURE_MODE1_TEMPSENSOR) {

View File

@ -71,11 +71,13 @@ inline static int dmj_check_retval(int ret, int len, struct device *dev,
return 0;
}
/* TODO: split up in "raw" read & writes, and higher-level ones with cmd and
* repstat, because the way this is currently overloaded, is, bad */
int dmj_transfer(struct platform_device *pdev, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize);
const void *wbuf, int wbufsize, void **rbuf, int *rbufsize);
inline static int dmj_read(struct platform_device *pdev, int recvflags,
void *rbuf, int *rbufsize)
void **rbuf, int *rbufsize)
{
return dmj_transfer(pdev, -1, recvflags, NULL, 0, rbuf, rbufsize);
}

View File

@ -54,14 +54,10 @@ struct dmj_i2c {
static int dmj_i2c_read(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
{
struct device *dev = &dmji->pdev->dev;
uint8_t *respbuf;
void *respbuf = NULL;
int ret, len;
uint8_t cmdbuf[1+2+2+2]; /* cmd, flags, addr, len */
len = msg->len;
respbuf = kzalloc(len, GFP_KERNEL); /* 0 length: returns status byte */
if (!respbuf) return -ENOMEM;
cmdbuf[0] = cmd;
cmdbuf[1] = (msg->flags >> 0) & 0xff;
cmdbuf[2] = (msg->flags >> 8) & 0xff;
@ -71,16 +67,15 @@ static int dmj_i2c_read(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
cmdbuf[6] = (msg->len >> 8) & 0xff;
ret = dmj_transfer(dmji->pdev, DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
cmdbuf, sizeof(cmdbuf), respbuf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c read", true, -1, -1);
if (ret < 0) goto err_free;
cmdbuf, sizeof(cmdbuf), &respbuf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c read", true, -1, msg->len);
if (ret < 0 || !respbuf) goto err_free;
memcpy(msg->buf, respbuf, msg->len);
kfree(respbuf);
return len;
ret = len;
err_free:
kfree(respbuf);
if (respbuf) kfree(respbuf);
return ret;
}
static int dmj_i2c_write(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
@ -89,8 +84,8 @@ static int dmj_i2c_write(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
uint8_t *cmdbuf;
int ret, len;
len = msg->len + 1+2+2+2;
cmdbuf = kzalloc(len, GFP_KERNEL); /* cmd, flags, addr, len */
len = msg->len + 1+2+2+2; /* cmd, flags, addr, len */
cmdbuf = kzalloc(len, GFP_KERNEL);
if (!cmdbuf) return -ENOMEM;
cmdbuf[0] = cmd;
@ -106,8 +101,7 @@ static int dmj_i2c_write(struct dmj_i2c *dmji, struct i2c_msg *msg, int cmd)
ret = dmj_check_retval(ret, len, dev, "i2c write", true, -1, -1);
if (ret < 0) goto err_free;
kfree(cmdbuf);
return msg->len;
ret = msg->len;
err_free:
kfree(cmdbuf);
@ -120,7 +114,7 @@ static int dmj_i2c_xfer(struct i2c_adapter *a, struct i2c_msg *msgs, int nmsg)
struct device *dev = &dmji->pdev->dev;
struct i2c_msg *pmsg;
int i, ret, cmd, stlen;
uint8_t status, i2ccmd;
uint8_t *status = NULL, i2ccmd;
for (i = 0; i < nmsg; ++i) {
cmd = DMJ_I2C_CMD_DO_XFER;
@ -154,14 +148,13 @@ static int dmj_i2c_xfer(struct i2c_adapter *a, struct i2c_msg *msgs, int nmsg)
/* read status */
i2ccmd = DMJ_I2C_CMD_GET_STATUS;
stlen = sizeof(status);
ret = dmj_transfer(dmji->pdev, DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
&i2ccmd, sizeof(i2ccmd), &status, &stlen);
&i2ccmd, sizeof(i2ccmd), (void**)&status, &stlen);
ret = dmj_check_retval(ret, stlen, dev, "i2c stat", true, sizeof(status), sizeof(status));
if (ret < 0) goto err_ret;
if (ret < 0 || !status) goto err_ret;
dev_warn(dev, " status = %d\n", status);
if (status == DMJ_I2C_STAT_NAK) {
dev_warn(dev, " status = %d\n", *status);
if (*status == DMJ_I2C_STAT_NAK) {
ret = -ENXIO;
goto err_ret;
}
@ -170,6 +163,7 @@ static int dmj_i2c_xfer(struct i2c_adapter *a, struct i2c_msg *msgs, int nmsg)
ret = i;
err_ret:
if (status) kfree(status);
return ret;
}
static uint32_t dmj_i2c_func(struct i2c_adapter *a)
@ -177,22 +171,27 @@ static uint32_t dmj_i2c_func(struct i2c_adapter *a)
struct dmj_i2c *dmji = i2c_get_adapdata(a);
struct device *dev = &dmji->pdev->dev;
__le32 func = 0;
int len = sizeof(func), ret;
uint32_t func = 0;
int len, ret;
uint8_t i2ccmd = DMJ_I2C_CMD_GET_FUNC;
uint8_t *fbuf = NULL;
ret = dmj_transfer(dmji->pdev, DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
&i2ccmd, sizeof(i2ccmd), &func, &len);
&i2ccmd, sizeof(i2ccmd), (void**)&fbuf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c get_func", true, sizeof(func), sizeof(func));
if (ret < 0) return 0;
if (ret < 0 || !fbuf) return 0;
dev_warn(dev, "I2C functionality: 0x%08x\n", le32_to_cpu(func));
func = (uint32_t)fbuf[0] | ((uint32_t)fbuf[1] << 8)
| ((uint32_t)fbuf[2] << 16) | ((uint32_t)fbuf[3] << 24);
return le32_to_cpu(func);
dev_warn(dev, "I2C functionality: 0x%08x\n", func);
kfree(fbuf);
return func;
}
static const struct i2c_algorithm dmj_i2c_algo = {
.master_xfer = dmj_i2c_xfer,
.master_xfer = dmj_i2c_xfer,
.functionality = dmj_i2c_func
};
static const struct i2c_adapter_quirks dmj_i2c_quirks = {
@ -210,57 +209,87 @@ static int dmj_i2c_check_hw(struct platform_device *pdev)
*/
struct device *dev = &pdev->dev;
__le16 m1ver;
uint16_t m1ver;
uint8_t curmode, m1feat, echoval;
const int ver_min = 0x0010, ver_max = 0x0010;
uint8_t i2ccmd[2];
int ret = 0, len;
uint8_t *buf = NULL;
len = sizeof(curmode);
ret = dmj_transfer(pdev, DMJ_CMD_CFG_GET_CUR_MODE,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &curmode, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c test 1", true, sizeof(curmode), sizeof(curmode));
if (ret < 0) return ret;
if (ret < 0 || !buf) goto out;
dev_warn(dev, "check hw 1\n");
curmode = buf[0];
kfree(buf); buf = NULL;
if (curmode != 0x1) {
dev_err(dev, "device must be in mode 1 for ICD to work, but it is in mode %d\n", curmode);
return -EIO;
ret = -EIO;
goto out;
}
len = sizeof(m1ver);
dev_warn(dev, "check hw 2\n");
ret = dmj_transfer(pdev, (1<<4) | DMJ_CMD_MODE_GET_VERSION,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &m1ver, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c test 2", true, sizeof(m1ver), sizeof(m1ver));
if (ret < 0) return ret;
if (le16_to_cpu(m1ver) > ver_max || le16_to_cpu(m1ver) < ver_min) {
if (ret < 0 || !buf) goto out;
dev_warn(dev, "check hw 3\n");
m1ver = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
kfree(buf); buf = NULL;
if (m1ver > ver_max || m1ver < ver_min) {
dev_err(dev, "bad mode 1 version %04x on device, must be between %04x and %04x\n",
le16_to_cpu(m1ver), ver_min, ver_max);
return -EIO;
m1ver, ver_min, ver_max);
ret = -EIO;
goto out;
}
len = sizeof(m1feat);
dev_warn(dev, "check hw 4\n");
ret = dmj_transfer(pdev, (1<<4) | DMJ_CMD_MODE_GET_FEATURES,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &m1feat, &len);
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c test 3", true, sizeof(m1feat), sizeof(m1feat));
if (ret < 0) return ret;
if (ret < 0 || !buf) goto out;
m1feat = buf[0];
kfree(buf); buf = NULL;
if (!(m1feat & DMJ_FEATURE_MODE1_I2C)) {
dev_err(dev, "device's mode 1 does not support I2C\n");
return -EIO;
ret = -EIO;
goto out;
}
dev_warn(dev, "check hw 5\n");
echoval = 0x42;
i2ccmd[0] = DMJ_I2C_CMD_ECHO;
i2ccmd[1] = ~echoval;
len = sizeof(echoval);
ret = dmj_transfer(pdev, (1<<4) | DMJ_CMD_MODE1_I2C,
DMJ_XFER_FLAGS_PARSE_RESP, i2ccmd, sizeof(i2ccmd), &m1feat, &len);
ret = dmj_check_retval(ret, len, dev, "i2c test", true, sizeof(m1feat), sizeof(m1feat));
if (ret < 0) return ret;
DMJ_XFER_FLAGS_PARSE_RESP, i2ccmd, sizeof(i2ccmd), (void**)&buf, &len);
ret = dmj_check_retval(ret, len, dev, "i2c test", true, sizeof(echoval), sizeof(echoval));
if (ret < 0 || !buf) goto out;
echoval = buf[0];
kfree(buf); buf = NULL;
if (echoval != i2ccmd[1]) {
dev_err(dev, "I2C echo test command not functional\n");
return -EIO;
ret = -EIO;
goto out;
}
return 0;
dev_warn(dev, "check hw 6\n");
ret = 0;
out:
dev_warn(dev, "check hw 7\n");
if (buf) kfree(buf);
return ret;
}
static int dmj_i2c_set_delay(struct platform_device *pdev, uint16_t us)
@ -269,14 +298,18 @@ static int dmj_i2c_set_delay(struct platform_device *pdev, uint16_t us)
uint8_t i2ccmd[3];
int ret = 0;
dev_warn(dev, "set delay 1\n");
i2ccmd[0] = DMJ_I2C_CMD_SET_DELAY;
i2ccmd[1] = (us >> 0) & 0xff;
i2ccmd[2] = (us >> 8) & 0xff;
ret = dmj_transfer(pdev, (1<<4) | DMJ_CMD_MODE1_I2C, DMJ_XFER_FLAGS_PARSE_RESP,
i2ccmd, sizeof(i2ccmd), NULL, NULL);
ret = dmj_write(pdev, (1<<4) | DMJ_CMD_MODE1_I2C, i2ccmd, sizeof(i2ccmd));
dev_dbg(dev, "set delay to %hu us, result %d\n", us, ret);
ret = dmj_check_retval(ret, -1, dev, "i2c set delay", true, -1, -1);
dev_warn(dev, "set delay 2\n");
return ret;
}
@ -302,18 +335,27 @@ static int dmj_i2c_probe(struct platform_device *pdev)
dmji->pdev = pdev;
dev_warn(dev, "probe 2\n");
dmji->adapter.owner = THIS_MODULE;
dmji->adapter.class = I2C_CLASS_HWMON;
dmji->adapter.algo = &dmj_i2c_algo;
dmji->adapter.quirks = &dmj_i2c_quirks; /* TODO: is this needed? probably... */
dmji->adapter.dev.of_node = dev->of_node;
dev_warn(dev, "probe 3\n");
i2c_set_adapdata(&dmji->adapter, dmji);
dev_warn(dev, "probe 4\n");
snprintf(dmji->adapter.name, sizeof(dmji->adapter.name), "%s-%s",
"dln2-i2c", dev_name(pdev->dev.parent));
"dmj-i2c", dev_name(pdev->dev.parent));
dev_warn(dev, "probe 5\n");
platform_set_drvdata(pdev, dmji);
dev_warn(dev, "probe 6\n");
return i2c_add_adapter(&dmji->adapter);
}
static int dmj_i2c_remove(struct platform_device *pdev)