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/delay.h>
#include <linux/crc16.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
@ -47,6 +48,7 @@ struct ucmb {
bool is_open;
unsigned int chunk_size;
unsigned int msg_delay_usec;
unsigned int gpio_reset;
bool reset_activelow;
@ -228,6 +230,48 @@ static int ucmb_ioctl(struct inode *inode, struct file *filp,
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,
size_t size, loff_t *offp)
{
@ -248,7 +292,7 @@ static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
if (!buf)
goto out;
err = spi_read(ucmb->sdev, (u8 *)&hdr, sizeof(hdr));
err = ucmb_spi_read(ucmb, &hdr, sizeof(hdr));
if (err)
goto out_free;
#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))
goto out_free;
size = le16_to_cpu(hdr.len);
err = spi_read(ucmb->sdev, buf, size);
err = ucmb_spi_read(ucmb, buf, size);
if (err)
goto out_free;
err = spi_read(ucmb->sdev, (u8 *)&footer, sizeof(footer));
err = ucmb_spi_read(ucmb, &footer, sizeof(footer));
if (err)
goto out_free;
@ -288,7 +332,7 @@ static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
err = 0;
out_send_status:
res = spi_write(ucmb->sdev, (u8 *)&status, sizeof(status));
res = ucmb_spi_write(ucmb, &status, sizeof(status));
if (res && !err)
err = res;
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_footer footer = { .crc = 0xFFFF, };
struct ucmb_status status;
struct spi_transfer spi_hdr_xfer;
struct spi_transfer spi_footer_xfer;
struct spi_transfer spi_data_xfer;
struct spi_message spi_msg;
size_t i, current_size, chunk_size;
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 ^= 0xFFFF;
spi_message_init(&spi_msg);
memset(&spi_hdr_xfer, 0, sizeof(spi_hdr_xfer));
spi_hdr_xfer.tx_buf = &hdr;
spi_hdr_xfer.len = sizeof(hdr);
spi_message_add_tail(&spi_hdr_xfer, &spi_msg);
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);
err = ucmb_spi_write(ucmb, &hdr, sizeof(hdr));
if (err)
goto out_free;
err = ucmb_spi_write(ucmb, buf, size);
if (err)
goto out_free;
err = ucmb_spi_write(ucmb, &footer, sizeof(footer));
if (err)
goto out_free;
@ -364,7 +393,7 @@ static ssize_t ucmb_write(struct file *filp, const char __user *user_buf,
}
/* Get the status code. */
err = spi_read(ucmb->sdev, (u8 *)&status, sizeof(status));
err = ucmb_spi_read(ucmb, &status, sizeof(status));
if (err)
goto out_free;
#ifdef DEBUG
@ -404,6 +433,14 @@ static int __devinit ucmb_probe(struct platform_device *pdev)
mutex_init(&ucmb->mutex);
ucmb->gpio_reset = pdata->gpio_reset;
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. */

View File

@ -41,6 +41,13 @@
*
* @mode: The SPI bus mode. SPI_MODE_*
* @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 {
const char *name;
@ -55,6 +62,7 @@ struct ucmb_platform_data {
u8 mode;
u32 max_speed_hz;
unsigned int chunk_size;
struct platform_device *pdev; /* internal */
};