summaryrefslogtreecommitdiffstats
path: root/drivers/spi/spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi.c')
-rw-r--r--drivers/spi/spi.c426
1 files changed, 416 insertions, 10 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 47eff8012a77..de2f2f90d799 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -144,6 +144,8 @@ SPI_STATISTICS_TRANSFER_BYTES_HISTO(14, "16384-32767");
SPI_STATISTICS_TRANSFER_BYTES_HISTO(15, "32768-65535");
SPI_STATISTICS_TRANSFER_BYTES_HISTO(16, "65536+");
+SPI_STATISTICS_SHOW(transfers_split_maxsize, "%lu");
+
static struct attribute *spi_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
@@ -181,6 +183,7 @@ static struct attribute *spi_device_statistics_attrs[] = {
&dev_attr_spi_device_transfer_bytes_histo14.attr,
&dev_attr_spi_device_transfer_bytes_histo15.attr,
&dev_attr_spi_device_transfer_bytes_histo16.attr,
+ &dev_attr_spi_device_transfers_split_maxsize.attr,
NULL,
};
@@ -223,6 +226,7 @@ static struct attribute *spi_master_statistics_attrs[] = {
&dev_attr_spi_master_transfer_bytes_histo14.attr,
&dev_attr_spi_master_transfer_bytes_histo15.attr,
&dev_attr_spi_master_transfer_bytes_histo16.attr,
+ &dev_attr_spi_master_transfers_split_maxsize.attr,
NULL,
};
@@ -702,6 +706,7 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
enum dma_data_direction dir)
{
const bool vmalloced_buf = is_vmalloc_addr(buf);
+ unsigned int max_seg_size = dma_get_max_seg_size(dev);
int desc_len;
int sgs;
struct page *vm_page;
@@ -710,10 +715,10 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
int i, ret;
if (vmalloced_buf) {
- desc_len = PAGE_SIZE;
+ desc_len = min_t(int, max_seg_size, PAGE_SIZE);
sgs = DIV_ROUND_UP(len + offset_in_page(buf), desc_len);
} else {
- desc_len = master->max_dma_len;
+ desc_len = min_t(int, max_seg_size, master->max_dma_len);
sgs = DIV_ROUND_UP(len, desc_len);
}
@@ -739,7 +744,6 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
sg_set_buf(&sgt->sgl[i], sg_buf, min);
}
-
buf += min;
len -= min;
}
@@ -1024,6 +1028,8 @@ out:
if (msg->status && master->handle_err)
master->handle_err(master, msg);
+ spi_res_release(master, msg);
+
spi_finalize_current_message(master);
return ret;
@@ -1047,6 +1053,7 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_transfer);
* __spi_pump_messages - function which processes spi message queue
* @master: master to process queue for
* @in_kthread: true if we are in the context of the message pump thread
+ * @bus_locked: true if the bus mutex is held when calling this function
*
* This function checks if there is any spi message in the queue that
* needs processing and if so call out to the driver to initialize hardware
@@ -1056,7 +1063,8 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_transfer);
* inside spi_sync(); the queue extraction handling at the top of the
* function should deal with this safely.
*/
-static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
+static void __spi_pump_messages(struct spi_master *master, bool in_kthread,
+ bool bus_locked)
{
unsigned long flags;
bool was_busy = false;
@@ -1152,6 +1160,9 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
}
}
+ if (!bus_locked)
+ mutex_lock(&master->bus_lock_mutex);
+
trace_spi_message_start(master->cur_msg);
if (master->prepare_message) {
@@ -1161,7 +1172,7 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
"failed to prepare message: %d\n", ret);
master->cur_msg->status = ret;
spi_finalize_current_message(master);
- return;
+ goto out;
}
master->cur_msg_prepared = true;
}
@@ -1170,15 +1181,23 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
if (ret) {
master->cur_msg->status = ret;
spi_finalize_current_message(master);
- return;
+ goto out;
}
ret = master->transfer_one_message(master, master->cur_msg);
if (ret) {
dev_err(&master->dev,
"failed to transfer one message from queue\n");
- return;
+ goto out;
}
+
+out:
+ if (!bus_locked)
+ mutex_unlock(&master->bus_lock_mutex);
+
+ /* Prod the scheduler in case transfer_one() was busy waiting */
+ if (!ret)
+ cond_resched();
}
/**
@@ -1190,7 +1209,7 @@ static void spi_pump_messages(struct kthread_work *work)
struct spi_master *master =
container_of(work, struct spi_master, pump_messages);
- __spi_pump_messages(master, true);
+ __spi_pump_messages(master, true, false);
}
static int spi_init_queue(struct spi_master *master)
@@ -1581,13 +1600,30 @@ static void of_register_spi_devices(struct spi_master *master) { }
static int acpi_spi_add_resource(struct acpi_resource *ares, void *data)
{
struct spi_device *spi = data;
+ struct spi_master *master = spi->master;
if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) {
struct acpi_resource_spi_serialbus *sb;
sb = &ares->data.spi_serial_bus;
if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_SPI) {
- spi->chip_select = sb->device_selection;
+ /*
+ * ACPI DeviceSelection numbering is handled by the
+ * host controller driver in Windows and can vary
+ * from driver to driver. In Linux we always expect
+ * 0 .. max - 1 so we need to ask the driver to
+ * translate between the two schemes.
+ */
+ if (master->fw_translate_cs) {
+ int cs = master->fw_translate_cs(master,
+ sb->device_selection);
+ if (cs < 0)
+ return cs;
+ spi->chip_select = cs;
+ } else {
+ spi->chip_select = sb->device_selection;
+ }
+
spi->max_speed_hz = sb->connection_speed;
if (sb->clock_phase == ACPI_SPI_SECOND_PHASE)
@@ -2013,6 +2049,336 @@ struct spi_master *spi_busnum_to_master(u16 bus_num)
}
EXPORT_SYMBOL_GPL(spi_busnum_to_master);
+/*-------------------------------------------------------------------------*/
+
+/* Core methods for SPI resource management */
+
+/**
+ * spi_res_alloc - allocate a spi resource that is life-cycle managed
+ * during the processing of a spi_message while using
+ * spi_transfer_one
+ * @spi: the spi device for which we allocate memory
+ * @release: the release code to execute for this resource
+ * @size: size to alloc and return
+ * @gfp: GFP allocation flags
+ *
+ * Return: the pointer to the allocated data
+ *
+ * This may get enhanced in the future to allocate from a memory pool
+ * of the @spi_device or @spi_master to avoid repeated allocations.
+ */
+void *spi_res_alloc(struct spi_device *spi,
+ spi_res_release_t release,
+ size_t size, gfp_t gfp)
+{
+ struct spi_res *sres;
+
+ sres = kzalloc(sizeof(*sres) + size, gfp);
+ if (!sres)
+ return NULL;
+
+ INIT_LIST_HEAD(&sres->entry);
+ sres->release = release;
+
+ return sres->data;
+}
+EXPORT_SYMBOL_GPL(spi_res_alloc);
+
+/**
+ * spi_res_free - free an spi resource
+ * @res: pointer to the custom data of a resource
+ *
+ */
+void spi_res_free(void *res)
+{
+ struct spi_res *sres = container_of(res, struct spi_res, data);
+
+ if (!res)
+ return;
+
+ WARN_ON(!list_empty(&sres->entry));
+ kfree(sres);
+}
+EXPORT_SYMBOL_GPL(spi_res_free);
+
+/**
+ * spi_res_add - add a spi_res to the spi_message
+ * @message: the spi message
+ * @res: the spi_resource
+ */
+void spi_res_add(struct spi_message *message, void *res)
+{
+ struct spi_res *sres = container_of(res, struct spi_res, data);
+
+ WARN_ON(!list_empty(&sres->entry));
+ list_add_tail(&sres->entry, &message->resources);
+}
+EXPORT_SYMBOL_GPL(spi_res_add);
+
+/**
+ * spi_res_release - release all spi resources for this message
+ * @master: the @spi_master
+ * @message: the @spi_message
+ */
+void spi_res_release(struct spi_master *master,
+ struct spi_message *message)
+{
+ struct spi_res *res;
+
+ while (!list_empty(&message->resources)) {
+ res = list_last_entry(&message->resources,
+ struct spi_res, entry);
+
+ if (res->release)
+ res->release(master, message, res->data);
+
+ list_del(&res->entry);
+
+ kfree(res);
+ }
+}
+EXPORT_SYMBOL_GPL(spi_res_release);
+
+/*-------------------------------------------------------------------------*/
+
+/* Core methods for spi_message alterations */
+
+static void __spi_replace_transfers_release(struct spi_master *master,
+ struct spi_message *msg,
+ void *res)
+{
+ struct spi_replaced_transfers *rxfer = res;
+ size_t i;
+
+ /* call extra callback if requested */
+ if (rxfer->release)
+ rxfer->release(master, msg, res);
+
+ /* insert replaced transfers back into the message */
+ list_splice(&rxfer->replaced_transfers, rxfer->replaced_after);
+
+ /* remove the formerly inserted entries */
+ for (i = 0; i < rxfer->inserted; i++)
+ list_del(&rxfer->inserted_transfers[i].transfer_list);
+}
+
+/**
+ * spi_replace_transfers - replace transfers with several transfers
+ * and register change with spi_message.resources
+ * @msg: the spi_message we work upon
+ * @xfer_first: the first spi_transfer we want to replace
+ * @remove: number of transfers to remove
+ * @insert: the number of transfers we want to insert instead
+ * @release: extra release code necessary in some circumstances
+ * @extradatasize: extra data to allocate (with alignment guarantees
+ * of struct @spi_transfer)
+ * @gfp: gfp flags
+ *
+ * Returns: pointer to @spi_replaced_transfers,
+ * PTR_ERR(...) in case of errors.
+ */
+struct spi_replaced_transfers *spi_replace_transfers(
+ struct spi_message *msg,
+ struct spi_transfer *xfer_first,
+ size_t remove,
+ size_t insert,
+ spi_replaced_release_t release,
+ size_t extradatasize,
+ gfp_t gfp)
+{
+ struct spi_replaced_transfers *rxfer;
+ struct spi_transfer *xfer;
+ size_t i;
+
+ /* allocate the structure using spi_res */
+ rxfer = spi_res_alloc(msg->spi, __spi_replace_transfers_release,
+ insert * sizeof(struct spi_transfer)
+ + sizeof(struct spi_replaced_transfers)
+ + extradatasize,
+ gfp);
+ if (!rxfer)
+ return ERR_PTR(-ENOMEM);
+
+ /* the release code to invoke before running the generic release */
+ rxfer->release = release;
+
+ /* assign extradata */
+ if (extradatasize)
+ rxfer->extradata =
+ &rxfer->inserted_transfers[insert];
+
+ /* init the replaced_transfers list */
+ INIT_LIST_HEAD(&rxfer->replaced_transfers);
+
+ /* assign the list_entry after which we should reinsert
+ * the @replaced_transfers - it may be spi_message.messages!
+ */
+ rxfer->replaced_after = xfer_first->transfer_list.prev;
+
+ /* remove the requested number of transfers */
+ for (i = 0; i < remove; i++) {
+ /* if the entry after replaced_after it is msg->transfers
+ * then we have been requested to remove more transfers
+ * than are in the list
+ */
+ if (rxfer->replaced_after->next == &msg->transfers) {
+ dev_err(&msg->spi->dev,
+ "requested to remove more spi_transfers than are available\n");
+ /* insert replaced transfers back into the message */
+ list_splice(&rxfer->replaced_transfers,
+ rxfer->replaced_after);
+
+ /* free the spi_replace_transfer structure */
+ spi_res_free(rxfer);
+
+ /* and return with an error */
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* remove the entry after replaced_after from list of
+ * transfers and add it to list of replaced_transfers
+ */
+ list_move_tail(rxfer->replaced_after->next,
+ &rxfer->replaced_transfers);
+ }
+
+ /* create copy of the given xfer with identical settings
+ * based on the first transfer to get removed
+ */
+ for (i = 0; i < insert; i++) {
+ /* we need to run in reverse order */
+ xfer = &rxfer->inserted_transfers[insert - 1 - i];
+
+ /* copy all spi_transfer data */
+ memcpy(xfer, xfer_first, sizeof(*xfer));
+
+ /* add to list */
+ list_add(&xfer->transfer_list, rxfer->replaced_after);
+
+ /* clear cs_change and delay_usecs for all but the last */
+ if (i) {
+ xfer->cs_change = false;
+ xfer->delay_usecs = 0;
+ }
+ }
+
+ /* set up inserted */
+ rxfer->inserted = insert;
+
+ /* and register it with spi_res/spi_message */
+ spi_res_add(msg, rxfer);
+
+ return rxfer;
+}
+EXPORT_SYMBOL_GPL(spi_replace_transfers);
+
+static int __spi_split_transfer_maxsize(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer **xferp,
+ size_t maxsize,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer = *xferp, *xfers;
+ struct spi_replaced_transfers *srt;
+ size_t offset;
+ size_t count, i;
+
+ /* warn once about this fact that we are splitting a transfer */
+ dev_warn_once(&msg->spi->dev,
+ "spi_transfer of length %i exceed max length of %zu - needed to split transfers\n",
+ xfer->len, maxsize);
+
+ /* calculate how many we have to replace */
+ count = DIV_ROUND_UP(xfer->len, maxsize);
+
+ /* create replacement */
+ srt = spi_replace_transfers(msg, xfer, 1, count, NULL, 0, gfp);
+ if (IS_ERR(srt))
+ return PTR_ERR(srt);
+ xfers = srt->inserted_transfers;
+
+ /* now handle each of those newly inserted spi_transfers
+ * note that the replacements spi_transfers all are preset
+ * to the same values as *xferp, so tx_buf, rx_buf and len
+ * are all identical (as well as most others)
+ * so we just have to fix up len and the pointers.
+ *
+ * this also includes support for the depreciated
+ * spi_message.is_dma_mapped interface
+ */
+
+ /* the first transfer just needs the length modified, so we
+ * run it outside the loop
+ */
+ xfers[0].len = min_t(size_t, maxsize, xfer[0].len);
+
+ /* all the others need rx_buf/tx_buf also set */
+ for (i = 1, offset = maxsize; i < count; offset += maxsize, i++) {
+ /* update rx_buf, tx_buf and dma */
+ if (xfers[i].rx_buf)
+ xfers[i].rx_buf += offset;
+ if (xfers[i].rx_dma)
+ xfers[i].rx_dma += offset;
+ if (xfers[i].tx_buf)
+ xfers[i].tx_buf += offset;
+ if (xfers[i].tx_dma)
+ xfers[i].tx_dma += offset;
+
+ /* update length */
+ xfers[i].len = min(maxsize, xfers[i].len - offset);
+ }
+
+ /* we set up xferp to the last entry we have inserted,
+ * so that we skip those already split transfers
+ */
+ *xferp = &xfers[count - 1];
+
+ /* increment statistics counters */
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+ transfers_split_maxsize);
+ SPI_STATISTICS_INCREMENT_FIELD(&msg->spi->statistics,
+ transfers_split_maxsize);
+
+ return 0;
+}
+
+/**
+ * spi_split_tranfers_maxsize - split spi transfers into multiple transfers
+ * when an individual transfer exceeds a
+ * certain size
+ * @master: the @spi_master for this transfer
+ * @msg: the @spi_message to transform
+ * @maxsize: the maximum when to apply this
+ * @gfp: GFP allocation flags
+ *
+ * Return: status of transformation
+ */
+int spi_split_transfers_maxsize(struct spi_master *master,
+ struct spi_message *msg,
+ size_t maxsize,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer;
+ int ret;
+
+ /* iterate over the transfer_list,
+ * but note that xfer is advanced to the last transfer inserted
+ * to avoid checking sizes again unnecessarily (also xfer does
+ * potentiall belong to a different list by the time the
+ * replacement has happened
+ */
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ if (xfer->len > maxsize) {
+ ret = __spi_split_transfer_maxsize(
+ master, msg, &xfer, maxsize, gfp);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize);
/*-------------------------------------------------------------------------*/
@@ -2351,6 +2717,46 @@ int spi_async_locked(struct spi_device *spi, struct spi_message *message)
EXPORT_SYMBOL_GPL(spi_async_locked);
+int spi_flash_read(struct spi_device *spi,
+ struct spi_flash_read_message *msg)
+
+{
+ struct spi_master *master = spi->master;
+ int ret;
+
+ if ((msg->opcode_nbits == SPI_NBITS_DUAL ||
+ msg->addr_nbits == SPI_NBITS_DUAL) &&
+ !(spi->mode & (SPI_TX_DUAL | SPI_TX_QUAD)))
+ return -EINVAL;
+ if ((msg->opcode_nbits == SPI_NBITS_QUAD ||
+ msg->addr_nbits == SPI_NBITS_QUAD) &&
+ !(spi->mode & SPI_TX_QUAD))
+ return -EINVAL;
+ if (msg->data_nbits == SPI_NBITS_DUAL &&
+ !(spi->mode & (SPI_RX_DUAL | SPI_RX_QUAD)))
+ return -EINVAL;
+ if (msg->data_nbits == SPI_NBITS_QUAD &&
+ !(spi->mode & SPI_RX_QUAD))
+ return -EINVAL;
+
+ if (master->auto_runtime_pm) {
+ ret = pm_runtime_get_sync(master->dev.parent);
+ if (ret < 0) {
+ dev_err(&master->dev, "Failed to power device: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ mutex_lock(&master->bus_lock_mutex);
+ ret = master->spi_flash_read(spi, msg);
+ mutex_unlock(&master->bus_lock_mutex);
+ if (master->auto_runtime_pm)
+ pm_runtime_put(master->dev.parent);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(spi_flash_read);
+
/*-------------------------------------------------------------------------*/
/* Utility methods for SPI master protocol drivers, layered on
@@ -2414,7 +2820,7 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
spi_sync_immediate);
SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
spi_sync_immediate);
- __spi_pump_messages(master, false);
+ __spi_pump_messages(master, false, bus_locked);
}
wait_for_completion(&done);
OpenPOWER on IntegriCloud