[alsa-devel] [PATCH 2/2] ASoC: aic: Support for AIC family DSPs
AIC family of audio CODECs from TI features a programmable miniDSP for performing signal processing operations. Due to commonality of functions across the CODECs a common library will be used to provide support for them.
Signed-off-by: Mehar Bajwa mehar.bajwa@ti.com --- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/aic3xxx_cfw.h | 529 +++++++++++++++++++ sound/soc/codecs/aic3xxx_cfw_ops.c | 989 ++++++++++++++++++++++++++++++++++++ sound/soc/codecs/aic3xxx_cfw_ops.h | 98 ++++ 5 files changed, 1623 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/aic3xxx_cfw.h create mode 100644 sound/soc/codecs/aic3xxx_cfw_ops.c create mode 100644 sound/soc/codecs/aic3xxx_cfw_ops.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 73d8cea..52b3601 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -188,6 +188,11 @@ config SND_SOC_ADAV80X config SND_SOC_ADS117X tristate
+config SND_SOC_AIC_CFW + tristate + default y if SND_SOC_TLV320AIC3262=y + default m if SND_SOC_TLV320AIC3262=m + config SND_SOC_AK4104 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index a3f8f4f..ffda11b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-adau1701-objs := adau1701.o snd-soc-adau1373-objs := adau1373.o snd-soc-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o +snd-soc-aic3xxx-cfw-ops-objs := aic3xxx_cfw_ops.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak4641-objs := ak4641.o @@ -132,6 +133,7 @@ obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o +obj-$(CONFIG_SND_SOC_AIC_CFW) += snd-soc-aic3xxx-cfw-ops.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o diff --git a/sound/soc/codecs/aic3xxx_cfw.h b/sound/soc/codecs/aic3xxx_cfw.h new file mode 100644 index 0000000..b88485d --- /dev/null +++ b/sound/soc/codecs/aic3xxx_cfw.h @@ -0,0 +1,529 @@ +/* + * aic3xxx_cfw.h -- SoC audio for TI OMAP44XX SDP + * Codec Firmware Declarations + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef AIC_FIRMWARE_H_ +#define AIC_FIRMWARE_H_ + + +#define AIC_FW_MAGIC 0xC0D1F1ED + + +/** \defgroup pd Arbitrary Limitations */ +/* @{ */ +#ifndef AIC_MAX_ID +# define AIC_MAX_ID (64) /**<Max length of string identifies*/ +# define AIC_MAX_VARS (256) /**<Number of "variables" alive at the*/ + /**<same time in an acx file*/ +#endif + +/* @} */ + + + +/** \defgroup st Enums, Flags, Macros and Supporting Types */ +/* @{ */ + + +/** + * Device Family Identifier + * + */ +enum __attribute__ ((__packed__)) aic_dfamily { + AIC_DFM_TYPE_A, + AIC_DFM_TYPE_B, + AIC_DFM_TYPE_C +}; + +/** + * Device Identifier + * + */ +enum __attribute__ ((__packed__)) aic_device { + AIC_DEV_DAC3120, + AIC_DEV_DAC3100, + AIC_DEV_AIC3120, + AIC_DEV_AIC3100, + AIC_DEV_AIC3110, + AIC_DEV_AIC3111, + AIC_DEV_AIC36, + AIC_DEV_AIC3206, + AIC_DEV_AIC3204, + AIC_DEV_AIC3254, + AIC_DEV_AIC3256, + AIC_DEV_AIC3253, + AIC_DEV_AIC3212, + AIC_DEV_AIC3262, + AIC_DEV_AIC3017, + AIC_DEV_AIC3008, + + AIC_DEV_AIC3266, + AIC_DEV_AIC3285, +}; + +/** + * Transition Sequence Identifier + * + */ +enum aic_transition_t { + AIC_TRN_INIT, + AIC_TRN_RESUME, + AIC_TRN_NEUTRAL, + AIC_TRN_A_MUTE, + AIC_TRN_D_MUTE, + AIC_TRN_AD_MUTE, + AIC_TRN_A_UNMUTE, + AIC_TRN_D_UNMUTE, + AIC_TRN_AD_UNMUTE, + AIC_TRN_SUSPEND, + AIC_TRN_EXIT, + AIC_TRN_N +}; + +#ifndef __cplusplus +static const char *const aic_transition_id[] = { + [AIC_TRN_INIT] "INIT", + [AIC_TRN_RESUME] "RESUME", + [AIC_TRN_NEUTRAL] "NEUTRAL", + [AIC_TRN_A_MUTE] "A_MUTE", + [AIC_TRN_D_MUTE] "D_MUTE", + [AIC_TRN_AD_MUTE] "AD_MUTE", + [AIC_TRN_A_UNMUTE] "A_UNMUTE", + [AIC_TRN_D_UNMUTE] "D_UNMUTE", + [AIC_TRN_AD_UNMUTE]"AD_UNMUTE", + [AIC_TRN_SUSPEND] "SUSPEND", + [AIC_TRN_EXIT] "EXIT", +}; +#endif + +/* @} */ + +/** \defgroup ds Data Structures */ +/* @{ */ + + +/** +* AIC Command +* These commands do not appear in the register +* set of the device. +*/ +enum __attribute__ ((__packed__)) aic_cmd_id { + AIC_CMD_NOP = 0x80, + AIC_CMD_DELAY, + AIC_CMD_UPDTBITS, + AIC_CMD_WAITBITS, + AIC_CMD_LOCK, + AIC_CMD_BURST, + AIC_CMD_RBURST, + AIC_CMD_LOAD_VAR_IM, + AIC_CMD_LOAD_VAR_ID, + AIC_CMD_STORE_VAR, + AIC_CMD_COND, + AIC_CMD_BRANCH, + AIC_CMD_BRANCH_IM, + AIC_CMD_BRANCH_ID, + AIC_CMD_PRINT, + AIC_CMD_OP_ADD = 0xC0, + AIC_CMD_OP_SUB, + AIC_CMD_OP_MUL, + AIC_CMD_OP_DIV, + AIC_CMD_OP_AND, + AIC_CMD_OP_OR, + AIC_CMD_OP_SHL, + AIC_CMD_OP_SHR, + AIC_CMD_OP_RR, + AIC_CMD_OP_XOR, + AIC_CMD_OP_NOT, + AIC_CMD_OP_LNOT, +}; + +/** +* AIC Delay +* Used for the cmd command delay +* Has one parameter of delay time in ms +*/ +struct aic_cmd_delay { + u16 delay; + enum aic_cmd_id cid; + u8 delay_fine; +}; + +/** +* AIC Lock +* Take codec mutex to avoid clashing with DAPM operations +*/ +struct aic_cmd_lock { + u16 lock; + enum aic_cmd_id cid; + u8 unused; +}; + + +/** + * AIC UPDTBITS, WAITBITS, CHKBITS + * Both these cmd commands have same arguments + * cid will be used to specify which command it is + * has parameters of book, page, offset and mask + */ +struct aic_cmd_bitop { + u16 unused1; + enum aic_cmd_id cid; + u8 mask; +}; + +/** + * AIC CMD Burst header + * Burst writes inside command array + * Followed by burst address, first byte + */ +struct aic_cmd_bhdr { + u16 len; + enum aic_cmd_id cid; + u8 unused; +}; + +/** + * AIC CMD Burst + * Burst writes inside command array + * Followed by data to the extent indicated in previous len + * Can be safely cast to aic_burst + */ +struct aic_cmd_burst { + u8 book; + u8 page; + u8 offset; + u8 data[1]; +}; +#define AIC_CMD_BURST_LEN(n) (2 + ((n) - 1 + 3)/4) + +/** + * AIC CMD Scratch register + * For load + * if (svar != dvar) + * dvar = setbits(svar, mask) // Ignore reg + * else + * dvar = setbits(reg, mask) + * For store + * if (svar != dvar) + * reg = setbits(svar, dvar) + * else + * reg = setbits(svar, mask) + * + */ +struct aic_cmd_ldst { + u8 dvar; + u8 svar; + enum aic_cmd_id cid; + u8 mask; +}; + +/** + * AIC CMD Conditional + * May only precede branch. Followed by nmatch+1 jump + * instructions + * cond = svar&mask + * At each of the following nmatch+1 branch command + * if (cond == match) + * take the branch + */ +struct aic_cmd_cond { + u8 svar; + u8 nmatch; + enum aic_cmd_id cid; + u8 mask; +}; +#define AIC_CMD_COND_LEN(nm) (1 + ((nm)+1)) + +/** + * AIC CMD Goto + * For branch, break, continue and stop + */ +struct aic_cmd_branch { + u16 address; + enum aic_cmd_id cid; + u8 match; +}; + +/** + * AIC Debug print + * For diagnostics + */ +struct aic_cmd_print { + u8 fmtlen; + u8 nargs; + enum aic_cmd_id cid; + char fmt[1]; +}; + +#define AIC_CMD_PRINT_LEN(p) (1 + ((p).fmtlen/4) + (((p).nargs + 3)/4)) +#define AIC_CMD_PRINT_ARG(p) (1 + ((p).fmtlen/4)) + +/** + * AIC Arithmetic and logical operations + * Bit 5 indicates if op1 is indirect + * Bit 6 indicates if op2 is indirect + */ +struct aic_cmd_op { + u8 op1; + u8 op2; + enum aic_cmd_id cid; + u8 dst; +}; +#define AIC_CMD_OP1_ID (1u<<5) +#define AIC_CMD_OP2_ID (1u<<4) + +#define AIC_CMD_OP_START AIC_CMD_OP_ADD +#define AIC_CMD_OP_END (AIC_CMD_OP_LNOT|AIC_CMD_OP1_ID|AIC_CMD_OP2_ID) +#define AIC_CMD_OP_IS_UNARY(x) \ + (((x) == AIC_CMD_OP_NOT) || ((x) == AIC_CMD_OP_LNOT)) + + +/** + * AIC Register + * + * A single reg write + * + */ +union aic_register { + struct { + u8 book; + u8 page; + u8 offset; + u8 data; + }; + u32 bpod; +}; + + + +/** + * AIC Command + * + * Can be a either a + * -# single register write, or + * -# command + * + */ +union aic_cmd { + struct { + u16 unused1; + enum aic_cmd_id cid; + u8 unused2; + }; + union aic_register reg; + struct aic_cmd_delay delay; + struct aic_cmd_lock lock; + struct aic_cmd_bitop bitop; + struct aic_cmd_bhdr bhdr; + struct aic_cmd_burst burst; + struct aic_cmd_ldst ldst; + struct aic_cmd_cond cond; + struct aic_cmd_branch branch; + struct aic_cmd_print print; + u8 print_arg[4]; + struct aic_cmd_op op; +}; + +#define AIC_REG_IS_CMD(x) ((x).cid >= AIC_CMD_DELAY) + +/** + * AIC Block Type + * + * Block identifier + * + */ +enum __attribute__ ((__packed__)) aic_block_t { + AIC_BLOCK_SYSTEM_PRE, + AIC_BLOCK_A_INST, + AIC_BLOCK_A_A_COEF, + AIC_BLOCK_A_B_COEF, + AIC_BLOCK_A_F_COEF, + AIC_BLOCK_D_INST, + AIC_BLOCK_D_A1_COEF, + AIC_BLOCK_D_B1_COEF, + AIC_BLOCK_D_A2_COEF, + AIC_BLOCK_D_B2_COEF, + AIC_BLOCK_D_F_COEF, + AIC_BLOCK_SYSTEM_POST, + AIC_BLOCK_N, + AIC_BLOCK_INVALID, +}; +#define AIC_BLOCK_D_A_COEF AIC_BLOCK_D_A1_COEF +#define AIC_BLOCK_D_B_COEF AIC_BLOCK_D_B1_COEF + +/** + * AIC Block + * + * A block of logically grouped sequences/commands/cmd-commands + * + */ +struct aic_block { + enum aic_block_t type; + int ncmds; + union aic_cmd cmd[]; +}; +#define AIC_BLOCK_SIZE(ncmds) (sizeof(struct aic_block) + \ + ((ncmds)*sizeof(union aic_cmd))) + +/** + * AIC Image + * + * A downloadable image + */ +struct aic_image { + char name[AIC_MAX_ID]; /**< Name of the pfw/overlay/configuration*/ + char *desc; /**< User string*/ + int mute_flags; + struct aic_block *block[AIC_BLOCK_N]; +}; + + + +/** + * AIC PLL + * + * PLL configuration sequence and match critirea + */ +struct aic_pll { + char name[AIC_MAX_ID]; /**< Name of the PLL sequence*/ + char *desc; /**< User string*/ + struct aic_block *seq; +}; + +/** + * AIC Control + * + * Run-time control for a process flow + */ +struct aic_control { + char name[AIC_MAX_ID]; /**< Control identifier*/ + char *desc; /**< User string*/ + int mute_flags; + + int min; /**< Min value of control (*100)*/ + int max; /**< Max value of control (*100)*/ + int step; /**< Control step size (*100)*/ + + int imax; /**< Max index into controls array*/ + int ireset; /**< Reset control to defaults*/ + int icur; /**< Last value set*/ + struct aic_block **output; /**< Array of sequences to send*/ +}; + +/** + * Process flow + * + * Complete description of a process flow + */ +struct aic_pfw { + char name[AIC_MAX_ID]; /**< Name of the process flow*/ + char *desc; /**< User string*/ + u32 version; + u8 prb_a; + u8 prb_d; + int novly; /**< Number of overlays (1 or more)*/ + int ncfg; /**< Number of configurations (0 or more)*/ + int nctrl; /**< Number of run-time controls*/ + struct aic_image *base; /**< Base sequence*/ + struct aic_image **ovly_cfg; /**< Overlay and cfg*/ + /**< patches (if any)*/ + struct aic_control **ctrl; /**< Array of run-time controls*/ +}; + +#define AIC_OCFG_NDX(p, o, c) (((o)*(p)->ncfg)+(c)) +/** + * Process transition + * + * Sequence for specific state transisitions within the driver + * + */ +struct aic_transition { + char name[AIC_MAX_ID]; /**< Name of the transition*/ + char *desc; /**< User string*/ + struct aic_block *block; +}; + +/** + * Device audio mode + * + * Link operating modes to process flows, + * configurations and sequences + * + */ +struct aic_mode { + char name[AIC_MAX_ID]; + char *desc; /**< User string*/ + u32 flags; + u8 pfw; + u8 ovly; + u8 cfg; + u8 pll; + struct aic_block *entry; + struct aic_block *exit; +}; + +struct aic_asoc_toc_entry { + char etext[AIC_MAX_ID]; + int mode; + int cfg; +}; + +struct aic_asoc_toc { + int nentries; + struct aic_asoc_toc_entry entry[]; +}; + +/** + * AIC Project + * + * Top level structure describing the AIC project + */ +struct aic_project { + u32 magic; /**< magic number for identifying F/W file*/ + u32 if_id; /**< Interface match code */ + u32 size; /**< Total size of the firmware (including this header)*/ + u32 cksum; /**< CRC32 of the pickled firmware */ + u32 version; /**< Firmware version (from CFD file)*/ + u32 tstamp; /**< Time stamp of firmware build (epoch seconds)*/ + char name[AIC_MAX_ID]; /**< Project name*/ + char *desc; /**< User string*/ + enum aic_dfamily dfamily; /**< Device family*/ + enum aic_device device; /**< Device identifier*/ + u32 flags; /**< AIC flags*/ + + struct aic_transition **transition; /**< Transition sequences*/ + + u16 npll; /**< Number of PLL settings*/ + struct aic_pll **pll; /**< PLL settings*/ + + u16 npfw; /**< Number of process flows*/ + struct aic_pfw **pfw; /**< Process flows*/ + + u16 nmode; /**< Number of operating modes*/ + struct aic_mode **mode; /**< Modes*/ + + struct aic_asoc_toc *asoc_toc; /**< list of amixer controls*/ +}; + + +/* @} */ + +/* **AIC_INTERFACE_ID=0x3FA6D547** */ + +#endif /* AIC_FIRMWARE_H_ */ diff --git a/sound/soc/codecs/aic3xxx_cfw_ops.c b/sound/soc/codecs/aic3xxx_cfw_ops.c new file mode 100644 index 0000000..79b975b --- /dev/null +++ b/sound/soc/codecs/aic3xxx_cfw_ops.c @@ -0,0 +1,989 @@ +/* + * 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> + +#include "aic3xxx_cfw.h" +#include "aic3xxx_cfw_ops.h" + +#define aic_warn(_cfw, fmt, ...)\ + dev_warn(_cfw->dev, "AIC: " fmt, ##__VA_ARGS__) +#define aic_error(_cfw, fmt, ...)\ + dev_err(_cfw->dev, "AIC: " fmt, ##__VA_ARGS__) +#define aic_dbg(_cfw, fmt, ...)\ + dev_dbg(_cfw->dev, "AIC: " fmt, ##__VA_ARGS__) + +#if defined(CONFIG_MFD_AIC3111) || defined(CONFIG_AIC3111_CORE) + +# define AIC3XXX_AIC_DEVICE "aic3111_cfw" +#elif defined(CONFIG_MFD_AIC3256) || defined(CONFIG_AIC3256_CORE) + +# define AIC3XXX_AIC_DEVICE "aic3256_cfw" +#elif defined(CONFIG_MFD_AIC262) || defined(CONFIG_AIC3262_CORE) +# define AIC3XXX_AIC_DEVICE "aic3262_cfw" +#else +# define AIC3XXX_AIC_DEVICE "aic3xxx_cfw" +#endif + +/* **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 AIC_FW_IF_ID +#define AIC_FW_IF_ID 0x3FA6D547 +static int aic_dlimage(struct aic_state *ps, struct aic_image *pim); +static int aic_dlcfg(struct aic_state *ps, struct aic_image *pim); +static int aic_dlctl(struct aic_state *ps, struct aic_block *pb, + u32 mute_flags); + +static void aic_dlcmds(struct aic_state *ps, struct aic_block *pb); +static int aic_set_mode_id(struct aic_state *ps); +static int aic_mute(struct aic_state *ps, int mute, u32 flags); +static int aic_setmode_cfg_u(struct aic_state *ps, int mode, int cfg); +static int aic_setcfg_u(struct aic_state *ps, int cfg); +static int aic_transition_u(struct aic_state *ps, char *ttype); +static int aic_set_pll_u(struct aic_state *ps, int asi); +static int aic_control_u(struct aic_state *ps, char *cname, int param); +static struct aic_project *aic_unpickle(void *pcfw, int n); + +static void aic_wait(struct aic_state *ps, unsigned int reg, u8 mask, + u8 data); +static void aic_set_bits(u8 *data, u8 mask, u8 val); + +int aic_init(struct aic_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); + + return 0; +} +EXPORT_SYMBOL_GPL(aic_init); + +int aic_lock(struct aic_state *ps, int lock) +{ + if (lock) + mutex_lock(&ps->mutex); + else + mutex_unlock(&ps->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(aic_lock); + +int aic_reload(struct aic_state *ps, void *pcfw, int n) +{ + ps->pjt = aic_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) { + aic_error(ps, "version mismatch\n"); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(aic_reload); + +int aic_setmode(struct aic_state *ps, int mode) +{ + struct aic_project *pjt; + int ret; + + aic_lock(ps, 1); + pjt = ps->pjt; + if (pjt == NULL) { + aic_lock(ps, 0); + return -EINVAL; + } + ret = aic_setmode_cfg_u(ps, mode, pjt->mode[mode]->cfg); + aic_lock(ps, 0); + return ret; +} + +int aic_setcfg(struct aic_state *ps, int cfg) +{ + int ret; + + aic_lock(ps, 1); + ret = aic_setcfg_u(ps, cfg); + aic_lock(ps, 0); + return ret; +} + +static int aic_setcfg_u(struct aic_state *ps, int cfg) +{ + struct aic_project *pjt = ps->pjt; + struct aic_pfw *pfw; + struct aic_image *patch; + + if (pjt == NULL) + return -EINVAL; + if (ps->cur_pfw < 0 || ps->cur_pfw >= pjt->npfw) + return -EINVAL; /* Non miniDSP */ + if (ps->cur_cfg == cfg) + return 0; + pfw = pjt->pfw[ps->cur_pfw]; + if (pfw->ncfg == 0 && cfg != 0) + return -EINVAL; + if (cfg > 0 && cfg >= pfw->ncfg) + return -EINVAL; + ps->cur_cfg = cfg; + aic_set_mode_id(ps); + patch = + pfw->ovly_cfg[AIC_OCFG_NDX(pfw, ps->cur_ovly, ps->cur_cfg)]; + if (pfw->ncfg != 0) + return aic_dlcfg(ps, patch); + return 0; +} + +int aic_setmode_cfg(struct aic_state *ps, int mode, int cfg) +{ + int ret; + + aic_lock(ps, 1); + ret = aic_setmode_cfg_u(ps, mode, cfg); + aic_lock(ps, 0); + return ret; +} +EXPORT_SYMBOL_GPL(aic_setmode_cfg); + +static int aic_setmode_cfg_u(struct aic_state *ps, int mode, int cfg) +{ + struct aic_project *pjt = ps->pjt; + struct aic_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 aic_setcfg_u(ps, cfg); + + /* Apply exit sequence for previous mode if present */ + if (ps->cur_mode >= 0) + aic_dlcmds(ps, pjt->mode[ps->cur_mode]->exit); + pmode = pjt->mode[mode]; + if (pjt->mode[mode]->pfw < pjt->npfw) { /* New mode uses miniDSP */ + struct aic_image *im; + struct aic_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[AIC_BLOCK_A_INST]) + which |= AIC3XXX_COPS_MDSP_A; + if (im->block[AIC_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); + aic_dlimage(ps, im); + if (pmode->ovly && pmode->ovly < pfw->novly) { + + /* New mode uses ovly */ + ocndx = AIC_OCFG_NDX(pfw, pmode->ovly, cfg); + aic_dlimage(ps, + pfw->ovly_cfg[ocndx]); + } else if (pfw->ncfg > 0) { + + /* new mode needs only a cfg change */ + ocndx = AIC_OCFG_NDX(pfw, 0, cfg); + aic_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 = AIC_OCFG_NDX(pfw, pmode->ovly, cfg); + which = ps->ops->stop(ps->codec, which); + aic_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 = AIC_OCFG_NDX(pfw, pmode->ovly, cfg); + aic_dlcfg(ps, pfw->ovly_cfg[ocndx]); + } + ps->cur_ovly = pmode->ovly; + ps->cur_cfg = cfg; + + ps->cur_mode = mode; + aic_set_pll_u(ps, 0); + + } else if (pjt->mode[mode]->pfw != 0xFF) { + + /* Not bypass mode */ + aic_warn(ps, "Bad pfw setting detected (%d). Max pfw=%d", + pmode->pfw, pjt->npfw); + } + ps->cur_mode = mode; + aic_set_mode_id(ps); + + /* Transition to netural mode */ + aic_transition_u(ps, "NEUTRAL"); + + /* Apply entry sequence if present */ + aic_dlcmds(ps, pmode->entry); + + aic_dbg(ps, "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: + aic_dbg(ps, "Failed to set firmware mode"); + return -EINVAL; +} + +int aic_transition(struct aic_state *ps, char *ttype) +{ + int ret; + + aic_lock(ps, 1); + ret = aic_transition_u(ps, ttype); + aic_lock(ps, 0); + return ret; +} +EXPORT_SYMBOL_GPL(aic_transition); + +static int aic_transition_u(struct aic_state *ps, char *ttype) +{ + int i; + + if (ps->pjt == NULL) + return -EINVAL; + for (i = 0; i < AIC_TRN_N; ++i) { + if (!strcasecmp(ttype, aic_transition_id[i])) { + struct aic_transition *pt = ps->pjt->transition[i]; + aic_dbg(ps, "Sending transition %s[%d]", ttype, i); + if (pt) + aic_dlcmds(ps, pt->block); + return 0; + } + } + aic_warn(ps, "Transition %s not present or invalid", ttype); + return 0; +} + +int aic_set_pll(struct aic_state *ps, int asi) +{ + int ret; + + aic_lock(ps, 1); + ret = aic_set_pll_u(ps, asi); + aic_lock(ps, 0); + return ret; +} +EXPORT_SYMBOL_GPL(aic_set_pll); + +static int aic_set_pll_u(struct aic_state *ps, int asi) +{ + struct aic_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) { + aic_dbg(ps, "Re-configuring PLL: %s==>%d", + pjt->pll[pll_id]->name, + pll_id); + aic_dlcmds(ps, pjt->pll[pll_id]->seq); + ps->cur_pll = pll_id; + } + return 0; +} + +int aic_control(struct aic_state *ps, char *cname, int param) +{ + int ret; + + aic_lock(ps, 1); + ret = aic_control_u(ps, cname, param); + aic_lock(ps, 0); + return ret; +} + +static int aic_control_u(struct aic_state *ps, char *cname, int param) +{ + struct aic_pfw *pfw; + int i; + + if (ps->cur_pfw < 0 || ps->cur_pfw >= ps->pjt->npfw) { + aic_warn(ps, "Not in MiniDSP mode"); + return 0; + } + pfw = ps->pjt->pfw[ps->cur_pfw]; + for (i = 0; i < pfw->nctrl; ++i) { + struct aic_control *pc = pfw->ctrl[i]; + if (strcasecmp(cname, pfw->ctrl[i]->name)) + continue; + if (param < 0 || param > pc->imax) { + aic_warn(ps, "Parameter out of range\n"); + return -EINVAL; + } + aic_dbg(ps, "Sending control %s[%d]", cname, param); + pc->icur = param; + aic_dlctl(ps, pc->output[param], pc->mute_flags); + return 0; + } + aic_warn(ps, "Control named %s not found in pfw %s", cname, pfw->name); + + return -EINVAL; +} + +static void aic_op(struct aic_state *ps, unsigned char *var, + struct aic_cmd_op cmd) +{ + u32 op1, op2; + u32 cid = cmd.cid; + + op1 = cmd.op1; + op2 = cmd.op2; + if (cid & AIC_CMD_OP1_ID) + op1 = var[op1]; + if (cid & AIC_CMD_OP2_ID) + op2 = var[op2]; + cid &= ~(AIC_CMD_OP1_ID | AIC_CMD_OP2_ID); + + switch (cid) { + case AIC_CMD_OP_ADD: + var[cmd.dst] = op1 + op2; + break; + case AIC_CMD_OP_SUB: + var[cmd.dst] = op1 - op2; + break; + case AIC_CMD_OP_MUL: + var[cmd.dst] = op1 * op2; + break; + case AIC_CMD_OP_DIV: + var[cmd.dst] = op1 / op2; + break; + case AIC_CMD_OP_AND: + var[cmd.dst] = op1 & op2; + break; + case AIC_CMD_OP_OR: + var[cmd.dst] = op1 | op2; + break; + case AIC_CMD_OP_SHL: + var[cmd.dst] = (op1 << op2); + break; + case AIC_CMD_OP_SHR: + var[cmd.dst] = (op1 >> op2); + break; + case AIC_CMD_OP_RR: + while (op2--) + var[cmd.dst] = (op1 >> 1) | ((op1 & 1) << 7); + break; + case AIC_CMD_OP_XOR: + var[cmd.dst] = op1 ^ op2; + break; + case AIC_CMD_OP_NOT: + var[cmd.dst] = ~op1; + break; + case AIC_CMD_OP_LNOT: + var[cmd.dst] = !op1; + break; + default: + break; + } +} + +static void aic_dlcmds(struct aic_state *ps, struct aic_block *pb) +{ + int pc = 0, cond = 0; + unsigned char var[256]; + + if (!pb) + return; + while (pc < pb->ncmds) { + union aic_cmd *c = &(pb->cmd[pc]); + if (c->cid != AIC_CMD_BRANCH_IM && + c->cid != AIC_CMD_BRANCH_ID && c->cid != AIC_CMD_NOP) + cond = 0; + switch (c->cid) { + case 0 ... (AIC_CMD_NOP - 1): + ps->ops->reg_write(ps->codec, c->reg.bpod, + c->reg.data); + pc += 1; + break; + case AIC_CMD_NOP: + pc += 1; + break; + case AIC_CMD_DELAY: + mdelay(c->delay.delay); + pc += 1; + break; + case AIC_CMD_UPDTBITS: + ps->ops->set_bits(ps->codec, c[1].reg.bpod, + c->bitop.mask, c[1].reg.data); + pc += 2; + break; + case AIC_CMD_WAITBITS: + aic_wait(ps, c[1].reg.bpod, c->bitop.mask, + c[1].reg.data); + pc += 2; + break; + case AIC_CMD_LOCK: + if (c->delay.delay) + ps->ops->lock(ps->codec); + else + ps->ops->unlock(ps->codec); + pc += 1; + break; + case AIC_CMD_BURST: + ps->ops->bulk_write(ps->codec, c[1].reg.bpod, + c->bhdr.len, c[1].burst.data); + pc += AIC_CMD_BURST_LEN(c->bhdr.len); + break; + case AIC_CMD_RBURST: + ps->ops->bulk_read(ps->codec, c[1].reg.bpod, + c->bhdr.len, c[1].burst.data); + pc += AIC_CMD_BURST_LEN(c->bhdr.len); + break; + case AIC_CMD_LOAD_VAR_IM: + aic_set_bits(&var[c->ldst.dvar], + c->ldst.mask, c->ldst.svar); + pc += 1; + break; + case AIC_CMD_LOAD_VAR_ID: + if (c->ldst.svar != c->ldst.dvar) { + aic_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); + aic_set_bits(&var[c->ldst.dvar], + c->ldst.mask, data); + pc += 2; + } + break; + case AIC_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 AIC_CMD_COND: + cond = var[c->cond.svar] & c->cond.mask; + pc += 1; + break; + case AIC_CMD_BRANCH: + pc = c->branch.address; + break; + case AIC_CMD_BRANCH_IM: + if (c->branch.match == cond) + pc = c->branch.address; + else + pc += 1; + break; + case AIC_CMD_BRANCH_ID: + if (var[c->branch.match] == cond) + pc = c->branch.address; + else + pc += 1; + break; + case AIC_CMD_PRINT: + { + union aic_cmd *parglist = + c + AIC_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 += AIC_CMD_PRINT_LEN(c->print); + } + break; + case AIC_CMD_OP_START ... AIC_CMD_OP_END: + aic_op(ps, var, c->op); + pc += 1; + break; + default: + aic_warn(ps, "Unknown cmd command %x. Skipped", c->cid); + pc += 1; + break; + } + } +} + +static void aic_wait(struct aic_state *ps, unsigned int reg, u8 mask, + u8 data) +{ + while ((ps->ops->reg_read(ps->codec, reg) & mask) != data) + usleep_range(500, 1000); +} + +static inline void aic_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 = AIC_BLOCK_A_A_COEF, + .buf_b = AIC_BLOCK_A_B_COEF + }, + { + .mdsp = AIC3XXX_COPS_MDSP_D, + .swap = AIC3XXX_ABUF_MDSP_D1, + .buf_a = AIC_BLOCK_D_A1_COEF, + .buf_b = AIC_BLOCK_D_B1_COEF + }, + { + .mdsp = AIC3XXX_COPS_MDSP_D, + .swap = AIC3XXX_ABUF_MDSP_D2, + .buf_a = AIC_BLOCK_D_A2_COEF, + .buf_b = AIC_BLOCK_D_B2_COEF + }, +}; +static int aic_dlctl(struct aic_state *ps, struct aic_block *pb, + u32 mute_flags) +{ + int i, btype = pb->type; + int run_state = ps->ops->lock(ps->codec); + + aic_dbg(ps, "Download CTL"); + for (i = 0; i < sizeof(csecs) / sizeof(csecs[0]); ++i) { + if (csecs[i].buf_a != btype && csecs[i].buf_b != btype) + continue; + aic_dbg(ps, "\tDownload once to %d\n", btype); + aic_dlcmds(ps, pb); + if (run_state & csecs[i].mdsp) { + aic_dbg(ps, "Download again to make sure it reaches B"); + aic_mute(ps, 1, run_state & mute_flags); + ps->ops->bswap(ps->codec, csecs[i].swap); + aic_mute(ps, 0, run_state & mute_flags); + aic_dlcmds(ps, pb); + } + break; + } + ps->ops->unlock(ps->codec); + return 0; +} + +static int aic_dlcfg(struct aic_state *ps, struct aic_image *pim) +{ + int i, run_state, swap; + + aic_dbg(ps, "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; + aic_dlcmds(ps, pim->block[csecs[i].buf_a]); + aic_dlcmds(ps, pim->block[csecs[i].buf_b]); + if (run_state & csecs[i].mdsp) + swap |= csecs[i].swap; + } + if (swap) { + aic_mute(ps, 1, run_state & pim->mute_flags); + ps->ops->bswap(ps->codec, swap); + aic_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; + aic_dlcmds(ps, pim->block[csecs[i].buf_a]); + aic_dlcmds(ps, pim->block[csecs[i].buf_b]); + } + } + ps->ops->unlock(ps->codec); + return 0; +} + +static int aic_dlimage(struct aic_state *ps, struct aic_image *pim) +{ + int i; + + if (!pim) + return 0; + aic_dbg(ps, "Download IMAGE %s", pim->name); + for (i = 0; i < AIC_BLOCK_N; ++i) + aic_dlcmds(ps, pim->block[i]); + return 0; +} + +static int aic_mute(struct aic_state *ps, int mute, u32 flags) +{ + if ((flags & AIC3XXX_COPS_MDSP_D) && (flags & AIC3XXX_COPS_MDSP_A)) + aic_transition_u(ps, + mute ? "AD_MUTE" : "AD_UNMUTE"); + else if (flags & AIC3XXX_COPS_MDSP_D) + aic_transition_u(ps, mute ? "D_MUTE" : "D_UNMUTE"); + else if (flags & AIC3XXX_COPS_MDSP_A) + aic_transition_u(ps, mute ? "A_MUTE" : "A_UNMUTE"); + return 0; +} + +static inline void *aic_ndx2ptr(void *p, u8 *base) +{ + return &base[(int)p]; +} +static inline char *aic_desc(void *p, u8 *base) +{ + if (p) + return aic_ndx2ptr(p, base); + return NULL; +} + +static void aic_unpickle_image(struct aic_image *im, void *p) +{ + int i; + + im->desc = aic_desc(im->desc, p); + for (i = 0; i < AIC_BLOCK_N; ++i) + if (im->block[i]) + im->block[i] = aic_ndx2ptr(im->block[i], p); +} + +static void aic_unpickle_control(struct aic_control *ct, void *p) +{ + int i; + + ct->output = aic_ndx2ptr(ct->output, p); + ct->desc = aic_desc(ct->desc, p); + for (i = 0; i <= ct->imax; ++i) + ct->output[i] = aic_ndx2ptr(ct->output[i], p); +} + +static struct aic_project *aic_unpickle(void *p, int n) +{ + struct aic_project *pjt = p; + int i, j; + + if (pjt->magic != AIC_FW_MAGIC || pjt->size != n || + pjt->if_id != AIC_FW_IF_ID) { + return NULL; + } + + pjt->desc = aic_desc(pjt->desc, p); + pjt->transition = aic_ndx2ptr(pjt->transition, p); + for (i = 0; i < AIC_TRN_N; i++) { + if (!pjt->transition[i]) + continue; + pjt->transition[i] = aic_ndx2ptr(pjt->transition[i], p); + pjt->transition[i]->desc = aic_desc( + pjt->transition[i]->desc, p); + pjt->transition[i]->block = aic_ndx2ptr( + pjt->transition[i]->block, p); + } + pjt->pll = aic_ndx2ptr(pjt->pll, p); + for (i = 0; i < pjt->npll; i++) { + pjt->pll[i] = aic_ndx2ptr(pjt->pll[i], p); + pjt->pll[i]->desc = aic_desc(pjt->pll[i]->desc, p); + pjt->pll[i]->seq = aic_ndx2ptr(pjt->pll[i]->seq, p); + } + + pjt->pfw = aic_ndx2ptr(pjt->pfw, p); + for (i = 0; i < pjt->npfw; i++) { + pjt->pfw[i] = aic_ndx2ptr(pjt->pfw[i], p); + pjt->pfw[i]->desc = aic_desc(pjt->pfw[i]->desc, p); + if (pjt->pfw[i]->base) { + pjt->pfw[i]->base = aic_ndx2ptr( + pjt->pfw[i]->base, p); + aic_unpickle_image(pjt->pfw[i]->base, p); + } + pjt->pfw[i]->ovly_cfg = aic_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] = aic_ndx2ptr( + pjt->pfw[i]->ovly_cfg[j], p); + aic_unpickle_image(pjt->pfw[i]->ovly_cfg[j], p); + } + if (pjt->pfw[i]->nctrl) + pjt->pfw[i]->ctrl = aic_ndx2ptr( + pjt->pfw[i]->ctrl, p); + for (j = 0; j < pjt->pfw[i]->nctrl; ++j) { + pjt->pfw[i]->ctrl[j] = aic_ndx2ptr( + pjt->pfw[i]->ctrl[j], p); + aic_unpickle_control(pjt->pfw[i]->ctrl[j], p); + } + } + + pjt->mode = aic_ndx2ptr(pjt->mode, p); + for (i = 0; i < pjt->nmode; i++) { + pjt->mode[i] = aic_ndx2ptr(pjt->mode[i], p); + pjt->mode[i]->desc = aic_desc(pjt->mode[i]->desc, p); + if (pjt->mode[i]->entry) + pjt->mode[i]->entry = aic_ndx2ptr( + pjt->mode[i]->entry, p); + if (pjt->mode[i]->exit) + pjt->mode[i]->exit = aic_ndx2ptr( + pjt->mode[i]->exit, p); + } + if (pjt->asoc_toc) + pjt->asoc_toc = aic_ndx2ptr(pjt->asoc_toc, p); + else + return NULL; + + return pjt; +} +static int aic_set_mode_id(struct aic_state *ps) +{ + struct aic_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; + } + } + aic_dbg(ps, "Unknown mode,cfg combination [%d,%d]", ps->cur_mode, + ps->cur_cfg); + return -EINVAL; +} + +/* **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 aic_state *ps = (struct aic_state *)kcontrol->private_value; + struct aic_pfw *pfw; + int i; + + if (ps->cur_pfw >= ps->pjt->npfw) { + aic_dbg(ps, "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 aic_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 aic_state *ps = (struct aic_state *)kcontrol->private_value; + + aic_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 aic_state *ps = (struct aic_state *)kcontrol->private_value; + struct aic_pfw *pfw; + int i; + + if (ps->cur_pfw >= ps->pjt->npfw) { + aic_dbg(ps, "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 aic_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 aic_add_controls(struct snd_soc_codec *codec, struct aic_state *ps) +{ + int i, j; + struct aic_pfw *pfw; + + for (j = 0; j < ps->pjt->npfw; ++j) { + pfw = ps->pjt->pfw[j]; + + for (i = 0; i < pfw->nctrl; ++i) { + struct aic_control *pc = pfw->ctrl[i]; + struct snd_kcontrol_new *generic_control = + devm_kzalloc(codec->dev, + sizeof(struct snd_kcontrol_new), + GFP_KERNEL); + unsigned int *tlv_array = + devm_kzalloc(codec->dev, + 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); + aic_dbg(ps, "Added control %s", pc->name); + } + } + return 0; + +} +EXPORT_SYMBOL_GPL(aic_add_controls); + +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 aic_state *ps = (struct aic_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 aic_state *ps = (struct aic_state *) e->mask; + struct aic_asoc_toc *toc; + int index, ret; + + aic_lock(ps, 1); + toc = ps->pjt->asoc_toc; + + index = ucontrol->value.enumerated.item[0]; + if (index < 0 || index >= toc->nentries) { + aic_lock(ps, 0); + return -EINVAL; + } + ret = aic_setmode_cfg_u(ps, toc->entry[index].mode, + toc->entry[index].cfg); + aic_lock(ps, 0); + return ret; +} +EXPORT_SYMBOL_GPL(aic3xxx_put_mode); + +int aic_add_modes(struct snd_soc_codec *codec, struct aic_state *ps) +{ + int j; + struct aic_asoc_toc *toc = ps->pjt->asoc_toc; + struct soc_enum *mode_cfg_enum = + devm_kzalloc(codec->dev, sizeof(struct soc_enum), GFP_KERNEL); + struct snd_kcontrol_new *mode_cfg_control = + devm_kzalloc(codec->dev, sizeof(struct snd_kcontrol_new), + GFP_KERNEL); + + if (mode_cfg_enum == NULL) + goto mem_err; + if (mode_cfg_control == NULL) + goto mem_err; + + mode_cfg_enum->dtexts = devm_kzalloc(codec->dev, + toc->nentries * sizeof(char *), + GFP_KERNEL); + if (mode_cfg_enum->dtexts == NULL) + goto mem_err; + + for (j = 0; j < toc->nentries; j++) + mode_cfg_enum->dtexts[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; + +} +EXPORT_SYMBOL_GPL(aic_add_modes); + +MODULE_AUTHOR("Hari Rajagopala harik@ti.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/aic3xxx_cfw_ops.h b/sound/soc/codecs/aic3xxx_cfw_ops.h new file mode 100644 index 0000000..6a600fb --- /dev/null +++ b/sound/soc/codecs/aic3xxx_cfw_ops.h @@ -0,0 +1,98 @@ +/* + * aic3xxx_cfw_ops.h -- SoC audio for TI OMAP44XX SDP + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef AIC3XXX_CFW_OPS_H_ +#define AIC3XXX_CFW_OPS_H_ + +#include <linux/mutex.h> +#include <sound/soc.h> +#include <linux/cdev.h> + +struct aic_project; +struct aic3xxx_codec_ops; + +struct aic_state { + const char *dev_name; + int version; + struct aic_project *pjt; + const struct aic3xxx_codec_ops *ops; + struct snd_soc_codec *codec; + struct device *dev; + struct mutex mutex; + int cur_mode_id; + int cur_pll; + int cur_mode; + int cur_pfw; + int cur_ovly; + int cur_cfg; + struct cdev cdev; + int is_open; +}; + +int aic_init(struct aic_state *ps, const struct aic3xxx_codec_ops *ops, + struct snd_soc_codec *codec); +int aic_lock(struct aic_state *ps, int lock); +int aic_reload(struct aic_state *ps, void *pcfw, int n); +int aic_setmode(struct aic_state *ps, int mode); +int aic_setmode_cfg(struct aic_state *ps, int mode, int cfg); +int aic_setcfg(struct aic_state *ps, int cfg); +int aic_transition(struct aic_state *ps, char *ttype); +int aic_set_pll(struct aic_state *ps, int asi); +int aic_control(struct aic_state *ps, char *cname, int param); +int aic_add_controls(struct snd_soc_codec *codec, struct aic_state *ps); +int aic_add_modes(struct snd_soc_codec *codec, struct aic_state *ps); + + +#define AIC3XXX_COPS_MDSP_D_L (0x00000002u) +#define AIC3XXX_COPS_MDSP_D_R (0x00000001u) +#define AIC3XXX_COPS_MDSP_D (AIC3XXX_COPS_MDSP_D_L|AIC3XXX_COPS_MDSP_D_R) + +#define AIC3XXX_COPS_MDSP_A_L (0x00000020u) +#define AIC3XXX_COPS_MDSP_A_R (0x00000010u) +#define AIC3XXX_COPS_MDSP_A (AIC3XXX_COPS_MDSP_A_L|AIC3XXX_COPS_MDSP_A_R) + +#define AIC3XXX_COPS_MDSP_ALL (AIC3XXX_COPS_MDSP_D|AIC3XXX_COPS_MDSP_A) + +#define AIC3XXX_ABUF_MDSP_D1 (0x00000001u) +#define AIC3XXX_ABUF_MDSP_D2 (0x00000002u) +#define AIC3XXX_ABUF_MDSP_A (0x00000010u) +#define AIC3XXX_ABUF_MDSP_ALL \ + (AIC3XXX_ABUF_MDSP_D1|AIC3XXX_ABUF_MDSP_D2|AIC3XXX_ABUF_MDSP_A) + + +struct aic3xxx_codec_ops { + int (*reg_read) (struct snd_soc_codec *codec, unsigned int reg); + int (*reg_write) (struct snd_soc_codec *codec, unsigned int reg, + unsigned char val); + int (*set_bits) (struct snd_soc_codec *codec, unsigned int reg, + unsigned char mask, unsigned char val); + int (*bulk_read) (struct snd_soc_codec *codec, unsigned int reg, + int count, u8 *buf); + int (*bulk_write) (struct snd_soc_codec *codec, unsigned int reg, + int count, const u8 *buf); + int (*lock) (struct snd_soc_codec *codec); + int (*unlock) (struct snd_soc_codec *codec); + int (*stop) (struct snd_soc_codec *codec, int mask); + int (*restore) (struct snd_soc_codec *codec, int runstate); + int (*bswap) (struct snd_soc_codec *codec, int mask); +}; + +MODULE_LICENSE("GPL"); + +#endif
participants (1)
-
Mehar Bajwa