| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2024 Analog Devices Inc. | |
| * Copyright (C) 2024 BayLibre, SAS | |
| */ | |
| /* | |
| * SPI Offloading support. | |
| * | |
| * Some SPI controllers support offloading of SPI transfers. Essentially, this | |
| * is the ability for a SPI controller to perform SPI transfers with minimal | |
| * or even no CPU intervention, e.g. via a specialized SPI controller with a | |
| * hardware trigger or via a conventional SPI controller using a non-Linux MCU | |
| * processor core to offload the work. | |
| */ | |
| #define DEFAULT_SYMBOL_NAMESPACE "SPI_OFFLOAD" | |
| #include <linux/cleanup.h> | |
| #include <linux/device.h> | |
| #include <linux/dmaengine.h> | |
| #include <linux/export.h> | |
| #include <linux/kref.h> | |
| #include <linux/list.h> | |
| #include <linux/mutex.h> | |
| #include <linux/of.h> | |
| #include <linux/property.h> | |
| #include <linux/spi/offload/consumer.h> | |
| #include <linux/spi/offload/provider.h> | |
| #include <linux/spi/offload/types.h> | |
| #include <linux/spi/spi.h> | |
| #include <linux/types.h> | |
| struct spi_controller_and_offload { | |
| struct spi_controller *controller; | |
| struct spi_offload *offload; | |
| }; | |
| struct spi_offload_trigger { | |
| struct list_head list; | |
| struct kref ref; | |
| struct fwnode_handle *fwnode; | |
| /* synchronizes calling ops and driver registration */ | |
| struct mutex lock; | |
| /* | |
| * If the provider goes away while the consumer still has a reference, | |
| * ops and priv will be set to NULL and all calls will fail with -ENODEV. | |
| */ | |
| const struct spi_offload_trigger_ops *ops; | |
| void *priv; | |
| }; | |
| static LIST_HEAD(spi_offload_triggers); | |
| static DEFINE_MUTEX(spi_offload_triggers_lock); | |
| /** | |
| * devm_spi_offload_alloc() - Allocate offload instance | |
| * @dev: Device for devm purposes and assigned to &struct spi_offload.provider_dev | |
| * @priv_size: Size of private data to allocate | |
| * | |
| * Offload providers should use this to allocate offload instances. | |
| * | |
| * Return: Pointer to new offload instance or error on failure. | |
| */ | |
| struct spi_offload *devm_spi_offload_alloc(struct device *dev, | |
| size_t priv_size) | |
| { | |
| struct spi_offload *offload; | |
| void *priv; | |
| offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL); | |
| if (!offload) | |
| return ERR_PTR(-ENOMEM); | |
| priv = devm_kzalloc(dev, priv_size, GFP_KERNEL); | |
| if (!priv) | |
| return ERR_PTR(-ENOMEM); | |
| offload->provider_dev = dev; | |
| offload->priv = priv; | |
| return offload; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_spi_offload_alloc); | |
| static void spi_offload_put(void *data) | |
| { | |
| struct spi_controller_and_offload *resource = data; | |
| resource->controller->put_offload(resource->offload); | |
| kfree(resource); | |
| } | |
| /** | |
| * devm_spi_offload_get() - Get an offload instance | |
| * @dev: Device for devm purposes | |
| * @spi: SPI device to use for the transfers | |
| * @config: Offload configuration | |
| * | |
| * Peripheral drivers call this function to get an offload instance that meets | |
| * the requirements specified in @config. If no suitable offload instance is | |
| * available, -ENODEV is returned. | |
| * | |
| * Return: Offload instance or error on failure. | |
| */ | |
| struct spi_offload *devm_spi_offload_get(struct device *dev, | |
| struct spi_device *spi, | |
| const struct spi_offload_config *config) | |
| { | |
| struct spi_controller_and_offload *resource; | |
| struct spi_offload *offload; | |
| int ret; | |
| if (!spi || !config) | |
| return ERR_PTR(-EINVAL); | |
| if (!spi->controller->get_offload) | |
| return ERR_PTR(-ENODEV); | |
| resource = kzalloc_obj(*resource); | |
| if (!resource) | |
| return ERR_PTR(-ENOMEM); | |
| offload = spi->controller->get_offload(spi, config); | |
| if (IS_ERR(offload)) { | |
| kfree(resource); | |
| return offload; | |
| } | |
| resource->controller = spi->controller; | |
| resource->offload = offload; | |
| ret = devm_add_action_or_reset(dev, spi_offload_put, resource); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return offload; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_spi_offload_get); | |
| static void spi_offload_trigger_free(struct kref *ref) | |
| { | |
| struct spi_offload_trigger *trigger = | |
| container_of(ref, struct spi_offload_trigger, ref); | |
| mutex_destroy(&trigger->lock); | |
| fwnode_handle_put(trigger->fwnode); | |
| kfree(trigger); | |
| } | |
| static void spi_offload_trigger_put(void *data) | |
| { | |
| struct spi_offload_trigger *trigger = data; | |
| scoped_guard(mutex, &trigger->lock) | |
| if (trigger->ops && trigger->ops->release) | |
| trigger->ops->release(trigger); | |
| kref_put(&trigger->ref, spi_offload_trigger_free); | |
| } | |
| static struct spi_offload_trigger | |
| *spi_offload_trigger_get(enum spi_offload_trigger_type type, | |
| struct fwnode_reference_args *args) | |
| { | |
| struct spi_offload_trigger *trigger; | |
| bool match = false; | |
| int ret; | |
| guard(mutex)(&spi_offload_triggers_lock); | |
| list_for_each_entry(trigger, &spi_offload_triggers, list) { | |
| if (trigger->fwnode != args->fwnode) | |
| continue; | |
| match = trigger->ops->match(trigger, type, args->args, args->nargs); | |
| if (match) | |
| break; | |
| } | |
| if (!match) | |
| return ERR_PTR(-EPROBE_DEFER); | |
| guard(mutex)(&trigger->lock); | |
| if (trigger->ops->request) { | |
| ret = trigger->ops->request(trigger, type, args->args, args->nargs); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| } | |
| kref_get(&trigger->ref); | |
| return trigger; | |
| } | |
| /** | |
| * devm_spi_offload_trigger_get() - Get an offload trigger instance | |
| * @dev: Device for devm purposes. | |
| * @offload: Offload instance connected to a trigger. | |
| * @type: Trigger type to get. | |
| * | |
| * Return: Offload trigger instance or error on failure. | |
| */ | |
| struct spi_offload_trigger | |
| *devm_spi_offload_trigger_get(struct device *dev, | |
| struct spi_offload *offload, | |
| enum spi_offload_trigger_type type) | |
| { | |
| struct spi_offload_trigger *trigger; | |
| struct fwnode_reference_args args; | |
| int ret; | |
| ret = fwnode_property_get_reference_args(dev_fwnode(offload->provider_dev), | |
| "trigger-sources", | |
| "#trigger-source-cells", 0, 0, | |
| &args); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| trigger = spi_offload_trigger_get(type, &args); | |
| fwnode_handle_put(args.fwnode); | |
| if (IS_ERR(trigger)) | |
| return trigger; | |
| ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return trigger; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_get); | |
| /** | |
| * spi_offload_trigger_validate - Validate the requested trigger | |
| * @trigger: Offload trigger instance | |
| * @config: Trigger config to validate | |
| * | |
| * On success, @config may be modifed to reflect what the hardware can do. | |
| * For example, the frequency of a periodic trigger may be adjusted to the | |
| * nearest supported value. | |
| * | |
| * Callers will likely need to do additional validation of the modified trigger | |
| * parameters. | |
| * | |
| * Return: 0 on success, negative error code on failure. | |
| */ | |
| int spi_offload_trigger_validate(struct spi_offload_trigger *trigger, | |
| struct spi_offload_trigger_config *config) | |
| { | |
| guard(mutex)(&trigger->lock); | |
| if (!trigger->ops) | |
| return -ENODEV; | |
| if (!trigger->ops->validate) | |
| return -EOPNOTSUPP; | |
| return trigger->ops->validate(trigger, config); | |
| } | |
| EXPORT_SYMBOL_GPL(spi_offload_trigger_validate); | |
| /** | |
| * spi_offload_trigger_enable - enables trigger for offload | |
| * @offload: Offload instance | |
| * @trigger: Offload trigger instance | |
| * @config: Trigger config to validate | |
| * | |
| * There must be a prepared offload instance with the specified ID (i.e. | |
| * spi_optimize_message() was called with the same offload assigned to the | |
| * message). This will also reserve the bus for exclusive use by the offload | |
| * instance until the trigger is disabled. Any other attempts to send a | |
| * transfer or lock the bus will fail with -EBUSY during this time. | |
| * | |
| * Calls must be balanced with spi_offload_trigger_disable(). | |
| * | |
| * Context: can sleep | |
| * Return: 0 on success, else a negative error code. | |
| */ | |
| int spi_offload_trigger_enable(struct spi_offload *offload, | |
| struct spi_offload_trigger *trigger, | |
| struct spi_offload_trigger_config *config) | |
| { | |
| int ret; | |
| guard(mutex)(&trigger->lock); | |
| if (!trigger->ops) | |
| return -ENODEV; | |
| if (offload->ops && offload->ops->trigger_enable) { | |
| ret = offload->ops->trigger_enable(offload); | |
| if (ret) | |
| return ret; | |
| } | |
| if (trigger->ops->enable) { | |
| ret = trigger->ops->enable(trigger, config); | |
| if (ret) { | |
| if (offload->ops && offload->ops->trigger_disable) | |
| offload->ops->trigger_disable(offload); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(spi_offload_trigger_enable); | |
| /** | |
| * spi_offload_trigger_disable - disables hardware trigger for offload | |
| * @offload: Offload instance | |
| * @trigger: Offload trigger instance | |
| * | |
| * Disables the hardware trigger for the offload instance with the specified ID | |
| * and releases the bus for use by other clients. | |
| * | |
| * Context: can sleep | |
| */ | |
| void spi_offload_trigger_disable(struct spi_offload *offload, | |
| struct spi_offload_trigger *trigger) | |
| { | |
| if (offload->ops && offload->ops->trigger_disable) | |
| offload->ops->trigger_disable(offload); | |
| guard(mutex)(&trigger->lock); | |
| if (!trigger->ops) | |
| return; | |
| if (trigger->ops->disable) | |
| trigger->ops->disable(trigger); | |
| } | |
| EXPORT_SYMBOL_GPL(spi_offload_trigger_disable); | |
| static void spi_offload_release_dma_chan(void *chan) | |
| { | |
| dma_release_channel(chan); | |
| } | |
| /** | |
| * devm_spi_offload_tx_stream_request_dma_chan - Get the DMA channel info for the TX stream | |
| * @dev: Device for devm purposes. | |
| * @offload: Offload instance | |
| * | |
| * This is the DMA channel that will provide data to transfers that use the | |
| * %SPI_OFFLOAD_XFER_TX_STREAM offload flag. | |
| * | |
| * Return: Pointer to DMA channel info, or negative error code | |
| */ | |
| struct dma_chan | |
| *devm_spi_offload_tx_stream_request_dma_chan(struct device *dev, | |
| struct spi_offload *offload) | |
| { | |
| struct dma_chan *chan; | |
| int ret; | |
| if (!offload->ops || !offload->ops->tx_stream_request_dma_chan) | |
| return ERR_PTR(-EOPNOTSUPP); | |
| chan = offload->ops->tx_stream_request_dma_chan(offload); | |
| if (IS_ERR(chan)) | |
| return chan; | |
| ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan, chan); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return chan; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_spi_offload_tx_stream_request_dma_chan); | |
| /** | |
| * devm_spi_offload_rx_stream_request_dma_chan - Get the DMA channel info for the RX stream | |
| * @dev: Device for devm purposes. | |
| * @offload: Offload instance | |
| * | |
| * This is the DMA channel that will receive data from transfers that use the | |
| * %SPI_OFFLOAD_XFER_RX_STREAM offload flag. | |
| * | |
| * Return: Pointer to DMA channel info, or negative error code | |
| */ | |
| struct dma_chan | |
| *devm_spi_offload_rx_stream_request_dma_chan(struct device *dev, | |
| struct spi_offload *offload) | |
| { | |
| struct dma_chan *chan; | |
| int ret; | |
| if (!offload->ops || !offload->ops->rx_stream_request_dma_chan) | |
| return ERR_PTR(-EOPNOTSUPP); | |
| chan = offload->ops->rx_stream_request_dma_chan(offload); | |
| if (IS_ERR(chan)) | |
| return chan; | |
| ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan, chan); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return chan; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_spi_offload_rx_stream_request_dma_chan); | |
| /* Triggers providers */ | |
| static void spi_offload_trigger_unregister(void *data) | |
| { | |
| struct spi_offload_trigger *trigger = data; | |
| scoped_guard(mutex, &spi_offload_triggers_lock) | |
| list_del(&trigger->list); | |
| scoped_guard(mutex, &trigger->lock) { | |
| trigger->priv = NULL; | |
| trigger->ops = NULL; | |
| } | |
| kref_put(&trigger->ref, spi_offload_trigger_free); | |
| } | |
| /** | |
| * devm_spi_offload_trigger_register() - Allocate and register an offload trigger | |
| * @dev: Device for devm purposes. | |
| * @info: Provider-specific trigger info. | |
| * | |
| * Return: 0 on success, else a negative error code. | |
| */ | |
| int devm_spi_offload_trigger_register(struct device *dev, | |
| struct spi_offload_trigger_info *info) | |
| { | |
| struct spi_offload_trigger *trigger; | |
| if (!info->fwnode || !info->ops || !info->ops->match) | |
| return -EINVAL; | |
| trigger = kzalloc(sizeof(*trigger), GFP_KERNEL); | |
| if (!trigger) | |
| return -ENOMEM; | |
| kref_init(&trigger->ref); | |
| mutex_init(&trigger->lock); | |
| trigger->fwnode = fwnode_handle_get(info->fwnode); | |
| trigger->ops = info->ops; | |
| trigger->priv = info->priv; | |
| scoped_guard(mutex, &spi_offload_triggers_lock) | |
| list_add_tail(&trigger->list, &spi_offload_triggers); | |
| return devm_add_action_or_reset(dev, spi_offload_trigger_unregister, trigger); | |
| } | |
| EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register); | |
| /** | |
| * spi_offload_trigger_get_priv() - Get the private data for the trigger | |
| * | |
| * @trigger: Offload trigger instance. | |
| * | |
| * Return: Private data for the trigger. | |
| */ | |
| void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger) | |
| { | |
| return trigger->priv; | |
| } | |
| EXPORT_SYMBOL_GPL(spi_offload_trigger_get_priv); |