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