Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

spi: offload: add support for hardware triggers

Extend SPI offloading to support hardware triggers.

This allows an arbitrary hardware trigger to be used to start a SPI
transfer that was previously set up with spi_optimize_message().

A new struct spi_offload_trigger is introduced that can be used to
configure any type of trigger. It has a type discriminator and a union
to allow it to be extended in the future. Two trigger types are defined
to start with. One is a trigger that indicates that the SPI peripheral
is ready to read or write data. The other is a periodic trigger to
repeat a SPI message at a fixed rate.

There is also a spi_offload_hw_trigger_validate() function that works
similar to clk_round_rate(). It basically asks the question of if we
enabled the hardware trigger what would the actual parameters be. This
can be used to test if the requested trigger type is actually supported
by the hardware and for periodic triggers, it can be used to find the
actual rate that the hardware is capable of.

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
Link: https://patch.msgid.link/20250207-dlech-mainline-spi-engine-offload-2-v8-2-e48a489be48c@baylibre.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

David Lechner and committed by
Mark Brown
d7231be4 8e02d188

+358
+281
drivers/spi/spi-offload.c
··· 19 19 #include <linux/cleanup.h> 20 20 #include <linux/device.h> 21 21 #include <linux/export.h> 22 + #include <linux/kref.h> 23 + #include <linux/list.h> 22 24 #include <linux/mutex.h> 25 + #include <linux/of.h> 26 + #include <linux/property.h> 23 27 #include <linux/spi/offload/consumer.h> 24 28 #include <linux/spi/offload/provider.h> 25 29 #include <linux/spi/offload/types.h> ··· 34 30 struct spi_controller *controller; 35 31 struct spi_offload *offload; 36 32 }; 33 + 34 + struct spi_offload_trigger { 35 + struct list_head list; 36 + struct kref ref; 37 + struct fwnode_handle *fwnode; 38 + /* synchronizes calling ops and driver registration */ 39 + struct mutex lock; 40 + /* 41 + * If the provider goes away while the consumer still has a reference, 42 + * ops and priv will be set to NULL and all calls will fail with -ENODEV. 43 + */ 44 + const struct spi_offload_trigger_ops *ops; 45 + void *priv; 46 + }; 47 + 48 + static LIST_HEAD(spi_offload_triggers); 49 + static DEFINE_MUTEX(spi_offload_triggers_lock); 37 50 38 51 /** 39 52 * devm_spi_offload_alloc() - Allocate offload instance ··· 133 112 return resource->offload; 134 113 } 135 114 EXPORT_SYMBOL_GPL(devm_spi_offload_get); 115 + 116 + static void spi_offload_trigger_free(struct kref *ref) 117 + { 118 + struct spi_offload_trigger *trigger = 119 + container_of(ref, struct spi_offload_trigger, ref); 120 + 121 + mutex_destroy(&trigger->lock); 122 + fwnode_handle_put(trigger->fwnode); 123 + kfree(trigger); 124 + } 125 + 126 + static void spi_offload_trigger_put(void *data) 127 + { 128 + struct spi_offload_trigger *trigger = data; 129 + 130 + scoped_guard(mutex, &trigger->lock) 131 + if (trigger->ops && trigger->ops->release) 132 + trigger->ops->release(trigger); 133 + 134 + kref_put(&trigger->ref, spi_offload_trigger_free); 135 + } 136 + 137 + static struct spi_offload_trigger 138 + *spi_offload_trigger_get(enum spi_offload_trigger_type type, 139 + struct fwnode_reference_args *args) 140 + { 141 + struct spi_offload_trigger *trigger; 142 + bool match = false; 143 + int ret; 144 + 145 + guard(mutex)(&spi_offload_triggers_lock); 146 + 147 + list_for_each_entry(trigger, &spi_offload_triggers, list) { 148 + if (trigger->fwnode != args->fwnode) 149 + continue; 150 + 151 + match = trigger->ops->match(trigger, type, args->args, args->nargs); 152 + if (match) 153 + break; 154 + } 155 + 156 + if (!match) 157 + return ERR_PTR(-EPROBE_DEFER); 158 + 159 + guard(mutex)(&trigger->lock); 160 + 161 + if (!trigger->ops) 162 + return ERR_PTR(-ENODEV); 163 + 164 + if (trigger->ops->request) { 165 + ret = trigger->ops->request(trigger, type, args->args, args->nargs); 166 + if (ret) 167 + return ERR_PTR(ret); 168 + } 169 + 170 + kref_get(&trigger->ref); 171 + 172 + return trigger; 173 + } 174 + 175 + /** 176 + * devm_spi_offload_trigger_get() - Get an offload trigger instance 177 + * @dev: Device for devm purposes. 178 + * @offload: Offload instance connected to a trigger. 179 + * @type: Trigger type to get. 180 + * 181 + * Return: Offload trigger instance or error on failure. 182 + */ 183 + struct spi_offload_trigger 184 + *devm_spi_offload_trigger_get(struct device *dev, 185 + struct spi_offload *offload, 186 + enum spi_offload_trigger_type type) 187 + { 188 + struct spi_offload_trigger *trigger; 189 + struct fwnode_reference_args args; 190 + int ret; 191 + 192 + ret = fwnode_property_get_reference_args(dev_fwnode(offload->provider_dev), 193 + "trigger-sources", 194 + "#trigger-source-cells", 0, 0, 195 + &args); 196 + if (ret) 197 + return ERR_PTR(ret); 198 + 199 + trigger = spi_offload_trigger_get(type, &args); 200 + fwnode_handle_put(args.fwnode); 201 + if (IS_ERR(trigger)) 202 + return trigger; 203 + 204 + ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger); 205 + if (ret) 206 + return ERR_PTR(ret); 207 + 208 + return trigger; 209 + } 210 + EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_get); 211 + 212 + /** 213 + * spi_offload_trigger_validate - Validate the requested trigger 214 + * @trigger: Offload trigger instance 215 + * @config: Trigger config to validate 216 + * 217 + * On success, @config may be modifed to reflect what the hardware can do. 218 + * For example, the frequency of a periodic trigger may be adjusted to the 219 + * nearest supported value. 220 + * 221 + * Callers will likely need to do additional validation of the modified trigger 222 + * parameters. 223 + * 224 + * Return: 0 on success, negative error code on failure. 225 + */ 226 + int spi_offload_trigger_validate(struct spi_offload_trigger *trigger, 227 + struct spi_offload_trigger_config *config) 228 + { 229 + guard(mutex)(&trigger->lock); 230 + 231 + if (!trigger->ops) 232 + return -ENODEV; 233 + 234 + if (!trigger->ops->validate) 235 + return -EOPNOTSUPP; 236 + 237 + return trigger->ops->validate(trigger, config); 238 + } 239 + EXPORT_SYMBOL_GPL(spi_offload_trigger_validate); 240 + 241 + /** 242 + * spi_offload_trigger_enable - enables trigger for offload 243 + * @offload: Offload instance 244 + * @trigger: Offload trigger instance 245 + * @config: Trigger config to validate 246 + * 247 + * There must be a prepared offload instance with the specified ID (i.e. 248 + * spi_optimize_message() was called with the same offload assigned to the 249 + * message). This will also reserve the bus for exclusive use by the offload 250 + * instance until the trigger is disabled. Any other attempts to send a 251 + * transfer or lock the bus will fail with -EBUSY during this time. 252 + * 253 + * Calls must be balanced with spi_offload_trigger_disable(). 254 + * 255 + * Context: can sleep 256 + * Return: 0 on success, else a negative error code. 257 + */ 258 + int spi_offload_trigger_enable(struct spi_offload *offload, 259 + struct spi_offload_trigger *trigger, 260 + struct spi_offload_trigger_config *config) 261 + { 262 + int ret; 263 + 264 + guard(mutex)(&trigger->lock); 265 + 266 + if (!trigger->ops) 267 + return -ENODEV; 268 + 269 + if (offload->ops && offload->ops->trigger_enable) { 270 + ret = offload->ops->trigger_enable(offload); 271 + if (ret) 272 + return ret; 273 + } 274 + 275 + if (trigger->ops->enable) { 276 + ret = trigger->ops->enable(trigger, config); 277 + if (ret) { 278 + if (offload->ops->trigger_disable) 279 + offload->ops->trigger_disable(offload); 280 + return ret; 281 + } 282 + } 283 + 284 + return 0; 285 + } 286 + EXPORT_SYMBOL_GPL(spi_offload_trigger_enable); 287 + 288 + /** 289 + * spi_offload_trigger_disable - disables hardware trigger for offload 290 + * @offload: Offload instance 291 + * @trigger: Offload trigger instance 292 + * 293 + * Disables the hardware trigger for the offload instance with the specified ID 294 + * and releases the bus for use by other clients. 295 + * 296 + * Context: can sleep 297 + */ 298 + void spi_offload_trigger_disable(struct spi_offload *offload, 299 + struct spi_offload_trigger *trigger) 300 + { 301 + if (offload->ops && offload->ops->trigger_disable) 302 + offload->ops->trigger_disable(offload); 303 + 304 + guard(mutex)(&trigger->lock); 305 + 306 + if (!trigger->ops) 307 + return; 308 + 309 + if (trigger->ops->disable) 310 + trigger->ops->disable(trigger); 311 + } 312 + EXPORT_SYMBOL_GPL(spi_offload_trigger_disable); 313 + 314 + /* Triggers providers */ 315 + 316 + static void spi_offload_trigger_unregister(void *data) 317 + { 318 + struct spi_offload_trigger *trigger = data; 319 + 320 + scoped_guard(mutex, &spi_offload_triggers_lock) 321 + list_del(&trigger->list); 322 + 323 + scoped_guard(mutex, &trigger->lock) { 324 + trigger->priv = NULL; 325 + trigger->ops = NULL; 326 + } 327 + 328 + kref_put(&trigger->ref, spi_offload_trigger_free); 329 + } 330 + 331 + /** 332 + * devm_spi_offload_trigger_register() - Allocate and register an offload trigger 333 + * @dev: Device for devm purposes. 334 + * @info: Provider-specific trigger info. 335 + * 336 + * Return: 0 on success, else a negative error code. 337 + */ 338 + int devm_spi_offload_trigger_register(struct device *dev, 339 + struct spi_offload_trigger_info *info) 340 + { 341 + struct spi_offload_trigger *trigger; 342 + 343 + if (!info->fwnode || !info->ops) 344 + return -EINVAL; 345 + 346 + trigger = kzalloc(sizeof(*trigger), GFP_KERNEL); 347 + if (!trigger) 348 + return -ENOMEM; 349 + 350 + kref_init(&trigger->ref); 351 + mutex_init(&trigger->lock); 352 + trigger->fwnode = fwnode_handle_get(info->fwnode); 353 + trigger->ops = info->ops; 354 + trigger->priv = info->priv; 355 + 356 + scoped_guard(mutex, &spi_offload_triggers_lock) 357 + list_add_tail(&trigger->list, &spi_offload_triggers); 358 + 359 + return devm_add_action_or_reset(dev, spi_offload_trigger_unregister, trigger); 360 + } 361 + EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register); 362 + 363 + /** 364 + * spi_offload_trigger_get_priv() - Get the private data for the trigger 365 + * 366 + * @trigger: Offload trigger instance. 367 + * 368 + * Return: Private data for the trigger. 369 + */ 370 + void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger) 371 + { 372 + return trigger->priv; 373 + } 374 + EXPORT_SYMBOL_GPL(spi_offload_trigger_get_priv);
+12
include/linux/spi/offload/consumer.h
··· 19 19 struct spi_offload *devm_spi_offload_get(struct device *dev, struct spi_device *spi, 20 20 const struct spi_offload_config *config); 21 21 22 + struct spi_offload_trigger 23 + *devm_spi_offload_trigger_get(struct device *dev, 24 + struct spi_offload *offload, 25 + enum spi_offload_trigger_type type); 26 + int spi_offload_trigger_validate(struct spi_offload_trigger *trigger, 27 + struct spi_offload_trigger_config *config); 28 + int spi_offload_trigger_enable(struct spi_offload *offload, 29 + struct spi_offload_trigger *trigger, 30 + struct spi_offload_trigger_config *config); 31 + void spi_offload_trigger_disable(struct spi_offload *offload, 32 + struct spi_offload_trigger *trigger); 33 + 22 34 #endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */
+28
include/linux/spi/offload/provider.h
··· 8 8 #define __LINUX_SPI_OFFLOAD_PROVIDER_H 9 9 10 10 #include <linux/module.h> 11 + #include <linux/spi/offload/types.h> 11 12 #include <linux/types.h> 12 13 13 14 MODULE_IMPORT_NS("SPI_OFFLOAD"); 14 15 15 16 struct device; 17 + struct spi_offload_trigger; 16 18 17 19 struct spi_offload *devm_spi_offload_alloc(struct device *dev, size_t priv_size); 20 + 21 + struct spi_offload_trigger_ops { 22 + bool (*match)(struct spi_offload_trigger *trigger, 23 + enum spi_offload_trigger_type type, u64 *args, u32 nargs); 24 + int (*request)(struct spi_offload_trigger *trigger, 25 + enum spi_offload_trigger_type type, u64 *args, u32 nargs); 26 + void (*release)(struct spi_offload_trigger *trigger); 27 + int (*validate)(struct spi_offload_trigger *trigger, 28 + struct spi_offload_trigger_config *config); 29 + int (*enable)(struct spi_offload_trigger *trigger, 30 + struct spi_offload_trigger_config *config); 31 + void (*disable)(struct spi_offload_trigger *trigger); 32 + }; 33 + 34 + struct spi_offload_trigger_info { 35 + /** @fwnode: Provider fwnode, used to match to consumer. */ 36 + struct fwnode_handle *fwnode; 37 + /** @ops: Provider-specific callbacks. */ 38 + const struct spi_offload_trigger_ops *ops; 39 + /** Provider-specific state to be used in callbacks. */ 40 + void *priv; 41 + }; 42 + 43 + int devm_spi_offload_trigger_register(struct device *dev, 44 + struct spi_offload_trigger_info *info); 45 + void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger); 18 46 19 47 #endif /* __LINUX_SPI_OFFLOAD_PROVIDER_H */
+37
include/linux/spi/offload/types.h
··· 38 38 struct device *provider_dev; 39 39 /** @priv: provider driver private data */ 40 40 void *priv; 41 + /** @ops: callbacks for offload support */ 42 + const struct spi_offload_ops *ops; 43 + }; 44 + 45 + enum spi_offload_trigger_type { 46 + /* Indication from SPI peripheral that data is read to read. */ 47 + SPI_OFFLOAD_TRIGGER_DATA_READY, 48 + /* Trigger comes from a periodic source such as a clock. */ 49 + SPI_OFFLOAD_TRIGGER_PERIODIC, 50 + }; 51 + 52 + struct spi_offload_trigger_periodic { 53 + u64 frequency_hz; 54 + }; 55 + 56 + struct spi_offload_trigger_config { 57 + /** @type: type discriminator for union */ 58 + enum spi_offload_trigger_type type; 59 + union { 60 + struct spi_offload_trigger_periodic periodic; 61 + }; 62 + }; 63 + 64 + /** 65 + * struct spi_offload_ops - callbacks implemented by offload providers 66 + */ 67 + struct spi_offload_ops { 68 + /** 69 + * @trigger_enable: Optional callback to enable the trigger for the 70 + * given offload instance. 71 + */ 72 + int (*trigger_enable)(struct spi_offload *offload); 73 + /** 74 + * @trigger_disable: Optional callback to disable the trigger for the 75 + * given offload instance. 76 + */ 77 + void (*trigger_disable)(struct spi_offload *offload); 41 78 }; 42 79 43 80 #endif /* __LINUX_SPI_OFFLOAD_TYPES_H */