ucmb: Allow chunk-ing big transfers to reduce latency issues.

git-svn-id: svn://svn.openwrt.org/openwrt/packages@14839 3c298f89-4303-0410-b956-a3cf2f4a3e73
This commit is contained in:
mb 2009-03-10 11:54:04 +00:00
parent fc6b6ca0e8
commit 516fbeb0ac
2 changed files with 73 additions and 28 deletions

View File

@ -28,6 +28,7 @@
#include <linux/gfp.h> #include <linux/gfp.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/crc16.h> #include <linux/crc16.h>
#include <linux/sched.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
@ -47,6 +48,7 @@ struct ucmb {
bool is_open; bool is_open;
unsigned int chunk_size;
unsigned int msg_delay_usec; unsigned int msg_delay_usec;
unsigned int gpio_reset; unsigned int gpio_reset;
bool reset_activelow; bool reset_activelow;
@ -228,6 +230,48 @@ static int ucmb_ioctl(struct inode *inode, struct file *filp,
return ret; return ret;
} }
static int ucmb_spi_write(struct ucmb *ucmb, const void *_buf, size_t size)
{
const u8 *buf = _buf;
size_t i, chunk_size, current_size;
int err = 0;
chunk_size = ucmb->chunk_size ? : size;
for (i = 0; i < size; i += chunk_size) {
current_size = chunk_size;
if (i + current_size > size)
current_size = size - i;
err = spi_write(ucmb->sdev, buf + i, current_size);
if (err)
goto out;
if (ucmb->chunk_size && need_resched())
msleep(1);
}
out:
return err;
}
static int ucmb_spi_read(struct ucmb *ucmb, void *_buf, size_t size)
{
u8 *buf = _buf;
size_t i, chunk_size, current_size;
int err = 0;
chunk_size = ucmb->chunk_size ? : size;
for (i = 0; i < size; i += chunk_size) {
current_size = chunk_size;
if (i + current_size > size)
current_size = size - i;
err = spi_read(ucmb->sdev, buf + i, current_size);
if (err)
goto out;
if (ucmb->chunk_size && need_resched())
msleep(1);
}
out:
return err;
}
static ssize_t ucmb_read(struct file *filp, char __user *user_buf, static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
size_t size, loff_t *offp) size_t size, loff_t *offp)
{ {
@ -248,7 +292,7 @@ static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
if (!buf) if (!buf)
goto out; goto out;
err = spi_read(ucmb->sdev, (u8 *)&hdr, sizeof(hdr)); err = ucmb_spi_read(ucmb, &hdr, sizeof(hdr));
if (err) if (err)
goto out_free; goto out_free;
#ifdef DEBUG #ifdef DEBUG
@ -262,10 +306,10 @@ static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
if (size < le16_to_cpu(hdr.len)) if (size < le16_to_cpu(hdr.len))
goto out_free; goto out_free;
size = le16_to_cpu(hdr.len); size = le16_to_cpu(hdr.len);
err = spi_read(ucmb->sdev, buf, size); err = ucmb_spi_read(ucmb, buf, size);
if (err) if (err)
goto out_free; goto out_free;
err = spi_read(ucmb->sdev, (u8 *)&footer, sizeof(footer)); err = ucmb_spi_read(ucmb, &footer, sizeof(footer));
if (err) if (err)
goto out_free; goto out_free;
@ -288,7 +332,7 @@ static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
err = 0; err = 0;
out_send_status: out_send_status:
res = spi_write(ucmb->sdev, (u8 *)&status, sizeof(status)); res = ucmb_spi_write(ucmb, &status, sizeof(status));
if (res && !err) if (res && !err)
err = res; err = res;
out_free: out_free:
@ -308,10 +352,7 @@ static ssize_t ucmb_write(struct file *filp, const char __user *user_buf,
struct ucmb_message_hdr hdr = { .magic = cpu_to_le16(UCMB_MAGIC), }; struct ucmb_message_hdr hdr = { .magic = cpu_to_le16(UCMB_MAGIC), };
struct ucmb_message_footer footer = { .crc = 0xFFFF, }; struct ucmb_message_footer footer = { .crc = 0xFFFF, };
struct ucmb_status status; struct ucmb_status status;
struct spi_transfer spi_hdr_xfer; size_t i, current_size, chunk_size;
struct spi_transfer spi_footer_xfer;
struct spi_transfer spi_data_xfer;
struct spi_message spi_msg;
mutex_lock(&ucmb->mutex); mutex_lock(&ucmb->mutex);
@ -330,25 +371,13 @@ static ssize_t ucmb_write(struct file *filp, const char __user *user_buf,
footer.crc = crc16(footer.crc, buf, size); footer.crc = crc16(footer.crc, buf, size);
footer.crc ^= 0xFFFF; footer.crc ^= 0xFFFF;
spi_message_init(&spi_msg); err = ucmb_spi_write(ucmb, &hdr, sizeof(hdr));
if (err)
memset(&spi_hdr_xfer, 0, sizeof(spi_hdr_xfer)); goto out_free;
spi_hdr_xfer.tx_buf = &hdr; err = ucmb_spi_write(ucmb, buf, size);
spi_hdr_xfer.len = sizeof(hdr); if (err)
spi_message_add_tail(&spi_hdr_xfer, &spi_msg); goto out_free;
err = ucmb_spi_write(ucmb, &footer, sizeof(footer));
memset(&spi_data_xfer, 0, sizeof(spi_data_xfer));
spi_data_xfer.tx_buf = buf;
spi_data_xfer.len = size;
spi_message_add_tail(&spi_data_xfer, &spi_msg);
memset(&spi_footer_xfer, 0, sizeof(spi_footer_xfer));
spi_footer_xfer.tx_buf = &footer;
spi_footer_xfer.len = sizeof(footer);
spi_message_add_tail(&spi_footer_xfer, &spi_msg);
/* Send the message, including header. */
err = spi_sync(ucmb->sdev, &spi_msg);
if (err) if (err)
goto out_free; goto out_free;
@ -364,7 +393,7 @@ static ssize_t ucmb_write(struct file *filp, const char __user *user_buf,
} }
/* Get the status code. */ /* Get the status code. */
err = spi_read(ucmb->sdev, (u8 *)&status, sizeof(status)); err = ucmb_spi_read(ucmb, &status, sizeof(status));
if (err) if (err)
goto out_free; goto out_free;
#ifdef DEBUG #ifdef DEBUG
@ -404,6 +433,14 @@ static int __devinit ucmb_probe(struct platform_device *pdev)
mutex_init(&ucmb->mutex); mutex_init(&ucmb->mutex);
ucmb->gpio_reset = pdata->gpio_reset; ucmb->gpio_reset = pdata->gpio_reset;
ucmb->reset_activelow = pdata->reset_activelow; ucmb->reset_activelow = pdata->reset_activelow;
ucmb->chunk_size = pdata->chunk_size;
#ifdef CONFIG_PREEMPT
/* A preemptible kernel does not need to sleep between
* chunks, because it can sleep at desire (if IRQs are enabled).
* So transmit/receive it all in one go. */
ucmb->chunk_size = 0;
#endif
/* Create the SPI GPIO bus master. */ /* Create the SPI GPIO bus master. */

View File

@ -41,6 +41,13 @@
* *
* @mode: The SPI bus mode. SPI_MODE_* * @mode: The SPI bus mode. SPI_MODE_*
* @max_speed_hz: The bus speed, in Hz. If zero the speed is not limited. * @max_speed_hz: The bus speed, in Hz. If zero the speed is not limited.
* @chunk_size: The maximum chunk size to transmit/receive in one go
* without sleeping. The kernel will be allowed to sleep
* after each chunk.
* If set to 0, the whole data will be transmitted/received
* in one big rush without sleeping. Note that this might hurt
* system responsiveness, if the kernel is not preemptible.
* If CONFIG_PREEMPT is enabled, chunk_size will be forced to 0.
*/ */
struct ucmb_platform_data { struct ucmb_platform_data {
const char *name; const char *name;
@ -55,6 +62,7 @@ struct ucmb_platform_data {
u8 mode; u8 mode;
u32 max_speed_hz; u32 max_speed_hz;
unsigned int chunk_size;
struct platform_device *pdev; /* internal */ struct platform_device *pdev; /* internal */
}; };