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

Configure Feed

Select the types of activity you want to include in your feed.

firmware: cs_dsp: Add write sequence interface

A write sequence is a sequence of register addresses
and values executed by some Cirrus DSPs following
certain power state transitions.

Add support for Cirrus drivers to update or add to a
write sequence present in firmware.

Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
Reviewed-by: Jeff LaBundy <jeff@labundy.com>
Link: https://lore.kernel.org/r/20240620161745.2312359-2-jogletre@opensource.cirrus.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

James Ogletree and committed by
Lee Jones
205fdba5 1613e604

+305
+278
drivers/firmware/cirrus/cs_dsp.c
··· 275 275 #define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff 276 276 #define HALO_MPU_VIO_ERR_SRC_SHIFT 0 277 277 278 + /* 279 + * Write Sequence 280 + */ 281 + #define WSEQ_OP_MAX_WORDS 3 282 + #define WSEQ_END_OF_SCRIPT 0xFFFFFF 283 + 278 284 struct cs_dsp_ops { 279 285 bool (*validate_version)(struct cs_dsp *dsp, unsigned int version); 280 286 unsigned int (*parse_sizes)(struct cs_dsp *dsp, ··· 3403 3397 return result; 3404 3398 } 3405 3399 EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP); 3400 + 3401 + 3402 + struct cs_dsp_wseq_op { 3403 + struct list_head list; 3404 + u32 address; 3405 + u32 data; 3406 + u16 offset; 3407 + u8 operation; 3408 + }; 3409 + 3410 + static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq) 3411 + { 3412 + struct cs_dsp_wseq_op *op, *op_tmp; 3413 + 3414 + list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) { 3415 + list_del(&op->list); 3416 + devm_kfree(dsp->dev, op); 3417 + } 3418 + } 3419 + 3420 + static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq) 3421 + { 3422 + struct cs_dsp_wseq_op *op = NULL; 3423 + struct cs_dsp_chunk chunk; 3424 + u8 *words; 3425 + int ret; 3426 + 3427 + if (!wseq->ctl) { 3428 + cs_dsp_err(dsp, "No control for write sequence\n"); 3429 + return -EINVAL; 3430 + } 3431 + 3432 + words = kzalloc(wseq->ctl->len, GFP_KERNEL); 3433 + if (!words) 3434 + return -ENOMEM; 3435 + 3436 + ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len); 3437 + if (ret) { 3438 + cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret); 3439 + goto err_free; 3440 + } 3441 + 3442 + INIT_LIST_HEAD(&wseq->ops); 3443 + 3444 + chunk = cs_dsp_chunk(words, wseq->ctl->len); 3445 + 3446 + while (!cs_dsp_chunk_end(&chunk)) { 3447 + op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL); 3448 + if (!op) { 3449 + ret = -ENOMEM; 3450 + goto err_free; 3451 + } 3452 + 3453 + op->offset = cs_dsp_chunk_bytes(&chunk); 3454 + op->operation = cs_dsp_chunk_read(&chunk, 8); 3455 + 3456 + switch (op->operation) { 3457 + case CS_DSP_WSEQ_END: 3458 + op->data = WSEQ_END_OF_SCRIPT; 3459 + break; 3460 + case CS_DSP_WSEQ_UNLOCK: 3461 + op->data = cs_dsp_chunk_read(&chunk, 16); 3462 + break; 3463 + case CS_DSP_WSEQ_ADDR8: 3464 + op->address = cs_dsp_chunk_read(&chunk, 8); 3465 + op->data = cs_dsp_chunk_read(&chunk, 32); 3466 + break; 3467 + case CS_DSP_WSEQ_H16: 3468 + case CS_DSP_WSEQ_L16: 3469 + op->address = cs_dsp_chunk_read(&chunk, 24); 3470 + op->data = cs_dsp_chunk_read(&chunk, 16); 3471 + break; 3472 + case CS_DSP_WSEQ_FULL: 3473 + op->address = cs_dsp_chunk_read(&chunk, 32); 3474 + op->data = cs_dsp_chunk_read(&chunk, 32); 3475 + break; 3476 + default: 3477 + ret = -EINVAL; 3478 + cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation); 3479 + devm_kfree(dsp->dev, op); 3480 + goto err_free; 3481 + } 3482 + 3483 + list_add_tail(&op->list, &wseq->ops); 3484 + 3485 + if (op->operation == CS_DSP_WSEQ_END) 3486 + break; 3487 + } 3488 + 3489 + if (op && op->operation != CS_DSP_WSEQ_END) { 3490 + cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname); 3491 + ret = -ENOENT; 3492 + } 3493 + 3494 + err_free: 3495 + kfree(words); 3496 + 3497 + return ret; 3498 + } 3499 + 3500 + /** 3501 + * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware 3502 + * @dsp: Pointer to DSP structure 3503 + * @wseqs: List of write sequences to initialize 3504 + * @num_wseqs: Number of write sequences to initialize 3505 + * 3506 + * Return: Zero for success, a negative number on error. 3507 + */ 3508 + int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs) 3509 + { 3510 + int i, ret; 3511 + 3512 + lockdep_assert_held(&dsp->pwr_lock); 3513 + 3514 + for (i = 0; i < num_wseqs; i++) { 3515 + ret = cs_dsp_populate_wseq(dsp, &wseqs[i]); 3516 + if (ret) { 3517 + cs_dsp_wseq_clear(dsp, &wseqs[i]); 3518 + return ret; 3519 + } 3520 + } 3521 + 3522 + return 0; 3523 + } 3524 + EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP); 3525 + 3526 + static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code, 3527 + struct list_head *wseq_ops) 3528 + { 3529 + struct cs_dsp_wseq_op *op; 3530 + 3531 + list_for_each_entry(op, wseq_ops, list) { 3532 + if (op->operation == op_code && op->address == addr) 3533 + return op; 3534 + } 3535 + 3536 + return NULL; 3537 + } 3538 + 3539 + /** 3540 + * cs_dsp_wseq_write() - Add or update an entry in a write sequence 3541 + * @dsp: Pointer to a DSP structure 3542 + * @wseq: Write sequence to write to 3543 + * @addr: Address of the register to be written to 3544 + * @data: Data to be written 3545 + * @op_code: The type of operation of the new entry 3546 + * @update: If true, searches for the first entry in the write sequence with 3547 + * the same address and op_code, and replaces it. If false, creates a new entry 3548 + * at the tail 3549 + * 3550 + * This function formats register address and value pairs into the format 3551 + * required for write sequence entries, and either updates or adds the 3552 + * new entry into the write sequence. 3553 + * 3554 + * If update is set to true and no matching entry is found, it will add a new entry. 3555 + * 3556 + * Return: Zero for success, a negative number on error. 3557 + */ 3558 + int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, 3559 + u32 addr, u32 data, u8 op_code, bool update) 3560 + { 3561 + struct cs_dsp_wseq_op *op_end, *op_new = NULL; 3562 + u32 words[WSEQ_OP_MAX_WORDS]; 3563 + struct cs_dsp_chunk chunk; 3564 + int new_op_size, ret; 3565 + 3566 + if (update) 3567 + op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops); 3568 + 3569 + /* If entry to update is not found, treat it as a new operation */ 3570 + if (!op_new) { 3571 + op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops); 3572 + if (!op_end) { 3573 + cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname); 3574 + return -EINVAL; 3575 + } 3576 + 3577 + op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL); 3578 + if (!op_new) 3579 + return -ENOMEM; 3580 + 3581 + op_new->operation = op_code; 3582 + op_new->address = addr; 3583 + op_new->offset = op_end->offset; 3584 + update = false; 3585 + } 3586 + 3587 + op_new->data = data; 3588 + 3589 + chunk = cs_dsp_chunk(words, sizeof(words)); 3590 + cs_dsp_chunk_write(&chunk, 8, op_new->operation); 3591 + 3592 + switch (op_code) { 3593 + case CS_DSP_WSEQ_FULL: 3594 + cs_dsp_chunk_write(&chunk, 32, op_new->address); 3595 + cs_dsp_chunk_write(&chunk, 32, op_new->data); 3596 + break; 3597 + case CS_DSP_WSEQ_L16: 3598 + case CS_DSP_WSEQ_H16: 3599 + cs_dsp_chunk_write(&chunk, 24, op_new->address); 3600 + cs_dsp_chunk_write(&chunk, 16, op_new->data); 3601 + break; 3602 + default: 3603 + ret = -EINVAL; 3604 + cs_dsp_err(dsp, "Operation %X not supported\n", op_code); 3605 + goto op_new_free; 3606 + } 3607 + 3608 + new_op_size = cs_dsp_chunk_bytes(&chunk); 3609 + 3610 + if (!update) { 3611 + if (wseq->ctl->len - op_end->offset < new_op_size) { 3612 + cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname); 3613 + ret = -E2BIG; 3614 + goto op_new_free; 3615 + } 3616 + 3617 + op_end->offset += new_op_size; 3618 + 3619 + ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32), 3620 + &op_end->data, sizeof(u32)); 3621 + if (ret) 3622 + goto op_new_free; 3623 + 3624 + list_add_tail(&op_new->list, &op_end->list); 3625 + } 3626 + 3627 + ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32), 3628 + words, new_op_size); 3629 + if (ret) 3630 + goto op_new_free; 3631 + 3632 + return 0; 3633 + 3634 + op_new_free: 3635 + devm_kfree(dsp->dev, op_new); 3636 + 3637 + return ret; 3638 + } 3639 + EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP); 3640 + 3641 + /** 3642 + * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence 3643 + * @dsp: Pointer to a DSP structure 3644 + * @wseq: Write sequence to write to 3645 + * @reg_seq: List of address-data pairs 3646 + * @num_regs: Number of address-data pairs 3647 + * @op_code: The types of operations of the new entries 3648 + * @update: If true, searches for the first entry in the write sequence with 3649 + * the same address and op_code, and replaces it. If false, creates a new entry 3650 + * at the tail 3651 + * 3652 + * This function calls cs_dsp_wseq_write() for multiple address-data pairs. 3653 + * 3654 + * Return: Zero for success, a negative number on error. 3655 + */ 3656 + int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, 3657 + const struct reg_sequence *reg_seq, int num_regs, 3658 + u8 op_code, bool update) 3659 + { 3660 + int i, ret; 3661 + 3662 + for (i = 0; i < num_regs; i++) { 3663 + ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg, 3664 + reg_seq[i].def, op_code, update); 3665 + if (ret) 3666 + return ret; 3667 + } 3668 + 3669 + return 0; 3670 + } 3671 + EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP); 3406 3672 3407 3673 MODULE_DESCRIPTION("Cirrus Logic DSP Support"); 3408 3674 MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+27
include/linux/firmware/cirrus/cs_dsp.h
··· 42 42 #define CS_DSP_ACKED_CTL_MIN_VALUE 0 43 43 #define CS_DSP_ACKED_CTL_MAX_VALUE 0xFFFFFF 44 44 45 + /* 46 + * Write sequence operation codes 47 + */ 48 + #define CS_DSP_WSEQ_FULL 0x00 49 + #define CS_DSP_WSEQ_ADDR8 0x02 50 + #define CS_DSP_WSEQ_L16 0x04 51 + #define CS_DSP_WSEQ_H16 0x05 52 + #define CS_DSP_WSEQ_UNLOCK 0xFD 53 + #define CS_DSP_WSEQ_END 0xFF 54 + 45 55 /** 46 56 * struct cs_dsp_region - Describes a logical memory region in DSP address space 47 57 * @type: Memory region type ··· 267 257 int type, unsigned int id); 268 258 269 259 const char *cs_dsp_mem_region_name(unsigned int type); 260 + 261 + /** 262 + * struct cs_dsp_wseq - Describes a write sequence 263 + * @ctl: Write sequence cs_dsp control 264 + * @ops: Operations contained within 265 + */ 266 + struct cs_dsp_wseq { 267 + struct cs_dsp_coeff_ctl *ctl; 268 + struct list_head ops; 269 + }; 270 + 271 + int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs); 272 + int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, u32 addr, u32 data, 273 + u8 op_code, bool update); 274 + int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, 275 + const struct reg_sequence *reg_seq, int num_regs, 276 + u8 op_code, bool update); 270 277 271 278 /** 272 279 * struct cs_dsp_chunk - Describes a buffer holding data formatted for the DSP