Skip to content

Commit 205fdba

Browse files
james1993lag-linaro
authored andcommitted
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 <[email protected]> Signed-off-by: James Ogletree <[email protected]> Reviewed-by: Jeff LaBundy <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Lee Jones <[email protected]>
1 parent 1613e60 commit 205fdba

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed

drivers/firmware/cirrus/cs_dsp.c

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@
275275
#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff
276276
#define HALO_MPU_VIO_ERR_SRC_SHIFT 0
277277

278+
/*
279+
* Write Sequence
280+
*/
281+
#define WSEQ_OP_MAX_WORDS 3
282+
#define WSEQ_END_OF_SCRIPT 0xFFFFFF
283+
278284
struct cs_dsp_ops {
279285
bool (*validate_version)(struct cs_dsp *dsp, unsigned int version);
280286
unsigned int (*parse_sizes)(struct cs_dsp *dsp,
@@ -3398,6 +3404,278 @@ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits)
33983404
}
33993405
EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP);
34003406

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+
34013679
MODULE_DESCRIPTION("Cirrus Logic DSP Support");
34023680
MODULE_AUTHOR("Simon Trimmer <[email protected]>");
34033681
MODULE_LICENSE("GPL v2");

include/linux/firmware/cirrus/cs_dsp.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@
4242
#define CS_DSP_ACKED_CTL_MIN_VALUE 0
4343
#define CS_DSP_ACKED_CTL_MAX_VALUE 0xFFFFFF
4444

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+
4555
/**
4656
* struct cs_dsp_region - Describes a logical memory region in DSP address space
4757
* @type: Memory region type
@@ -258,6 +268,23 @@ struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp,
258268

259269
const char *cs_dsp_mem_region_name(unsigned int type);
260270

271+
/**
272+
* struct cs_dsp_wseq - Describes a write sequence
273+
* @ctl: Write sequence cs_dsp control
274+
* @ops: Operations contained within
275+
*/
276+
struct cs_dsp_wseq {
277+
struct cs_dsp_coeff_ctl *ctl;
278+
struct list_head ops;
279+
};
280+
281+
int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs);
282+
int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, u32 addr, u32 data,
283+
u8 op_code, bool update);
284+
int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
285+
const struct reg_sequence *reg_seq, int num_regs,
286+
u8 op_code, bool update);
287+
261288
/**
262289
* struct cs_dsp_chunk - Describes a buffer holding data formatted for the DSP
263290
* @data: Pointer to underlying buffer memory

0 commit comments

Comments
 (0)