summaryrefslogtreecommitdiff
path: root/sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c')
-rw-r--r--sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c1133
1 files changed, 1133 insertions, 0 deletions
diff --git a/sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c b/sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c
new file mode 100644
index 000000000000..83c6b4843576
--- /dev/null
+++ b/sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c
@@ -0,0 +1,1133 @@
+/*
+ * linux/sound/soc/codecs/aic3xxx/aic3xxx_cfw_ops.c
+ *
+ * Copyright (C) 2011 Texas Instruments Inc.,
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/slab.h>
+#include <sound/tlv.h>
+
+/* FIXME to be removed/replaced */
+#define warn(fmt, ...) printk(fmt "\n", ##__VA_ARGS__)
+#define error(fmt, ...) printk(fmt "\n", ##__VA_ARGS__)
+#define DBG printk
+
+#include "aic3xxx_cfw.h"
+#include "aic3xxx_cfw_ops.h"
+
+
+/* **Code beyond this point is compilable on host** */
+
+/*
+ * Firmware version numbers are used to make sure that the
+ * host and target code stay in sync. It is _not_ recommended
+ * to provide this number from the outside (E.g., from a makefile)
+ * Instead, a set of automated tools are relied upon to keep the numbers
+ * in sync at the time of host testing.
+ */
+#undef CFW_FW_IF_ID
+#define CFW_FW_IF_ID 0x3FA6D547
+static int aic3xxx_cfw_dlimage(struct cfw_state *ps, struct cfw_image *pim);
+static int aic3xxx_cfw_dlcfg(struct cfw_state *ps, struct cfw_image *pim);
+static int aic3xxx_cfw_dlctl(struct cfw_state *ps, struct cfw_block *pb,
+ u32 mute_flags);
+
+static void aic3xxx_cfw_dlcmds(struct cfw_state *ps, struct cfw_block *pb);
+static int aic3xxx_cfw_set_mode_id(struct cfw_state *ps);
+static int aic3xxx_cfw_mute(struct cfw_state *ps, int mute, u32 flags);
+static int aic3xxx_cfw_setmode_cfg_u(struct cfw_state *ps, int mode, int cfg);
+static int aic3xxx_cfw_setcfg_u(struct cfw_state *ps, int cfg);
+static int aic3xxx_cfw_transition_u(struct cfw_state *ps, char *ttype);
+static int aic3xxx_cfw_set_pll_u(struct cfw_state *ps, int asi);
+static int aic3xxx_cfw_control_u(struct cfw_state *ps, char *cname, int param);
+static struct cfw_project *aic3xxx_cfw_unpickle(void *pcfw, int n);
+
+static void aic3xxx_wait(struct cfw_state *ps, unsigned int reg, u8 mask,
+ u8 data);
+static void aic3xxx_set_bits(u8 *data, u8 mask, u8 val);
+static int aic3xxx_driver_init(struct cfw_state *ps);
+
+int aic3xxx_cfw_init(struct cfw_state *ps, const struct aic3xxx_codec_ops *ops,
+ struct snd_soc_codec *codec)
+{
+ ps->ops = ops;
+ ps->codec = codec;
+ ps->pjt = NULL;
+ mutex_init(&ps->mutex);
+
+ /* FIXME Need a special CONFIG flag to disable debug driver */
+ aic3xxx_driver_init(ps);
+ return 0;
+}
+
+int aic3xxx_cfw_lock(struct cfw_state *ps, int lock)
+{
+ if (lock)
+ mutex_lock(&ps->mutex);
+ else
+ mutex_unlock(&ps->mutex);
+ return 0;
+}
+
+int aic3xxx_cfw_reload(struct cfw_state *ps, void *pcfw, int n)
+{
+ ps->pjt = aic3xxx_cfw_unpickle(pcfw, n);
+ ps->cur_mode_id =
+ ps->cur_mode = ps->cur_pll = ps->cur_pfw =
+ ps->cur_ovly = ps->cur_cfg = -1;
+ if (ps->pjt == NULL)
+ return -1;
+ return 0;
+}
+
+int aic3xxx_cfw_setmode(struct cfw_state *ps, int mode)
+{
+ struct cfw_project *pjt;
+ int ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ pjt = ps->pjt;
+ if (pjt == NULL) {
+ aic3xxx_cfw_lock(ps, 0);
+ return -1;
+ }
+ ret = aic3xxx_cfw_setmode_cfg_u(ps, mode, pjt->mode[mode]->cfg);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+int aic3xxx_cfw_setcfg(struct cfw_state *ps, int cfg)
+{
+ int ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ ret = aic3xxx_cfw_setcfg_u(ps, cfg);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+static int aic3xxx_cfw_setcfg_u(struct cfw_state *ps, int cfg)
+{
+ struct cfw_project *pjt = ps->pjt;
+ struct cfw_pfw *pfw;
+ struct cfw_image *patch;
+
+ if (pjt == NULL)
+ return -1;
+ if (ps->cur_pfw < 0 || ps->cur_pfw >= pjt->npfw)
+ return -1; /* Non miniDSP */
+ if (ps->cur_cfg == cfg)
+ return 0;
+ pfw = pjt->pfw[ps->cur_pfw];
+ if (pfw->ncfg == 0 && cfg != 0)
+ return -1;
+ if (cfg > 0 && cfg >= pfw->ncfg)
+ return -1;
+ ps->cur_cfg = cfg;
+ aic3xxx_cfw_set_mode_id(ps);
+ patch =
+ pfw->ovly_cfg[CFW_OCFG_NDX(pfw, ps->cur_ovly, ps->cur_cfg)];
+ if (pfw->ncfg != 0)
+ return aic3xxx_cfw_dlcfg(ps, patch);
+ return 0;
+}
+
+int aic3xxx_cfw_setmode_cfg(struct cfw_state *ps, int mode, int cfg)
+{
+ int ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ ret = aic3xxx_cfw_setmode_cfg_u(ps, mode, cfg);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+static int aic3xxx_cfw_setmode_cfg_u(struct cfw_state *ps, int mode, int cfg)
+{
+ struct cfw_project *pjt = ps->pjt;
+ struct cfw_mode *pmode;
+ int which = 0, ocndx;
+
+ if (pjt == NULL)
+ goto err;
+ if ((mode < 0) || (mode >= pjt->nmode))
+ goto err;
+ if (cfg < 0)
+ goto err;
+ if (mode == ps->cur_mode)
+ return aic3xxx_cfw_setcfg_u(ps, cfg);
+
+ /* Apply exit sequence for previous mode if present */
+ if (ps->cur_mode >= 0)
+ aic3xxx_cfw_dlcmds(ps, pjt->mode[ps->cur_mode]->exit);
+ pmode = pjt->mode[mode];
+ if (pjt->mode[mode]->pfw < pjt->npfw) { /* New mode uses miniDSP */
+ struct cfw_image *im;
+ struct cfw_pfw *pfw = pjt->pfw[pmode->pfw];
+
+ /* Make sure cfg is valid and supported in this mode */
+ if (pfw->ncfg == 0 && cfg != 0)
+ goto err;
+ if (cfg > 0 && cfg >= pfw->ncfg)
+ goto err;
+
+ /*
+ * Decisions about which miniDSP to stop/restart are taken
+ * on the basis of sections present in the _base_ image
+ * This allows for correct sync mode operation even in cases
+ * where the base PFW uses both miniDSPs where a particular
+ * overlay applies only to one
+ */
+ im = pfw->base;
+ if (im->block[CFW_BLOCK_A_INST])
+ which |= AIC3XXX_COPS_MDSP_A;
+ if (im->block[CFW_BLOCK_D_INST])
+ which |= AIC3XXX_COPS_MDSP_D;
+
+ if (pmode->pfw != ps->cur_pfw) {
+
+ /* New mode requires different PFW */
+ ps->cur_pfw = pmode->pfw;
+ ps->cur_ovly = 0;
+ ps->cur_cfg = 0;
+
+ which = ps->ops->stop(ps->codec, which);
+ aic3xxx_cfw_dlimage(ps, im);
+ if (pmode->ovly && pmode->ovly < pfw->novly) {
+
+ /* New mode uses ovly */
+ ocndx = CFW_OCFG_NDX(pfw, pmode->ovly, cfg);
+ aic3xxx_cfw_dlimage(ps,
+ pfw->ovly_cfg[ocndx]);
+ } else if (pfw->ncfg > 0) {
+
+ /* new mode needs only a cfg change */
+ ocndx = CFW_OCFG_NDX(pfw, 0, cfg);
+ aic3xxx_cfw_dlimage(ps,
+ pfw->ovly_cfg[ocndx]);
+ }
+ ps->ops->restore(ps->codec, which);
+
+ } else if (pmode->ovly != ps->cur_ovly) {
+
+ /* New mode requires only an ovly change */
+ ocndx = CFW_OCFG_NDX(pfw, pmode->ovly, cfg);
+ which = ps->ops->stop(ps->codec, which);
+ aic3xxx_cfw_dlimage(ps, pfw->ovly_cfg[ocndx]);
+ ps->ops->restore(ps->codec, which);
+ } else if (pfw->ncfg > 0 && cfg != ps->cur_cfg) {
+
+ /* New mode requires only a cfg change */
+ ocndx = CFW_OCFG_NDX(pfw, pmode->ovly, cfg);
+ aic3xxx_cfw_dlcfg(ps, pfw->ovly_cfg[ocndx]);
+ }
+ ps->cur_ovly = pmode->ovly;
+ ps->cur_cfg = cfg;
+
+ ps->cur_mode = mode;
+ aic3xxx_cfw_set_pll_u(ps, 0);
+
+ } else if (pjt->mode[mode]->pfw != 0xFF) {
+
+ /* Not bypass mode */
+ warn("Bad pfw setting detected (%d). Max pfw=%d",
+ pmode->pfw, pjt->npfw);
+ }
+ ps->cur_mode = mode;
+ aic3xxx_cfw_set_mode_id(ps);
+
+ /* Transition to netural mode */
+ aic3xxx_cfw_transition_u(ps, "NEUTRAL");
+
+ /* Apply entry sequence if present */
+ aic3xxx_cfw_dlcmds(ps, pmode->entry);
+
+ DBG("setmode_cfg: DONE (mode=%d pfw=%d ovly=%d cfg=%d)",
+ ps->cur_mode, ps->cur_pfw, ps->cur_ovly, ps->cur_cfg);
+ return 0;
+
+err:
+ DBG("Failed to set firmware mode");
+ return -EINVAL;
+}
+
+int aic3xxx_cfw_transition(struct cfw_state *ps, char *ttype)
+{
+ int ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ ret = aic3xxx_cfw_transition_u(ps, ttype);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+static int aic3xxx_cfw_transition_u(struct cfw_state *ps, char *ttype)
+{
+ int i;
+
+ if (ps->pjt == NULL)
+ return -EINVAL;
+ for (i = 0; i < CFW_TRN_N; ++i) {
+ if (!strcasecmp(ttype, cfw_transition_id[i])) {
+ struct cfw_transition *pt = ps->pjt->transition[i];
+ DBG("Sending transition %s[%d]", ttype, i);
+ if (pt)
+ aic3xxx_cfw_dlcmds(ps, pt->block);
+ return 0;
+ }
+ }
+ warn("Transition %s not present or invalid", ttype);
+ return 0;
+}
+
+int aic3xxx_cfw_set_pll(struct cfw_state *ps, int asi)
+{
+ int ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ ret = aic3xxx_cfw_set_pll_u(ps, asi);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+static int aic3xxx_cfw_set_pll_u(struct cfw_state *ps, int asi)
+{
+ struct cfw_project *pjt = ps->pjt;
+ int pll_id;
+
+ if (pjt == NULL)
+ return -EINVAL;
+ if (ps->cur_mode < 0)
+ return -EINVAL;
+ pll_id = pjt->mode[ps->cur_mode]->pll;
+ if (ps->cur_pll != pll_id) {
+ DBG("Re-configuring PLL: %s==>%d", pjt->pll[pll_id]->name,
+ pll_id);
+ aic3xxx_cfw_dlcmds(ps, pjt->pll[pll_id]->seq);
+ ps->cur_pll = pll_id;
+ }
+ return 0;
+}
+
+int aic3xxx_cfw_control(struct cfw_state *ps, char *cname, int param)
+{
+ int ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ ret = aic3xxx_cfw_control_u(ps, cname, param);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+static int aic3xxx_cfw_control_u(struct cfw_state *ps, char *cname, int param)
+{
+ struct cfw_pfw *pfw;
+ int i;
+
+ if (ps->cur_pfw < 0 || ps->cur_pfw >= ps->pjt->npfw) {
+ warn("Not in MiniDSP mode");
+ return 0;
+ }
+ pfw = ps->pjt->pfw[ps->cur_pfw];
+ for (i = 0; i < pfw->nctrl; ++i) {
+ struct cfw_control *pc = pfw->ctrl[i];
+ if (strcasecmp(cname, pfw->ctrl[i]->name))
+ continue;
+ if (param < 0 || param > pc->imax) {
+ warn("Parameter out of range\n");
+ return -EINVAL;
+ }
+ DBG("Sending control %s[%d]", cname, param);
+ pc->icur = param;
+ aic3xxx_cfw_dlctl(ps, pc->output[param], pc->mute_flags);
+ return 0;
+ }
+ warn("Control named %s not found in pfw %s", cname, pfw->name);
+
+ return -EINVAL;
+}
+
+static void aic3xxx_cfw_op(struct cfw_state *ps, unsigned char *var,
+ struct cfw_cmd_op cmd)
+{
+ u32 op1, op2;
+ u32 cid = cmd.cid;
+
+ op1 = cmd.op1;
+ op2 = cmd.op2;
+ if (cid & CFW_CMD_OP1_ID)
+ op1 = var[op1];
+ if (cid & CFW_CMD_OP2_ID)
+ op2 = var[op2];
+ cid &= ~(CFW_CMD_OP1_ID | CFW_CMD_OP2_ID);
+
+ switch (cid) {
+ case CFW_CMD_OP_ADD:
+ var[cmd.dst] = op1 + op2;
+ break;
+ case CFW_CMD_OP_SUB:
+ var[cmd.dst] = op1 - op2;
+ break;
+ case CFW_CMD_OP_MUL:
+ var[cmd.dst] = op1 * op2;
+ break;
+ case CFW_CMD_OP_DIV:
+ var[cmd.dst] = op1 / op2;
+ break;
+ case CFW_CMD_OP_AND:
+ var[cmd.dst] = op1 & op2;
+ break;
+ case CFW_CMD_OP_OR:
+ var[cmd.dst] = op1 | op2;
+ break;
+ case CFW_CMD_OP_SHL:
+ var[cmd.dst] = (op1 << op2);
+ break;
+ case CFW_CMD_OP_SHR:
+ var[cmd.dst] = (op1 >> op2);
+ break;
+ case CFW_CMD_OP_RR:
+ while (op2--)
+ var[cmd.dst] = (op1 >> 1) | ((op1 & 1) << 7);
+ break;
+ case CFW_CMD_OP_XOR:
+ var[cmd.dst] = op1 ^ op2;
+ break;
+ case CFW_CMD_OP_NOT:
+ var[cmd.dst] = ~op1;
+ break;
+ case CFW_CMD_OP_LNOT:
+ var[cmd.dst] = !op1;
+ break;
+ default:
+ break;
+ }
+}
+
+static void aic3xxx_cfw_dlcmds(struct cfw_state *ps, struct cfw_block *pb)
+{
+ int pc = 0, cond = 0;
+ unsigned char var[256];
+
+ if (!pb)
+ return;
+ while (pc < pb->ncmds) {
+ union cfw_cmd *c = &(pb->cmd[pc]);
+ if (c->cid != CFW_CMD_BRANCH_IM &&
+ c->cid != CFW_CMD_BRANCH_ID && c->cid != CFW_CMD_NOP)
+ cond = 0;
+ switch (c->cid) {
+ case 0 ... (CFW_CMD_NOP - 1):
+ ps->ops->reg_write(ps->codec, c->reg.bpod,
+ c->reg.data);
+ pc += 1;
+ break;
+ case CFW_CMD_NOP:
+ pc += 1;
+ break;
+ case CFW_CMD_DELAY:
+ mdelay(c->delay.delay);
+ pc += 1;
+ break;
+ case CFW_CMD_UPDTBITS:
+ ps->ops->set_bits(ps->codec, c[1].reg.bpod,
+ c->bitop.mask, c[1].reg.data);
+ pc += 2;
+ break;
+ case CFW_CMD_WAITBITS:
+ aic3xxx_wait(ps, c[1].reg.bpod, c->bitop.mask,
+ c[1].reg.data);
+ pc += 2;
+ break;
+ case CFW_CMD_LOCK:
+ if (c->delay.delay)
+ ps->ops->lock(ps->codec);
+ else
+ ps->ops->unlock(ps->codec);
+ pc += 1;
+ break;
+ case CFW_CMD_BURST:
+ ps->ops->bulk_write(ps->codec, c[1].reg.bpod,
+ c->bhdr.len, c[1].burst.data);
+ pc += CFW_CMD_BURST_LEN(c->bhdr.len);
+ break;
+ case CFW_CMD_RBURST:
+ ps->ops->bulk_read(ps->codec, c[1].reg.bpod,
+ c->bhdr.len, c[1].burst.data);
+ pc += CFW_CMD_BURST_LEN(c->bhdr.len);
+ break;
+ case CFW_CMD_LOAD_VAR_IM:
+ aic3xxx_set_bits(&var[c->ldst.dvar],
+ c->ldst.mask, c->ldst.svar);
+ pc += 1;
+ break;
+ case CFW_CMD_LOAD_VAR_ID:
+ if (c->ldst.svar != c->ldst.dvar) {
+ aic3xxx_set_bits(&var[c->ldst.dvar],
+ c->ldst.mask,
+ var[c->ldst.svar]);
+ pc += 1;
+ } else {
+ u8 data;
+ data = ps->ops->reg_read(ps->codec,
+ c[1].reg.bpod);
+ aic3xxx_set_bits(&var[c->ldst.dvar],
+ c->ldst.mask, data);
+ pc += 2;
+ }
+ break;
+ case CFW_CMD_STORE_VAR:
+ if (c->ldst.svar != c->ldst.dvar)
+ ps->ops->set_bits(ps->codec,
+ c[1].reg.bpod,
+ var[c->ldst.dvar],
+ var[c->ldst.svar]);
+ else
+ ps->ops->set_bits(ps->codec,
+ c[1].reg.bpod,
+ c->ldst.mask,
+ var[c->ldst.svar]);
+ pc += 2;
+ break;
+ case CFW_CMD_COND:
+ cond = var[c->cond.svar] & c->cond.mask;
+ pc += 1;
+ break;
+ case CFW_CMD_BRANCH:
+ pc = c->branch.address;
+ break;
+ case CFW_CMD_BRANCH_IM:
+ if (c->branch.match == cond)
+ pc = c->branch.address;
+ else
+ pc += 1;
+ break;
+ case CFW_CMD_BRANCH_ID:
+ if (var[c->branch.match] == cond)
+ pc = c->branch.address;
+ else
+ pc += 1;
+ break;
+ case CFW_CMD_PRINT:
+ {
+ union cfw_cmd *parglist =
+ c + CFW_CMD_PRINT_ARG(c->print);
+ printk(c->print.fmt,
+ var[parglist->print_arg[0]],
+ var[parglist->print_arg[1]],
+ var[parglist->print_arg[2]],
+ var[parglist->print_arg[3]]);
+ pc += CFW_CMD_PRINT_LEN(c->print);
+ }
+ break;
+ case CFW_CMD_OP_START ... CFW_CMD_OP_END:
+ aic3xxx_cfw_op(ps, var, c->op);
+ pc += 1;
+ break;
+ default:
+ warn("Unknown cmd command %x. Skipped", c->cid);
+ pc += 1;
+ break;
+ }
+ }
+}
+
+static void aic3xxx_wait(struct cfw_state *ps, unsigned int reg, u8 mask,
+ u8 data)
+{
+ while ((ps->ops->reg_read(ps->codec, reg) & mask) != data)
+ mdelay(2);
+}
+
+static void aic3xxx_set_bits(u8 *data, u8 mask, u8 val)
+{
+ *data = (*data & (~mask)) | (val & mask);
+}
+
+static const struct {
+ u32 mdsp;
+ int buf_a, buf_b;
+ u32 swap;
+} csecs[] = {
+ {
+ .mdsp = AIC3XXX_COPS_MDSP_A,
+ .swap = AIC3XXX_ABUF_MDSP_A,
+ .buf_a = CFW_BLOCK_A_A_COEF,
+ .buf_b = CFW_BLOCK_A_B_COEF
+ },
+ {
+ .mdsp = AIC3XXX_COPS_MDSP_D,
+ .swap = AIC3XXX_ABUF_MDSP_D1,
+ .buf_a = CFW_BLOCK_D_A1_COEF,
+ .buf_b = CFW_BLOCK_D_B1_COEF
+ },
+ {
+ .mdsp = AIC3XXX_COPS_MDSP_D,
+ .swap = AIC3XXX_ABUF_MDSP_D2,
+ .buf_a = CFW_BLOCK_D_A2_COEF,
+ .buf_b = CFW_BLOCK_D_B2_COEF
+ },
+};
+static int aic3xxx_cfw_dlctl(struct cfw_state *ps, struct cfw_block *pb,
+ u32 mute_flags)
+{
+ int i, btype = pb->type;
+ int run_state = ps->ops->lock(ps->codec);
+
+ DBG("Download CTL");
+ for (i = 0; i < sizeof(csecs) / sizeof(csecs[0]); ++i) {
+ if (csecs[i].buf_a != btype && csecs[i].buf_b != btype)
+ continue;
+ DBG("\tDownload once to %d", btype);
+ aic3xxx_cfw_dlcmds(ps, pb);
+ if (run_state & csecs[i].mdsp) {
+ DBG("\tDownload again to make sure it reaches B");
+ aic3xxx_cfw_mute(ps, 1, run_state & mute_flags);
+ ps->ops->bswap(ps->codec, csecs[i].swap);
+ aic3xxx_cfw_mute(ps, 0, run_state & mute_flags);
+ aic3xxx_cfw_dlcmds(ps, pb);
+ }
+ break;
+ }
+ ps->ops->unlock(ps->codec);
+ return 0;
+}
+
+static int aic3xxx_cfw_dlcfg(struct cfw_state *ps, struct cfw_image *pim)
+{
+ int i, run_state, swap;
+
+ DBG("Download CFG %s", pim->name);
+ run_state = ps->ops->lock(ps->codec);
+ swap = 0;
+ for (i = 0; i < sizeof(csecs) / sizeof(csecs[0]); ++i) {
+ if (!pim->block[csecs[i].buf_a])
+ continue;
+ aic3xxx_cfw_dlcmds(ps, pim->block[csecs[i].buf_a]);
+ aic3xxx_cfw_dlcmds(ps, pim->block[csecs[i].buf_b]);
+ if (run_state & csecs[i].mdsp)
+ swap |= csecs[i].swap;
+ }
+ if (swap) {
+ aic3xxx_cfw_mute(ps, 1, run_state & pim->mute_flags);
+ ps->ops->bswap(ps->codec, swap);
+ aic3xxx_cfw_mute(ps, 0, run_state & pim->mute_flags);
+ for (i = 0; i < sizeof(csecs) / sizeof(csecs[0]); ++i) {
+ if (!pim->block[csecs[i].buf_a])
+ continue;
+ if (!(run_state & csecs[i].mdsp))
+ continue;
+ aic3xxx_cfw_dlcmds(ps, pim->block[csecs[i].buf_a]);
+ aic3xxx_cfw_dlcmds(ps, pim->block[csecs[i].buf_b]);
+ }
+ }
+ ps->ops->unlock(ps->codec);
+ return 0;
+}
+
+static int aic3xxx_cfw_dlimage(struct cfw_state *ps, struct cfw_image *pim)
+{
+ int i;
+
+ if (!pim)
+ return 0;
+ DBG("Download IMAGE %s", pim->name);
+ for (i = 0; i < CFW_BLOCK_N; ++i)
+ aic3xxx_cfw_dlcmds(ps, pim->block[i]);
+ return 0;
+}
+
+static int aic3xxx_cfw_mute(struct cfw_state *ps, int mute, u32 flags)
+{
+ if ((flags & AIC3XXX_COPS_MDSP_D) && (flags & AIC3XXX_COPS_MDSP_A))
+ aic3xxx_cfw_transition_u(ps,
+ mute ? "AD_MUTE" : "AD_UNMUTE");
+ else if (flags & AIC3XXX_COPS_MDSP_D)
+ aic3xxx_cfw_transition_u(ps, mute ? "D_MUTE" : "D_UNMUTE");
+ else if (flags & AIC3XXX_COPS_MDSP_A)
+ aic3xxx_cfw_transition_u(ps, mute ? "A_MUTE" : "A_UNMUTE");
+ return 0;
+}
+
+static inline void *aic3xxx_cfw_ndx2ptr(void *p, u8 *base)
+{
+ return &base[(int)p];
+}
+static inline char *aic3xxx_cfw_desc(void *p, u8 *base)
+{
+ if (p)
+ return aic3xxx_cfw_ndx2ptr(p, base);
+ return NULL;
+}
+
+static void aic3xxx_cfw_unpickle_image(struct cfw_image *im, void *p)
+{
+ int i;
+
+ im->desc = aic3xxx_cfw_desc(im->desc, p);
+ for (i = 0; i < CFW_BLOCK_N; ++i)
+ if (im->block[i])
+ im->block[i] = aic3xxx_cfw_ndx2ptr(im->block[i], p);
+}
+
+static void aic3xxx_cfw_unpickle_control(struct cfw_control *ct, void *p)
+{
+ int i;
+
+ ct->output = aic3xxx_cfw_ndx2ptr(ct->output, p);
+ ct->desc = aic3xxx_cfw_desc(ct->desc, p);
+ for (i = 0; i <= ct->imax; ++i)
+ ct->output[i] = aic3xxx_cfw_ndx2ptr(ct->output[i], p);
+}
+
+static unsigned int crc32(unsigned int *pdata, int n)
+{
+ u32 crc = 0, i, crc_poly = 0x04C11DB7; /* CRC - 32 */
+ u32 msb;
+ u32 residue_value = 0;
+ int bits;
+
+ for (i = 0; i < (n >> 2); i++) {
+ bits = 32;
+ while (--bits >= 0) {
+ msb = crc & 0x80000000;
+ crc = (crc << 1) ^ ((*pdata >> bits) & 1);
+ if (msb)
+ crc = crc ^ crc_poly;
+ }
+ pdata++;
+ }
+
+ switch (n & 3) {
+ case 0:
+ break;
+ case 1:
+ residue_value = (*pdata & 0xFF);
+ bits = 8;
+ break;
+ case 2:
+ residue_value = (*pdata & 0xFFFF);
+ bits = 16;
+ break;
+ case 3:
+ residue_value = (*pdata & 0xFFFFFF);
+ bits = 24;
+ break;
+ }
+
+ if (n & 3) {
+ while (--bits >= 0) {
+ msb = crc & 0x80000000;
+ crc = (crc << 1) ^ ((residue_value >> bits) & 1);
+ if (msb)
+ crc = crc ^ crc_poly;
+ }
+ }
+ return crc;
+}
+
+static int crc_chk(void *p, int n)
+{
+ struct cfw_project *pjt = (void *) p;
+ u32 crc = pjt->cksum, crc_comp;
+
+ pjt->cksum = 0;
+ DBG("Entering crc %d", n);
+ crc_comp = crc32(p, n);
+ if (crc_comp != crc) {
+ DBG("CRC mismatch 0x%08X != 0x%08X", crc, crc_comp);
+ return 0;
+ }
+ DBG("CRC pass");
+ pjt->cksum = crc;
+ return 1;
+}
+
+static struct cfw_project *aic3xxx_cfw_unpickle(void *p, int n)
+{
+ struct cfw_project *pjt = p;
+ int i, j;
+
+ if (pjt->magic != CFW_FW_MAGIC || pjt->size != n ||
+ pjt->if_id != CFW_FW_IF_ID || !crc_chk(p, n)) {
+ error("Version mismatch: unable to load firmware\n");
+ return NULL;
+ }
+ DBG("Loaded firmware inside unpickle\n");
+
+ pjt->desc = aic3xxx_cfw_desc(pjt->desc, p);
+ pjt->transition = aic3xxx_cfw_ndx2ptr(pjt->transition, p);
+ for (i = 0; i < CFW_TRN_N; i++) {
+ if (!pjt->transition[i])
+ continue;
+ pjt->transition[i] = aic3xxx_cfw_ndx2ptr(pjt->transition[i], p);
+ pjt->transition[i]->desc = aic3xxx_cfw_desc(
+ pjt->transition[i]->desc, p);
+ pjt->transition[i]->block = aic3xxx_cfw_ndx2ptr(
+ pjt->transition[i]->block, p);
+ }
+ pjt->pll = aic3xxx_cfw_ndx2ptr(pjt->pll, p);
+ for (i = 0; i < pjt->npll; i++) {
+ pjt->pll[i] = aic3xxx_cfw_ndx2ptr(pjt->pll[i], p);
+ pjt->pll[i]->desc = aic3xxx_cfw_desc(pjt->pll[i]->desc, p);
+ pjt->pll[i]->seq = aic3xxx_cfw_ndx2ptr(pjt->pll[i]->seq, p);
+ }
+
+ pjt->pfw = aic3xxx_cfw_ndx2ptr(pjt->pfw, p);
+ for (i = 0; i < pjt->npfw; i++) {
+ DBG("loading pfw %d\n", i);
+ pjt->pfw[i] = aic3xxx_cfw_ndx2ptr(pjt->pfw[i], p);
+ pjt->pfw[i]->desc = aic3xxx_cfw_desc(pjt->pfw[i]->desc, p);
+ if (pjt->pfw[i]->base) {
+ pjt->pfw[i]->base = aic3xxx_cfw_ndx2ptr(
+ pjt->pfw[i]->base, p);
+ aic3xxx_cfw_unpickle_image(pjt->pfw[i]->base, p);
+ }
+ pjt->pfw[i]->ovly_cfg = aic3xxx_cfw_ndx2ptr(
+ pjt->pfw[i]->ovly_cfg, p);
+ for (j = 0; j < pjt->pfw[i]->novly * pjt->pfw[i]->ncfg; ++j) {
+ pjt->pfw[i]->ovly_cfg[j] = aic3xxx_cfw_ndx2ptr(
+ pjt->pfw[i]->ovly_cfg[j], p);
+ aic3xxx_cfw_unpickle_image(pjt->pfw[i]->ovly_cfg[j], p);
+ }
+ if (pjt->pfw[i]->nctrl)
+ pjt->pfw[i]->ctrl = aic3xxx_cfw_ndx2ptr(
+ pjt->pfw[i]->ctrl, p);
+ for (j = 0; j < pjt->pfw[i]->nctrl; ++j) {
+ pjt->pfw[i]->ctrl[j] = aic3xxx_cfw_ndx2ptr(
+ pjt->pfw[i]->ctrl[j], p);
+ aic3xxx_cfw_unpickle_control(pjt->pfw[i]->ctrl[j], p);
+ }
+ }
+
+ DBG("loaded pfw's\n");
+ pjt->mode = aic3xxx_cfw_ndx2ptr(pjt->mode, p);
+ for (i = 0; i < pjt->nmode; i++) {
+ pjt->mode[i] = aic3xxx_cfw_ndx2ptr(pjt->mode[i], p);
+ pjt->mode[i]->desc = aic3xxx_cfw_desc(pjt->mode[i]->desc, p);
+ if (pjt->mode[i]->entry)
+ pjt->mode[i]->entry = aic3xxx_cfw_ndx2ptr(
+ pjt->mode[i]->entry, p);
+ if (pjt->mode[i]->exit)
+ pjt->mode[i]->exit = aic3xxx_cfw_ndx2ptr(
+ pjt->mode[i]->exit, p);
+ }
+ if (pjt->asoc_toc)
+ pjt->asoc_toc = aic3xxx_cfw_ndx2ptr(pjt->asoc_toc, p);
+ else {
+ warn("asoc_toc not defined. FW version mismatch?");
+ return NULL;
+ }
+ DBG("loaded modes");
+ return pjt;
+}
+static int aic3xxx_cfw_set_mode_id(struct cfw_state *ps)
+{
+ struct cfw_asoc_toc *toc = ps->pjt->asoc_toc;
+ int i;
+
+ for (i = 0; i < toc->nentries; ++i) {
+ if (toc->entry[i].cfg == ps->cur_cfg &&
+ toc->entry[i].mode == ps->cur_mode) {
+ ps->cur_mode_id = i;
+ return 0;
+ }
+ }
+ DBG("Unknown mode,cfg combination [%d,%d]", ps->cur_mode,
+ ps->cur_cfg);
+ return -1;
+}
+
+/* **Code beyond this point is not compilable on host** */
+
+static int aic3xxx_get_control(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cfw_state *ps = (struct cfw_state *)kcontrol->private_value;
+ struct cfw_pfw *pfw;
+ int i;
+
+ if (ps->cur_pfw >= ps->pjt->npfw) {
+ DBG("Not in MiniDSP mode");
+ return 0;
+ }
+ pfw = ps->pjt->pfw[ps->cur_pfw];
+ for (i = 0; i < pfw->nctrl; ++i) {
+ if (!strcasecmp(kcontrol->id.name, pfw->ctrl[i]->name)) {
+ struct cfw_control *pc = pfw->ctrl[i];
+ ucontrol->value.integer.value[0] = pc->icur;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int aic3xxx_put_control(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cfw_state *ps = (struct cfw_state *)kcontrol->private_value;
+
+ aic3xxx_cfw_control(ps, kcontrol->id.name,
+ ucontrol->value.integer.value[0]);
+ return 0;
+}
+
+static int aic3xxx_info_control(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *ucontrol)
+{
+ struct cfw_state *ps = (struct cfw_state *)kcontrol->private_value;
+ struct cfw_pfw *pfw;
+ int i;
+
+ if (ps->cur_pfw >= ps->pjt->npfw) {
+ DBG("Not in MiniDSP mode");
+ return 0;
+ }
+ pfw = ps->pjt->pfw[ps->cur_pfw];
+ for (i = 0; i < pfw->nctrl; ++i) {
+ if (!strcasecmp(kcontrol->id.name, pfw->ctrl[i]->name)) {
+ struct cfw_control *pc = pfw->ctrl[i];
+ ucontrol->value.integer.min = 0;
+ ucontrol->value.integer.max = pc->imax;
+ if (pc->imax == 1)
+ ucontrol->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ ucontrol->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ }
+ }
+
+ ucontrol->count = 1;
+ return 0;
+}
+int aic3xxx_cfw_add_controls(struct snd_soc_codec *codec, struct cfw_state *ps)
+{
+ int i, j;
+ struct cfw_pfw *pfw;
+
+ for (j = 0; j < ps->pjt->npfw; ++j) {
+ pfw = ps->pjt->pfw[j];
+
+ for (i = 0; i < pfw->nctrl; ++i) {
+ struct cfw_control *pc = pfw->ctrl[i];
+ struct snd_kcontrol_new *generic_control =
+ kzalloc(sizeof(struct snd_kcontrol_new),
+ GFP_KERNEL);
+ unsigned int *tlv_array =
+ kzalloc(4 * sizeof(unsigned int), GFP_KERNEL);
+
+ if (generic_control == NULL)
+ return -ENOMEM;
+ generic_control->access =
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_READWRITE;
+ tlv_array[0] = SNDRV_CTL_TLVT_DB_SCALE;
+ tlv_array[1] = 2 * sizeof(unsigned int);
+ tlv_array[2] = pc->min;
+ tlv_array[3] = ((pc->step) & TLV_DB_SCALE_MASK);
+ if (pc->step > 0)
+ generic_control->tlv.p = tlv_array;
+ generic_control->name = pc->name;
+ generic_control->private_value = (unsigned long) ps;
+ generic_control->get = aic3xxx_get_control;
+ generic_control->put = aic3xxx_put_control;
+ generic_control->info = aic3xxx_info_control;
+ generic_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ snd_soc_add_codec_controls(codec, generic_control, 1);
+ DBG("Added control %s", pc->name);
+ }
+ }
+ return 0;
+
+}
+
+
+static int aic3xxx_get_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_enum *e = (struct soc_enum *) kcontrol->private_value;
+ struct cfw_state *ps = (struct cfw_state *) e->mask;
+
+ ucontrol->value.enumerated.item[0] = ps->cur_mode_id;
+
+ return 0;
+}
+
+static int aic3xxx_put_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_enum *e = (struct soc_enum *) kcontrol->private_value;
+ struct cfw_state *ps = (struct cfw_state *) e->mask;
+ struct cfw_asoc_toc *toc;
+ int index, ret;
+
+ aic3xxx_cfw_lock(ps, 1);
+ toc = ps->pjt->asoc_toc;
+
+ index = ucontrol->value.enumerated.item[0];
+ if (index < 0 || index >= toc->nentries) {
+ aic3xxx_cfw_lock(ps, 0);
+ return -EINVAL;
+ }
+ ret = aic3xxx_cfw_setmode_cfg_u(ps, toc->entry[index].mode,
+ toc->entry[index].cfg);
+ aic3xxx_cfw_lock(ps, 0);
+ return ret;
+}
+
+int aic3xxx_cfw_add_modes(struct snd_soc_codec *codec, struct cfw_state *ps)
+{
+ int j;
+ struct cfw_asoc_toc *toc = ps->pjt->asoc_toc;
+ struct soc_enum *mode_cfg_enum =
+ kzalloc(sizeof(struct soc_enum), GFP_KERNEL);
+ struct snd_kcontrol_new *mode_cfg_control =
+ kzalloc(sizeof(struct snd_kcontrol_new), GFP_KERNEL);
+ char **enum_texts;
+
+ if (mode_cfg_enum == NULL)
+ goto mem_err;
+ if (mode_cfg_control == NULL)
+ goto mem_err;
+
+ mode_cfg_enum->texts = kzalloc(toc->nentries * sizeof(char *),
+ GFP_KERNEL);
+ if (mode_cfg_enum->texts == NULL)
+ goto mem_err;
+ /* Hack to overwrite the const * const pointer */
+ enum_texts = (char **) mode_cfg_enum->texts;
+
+ for (j = 0; j < toc->nentries; j++)
+ enum_texts[j] = toc->entry[j].etext;
+
+ mode_cfg_enum->reg = j;
+ mode_cfg_enum->max = toc->nentries;
+ mode_cfg_enum->mask = (unsigned int) ps;
+ mode_cfg_control->name = "Codec Firmware Setmode";
+ mode_cfg_control->get = aic3xxx_get_mode;
+ mode_cfg_control->put = aic3xxx_put_mode;
+ mode_cfg_control->info = snd_soc_info_enum_ext;
+ mode_cfg_control->private_value = (unsigned long) mode_cfg_enum;
+ mode_cfg_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ snd_soc_add_codec_controls(codec, mode_cfg_control, 1);
+ return 0;
+mem_err:
+ kfree(mode_cfg_control);
+ kfree(mode_cfg_enum);
+ kfree(mode_cfg_enum->texts);
+ return -ENOMEM;
+
+}
+
+#if defined(CONFIG_AIC3111_CODEC) || defined(CONFIG_AIC3111_CORE)
+
+# define AIC3XXX_CFW_DEVICE "aic3111_cfw"
+#elif defined(CONFIG_AIC3256_CODEC) || defined(CONFIG_AIC3256_CORE)
+
+# define AIC3XXX_CFW_DEVICE "aic3256_cfw"
+#elif defined(CONFIG_AIC3262_CODEC) || defined(CONFIG_AIC3262_CORE)
+# define AIC3XXX_CFW_DEVICE "aic3262_cfw"
+#else
+# define AIC3XXX_CFW_DEVICE "aic3xxx_cfw"
+#endif
+
+static int aic3xxx_cfw_open(struct inode *in, struct file *filp)
+{
+ struct cfw_state *ps = container_of(in->i_cdev, struct cfw_state, cdev);
+ if (ps->is_open) {
+ warn("driver_open: device is already open");
+ return -1;
+ }
+ ps->is_open++;
+ filp->private_data = ps;
+ return 0;
+}
+static int aic3xxx_cfw_release(struct inode *in, struct file *filp)
+{
+ struct cfw_state *ps = filp->private_data;
+ ps->is_open--;
+ return ps->is_open;
+}
+static long aic3xxx_cfw_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ return 0;
+}
+static ssize_t aic3xxx_cfw_rw(struct file *filp, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct cfw_state *ps = filp->private_data;
+ struct cfw_block *kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf || copy_from_user(kbuf, buf, count)) {
+ warn("dev_rw: Allocation or copy failure");
+ goto err;
+ }
+ if (count != CFW_BLOCK_SIZE(kbuf->ncmds)) {
+ warn("dev_rw: Bad packet received\n");
+ goto err;
+ }
+ aic3xxx_cfw_dlcmds(ps, kbuf);
+ if (copy_to_user(buf, kbuf, count)) {
+ warn("dev_rw: copy failure");
+ goto err;
+ }
+ kfree(kbuf);
+ return count;
+err:
+ kfree(kbuf);
+ return -EINVAL;
+}
+
+static const struct file_operations aic3xxx_cfw_fops = {
+ .owner = THIS_MODULE,
+ .open = aic3xxx_cfw_open,
+ .release = aic3xxx_cfw_release,
+ .read = aic3xxx_cfw_rw,
+ .write = (ssize_t (*)(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset))aic3xxx_cfw_rw,
+ .unlocked_ioctl = aic3xxx_cfw_ioctl,
+};
+static int aic3xxx_driver_init(struct cfw_state *ps)
+{
+ int err;
+
+ dev_t dev = MKDEV(0, 0);
+
+ err = alloc_chrdev_region(&dev, 0, 1, AIC3XXX_CFW_DEVICE);
+ if (err < 0) {
+ warn("driver_init: Error allocating device number");
+ return err;
+ }
+ warn("driver_init: Allocated Major Number: %d\n", MAJOR(dev));
+
+ cdev_init(&(ps->cdev), &aic3xxx_cfw_fops);
+ ps->cdev.owner = THIS_MODULE;
+ ps->cdev.ops = &aic3xxx_cfw_fops;
+ ps->is_open = 0;
+
+ err = cdev_add(&(ps->cdev), dev, 1);
+ if (err < 0) {
+ warn("driver_init: cdev_add failed");
+ unregister_chrdev_region(dev, 1);
+ return err;
+ }
+ warn("driver_init: Registered cfw driver");
+ return 0;
+}
+
+MODULE_DESCRIPTION("ASoC tlv320aic3xxx codec driver firmware functions");
+MODULE_AUTHOR("Hari Rajagopala <harik@ti.com>");
+MODULE_LICENSE("GPL");