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

SDCA System Suspend Support

Merge series from Charles Keepax <ckeepax@opensource.cirrus.com>:

Add support for system suspend into the class driver, now split
out into a separate patch series.

Where we got to on the previous discussion, was we don't currently
have any parts requiring download on runtime resume, doing so
will add noticeable delay to the runtime resume, and we are not
blocking someone from adding support for firmware download on
runtime resume in the future. Also as runtime resume is really
a kernel concept and power rails are primarily controlled by
ACPI it is quite unlikely anyone will actually power down the
part on a runtime suspend anyway. So this version of the chain
still only downloads firmware on probe and system resume.

+240 -30
+7
include/sound/sdca_interrupts.h
··· 84 84 struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev, 85 85 struct regmap *regmap, int irq); 86 86 87 + void sdca_irq_enable_early(struct sdca_function_data *function, 88 + struct sdca_interrupt_info *info); 89 + void sdca_irq_enable(struct sdca_function_data *function, 90 + struct sdca_interrupt_info *info); 91 + void sdca_irq_disable(struct sdca_function_data *function, 92 + struct sdca_interrupt_info *info); 93 + 87 94 #endif
+34
sound/soc/sdca/sdca_class.c
··· 205 205 drv->dev = dev; 206 206 drv->sdw = sdw; 207 207 mutex_init(&drv->regmap_lock); 208 + mutex_init(&drv->init_lock); 208 209 209 210 dev_set_drvdata(drv->dev, drv); 210 211 ··· 235 234 return ret; 236 235 237 236 queue_work(system_long_wq, &drv->boot_work); 237 + 238 + return 0; 239 + } 240 + 241 + static int class_suspend(struct device *dev) 242 + { 243 + struct sdca_class_drv *drv = dev_get_drvdata(dev); 244 + int ret; 245 + 246 + disable_irq(drv->sdw->irq); 247 + 248 + ret = pm_runtime_force_suspend(dev); 249 + if (ret) { 250 + dev_err(dev, "failed to force suspend: %d\n", ret); 251 + return ret; 252 + } 253 + 254 + return 0; 255 + } 256 + 257 + static int class_resume(struct device *dev) 258 + { 259 + struct sdca_class_drv *drv = dev_get_drvdata(dev); 260 + int ret; 261 + 262 + ret = pm_runtime_force_resume(dev); 263 + if (ret) { 264 + dev_err(dev, "failed to force resume: %d\n", ret); 265 + return ret; 266 + } 267 + 268 + enable_irq(drv->sdw->irq); 238 269 239 270 return 0; 240 271 } ··· 311 278 } 312 279 313 280 static const struct dev_pm_ops class_pm_ops = { 281 + SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume) 314 282 RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL) 315 283 }; 316 284
+2
sound/soc/sdca/sdca_class.h
··· 28 28 struct sdca_interrupt_info *irq_info; 29 29 30 30 struct mutex regmap_lock; 31 + /* Serialise function initialisations */ 32 + struct mutex init_lock; 31 33 struct work_struct boot_work; 32 34 struct completion device_attach; 33 35
+109 -25
sound/soc/sdca/sdca_class_function.c
··· 8 8 */ 9 9 10 10 #include <linux/auxiliary_bus.h> 11 + #include <linux/cleanup.h> 11 12 #include <linux/minmax.h> 12 13 #include <linux/module.h> 13 14 #include <linux/pm.h> ··· 34 33 struct sdca_class_drv *core; 35 34 36 35 struct sdca_function_data *function; 36 + bool suspended; 37 37 }; 38 38 39 39 static void class_function_regmap_lock(void *data) ··· 212 210 .endianness = 1, 213 211 }; 214 212 213 + static int class_function_init_device(struct class_function_drv *drv, 214 + unsigned int status) 215 + { 216 + int ret; 217 + 218 + if (!(status & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) { 219 + dev_dbg(drv->dev, "reset function device\n"); 220 + 221 + ret = sdca_reset_function(drv->dev, drv->function, drv->regmap); 222 + if (ret) 223 + return ret; 224 + } 225 + 226 + if (status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) { 227 + dev_dbg(drv->dev, "write initialisation\n"); 228 + 229 + ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap, 230 + drv->function); 231 + if (ret) 232 + return ret; 233 + } 234 + 235 + return 0; 236 + } 237 + 215 238 static int class_function_boot(struct class_function_drv *drv) 216 239 { 217 240 unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr, ··· 245 218 unsigned int val; 246 219 int ret; 247 220 221 + guard(mutex)(&drv->core->init_lock); 222 + 248 223 ret = regmap_read(drv->regmap, reg, &val); 249 224 if (ret < 0) { 250 225 dev_err(drv->dev, "failed to read function status: %d\n", ret); 251 226 return ret; 252 227 } 253 228 254 - if (!(val & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) { 255 - dev_dbg(drv->dev, "reset function device\n"); 256 - 257 - ret = sdca_reset_function(drv->dev, drv->function, drv->regmap); 258 - if (ret) 259 - return ret; 260 - } 261 - 262 - if (val & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) { 263 - dev_dbg(drv->dev, "write initialisation\n"); 264 - 265 - ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap, 266 - drv->function); 267 - if (ret) 268 - return ret; 269 - 270 - ret = regmap_write(drv->regmap, reg, 271 - SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); 272 - if (ret < 0) { 273 - dev_err(drv->dev, 274 - "failed to clear function init status: %d\n", 275 - ret); 276 - return ret; 277 - } 278 - } 229 + ret = class_function_init_device(drv, val); 230 + if (ret) 231 + return ret; 279 232 280 233 /* Start FDL process */ 281 234 ret = sdca_irq_populate_early(drv->dev, drv->regmap, drv->function, ··· 421 414 struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 422 415 int ret; 423 416 417 + guard(mutex)(&drv->core->init_lock); 418 + 424 419 regcache_mark_dirty(drv->regmap); 425 420 regcache_cache_only(drv->regmap, false); 421 + 422 + if (drv->suspended) { 423 + unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr, 424 + SDCA_ENTITY_TYPE_ENTITY_0, 425 + SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0); 426 + unsigned int val; 427 + 428 + ret = regmap_read(drv->regmap, reg, &val); 429 + if (ret < 0) { 430 + dev_err(drv->dev, "failed to read function status: %d\n", ret); 431 + goto err; 432 + } 433 + 434 + ret = class_function_init_device(drv, val); 435 + if (ret) 436 + goto err; 437 + 438 + sdca_irq_enable_early(drv->function, drv->core->irq_info); 439 + 440 + ret = sdca_fdl_sync(drv->dev, drv->function, drv->core->irq_info); 441 + if (ret) 442 + goto err; 443 + 444 + sdca_irq_enable(drv->function, drv->core->irq_info); 445 + 446 + ret = regmap_write(drv->regmap, reg, 0xFF); 447 + if (ret < 0) { 448 + dev_err(drv->dev, "failed to clear function status: %d\n", ret); 449 + goto err; 450 + } 451 + 452 + drv->suspended = false; 453 + } 426 454 427 455 ret = regcache_sync(drv->regmap); 428 456 if (ret) { ··· 473 431 return ret; 474 432 } 475 433 434 + static int class_function_suspend(struct device *dev) 435 + { 436 + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); 437 + struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 438 + int ret; 439 + 440 + drv->suspended = true; 441 + 442 + /* Ensure runtime resume runs on resume */ 443 + ret = pm_runtime_resume_and_get(dev); 444 + if (ret) { 445 + dev_err(dev, "failed to resume for suspend: %d\n", ret); 446 + return ret; 447 + } 448 + 449 + sdca_irq_disable(drv->function, drv->core->irq_info); 450 + 451 + ret = pm_runtime_force_suspend(dev); 452 + if (ret) { 453 + dev_err(dev, "failed to force suspend: %d\n", ret); 454 + return ret; 455 + } 456 + 457 + pm_runtime_put_noidle(dev); 458 + 459 + return 0; 460 + } 461 + 462 + static int class_function_resume(struct device *dev) 463 + { 464 + int ret; 465 + 466 + ret = pm_runtime_force_resume(dev); 467 + if (ret) { 468 + dev_err(dev, "failed to force resume: %d\n", ret); 469 + return ret; 470 + } 471 + 472 + return 0; 473 + } 474 + 476 475 static const struct dev_pm_ops class_function_pm_ops = { 476 + SYSTEM_SLEEP_PM_OPS(class_function_suspend, class_function_resume) 477 477 RUNTIME_PM_OPS(class_function_runtime_suspend, 478 478 class_function_runtime_resume, NULL) 479 479 };
+88 -5
sound/soc/sdca/sdca_interrupts.c
··· 205 205 irqreturn_t irqret = IRQ_NONE; 206 206 int ret; 207 207 208 - ret = pm_runtime_get_sync(dev); 209 - if (ret < 0) { 210 - dev_err(dev, "failed to resume for fdl: %d\n", ret); 211 - goto error; 208 + /* 209 + * FDL has to run from the system resume handler, at which point 210 + * we can't wait for the pm runtime. 211 + */ 212 + if (completion_done(&dev->power.completion)) { 213 + ret = pm_runtime_get_sync(dev); 214 + if (ret < 0) { 215 + dev_err(dev, "failed to resume for fdl: %d\n", ret); 216 + goto error; 217 + } 212 218 } 213 219 214 220 ret = sdca_fdl_process(interrupt); ··· 223 217 224 218 irqret = IRQ_HANDLED; 225 219 error: 226 - pm_runtime_put(dev); 220 + if (completion_done(&dev->power.completion)) 221 + pm_runtime_put(dev); 227 222 return irqret; 228 223 } 229 224 ··· 548 541 return info; 549 542 } 550 543 EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA"); 544 + 545 + static void irq_enable_flags(struct sdca_function_data *function, 546 + struct sdca_interrupt_info *info, bool early) 547 + { 548 + struct sdca_interrupt *interrupt; 549 + int i; 550 + 551 + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { 552 + interrupt = &info->irqs[i]; 553 + 554 + if (!interrupt || interrupt->function != function) 555 + continue; 556 + 557 + switch (SDCA_CTL_TYPE(interrupt->entity->type, 558 + interrupt->control->sel)) { 559 + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): 560 + if (early) 561 + enable_irq(interrupt->irq); 562 + break; 563 + default: 564 + if (!early) 565 + enable_irq(interrupt->irq); 566 + break; 567 + } 568 + } 569 + } 570 + 571 + /** 572 + * sdca_irq_enable_early - Re-enable early SDCA IRQs for a given function 573 + * @function: Pointer to the SDCA Function. 574 + * @info: Pointer to the SDCA interrupt info for this device. 575 + * 576 + * The early version of the IRQ enable allows enabling IRQs which may be 577 + * necessary to bootstrap functionality for other IRQs, such as the FDL 578 + * process. 579 + */ 580 + void sdca_irq_enable_early(struct sdca_function_data *function, 581 + struct sdca_interrupt_info *info) 582 + { 583 + irq_enable_flags(function, info, true); 584 + } 585 + EXPORT_SYMBOL_NS_GPL(sdca_irq_enable_early, "SND_SOC_SDCA"); 586 + 587 + /** 588 + * sdca_irq_enable - Re-enable SDCA IRQs for a given function 589 + * @function: Pointer to the SDCA Function. 590 + * @info: Pointer to the SDCA interrupt info for this device. 591 + */ 592 + void sdca_irq_enable(struct sdca_function_data *function, 593 + struct sdca_interrupt_info *info) 594 + { 595 + irq_enable_flags(function, info, false); 596 + } 597 + EXPORT_SYMBOL_NS_GPL(sdca_irq_enable, "SND_SOC_SDCA"); 598 + 599 + /** 600 + * sdca_irq_disable - Disable SDCA IRQs for a given function 601 + * @function: Pointer to the SDCA Function. 602 + * @info: Pointer to the SDCA interrupt info for this device. 603 + */ 604 + void sdca_irq_disable(struct sdca_function_data *function, 605 + struct sdca_interrupt_info *info) 606 + { 607 + struct sdca_interrupt *interrupt; 608 + int i; 609 + 610 + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { 611 + interrupt = &info->irqs[i]; 612 + 613 + if (!interrupt || interrupt->function != function) 614 + continue; 615 + 616 + disable_irq(interrupt->irq); 617 + } 618 + } 619 + EXPORT_SYMBOL_NS_GPL(sdca_irq_disable, "SND_SOC_SDCA");