mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Add a safe guard in spi_offload_trigger to check the existence of offload->ops before invoking the trigger_disable callback Signed-off-by: Andres Urian Florez <andres.emb.sys@gmail.com> Link: https://patch.msgid.link/20250608230422.325360-1-andres.emb.sys@gmail.com Signed-off-by: Mark Brown <broonie@kernel.org>
465 lines
12 KiB
C
465 lines
12 KiB
C
// 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(sizeof(*resource), GFP_KERNEL);
|
|
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);
|