diff options
Diffstat (limited to 'sound/soc/codecs/aic3xxx_cfw_ops.c')
-rw-r--r-- | sound/soc/codecs/aic3xxx_cfw_ops.c | 922 |
1 files changed, 922 insertions, 0 deletions
diff --git a/sound/soc/codecs/aic3xxx_cfw_ops.c b/sound/soc/codecs/aic3xxx_cfw_ops.c new file mode 100644 index 000000000000..83bcdf1d69bc --- /dev/null +++ b/sound/soc/codecs/aic3xxx_cfw_ops.c @@ -0,0 +1,922 @@ +#ifndef AIC3XXX_CFW_HOST_BLD +# 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> +# define warn(fmt, ...) printk(fmt "\n", ##__VA_ARGS__) +# define error(fmt, ...) printk(fmt "\n", ##__VA_ARGS__) + +#else +# define _GNU_SOURCE +# include <stdlib.h> +# include "utils.h" +# include <string.h> +# include <assert.h> +# define EINVAL 1 + +#endif + +#include "aic3xxx_cfw.h" +#include "aic3xxx_cfw_ops.h" +#ifndef AIC3XXX_CFW_HOST_BLD +static struct cfw_project *aic3xxx_cfw_unpickle(void *pcfw, int n); +#endif + + +/* + * 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. + */ +#define CFW_FW_VERSION 0x000100B3 + +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 void aic3xxx_wait(struct cfw_state *ps, unsigned int reg, u8 mask, + u8 data); +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); + + +#if defined(AIC3XXX_CFW_HOST_BLD) + +static int mutex_init(struct mutex *m) +{ + m->lock = 0; + return 0; +} + +static int mutex_lock(struct mutex *m) +{ + assert(m->lock == 0); + m->lock = 1; + return 0; +} + +static int mutex_unlock(struct mutex *m) +{ + assert(m->lock == 1); + m->lock = 0; + return 0; +} +/* +static void mdelay(int val) +{ + int i; + for (i = 0; i < (val * 10); i++); +} +*/ +#endif + +int aic3xxx_cfw_init(struct cfw_state *ps, struct aic3xxx_codec_ops const *ops, + void *ops_obj) +{ + ps->ops = ops; + ps->ops_obj = ops_obj; + ps->pjt = NULL; + mutex_init(&ps->mutex); + 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_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; + + if (pjt == NULL) + return -1; + if (ps->cur_pfw < 0 || ps->cur_pfw >= pjt->npfw) + return -1; + 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); + if (pfw->ncfg != 0) + return aic3xxx_cfw_dlcfg(ps, + pfw->ovly_cfg[ps->cur_ovly][ps-> + cur_cfg]); + 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; + int which = 0; + struct cfw_pfw *pfw; + struct cfw_image *im; + + if (pjt == NULL) + return -1; + if ((mode < 0) || (mode >= pjt->nmode)) + return -1; + if (cfg < 0) + return -1; + 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 && pjt->mode[ps->cur_mode]->exit) + aic3xxx_cfw_dlcmds(ps, pjt->mode[ps->cur_mode]->exit); + + if (pjt->mode[mode]->pfw < pjt->npfw) { + /* New mode uses miniDSP */ + pfw = pjt->pfw[pjt->mode[mode]->pfw]; + /* Make sure cfg is valid and supported in this mode */ + if (pfw->ncfg == 0 && cfg != 0) + return -1; + if (cfg > 0 && cfg >= pfw->ncfg) + return -1; + /* + * 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 |= AIC3XX_COPS_MDSP_A; + if (im->block[CFW_BLOCK_D_INST]) + which |= AIC3XX_COPS_MDSP_D; + + /* New mode requires different PFW */ + if (pjt->mode[mode]->pfw != ps->cur_pfw) { + ps->cur_pfw = pjt->mode[mode]->pfw; + ps->cur_ovly = 0; + ps->cur_cfg = 0; + + which = ps->ops->stop(ps->ops_obj, which); + aic3xxx_cfw_dlimage(ps, im); + if (pjt->mode[mode]->ovly + && pjt->mode[mode]->ovly < pfw->novly) { + /* New mode uses ovly */ + if (pfw->ovly_cfg[pjt->mode[mode] + ->ovly][cfg] != NULL) + aic3xxx_cfw_dlimage(ps, + pfw->ovly_cfg[pjt-> + mode[mode]-> + ovly][cfg]); + } else if (pfw->ncfg > 0) { + /* new mode needs only a cfg change */ + aic3xxx_cfw_dlimage(ps, pfw->ovly_cfg[0][cfg]); + } + ps->ops->restore(ps->ops_obj, which); + + } else if (pjt->mode[mode]->ovly != ps->cur_ovly) { + /* New mode requires only an ovly change */ + which = ps->ops->stop(ps->ops_obj, which); + aic3xxx_cfw_dlimage(ps, + pfw->ovly_cfg[pjt->mode[mode]-> + ovly][cfg]); + ps->ops->restore(ps->ops_obj, which); + } else if (pfw->ncfg > 0 && cfg != ps->cur_cfg) { + /* New mode requires only a cfg change */ + aic3xxx_cfw_dlcfg(ps, + pfw->ovly_cfg[pjt->mode[mode]-> + ovly][cfg]); + } + ps->cur_ovly = pjt->mode[mode]->ovly; + ps->cur_cfg = cfg; + + ps->cur_mode = mode; + aic3xxx_cfw_set_pll_u(ps, 0); + + } else if (pjt->mode[mode]->pfw != 0xFF) { + warn("Bad pfw setting detected (%d). Max pfw=%d", + pjt->mode[mode]->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 */ + if (pjt->mode[mode]->entry) + aic3xxx_cfw_dlcmds(ps, pjt->mode[mode]->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; +} + +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 -1; + for (i = 0; i < CFW_TRN_N; ++i) { + if (!strcasecmp(ttype, cfw_transition_id[i])) { + DBG("Sending transition %s[%d]", ttype, i); + if (ps->pjt->transition[i]) { + aic3xxx_cfw_dlcmds(ps, + ps->pjt->transition[i]-> + 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; + struct cfw_pfw *pfw; + + if (pjt == NULL) + return -1; + if (ps->cur_mode < 0) + return -EINVAL; + pfw = pjt->pfw[pjt->mode[ps->cur_mode]->pfw]; + if (pfw->pll) { + DBG("Configuring PLL for ASI%d using PFW%d", asi, + pjt->mode[ps->cur_mode]->pfw); + aic3xxx_cfw_dlcmds(ps, pfw->pll); + } + 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) { + if (!strcasecmp(cname, pfw->ctrl[i]->name)) { + struct cfw_control *pc = pfw->ctrl[i]; + 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 nort found in pfw %s", cname, pfw->name); + + return 0; +} + +static void aic3xxx_cfw_dlcmds(struct cfw_state *ps, struct cfw_block *pb) +{ + int i = 0, lock = 0; + + while (i < pb->ncmds) { + if (CFW_BLOCK_BURSTS(pb->type)) + ps->ops->bulk_write(ps->ops_obj, + pb->cmd[i].burst->reg.bpod, + pb->cmd[i].burst->length, + pb->cmd[i].burst->data); + else { + struct cfw_meta_delay d = pb->cmd[i].reg.meta.delay; + struct cfw_meta_bitop b = pb->cmd[i].reg.meta.bitop; + switch (pb->cmd[i].reg.meta.mcmd) { + case CFW_META_DELAY: + mdelay(d.delay); + break; + case CFW_META_UPDTBITS: + ps->ops->set_bits(ps->ops_obj, + pb->cmd[i + 1].reg.bpod, + b.mask, + pb->cmd[i + 1].reg.data); + i++; + break; + case CFW_META_WAITBITS: + aic3xxx_wait(ps, pb->cmd[i + 1].reg.bpod, + b.mask, pb->cmd[i + 1].reg.data); + i++; + break; + case CFW_META_LOCK: + if (d.delay) { + ps->ops->lock(ps->ops_obj); + lock = 1; + } else { + if (!lock) + error("already lock\n"); + ps->ops->unlock(ps->ops_obj); + lock = 0; + } + break; + default: + ps->ops->reg_write(ps->ops_obj, + pb->cmd[i].reg.bpod, + pb->cmd[i].reg.data); + } + } + ++i; + } + if (lock) + error("exiting blkcmds with lock ON"); +} + +static void aic3xxx_wait(struct cfw_state *ps, unsigned int reg, u8 mask, + u8 data) +{ + while ((ps->ops->reg_read(ps->ops_obj, reg) & mask) != data) + mdelay(2); +} + +static const struct { + u32 mdsp; + int buf_a, buf_b; + u32 swap; +} csecs[] = { + { + .mdsp = AIC3XX_COPS_MDSP_A, + .swap = AIC3XX_ABUF_MDSP_A, + .buf_a = CFW_BLOCK_A_A_COEF, + .buf_b = CFW_BLOCK_A_B_COEF + }, + { + .mdsp = AIC3XX_COPS_MDSP_D, + .swap = AIC3XX_ABUF_MDSP_D1, + .buf_a = CFW_BLOCK_D_A1_COEF, + .buf_b = CFW_BLOCK_D_B1_COEF + }, + { + .mdsp = AIC3XX_COPS_MDSP_D, + .swap = AIC3XX_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 = CFW_BLOCK_TYPE(pb->type); + int run_state = ps->ops->lock(ps->ops_obj); + + DBG("Download CTL"); + for (i = 0; i < sizeof(csecs) / sizeof(csecs[0]); ++i) { + if (csecs[i].buf_a == btype || csecs[i].buf_b == btype) { + DBG("\tDownload once to %d", btype); + aic3xxx_cfw_dlcmds(ps, pb); + if (run_state & csecs[i].mdsp) { + DBG("Download again %d", btype); + aic3xxx_cfw_mute(ps, 1, run_state & mute_flags); + ps->ops->bswap(ps->ops_obj, csecs[i].swap); + aic3xxx_cfw_mute(ps, 0, run_state & mute_flags); + aic3xxx_cfw_dlcmds(ps, pb); + } + break; + } + } + ps->ops->unlock(ps->ops_obj); + 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->ops_obj); + swap = 0; + for (i = 0; i < sizeof(csecs) / sizeof(csecs[0]); ++i) { + if (pim->block[csecs[i].buf_a]) { + if (run_state & csecs[i].mdsp) { + aic3xxx_cfw_dlcmds(ps, + pim->block[csecs[i].buf_a]); + swap |= csecs[i].swap; + } else { + aic3xxx_cfw_dlcmds(ps, + pim->block[csecs[i].buf_a]); + aic3xxx_cfw_dlcmds(ps, + pim->block[csecs[i].buf_b]); + } + } + } + if (swap) { + aic3xxx_cfw_mute(ps, 1, run_state & pim->mute_flags); + ps->ops->bswap(ps->ops_obj, 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]) { + if (run_state & csecs[i].mdsp) + aic3xxx_cfw_dlcmds(ps, + pim->block[csecs[i]. + buf_a]); + } + } + } + ps->ops->unlock(ps->ops_obj); + return 0; +} + +static int aic3xxx_cfw_dlimage(struct cfw_state *ps, struct cfw_image *pim) +{ + int i; + + DBG("Download IMAGE %s", pim->name); + for (i = 0; i < CFW_BLOCK_N; ++i) + if (pim->block[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 & AIC3XX_COPS_MDSP_D) && (flags & AIC3XX_COPS_MDSP_A)) + aic3xxx_cfw_transition_u(ps, mute ? "AD_MUTE" : "AD_UNMUTE"); + else if (flags & AIC3XX_COPS_MDSP_D) + aic3xxx_cfw_transition_u(ps, mute ? "D_MUTE" : "D_UNMUTE"); + else if (flags & AIC3XX_COPS_MDSP_A) + aic3xxx_cfw_transition_u(ps, mute ? "A_MUTE" : "A_UNMUTE"); + return 0; +} + +#define FW_NDX2PTR(x, b) do { \ +x = (void *)((u8 *)(b) + ((int)(x))); \ +} while (0) + +static void aic3xxx_cfw_unpickle_block(struct cfw_block *pb, void *p) +{ + int i; + + if (CFW_BLOCK_BURSTS(pb->type)) + for (i = 0; i < pb->ncmds; ++i) + FW_NDX2PTR(pb->cmd[i].burst, p); +} + +static void aic3xxx_cfw_unpickle_image(struct cfw_image *im, void *p) +{ + int i; + for (i = 0; i < CFW_BLOCK_N; ++i) + if (im->block[i]) { + FW_NDX2PTR(im->block[i], p); + aic3xxx_cfw_unpickle_block(im->block[i], p); + } +} + +static void aic3xxx_cfw_unpickle_control(struct cfw_control *ct, void *p) +{ + int i; + FW_NDX2PTR(ct->output, p); + for (i = 0; i <= ct->imax; ++i) { + FW_NDX2PTR(ct->output[i], p); + aic3xxx_cfw_unpickle_block(ct->output[i], p); + } +} +#ifndef AIC3XXX_CFW_HOST_BLD +static +#endif +unsigned int crc32(unsigned int *pdata, int n) +{ + u32 crc = 0, i, crc_poly = 0x04C11DB7; /* CRC - 32 */ + u32 msb; + u32 residue_value; + 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; +} +#ifndef AIC3XXX_CFW_HOST_BLD +static +#endif +struct cfw_project *aic3xxx_cfw_unpickle(void *p, int n) +{ + struct cfw_project *pjt = p; + int i, j, k; + + if (pjt->magic != CFW_FW_MAGIC || + pjt->size != n || pjt->bmagic != CFW_FW_VERSION || + !crc_chk(p, n)) { + error + ("magic:0x%08X!=0x%08X || size:%d!=%d ||version:0x%08X!=0x%08X", + pjt->magic, CFW_FW_MAGIC, pjt->size, n, pjt->cksum, + CFW_FW_VERSION); + + return NULL; + } + DBG("Loaded firmware inside unpickle\n"); + + for (i = 0; i < CFW_MAX_TRANSITIONS; i++) { + if (pjt->transition[i]) { + FW_NDX2PTR(pjt->transition[i], p); + FW_NDX2PTR(pjt->transition[i]->block, p); + aic3xxx_cfw_unpickle_block(pjt->transition[i]->block, + p); + } + } + + for (i = 0; i < pjt->npfw; i++) { + DBG("loading pfw %d\n", i); + FW_NDX2PTR(pjt->pfw[i], p); + if (pjt->pfw[i]->base) { + FW_NDX2PTR(pjt->pfw[i]->base, p); + aic3xxx_cfw_unpickle_image(pjt->pfw[i]->base, p); + } + if (pjt->pfw[i]->pll) { + FW_NDX2PTR(pjt->pfw[i]->pll, p); + aic3xxx_cfw_unpickle_block(pjt->pfw[i]->pll, p); + } + for (j = 0; j < pjt->pfw[i]->novly; ++j) + for (k = 0; k < pjt->pfw[i]->ncfg; ++k) { + FW_NDX2PTR(pjt->pfw[i]->ovly_cfg[j][k], p); + aic3xxx_cfw_unpickle_image(pjt->pfw[i]-> + ovly_cfg[j][k], p); + } + for (j = 0; j < pjt->pfw[i]->nctrl; ++j) { + FW_NDX2PTR(pjt->pfw[i]->ctrl[j], p); + aic3xxx_cfw_unpickle_control(pjt->pfw[i]->ctrl[j], p); + } + } + + DBG("loaded pfw's\n"); + for (i = 0; i < pjt->nmode; i++) { + FW_NDX2PTR(pjt->mode[i], p); + if (pjt->mode[i]->entry) { + FW_NDX2PTR(pjt->mode[i]->entry, p); + aic3xxx_cfw_unpickle_block(pjt->mode[i]->entry, p); + } + if (pjt->mode[i]->exit) { + FW_NDX2PTR(pjt->mode[i]->exit, p); + aic3xxx_cfw_unpickle_block(pjt->mode[i]->exit, p); + } + } + if (pjt->asoc_toc) + FW_NDX2PTR(pjt->asoc_toc, p); + else { + warn("asoc_toc not defined. FW version mismatch?"); + return NULL; + } + DBG("loaded modes"); + return pjt; +} + +#ifndef AIC3XXX_CFW_HOST_BLD +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 snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + 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; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + + 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 snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + 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; +} +#endif +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]; +#ifndef AIC3XXX_CFW_HOST_BLD + 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; +#endif + DBG("Adding control %s", pc->name); +#ifndef AIC3XXX_CFW_HOST_BLD + snd_soc_add_controls(codec, generic_control, 1); +#endif + } + } + return 0; + +} + +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; +} +#ifndef AIC3XXX_CFW_HOST_BLD +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; +} +#endif +#ifndef AIC3XXX_CFW_HOST_BLD +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; +} +#endif + +int aic3xxx_cfw_add_modes(struct snd_soc_codec *codec, struct cfw_state *ps) +{ +#ifndef AIC3XXX_CFW_HOST_BLD + 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_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; +#else + return 0; +#endif + +} |