/* * Copyright (c) 2015 Samsung Electronics Co. Ltd. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SND_SOC_REGMAP_FIRMWARE #include #endif #include #include "exynos-audmixer-regs.h" /* * The sysclk is derived from the audio PLL. The value of the PLL is not always * rounded, many times the actual rate is a little bit higher or less than the * rounded value. * * 'clk_set_rate' operation tries to set the given rate or the next lower * possible value. Thus if the PLL rate is slightly higher than rounded value, * we won't get the given rate through a direct division. This will result in * getting a lower clock rate. Asking for a slightly higher clock rate will * result in setting appropriate clock rate. * * The value '100' is a heuristic value and there is no clear rule to derive * this value. */ #define AUDMIXER_SYS_CLK_FREQ_48KHZ (24576000U + 100) #define AUDMIXER_SYS_CLK_FREQ_192KHZ (49152000U + 100) #define AUDMIXER_SAMPLE_RATE_8KHZ 8000 #define AUDMIXER_SAMPLE_RATE_16KHZ 16000 #define AUDMIXER_SAMPLE_RATE_48KHZ 48000 #define AUDMIXER_SAMPLE_RATE_192KHZ 192000 /* * During every mixer control operation, the Audio Mixer needs to be resumed and * for power-saving, it needs to be suspended afterwards. In normal scenario, * a batch of control operations is performed. In this case, the mixer would * require to perform a resume-suspend cycle for each operation. To avoid the * unnecessary suspend-resume cycles, mixer suspend is called after a certain * delay. Following macro defines that delay period in milli-seconds. * * Further details, refer to function audmixer_soc_kcontrol_handler. */ #define AUDMIXER_RPM_SUSPEND_DELAY_MS (500) #define S2803X_FIRMWARE_NAME "cod3025x-s2803x-aud-fw.bin" #ifdef CONFIG_SND_SOC_SAMSUNG_VERBOSE_DEBUG #ifdef dev_dbg #undef dev_dbg #endif #define dev_dbg dev_err #endif /* enum audmixer_type: Audio Mixer revision identifier */ enum audmixer_type { SRC2801X, SRC2803X, SRC1402X, SRC1403X }; /* * enum audmixer_bus_type: Connection interface type of the Audio Mixer * * The initial revision of Audio Mixer chip (SRC2801X) was connected to an I2C * bus, whereas the later revisions were connected to the APB bus. This changes * the way the regmap/cache etc works. * * If bus type is AUDMIXER_I2C, regmap-i2c is used, regcache is used. * If bus type is AUDMIXER_APB, regmap-mmio is used, regcache is not used. */ enum audmixer_bus_type { AUDMIXER_I2C = 0, AUDMIXER_APB, }; /* * struct audmixer_hw_config: H/W configuration information for Audio Mixer * @type: Audio Mixer chip revision identifier * @bus: Audio Mixer chip connection interface type */ struct audmixer_hw_config { enum audmixer_type type; enum audmixer_bus_type bus; }; /* H/W configuration information for SRC2801X */ static struct audmixer_hw_config s2801x_config = { .type = SRC2801X, .bus = AUDMIXER_I2C, }; /* H/W configuration information for SRC2803X */ static struct audmixer_hw_config s2803x_config = { .type = SRC2803X, .bus = AUDMIXER_APB, }; /* H/W configuration information for SRC1402X */ static struct audmixer_hw_config s1402x_config = { .type = SRC1402X, .bus = AUDMIXER_APB, }; /* H/W configuration information for SRC1403X */ static struct audmixer_hw_config s1403x_config = { .type = SRC1403X, .bus = AUDMIXER_APB, }; /* I2C device ID information for SRC2801X */ static const struct i2c_device_id audmixer_i2c_id[] = { { "s2801x", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, audmixer_i2c_id); /* * auxmixer_i2c_dt_ids: List of supported compatible strings for I2C driver * * @compatible: The supported compatible string name * @data: Related H/W configuration structure */ static const struct of_device_id audmixer_i2c_dt_ids[] = { { .compatible = "samsung,s2801x", .data = (void *) &s2801x_config }, { } }; MODULE_DEVICE_TABLE(of, audmixer_i2c_dt_ids); /* * auxmixer_apb_dt_ids: List of supported compatible strings for APB driver * * @compatible: The supported compatible string name * @data: Related H/W configuration structure */ static const struct of_device_id audmixer_apb_dt_ids[] = { { .compatible = "samsung,s2803x", .data = (void *) &s2803x_config }, { .compatible = "samsung,s1402x", .data = (void *) &s1402x_config }, { .compatible = "samsung,s1403x", .data = (void *) &s1403x_config }, { } }; MODULE_DEVICE_TABLE(of, audmixer_apb_dt_ids); /* * struct audmixer_extrareg_info: Structure to hold information about some SoC * specific bits * */ struct audmixer_soc_reg_info { void __iomem *reg; unsigned int bit; bool active_high; }; /* * struct audmixer_priv: Private data structure for Audio Mixer Driver * * @regmap: Pointer to regmap handler * @codec: Pointer to ALSA codec structure * @dev: Pointer to core device structure * @regs: IO mapped address for Audio Mixer SFR base address (for APB bus) * @aclk: ACLK for Audio Mixer SFR access (for APB bus) * @sclk: SCLK for Audio Mixer * @bclk0, @bclk1, @bclk2: BCLK for AP/BT/CP interfaces of Audio Mixer * @clk_dout: Clock to manage the rate for SCLK * @sysreg_reset: RESET register structure variable * @reg_alive: ALIVE register structure variable * @sysreg_i2c_id: IO mapped address for I2C_ADDR register (for I2C bus) * @i2c_addr: The I2C slave address of the device * @is_cp_running: Counter to detect if Audio Mixer is active * @num_active_stream: Counter to find number of streams active in all i/f * @use_count: Counter to find number of active streams per i/f * @pinctrl: Pointer to pinctrl handler * @aifrate: Current active sample rate of AIF1 interface (to set clock rate) * @is_bck4_mcko_enabled: If true, codec MCLK is provided through BCLK line of * AIF4, otherwise normal BCLK is provided. Set it to false if a voice-processor * is connected between AIF4 and the audio codec; otherwise it should be true. * @update_fw: Flag to check if firmware needs to be updated during boot time * @hw: H/W configuration for specific revision of Audio Mixer chip */ struct audmixer_priv { struct regmap *regmap; struct snd_soc_codec *codec; struct device *dev; void __iomem *regs; struct clk *aclk; struct clk *sclk; struct clk *audclk; struct clk *clk_dout; void *sysreg_i2c_id; unsigned short i2c_addr; struct audmixer_soc_reg_info reg_reset; struct audmixer_soc_reg_info reg_alive; struct audmixer_soc_reg_info reg_i2c; atomic_t num_active_stream; atomic_t use_count[AUDMIXER_IF_COUNT]; struct pinctrl *pinctrl; unsigned int aifrate; bool is_active; bool is_bck4_mcko_enabled; bool is_alc_enabled; bool update_fw; bool is_regs_stored; const struct audmixer_hw_config *hw; }; struct audmixer_priv *g_audmixer; /* When the user-space needs to read the value of a register, it reads the * regcache value first. If the cache value is not updated (in case where there * hasn't been any write to this register yet), it tries to read the value from * the hardware. If the device is run-time suspended during that time, it * returns an error and read operation fails. * * To fix this scenario, following registers need to be updated in boot time * before they are read by user-space. */ static struct reg_default audmixer_init_reg_list[] = { /* { reg, def } */ { 0x0d, 0x04 }, { 0x10, 0x00 }, { 0x11, 0x00 }, { 0x16, 0x00 }, { 0x17, 0x00 }, }; /** * Return value: * true: if the register value cannot be cached, hence we have to read from the * register directly * false: if the register value can be read from cache */ static bool audmixer_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB: return true; default: return false; } } /** * Return value: * true: if the register value can be read * flase: if the register cannot be read */ static bool audmixer_readable_register(struct device *dev, unsigned int reg) { if (g_audmixer->hw->bus == AUDMIXER_APB) { if (reg % 4) return false; } switch (g_audmixer->hw->type) { case SRC2801X: switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB ... AUDMIXER_REG_11_DMIX2: case AUDMIXER_REG_16_DOUTMX1 ... AUDMIXER_REG_17_DOUTMX2: case AUDMIXER_REG_68_ALC_CTL ... AUDMIXER_REG_72_ALC_SGR: return true; default: return false; } break; default: case SRC2803X: case SRC1402X: switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB ... AUDMIXER_REG_11_DMIX2: case AUDMIXER_REG_16_DOUTMX1 ... AUDMIXER_REG_1A_OUTCP1_CTL: case AUDMIXER_REG_68_ALC_CTL ... AUDMIXER_REG_72_ALC_SGR: return true; default: return false; } break; case SRC1403X: switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB ... AUDMIXER_REG_07_IN3_CTL1: case AUDMIXER_REG_0A_HQ_CTL ... AUDMIXER_REG_11_DMIX2: case AUDMIXER_REG_16_DOUTMX1 ... AUDMIXER_REG_1A_OUTCP1_CTL: return true; default: return false; } break; } } /** * Return value: * true: if the register value can be modified * flase: if the register value cannot be modified */ static bool audmixer_writeable_register(struct device *dev, unsigned int reg) { /* If the Audio Mixer is on APB bus, the register offsets are multiple * of 4. */ if (g_audmixer->hw->bus == AUDMIXER_APB) { if (reg % 4) return false; } switch (g_audmixer->hw->type) { case SRC2801X: switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB ... AUDMIXER_REG_11_DMIX2: case AUDMIXER_REG_16_DOUTMX1 ... AUDMIXER_REG_17_DOUTMX2: case AUDMIXER_REG_68_ALC_CTL ... AUDMIXER_REG_72_ALC_SGR: return true; default: return false; } break; default: case SRC2803X: case SRC1402X: switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB ... AUDMIXER_REG_11_DMIX2: case AUDMIXER_REG_16_DOUTMX1 ... AUDMIXER_REG_1A_OUTCP1_CTL: case AUDMIXER_REG_68_ALC_CTL ... AUDMIXER_REG_72_ALC_SGR: return true; default: return false; } break; case SRC1403X: switch (reg) { case AUDMIXER_REG_00_SOFT_RSTB ... AUDMIXER_REG_07_IN3_CTL1: case AUDMIXER_REG_0A_HQ_CTL ... AUDMIXER_REG_11_DMIX2: case AUDMIXER_REG_16_DOUTMX1 ... AUDMIXER_REG_1A_OUTCP1_CTL: return true; default: return false; } break; } } static const struct reg_default audmixer_reg_defaults_src1402x[] = { {.reg = 0x0004, .def = 0x0C}, {.reg = 0x0008, .def = 0x62}, {.reg = 0x000C, .def = 0x24}, {.reg = 0x0010, .def = 0x0C}, {.reg = 0x0014, .def = 0x62}, {.reg = 0x0018, .def = 0x24}, {.reg = 0x001C, .def = 0x0C}, {.reg = 0x0020, .def = 0x62}, {.reg = 0x0024, .def = 0x24}, {.reg = 0x0028, .def = 0x04}, {.reg = 0x002C, .def = 0x01}, {.reg = 0x0030, .def = 0x02}, {.reg = 0x0034, .def = 0x00}, {.reg = 0x0038, .def = 0x01}, {.reg = 0x003C, .def = 0x00}, {.reg = 0x0040, .def = 0x00}, {.reg = 0x0044, .def = 0x00}, {.reg = 0x0058, .def = 0x00}, {.reg = 0x005C, .def = 0x00}, {.reg = 0x0060, .def = 0x2A}, {.reg = 0x0064, .def = 0x22}, {.reg = 0x0068, .def = 0x22}, {.reg = 0x01A0, .def = 0x00}, {.reg = 0x01A4, .def = 0x80}, {.reg = 0x01A8, .def = 0x58}, {.reg = 0x01AC, .def = 0x1A}, {.reg = 0x01B0, .def = 0x1A}, {.reg = 0x01B4, .def = 0x12}, {.reg = 0x01B8, .def = 0x12}, {.reg = 0x01BC, .def = 0x12}, {.reg = 0x01C0, .def = 0x17}, {.reg = 0x01C4, .def = 0x6C}, {.reg = 0x01C8, .def = 0x6C}, }; static const struct reg_default audmixer_reg_defaults_src1403x[] = { {.reg = 0x0004, .def = 0x00}, {.reg = 0x0008, .def = 0x62}, {.reg = 0x000C, .def = 0x24}, {.reg = 0x0010, .def = 0x00}, {.reg = 0x0014, .def = 0x62}, {.reg = 0x0018, .def = 0x24}, {.reg = 0x001C, .def = 0x00}, {.reg = 0x0028, .def = 0x04}, {.reg = 0x002C, .def = 0x01}, {.reg = 0x0030, .def = 0x02}, {.reg = 0x0034, .def = 0x00}, {.reg = 0x0038, .def = 0x01}, {.reg = 0x003C, .def = 0x00}, {.reg = 0x0040, .def = 0x00}, {.reg = 0x0044, .def = 0x00}, {.reg = 0x0058, .def = 0x00}, {.reg = 0x005C, .def = 0x00}, {.reg = 0x0060, .def = 0x2A}, {.reg = 0x0064, .def = 0x22}, {.reg = 0x0068, .def = 0x2A}, }; static const struct regmap_config audmixer_regmap_mmio = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = AUDMIXER_MAX_REGISTER, .volatile_reg = audmixer_volatile_register, .readable_reg = audmixer_readable_register, .writeable_reg = audmixer_writeable_register, .reg_defaults = audmixer_reg_defaults_src1403x, .num_reg_defaults = ARRAY_SIZE(audmixer_reg_defaults_src1403x), .cache_type = REGCACHE_RBTREE, }; static const struct regmap_config audmixer_regmap_i2c = { .reg_bits = 8, .val_bits = 8, .max_register = AUDMIXER_MAX_REGISTER, .volatile_reg = audmixer_volatile_register, .readable_reg = audmixer_readable_register, .writeable_reg = audmixer_writeable_register, .cache_type = REGCACHE_RBTREE, }; #ifdef CONFIG_PM_RUNTIME static int audmixer_reset_sys_data(void) { regmap_write(g_audmixer->regmap, AUDMIXER_REG_00_SOFT_RSTB, 0x0); msleep(1); regmap_write(g_audmixer->regmap, AUDMIXER_REG_00_SOFT_RSTB, BIT(SOFT_RSTB_DATA_RSTB_SHIFT) | BIT(SOFT_RSTB_SYS_RSTB_SHIFT)); msleep(1); return 0; } #endif /* * audmixer_reset_data: Reset the data path of audio mixer * * This function needs to be called after the h/w configuration of the audio * mixer is modified so that existing data buffer is flushed. */ static void audmixer_reset_data(void) { /* Data reset sequence, toggle bit 1 */ regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_00_SOFT_RSTB, BIT(SOFT_RSTB_DATA_RSTB_SHIFT), 0x0); regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_00_SOFT_RSTB, BIT(SOFT_RSTB_DATA_RSTB_SHIFT), BIT(SOFT_RSTB_DATA_RSTB_SHIFT)); } /* * audmixer_init_mixer: Configure mixer registers for default operations * * This function is called once during boot time. */ static int audmixer_init_mixer(void) { /** * Set default configuration for AP/CP/BT interfaces * * BCLK = 32fs * LRCLK polarity normal * I2S data format is in I2S standard * I2S data length is 16 bits per sample */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_02_IN1_CTL2, I2S_XFS_32FS << INCTL2_I2S_XFS_SHIFT | LRCLK_POL_LEFT << INCTL2_LRCK_POL_SHIFT | I2S_DF_I2S << INCTL2_I2S_DF_SHIFT | I2S_DL_16BIT << INCTL2_I2S_DL_SHIFT); regmap_write(g_audmixer->regmap, AUDMIXER_REG_05_IN2_CTL2, I2S_XFS_32FS << INCTL2_I2S_XFS_SHIFT | LRCLK_POL_LEFT << INCTL2_LRCK_POL_SHIFT | I2S_DF_I2S << INCTL2_I2S_DF_SHIFT | I2S_DL_16BIT << INCTL2_I2S_DL_SHIFT); switch (g_audmixer->hw->type) { case SRC1403X: break; default: /* BT Configuration Initialisation */ /* I2s mode - Mixer Slave - 32 BCK configuration*/ regmap_write(g_audmixer->regmap, AUDMIXER_REG_07_IN3_CTL1, MIXER_SLAVE << INCTL1_MASTER_SHIFT | MPCM_SLOT_32BCK << INCTL1_MPCM_SLOT_SHIFT | I2S_PCM_MODE_I2S << INCTL1_I2S_PCM_SHIFT); /* 32xfs - i2s format 16bit */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_08_IN3_CTL2, I2S_XFS_32FS << INCTL2_I2S_XFS_SHIFT | LRCLK_POL_LEFT << INCTL2_LRCK_POL_SHIFT | I2S_DF_I2S << INCTL2_I2S_DF_SHIFT | I2S_DL_16BIT << INCTL2_I2S_DL_SHIFT); break; } /* * Below setting only requird for PCM mode, but it has no impact for I2S * mode. */ /* 0 delay, pcm short frame sync */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_09_IN3_CTL3, PCM_DAD_0BCK << INCTL3_PCM_DAD_SHIFT | PCM_DF_SHORT_FRAME << INCTL3_PCM_DF_SHIFT); /* SLOT_L and SLOT_R registers are different since SRC1402X */ /* SLOT_L - 1st slot */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_0B_SLOT_L, SLOT_SEL_1ST_SLOT << SLOT_L_SEL_SHIFT); /* SLOT_R - 2nd slot */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_0C_SLOT_R, SLOT_SEL_2ND_SLOT << SLOT_R_SEL_SHIFT); /* T - Slots 2 slots used */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_0E_TSLOT, TSLOT_USED_2 << TSLOT_SLOT_SHIFT); /* amp input configiration */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_18_INAMP_CTL, LRCLK_POL_LEFT << INAMP_CTL_LRCLK_POL_SHIFT | BCK_POL_NORMAL << INAMP_CTL_BCK_POL_SHIFT | 2 << 4 /* reserved data value 2 */ | I2S_XFS_64FS << INAMP_CTL_I2S_XFS_SHIFT | AMP_I2S_DL_24BIT << INAMP_CTL_I2S_DL_SHIFT); /* amp output configiration fot ap */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_19_OUTAP1_CTL, LRCLK_POL_LEFT << OUTAMP_CTL_LRCLK_POL_SHIFT | BCK_POL_NORMAL << OUTAMP_CTL_BCK_POL_SHIFT | AMP_I2S_DL_16BIT << OUTAMP_CTL_I2S_DL_SHIFT | I2S_XFS_32FS << OUTAMP_CTL_I2S_XFS_SHIFT); /* amp output configiration fot cp */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_1A_OUTCP1_CTL, LRCLK_POL_LEFT << OUTAMP_CTL_LRCLK_POL_SHIFT | BCK_POL_NORMAL << OUTAMP_CTL_BCK_POL_SHIFT | AMP_I2S_DL_16BIT << OUTAMP_CTL_I2S_DL_SHIFT | I2S_XFS_32FS << OUTAMP_CTL_I2S_XFS_SHIFT); /** * BCK4 output is normal BCK for Universal board, the clock output goes * to voice processor. It should be MCLK for SMDK board, as the clock * output goes to codec as MCLK. */ if (g_audmixer->is_bck4_mcko_enabled) regmap_write(g_audmixer->regmap, AUDMIXER_REG_0A_HQ_CTL, BIT(HQ_CTL_MCKO_EN_SHIFT) | BIT(HQ_CTL_BCK4_MODE_SHIFT)); else regmap_write(g_audmixer->regmap, AUDMIXER_REG_0A_HQ_CTL, BIT(HQ_CTL_MCKO_EN_SHIFT)); /* Enable digital mixer */ regmap_write(g_audmixer->regmap, AUDMIXER_REG_0F_DIG_EN, BIT(DIG_EN_MIX_EN_SHIFT)); /* Reset DATA path */ audmixer_reset_data(); return 0; } /* * audmixer_cfg_gpio: Configure pin-control states * * This function is used to update the pin-control states during device * suspend/resume and interface startup/shutdown calls. */ static void audmixer_cfg_gpio(struct device *dev, const char *name) { struct pinctrl_state *pin_state; int ret; pin_state = pinctrl_lookup_state(g_audmixer->pinctrl, name); if (IS_ERR(pin_state)) { dev_err(dev, "Couldn't find pinctrl %s\n", name); } else { ret = pinctrl_select_state(g_audmixer->pinctrl, pin_state); if (ret < 0) dev_err(dev, "Unable to configure pinctrl %s\n", name); } } #ifdef CONFIG_PM_RUNTIME /* * audmixer_save_regs: Prepare the registers before suspend */ static void audmixer_save_regs(struct device *dev) { regcache_cache_only(g_audmixer->regmap, true); regcache_mark_dirty(g_audmixer->regmap); } /* * audmixer_restore_regs: Restore the h/w register values */ static void audmixer_restore_regs(struct device *dev) { regcache_cache_only(g_audmixer->regmap, false); regcache_sync(g_audmixer->regmap); } #endif static int audmixer_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) { enum audmixer_if_t interface = (enum audmixer_if_t)(dai->id); int xfs; int ret = 0; /* Only I2S_XFS_32FS is verified now */ switch (ratio) { case 32: xfs = I2S_XFS_32FS; break; case 48: xfs = I2S_XFS_48FS; break; case 64: xfs = I2S_XFS_64FS; break; default: dev_err(g_audmixer->dev, "%s: Unsupported bfs (%d)\n", __func__, ratio); return -EINVAL; } switch (interface) { case AUDMIXER_IF_AP: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_02_IN1_CTL2, (INCTL2_I2S_XFS_MASK << INCTL2_I2S_XFS_SHIFT), (xfs << INCTL2_I2S_XFS_SHIFT)); break; case AUDMIXER_IF_CP: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_05_IN2_CTL2, (INCTL2_I2S_XFS_MASK << INCTL2_I2S_XFS_SHIFT), (xfs << INCTL2_I2S_XFS_SHIFT)); break; case AUDMIXER_IF_BT: case AUDMIXER_IF_FM: switch (g_audmixer->hw->type) { case SRC1403X: break; default: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_08_IN3_CTL2, (INCTL2_I2S_XFS_MASK << INCTL2_I2S_XFS_SHIFT), (xfs << INCTL2_I2S_XFS_SHIFT)); break; } break; case AUDMIXER_IF_AP1: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_19_OUTAP1_CTL, (OUTAMP_CTL_I2S_XFS_MASK << OUTAMP_CTL_I2S_XFS_SHIFT), (xfs << OUTAMP_CTL_I2S_XFS_SHIFT)); break; case AUDMIXER_IF_CP1: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_1A_OUTCP1_CTL, (OUTAMP_CTL_I2S_XFS_MASK << OUTAMP_CTL_I2S_XFS_SHIFT), (xfs << OUTAMP_CTL_I2S_XFS_SHIFT)); break; default: dev_err(g_audmixer->dev, "%s: Unsupported interface (%d)\n", __func__, interface); break; } return ret; } /* * audmixer_hw_params: Configure different interfaces of Audio Mixer. * * This function is called from the hw_params call of sound-card. * It does following: * 1. Sets BFS value * 2. Sets Data length (16bit or 24bit) * 3. Sets sysclk rate (depending on 48KHz or 192KHz playback) * * @substream: The substream structure passed from ALSA core to sound-card * @params: The hw_params structure passed from ALSA core to sound-card * @bfs: The BFS value for the particular interface * @interface: The ID of the current interface * * Returns 0 on success otherwise some error code. */ static int audmixer_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { enum audmixer_if_t interface = (enum audmixer_if_t)(dai->id); int dl_bit; unsigned int hq_mode; unsigned int sys_clk_freq; unsigned int aifrate; int ret; if (g_audmixer == NULL) return -EINVAL; dev_dbg(g_audmixer->dev, "(%s) %s called for aif%d\n", substream->stream ? "C" : "P", __func__, interface); /* Only I2S_DL_16BIT is verified now */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_U24: case SNDRV_PCM_FORMAT_S24: if ((interface == AUDMIXER_IF_AP1) || (interface == AUDMIXER_IF_CP1)) dl_bit = AMP_I2S_DL_24BIT; else dl_bit = I2S_DL_24BIT; break; case SNDRV_PCM_FORMAT_U16_LE: case SNDRV_PCM_FORMAT_S16_LE: dl_bit = I2S_DL_16BIT; break; default: dev_err(g_audmixer->dev, "%s: Unsupported format\n", __func__); return -EINVAL; } switch (interface) { case AUDMIXER_IF_AP: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_02_IN1_CTL2, (INCTL2_I2S_DL_MASK << INCTL2_I2S_DL_SHIFT), dl_bit); aifrate = params_rate(params); switch (aifrate) { case AUDMIXER_SAMPLE_RATE_192KHZ: sys_clk_freq = AUDMIXER_SYS_CLK_FREQ_192KHZ; hq_mode = HQ_CTL_HQ_EN_MASK; break; case AUDMIXER_SAMPLE_RATE_48KHZ: sys_clk_freq = AUDMIXER_SYS_CLK_FREQ_48KHZ; hq_mode = 0; break; default: dev_err(g_audmixer->dev, "%s: Unsupported sample-rate (%u)\n", __func__, aifrate); return -EINVAL; } ret = clk_set_rate(g_audmixer->clk_dout, sys_clk_freq); if (ret != 0) { dev_err(g_audmixer->dev, "%s: Error setting mixer sysclk rate as %u\n", __func__, sys_clk_freq); return ret; } /* Change the clock only when switching the sample rate */ if (g_audmixer->aifrate == aifrate) break; regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_0A_HQ_CTL, HQ_CTL_HQ_EN_MASK, hq_mode); g_audmixer->aifrate = aifrate; break; case AUDMIXER_IF_CP: ret = clk_set_rate(g_audmixer->clk_dout, AUDMIXER_SYS_CLK_FREQ_48KHZ); if (ret != 0) { dev_err(g_audmixer->dev, "%s: Error setting mixer sysclk rate as %u\n", __func__, AUDMIXER_SYS_CLK_FREQ_48KHZ); return ret; } ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_05_IN2_CTL2, (INCTL2_I2S_DL_MASK << INCTL2_I2S_DL_SHIFT), dl_bit); break; case AUDMIXER_IF_BT: case AUDMIXER_IF_FM: switch (g_audmixer->hw->type) { case SRC1403X: break; default: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_08_IN3_CTL2, (INCTL2_I2S_DL_MASK << INCTL2_I2S_DL_SHIFT), dl_bit); /* * Sample rate setting only requird for PCM master mode, but the * below configuration have no impact in I2S mode. */ aifrate = params_rate(params); switch (aifrate) { case AUDMIXER_SAMPLE_RATE_8KHZ: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_07_IN3_CTL1, (INCTL1_MPCM_SRATE_MASK << INCTL1_MPCM_SRATE_SHIFT), (MPCM_SRATE_8KHZ << INCTL1_MPCM_SRATE_SHIFT)); break; case AUDMIXER_SAMPLE_RATE_16KHZ: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_07_IN3_CTL1, (INCTL1_MPCM_SRATE_MASK << INCTL1_MPCM_SRATE_SHIFT), (MPCM_SRATE_16KHZ << INCTL1_MPCM_SRATE_SHIFT)); break; default: dev_warn(g_audmixer->dev, "%s: Unsupported BT samplerate (%d)\n", __func__, aifrate); break; } break; } break; case AUDMIXER_IF_AP1: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_19_OUTAP1_CTL, (OUTAMP_CTL_I2S_DL_MASK << OUTAMP_CTL_I2S_DL_SHIFT), (dl_bit << OUTAMP_CTL_I2S_DL_SHIFT)); break; case AUDMIXER_IF_CP1: ret = regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_1A_OUTCP1_CTL, (OUTAMP_CTL_I2S_DL_MASK << OUTAMP_CTL_I2S_DL_SHIFT), (dl_bit << OUTAMP_CTL_I2S_DL_SHIFT)); break; default: dev_err(g_audmixer->dev, "%s: Unsupported interface (%d)\n", __func__, interface); return -EINVAL; } /* Reset the data path only if current substream is active */ if (atomic_read(&g_audmixer->num_active_stream) == 1) audmixer_reset_data(); return 0; } /* * audmixer_startup: Start a particular interface of Audio Mixer * * This function is called from the startup call of sound-card. * It does following. * 1. Enables mixer device through runtime resume call * 2. Increments various usage counts * 3. Configures BT GPIOs, if required. * * @interface: The ID of the current interface */ static int audmixer_startup(struct snd_pcm_substream *stream, struct snd_soc_dai *dai) { enum audmixer_if_t interface = (enum audmixer_if_t)(dai->id); if (g_audmixer == NULL) return -ENODEV; dev_dbg(g_audmixer->dev, "aif%d: %s called\n", interface, __func__); /* * Runtime resume sequence internally checks is_active variable for * CP call mode. If the value of is_active variable is non-zero, the * system assumes that the it is resuming from CP call mode and skips * the power-domain powering on sequence. * * If this variable is incremented before pm_runtime_get_sync() is * called, the framework won't power on the power-domain even though the * call was made during the start of a call. * * Hence, is_active variable should be enabled after the * pm_runtime_get_sync() function is called. */ pm_runtime_get_sync(g_audmixer->dev); /* Keep a count of number of active interfaces, useful if we need to * perform some operations based on the usage count of an interface */ atomic_inc(&g_audmixer->use_count[interface]); switch (g_audmixer->hw->type) { case SRC1403X: switch (interface) { case AUDMIXER_IF_BT: regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_07_IN3_CTL1, INCTL1_FM_BT_SEL_MASK << INCTL1_FM_BT_SEL_SHIFT, 0 << INCTL1_FM_BT_SEL_SHIFT); break; case AUDMIXER_IF_FM: regmap_update_bits(g_audmixer->regmap, AUDMIXER_REG_07_IN3_CTL1, INCTL1_FM_BT_SEL_MASK << INCTL1_FM_BT_SEL_SHIFT, 1 << INCTL1_FM_BT_SEL_SHIFT); break; default: break; } break; default: if (interface == AUDMIXER_IF_BT) if (atomic_read(&g_audmixer->use_count[AUDMIXER_IF_BT]) == 1) audmixer_cfg_gpio(g_audmixer->dev, "default"); break; } atomic_inc(&g_audmixer->num_active_stream); return 0; } /* * audmixer_shutdown: Stop a particular interface of Audio Mixer * * This function is called from the shutdown call of sound-card. * It does following. * 1. Decrements various usage counts * 2. Reset BT GPIOs, if required. * 3. Disables mixer device through runtime suspend call if use count is zero * * @interface: The ID of the current interface */ static void audmixer_shutdown(struct snd_pcm_substream *stream, struct snd_soc_dai *dai) { enum audmixer_if_t interface = (enum audmixer_if_t)(dai->id); if (g_audmixer == NULL) return; dev_dbg(g_audmixer->dev, "aif%d: %s called\n", interface, __func__); atomic_dec(&g_audmixer->num_active_stream); atomic_dec(&g_audmixer->use_count[interface]); switch (g_audmixer->hw->type) { case SRC1403X: break; default: if (interface == AUDMIXER_IF_BT) if (atomic_read(&g_audmixer->use_count[AUDMIXER_IF_BT]) == 0) audmixer_cfg_gpio(g_audmixer->dev, "bt-idle"); break; } pm_runtime_put_sync(g_audmixer->dev); } static const struct snd_soc_dai_ops audmixer_dai_ops = { .set_bclk_ratio = audmixer_set_bclk_ratio, .startup = audmixer_startup, .shutdown = audmixer_shutdown, .hw_params = audmixer_hw_params, }; /** * is_cp_aud_enabled(void): Checks whether mixer is active or not * * This function is used by PM core to find out if the audio path is active or * not. If audio path is active, the system goes into LPA or CP_CALL mode during * system suspend time. * * Returns true if audio path is enabled, false otherwise. */ bool is_cp_aud_enabled(void) { if (g_audmixer == NULL) return false; return g_audmixer->is_active; } EXPORT_SYMBOL_GPL(is_cp_aud_enabled); /** * TLV_DB_SCALE_ITEM * * (TLV: Threshold Limit Value) * * For various properties, the dB values don't change linearly with respect to * the digital value of related bit-field. At most, they are quasi-linear, * that means they are linear for various ranges of digital values. Following * table define such ranges of various properties. * * TLV_DB_RANGE_HEAD(num) * num defines the number of linear ranges of dB values. * * s0, e0, TLV_DB_SCALE_ITEM(min, step, mute), * s0: digital start value of this range (inclusive) * e0: digital end valeu of this range (inclusive) * min: dB value corresponding to s0 * step: the delta of dB value in this range * mute: ? * * Example: * TLV_DB_RANGE_HEAD(3), * 0, 1, TLV_DB_SCALE_ITEM(-2000, 2000, 0), * 2, 4, TLV_DB_SCALE_ITEM(1000, 1000, 0), * 5, 6, TLV_DB_SCALE_ITEM(3800, 8000, 0), * * The above code has 3 linear ranges with following digital-dB mapping. * (0...6) -> (-2000dB, 0dB, 1000dB, 2000dB, 3000dB, 3800dB, 4600dB), * * DECLARE_TLV_DB_SCALE * * This macro is used in case where there is a linear mapping between * the digital value and dB value. * * DECLARE_TLV_DB_SCALE(name, min, step, mute) * * name: name of this dB scale * min: minimum dB value corresponding to digital 0 * step: the delta of dB value * mute: ? * * NOTE: The information is mostly for user-space consumption, to be viewed * alongwith amixer. */ /** * DONE * audmixer_rmix_tlv * * Range: * 0dB, -2.87dB, -6.02dB, -9.28dB, -12.04dB, -14.54dB, -18.06dB, -20.56dB * * This range is used for following controls * RMIX1_LVL, reg(0x0d), shift(0), width(3), invert(1), max(7) * RMIX2_LVL, reg(0x0d), shift(4), width(3), invert(1), max(7) * MIX1_LVL, reg(0x10), shift(0), width(3), invert(1), max(7) * MIX2_LVL, reg(0x10), shift(4), width(3), invert(1), max(7) * MIX3_LVL, reg(0x11), shift(0), width(3), invert(1), max(7) * MIX4_LVL, reg(0x11), shift(4), width(3), invert(1), max(7) */ static const unsigned int audmixer_mix_tlv[] = { TLV_DB_RANGE_HEAD(4), 0x0, 0x1, TLV_DB_SCALE_ITEM(0, 287, 0), 0x2, 0x3, TLV_DB_SCALE_ITEM(602, 326, 0), 0x4, 0x5, TLV_DB_SCALE_ITEM(1204, 250, 0), 0x6, 0x7, TLV_DB_SCALE_ITEM(1806, 250, 0), }; /** * audmixer_alc_ng_hys_tlv * * Range: 3dB to 12dB, step 3dB * * ALC_NG_HYS, reg(0x68), shift(6), width(2), invert(0), max(31) */ static const DECLARE_TLV_DB_SCALE(audmixer_alc_ng_hys_tlv, 300, 300, 0); /** * audmixer_alc_max_gain_tlv * * Range: * 0x6c to 0x9c => 0dB to 24dB, step 0.5dB * * ALC_MAX_GAIN, reg(0x69), shift(0), width(8), min(0x6c), max(0x9c) * ALC_START_GAIN_L, reg(0x71), shift(0), width(8), min(0x6c), max(0x9c) * ALC_START_GAIN_R, reg(0x72), shift(0), width(8), min(0x6c), max(0x9c) */ static const DECLARE_TLV_DB_SCALE(audmixer_alc_max_gain_tlv, 0, 50, 0); /** * audmixer_alc_min_gain_tlv * * Range: * 0x00 to 0x6c => -54dB to 0dB, step 0.5dB * * ALC_MIN_GAIN, reg(0x6a), shift(0), width(8), invert(0), max(0x6c) */ static const DECLARE_TLV_DB_SCALE(audmixer_alc_min_gain_tlv, -5400, 50, 0); /** * audmixer_alc_lvl_tlv * * Range: -48dB to 0, step 1.5dB * * ALC_LVL_L, reg(0x6b), shift(0), width(5), invert(0), max(31) * ALC_LVL_R, reg(0x6c), shift(0), width(5), invert(0), max(31) */ static const DECLARE_TLV_DB_SCALE(audmixer_alc_lvl_tlv, -4800, 150, 0); /** * audmixer_alc_ng_th_tlv * * Range: -76.5dB to -30dB, step 1.5dB * * ALCNGTH, reg(0x70), shift(0), width(5), invert(0), max(31) */ static const DECLARE_TLV_DB_SCALE(audmixer_alc_ng_th_tlv, -7650, 150, 0); /** * audmixer_alc_winsel * * ALC Window-Length Select */ static const char *audmixer_alc_winsel_text[] = { "fs600", "fs1200", "fs2400", "fs300" }; static SOC_ENUM_SINGLE_DECL(audmixer_alc_winsel_enum, AUDMIXER_REG_68_ALC_CTL, ALC_CTL_WINSEL_SHIFT, audmixer_alc_winsel_text); /** * audmixer_alc_mode * * ALC Function Select */ static const char *audmixer_alc_mode_text[] = { "Stereo", "Right", "Left", "Independent" }; static SOC_ENUM_SINGLE_DECL(audmixer_alc_mode_enum, AUDMIXER_REG_68_ALC_CTL, ALC_CTL_ALC_MODE_SHIFT, audmixer_alc_mode_text); /** * ALC Path selection */ static const char *audmixer_alc_path_sel_text[] = { "ADC", "Mixer" }; static const struct soc_enum audmixer_alc_path_sel_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_6D_ALC_HLD, ALC_HLD_ALC_PATH_SEL_SHIFT, ARRAY_SIZE(audmixer_alc_path_sel_text), audmixer_alc_path_sel_text); /** * audmixer_mpcm_srate * * Master PCM sample rate selection */ static const char *audmixer_mpcm_master_srate_text[] = { "8KHz", "16KHz", "24KHz", "32KHz" }; static const struct soc_enum audmixer_mpcm_srate1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_01_IN1_CTL1, INCTL1_MPCM_SRATE_SHIFT, ARRAY_SIZE(audmixer_mpcm_master_srate_text), audmixer_mpcm_master_srate_text); static const struct soc_enum audmixer_mpcm_srate2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_04_IN2_CTL1, INCTL1_MPCM_SRATE_SHIFT, ARRAY_SIZE(audmixer_mpcm_master_srate_text), audmixer_mpcm_master_srate_text); static const struct soc_enum audmixer_mpcm_srate3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_07_IN3_CTL1, INCTL1_MPCM_SRATE_SHIFT, ARRAY_SIZE(audmixer_mpcm_master_srate_text), audmixer_mpcm_master_srate_text); /** * mpcm_slot_sel * * Master PCM slot selection */ static const char *audmixer_mpcm_slot_text[] = { "1 slot", "2 slots", "3 slots", "4 slots" }; static const struct soc_enum audmixer_mpcm_slot1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_01_IN1_CTL1, INCTL1_MPCM_SLOT_SHIFT, ARRAY_SIZE(audmixer_mpcm_slot_text), audmixer_mpcm_slot_text); static const struct soc_enum audmixer_mpcm_slot2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_04_IN2_CTL1, INCTL1_MPCM_SLOT_SHIFT, ARRAY_SIZE(audmixer_mpcm_slot_text), audmixer_mpcm_slot_text); static const struct soc_enum audmixer_mpcm_slot3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_07_IN3_CTL1, INCTL1_MPCM_SLOT_SHIFT, ARRAY_SIZE(audmixer_mpcm_slot_text), audmixer_mpcm_slot_text); /** * bclk_pol * * Polarity of various bit-clocks */ static const char *audmixer_clock_pol_text[] = { "Normal", "Inverted" }; static const struct soc_enum audmixer_bck_pol1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_01_IN1_CTL1, INCTL1_BCK_POL_SHIFT, ARRAY_SIZE(audmixer_clock_pol_text), audmixer_clock_pol_text); static const struct soc_enum audmixer_bck_pol2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_04_IN2_CTL1, INCTL1_BCK_POL_SHIFT, ARRAY_SIZE(audmixer_clock_pol_text), audmixer_clock_pol_text); static const struct soc_enum audmixer_bck_pol3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_07_IN3_CTL1, INCTL1_BCK_POL_SHIFT, ARRAY_SIZE(audmixer_clock_pol_text), audmixer_clock_pol_text); static const struct soc_enum audmixer_lrck_pol1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_02_IN1_CTL2, INCTL2_LRCK_POL_SHIFT, ARRAY_SIZE(audmixer_clock_pol_text), audmixer_clock_pol_text); static const struct soc_enum audmixer_lrck_pol2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_05_IN2_CTL2, INCTL2_LRCK_POL_SHIFT, ARRAY_SIZE(audmixer_clock_pol_text), audmixer_clock_pol_text); static const struct soc_enum audmixer_lrck_pol3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_08_IN3_CTL2, INCTL2_LRCK_POL_SHIFT, ARRAY_SIZE(audmixer_clock_pol_text), audmixer_clock_pol_text); /** * i2s_pcm * * Input Audio Mode */ static const char *audmixer_i2s_pcm_text[] = { "I2S", "PCM" }; static const struct soc_enum audmixer_i2s_pcm1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_01_IN1_CTL1, INCTL1_I2S_PCM_SHIFT, ARRAY_SIZE(audmixer_i2s_pcm_text), audmixer_i2s_pcm_text); static const struct soc_enum audmixer_i2s_pcm2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_04_IN2_CTL1, INCTL1_I2S_PCM_SHIFT, ARRAY_SIZE(audmixer_i2s_pcm_text), audmixer_i2s_pcm_text); static const struct soc_enum audmixer_i2s_pcm3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_07_IN3_CTL1, INCTL1_I2S_PCM_SHIFT, ARRAY_SIZE(audmixer_i2s_pcm_text), audmixer_i2s_pcm_text); /** * i2s_xfs * * BCK vs LRCK condition */ static const char *audmixer_i2s_xfs_text[] = { "32fs", "48fs", "64fs", "64fs" }; static const struct soc_enum audmixer_i2s_xfs1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_02_IN1_CTL2, INCTL2_I2S_XFS_SHIFT, ARRAY_SIZE(audmixer_i2s_xfs_text), audmixer_i2s_xfs_text); static const struct soc_enum audmixer_i2s_xfs2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_05_IN2_CTL2, INCTL2_I2S_XFS_SHIFT, ARRAY_SIZE(audmixer_i2s_xfs_text), audmixer_i2s_xfs_text); static const struct soc_enum audmixer_i2s_xfs3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_08_IN3_CTL2, INCTL2_I2S_XFS_SHIFT, ARRAY_SIZE(audmixer_i2s_xfs_text), audmixer_i2s_xfs_text); /** * i2s_df * * I2S Data Format */ static const char *audmixer_i2s_df_text[] = { "I2S", "Left-Justified", "Right-Justified", "Invalid" }; static const struct soc_enum audmixer_i2s_df1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_02_IN1_CTL2, INCTL2_I2S_DF_SHIFT, ARRAY_SIZE(audmixer_i2s_df_text), audmixer_i2s_df_text); static const struct soc_enum audmixer_i2s_df2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_05_IN2_CTL2, INCTL2_I2S_DF_SHIFT, ARRAY_SIZE(audmixer_i2s_df_text), audmixer_i2s_df_text); static const struct soc_enum audmixer_i2s_df3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_08_IN3_CTL2, INCTL2_I2S_DF_SHIFT, ARRAY_SIZE(audmixer_i2s_df_text), audmixer_i2s_df_text); /** * i2s_dl * * I2S Data Length */ static const char *audmixer_i2s_dl_text[] = { "16-bit", "18-bit", "20-bit", "24-bit" }; static const struct soc_enum audmixer_i2s_dl1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_02_IN1_CTL2, INCTL2_I2S_DL_SHIFT, ARRAY_SIZE(audmixer_i2s_dl_text), audmixer_i2s_dl_text); static const struct soc_enum audmixer_i2s_dl2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_05_IN2_CTL2, INCTL2_I2S_DL_SHIFT, ARRAY_SIZE(audmixer_i2s_dl_text), audmixer_i2s_dl_text); static const struct soc_enum audmixer_i2s_dl3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_08_IN3_CTL2, INCTL2_I2S_DL_SHIFT, ARRAY_SIZE(audmixer_i2s_dl_text), audmixer_i2s_dl_text); /** * pcm_dad * * PCM Data Additional Delay */ static const char *audmixer_pcm_dad_text[] = { "1 bck", "0 bck", "2 bck", "", "3 bck", "", "4 bck", "" }; static const struct soc_enum audmixer_pcm_dad1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_03_IN1_CTL3, INCTL3_PCM_DAD_SHIFT, ARRAY_SIZE(audmixer_pcm_dad_text), audmixer_pcm_dad_text); static const struct soc_enum audmixer_pcm_dad2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_06_IN2_CTL3, INCTL3_PCM_DAD_SHIFT, ARRAY_SIZE(audmixer_pcm_dad_text), audmixer_pcm_dad_text); static const struct soc_enum audmixer_pcm_dad3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_09_IN3_CTL3, INCTL3_PCM_DAD_SHIFT, ARRAY_SIZE(audmixer_pcm_dad_text), audmixer_pcm_dad_text); /** * pcm_df * * PCM Data Format */ static const char *audmixer_pcm_df_text[] = { "", "", "", "", "Short Frame", "", "", "", "", "", "", "", "Long Frame" }; static const struct soc_enum audmixer_pcm_df1_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_03_IN1_CTL3, INCTL3_PCM_DF_SHIFT, ARRAY_SIZE(audmixer_pcm_df_text), audmixer_pcm_df_text); static const struct soc_enum audmixer_pcm_df2_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_06_IN2_CTL3, INCTL3_PCM_DF_SHIFT, ARRAY_SIZE(audmixer_pcm_df_text), audmixer_pcm_df_text); static const struct soc_enum audmixer_pcm_df3_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_09_IN3_CTL3, INCTL3_PCM_DF_SHIFT, ARRAY_SIZE(audmixer_pcm_df_text), audmixer_pcm_df_text); /** * in3_pol * * Polarity of internal bus for IN3 */ static const char *audmixer_in3_pol_text[] = { "Normal", "Inverted" }; static const struct soc_enum audmixer_in3_isync_pol_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_07_IN3_CTL1, INCTL1_ISYNC_POL_SHIFT, ARRAY_SIZE(audmixer_in3_pol_text), audmixer_in3_pol_text); static const struct soc_enum audmixer_in3_bt_osync_pol_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_07_IN3_CTL1, INCTL1_BT_OSYNC_POL_SHIFT, ARRAY_SIZE(audmixer_in3_pol_text), audmixer_in3_pol_text); /** * bck4_mode * * BCK4 Output Selection */ static const char *audmixer_bck4_mode_text[] = { "Normal BCK", "MCKO" }; static const struct soc_enum audmixer_bck4_mode_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0A_HQ_CTL, HQ_CTL_BCK4_MODE_SHIFT, ARRAY_SIZE(audmixer_bck4_mode_text), audmixer_bck4_mode_text); /** * dout_sel1 * * CH1 Digital Output Selection */ static const char *audmixer_dout_sel1_text[] = { "DMIX_OUT", "AIF4IN", "RMIX_OUT" }; static SOC_ENUM_SINGLE_DECL(audmixer_dout_sel1_enum, AUDMIXER_REG_16_DOUTMX1, DOUTMX1_DOUT_SEL1_SHIFT, audmixer_dout_sel1_text); /** * dout_sel2 * * CH2 Digital Output Selection */ static const char *audmixer_dout_sel2_text[] = { "DMIX_OUT", "AIF4IN", "AIF3IN" }; static SOC_ENUM_SINGLE_DECL(audmixer_dout_sel2_enum, AUDMIXER_REG_16_DOUTMX1, DOUTMX1_DOUT_SEL2_SHIFT, audmixer_dout_sel2_text); /** * dout_sel3 * * CH3 Digital Output Selection */ static const char *audmixer_dout_sel3_text[] = { "DMIX_OUT", "AIF4IN", "AIF2IN" }; static SOC_ENUM_SINGLE_DECL(audmixer_dout_sel3_enum, AUDMIXER_REG_17_DOUTMX2, DOUTMX2_DOUT_SEL3_SHIFT, audmixer_dout_sel3_text); static const char *audmixer_off_on_text[] = { "Off", "On" }; static const struct soc_enum audmixer_hq_en_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0A_HQ_CTL, HQ_CTL_HQ_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_ch3_rec_en_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0A_HQ_CTL, HQ_CTL_CH3_SEL_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_mcko_en_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0A_HQ_CTL, HQ_CTL_MCKO_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_rmix1_en_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0D_RMIX_CTL, RMIX_CTL_RMIX1_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_rmix2_en_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0D_RMIX_CTL, RMIX_CTL_RMIX2_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum mixer_ch1_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_10_DMIX1, DMIX1_MIX_EN1_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum mixer_ch2_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_10_DMIX1, DMIX1_MIX_EN2_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum mixer_ch3_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_11_DMIX2, DMIX2_MIX_EN3_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum mixer_ch4_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_11_DMIX2, DMIX2_MIX_EN4_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum mixer_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_MIX_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum src3_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_SRC3_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum src2_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_SRC2_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum src1_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_SRC1_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum ap0_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_AP0_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum ap1_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_AP1_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum cp1_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_0F_DIG_EN, DIG_EN_CP1_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum alc_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_68_ALC_CTL, ALC_CTL_ALC_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_alc_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_68_ALC_CTL, ALC_CTL_ALC_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_alc_limiter_mode_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_68_ALC_CTL, ALC_CTL_ALC_LIM_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_alc_start_gain_en_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_6D_ALC_HLD, ALC_HLD_ST_GAIN_EN_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); static const struct soc_enum audmixer_noise_gate_enable_enum = SOC_ENUM_SINGLE(AUDMIXER_REG_70_ALC_NG, ALC_NG_NGAT_SHIFT, ARRAY_SIZE(audmixer_off_on_text), audmixer_off_on_text); /* * audmixer_soc_kcontrol_handler: Provide runtime PM handling during the get/put * event handler of a specific control * * Since we don't have regcache support for this device, the get/put events will * access the hardware everytime the user-space accesses the device controls. If * the audio block is in suspend state, this might will result in OOPs because * the power-domain/clocks might be in off state. The best approach would be to * enable the device, execute the get/put function and then put the device in * suspended state. This function provides helpers for this purpose. * * Arguments * kcontrol/ucontrol: Passed to the framework API * func: The default framework API used to manage this event. */ static int audmixer_soc_kcontrol_handler(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, int (*func)(struct snd_kcontrol *, struct snd_ctl_elem_value *)) { return func(kcontrol, ucontrol); } /* Function to get TLV control value */ static int audmixer_snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return audmixer_soc_kcontrol_handler(kcontrol, ucontrol, snd_soc_get_volsw); } /* Function to set TLV control value */ static int audmixer_snd_soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return audmixer_soc_kcontrol_handler(kcontrol, ucontrol, snd_soc_put_volsw); } /* Function to get TLV control value */ static int audmixer_snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return audmixer_soc_kcontrol_handler(kcontrol, ucontrol, snd_soc_get_volsw_range); } /* Function to set TLV control value */ static int audmixer_snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return audmixer_soc_kcontrol_handler(kcontrol, ucontrol, snd_soc_put_volsw_range); } /* Function to get ENUM control value */ static int audmixer_soc_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return audmixer_soc_kcontrol_handler(kcontrol, ucontrol, snd_soc_get_enum_double); } /* Function to set ENUM control value */ static int audmixer_soc_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { return audmixer_soc_kcontrol_handler(kcontrol, ucontrol, snd_soc_put_enum_double); } /** * struct snd_kcontrol_new audmixer_snd_controls * * Every distinct bit-fields within the CODEC SFR range may be considered * as a control elements. Such control elements are defined here. * * Depending on the access mode of these registers, different macros are * used to define these control elements. * * SOC_ENUM: 1-to-1 mapping between bit-field value and provided text * SOC_SINGLE: Single register, value is a number * SOC_SINGLE_TLV: Single register, value corresponds to a TLV scale * SOC_SINGLE_TLV_EXT: Above + custom get/set operation for this value * SOC_SINGLE_RANGE_TLV: Register value is an offset from minimum value * SOC_DOUBLE: Two bit-fields are updated in a single register * SOC_DOUBLE_R: Two bit-fields in 2 different registers are updated */ static const struct snd_kcontrol_new audmixer_common_snd_controls[] = { SOC_SINGLE_EXT_TLV("RMIX1_LVL", AUDMIXER_REG_0D_RMIX_CTL, RMIX_CTL_RMIX2_LVL_SHIFT, BIT(RMIX_CTL_RMIX2_LVL_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_mix_tlv), SOC_SINGLE_EXT_TLV("RMIX2_LVL", AUDMIXER_REG_0D_RMIX_CTL, RMIX_CTL_RMIX1_LVL_SHIFT, BIT(RMIX_CTL_RMIX1_LVL_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_mix_tlv), SOC_SINGLE_EXT_TLV("MIX1_LVL", AUDMIXER_REG_10_DMIX1, DMIX1_MIX_LVL1_SHIFT, BIT(DMIX1_MIX_LVL1_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_mix_tlv), SOC_SINGLE_EXT_TLV("MIX2_LVL", AUDMIXER_REG_10_DMIX1, DMIX1_MIX_LVL2_SHIFT, BIT(DMIX1_MIX_LVL2_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_mix_tlv), SOC_SINGLE_EXT_TLV("MIX3_LVL", AUDMIXER_REG_11_DMIX2, DMIX2_MIX_LVL3_SHIFT, BIT(DMIX2_MIX_LVL3_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_mix_tlv), SOC_SINGLE_EXT_TLV("MIX4_LVL", AUDMIXER_REG_11_DMIX2, DMIX2_MIX_LVL4_SHIFT, BIT(DMIX2_MIX_LVL4_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_mix_tlv), SOC_ENUM_EXT("CH1 Master PCM Sample Rate", audmixer_mpcm_srate1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 Master PCM Sample Rate", audmixer_mpcm_srate2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 Master PCM Slot", audmixer_mpcm_slot1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 Master PCM Slot", audmixer_mpcm_slot2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 BCLK Polarity", audmixer_bck_pol1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 BCLK Polarity", audmixer_bck_pol2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 LRCLK Polarity", audmixer_lrck_pol1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 LRCLK Polarity", audmixer_lrck_pol2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 Input Audio Mode", audmixer_i2s_pcm1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 Input Audio Mode", audmixer_i2s_pcm2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 XFS", audmixer_i2s_xfs1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 XFS", audmixer_i2s_xfs2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 I2S Format", audmixer_i2s_df1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 I2S Format", audmixer_i2s_df2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 I2S Data Length", audmixer_i2s_dl1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 I2S Data Length", audmixer_i2s_dl2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 PCM DAD", audmixer_pcm_dad1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 PCM DAD", audmixer_pcm_dad2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 PCM Data Format", audmixer_pcm_df1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 PCM Data Format", audmixer_pcm_df2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 Rec En", audmixer_ch3_rec_en_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("MCKO En", audmixer_mcko_en_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("RMIX1 En", audmixer_rmix1_en_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("RMIX2 En", audmixer_rmix2_en_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("BCK4 Output Selection", audmixer_bck4_mode_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("HQ En", audmixer_hq_en_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 DOUT Select", audmixer_dout_sel1_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 DOUT Select", audmixer_dout_sel2_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 DOUT Select", audmixer_dout_sel3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH1 Mixer En", mixer_ch1_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH2 Mixer En", mixer_ch2_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 Mixer En", mixer_ch3_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH4 Mixer En", mixer_ch4_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("Mixer En", mixer_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("SRC2 En", src2_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("SRC3 En", src3_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), }; static const struct snd_kcontrol_new audmixer_external_in3_snd_controls[] = { SOC_ENUM_EXT("CH3 Master PCM Sample Rate", audmixer_mpcm_srate3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 Master PCM Slot", audmixer_mpcm_slot3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 BCLK Polarity", audmixer_bck_pol3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 LRCLK Polarity", audmixer_lrck_pol3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 Input Audio Mode", audmixer_i2s_pcm3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 XFS", audmixer_i2s_xfs3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 I2S Format", audmixer_i2s_df3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 I2S Data Length", audmixer_i2s_dl3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 PCM DAD", audmixer_pcm_dad3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 PCM Data Format", audmixer_pcm_df3_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), }; static const struct snd_kcontrol_new audmixer_internal_in3_snd_controls[] = { SOC_ENUM_EXT("CH3 ISYNC Polarity", audmixer_in3_isync_pol_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CH3 BT OSYNC Polarity", audmixer_in3_bt_osync_pol_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), }; static const struct snd_kcontrol_new audmixer_alc_snd_controls[] = { SOC_SINGLE_EXT_TLV("ALC NG HYS", AUDMIXER_REG_68_ALC_CTL, ALC_CTL_ALC_NG_HYS_SHIFT, BIT(ALC_CTL_ALC_NG_HYS_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_alc_ng_hys_tlv), SOC_SINGLE_RANGE_EXT_TLV("ALC Max Gain", AUDMIXER_REG_69_ALC_GA1, ALC_GA1_ALC_MAX_GAIN_SHIFT, ALC_GA1_ALC_MAX_GAIN_MINVAL, ALC_GA1_ALC_MAX_GAIN_MAXVAL, 0, audmixer_snd_soc_get_volsw_range, audmixer_snd_soc_put_volsw_range, audmixer_alc_max_gain_tlv), SOC_SINGLE_RANGE_EXT_TLV("ALC Min Gain", AUDMIXER_REG_6A_ALC_GA2, ALC_GA2_ALC_MIN_GAIN_SHIFT, ALC_GA2_ALC_MIN_GAIN_MINVAL, ALC_GA2_ALC_MIN_GAIN_MAXVAL, 0, audmixer_snd_soc_get_volsw_range, audmixer_snd_soc_put_volsw_range, audmixer_alc_min_gain_tlv), SOC_SINGLE_RANGE_EXT_TLV("ALC Start Gain Left", AUDMIXER_REG_71_ALC_SGL, ALC_SGL_START_GAIN_L_SHIFT, ALC_SGL_START_GAIN_L_MINVAL, ALC_SGL_START_GAIN_L_MAXVAL, 0, audmixer_snd_soc_get_volsw_range, audmixer_snd_soc_put_volsw_range, audmixer_alc_max_gain_tlv), SOC_SINGLE_RANGE_EXT_TLV("ALC Start Gain Right", AUDMIXER_REG_72_ALC_SGR, ALC_SGR_START_GAIN_R_SHIFT, ALC_SGR_START_GAIN_R_MINVAL, ALC_SGR_START_GAIN_R_MAXVAL, 0, audmixer_snd_soc_get_volsw_range, audmixer_snd_soc_put_volsw_range, audmixer_alc_max_gain_tlv), SOC_SINGLE_EXT_TLV("ALC LVL Left", AUDMIXER_REG_6B_ALC_LVL, ALC_LVL_LVL_SHIFT, BIT(ALC_LVL_LVL_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_alc_lvl_tlv), SOC_SINGLE_EXT_TLV("ALC LVL Right", AUDMIXER_REG_6C_ALC_LVR, ALC_LVR_LVL_SHIFT, BIT(ALC_LVR_LVL_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_alc_lvl_tlv), SOC_SINGLE_EXT_TLV("ALC Noise Gate Threshold", AUDMIXER_REG_70_ALC_NG, ALC_NG_ALCNGTH_SHIFT, BIT(ALC_NG_ALCNGTH_WIDTH) - 1, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw, audmixer_alc_ng_th_tlv), SOC_ENUM_EXT("ALC Window Length", audmixer_alc_winsel_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("ALC Mode", audmixer_alc_mode_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("ALC En", audmixer_alc_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("ALC Limiter Mode En", audmixer_alc_limiter_mode_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("ALC Start Gain En", audmixer_alc_start_gain_en_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_SINGLE_EXT("ALC Hold Time", AUDMIXER_REG_6D_ALC_HLD, ALC_HLD_HOLD_SHIFT, ALC_HLD_HOLD_MASK, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw), SOC_SINGLE_EXT("ALC Attack Time", AUDMIXER_REG_6E_ALC_ATK, ALC_ATK_ATTACK_SHIFT, ALC_ATK_ATTACK_MASK, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw), SOC_SINGLE_EXT("ALC Decay Time", AUDMIXER_REG_6F_ALC_DCY, ALC_DCY_DECAY_SHIFT, ALC_DCY_DECAY_MASK, 0, audmixer_snd_soc_get_volsw, audmixer_snd_soc_put_volsw), SOC_ENUM_EXT("ALC Path", audmixer_alc_path_sel_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("ALC Noise Gate En", audmixer_noise_gate_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), }; static const struct snd_kcontrol_new audmixer_src1_snd_controls[] = { SOC_ENUM_EXT("SRC1 En", src1_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), }; static const struct snd_kcontrol_new audmixer_splited_src1_snd_controls[] = { SOC_ENUM_EXT("AP0 En", ap0_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("AP1 En", ap1_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), SOC_ENUM_EXT("CP1 En", cp1_enable_enum, audmixer_soc_enum_get, audmixer_soc_enum_put), }; /** * audmixer_get_clk: Get clock references * * This function gets all the clock related to the audio i2smixer and stores in * mixer private structure. */ static int audmixer_get_clk(struct device *dev) { g_audmixer->clk_dout = devm_clk_get(dev, "audmixer_dout"); if (IS_ERR(g_audmixer->clk_dout)) { dev_err(dev, "audmixer_dout clk not found\n"); return PTR_ERR(g_audmixer->clk_dout); } if (g_audmixer->hw->bus == AUDMIXER_APB) { g_audmixer->aclk = devm_clk_get(dev, "audmixer_aclk"); if (IS_ERR(g_audmixer->aclk)) { dev_err(dev, "audmixer_aclk clk not found\n"); return PTR_ERR(g_audmixer->aclk); } } return 0; } /** * audmixer_clk_put: Reset clock references * * This function puts all the clock related to the audio i2smixer */ static void audmixer_clk_put(struct device *dev) { clk_put(g_audmixer->clk_dout); if (g_audmixer->hw->bus == AUDMIXER_APB) { clk_put(g_audmixer->aclk); } } /** * audmixer_clk_enable: Enables all audio mixer related clocks * * This function enables all the clock related to the audio i2smixer */ static void audmixer_clk_enable(struct device *dev) { /* TODO: Check if aclk can be enabled here */ clk_prepare_enable(g_audmixer->aclk); clk_prepare_enable(g_audmixer->clk_dout); } /** * audmixer_clk_disable: Disables all audio mixer related clocks * * This function disable all the clock related to the audio i2smixer */ static void audmixer_clk_disable(struct device *dev) { clk_disable_unprepare(g_audmixer->clk_dout); clk_disable_unprepare(g_audmixer->aclk); /* TODO: Check if aclk can be disabled here */ } /* * audmixer_update_soc_reg: Update SoC specific registers * * Audio Mixer requires a few SoC specific registers to be updated during its * operations. * PMU_ALIVE: To enable PMU specific bit for the Audio Mixer * SYS_RESET: The sysreg register to reset the Audio Mixer block * * @socreg: The structure holding information about the SoC specific register, * target bit and whether the bit should be 1 or 0 during Audio Mixer operation * @enable: When the Audio Mixer is getting enabled or disabled */ static void audmixer_update_soc_reg(struct audmixer_soc_reg_info *socreg, bool enable) { /* * If socreg->active_high is true, the specific bit should be set while * enabling and cleared while disabling. * If socreg->active_high is false, the specific bit should be cleared * while enabling and set while disabling. */ bool set_val = socreg->active_high ? enable : !enable; unsigned int val; if (socreg->reg) { val = readl(socreg->reg); if (set_val) val |= BIT(socreg->bit); else val &= ~BIT(socreg->bit); writel(val, socreg->reg); } } #ifdef CONFIG_PM_RUNTIME /** * audmixer_power_on: SoC specific powering on configurations * * This functions sets sysreg regsiters to enable Audio Mixer. */ static int audmixer_power_on(struct device *dev) { /* Audio mixer PMU ALIVE configuration */ audmixer_update_soc_reg(&g_audmixer->reg_alive, true); /* Audio mixer unreset */ audmixer_update_soc_reg(&g_audmixer->reg_reset, true); if (g_audmixer->hw->bus == AUDMIXER_I2C) { /*write Audio mixer i2c address */ if (g_audmixer->sysreg_i2c_id == NULL) { dev_err(dev, "sysreg_i2c_id registers not set\n"); return -ENXIO; } writel(g_audmixer->i2c_addr, g_audmixer->sysreg_i2c_id); } return 0; } #endif /** * audmixer_power_off: SoC specific power off configuration * * This functions resets sysreg regsiters to disable Audio Mixer. */ static void audmixer_power_off(struct device *dev) { /* Audio mixer unreset */ audmixer_update_soc_reg(&g_audmixer->reg_reset, false); /* Audio mixer PMU ALIVE configuration */ audmixer_update_soc_reg(&g_audmixer->reg_alive, false); } #define AUDMIXER_RATES SNDRV_PCM_RATE_8000_192000 #define AUDMIXER_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) static struct snd_soc_dai_driver audmixer_dais[] = {{ .name = "AP0", .id = AUDMIXER_IF_AP, .ops = &audmixer_dai_ops, .playback = { .stream_name = "AP0 Playback", .channels_min = 2, .channels_max = 2, .rate_min = 48000, .rate_max = 192000, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_192000, .formats = AUDMIXER_FORMATS, }, .capture = { .stream_name = "AP0 Capture", .channels_min = 2, .channels_max = 2, .rate_min = 48000, .rate_max = 192000, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_192000, .formats = AUDMIXER_FORMATS, }, .symmetric_rates = 1, .symmetric_channels = 1, .symmetric_samplebits = 1, },{ .name = "CP0", .id = AUDMIXER_IF_CP, .ops = &audmixer_dai_ops, .playback = { .stream_name = "CP0 Playback", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, .formats = AUDMIXER_FORMATS, }, .capture = { .stream_name = "CP0 Capture", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, .formats = AUDMIXER_FORMATS, }, .symmetric_rates = 1, .symmetric_channels = 1, .symmetric_samplebits = 1, },{ .name = "BT", .id = AUDMIXER_IF_BT, .ops = &audmixer_dai_ops, .playback = { .stream_name = "BT Playback", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT, .formats = AUDMIXER_FORMATS, }, .capture = { .stream_name = "BT Capture", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT, .formats = AUDMIXER_FORMATS, }, .symmetric_rates = 1, .symmetric_channels = 1, .symmetric_samplebits = 1, },{ .name = "FM", .id = AUDMIXER_IF_FM, .ops = &audmixer_dai_ops, .playback = { .stream_name = "FM Playback", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT, .formats = AUDMIXER_FORMATS, }, .capture = { .stream_name = "FM Capture", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT, .formats = AUDMIXER_FORMATS, }, .symmetric_rates = 1, .symmetric_channels = 1, .symmetric_samplebits = 1, },{ .name = "AMP", .id = AUDMIXER_IF_ADC, .capture = { .stream_name = "AMP Capture", .channels_min = 2, .channels_max = 2, .rate_min = 48000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_48000, .formats = AUDMIXER_FORMATS, }, },{ .name = "AP1", .id = AUDMIXER_IF_AP1, .ops = &audmixer_dai_ops, .capture = { .stream_name = "AP1 Capture", .channels_min = 2, .channels_max = 2, .rate_min = 48000, .rate_max = 48000, .rates = SNDRV_PCM_RATE_48000, .formats = AUDMIXER_FORMATS, }, },{ .name = "CP1", .id = AUDMIXER_IF_CP1, .ops = &audmixer_dai_ops, .capture = { .stream_name = "CP1 Capture", .channels_min = 2, .channels_max = 2, .rate_min = 8000, .rate_max = 16000, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats = AUDMIXER_FORMATS, }, }, }; /* * post_update_fw: Configurations after f/w update operations * * Audio Mixer driver supports updating its tuning registers through firmware. * After updating the tuning registers, this function is called to reinitilize * some of the configuration registers which might have been updated by the * firmware. This function is also called when the firmware could not be found * to set default values. */ static void post_update_fw(void *context) { struct snd_soc_codec *codec = (struct snd_soc_codec *)context; dev_dbg(codec->dev, "(*) %s\n", __func__); pm_runtime_get_sync(codec->dev); /* set ap path by defaut*/ audmixer_init_mixer(); pm_runtime_put_sync(codec->dev); } /* * audmixer_drvdata_init: Initialize device specific private data structure */ static void audmixer_drvdata_init(void) { int i; g_audmixer->is_alc_enabled = false; g_audmixer->aifrate = 0; g_audmixer->is_active = false; g_audmixer->is_regs_stored = false; atomic_set(&g_audmixer->num_active_stream, 0); for (i = 0; i < AUDMIXER_IF_COUNT; i++) atomic_set(&g_audmixer->use_count[i], 0); } /** * audmixer_parse_dt: Parse DT properties and update private data structure * * All of following properties are optional. * reg-alive: Details about PMU ALIVE register * reg-reset: Details about SYSREG RESET register * sysreg-i2c: Register offset where I2C ID needs to be updated (for SRC2801X) * samsung,lpass-subip: To bind this IP to LPASS * bck-mcko-mode: To provide codec MCLK in AIF4 bclk line * update-firmware: To enable update of tuning register through firmware */ static int audmixer_parse_dt(struct device *dev) { int ret; unsigned int sysreg; unsigned int res[3]; const struct of_device_id *match; match = of_match_device(audmixer_i2c_dt_ids, dev); if (!match) match = of_match_device(audmixer_apb_dt_ids, dev); if (!match){ dev_err(dev, "Device not found in device dt id list \n"); return -ENODEV; } g_audmixer->hw = match->data; /* 'pmureg-alive' specifies the ALIVE register for Audio Mixer * IP block. If not specified, it is taken care by LPASS block. */ ret = of_property_read_u32_array(dev->of_node, "reg-alive", res, 3); if (ret) { dev_info(dev, "Property 'reg-alive' not found\n"); g_audmixer->reg_alive.reg = NULL; } else { g_audmixer->reg_alive.reg = devm_ioremap(dev, res[0], 0x4); if (g_audmixer->reg_alive.reg == NULL) { dev_err(dev, "Cannot ioremap %x\n", res[0]); return -ENXIO; } g_audmixer->reg_alive.bit = res[1]; g_audmixer->reg_alive.active_high = !!res[2]; } /* 'sysreg-reset' specifies the reset register for Audio Mixer * IP block. If not specified, the reset is taken care by LPASS block. */ ret = of_property_read_u32_array(dev->of_node, "reg-reset", res, 3); if (ret) { dev_info(dev, "Property 'reg-reset' not found\n"); g_audmixer->reg_reset.reg = NULL; } else { g_audmixer->reg_reset.reg = devm_ioremap(dev, res[0], 0x4); if (g_audmixer->reg_reset.reg == NULL) { dev_err(dev, "Cannot ioremap %x\n", res[0]); return -ENXIO; } g_audmixer->reg_reset.bit = res[1]; g_audmixer->reg_reset.active_high = !!res[2]; } /* 'sysreg-i2c' specifies the system register on which the I2C slave * address of the Audio Mixer needs to be updated. This register is * valid only for SRC2801X. */ if (g_audmixer->hw->bus == AUDMIXER_I2C) { ret = of_property_read_u32(dev->of_node, "sysreg-i2c", &sysreg); if (ret) { dev_err(dev, "Property 'sysreg-i2c' not found\n"); return -ENOENT; } g_audmixer->sysreg_i2c_id = devm_ioremap(dev, sysreg, 0x4); if (g_audmixer->sysreg_i2c_id == NULL) { dev_err(dev, "Cannot ioremap %x\n", sysreg); return -ENXIO; } } /* 'samsung,lpass-subip' specifies binding with LPASS subsystem */ if (of_find_property(g_audmixer->dev->of_node, "samsung,lpass-subip", NULL)) lpass_register_subip(g_audmixer->dev, "audiomixer"); /* 'bck-mcko-mode' specifies that MCLK should be provided in BCK line */ if (of_find_property(g_audmixer->dev->of_node, "bck-mcko-mode", NULL)) g_audmixer->is_bck4_mcko_enabled = true; else g_audmixer->is_bck4_mcko_enabled = false; /* 'update-firmware' specifies that tuning values should be updated * through fimrware binary */ if (of_find_property(g_audmixer->dev->of_node, "update-firmware", NULL)) g_audmixer->update_fw = true; else g_audmixer->update_fw = false; return 0; } /* audmixer_initialize_regs: Initialize a list of registers during boot time * * When user-space tries to read the value of some controls, the values are read * from the regcache. If the regcache doesn't have a copy of the particualr * register, the value is read from the hardware. * * In case the device is suspended and cache-only mode is set, regmap read * returns an error code and the user-space is not able to read the hardware * value. This results in some contols not getting updated at the boot time. * * To fix this issue, it is ensured that the registers for which user-space * controls are defined are initialized at the boot time. This in turn updates * the regcache and there is no read error during control operation. */ static void audmixer_initialize_regs(void) { int i; for (i = 0; i < ARRAY_SIZE(audmixer_init_reg_list); i++) regmap_write(g_audmixer->regmap, audmixer_init_reg_list[i].reg, audmixer_init_reg_list[i].def); } /* * audmixer_probe: probe function for ALSA codec driver * * It does following. * 1. Parses the DT properties and updates device specific private data * structure * 2. Sets codec cache IO * 3. Gets pin-control handle (to be used to configure different pin-control * states) * 4. Gets clock handles * 5. Enables the codec, updates firmware if required, configures mixer as per * initial settings and then disables the codec. * * Returns 0 on success, otherwise an error code */ static int audmixer_probe(struct snd_soc_codec *codec) { int ret = 0; dev_dbg(codec->dev, "(*) %s\n", __func__); codec->dev = g_audmixer->dev; codec->control_data = g_audmixer->regmap; g_audmixer->codec = codec; /* Should we move this to bus-probe */ ret = audmixer_parse_dt(codec->dev); if (ret) { dev_err(codec->dev, "Failed to parse Device-Tree\n"); return ret; } /* Get pin-control handle */ g_audmixer->pinctrl = devm_pinctrl_get(codec->dev); if (IS_ERR(g_audmixer->pinctrl)) { dev_err(codec->dev, "Couldn't get pins (%li)\n", PTR_ERR(g_audmixer->pinctrl)); return PTR_ERR(g_audmixer->pinctrl); } /* Get Clock for Mixer */ ret = audmixer_get_clk(codec->dev); if (ret) { dev_err(codec->dev, "Failed to get clk for mixer\n"); return ret; } #ifndef CONFIG_PM_RUNTIME audmixer_clk_enable(codec->dev); #endif audmixer_cfg_gpio(codec->dev, "default"); /* Set auto-suspend delay and enable RPM support */ pm_runtime_enable(g_audmixer->dev); pm_runtime_use_autosuspend(g_audmixer->dev); pm_runtime_set_autosuspend_delay(g_audmixer->dev, AUDMIXER_RPM_SUSPEND_DELAY_MS); pm_runtime_get_sync(g_audmixer->dev); /* Find out whether we need the firmware update code for audio mixer as * it is not used in any of the products */ #ifdef SND_SOC_REGMAP_FIRMWARE if (g_audmixer->update_fw) exynos_regmap_update_fw(S2803X_FIRMWARE_NAME, codec->dev, g_audmixer->regmap, g_audmixer->i2c_addr, post_update_fw, codec, post_update_fw, codec); else #endif post_update_fw(codec); /* Update the default value of registers that are accessible from * user-space, as the regcache needs to have a copy of those registers * before they are read for the first time */ if (g_audmixer->hw->bus != AUDMIXER_APB) audmixer_initialize_regs(); pm_runtime_put_sync(g_audmixer->dev); switch (g_audmixer->hw->type) { case SRC2801X: case SRC2803X: snd_soc_add_codec_controls(codec, audmixer_external_in3_snd_controls, ARRAY_SIZE(audmixer_external_in3_snd_controls)); snd_soc_add_codec_controls(codec, audmixer_alc_snd_controls, ARRAY_SIZE(audmixer_alc_snd_controls)); snd_soc_add_codec_controls(codec, audmixer_src1_snd_controls, ARRAY_SIZE(audmixer_src1_snd_controls)); break; default: case SRC1402X: snd_soc_add_codec_controls(codec, audmixer_external_in3_snd_controls, ARRAY_SIZE(audmixer_external_in3_snd_controls)); snd_soc_add_codec_controls(codec, audmixer_alc_snd_controls, ARRAY_SIZE(audmixer_alc_snd_controls)); snd_soc_add_codec_controls(codec, audmixer_splited_src1_snd_controls, ARRAY_SIZE(audmixer_splited_src1_snd_controls)); break; case SRC1403X: snd_soc_add_codec_controls(codec, audmixer_internal_in3_snd_controls, ARRAY_SIZE(audmixer_internal_in3_snd_controls)); snd_soc_add_codec_controls(codec, audmixer_splited_src1_snd_controls, ARRAY_SIZE(audmixer_splited_src1_snd_controls)); break; } return 0; } /* * audmixer_remove: ALSA code driver removal settings */ static int audmixer_remove(struct snd_soc_codec *codec) { dev_dbg(codec->dev, "(*) %s\n", __func__); audmixer_clk_disable(codec->dev); audmixer_clk_put(codec->dev); audmixer_power_off(codec->dev); return 0; } /* Codec driver structure */ static struct snd_soc_codec_driver soc_codec_dev_audmixer = { .probe = audmixer_probe, .remove = audmixer_remove, .controls = audmixer_common_snd_controls, .num_controls = ARRAY_SIZE(audmixer_common_snd_controls), .idle_bias_off = true, .ignore_pmdown_time = true, }; /* * audmixer_allocate_drvdata: Called from bus probe function to allocation of * private data structure */ static int audmixer_allocate_drvdata(struct device *dev) { g_audmixer = devm_kzalloc(dev, sizeof(struct audmixer_priv), GFP_KERNEL); if (g_audmixer == NULL) { dev_err(dev, "Error allocating driver private data\n"); return -ENOMEM; } g_audmixer->dev = dev; audmixer_drvdata_init(); return 0; } /* * audmixer_apb_probe: Probe function for APB bus based Audio Mixer * * It does following. * 1. Allocates private data structure and links that to device structure. * 2. Prepares SFR base address. * 3. Prepares regmap handle. * 4. Registers codec driver * * Returns 0 on success, otherwise an error code. */ static int audmixer_apb_probe(struct platform_device *pdev) { int ret; struct resource *res; dev_dbg(&pdev->dev, "(*) %s\n", __func__); ret = audmixer_allocate_drvdata(&pdev->dev); if (ret) return ret; dev_set_drvdata(&pdev->dev, g_audmixer); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Unable to get SFR base addr\n"); return -ENXIO; } g_audmixer->regs = devm_ioremap_resource(&pdev->dev, res); if (!g_audmixer->regs) { dev_err(&pdev->dev, "SFR ioremap failed\n"); return -ENOMEM; } g_audmixer->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "audmixer_aclk", g_audmixer->regs, &audmixer_regmap_mmio); if (IS_ERR(g_audmixer->regmap)) { ret = PTR_ERR(g_audmixer->regmap); dev_err(&pdev->dev, "Failed to allocate regmap: %d\n", ret); return ret; } ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_audmixer, audmixer_dais, ARRAY_SIZE(audmixer_dais)); if (ret < 0) dev_err(&pdev->dev, "Failed to register codec: %d\n", ret); return ret; } /* audmixer_apb_remove: APB bus specific removal sequence */ static int audmixer_apb_remove(struct platform_device *pdev) { snd_soc_unregister_codec(&pdev->dev); return 0; } /* * audmixer_i2c_probe: Probe function for I2C bus based Audio Mixer * * It does following. * 1. Allocates private data structure and links that to device structure. * 3. Prepares regmap handle. * 4. Registers codec driver * * Returns 0 on success, otherwise an error code. */ static int audmixer_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { int ret; dev_dbg(&i2c->dev, "(*) %s\n", __func__); ret = audmixer_allocate_drvdata(&i2c->dev); if (ret) return ret; g_audmixer->i2c_addr = i2c->addr; i2c_set_clientdata(i2c, g_audmixer); g_audmixer->regmap = devm_regmap_init_i2c(i2c, &audmixer_regmap_i2c); if (IS_ERR(g_audmixer->regmap)) { ret = PTR_ERR(g_audmixer->regmap); dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); return ret; } ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_audmixer, audmixer_dais, ARRAY_SIZE(audmixer_dais)); if (ret < 0) dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); return ret; } /* audmixer_apb_remove: APB bus specific removal sequence */ static int audmixer_i2c_remove(struct i2c_client *client) { snd_soc_unregister_codec(&client->dev); return 0; } #ifdef CONFIG_PM_RUNTIME /* * audmixer_runtime_resume: Runtime resume handler for Audio Mixer * * It does following. * 1. Enables LPASS (as clocks/reset etc are controlled by LPASS) * 2. Configures SoC specific registers for power/reset * 3. Enables clocks * 4. Sets pins(GPIOs) to appropriates states * 5. Restores the h/w registers * 6. Marks the mixer as active (to be used indirectly by PM framework for * checking whether mixer is active) */ static int audmixer_runtime_resume(struct device *dev) { static unsigned int count; dev_dbg(dev, "(*) %s (count = %d)\n", __func__, ++count); lpass_get_sync(dev); audmixer_power_on(dev); audmixer_clk_enable(dev); audmixer_cfg_gpio(dev, "default"); /* sys reset audio mixer */ audmixer_reset_sys_data(); if (g_audmixer->is_regs_stored == true) audmixer_restore_regs(dev); g_audmixer->is_regs_stored = false; g_audmixer->is_active = true; return 0; } /* * audmixer_runtime_suspend: Runtime suspend handler for Audio Mixer * * It does following. * 1. Marks the mixer as inactive (to be used indirectly by PM framework for * checking whether mixer is active) * 2. Saves the h/w registers * 3. Sets pins(GPIOs) to appropriates states * 4. Disables clocks * 5. Configures SoC specific registers for power/reset * 6. Disables LPASS (LPASS is disabled only if it internal use count is zero) */ static int audmixer_runtime_suspend(struct device *dev) { static unsigned int count; dev_dbg(dev, "(*) %s (count = %d)\n", __func__, ++count); g_audmixer->is_active = false; audmixer_save_regs(dev); g_audmixer->is_regs_stored = true; audmixer_cfg_gpio(dev, "idle"); audmixer_clk_disable(dev); audmixer_power_off(dev); lpass_put_sync(dev); return 0; } /* * audmixer_get_sync: Exported function to enable the mixer * * The sound-card driver doesn't have the handle to the mixer, hence uses this * function to enable the mixer. * * TODO: Use the device structure reference in sound-card driver to * enable/disable the mixer. */ void audmixer_get_sync() { if (g_audmixer != NULL) pm_runtime_get_sync(g_audmixer->dev); } /* * audmixer_put_sync: Exported function to disable the mixer * * The sound-card driver doesn't have the handle to the mixer, hence uses this * function to disable the mixer. * * TODO: Use the device structure reference in sound-card driver to * enable/disable the mixer. */ void audmixer_put_sync() { if (g_audmixer != NULL) pm_runtime_put_sync(g_audmixer->dev); } #endif static const struct dev_pm_ops audmixer_pm = { SET_SYSTEM_SLEEP_PM_OPS( NULL, NULL ) #ifdef CONFIG_PM_RUNTIME SET_RUNTIME_PM_OPS( audmixer_runtime_suspend, audmixer_runtime_resume, NULL ) #endif }; static struct platform_driver audmixer_apb_driver = { .driver = { .name = "audmixer", .owner = THIS_MODULE, .pm = &audmixer_pm, .of_match_table = of_match_ptr(audmixer_apb_dt_ids), }, .probe = audmixer_apb_probe, .remove = audmixer_apb_remove, }; module_platform_driver(audmixer_apb_driver); static struct i2c_driver audmixer_i2c_driver = { .driver = { .name = "s2801x", .owner = THIS_MODULE, .pm = &audmixer_pm, .of_match_table = of_match_ptr(audmixer_i2c_dt_ids), }, .probe = audmixer_i2c_probe, .remove = audmixer_i2c_remove, .id_table = audmixer_i2c_id, }; module_i2c_driver(audmixer_i2c_driver); MODULE_DESCRIPTION("ASoC AudMixer driver"); MODULE_AUTHOR("Tushar Behera "); MODULE_AUTHOR("R Chandrasekar "); MODULE_AUTHOR("Dong-Gyun Go "); MODULE_LICENSE("GPL v2"); MODULE_FIRMWARE(S2803X_FIRMWARE_NAME);