diff --git a/utils/ucmb/driver/ucmb.c b/utils/ucmb/driver/ucmb.c index 374f46721..bd02d9e8a 100644 --- a/utils/ucmb/driver/ucmb.c +++ b/utils/ucmb/driver/ucmb.c @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -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. */ diff --git a/utils/ucmb/driver/ucmb.h b/utils/ucmb/driver/ucmb.h index d64f2e4f6..c0d35d207 100644 --- a/utils/ucmb/driver/ucmb.h +++ b/utils/ucmb/driver/ucmb.h @@ -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 */ };