summaryrefslogtreecommitdiff
path: root/drivers/firmware/cirrus/cs_dsp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware/cirrus/cs_dsp.c')
-rw-r--r--drivers/firmware/cirrus/cs_dsp.c278
1 files changed, 278 insertions, 0 deletions
diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c
index 8a347b938406..42c9cd0ebfb7 100644
--- a/drivers/firmware/cirrus/cs_dsp.c
+++ b/drivers/firmware/cirrus/cs_dsp.c
@@ -275,6 +275,12 @@
#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff
#define HALO_MPU_VIO_ERR_SRC_SHIFT 0
+/*
+ * Write Sequence
+ */
+#define WSEQ_OP_MAX_WORDS 3
+#define WSEQ_END_OF_SCRIPT 0xFFFFFF
+
struct cs_dsp_ops {
bool (*validate_version)(struct cs_dsp *dsp, unsigned int version);
unsigned int (*parse_sizes)(struct cs_dsp *dsp,
@@ -3495,6 +3501,278 @@ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits)
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP);
+
+struct cs_dsp_wseq_op {
+ struct list_head list;
+ u32 address;
+ u32 data;
+ u16 offset;
+ u8 operation;
+};
+
+static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
+{
+ struct cs_dsp_wseq_op *op, *op_tmp;
+
+ list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) {
+ list_del(&op->list);
+ devm_kfree(dsp->dev, op);
+ }
+}
+
+static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
+{
+ struct cs_dsp_wseq_op *op = NULL;
+ struct cs_dsp_chunk chunk;
+ u8 *words;
+ int ret;
+
+ if (!wseq->ctl) {
+ cs_dsp_err(dsp, "No control for write sequence\n");
+ return -EINVAL;
+ }
+
+ words = kzalloc(wseq->ctl->len, GFP_KERNEL);
+ if (!words)
+ return -ENOMEM;
+
+ ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len);
+ if (ret) {
+ cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret);
+ goto err_free;
+ }
+
+ INIT_LIST_HEAD(&wseq->ops);
+
+ chunk = cs_dsp_chunk(words, wseq->ctl->len);
+
+ while (!cs_dsp_chunk_end(&chunk)) {
+ op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL);
+ if (!op) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ op->offset = cs_dsp_chunk_bytes(&chunk);
+ op->operation = cs_dsp_chunk_read(&chunk, 8);
+
+ switch (op->operation) {
+ case CS_DSP_WSEQ_END:
+ op->data = WSEQ_END_OF_SCRIPT;
+ break;
+ case CS_DSP_WSEQ_UNLOCK:
+ op->data = cs_dsp_chunk_read(&chunk, 16);
+ break;
+ case CS_DSP_WSEQ_ADDR8:
+ op->address = cs_dsp_chunk_read(&chunk, 8);
+ op->data = cs_dsp_chunk_read(&chunk, 32);
+ break;
+ case CS_DSP_WSEQ_H16:
+ case CS_DSP_WSEQ_L16:
+ op->address = cs_dsp_chunk_read(&chunk, 24);
+ op->data = cs_dsp_chunk_read(&chunk, 16);
+ break;
+ case CS_DSP_WSEQ_FULL:
+ op->address = cs_dsp_chunk_read(&chunk, 32);
+ op->data = cs_dsp_chunk_read(&chunk, 32);
+ break;
+ default:
+ ret = -EINVAL;
+ cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation);
+ devm_kfree(dsp->dev, op);
+ goto err_free;
+ }
+
+ list_add_tail(&op->list, &wseq->ops);
+
+ if (op->operation == CS_DSP_WSEQ_END)
+ break;
+ }
+
+ if (op && op->operation != CS_DSP_WSEQ_END) {
+ cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname);
+ ret = -ENOENT;
+ }
+
+err_free:
+ kfree(words);
+
+ return ret;
+}
+
+/**
+ * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware
+ * @dsp: Pointer to DSP structure
+ * @wseqs: List of write sequences to initialize
+ * @num_wseqs: Number of write sequences to initialize
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs)
+{
+ int i, ret;
+
+ lockdep_assert_held(&dsp->pwr_lock);
+
+ for (i = 0; i < num_wseqs; i++) {
+ ret = cs_dsp_populate_wseq(dsp, &wseqs[i]);
+ if (ret) {
+ cs_dsp_wseq_clear(dsp, &wseqs[i]);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP);
+
+static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code,
+ struct list_head *wseq_ops)
+{
+ struct cs_dsp_wseq_op *op;
+
+ list_for_each_entry(op, wseq_ops, list) {
+ if (op->operation == op_code && op->address == addr)
+ return op;
+ }
+
+ return NULL;
+}
+
+/**
+ * cs_dsp_wseq_write() - Add or update an entry in a write sequence
+ * @dsp: Pointer to a DSP structure
+ * @wseq: Write sequence to write to
+ * @addr: Address of the register to be written to
+ * @data: Data to be written
+ * @op_code: The type of operation of the new entry
+ * @update: If true, searches for the first entry in the write sequence with
+ * the same address and op_code, and replaces it. If false, creates a new entry
+ * at the tail
+ *
+ * This function formats register address and value pairs into the format
+ * required for write sequence entries, and either updates or adds the
+ * new entry into the write sequence.
+ *
+ * If update is set to true and no matching entry is found, it will add a new entry.
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+ u32 addr, u32 data, u8 op_code, bool update)
+{
+ struct cs_dsp_wseq_op *op_end, *op_new = NULL;
+ u32 words[WSEQ_OP_MAX_WORDS];
+ struct cs_dsp_chunk chunk;
+ int new_op_size, ret;
+
+ if (update)
+ op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops);
+
+ /* If entry to update is not found, treat it as a new operation */
+ if (!op_new) {
+ op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops);
+ if (!op_end) {
+ cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname);
+ return -EINVAL;
+ }
+
+ op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL);
+ if (!op_new)
+ return -ENOMEM;
+
+ op_new->operation = op_code;
+ op_new->address = addr;
+ op_new->offset = op_end->offset;
+ update = false;
+ }
+
+ op_new->data = data;
+
+ chunk = cs_dsp_chunk(words, sizeof(words));
+ cs_dsp_chunk_write(&chunk, 8, op_new->operation);
+
+ switch (op_code) {
+ case CS_DSP_WSEQ_FULL:
+ cs_dsp_chunk_write(&chunk, 32, op_new->address);
+ cs_dsp_chunk_write(&chunk, 32, op_new->data);
+ break;
+ case CS_DSP_WSEQ_L16:
+ case CS_DSP_WSEQ_H16:
+ cs_dsp_chunk_write(&chunk, 24, op_new->address);
+ cs_dsp_chunk_write(&chunk, 16, op_new->data);
+ break;
+ default:
+ ret = -EINVAL;
+ cs_dsp_err(dsp, "Operation %X not supported\n", op_code);
+ goto op_new_free;
+ }
+
+ new_op_size = cs_dsp_chunk_bytes(&chunk);
+
+ if (!update) {
+ if (wseq->ctl->len - op_end->offset < new_op_size) {
+ cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname);
+ ret = -E2BIG;
+ goto op_new_free;
+ }
+
+ op_end->offset += new_op_size;
+
+ ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32),
+ &op_end->data, sizeof(u32));
+ if (ret)
+ goto op_new_free;
+
+ list_add_tail(&op_new->list, &op_end->list);
+ }
+
+ ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32),
+ words, new_op_size);
+ if (ret)
+ goto op_new_free;
+
+ return 0;
+
+op_new_free:
+ devm_kfree(dsp->dev, op_new);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP);
+
+/**
+ * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence
+ * @dsp: Pointer to a DSP structure
+ * @wseq: Write sequence to write to
+ * @reg_seq: List of address-data pairs
+ * @num_regs: Number of address-data pairs
+ * @op_code: The types of operations of the new entries
+ * @update: If true, searches for the first entry in the write sequence with
+ * the same address and op_code, and replaces it. If false, creates a new entry
+ * at the tail
+ *
+ * This function calls cs_dsp_wseq_write() for multiple address-data pairs.
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+ const struct reg_sequence *reg_seq, int num_regs,
+ u8 op_code, bool update)
+{
+ int i, ret;
+
+ for (i = 0; i < num_regs; i++) {
+ ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg,
+ reg_seq[i].def, op_code, update);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP);
+
MODULE_DESCRIPTION("Cirrus Logic DSP Support");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL v2");