diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-15 21:07:33 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-15 21:07:33 -0700 |
commit | ff280e3639548fc8c366f6e4bd471e715ac590c7 (patch) | |
tree | 91c960af9702419a9e28f6bdc2f9cdb3921deb40 /drivers/spi/spi.c | |
parent | 5ca5446ec5ba5e79a6f271cd026bb153d6850fcc (diff) | |
parent | c508709bcffb644afbf5e5016fc7c90bf80c30ff (diff) | |
download | talos-op-linux-ff280e3639548fc8c366f6e4bd471e715ac590c7.tar.gz talos-op-linux-ff280e3639548fc8c366f6e4bd471e715ac590c7.zip |
Merge tag 'spi-v4.6' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi
Pull spi updates from Mark Brown:
"Not the biggest set of changes for SPI but a bit of a pickup in
activity on the core:
- Support for memory mapped read from flash devices via a SPI
controller.
- The beginnings of a message rewriting framework in the core which
should in time allow us to support transforming messages to work
around the limits of controllers or optimise the performance for
controllers transparently to calling drivers.
- Updates to the PXA2xx, the main functional change being to improve
the ACPI support.
- A new driver for the Analog Devices AXI SPI engine"
* tag 'spi-v4.6' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi: (66 commits)
spi: Add gfp parameter to kernel-doc to fix build warning
spi: Fix htmldocs build error due struct spi_replaced_transfers
spi: rockchip: covert rsd_nsecs to u32 type
spi: rockchip: header file cleanup
spi: xilinx: Add devicetree binding for spi-xilinx
spi: respect the maximum segment size of DMA device
spi: rockchip: check requesting dma channel with EPROBE_DEFER
spi: rockchip: migrate to dmaengine_terminate_async
spi: rockchip: check return value of dmaengine_prep_slave_sg
spi: core: Fix deadlock when sending messages
spi/rockchip: fix endian mode for 16-bit transfers
spi/rockchip: Make sure spi clk is on in rockchip_spi_set_cs
spi: pxa2xx: Use newer more explicit DMAengine terminate API
spi: pxa2xx: Add support for Intel Broxton B-Step
spi: lp-8841: return correct error code from probe
spi: imx: drop bogus tests for rx/tx bufs in DMA transfer
spi: imx: set MX51_ECSPI_CTRL_SMC bit in setup function
spi: imx: make some register defines simpler
spi: imx: remove unnecessary bit clearing in mx51_ecspi_config
spi: imx: add support for all SPI word width for DMA
...
Diffstat (limited to 'drivers/spi/spi.c')
-rw-r--r-- | drivers/spi/spi.c | 426 |
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); |