Skip to content

Instantly share code, notes, and snippets.

@sgoedecke
Created March 8, 2022 02:17
Show Gist options
  • Save sgoedecke/556ac60530b65b856ac162531f608919 to your computer and use it in GitHub Desktop.
Save sgoedecke/556ac60530b65b856ac162531f608919 to your computer and use it in GitHub Desktop.
This gist exceeds the recommended number of files (~10). To access all files, please clone this gist.
// SPDX-License-Identifier: GPL-2.0-only
/*
* 88pm860x-codec.c -- 88PM860x ALSA SoC Audio Driver
*
* Copyright 2010 Marvell International Ltd.
* Author: Haojian Zhuang <haojian.zhuang@marvell.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/mfd/88pm860x.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <sound/initval.h>
#include <sound/jack.h>
#include <trace/events/asoc.h>
#include "88pm860x-codec.h"
#define MAX_NAME_LEN 20
#define REG_CACHE_SIZE 0x40
#define REG_CACHE_BASE 0xb0
/* Status Register 1 (0x01) */
#define REG_STATUS_1 0x01
#define MIC_STATUS (1 << 7)
#define HOOK_STATUS (1 << 6)
#define HEADSET_STATUS (1 << 5)
/* Mic Detection Register (0x37) */
#define REG_MIC_DET 0x37
#define CONTINUOUS_POLLING (3 << 1)
#define EN_MIC_DET (1 << 0)
#define MICDET_MASK 0x07
/* Headset Detection Register (0x38) */
#define REG_HS_DET 0x38
#define EN_HS_DET (1 << 0)
/* Misc2 Register (0x42) */
#define REG_MISC2 0x42
#define AUDIO_PLL (1 << 5)
#define AUDIO_SECTION_RESET (1 << 4)
#define AUDIO_SECTION_ON (1 << 3)
/* PCM Interface Register 2 (0xb1) */
#define PCM_INF2_BCLK (1 << 6) /* Bit clock polarity */
#define PCM_INF2_FS (1 << 5) /* Frame Sync polarity */
#define PCM_INF2_MASTER (1 << 4) /* Master / Slave */
#define PCM_INF2_18WL (1 << 3) /* 18 / 16 bits */
#define PCM_GENERAL_I2S 0
#define PCM_EXACT_I2S 1
#define PCM_LEFT_I2S 2
#define PCM_RIGHT_I2S 3
#define PCM_SHORT_FS 4
#define PCM_LONG_FS 5
#define PCM_MODE_MASK 7
/* I2S Interface Register 4 (0xbe) */
#define I2S_EQU_BYP (1 << 6)
/* DAC Offset Register (0xcb) */
#define DAC_MUTE (1 << 7)
#define MUTE_LEFT (1 << 6)
#define MUTE_RIGHT (1 << 2)
/* ADC Analog Register 1 (0xd0) */
#define REG_ADC_ANA_1 0xd0
#define MIC1BIAS_MASK 0x60
/* Earpiece/Speaker Control Register 2 (0xda) */
#define REG_EAR2 0xda
#define RSYNC_CHANGE (1 << 2)
/* Audio Supplies Register 2 (0xdc) */
#define REG_SUPPLIES2 0xdc
#define LDO15_READY (1 << 4)
#define LDO15_EN (1 << 3)
#define CPUMP_READY (1 << 2)
#define CPUMP_EN (1 << 1)
#define AUDIO_EN (1 << 0)
#define SUPPLY_MASK (LDO15_EN | CPUMP_EN | AUDIO_EN)
/* Audio Enable Register 1 (0xdd) */
#define ADC_MOD_RIGHT (1 << 1)
#define ADC_MOD_LEFT (1 << 0)
/* Audio Enable Register 2 (0xde) */
#define ADC_LEFT (1 << 5)
#define ADC_RIGHT (1 << 4)
/* DAC Enable Register 2 (0xe1) */
#define DAC_LEFT (1 << 5)
#define DAC_RIGHT (1 << 4)
#define MODULATOR (1 << 3)
/* Shorts Register (0xeb) */
#define REG_SHORTS 0xeb
#define CLR_SHORT_LO2 (1 << 7)
#define SHORT_LO2 (1 << 6)
#define CLR_SHORT_LO1 (1 << 5)
#define SHORT_LO1 (1 << 4)
#define CLR_SHORT_HS2 (1 << 3)
#define SHORT_HS2 (1 << 2)
#define CLR_SHORT_HS1 (1 << 1)
#define SHORT_HS1 (1 << 0)
/*
* This widget should be just after DAC & PGA in DAPM power-on sequence and
* before DAC & PGA in DAPM power-off sequence.
*/
#define PM860X_DAPM_OUTPUT(wname, wevent) \
SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, 0, 0, NULL, 0, wevent, \
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD)
struct pm860x_det {
struct snd_soc_jack *hp_jack;
struct snd_soc_jack *mic_jack;
int hp_det;
int mic_det;
int hook_det;
int hs_shrt;
int lo_shrt;
};
struct pm860x_priv {
unsigned int sysclk;
unsigned int pcmclk;
unsigned int dir;
unsigned int filter;
struct snd_soc_component *component;
struct i2c_client *i2c;
struct regmap *regmap;
struct pm860x_chip *chip;
struct pm860x_det det;
int irq[4];
unsigned char name[4][MAX_NAME_LEN+1];
};
/* -9450dB to 0dB in 150dB steps ( mute instead of -9450dB) */
static const DECLARE_TLV_DB_SCALE(dpga_tlv, -9450, 150, 1);
/* -9dB to 0db in 3dB steps */
static const DECLARE_TLV_DB_SCALE(adc_tlv, -900, 300, 0);
/* {-23, -17, -13.5, -11, -9, -6, -3, 0}dB */
static const DECLARE_TLV_DB_RANGE(mic_tlv,
0, 0, TLV_DB_SCALE_ITEM(-2300, 0, 0),
1, 1, TLV_DB_SCALE_ITEM(-1700, 0, 0),
2, 2, TLV_DB_SCALE_ITEM(-1350, 0, 0),
3, 3, TLV_DB_SCALE_ITEM(-1100, 0, 0),
4, 7, TLV_DB_SCALE_ITEM(-900, 300, 0)
);
/* {0, 0, 0, -6, 0, 6, 12, 18}dB */
static const DECLARE_TLV_DB_RANGE(aux_tlv,
0, 2, TLV_DB_SCALE_ITEM(0, 0, 0),
3, 7, TLV_DB_SCALE_ITEM(-600, 600, 0)
);
/* {-16, -13, -10, -7, -5.2, -3,3, -2.2, 0}dB, mute instead of -16dB */
static const DECLARE_TLV_DB_RANGE(out_tlv,
0, 3, TLV_DB_SCALE_ITEM(-1600, 300, 1),
4, 4, TLV_DB_SCALE_ITEM(-520, 0, 0),
5, 5, TLV_DB_SCALE_ITEM(-330, 0, 0),
6, 7, TLV_DB_SCALE_ITEM(-220, 220, 0)
);
static const DECLARE_TLV_DB_RANGE(st_tlv,
0, 1, TLV_DB_SCALE_ITEM(-12041, 602, 0),
2, 3, TLV_DB_SCALE_ITEM(-11087, 250, 0),
4, 5, TLV_DB_SCALE_ITEM(-10643, 158, 0),
6, 7, TLV_DB_SCALE_ITEM(-10351, 116, 0),
8, 9, TLV_DB_SCALE_ITEM(-10133, 92, 0),
10, 13, TLV_DB_SCALE_ITEM(-9958, 70, 0),
14, 17, TLV_DB_SCALE_ITEM(-9689, 53, 0),
18, 271, TLV_DB_SCALE_ITEM(-9484, 37, 0)
);
/* Sidetone Gain = M * 2^(-5-N) */
struct st_gain {
unsigned int db;
unsigned int m;
unsigned int n;
};
static struct st_gain st_table[] = {
{-12041, 1, 15}, {-11439, 1, 14}, {-11087, 3, 15}, {-10837, 1, 13},
{-10643, 5, 15}, {-10485, 3, 14}, {-10351, 7, 15}, {-10235, 1, 12},
{-10133, 9, 15}, {-10041, 5, 14}, { -9958, 11, 15}, { -9883, 3, 13},
{ -9813, 13, 15}, { -9749, 7, 14}, { -9689, 15, 15}, { -9633, 1, 11},
{ -9580, 17, 15}, { -9531, 9, 14}, { -9484, 19, 15}, { -9439, 5, 13},
{ -9397, 21, 15}, { -9356, 11, 14}, { -9318, 23, 15}, { -9281, 3, 12},
{ -9245, 25, 15}, { -9211, 13, 14}, { -9178, 27, 15}, { -9147, 7, 13},
{ -9116, 29, 15}, { -9087, 15, 14}, { -9058, 31, 15}, { -9031, 1, 10},
{ -8978, 17, 14}, { -8929, 9, 13}, { -8882, 19, 14}, { -8837, 5, 12},
{ -8795, 21, 14}, { -8754, 11, 13}, { -8716, 23, 14}, { -8679, 3, 11},
{ -8643, 25, 14}, { -8609, 13, 13}, { -8576, 27, 14}, { -8545, 7, 12},
{ -8514, 29, 14}, { -8485, 15, 13}, { -8456, 31, 14}, { -8429, 1, 9},
{ -8376, 17, 13}, { -8327, 9, 12}, { -8280, 19, 13}, { -8235, 5, 11},
{ -8193, 21, 13}, { -8152, 11, 12}, { -8114, 23, 13}, { -8077, 3, 10},
{ -8041, 25, 13}, { -8007, 13, 12}, { -7974, 27, 13}, { -7943, 7, 11},
{ -7912, 29, 13}, { -7883, 15, 12}, { -7854, 31, 13}, { -7827, 1, 8},
{ -7774, 17, 12}, { -7724, 9, 11}, { -7678, 19, 12}, { -7633, 5, 10},
{ -7591, 21, 12}, { -7550, 11, 11}, { -7512, 23, 12}, { -7475, 3, 9},
{ -7439, 25, 12}, { -7405, 13, 11}, { -7372, 27, 12}, { -7341, 7, 10},
{ -7310, 29, 12}, { -7281, 15, 11}, { -7252, 31, 12}, { -7225, 1, 7},
{ -7172, 17, 11}, { -7122, 9, 10}, { -7075, 19, 11}, { -7031, 5, 9},
{ -6989, 21, 11}, { -6948, 11, 10}, { -6910, 23, 11}, { -6873, 3, 8},
{ -6837, 25, 11}, { -6803, 13, 10}, { -6770, 27, 11}, { -6739, 7, 9},
{ -6708, 29, 11}, { -6679, 15, 10}, { -6650, 31, 11}, { -6623, 1, 6},
{ -6570, 17, 10}, { -6520, 9, 9}, { -6473, 19, 10}, { -6429, 5, 8},
{ -6386, 21, 10}, { -6346, 11, 9}, { -6307, 23, 10}, { -6270, 3, 7},
{ -6235, 25, 10}, { -6201, 13, 9}, { -6168, 27, 10}, { -6137, 7, 8},
{ -6106, 29, 10}, { -6077, 15, 9}, { -6048, 31, 10}, { -6021, 1, 5},
{ -5968, 17, 9}, { -5918, 9, 8}, { -5871, 19, 9}, { -5827, 5, 7},
{ -5784, 21, 9}, { -5744, 11, 8}, { -5705, 23, 9}, { -5668, 3, 6},
{ -5633, 25, 9}, { -5599, 13, 8}, { -5566, 27, 9}, { -5535, 7, 7},
{ -5504, 29, 9}, { -5475, 15, 8}, { -5446, 31, 9}, { -5419, 1, 4},
{ -5366, 17, 8}, { -5316, 9, 7}, { -5269, 19, 8}, { -5225, 5, 6},
{ -5182, 21, 8}, { -5142, 11, 7}, { -5103, 23, 8}, { -5066, 3, 5},
{ -5031, 25, 8}, { -4997, 13, 7}, { -4964, 27, 8}, { -4932, 7, 6},
{ -4902, 29, 8}, { -4873, 15, 7}, { -4844, 31, 8}, { -4816, 1, 3},
{ -4764, 17, 7}, { -4714, 9, 6}, { -4667, 19, 7}, { -4623, 5, 5},
{ -4580, 21, 7}, { -4540, 11, 6}, { -4501, 23, 7}, { -4464, 3, 4},
{ -4429, 25, 7}, { -4395, 13, 6}, { -4362, 27, 7}, { -4330, 7, 5},
{ -4300, 29, 7}, { -4270, 15, 6}, { -4242, 31, 7}, { -4214, 1, 2},
{ -4162, 17, 6}, { -4112, 9, 5}, { -4065, 19, 6}, { -4021, 5, 4},
{ -3978, 21, 6}, { -3938, 11, 5}, { -3899, 23, 6}, { -3862, 3, 3},
{ -3827, 25, 6}, { -3793, 13, 5}, { -3760, 27, 6}, { -3728, 7, 4},
{ -3698, 29, 6}, { -3668, 15, 5}, { -3640, 31, 6}, { -3612, 1, 1},
{ -3560, 17, 5}, { -3510, 9, 4}, { -3463, 19, 5}, { -3419, 5, 3},
{ -3376, 21, 5}, { -3336, 11, 4}, { -3297, 23, 5}, { -3260, 3, 2},
{ -3225, 25, 5}, { -3191, 13, 4}, { -3158, 27, 5}, { -3126, 7, 3},
{ -3096, 29, 5}, { -3066, 15, 4}, { -3038, 31, 5}, { -3010, 1, 0},
{ -2958, 17, 4}, { -2908, 9, 3}, { -2861, 19, 4}, { -2816, 5, 2},
{ -2774, 21, 4}, { -2734, 11, 3}, { -2695, 23, 4}, { -2658, 3, 1},
{ -2623, 25, 4}, { -2589, 13, 3}, { -2556, 27, 4}, { -2524, 7, 2},
{ -2494, 29, 4}, { -2464, 15, 3}, { -2436, 31, 4}, { -2408, 2, 0},
{ -2356, 17, 3}, { -2306, 9, 2}, { -2259, 19, 3}, { -2214, 5, 1},
{ -2172, 21, 3}, { -2132, 11, 2}, { -2093, 23, 3}, { -2056, 3, 0},
{ -2021, 25, 3}, { -1987, 13, 2}, { -1954, 27, 3}, { -1922, 7, 1},
{ -1892, 29, 3}, { -1862, 15, 2}, { -1834, 31, 3}, { -1806, 4, 0},
{ -1754, 17, 2}, { -1704, 9, 1}, { -1657, 19, 2}, { -1612, 5, 0},
{ -1570, 21, 2}, { -1530, 11, 1}, { -1491, 23, 2}, { -1454, 6, 0},
{ -1419, 25, 2}, { -1384, 13, 1}, { -1352, 27, 2}, { -1320, 7, 0},
{ -1290, 29, 2}, { -1260, 15, 1}, { -1232, 31, 2}, { -1204, 8, 0},
{ -1151, 17, 1}, { -1102, 9, 0}, { -1055, 19, 1}, { -1010, 10, 0},
{ -968, 21, 1}, { -928, 11, 0}, { -889, 23, 1}, { -852, 12, 0},
{ -816, 25, 1}, { -782, 13, 0}, { -750, 27, 1}, { -718, 14, 0},
{ -688, 29, 1}, { -658, 15, 0}, { -630, 31, 1}, { -602, 16, 0},
{ -549, 17, 0}, { -500, 18, 0}, { -453, 19, 0}, { -408, 20, 0},
{ -366, 21, 0}, { -325, 22, 0}, { -287, 23, 0}, { -250, 24, 0},
{ -214, 25, 0}, { -180, 26, 0}, { -148, 27, 0}, { -116, 28, 0},
{ -86, 29, 0}, { -56, 30, 0}, { -28, 31, 0}, { 0, 0, 0},
};
static int snd_soc_get_volsw_2r_st(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
int val[2], val2[2], i;
val[0] = snd_soc_component_read(component, reg) & 0x3f;
val[1] = (snd_soc_component_read(component, PM860X_SIDETONE_SHIFT) >> 4) & 0xf;
val2[0] = snd_soc_component_read(component, reg2) & 0x3f;
val2[1] = (snd_soc_component_read(component, PM860X_SIDETONE_SHIFT)) & 0xf;
for (i = 0; i < ARRAY_SIZE(st_table); i++) {
if ((st_table[i].m == val[0]) && (st_table[i].n == val[1]))
ucontrol->value.integer.value[0] = i;
if ((st_table[i].m == val2[0]) && (st_table[i].n == val2[1]))
ucontrol->value.integer.value[1] = i;
}
return 0;
}
static int snd_soc_put_volsw_2r_st(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
int err;
unsigned int val, val2;
val = ucontrol->value.integer.value[0];
val2 = ucontrol->value.integer.value[1];
if (val >= ARRAY_SIZE(st_table) || val2 >= ARRAY_SIZE(st_table))
return -EINVAL;
err = snd_soc_component_update_bits(component, reg, 0x3f, st_table[val].m);
if (err < 0)
return err;
err = snd_soc_component_update_bits(component, PM860X_SIDETONE_SHIFT, 0xf0,
st_table[val].n << 4);
if (err < 0)
return err;
err = snd_soc_component_update_bits(component, reg2, 0x3f, st_table[val2].m);
if (err < 0)
return err;
err = snd_soc_component_update_bits(component, PM860X_SIDETONE_SHIFT, 0x0f,
st_table[val2].n);
return err;
}
static int snd_soc_get_volsw_2r_out(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
unsigned int shift = mc->shift;
int max = mc->max, val, val2;
unsigned int mask = (1 << fls(max)) - 1;
val = snd_soc_component_read(component, reg) >> shift;
val2 = snd_soc_component_read(component, reg2) >> shift;
ucontrol->value.integer.value[0] = (max - val) & mask;
ucontrol->value.integer.value[1] = (max - val2) & mask;
return 0;
}
static int snd_soc_put_volsw_2r_out(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
unsigned int shift = mc->shift;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
int err;
unsigned int val, val2, val_mask;
val_mask = mask << shift;
val = ((max - ucontrol->value.integer.value[0]) & mask);
val2 = ((max - ucontrol->value.integer.value[1]) & mask);
val = val << shift;
val2 = val2 << shift;
err = snd_soc_component_update_bits(component, reg, val_mask, val);
if (err < 0)
return err;
err = snd_soc_component_update_bits(component, reg2, val_mask, val2);
return err;
}
/* DAPM Widget Events */
/*
* A lot registers are belong to RSYNC domain. It requires enabling RSYNC bit
* after updating these registers. Otherwise, these updated registers won't
* be effective.
*/
static int pm860x_rsync_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
/*
* In order to avoid current on the load, mute power-on and power-off
* should be transients.
* Unmute by DAC_MUTE. It should be unmuted when DAPM sequence is
* finished.
*/
snd_soc_component_update_bits(component, PM860X_DAC_OFFSET, DAC_MUTE, 0);
snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2,
RSYNC_CHANGE, RSYNC_CHANGE);
return 0;
}
static int pm860x_dac_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
unsigned int dac = 0;
int data;
if (!strcmp(w->name, "Left DAC"))
dac = DAC_LEFT;
if (!strcmp(w->name, "Right DAC"))
dac = DAC_RIGHT;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
if (dac) {
/* Auto mute in power-on sequence. */
dac |= MODULATOR;
snd_soc_component_update_bits(component, PM860X_DAC_OFFSET,
DAC_MUTE, DAC_MUTE);
snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2,
RSYNC_CHANGE, RSYNC_CHANGE);
/* update dac */
snd_soc_component_update_bits(component, PM860X_DAC_EN_2,
dac, dac);
}
break;
case SND_SOC_DAPM_PRE_PMD:
if (dac) {
/* Auto mute in power-off sequence. */
snd_soc_component_update_bits(component, PM860X_DAC_OFFSET,
DAC_MUTE, DAC_MUTE);
snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2,
RSYNC_CHANGE, RSYNC_CHANGE);
/* update dac */
data = snd_soc_component_read(component, PM860X_DAC_EN_2);
data &= ~dac;
if (!(data & (DAC_LEFT | DAC_RIGHT)))
data &= ~MODULATOR;
snd_soc_component_write(component, PM860X_DAC_EN_2, data);
}
break;
}
return 0;
}
static const char *pm860x_opamp_texts[] = {"-50%", "-25%", "0%", "75%"};
static const char *pm860x_pa_texts[] = {"-33%", "0%", "33%", "66%"};
static SOC_ENUM_SINGLE_DECL(pm860x_hs1_opamp_enum,
PM860X_HS1_CTRL, 5, pm860x_opamp_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_hs2_opamp_enum,
PM860X_HS2_CTRL, 5, pm860x_opamp_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_hs1_pa_enum,
PM860X_HS1_CTRL, 3, pm860x_pa_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_hs2_pa_enum,
PM860X_HS2_CTRL, 3, pm860x_pa_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_lo1_opamp_enum,
PM860X_LO1_CTRL, 5, pm860x_opamp_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_lo2_opamp_enum,
PM860X_LO2_CTRL, 5, pm860x_opamp_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_lo1_pa_enum,
PM860X_LO1_CTRL, 3, pm860x_pa_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_lo2_pa_enum,
PM860X_LO2_CTRL, 3, pm860x_pa_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_spk_pa_enum,
PM860X_EAR_CTRL_1, 5, pm860x_pa_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_ear_pa_enum,
PM860X_EAR_CTRL_2, 0, pm860x_pa_texts);
static SOC_ENUM_SINGLE_DECL(pm860x_spk_ear_opamp_enum,
PM860X_EAR_CTRL_1, 3, pm860x_opamp_texts);
static const struct snd_kcontrol_new pm860x_snd_controls[] = {
SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2,
PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv),
SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0,
aux_tlv),
SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0,
mic_tlv),
SOC_SINGLE_TLV("MIC3 Capture Volume", PM860X_ADC_ANA_2, 3, 7, 0,
mic_tlv),
SOC_DOUBLE_R_EXT_TLV("Sidetone Volume", PM860X_SIDETONE_L_GAIN,
PM860X_SIDETONE_R_GAIN, 0, ARRAY_SIZE(st_table)-1,
0, snd_soc_get_volsw_2r_st,
snd_soc_put_volsw_2r_st, st_tlv),
SOC_SINGLE_TLV("Speaker Playback Volume", PM860X_EAR_CTRL_1,
0, 7, 0, out_tlv),
SOC_DOUBLE_R_TLV("Line Playback Volume", PM860X_LO1_CTRL,
PM860X_LO2_CTRL, 0, 7, 0, out_tlv),
SOC_DOUBLE_R_TLV("Headset Playback Volume", PM860X_HS1_CTRL,
PM860X_HS2_CTRL, 0, 7, 0, out_tlv),
SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume",
PM860X_HIFIL_GAIN_LEFT,
PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
SOC_DOUBLE_R_EXT_TLV("Hifi Right Playback Volume",
PM860X_HIFIR_GAIN_LEFT,
PM860X_HIFIR_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
SOC_DOUBLE_R_EXT_TLV("Lofi Playback Volume", PM860X_LOFI_GAIN_LEFT,
PM860X_LOFI_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
SOC_ENUM("Headset1 Operational Amplifier Current",
pm860x_hs1_opamp_enum),
SOC_ENUM("Headset2 Operational Amplifier Current",
pm860x_hs2_opamp_enum),
SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum),
SOC_ENUM("Headset2 Amplifier Current", pm860x_hs2_pa_enum),
SOC_ENUM("Lineout1 Operational Amplifier Current",
pm860x_lo1_opamp_enum),
SOC_ENUM("Lineout2 Operational Amplifier Current",
pm860x_lo2_opamp_enum),
SOC_ENUM("Lineout1 Amplifier Current", pm860x_lo1_pa_enum),
SOC_ENUM("Lineout2 Amplifier Current", pm860x_lo2_pa_enum),
SOC_ENUM("Speaker Operational Amplifier Current",
pm860x_spk_ear_opamp_enum),
SOC_ENUM("Speaker Amplifier Current", pm860x_spk_pa_enum),
SOC_ENUM("Earpiece Amplifier Current", pm860x_ear_pa_enum),
};
/*
* DAPM Controls
*/
/* AUX1 Switch */
static const struct snd_kcontrol_new aux1_switch_controls =
SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 4, 1, 0);
/* AUX2 Switch */
static const struct snd_kcontrol_new aux2_switch_controls =
SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 5, 1, 0);
/* Left Ex. PA Switch */
static const struct snd_kcontrol_new lepa_switch_controls =
SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 2, 1, 0);
/* Right Ex. PA Switch */
static const struct snd_kcontrol_new repa_switch_controls =
SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 1, 1, 0);
/* I2S Mux / Mux9 */
static const char *i2s_din_text[] = {
"DIN", "DIN1",
};
static SOC_ENUM_SINGLE_DECL(i2s_din_enum,
PM860X_I2S_IFACE_3, 1, i2s_din_text);
static const struct snd_kcontrol_new i2s_din_mux =
SOC_DAPM_ENUM("I2S DIN Mux", i2s_din_enum);
/* I2S Mic Mux / Mux8 */
static const char *i2s_mic_text[] = {
"Ex PA", "ADC",
};
static SOC_ENUM_SINGLE_DECL(i2s_mic_enum,
PM860X_I2S_IFACE_3, 4, i2s_mic_text);
static const struct snd_kcontrol_new i2s_mic_mux =
SOC_DAPM_ENUM("I2S Mic Mux", i2s_mic_enum);
/* ADCL Mux / Mux2 */
static const char *adcl_text[] = {
"ADCR", "ADCL",
};
static SOC_ENUM_SINGLE_DECL(adcl_enum,
PM860X_PCM_IFACE_3, 4, adcl_text);
static const struct snd_kcontrol_new adcl_mux =
SOC_DAPM_ENUM("ADC Left Mux", adcl_enum);
/* ADCR Mux / Mux3 */
static const char *adcr_text[] = {
"ADCL", "ADCR",
};
static SOC_ENUM_SINGLE_DECL(adcr_enum,
PM860X_PCM_IFACE_3, 2, adcr_text);
static const struct snd_kcontrol_new adcr_mux =
SOC_DAPM_ENUM("ADC Right Mux", adcr_enum);
/* ADCR EC Mux / Mux6 */
static const char *adcr_ec_text[] = {
"ADCR", "EC",
};
static SOC_ENUM_SINGLE_DECL(adcr_ec_enum,
PM860X_ADC_EN_2, 3, adcr_ec_text);
static const struct snd_kcontrol_new adcr_ec_mux =
SOC_DAPM_ENUM("ADCR EC Mux", adcr_ec_enum);
/* EC Mux / Mux4 */
static const char *ec_text[] = {
"Left", "Right", "Left + Right",
};
static SOC_ENUM_SINGLE_DECL(ec_enum,
PM860X_EC_PATH, 1, ec_text);
static const struct snd_kcontrol_new ec_mux =
SOC_DAPM_ENUM("EC Mux", ec_enum);
static const char *dac_text[] = {
"No input", "Right", "Left", "No input",
};
/* DAC Headset 1 Mux / Mux10 */
static SOC_ENUM_SINGLE_DECL(dac_hs1_enum,
PM860X_ANA_INPUT_SEL_1, 0, dac_text);
static const struct snd_kcontrol_new dac_hs1_mux =
SOC_DAPM_ENUM("DAC HS1 Mux", dac_hs1_enum);
/* DAC Headset 2 Mux / Mux11 */
static SOC_ENUM_SINGLE_DECL(dac_hs2_enum,
PM860X_ANA_INPUT_SEL_1, 2, dac_text);
static const struct snd_kcontrol_new dac_hs2_mux =
SOC_DAPM_ENUM("DAC HS2 Mux", dac_hs2_enum);
/* DAC Lineout 1 Mux / Mux12 */
static SOC_ENUM_SINGLE_DECL(dac_lo1_enum,
PM860X_ANA_INPUT_SEL_1, 4, dac_text);
static const struct snd_kcontrol_new dac_lo1_mux =
SOC_DAPM_ENUM("DAC LO1 Mux", dac_lo1_enum);
/* DAC Lineout 2 Mux / Mux13 */
static SOC_ENUM_SINGLE_DECL(dac_lo2_enum,
PM860X_ANA_INPUT_SEL_1, 6, dac_text);
static const struct snd_kcontrol_new dac_lo2_mux =
SOC_DAPM_ENUM("DAC LO2 Mux", dac_lo2_enum);
/* DAC Spearker Earphone Mux / Mux14 */
static SOC_ENUM_SINGLE_DECL(dac_spk_ear_enum,
PM860X_ANA_INPUT_SEL_2, 0, dac_text);
static const struct snd_kcontrol_new dac_spk_ear_mux =
SOC_DAPM_ENUM("DAC SP Mux", dac_spk_ear_enum);
/* Headset 1 Mux / Mux15 */
static const char *in_text[] = {
"Digital", "Analog",
};
static SOC_ENUM_SINGLE_DECL(hs1_enum,
PM860X_ANA_TO_ANA, 0, in_text);
static const struct snd_kcontrol_new hs1_mux =
SOC_DAPM_ENUM("Headset1 Mux", hs1_enum);
/* Headset 2 Mux / Mux16 */
static SOC_ENUM_SINGLE_DECL(hs2_enum,
PM860X_ANA_TO_ANA, 1, in_text);
static const struct snd_kcontrol_new hs2_mux =
SOC_DAPM_ENUM("Headset2 Mux", hs2_enum);
/* Lineout 1 Mux / Mux17 */
static SOC_ENUM_SINGLE_DECL(lo1_enum,
PM860X_ANA_TO_ANA, 2, in_text);
static const struct snd_kcontrol_new lo1_mux =
SOC_DAPM_ENUM("Lineout1 Mux", lo1_enum);
/* Lineout 2 Mux / Mux18 */
static SOC_ENUM_SINGLE_DECL(lo2_enum,
PM860X_ANA_TO_ANA, 3, in_text);
static const struct snd_kcontrol_new lo2_mux =
SOC_DAPM_ENUM("Lineout2 Mux", lo2_enum);
/* Speaker Earpiece Demux */
static const char *spk_text[] = {
"Earpiece", "Speaker",
};
static SOC_ENUM_SINGLE_DECL(spk_enum,
PM860X_ANA_TO_ANA, 6, spk_text);
static const struct snd_kcontrol_new spk_demux =
SOC_DAPM_ENUM("Speaker Earpiece Demux", spk_enum);
/* MIC Mux / Mux1 */
static const char *mic_text[] = {
"Mic 1", "Mic 2",
};
static SOC_ENUM_SINGLE_DECL(mic_enum,
PM860X_ADC_ANA_4, 4, mic_text);
static const struct snd_kcontrol_new mic_mux =
SOC_DAPM_ENUM("MIC Mux", mic_enum);
static const struct snd_soc_dapm_widget pm860x_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("PCM SDI", "PCM Playback", 0,
PM860X_ADC_EN_2, 0, 0),
SND_SOC_DAPM_AIF_OUT("PCM SDO", "PCM Capture", 0,
PM860X_PCM_IFACE_3, 1, 1),
SND_SOC_DAPM_AIF_IN("I2S DIN", "I2S Playback", 0,
SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("I2S DIN1", "I2S Playback", 0,
SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("I2S DOUT", "I2S Capture", 0,
PM860X_I2S_IFACE_3, 5, 1),
SND_SOC_DAPM_SUPPLY("I2S CLK", PM860X_DAC_EN_2, 0, 0, NULL, 0),
SND_SOC_DAPM_MUX("I2S Mic Mux", SND_SOC_NOPM, 0, 0, &i2s_mic_mux),
SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adcl_mux),
SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcr_mux),
SND_SOC_DAPM_MUX("EC Mux", SND_SOC_NOPM, 0, 0, &ec_mux),
SND_SOC_DAPM_MUX("ADCR EC Mux", SND_SOC_NOPM, 0, 0, &adcr_ec_mux),
SND_SOC_DAPM_SWITCH("Left EPA", SND_SOC_NOPM, 0, 0,
&lepa_switch_controls),
SND_SOC_DAPM_SWITCH("Right EPA", SND_SOC_NOPM, 0, 0,
&repa_switch_controls),
SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Left ADC MOD", PM860X_ADC_EN_1,
0, 1, 1, 0),
SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Right ADC MOD", PM860X_ADC_EN_1,
1, 1, 1, 0),
SND_SOC_DAPM_ADC("Left ADC", NULL, PM860X_ADC_EN_2, 5, 0),
SND_SOC_DAPM_ADC("Right ADC", NULL, PM860X_ADC_EN_2, 4, 0),
SND_SOC_DAPM_SWITCH("AUX1 Switch", SND_SOC_NOPM, 0, 0,
&aux1_switch_controls),
SND_SOC_DAPM_SWITCH("AUX2 Switch", SND_SOC_NOPM, 0, 0,
&aux2_switch_controls),
SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &mic_mux),
SND_SOC_DAPM_MICBIAS("Mic1 Bias", PM860X_ADC_ANA_1, 2, 0),
SND_SOC_DAPM_MICBIAS("Mic3 Bias", PM860X_ADC_ANA_1, 7, 0),
SND_SOC_DAPM_PGA("MIC1 Volume", PM860X_ADC_EN_1, 2, 0, NULL, 0),
SND_SOC_DAPM_PGA("MIC3 Volume", PM860X_ADC_EN_1, 3, 0, NULL, 0),
SND_SOC_DAPM_PGA("AUX1 Volume", PM860X_ADC_EN_1, 4, 0, NULL, 0),
SND_SOC_DAPM_PGA("AUX2 Volume", PM860X_ADC_EN_1, 5, 0, NULL, 0),
SND_SOC_DAPM_PGA("Sidetone PGA", PM860X_ADC_EN_2, 1, 0, NULL, 0),
SND_SOC_DAPM_PGA("Lofi PGA", PM860X_ADC_EN_2, 2, 0, NULL, 0),
SND_SOC_DAPM_INPUT("AUX1"),
SND_SOC_DAPM_INPUT("AUX2"),
SND_SOC_DAPM_INPUT("MIC1P"),
SND_SOC_DAPM_INPUT("MIC1N"),
SND_SOC_DAPM_INPUT("MIC2P"),
SND_SOC_DAPM_INPUT("MIC2N"),
SND_SOC_DAPM_INPUT("MIC3P"),
SND_SOC_DAPM_INPUT("MIC3N"),
SND_SOC_DAPM_DAC_E("Left DAC", NULL, SND_SOC_NOPM, 0, 0,
pm860x_dac_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_DAC_E("Right DAC", NULL, SND_SOC_NOPM, 0, 0,
pm860x_dac_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_MUX("I2S DIN Mux", SND_SOC_NOPM, 0, 0, &i2s_din_mux),
SND_SOC_DAPM_MUX("DAC HS1 Mux", SND_SOC_NOPM, 0, 0, &dac_hs1_mux),
SND_SOC_DAPM_MUX("DAC HS2 Mux", SND_SOC_NOPM, 0, 0, &dac_hs2_mux),
SND_SOC_DAPM_MUX("DAC LO1 Mux", SND_SOC_NOPM, 0, 0, &dac_lo1_mux),
SND_SOC_DAPM_MUX("DAC LO2 Mux", SND_SOC_NOPM, 0, 0, &dac_lo2_mux),
SND_SOC_DAPM_MUX("DAC SP Mux", SND_SOC_NOPM, 0, 0, &dac_spk_ear_mux),
SND_SOC_DAPM_MUX("Headset1 Mux", SND_SOC_NOPM, 0, 0, &hs1_mux),
SND_SOC_DAPM_MUX("Headset2 Mux", SND_SOC_NOPM, 0, 0, &hs2_mux),
SND_SOC_DAPM_MUX("Lineout1 Mux", SND_SOC_NOPM, 0, 0, &lo1_mux),
SND_SOC_DAPM_MUX("Lineout2 Mux", SND_SOC_NOPM, 0, 0, &lo2_mux),
SND_SOC_DAPM_MUX("Speaker Earpiece Demux", SND_SOC_NOPM, 0, 0,
&spk_demux),
SND_SOC_DAPM_PGA("Headset1 PGA", PM860X_DAC_EN_1, 0, 0, NULL, 0),
SND_SOC_DAPM_PGA("Headset2 PGA", PM860X_DAC_EN_1, 1, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("HS1"),
SND_SOC_DAPM_OUTPUT("HS2"),
SND_SOC_DAPM_PGA("Lineout1 PGA", PM860X_DAC_EN_1, 2, 0, NULL, 0),
SND_SOC_DAPM_PGA("Lineout2 PGA", PM860X_DAC_EN_1, 3, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("LINEOUT1"),
SND_SOC_DAPM_OUTPUT("LINEOUT2"),
SND_SOC_DAPM_PGA("Earpiece PGA", PM860X_DAC_EN_1, 4, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("EARP"),
SND_SOC_DAPM_OUTPUT("EARN"),
SND_SOC_DAPM_PGA("Speaker PGA", PM860X_DAC_EN_1, 5, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("LSP"),
SND_SOC_DAPM_OUTPUT("LSN"),
SND_SOC_DAPM_REG(snd_soc_dapm_supply, "VCODEC", PM860X_AUDIO_SUPPLIES_2,
0, SUPPLY_MASK, SUPPLY_MASK, 0),
PM860X_DAPM_OUTPUT("RSYNC", pm860x_rsync_event),
};
static const struct snd_soc_dapm_route pm860x_dapm_routes[] = {
/* supply */
{"Left DAC", NULL, "VCODEC"},
{"Right DAC", NULL, "VCODEC"},
{"Left ADC", NULL, "VCODEC"},
{"Right ADC", NULL, "VCODEC"},
{"Left ADC", NULL, "Left ADC MOD"},
{"Right ADC", NULL, "Right ADC MOD"},
/* I2S Clock */
{"I2S DIN", NULL, "I2S CLK"},
{"I2S DIN1", NULL, "I2S CLK"},
{"I2S DOUT", NULL, "I2S CLK"},
/* PCM/AIF1 Inputs */
{"PCM SDO", NULL, "ADC Left Mux"},
{"PCM SDO", NULL, "ADCR EC Mux"},
/* PCM/AFI2 Outputs */
{"Lofi PGA", NULL, "PCM SDI"},
{"Lofi PGA", NULL, "Sidetone PGA"},
{"Left DAC", NULL, "Lofi PGA"},
{"Right DAC", NULL, "Lofi PGA"},
/* I2S/AIF2 Inputs */
{"MIC Mux", "Mic 1", "MIC1P"},
{"MIC Mux", "Mic 1", "MIC1N"},
{"MIC Mux", "Mic 2", "MIC2P"},
{"MIC Mux", "Mic 2", "MIC2N"},
{"MIC1 Volume", NULL, "MIC Mux"},
{"MIC3 Volume", NULL, "MIC3P"},
{"MIC3 Volume", NULL, "MIC3N"},
{"Left ADC", NULL, "MIC1 Volume"},
{"Right ADC", NULL, "MIC3 Volume"},
{"ADC Left Mux", "ADCR", "Right ADC"},
{"ADC Left Mux", "ADCL", "Left ADC"},
{"ADC Right Mux", "ADCL", "Left ADC"},
{"ADC Right Mux", "ADCR", "Right ADC"},
{"Left EPA", "Switch", "Left DAC"},
{"Right EPA", "Switch", "Right DAC"},
{"EC Mux", "Left", "Left DAC"},
{"EC Mux", "Right", "Right DAC"},
{"EC Mux", "Left + Right", "Left DAC"},
{"EC Mux", "Left + Right", "Right DAC"},
{"ADCR EC Mux", "ADCR", "ADC Right Mux"},
{"ADCR EC Mux", "EC", "EC Mux"},
{"I2S Mic Mux", "Ex PA", "Left EPA"},
{"I2S Mic Mux", "Ex PA", "Right EPA"},
{"I2S Mic Mux", "ADC", "ADC Left Mux"},
{"I2S Mic Mux", "ADC", "ADCR EC Mux"},
{"I2S DOUT", NULL, "I2S Mic Mux"},
/* I2S/AIF2 Outputs */
{"I2S DIN Mux", "DIN", "I2S DIN"},
{"I2S DIN Mux", "DIN1", "I2S DIN1"},
{"Left DAC", NULL, "I2S DIN Mux"},
{"Right DAC", NULL, "I2S DIN Mux"},
{"DAC HS1 Mux", "Left", "Left DAC"},
{"DAC HS1 Mux", "Right", "Right DAC"},
{"DAC HS2 Mux", "Left", "Left DAC"},
{"DAC HS2 Mux", "Right", "Right DAC"},
{"DAC LO1 Mux", "Left", "Left DAC"},
{"DAC LO1 Mux", "Right", "Right DAC"},
{"DAC LO2 Mux", "Left", "Left DAC"},
{"DAC LO2 Mux", "Right", "Right DAC"},
{"Headset1 Mux", "Digital", "DAC HS1 Mux"},
{"Headset2 Mux", "Digital", "DAC HS2 Mux"},
{"Lineout1 Mux", "Digital", "DAC LO1 Mux"},
{"Lineout2 Mux", "Digital", "DAC LO2 Mux"},
{"Headset1 PGA", NULL, "Headset1 Mux"},
{"Headset2 PGA", NULL, "Headset2 Mux"},
{"Lineout1 PGA", NULL, "Lineout1 Mux"},
{"Lineout2 PGA", NULL, "Lineout2 Mux"},
{"DAC SP Mux", "Left", "Left DAC"},
{"DAC SP Mux", "Right", "Right DAC"},
{"Speaker Earpiece Demux", "Speaker", "DAC SP Mux"},
{"Speaker PGA", NULL, "Speaker Earpiece Demux"},
{"Earpiece PGA", NULL, "Speaker Earpiece Demux"},
{"RSYNC", NULL, "Headset1 PGA"},
{"RSYNC", NULL, "Headset2 PGA"},
{"RSYNC", NULL, "Lineout1 PGA"},
{"RSYNC", NULL, "Lineout2 PGA"},
{"RSYNC", NULL, "Speaker PGA"},
{"RSYNC", NULL, "Speaker PGA"},
{"RSYNC", NULL, "Earpiece PGA"},
{"RSYNC", NULL, "Earpiece PGA"},
{"HS1", NULL, "RSYNC"},
{"HS2", NULL, "RSYNC"},
{"LINEOUT1", NULL, "RSYNC"},
{"LINEOUT2", NULL, "RSYNC"},
{"LSP", NULL, "RSYNC"},
{"LSN", NULL, "RSYNC"},
{"EARP", NULL, "RSYNC"},
{"EARN", NULL, "RSYNC"},
};
/*
* Use MUTE_LEFT & MUTE_RIGHT to implement digital mute.
* These bits can also be used to mute.
*/
static int pm860x_mute_stream(struct snd_soc_dai *codec_dai, int mute, int direction)
{
struct snd_soc_component *component = codec_dai->component;
int data = 0, mask = MUTE_LEFT | MUTE_RIGHT;
if (mute)
data = mask;
snd_soc_component_update_bits(component, PM860X_DAC_OFFSET, mask, data);
snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2,
RSYNC_CHANGE, RSYNC_CHANGE);
return 0;
}
static int pm860x_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
unsigned char inf = 0, mask = 0;
/* bit size */
switch (params_width(params)) {
case 16:
inf &= ~PCM_INF2_18WL;
break;
case 18:
inf |= PCM_INF2_18WL;
break;
default:
return -EINVAL;
}
mask |= PCM_INF2_18WL;
snd_soc_component_update_bits(component, PM860X_PCM_IFACE_2, mask, inf);
/* sample rate */
switch (params_rate(params)) {
case 8000:
inf = 0;
break;
case 16000:
inf = 3;
break;
case 32000:
inf = 6;
break;
case 48000:
inf = 8;
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, PM860X_PCM_RATE, 0x0f, inf);
return 0;
}
static int pm860x_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_component *component = codec_dai->component;
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
unsigned char inf = 0, mask = 0;
int ret = -EINVAL;
mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER;
/* set audio interface clocking */
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
case SND_SOC_DAIFMT_CBP_CFC:
if (pm860x->dir == PM860X_CLK_DIR_OUT) {
inf |= PCM_INF2_MASTER;
ret = 0;
}
break;
case SND_SOC_DAIFMT_CBC_CFC:
if (pm860x->dir == PM860X_CLK_DIR_IN) {
inf &= ~PCM_INF2_MASTER;
ret = 0;
}
break;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
inf |= PCM_EXACT_I2S;
ret = 0;
break;
}
mask |= PCM_MODE_MASK;
if (ret)
return ret;
snd_soc_component_update_bits(component, PM860X_PCM_IFACE_2, mask, inf);
return 0;
}
static int pm860x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_component *component = codec_dai->component;
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
if (dir == PM860X_CLK_DIR_OUT)
pm860x->dir = PM860X_CLK_DIR_OUT;
else /* Slave mode is not supported */
return -EINVAL;
return 0;
}
static int pm860x_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
unsigned char inf;
/* bit size */
switch (params_width(params)) {
case 16:
inf = 0;
break;
case 18:
inf = PCM_INF2_18WL;
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, PM860X_I2S_IFACE_2, PCM_INF2_18WL, inf);
/* sample rate */
switch (params_rate(params)) {
case 8000:
inf = 0;
break;
case 11025:
inf = 1;
break;
case 16000:
inf = 3;
break;
case 22050:
inf = 4;
break;
case 32000:
inf = 6;
break;
case 44100:
inf = 7;
break;
case 48000:
inf = 8;
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, PM860X_I2S_IFACE_4, 0xf, inf);
return 0;
}
static int pm860x_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_component *component = codec_dai->component;
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
unsigned char inf = 0, mask = 0;
mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER;
/* set audio interface clocking */
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
if (pm860x->dir == PM860X_CLK_DIR_OUT)
inf |= PCM_INF2_MASTER;
else
return -EINVAL;
break;
case SND_SOC_DAIFMT_CBC_CFC:
if (pm860x->dir == PM860X_CLK_DIR_IN)
inf &= ~PCM_INF2_MASTER;
else
return -EINVAL;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
inf |= PCM_EXACT_I2S;
break;
default:
return -EINVAL;
}
mask |= PCM_MODE_MASK;
snd_soc_component_update_bits(component, PM860X_I2S_IFACE_2, mask, inf);
return 0;
}
static int pm860x_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
int data;
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
/* Enable Audio PLL & Audio section */
data = AUDIO_PLL | AUDIO_SECTION_ON;
pm860x_reg_write(pm860x->i2c, REG_MISC2, data);
udelay(300);
data = AUDIO_PLL | AUDIO_SECTION_RESET
| AUDIO_SECTION_ON;
pm860x_reg_write(pm860x->i2c, REG_MISC2, data);
}
break;
case SND_SOC_BIAS_OFF:
data = AUDIO_PLL | AUDIO_SECTION_RESET | AUDIO_SECTION_ON;
pm860x_set_bits(pm860x->i2c, REG_MISC2, data, 0);
break;
}
return 0;
}
static const struct snd_soc_dai_ops pm860x_pcm_dai_ops = {
.mute_stream = pm860x_mute_stream,
.hw_params = pm860x_pcm_hw_params,
.set_fmt = pm860x_pcm_set_dai_fmt,
.set_sysclk = pm860x_set_dai_sysclk,
.no_capture_mute = 1,
};
static const struct snd_soc_dai_ops pm860x_i2s_dai_ops = {
.mute_stream = pm860x_mute_stream,
.hw_params = pm860x_i2s_hw_params,
.set_fmt = pm860x_i2s_set_dai_fmt,
.set_sysclk = pm860x_set_dai_sysclk,
.no_capture_mute = 1,
};
#define PM860X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000)
static struct snd_soc_dai_driver pm860x_dai[] = {
{
/* DAI PCM */
.name = "88pm860x-pcm",
.id = 1,
.playback = {
.stream_name = "PCM Playback",
.channels_min = 2,
.channels_max = 2,
.rates = PM860X_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE,
},
.capture = {
.stream_name = "PCM Capture",
.channels_min = 2,
.channels_max = 2,
.rates = PM860X_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE,
},
.ops = &pm860x_pcm_dai_ops,
}, {
/* DAI I2S */
.name = "88pm860x-i2s",
.id = 2,
.playback = {
.stream_name = "I2S Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE,
},
.capture = {
.stream_name = "I2S Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE,
},
.ops = &pm860x_i2s_dai_ops,
},
};
static irqreturn_t pm860x_component_handler(int irq, void *data)
{
struct pm860x_priv *pm860x = data;
int status, shrt, report = 0, mic_report = 0;
int mask;
status = pm860x_reg_read(pm860x->i2c, REG_STATUS_1);
shrt = pm860x_reg_read(pm860x->i2c, REG_SHORTS);
mask = pm860x->det.hs_shrt | pm860x->det.hook_det | pm860x->det.lo_shrt
| pm860x->det.hp_det;
#ifndef CONFIG_SND_SOC_88PM860X_MODULE
if (status & (HEADSET_STATUS | MIC_STATUS | SHORT_HS1 | SHORT_HS2 |
SHORT_LO1 | SHORT_LO2))
trace_snd_soc_jack_irq(dev_name(pm860x->component->dev));
#endif
if ((pm860x->det.hp_det & SND_JACK_HEADPHONE)
&& (status & HEADSET_STATUS))
report |= SND_JACK_HEADPHONE;
if ((pm860x->det.mic_det & SND_JACK_MICROPHONE)
&& (status & MIC_STATUS))
mic_report |= SND_JACK_MICROPHONE;
if (pm860x->det.hs_shrt && (shrt & (SHORT_HS1 | SHORT_HS2)))
report |= pm860x->det.hs_shrt;
if (pm860x->det.hook_det && (status & HOOK_STATUS))
report |= pm860x->det.hook_det;
if (pm860x->det.lo_shrt && (shrt & (SHORT_LO1 | SHORT_LO2)))
report |= pm860x->det.lo_shrt;
if (report)
snd_soc_jack_report(pm860x->det.hp_jack, report, mask);
if (mic_report)
snd_soc_jack_report(pm860x->det.mic_jack, SND_JACK_MICROPHONE,
SND_JACK_MICROPHONE);
dev_dbg(pm860x->component->dev, "headphone report:0x%x, mask:%x\n",
report, mask);
dev_dbg(pm860x->component->dev, "microphone report:0x%x\n", mic_report);
return IRQ_HANDLED;
}
int pm860x_hs_jack_detect(struct snd_soc_component *component,
struct snd_soc_jack *jack,
int det, int hook, int hs_shrt, int lo_shrt)
{
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
int data;
pm860x->det.hp_jack = jack;
pm860x->det.hp_det = det;
pm860x->det.hook_det = hook;
pm860x->det.hs_shrt = hs_shrt;
pm860x->det.lo_shrt = lo_shrt;
if (det & SND_JACK_HEADPHONE)
pm860x_set_bits(pm860x->i2c, REG_HS_DET,
EN_HS_DET, EN_HS_DET);
/* headset short detect */
if (hs_shrt) {
data = CLR_SHORT_HS2 | CLR_SHORT_HS1;
pm860x_set_bits(pm860x->i2c, REG_SHORTS, data, data);
}
/* Lineout short detect */
if (lo_shrt) {
data = CLR_SHORT_LO2 | CLR_SHORT_LO1;
pm860x_set_bits(pm860x->i2c, REG_SHORTS, data, data);
}
/* sync status */
pm860x_component_handler(0, pm860x);
return 0;
}
EXPORT_SYMBOL_GPL(pm860x_hs_jack_detect);
int pm860x_mic_jack_detect(struct snd_soc_component *component,
struct snd_soc_jack *jack, int det)
{
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
pm860x->det.mic_jack = jack;
pm860x->det.mic_det = det;
if (det & SND_JACK_MICROPHONE)
pm860x_set_bits(pm860x->i2c, REG_MIC_DET,
MICDET_MASK, MICDET_MASK);
/* sync status */
pm860x_component_handler(0, pm860x);
return 0;
}
EXPORT_SYMBOL_GPL(pm860x_mic_jack_detect);
static int pm860x_probe(struct snd_soc_component *component)
{
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
int i, ret;
pm860x->component = component;
snd_soc_component_init_regmap(component, pm860x->regmap);
for (i = 0; i < 4; i++) {
ret = request_threaded_irq(pm860x->irq[i], NULL,
pm860x_component_handler, IRQF_ONESHOT,
pm860x->name[i], pm860x);
if (ret < 0) {
dev_err(component->dev, "Failed to request IRQ!\n");
goto out;
}
}
return 0;
out:
while (--i >= 0)
free_irq(pm860x->irq[i], pm860x);
return ret;
}
static void pm860x_remove(struct snd_soc_component *component)
{
struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component);
int i;
for (i = 3; i >= 0; i--)
free_irq(pm860x->irq[i], pm860x);
}
static const struct snd_soc_component_driver soc_component_dev_pm860x = {
.probe = pm860x_probe,
.remove = pm860x_remove,
.set_bias_level = pm860x_set_bias_level,
.controls = pm860x_snd_controls,
.num_controls = ARRAY_SIZE(pm860x_snd_controls),
.dapm_widgets = pm860x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(pm860x_dapm_widgets),
.dapm_routes = pm860x_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(pm860x_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int pm860x_codec_probe(struct platform_device *pdev)
{
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
struct pm860x_priv *pm860x;
struct resource *res;
int i, ret;
pm860x = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_priv),
GFP_KERNEL);
if (pm860x == NULL)
return -ENOMEM;
pm860x->chip = chip;
pm860x->i2c = (chip->id == CHIP_PM8607) ? chip->client
: chip->companion;
pm860x->regmap = (chip->id == CHIP_PM8607) ? chip->regmap
: chip->regmap_companion;
platform_set_drvdata(pdev, pm860x);
for (i = 0; i < 4; i++) {
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if (!res) {
dev_err(&pdev->dev, "Failed to get IRQ resources\n");
return -EINVAL;
}
pm860x->irq[i] = res->start + chip->irq_base;
strncpy(pm860x->name[i], res->name, MAX_NAME_LEN);
}
ret = devm_snd_soc_register_component(&pdev->dev,
&soc_component_dev_pm860x,
pm860x_dai, ARRAY_SIZE(pm860x_dai));
if (ret) {
dev_err(&pdev->dev, "Failed to register component\n");
return -EINVAL;
}
return ret;
}
static int pm860x_codec_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver pm860x_codec_driver = {
.driver = {
.name = "88pm860x-codec",
},
.probe = pm860x_codec_probe,
.remove = pm860x_codec_remove,
};
module_platform_driver(pm860x_codec_driver);
MODULE_DESCRIPTION("ASoC 88PM860x driver");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:88pm860x-codec");
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver
*
* Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved.
*
* Documentation: ARM DDI 0173B
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/amba/bus.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/ac97_codec.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "aaci.h"
#define DRIVER_NAME "aaci-pl041"
#define FRAME_PERIOD_US 21
/*
* PM support is not complete. Turn it off.
*/
#undef CONFIG_PM
static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97)
{
u32 v, maincr = aaci->maincr | MAINCR_SCRA(ac97->num);
/*
* Ensure that the slot 1/2 RX registers are empty.
*/
v = readl(aaci->base + AACI_SLFR);
if (v & SLFR_2RXV)
readl(aaci->base + AACI_SL2RX);
if (v & SLFR_1RXV)
readl(aaci->base + AACI_SL1RX);
if (maincr != readl(aaci->base + AACI_MAINCR)) {
writel(maincr, aaci->base + AACI_MAINCR);
readl(aaci->base + AACI_MAINCR);
udelay(1);
}
}
/*
* P29:
* The recommended use of programming the external codec through slot 1
* and slot 2 data is to use the channels during setup routines and the
* slot register at any other time. The data written into slot 1, slot 2
* and slot 12 registers is transmitted only when their corresponding
* SI1TxEn, SI2TxEn and SI12TxEn bits are set in the AACI_MAINCR
* register.
*/
static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short val)
{
struct aaci *aaci = ac97->private_data;
int timeout;
u32 v;
if (ac97->num >= 4)
return;
mutex_lock(&aaci->ac97_sem);
aaci_ac97_select_codec(aaci, ac97);
/*
* P54: You must ensure that AACI_SL2TX is always written
* to, if required, before data is written to AACI_SL1TX.
*/
writel(val << 4, aaci->base + AACI_SL2TX);
writel(reg << 12, aaci->base + AACI_SL1TX);
/* Initially, wait one frame period */
udelay(FRAME_PERIOD_US);
/* And then wait an additional eight frame periods for it to be sent */
timeout = FRAME_PERIOD_US * 8;
do {
udelay(1);
v = readl(aaci->base + AACI_SLFR);
} while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout);
if (v & (SLFR_1TXB|SLFR_2TXB))
dev_err(&aaci->dev->dev,
"timeout waiting for write to complete\n");
mutex_unlock(&aaci->ac97_sem);
}
/*
* Read an AC'97 register.
*/
static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
struct aaci *aaci = ac97->private_data;
int timeout, retries = 10;
u32 v;
if (ac97->num >= 4)
return ~0;
mutex_lock(&aaci->ac97_sem);
aaci_ac97_select_codec(aaci, ac97);
/*
* Write the register address to slot 1.
*/
writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX);
/* Initially, wait one frame period */
udelay(FRAME_PERIOD_US);
/* And then wait an additional eight frame periods for it to be sent */
timeout = FRAME_PERIOD_US * 8;
do {
udelay(1);
v = readl(aaci->base + AACI_SLFR);
} while ((v & SLFR_1TXB) && --timeout);
if (v & SLFR_1TXB) {
dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n");
v = ~0;
goto out;
}
/* Now wait for the response frame */
udelay(FRAME_PERIOD_US);
/* And then wait an additional eight frame periods for data */
timeout = FRAME_PERIOD_US * 8;
do {
udelay(1);
cond_resched();
v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV);
} while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout);
if (v != (SLFR_1RXV|SLFR_2RXV)) {
dev_err(&aaci->dev->dev, "timeout on RX valid\n");
v = ~0;
goto out;
}
do {
v = readl(aaci->base + AACI_SL1RX) >> 12;
if (v == reg) {
v = readl(aaci->base + AACI_SL2RX) >> 4;
break;
} else if (--retries) {
dev_warn(&aaci->dev->dev,
"ac97 read back fail. retry\n");
continue;
} else {
dev_warn(&aaci->dev->dev,
"wrong ac97 register read back (%x != %x)\n",
v, reg);
v = ~0;
}
} while (retries);
out:
mutex_unlock(&aaci->ac97_sem);
return v;
}
static inline void
aaci_chan_wait_ready(struct aaci_runtime *aacirun, unsigned long mask)
{
u32 val;
int timeout = 5000;
do {
udelay(1);
val = readl(aacirun->base + AACI_SR);
} while (val & mask && timeout--);
}
/*
* Interrupt support.
*/
static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
{
if (mask & ISR_ORINTR) {
dev_warn(&aaci->dev->dev, "RX overrun on chan %d\n", channel);
writel(ICLR_RXOEC1 << channel, aaci->base + AACI_INTCLR);
}
if (mask & ISR_RXTOINTR) {
dev_warn(&aaci->dev->dev, "RX timeout on chan %d\n", channel);
writel(ICLR_RXTOFEC1 << channel, aaci->base + AACI_INTCLR);
}
if (mask & ISR_RXINTR) {
struct aaci_runtime *aacirun = &aaci->capture;
bool period_elapsed = false;
void *ptr;
if (!aacirun->substream || !aacirun->start) {
dev_warn(&aaci->dev->dev, "RX interrupt???\n");
writel(0, aacirun->base + AACI_IE);
return;
}
spin_lock(&aacirun->lock);
ptr = aacirun->ptr;
do {
unsigned int len = aacirun->fifo_bytes;
u32 val;
if (aacirun->bytes <= 0) {
aacirun->bytes += aacirun->period;
period_elapsed = true;
}
if (!(aacirun->cr & CR_EN))
break;
val = readl(aacirun->base + AACI_SR);
if (!(val & SR_RXHF))
break;
if (!(val & SR_RXFF))
len >>= 1;
aacirun->bytes -= len;
/* reading 16 bytes at a time */
for( ; len > 0; len -= 16) {
asm(
"ldmia %1, {r0, r1, r2, r3}\n\t"
"stmia %0!, {r0, r1, r2, r3}"
: "+r" (ptr)
: "r" (aacirun->fifo)
: "r0", "r1", "r2", "r3", "cc");
if (ptr >= aacirun->end)
ptr = aacirun->start;
}
} while(1);
aacirun->ptr = ptr;
spin_unlock(&aacirun->lock);
if (period_elapsed)
snd_pcm_period_elapsed(aacirun->substream);
}
if (mask & ISR_URINTR) {
dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel);
writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR);
}
if (mask & ISR_TXINTR) {
struct aaci_runtime *aacirun = &aaci->playback;
bool period_elapsed = false;
void *ptr;
if (!aacirun->substream || !aacirun->start) {
dev_warn(&aaci->dev->dev, "TX interrupt???\n");
writel(0, aacirun->base + AACI_IE);
return;
}
spin_lock(&aacirun->lock);
ptr = aacirun->ptr;
do {
unsigned int len = aacirun->fifo_bytes;
u32 val;
if (aacirun->bytes <= 0) {
aacirun->bytes += aacirun->period;
period_elapsed = true;
}
if (!(aacirun->cr & CR_EN))
break;
val = readl(aacirun->base + AACI_SR);
if (!(val & SR_TXHE))
break;
if (!(val & SR_TXFE))
len >>= 1;
aacirun->bytes -= len;
/* writing 16 bytes at a time */
for ( ; len > 0; len -= 16) {
asm(
"ldmia %0!, {r0, r1, r2, r3}\n\t"
"stmia %1, {r0, r1, r2, r3}"
: "+r" (ptr)
: "r" (aacirun->fifo)
: "r0", "r1", "r2", "r3", "cc");
if (ptr >= aacirun->end)
ptr = aacirun->start;
}
} while (1);
aacirun->ptr = ptr;
spin_unlock(&aacirun->lock);
if (period_elapsed)
snd_pcm_period_elapsed(aacirun->substream);
}
}
static irqreturn_t aaci_irq(int irq, void *devid)
{
struct aaci *aaci = devid;
u32 mask;
int i;
mask = readl(aaci->base + AACI_ALLINTS);
if (mask) {
u32 m = mask;
for (i = 0; i < 4; i++, m >>= 7) {
if (m & 0x7f) {
aaci_fifo_irq(aaci, i, m);
}
}
}
return mask ? IRQ_HANDLED : IRQ_NONE;
}
/*
* ALSA support.
*/
static const struct snd_pcm_hardware aaci_hw_info = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_RESUME,
/*
* ALSA doesn't support 18-bit or 20-bit packed into 32-bit
* words. It also doesn't support 12-bit at all.
*/
.formats = SNDRV_PCM_FMTBIT_S16_LE,
/* rates are setup from the AC'97 codec */
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 64 * 1024,
.period_bytes_min = 256,
.period_bytes_max = PAGE_SIZE,
.periods_min = 4,
.periods_max = PAGE_SIZE / 16,
};
/*
* We can support two and four channel audio. Unfortunately
* six channel audio requires a non-standard channel ordering:
* 2 -> FL(3), FR(4)
* 4 -> FL(3), FR(4), SL(7), SR(8)
* 6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required)
* FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual)
* This requires an ALSA configuration file to correct.
*/
static int aaci_rule_channels(struct snd_pcm_hw_params *p,
struct snd_pcm_hw_rule *rule)
{
static const unsigned int channel_list[] = { 2, 4, 6 };
struct aaci *aaci = rule->private;
unsigned int mask = 1 << 0, slots;
/* pcms[0] is the our 5.1 PCM instance. */
slots = aaci->ac97_bus->pcms[0].r[0].slots;
if (slots & (1 << AC97_SLOT_PCM_SLEFT)) {
mask |= 1 << 1;
if (slots & (1 << AC97_SLOT_LFE))
mask |= 1 << 2;
}
return snd_interval_list(hw_param_interval(p, rule->var),
ARRAY_SIZE(channel_list), channel_list, mask);
}
static int aaci_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct aaci *aaci = substream->private_data;
struct aaci_runtime *aacirun;
int ret = 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
aacirun = &aaci->playback;
} else {
aacirun = &aaci->capture;
}
aacirun->substream = substream;
runtime->private_data = aacirun;
runtime->hw = aaci_hw_info;
runtime->hw.rates = aacirun->pcm->rates;
snd_pcm_limit_hw_rates(runtime);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
runtime->hw.channels_max = 6;
/* Add rule describing channel dependency. */
ret = snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
aaci_rule_channels, aaci,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (ret)
return ret;
if (aacirun->pcm->r[1].slots)
snd_ac97_pcm_double_rate_rules(runtime);
}
/*
* ALSA wants the byte-size of the FIFOs. As we only support
* 16-bit samples, this is twice the FIFO depth irrespective
* of whether it's in compact mode or not.
*/
runtime->hw.fifo_size = aaci->fifo_depth * 2;
mutex_lock(&aaci->irq_lock);
if (!aaci->users++) {
ret = request_irq(aaci->dev->irq[0], aaci_irq,
IRQF_SHARED, DRIVER_NAME, aaci);
if (ret != 0)
aaci->users--;
}
mutex_unlock(&aaci->irq_lock);
return ret;
}
/*
* Common ALSA stuff
*/
static int aaci_pcm_close(struct snd_pcm_substream *substream)
{
struct aaci *aaci = substream->private_data;
struct aaci_runtime *aacirun = substream->runtime->private_data;
WARN_ON(aacirun->cr & CR_EN);
aacirun->substream = NULL;
mutex_lock(&aaci->irq_lock);
if (!--aaci->users)
free_irq(aaci->dev->irq[0], aaci);
mutex_unlock(&aaci->irq_lock);
return 0;
}
static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct aaci_runtime *aacirun = substream->runtime->private_data;
/*
* This must not be called with the device enabled.
*/
WARN_ON(aacirun->cr & CR_EN);
if (aacirun->pcm_open)
snd_ac97_pcm_close(aacirun->pcm);
aacirun->pcm_open = 0;
return 0;
}
/* Channel to slot mask */
static const u32 channels_to_slotmask[] = {
[2] = CR_SL3 | CR_SL4,
[4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8,
[6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9,
};
static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct aaci_runtime *aacirun = substream->runtime->private_data;
struct aaci *aaci = substream->private_data;
unsigned int channels = params_channels(params);
unsigned int rate = params_rate(params);
int dbl = rate > 48000;
int err;
aaci_pcm_hw_free(substream);
if (aacirun->pcm_open) {
snd_ac97_pcm_close(aacirun->pcm);
aacirun->pcm_open = 0;
}
/* channels is already limited to 2, 4, or 6 by aaci_rule_channels */
if (dbl && channels != 2)
return -EINVAL;
err = snd_ac97_pcm_open(aacirun->pcm, rate, channels,
aacirun->pcm->r[dbl].slots);
aacirun->pcm_open = err == 0;
aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16;
aacirun->cr |= channels_to_slotmask[channels + dbl * 2];
/*
* fifo_bytes is the number of bytes we transfer to/from
* the FIFO, including padding. So that's x4. As we're
* in compact mode, the FIFO is half the size.
*/
aacirun->fifo_bytes = aaci->fifo_depth * 4 / 2;
return err;
}
static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct aaci_runtime *aacirun = runtime->private_data;
aacirun->period = snd_pcm_lib_period_bytes(substream);
aacirun->start = runtime->dma_area;
aacirun->end = aacirun->start + snd_pcm_lib_buffer_bytes(substream);
aacirun->ptr = aacirun->start;
aacirun->bytes = aacirun->period;
return 0;
}
static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct aaci_runtime *aacirun = runtime->private_data;
ssize_t bytes = aacirun->ptr - aacirun->start;
return bytes_to_frames(runtime, bytes);
}
/*
* Playback specific ALSA stuff
*/
static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun)
{
u32 ie;
ie = readl(aacirun->base + AACI_IE);
ie &= ~(IE_URIE|IE_TXIE);
writel(ie, aacirun->base + AACI_IE);
aacirun->cr &= ~CR_EN;
aaci_chan_wait_ready(aacirun, SR_TXB);
writel(aacirun->cr, aacirun->base + AACI_TXCR);
}
static void aaci_pcm_playback_start(struct aaci_runtime *aacirun)
{
u32 ie;
aaci_chan_wait_ready(aacirun, SR_TXB);
aacirun->cr |= CR_EN;
ie = readl(aacirun->base + AACI_IE);
ie |= IE_URIE | IE_TXIE;
writel(ie, aacirun->base + AACI_IE);
writel(aacirun->cr, aacirun->base + AACI_TXCR);
}
static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct aaci_runtime *aacirun = substream->runtime->private_data;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&aacirun->lock, flags);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
aaci_pcm_playback_start(aacirun);
break;
case SNDRV_PCM_TRIGGER_RESUME:
aaci_pcm_playback_start(aacirun);
break;
case SNDRV_PCM_TRIGGER_STOP:
aaci_pcm_playback_stop(aacirun);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
aaci_pcm_playback_stop(aacirun);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
break;
default:
ret = -EINVAL;
}
spin_unlock_irqrestore(&aacirun->lock, flags);
return ret;
}
static const struct snd_pcm_ops aaci_playback_ops = {
.open = aaci_pcm_open,
.close = aaci_pcm_close,
.hw_params = aaci_pcm_hw_params,
.hw_free = aaci_pcm_hw_free,
.prepare = aaci_pcm_prepare,
.trigger = aaci_pcm_playback_trigger,
.pointer = aaci_pcm_pointer,
};
static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun)
{
u32 ie;
aaci_chan_wait_ready(aacirun, SR_RXB);
ie = readl(aacirun->base + AACI_IE);
ie &= ~(IE_ORIE | IE_RXIE);
writel(ie, aacirun->base+AACI_IE);
aacirun->cr &= ~CR_EN;
writel(aacirun->cr, aacirun->base + AACI_RXCR);
}
static void aaci_pcm_capture_start(struct aaci_runtime *aacirun)
{
u32 ie;
aaci_chan_wait_ready(aacirun, SR_RXB);
#ifdef DEBUG
/* RX Timeout value: bits 28:17 in RXCR */
aacirun->cr |= 0xf << 17;
#endif
aacirun->cr |= CR_EN;
writel(aacirun->cr, aacirun->base + AACI_RXCR);
ie = readl(aacirun->base + AACI_IE);
ie |= IE_ORIE |IE_RXIE; // overrun and rx interrupt -- half full
writel(ie, aacirun->base + AACI_IE);
}
static int aaci_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct aaci_runtime *aacirun = substream->runtime->private_data;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&aacirun->lock, flags);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
aaci_pcm_capture_start(aacirun);
break;
case SNDRV_PCM_TRIGGER_RESUME:
aaci_pcm_capture_start(aacirun);
break;
case SNDRV_PCM_TRIGGER_STOP:
aaci_pcm_capture_stop(aacirun);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
aaci_pcm_capture_stop(aacirun);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
break;
default:
ret = -EINVAL;
}
spin_unlock_irqrestore(&aacirun->lock, flags);
return ret;
}
static int aaci_pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct aaci *aaci = substream->private_data;
aaci_pcm_prepare(substream);
/* allow changing of sample rate */
aaci_ac97_write(aaci->ac97, AC97_EXTENDED_STATUS, 0x0001); /* VRA */
aaci_ac97_write(aaci->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
aaci_ac97_write(aaci->ac97, AC97_PCM_MIC_ADC_RATE, runtime->rate);
/* Record select: Mic: 0, Aux: 3, Line: 4 */
aaci_ac97_write(aaci->ac97, AC97_REC_SEL, 0x0404);
return 0;
}
static const struct snd_pcm_ops aaci_capture_ops = {
.open = aaci_pcm_open,
.close = aaci_pcm_close,
.hw_params = aaci_pcm_hw_params,
.hw_free = aaci_pcm_hw_free,
.prepare = aaci_pcm_capture_prepare,
.trigger = aaci_pcm_capture_trigger,
.pointer = aaci_pcm_pointer,
};
/*
* Power Management.
*/
#ifdef CONFIG_PM
static int aaci_do_suspend(struct snd_card *card)
{
struct aaci *aaci = card->private_data;
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
return 0;
}
static int aaci_do_resume(struct snd_card *card)
{
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
static int aaci_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
return card ? aaci_do_suspend(card) : 0;
}
static int aaci_resume(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
return card ? aaci_do_resume(card) : 0;
}
static SIMPLE_DEV_PM_OPS(aaci_dev_pm_ops, aaci_suspend, aaci_resume);
#define AACI_DEV_PM_OPS (&aaci_dev_pm_ops)
#else
#define AACI_DEV_PM_OPS NULL
#endif
static const struct ac97_pcm ac97_defs[] = {
[0] = { /* Front PCM */
.exclusive = 1,
.r = {
[0] = {
.slots = (1 << AC97_SLOT_PCM_LEFT) |
(1 << AC97_SLOT_PCM_RIGHT) |
(1 << AC97_SLOT_PCM_CENTER) |
(1 << AC97_SLOT_PCM_SLEFT) |
(1 << AC97_SLOT_PCM_SRIGHT) |
(1 << AC97_SLOT_LFE),
},
[1] = {
.slots = (1 << AC97_SLOT_PCM_LEFT) |
(1 << AC97_SLOT_PCM_RIGHT) |
(1 << AC97_SLOT_PCM_LEFT_0) |
(1 << AC97_SLOT_PCM_RIGHT_0),
},
},
},
[1] = { /* PCM in */
.stream = 1,
.exclusive = 1,
.r = {
[0] = {
.slots = (1 << AC97_SLOT_PCM_LEFT) |
(1 << AC97_SLOT_PCM_RIGHT),
},
},
},
[2] = { /* Mic in */
.stream = 1,
.exclusive = 1,
.r = {
[0] = {
.slots = (1 << AC97_SLOT_MIC),
},
},
}
};
static const struct snd_ac97_bus_ops aaci_bus_ops = {
.write = aaci_ac97_write,
.read = aaci_ac97_read,
};
static int aaci_probe_ac97(struct aaci *aaci)
{
struct snd_ac97_template ac97_template;
struct snd_ac97_bus *ac97_bus;
struct snd_ac97 *ac97;
int ret;
/*
* Assert AACIRESET for 2us
*/
writel(0, aaci->base + AACI_RESET);
udelay(2);
writel(RESET_NRST, aaci->base + AACI_RESET);
/*
* Give the AC'97 codec more than enough time
* to wake up. (42us = ~2 frames at 48kHz.)
*/
udelay(FRAME_PERIOD_US * 2);
ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus);
if (ret)
goto out;
ac97_bus->clock = 48000;
aaci->ac97_bus = ac97_bus;
memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
ac97_template.private_data = aaci;
ac97_template.num = 0;
ac97_template.scaps = AC97_SCAP_SKIP_MODEM;
ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
if (ret)
goto out;
aaci->ac97 = ac97;
/*
* Disable AC97 PC Beep input on audio codecs.
*/
if (ac97_is_audio(ac97))
snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x801e);
ret = snd_ac97_pcm_assign(ac97_bus, ARRAY_SIZE(ac97_defs), ac97_defs);
if (ret)
goto out;
aaci->playback.pcm = &ac97_bus->pcms[0];
aaci->capture.pcm = &ac97_bus->pcms[1];
out:
return ret;
}
static void aaci_free_card(struct snd_card *card)
{
struct aaci *aaci = card->private_data;
iounmap(aaci->base);
}
static struct aaci *aaci_init_card(struct amba_device *dev)
{
struct aaci *aaci;
struct snd_card *card;
int err;
err = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
THIS_MODULE, sizeof(struct aaci), &card);
if (err < 0)
return NULL;
card->private_free = aaci_free_card;
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
strscpy(card->shortname, "ARM AC'97 Interface", sizeof(card->shortname));
snprintf(card->longname, sizeof(card->longname),
"%s PL%03x rev%u at 0x%08llx, irq %d",
card->shortname, amba_part(dev), amba_rev(dev),
(unsigned long long)dev->res.start, dev->irq[0]);
aaci = card->private_data;
mutex_init(&aaci->ac97_sem);
mutex_init(&aaci->irq_lock);
aaci->card = card;
aaci->dev = dev;
/* Set MAINCR to allow slot 1 and 2 data IO */
aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN |
MAINCR_SL2RXEN | MAINCR_SL2TXEN;
return aaci;
}
static int aaci_init_pcm(struct aaci *aaci)
{
struct snd_pcm *pcm;
int ret;
ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm);
if (ret == 0) {
aaci->pcm = pcm;
pcm->private_data = aaci;
pcm->info_flags = 0;
strscpy(pcm->name, DRIVER_NAME, sizeof(pcm->name));
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
aaci->card->dev,
0, 64 * 1024);
}
return ret;
}
static unsigned int aaci_size_fifo(struct aaci *aaci)
{
struct aaci_runtime *aacirun = &aaci->playback;
int i;
/*
* Enable the channel, but don't assign it to any slots, so
* it won't empty onto the AC'97 link.
*/
writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR);
for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++)
writel(0, aacirun->fifo);
writel(0, aacirun->base + AACI_TXCR);
/*
* Re-initialise the AACI after the FIFO depth test, to
* ensure that the FIFOs are empty. Unfortunately, merely
* disabling the channel doesn't clear the FIFO.
*/
writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
readl(aaci->base + AACI_MAINCR);
udelay(1);
writel(aaci->maincr, aaci->base + AACI_MAINCR);
/*
* If we hit 4096 entries, we failed. Go back to the specified
* fifo depth.
*/
if (i == 4096)
i = 8;
return i;
}
static int aaci_probe(struct amba_device *dev,
const struct amba_id *id)
{
struct aaci *aaci;
int ret, i;
ret = amba_request_regions(dev, NULL);
if (ret)
return ret;
aaci = aaci_init_card(dev);
if (!aaci) {
ret = -ENOMEM;
goto out;
}
aaci->base = ioremap(dev->res.start, resource_size(&dev->res));
if (!aaci->base) {
ret = -ENOMEM;
goto out;
}
/*
* Playback uses AACI channel 0
*/
spin_lock_init(&aaci->playback.lock);
aaci->playback.base = aaci->base + AACI_CSCH1;
aaci->playback.fifo = aaci->base + AACI_DR1;
/*
* Capture uses AACI channel 0
*/
spin_lock_init(&aaci->capture.lock);
aaci->capture.base = aaci->base + AACI_CSCH1;
aaci->capture.fifo = aaci->base + AACI_DR1;
for (i = 0; i < 4; i++) {
void __iomem *base = aaci->base + i * 0x14;
writel(0, base + AACI_IE);
writel(0, base + AACI_TXCR);
writel(0, base + AACI_RXCR);
}
writel(0x1fff, aaci->base + AACI_INTCLR);
writel(aaci->maincr, aaci->base + AACI_MAINCR);
/*
* Fix: ac97 read back fail errors by reading
* from any arbitrary aaci register.
*/
readl(aaci->base + AACI_CSCH1);
ret = aaci_probe_ac97(aaci);
if (ret)
goto out;
/*
* Size the FIFOs (must be multiple of 16).
* This is the number of entries in the FIFO.
*/
aaci->fifo_depth = aaci_size_fifo(aaci);
if (aaci->fifo_depth & 15) {
printk(KERN_WARNING "AACI: FIFO depth %d not supported\n",
aaci->fifo_depth);
ret = -ENODEV;
goto out;
}
ret = aaci_init_pcm(aaci);
if (ret)
goto out;
ret = snd_card_register(aaci->card);
if (ret == 0) {
dev_info(&dev->dev, "%s\n", aaci->card->longname);
dev_info(&dev->dev, "FIFO %u entries\n", aaci->fifo_depth);
amba_set_drvdata(dev, aaci->card);
return ret;
}
out:
if (aaci)
snd_card_free(aaci->card);
amba_release_regions(dev);
return ret;
}
static void aaci_remove(struct amba_device *dev)
{
struct snd_card *card = amba_get_drvdata(dev);
if (card) {
struct aaci *aaci = card->private_data;
writel(0, aaci->base + AACI_MAINCR);
snd_card_free(card);
amba_release_regions(dev);
}
}
static struct amba_id aaci_ids[] = {
{
.id = 0x00041041,
.mask = 0x000fffff,
},
{ 0, 0 },
};
MODULE_DEVICE_TABLE(amba, aaci_ids);
static struct amba_driver aaci_driver = {
.drv = {
.name = DRIVER_NAME,
.pm = AACI_DEV_PM_OPS,
},
.probe = aaci_probe,
.remove = aaci_remove,
.id_table = aaci_ids,
};
module_amba_driver(aaci_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ARM PrimeCell PL041 Advanced Audio CODEC Interface driver");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Kristoffer Karlsson <kristoffer.karlsson@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>,
* for ST-Ericsson.
*
* Based on the early work done by:
* Mikko J. Lehto <mikko.lehto@symbio.com>,
* Mikko Sarmanne <mikko.sarmanne@symbio.com>,
* Jarmo K. Kuronen <jarmo.kuronen@symbio.com>,
* for ST-Ericsson.
*
* License terms:
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/mfd/abx500/ab8500.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500-sysctrl.h>
#include <linux/mfd/abx500/ab8500-codec.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "ab8500-codec.h"
/* Macrocell value definitions */
#define CLK_32K_OUT2_DISABLE 0x01
#define INACTIVE_RESET_AUDIO 0x02
#define ENABLE_AUDIO_CLK_TO_AUDIO_BLK 0x10
#define ENABLE_VINTCORE12_SUPPLY 0x04
#define GPIO27_DIR_OUTPUT 0x04
#define GPIO29_DIR_OUTPUT 0x10
#define GPIO31_DIR_OUTPUT 0x40
/* Macrocell register definitions */
#define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */
/* Nr of FIR/IIR-coeff banks in ANC-block */
#define AB8500_NR_OF_ANC_COEFF_BANKS 2
/* Minimum duration to keep ANC IIR Init bit high or
low before proceeding with the configuration sequence */
#define AB8500_ANC_SM_DELAY 2000
#define AB8500_FILTER_CONTROL(xname, xcount, xmin, xmax) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.info = filter_control_info, \
.get = filter_control_get, .put = filter_control_put, \
.private_value = (unsigned long)&(struct filter_control) \
{.count = xcount, .min = xmin, .max = xmax} }
struct filter_control {
long min, max;
unsigned int count;
long value[128];
};
/* Sidetone states */
static const char * const enum_sid_state[] = {
"Unconfigured",
"Apply FIR",
"FIR is configured",
};
enum sid_state {
SID_UNCONFIGURED = 0,
SID_APPLY_FIR = 1,
SID_FIR_CONFIGURED = 2,
};
static const char * const enum_anc_state[] = {
"Unconfigured",
"Apply FIR and IIR",
"FIR and IIR are configured",
"Apply FIR",
"FIR is configured",
"Apply IIR",
"IIR is configured"
};
enum anc_state {
ANC_UNCONFIGURED = 0,
ANC_APPLY_FIR_IIR = 1,
ANC_FIR_IIR_CONFIGURED = 2,
ANC_APPLY_FIR = 3,
ANC_FIR_CONFIGURED = 4,
ANC_APPLY_IIR = 5,
ANC_IIR_CONFIGURED = 6
};
/* Analog microphones */
enum amic_idx {
AMIC_IDX_1A,
AMIC_IDX_1B,
AMIC_IDX_2
};
/* Private data for AB8500 device-driver */
struct ab8500_codec_drvdata {
struct regmap *regmap;
struct mutex ctrl_lock;
/* Sidetone */
long *sid_fir_values;
enum sid_state sid_status;
/* ANC */
long *anc_fir_values;
long *anc_iir_values;
enum anc_state anc_status;
};
static inline const char *amic_micbias_str(enum amic_micbias micbias)
{
switch (micbias) {
case AMIC_MICBIAS_VAMIC1:
return "VAMIC1";
case AMIC_MICBIAS_VAMIC2:
return "VAMIC2";
default:
return "Unknown";
}
}
static inline const char *amic_type_str(enum amic_type type)
{
switch (type) {
case AMIC_TYPE_DIFFERENTIAL:
return "DIFFERENTIAL";
case AMIC_TYPE_SINGLE_ENDED:
return "SINGLE ENDED";
default:
return "Unknown";
}
}
/*
* Read'n'write functions
*/
/* Read a register from the audio-bank of AB8500 */
static int ab8500_codec_read_reg(void *context, unsigned int reg,
unsigned int *value)
{
struct device *dev = context;
int status;
u8 value8;
status = abx500_get_register_interruptible(dev, AB8500_AUDIO,
reg, &value8);
*value = (unsigned int)value8;
return status;
}
/* Write to a register in the audio-bank of AB8500 */
static int ab8500_codec_write_reg(void *context, unsigned int reg,
unsigned int value)
{
struct device *dev = context;
return abx500_set_register_interruptible(dev, AB8500_AUDIO,
reg, value);
}
static const struct regmap_config ab8500_codec_regmap = {
.reg_read = ab8500_codec_read_reg,
.reg_write = ab8500_codec_write_reg,
};
/*
* Controls - DAPM
*/
/* Earpiece */
/* Earpiece source selector */
static const char * const enum_ear_lineout_source[] = {"Headset Left",
"Speaker Left"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_ear_lineout_source, AB8500_DMICFILTCONF,
AB8500_DMICFILTCONF_DA3TOEAR, enum_ear_lineout_source);
static const struct snd_kcontrol_new dapm_ear_lineout_source =
SOC_DAPM_ENUM("Earpiece or LineOut Mono Source",
dapm_enum_ear_lineout_source);
/* LineOut */
/* LineOut source selector */
static const char * const enum_lineout_source[] = {"Mono Path", "Stereo Path"};
static SOC_ENUM_DOUBLE_DECL(dapm_enum_lineout_source, AB8500_ANACONF5,
AB8500_ANACONF5_HSLDACTOLOL,
AB8500_ANACONF5_HSRDACTOLOR, enum_lineout_source);
static const struct snd_kcontrol_new dapm_lineout_source[] = {
SOC_DAPM_ENUM("LineOut Source", dapm_enum_lineout_source),
};
/* Handsfree */
/* Speaker Left - ANC selector */
static const char * const enum_HFx_sel[] = {"Audio Path", "ANC"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_HFl_sel, AB8500_DIGMULTCONF2,
AB8500_DIGMULTCONF2_HFLSEL, enum_HFx_sel);
static const struct snd_kcontrol_new dapm_HFl_select[] = {
SOC_DAPM_ENUM("Speaker Left Source", dapm_enum_HFl_sel),
};
/* Speaker Right - ANC selector */
static SOC_ENUM_SINGLE_DECL(dapm_enum_HFr_sel, AB8500_DIGMULTCONF2,
AB8500_DIGMULTCONF2_HFRSEL, enum_HFx_sel);
static const struct snd_kcontrol_new dapm_HFr_select[] = {
SOC_DAPM_ENUM("Speaker Right Source", dapm_enum_HFr_sel),
};
/* Mic 1 */
/* Mic 1 - Mic 1a or 1b selector */
static const char * const enum_mic1ab_sel[] = {"Mic 1b", "Mic 1a"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_mic1ab_sel, AB8500_ANACONF3,
AB8500_ANACONF3_MIC1SEL, enum_mic1ab_sel);
static const struct snd_kcontrol_new dapm_mic1ab_mux[] = {
SOC_DAPM_ENUM("Mic 1a or 1b Select", dapm_enum_mic1ab_sel),
};
/* Mic 1 - AD3 - Mic 1 or DMic 3 selector */
static const char * const enum_ad3_sel[] = {"Mic 1", "DMic 3"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_ad3_sel, AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_AD3SEL, enum_ad3_sel);
static const struct snd_kcontrol_new dapm_ad3_select[] = {
SOC_DAPM_ENUM("AD3 Source Select", dapm_enum_ad3_sel),
};
/* Mic 1 - AD6 - Mic 1 or DMic 6 selector */
static const char * const enum_ad6_sel[] = {"Mic 1", "DMic 6"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_ad6_sel, AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_AD6SEL, enum_ad6_sel);
static const struct snd_kcontrol_new dapm_ad6_select[] = {
SOC_DAPM_ENUM("AD6 Source Select", dapm_enum_ad6_sel),
};
/* Mic 2 */
/* Mic 2 - AD5 - Mic 2 or DMic 5 selector */
static const char * const enum_ad5_sel[] = {"Mic 2", "DMic 5"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_ad5_sel, AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_AD5SEL, enum_ad5_sel);
static const struct snd_kcontrol_new dapm_ad5_select[] = {
SOC_DAPM_ENUM("AD5 Source Select", dapm_enum_ad5_sel),
};
/* LineIn */
/* LineIn left - AD1 - LineIn Left or DMic 1 selector */
static const char * const enum_ad1_sel[] = {"LineIn Left", "DMic 1"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_ad1_sel, AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_AD1SEL, enum_ad1_sel);
static const struct snd_kcontrol_new dapm_ad1_select[] = {
SOC_DAPM_ENUM("AD1 Source Select", dapm_enum_ad1_sel),
};
/* LineIn right - Mic 2 or LineIn Right selector */
static const char * const enum_mic2lr_sel[] = {"Mic 2", "LineIn Right"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_mic2lr_sel, AB8500_ANACONF3,
AB8500_ANACONF3_LINRSEL, enum_mic2lr_sel);
static const struct snd_kcontrol_new dapm_mic2lr_select[] = {
SOC_DAPM_ENUM("Mic 2 or LINR Select", dapm_enum_mic2lr_sel),
};
/* LineIn right - AD2 - LineIn Right or DMic2 selector */
static const char * const enum_ad2_sel[] = {"LineIn Right", "DMic 2"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_ad2_sel, AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_AD2SEL, enum_ad2_sel);
static const struct snd_kcontrol_new dapm_ad2_select[] = {
SOC_DAPM_ENUM("AD2 Source Select", dapm_enum_ad2_sel),
};
/* ANC */
static const char * const enum_anc_in_sel[] = {"Mic 1 / DMic 6",
"Mic 2 / DMic 5"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_in_sel, AB8500_DMICFILTCONF,
AB8500_DMICFILTCONF_ANCINSEL, enum_anc_in_sel);
static const struct snd_kcontrol_new dapm_anc_in_select[] = {
SOC_DAPM_ENUM("ANC Source", dapm_enum_anc_in_sel),
};
/* ANC - Enable/Disable */
static const struct snd_kcontrol_new dapm_anc_enable[] = {
SOC_DAPM_SINGLE("Switch", AB8500_ANCCONF1,
AB8500_ANCCONF1_ENANC, 0, 0),
};
/* ANC to Earpiece - Mute */
static const struct snd_kcontrol_new dapm_anc_ear_mute[] = {
SOC_DAPM_SINGLE("Switch", AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_ANCSEL, 1, 0),
};
/* Sidetone left */
/* Sidetone left - Input selector */
static const char * const enum_stfir1_in_sel[] = {
"LineIn Left", "LineIn Right", "Mic 1", "Headset Left"
};
static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir1_in_sel, AB8500_DIGMULTCONF2,
AB8500_DIGMULTCONF2_FIRSID1SEL, enum_stfir1_in_sel);
static const struct snd_kcontrol_new dapm_stfir1_in_select[] = {
SOC_DAPM_ENUM("Sidetone Left Source", dapm_enum_stfir1_in_sel),
};
/* Sidetone right path */
/* Sidetone right - Input selector */
static const char * const enum_stfir2_in_sel[] = {
"LineIn Right", "Mic 1", "DMic 4", "Headset Right"
};
static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir2_in_sel, AB8500_DIGMULTCONF2,
AB8500_DIGMULTCONF2_FIRSID2SEL, enum_stfir2_in_sel);
static const struct snd_kcontrol_new dapm_stfir2_in_select[] = {
SOC_DAPM_ENUM("Sidetone Right Source", dapm_enum_stfir2_in_sel),
};
/* Vibra */
static const char * const enum_pwm2vibx[] = {"Audio Path", "PWM Generator"};
static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib1, AB8500_PWMGENCONF1,
AB8500_PWMGENCONF1_PWMTOVIB1, enum_pwm2vibx);
static const struct snd_kcontrol_new dapm_pwm2vib1[] = {
SOC_DAPM_ENUM("Vibra 1 Controller", dapm_enum_pwm2vib1),
};
static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib2, AB8500_PWMGENCONF1,
AB8500_PWMGENCONF1_PWMTOVIB2, enum_pwm2vibx);
static const struct snd_kcontrol_new dapm_pwm2vib2[] = {
SOC_DAPM_ENUM("Vibra 2 Controller", dapm_enum_pwm2vib2),
};
/*
* DAPM-widgets
*/
static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = {
/* Clocks */
SND_SOC_DAPM_CLOCK_SUPPLY("audioclk"),
/* Regulators */
SND_SOC_DAPM_REGULATOR_SUPPLY("V-AUD", 0, 0),
SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC1", 0, 0),
SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC2", 0, 0),
SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0),
/* Power */
SND_SOC_DAPM_SUPPLY("Audio Power",
AB8500_POWERUP, AB8500_POWERUP_POWERUP, 0,
NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("Audio Analog Power",
AB8500_POWERUP, AB8500_POWERUP_ENANA, 0,
NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
/* Main supply node */
SND_SOC_DAPM_SUPPLY("Main Supply", SND_SOC_NOPM, 0, 0,
NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
/* DA/AD */
SND_SOC_DAPM_INPUT("ADC Input"),
SND_SOC_DAPM_ADC("ADC", "ab8500_0c", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_OUTPUT("DAC Output"),
SND_SOC_DAPM_AIF_IN("DA_IN1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("DA_IN2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("DA_IN3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("DA_IN4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("DA_IN5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("DA_IN6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AD_OUT1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AD_OUT2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AD_OUT3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AD_OUT57", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AD_OUT68", NULL, 0, SND_SOC_NOPM, 0, 0),
/* Headset path */
SND_SOC_DAPM_SUPPLY("Charge Pump", AB8500_ANACONF5,
AB8500_ANACONF5_ENCPHS, 0, NULL, 0),
SND_SOC_DAPM_DAC("DA1 Enable", "ab8500_0p",
AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA1, 0),
SND_SOC_DAPM_DAC("DA2 Enable", "ab8500_0p",
AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA2, 0),
SND_SOC_DAPM_PGA("HSL Digital Volume", SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_PGA("HSR Digital Volume", SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_DAC("HSL DAC", "ab8500_0p",
AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACHSL, 0),
SND_SOC_DAPM_DAC("HSR DAC", "ab8500_0p",
AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACHSR, 0),
SND_SOC_DAPM_MIXER("HSL DAC Mute", AB8500_MUTECONF,
AB8500_MUTECONF_MUTDACHSL, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("HSR DAC Mute", AB8500_MUTECONF,
AB8500_MUTECONF_MUTDACHSR, 1,
NULL, 0),
SND_SOC_DAPM_DAC("HSL DAC Driver", "ab8500_0p",
AB8500_ANACONF3, AB8500_ANACONF3_ENDRVHSL, 0),
SND_SOC_DAPM_DAC("HSR DAC Driver", "ab8500_0p",
AB8500_ANACONF3, AB8500_ANACONF3_ENDRVHSR, 0),
SND_SOC_DAPM_MIXER("HSL Mute",
AB8500_MUTECONF, AB8500_MUTECONF_MUTHSL, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("HSR Mute",
AB8500_MUTECONF, AB8500_MUTECONF_MUTHSR, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("HSL Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENHSL, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("HSR Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENHSR, 0,
NULL, 0),
SND_SOC_DAPM_PGA("HSL Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_PGA("HSR Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_OUTPUT("Headset Left"),
SND_SOC_DAPM_OUTPUT("Headset Right"),
/* LineOut path */
SND_SOC_DAPM_MUX("LineOut Source",
SND_SOC_NOPM, 0, 0, dapm_lineout_source),
SND_SOC_DAPM_MIXER("LOL Disable HFL",
AB8500_ANACONF4, AB8500_ANACONF4_ENHFL, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("LOR Disable HFR",
AB8500_ANACONF4, AB8500_ANACONF4_ENHFR, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("LOL Enable",
AB8500_ANACONF5, AB8500_ANACONF5_ENLOL, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("LOR Enable",
AB8500_ANACONF5, AB8500_ANACONF5_ENLOR, 0,
NULL, 0),
SND_SOC_DAPM_OUTPUT("LineOut Left"),
SND_SOC_DAPM_OUTPUT("LineOut Right"),
/* Earpiece path */
SND_SOC_DAPM_MUX("Earpiece or LineOut Mono Source",
SND_SOC_NOPM, 0, 0, &dapm_ear_lineout_source),
SND_SOC_DAPM_MIXER("EAR DAC",
AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACEAR, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("EAR Mute",
AB8500_MUTECONF, AB8500_MUTECONF_MUTEAR, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("EAR Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENEAR, 0,
NULL, 0),
SND_SOC_DAPM_OUTPUT("Earpiece"),
/* Handsfree path */
SND_SOC_DAPM_MIXER("DA3 Channel Volume",
AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA3, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DA4 Channel Volume",
AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA4, 0,
NULL, 0),
SND_SOC_DAPM_MUX("Speaker Left Source",
SND_SOC_NOPM, 0, 0, dapm_HFl_select),
SND_SOC_DAPM_MUX("Speaker Right Source",
SND_SOC_NOPM, 0, 0, dapm_HFr_select),
SND_SOC_DAPM_MIXER("HFL DAC", AB8500_DAPATHCONF,
AB8500_DAPATHCONF_ENDACHFL, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("HFR DAC",
AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACHFR, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DA4 or ANC path to HfR",
AB8500_DIGMULTCONF2, AB8500_DIGMULTCONF2_DATOHFREN, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DA3 or ANC path to HfL",
AB8500_DIGMULTCONF2, AB8500_DIGMULTCONF2_DATOHFLEN, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("HFL Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENHFL, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("HFR Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENHFR, 0,
NULL, 0),
SND_SOC_DAPM_OUTPUT("Speaker Left"),
SND_SOC_DAPM_OUTPUT("Speaker Right"),
/* Vibrator path */
SND_SOC_DAPM_INPUT("PWMGEN1"),
SND_SOC_DAPM_INPUT("PWMGEN2"),
SND_SOC_DAPM_MIXER("DA5 Channel Volume",
AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA5, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DA6 Channel Volume",
AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA6, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("VIB1 DAC",
AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACVIB1, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("VIB2 DAC",
AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACVIB2, 0,
NULL, 0),
SND_SOC_DAPM_MUX("Vibra 1 Controller",
SND_SOC_NOPM, 0, 0, dapm_pwm2vib1),
SND_SOC_DAPM_MUX("Vibra 2 Controller",
SND_SOC_NOPM, 0, 0, dapm_pwm2vib2),
SND_SOC_DAPM_MIXER("VIB1 Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENVIB1, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("VIB2 Enable",
AB8500_ANACONF4, AB8500_ANACONF4_ENVIB2, 0,
NULL, 0),
SND_SOC_DAPM_OUTPUT("Vibra 1"),
SND_SOC_DAPM_OUTPUT("Vibra 2"),
/* Mic 1 */
SND_SOC_DAPM_INPUT("Mic 1"),
SND_SOC_DAPM_MUX("Mic 1a or 1b Select",
SND_SOC_NOPM, 0, 0, dapm_mic1ab_mux),
SND_SOC_DAPM_MIXER("MIC1 Mute",
AB8500_ANACONF2, AB8500_ANACONF2_MUTMIC1, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("MIC1A V-AMICx Enable",
AB8500_ANACONF2, AB8500_ANACONF2_ENMIC1, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("MIC1B V-AMICx Enable",
AB8500_ANACONF2, AB8500_ANACONF2_ENMIC1, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("MIC1 ADC",
AB8500_ANACONF3, AB8500_ANACONF3_ENADCMIC, 0,
NULL, 0),
SND_SOC_DAPM_MUX("AD3 Source Select",
SND_SOC_NOPM, 0, 0, dapm_ad3_select),
SND_SOC_DAPM_MIXER("AD3 Channel Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD3 Enable",
AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, 0,
NULL, 0),
/* Mic 2 */
SND_SOC_DAPM_INPUT("Mic 2"),
SND_SOC_DAPM_MIXER("MIC2 Mute",
AB8500_ANACONF2, AB8500_ANACONF2_MUTMIC2, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("MIC2 V-AMICx Enable", AB8500_ANACONF2,
AB8500_ANACONF2_ENMIC2, 0,
NULL, 0),
/* LineIn */
SND_SOC_DAPM_INPUT("LineIn Left"),
SND_SOC_DAPM_INPUT("LineIn Right"),
SND_SOC_DAPM_MIXER("LINL Mute",
AB8500_ANACONF2, AB8500_ANACONF2_MUTLINL, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("LINR Mute",
AB8500_ANACONF2, AB8500_ANACONF2_MUTLINR, 1,
NULL, 0),
SND_SOC_DAPM_MIXER("LINL Enable", AB8500_ANACONF2,
AB8500_ANACONF2_ENLINL, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("LINR Enable", AB8500_ANACONF2,
AB8500_ANACONF2_ENLINR, 0,
NULL, 0),
/* LineIn Bypass path */
SND_SOC_DAPM_MIXER("LINL to HSL Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("LINR to HSR Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
/* LineIn, Mic 2 */
SND_SOC_DAPM_MUX("Mic 2 or LINR Select",
SND_SOC_NOPM, 0, 0, dapm_mic2lr_select),
SND_SOC_DAPM_MIXER("LINL ADC", AB8500_ANACONF3,
AB8500_ANACONF3_ENADCLINL, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("LINR ADC", AB8500_ANACONF3,
AB8500_ANACONF3_ENADCLINR, 0,
NULL, 0),
SND_SOC_DAPM_MUX("AD1 Source Select",
SND_SOC_NOPM, 0, 0, dapm_ad1_select),
SND_SOC_DAPM_MUX("AD2 Source Select",
SND_SOC_NOPM, 0, 0, dapm_ad2_select),
SND_SOC_DAPM_MIXER("AD1 Channel Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD2 Channel Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD12 Enable",
AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD12, 0,
NULL, 0),
/* HD Capture path */
SND_SOC_DAPM_MUX("AD5 Source Select",
SND_SOC_NOPM, 0, 0, dapm_ad5_select),
SND_SOC_DAPM_MUX("AD6 Source Select",
SND_SOC_NOPM, 0, 0, dapm_ad6_select),
SND_SOC_DAPM_MIXER("AD5 Channel Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD6 Channel Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD57 Enable",
AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD68 Enable",
AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0,
NULL, 0),
/* Digital Microphone path */
SND_SOC_DAPM_INPUT("DMic 1"),
SND_SOC_DAPM_INPUT("DMic 2"),
SND_SOC_DAPM_INPUT("DMic 3"),
SND_SOC_DAPM_INPUT("DMic 4"),
SND_SOC_DAPM_INPUT("DMic 5"),
SND_SOC_DAPM_INPUT("DMic 6"),
SND_SOC_DAPM_MIXER("DMIC1",
AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DMIC2",
AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DMIC3",
AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DMIC4",
AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DMIC5",
AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("DMIC6",
AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD4 Channel Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("AD4 Enable",
AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34,
0, NULL, 0),
/* Acoustical Noise Cancellation path */
SND_SOC_DAPM_INPUT("ANC Configure Input"),
SND_SOC_DAPM_OUTPUT("ANC Configure Output"),
SND_SOC_DAPM_MUX("ANC Source",
SND_SOC_NOPM, 0, 0,
dapm_anc_in_select),
SND_SOC_DAPM_SWITCH("ANC",
SND_SOC_NOPM, 0, 0,
dapm_anc_enable),
SND_SOC_DAPM_SWITCH("ANC to Earpiece",
SND_SOC_NOPM, 0, 0,
dapm_anc_ear_mute),
/* Sidetone Filter path */
SND_SOC_DAPM_MUX("Sidetone Left Source",
SND_SOC_NOPM, 0, 0,
dapm_stfir1_in_select),
SND_SOC_DAPM_MUX("Sidetone Right Source",
SND_SOC_NOPM, 0, 0,
dapm_stfir2_in_select),
SND_SOC_DAPM_MIXER("STFIR1 Control",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("STFIR2 Control",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("STFIR1 Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
SND_SOC_DAPM_MIXER("STFIR2 Volume",
SND_SOC_NOPM, 0, 0,
NULL, 0),
};
/*
* DAPM-routes
*/
static const struct snd_soc_dapm_route ab8500_dapm_routes[] = {
/* Power AB8500 audio-block when AD/DA is active */
{"Main Supply", NULL, "V-AUD"},
{"Main Supply", NULL, "audioclk"},
{"Main Supply", NULL, "Audio Power"},
{"Main Supply", NULL, "Audio Analog Power"},
{"DAC", NULL, "ab8500_0p"},
{"DAC", NULL, "Main Supply"},
{"ADC", NULL, "ab8500_0c"},
{"ADC", NULL, "Main Supply"},
/* ANC Configure */
{"ANC Configure Input", NULL, "Main Supply"},
{"ANC Configure Output", NULL, "ANC Configure Input"},
/* AD/DA */
{"ADC", NULL, "ADC Input"},
{"DAC Output", NULL, "DAC"},
/* Powerup charge pump if DA1/2 is in use */
{"DA_IN1", NULL, "ab8500_0p"},
{"DA_IN1", NULL, "Charge Pump"},
{"DA_IN2", NULL, "ab8500_0p"},
{"DA_IN2", NULL, "Charge Pump"},
/* Headset path */
{"DA1 Enable", NULL, "DA_IN1"},
{"DA2 Enable", NULL, "DA_IN2"},
{"HSL Digital Volume", NULL, "DA1 Enable"},
{"HSR Digital Volume", NULL, "DA2 Enable"},
{"HSL DAC", NULL, "HSL Digital Volume"},
{"HSR DAC", NULL, "HSR Digital Volume"},
{"HSL DAC Mute", NULL, "HSL DAC"},
{"HSR DAC Mute", NULL, "HSR DAC"},
{"HSL DAC Driver", NULL, "HSL DAC Mute"},
{"HSR DAC Driver", NULL, "HSR DAC Mute"},
{"HSL Mute", NULL, "HSL DAC Driver"},
{"HSR Mute", NULL, "HSR DAC Driver"},
{"HSL Enable", NULL, "HSL Mute"},
{"HSR Enable", NULL, "HSR Mute"},
{"HSL Volume", NULL, "HSL Enable"},
{"HSR Volume", NULL, "HSR Enable"},
{"Headset Left", NULL, "HSL Volume"},
{"Headset Right", NULL, "HSR Volume"},
/* HF or LineOut path */
{"DA_IN3", NULL, "ab8500_0p"},
{"DA3 Channel Volume", NULL, "DA_IN3"},
{"DA_IN4", NULL, "ab8500_0p"},
{"DA4 Channel Volume", NULL, "DA_IN4"},
{"Speaker Left Source", "Audio Path", "DA3 Channel Volume"},
{"Speaker Right Source", "Audio Path", "DA4 Channel Volume"},
{"DA3 or ANC path to HfL", NULL, "Speaker Left Source"},
{"DA4 or ANC path to HfR", NULL, "Speaker Right Source"},
/* HF path */
{"HFL DAC", NULL, "DA3 or ANC path to HfL"},
{"HFR DAC", NULL, "DA4 or ANC path to HfR"},
{"HFL Enable", NULL, "HFL DAC"},
{"HFR Enable", NULL, "HFR DAC"},
{"Speaker Left", NULL, "HFL Enable"},
{"Speaker Right", NULL, "HFR Enable"},
/* Earpiece path */
{"Earpiece or LineOut Mono Source", "Headset Left",
"HSL Digital Volume"},
{"Earpiece or LineOut Mono Source", "Speaker Left",
"DA3 or ANC path to HfL"},
{"EAR DAC", NULL, "Earpiece or LineOut Mono Source"},
{"EAR Mute", NULL, "EAR DAC"},
{"EAR Enable", NULL, "EAR Mute"},
{"Earpiece", NULL, "EAR Enable"},
/* LineOut path stereo */
{"LineOut Source", "Stereo Path", "HSL DAC Driver"},
{"LineOut Source", "Stereo Path", "HSR DAC Driver"},
/* LineOut path mono */
{"LineOut Source", "Mono Path", "EAR DAC"},
/* LineOut path */
{"LOL Disable HFL", NULL, "LineOut Source"},
{"LOR Disable HFR", NULL, "LineOut Source"},
{"LOL Enable", NULL, "LOL Disable HFL"},
{"LOR Enable", NULL, "LOR Disable HFR"},
{"LineOut Left", NULL, "LOL Enable"},
{"LineOut Right", NULL, "LOR Enable"},
/* Vibrator path */
{"DA_IN5", NULL, "ab8500_0p"},
{"DA5 Channel Volume", NULL, "DA_IN5"},
{"DA_IN6", NULL, "ab8500_0p"},
{"DA6 Channel Volume", NULL, "DA_IN6"},
{"VIB1 DAC", NULL, "DA5 Channel Volume"},
{"VIB2 DAC", NULL, "DA6 Channel Volume"},
{"Vibra 1 Controller", "Audio Path", "VIB1 DAC"},
{"Vibra 2 Controller", "Audio Path", "VIB2 DAC"},
{"Vibra 1 Controller", "PWM Generator", "PWMGEN1"},
{"Vibra 2 Controller", "PWM Generator", "PWMGEN2"},
{"VIB1 Enable", NULL, "Vibra 1 Controller"},
{"VIB2 Enable", NULL, "Vibra 2 Controller"},
{"Vibra 1", NULL, "VIB1 Enable"},
{"Vibra 2", NULL, "VIB2 Enable"},
/* Mic 2 */
{"MIC2 V-AMICx Enable", NULL, "Mic 2"},
/* LineIn */
{"LINL Mute", NULL, "LineIn Left"},
{"LINR Mute", NULL, "LineIn Right"},
{"LINL Enable", NULL, "LINL Mute"},
{"LINR Enable", NULL, "LINR Mute"},
/* LineIn, Mic 2 */
{"Mic 2 or LINR Select", "LineIn Right", "LINR Enable"},
{"Mic 2 or LINR Select", "Mic 2", "MIC2 V-AMICx Enable"},
{"LINL ADC", NULL, "LINL Enable"},
{"LINR ADC", NULL, "Mic 2 or LINR Select"},
{"AD1 Source Select", "LineIn Left", "LINL ADC"},
{"AD2 Source Select", "LineIn Right", "LINR ADC"},
{"AD1 Channel Volume", NULL, "AD1 Source Select"},
{"AD2 Channel Volume", NULL, "AD2 Source Select"},
{"AD12 Enable", NULL, "AD1 Channel Volume"},
{"AD12 Enable", NULL, "AD2 Channel Volume"},
{"AD_OUT1", NULL, "ab8500_0c"},
{"AD_OUT1", NULL, "AD12 Enable"},
{"AD_OUT2", NULL, "ab8500_0c"},
{"AD_OUT2", NULL, "AD12 Enable"},
/* Mic 1 */
{"MIC1 Mute", NULL, "Mic 1"},
{"MIC1A V-AMICx Enable", NULL, "MIC1 Mute"},
{"MIC1B V-AMICx Enable", NULL, "MIC1 Mute"},
{"Mic 1a or 1b Select", "Mic 1a", "MIC1A V-AMICx Enable"},
{"Mic 1a or 1b Select", "Mic 1b", "MIC1B V-AMICx Enable"},
{"MIC1 ADC", NULL, "Mic 1a or 1b Select"},
{"AD3 Source Select", "Mic 1", "MIC1 ADC"},
{"AD3 Channel Volume", NULL, "AD3 Source Select"},
{"AD3 Enable", NULL, "AD3 Channel Volume"},
{"AD_OUT3", NULL, "ab8500_0c"},
{"AD_OUT3", NULL, "AD3 Enable"},
/* HD Capture path */
{"AD5 Source Select", "Mic 2", "LINR ADC"},
{"AD6 Source Select", "Mic 1", "MIC1 ADC"},
{"AD5 Channel Volume", NULL, "AD5 Source Select"},
{"AD6 Channel Volume", NULL, "AD6 Source Select"},
{"AD57 Enable", NULL, "AD5 Channel Volume"},
{"AD68 Enable", NULL, "AD6 Channel Volume"},
{"AD_OUT57", NULL, "ab8500_0c"},
{"AD_OUT57", NULL, "AD57 Enable"},
{"AD_OUT68", NULL, "ab8500_0c"},
{"AD_OUT68", NULL, "AD68 Enable"},
/* Digital Microphone path */
{"DMic 1", NULL, "V-DMIC"},
{"DMic 2", NULL, "V-DMIC"},
{"DMic 3", NULL, "V-DMIC"},
{"DMic 4", NULL, "V-DMIC"},
{"DMic 5", NULL, "V-DMIC"},
{"DMic 6", NULL, "V-DMIC"},
{"AD1 Source Select", NULL, "DMic 1"},
{"AD2 Source Select", NULL, "DMic 2"},
{"AD3 Source Select", NULL, "DMic 3"},
{"AD5 Source Select", NULL, "DMic 5"},
{"AD6 Source Select", NULL, "DMic 6"},
{"AD4 Channel Volume", NULL, "DMic 4"},
{"AD4 Enable", NULL, "AD4 Channel Volume"},
{"AD_OUT4", NULL, "ab8500_0c"},
{"AD_OUT4", NULL, "AD4 Enable"},
/* LineIn Bypass path */
{"LINL to HSL Volume", NULL, "LINL Enable"},
{"LINR to HSR Volume", NULL, "LINR Enable"},
{"HSL DAC Driver", NULL, "LINL to HSL Volume"},
{"HSR DAC Driver", NULL, "LINR to HSR Volume"},
/* ANC path (Acoustic Noise Cancellation) */
{"ANC Source", "Mic 2 / DMic 5", "AD5 Channel Volume"},
{"ANC Source", "Mic 1 / DMic 6", "AD6 Channel Volume"},
{"ANC", "Switch", "ANC Source"},
{"Speaker Left Source", "ANC", "ANC"},
{"Speaker Right Source", "ANC", "ANC"},
{"ANC to Earpiece", "Switch", "ANC"},
{"HSL Digital Volume", NULL, "ANC to Earpiece"},
/* Sidetone Filter path */
{"Sidetone Left Source", "LineIn Left", "AD12 Enable"},
{"Sidetone Left Source", "LineIn Right", "AD12 Enable"},
{"Sidetone Left Source", "Mic 1", "AD3 Enable"},
{"Sidetone Left Source", "Headset Left", "DA_IN1"},
{"Sidetone Right Source", "LineIn Right", "AD12 Enable"},
{"Sidetone Right Source", "Mic 1", "AD3 Enable"},
{"Sidetone Right Source", "DMic 4", "AD4 Enable"},
{"Sidetone Right Source", "Headset Right", "DA_IN2"},
{"STFIR1 Control", NULL, "Sidetone Left Source"},
{"STFIR2 Control", NULL, "Sidetone Right Source"},
{"STFIR1 Volume", NULL, "STFIR1 Control"},
{"STFIR2 Volume", NULL, "STFIR2 Control"},
{"DA1 Enable", NULL, "STFIR1 Volume"},
{"DA2 Enable", NULL, "STFIR2 Volume"},
};
static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1a_vamicx[] = {
{"MIC1A V-AMICx Enable", NULL, "V-AMIC1"},
{"MIC1A V-AMICx Enable", NULL, "V-AMIC2"},
};
static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1b_vamicx[] = {
{"MIC1B V-AMICx Enable", NULL, "V-AMIC1"},
{"MIC1B V-AMICx Enable", NULL, "V-AMIC2"},
};
static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = {
{"MIC2 V-AMICx Enable", NULL, "V-AMIC1"},
{"MIC2 V-AMICx Enable", NULL, "V-AMIC2"},
};
/* ANC FIR-coefficients configuration sequence */
static void anc_fir(struct snd_soc_component *component,
unsigned int bnk, unsigned int par, unsigned int val)
{
if (par == 0 && bnk == 0)
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ANCFIRUPDATE),
BIT(AB8500_ANCCONF1_ANCFIRUPDATE));
snd_soc_component_write(component, AB8500_ANCCONF5, val >> 8 & 0xff);
snd_soc_component_write(component, AB8500_ANCCONF6, val & 0xff);
if (par == AB8500_ANC_FIR_COEFFS - 1 && bnk == 1)
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ANCFIRUPDATE), 0);
}
/* ANC IIR-coefficients configuration sequence */
static void anc_iir(struct snd_soc_component *component, unsigned int bnk,
unsigned int par, unsigned int val)
{
if (par == 0) {
if (bnk == 0) {
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ANCIIRINIT),
BIT(AB8500_ANCCONF1_ANCIIRINIT));
usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY*2);
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ANCIIRINIT), 0);
usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY*2);
} else {
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ANCIIRUPDATE),
BIT(AB8500_ANCCONF1_ANCIIRUPDATE));
}
} else if (par > 3) {
snd_soc_component_write(component, AB8500_ANCCONF7, 0);
snd_soc_component_write(component, AB8500_ANCCONF8, val >> 16 & 0xff);
}
snd_soc_component_write(component, AB8500_ANCCONF7, val >> 8 & 0xff);
snd_soc_component_write(component, AB8500_ANCCONF8, val & 0xff);
if (par == AB8500_ANC_IIR_COEFFS - 1 && bnk == 1)
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ANCIIRUPDATE), 0);
}
/* ANC IIR-/FIR-coefficients configuration sequence */
static void anc_configure(struct snd_soc_component *component,
bool apply_fir, bool apply_iir)
{
struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev);
unsigned int bnk, par, val;
dev_dbg(component->dev, "%s: Enter.\n", __func__);
if (apply_fir)
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ENANC), 0);
snd_soc_component_update_bits(component, AB8500_ANCCONF1,
BIT(AB8500_ANCCONF1_ENANC), BIT(AB8500_ANCCONF1_ENANC));
if (apply_fir)
for (bnk = 0; bnk < AB8500_NR_OF_ANC_COEFF_BANKS; bnk++)
for (par = 0; par < AB8500_ANC_FIR_COEFFS; par++) {
val = snd_soc_component_read(component,
drvdata->anc_fir_values[par]);
anc_fir(component, bnk, par, val);
}
if (apply_iir)
for (bnk = 0; bnk < AB8500_NR_OF_ANC_COEFF_BANKS; bnk++)
for (par = 0; par < AB8500_ANC_IIR_COEFFS; par++) {
val = snd_soc_component_read(component,
drvdata->anc_iir_values[par]);
anc_iir(component, bnk, par, val);
}
dev_dbg(component->dev, "%s: Exit.\n", __func__);
}
/*
* Control-events
*/
static int sid_status_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev);
mutex_lock(&drvdata->ctrl_lock);
ucontrol->value.enumerated.item[0] = drvdata->sid_status;
mutex_unlock(&drvdata->ctrl_lock);
return 0;
}
/* Write sidetone FIR-coefficients configuration sequence */
static int sid_status_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev);
unsigned int param, sidconf, val;
int status = 1;
dev_dbg(component->dev, "%s: Enter\n", __func__);
if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) {
dev_err(component->dev,
"%s: ERROR: This control supports '%s' only!\n",
__func__, enum_sid_state[SID_APPLY_FIR]);
return -EIO;
}
mutex_lock(&drvdata->ctrl_lock);
sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF);
if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) {
if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) {
dev_err(component->dev, "%s: Sidetone busy while off!\n",
__func__);
status = -EPERM;
} else {
status = -EBUSY;
}
goto out;
}
snd_soc_component_write(component, AB8500_SIDFIRADR, 0);
for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) {
val = snd_soc_component_read(component, drvdata->sid_fir_values[param]);
snd_soc_component_write(component, AB8500_SIDFIRCOEF1, val >> 8 & 0xff);
snd_soc_component_write(component, AB8500_SIDFIRCOEF2, val & 0xff);
}
snd_soc_component_update_bits(component, AB8500_SIDFIRADR,
BIT(AB8500_SIDFIRADR_FIRSIDSET),
BIT(AB8500_SIDFIRADR_FIRSIDSET));
snd_soc_component_update_bits(component, AB8500_SIDFIRADR,
BIT(AB8500_SIDFIRADR_FIRSIDSET), 0);
drvdata->sid_status = SID_FIR_CONFIGURED;
out:
mutex_unlock(&drvdata->ctrl_lock);
dev_dbg(component->dev, "%s: Exit\n", __func__);
return status;
}
static int anc_status_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev);
mutex_lock(&drvdata->ctrl_lock);
ucontrol->value.enumerated.item[0] = drvdata->anc_status;
mutex_unlock(&drvdata->ctrl_lock);
return 0;
}
static int anc_status_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev);
struct device *dev = component->dev;
bool apply_fir, apply_iir;
unsigned int req;
int status;
dev_dbg(dev, "%s: Enter.\n", __func__);
mutex_lock(&drvdata->ctrl_lock);
req = ucontrol->value.enumerated.item[0];
if (req >= ARRAY_SIZE(enum_anc_state)) {
status = -EINVAL;
goto cleanup;
}
if (req != ANC_APPLY_FIR_IIR && req != ANC_APPLY_FIR &&
req != ANC_APPLY_IIR) {
dev_err(dev, "%s: ERROR: Unsupported status to set '%s'!\n",
__func__, enum_anc_state[req]);
status = -EINVAL;
goto cleanup;
}
apply_fir = req == ANC_APPLY_FIR || req == ANC_APPLY_FIR_IIR;
apply_iir = req == ANC_APPLY_IIR || req == ANC_APPLY_FIR_IIR;
status = snd_soc_dapm_force_enable_pin(dapm, "ANC Configure Input");
if (status < 0) {
dev_err(dev,
"%s: ERROR: Failed to enable power (status = %d)!\n",
__func__, status);
goto cleanup;
}
snd_soc_dapm_sync(dapm);
anc_configure(component, apply_fir, apply_iir);
if (apply_fir) {
if (drvdata->anc_status == ANC_IIR_CONFIGURED)
drvdata->anc_status = ANC_FIR_IIR_CONFIGURED;
else if (drvdata->anc_status != ANC_FIR_IIR_CONFIGURED)
drvdata->anc_status = ANC_FIR_CONFIGURED;
}
if (apply_iir) {
if (drvdata->anc_status == ANC_FIR_CONFIGURED)
drvdata->anc_status = ANC_FIR_IIR_CONFIGURED;
else if (drvdata->anc_status != ANC_FIR_IIR_CONFIGURED)
drvdata->anc_status = ANC_IIR_CONFIGURED;
}
status = snd_soc_dapm_disable_pin(dapm, "ANC Configure Input");
snd_soc_dapm_sync(dapm);
cleanup:
mutex_unlock(&drvdata->ctrl_lock);
if (status < 0)
dev_err(dev, "%s: Unable to configure ANC! (status = %d)\n",
__func__, status);
dev_dbg(dev, "%s: Exit.\n", __func__);
return (status < 0) ? status : 1;
}
static int filter_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct filter_control *fc =
(struct filter_control *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = fc->count;
uinfo->value.integer.min = fc->min;
uinfo->value.integer.max = fc->max;
return 0;
}
static int filter_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct ab8500_codec_drvdata *drvdata = snd_soc_component_get_drvdata(component);
struct filter_control *fc =
(struct filter_control *)kcontrol->private_value;
unsigned int i;
mutex_lock(&drvdata->ctrl_lock);
for (i = 0; i < fc->count; i++)
ucontrol->value.integer.value[i] = fc->value[i];
mutex_unlock(&drvdata->ctrl_lock);
return 0;
}
static int filter_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct ab8500_codec_drvdata *drvdata = snd_soc_component_get_drvdata(component);
struct filter_control *fc =
(struct filter_control *)kcontrol->private_value;
unsigned int i;
mutex_lock(&drvdata->ctrl_lock);
for (i = 0; i < fc->count; i++)
fc->value[i] = ucontrol->value.integer.value[i];
mutex_unlock(&drvdata->ctrl_lock);
return 0;
}
/*
* Controls - Non-DAPM ASoC
*/
static DECLARE_TLV_DB_SCALE(adx_dig_gain_tlv, -3200, 100, 1);
/* -32dB = Mute */
static DECLARE_TLV_DB_SCALE(dax_dig_gain_tlv, -6300, 100, 1);
/* -63dB = Mute */
static DECLARE_TLV_DB_SCALE(hs_ear_dig_gain_tlv, -100, 100, 1);
/* -1dB = Mute */
static const DECLARE_TLV_DB_RANGE(hs_gain_tlv,
0, 3, TLV_DB_SCALE_ITEM(-3200, 400, 0),
4, 15, TLV_DB_SCALE_ITEM(-1800, 200, 0)
);
static DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0);
static DECLARE_TLV_DB_SCALE(lin_gain_tlv, -1000, 200, 0);
static DECLARE_TLV_DB_SCALE(lin2hs_gain_tlv, -3800, 200, 1);
/* -38dB = Mute */
static const char * const enum_hsfadspeed[] = {"2ms", "0.5ms", "10.6ms",
"5ms"};
static SOC_ENUM_SINGLE_DECL(soc_enum_hsfadspeed,
AB8500_DIGMICCONF, AB8500_DIGMICCONF_HSFADSPEED, enum_hsfadspeed);
static const char * const enum_envdetthre[] = {
"250mV", "300mV", "350mV", "400mV",
"450mV", "500mV", "550mV", "600mV",
"650mV", "700mV", "750mV", "800mV",
"850mV", "900mV", "950mV", "1.00V" };
static SOC_ENUM_SINGLE_DECL(soc_enum_envdeththre,
AB8500_ENVCPCONF, AB8500_ENVCPCONF_ENVDETHTHRE, enum_envdetthre);
static SOC_ENUM_SINGLE_DECL(soc_enum_envdetlthre,
AB8500_ENVCPCONF, AB8500_ENVCPCONF_ENVDETLTHRE, enum_envdetthre);
static const char * const enum_envdettime[] = {
"26.6us", "53.2us", "106us", "213us",
"426us", "851us", "1.70ms", "3.40ms",
"6.81ms", "13.6ms", "27.2ms", "54.5ms",
"109ms", "218ms", "436ms", "872ms" };
static SOC_ENUM_SINGLE_DECL(soc_enum_envdettime,
AB8500_SIGENVCONF, AB8500_SIGENVCONF_ENVDETTIME, enum_envdettime);
static const char * const enum_sinc31[] = {"Sinc 3", "Sinc 1"};
static SOC_ENUM_SINGLE_DECL(soc_enum_hsesinc, AB8500_HSLEARDIGGAIN,
AB8500_HSLEARDIGGAIN_HSSINC1, enum_sinc31);
static const char * const enum_fadespeed[] = {"1ms", "4ms", "8ms", "16ms"};
static SOC_ENUM_SINGLE_DECL(soc_enum_fadespeed, AB8500_HSRDIGGAIN,
AB8500_HSRDIGGAIN_FADESPEED, enum_fadespeed);
/* Earpiece */
static const char * const enum_lowpow[] = {"Normal", "Low Power"};
static SOC_ENUM_SINGLE_DECL(soc_enum_eardaclowpow, AB8500_ANACONF1,
AB8500_ANACONF1_EARDACLOWPOW, enum_lowpow);
static SOC_ENUM_SINGLE_DECL(soc_enum_eardrvlowpow, AB8500_ANACONF1,
AB8500_ANACONF1_EARDRVLOWPOW, enum_lowpow);
static const char * const enum_av_mode[] = {"Audio", "Voice"};
static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12voice, AB8500_ADFILTCONF,
AB8500_ADFILTCONF_AD1VOICE, AB8500_ADFILTCONF_AD2VOICE, enum_av_mode);
static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34voice, AB8500_ADFILTCONF,
AB8500_ADFILTCONF_AD3VOICE, AB8500_ADFILTCONF_AD4VOICE, enum_av_mode);
/* DA */
static SOC_ENUM_SINGLE_DECL(soc_enum_da12voice,
AB8500_DASLOTCONF1, AB8500_DASLOTCONF1_DA12VOICE,
enum_av_mode);
static SOC_ENUM_SINGLE_DECL(soc_enum_da34voice,
AB8500_DASLOTCONF3, AB8500_DASLOTCONF3_DA34VOICE,
enum_av_mode);
static SOC_ENUM_SINGLE_DECL(soc_enum_da56voice,
AB8500_DASLOTCONF5, AB8500_DASLOTCONF5_DA56VOICE,
enum_av_mode);
static const char * const enum_da2hslr[] = {"Sidetone", "Audio Path"};
static SOC_ENUM_DOUBLE_DECL(soc_enum_da2hslr, AB8500_DIGMULTCONF1,
AB8500_DIGMULTCONF1_DATOHSLEN,
AB8500_DIGMULTCONF1_DATOHSREN, enum_da2hslr);
static const char * const enum_sinc53[] = {"Sinc 5", "Sinc 3"};
static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic12sinc, AB8500_DMICFILTCONF,
AB8500_DMICFILTCONF_DMIC1SINC3,
AB8500_DMICFILTCONF_DMIC2SINC3, enum_sinc53);
static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic34sinc, AB8500_DMICFILTCONF,
AB8500_DMICFILTCONF_DMIC3SINC3,
AB8500_DMICFILTCONF_DMIC4SINC3, enum_sinc53);
static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic56sinc, AB8500_DMICFILTCONF,
AB8500_DMICFILTCONF_DMIC5SINC3,
AB8500_DMICFILTCONF_DMIC6SINC3, enum_sinc53);
/* Digital interface - DA from slot mapping */
static const char * const enum_da_from_slot_map[] = {"SLOT0",
"SLOT1",
"SLOT2",
"SLOT3",
"SLOT4",
"SLOT5",
"SLOT6",
"SLOT7",
"SLOT8",
"SLOT9",
"SLOT10",
"SLOT11",
"SLOT12",
"SLOT13",
"SLOT14",
"SLOT15",
"SLOT16",
"SLOT17",
"SLOT18",
"SLOT19",
"SLOT20",
"SLOT21",
"SLOT22",
"SLOT23",
"SLOT24",
"SLOT25",
"SLOT26",
"SLOT27",
"SLOT28",
"SLOT29",
"SLOT30",
"SLOT31"};
static SOC_ENUM_SINGLE_DECL(soc_enum_da1slotmap,
AB8500_DASLOTCONF1, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da2slotmap,
AB8500_DASLOTCONF2, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da3slotmap,
AB8500_DASLOTCONF3, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da4slotmap,
AB8500_DASLOTCONF4, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da5slotmap,
AB8500_DASLOTCONF5, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da6slotmap,
AB8500_DASLOTCONF6, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da7slotmap,
AB8500_DASLOTCONF7, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_da8slotmap,
AB8500_DASLOTCONF8, AB8500_DASLOTCONFX_SLTODAX_SHIFT,
enum_da_from_slot_map);
/* Digital interface - AD to slot mapping */
static const char * const enum_ad_to_slot_map[] = {"AD_OUT1",
"AD_OUT2",
"AD_OUT3",
"AD_OUT4",
"AD_OUT5",
"AD_OUT6",
"AD_OUT7",
"AD_OUT8",
"zeroes",
"zeroes",
"zeroes",
"zeroes",
"tristate",
"tristate",
"tristate",
"tristate"};
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot0map,
AB8500_ADSLOTSEL1, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot1map,
AB8500_ADSLOTSEL1, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot2map,
AB8500_ADSLOTSEL2, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot3map,
AB8500_ADSLOTSEL2, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot4map,
AB8500_ADSLOTSEL3, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot5map,
AB8500_ADSLOTSEL3, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot6map,
AB8500_ADSLOTSEL4, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot7map,
AB8500_ADSLOTSEL4, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot8map,
AB8500_ADSLOTSEL5, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot9map,
AB8500_ADSLOTSEL5, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot10map,
AB8500_ADSLOTSEL6, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot11map,
AB8500_ADSLOTSEL6, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot12map,
AB8500_ADSLOTSEL7, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot13map,
AB8500_ADSLOTSEL7, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot14map,
AB8500_ADSLOTSEL8, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot15map,
AB8500_ADSLOTSEL8, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot16map,
AB8500_ADSLOTSEL9, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot17map,
AB8500_ADSLOTSEL9, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot18map,
AB8500_ADSLOTSEL10, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot19map,
AB8500_ADSLOTSEL10, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot20map,
AB8500_ADSLOTSEL11, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot21map,
AB8500_ADSLOTSEL11, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot22map,
AB8500_ADSLOTSEL12, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot23map,
AB8500_ADSLOTSEL12, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot24map,
AB8500_ADSLOTSEL13, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot25map,
AB8500_ADSLOTSEL13, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot26map,
AB8500_ADSLOTSEL14, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot27map,
AB8500_ADSLOTSEL14, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot28map,
AB8500_ADSLOTSEL15, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot29map,
AB8500_ADSLOTSEL15, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot30map,
AB8500_ADSLOTSEL16, AB8500_ADSLOTSELX_EVEN_SHIFT,
enum_ad_to_slot_map);
static SOC_ENUM_SINGLE_DECL(soc_enum_adslot31map,
AB8500_ADSLOTSEL16, AB8500_ADSLOTSELX_ODD_SHIFT,
enum_ad_to_slot_map);
/* Digital interface - Burst mode */
static const char * const enum_mask[] = {"Unmasked", "Masked"};
static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomask,
AB8500_FIFOCONF1, AB8500_FIFOCONF1_BFIFOMASK,
enum_mask);
static const char * const enum_bitclk0[] = {"19_2_MHz", "38_4_MHz"};
static SOC_ENUM_SINGLE_DECL(soc_enum_bfifo19m2,
AB8500_FIFOCONF1, AB8500_FIFOCONF1_BFIFO19M2,
enum_bitclk0);
static const char * const enum_slavemaster[] = {"Slave", "Master"};
static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast,
AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT,
enum_slavemaster);
/* Sidetone */
static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state);
/* ANC */
static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state);
static struct snd_kcontrol_new ab8500_ctrls[] = {
/* Charge pump */
SOC_ENUM("Charge Pump High Threshold For Low Voltage",
soc_enum_envdeththre),
SOC_ENUM("Charge Pump Low Threshold For Low Voltage",
soc_enum_envdetlthre),
SOC_SINGLE("Charge Pump Envelope Detection Switch",
AB8500_SIGENVCONF, AB8500_SIGENVCONF_ENVDETCPEN,
1, 0),
SOC_ENUM("Charge Pump Envelope Detection Decay Time",
soc_enum_envdettime),
/* Headset */
SOC_ENUM("Headset Mode", soc_enum_da12voice),
SOC_SINGLE("Headset High Pass Switch",
AB8500_ANACONF1, AB8500_ANACONF1_HSHPEN,
1, 0),
SOC_SINGLE("Headset Low Power Switch",
AB8500_ANACONF1, AB8500_ANACONF1_HSLOWPOW,
1, 0),
SOC_SINGLE("Headset DAC Low Power Switch",
AB8500_ANACONF1, AB8500_ANACONF1_DACLOWPOW1,
1, 0),
SOC_SINGLE("Headset DAC Drv Low Power Switch",
AB8500_ANACONF1, AB8500_ANACONF1_DACLOWPOW0,
1, 0),
SOC_ENUM("Headset Fade Speed", soc_enum_hsfadspeed),
SOC_ENUM("Headset Source", soc_enum_da2hslr),
SOC_ENUM("Headset Filter", soc_enum_hsesinc),
SOC_DOUBLE_R_TLV("Headset Master Volume",
AB8500_DADIGGAIN1, AB8500_DADIGGAIN2,
0, AB8500_DADIGGAINX_DAXGAIN_MAX, 1, dax_dig_gain_tlv),
SOC_DOUBLE_R_TLV("Headset Digital Volume",
AB8500_HSLEARDIGGAIN, AB8500_HSRDIGGAIN,
0, AB8500_HSLEARDIGGAIN_HSLDGAIN_MAX, 1, hs_ear_dig_gain_tlv),
SOC_DOUBLE_TLV("Headset Volume",
AB8500_ANAGAIN3,
AB8500_ANAGAIN3_HSLGAIN, AB8500_ANAGAIN3_HSRGAIN,
AB8500_ANAGAIN3_HSXGAIN_MAX, 1, hs_gain_tlv),
/* Earpiece */
SOC_ENUM("Earpiece DAC Mode",
soc_enum_eardaclowpow),
SOC_ENUM("Earpiece DAC Drv Mode",
soc_enum_eardrvlowpow),
/* HandsFree */
SOC_ENUM("HF Mode", soc_enum_da34voice),
SOC_SINGLE("HF and Headset Swap Switch",
AB8500_DASLOTCONF1, AB8500_DASLOTCONF1_SWAPDA12_34,
1, 0),
SOC_DOUBLE("HF Low EMI Mode Switch",
AB8500_CLASSDCONF1,
AB8500_CLASSDCONF1_HFLSWAPEN, AB8500_CLASSDCONF1_HFRSWAPEN,
1, 0),
SOC_DOUBLE("HF FIR Bypass Switch",
AB8500_CLASSDCONF2,
AB8500_CLASSDCONF2_FIRBYP0, AB8500_CLASSDCONF2_FIRBYP1,
1, 0),
SOC_DOUBLE("HF High Volume Switch",
AB8500_CLASSDCONF2,
AB8500_CLASSDCONF2_HIGHVOLEN0, AB8500_CLASSDCONF2_HIGHVOLEN1,
1, 0),
SOC_SINGLE("HF L and R Bridge Switch",
AB8500_CLASSDCONF1, AB8500_CLASSDCONF1_PARLHF,
1, 0),
SOC_DOUBLE_R_TLV("HF Master Volume",
AB8500_DADIGGAIN3, AB8500_DADIGGAIN4,
0, AB8500_DADIGGAINX_DAXGAIN_MAX, 1, dax_dig_gain_tlv),
/* Vibra */
SOC_DOUBLE("Vibra High Volume Switch",
AB8500_CLASSDCONF2,
AB8500_CLASSDCONF2_HIGHVOLEN2, AB8500_CLASSDCONF2_HIGHVOLEN3,
1, 0),
SOC_DOUBLE("Vibra Low EMI Mode Switch",
AB8500_CLASSDCONF1,
AB8500_CLASSDCONF1_VIB1SWAPEN, AB8500_CLASSDCONF1_VIB2SWAPEN,
1, 0),
SOC_DOUBLE("Vibra FIR Bypass Switch",
AB8500_CLASSDCONF2,
AB8500_CLASSDCONF2_FIRBYP2, AB8500_CLASSDCONF2_FIRBYP3,
1, 0),
SOC_ENUM("Vibra Mode", soc_enum_da56voice),
SOC_DOUBLE_R("Vibra PWM Duty Cycle N",
AB8500_PWMGENCONF3, AB8500_PWMGENCONF5,
AB8500_PWMGENCONFX_PWMVIBXDUTCYC,
AB8500_PWMGENCONFX_PWMVIBXDUTCYC_MAX, 0),
SOC_DOUBLE_R("Vibra PWM Duty Cycle P",
AB8500_PWMGENCONF2, AB8500_PWMGENCONF4,
AB8500_PWMGENCONFX_PWMVIBXDUTCYC,
AB8500_PWMGENCONFX_PWMVIBXDUTCYC_MAX, 0),
SOC_SINGLE("Vibra 1 and 2 Bridge Switch",
AB8500_CLASSDCONF1, AB8500_CLASSDCONF1_PARLVIB,
1, 0),
SOC_DOUBLE_R_TLV("Vibra Master Volume",
AB8500_DADIGGAIN5, AB8500_DADIGGAIN6,
0, AB8500_DADIGGAINX_DAXGAIN_MAX, 1, dax_dig_gain_tlv),
/* HandsFree, Vibra */
SOC_SINGLE("ClassD High Pass Volume",
AB8500_CLASSDCONF3, AB8500_CLASSDCONF3_DITHHPGAIN,
AB8500_CLASSDCONF3_DITHHPGAIN_MAX, 0),
SOC_SINGLE("ClassD White Volume",
AB8500_CLASSDCONF3, AB8500_CLASSDCONF3_DITHWGAIN,
AB8500_CLASSDCONF3_DITHWGAIN_MAX, 0),
/* Mic 1, Mic 2, LineIn */
SOC_DOUBLE_R_TLV("Mic Master Volume",
AB8500_ADDIGGAIN3, AB8500_ADDIGGAIN4,
0, AB8500_ADDIGGAINX_ADXGAIN_MAX, 1, adx_dig_gain_tlv),
/* Mic 1 */
SOC_SINGLE_TLV("Mic 1",
AB8500_ANAGAIN1,
AB8500_ANAGAINX_MICXGAIN,
AB8500_ANAGAINX_MICXGAIN_MAX, 0, mic_gain_tlv),
SOC_SINGLE("Mic 1 Low Power Switch",
AB8500_ANAGAIN1, AB8500_ANAGAINX_LOWPOWMICX,
1, 0),
/* Mic 2 */
SOC_DOUBLE("Mic High Pass Switch",
AB8500_ADFILTCONF,
AB8500_ADFILTCONF_AD3NH, AB8500_ADFILTCONF_AD4NH,
1, 1),
SOC_ENUM("Mic Mode", soc_enum_ad34voice),
SOC_ENUM("Mic Filter", soc_enum_dmic34sinc),
SOC_SINGLE_TLV("Mic 2",
AB8500_ANAGAIN2,
AB8500_ANAGAINX_MICXGAIN,
AB8500_ANAGAINX_MICXGAIN_MAX, 0, mic_gain_tlv),
SOC_SINGLE("Mic 2 Low Power Switch",
AB8500_ANAGAIN2, AB8500_ANAGAINX_LOWPOWMICX,
1, 0),
/* LineIn */
SOC_DOUBLE("LineIn High Pass Switch",
AB8500_ADFILTCONF,
AB8500_ADFILTCONF_AD1NH, AB8500_ADFILTCONF_AD2NH,
1, 1),
SOC_ENUM("LineIn Filter", soc_enum_dmic12sinc),
SOC_ENUM("LineIn Mode", soc_enum_ad12voice),
SOC_DOUBLE_R_TLV("LineIn Master Volume",
AB8500_ADDIGGAIN1, AB8500_ADDIGGAIN2,
0, AB8500_ADDIGGAINX_ADXGAIN_MAX, 1, adx_dig_gain_tlv),
SOC_DOUBLE_TLV("LineIn",
AB8500_ANAGAIN4,
AB8500_ANAGAIN4_LINLGAIN, AB8500_ANAGAIN4_LINRGAIN,
AB8500_ANAGAIN4_LINXGAIN_MAX, 0, lin_gain_tlv),
SOC_DOUBLE_R_TLV("LineIn to Headset Volume",
AB8500_DIGLINHSLGAIN, AB8500_DIGLINHSRGAIN,
AB8500_DIGLINHSXGAIN_LINTOHSXGAIN,
AB8500_DIGLINHSXGAIN_LINTOHSXGAIN_MAX,
1, lin2hs_gain_tlv),
/* DMic */
SOC_ENUM("DMic Filter", soc_enum_dmic56sinc),
SOC_DOUBLE_R_TLV("DMic Master Volume",
AB8500_ADDIGGAIN5, AB8500_ADDIGGAIN6,
0, AB8500_ADDIGGAINX_ADXGAIN_MAX, 1, adx_dig_gain_tlv),
/* Digital gains */
SOC_ENUM("Digital Gain Fade Speed", soc_enum_fadespeed),
/* Analog loopback */
SOC_DOUBLE_R_TLV("Analog Loopback Volume",
AB8500_ADDIGLOOPGAIN1, AB8500_ADDIGLOOPGAIN2,
0, AB8500_ADDIGLOOPGAINX_ADXLBGAIN_MAX, 1, dax_dig_gain_tlv),
/* Digital interface - DA from slot mapping */
SOC_ENUM("Digital Interface DA 1 From Slot Map", soc_enum_da1slotmap),
SOC_ENUM("Digital Interface DA 2 From Slot Map", soc_enum_da2slotmap),
SOC_ENUM("Digital Interface DA 3 From Slot Map", soc_enum_da3slotmap),
SOC_ENUM("Digital Interface DA 4 From Slot Map", soc_enum_da4slotmap),
SOC_ENUM("Digital Interface DA 5 From Slot Map", soc_enum_da5slotmap),
SOC_ENUM("Digital Interface DA 6 From Slot Map", soc_enum_da6slotmap),
SOC_ENUM("Digital Interface DA 7 From Slot Map", soc_enum_da7slotmap),
SOC_ENUM("Digital Interface DA 8 From Slot Map", soc_enum_da8slotmap),
/* Digital interface - AD to slot mapping */
SOC_ENUM("Digital Interface AD To Slot 0 Map", soc_enum_adslot0map),
SOC_ENUM("Digital Interface AD To Slot 1 Map", soc_enum_adslot1map),
SOC_ENUM("Digital Interface AD To Slot 2 Map", soc_enum_adslot2map),
SOC_ENUM("Digital Interface AD To Slot 3 Map", soc_enum_adslot3map),
SOC_ENUM("Digital Interface AD To Slot 4 Map", soc_enum_adslot4map),
SOC_ENUM("Digital Interface AD To Slot 5 Map", soc_enum_adslot5map),
SOC_ENUM("Digital Interface AD To Slot 6 Map", soc_enum_adslot6map),
SOC_ENUM("Digital Interface AD To Slot 7 Map", soc_enum_adslot7map),
SOC_ENUM("Digital Interface AD To Slot 8 Map", soc_enum_adslot8map),
SOC_ENUM("Digital Interface AD To Slot 9 Map", soc_enum_adslot9map),
SOC_ENUM("Digital Interface AD To Slot 10 Map", soc_enum_adslot10map),
SOC_ENUM("Digital Interface AD To Slot 11 Map", soc_enum_adslot11map),
SOC_ENUM("Digital Interface AD To Slot 12 Map", soc_enum_adslot12map),
SOC_ENUM("Digital Interface AD To Slot 13 Map", soc_enum_adslot13map),
SOC_ENUM("Digital Interface AD To Slot 14 Map", soc_enum_adslot14map),
SOC_ENUM("Digital Interface AD To Slot 15 Map", soc_enum_adslot15map),
SOC_ENUM("Digital Interface AD To Slot 16 Map", soc_enum_adslot16map),
SOC_ENUM("Digital Interface AD To Slot 17 Map", soc_enum_adslot17map),
SOC_ENUM("Digital Interface AD To Slot 18 Map", soc_enum_adslot18map),
SOC_ENUM("Digital Interface AD To Slot 19 Map", soc_enum_adslot19map),
SOC_ENUM("Digital Interface AD To Slot 20 Map", soc_enum_adslot20map),
SOC_ENUM("Digital Interface AD To Slot 21 Map", soc_enum_adslot21map),
SOC_ENUM("Digital Interface AD To Slot 22 Map", soc_enum_adslot22map),
SOC_ENUM("Digital Interface AD To Slot 23 Map", soc_enum_adslot23map),
SOC_ENUM("Digital Interface AD To Slot 24 Map", soc_enum_adslot24map),
SOC_ENUM("Digital Interface AD To Slot 25 Map", soc_enum_adslot25map),
SOC_ENUM("Digital Interface AD To Slot 26 Map", soc_enum_adslot26map),
SOC_ENUM("Digital Interface AD To Slot 27 Map", soc_enum_adslot27map),
SOC_ENUM("Digital Interface AD To Slot 28 Map", soc_enum_adslot28map),
SOC_ENUM("Digital Interface AD To Slot 29 Map", soc_enum_adslot29map),
SOC_ENUM("Digital Interface AD To Slot 30 Map", soc_enum_adslot30map),
SOC_ENUM("Digital Interface AD To Slot 31 Map", soc_enum_adslot31map),
/* Digital interface - Loopback */
SOC_SINGLE("Digital Interface AD 1 Loopback Switch",
AB8500_DASLOTCONF1, AB8500_DASLOTCONF1_DAI7TOADO1,
1, 0),
SOC_SINGLE("Digital Interface AD 2 Loopback Switch",
AB8500_DASLOTCONF2, AB8500_DASLOTCONF2_DAI8TOADO2,
1, 0),
SOC_SINGLE("Digital Interface AD 3 Loopback Switch",
AB8500_DASLOTCONF3, AB8500_DASLOTCONF3_DAI7TOADO3,
1, 0),
SOC_SINGLE("Digital Interface AD 4 Loopback Switch",
AB8500_DASLOTCONF4, AB8500_DASLOTCONF4_DAI8TOADO4,
1, 0),
SOC_SINGLE("Digital Interface AD 5 Loopback Switch",
AB8500_DASLOTCONF5, AB8500_DASLOTCONF5_DAI7TOADO5,
1, 0),
SOC_SINGLE("Digital Interface AD 6 Loopback Switch",
AB8500_DASLOTCONF6, AB8500_DASLOTCONF6_DAI8TOADO6,
1, 0),
SOC_SINGLE("Digital Interface AD 7 Loopback Switch",
AB8500_DASLOTCONF7, AB8500_DASLOTCONF7_DAI8TOADO7,
1, 0),
SOC_SINGLE("Digital Interface AD 8 Loopback Switch",
AB8500_DASLOTCONF8, AB8500_DASLOTCONF8_DAI7TOADO8,
1, 0),
/* Digital interface - Burst FIFO */
SOC_SINGLE("Digital Interface 0 FIFO Enable Switch",
AB8500_DIGIFCONF3, AB8500_DIGIFCONF3_IF0BFIFOEN,
1, 0),
SOC_ENUM("Burst FIFO Mask", soc_enum_bfifomask),
SOC_ENUM("Burst FIFO Bit-clock Frequency", soc_enum_bfifo19m2),
SOC_SINGLE("Burst FIFO Threshold",
AB8500_FIFOCONF1, AB8500_FIFOCONF1_BFIFOINT_SHIFT,
AB8500_FIFOCONF1_BFIFOINT_MAX, 0),
SOC_SINGLE("Burst FIFO Length",
AB8500_FIFOCONF2, AB8500_FIFOCONF2_BFIFOTX_SHIFT,
AB8500_FIFOCONF2_BFIFOTX_MAX, 0),
SOC_SINGLE("Burst FIFO EOS Extra Slots",
AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOEXSL_SHIFT,
AB8500_FIFOCONF3_BFIFOEXSL_MAX, 0),
SOC_SINGLE("Burst FIFO FS Extra Bit-clocks",
AB8500_FIFOCONF3, AB8500_FIFOCONF3_PREBITCLK0_SHIFT,
AB8500_FIFOCONF3_PREBITCLK0_MAX, 0),
SOC_ENUM("Burst FIFO Interface Mode", soc_enum_bfifomast),
SOC_SINGLE("Burst FIFO Interface Switch",
AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFORUN_SHIFT,
1, 0),
SOC_SINGLE("Burst FIFO Switch Frame Number",
AB8500_FIFOCONF4, AB8500_FIFOCONF4_BFIFOFRAMSW_SHIFT,
AB8500_FIFOCONF4_BFIFOFRAMSW_MAX, 0),
SOC_SINGLE("Burst FIFO Wake Up Delay",
AB8500_FIFOCONF5, AB8500_FIFOCONF5_BFIFOWAKEUP_SHIFT,
AB8500_FIFOCONF5_BFIFOWAKEUP_MAX, 0),
SOC_SINGLE("Burst FIFO Samples In FIFO",
AB8500_FIFOCONF6, AB8500_FIFOCONF6_BFIFOSAMPLE_SHIFT,
AB8500_FIFOCONF6_BFIFOSAMPLE_MAX, 0),
/* ANC */
SOC_ENUM_EXT("ANC Status", soc_enum_ancstate,
anc_status_control_get, anc_status_control_put),
SOC_SINGLE_XR_SX("ANC Warp Delay Shift",
AB8500_ANCCONF2, 1, AB8500_ANCCONF2_SHIFT,
AB8500_ANCCONF2_MIN, AB8500_ANCCONF2_MAX, 0),
SOC_SINGLE_XR_SX("ANC FIR Output Shift",
AB8500_ANCCONF3, 1, AB8500_ANCCONF3_SHIFT,
AB8500_ANCCONF3_MIN, AB8500_ANCCONF3_MAX, 0),
SOC_SINGLE_XR_SX("ANC IIR Output Shift",
AB8500_ANCCONF4, 1, AB8500_ANCCONF4_SHIFT,
AB8500_ANCCONF4_MIN, AB8500_ANCCONF4_MAX, 0),
SOC_SINGLE_XR_SX("ANC Warp Delay",
AB8500_ANCCONF9, 2, AB8500_ANC_WARP_DELAY_SHIFT,
AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0),
/* Sidetone */
SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate,
sid_status_control_get, sid_status_control_put),
SOC_SINGLE_STROBE("Sidetone Reset",
AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0),
};
static struct snd_kcontrol_new ab8500_filter_controls[] = {
AB8500_FILTER_CONTROL("ANC FIR Coefficients", AB8500_ANC_FIR_COEFFS,
AB8500_ANC_FIR_COEFF_MIN, AB8500_ANC_FIR_COEFF_MAX),
AB8500_FILTER_CONTROL("ANC IIR Coefficients", AB8500_ANC_IIR_COEFFS,
AB8500_ANC_IIR_COEFF_MIN, AB8500_ANC_IIR_COEFF_MAX),
AB8500_FILTER_CONTROL("Sidetone FIR Coefficients",
AB8500_SID_FIR_COEFFS, AB8500_SID_FIR_COEFF_MIN,
AB8500_SID_FIR_COEFF_MAX)
};
enum ab8500_filter {
AB8500_FILTER_ANC_FIR = 0,
AB8500_FILTER_ANC_IIR = 1,
AB8500_FILTER_SID_FIR = 2,
};
/*
* Extended interface for codec-driver
*/
static int ab8500_audio_init_audioblock(struct snd_soc_component *component)
{
int status;
dev_dbg(component->dev, "%s: Enter.\n", __func__);
/* Reset audio-registers and disable 32kHz-clock output 2 */
status = ab8500_sysctrl_write(AB8500_STW4500CTRL3,
AB8500_STW4500CTRL3_CLK32KOUT2DIS |
AB8500_STW4500CTRL3_RESETAUDN,
AB8500_STW4500CTRL3_RESETAUDN);
if (status < 0)
return status;
return 0;
}
static int ab8500_audio_setup_mics(struct snd_soc_component *component,
struct amic_settings *amics)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
u8 value8;
unsigned int value;
int status;
const struct snd_soc_dapm_route *route;
dev_dbg(component->dev, "%s: Enter.\n", __func__);
/* Set DMic-clocks to outputs */
status = abx500_get_register_interruptible(component->dev, AB8500_MISC,
AB8500_GPIO_DIR4_REG,
&value8);
if (status < 0)
return status;
value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT |
GPIO31_DIR_OUTPUT;
status = abx500_set_register_interruptible(component->dev,
AB8500_MISC,
AB8500_GPIO_DIR4_REG,
value);
if (status < 0)
return status;
/* Attach regulators to AMic DAPM-paths */
dev_dbg(component->dev, "%s: Mic 1a regulator: %s\n", __func__,
amic_micbias_str(amics->mic1a_micbias));
route = &ab8500_dapm_routes_mic1a_vamicx[amics->mic1a_micbias];
status = snd_soc_dapm_add_routes(dapm, route, 1);
dev_dbg(component->dev, "%s: Mic 1b regulator: %s\n", __func__,
amic_micbias_str(amics->mic1b_micbias));
route = &ab8500_dapm_routes_mic1b_vamicx[amics->mic1b_micbias];
status |= snd_soc_dapm_add_routes(dapm, route, 1);
dev_dbg(component->dev, "%s: Mic 2 regulator: %s\n", __func__,
amic_micbias_str(amics->mic2_micbias));
route = &ab8500_dapm_routes_mic2_vamicx[amics->mic2_micbias];
status |= snd_soc_dapm_add_routes(dapm, route, 1);
if (status < 0) {
dev_err(component->dev,
"%s: Failed to add AMic-regulator DAPM-routes (%d).\n",
__func__, status);
return status;
}
/* Set AMic-configuration */
dev_dbg(component->dev, "%s: Mic 1 mic-type: %s\n", __func__,
amic_type_str(amics->mic1_type));
snd_soc_component_update_bits(component, AB8500_ANAGAIN1, AB8500_ANAGAINX_ENSEMICX,
amics->mic1_type == AMIC_TYPE_DIFFERENTIAL ?
0 : AB8500_ANAGAINX_ENSEMICX);
dev_dbg(component->dev, "%s: Mic 2 mic-type: %s\n", __func__,
amic_type_str(amics->mic2_type));
snd_soc_component_update_bits(component, AB8500_ANAGAIN2, AB8500_ANAGAINX_ENSEMICX,
amics->mic2_type == AMIC_TYPE_DIFFERENTIAL ?
0 : AB8500_ANAGAINX_ENSEMICX);
return 0;
}
static int ab8500_audio_set_ear_cmv(struct snd_soc_component *component,
enum ear_cm_voltage ear_cmv)
{
char *cmv_str;
switch (ear_cmv) {
case EAR_CMV_0_95V:
cmv_str = "0.95V";
break;
case EAR_CMV_1_10V:
cmv_str = "1.10V";
break;
case EAR_CMV_1_27V:
cmv_str = "1.27V";
break;
case EAR_CMV_1_58V:
cmv_str = "1.58V";
break;
default:
dev_err(component->dev,
"%s: Unknown earpiece CM-voltage (%d)!\n",
__func__, (int)ear_cmv);
return -EINVAL;
}
dev_dbg(component->dev, "%s: Earpiece CM-voltage: %s\n", __func__,
cmv_str);
snd_soc_component_update_bits(component, AB8500_ANACONF1, AB8500_ANACONF1_EARSELCM,
ear_cmv);
return 0;
}
static int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai,
unsigned int delay)
{
unsigned int mask, val;
struct snd_soc_component *component = dai->component;
mask = BIT(AB8500_DIGIFCONF2_IF0DEL);
val = 0;
switch (delay) {
case 0:
break;
case 1:
val |= BIT(AB8500_DIGIFCONF2_IF0DEL);
break;
default:
dev_err(dai->component->dev,
"%s: ERROR: Unsupported bit-delay (0x%x)!\n",
__func__, delay);
return -EINVAL;
}
dev_dbg(dai->component->dev, "%s: IF0 Bit-delay: %d bits.\n",
__func__, delay);
snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val);
return 0;
}
/* Gates clocking according format mask */
static int ab8500_codec_set_dai_clock_gate(struct snd_soc_component *component,
unsigned int fmt)
{
unsigned int mask;
unsigned int val;
mask = BIT(AB8500_DIGIFCONF1_ENMASTGEN) |
BIT(AB8500_DIGIFCONF1_ENFSBITCLK0);
val = BIT(AB8500_DIGIFCONF1_ENMASTGEN);
switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
case SND_SOC_DAIFMT_CONT: /* continuous clock */
dev_dbg(component->dev, "%s: IF0 Clock is continuous.\n",
__func__);
val |= BIT(AB8500_DIGIFCONF1_ENFSBITCLK0);
break;
case SND_SOC_DAIFMT_GATED: /* clock is gated */
dev_dbg(component->dev, "%s: IF0 Clock is gated.\n",
__func__);
break;
default:
dev_err(component->dev,
"%s: ERROR: Unsupported clock mask (0x%x)!\n",
__func__, fmt & SND_SOC_DAIFMT_CLOCK_MASK);
return -EINVAL;
}
snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val);
return 0;
}
static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
unsigned int mask;
unsigned int val;
struct snd_soc_component *component = dai->component;
int status;
dev_dbg(component->dev, "%s: Enter (fmt = 0x%x)\n", __func__, fmt);
mask = BIT(AB8500_DIGIFCONF3_IF1DATOIF0AD) |
BIT(AB8500_DIGIFCONF3_IF1CLKTOIF0CLK) |
BIT(AB8500_DIGIFCONF3_IF0BFIFOEN) |
BIT(AB8500_DIGIFCONF3_IF0MASTER);
val = 0;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
dev_dbg(dai->component->dev,
"%s: IF0 Master-mode: AB8500 provider.\n", __func__);
val |= BIT(AB8500_DIGIFCONF3_IF0MASTER);
break;
case SND_SOC_DAIFMT_CBC_CFC:
dev_dbg(dai->component->dev,
"%s: IF0 Master-mode: AB8500 consumer.\n", __func__);
break;
case SND_SOC_DAIFMT_CBC_CFP:
case SND_SOC_DAIFMT_CBP_CFC:
dev_err(dai->component->dev,
"%s: ERROR: The device is either a provider or a consumer.\n",
__func__);
fallthrough;
default:
dev_err(dai->component->dev,
"%s: ERROR: Unsupporter clocking mask 0x%x\n",
__func__, fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK);
return -EINVAL;
}
snd_soc_component_update_bits(component, AB8500_DIGIFCONF3, mask, val);
/* Set clock gating */
status = ab8500_codec_set_dai_clock_gate(component, fmt);
if (status) {
dev_err(dai->component->dev,
"%s: ERROR: Failed to set clock gate (%d).\n",
__func__, status);
return status;
}
/* Setting data transfer format */
mask = BIT(AB8500_DIGIFCONF2_IF0FORMAT0) |
BIT(AB8500_DIGIFCONF2_IF0FORMAT1) |
BIT(AB8500_DIGIFCONF2_FSYNC0P) |
BIT(AB8500_DIGIFCONF2_BITCLK0P);
val = 0;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S: /* I2S mode */
dev_dbg(dai->component->dev, "%s: IF0 Protocol: I2S\n", __func__);
val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT1);
ab8500_audio_set_bit_delay(dai, 0);
break;
case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */
dev_dbg(dai->component->dev,
"%s: IF0 Protocol: DSP A (TDM)\n", __func__);
val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0);
ab8500_audio_set_bit_delay(dai, 1);
break;
case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */
dev_dbg(dai->component->dev,
"%s: IF0 Protocol: DSP B (TDM)\n", __func__);
val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0);
ab8500_audio_set_bit_delay(dai, 0);
break;
default:
dev_err(dai->component->dev,
"%s: ERROR: Unsupported format (0x%x)!\n",
__func__, fmt & SND_SOC_DAIFMT_FORMAT_MASK);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
dev_dbg(dai->component->dev,
"%s: IF0: Normal bit clock, normal frame\n",
__func__);
break;
case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */
dev_dbg(dai->component->dev,
"%s: IF0: Normal bit clock, inverted frame\n",
__func__);
val |= BIT(AB8500_DIGIFCONF2_FSYNC0P);
break;
case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */
dev_dbg(dai->component->dev,
"%s: IF0: Inverted bit clock, normal frame\n",
__func__);
val |= BIT(AB8500_DIGIFCONF2_BITCLK0P);
break;
case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */
dev_dbg(dai->component->dev,
"%s: IF0: Inverted bit clock, inverted frame\n",
__func__);
val |= BIT(AB8500_DIGIFCONF2_FSYNC0P);
val |= BIT(AB8500_DIGIFCONF2_BITCLK0P);
break;
default:
dev_err(dai->component->dev,
"%s: ERROR: Unsupported INV mask 0x%x\n",
__func__, fmt & SND_SOC_DAIFMT_INV_MASK);
return -EINVAL;
}
snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val);
return 0;
}
static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
struct snd_soc_component *component = dai->component;
unsigned int val, mask, slot, slots_active;
mask = BIT(AB8500_DIGIFCONF2_IF0WL0) |
BIT(AB8500_DIGIFCONF2_IF0WL1);
val = 0;
switch (slot_width) {
case 16:
break;
case 20:
val |= BIT(AB8500_DIGIFCONF2_IF0WL0);
break;
case 24:
val |= BIT(AB8500_DIGIFCONF2_IF0WL1);
break;
case 32:
val |= BIT(AB8500_DIGIFCONF2_IF0WL1) |
BIT(AB8500_DIGIFCONF2_IF0WL0);
break;
default:
dev_err(dai->component->dev, "%s: Unsupported slot-width 0x%x\n",
__func__, slot_width);
return -EINVAL;
}
dev_dbg(dai->component->dev, "%s: IF0 slot-width: %d bits.\n",
__func__, slot_width);
snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val);
/* Setup TDM clocking according to slot count */
dev_dbg(dai->component->dev, "%s: Slots, total: %d\n", __func__, slots);
mask = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) |
BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1);
switch (slots) {
case 2:
val = AB8500_MASK_NONE;
break;
case 4:
val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0);
break;
case 8:
val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1);
break;
case 16:
val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) |
BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1);
break;
default:
dev_err(dai->component->dev,
"%s: ERROR: Unsupported number of slots (%d)!\n",
__func__, slots);
return -EINVAL;
}
snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val);
/* Setup TDM DA according to active tx slots */
if (tx_mask & ~0xff)
return -EINVAL;
mask = AB8500_DASLOTCONFX_SLTODAX_MASK;
tx_mask = tx_mask << AB8500_DA_DATA0_OFFSET;
slots_active = hweight32(tx_mask);
dev_dbg(dai->component->dev, "%s: Slots, active, TX: %d\n", __func__,
slots_active);
switch (slots_active) {
case 0:
break;
case 1:
slot = ffs(tx_mask);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot);
break;
case 2:
slot = ffs(tx_mask);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot);
slot = fls(tx_mask);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot);
snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot);
break;
case 8:
dev_dbg(dai->component->dev,
"%s: In 8-channel mode DA-from-slot mapping is set manually.",
__func__);
break;
default:
dev_err(dai->component->dev,
"%s: Unsupported number of active TX-slots (%d)!\n",
__func__, slots_active);
return -EINVAL;
}
/* Setup TDM AD according to active RX-slots */
if (rx_mask & ~0xff)
return -EINVAL;
rx_mask = rx_mask << AB8500_AD_DATA0_OFFSET;
slots_active = hweight32(rx_mask);
dev_dbg(dai->component->dev, "%s: Slots, active, RX: %d\n", __func__,
slots_active);
switch (slots_active) {
case 0:
break;
case 1:
slot = ffs(rx_mask);
snd_soc_component_update_bits(component, AB8500_ADSLOTSEL(slot),
AB8500_MASK_SLOT(slot),
AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot));
break;
case 2:
slot = ffs(rx_mask);
snd_soc_component_update_bits(component,
AB8500_ADSLOTSEL(slot),
AB8500_MASK_SLOT(slot),
AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot));
slot = fls(rx_mask);
snd_soc_component_update_bits(component,
AB8500_ADSLOTSEL(slot),
AB8500_MASK_SLOT(slot),
AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT2, slot));
break;
case 8:
dev_dbg(dai->component->dev,
"%s: In 8-channel mode AD-to-slot mapping is set manually.",
__func__);
break;
default:
dev_err(dai->component->dev,
"%s: Unsupported number of active RX-slots (%d)!\n",
__func__, slots_active);
return -EINVAL;
}
return 0;
}
static const struct snd_soc_dai_ops ab8500_codec_ops = {
.set_fmt = ab8500_codec_set_dai_fmt,
.set_tdm_slot = ab8500_codec_set_dai_tdm_slot,
};
static struct snd_soc_dai_driver ab8500_codec_dai[] = {
{
.name = "ab8500-codec-dai.0",
.id = 0,
.playback = {
.stream_name = "ab8500_0p",
.channels_min = 1,
.channels_max = 8,
.rates = AB8500_SUPPORTED_RATE,
.formats = AB8500_SUPPORTED_FMT,
},
.ops = &ab8500_codec_ops,
.symmetric_rate = 1
},
{
.name = "ab8500-codec-dai.1",
.id = 1,
.capture = {
.stream_name = "ab8500_0c",
.channels_min = 1,
.channels_max = 8,
.rates = AB8500_SUPPORTED_RATE,
.formats = AB8500_SUPPORTED_FMT,
},
.ops = &ab8500_codec_ops,
.symmetric_rate = 1
}
};
static void ab8500_codec_of_probe(struct device *dev, struct device_node *np,
struct ab8500_codec_platform_data *codec)
{
u32 value;
if (of_property_read_bool(np, "stericsson,amic1-type-single-ended"))
codec->amics.mic1_type = AMIC_TYPE_SINGLE_ENDED;
else
codec->amics.mic1_type = AMIC_TYPE_DIFFERENTIAL;
if (of_property_read_bool(np, "stericsson,amic2-type-single-ended"))
codec->amics.mic2_type = AMIC_TYPE_SINGLE_ENDED;
else
codec->amics.mic2_type = AMIC_TYPE_DIFFERENTIAL;
/* Has a non-standard Vamic been requested? */
if (of_property_read_bool(np, "stericsson,amic1a-bias-vamic2"))
codec->amics.mic1a_micbias = AMIC_MICBIAS_VAMIC2;
else
codec->amics.mic1a_micbias = AMIC_MICBIAS_VAMIC1;
if (of_property_read_bool(np, "stericsson,amic1b-bias-vamic2"))
codec->amics.mic1b_micbias = AMIC_MICBIAS_VAMIC2;
else
codec->amics.mic1b_micbias = AMIC_MICBIAS_VAMIC1;
if (of_property_read_bool(np, "stericsson,amic2-bias-vamic1"))
codec->amics.mic2_micbias = AMIC_MICBIAS_VAMIC1;
else
codec->amics.mic2_micbias = AMIC_MICBIAS_VAMIC2;
if (!of_property_read_u32(np, "stericsson,earpeice-cmv", &value)) {
switch (value) {
case 950 :
codec->ear_cmv = EAR_CMV_0_95V;
break;
case 1100 :
codec->ear_cmv = EAR_CMV_1_10V;
break;
case 1270 :
codec->ear_cmv = EAR_CMV_1_27V;
break;
case 1580 :
codec->ear_cmv = EAR_CMV_1_58V;
break;
default :
codec->ear_cmv = EAR_CMV_UNKNOWN;
dev_err(dev, "Unsuitable earpiece voltage found in DT\n");
}
} else {
dev_warn(dev, "No earpiece voltage found in DT - using default\n");
codec->ear_cmv = EAR_CMV_0_95V;
}
}
static int ab8500_codec_probe(struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct device *dev = component->dev;
struct device_node *np = dev->of_node;
struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev);
struct ab8500_codec_platform_data codec_pdata;
struct filter_control *fc;
int status;
dev_dbg(dev, "%s: Enter.\n", __func__);
ab8500_codec_of_probe(dev, np, &codec_pdata);
status = ab8500_audio_setup_mics(component, &codec_pdata.amics);
if (status < 0) {
pr_err("%s: Failed to setup mics (%d)!\n", __func__, status);
return status;
}
status = ab8500_audio_set_ear_cmv(component, codec_pdata.ear_cmv);
if (status < 0) {
pr_err("%s: Failed to set earpiece CM-voltage (%d)!\n",
__func__, status);
return status;
}
status = ab8500_audio_init_audioblock(component);
if (status < 0) {
dev_err(dev, "%s: failed to init audio-block (%d)!\n",
__func__, status);
return status;
}
/* Override HW-defaults */
snd_soc_component_write(component, AB8500_ANACONF5,
BIT(AB8500_ANACONF5_HSAUTOEN));
snd_soc_component_write(component, AB8500_SHORTCIRCONF,
BIT(AB8500_SHORTCIRCONF_HSZCDDIS));
/* Add filter controls */
status = snd_soc_add_component_controls(component, ab8500_filter_controls,
ARRAY_SIZE(ab8500_filter_controls));
if (status < 0) {
dev_err(dev,
"%s: failed to add ab8500 filter controls (%d).\n",
__func__, status);
return status;
}
fc = (struct filter_control *)
&ab8500_filter_controls[AB8500_FILTER_ANC_FIR].private_value;
drvdata->anc_fir_values = (long *)fc->value;
fc = (struct filter_control *)
&ab8500_filter_controls[AB8500_FILTER_ANC_IIR].private_value;
drvdata->anc_iir_values = (long *)fc->value;
fc = (struct filter_control *)
&ab8500_filter_controls[AB8500_FILTER_SID_FIR].private_value;
drvdata->sid_fir_values = (long *)fc->value;
snd_soc_dapm_disable_pin(dapm, "ANC Configure Input");
mutex_init(&drvdata->ctrl_lock);
return status;
}
static const struct snd_soc_component_driver ab8500_component_driver = {
.probe = ab8500_codec_probe,
.controls = ab8500_ctrls,
.num_controls = ARRAY_SIZE(ab8500_ctrls),
.dapm_widgets = ab8500_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ab8500_dapm_widgets),
.dapm_routes = ab8500_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ab8500_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int ab8500_codec_driver_probe(struct platform_device *pdev)
{
int status;
struct ab8500_codec_drvdata *drvdata;
dev_dbg(&pdev->dev, "%s: Enter.\n", __func__);
/* Create driver private-data struct */
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct ab8500_codec_drvdata),
GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->sid_status = SID_UNCONFIGURED;
drvdata->anc_status = ANC_UNCONFIGURED;
dev_set_drvdata(&pdev->dev, drvdata);
drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev,
&ab8500_codec_regmap);
if (IS_ERR(drvdata->regmap)) {
status = PTR_ERR(drvdata->regmap);
dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n",
__func__, status);
return status;
}
dev_dbg(&pdev->dev, "%s: Register codec.\n", __func__);
status = devm_snd_soc_register_component(&pdev->dev,
&ab8500_component_driver,
ab8500_codec_dai,
ARRAY_SIZE(ab8500_codec_dai));
if (status < 0)
dev_err(&pdev->dev,
"%s: Error: Failed to register codec (%d).\n",
__func__, status);
return status;
}
static struct platform_driver ab8500_codec_platform_driver = {
.driver = {
.name = "ab8500-codec",
},
.probe = ab8500_codec_driver_probe,
};
module_platform_driver(ab8500_codec_platform_driver);
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ac97.c -- ALSA Soc AC97 codec support
*
* Copyright 2005 Wolfson Microelectronics PLC.
* Author: Liam Girdwood <lrg@slimlogic.co.uk>
*
* Generic AC97 support.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
static const struct snd_soc_dapm_widget ac97_widgets[] = {
SND_SOC_DAPM_INPUT("RX"),
SND_SOC_DAPM_OUTPUT("TX"),
};
static const struct snd_soc_dapm_route ac97_routes[] = {
{ "AC97 Capture", NULL, "RX" },
{ "TX", NULL, "AC97 Playback" },
};
static int ac97_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
return snd_ac97_set_rate(ac97, reg, substream->runtime->rate);
}
static const struct snd_soc_dai_ops ac97_dai_ops = {
.prepare = ac97_prepare,
};
static struct snd_soc_dai_driver ac97_dai = {
.name = "ac97-hifi",
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = SND_SOC_STD_AC97_FMTS,},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = SND_SOC_STD_AC97_FMTS,},
.ops = &ac97_dai_ops,
};
static int ac97_soc_probe(struct snd_soc_component *component)
{
struct snd_ac97 *ac97;
struct snd_ac97_bus *ac97_bus;
struct snd_ac97_template ac97_template;
int ret;
/* add codec as bus device for standard ac97 */
ret = snd_ac97_bus(component->card->snd_card, 0, soc_ac97_ops,
NULL, &ac97_bus);
if (ret < 0)
return ret;
memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
if (ret < 0)
return ret;
snd_soc_component_set_drvdata(component, ac97);
return 0;
}
#ifdef CONFIG_PM
static int ac97_soc_suspend(struct snd_soc_component *component)
{
struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
snd_ac97_suspend(ac97);
return 0;
}
static int ac97_soc_resume(struct snd_soc_component *component)
{
struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
snd_ac97_resume(ac97);
return 0;
}
#else
#define ac97_soc_suspend NULL
#define ac97_soc_resume NULL
#endif
static const struct snd_soc_component_driver soc_component_dev_ac97 = {
.probe = ac97_soc_probe,
.suspend = ac97_soc_suspend,
.resume = ac97_soc_resume,
.dapm_widgets = ac97_widgets,
.num_dapm_widgets = ARRAY_SIZE(ac97_widgets),
.dapm_routes = ac97_routes,
.num_dapm_routes = ARRAY_SIZE(ac97_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int ac97_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev,
&soc_component_dev_ac97, &ac97_dai, 1);
}
static int ac97_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver ac97_codec_driver = {
.driver = {
.name = "ac97-codec",
},
.probe = ac97_probe,
.remove = ac97_remove,
};
module_platform_driver(ac97_codec_driver);
MODULE_DESCRIPTION("Soc Generic AC97 driver");
MODULE_AUTHOR("Liam Girdwood");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ac97-codec");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Linux driver model AC97 bus interface
*
* Author: Nicolas Pitre
* Created: Jan 14, 2005
* Copyright: (C) MontaVista Software Inc.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <sound/ac97_codec.h>
/*
* snd_ac97_check_id() - Reads and checks the vendor ID of the device
* @ac97: The AC97 device to check
* @id: The ID to compare to
* @id_mask: Mask that is applied to the device ID before comparing to @id
*
* If @id is 0 this function returns true if the read device vendor ID is
* a valid ID. If @id is non 0 this functions returns true if @id
* matches the read vendor ID. Otherwise the function returns false.
*/
static bool snd_ac97_check_id(struct snd_ac97 *ac97, unsigned int id,
unsigned int id_mask)
{
ac97->id = ac97->bus->ops->read(ac97, AC97_VENDOR_ID1) << 16;
ac97->id |= ac97->bus->ops->read(ac97, AC97_VENDOR_ID2);
if (ac97->id == 0x0 || ac97->id == 0xffffffff)
return false;
if (id != 0 && id != (ac97->id & id_mask))
return false;
return true;
}
/**
* snd_ac97_reset() - Reset AC'97 device
* @ac97: The AC'97 device to reset
* @try_warm: Try a warm reset first
* @id: Expected device vendor ID
* @id_mask: Mask that is applied to the device ID before comparing to @id
*
* This function resets the AC'97 device. If @try_warm is true the function
* first performs a warm reset. If the warm reset is successful the function
* returns 1. Otherwise or if @try_warm is false the function issues cold reset
* followed by a warm reset. If this is successful the function returns 0,
* otherwise a negative error code. If @id is 0 any valid device ID will be
* accepted, otherwise only the ID that matches @id and @id_mask is accepted.
*/
int snd_ac97_reset(struct snd_ac97 *ac97, bool try_warm, unsigned int id,
unsigned int id_mask)
{
const struct snd_ac97_bus_ops *ops = ac97->bus->ops;
if (try_warm && ops->warm_reset) {
ops->warm_reset(ac97);
if (snd_ac97_check_id(ac97, id, id_mask))
return 1;
}
if (ops->reset)
ops->reset(ac97);
if (ops->warm_reset)
ops->warm_reset(ac97);
if (snd_ac97_check_id(ac97, id, id_mask))
return 0;
return -ENODEV;
}
EXPORT_SYMBOL_GPL(snd_ac97_reset);
/*
* Let drivers decide whether they want to support given codec from their
* probe method. Drivers have direct access to the struct snd_ac97
* structure and may decide based on the id field amongst other things.
*/
static int ac97_bus_match(struct device *dev, struct device_driver *drv)
{
return 1;
}
struct bus_type ac97_bus_type = {
.name = "ac97",
.match = ac97_bus_match,
};
static int __init ac97_bus_init(void)
{
return bus_register(&ac97_bus_type);
}
subsys_initcall(ac97_bus_init);
static void __exit ac97_bus_exit(void)
{
bus_unregister(&ac97_bus_type);
}
module_exit(ac97_bus_exit);
EXPORT_SYMBOL(ac97_bus_type);
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com).
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/tlv.h>
#include <sound/ac97_codec.h>
#include <sound/asoundef.h>
#include <sound/initval.h>
#include "ac97_id.h"
#include "ac97_patch.c"
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Universal interface for Audio Codec '97");
MODULE_LICENSE("GPL");
static bool enable_loopback;
module_param(enable_loopback, bool, 0444);
MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control");
#ifdef CONFIG_SND_AC97_POWER_SAVE
static int power_save = CONFIG_SND_AC97_POWER_SAVE_DEFAULT;
module_param(power_save, int, 0644);
MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
"(in second, 0 = disable).");
#endif
/*
*/
struct ac97_codec_id {
unsigned int id;
unsigned int mask;
const char *name;
int (*patch)(struct snd_ac97 *ac97);
int (*mpatch)(struct snd_ac97 *ac97);
unsigned int flags;
};
static const struct ac97_codec_id snd_ac97_codec_id_vendors[] = {
{ 0x41445300, 0xffffff00, "Analog Devices", NULL, NULL },
{ 0x414b4d00, 0xffffff00, "Asahi Kasei", NULL, NULL },
{ 0x414c4300, 0xffffff00, "Realtek", NULL, NULL },
{ 0x414c4700, 0xffffff00, "Realtek", NULL, NULL },
/*
* This is an _inofficial_ Aztech Labs entry
* (value might differ from unknown official Aztech ID),
* currently used by the AC97 emulation of the almost-AC97 PCI168 card.
*/
{ 0x415a5400, 0xffffff00, "Aztech Labs (emulated)", NULL, NULL },
{ 0x434d4900, 0xffffff00, "C-Media Electronics", NULL, NULL },
{ 0x43525900, 0xffffff00, "Cirrus Logic", NULL, NULL },
{ 0x43585400, 0xffffff00, "Conexant", NULL, NULL },
{ 0x44543000, 0xffffff00, "Diamond Technology", NULL, NULL },
{ 0x454d4300, 0xffffff00, "eMicro", NULL, NULL },
{ 0x45838300, 0xffffff00, "ESS Technology", NULL, NULL },
{ 0x48525300, 0xffffff00, "Intersil", NULL, NULL },
{ 0x49434500, 0xffffff00, "ICEnsemble", NULL, NULL },
{ 0x49544500, 0xffffff00, "ITE Tech.Inc", NULL, NULL },
{ 0x4e534300, 0xffffff00, "National Semiconductor", NULL, NULL },
{ 0x50534300, 0xffffff00, "Philips", NULL, NULL },
{ 0x53494c00, 0xffffff00, "Silicon Laboratory", NULL, NULL },
{ 0x53544d00, 0xffffff00, "STMicroelectronics", NULL, NULL },
{ 0x54524100, 0xffffff00, "TriTech", NULL, NULL },
{ 0x54584e00, 0xffffff00, "Texas Instruments", NULL, NULL },
{ 0x56494100, 0xffffff00, "VIA Technologies", NULL, NULL },
{ 0x57454300, 0xffffff00, "Winbond", NULL, NULL },
{ 0x574d4c00, 0xffffff00, "Wolfson", NULL, NULL },
{ 0x594d4800, 0xffffff00, "Yamaha", NULL, NULL },
{ 0x83847600, 0xffffff00, "SigmaTel", NULL, NULL },
{ 0, 0, NULL, NULL, NULL }
};
static const struct ac97_codec_id snd_ac97_codec_ids[] = {
{ 0x41445303, 0xffffffff, "AD1819", patch_ad1819, NULL },
{ 0x41445340, 0xffffffff, "AD1881", patch_ad1881, NULL },
{ 0x41445348, 0xffffffff, "AD1881A", patch_ad1881, NULL },
{ 0x41445360, 0xffffffff, "AD1885", patch_ad1885, NULL },
{ 0x41445361, 0xffffffff, "AD1886", patch_ad1886, NULL },
{ 0x41445362, 0xffffffff, "AD1887", patch_ad1881, NULL },
{ 0x41445363, 0xffffffff, "AD1886A", patch_ad1881, NULL },
{ 0x41445368, 0xffffffff, "AD1888", patch_ad1888, NULL },
{ 0x41445370, 0xffffffff, "AD1980", patch_ad1980, NULL },
{ 0x41445372, 0xffffffff, "AD1981A", patch_ad1981a, NULL },
{ 0x41445374, 0xffffffff, "AD1981B", patch_ad1981b, NULL },
{ 0x41445375, 0xffffffff, "AD1985", patch_ad1985, NULL },
{ 0x41445378, 0xffffffff, "AD1986", patch_ad1986, NULL },
{ 0x414b4d00, 0xffffffff, "AK4540", NULL, NULL },
{ 0x414b4d01, 0xffffffff, "AK4542", NULL, NULL },
{ 0x414b4d02, 0xffffffff, "AK4543", NULL, NULL },
{ 0x414b4d06, 0xffffffff, "AK4544A", NULL, NULL },
{ 0x414b4d07, 0xffffffff, "AK4545", NULL, NULL },
{ 0x414c4300, 0xffffff00, "ALC100,100P", NULL, NULL },
{ 0x414c4710, 0xfffffff0, "ALC200,200P", NULL, NULL },
{ 0x414c4721, 0xffffffff, "ALC650D", NULL, NULL }, /* already patched */
{ 0x414c4722, 0xffffffff, "ALC650E", NULL, NULL }, /* already patched */
{ 0x414c4723, 0xffffffff, "ALC650F", NULL, NULL }, /* already patched */
{ 0x414c4720, 0xfffffff0, "ALC650", patch_alc650, NULL },
{ 0x414c4730, 0xffffffff, "ALC101", NULL, NULL },
{ 0x414c4740, 0xfffffff0, "ALC202", NULL, NULL },
{ 0x414c4750, 0xfffffff0, "ALC250", NULL, NULL },
{ 0x414c4760, 0xfffffff0, "ALC655", patch_alc655, NULL },
{ 0x414c4770, 0xfffffff0, "ALC203", patch_alc203, NULL },
{ 0x414c4781, 0xffffffff, "ALC658D", NULL, NULL }, /* already patched */
{ 0x414c4780, 0xfffffff0, "ALC658", patch_alc655, NULL },
{ 0x414c4790, 0xfffffff0, "ALC850", patch_alc850, NULL },
{ 0x415a5401, 0xffffffff, "AZF3328", patch_aztech_azf3328, NULL },
{ 0x434d4941, 0xffffffff, "CMI9738", patch_cm9738, NULL },
{ 0x434d4961, 0xffffffff, "CMI9739", patch_cm9739, NULL },
{ 0x434d4969, 0xffffffff, "CMI9780", patch_cm9780, NULL },
{ 0x434d4978, 0xffffffff, "CMI9761A", patch_cm9761, NULL },
{ 0x434d4982, 0xffffffff, "CMI9761B", patch_cm9761, NULL },
{ 0x434d4983, 0xffffffff, "CMI9761A+", patch_cm9761, NULL },
{ 0x43525900, 0xfffffff8, "CS4297", NULL, NULL },
{ 0x43525910, 0xfffffff8, "CS4297A", patch_cirrus_spdif, NULL },
{ 0x43525920, 0xfffffff8, "CS4298", patch_cirrus_spdif, NULL },
{ 0x43525928, 0xfffffff8, "CS4294", NULL, NULL },
{ 0x43525930, 0xfffffff8, "CS4299", patch_cirrus_cs4299, NULL },
{ 0x43525948, 0xfffffff8, "CS4201", NULL, NULL },
{ 0x43525958, 0xfffffff8, "CS4205", patch_cirrus_spdif, NULL },
{ 0x43525960, 0xfffffff8, "CS4291", NULL, NULL },
{ 0x43525970, 0xfffffff8, "CS4202", NULL, NULL },
{ 0x43585421, 0xffffffff, "HSD11246", NULL, NULL }, // SmartMC II
{ 0x43585428, 0xfffffff8, "Cx20468", patch_conexant, NULL }, // SmartAMC fixme: the mask might be different
{ 0x43585430, 0xffffffff, "Cx20468-31", patch_conexant, NULL },
{ 0x43585431, 0xffffffff, "Cx20551", patch_cx20551, NULL },
{ 0x44543031, 0xfffffff0, "DT0398", NULL, NULL },
{ 0x454d4328, 0xffffffff, "EM28028", NULL, NULL }, // same as TR28028?
{ 0x45838308, 0xffffffff, "ESS1988", NULL, NULL },
{ 0x48525300, 0xffffff00, "HMP9701", NULL, NULL },
{ 0x49434501, 0xffffffff, "ICE1230", NULL, NULL },
{ 0x49434511, 0xffffffff, "ICE1232", NULL, NULL }, // alias VIA VT1611A?
{ 0x49434514, 0xffffffff, "ICE1232A", NULL, NULL },
{ 0x49434551, 0xffffffff, "VT1616", patch_vt1616, NULL },
{ 0x49434552, 0xffffffff, "VT1616i", patch_vt1616, NULL }, // VT1616 compatible (chipset integrated)
{ 0x49544520, 0xffffffff, "IT2226E", NULL, NULL },
{ 0x49544561, 0xffffffff, "IT2646E", patch_it2646, NULL },
{ 0x4e534300, 0xffffffff, "LM4540,43,45,46,48", NULL, NULL }, // only guess --jk
{ 0x4e534331, 0xffffffff, "LM4549", NULL, NULL },
{ 0x4e534350, 0xffffffff, "LM4550", patch_lm4550, NULL }, // volume wrap fix
{ 0x50534304, 0xffffffff, "UCB1400", patch_ucb1400, NULL },
{ 0x53494c20, 0xffffffe0, "Si3036,8", mpatch_si3036, mpatch_si3036, AC97_MODEM_PATCH },
{ 0x53544d02, 0xffffffff, "ST7597", NULL, NULL },
{ 0x54524102, 0xffffffff, "TR28022", NULL, NULL },
{ 0x54524103, 0xffffffff, "TR28023", NULL, NULL },
{ 0x54524106, 0xffffffff, "TR28026", NULL, NULL },
{ 0x54524108, 0xffffffff, "TR28028", patch_tritech_tr28028, NULL }, // added by xin jin [07/09/99]
{ 0x54524123, 0xffffffff, "TR28602", NULL, NULL }, // only guess --jk [TR28023 = eMicro EM28023 (new CT1297)]
{ 0x54584e03, 0xffffffff, "TLV320AIC27", NULL, NULL },
{ 0x54584e20, 0xffffffff, "TLC320AD9xC", NULL, NULL },
{ 0x56494120, 0xfffffff0, "VIA1613", patch_vt1613, NULL },
{ 0x56494161, 0xffffffff, "VIA1612A", NULL, NULL }, // modified ICE1232 with S/PDIF
{ 0x56494170, 0xffffffff, "VIA1617A", patch_vt1617a, NULL }, // modified VT1616 with S/PDIF
{ 0x56494182, 0xffffffff, "VIA1618", patch_vt1618, NULL },
{ 0x57454301, 0xffffffff, "W83971D", NULL, NULL },
{ 0x574d4c00, 0xffffffff, "WM9701,WM9701A", NULL, NULL },
{ 0x574d4C03, 0xffffffff, "WM9703,WM9707,WM9708,WM9717", patch_wolfson03, NULL},
{ 0x574d4C04, 0xffffffff, "WM9704M,WM9704Q", patch_wolfson04, NULL},
{ 0x574d4C05, 0xffffffff, "WM9705,WM9710", patch_wolfson05, NULL},
{ 0x574d4C09, 0xffffffff, "WM9709", NULL, NULL},
{ 0x574d4C12, 0xffffffff, "WM9711,WM9712,WM9715", patch_wolfson11, NULL},
{ 0x574d4c13, 0xffffffff, "WM9713,WM9714", patch_wolfson13, NULL, AC97_DEFAULT_POWER_OFF},
{ 0x594d4800, 0xffffffff, "YMF743", patch_yamaha_ymf743, NULL },
{ 0x594d4802, 0xffffffff, "YMF752", NULL, NULL },
{ 0x594d4803, 0xffffffff, "YMF753", patch_yamaha_ymf753, NULL },
{ 0x83847600, 0xffffffff, "STAC9700,83,84", patch_sigmatel_stac9700, NULL },
{ 0x83847604, 0xffffffff, "STAC9701,3,4,5", NULL, NULL },
{ 0x83847605, 0xffffffff, "STAC9704", NULL, NULL },
{ 0x83847608, 0xffffffff, "STAC9708,11", patch_sigmatel_stac9708, NULL },
{ 0x83847609, 0xffffffff, "STAC9721,23", patch_sigmatel_stac9721, NULL },
{ 0x83847644, 0xffffffff, "STAC9744", patch_sigmatel_stac9744, NULL },
{ 0x83847650, 0xffffffff, "STAC9750,51", NULL, NULL }, // patch?
{ 0x83847652, 0xffffffff, "STAC9752,53", NULL, NULL }, // patch?
{ 0x83847656, 0xffffffff, "STAC9756,57", patch_sigmatel_stac9756, NULL },
{ 0x83847658, 0xffffffff, "STAC9758,59", patch_sigmatel_stac9758, NULL },
{ 0x83847666, 0xffffffff, "STAC9766,67", NULL, NULL }, // patch?
{ 0, 0, NULL, NULL, NULL }
};
static void update_power_regs(struct snd_ac97 *ac97);
#ifdef CONFIG_SND_AC97_POWER_SAVE
#define ac97_is_power_save_mode(ac97) \
((ac97->scaps & AC97_SCAP_POWER_SAVE) && power_save)
#else
#define ac97_is_power_save_mode(ac97) 0
#endif
#define ac97_err(ac97, fmt, args...) \
dev_err((ac97)->bus->card->dev, fmt, ##args)
#define ac97_warn(ac97, fmt, args...) \
dev_warn((ac97)->bus->card->dev, fmt, ##args)
#define ac97_dbg(ac97, fmt, args...) \
dev_dbg((ac97)->bus->card->dev, fmt, ##args)
/*
* I/O routines
*/
static int snd_ac97_valid_reg(struct snd_ac97 *ac97, unsigned short reg)
{
/* filter some registers for buggy codecs */
switch (ac97->id) {
case AC97_ID_ST_AC97_ID4:
if (reg == 0x08)
return 0;
fallthrough;
case AC97_ID_ST7597:
if (reg == 0x22 || reg == 0x7a)
return 1;
fallthrough;
case AC97_ID_AK4540:
case AC97_ID_AK4542:
if (reg <= 0x1c || reg == 0x20 || reg == 0x26 || reg >= 0x7c)
return 1;
return 0;
case AC97_ID_AD1819: /* AD1819 */
case AC97_ID_AD1881: /* AD1881 */
case AC97_ID_AD1881A: /* AD1881A */
if (reg >= 0x3a && reg <= 0x6e) /* 0x59 */
return 0;
return 1;
case AC97_ID_AD1885: /* AD1885 */
case AC97_ID_AD1886: /* AD1886 */
case AC97_ID_AD1886A: /* AD1886A - !!verify!! --jk */
case AC97_ID_AD1887: /* AD1887 - !!verify!! --jk */
if (reg == 0x5a)
return 1;
if (reg >= 0x3c && reg <= 0x6e) /* 0x59 */
return 0;
return 1;
case AC97_ID_STAC9700:
case AC97_ID_STAC9704:
case AC97_ID_STAC9705:
case AC97_ID_STAC9708:
case AC97_ID_STAC9721:
case AC97_ID_STAC9744:
case AC97_ID_STAC9756:
if (reg <= 0x3a || reg >= 0x5a)
return 1;
return 0;
}
return 1;
}
/**
* snd_ac97_write - write a value on the given register
* @ac97: the ac97 instance
* @reg: the register to change
* @value: the value to set
*
* Writes a value on the given register. This will invoke the write
* callback directly after the register check.
* This function doesn't change the register cache unlike
* #snd_ca97_write_cache(), so use this only when you don't want to
* reflect the change to the suspend/resume state.
*/
void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value)
{
if (!snd_ac97_valid_reg(ac97, reg))
return;
if ((ac97->id & 0xffffff00) == AC97_ID_ALC100) {
/* Fix H/W bug of ALC100/100P */
if (reg == AC97_MASTER || reg == AC97_HEADPHONE)
ac97->bus->ops->write(ac97, AC97_RESET, 0); /* reset audio codec */
}
ac97->bus->ops->write(ac97, reg, value);
}
EXPORT_SYMBOL(snd_ac97_write);
/**
* snd_ac97_read - read a value from the given register
*
* @ac97: the ac97 instance
* @reg: the register to read
*
* Reads a value from the given register. This will invoke the read
* callback directly after the register check.
*
* Return: The read value.
*/
unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
if (!snd_ac97_valid_reg(ac97, reg))
return 0;
return ac97->bus->ops->read(ac97, reg);
}
/* read a register - return the cached value if already read */
static inline unsigned short snd_ac97_read_cache(struct snd_ac97 *ac97, unsigned short reg)
{
if (! test_bit(reg, ac97->reg_accessed)) {
ac97->regs[reg] = ac97->bus->ops->read(ac97, reg);
// set_bit(reg, ac97->reg_accessed);
}
return ac97->regs[reg];
}
EXPORT_SYMBOL(snd_ac97_read);
/**
* snd_ac97_write_cache - write a value on the given register and update the cache
* @ac97: the ac97 instance
* @reg: the register to change
* @value: the value to set
*
* Writes a value on the given register and updates the register
* cache. The cached values are used for the cached-read and the
* suspend/resume.
*/
void snd_ac97_write_cache(struct snd_ac97 *ac97, unsigned short reg, unsigned short value)
{
if (!snd_ac97_valid_reg(ac97, reg))
return;
mutex_lock(&ac97->reg_mutex);
ac97->regs[reg] = value;
ac97->bus->ops->write(ac97, reg, value);
set_bit(reg, ac97->reg_accessed);
mutex_unlock(&ac97->reg_mutex);
}
EXPORT_SYMBOL(snd_ac97_write_cache);
/**
* snd_ac97_update - update the value on the given register
* @ac97: the ac97 instance
* @reg: the register to change
* @value: the value to set
*
* Compares the value with the register cache and updates the value
* only when the value is changed.
*
* Return: 1 if the value is changed, 0 if no change, or a negative
* code on failure.
*/
int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value)
{
int change;
if (!snd_ac97_valid_reg(ac97, reg))
return -EINVAL;
mutex_lock(&ac97->reg_mutex);
change = ac97->regs[reg] != value;
if (change) {
ac97->regs[reg] = value;
ac97->bus->ops->write(ac97, reg, value);
}
set_bit(reg, ac97->reg_accessed);
mutex_unlock(&ac97->reg_mutex);
return change;
}
EXPORT_SYMBOL(snd_ac97_update);
/**
* snd_ac97_update_bits - update the bits on the given register
* @ac97: the ac97 instance
* @reg: the register to change
* @mask: the bit-mask to change
* @value: the value to set
*
* Updates the masked-bits on the given register only when the value
* is changed.
*
* Return: 1 if the bits are changed, 0 if no change, or a negative
* code on failure.
*/
int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value)
{
int change;
if (!snd_ac97_valid_reg(ac97, reg))
return -EINVAL;
mutex_lock(&ac97->reg_mutex);
change = snd_ac97_update_bits_nolock(ac97, reg, mask, value);
mutex_unlock(&ac97->reg_mutex);
return change;
}
EXPORT_SYMBOL(snd_ac97_update_bits);
/* no lock version - see snd_ac97_update_bits() */
int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg,
unsigned short mask, unsigned short value)
{
int change;
unsigned short old, new;
old = snd_ac97_read_cache(ac97, reg);
new = (old & ~mask) | (value & mask);
change = old != new;
if (change) {
ac97->regs[reg] = new;
ac97->bus->ops->write(ac97, reg, new);
}
set_bit(reg, ac97->reg_accessed);
return change;
}
static int snd_ac97_ad18xx_update_pcm_bits(struct snd_ac97 *ac97, int codec, unsigned short mask, unsigned short value)
{
int change;
unsigned short old, new, cfg;
mutex_lock(&ac97->page_mutex);
old = ac97->spec.ad18xx.pcmreg[codec];
new = (old & ~mask) | (value & mask);
change = old != new;
if (change) {
mutex_lock(&ac97->reg_mutex);
cfg = snd_ac97_read_cache(ac97, AC97_AD_SERIAL_CFG);
ac97->spec.ad18xx.pcmreg[codec] = new;
/* select single codec */
ac97->bus->ops->write(ac97, AC97_AD_SERIAL_CFG,
(cfg & ~0x7000) |
ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
/* update PCM bits */
ac97->bus->ops->write(ac97, AC97_PCM, new);
/* select all codecs */
ac97->bus->ops->write(ac97, AC97_AD_SERIAL_CFG,
cfg | 0x7000);
mutex_unlock(&ac97->reg_mutex);
}
mutex_unlock(&ac97->page_mutex);
return change;
}
/*
* Controls
*/
static int snd_ac97_info_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value;
return snd_ctl_enum_info(uinfo, e->shift_l == e->shift_r ? 1 : 2,
e->mask, e->texts);
}
static int snd_ac97_get_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value;
unsigned short val, bitmask;
for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
;
val = snd_ac97_read_cache(ac97, e->reg);
ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1);
if (e->shift_l != e->shift_r)
ucontrol->value.enumerated.item[1] = (val >> e->shift_r) & (bitmask - 1);
return 0;
}
static int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value;
unsigned short val;
unsigned short mask, bitmask;
for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
;
if (ucontrol->value.enumerated.item[0] > e->mask - 1)
return -EINVAL;
val = ucontrol->value.enumerated.item[0] << e->shift_l;
mask = (bitmask - 1) << e->shift_l;
if (e->shift_l != e->shift_r) {
if (ucontrol->value.enumerated.item[1] > e->mask - 1)
return -EINVAL;
val |= ucontrol->value.enumerated.item[1] << e->shift_r;
mask |= (bitmask - 1) << e->shift_r;
}
return snd_ac97_update_bits(ac97, e->reg, mask, val);
}
/* save/restore ac97 v2.3 paging */
static int snd_ac97_page_save(struct snd_ac97 *ac97, int reg, struct snd_kcontrol *kcontrol)
{
int page_save = -1;
if ((kcontrol->private_value & (1<<25)) &&
(ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23 &&
(reg >= 0x60 && reg < 0x70)) {
unsigned short page = (kcontrol->private_value >> 26) & 0x0f;
mutex_lock(&ac97->page_mutex); /* lock paging */
page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page);
}
return page_save;
}
static void snd_ac97_page_restore(struct snd_ac97 *ac97, int page_save)
{
if (page_save >= 0) {
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save);
mutex_unlock(&ac97->page_mutex); /* unlock paging */
}
}
/* volume and switch controls */
static int snd_ac97_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 16) & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = shift == rshift ? 1 : 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_ac97_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0x01;
int page_save;
page_save = snd_ac97_page_save(ac97, reg, kcontrol);
ucontrol->value.integer.value[0] = (snd_ac97_read_cache(ac97, reg) >> shift) & mask;
if (shift != rshift)
ucontrol->value.integer.value[1] = (snd_ac97_read_cache(ac97, reg) >> rshift) & mask;
if (invert) {
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
if (shift != rshift)
ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
}
snd_ac97_page_restore(ac97, page_save);
return 0;
}
static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0x01;
int err, page_save;
unsigned short val, val2, val_mask;
page_save = snd_ac97_page_save(ac97, reg, kcontrol);
val = (ucontrol->value.integer.value[0] & mask);
if (invert)
val = mask - val;
val_mask = mask << shift;
val = val << shift;
if (shift != rshift) {
val2 = (ucontrol->value.integer.value[1] & mask);
if (invert)
val2 = mask - val2;
val_mask |= mask << rshift;
val |= val2 << rshift;
}
err = snd_ac97_update_bits(ac97, reg, val_mask, val);
snd_ac97_page_restore(ac97, page_save);
#ifdef CONFIG_SND_AC97_POWER_SAVE
/* check analog mixer power-down */
if ((val_mask & AC97_PD_EAPD) &&
(kcontrol->private_value & (1<<30))) {
if (val & AC97_PD_EAPD)
ac97->power_up &= ~(1 << (reg>>1));
else
ac97->power_up |= 1 << (reg>>1);
update_power_regs(ac97);
}
#endif
return err;
}
static const struct snd_kcontrol_new snd_ac97_controls_tone[2] = {
AC97_SINGLE("Tone Control - Bass", AC97_MASTER_TONE, 8, 15, 1),
AC97_SINGLE("Tone Control - Treble", AC97_MASTER_TONE, 0, 15, 1)
};
static const struct snd_kcontrol_new snd_ac97_controls_pc_beep[2] = {
AC97_SINGLE("Beep Playback Switch", AC97_PC_BEEP, 15, 1, 1),
AC97_SINGLE("Beep Playback Volume", AC97_PC_BEEP, 1, 15, 1)
};
static const struct snd_kcontrol_new snd_ac97_controls_mic_boost =
AC97_SINGLE("Mic Boost (+20dB)", AC97_MIC, 6, 1, 0);
static const char* std_rec_sel[] = {"Mic", "CD", "Video", "Aux", "Line", "Mix", "Mix Mono", "Phone"};
static const char* std_3d_path[] = {"pre 3D", "post 3D"};
static const char* std_mix[] = {"Mix", "Mic"};
static const char* std_mic[] = {"Mic1", "Mic2"};
static const struct ac97_enum std_enum[] = {
AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, std_rec_sel),
AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, std_3d_path),
AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, std_mix),
AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, std_mic),
};
static const struct snd_kcontrol_new snd_ac97_control_capture_src =
AC97_ENUM("Capture Source", std_enum[0]);
static const struct snd_kcontrol_new snd_ac97_control_capture_vol =
AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0);
static const struct snd_kcontrol_new snd_ac97_controls_mic_capture[2] = {
AC97_SINGLE("Mic Capture Switch", AC97_REC_GAIN_MIC, 15, 1, 1),
AC97_SINGLE("Mic Capture Volume", AC97_REC_GAIN_MIC, 0, 15, 0)
};
enum {
AC97_GENERAL_PCM_OUT = 0,
AC97_GENERAL_STEREO_ENHANCEMENT,
AC97_GENERAL_3D,
AC97_GENERAL_LOUDNESS,
AC97_GENERAL_MONO,
AC97_GENERAL_MIC,
AC97_GENERAL_LOOPBACK
};
static const struct snd_kcontrol_new snd_ac97_controls_general[7] = {
AC97_ENUM("PCM Out Path & Mute", std_enum[1]),
AC97_SINGLE("Simulated Stereo Enhancement", AC97_GENERAL_PURPOSE, 14, 1, 0),
AC97_SINGLE("3D Control - Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
AC97_SINGLE("Loudness (bass boost)", AC97_GENERAL_PURPOSE, 12, 1, 0),
AC97_ENUM("Mono Output Select", std_enum[2]),
AC97_ENUM("Mic Select", std_enum[3]),
AC97_SINGLE("ADC/DAC Loopback", AC97_GENERAL_PURPOSE, 7, 1, 0)
};
static const struct snd_kcontrol_new snd_ac97_controls_3d[2] = {
AC97_SINGLE("3D Control - Center", AC97_3D_CONTROL, 8, 15, 0),
AC97_SINGLE("3D Control - Depth", AC97_3D_CONTROL, 0, 15, 0)
};
static const struct snd_kcontrol_new snd_ac97_controls_center[2] = {
AC97_SINGLE("Center Playback Switch", AC97_CENTER_LFE_MASTER, 7, 1, 1),
AC97_SINGLE("Center Playback Volume", AC97_CENTER_LFE_MASTER, 0, 31, 1)
};
static const struct snd_kcontrol_new snd_ac97_controls_lfe[2] = {
AC97_SINGLE("LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 1, 1),
AC97_SINGLE("LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 31, 1)
};
static const struct snd_kcontrol_new snd_ac97_control_eapd =
AC97_SINGLE("External Amplifier", AC97_POWERDOWN, 15, 1, 1);
static const struct snd_kcontrol_new snd_ac97_controls_modem_switches[2] = {
AC97_SINGLE("Off-hook Switch", AC97_GPIO_STATUS, 0, 1, 0),
AC97_SINGLE("Caller ID Switch", AC97_GPIO_STATUS, 2, 1, 0)
};
/* change the existing EAPD control as inverted */
static void set_inv_eapd(struct snd_ac97 *ac97, struct snd_kcontrol *kctl)
{
kctl->private_value = AC97_SINGLE_VALUE(AC97_POWERDOWN, 15, 1, 0);
snd_ac97_update_bits(ac97, AC97_POWERDOWN, (1<<15), (1<<15)); /* EAPD up */
ac97->scaps |= AC97_SCAP_INV_EAPD;
}
static int snd_ac97_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_ac97_spdif_cmask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
IEC958_AES0_NONAUDIO |
IEC958_AES0_CON_EMPHASIS_5015 |
IEC958_AES0_CON_NOT_COPYRIGHT;
ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
IEC958_AES1_CON_ORIGINAL;
ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
return 0;
}
static int snd_ac97_spdif_pmask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
/* FIXME: AC'97 spec doesn't say which bits are used for what */
ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
IEC958_AES0_NONAUDIO |
IEC958_AES0_PRO_FS |
IEC958_AES0_PRO_EMPHASIS_5015;
return 0;
}
static int snd_ac97_spdif_default_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
mutex_lock(&ac97->reg_mutex);
ucontrol->value.iec958.status[0] = ac97->spdif_status & 0xff;
ucontrol->value.iec958.status[1] = (ac97->spdif_status >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (ac97->spdif_status >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (ac97->spdif_status >> 24) & 0xff;
mutex_unlock(&ac97->reg_mutex);
return 0;
}
static int snd_ac97_spdif_default_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned int new = 0;
unsigned short val = 0;
int change;
new = val = ucontrol->value.iec958.status[0] & (IEC958_AES0_PROFESSIONAL|IEC958_AES0_NONAUDIO);
if (ucontrol->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL) {
new |= ucontrol->value.iec958.status[0] & (IEC958_AES0_PRO_FS|IEC958_AES0_PRO_EMPHASIS_5015);
switch (new & IEC958_AES0_PRO_FS) {
case IEC958_AES0_PRO_FS_44100: val |= 0<<12; break;
case IEC958_AES0_PRO_FS_48000: val |= 2<<12; break;
case IEC958_AES0_PRO_FS_32000: val |= 3<<12; break;
default: val |= 1<<12; break;
}
if ((new & IEC958_AES0_PRO_EMPHASIS) == IEC958_AES0_PRO_EMPHASIS_5015)
val |= 1<<3;
} else {
new |= ucontrol->value.iec958.status[0] & (IEC958_AES0_CON_EMPHASIS_5015|IEC958_AES0_CON_NOT_COPYRIGHT);
new |= ((ucontrol->value.iec958.status[1] & (IEC958_AES1_CON_CATEGORY|IEC958_AES1_CON_ORIGINAL)) << 8);
new |= ((ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) << 24);
if ((new & IEC958_AES0_CON_EMPHASIS) == IEC958_AES0_CON_EMPHASIS_5015)
val |= 1<<3;
if (!(new & IEC958_AES0_CON_NOT_COPYRIGHT))
val |= 1<<2;
val |= ((new >> 8) & 0xff) << 4; // category + original
switch ((new >> 24) & 0xff) {
case IEC958_AES3_CON_FS_44100: val |= 0<<12; break;
case IEC958_AES3_CON_FS_48000: val |= 2<<12; break;
case IEC958_AES3_CON_FS_32000: val |= 3<<12; break;
default: val |= 1<<12; break;
}
}
mutex_lock(&ac97->reg_mutex);
change = ac97->spdif_status != new;
ac97->spdif_status = new;
if (ac97->flags & AC97_CS_SPDIF) {
int x = (val >> 12) & 0x03;
switch (x) {
case 0: x = 1; break; // 44.1
case 2: x = 0; break; // 48.0
default: x = 0; break; // illegal.
}
change |= snd_ac97_update_bits_nolock(ac97, AC97_CSR_SPDIF, 0x3fff, ((val & 0xcfff) | (x << 12)));
} else if (ac97->flags & AC97_CX_SPDIF) {
int v;
v = new & (IEC958_AES0_CON_EMPHASIS_5015|IEC958_AES0_CON_NOT_COPYRIGHT) ? 0 : AC97_CXR_COPYRGT;
v |= new & IEC958_AES0_NONAUDIO ? AC97_CXR_SPDIF_AC3 : AC97_CXR_SPDIF_PCM;
change |= snd_ac97_update_bits_nolock(ac97, AC97_CXR_AUDIO_MISC,
AC97_CXR_SPDIF_MASK | AC97_CXR_COPYRGT,
v);
} else if (ac97->id == AC97_ID_YMF743) {
change |= snd_ac97_update_bits_nolock(ac97,
AC97_YMF7X3_DIT_CTRL,
0xff38,
((val << 4) & 0xff00) |
((val << 2) & 0x0038));
} else {
unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS);
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */
change |= snd_ac97_update_bits_nolock(ac97, AC97_SPDIF, 0x3fff, val);
if (extst & AC97_EA_SPDIF) {
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */
}
}
mutex_unlock(&ac97->reg_mutex);
return change;
}
static int snd_ac97_put_spsa(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
// int invert = (kcontrol->private_value >> 24) & 0xff;
unsigned short value, old, new;
int change;
value = (ucontrol->value.integer.value[0] & mask);
mutex_lock(&ac97->reg_mutex);
mask <<= shift;
value <<= shift;
old = snd_ac97_read_cache(ac97, reg);
new = (old & ~mask) | value;
change = old != new;
if (change) {
unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS);
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */
change = snd_ac97_update_bits_nolock(ac97, reg, mask, value);
if (extst & AC97_EA_SPDIF)
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */
}
mutex_unlock(&ac97->reg_mutex);
return change;
}
static const struct snd_kcontrol_new snd_ac97_controls_spdif[5] = {
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
.info = snd_ac97_spdif_mask_info,
.get = snd_ac97_spdif_cmask_get,
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
.info = snd_ac97_spdif_mask_info,
.get = snd_ac97_spdif_pmask_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = snd_ac97_spdif_mask_info,
.get = snd_ac97_spdif_default_get,
.put = snd_ac97_spdif_default_put,
},
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),AC97_EXTENDED_STATUS, 2, 1, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA",
.info = snd_ac97_info_volsw,
.get = snd_ac97_get_volsw,
.put = snd_ac97_put_spsa,
.private_value = AC97_SINGLE_VALUE(AC97_EXTENDED_STATUS, 4, 3, 0)
},
};
#define AD18XX_PCM_BITS(xname, codec, lshift, rshift, mask) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_bits, \
.get = snd_ac97_ad18xx_pcm_get_bits, .put = snd_ac97_ad18xx_pcm_put_bits, \
.private_value = (codec) | ((lshift) << 8) | ((rshift) << 12) | ((mask) << 16) }
static int snd_ac97_ad18xx_pcm_info_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int mask = (kcontrol->private_value >> 16) & 0x0f;
int lshift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES))
uinfo->count = 2;
else
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_ac97_ad18xx_pcm_get_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int codec = kcontrol->private_value & 3;
int lshift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
ucontrol->value.integer.value[0] = mask - ((ac97->spec.ad18xx.pcmreg[codec] >> lshift) & mask);
if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES))
ucontrol->value.integer.value[1] = mask - ((ac97->spec.ad18xx.pcmreg[codec] >> rshift) & mask);
return 0;
}
static int snd_ac97_ad18xx_pcm_put_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int codec = kcontrol->private_value & 3;
int lshift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
unsigned short val, valmask;
val = (mask - (ucontrol->value.integer.value[0] & mask)) << lshift;
valmask = mask << lshift;
if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES)) {
val |= (mask - (ucontrol->value.integer.value[1] & mask)) << rshift;
valmask |= mask << rshift;
}
return snd_ac97_ad18xx_update_pcm_bits(ac97, codec, valmask, val);
}
#define AD18XX_PCM_VOLUME(xname, codec) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_volume, \
.get = snd_ac97_ad18xx_pcm_get_volume, .put = snd_ac97_ad18xx_pcm_put_volume, \
.private_value = codec }
static int snd_ac97_ad18xx_pcm_info_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 31;
return 0;
}
static int snd_ac97_ad18xx_pcm_get_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int codec = kcontrol->private_value & 3;
mutex_lock(&ac97->page_mutex);
ucontrol->value.integer.value[0] = 31 - ((ac97->spec.ad18xx.pcmreg[codec] >> 0) & 31);
ucontrol->value.integer.value[1] = 31 - ((ac97->spec.ad18xx.pcmreg[codec] >> 8) & 31);
mutex_unlock(&ac97->page_mutex);
return 0;
}
static int snd_ac97_ad18xx_pcm_put_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int codec = kcontrol->private_value & 3;
unsigned short val1, val2;
val1 = 31 - (ucontrol->value.integer.value[0] & 31);
val2 = 31 - (ucontrol->value.integer.value[1] & 31);
return snd_ac97_ad18xx_update_pcm_bits(ac97, codec, 0x1f1f, (val1 << 8) | val2);
}
static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_pcm[2] = {
AD18XX_PCM_BITS("PCM Playback Switch", 0, 15, 7, 1),
AD18XX_PCM_VOLUME("PCM Playback Volume", 0)
};
static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_surround[2] = {
AD18XX_PCM_BITS("Surround Playback Switch", 1, 15, 7, 1),
AD18XX_PCM_VOLUME("Surround Playback Volume", 1)
};
static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_center[2] = {
AD18XX_PCM_BITS("Center Playback Switch", 2, 15, 15, 1),
AD18XX_PCM_BITS("Center Playback Volume", 2, 8, 8, 31)
};
static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_lfe[2] = {
AD18XX_PCM_BITS("LFE Playback Switch", 2, 7, 7, 1),
AD18XX_PCM_BITS("LFE Playback Volume", 2, 0, 0, 31)
};
/*
*
*/
static void snd_ac97_powerdown(struct snd_ac97 *ac97);
static int snd_ac97_bus_free(struct snd_ac97_bus *bus)
{
if (bus) {
snd_ac97_bus_proc_done(bus);
kfree(bus->pcms);
if (bus->private_free)
bus->private_free(bus);
kfree(bus);
}
return 0;
}
static int snd_ac97_bus_dev_free(struct snd_device *device)
{
struct snd_ac97_bus *bus = device->device_data;
return snd_ac97_bus_free(bus);
}
static int snd_ac97_free(struct snd_ac97 *ac97)
{
if (ac97) {
#ifdef CONFIG_SND_AC97_POWER_SAVE
cancel_delayed_work_sync(&ac97->power_work);
#endif
snd_ac97_proc_done(ac97);
if (ac97->bus)
ac97->bus->codec[ac97->num] = NULL;
if (ac97->private_free)
ac97->private_free(ac97);
kfree(ac97);
}
return 0;
}
static int snd_ac97_dev_free(struct snd_device *device)
{
struct snd_ac97 *ac97 = device->device_data;
snd_ac97_powerdown(ac97); /* for avoiding click noises during shut down */
return snd_ac97_free(ac97);
}
static int snd_ac97_try_volume_mix(struct snd_ac97 * ac97, int reg)
{
unsigned short val, mask = AC97_MUTE_MASK_MONO;
if (! snd_ac97_valid_reg(ac97, reg))
return 0;
switch (reg) {
case AC97_MASTER_TONE:
return ac97->caps & AC97_BC_BASS_TREBLE ? 1 : 0;
case AC97_HEADPHONE:
return ac97->caps & AC97_BC_HEADPHONE ? 1 : 0;
case AC97_REC_GAIN_MIC:
return ac97->caps & AC97_BC_DEDICATED_MIC ? 1 : 0;
case AC97_3D_CONTROL:
if (ac97->caps & AC97_BC_3D_TECH_ID_MASK) {
val = snd_ac97_read(ac97, reg);
/* if nonzero - fixed and we can't set it */
return val == 0;
}
return 0;
case AC97_CENTER_LFE_MASTER: /* center */
if ((ac97->ext_id & AC97_EI_CDAC) == 0)
return 0;
break;
case AC97_CENTER_LFE_MASTER+1: /* lfe */
if ((ac97->ext_id & AC97_EI_LDAC) == 0)
return 0;
reg = AC97_CENTER_LFE_MASTER;
mask = 0x0080;
break;
case AC97_SURROUND_MASTER:
if ((ac97->ext_id & AC97_EI_SDAC) == 0)
return 0;
break;
}
val = snd_ac97_read(ac97, reg);
if (!(val & mask)) {
/* nothing seems to be here - mute flag is not set */
/* try another test */
snd_ac97_write_cache(ac97, reg, val | mask);
val = snd_ac97_read(ac97, reg);
val = snd_ac97_read(ac97, reg);
if (!(val & mask))
return 0; /* nothing here */
}
return 1; /* success, useable */
}
static void check_volume_resolution(struct snd_ac97 *ac97, int reg, unsigned char *lo_max, unsigned char *hi_max)
{
unsigned short cbit[3] = { 0x20, 0x10, 0x01 };
unsigned char max[3] = { 63, 31, 15 };
int i;
/* first look up the static resolution table */
if (ac97->res_table) {
const struct snd_ac97_res_table *tbl;
for (tbl = ac97->res_table; tbl->reg; tbl++) {
if (tbl->reg == reg) {
*lo_max = tbl->bits & 0xff;
*hi_max = (tbl->bits >> 8) & 0xff;
return;
}
}
}
*lo_max = *hi_max = 0;
for (i = 0 ; i < ARRAY_SIZE(cbit); i++) {
unsigned short val;
snd_ac97_write(
ac97, reg,
AC97_MUTE_MASK_STEREO | cbit[i] | (cbit[i] << 8)
);
/* Do the read twice due to buffers on some ac97 codecs.
* e.g. The STAC9704 returns exactly what you wrote to the register
* if you read it immediately. This causes the detect routine to fail.
*/
val = snd_ac97_read(ac97, reg);
val = snd_ac97_read(ac97, reg);
if (! *lo_max && (val & 0x7f) == cbit[i])
*lo_max = max[i];
if (! *hi_max && ((val >> 8) & 0x7f) == cbit[i])
*hi_max = max[i];
if (*lo_max && *hi_max)
break;
}
}
static int snd_ac97_try_bit(struct snd_ac97 * ac97, int reg, int bit)
{
unsigned short mask, val, orig, res;
mask = 1 << bit;
orig = snd_ac97_read(ac97, reg);
val = orig ^ mask;
snd_ac97_write(ac97, reg, val);
res = snd_ac97_read(ac97, reg);
snd_ac97_write_cache(ac97, reg, orig);
return res == val;
}
/* check the volume resolution of center/lfe */
static void snd_ac97_change_volume_params2(struct snd_ac97 * ac97, int reg, int shift, unsigned char *max)
{
unsigned short val, val1;
*max = 63;
val = AC97_MUTE_MASK_STEREO | (0x20 << shift);
snd_ac97_write(ac97, reg, val);
val1 = snd_ac97_read(ac97, reg);
if (val != val1) {
*max = 31;
}
/* reset volume to zero */
snd_ac97_write_cache(ac97, reg, AC97_MUTE_MASK_STEREO);
}
static inline int printable(unsigned int x)
{
x &= 0xff;
if (x < ' ' || x >= 0x71) {
if (x <= 0x89)
return x - 0x71 + 'A';
return '?';
}
return x;
}
static struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template,
struct snd_ac97 * ac97)
{
struct snd_kcontrol_new template;
memcpy(&template, _template, sizeof(template));
template.index = ac97->num;
return snd_ctl_new1(&template, ac97);
}
/*
* create mute switch(es) for normal stereo controls
*/
static int snd_ac97_cmute_new_stereo(struct snd_card *card, char *name, int reg,
int check_stereo, int check_amix,
struct snd_ac97 *ac97)
{
struct snd_kcontrol *kctl;
int err;
unsigned short val, val1, mute_mask;
if (! snd_ac97_valid_reg(ac97, reg))
return 0;
mute_mask = AC97_MUTE_MASK_MONO;
val = snd_ac97_read(ac97, reg);
if (check_stereo || (ac97->flags & AC97_STEREO_MUTES)) {
/* check whether both mute bits work */
val1 = val | AC97_MUTE_MASK_STEREO;
snd_ac97_write(ac97, reg, val1);
if (val1 == snd_ac97_read(ac97, reg))
mute_mask = AC97_MUTE_MASK_STEREO;
}
if (mute_mask == AC97_MUTE_MASK_STEREO) {
struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 15, 7, 1, 1);
if (check_amix)
tmp.private_value |= (1 << 30);
tmp.index = ac97->num;
kctl = snd_ctl_new1(&tmp, ac97);
} else {
struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 15, 1, 1);
if (check_amix)
tmp.private_value |= (1 << 30);
tmp.index = ac97->num;
kctl = snd_ctl_new1(&tmp, ac97);
}
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
/* mute as default */
snd_ac97_write_cache(ac97, reg, val | mute_mask);
return 0;
}
/*
* set dB information
*/
static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
static const unsigned int *find_db_scale(unsigned int maxval)
{
switch (maxval) {
case 0x0f: return db_scale_4bit;
case 0x1f: return db_scale_5bit;
case 0x3f: return db_scale_6bit;
}
return NULL;
}
static void set_tlv_db_scale(struct snd_kcontrol *kctl, const unsigned int *tlv)
{
kctl->tlv.p = tlv;
if (tlv)
kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
}
/*
* create a volume for normal stereo/mono controls
*/
static int snd_ac97_cvol_new(struct snd_card *card, char *name, int reg, unsigned int lo_max,
unsigned int hi_max, struct snd_ac97 *ac97)
{
int err;
struct snd_kcontrol *kctl;
if (! snd_ac97_valid_reg(ac97, reg))
return 0;
if (hi_max) {
/* invert */
struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 8, 0, lo_max, 1);
tmp.index = ac97->num;
kctl = snd_ctl_new1(&tmp, ac97);
} else {
/* invert */
struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 0, lo_max, 1);
tmp.index = ac97->num;
kctl = snd_ctl_new1(&tmp, ac97);
}
if (!kctl)
return -ENOMEM;
if (reg >= AC97_PHONE && reg <= AC97_PCM)
set_tlv_db_scale(kctl, db_scale_5bit_12db_max);
else
set_tlv_db_scale(kctl, find_db_scale(lo_max));
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
snd_ac97_write_cache(
ac97, reg,
(snd_ac97_read(ac97, reg) & AC97_MUTE_MASK_STEREO)
| lo_max | (hi_max << 8)
);
return 0;
}
/*
* create a mute-switch and a volume for normal stereo/mono controls
*/
static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx,
int reg, int check_stereo, int check_amix,
struct snd_ac97 *ac97)
{
int err;
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
unsigned char lo_max, hi_max;
if (! snd_ac97_valid_reg(ac97, reg))
return 0;
if (snd_ac97_try_bit(ac97, reg, 15)) {
sprintf(name, "%s Switch", pfx);
err = snd_ac97_cmute_new_stereo(card, name, reg,
check_stereo, check_amix,
ac97);
if (err < 0)
return err;
}
check_volume_resolution(ac97, reg, &lo_max, &hi_max);
if (lo_max) {
sprintf(name, "%s Volume", pfx);
err = snd_ac97_cvol_new(card, name, reg, lo_max, hi_max, ac97);
if (err < 0)
return err;
}
return 0;
}
#define snd_ac97_cmix_new(card, pfx, reg, acheck, ac97) \
snd_ac97_cmix_new_stereo(card, pfx, reg, 0, acheck, ac97)
#define snd_ac97_cmute_new(card, name, reg, acheck, ac97) \
snd_ac97_cmute_new_stereo(card, name, reg, 0, acheck, ac97)
static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97);
static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
{
struct snd_card *card = ac97->bus->card;
struct snd_kcontrol *kctl;
int err;
unsigned int idx;
unsigned char max;
/* build master controls */
/* AD claims to remove this control from AD1887, although spec v2.2 does not allow this */
if (snd_ac97_try_volume_mix(ac97, AC97_MASTER)) {
if (ac97->flags & AC97_HAS_NO_MASTER_VOL)
err = snd_ac97_cmute_new(card, "Master Playback Switch",
AC97_MASTER, 0, ac97);
else
err = snd_ac97_cmix_new(card, "Master Playback",
AC97_MASTER, 0, ac97);
if (err < 0)
return err;
}
ac97->regs[AC97_CENTER_LFE_MASTER] = AC97_MUTE_MASK_STEREO;
/* build center controls */
if ((snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER))
&& !(ac97->flags & AC97_AD_MULTI)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_center[0], ac97));
if (err < 0)
return err;
err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_center[1], ac97));
if (err < 0)
return err;
snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, 0, &max);
kctl->private_value &= ~(0xff << 16);
kctl->private_value |= (int)max << 16;
set_tlv_db_scale(kctl, find_db_scale(max));
snd_ac97_write_cache(ac97, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER] | max);
}
/* build LFE controls */
if ((snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER+1))
&& !(ac97->flags & AC97_AD_MULTI)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_lfe[0], ac97));
if (err < 0)
return err;
err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_lfe[1], ac97));
if (err < 0)
return err;
snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, 8, &max);
kctl->private_value &= ~(0xff << 16);
kctl->private_value |= (int)max << 16;
set_tlv_db_scale(kctl, find_db_scale(max));
snd_ac97_write_cache(ac97, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER] | max << 8);
}
/* build surround controls */
if ((snd_ac97_try_volume_mix(ac97, AC97_SURROUND_MASTER))
&& !(ac97->flags & AC97_AD_MULTI)) {
/* Surround Master (0x38) is with stereo mutes */
err = snd_ac97_cmix_new_stereo(card, "Surround Playback",
AC97_SURROUND_MASTER, 1, 0,
ac97);
if (err < 0)
return err;
}
/* build headphone controls */
if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) {
err = snd_ac97_cmix_new(card, "Headphone Playback",
AC97_HEADPHONE, 0, ac97);
if (err < 0)
return err;
}
/* build master mono controls */
if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_MONO)) {
err = snd_ac97_cmix_new(card, "Master Mono Playback",
AC97_MASTER_MONO, 0, ac97);
if (err < 0)
return err;
}
/* build master tone controls */
if (!(ac97->flags & AC97_HAS_NO_TONE)) {
if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_TONE)) {
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_tone[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
if (ac97->id == AC97_ID_YMF743 ||
ac97->id == AC97_ID_YMF753) {
kctl->private_value &= ~(0xff << 16);
kctl->private_value |= 7 << 16;
}
}
snd_ac97_write_cache(ac97, AC97_MASTER_TONE, 0x0f0f);
}
}
/* build Beep controls */
if (!(ac97->flags & AC97_HAS_NO_PC_BEEP) &&
((ac97->flags & AC97_HAS_PC_BEEP) ||
snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP))) {
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_pc_beep[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
set_tlv_db_scale(kctl, db_scale_4bit);
snd_ac97_write_cache(
ac97,
AC97_PC_BEEP,
(snd_ac97_read(ac97, AC97_PC_BEEP)
| AC97_MUTE_MASK_MONO | 0x001e)
);
}
/* build Phone controls */
if (!(ac97->flags & AC97_HAS_NO_PHONE)) {
if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) {
err = snd_ac97_cmix_new(card, "Phone Playback",
AC97_PHONE, 1, ac97);
if (err < 0)
return err;
}
}
/* build MIC controls */
if (!(ac97->flags & AC97_HAS_NO_MIC)) {
if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) {
err = snd_ac97_cmix_new(card, "Mic Playback",
AC97_MIC, 1, ac97);
if (err < 0)
return err;
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_mic_boost, ac97));
if (err < 0)
return err;
}
}
/* build Line controls */
if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) {
err = snd_ac97_cmix_new(card, "Line Playback",
AC97_LINE, 1, ac97);
if (err < 0)
return err;
}
/* build CD controls */
if (!(ac97->flags & AC97_HAS_NO_CD)) {
if (snd_ac97_try_volume_mix(ac97, AC97_CD)) {
err = snd_ac97_cmix_new(card, "CD Playback",
AC97_CD, 1, ac97);
if (err < 0)
return err;
}
}
/* build Video controls */
if (!(ac97->flags & AC97_HAS_NO_VIDEO)) {
if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) {
err = snd_ac97_cmix_new(card, "Video Playback",
AC97_VIDEO, 1, ac97);
if (err < 0)
return err;
}
}
/* build Aux controls */
if (!(ac97->flags & AC97_HAS_NO_AUX)) {
if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) {
err = snd_ac97_cmix_new(card, "Aux Playback",
AC97_AUX, 1, ac97);
if (err < 0)
return err;
}
}
/* build PCM controls */
if (ac97->flags & AC97_AD_MULTI) {
unsigned short init_val;
if (ac97->flags & AC97_STEREO_MUTES)
init_val = 0x9f9f;
else
init_val = 0x9f1f;
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_pcm[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
set_tlv_db_scale(kctl, db_scale_5bit);
ac97->spec.ad18xx.pcmreg[0] = init_val;
if (ac97->scaps & AC97_SCAP_SURROUND_DAC) {
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_surround[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
set_tlv_db_scale(kctl, db_scale_5bit);
ac97->spec.ad18xx.pcmreg[1] = init_val;
}
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) {
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_center[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
set_tlv_db_scale(kctl, db_scale_5bit);
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_lfe[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
set_tlv_db_scale(kctl, db_scale_5bit);
ac97->spec.ad18xx.pcmreg[2] = init_val;
}
snd_ac97_write_cache(ac97, AC97_PCM, init_val);
} else {
if (!(ac97->flags & AC97_HAS_NO_STD_PCM)) {
if (ac97->flags & AC97_HAS_NO_PCM_VOL)
err = snd_ac97_cmute_new(card,
"PCM Playback Switch",
AC97_PCM, 0, ac97);
else
err = snd_ac97_cmix_new(card, "PCM Playback",
AC97_PCM, 0, ac97);
if (err < 0)
return err;
}
}
/* build Capture controls */
if (!(ac97->flags & AC97_HAS_NO_REC_GAIN)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97));
if (err < 0)
return err;
if (snd_ac97_try_bit(ac97, AC97_REC_GAIN, 15)) {
err = snd_ac97_cmute_new(card, "Capture Switch",
AC97_REC_GAIN, 0, ac97);
if (err < 0)
return err;
}
kctl = snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
set_tlv_db_scale(kctl, db_scale_rec_gain);
snd_ac97_write_cache(ac97, AC97_REC_SEL, 0x0000);
snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x0000);
}
/* build MIC Capture controls */
if (snd_ac97_try_volume_mix(ac97, AC97_REC_GAIN_MIC)) {
for (idx = 0; idx < 2; idx++) {
kctl = snd_ac97_cnew(&snd_ac97_controls_mic_capture[idx], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
set_tlv_db_scale(kctl, db_scale_rec_gain);
snd_ac97_write_cache(ac97, AC97_REC_GAIN_MIC, 0x0000);
}
/* build PCM out path & mute control */
if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 15)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_PCM_OUT], ac97));
if (err < 0)
return err;
}
/* build Simulated Stereo Enhancement control */
if (ac97->caps & AC97_BC_SIM_STEREO) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_STEREO_ENHANCEMENT], ac97));
if (err < 0)
return err;
}
/* build 3D Stereo Enhancement control */
if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 13)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_3D], ac97));
if (err < 0)
return err;
}
/* build Loudness control */
if (ac97->caps & AC97_BC_LOUDNESS) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOUDNESS], ac97));
if (err < 0)
return err;
}
/* build Mono output select control */
if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 9)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MONO], ac97));
if (err < 0)
return err;
}
/* build Mic select control */
if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 8)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MIC], ac97));
if (err < 0)
return err;
}
/* build ADC/DAC loopback control */
if (enable_loopback && snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 7)) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOOPBACK], ac97));
if (err < 0)
return err;
}
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, ~AC97_GP_DRSS_MASK, 0x0000);
/* build 3D controls */
if (ac97->build_ops->build_3d) {
ac97->build_ops->build_3d(ac97);
} else {
if (snd_ac97_try_volume_mix(ac97, AC97_3D_CONTROL)) {
unsigned short val;
val = 0x0707;
snd_ac97_write(ac97, AC97_3D_CONTROL, val);
val = snd_ac97_read(ac97, AC97_3D_CONTROL);
val = val == 0x0606;
kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
if (val)
kctl->private_value = AC97_3D_CONTROL | (9 << 8) | (7 << 16);
kctl = snd_ac97_cnew(&snd_ac97_controls_3d[1], ac97);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
if (val)
kctl->private_value = AC97_3D_CONTROL | (1 << 8) | (7 << 16);
snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
}
}
/* build S/PDIF controls */
/* Hack for ASUS P5P800-VM, which does not indicate S/PDIF capability */
if (ac97->subsystem_vendor == 0x1043 &&
ac97->subsystem_device == 0x810f)
ac97->ext_id |= AC97_EI_SPDIF;
if ((ac97->ext_id & AC97_EI_SPDIF) && !(ac97->scaps & AC97_SCAP_NO_SPDIF)) {
if (ac97->build_ops->build_spdif) {
err = ac97->build_ops->build_spdif(ac97);
if (err < 0)
return err;
} else {
for (idx = 0; idx < 5; idx++) {
err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_spdif[idx], ac97));
if (err < 0)
return err;
}
if (ac97->build_ops->build_post_spdif) {
err = ac97->build_ops->build_post_spdif(ac97);
if (err < 0)
return err;
}
/* set default PCM S/PDIF params */
/* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
snd_ac97_write_cache(ac97, AC97_SPDIF, 0x2a20);
ac97->rates[AC97_RATES_SPDIF] = snd_ac97_determine_spdif_rates(ac97);
}
ac97->spdif_status = SNDRV_PCM_DEFAULT_CON_SPDIF;
}
/* build chip specific controls */
if (ac97->build_ops->build_specific) {
err = ac97->build_ops->build_specific(ac97);
if (err < 0)
return err;
}
if (snd_ac97_try_bit(ac97, AC97_POWERDOWN, 15)) {
kctl = snd_ac97_cnew(&snd_ac97_control_eapd, ac97);
if (! kctl)
return -ENOMEM;
if (ac97->scaps & AC97_SCAP_INV_EAPD)
set_inv_eapd(ac97, kctl);
err = snd_ctl_add(card, kctl);
if (err < 0)
return err;
}
return 0;
}
static int snd_ac97_modem_build(struct snd_card *card, struct snd_ac97 * ac97)
{
int err, idx;
/*
ac97_dbg(ac97, "AC97_GPIO_CFG = %x\n",
snd_ac97_read(ac97,AC97_GPIO_CFG));
*/
snd_ac97_write(ac97, AC97_GPIO_CFG, 0xffff & ~(AC97_GPIO_LINE1_OH));
snd_ac97_write(ac97, AC97_GPIO_POLARITY, 0xffff & ~(AC97_GPIO_LINE1_OH));
snd_ac97_write(ac97, AC97_GPIO_STICKY, 0xffff);
snd_ac97_write(ac97, AC97_GPIO_WAKEUP, 0x0);
snd_ac97_write(ac97, AC97_MISC_AFE, 0x0);
/* build modem switches */
for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_modem_switches); idx++) {
err = snd_ctl_add(card, snd_ctl_new1(&snd_ac97_controls_modem_switches[idx], ac97));
if (err < 0)
return err;
}
/* build chip specific controls */
if (ac97->build_ops->build_specific) {
err = ac97->build_ops->build_specific(ac97);
if (err < 0)
return err;
}
return 0;
}
static int snd_ac97_test_rate(struct snd_ac97 *ac97, int reg, int shadow_reg, int rate)
{
unsigned short val;
unsigned int tmp;
tmp = ((unsigned int)rate * ac97->bus->clock) / 48000;
snd_ac97_write_cache(ac97, reg, tmp & 0xffff);
if (shadow_reg)
snd_ac97_write_cache(ac97, shadow_reg, tmp & 0xffff);
val = snd_ac97_read(ac97, reg);
return val == (tmp & 0xffff);
}
static void snd_ac97_determine_rates(struct snd_ac97 *ac97, int reg, int shadow_reg, unsigned int *r_result)
{
unsigned int result = 0;
unsigned short saved;
if (ac97->bus->no_vra) {
*r_result = SNDRV_PCM_RATE_48000;
if ((ac97->flags & AC97_DOUBLE_RATE) &&
reg == AC97_PCM_FRONT_DAC_RATE)
*r_result |= SNDRV_PCM_RATE_96000;
return;
}
saved = snd_ac97_read(ac97, reg);
if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE)
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
AC97_EA_DRA, 0);
/* test a non-standard rate */
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 11000))
result |= SNDRV_PCM_RATE_CONTINUOUS;
/* let's try to obtain standard rates */
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 8000))
result |= SNDRV_PCM_RATE_8000;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 11025))
result |= SNDRV_PCM_RATE_11025;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 16000))
result |= SNDRV_PCM_RATE_16000;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 22050))
result |= SNDRV_PCM_RATE_22050;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 32000))
result |= SNDRV_PCM_RATE_32000;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 44100))
result |= SNDRV_PCM_RATE_44100;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 48000))
result |= SNDRV_PCM_RATE_48000;
if ((ac97->flags & AC97_DOUBLE_RATE) &&
reg == AC97_PCM_FRONT_DAC_RATE) {
/* test standard double rates */
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
AC97_EA_DRA, AC97_EA_DRA);
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 64000 / 2))
result |= SNDRV_PCM_RATE_64000;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 88200 / 2))
result |= SNDRV_PCM_RATE_88200;
if (snd_ac97_test_rate(ac97, reg, shadow_reg, 96000 / 2))
result |= SNDRV_PCM_RATE_96000;
/* some codecs don't support variable double rates */
if (!snd_ac97_test_rate(ac97, reg, shadow_reg, 76100 / 2))
result &= ~SNDRV_PCM_RATE_CONTINUOUS;
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
AC97_EA_DRA, 0);
}
/* restore the default value */
snd_ac97_write_cache(ac97, reg, saved);
if (shadow_reg)
snd_ac97_write_cache(ac97, shadow_reg, saved);
*r_result = result;
}
/* check AC97_SPDIF register to accept which sample rates */
static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97)
{
unsigned int result = 0;
int i;
static const unsigned short ctl_bits[] = {
AC97_SC_SPSR_44K, AC97_SC_SPSR_32K, AC97_SC_SPSR_48K
};
static const unsigned int rate_bits[] = {
SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_32000, SNDRV_PCM_RATE_48000
};
for (i = 0; i < (int)ARRAY_SIZE(ctl_bits); i++) {
snd_ac97_update_bits(ac97, AC97_SPDIF, AC97_SC_SPSR_MASK, ctl_bits[i]);
if ((snd_ac97_read(ac97, AC97_SPDIF) & AC97_SC_SPSR_MASK) == ctl_bits[i])
result |= rate_bits[i];
}
return result;
}
/* look for the codec id table matching with the given id */
static const struct ac97_codec_id *look_for_codec_id(const struct ac97_codec_id *table,
unsigned int id)
{
const struct ac97_codec_id *pid;
for (pid = table; pid->id; pid++)
if (pid->id == (id & pid->mask))
return pid;
return NULL;
}
void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name, int modem)
{
const struct ac97_codec_id *pid;
sprintf(name, "0x%x %c%c%c", id,
printable(id >> 24),
printable(id >> 16),
printable(id >> 8));
pid = look_for_codec_id(snd_ac97_codec_id_vendors, id);
if (! pid)
return;
strcpy(name, pid->name);
if (ac97 && pid->patch) {
if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
(! modem && ! (pid->flags & AC97_MODEM_PATCH)))
pid->patch(ac97);
}
pid = look_for_codec_id(snd_ac97_codec_ids, id);
if (pid) {
strcat(name, " ");
strcat(name, pid->name);
if (pid->mask != 0xffffffff)
sprintf(name + strlen(name), " rev %d", id & ~pid->mask);
if (ac97 && pid->patch) {
if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
(! modem && ! (pid->flags & AC97_MODEM_PATCH)))
pid->patch(ac97);
}
} else
sprintf(name + strlen(name), " id %x", id & 0xff);
}
/**
* snd_ac97_get_short_name - retrieve codec name
* @ac97: the codec instance
*
* Return: The short identifying name of the codec.
*/
const char *snd_ac97_get_short_name(struct snd_ac97 *ac97)
{
const struct ac97_codec_id *pid;
for (pid = snd_ac97_codec_ids; pid->id; pid++)
if (pid->id == (ac97->id & pid->mask))
return pid->name;
return "unknown codec";
}
EXPORT_SYMBOL(snd_ac97_get_short_name);
/* wait for a while until registers are accessible after RESET
* return 0 if ok, negative not ready
*/
static int ac97_reset_wait(struct snd_ac97 *ac97, int timeout, int with_modem)
{
unsigned long end_time;
unsigned short val;
end_time = jiffies + timeout;
do {
/* use preliminary reads to settle the communication */
snd_ac97_read(ac97, AC97_RESET);
snd_ac97_read(ac97, AC97_VENDOR_ID1);
snd_ac97_read(ac97, AC97_VENDOR_ID2);
/* modem? */
if (with_modem) {
val = snd_ac97_read(ac97, AC97_EXTENDED_MID);
if (val != 0xffff && (val & 1) != 0)
return 0;
}
if (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) {
/* probably only Xbox issue - all registers are read as zero */
val = snd_ac97_read(ac97, AC97_VENDOR_ID1);
if (val != 0 && val != 0xffff)
return 0;
} else {
/* because the PCM or MASTER volume registers can be modified,
* the REC_GAIN register is used for tests
*/
/* test if we can write to the record gain volume register */
snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a05);
if ((snd_ac97_read(ac97, AC97_REC_GAIN) & 0x7fff) == 0x0a05)
return 0;
}
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
return -ENODEV;
}
/**
* snd_ac97_bus - create an AC97 bus component
* @card: the card instance
* @num: the bus number
* @ops: the bus callbacks table
* @private_data: private data pointer for the new instance
* @rbus: the pointer to store the new AC97 bus instance.
*
* Creates an AC97 bus component. An struct snd_ac97_bus instance is newly
* allocated and initialized.
*
* The ops table must include valid callbacks (at least read and
* write). The other callbacks, wait and reset, are not mandatory.
*
* The clock is set to 48000. If another clock is needed, set
* ``(*rbus)->clock`` manually.
*
* The AC97 bus instance is registered as a low-level device, so you don't
* have to release it manually.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_bus(struct snd_card *card, int num,
const struct snd_ac97_bus_ops *ops,
void *private_data, struct snd_ac97_bus **rbus)
{
int err;
struct snd_ac97_bus *bus;
static const struct snd_device_ops dev_ops = {
.dev_free = snd_ac97_bus_dev_free,
};
if (snd_BUG_ON(!card))
return -EINVAL;
bus = kzalloc(sizeof(*bus), GFP_KERNEL);
if (bus == NULL)
return -ENOMEM;
bus->card = card;
bus->num = num;
bus->ops = ops;
bus->private_data = private_data;
bus->clock = 48000;
spin_lock_init(&bus->bus_lock);
snd_ac97_bus_proc_init(bus);
err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);
if (err < 0) {
snd_ac97_bus_free(bus);
return err;
}
if (rbus)
*rbus = bus;
return 0;
}
EXPORT_SYMBOL(snd_ac97_bus);
/* stop no dev release warning */
static void ac97_device_release(struct device * dev)
{
}
/* register ac97 codec to bus */
static int snd_ac97_dev_register(struct snd_device *device)
{
struct snd_ac97 *ac97 = device->device_data;
int err;
ac97->dev.bus = &ac97_bus_type;
ac97->dev.parent = ac97->bus->card->dev;
ac97->dev.release = ac97_device_release;
dev_set_name(&ac97->dev, "%d-%d:%s",
ac97->bus->card->number, ac97->num,
snd_ac97_get_short_name(ac97));
err = device_register(&ac97->dev);
if (err < 0) {
ac97_err(ac97, "Can't register ac97 bus\n");
ac97->dev.bus = NULL;
return err;
}
return 0;
}
/* disconnect ac97 codec */
static int snd_ac97_dev_disconnect(struct snd_device *device)
{
struct snd_ac97 *ac97 = device->device_data;
if (ac97->dev.bus)
device_unregister(&ac97->dev);
return 0;
}
/* build_ops to do nothing */
static const struct snd_ac97_build_ops null_build_ops;
#ifdef CONFIG_SND_AC97_POWER_SAVE
static void do_update_power(struct work_struct *work)
{
update_power_regs(
container_of(work, struct snd_ac97, power_work.work));
}
#endif
/**
* snd_ac97_mixer - create an Codec97 component
* @bus: the AC97 bus which codec is attached to
* @template: the template of ac97, including index, callbacks and
* the private data.
* @rac97: the pointer to store the new ac97 instance.
*
* Creates an Codec97 component. An struct snd_ac97 instance is newly
* allocated and initialized from the template. The codec
* is then initialized by the standard procedure.
*
* The template must include the codec number (num) and address (addr),
* and the private data (private_data).
*
* The ac97 instance is registered as a low-level device, so you don't
* have to release it manually.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97)
{
int err;
struct snd_ac97 *ac97;
struct snd_card *card;
char name[64];
unsigned long end_time;
unsigned int reg;
const struct ac97_codec_id *pid;
static const struct snd_device_ops ops = {
.dev_free = snd_ac97_dev_free,
.dev_register = snd_ac97_dev_register,
.dev_disconnect = snd_ac97_dev_disconnect,
};
if (rac97)
*rac97 = NULL;
if (snd_BUG_ON(!bus || !template))
return -EINVAL;
if (snd_BUG_ON(template->num >= 4))
return -EINVAL;
if (bus->codec[template->num])
return -EBUSY;
card = bus->card;
ac97 = kzalloc(sizeof(*ac97), GFP_KERNEL);
if (ac97 == NULL)
return -ENOMEM;
ac97->private_data = template->private_data;
ac97->private_free = template->private_free;
ac97->bus = bus;
ac97->pci = template->pci;
ac97->num = template->num;
ac97->addr = template->addr;
ac97->scaps = template->scaps;
ac97->res_table = template->res_table;
bus->codec[ac97->num] = ac97;
mutex_init(&ac97->reg_mutex);
mutex_init(&ac97->page_mutex);
#ifdef CONFIG_SND_AC97_POWER_SAVE
INIT_DELAYED_WORK(&ac97->power_work, do_update_power);
#endif
#ifdef CONFIG_PCI
if (ac97->pci) {
pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_VENDOR_ID, &ac97->subsystem_vendor);
pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_ID, &ac97->subsystem_device);
}
#endif
if (bus->ops->reset) {
bus->ops->reset(ac97);
goto __access_ok;
}
ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16;
ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2);
if (ac97->id && ac97->id != (unsigned int)-1) {
pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id);
if (pid && (pid->flags & AC97_DEFAULT_POWER_OFF))
goto __access_ok;
}
/* reset to defaults */
if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO))
snd_ac97_write(ac97, AC97_RESET, 0);
if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM))
snd_ac97_write(ac97, AC97_EXTENDED_MID, 0);
if (bus->ops->wait)
bus->ops->wait(ac97);
else {
udelay(50);
if (ac97->scaps & AC97_SCAP_SKIP_AUDIO)
err = ac97_reset_wait(ac97, msecs_to_jiffies(500), 1);
else {
err = ac97_reset_wait(ac97, msecs_to_jiffies(500), 0);
if (err < 0)
err = ac97_reset_wait(ac97,
msecs_to_jiffies(500), 1);
}
if (err < 0) {
ac97_warn(ac97, "AC'97 %d does not respond - RESET\n",
ac97->num);
/* proceed anyway - it's often non-critical */
}
}
__access_ok:
ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16;
ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2);
if (! (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) &&
(ac97->id == 0x00000000 || ac97->id == 0xffffffff)) {
ac97_err(ac97,
"AC'97 %d access is not valid [0x%x], removing mixer.\n",
ac97->num, ac97->id);
snd_ac97_free(ac97);
return -EIO;
}
pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id);
if (pid)
ac97->flags |= pid->flags;
/* test for AC'97 */
if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO) && !(ac97->scaps & AC97_SCAP_AUDIO)) {
/* test if we can write to the record gain volume register */
snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a06);
err = snd_ac97_read(ac97, AC97_REC_GAIN);
if ((err & 0x7fff) == 0x0a06)
ac97->scaps |= AC97_SCAP_AUDIO;
}
if (ac97->scaps & AC97_SCAP_AUDIO) {
ac97->caps = snd_ac97_read(ac97, AC97_RESET);
ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID);
if (ac97->ext_id == 0xffff) /* invalid combination */
ac97->ext_id = 0;
}
/* test for MC'97 */
if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM) && !(ac97->scaps & AC97_SCAP_MODEM)) {
ac97->ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID);
if (ac97->ext_mid == 0xffff) /* invalid combination */
ac97->ext_mid = 0;
if (ac97->ext_mid & 1)
ac97->scaps |= AC97_SCAP_MODEM;
}
if (!ac97_is_audio(ac97) && !ac97_is_modem(ac97)) {
if (!(ac97->scaps & (AC97_SCAP_SKIP_AUDIO|AC97_SCAP_SKIP_MODEM)))
ac97_err(ac97,
"AC'97 %d access error (not audio or modem codec)\n",
ac97->num);
snd_ac97_free(ac97);
return -EACCES;
}
if (bus->ops->reset) // FIXME: always skipping?
goto __ready_ok;
/* FIXME: add powerdown control */
if (ac97_is_audio(ac97)) {
/* nothing should be in powerdown mode */
snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0);
if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) {
snd_ac97_write_cache(ac97, AC97_RESET, 0); /* reset to defaults */
udelay(100);
snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0);
}
/* nothing should be in powerdown mode */
snd_ac97_write_cache(ac97, AC97_GENERAL_PURPOSE, 0);
end_time = jiffies + msecs_to_jiffies(5000);
do {
if ((snd_ac97_read(ac97, AC97_POWERDOWN) & 0x0f) == 0x0f)
goto __ready_ok;
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
ac97_warn(ac97,
"AC'97 %d analog subsections not ready\n", ac97->num);
}
/* FIXME: add powerdown control */
if (ac97_is_modem(ac97)) {
unsigned char tmp;
/* nothing should be in powerdown mode */
/* note: it's important to set the rate at first */
tmp = AC97_MEA_GPIO;
if (ac97->ext_mid & AC97_MEI_LINE1) {
snd_ac97_write_cache(ac97, AC97_LINE1_RATE, 8000);
tmp |= AC97_MEA_ADC1 | AC97_MEA_DAC1;
}
if (ac97->ext_mid & AC97_MEI_LINE2) {
snd_ac97_write_cache(ac97, AC97_LINE2_RATE, 8000);
tmp |= AC97_MEA_ADC2 | AC97_MEA_DAC2;
}
if (ac97->ext_mid & AC97_MEI_HANDSET) {
snd_ac97_write_cache(ac97, AC97_HANDSET_RATE, 8000);
tmp |= AC97_MEA_HADC | AC97_MEA_HDAC;
}
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0);
udelay(100);
/* nothing should be in powerdown mode */
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0);
end_time = jiffies + msecs_to_jiffies(100);
do {
if ((snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS) & tmp) == tmp)
goto __ready_ok;
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
ac97_warn(ac97,
"MC'97 %d converters and GPIO not ready (0x%x)\n",
ac97->num,
snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS));
}
__ready_ok:
if (ac97_is_audio(ac97))
ac97->addr = (ac97->ext_id & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT;
else
ac97->addr = (ac97->ext_mid & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT;
if (ac97->ext_id & 0x01c9) { /* L/R, MIC, SDAC, LDAC VRA support */
reg = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
reg |= ac97->ext_id & 0x01c0; /* LDAC/SDAC/CDAC */
if (! bus->no_vra)
reg |= ac97->ext_id & 0x0009; /* VRA/VRM */
snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, reg);
}
if ((ac97->ext_id & AC97_EI_DRA) && bus->dra) {
/* Intel controllers require double rate data to be put in
* slots 7+8, so let's hope the codec supports it. */
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, AC97_GP_DRSS_78);
if ((snd_ac97_read(ac97, AC97_GENERAL_PURPOSE) & AC97_GP_DRSS_MASK) == AC97_GP_DRSS_78)
ac97->flags |= AC97_DOUBLE_RATE;
/* restore to slots 10/11 to avoid the confliction with surrounds */
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, 0);
}
if (ac97->ext_id & AC97_EI_VRA) { /* VRA support */
snd_ac97_determine_rates(ac97, AC97_PCM_FRONT_DAC_RATE, 0, &ac97->rates[AC97_RATES_FRONT_DAC]);
snd_ac97_determine_rates(ac97, AC97_PCM_LR_ADC_RATE, 0, &ac97->rates[AC97_RATES_ADC]);
} else {
ac97->rates[AC97_RATES_FRONT_DAC] = SNDRV_PCM_RATE_48000;
if (ac97->flags & AC97_DOUBLE_RATE)
ac97->rates[AC97_RATES_FRONT_DAC] |= SNDRV_PCM_RATE_96000;
ac97->rates[AC97_RATES_ADC] = SNDRV_PCM_RATE_48000;
}
if (ac97->ext_id & AC97_EI_SPDIF) {
/* codec specific code (patch) should override these values */
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000;
}
if (ac97->ext_id & AC97_EI_VRM) { /* MIC VRA support */
snd_ac97_determine_rates(ac97, AC97_PCM_MIC_ADC_RATE, 0, &ac97->rates[AC97_RATES_MIC_ADC]);
} else {
ac97->rates[AC97_RATES_MIC_ADC] = SNDRV_PCM_RATE_48000;
}
if (ac97->ext_id & AC97_EI_SDAC) { /* SDAC support */
snd_ac97_determine_rates(ac97, AC97_PCM_SURR_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_SURR_DAC]);
ac97->scaps |= AC97_SCAP_SURROUND_DAC;
}
if (ac97->ext_id & AC97_EI_LDAC) { /* LDAC support */
snd_ac97_determine_rates(ac97, AC97_PCM_LFE_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_LFE_DAC]);
ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC;
}
/* additional initializations */
if (bus->ops->init)
bus->ops->init(ac97);
snd_ac97_get_name(ac97, ac97->id, name, !ac97_is_audio(ac97));
snd_ac97_get_name(NULL, ac97->id, name, !ac97_is_audio(ac97)); // ac97->id might be changed in the special setup code
if (! ac97->build_ops)
ac97->build_ops = &null_build_ops;
if (ac97_is_audio(ac97)) {
char comp[16];
if (card->mixername[0] == '\0') {
strcpy(card->mixername, name);
} else {
if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) {
strcat(card->mixername, ",");
strcat(card->mixername, name);
}
}
sprintf(comp, "AC97a:%08x", ac97->id);
err = snd_component_add(card, comp);
if (err < 0) {
snd_ac97_free(ac97);
return err;
}
if (snd_ac97_mixer_build(ac97) < 0) {
snd_ac97_free(ac97);
return -ENOMEM;
}
}
if (ac97_is_modem(ac97)) {
char comp[16];
if (card->mixername[0] == '\0') {
strcpy(card->mixername, name);
} else {
if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) {
strcat(card->mixername, ",");
strcat(card->mixername, name);
}
}
sprintf(comp, "AC97m:%08x", ac97->id);
err = snd_component_add(card, comp);
if (err < 0) {
snd_ac97_free(ac97);
return err;
}
if (snd_ac97_modem_build(card, ac97) < 0) {
snd_ac97_free(ac97);
return -ENOMEM;
}
}
if (ac97_is_audio(ac97))
update_power_regs(ac97);
snd_ac97_proc_init(ac97);
err = snd_device_new(card, SNDRV_DEV_CODEC, ac97, &ops);
if (err < 0) {
snd_ac97_free(ac97);
return err;
}
*rac97 = ac97;
return 0;
}
EXPORT_SYMBOL(snd_ac97_mixer);
/*
* Power down the chip.
*
* MASTER and HEADPHONE registers are muted but the register cache values
* are not changed, so that the values can be restored in snd_ac97_resume().
*/
static void snd_ac97_powerdown(struct snd_ac97 *ac97)
{
unsigned short power;
if (ac97_is_audio(ac97)) {
/* some codecs have stereo mute bits */
snd_ac97_write(ac97, AC97_MASTER, 0x9f9f);
snd_ac97_write(ac97, AC97_HEADPHONE, 0x9f9f);
}
/* surround, CLFE, mic powerdown */
power = ac97->regs[AC97_EXTENDED_STATUS];
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
power |= AC97_EA_PRJ;
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
power |= AC97_EA_PRI | AC97_EA_PRK;
power |= AC97_EA_PRL;
snd_ac97_write(ac97, AC97_EXTENDED_STATUS, power);
/* powerdown external amplifier */
if (ac97->scaps & AC97_SCAP_INV_EAPD)
power = ac97->regs[AC97_POWERDOWN] & ~AC97_PD_EAPD;
else if (! (ac97->scaps & AC97_SCAP_EAPD_LED))
power = ac97->regs[AC97_POWERDOWN] | AC97_PD_EAPD;
power |= AC97_PD_PR6; /* Headphone amplifier powerdown */
power |= AC97_PD_PR0 | AC97_PD_PR1; /* ADC & DAC powerdown */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
udelay(100);
power |= AC97_PD_PR2; /* Analog Mixer powerdown (Vref on) */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
if (ac97_is_power_save_mode(ac97)) {
power |= AC97_PD_PR3; /* Analog Mixer powerdown */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
udelay(100);
/* AC-link powerdown, internal Clk disable */
/* FIXME: this may cause click noises on some boards */
power |= AC97_PD_PR4 | AC97_PD_PR5;
snd_ac97_write(ac97, AC97_POWERDOWN, power);
}
}
struct ac97_power_reg {
unsigned short reg;
unsigned short power_reg;
unsigned short mask;
};
enum { PWIDX_ADC, PWIDX_FRONT, PWIDX_CLFE, PWIDX_SURR, PWIDX_MIC, PWIDX_SIZE };
static const struct ac97_power_reg power_regs[PWIDX_SIZE] = {
[PWIDX_ADC] = { AC97_PCM_LR_ADC_RATE, AC97_POWERDOWN, AC97_PD_PR0},
[PWIDX_FRONT] = { AC97_PCM_FRONT_DAC_RATE, AC97_POWERDOWN, AC97_PD_PR1},
[PWIDX_CLFE] = { AC97_PCM_LFE_DAC_RATE, AC97_EXTENDED_STATUS,
AC97_EA_PRI | AC97_EA_PRK},
[PWIDX_SURR] = { AC97_PCM_SURR_DAC_RATE, AC97_EXTENDED_STATUS,
AC97_EA_PRJ},
[PWIDX_MIC] = { AC97_PCM_MIC_ADC_RATE, AC97_EXTENDED_STATUS,
AC97_EA_PRL},
};
#ifdef CONFIG_SND_AC97_POWER_SAVE
/**
* snd_ac97_update_power - update the powerdown register
* @ac97: the codec instance
* @reg: the rate register, e.g. AC97_PCM_FRONT_DAC_RATE
* @powerup: non-zero when power up the part
*
* Update the AC97 powerdown register bits of the given part.
*
* Return: Zero.
*/
int snd_ac97_update_power(struct snd_ac97 *ac97, int reg, int powerup)
{
int i;
if (! ac97)
return 0;
if (reg) {
/* SPDIF requires DAC power, too */
if (reg == AC97_SPDIF)
reg = AC97_PCM_FRONT_DAC_RATE;
for (i = 0; i < PWIDX_SIZE; i++) {
if (power_regs[i].reg == reg) {
if (powerup)
ac97->power_up |= (1 << i);
else
ac97->power_up &= ~(1 << i);
break;
}
}
}
if (ac97_is_power_save_mode(ac97) && !powerup)
/* adjust power-down bits after two seconds delay
* (for avoiding loud click noises for many (OSS) apps
* that open/close frequently)
*/
schedule_delayed_work(&ac97->power_work,
msecs_to_jiffies(power_save * 1000));
else {
cancel_delayed_work(&ac97->power_work);
update_power_regs(ac97);
}
return 0;
}
EXPORT_SYMBOL(snd_ac97_update_power);
#endif /* CONFIG_SND_AC97_POWER_SAVE */
static void update_power_regs(struct snd_ac97 *ac97)
{
unsigned int power_up, bits;
int i;
power_up = (1 << PWIDX_FRONT) | (1 << PWIDX_ADC);
power_up |= (1 << PWIDX_MIC);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
power_up |= (1 << PWIDX_SURR);
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
power_up |= (1 << PWIDX_CLFE);
#ifdef CONFIG_SND_AC97_POWER_SAVE
if (ac97_is_power_save_mode(ac97))
power_up = ac97->power_up;
#endif
if (power_up) {
if (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2) {
/* needs power-up analog mix and vref */
snd_ac97_update_bits(ac97, AC97_POWERDOWN,
AC97_PD_PR3, 0);
msleep(1);
snd_ac97_update_bits(ac97, AC97_POWERDOWN,
AC97_PD_PR2, 0);
}
}
for (i = 0; i < PWIDX_SIZE; i++) {
if (power_up & (1 << i))
bits = 0;
else
bits = power_regs[i].mask;
snd_ac97_update_bits(ac97, power_regs[i].power_reg,
power_regs[i].mask, bits);
}
if (! power_up) {
if (! (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2)) {
/* power down analog mix and vref */
snd_ac97_update_bits(ac97, AC97_POWERDOWN,
AC97_PD_PR2, AC97_PD_PR2);
snd_ac97_update_bits(ac97, AC97_POWERDOWN,
AC97_PD_PR3, AC97_PD_PR3);
}
}
}
#ifdef CONFIG_PM
/**
* snd_ac97_suspend - General suspend function for AC97 codec
* @ac97: the ac97 instance
*
* Suspends the codec, power down the chip.
*/
void snd_ac97_suspend(struct snd_ac97 *ac97)
{
if (! ac97)
return;
if (ac97->build_ops->suspend)
ac97->build_ops->suspend(ac97);
#ifdef CONFIG_SND_AC97_POWER_SAVE
cancel_delayed_work_sync(&ac97->power_work);
#endif
snd_ac97_powerdown(ac97);
}
EXPORT_SYMBOL(snd_ac97_suspend);
/*
* restore ac97 status
*/
static void snd_ac97_restore_status(struct snd_ac97 *ac97)
{
int i;
for (i = 2; i < 0x7c ; i += 2) {
if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID)
continue;
/* restore only accessible registers
* some chip (e.g. nm256) may hang up when unsupported registers
* are accessed..!
*/
if (test_bit(i, ac97->reg_accessed)) {
snd_ac97_write(ac97, i, ac97->regs[i]);
snd_ac97_read(ac97, i);
}
}
}
/*
* restore IEC958 status
*/
static void snd_ac97_restore_iec958(struct snd_ac97 *ac97)
{
if (ac97->ext_id & AC97_EI_SPDIF) {
if (ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_SPDIF) {
/* reset spdif status */
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
snd_ac97_write(ac97, AC97_EXTENDED_STATUS, ac97->regs[AC97_EXTENDED_STATUS]);
if (ac97->flags & AC97_CS_SPDIF)
snd_ac97_write(ac97, AC97_CSR_SPDIF, ac97->regs[AC97_CSR_SPDIF]);
else
snd_ac97_write(ac97, AC97_SPDIF, ac97->regs[AC97_SPDIF]);
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */
}
}
}
/**
* snd_ac97_resume - General resume function for AC97 codec
* @ac97: the ac97 instance
*
* Do the standard resume procedure, power up and restoring the
* old register values.
*/
void snd_ac97_resume(struct snd_ac97 *ac97)
{
unsigned long end_time;
if (! ac97)
return;
if (ac97->bus->ops->reset) {
ac97->bus->ops->reset(ac97);
goto __reset_ready;
}
snd_ac97_write(ac97, AC97_POWERDOWN, 0);
if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) {
if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO))
snd_ac97_write(ac97, AC97_RESET, 0);
else if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM))
snd_ac97_write(ac97, AC97_EXTENDED_MID, 0);
udelay(100);
snd_ac97_write(ac97, AC97_POWERDOWN, 0);
}
snd_ac97_write(ac97, AC97_GENERAL_PURPOSE, 0);
snd_ac97_write(ac97, AC97_POWERDOWN, ac97->regs[AC97_POWERDOWN]);
if (ac97_is_audio(ac97)) {
ac97->bus->ops->write(ac97, AC97_MASTER, 0x8101);
end_time = jiffies + msecs_to_jiffies(100);
do {
if (snd_ac97_read(ac97, AC97_MASTER) == 0x8101)
break;
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
/* FIXME: extra delay */
ac97->bus->ops->write(ac97, AC97_MASTER, AC97_MUTE_MASK_MONO);
if (snd_ac97_read(ac97, AC97_MASTER) != AC97_MUTE_MASK_MONO)
msleep(250);
} else {
end_time = jiffies + msecs_to_jiffies(100);
do {
unsigned short val = snd_ac97_read(ac97, AC97_EXTENDED_MID);
if (val != 0xffff && (val & 1) != 0)
break;
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
}
__reset_ready:
if (ac97->bus->ops->init)
ac97->bus->ops->init(ac97);
if (ac97->build_ops->resume)
ac97->build_ops->resume(ac97);
else {
snd_ac97_restore_status(ac97);
snd_ac97_restore_iec958(ac97);
}
}
EXPORT_SYMBOL(snd_ac97_resume);
#endif
/*
* Hardware tuning
*/
static void set_ctl_name(char *dst, const char *src, const char *suffix)
{
if (suffix)
sprintf(dst, "%s %s", src, suffix);
else
strcpy(dst, src);
}
/* remove the control with the given name and optional suffix */
static int snd_ac97_remove_ctl(struct snd_ac97 *ac97, const char *name,
const char *suffix)
{
struct snd_ctl_elem_id id;
memset(&id, 0, sizeof(id));
set_ctl_name(id.name, name, suffix);
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
return snd_ctl_remove_id(ac97->bus->card, &id);
}
static struct snd_kcontrol *ctl_find(struct snd_ac97 *ac97, const char *name, const char *suffix)
{
struct snd_ctl_elem_id sid;
memset(&sid, 0, sizeof(sid));
set_ctl_name(sid.name, name, suffix);
sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
return snd_ctl_find_id(ac97->bus->card, &sid);
}
/* rename the control with the given name and optional suffix */
static int snd_ac97_rename_ctl(struct snd_ac97 *ac97, const char *src,
const char *dst, const char *suffix)
{
struct snd_kcontrol *kctl = ctl_find(ac97, src, suffix);
if (kctl) {
set_ctl_name(kctl->id.name, dst, suffix);
return 0;
}
return -ENOENT;
}
/* rename both Volume and Switch controls - don't check the return value */
static void snd_ac97_rename_vol_ctl(struct snd_ac97 *ac97, const char *src,
const char *dst)
{
snd_ac97_rename_ctl(ac97, src, dst, "Switch");
snd_ac97_rename_ctl(ac97, src, dst, "Volume");
}
/* swap controls */
static int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1,
const char *s2, const char *suffix)
{
struct snd_kcontrol *kctl1, *kctl2;
kctl1 = ctl_find(ac97, s1, suffix);
kctl2 = ctl_find(ac97, s2, suffix);
if (kctl1 && kctl2) {
set_ctl_name(kctl1->id.name, s2, suffix);
set_ctl_name(kctl2->id.name, s1, suffix);
return 0;
}
return -ENOENT;
}
#if 1
/* bind hp and master controls instead of using only hp control */
static int bind_hp_volsw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int err = snd_ac97_put_volsw(kcontrol, ucontrol);
if (err > 0) {
unsigned long priv_saved = kcontrol->private_value;
kcontrol->private_value = (kcontrol->private_value & ~0xff) | AC97_HEADPHONE;
snd_ac97_put_volsw(kcontrol, ucontrol);
kcontrol->private_value = priv_saved;
}
return err;
}
/* ac97 tune: bind Master and Headphone controls */
static int tune_hp_only(struct snd_ac97 *ac97)
{
struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL);
struct snd_kcontrol *mvol = ctl_find(ac97, "Master Playback Volume", NULL);
if (! msw || ! mvol)
return -ENOENT;
msw->put = bind_hp_volsw_put;
mvol->put = bind_hp_volsw_put;
snd_ac97_remove_ctl(ac97, "Headphone Playback", "Switch");
snd_ac97_remove_ctl(ac97, "Headphone Playback", "Volume");
return 0;
}
#else
/* ac97 tune: use Headphone control as master */
static int tune_hp_only(struct snd_ac97 *ac97)
{
if (ctl_find(ac97, "Headphone Playback Switch", NULL) == NULL)
return -ENOENT;
snd_ac97_remove_ctl(ac97, "Master Playback", "Switch");
snd_ac97_remove_ctl(ac97, "Master Playback", "Volume");
snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback");
return 0;
}
#endif
/* ac97 tune: swap Headphone and Master controls */
static int tune_swap_hp(struct snd_ac97 *ac97)
{
if (ctl_find(ac97, "Headphone Playback Switch", NULL) == NULL)
return -ENOENT;
snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Line-Out Playback");
snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback");
return 0;
}
/* ac97 tune: swap Surround and Master controls */
static int tune_swap_surround(struct snd_ac97 *ac97)
{
if (snd_ac97_swap_ctl(ac97, "Master Playback", "Surround Playback", "Switch") ||
snd_ac97_swap_ctl(ac97, "Master Playback", "Surround Playback", "Volume"))
return -ENOENT;
return 0;
}
/* ac97 tune: set up mic sharing for AD codecs */
static int tune_ad_sharing(struct snd_ac97 *ac97)
{
unsigned short scfg;
if ((ac97->id & 0xffffff00) != 0x41445300) {
ac97_err(ac97, "ac97_quirk AD_SHARING is only for AD codecs\n");
return -EINVAL;
}
/* Turn on OMS bit to route microphone to back panel */
scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x0200);
return 0;
}
static const struct snd_kcontrol_new snd_ac97_alc_jack_detect =
AC97_SINGLE("Jack Detect", AC97_ALC650_CLOCK, 5, 1, 0);
/* ac97 tune: set up ALC jack-select */
static int tune_alc_jack(struct snd_ac97 *ac97)
{
if ((ac97->id & 0xffffff00) != 0x414c4700) {
ac97_err(ac97,
"ac97_quirk ALC_JACK is only for Realtek codecs\n");
return -EINVAL;
}
snd_ac97_update_bits(ac97, 0x7a, 0x20, 0x20); /* select jack detect function */
snd_ac97_update_bits(ac97, 0x7a, 0x01, 0x01); /* Line-out auto mute */
if (ac97->id == AC97_ID_ALC658D)
snd_ac97_update_bits(ac97, 0x74, 0x0800, 0x0800);
return snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&snd_ac97_alc_jack_detect, ac97));
}
/* ac97 tune: inversed EAPD bit */
static int tune_inv_eapd(struct snd_ac97 *ac97)
{
struct snd_kcontrol *kctl = ctl_find(ac97, "External Amplifier", NULL);
if (! kctl)
return -ENOENT;
set_inv_eapd(ac97, kctl);
return 0;
}
static int master_mute_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int err = snd_ac97_put_volsw(kcontrol, ucontrol);
if (err > 0) {
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
unsigned short mask;
if (shift != rshift)
mask = AC97_MUTE_MASK_STEREO;
else
mask = AC97_MUTE_MASK_MONO;
snd_ac97_update_bits(ac97, AC97_POWERDOWN, AC97_PD_EAPD,
(ac97->regs[AC97_MASTER] & mask) == mask ?
AC97_PD_EAPD : 0);
}
return err;
}
/* ac97 tune: EAPD controls mute LED bound with the master mute */
static int tune_mute_led(struct snd_ac97 *ac97)
{
struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL);
if (! msw)
return -ENOENT;
msw->put = master_mute_sw_put;
snd_ac97_remove_ctl(ac97, "External Amplifier", NULL);
snd_ac97_update_bits(
ac97, AC97_POWERDOWN,
AC97_PD_EAPD, AC97_PD_EAPD /* mute LED on */
);
ac97->scaps |= AC97_SCAP_EAPD_LED;
return 0;
}
static int hp_master_mute_sw_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int err = bind_hp_volsw_put(kcontrol, ucontrol);
if (err > 0) {
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
unsigned short mask;
if (shift != rshift)
mask = AC97_MUTE_MASK_STEREO;
else
mask = AC97_MUTE_MASK_MONO;
snd_ac97_update_bits(ac97, AC97_POWERDOWN, AC97_PD_EAPD,
(ac97->regs[AC97_MASTER] & mask) == mask ?
AC97_PD_EAPD : 0);
}
return err;
}
static int tune_hp_mute_led(struct snd_ac97 *ac97)
{
struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL);
struct snd_kcontrol *mvol = ctl_find(ac97, "Master Playback Volume", NULL);
if (! msw || ! mvol)
return -ENOENT;
msw->put = hp_master_mute_sw_put;
mvol->put = bind_hp_volsw_put;
snd_ac97_remove_ctl(ac97, "External Amplifier", NULL);
snd_ac97_remove_ctl(ac97, "Headphone Playback", "Switch");
snd_ac97_remove_ctl(ac97, "Headphone Playback", "Volume");
snd_ac97_update_bits(
ac97, AC97_POWERDOWN,
AC97_PD_EAPD, AC97_PD_EAPD /* mute LED on */
);
return 0;
}
struct quirk_table {
const char *name;
int (*func)(struct snd_ac97 *);
};
static const struct quirk_table applicable_quirks[] = {
{ "none", NULL },
{ "hp_only", tune_hp_only },
{ "swap_hp", tune_swap_hp },
{ "swap_surround", tune_swap_surround },
{ "ad_sharing", tune_ad_sharing },
{ "alc_jack", tune_alc_jack },
{ "inv_eapd", tune_inv_eapd },
{ "mute_led", tune_mute_led },
{ "hp_mute_led", tune_hp_mute_led },
};
/* apply the quirk with the given type */
static int apply_quirk(struct snd_ac97 *ac97, int type)
{
if (type <= 0)
return 0;
else if (type >= ARRAY_SIZE(applicable_quirks))
return -EINVAL;
if (applicable_quirks[type].func)
return applicable_quirks[type].func(ac97);
return 0;
}
/* apply the quirk with the given name */
static int apply_quirk_str(struct snd_ac97 *ac97, const char *typestr)
{
int i;
const struct quirk_table *q;
for (i = 0; i < ARRAY_SIZE(applicable_quirks); i++) {
q = &applicable_quirks[i];
if (q->name && ! strcmp(typestr, q->name))
return apply_quirk(ac97, i);
}
/* for compatibility, accept the numbers, too */
if (*typestr >= '0' && *typestr <= '9')
return apply_quirk(ac97, (int)simple_strtoul(typestr, NULL, 10));
return -EINVAL;
}
/**
* snd_ac97_tune_hardware - tune up the hardware
* @ac97: the ac97 instance
* @quirk: quirk list
* @override: explicit quirk value (overrides the list if non-NULL)
*
* Do some workaround for each pci device, such as renaming of the
* headphone (true line-out) control as "Master".
* The quirk-list must be terminated with a zero-filled entry.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_tune_hardware(struct snd_ac97 *ac97,
const struct ac97_quirk *quirk, const char *override)
{
int result;
/* quirk overriden? */
if (override && strcmp(override, "-1") && strcmp(override, "default")) {
result = apply_quirk_str(ac97, override);
if (result < 0)
ac97_err(ac97, "applying quirk type %s failed (%d)\n",
override, result);
return result;
}
if (! quirk)
return -EINVAL;
for (; quirk->subvendor; quirk++) {
if (quirk->subvendor != ac97->subsystem_vendor)
continue;
if ((! quirk->mask && quirk->subdevice == ac97->subsystem_device) ||
quirk->subdevice == (quirk->mask & ac97->subsystem_device)) {
if (quirk->codec_id && quirk->codec_id != ac97->id)
continue;
ac97_dbg(ac97, "ac97 quirk for %s (%04x:%04x)\n",
quirk->name, ac97->subsystem_vendor,
ac97->subsystem_device);
result = apply_quirk(ac97, quirk->type);
if (result < 0)
ac97_err(ac97,
"applying quirk type %d for %s failed (%d)\n",
quirk->type, quirk->name, result);
return result;
}
}
return 0;
}
EXPORT_SYMBOL(snd_ac97_tune_hardware);
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com) and to datasheets
* for specific codecs.
*/
#include "ac97_local.h"
#include "ac97_patch.h"
/*
* Forward declarations
*/
static struct snd_kcontrol *snd_ac97_find_mixer_ctl(struct snd_ac97 *ac97,
const char *name);
static int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name,
const unsigned int *tlv,
const char * const *followers);
/*
* Chip specific initialization
*/
static int patch_build_controls(struct snd_ac97 * ac97, const struct snd_kcontrol_new *controls, int count)
{
int idx, err;
for (idx = 0; idx < count; idx++) {
err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&controls[idx], ac97));
if (err < 0)
return err;
}
return 0;
}
/* replace with a new TLV */
static void reset_tlv(struct snd_ac97 *ac97, const char *name,
const unsigned int *tlv)
{
struct snd_ctl_elem_id sid;
struct snd_kcontrol *kctl;
memset(&sid, 0, sizeof(sid));
strcpy(sid.name, name);
sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
kctl = snd_ctl_find_id(ac97->bus->card, &sid);
if (kctl && kctl->tlv.p)
kctl->tlv.p = tlv;
}
/* set to the page, update bits and restore the page */
static int ac97_update_bits_page(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value, unsigned short page)
{
unsigned short page_save;
int ret;
mutex_lock(&ac97->page_mutex);
page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page);
ret = snd_ac97_update_bits(ac97, reg, mask, value);
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save);
mutex_unlock(&ac97->page_mutex); /* unlock paging */
return ret;
}
/*
* shared line-in/mic controls
*/
static int ac97_surround_jack_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[] = { "Shared", "Independent" };
return snd_ctl_enum_info(uinfo, 1, 2, texts);
}
static int ac97_surround_jack_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = ac97->indep_surround;
return 0;
}
static int ac97_surround_jack_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned char indep = !!ucontrol->value.enumerated.item[0];
if (indep != ac97->indep_surround) {
ac97->indep_surround = indep;
if (ac97->build_ops->update_jacks)
ac97->build_ops->update_jacks(ac97);
return 1;
}
return 0;
}
static int ac97_channel_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[] = { "2ch", "4ch", "6ch", "8ch" };
return snd_ctl_enum_info(uinfo, 1, kcontrol->private_value, texts);
}
static int ac97_channel_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = ac97->channel_mode;
return 0;
}
static int ac97_channel_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned char mode = ucontrol->value.enumerated.item[0];
if (mode >= kcontrol->private_value)
return -EINVAL;
if (mode != ac97->channel_mode) {
ac97->channel_mode = mode;
if (ac97->build_ops->update_jacks)
ac97->build_ops->update_jacks(ac97);
return 1;
}
return 0;
}
#define AC97_SURROUND_JACK_MODE_CTL \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = "Surround Jack Mode", \
.info = ac97_surround_jack_mode_info, \
.get = ac97_surround_jack_mode_get, \
.put = ac97_surround_jack_mode_put, \
}
/* 6ch */
#define AC97_CHANNEL_MODE_CTL \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = "Channel Mode", \
.info = ac97_channel_mode_info, \
.get = ac97_channel_mode_get, \
.put = ac97_channel_mode_put, \
.private_value = 3, \
}
/* 4ch */
#define AC97_CHANNEL_MODE_4CH_CTL \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = "Channel Mode", \
.info = ac97_channel_mode_info, \
.get = ac97_channel_mode_get, \
.put = ac97_channel_mode_put, \
.private_value = 2, \
}
/* 8ch */
#define AC97_CHANNEL_MODE_8CH_CTL \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = "Channel Mode", \
.info = ac97_channel_mode_info, \
.get = ac97_channel_mode_get, \
.put = ac97_channel_mode_put, \
.private_value = 4, \
}
static inline int is_surround_on(struct snd_ac97 *ac97)
{
return ac97->channel_mode >= 1;
}
static inline int is_clfe_on(struct snd_ac97 *ac97)
{
return ac97->channel_mode >= 2;
}
/* system has shared jacks with surround out enabled */
static inline int is_shared_surrout(struct snd_ac97 *ac97)
{
return !ac97->indep_surround && is_surround_on(ac97);
}
/* system has shared jacks with center/lfe out enabled */
static inline int is_shared_clfeout(struct snd_ac97 *ac97)
{
return !ac97->indep_surround && is_clfe_on(ac97);
}
/* system has shared jacks with line in enabled */
static inline int is_shared_linein(struct snd_ac97 *ac97)
{
return !ac97->indep_surround && !is_surround_on(ac97);
}
/* system has shared jacks with mic in enabled */
static inline int is_shared_micin(struct snd_ac97 *ac97)
{
return !ac97->indep_surround && !is_clfe_on(ac97);
}
static inline int alc850_is_aux_back_surround(struct snd_ac97 *ac97)
{
return is_surround_on(ac97);
}
/* The following snd_ac97_ymf753_... items added by David Shust (dshust@shustring.com) */
/* Modified for YMF743 by Keita Maehara <maehara@debian.org> */
/* It is possible to indicate to the Yamaha YMF7x3 the type of
speakers being used. */
static int snd_ac97_ymf7x3_info_speaker(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[3] = {
"Standard", "Small", "Smaller"
};
return snd_ctl_enum_info(uinfo, 1, 3, texts);
}
static int snd_ac97_ymf7x3_get_speaker(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_YMF7X3_3D_MODE_SEL];
val = (val >> 10) & 3;
if (val > 0) /* 0 = invalid */
val--;
ucontrol->value.enumerated.item[0] = val;
return 0;
}
static int snd_ac97_ymf7x3_put_speaker(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 2)
return -EINVAL;
val = (ucontrol->value.enumerated.item[0] + 1) << 10;
return snd_ac97_update(ac97, AC97_YMF7X3_3D_MODE_SEL, val);
}
static const struct snd_kcontrol_new snd_ac97_ymf7x3_controls_speaker =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "3D Control - Speaker",
.info = snd_ac97_ymf7x3_info_speaker,
.get = snd_ac97_ymf7x3_get_speaker,
.put = snd_ac97_ymf7x3_put_speaker,
};
/* It is possible to indicate to the Yamaha YMF7x3 the source to
direct to the S/PDIF output. */
static int snd_ac97_ymf7x3_spdif_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[2] = { "AC-Link", "A/D Converter" };
return snd_ctl_enum_info(uinfo, 1, 2, texts);
}
static int snd_ac97_ymf7x3_spdif_source_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_YMF7X3_DIT_CTRL];
ucontrol->value.enumerated.item[0] = (val >> 1) & 1;
return 0;
}
static int snd_ac97_ymf7x3_spdif_source_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
val = ucontrol->value.enumerated.item[0] << 1;
return snd_ac97_update_bits(ac97, AC97_YMF7X3_DIT_CTRL, 0x0002, val);
}
static int patch_yamaha_ymf7x3_3d(struct snd_ac97 *ac97)
{
struct snd_kcontrol *kctl;
int err;
kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97);
err = snd_ctl_add(ac97->bus->card, kctl);
if (err < 0)
return err;
strcpy(kctl->id.name, "3D Control - Wide");
kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 9, 7, 0);
snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
err = snd_ctl_add(ac97->bus->card,
snd_ac97_cnew(&snd_ac97_ymf7x3_controls_speaker,
ac97));
if (err < 0)
return err;
snd_ac97_write_cache(ac97, AC97_YMF7X3_3D_MODE_SEL, 0x0c00);
return 0;
}
static const struct snd_kcontrol_new snd_ac97_yamaha_ymf743_controls_spdif[3] =
{
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
AC97_YMF7X3_DIT_CTRL, 0, 1, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Source",
.info = snd_ac97_ymf7x3_spdif_source_info,
.get = snd_ac97_ymf7x3_spdif_source_get,
.put = snd_ac97_ymf7x3_spdif_source_put,
},
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", NONE, NONE) "Mute",
AC97_YMF7X3_DIT_CTRL, 2, 1, 1)
};
static int patch_yamaha_ymf743_build_spdif(struct snd_ac97 *ac97)
{
int err;
err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3);
if (err < 0)
return err;
err = patch_build_controls(ac97,
snd_ac97_yamaha_ymf743_controls_spdif, 3);
if (err < 0)
return err;
/* set default PCM S/PDIF params */
/* PCM audio,no copyright,no preemphasis,PCM coder,original */
snd_ac97_write_cache(ac97, AC97_YMF7X3_DIT_CTRL, 0xa201);
return 0;
}
static const struct snd_ac97_build_ops patch_yamaha_ymf743_ops = {
.build_spdif = patch_yamaha_ymf743_build_spdif,
.build_3d = patch_yamaha_ymf7x3_3d,
};
static int patch_yamaha_ymf743(struct snd_ac97 *ac97)
{
ac97->build_ops = &patch_yamaha_ymf743_ops;
ac97->caps |= AC97_BC_BASS_TREBLE;
ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
return 0;
}
/* The AC'97 spec states that the S/PDIF signal is to be output at pin 48.
The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48.
By default, no output pin is selected, and the S/PDIF signal is not output.
There is also a bit to mute S/PDIF output in a vendor-specific register. */
static int snd_ac97_ymf753_spdif_output_pin_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[3] = { "Disabled", "Pin 43", "Pin 48" };
return snd_ctl_enum_info(uinfo, 1, 3, texts);
}
static int snd_ac97_ymf753_spdif_output_pin_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_YMF7X3_DIT_CTRL];
ucontrol->value.enumerated.item[0] = (val & 0x0008) ? 2 : (val & 0x0020) ? 1 : 0;
return 0;
}
static int snd_ac97_ymf753_spdif_output_pin_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 2)
return -EINVAL;
val = (ucontrol->value.enumerated.item[0] == 2) ? 0x0008 :
(ucontrol->value.enumerated.item[0] == 1) ? 0x0020 : 0;
return snd_ac97_update_bits(ac97, AC97_YMF7X3_DIT_CTRL, 0x0028, val);
/* The following can be used to direct S/PDIF output to pin 47 (EAPD).
snd_ac97_write_cache(ac97, 0x62, snd_ac97_read(ac97, 0x62) | 0x0008); */
}
static const struct snd_kcontrol_new snd_ac97_ymf753_controls_spdif[3] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
.info = snd_ac97_ymf7x3_spdif_source_info,
.get = snd_ac97_ymf7x3_spdif_source_get,
.put = snd_ac97_ymf7x3_spdif_source_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Output Pin",
.info = snd_ac97_ymf753_spdif_output_pin_info,
.get = snd_ac97_ymf753_spdif_output_pin_get,
.put = snd_ac97_ymf753_spdif_output_pin_put,
},
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", NONE, NONE) "Mute",
AC97_YMF7X3_DIT_CTRL, 2, 1, 1)
};
static int patch_yamaha_ymf753_post_spdif(struct snd_ac97 * ac97)
{
int err;
err = patch_build_controls(ac97, snd_ac97_ymf753_controls_spdif, ARRAY_SIZE(snd_ac97_ymf753_controls_spdif));
if (err < 0)
return err;
return 0;
}
static const struct snd_ac97_build_ops patch_yamaha_ymf753_ops = {
.build_3d = patch_yamaha_ymf7x3_3d,
.build_post_spdif = patch_yamaha_ymf753_post_spdif
};
static int patch_yamaha_ymf753(struct snd_ac97 * ac97)
{
/* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com.
This chip has nonstandard and extended behaviour with regard to its S/PDIF output.
The AC'97 spec states that the S/PDIF signal is to be output at pin 48.
The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48.
By default, no output pin is selected, and the S/PDIF signal is not output.
There is also a bit to mute S/PDIF output in a vendor-specific register.
*/
ac97->build_ops = &patch_yamaha_ymf753_ops;
ac97->caps |= AC97_BC_BASS_TREBLE;
ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */
return 0;
}
/*
* May 2, 2003 Liam Girdwood <lrg@slimlogic.co.uk>
* removed broken wolfson00 patch.
* added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717.
*/
static const struct snd_kcontrol_new wm97xx_snd_ac97_controls[] = {
AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1),
AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1),
};
static int patch_wolfson_wm9703_specific(struct snd_ac97 * ac97)
{
/* This is known to work for the ViewSonic ViewPad 1000
* Randolph Bentson <bentson@holmsjoen.com>
* WM9703/9707/9708/9717
*/
int err, i;
for (i = 0; i < ARRAY_SIZE(wm97xx_snd_ac97_controls); i++) {
err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm97xx_snd_ac97_controls[i], ac97));
if (err < 0)
return err;
}
snd_ac97_write_cache(ac97, AC97_WM97XX_FMIXER_VOL, 0x0808);
return 0;
}
static const struct snd_ac97_build_ops patch_wolfson_wm9703_ops = {
.build_specific = patch_wolfson_wm9703_specific,
};
static int patch_wolfson03(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_wolfson_wm9703_ops;
return 0;
}
static const struct snd_kcontrol_new wm9704_snd_ac97_controls[] = {
AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1),
AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1),
AC97_DOUBLE("Rear Playback Volume", AC97_WM9704_RMIXER_VOL, 8, 0, 31, 1),
AC97_SINGLE("Rear Playback Switch", AC97_WM9704_RMIXER_VOL, 15, 1, 1),
AC97_DOUBLE("Rear DAC Volume", AC97_WM9704_RPCM_VOL, 8, 0, 31, 1),
AC97_DOUBLE("Surround Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
};
static int patch_wolfson_wm9704_specific(struct snd_ac97 * ac97)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(wm9704_snd_ac97_controls); i++) {
err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9704_snd_ac97_controls[i], ac97));
if (err < 0)
return err;
}
/* patch for DVD noise */
snd_ac97_write_cache(ac97, AC97_WM9704_TEST, 0x0200);
return 0;
}
static const struct snd_ac97_build_ops patch_wolfson_wm9704_ops = {
.build_specific = patch_wolfson_wm9704_specific,
};
static int patch_wolfson04(struct snd_ac97 * ac97)
{
/* WM9704M/9704Q */
ac97->build_ops = &patch_wolfson_wm9704_ops;
return 0;
}
static int patch_wolfson05(struct snd_ac97 * ac97)
{
/* WM9705, WM9710 */
ac97->build_ops = &patch_wolfson_wm9703_ops;
#ifdef CONFIG_TOUCHSCREEN_WM9705
/* WM9705 touchscreen uses AUX and VIDEO for touch */
ac97->flags |= AC97_HAS_NO_VIDEO | AC97_HAS_NO_AUX;
#endif
return 0;
}
static const char* wm9711_alc_select[] = {"None", "Left", "Right", "Stereo"};
static const char* wm9711_alc_mix[] = {"Stereo", "Right", "Left", "None"};
static const char* wm9711_out3_src[] = {"Left", "VREF", "Left + Right", "Mono"};
static const char* wm9711_out3_lrsrc[] = {"Master Mix", "Headphone Mix"};
static const char* wm9711_rec_adc[] = {"Stereo", "Left", "Right", "Mute"};
static const char* wm9711_base[] = {"Linear Control", "Adaptive Boost"};
static const char* wm9711_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
static const char* wm9711_mic[] = {"Mic 1", "Differential", "Mic 2", "Stereo"};
static const char* wm9711_rec_sel[] =
{"Mic 1", "NC", "NC", "Master Mix", "Line", "Headphone Mix", "Phone Mix", "Phone"};
static const char* wm9711_ng_type[] = {"Constant Gain", "Mute"};
static const struct ac97_enum wm9711_enum[] = {
AC97_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9711_alc_select),
AC97_ENUM_SINGLE(AC97_VIDEO, 10, 4, wm9711_alc_mix),
AC97_ENUM_SINGLE(AC97_AUX, 9, 4, wm9711_out3_src),
AC97_ENUM_SINGLE(AC97_AUX, 8, 2, wm9711_out3_lrsrc),
AC97_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9711_rec_adc),
AC97_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9711_base),
AC97_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9711_rec_gain),
AC97_ENUM_SINGLE(AC97_MIC, 5, 4, wm9711_mic),
AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, wm9711_rec_sel),
AC97_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9711_ng_type),
};
static const struct snd_kcontrol_new wm9711_snd_ac97_controls[] = {
AC97_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
AC97_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
AC97_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
AC97_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
AC97_ENUM("ALC Function", wm9711_enum[0]),
AC97_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 1),
AC97_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1),
AC97_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
AC97_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
AC97_ENUM("ALC NG Type", wm9711_enum[9]),
AC97_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1),
AC97_SINGLE("Side Tone Switch", AC97_VIDEO, 15, 1, 1),
AC97_SINGLE("Side Tone Volume", AC97_VIDEO, 12, 7, 1),
AC97_ENUM("ALC Headphone Mux", wm9711_enum[1]),
AC97_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1),
AC97_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1),
AC97_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 0),
AC97_ENUM("Out3 Mux", wm9711_enum[2]),
AC97_ENUM("Out3 LR Mux", wm9711_enum[3]),
AC97_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1),
AC97_SINGLE("Beep to Headphone Switch", AC97_PC_BEEP, 15, 1, 1),
AC97_SINGLE("Beep to Headphone Volume", AC97_PC_BEEP, 12, 7, 1),
AC97_SINGLE("Beep to Side Tone Switch", AC97_PC_BEEP, 11, 1, 1),
AC97_SINGLE("Beep to Side Tone Volume", AC97_PC_BEEP, 8, 7, 1),
AC97_SINGLE("Beep to Phone Switch", AC97_PC_BEEP, 7, 1, 1),
AC97_SINGLE("Beep to Phone Volume", AC97_PC_BEEP, 4, 7, 1),
AC97_SINGLE("Aux to Headphone Switch", AC97_CD, 15, 1, 1),
AC97_SINGLE("Aux to Headphone Volume", AC97_CD, 12, 7, 1),
AC97_SINGLE("Aux to Side Tone Switch", AC97_CD, 11, 1, 1),
AC97_SINGLE("Aux to Side Tone Volume", AC97_CD, 8, 7, 1),
AC97_SINGLE("Aux to Phone Switch", AC97_CD, 7, 1, 1),
AC97_SINGLE("Aux to Phone Volume", AC97_CD, 4, 7, 1),
AC97_SINGLE("Phone to Headphone Switch", AC97_PHONE, 15, 1, 1),
AC97_SINGLE("Phone to Master Switch", AC97_PHONE, 14, 1, 1),
AC97_SINGLE("Line to Headphone Switch", AC97_LINE, 15, 1, 1),
AC97_SINGLE("Line to Master Switch", AC97_LINE, 14, 1, 1),
AC97_SINGLE("Line to Phone Switch", AC97_LINE, 13, 1, 1),
AC97_SINGLE("PCM Playback to Headphone Switch", AC97_PCM, 15, 1, 1),
AC97_SINGLE("PCM Playback to Master Switch", AC97_PCM, 14, 1, 1),
AC97_SINGLE("PCM Playback to Phone Switch", AC97_PCM, 13, 1, 1),
AC97_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0),
AC97_ENUM("Capture to Phone Mux", wm9711_enum[4]),
AC97_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1),
AC97_ENUM("Capture Select", wm9711_enum[8]),
AC97_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1),
AC97_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1),
AC97_ENUM("Bass Control", wm9711_enum[5]),
AC97_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1),
AC97_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1),
AC97_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0),
AC97_SINGLE("ADC Switch", AC97_REC_GAIN, 15, 1, 1),
AC97_ENUM("Capture Volume Steps", wm9711_enum[6]),
AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1),
AC97_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0),
AC97_SINGLE("Mic 1 to Phone Switch", AC97_MIC, 14, 1, 1),
AC97_SINGLE("Mic 2 to Phone Switch", AC97_MIC, 13, 1, 1),
AC97_ENUM("Mic Select Source", wm9711_enum[7]),
AC97_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
AC97_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
AC97_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0),
AC97_SINGLE("Master Left Inv Switch", AC97_MASTER, 6, 1, 0),
AC97_SINGLE("Master ZC Switch", AC97_MASTER, 7, 1, 0),
AC97_SINGLE("Headphone ZC Switch", AC97_HEADPHONE, 7, 1, 0),
AC97_SINGLE("Mono ZC Switch", AC97_MASTER_MONO, 7, 1, 0),
};
static int patch_wolfson_wm9711_specific(struct snd_ac97 * ac97)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(wm9711_snd_ac97_controls); i++) {
err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9711_snd_ac97_controls[i], ac97));
if (err < 0)
return err;
}
snd_ac97_write_cache(ac97, AC97_CODEC_CLASS_REV, 0x0808);
snd_ac97_write_cache(ac97, AC97_PCI_SVID, 0x0808);
snd_ac97_write_cache(ac97, AC97_VIDEO, 0x0808);
snd_ac97_write_cache(ac97, AC97_AUX, 0x0808);
snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808);
snd_ac97_write_cache(ac97, AC97_CD, 0x0000);
return 0;
}
static const struct snd_ac97_build_ops patch_wolfson_wm9711_ops = {
.build_specific = patch_wolfson_wm9711_specific,
};
static int patch_wolfson11(struct snd_ac97 * ac97)
{
/* WM9711, WM9712 */
ac97->build_ops = &patch_wolfson_wm9711_ops;
ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_MIC |
AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD;
return 0;
}
static const char* wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
static const char* wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
static const char* wm9713_rec_src[] =
{"Mic 1", "Mic 2", "Line", "Mono In", "Headphone Mix", "Master Mix",
"Mono Mix", "Zh"};
static const char* wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
static const char* wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
static const char* wm9713_mono_pga[] = {"Vmid", "Zh", "Mono Mix", "Inv 1"};
static const char* wm9713_spk_pga[] =
{"Vmid", "Zh", "Headphone Mix", "Master Mix", "Inv", "NC", "NC", "NC"};
static const char* wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone Mix", "NC"};
static const char* wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "NC"};
static const char* wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "NC"};
static const char* wm9713_dac_inv[] =
{"Off", "Mono Mix", "Master Mix", "Headphone Mix L", "Headphone Mix R",
"Headphone Mix Mono", "NC", "Vmid"};
static const char* wm9713_base[] = {"Linear Control", "Adaptive Boost"};
static const char* wm9713_ng_type[] = {"Constant Gain", "Mute"};
static const struct ac97_enum wm9713_enum[] = {
AC97_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer),
AC97_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux),
AC97_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux),
AC97_ENUM_DOUBLE(AC97_VIDEO, 3, 0, 8, wm9713_rec_src),
AC97_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain),
AC97_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select),
AC97_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga),
AC97_ENUM_DOUBLE(AC97_REC_GAIN, 11, 8, 8, wm9713_spk_pga),
AC97_ENUM_DOUBLE(AC97_REC_GAIN, 6, 4, 4, wm9713_hp_pga),
AC97_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga),
AC97_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga),
AC97_ENUM_DOUBLE(AC97_REC_GAIN_MIC, 13, 10, 8, wm9713_dac_inv),
AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_base),
AC97_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type),
};
static const struct snd_kcontrol_new wm13_snd_ac97_controls[] = {
AC97_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1),
AC97_SINGLE("Line In to Headphone Switch", AC97_PC_BEEP, 15, 1, 1),
AC97_SINGLE("Line In to Master Switch", AC97_PC_BEEP, 14, 1, 1),
AC97_SINGLE("Line In to Mono Switch", AC97_PC_BEEP, 13, 1, 1),
AC97_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1),
AC97_SINGLE("PCM Playback to Headphone Switch", AC97_PHONE, 15, 1, 1),
AC97_SINGLE("PCM Playback to Master Switch", AC97_PHONE, 14, 1, 1),
AC97_SINGLE("PCM Playback to Mono Switch", AC97_PHONE, 13, 1, 1),
AC97_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
AC97_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
AC97_SINGLE("Mic 1 to Mono Switch", AC97_LINE, 7, 1, 1),
AC97_SINGLE("Mic 2 to Mono Switch", AC97_LINE, 6, 1, 1),
AC97_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
AC97_ENUM("Mic to Headphone Mux", wm9713_enum[0]),
AC97_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
AC97_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
AC97_ENUM("Capture Volume Steps", wm9713_enum[4]),
AC97_DOUBLE("Capture Volume", AC97_CD, 8, 0, 15, 0),
AC97_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
AC97_ENUM("Capture to Headphone Mux", wm9713_enum[1]),
AC97_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1),
AC97_ENUM("Capture to Mono Mux", wm9713_enum[2]),
AC97_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
AC97_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
AC97_ENUM("Capture Select", wm9713_enum[3]),
AC97_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
AC97_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
AC97_SINGLE("ALC Decay Time ", AC97_CODEC_CLASS_REV, 4, 15, 0),
AC97_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
AC97_ENUM("ALC Function", wm9713_enum[5]),
AC97_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
AC97_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
AC97_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
AC97_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
AC97_ENUM("ALC NG Type", wm9713_enum[13]),
AC97_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
AC97_DOUBLE("Master ZC Switch", AC97_MASTER, 14, 6, 1, 0),
AC97_DOUBLE("Headphone ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
AC97_DOUBLE("Out3/4 ZC Switch", AC97_MASTER_MONO, 14, 6, 1, 0),
AC97_SINGLE("Master Right Switch", AC97_MASTER, 7, 1, 1),
AC97_SINGLE("Headphone Right Switch", AC97_HEADPHONE, 7, 1, 1),
AC97_SINGLE("Out3/4 Right Switch", AC97_MASTER_MONO, 7, 1, 1),
AC97_SINGLE("Mono In to Headphone Switch", AC97_MASTER_TONE, 15, 1, 1),
AC97_SINGLE("Mono In to Master Switch", AC97_MASTER_TONE, 14, 1, 1),
AC97_SINGLE("Mono In Volume", AC97_MASTER_TONE, 8, 31, 1),
AC97_SINGLE("Mono Switch", AC97_MASTER_TONE, 7, 1, 1),
AC97_SINGLE("Mono ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
AC97_SINGLE("Mono Volume", AC97_MASTER_TONE, 0, 31, 1),
AC97_SINGLE("Beep to Headphone Switch", AC97_AUX, 15, 1, 1),
AC97_SINGLE("Beep to Headphone Volume", AC97_AUX, 12, 7, 1),
AC97_SINGLE("Beep to Master Switch", AC97_AUX, 11, 1, 1),
AC97_SINGLE("Beep to Master Volume", AC97_AUX, 8, 7, 1),
AC97_SINGLE("Beep to Mono Switch", AC97_AUX, 7, 1, 1),
AC97_SINGLE("Beep to Mono Volume", AC97_AUX, 4, 7, 1),
AC97_SINGLE("Voice to Headphone Switch", AC97_PCM, 15, 1, 1),
AC97_SINGLE("Voice to Headphone Volume", AC97_PCM, 12, 7, 1),
AC97_SINGLE("Voice to Master Switch", AC97_PCM, 11, 1, 1),
AC97_SINGLE("Voice to Master Volume", AC97_PCM, 8, 7, 1),
AC97_SINGLE("Voice to Mono Switch", AC97_PCM, 7, 1, 1),
AC97_SINGLE("Voice to Mono Volume", AC97_PCM, 4, 7, 1),
AC97_SINGLE("Aux to Headphone Switch", AC97_REC_SEL, 15, 1, 1),
AC97_SINGLE("Aux to Headphone Volume", AC97_REC_SEL, 12, 7, 1),
AC97_SINGLE("Aux to Master Switch", AC97_REC_SEL, 11, 1, 1),
AC97_SINGLE("Aux to Master Volume", AC97_REC_SEL, 8, 7, 1),
AC97_SINGLE("Aux to Mono Switch", AC97_REC_SEL, 7, 1, 1),
AC97_SINGLE("Aux to Mono Volume", AC97_REC_SEL, 4, 7, 1),
AC97_ENUM("Mono Input Mux", wm9713_enum[6]),
AC97_ENUM("Master Input Mux", wm9713_enum[7]),
AC97_ENUM("Headphone Input Mux", wm9713_enum[8]),
AC97_ENUM("Out 3 Input Mux", wm9713_enum[9]),
AC97_ENUM("Out 4 Input Mux", wm9713_enum[10]),
AC97_ENUM("Bass Control", wm9713_enum[12]),
AC97_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
AC97_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
AC97_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
AC97_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
AC97_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
};
static const struct snd_kcontrol_new wm13_snd_ac97_controls_3d[] = {
AC97_ENUM("Inv Input Mux", wm9713_enum[11]),
AC97_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
AC97_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
AC97_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
};
static int patch_wolfson_wm9713_3d (struct snd_ac97 * ac97)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_3d); i++) {
err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_3d[i], ac97));
if (err < 0)
return err;
}
return 0;
}
static int patch_wolfson_wm9713_specific(struct snd_ac97 * ac97)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls); i++) {
err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls[i], ac97));
if (err < 0)
return err;
}
snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808);
snd_ac97_write_cache(ac97, AC97_PHONE, 0x0808);
snd_ac97_write_cache(ac97, AC97_MIC, 0x0808);
snd_ac97_write_cache(ac97, AC97_LINE, 0x00da);
snd_ac97_write_cache(ac97, AC97_CD, 0x0808);
snd_ac97_write_cache(ac97, AC97_VIDEO, 0xd612);
snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x1ba0);
return 0;
}
#ifdef CONFIG_PM
static void patch_wolfson_wm9713_suspend (struct snd_ac97 * ac97)
{
snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xfeff);
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0xffff);
}
static void patch_wolfson_wm9713_resume (struct snd_ac97 * ac97)
{
snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00);
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810);
snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0);
}
#endif
static const struct snd_ac97_build_ops patch_wolfson_wm9713_ops = {
.build_specific = patch_wolfson_wm9713_specific,
.build_3d = patch_wolfson_wm9713_3d,
#ifdef CONFIG_PM
.suspend = patch_wolfson_wm9713_suspend,
.resume = patch_wolfson_wm9713_resume
#endif
};
static int patch_wolfson13(struct snd_ac97 * ac97)
{
/* WM9713, WM9714 */
ac97->build_ops = &patch_wolfson_wm9713_ops;
ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_PHONE |
AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD | AC97_HAS_NO_TONE |
AC97_HAS_NO_STD_PCM;
ac97->scaps &= ~AC97_SCAP_MODEM;
snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00);
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810);
snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0);
return 0;
}
/*
* Tritech codec
*/
static int patch_tritech_tr28028(struct snd_ac97 * ac97)
{
snd_ac97_write_cache(ac97, 0x26, 0x0300);
snd_ac97_write_cache(ac97, 0x26, 0x0000);
snd_ac97_write_cache(ac97, AC97_SURROUND_MASTER, 0x0000);
snd_ac97_write_cache(ac97, AC97_SPDIF, 0x0000);
return 0;
}
/*
* Sigmatel STAC97xx codecs
*/
static int patch_sigmatel_stac9700_3d(struct snd_ac97 * ac97)
{
struct snd_kcontrol *kctl;
int err;
err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97));
if (err < 0)
return err;
strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0);
snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
return 0;
}
static int patch_sigmatel_stac9708_3d(struct snd_ac97 * ac97)
{
struct snd_kcontrol *kctl;
int err;
kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97);
err = snd_ctl_add(ac97->bus->card, kctl);
if (err < 0)
return err;
strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 0, 3, 0);
kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97);
err = snd_ctl_add(ac97->bus->card, kctl);
if (err < 0)
return err;
strcpy(kctl->id.name, "3D Control Sigmatel - Rear Depth");
kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0);
snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
return 0;
}
static const struct snd_kcontrol_new snd_ac97_sigmatel_4speaker =
AC97_SINGLE("Sigmatel 4-Speaker Stereo Playback Switch",
AC97_SIGMATEL_DAC2INVERT, 2, 1, 0);
/* "Sigmatel " removed due to excessive name length: */
static const struct snd_kcontrol_new snd_ac97_sigmatel_phaseinvert =
AC97_SINGLE("Surround Phase Inversion Playback Switch",
AC97_SIGMATEL_DAC2INVERT, 3, 1, 0);
static const struct snd_kcontrol_new snd_ac97_sigmatel_controls[] = {
AC97_SINGLE("Sigmatel DAC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 1, 1, 0),
AC97_SINGLE("Sigmatel ADC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 0, 1, 0)
};
static int patch_sigmatel_stac97xx_specific(struct snd_ac97 * ac97)
{
int err;
snd_ac97_write_cache(ac97, AC97_SIGMATEL_ANALOG, snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) & ~0x0003);
if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 1)) {
err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[0], 1);
if (err < 0)
return err;
}
if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 0)) {
err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[1], 1);
if (err < 0)
return err;
}
if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 2)) {
err = patch_build_controls(ac97, &snd_ac97_sigmatel_4speaker, 1);
if (err < 0)
return err;
}
if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 3)) {
err = patch_build_controls(ac97, &snd_ac97_sigmatel_phaseinvert, 1);
if (err < 0)
return err;
}
return 0;
}
static const struct snd_ac97_build_ops patch_sigmatel_stac9700_ops = {
.build_3d = patch_sigmatel_stac9700_3d,
.build_specific = patch_sigmatel_stac97xx_specific
};
static int patch_sigmatel_stac9700(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_sigmatel_stac9700_ops;
return 0;
}
static int snd_ac97_stac9708_put_bias(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int err;
mutex_lock(&ac97->page_mutex);
snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
err = snd_ac97_update_bits(ac97, AC97_SIGMATEL_BIAS2, 0x0010,
(ucontrol->value.integer.value[0] & 1) << 4);
snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0);
mutex_unlock(&ac97->page_mutex);
return err;
}
static const struct snd_kcontrol_new snd_ac97_stac9708_bias_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Sigmatel Output Bias Switch",
.info = snd_ac97_info_volsw,
.get = snd_ac97_get_volsw,
.put = snd_ac97_stac9708_put_bias,
.private_value = AC97_SINGLE_VALUE(AC97_SIGMATEL_BIAS2, 4, 1, 0),
};
static int patch_sigmatel_stac9708_specific(struct snd_ac97 *ac97)
{
int err;
/* the register bit is writable, but the function is not implemented: */
snd_ac97_remove_ctl(ac97, "PCM Out Path & Mute", NULL);
snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Sigmatel Surround Playback");
err = patch_build_controls(ac97, &snd_ac97_stac9708_bias_control, 1);
if (err < 0)
return err;
return patch_sigmatel_stac97xx_specific(ac97);
}
static const struct snd_ac97_build_ops patch_sigmatel_stac9708_ops = {
.build_3d = patch_sigmatel_stac9708_3d,
.build_specific = patch_sigmatel_stac9708_specific
};
static int patch_sigmatel_stac9708(struct snd_ac97 * ac97)
{
unsigned int codec72, codec6c;
ac97->build_ops = &patch_sigmatel_stac9708_ops;
ac97->caps |= 0x10; /* HP (sigmatel surround) support */
codec72 = snd_ac97_read(ac97, AC97_SIGMATEL_BIAS2) & 0x8000;
codec6c = snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG);
if ((codec72==0) && (codec6c==0)) {
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1000);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0007);
} else if ((codec72==0x8000) && (codec6c==0)) {
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1001);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_DAC2INVERT, 0x0008);
} else if ((codec72==0x8000) && (codec6c==0x0080)) {
/* nothing */
}
snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
return 0;
}
static int patch_sigmatel_stac9721(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_sigmatel_stac9700_ops;
if (snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) == 0) {
// patch for SigmaTel
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x4000);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
}
snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
return 0;
}
static int patch_sigmatel_stac9744(struct snd_ac97 * ac97)
{
// patch for SigmaTel
ac97->build_ops = &patch_sigmatel_stac9700_ops;
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000); /* is this correct? --jk */
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
return 0;
}
static int patch_sigmatel_stac9756(struct snd_ac97 * ac97)
{
// patch for SigmaTel
ac97->build_ops = &patch_sigmatel_stac9700_ops;
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000); /* is this correct? --jk */
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
return 0;
}
static int snd_ac97_stac9758_output_jack_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[5] = {
"Input/Disabled", "Front Output",
"Rear Output", "Center/LFE Output", "Mixer Output" };
return snd_ctl_enum_info(uinfo, 1, 5, texts);
}
static int snd_ac97_stac9758_output_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int shift = kcontrol->private_value;
unsigned short val;
val = ac97->regs[AC97_SIGMATEL_OUTSEL] >> shift;
if (!(val & 4))
ucontrol->value.enumerated.item[0] = 0;
else
ucontrol->value.enumerated.item[0] = 1 + (val & 3);
return 0;
}
static int snd_ac97_stac9758_output_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int shift = kcontrol->private_value;
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 4)
return -EINVAL;
if (ucontrol->value.enumerated.item[0] == 0)
val = 0;
else
val = 4 | (ucontrol->value.enumerated.item[0] - 1);
return ac97_update_bits_page(ac97, AC97_SIGMATEL_OUTSEL,
7 << shift, val << shift, 0);
}
static int snd_ac97_stac9758_input_jack_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[7] = {
"Mic2 Jack", "Mic1 Jack", "Line In Jack",
"Front Jack", "Rear Jack", "Center/LFE Jack", "Mute" };
return snd_ctl_enum_info(uinfo, 1, 7, texts);
}
static int snd_ac97_stac9758_input_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int shift = kcontrol->private_value;
unsigned short val;
val = ac97->regs[AC97_SIGMATEL_INSEL];
ucontrol->value.enumerated.item[0] = (val >> shift) & 7;
return 0;
}
static int snd_ac97_stac9758_input_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int shift = kcontrol->private_value;
return ac97_update_bits_page(ac97, AC97_SIGMATEL_INSEL, 7 << shift,
ucontrol->value.enumerated.item[0] << shift, 0);
}
static int snd_ac97_stac9758_phonesel_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[3] = {
"None", "Front Jack", "Rear Jack"
};
return snd_ctl_enum_info(uinfo, 1, 3, texts);
}
static int snd_ac97_stac9758_phonesel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = ac97->regs[AC97_SIGMATEL_IOMISC] & 3;
return 0;
}
static int snd_ac97_stac9758_phonesel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
return ac97_update_bits_page(ac97, AC97_SIGMATEL_IOMISC, 3,
ucontrol->value.enumerated.item[0], 0);
}
#define STAC9758_OUTPUT_JACK(xname, shift) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_ac97_stac9758_output_jack_info, \
.get = snd_ac97_stac9758_output_jack_get, \
.put = snd_ac97_stac9758_output_jack_put, \
.private_value = shift }
#define STAC9758_INPUT_JACK(xname, shift) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_ac97_stac9758_input_jack_info, \
.get = snd_ac97_stac9758_input_jack_get, \
.put = snd_ac97_stac9758_input_jack_put, \
.private_value = shift }
static const struct snd_kcontrol_new snd_ac97_sigmatel_stac9758_controls[] = {
STAC9758_OUTPUT_JACK("Mic1 Jack", 1),
STAC9758_OUTPUT_JACK("LineIn Jack", 4),
STAC9758_OUTPUT_JACK("Front Jack", 7),
STAC9758_OUTPUT_JACK("Rear Jack", 10),
STAC9758_OUTPUT_JACK("Center/LFE Jack", 13),
STAC9758_INPUT_JACK("Mic Input Source", 0),
STAC9758_INPUT_JACK("Line Input Source", 8),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Headphone Amp",
.info = snd_ac97_stac9758_phonesel_info,
.get = snd_ac97_stac9758_phonesel_get,
.put = snd_ac97_stac9758_phonesel_put
},
AC97_SINGLE("Exchange Center/LFE", AC97_SIGMATEL_IOMISC, 4, 1, 0),
AC97_SINGLE("Headphone +3dB Boost", AC97_SIGMATEL_IOMISC, 8, 1, 0)
};
static int patch_sigmatel_stac9758_specific(struct snd_ac97 *ac97)
{
int err;
err = patch_sigmatel_stac97xx_specific(ac97);
if (err < 0)
return err;
err = patch_build_controls(ac97, snd_ac97_sigmatel_stac9758_controls,
ARRAY_SIZE(snd_ac97_sigmatel_stac9758_controls));
if (err < 0)
return err;
/* DAC-A direct */
snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Front Playback");
/* DAC-A to Mix = PCM */
/* DAC-B direct = Surround */
/* DAC-B to Mix */
snd_ac97_rename_vol_ctl(ac97, "Video Playback", "Surround Mix Playback");
/* DAC-C direct = Center/LFE */
return 0;
}
static const struct snd_ac97_build_ops patch_sigmatel_stac9758_ops = {
.build_3d = patch_sigmatel_stac9700_3d,
.build_specific = patch_sigmatel_stac9758_specific
};
static int patch_sigmatel_stac9758(struct snd_ac97 * ac97)
{
static const unsigned short regs[4] = {
AC97_SIGMATEL_OUTSEL,
AC97_SIGMATEL_IOMISC,
AC97_SIGMATEL_INSEL,
AC97_SIGMATEL_VARIOUS
};
static const unsigned short def_regs[4] = {
/* OUTSEL */ 0xd794, /* CL:CL, SR:SR, LO:MX, LI:DS, MI:DS */
/* IOMISC */ 0x2001,
/* INSEL */ 0x0201, /* LI:LI, MI:M1 */
/* VARIOUS */ 0x0040
};
static const unsigned short m675_regs[4] = {
/* OUTSEL */ 0xfc70, /* CL:MX, SR:MX, LO:DS, LI:MX, MI:DS */
/* IOMISC */ 0x2102, /* HP amp on */
/* INSEL */ 0x0203, /* LI:LI, MI:FR */
/* VARIOUS */ 0x0041 /* stereo mic */
};
const unsigned short *pregs = def_regs;
int i;
/* Gateway M675 notebook */
if (ac97->pci &&
ac97->subsystem_vendor == 0x107b &&
ac97->subsystem_device == 0x0601)
pregs = m675_regs;
// patch for SigmaTel
ac97->build_ops = &patch_sigmatel_stac9758_ops;
/* FIXME: assume only page 0 for writing cache */
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
for (i = 0; i < 4; i++)
snd_ac97_write_cache(ac97, regs[i], pregs[i]);
ac97->flags |= AC97_STEREO_MUTES;
return 0;
}
/*
* Cirrus Logic CS42xx codecs
*/
static const struct snd_kcontrol_new snd_ac97_cirrus_controls_spdif[2] = {
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CSR_SPDIF, 15, 1, 0),
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA", AC97_CSR_ACMODE, 0, 3, 0)
};
static int patch_cirrus_build_spdif(struct snd_ac97 * ac97)
{
int err;
/* con mask, pro mask, default */
err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3);
if (err < 0)
return err;
/* switch, spsa */
err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[0], 1);
if (err < 0)
return err;
switch (ac97->id & AC97_ID_CS_MASK) {
case AC97_ID_CS4205:
err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[1], 1);
if (err < 0)
return err;
break;
}
/* set default PCM S/PDIF params */
/* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
snd_ac97_write_cache(ac97, AC97_CSR_SPDIF, 0x0a20);
return 0;
}
static const struct snd_ac97_build_ops patch_cirrus_ops = {
.build_spdif = patch_cirrus_build_spdif
};
static int patch_cirrus_spdif(struct snd_ac97 * ac97)
{
/* Basically, the cs4201/cs4205/cs4297a has non-standard sp/dif registers.
WHY CAN'T ANYONE FOLLOW THE BLOODY SPEC? *sigh*
- sp/dif EA ID is not set, but sp/dif is always present.
- enable/disable is spdif register bit 15.
- sp/dif control register is 0x68. differs from AC97:
- valid is bit 14 (vs 15)
- no DRS
- only 44.1/48k [00 = 48, 01=44,1] (AC97 is 00=44.1, 10=48)
- sp/dif ssource select is in 0x5e bits 0,1.
*/
ac97->build_ops = &patch_cirrus_ops;
ac97->flags |= AC97_CS_SPDIF;
ac97->rates[AC97_RATES_SPDIF] &= ~SNDRV_PCM_RATE_32000;
ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
snd_ac97_write_cache(ac97, AC97_CSR_ACMODE, 0x0080);
return 0;
}
static int patch_cirrus_cs4299(struct snd_ac97 * ac97)
{
/* force the detection of PC Beep */
ac97->flags |= AC97_HAS_PC_BEEP;
return patch_cirrus_spdif(ac97);
}
/*
* Conexant codecs
*/
static const struct snd_kcontrol_new snd_ac97_conexant_controls_spdif[1] = {
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CXR_AUDIO_MISC, 3, 1, 0),
};
static int patch_conexant_build_spdif(struct snd_ac97 * ac97)
{
int err;
/* con mask, pro mask, default */
err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3);
if (err < 0)
return err;
/* switch */
err = patch_build_controls(ac97, &snd_ac97_conexant_controls_spdif[0], 1);
if (err < 0)
return err;
/* set default PCM S/PDIF params */
/* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
snd_ac97_write_cache(ac97, AC97_CXR_AUDIO_MISC,
snd_ac97_read(ac97, AC97_CXR_AUDIO_MISC) & ~(AC97_CXR_SPDIFEN|AC97_CXR_COPYRGT|AC97_CXR_SPDIF_MASK));
return 0;
}
static const struct snd_ac97_build_ops patch_conexant_ops = {
.build_spdif = patch_conexant_build_spdif
};
static int patch_conexant(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_conexant_ops;
ac97->flags |= AC97_CX_SPDIF;
ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
return 0;
}
static int patch_cx20551(struct snd_ac97 *ac97)
{
snd_ac97_update_bits(ac97, 0x5c, 0x01, 0x01);
return 0;
}
/*
* Analog Devices AD18xx, AD19xx codecs
*/
#ifdef CONFIG_PM
static void ad18xx_resume(struct snd_ac97 *ac97)
{
static const unsigned short setup_regs[] = {
AC97_AD_MISC, AC97_AD_SERIAL_CFG, AC97_AD_JACK_SPDIF,
};
int i, codec;
for (i = 0; i < (int)ARRAY_SIZE(setup_regs); i++) {
unsigned short reg = setup_regs[i];
if (test_bit(reg, ac97->reg_accessed)) {
snd_ac97_write(ac97, reg, ac97->regs[reg]);
snd_ac97_read(ac97, reg);
}
}
if (! (ac97->flags & AC97_AD_MULTI))
/* normal restore */
snd_ac97_restore_status(ac97);
else {
/* restore the AD18xx codec configurations */
for (codec = 0; codec < 3; codec++) {
if (! ac97->spec.ad18xx.id[codec])
continue;
/* select single codec */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
ac97->bus->ops->write(ac97, AC97_AD_CODEC_CFG, ac97->spec.ad18xx.codec_cfg[codec]);
}
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
/* restore status */
for (i = 2; i < 0x7c ; i += 2) {
if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID)
continue;
if (test_bit(i, ac97->reg_accessed)) {
/* handle multi codecs for AD18xx */
if (i == AC97_PCM) {
for (codec = 0; codec < 3; codec++) {
if (! ac97->spec.ad18xx.id[codec])
continue;
/* select single codec */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
/* update PCM bits */
ac97->bus->ops->write(ac97, AC97_PCM, ac97->spec.ad18xx.pcmreg[codec]);
}
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
continue;
} else if (i == AC97_AD_TEST ||
i == AC97_AD_CODEC_CFG ||
i == AC97_AD_SERIAL_CFG)
continue; /* ignore */
}
snd_ac97_write(ac97, i, ac97->regs[i]);
snd_ac97_read(ac97, i);
}
}
snd_ac97_restore_iec958(ac97);
}
static void ad1888_resume(struct snd_ac97 *ac97)
{
ad18xx_resume(ac97);
snd_ac97_write_cache(ac97, AC97_CODEC_CLASS_REV, 0x8080);
}
#endif
static const struct snd_ac97_res_table ad1819_restbl[] = {
{ AC97_PHONE, 0x9f1f },
{ AC97_MIC, 0x9f1f },
{ AC97_LINE, 0x9f1f },
{ AC97_CD, 0x9f1f },
{ AC97_VIDEO, 0x9f1f },
{ AC97_AUX, 0x9f1f },
{ AC97_PCM, 0x9f1f },
{ } /* terminator */
};
static int patch_ad1819(struct snd_ac97 * ac97)
{
unsigned short scfg;
// patch for Analog Devices
scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x7000); /* select all codecs */
ac97->res_table = ad1819_restbl;
return 0;
}
static unsigned short patch_ad1881_unchained(struct snd_ac97 * ac97, int idx, unsigned short mask)
{
unsigned short val;
// test for unchained codec
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, mask);
snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000); /* ID0C, ID1C, SDIE = off */
val = snd_ac97_read(ac97, AC97_VENDOR_ID2);
if ((val & 0xff40) != 0x5340)
return 0;
ac97->spec.ad18xx.unchained[idx] = mask;
ac97->spec.ad18xx.id[idx] = val;
ac97->spec.ad18xx.codec_cfg[idx] = 0x0000;
return mask;
}
static int patch_ad1881_chained1(struct snd_ac97 * ac97, int idx, unsigned short codec_bits)
{
static const int cfg_bits[3] = { 1<<12, 1<<14, 1<<13 };
unsigned short val;
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, cfg_bits[idx]);
snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0004); // SDIE
val = snd_ac97_read(ac97, AC97_VENDOR_ID2);
if ((val & 0xff40) != 0x5340)
return 0;
if (codec_bits)
snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, codec_bits);
ac97->spec.ad18xx.chained[idx] = cfg_bits[idx];
ac97->spec.ad18xx.id[idx] = val;
ac97->spec.ad18xx.codec_cfg[idx] = codec_bits ? codec_bits : 0x0004;
return 1;
}
static void patch_ad1881_chained(struct snd_ac97 * ac97, int unchained_idx, int cidx1, int cidx2)
{
// already detected?
if (ac97->spec.ad18xx.unchained[cidx1] || ac97->spec.ad18xx.chained[cidx1])
cidx1 = -1;
if (ac97->spec.ad18xx.unchained[cidx2] || ac97->spec.ad18xx.chained[cidx2])
cidx2 = -1;
if (cidx1 < 0 && cidx2 < 0)
return;
// test for chained codecs
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[unchained_idx]);
snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0002); // ID1C
ac97->spec.ad18xx.codec_cfg[unchained_idx] = 0x0002;
if (cidx1 >= 0) {
if (cidx2 < 0)
patch_ad1881_chained1(ac97, cidx1, 0);
else if (patch_ad1881_chained1(ac97, cidx1, 0x0006)) // SDIE | ID1C
patch_ad1881_chained1(ac97, cidx2, 0);
else if (patch_ad1881_chained1(ac97, cidx2, 0x0006)) // SDIE | ID1C
patch_ad1881_chained1(ac97, cidx1, 0);
} else if (cidx2 >= 0) {
patch_ad1881_chained1(ac97, cidx2, 0);
}
}
static const struct snd_ac97_build_ops patch_ad1881_build_ops = {
#ifdef CONFIG_PM
.resume = ad18xx_resume
#endif
};
static int patch_ad1881(struct snd_ac97 * ac97)
{
static const char cfg_idxs[3][2] = {
{2, 1},
{0, 2},
{0, 1}
};
// patch for Analog Devices
unsigned short codecs[3];
unsigned short val;
int idx, num;
val = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, val);
codecs[0] = patch_ad1881_unchained(ac97, 0, (1<<12));
codecs[1] = patch_ad1881_unchained(ac97, 1, (1<<14));
codecs[2] = patch_ad1881_unchained(ac97, 2, (1<<13));
if (! (codecs[0] || codecs[1] || codecs[2]))
goto __end;
for (idx = 0; idx < 3; idx++)
if (ac97->spec.ad18xx.unchained[idx])
patch_ad1881_chained(ac97, idx, cfg_idxs[idx][0], cfg_idxs[idx][1]);
if (ac97->spec.ad18xx.id[1]) {
ac97->flags |= AC97_AD_MULTI;
ac97->scaps |= AC97_SCAP_SURROUND_DAC;
}
if (ac97->spec.ad18xx.id[2]) {
ac97->flags |= AC97_AD_MULTI;
ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC;
}
__end:
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
/* check if only one codec is present */
for (idx = num = 0; idx < 3; idx++)
if (ac97->spec.ad18xx.id[idx])
num++;
if (num == 1) {
/* ok, deselect all ID bits */
snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000);
ac97->spec.ad18xx.codec_cfg[0] =
ac97->spec.ad18xx.codec_cfg[1] =
ac97->spec.ad18xx.codec_cfg[2] = 0x0000;
}
/* required for AD1886/AD1885 combination */
ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID);
if (ac97->spec.ad18xx.id[0]) {
ac97->id &= 0xffff0000;
ac97->id |= ac97->spec.ad18xx.id[0];
}
ac97->build_ops = &patch_ad1881_build_ops;
return 0;
}
static const struct snd_kcontrol_new snd_ac97_controls_ad1885[] = {
AC97_SINGLE("Digital Mono Direct", AC97_AD_MISC, 11, 1, 0),
/* AC97_SINGLE("Digital Audio Mode", AC97_AD_MISC, 12, 1, 0), */ /* seems problematic */
AC97_SINGLE("Low Power Mixer", AC97_AD_MISC, 14, 1, 0),
AC97_SINGLE("Zero Fill DAC", AC97_AD_MISC, 15, 1, 0),
AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 9, 1, 1), /* inverted */
AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 8, 1, 1), /* inverted */
};
static const DECLARE_TLV_DB_SCALE(db_scale_6bit_6db_max, -8850, 150, 0);
static int patch_ad1885_specific(struct snd_ac97 * ac97)
{
int err;
err = patch_build_controls(ac97, snd_ac97_controls_ad1885, ARRAY_SIZE(snd_ac97_controls_ad1885));
if (err < 0)
return err;
reset_tlv(ac97, "Headphone Playback Volume",
db_scale_6bit_6db_max);
return 0;
}
static const struct snd_ac97_build_ops patch_ad1885_build_ops = {
.build_specific = &patch_ad1885_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume
#endif
};
static int patch_ad1885(struct snd_ac97 * ac97)
{
patch_ad1881(ac97);
/* This is required to deal with the Intel D815EEAL2 */
/* i.e. Line out is actually headphone out from codec */
/* set default */
snd_ac97_write_cache(ac97, AC97_AD_MISC, 0x0404);
ac97->build_ops = &patch_ad1885_build_ops;
return 0;
}
static int patch_ad1886_specific(struct snd_ac97 * ac97)
{
reset_tlv(ac97, "Headphone Playback Volume",
db_scale_6bit_6db_max);
return 0;
}
static const struct snd_ac97_build_ops patch_ad1886_build_ops = {
.build_specific = &patch_ad1886_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume
#endif
};
static int patch_ad1886(struct snd_ac97 * ac97)
{
patch_ad1881(ac97);
/* Presario700 workaround */
/* for Jack Sense/SPDIF Register misetting causing */
snd_ac97_write_cache(ac97, AC97_AD_JACK_SPDIF, 0x0010);
ac97->build_ops = &patch_ad1886_build_ops;
return 0;
}
/* MISC bits (AD1888/AD1980/AD1985 register 0x76) */
#define AC97_AD198X_MBC 0x0003 /* mic boost */
#define AC97_AD198X_MBC_20 0x0000 /* +20dB */
#define AC97_AD198X_MBC_10 0x0001 /* +10dB */
#define AC97_AD198X_MBC_30 0x0002 /* +30dB */
#define AC97_AD198X_VREFD 0x0004 /* VREF high-Z */
#define AC97_AD198X_VREFH 0x0008 /* 0=2.25V, 1=3.7V */
#define AC97_AD198X_VREF_0 0x000c /* 0V (AD1985 only) */
#define AC97_AD198X_VREF_MASK (AC97_AD198X_VREFH | AC97_AD198X_VREFD)
#define AC97_AD198X_VREF_SHIFT 2
#define AC97_AD198X_SRU 0x0010 /* sample rate unlock */
#define AC97_AD198X_LOSEL 0x0020 /* LINE_OUT amplifiers input select */
#define AC97_AD198X_2MIC 0x0040 /* 2-channel mic select */
#define AC97_AD198X_SPRD 0x0080 /* SPREAD enable */
#define AC97_AD198X_DMIX0 0x0100 /* downmix mode: */
/* 0 = 6-to-4, 1 = 6-to-2 downmix */
#define AC97_AD198X_DMIX1 0x0200 /* downmix mode: 1 = enabled */
#define AC97_AD198X_HPSEL 0x0400 /* headphone amplifier input select */
#define AC97_AD198X_CLDIS 0x0800 /* center/lfe disable */
#define AC97_AD198X_LODIS 0x1000 /* LINE_OUT disable */
#define AC97_AD198X_MSPLT 0x2000 /* mute split */
#define AC97_AD198X_AC97NC 0x4000 /* AC97 no compatible mode */
#define AC97_AD198X_DACZ 0x8000 /* DAC zero-fill mode */
/* MISC 1 bits (AD1986 register 0x76) */
#define AC97_AD1986_MBC 0x0003 /* mic boost */
#define AC97_AD1986_MBC_20 0x0000 /* +20dB */
#define AC97_AD1986_MBC_10 0x0001 /* +10dB */
#define AC97_AD1986_MBC_30 0x0002 /* +30dB */
#define AC97_AD1986_LISEL0 0x0004 /* LINE_IN select bit 0 */
#define AC97_AD1986_LISEL1 0x0008 /* LINE_IN select bit 1 */
#define AC97_AD1986_LISEL_MASK (AC97_AD1986_LISEL1 | AC97_AD1986_LISEL0)
#define AC97_AD1986_LISEL_LI 0x0000 /* LINE_IN pins as LINE_IN source */
#define AC97_AD1986_LISEL_SURR 0x0004 /* SURROUND pins as LINE_IN source */
#define AC97_AD1986_LISEL_MIC 0x0008 /* MIC_1/2 pins as LINE_IN source */
#define AC97_AD1986_SRU 0x0010 /* sample rate unlock */
#define AC97_AD1986_SOSEL 0x0020 /* SURROUND_OUT amplifiers input sel */
#define AC97_AD1986_2MIC 0x0040 /* 2-channel mic select */
#define AC97_AD1986_SPRD 0x0080 /* SPREAD enable */
#define AC97_AD1986_DMIX0 0x0100 /* downmix mode: */
/* 0 = 6-to-4, 1 = 6-to-2 downmix */
#define AC97_AD1986_DMIX1 0x0200 /* downmix mode: 1 = enabled */
#define AC97_AD1986_CLDIS 0x0800 /* center/lfe disable */
#define AC97_AD1986_SODIS 0x1000 /* SURROUND_OUT disable */
#define AC97_AD1986_MSPLT 0x2000 /* mute split (read only 1) */
#define AC97_AD1986_AC97NC 0x4000 /* AC97 no compatible mode (r/o 1) */
#define AC97_AD1986_DACZ 0x8000 /* DAC zero-fill mode */
/* MISC 2 bits (AD1986 register 0x70) */
#define AC97_AD_MISC2 0x70 /* Misc Control Bits 2 (AD1986) */
#define AC97_AD1986_CVREF0 0x0004 /* C/LFE VREF_OUT 2.25V */
#define AC97_AD1986_CVREF1 0x0008 /* C/LFE VREF_OUT 0V */
#define AC97_AD1986_CVREF2 0x0010 /* C/LFE VREF_OUT 3.7V */
#define AC97_AD1986_CVREF_MASK \
(AC97_AD1986_CVREF2 | AC97_AD1986_CVREF1 | AC97_AD1986_CVREF0)
#define AC97_AD1986_JSMAP 0x0020 /* Jack Sense Mapping 1 = alternate */
#define AC97_AD1986_MMDIS 0x0080 /* Mono Mute Disable */
#define AC97_AD1986_MVREF0 0x0400 /* MIC VREF_OUT 2.25V */
#define AC97_AD1986_MVREF1 0x0800 /* MIC VREF_OUT 0V */
#define AC97_AD1986_MVREF2 0x1000 /* MIC VREF_OUT 3.7V */
#define AC97_AD1986_MVREF_MASK \
(AC97_AD1986_MVREF2 | AC97_AD1986_MVREF1 | AC97_AD1986_MVREF0)
/* MISC 3 bits (AD1986 register 0x7a) */
#define AC97_AD_MISC3 0x7a /* Misc Control Bits 3 (AD1986) */
#define AC97_AD1986_MMIX 0x0004 /* Mic Mix, left/right */
#define AC97_AD1986_GPO 0x0008 /* General Purpose Out */
#define AC97_AD1986_LOHPEN 0x0010 /* LINE_OUT headphone drive */
#define AC97_AD1986_LVREF0 0x0100 /* LINE_OUT VREF_OUT 2.25V */
#define AC97_AD1986_LVREF1 0x0200 /* LINE_OUT VREF_OUT 0V */
#define AC97_AD1986_LVREF2 0x0400 /* LINE_OUT VREF_OUT 3.7V */
#define AC97_AD1986_LVREF_MASK \
(AC97_AD1986_LVREF2 | AC97_AD1986_LVREF1 | AC97_AD1986_LVREF0)
#define AC97_AD1986_JSINVA 0x0800 /* Jack Sense Invert SENSE_A */
#define AC97_AD1986_LOSEL 0x1000 /* LINE_OUT amplifiers input select */
#define AC97_AD1986_HPSEL0 0x2000 /* Headphone amplifiers */
/* input select Surround DACs */
#define AC97_AD1986_HPSEL1 0x4000 /* Headphone amplifiers input */
/* select C/LFE DACs */
#define AC97_AD1986_JSINVB 0x8000 /* Jack Sense Invert SENSE_B */
/* Serial Config bits (AD1986 register 0x74) (incomplete) */
#define AC97_AD1986_OMS0 0x0100 /* Optional Mic Selector bit 0 */
#define AC97_AD1986_OMS1 0x0200 /* Optional Mic Selector bit 1 */
#define AC97_AD1986_OMS2 0x0400 /* Optional Mic Selector bit 2 */
#define AC97_AD1986_OMS_MASK \
(AC97_AD1986_OMS2 | AC97_AD1986_OMS1 | AC97_AD1986_OMS0)
#define AC97_AD1986_OMS_M 0x0000 /* MIC_1/2 pins are MIC sources */
#define AC97_AD1986_OMS_L 0x0100 /* LINE_IN pins are MIC sources */
#define AC97_AD1986_OMS_C 0x0200 /* Center/LFE pins are MCI sources */
#define AC97_AD1986_OMS_MC 0x0400 /* Mix of MIC and C/LFE pins */
/* are MIC sources */
#define AC97_AD1986_OMS_ML 0x0500 /* MIX of MIC and LINE_IN pins */
/* are MIC sources */
#define AC97_AD1986_OMS_LC 0x0600 /* MIX of LINE_IN and C/LFE pins */
/* are MIC sources */
#define AC97_AD1986_OMS_MLC 0x0700 /* MIX of MIC, LINE_IN, C/LFE pins */
/* are MIC sources */
static int snd_ac97_ad198x_spdif_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[2] = { "AC-Link", "A/D Converter" };
return snd_ctl_enum_info(uinfo, 1, 2, texts);
}
static int snd_ac97_ad198x_spdif_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_AD_SERIAL_CFG];
ucontrol->value.enumerated.item[0] = (val >> 2) & 1;
return 0;
}
static int snd_ac97_ad198x_spdif_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
val = ucontrol->value.enumerated.item[0] << 2;
return snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x0004, val);
}
static const struct snd_kcontrol_new snd_ac97_ad198x_spdif_source = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
.info = snd_ac97_ad198x_spdif_source_info,
.get = snd_ac97_ad198x_spdif_source_get,
.put = snd_ac97_ad198x_spdif_source_put,
};
static int patch_ad198x_post_spdif(struct snd_ac97 * ac97)
{
return patch_build_controls(ac97, &snd_ac97_ad198x_spdif_source, 1);
}
static const struct snd_kcontrol_new snd_ac97_ad1981x_jack_sense[] = {
AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 11, 1, 0),
AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0),
};
/* deny list to avoid HP/Line jack-sense controls
* (SS vendor << 16 | device)
*/
static const unsigned int ad1981_jacks_denylist[] = {
0x10140523, /* Thinkpad R40 */
0x10140534, /* Thinkpad X31 */
0x10140537, /* Thinkpad T41p */
0x1014053e, /* Thinkpad R40e */
0x10140554, /* Thinkpad T42p/R50p */
0x10140567, /* Thinkpad T43p 2668-G7U */
0x10140581, /* Thinkpad X41-2527 */
0x10280160, /* Dell Dimension 2400 */
0x104380b0, /* Asus A7V8X-MX */
0x11790241, /* Toshiba Satellite A-15 S127 */
0x1179ff10, /* Toshiba P500 */
0x144dc01a, /* Samsung NP-X20C004/SEG */
0 /* end */
};
static int check_list(struct snd_ac97 *ac97, const unsigned int *list)
{
u32 subid = ((u32)ac97->subsystem_vendor << 16) | ac97->subsystem_device;
for (; *list; list++)
if (*list == subid)
return 1;
return 0;
}
static int patch_ad1981a_specific(struct snd_ac97 * ac97)
{
if (check_list(ac97, ad1981_jacks_denylist))
return 0;
return patch_build_controls(ac97, snd_ac97_ad1981x_jack_sense,
ARRAY_SIZE(snd_ac97_ad1981x_jack_sense));
}
static const struct snd_ac97_build_ops patch_ad1981a_build_ops = {
.build_post_spdif = patch_ad198x_post_spdif,
.build_specific = patch_ad1981a_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume
#endif
};
/* allow list to enable HP jack-sense bits
* (SS vendor << 16 | device)
*/
static const unsigned int ad1981_jacks_allowlist[] = {
0x0e11005a, /* HP nc4000/4010 */
0x103c0890, /* HP nc6000 */
0x103c0938, /* HP nc4220 */
0x103c099c, /* HP nx6110 */
0x103c0944, /* HP nc6220 */
0x103c0934, /* HP nc8220 */
0x103c006d, /* HP nx9105 */
0x103c300d, /* HP Compaq dc5100 SFF(PT003AW) */
0x17340088, /* FSC Scenic-W */
0 /* end */
};
static void check_ad1981_hp_jack_sense(struct snd_ac97 *ac97)
{
if (check_list(ac97, ad1981_jacks_allowlist))
/* enable headphone jack sense */
snd_ac97_update_bits(ac97, AC97_AD_JACK_SPDIF, 1<<11, 1<<11);
}
static int patch_ad1981a(struct snd_ac97 *ac97)
{
patch_ad1881(ac97);
ac97->build_ops = &patch_ad1981a_build_ops;
snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD198X_MSPLT, AC97_AD198X_MSPLT);
ac97->flags |= AC97_STEREO_MUTES;
check_ad1981_hp_jack_sense(ac97);
return 0;
}
static const struct snd_kcontrol_new snd_ac97_ad198x_2cmic =
AC97_SINGLE("Stereo Mic", AC97_AD_MISC, 6, 1, 0);
static int patch_ad1981b_specific(struct snd_ac97 *ac97)
{
int err;
err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1);
if (err < 0)
return err;
if (check_list(ac97, ad1981_jacks_denylist))
return 0;
return patch_build_controls(ac97, snd_ac97_ad1981x_jack_sense,
ARRAY_SIZE(snd_ac97_ad1981x_jack_sense));
}
static const struct snd_ac97_build_ops patch_ad1981b_build_ops = {
.build_post_spdif = patch_ad198x_post_spdif,
.build_specific = patch_ad1981b_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume
#endif
};
static int patch_ad1981b(struct snd_ac97 *ac97)
{
patch_ad1881(ac97);
ac97->build_ops = &patch_ad1981b_build_ops;
snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD198X_MSPLT, AC97_AD198X_MSPLT);
ac97->flags |= AC97_STEREO_MUTES;
check_ad1981_hp_jack_sense(ac97);
return 0;
}
#define snd_ac97_ad1888_lohpsel_info snd_ctl_boolean_mono_info
static int snd_ac97_ad1888_lohpsel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_AD_MISC];
ucontrol->value.integer.value[0] = !(val & AC97_AD198X_LOSEL);
if (ac97->spec.ad18xx.lo_as_master)
ucontrol->value.integer.value[0] =
!ucontrol->value.integer.value[0];
return 0;
}
static int snd_ac97_ad1888_lohpsel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = !ucontrol->value.integer.value[0];
if (ac97->spec.ad18xx.lo_as_master)
val = !val;
val = val ? (AC97_AD198X_LOSEL | AC97_AD198X_HPSEL) : 0;
return snd_ac97_update_bits(ac97, AC97_AD_MISC,
AC97_AD198X_LOSEL | AC97_AD198X_HPSEL, val);
}
static int snd_ac97_ad1888_downmix_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[3] = {"Off", "6 -> 4", "6 -> 2"};
return snd_ctl_enum_info(uinfo, 1, 3, texts);
}
static int snd_ac97_ad1888_downmix_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_AD_MISC];
if (!(val & AC97_AD198X_DMIX1))
ucontrol->value.enumerated.item[0] = 0;
else
ucontrol->value.enumerated.item[0] = 1 + ((val >> 8) & 1);
return 0;
}
static int snd_ac97_ad1888_downmix_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 2)
return -EINVAL;
if (ucontrol->value.enumerated.item[0] == 0)
val = 0;
else
val = AC97_AD198X_DMIX1 |
((ucontrol->value.enumerated.item[0] - 1) << 8);
return snd_ac97_update_bits(ac97, AC97_AD_MISC,
AC97_AD198X_DMIX0 | AC97_AD198X_DMIX1, val);
}
static void ad1888_update_jacks(struct snd_ac97 *ac97)
{
unsigned short val = 0;
/* clear LODIS if shared jack is to be used for Surround out */
if (!ac97->spec.ad18xx.lo_as_master && is_shared_linein(ac97))
val |= (1 << 12);
/* clear CLDIS if shared jack is to be used for C/LFE out */
if (is_shared_micin(ac97))
val |= (1 << 11);
/* shared Line-In */
snd_ac97_update_bits(ac97, AC97_AD_MISC, (1 << 11) | (1 << 12), val);
}
static const struct snd_kcontrol_new snd_ac97_ad1888_controls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Exchange Front/Surround",
.info = snd_ac97_ad1888_lohpsel_info,
.get = snd_ac97_ad1888_lohpsel_get,
.put = snd_ac97_ad1888_lohpsel_put
},
AC97_SINGLE("V_REFOUT Enable", AC97_AD_MISC, AC97_AD_VREFD_SHIFT, 1, 1),
AC97_SINGLE("High Pass Filter Enable", AC97_AD_TEST2,
AC97_AD_HPFD_SHIFT, 1, 1),
AC97_SINGLE("Spread Front to Surround and Center/LFE", AC97_AD_MISC, 7, 1, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Downmix",
.info = snd_ac97_ad1888_downmix_info,
.get = snd_ac97_ad1888_downmix_get,
.put = snd_ac97_ad1888_downmix_put
},
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0),
};
static int patch_ad1888_specific(struct snd_ac97 *ac97)
{
if (!ac97->spec.ad18xx.lo_as_master) {
/* rename 0x04 as "Master" and 0x02 as "Master Surround" */
snd_ac97_rename_vol_ctl(ac97, "Master Playback",
"Master Surround Playback");
snd_ac97_rename_vol_ctl(ac97, "Headphone Playback",
"Master Playback");
}
return patch_build_controls(ac97, snd_ac97_ad1888_controls, ARRAY_SIZE(snd_ac97_ad1888_controls));
}
static const struct snd_ac97_build_ops patch_ad1888_build_ops = {
.build_post_spdif = patch_ad198x_post_spdif,
.build_specific = patch_ad1888_specific,
#ifdef CONFIG_PM
.resume = ad1888_resume,
#endif
.update_jacks = ad1888_update_jacks,
};
static int patch_ad1888(struct snd_ac97 * ac97)
{
unsigned short misc;
patch_ad1881(ac97);
ac97->build_ops = &patch_ad1888_build_ops;
/*
* LO can be used as a real line-out on some devices,
* and we need to revert the front/surround mixer switches
*/
if (ac97->subsystem_vendor == 0x1043 &&
ac97->subsystem_device == 0x1193) /* ASUS A9T laptop */
ac97->spec.ad18xx.lo_as_master = 1;
misc = snd_ac97_read(ac97, AC97_AD_MISC);
/* AD-compatible mode */
/* Stereo mutes enabled */
misc |= AC97_AD198X_MSPLT | AC97_AD198X_AC97NC;
if (!ac97->spec.ad18xx.lo_as_master)
/* Switch FRONT/SURROUND LINE-OUT/HP-OUT default connection */
/* it seems that most vendors connect line-out connector to
* headphone out of AC'97
*/
misc |= AC97_AD198X_LOSEL | AC97_AD198X_HPSEL;
snd_ac97_write_cache(ac97, AC97_AD_MISC, misc);
ac97->flags |= AC97_STEREO_MUTES;
return 0;
}
static int patch_ad1980_specific(struct snd_ac97 *ac97)
{
int err;
err = patch_ad1888_specific(ac97);
if (err < 0)
return err;
return patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1);
}
static const struct snd_ac97_build_ops patch_ad1980_build_ops = {
.build_post_spdif = patch_ad198x_post_spdif,
.build_specific = patch_ad1980_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume,
#endif
.update_jacks = ad1888_update_jacks,
};
static int patch_ad1980(struct snd_ac97 * ac97)
{
patch_ad1888(ac97);
ac97->build_ops = &patch_ad1980_build_ops;
return 0;
}
static int snd_ac97_ad1985_vrefout_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[4] = {
"High-Z", "3.7 V", "2.25 V", "0 V"
};
return snd_ctl_enum_info(uinfo, 1, 4, texts);
}
static int snd_ac97_ad1985_vrefout_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
static const int reg2ctrl[4] = {2, 0, 1, 3};
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = (ac97->regs[AC97_AD_MISC] & AC97_AD198X_VREF_MASK)
>> AC97_AD198X_VREF_SHIFT;
ucontrol->value.enumerated.item[0] = reg2ctrl[val];
return 0;
}
static int snd_ac97_ad1985_vrefout_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
static const int ctrl2reg[4] = {1, 2, 0, 3};
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
if (ucontrol->value.enumerated.item[0] > 3)
return -EINVAL;
val = ctrl2reg[ucontrol->value.enumerated.item[0]]
<< AC97_AD198X_VREF_SHIFT;
return snd_ac97_update_bits(ac97, AC97_AD_MISC,
AC97_AD198X_VREF_MASK, val);
}
static const struct snd_kcontrol_new snd_ac97_ad1985_controls[] = {
AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Exchange Front/Surround",
.info = snd_ac97_ad1888_lohpsel_info,
.get = snd_ac97_ad1888_lohpsel_get,
.put = snd_ac97_ad1888_lohpsel_put
},
AC97_SINGLE("High Pass Filter Enable", AC97_AD_TEST2, 12, 1, 1),
AC97_SINGLE("Spread Front to Surround and Center/LFE",
AC97_AD_MISC, 7, 1, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Downmix",
.info = snd_ac97_ad1888_downmix_info,
.get = snd_ac97_ad1888_downmix_get,
.put = snd_ac97_ad1888_downmix_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "V_REFOUT",
.info = snd_ac97_ad1985_vrefout_info,
.get = snd_ac97_ad1985_vrefout_get,
.put = snd_ac97_ad1985_vrefout_put
},
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0),
};
static void ad1985_update_jacks(struct snd_ac97 *ac97)
{
ad1888_update_jacks(ac97);
/* clear OMS if shared jack is to be used for C/LFE out */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 1 << 9,
is_shared_micin(ac97) ? 1 << 9 : 0);
}
static int patch_ad1985_specific(struct snd_ac97 *ac97)
{
int err;
/* rename 0x04 as "Master" and 0x02 as "Master Surround" */
snd_ac97_rename_vol_ctl(ac97, "Master Playback",
"Master Surround Playback");
snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback");
err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1);
if (err < 0)
return err;
return patch_build_controls(ac97, snd_ac97_ad1985_controls,
ARRAY_SIZE(snd_ac97_ad1985_controls));
}
static const struct snd_ac97_build_ops patch_ad1985_build_ops = {
.build_post_spdif = patch_ad198x_post_spdif,
.build_specific = patch_ad1985_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume,
#endif
.update_jacks = ad1985_update_jacks,
};
static int patch_ad1985(struct snd_ac97 * ac97)
{
unsigned short misc;
patch_ad1881(ac97);
ac97->build_ops = &patch_ad1985_build_ops;
misc = snd_ac97_read(ac97, AC97_AD_MISC);
/* switch front/surround line-out/hp-out */
/* AD-compatible mode */
/* Stereo mutes enabled */
snd_ac97_write_cache(ac97, AC97_AD_MISC, misc |
AC97_AD198X_LOSEL |
AC97_AD198X_HPSEL |
AC97_AD198X_MSPLT |
AC97_AD198X_AC97NC);
ac97->flags |= AC97_STEREO_MUTES;
/* update current jack configuration */
ad1985_update_jacks(ac97);
/* on AD1985 rev. 3, AC'97 revision bits are zero */
ac97->ext_id = (ac97->ext_id & ~AC97_EI_REV_MASK) | AC97_EI_REV_23;
return 0;
}
#define snd_ac97_ad1986_bool_info snd_ctl_boolean_mono_info
static int snd_ac97_ad1986_lososel_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_AD_MISC3];
ucontrol->value.integer.value[0] = (val & AC97_AD1986_LOSEL) != 0;
return 0;
}
static int snd_ac97_ad1986_lososel_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int ret0;
int ret1;
int sprd = (ac97->regs[AC97_AD_MISC] & AC97_AD1986_SPRD) != 0;
ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC3, AC97_AD1986_LOSEL,
ucontrol->value.integer.value[0] != 0
? AC97_AD1986_LOSEL : 0);
if (ret0 < 0)
return ret0;
/* SOSEL is set to values of "Spread" or "Exchange F/S" controls */
ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL,
(ucontrol->value.integer.value[0] != 0
|| sprd)
? AC97_AD1986_SOSEL : 0);
if (ret1 < 0)
return ret1;
return (ret0 > 0 || ret1 > 0) ? 1 : 0;
}
static int snd_ac97_ad1986_spread_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_AD_MISC];
ucontrol->value.integer.value[0] = (val & AC97_AD1986_SPRD) != 0;
return 0;
}
static int snd_ac97_ad1986_spread_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
int ret0;
int ret1;
int sprd = (ac97->regs[AC97_AD_MISC3] & AC97_AD1986_LOSEL) != 0;
ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SPRD,
ucontrol->value.integer.value[0] != 0
? AC97_AD1986_SPRD : 0);
if (ret0 < 0)
return ret0;
/* SOSEL is set to values of "Spread" or "Exchange F/S" controls */
ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL,
(ucontrol->value.integer.value[0] != 0
|| sprd)
? AC97_AD1986_SOSEL : 0);
if (ret1 < 0)
return ret1;
return (ret0 > 0 || ret1 > 0) ? 1 : 0;
}
static int snd_ac97_ad1986_miclisel_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = ac97->spec.ad18xx.swap_mic_linein;
return 0;
}
static int snd_ac97_ad1986_miclisel_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned char swap = ucontrol->value.integer.value[0] != 0;
if (swap != ac97->spec.ad18xx.swap_mic_linein) {
ac97->spec.ad18xx.swap_mic_linein = swap;
if (ac97->build_ops->update_jacks)
ac97->build_ops->update_jacks(ac97);
return 1;
}
return 0;
}
static int snd_ac97_ad1986_vrefout_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* Use MIC_1/2 V_REFOUT as the "get" value */
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
unsigned short reg = ac97->regs[AC97_AD_MISC2];
if ((reg & AC97_AD1986_MVREF0) != 0)
val = 2;
else if ((reg & AC97_AD1986_MVREF1) != 0)
val = 3;
else if ((reg & AC97_AD1986_MVREF2) != 0)
val = 1;
else
val = 0;
ucontrol->value.enumerated.item[0] = val;
return 0;
}
static int snd_ac97_ad1986_vrefout_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short cval;
unsigned short lval;
unsigned short mval;
int cret;
int lret;
int mret;
switch (ucontrol->value.enumerated.item[0])
{
case 0: /* High-Z */
cval = 0;
lval = 0;
mval = 0;
break;
case 1: /* 3.7 V */
cval = AC97_AD1986_CVREF2;
lval = AC97_AD1986_LVREF2;
mval = AC97_AD1986_MVREF2;
break;
case 2: /* 2.25 V */
cval = AC97_AD1986_CVREF0;
lval = AC97_AD1986_LVREF0;
mval = AC97_AD1986_MVREF0;
break;
case 3: /* 0 V */
cval = AC97_AD1986_CVREF1;
lval = AC97_AD1986_LVREF1;
mval = AC97_AD1986_MVREF1;
break;
default:
return -EINVAL;
}
cret = snd_ac97_update_bits(ac97, AC97_AD_MISC2,
AC97_AD1986_CVREF_MASK, cval);
if (cret < 0)
return cret;
lret = snd_ac97_update_bits(ac97, AC97_AD_MISC3,
AC97_AD1986_LVREF_MASK, lval);
if (lret < 0)
return lret;
mret = snd_ac97_update_bits(ac97, AC97_AD_MISC2,
AC97_AD1986_MVREF_MASK, mval);
if (mret < 0)
return mret;
return (cret > 0 || lret > 0 || mret > 0) ? 1 : 0;
}
static const struct snd_kcontrol_new snd_ac97_ad1986_controls[] = {
AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Exchange Front/Surround",
.info = snd_ac97_ad1986_bool_info,
.get = snd_ac97_ad1986_lososel_get,
.put = snd_ac97_ad1986_lososel_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Exchange Mic/Line In",
.info = snd_ac97_ad1986_bool_info,
.get = snd_ac97_ad1986_miclisel_get,
.put = snd_ac97_ad1986_miclisel_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Spread Front to Surround and Center/LFE",
.info = snd_ac97_ad1986_bool_info,
.get = snd_ac97_ad1986_spread_get,
.put = snd_ac97_ad1986_spread_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Downmix",
.info = snd_ac97_ad1888_downmix_info,
.get = snd_ac97_ad1888_downmix_get,
.put = snd_ac97_ad1888_downmix_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "V_REFOUT",
.info = snd_ac97_ad1985_vrefout_info,
.get = snd_ac97_ad1986_vrefout_get,
.put = snd_ac97_ad1986_vrefout_put
},
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0)
};
static void ad1986_update_jacks(struct snd_ac97 *ac97)
{
unsigned short misc_val = 0;
unsigned short ser_val;
/* disable SURROUND and CENTER/LFE if not surround mode */
if (!is_surround_on(ac97))
misc_val |= AC97_AD1986_SODIS;
if (!is_clfe_on(ac97))
misc_val |= AC97_AD1986_CLDIS;
/* select line input (default=LINE_IN, SURROUND or MIC_1/2) */
if (is_shared_linein(ac97))
misc_val |= AC97_AD1986_LISEL_SURR;
else if (ac97->spec.ad18xx.swap_mic_linein != 0)
misc_val |= AC97_AD1986_LISEL_MIC;
snd_ac97_update_bits(ac97, AC97_AD_MISC,
AC97_AD1986_SODIS | AC97_AD1986_CLDIS |
AC97_AD1986_LISEL_MASK,
misc_val);
/* select microphone input (MIC_1/2, Center/LFE or LINE_IN) */
if (is_shared_micin(ac97))
ser_val = AC97_AD1986_OMS_C;
else if (ac97->spec.ad18xx.swap_mic_linein != 0)
ser_val = AC97_AD1986_OMS_L;
else
ser_val = AC97_AD1986_OMS_M;
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG,
AC97_AD1986_OMS_MASK,
ser_val);
}
static int patch_ad1986_specific(struct snd_ac97 *ac97)
{
int err;
err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1);
if (err < 0)
return err;
return patch_build_controls(ac97, snd_ac97_ad1986_controls,
ARRAY_SIZE(snd_ac97_ad1985_controls));
}
static const struct snd_ac97_build_ops patch_ad1986_build_ops = {
.build_post_spdif = patch_ad198x_post_spdif,
.build_specific = patch_ad1986_specific,
#ifdef CONFIG_PM
.resume = ad18xx_resume,
#endif
.update_jacks = ad1986_update_jacks,
};
static int patch_ad1986(struct snd_ac97 * ac97)
{
patch_ad1881(ac97);
ac97->build_ops = &patch_ad1986_build_ops;
ac97->flags |= AC97_STEREO_MUTES;
/* update current jack configuration */
ad1986_update_jacks(ac97);
return 0;
}
/*
* realtek ALC203: use mono-out for pin 37
*/
static int patch_alc203(struct snd_ac97 *ac97)
{
snd_ac97_update_bits(ac97, 0x7a, 0x400, 0x400);
return 0;
}
/*
* realtek ALC65x/850 codecs
*/
static void alc650_update_jacks(struct snd_ac97 *ac97)
{
int shared;
/* shared Line-In / Surround Out */
shared = is_shared_surrout(ac97);
snd_ac97_update_bits(ac97, AC97_ALC650_MULTICH, 1 << 9,
shared ? (1 << 9) : 0);
/* update shared Mic In / Center/LFE Out */
shared = is_shared_clfeout(ac97);
/* disable/enable vref */
snd_ac97_update_bits(ac97, AC97_ALC650_CLOCK, 1 << 12,
shared ? (1 << 12) : 0);
/* turn on/off center-on-mic */
snd_ac97_update_bits(ac97, AC97_ALC650_MULTICH, 1 << 10,
shared ? (1 << 10) : 0);
/* GPIO0 high for mic */
snd_ac97_update_bits(ac97, AC97_ALC650_GPIO_STATUS, 0x100,
shared ? 0 : 0x100);
}
static int alc650_swap_surround_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
struct snd_pcm_chmap *map = ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK];
if (map) {
if (ucontrol->value.integer.value[0])
map->chmap = snd_pcm_std_chmaps;
else
map->chmap = snd_pcm_alt_chmaps;
}
return snd_ac97_put_volsw(kcontrol, ucontrol);
}
static const struct snd_kcontrol_new snd_ac97_controls_alc650[] = {
AC97_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0),
AC97_SINGLE("Surround Down Mix", AC97_ALC650_MULTICH, 1, 1, 0),
AC97_SINGLE("Center/LFE Down Mix", AC97_ALC650_MULTICH, 2, 1, 0),
AC97_SINGLE("Exchange Center/LFE", AC97_ALC650_MULTICH, 3, 1, 0),
/* 4: Analog Input To Surround */
/* 5: Analog Input To Center/LFE */
/* 6: Independent Master Volume Right */
/* 7: Independent Master Volume Left */
/* 8: reserved */
/* 9: Line-In/Surround share */
/* 10: Mic/CLFE share */
/* 11-13: in IEC958 controls */
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Swap Surround Slot",
.info = snd_ac97_info_volsw,
.get = snd_ac97_get_volsw,
.put = alc650_swap_surround_put,
.private_value = AC97_SINGLE_VALUE(AC97_ALC650_MULTICH, 14, 1, 0),
},
#if 0 /* always set in patch_alc650 */
AC97_SINGLE("IEC958 Input Clock Enable", AC97_ALC650_CLOCK, 0, 1, 0),
AC97_SINGLE("IEC958 Input Pin Enable", AC97_ALC650_CLOCK, 1, 1, 0),
AC97_SINGLE("Surround DAC Switch", AC97_ALC650_SURR_DAC_VOL, 15, 1, 1),
AC97_DOUBLE("Surround DAC Volume", AC97_ALC650_SURR_DAC_VOL, 8, 0, 31, 1),
AC97_SINGLE("Center/LFE DAC Switch", AC97_ALC650_LFE_DAC_VOL, 15, 1, 1),
AC97_DOUBLE("Center/LFE DAC Volume", AC97_ALC650_LFE_DAC_VOL, 8, 0, 31, 1),
#endif
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
};
static const struct snd_kcontrol_new snd_ac97_spdif_controls_alc650[] = {
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_ALC650_MULTICH, 11, 1, 0),
AC97_SINGLE("Analog to IEC958 Output", AC97_ALC650_MULTICH, 12, 1, 0),
/* disable this controls since it doesn't work as expected */
/* AC97_SINGLE("IEC958 Input Monitor", AC97_ALC650_MULTICH, 13, 1, 0), */
};
static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_max, -4350, 150, 0);
static int patch_alc650_specific(struct snd_ac97 * ac97)
{
int err;
err = patch_build_controls(ac97, snd_ac97_controls_alc650, ARRAY_SIZE(snd_ac97_controls_alc650));
if (err < 0)
return err;
if (ac97->ext_id & AC97_EI_SPDIF) {
err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc650, ARRAY_SIZE(snd_ac97_spdif_controls_alc650));
if (err < 0)
return err;
}
if (ac97->id != AC97_ID_ALC650F)
reset_tlv(ac97, "Master Playback Volume",
db_scale_5bit_3db_max);
return 0;
}
static const struct snd_ac97_build_ops patch_alc650_ops = {
.build_specific = patch_alc650_specific,
.update_jacks = alc650_update_jacks
};
static int patch_alc650(struct snd_ac97 * ac97)
{
unsigned short val;
ac97->build_ops = &patch_alc650_ops;
/* determine the revision */
val = snd_ac97_read(ac97, AC97_ALC650_REVISION) & 0x3f;
if (val < 3)
ac97->id = 0x414c4720; /* Old version */
else if (val < 0x10)
ac97->id = 0x414c4721; /* D version */
else if (val < 0x20)
ac97->id = 0x414c4722; /* E version */
else if (val < 0x30)
ac97->id = 0x414c4723; /* F version */
/* revision E or F */
/* FIXME: what about revision D ? */
ac97->spec.dev_flags = (ac97->id == 0x414c4722 ||
ac97->id == 0x414c4723);
/* enable AC97_ALC650_GPIO_SETUP, AC97_ALC650_CLOCK for R/W */
snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_STATUS,
snd_ac97_read(ac97, AC97_ALC650_GPIO_STATUS) | 0x8000);
/* Enable SPDIF-IN only on Rev.E and above */
val = snd_ac97_read(ac97, AC97_ALC650_CLOCK);
/* SPDIF IN with pin 47 */
if (ac97->spec.dev_flags &&
/* ASUS A6KM requires EAPD */
! (ac97->subsystem_vendor == 0x1043 &&
ac97->subsystem_device == 0x1103))
val |= 0x03; /* enable */
else
val &= ~0x03; /* disable */
snd_ac97_write_cache(ac97, AC97_ALC650_CLOCK, val);
/* set default: slot 3,4,7,8,6,9
spdif-in monitor off, analog-spdif off, spdif-in off
center on mic off, surround on line-in off
downmix off, duplicate front off
*/
snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 0);
/* set GPIO0 for mic bias */
/* GPIO0 pin output, no interrupt, high */
snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_SETUP,
snd_ac97_read(ac97, AC97_ALC650_GPIO_SETUP) | 0x01);
snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_STATUS,
(snd_ac97_read(ac97, AC97_ALC650_GPIO_STATUS) | 0x100) & ~0x10);
/* full DAC volume */
snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808);
snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808);
return 0;
}
static void alc655_update_jacks(struct snd_ac97 *ac97)
{
int shared;
/* shared Line-In / Surround Out */
shared = is_shared_surrout(ac97);
ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 1 << 9,
shared ? (1 << 9) : 0, 0);
/* update shared Mic In / Center/LFE Out */
shared = is_shared_clfeout(ac97);
/* misc control; vrefout disable */
snd_ac97_update_bits(ac97, AC97_ALC650_CLOCK, 1 << 12,
shared ? (1 << 12) : 0);
ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 1 << 10,
shared ? (1 << 10) : 0, 0);
}
static const struct snd_kcontrol_new snd_ac97_controls_alc655[] = {
AC97_PAGE_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0, 0),
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
};
static int alc655_iec958_route_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts_655[3] = {
"PCM", "Analog In", "IEC958 In"
};
static const char * const texts_658[4] = {
"PCM", "Analog1 In", "Analog2 In", "IEC958 In"
};
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
if (ac97->spec.dev_flags)
return snd_ctl_enum_info(uinfo, 1, 4, texts_658);
else
return snd_ctl_enum_info(uinfo, 1, 3, texts_655);
}
static int alc655_iec958_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_ALC650_MULTICH];
val = (val >> 12) & 3;
if (ac97->spec.dev_flags && val == 3)
val = 0;
ucontrol->value.enumerated.item[0] = val;
return 0;
}
static int alc655_iec958_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
return ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 3 << 12,
(unsigned short)ucontrol->value.enumerated.item[0] << 12,
0);
}
static const struct snd_kcontrol_new snd_ac97_spdif_controls_alc655[] = {
AC97_PAGE_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_ALC650_MULTICH, 11, 1, 0, 0),
/* disable this controls since it doesn't work as expected */
/* AC97_PAGE_SINGLE("IEC958 Input Monitor", AC97_ALC650_MULTICH, 14, 1, 0, 0), */
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
.info = alc655_iec958_route_info,
.get = alc655_iec958_route_get,
.put = alc655_iec958_route_put,
},
};
static int patch_alc655_specific(struct snd_ac97 * ac97)
{
int err;
err = patch_build_controls(ac97, snd_ac97_controls_alc655, ARRAY_SIZE(snd_ac97_controls_alc655));
if (err < 0)
return err;
if (ac97->ext_id & AC97_EI_SPDIF) {
err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655));
if (err < 0)
return err;
}
return 0;
}
static const struct snd_ac97_build_ops patch_alc655_ops = {
.build_specific = patch_alc655_specific,
.update_jacks = alc655_update_jacks
};
static int patch_alc655(struct snd_ac97 * ac97)
{
unsigned int val;
if (ac97->id == AC97_ID_ALC658) {
ac97->spec.dev_flags = 1; /* ALC658 */
if ((snd_ac97_read(ac97, AC97_ALC650_REVISION) & 0x3f) == 2) {
ac97->id = AC97_ID_ALC658D;
ac97->spec.dev_flags = 2;
}
}
ac97->build_ops = &patch_alc655_ops;
/* assume only page 0 for writing cache */
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
/* adjust default values */
val = snd_ac97_read(ac97, 0x7a); /* misc control */
if (ac97->spec.dev_flags) /* ALC658 */
val &= ~(1 << 1); /* Pin 47 is spdif input pin */
else { /* ALC655 */
if (ac97->subsystem_vendor == 0x1462 &&
(ac97->subsystem_device == 0x0131 || /* MSI S270 laptop */
ac97->subsystem_device == 0x0161 || /* LG K1 Express */
ac97->subsystem_device == 0x0351 || /* MSI L725 laptop */
ac97->subsystem_device == 0x0471 || /* MSI L720 laptop */
ac97->subsystem_device == 0x0061)) /* MSI S250 laptop */
val &= ~(1 << 1); /* Pin 47 is EAPD (for internal speaker) */
else
val |= (1 << 1); /* Pin 47 is spdif input pin */
/* this seems missing on some hardwares */
ac97->ext_id |= AC97_EI_SPDIF;
}
val &= ~(1 << 12); /* vref enable */
snd_ac97_write_cache(ac97, 0x7a, val);
/* set default: spdif-in enabled,
spdif-in monitor off, spdif-in PCM off
center on mic off, surround on line-in off
duplicate front off
*/
snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 1<<15);
/* full DAC volume */
snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808);
snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808);
/* update undocumented bit... */
if (ac97->id == AC97_ID_ALC658D)
snd_ac97_update_bits(ac97, 0x74, 0x0800, 0x0800);
return 0;
}
#define AC97_ALC850_JACK_SELECT 0x76
#define AC97_ALC850_MISC1 0x7a
#define AC97_ALC850_MULTICH 0x6a
static void alc850_update_jacks(struct snd_ac97 *ac97)
{
int shared;
int aux_is_back_surround;
/* shared Line-In / Surround Out */
shared = is_shared_surrout(ac97);
/* SURR 1kOhm (bit4), Amp (bit5) */
snd_ac97_update_bits(ac97, AC97_ALC850_MISC1, (1<<4)|(1<<5),
shared ? (1<<5) : (1<<4));
/* LINE-IN = 0, SURROUND = 2 */
snd_ac97_update_bits(ac97, AC97_ALC850_JACK_SELECT, 7 << 12,
shared ? (2<<12) : (0<<12));
/* update shared Mic In / Center/LFE Out */
shared = is_shared_clfeout(ac97);
/* Vref disable (bit12), 1kOhm (bit13) */
snd_ac97_update_bits(ac97, AC97_ALC850_MISC1, (1<<12)|(1<<13),
shared ? (1<<12) : (1<<13));
/* MIC-IN = 1, CENTER-LFE = 5 */
snd_ac97_update_bits(ac97, AC97_ALC850_JACK_SELECT, 7 << 4,
shared ? (5<<4) : (1<<4));
aux_is_back_surround = alc850_is_aux_back_surround(ac97);
/* Aux is Back Surround */
snd_ac97_update_bits(ac97, AC97_ALC850_MULTICH, 1 << 10,
aux_is_back_surround ? (1<<10) : (0<<10));
}
static const struct snd_kcontrol_new snd_ac97_controls_alc850[] = {
AC97_PAGE_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0, 0),
AC97_SINGLE("Mic Front Input Switch", AC97_ALC850_JACK_SELECT, 15, 1, 1),
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_8CH_CTL,
};
static int patch_alc850_specific(struct snd_ac97 *ac97)
{
int err;
err = patch_build_controls(ac97, snd_ac97_controls_alc850, ARRAY_SIZE(snd_ac97_controls_alc850));
if (err < 0)
return err;
if (ac97->ext_id & AC97_EI_SPDIF) {
err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655));
if (err < 0)
return err;
}
return 0;
}
static const struct snd_ac97_build_ops patch_alc850_ops = {
.build_specific = patch_alc850_specific,
.update_jacks = alc850_update_jacks
};
static int patch_alc850(struct snd_ac97 *ac97)
{
ac97->build_ops = &patch_alc850_ops;
ac97->spec.dev_flags = 0; /* for IEC958 playback route - ALC655 compatible */
ac97->flags |= AC97_HAS_8CH;
/* assume only page 0 for writing cache */
snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
/* adjust default values */
/* set default: spdif-in enabled,
spdif-in monitor off, spdif-in PCM off
center on mic off, surround on line-in off
duplicate front off
NB default bit 10=0 = Aux is Capture, not Back Surround
*/
snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 1<<15);
/* SURR_OUT: on, Surr 1kOhm: on, Surr Amp: off, Front 1kOhm: off
* Front Amp: on, Vref: enable, Center 1kOhm: on, Mix: on
*/
snd_ac97_write_cache(ac97, 0x7a, (1<<1)|(1<<4)|(0<<5)|(1<<6)|
(1<<7)|(0<<12)|(1<<13)|(0<<14));
/* detection UIO2,3: all path floating, UIO3: MIC, Vref2: disable,
* UIO1: FRONT, Vref3: disable, UIO3: LINE, Front-Mic: mute
*/
snd_ac97_write_cache(ac97, 0x76, (0<<0)|(0<<2)|(1<<4)|(1<<7)|(2<<8)|
(1<<11)|(0<<12)|(1<<15));
/* full DAC volume */
snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808);
snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808);
return 0;
}
static int patch_aztech_azf3328_specific(struct snd_ac97 *ac97)
{
struct snd_kcontrol *kctl_3d_center =
snd_ac97_find_mixer_ctl(ac97, "3D Control - Center");
struct snd_kcontrol *kctl_3d_depth =
snd_ac97_find_mixer_ctl(ac97, "3D Control - Depth");
/*
* 3D register is different from AC97 standard layout
* (also do some renaming, to resemble Windows driver naming)
*/
if (kctl_3d_center) {
kctl_3d_center->private_value =
AC97_SINGLE_VALUE(AC97_3D_CONTROL, 1, 0x07, 0);
snd_ac97_rename_vol_ctl(ac97,
"3D Control - Center", "3D Control - Width"
);
}
if (kctl_3d_depth)
kctl_3d_depth->private_value =
AC97_SINGLE_VALUE(AC97_3D_CONTROL, 8, 0x03, 0);
/* Aztech Windows driver calls the
equivalent control "Modem Playback", thus rename it: */
snd_ac97_rename_vol_ctl(ac97,
"Master Mono Playback", "Modem Playback"
);
snd_ac97_rename_vol_ctl(ac97,
"Headphone Playback", "FM Synth Playback"
);
return 0;
}
static const struct snd_ac97_build_ops patch_aztech_azf3328_ops = {
.build_specific = patch_aztech_azf3328_specific
};
static int patch_aztech_azf3328(struct snd_ac97 *ac97)
{
ac97->build_ops = &patch_aztech_azf3328_ops;
return 0;
}
/*
* C-Media CM97xx codecs
*/
static void cm9738_update_jacks(struct snd_ac97 *ac97)
{
/* shared Line-In / Surround Out */
snd_ac97_update_bits(ac97, AC97_CM9738_VENDOR_CTRL, 1 << 10,
is_shared_surrout(ac97) ? (1 << 10) : 0);
}
static const struct snd_kcontrol_new snd_ac97_cm9738_controls[] = {
AC97_SINGLE("Duplicate Front", AC97_CM9738_VENDOR_CTRL, 13, 1, 0),
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_4CH_CTL,
};
static int patch_cm9738_specific(struct snd_ac97 * ac97)
{
return patch_build_controls(ac97, snd_ac97_cm9738_controls, ARRAY_SIZE(snd_ac97_cm9738_controls));
}
static const struct snd_ac97_build_ops patch_cm9738_ops = {
.build_specific = patch_cm9738_specific,
.update_jacks = cm9738_update_jacks
};
static int patch_cm9738(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_cm9738_ops;
/* FIXME: can anyone confirm below? */
/* CM9738 has no PCM volume although the register reacts */
ac97->flags |= AC97_HAS_NO_PCM_VOL;
snd_ac97_write_cache(ac97, AC97_PCM, 0x8000);
return 0;
}
static int snd_ac97_cmedia_spdif_playback_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[] = { "Analog", "Digital" };
return snd_ctl_enum_info(uinfo, 1, 2, texts);
}
static int snd_ac97_cmedia_spdif_playback_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
val = ac97->regs[AC97_CM9739_SPDIF_CTRL];
ucontrol->value.enumerated.item[0] = (val >> 1) & 0x01;
return 0;
}
static int snd_ac97_cmedia_spdif_playback_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
return snd_ac97_update_bits(ac97, AC97_CM9739_SPDIF_CTRL,
0x01 << 1,
(ucontrol->value.enumerated.item[0] & 0x01) << 1);
}
static const struct snd_kcontrol_new snd_ac97_cm9739_controls_spdif[] = {
/* BIT 0: SPDI_EN - always true */
{ /* BIT 1: SPDIFS */
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
.info = snd_ac97_cmedia_spdif_playback_source_info,
.get = snd_ac97_cmedia_spdif_playback_source_get,
.put = snd_ac97_cmedia_spdif_playback_source_put,
},
/* BIT 2: IG_SPIV */
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Valid Switch", AC97_CM9739_SPDIF_CTRL, 2, 1, 0),
/* BIT 3: SPI2F */
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Monitor", AC97_CM9739_SPDIF_CTRL, 3, 1, 0),
/* BIT 4: SPI2SDI */
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_CM9739_SPDIF_CTRL, 4, 1, 0),
/* BIT 8: SPD32 - 32bit SPDIF - not supported yet */
};
static void cm9739_update_jacks(struct snd_ac97 *ac97)
{
/* shared Line-In / Surround Out */
snd_ac97_update_bits(ac97, AC97_CM9739_MULTI_CHAN, 1 << 10,
is_shared_surrout(ac97) ? (1 << 10) : 0);
/* shared Mic In / Center/LFE Out **/
snd_ac97_update_bits(ac97, AC97_CM9739_MULTI_CHAN, 0x3000,
is_shared_clfeout(ac97) ? 0x1000 : 0x2000);
}
static const struct snd_kcontrol_new snd_ac97_cm9739_controls[] = {
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
};
static int patch_cm9739_specific(struct snd_ac97 * ac97)
{
return patch_build_controls(ac97, snd_ac97_cm9739_controls, ARRAY_SIZE(snd_ac97_cm9739_controls));
}
static int patch_cm9739_post_spdif(struct snd_ac97 * ac97)
{
return patch_build_controls(ac97, snd_ac97_cm9739_controls_spdif, ARRAY_SIZE(snd_ac97_cm9739_controls_spdif));
}
static const struct snd_ac97_build_ops patch_cm9739_ops = {
.build_specific = patch_cm9739_specific,
.build_post_spdif = patch_cm9739_post_spdif,
.update_jacks = cm9739_update_jacks
};
static int patch_cm9739(struct snd_ac97 * ac97)
{
unsigned short val;
ac97->build_ops = &patch_cm9739_ops;
/* CM9739/A has no Master and PCM volume although the register reacts */
ac97->flags |= AC97_HAS_NO_MASTER_VOL | AC97_HAS_NO_PCM_VOL;
snd_ac97_write_cache(ac97, AC97_MASTER, 0x8000);
snd_ac97_write_cache(ac97, AC97_PCM, 0x8000);
/* check spdif */
val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
if (val & AC97_EA_SPCV) {
/* enable spdif in */
snd_ac97_write_cache(ac97, AC97_CM9739_SPDIF_CTRL,
snd_ac97_read(ac97, AC97_CM9739_SPDIF_CTRL) | 0x01);
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
} else {
ac97->ext_id &= ~AC97_EI_SPDIF; /* disable extended-id */
ac97->rates[AC97_RATES_SPDIF] = 0;
}
/* set-up multi channel */
/* bit 14: 0 = SPDIF, 1 = EAPD */
/* bit 13: enable internal vref output for mic */
/* bit 12: disable center/lfe (switchable) */
/* bit 10: disable surround/line (switchable) */
/* bit 9: mix 2 surround off */
/* bit 4: undocumented; 0 mutes the CM9739A, which defaults to 1 */
/* bit 3: undocumented; surround? */
/* bit 0: dB */
val = snd_ac97_read(ac97, AC97_CM9739_MULTI_CHAN) & (1 << 4);
val |= (1 << 3);
val |= (1 << 13);
if (! (ac97->ext_id & AC97_EI_SPDIF))
val |= (1 << 14);
snd_ac97_write_cache(ac97, AC97_CM9739_MULTI_CHAN, val);
/* FIXME: set up GPIO */
snd_ac97_write_cache(ac97, 0x70, 0x0100);
snd_ac97_write_cache(ac97, 0x72, 0x0020);
/* Special exception for ASUS W1000/CMI9739. It does not have an SPDIF in. */
if (ac97->pci &&
ac97->subsystem_vendor == 0x1043 &&
ac97->subsystem_device == 0x1843) {
snd_ac97_write_cache(ac97, AC97_CM9739_SPDIF_CTRL,
snd_ac97_read(ac97, AC97_CM9739_SPDIF_CTRL) & ~0x01);
snd_ac97_write_cache(ac97, AC97_CM9739_MULTI_CHAN,
snd_ac97_read(ac97, AC97_CM9739_MULTI_CHAN) | (1 << 14));
}
return 0;
}
#define AC97_CM9761_MULTI_CHAN 0x64
#define AC97_CM9761_FUNC 0x66
#define AC97_CM9761_SPDIF_CTRL 0x6c
static void cm9761_update_jacks(struct snd_ac97 *ac97)
{
/* FIXME: check the bits for each model
* model 83 is confirmed to work
*/
static const unsigned short surr_on[3][2] = {
{ 0x0008, 0x0000 }, /* 9761-78 & 82 */
{ 0x0000, 0x0008 }, /* 9761-82 rev.B */
{ 0x0000, 0x0008 }, /* 9761-83 */
};
static const unsigned short clfe_on[3][2] = {
{ 0x0000, 0x1000 }, /* 9761-78 & 82 */
{ 0x1000, 0x0000 }, /* 9761-82 rev.B */
{ 0x0000, 0x1000 }, /* 9761-83 */
};
static const unsigned short surr_shared[3][2] = {
{ 0x0000, 0x0400 }, /* 9761-78 & 82 */
{ 0x0000, 0x0400 }, /* 9761-82 rev.B */
{ 0x0000, 0x0400 }, /* 9761-83 */
};
static const unsigned short clfe_shared[3][2] = {
{ 0x2000, 0x0880 }, /* 9761-78 & 82 */
{ 0x0000, 0x2880 }, /* 9761-82 rev.B */
{ 0x2000, 0x0800 }, /* 9761-83 */
};
unsigned short val = 0;
val |= surr_on[ac97->spec.dev_flags][is_surround_on(ac97)];
val |= clfe_on[ac97->spec.dev_flags][is_clfe_on(ac97)];
val |= surr_shared[ac97->spec.dev_flags][is_shared_surrout(ac97)];
val |= clfe_shared[ac97->spec.dev_flags][is_shared_clfeout(ac97)];
snd_ac97_update_bits(ac97, AC97_CM9761_MULTI_CHAN, 0x3c88, val);
}
static const struct snd_kcontrol_new snd_ac97_cm9761_controls[] = {
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
};
static int cm9761_spdif_out_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[] = { "AC-Link", "ADC", "SPDIF-In" };
return snd_ctl_enum_info(uinfo, 1, 3, texts);
}
static int cm9761_spdif_out_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
if (ac97->regs[AC97_CM9761_FUNC] & 0x1)
ucontrol->value.enumerated.item[0] = 2; /* SPDIF-loopback */
else if (ac97->regs[AC97_CM9761_SPDIF_CTRL] & 0x2)
ucontrol->value.enumerated.item[0] = 1; /* ADC loopback */
else
ucontrol->value.enumerated.item[0] = 0; /* AC-link */
return 0;
}
static int cm9761_spdif_out_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
if (ucontrol->value.enumerated.item[0] == 2)
return snd_ac97_update_bits(ac97, AC97_CM9761_FUNC, 0x1, 0x1);
snd_ac97_update_bits(ac97, AC97_CM9761_FUNC, 0x1, 0);
return snd_ac97_update_bits(ac97, AC97_CM9761_SPDIF_CTRL, 0x2,
ucontrol->value.enumerated.item[0] == 1 ? 0x2 : 0);
}
static const char * const cm9761_dac_clock[] = {
"AC-Link", "SPDIF-In", "Both"
};
static const struct ac97_enum cm9761_dac_clock_enum =
AC97_ENUM_SINGLE(AC97_CM9761_SPDIF_CTRL, 9, 3, cm9761_dac_clock);
static const struct snd_kcontrol_new snd_ac97_cm9761_controls_spdif[] = {
{ /* BIT 1: SPDIFS */
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
.info = cm9761_spdif_out_source_info,
.get = cm9761_spdif_out_source_get,
.put = cm9761_spdif_out_source_put,
},
/* BIT 2: IG_SPIV */
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Valid Switch", AC97_CM9761_SPDIF_CTRL, 2, 1, 0),
/* BIT 3: SPI2F */
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Monitor", AC97_CM9761_SPDIF_CTRL, 3, 1, 0),
/* BIT 4: SPI2SDI */
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_CM9761_SPDIF_CTRL, 4, 1, 0),
/* BIT 9-10: DAC_CTL */
AC97_ENUM("DAC Clock Source", cm9761_dac_clock_enum),
};
static int patch_cm9761_post_spdif(struct snd_ac97 * ac97)
{
return patch_build_controls(ac97, snd_ac97_cm9761_controls_spdif, ARRAY_SIZE(snd_ac97_cm9761_controls_spdif));
}
static int patch_cm9761_specific(struct snd_ac97 * ac97)
{
return patch_build_controls(ac97, snd_ac97_cm9761_controls, ARRAY_SIZE(snd_ac97_cm9761_controls));
}
static const struct snd_ac97_build_ops patch_cm9761_ops = {
.build_specific = patch_cm9761_specific,
.build_post_spdif = patch_cm9761_post_spdif,
.update_jacks = cm9761_update_jacks
};
static int patch_cm9761(struct snd_ac97 *ac97)
{
unsigned short val;
/* CM9761 has no PCM volume although the register reacts */
/* Master volume seems to have _some_ influence on the analog
* input sounds
*/
ac97->flags |= /*AC97_HAS_NO_MASTER_VOL |*/ AC97_HAS_NO_PCM_VOL;
snd_ac97_write_cache(ac97, AC97_MASTER, 0x8808);
snd_ac97_write_cache(ac97, AC97_PCM, 0x8808);
ac97->spec.dev_flags = 0; /* 1 = model 82 revision B, 2 = model 83 */
if (ac97->id == AC97_ID_CM9761_82) {
unsigned short tmp;
/* check page 1, reg 0x60 */
val = snd_ac97_read(ac97, AC97_INT_PAGING);
snd_ac97_write_cache(ac97, AC97_INT_PAGING, (val & ~0x0f) | 0x01);
tmp = snd_ac97_read(ac97, 0x60);
ac97->spec.dev_flags = tmp & 1; /* revision B? */
snd_ac97_write_cache(ac97, AC97_INT_PAGING, val);
} else if (ac97->id == AC97_ID_CM9761_83)
ac97->spec.dev_flags = 2;
ac97->build_ops = &patch_cm9761_ops;
/* enable spdif */
/* force the SPDIF bit in ext_id - codec doesn't set this bit! */
ac97->ext_id |= AC97_EI_SPDIF;
/* to be sure: we overwrite the ext status bits */
snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, 0x05c0);
/* Don't set 0x0200 here. This results in the silent analog output */
snd_ac97_write_cache(ac97, AC97_CM9761_SPDIF_CTRL, 0x0001); /* enable spdif-in */
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
/* set-up multi channel */
/* bit 15: pc master beep off
* bit 14: pin47 = EAPD/SPDIF
* bit 13: vref ctl [= cm9739]
* bit 12: CLFE control (reverted on rev B)
* bit 11: Mic/center share (reverted on rev B)
* bit 10: suddound/line share
* bit 9: Analog-in mix -> surround
* bit 8: Analog-in mix -> CLFE
* bit 7: Mic/LFE share (mic/center/lfe)
* bit 5: vref select (9761A)
* bit 4: front control
* bit 3: surround control (revereted with rev B)
* bit 2: front mic
* bit 1: stereo mic
* bit 0: mic boost level (0=20dB, 1=30dB)
*/
#if 0
if (ac97->spec.dev_flags)
val = 0x0214;
else
val = 0x321c;
#endif
val = snd_ac97_read(ac97, AC97_CM9761_MULTI_CHAN);
val |= (1 << 4); /* front on */
snd_ac97_write_cache(ac97, AC97_CM9761_MULTI_CHAN, val);
/* FIXME: set up GPIO */
snd_ac97_write_cache(ac97, 0x70, 0x0100);
snd_ac97_write_cache(ac97, 0x72, 0x0020);
return 0;
}
#define AC97_CM9780_SIDE 0x60
#define AC97_CM9780_JACK 0x62
#define AC97_CM9780_MIXER 0x64
#define AC97_CM9780_MULTI_CHAN 0x66
#define AC97_CM9780_SPDIF 0x6c
static const char * const cm9780_ch_select[] = {
"Front", "Side", "Center/LFE", "Rear"
};
static const struct ac97_enum cm9780_ch_select_enum =
AC97_ENUM_SINGLE(AC97_CM9780_MULTI_CHAN, 6, 4, cm9780_ch_select);
static const struct snd_kcontrol_new cm9780_controls[] = {
AC97_DOUBLE("Side Playback Switch", AC97_CM9780_SIDE, 15, 7, 1, 1),
AC97_DOUBLE("Side Playback Volume", AC97_CM9780_SIDE, 8, 0, 31, 0),
AC97_ENUM("Side Playback Route", cm9780_ch_select_enum),
};
static int patch_cm9780_specific(struct snd_ac97 *ac97)
{
return patch_build_controls(ac97, cm9780_controls, ARRAY_SIZE(cm9780_controls));
}
static const struct snd_ac97_build_ops patch_cm9780_ops = {
.build_specific = patch_cm9780_specific,
.build_post_spdif = patch_cm9761_post_spdif /* identical with CM9761 */
};
static int patch_cm9780(struct snd_ac97 *ac97)
{
unsigned short val;
ac97->build_ops = &patch_cm9780_ops;
/* enable spdif */
if (ac97->ext_id & AC97_EI_SPDIF) {
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
val = snd_ac97_read(ac97, AC97_CM9780_SPDIF);
val |= 0x1; /* SPDI_EN */
snd_ac97_write_cache(ac97, AC97_CM9780_SPDIF, val);
}
return 0;
}
/*
* VIA VT1613 codec
*/
static const struct snd_kcontrol_new snd_ac97_controls_vt1613[] = {
AC97_SINGLE("DC Offset removal", 0x5a, 10, 1, 0),
};
static int patch_vt1613_specific(struct snd_ac97 *ac97)
{
return patch_build_controls(ac97, &snd_ac97_controls_vt1613[0],
ARRAY_SIZE(snd_ac97_controls_vt1613));
};
static const struct snd_ac97_build_ops patch_vt1613_ops = {
.build_specific = patch_vt1613_specific
};
static int patch_vt1613(struct snd_ac97 *ac97)
{
ac97->build_ops = &patch_vt1613_ops;
ac97->flags |= AC97_HAS_NO_VIDEO;
ac97->caps |= AC97_BC_HEADPHONE;
return 0;
}
/*
* VIA VT1616 codec
*/
static const struct snd_kcontrol_new snd_ac97_controls_vt1616[] = {
AC97_SINGLE("DC Offset removal", 0x5a, 10, 1, 0),
AC97_SINGLE("Alternate Level to Surround Out", 0x5a, 15, 1, 0),
AC97_SINGLE("Downmix LFE and Center to Front", 0x5a, 12, 1, 0),
AC97_SINGLE("Downmix Surround to Front", 0x5a, 11, 1, 0),
};
static const char * const follower_vols_vt1616[] = {
"Front Playback Volume",
"Surround Playback Volume",
"Center Playback Volume",
"LFE Playback Volume",
NULL
};
static const char * const follower_sws_vt1616[] = {
"Front Playback Switch",
"Surround Playback Switch",
"Center Playback Switch",
"LFE Playback Switch",
NULL
};
/* find a mixer control element with the given name */
static struct snd_kcontrol *snd_ac97_find_mixer_ctl(struct snd_ac97 *ac97,
const char *name)
{
struct snd_ctl_elem_id id;
memset(&id, 0, sizeof(id));
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
strcpy(id.name, name);
return snd_ctl_find_id(ac97->bus->card, &id);
}
/* create a virtual master control and add followers */
static int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name,
const unsigned int *tlv,
const char * const *followers)
{
struct snd_kcontrol *kctl;
const char * const *s;
int err;
kctl = snd_ctl_make_virtual_master(name, tlv);
if (!kctl)
return -ENOMEM;
err = snd_ctl_add(ac97->bus->card, kctl);
if (err < 0)
return err;
for (s = followers; *s; s++) {
struct snd_kcontrol *sctl;
sctl = snd_ac97_find_mixer_ctl(ac97, *s);
if (!sctl) {
dev_dbg(ac97->bus->card->dev,
"Cannot find follower %s, skipped\n", *s);
continue;
}
err = snd_ctl_add_follower(kctl, sctl);
if (err < 0)
return err;
}
return 0;
}
static int patch_vt1616_specific(struct snd_ac97 * ac97)
{
struct snd_kcontrol *kctl;
int err;
if (snd_ac97_try_bit(ac97, 0x5a, 9)) {
err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[0], 1);
if (err < 0)
return err;
}
err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[1], ARRAY_SIZE(snd_ac97_controls_vt1616) - 1);
if (err < 0)
return err;
/* There is already a misnamed master switch. Rename it. */
kctl = snd_ac97_find_mixer_ctl(ac97, "Master Playback Volume");
if (!kctl)
return -EINVAL;
snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Front Playback");
err = snd_ac97_add_vmaster(ac97, "Master Playback Volume",
kctl->tlv.p, follower_vols_vt1616);
if (err < 0)
return err;
err = snd_ac97_add_vmaster(ac97, "Master Playback Switch",
NULL, follower_sws_vt1616);
if (err < 0)
return err;
return 0;
}
static const struct snd_ac97_build_ops patch_vt1616_ops = {
.build_specific = patch_vt1616_specific
};
static int patch_vt1616(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_vt1616_ops;
return 0;
}
/*
* VT1617A codec
*/
/*
* unfortunately, the vt1617a stashes the twiddlers required for
* noodling the i/o jacks on 2 different regs. that means that we can't
* use the easy way provided by AC97_ENUM_DOUBLE() we have to write
* are own funcs.
*
* NB: this is absolutely and utterly different from the vt1618. dunno
* about the 1616.
*/
/* copied from ac97_surround_jack_mode_info() */
static int snd_ac97_vt1617a_smart51_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
/* ordering in this list reflects vt1617a docs for Reg 20 and
* 7a and Table 6 that lays out the matrix NB WRT Table6: SM51
* is SM51EN *AND* it's Bit14, not Bit15 so the table is very
* counter-intuitive */
static const char * const texts[] = {"LineIn Mic1", "LineIn Mic1 Mic3",
"Surr LFE/C Mic3", "LineIn LFE/C Mic3",
"LineIn Mic2", "LineIn Mic2 Mic1",
"Surr LFE Mic1", "Surr LFE Mic1 Mic2"};
return snd_ctl_enum_info(uinfo, 1, 8, texts);
}
static int snd_ac97_vt1617a_smart51_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ushort usSM51, usMS;
struct snd_ac97 *pac97;
pac97 = snd_kcontrol_chip(kcontrol); /* grab codec handle */
/* grab our desired bits, then mash them together in a manner
* consistent with Table 6 on page 17 in the 1617a docs */
usSM51 = snd_ac97_read(pac97, 0x7a) >> 14;
usMS = snd_ac97_read(pac97, 0x20) >> 8;
ucontrol->value.enumerated.item[0] = (usSM51 << 1) + usMS;
return 0;
}
static int snd_ac97_vt1617a_smart51_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ushort usSM51, usMS, usReg;
struct snd_ac97 *pac97;
pac97 = snd_kcontrol_chip(kcontrol); /* grab codec handle */
usSM51 = ucontrol->value.enumerated.item[0] >> 1;
usMS = ucontrol->value.enumerated.item[0] & 1;
/* push our values into the register - consider that things will be left
* in a funky state if the write fails */
usReg = snd_ac97_read(pac97, 0x7a);
snd_ac97_write_cache(pac97, 0x7a, (usReg & 0x3FFF) + (usSM51 << 14));
usReg = snd_ac97_read(pac97, 0x20);
snd_ac97_write_cache(pac97, 0x20, (usReg & 0xFEFF) + (usMS << 8));
return 0;
}
static const struct snd_kcontrol_new snd_ac97_controls_vt1617a[] = {
AC97_SINGLE("Center/LFE Exchange", 0x5a, 8, 1, 0),
/*
* These are used to enable/disable surround sound on motherboards
* that have 3 bidirectional analog jacks
*/
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Smart 5.1 Select",
.info = snd_ac97_vt1617a_smart51_info,
.get = snd_ac97_vt1617a_smart51_get,
.put = snd_ac97_vt1617a_smart51_put,
},
};
static int patch_vt1617a(struct snd_ac97 * ac97)
{
int err = 0;
int val;
/* we choose to not fail out at this point, but we tell the
caller when we return */
err = patch_build_controls(ac97, &snd_ac97_controls_vt1617a[0],
ARRAY_SIZE(snd_ac97_controls_vt1617a));
/* bring analog power consumption to normal by turning off the
* headphone amplifier, like WinXP driver for EPIA SP
*/
/* We need to check the bit before writing it.
* On some (many?) hardwares, setting bit actually clears it!
*/
val = snd_ac97_read(ac97, 0x5c);
if (!(val & 0x20))
snd_ac97_write_cache(ac97, 0x5c, 0x20);
ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000;
ac97->build_ops = &patch_vt1616_ops;
return err;
}
/* VIA VT1618 8 CHANNEL AC97 CODEC
*
* VIA implements 'Smart 5.1' completely differently on the 1618 than
* it does on the 1617a. awesome! They seem to have sourced this
* particular revision of the technology from somebody else, it's
* called Universal Audio Jack and it shows up on some other folk's chips
* as well.
*
* ordering in this list reflects vt1618 docs for Reg 60h and
* the block diagram, DACs are as follows:
*
* OUT_O -> Front,
* OUT_1 -> Surround,
* OUT_2 -> C/LFE
*
* Unlike the 1617a, each OUT has a consistent set of mappings
* for all bitpatterns other than 00:
*
* 01 Unmixed Output
* 10 Line In
* 11 Mic In
*
* Special Case of 00:
*
* OUT_0 Mixed Output
* OUT_1 Reserved
* OUT_2 Reserved
*
* I have no idea what the hell Reserved does, but on an MSI
* CN700T, i have to set it to get 5.1 output - YMMV, bad
* shit may happen.
*
* If other chips use Universal Audio Jack, then this code might be applicable
* to them.
*/
struct vt1618_uaj_item {
unsigned short mask;
unsigned short shift;
const char * const items[4];
};
/* This list reflects the vt1618 docs for Vendor Defined Register 0x60. */
static const struct vt1618_uaj_item vt1618_uaj[3] = {
{
/* speaker jack */
.mask = 0x03,
.shift = 0,
.items = {
"Speaker Out", "DAC Unmixed Out", "Line In", "Mic In"
}
},
{
/* line jack */
.mask = 0x0c,
.shift = 2,
.items = {
"Surround Out", "DAC Unmixed Out", "Line In", "Mic In"
}
},
{
/* mic jack */
.mask = 0x30,
.shift = 4,
.items = {
"Center LFE Out", "DAC Unmixed Out", "Line In", "Mic In"
},
},
};
static int snd_ac97_vt1618_UAJ_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
return snd_ctl_enum_info(uinfo, 1, 4,
vt1618_uaj[kcontrol->private_value].items);
}
/* All of the vt1618 Universal Audio Jack twiddlers are on
* Vendor Defined Register 0x60, page 0. The bits, and thus
* the mask, are the only thing that changes
*/
static int snd_ac97_vt1618_UAJ_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
unsigned short datpag, uaj;
struct snd_ac97 *pac97 = snd_kcontrol_chip(kcontrol);
mutex_lock(&pac97->page_mutex);
datpag = snd_ac97_read(pac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
snd_ac97_update_bits(pac97, AC97_INT_PAGING, AC97_PAGE_MASK, 0);
uaj = snd_ac97_read(pac97, 0x60) &
vt1618_uaj[kcontrol->private_value].mask;
snd_ac97_update_bits(pac97, AC97_INT_PAGING, AC97_PAGE_MASK, datpag);
mutex_unlock(&pac97->page_mutex);
ucontrol->value.enumerated.item[0] = uaj >>
vt1618_uaj[kcontrol->private_value].shift;
return 0;
}
static int snd_ac97_vt1618_UAJ_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return ac97_update_bits_page(snd_kcontrol_chip(kcontrol), 0x60,
vt1618_uaj[kcontrol->private_value].mask,
ucontrol->value.enumerated.item[0]<<
vt1618_uaj[kcontrol->private_value].shift,
0);
}
/* config aux in jack - not found on 3 jack motherboards or soundcards */
static int snd_ac97_vt1618_aux_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static const char * const txt_aux[] = {"Aux In", "Back Surr Out"};
return snd_ctl_enum_info(uinfo, 1, 2, txt_aux);
}
static int snd_ac97_vt1618_aux_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.enumerated.item[0] =
(snd_ac97_read(snd_kcontrol_chip(kcontrol), 0x5c) & 0x0008)>>3;
return 0;
}
static int snd_ac97_vt1618_aux_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* toggle surround rear dac power */
snd_ac97_update_bits(snd_kcontrol_chip(kcontrol), 0x5c, 0x0008,
ucontrol->value.enumerated.item[0] << 3);
/* toggle aux in surround rear out jack */
return snd_ac97_update_bits(snd_kcontrol_chip(kcontrol), 0x76, 0x0008,
ucontrol->value.enumerated.item[0] << 3);
}
static const struct snd_kcontrol_new snd_ac97_controls_vt1618[] = {
AC97_SINGLE("Exchange Center/LFE", 0x5a, 8, 1, 0),
AC97_SINGLE("DC Offset", 0x5a, 10, 1, 0),
AC97_SINGLE("Soft Mute", 0x5c, 0, 1, 1),
AC97_SINGLE("Headphone Amp", 0x5c, 5, 1, 1),
AC97_DOUBLE("Back Surr Volume", 0x5e, 8, 0, 31, 1),
AC97_SINGLE("Back Surr Switch", 0x5e, 15, 1, 1),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Speaker Jack Mode",
.info = snd_ac97_vt1618_UAJ_info,
.get = snd_ac97_vt1618_UAJ_get,
.put = snd_ac97_vt1618_UAJ_put,
.private_value = 0
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line Jack Mode",
.info = snd_ac97_vt1618_UAJ_info,
.get = snd_ac97_vt1618_UAJ_get,
.put = snd_ac97_vt1618_UAJ_put,
.private_value = 1
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Mic Jack Mode",
.info = snd_ac97_vt1618_UAJ_info,
.get = snd_ac97_vt1618_UAJ_get,
.put = snd_ac97_vt1618_UAJ_put,
.private_value = 2
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Aux Jack Mode",
.info = snd_ac97_vt1618_aux_info,
.get = snd_ac97_vt1618_aux_get,
.put = snd_ac97_vt1618_aux_put,
}
};
static int patch_vt1618(struct snd_ac97 *ac97)
{
return patch_build_controls(ac97, snd_ac97_controls_vt1618,
ARRAY_SIZE(snd_ac97_controls_vt1618));
}
/*
*/
static void it2646_update_jacks(struct snd_ac97 *ac97)
{
/* shared Line-In / Surround Out */
snd_ac97_update_bits(ac97, 0x76, 1 << 9,
is_shared_surrout(ac97) ? (1<<9) : 0);
/* shared Mic / Center/LFE Out */
snd_ac97_update_bits(ac97, 0x76, 1 << 10,
is_shared_clfeout(ac97) ? (1<<10) : 0);
}
static const struct snd_kcontrol_new snd_ac97_controls_it2646[] = {
AC97_SURROUND_JACK_MODE_CTL,
AC97_CHANNEL_MODE_CTL,
};
static const struct snd_kcontrol_new snd_ac97_spdif_controls_it2646[] = {
AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0x76, 11, 1, 0),
AC97_SINGLE("Analog to IEC958 Output", 0x76, 12, 1, 0),
AC97_SINGLE("IEC958 Input Monitor", 0x76, 13, 1, 0),
};
static int patch_it2646_specific(struct snd_ac97 * ac97)
{
int err;
err = patch_build_controls(ac97, snd_ac97_controls_it2646, ARRAY_SIZE(snd_ac97_controls_it2646));
if (err < 0)
return err;
err = patch_build_controls(ac97, snd_ac97_spdif_controls_it2646, ARRAY_SIZE(snd_ac97_spdif_controls_it2646));
if (err < 0)
return err;
return 0;
}
static const struct snd_ac97_build_ops patch_it2646_ops = {
.build_specific = patch_it2646_specific,
.update_jacks = it2646_update_jacks
};
static int patch_it2646(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_it2646_ops;
/* full DAC volume */
snd_ac97_write_cache(ac97, 0x5E, 0x0808);
snd_ac97_write_cache(ac97, 0x7A, 0x0808);
return 0;
}
/*
* Si3036 codec
*/
#define AC97_SI3036_CHIP_ID 0x5a
#define AC97_SI3036_LINE_CFG 0x5c
static const struct snd_kcontrol_new snd_ac97_controls_si3036[] = {
AC97_DOUBLE("Modem Speaker Volume", 0x5c, 14, 12, 3, 1)
};
static int patch_si3036_specific(struct snd_ac97 * ac97)
{
int idx, err;
for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_si3036); idx++) {
err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_si3036[idx], ac97));
if (err < 0)
return err;
}
return 0;
}
static const struct snd_ac97_build_ops patch_si3036_ops = {
.build_specific = patch_si3036_specific,
};
static int mpatch_si3036(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_si3036_ops;
snd_ac97_write_cache(ac97, 0x5c, 0xf210 );
snd_ac97_write_cache(ac97, 0x68, 0);
return 0;
}
/*
* LM 4550 Codec
*
* We use a static resolution table since LM4550 codec cannot be
* properly autoprobed to determine the resolution via
* check_volume_resolution().
*/
static const struct snd_ac97_res_table lm4550_restbl[] = {
{ AC97_MASTER, 0x1f1f },
{ AC97_HEADPHONE, 0x1f1f },
{ AC97_MASTER_MONO, 0x001f },
{ AC97_PC_BEEP, 0x001f }, /* LSB is ignored */
{ AC97_PHONE, 0x001f },
{ AC97_MIC, 0x001f },
{ AC97_LINE, 0x1f1f },
{ AC97_CD, 0x1f1f },
{ AC97_VIDEO, 0x1f1f },
{ AC97_AUX, 0x1f1f },
{ AC97_PCM, 0x1f1f },
{ AC97_REC_GAIN, 0x0f0f },
{ } /* terminator */
};
static int patch_lm4550(struct snd_ac97 *ac97)
{
ac97->res_table = lm4550_restbl;
return 0;
}
/*
* UCB1400 codec (http://www.semiconductors.philips.com/acrobat_download/datasheets/UCB1400-02.pdf)
*/
static const struct snd_kcontrol_new snd_ac97_controls_ucb1400[] = {
/* enable/disable headphone driver which allows direct connection to
stereo headphone without the use of external DC blocking
capacitors */
AC97_SINGLE("Headphone Driver", 0x6a, 6, 1, 0),
/* Filter used to compensate the DC offset is added in the ADC to remove idle
tones from the audio band. */
AC97_SINGLE("DC Filter", 0x6a, 4, 1, 0),
/* Control smart-low-power mode feature. Allows automatic power down
of unused blocks in the ADC analog front end and the PLL. */
AC97_SINGLE("Smart Low Power Mode", 0x6c, 4, 3, 0),
};
static int patch_ucb1400_specific(struct snd_ac97 * ac97)
{
int idx, err;
for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_ucb1400); idx++) {
err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_ucb1400[idx], ac97));
if (err < 0)
return err;
}
return 0;
}
static const struct snd_ac97_build_ops patch_ucb1400_ops = {
.build_specific = patch_ucb1400_specific,
};
static int patch_ucb1400(struct snd_ac97 * ac97)
{
ac97->build_ops = &patch_ucb1400_ops;
/* enable headphone driver and smart low power mode by default */
snd_ac97_write_cache(ac97, 0x6a, 0x0050);
snd_ac97_write_cache(ac97, 0x6c, 0x0030);
return 0;
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com) and to datasheets
* for specific codecs.
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/ac97_codec.h>
#include <sound/asoundef.h>
#include "ac97_id.h"
#include "ac97_local.h"
/*
* PCM support
*/
static const unsigned char rate_reg_tables[2][4][9] = {
{
/* standard rates */
{
/* 3&4 front, 7&8 rear, 6&9 center/lfe */
AC97_PCM_FRONT_DAC_RATE, /* slot 3 */
AC97_PCM_FRONT_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
AC97_PCM_LFE_DAC_RATE, /* slot 6 */
AC97_PCM_SURR_DAC_RATE, /* slot 7 */
AC97_PCM_SURR_DAC_RATE, /* slot 8 */
AC97_PCM_LFE_DAC_RATE, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
/* 7&8 front, 6&9 rear, 10&11 center/lfe */
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
AC97_PCM_SURR_DAC_RATE, /* slot 6 */
AC97_PCM_FRONT_DAC_RATE, /* slot 7 */
AC97_PCM_FRONT_DAC_RATE, /* slot 8 */
AC97_PCM_SURR_DAC_RATE, /* slot 9 */
AC97_PCM_LFE_DAC_RATE, /* slot 10 */
AC97_PCM_LFE_DAC_RATE, /* slot 11 */
},
{
/* 6&9 front, 10&11 rear, 3&4 center/lfe */
AC97_PCM_LFE_DAC_RATE, /* slot 3 */
AC97_PCM_LFE_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
AC97_PCM_FRONT_DAC_RATE, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
AC97_PCM_FRONT_DAC_RATE, /* slot 9 */
AC97_PCM_SURR_DAC_RATE, /* slot 10 */
AC97_PCM_SURR_DAC_RATE, /* slot 11 */
},
{
/* 10&11 front, 3&4 rear, 7&8 center/lfe */
AC97_PCM_SURR_DAC_RATE, /* slot 3 */
AC97_PCM_SURR_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
AC97_PCM_LFE_DAC_RATE, /* slot 7 */
AC97_PCM_LFE_DAC_RATE, /* slot 8 */
0xff, /* slot 9 */
AC97_PCM_FRONT_DAC_RATE, /* slot 10 */
AC97_PCM_FRONT_DAC_RATE, /* slot 11 */
},
},
{
/* double rates */
{
/* 3&4 front, 7&8 front (t+1) */
AC97_PCM_FRONT_DAC_RATE, /* slot 3 */
AC97_PCM_FRONT_DAC_RATE, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
AC97_PCM_FRONT_DAC_RATE, /* slot 7 */
AC97_PCM_FRONT_DAC_RATE, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
/* not specified in the specification */
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
},
{
0xff, /* slot 3 */
0xff, /* slot 4 */
0xff, /* slot 5 */
0xff, /* slot 6 */
0xff, /* slot 7 */
0xff, /* slot 8 */
0xff, /* slot 9 */
0xff, /* slot 10 */
0xff, /* slot 11 */
}
}};
/* FIXME: more various mappings for ADC? */
static const unsigned char rate_cregs[9] = {
AC97_PCM_LR_ADC_RATE, /* 3 */
AC97_PCM_LR_ADC_RATE, /* 4 */
0xff, /* 5 */
AC97_PCM_MIC_ADC_RATE, /* 6 */
0xff, /* 7 */
0xff, /* 8 */
0xff, /* 9 */
0xff, /* 10 */
0xff, /* 11 */
};
static unsigned char get_slot_reg(struct ac97_pcm *pcm, unsigned short cidx,
unsigned short slot, int dbl)
{
if (slot < 3)
return 0xff;
if (slot > 11)
return 0xff;
if (pcm->spdif)
return AC97_SPDIF; /* pseudo register */
if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK)
return rate_reg_tables[dbl][pcm->r[dbl].rate_table[cidx]][slot - 3];
else
return rate_cregs[slot - 3];
}
static int set_spdif_rate(struct snd_ac97 *ac97, unsigned short rate)
{
unsigned short old, bits, reg, mask;
unsigned int sbits;
if (! (ac97->ext_id & AC97_EI_SPDIF))
return -ENODEV;
/* TODO: double rate support */
if (ac97->flags & AC97_CS_SPDIF) {
switch (rate) {
case 48000: bits = 0; break;
case 44100: bits = 1 << AC97_SC_SPSR_SHIFT; break;
default: /* invalid - disable output */
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
return -EINVAL;
}
reg = AC97_CSR_SPDIF;
mask = 1 << AC97_SC_SPSR_SHIFT;
} else {
if (ac97->id == AC97_ID_CM9739 && rate != 48000) {
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
return -EINVAL;
}
switch (rate) {
case 44100: bits = AC97_SC_SPSR_44K; break;
case 48000: bits = AC97_SC_SPSR_48K; break;
case 32000: bits = AC97_SC_SPSR_32K; break;
default: /* invalid - disable output */
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
return -EINVAL;
}
reg = AC97_SPDIF;
mask = AC97_SC_SPSR_MASK;
}
mutex_lock(&ac97->reg_mutex);
old = snd_ac97_read(ac97, reg) & mask;
if (old != bits) {
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
snd_ac97_update_bits_nolock(ac97, reg, mask, bits);
/* update the internal spdif bits */
sbits = ac97->spdif_status;
if (sbits & IEC958_AES0_PROFESSIONAL) {
sbits &= ~IEC958_AES0_PRO_FS;
switch (rate) {
case 44100: sbits |= IEC958_AES0_PRO_FS_44100; break;
case 48000: sbits |= IEC958_AES0_PRO_FS_48000; break;
case 32000: sbits |= IEC958_AES0_PRO_FS_32000; break;
}
} else {
sbits &= ~(IEC958_AES3_CON_FS << 24);
switch (rate) {
case 44100: sbits |= IEC958_AES3_CON_FS_44100<<24; break;
case 48000: sbits |= IEC958_AES3_CON_FS_48000<<24; break;
case 32000: sbits |= IEC958_AES3_CON_FS_32000<<24; break;
}
}
ac97->spdif_status = sbits;
}
snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF);
mutex_unlock(&ac97->reg_mutex);
return 0;
}
/**
* snd_ac97_set_rate - change the rate of the given input/output.
* @ac97: the ac97 instance
* @reg: the register to change
* @rate: the sample rate to set
*
* Changes the rate of the given input/output on the codec.
* If the codec doesn't support VAR, the rate must be 48000 (except
* for SPDIF).
*
* The valid registers are AC97_PCM_MIC_ADC_RATE,
* AC97_PCM_FRONT_DAC_RATE, AC97_PCM_LR_ADC_RATE.
* AC97_PCM_SURR_DAC_RATE and AC97_PCM_LFE_DAC_RATE are accepted
* if the codec supports them.
* AC97_SPDIF is accepted as a pseudo register to modify the SPDIF
* status bits.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate)
{
int dbl;
unsigned int tmp;
dbl = rate > 48000;
if (dbl) {
if (!(ac97->flags & AC97_DOUBLE_RATE))
return -EINVAL;
if (reg != AC97_PCM_FRONT_DAC_RATE)
return -EINVAL;
}
snd_ac97_update_power(ac97, reg, 1);
switch (reg) {
case AC97_PCM_MIC_ADC_RATE:
if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRM) == 0) /* MIC VRA */
if (rate != 48000)
return -EINVAL;
break;
case AC97_PCM_FRONT_DAC_RATE:
case AC97_PCM_LR_ADC_RATE:
if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRA) == 0) /* VRA */
if (rate != 48000 && rate != 96000)
return -EINVAL;
break;
case AC97_PCM_SURR_DAC_RATE:
if (! (ac97->scaps & AC97_SCAP_SURROUND_DAC))
return -EINVAL;
break;
case AC97_PCM_LFE_DAC_RATE:
if (! (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
return -EINVAL;
break;
case AC97_SPDIF:
/* special case */
return set_spdif_rate(ac97, rate);
default:
return -EINVAL;
}
if (dbl)
rate /= 2;
tmp = (rate * ac97->bus->clock) / 48000;
if (tmp > 65535)
return -EINVAL;
if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE)
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
AC97_EA_DRA, dbl ? AC97_EA_DRA : 0);
snd_ac97_update(ac97, reg, tmp & 0xffff);
snd_ac97_read(ac97, reg);
if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) {
/* Intel controllers require double rate data to be put in
* slots 7+8
*/
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE,
AC97_GP_DRSS_MASK,
dbl ? AC97_GP_DRSS_78 : 0);
snd_ac97_read(ac97, AC97_GENERAL_PURPOSE);
}
return 0;
}
EXPORT_SYMBOL(snd_ac97_set_rate);
static unsigned short get_pslots(struct snd_ac97 *ac97, unsigned char *rate_table, unsigned short *spdif_slots)
{
if (!ac97_is_audio(ac97))
return 0;
if (ac97_is_rev22(ac97) || ac97_can_amap(ac97)) {
unsigned short slots = 0;
if (ac97_is_rev22(ac97)) {
/* Note: it's simply emulation of AMAP behaviour */
u16 es;
es = ac97->regs[AC97_EXTENDED_ID] &= ~AC97_EI_DACS_SLOT_MASK;
switch (ac97->addr) {
case 1:
case 2: es |= (1<<AC97_EI_DACS_SLOT_SHIFT); break;
case 3: es |= (2<<AC97_EI_DACS_SLOT_SHIFT); break;
}
snd_ac97_write_cache(ac97, AC97_EXTENDED_ID, es);
}
switch (ac97->addr) {
case 0:
slots |= (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF) {
if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
else
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
}
*rate_table = 0;
break;
case 1:
case 2:
slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF) {
if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
else
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
}
*rate_table = 1;
break;
case 3:
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF)
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
*rate_table = 2;
break;
}
return slots;
} else {
unsigned short slots;
slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
if (ac97->ext_id & AC97_EI_SPDIF) {
if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
else
*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
}
*rate_table = 0;
return slots;
}
}
static unsigned short get_cslots(struct snd_ac97 *ac97)
{
unsigned short slots;
if (!ac97_is_audio(ac97))
return 0;
slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
slots |= (1<<AC97_SLOT_MIC);
return slots;
}
static unsigned int get_rates(struct ac97_pcm *pcm, unsigned int cidx, unsigned short slots, int dbl)
{
int i, idx;
unsigned int rates = ~0;
unsigned char reg;
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
reg = get_slot_reg(pcm, cidx, i, dbl);
switch (reg) {
case AC97_PCM_FRONT_DAC_RATE: idx = AC97_RATES_FRONT_DAC; break;
case AC97_PCM_SURR_DAC_RATE: idx = AC97_RATES_SURR_DAC; break;
case AC97_PCM_LFE_DAC_RATE: idx = AC97_RATES_LFE_DAC; break;
case AC97_PCM_LR_ADC_RATE: idx = AC97_RATES_ADC; break;
case AC97_PCM_MIC_ADC_RATE: idx = AC97_RATES_MIC_ADC; break;
default: idx = AC97_RATES_SPDIF; break;
}
rates &= pcm->r[dbl].codec[cidx]->rates[idx];
}
if (!dbl)
rates &= ~(SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000);
return rates;
}
/**
* snd_ac97_pcm_assign - assign AC97 slots to given PCM streams
* @bus: the ac97 bus instance
* @pcms_count: count of PCMs to be assigned
* @pcms: PCMs to be assigned
*
* It assigns available AC97 slots for given PCMs. If none or only
* some slots are available, pcm->xxx.slots and pcm->xxx.rslots[] members
* are reduced and might be zero.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_pcm_assign(struct snd_ac97_bus *bus,
unsigned short pcms_count,
const struct ac97_pcm *pcms)
{
int i, j, k;
const struct ac97_pcm *pcm;
struct ac97_pcm *rpcms, *rpcm;
unsigned short avail_slots[2][4];
unsigned char rate_table[2][4];
unsigned short tmp, slots;
unsigned short spdif_slots[4];
unsigned int rates;
struct snd_ac97 *codec;
rpcms = kcalloc(pcms_count, sizeof(struct ac97_pcm), GFP_KERNEL);
if (rpcms == NULL)
return -ENOMEM;
memset(avail_slots, 0, sizeof(avail_slots));
memset(rate_table, 0, sizeof(rate_table));
memset(spdif_slots, 0, sizeof(spdif_slots));
for (i = 0; i < 4; i++) {
codec = bus->codec[i];
if (!codec)
continue;
avail_slots[0][i] = get_pslots(codec, &rate_table[0][i], &spdif_slots[i]);
avail_slots[1][i] = get_cslots(codec);
if (!(codec->scaps & AC97_SCAP_INDEP_SDIN)) {
for (j = 0; j < i; j++) {
if (bus->codec[j])
avail_slots[1][i] &= ~avail_slots[1][j];
}
}
}
/* first step - exclusive devices */
for (i = 0; i < pcms_count; i++) {
pcm = &pcms[i];
rpcm = &rpcms[i];
/* low-level driver thinks that it's more clever */
if (pcm->copy_flag) {
*rpcm = *pcm;
continue;
}
rpcm->stream = pcm->stream;
rpcm->exclusive = pcm->exclusive;
rpcm->spdif = pcm->spdif;
rpcm->private_value = pcm->private_value;
rpcm->bus = bus;
rpcm->rates = ~0;
slots = pcm->r[0].slots;
for (j = 0; j < 4 && slots; j++) {
if (!bus->codec[j])
continue;
rates = ~0;
if (pcm->spdif && pcm->stream == 0)
tmp = spdif_slots[j];
else
tmp = avail_slots[pcm->stream][j];
if (pcm->exclusive) {
/* exclusive access */
tmp &= slots;
for (k = 0; k < i; k++) {
if (rpcm->stream == rpcms[k].stream)
tmp &= ~rpcms[k].r[0].rslots[j];
}
} else {
/* non-exclusive access */
tmp &= pcm->r[0].slots;
}
if (tmp) {
rpcm->r[0].rslots[j] = tmp;
rpcm->r[0].codec[j] = bus->codec[j];
rpcm->r[0].rate_table[j] = rate_table[pcm->stream][j];
if (bus->no_vra)
rates = SNDRV_PCM_RATE_48000;
else
rates = get_rates(rpcm, j, tmp, 0);
if (pcm->exclusive)
avail_slots[pcm->stream][j] &= ~tmp;
}
slots &= ~tmp;
rpcm->r[0].slots |= tmp;
rpcm->rates &= rates;
}
/* for double rate, we check the first codec only */
if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK &&
bus->codec[0] && (bus->codec[0]->flags & AC97_DOUBLE_RATE) &&
rate_table[pcm->stream][0] == 0) {
tmp = (1<<AC97_SLOT_PCM_LEFT) | (1<<AC97_SLOT_PCM_RIGHT) |
(1<<AC97_SLOT_PCM_LEFT_0) | (1<<AC97_SLOT_PCM_RIGHT_0);
if ((tmp & pcm->r[1].slots) == tmp) {
rpcm->r[1].slots = tmp;
rpcm->r[1].rslots[0] = tmp;
rpcm->r[1].rate_table[0] = 0;
rpcm->r[1].codec[0] = bus->codec[0];
if (pcm->exclusive)
avail_slots[pcm->stream][0] &= ~tmp;
if (bus->no_vra)
rates = SNDRV_PCM_RATE_96000;
else
rates = get_rates(rpcm, 0, tmp, 1);
rpcm->rates |= rates;
}
}
if (rpcm->rates == ~0)
rpcm->rates = 0; /* not used */
}
bus->pcms_count = pcms_count;
bus->pcms = rpcms;
return 0;
}
EXPORT_SYMBOL(snd_ac97_pcm_assign);
/**
* snd_ac97_pcm_open - opens the given AC97 pcm
* @pcm: the ac97 pcm instance
* @rate: rate in Hz, if codec does not support VRA, this value must be 48000Hz
* @cfg: output stream characteristics
* @slots: a subset of allocated slots (snd_ac97_pcm_assign) for this pcm
*
* It locks the specified slots and sets the given rate to AC97 registers.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate,
enum ac97_pcm_cfg cfg, unsigned short slots)
{
struct snd_ac97_bus *bus;
int i, cidx, r, ok_flag;
unsigned int reg_ok[4] = {0,0,0,0};
unsigned char reg;
int err = 0;
r = rate > 48000;
bus = pcm->bus;
if (cfg == AC97_PCM_CFG_SPDIF) {
for (cidx = 0; cidx < 4; cidx++)
if (bus->codec[cidx] && (bus->codec[cidx]->ext_id & AC97_EI_SPDIF)) {
err = set_spdif_rate(bus->codec[cidx], rate);
if (err < 0)
return err;
}
}
spin_lock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
ok_flag = 0;
for (cidx = 0; cidx < 4; cidx++) {
if (bus->used_slots[pcm->stream][cidx] & (1 << i)) {
spin_unlock_irq(&pcm->bus->bus_lock);
err = -EBUSY;
goto error;
}
if (pcm->r[r].rslots[cidx] & (1 << i)) {
bus->used_slots[pcm->stream][cidx] |= (1 << i);
ok_flag++;
}
}
if (!ok_flag) {
spin_unlock_irq(&pcm->bus->bus_lock);
dev_err(bus->card->dev,
"cannot find configuration for AC97 slot %i\n",
i);
err = -EAGAIN;
goto error;
}
}
pcm->cur_dbl = r;
spin_unlock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++) {
if (pcm->r[r].rslots[cidx] & (1 << i)) {
reg = get_slot_reg(pcm, cidx, i, r);
if (reg == 0xff) {
dev_err(bus->card->dev,
"invalid AC97 slot %i?\n", i);
continue;
}
if (reg_ok[cidx] & (1 << (reg - AC97_PCM_FRONT_DAC_RATE)))
continue;
dev_dbg(bus->card->dev,
"setting ac97 reg 0x%x to rate %d\n",
reg, rate);
err = snd_ac97_set_rate(pcm->r[r].codec[cidx], reg, rate);
if (err < 0)
dev_err(bus->card->dev,
"error in snd_ac97_set_rate: cidx=%d, reg=0x%x, rate=%d, err=%d\n",
cidx, reg, rate, err);
else
reg_ok[cidx] |= (1 << (reg - AC97_PCM_FRONT_DAC_RATE));
}
}
}
pcm->aslots = slots;
return 0;
error:
pcm->aslots = slots;
snd_ac97_pcm_close(pcm);
return err;
}
EXPORT_SYMBOL(snd_ac97_pcm_open);
/**
* snd_ac97_pcm_close - closes the given AC97 pcm
* @pcm: the ac97 pcm instance
*
* It frees the locked AC97 slots.
*
* Return: Zero.
*/
int snd_ac97_pcm_close(struct ac97_pcm *pcm)
{
struct snd_ac97_bus *bus;
unsigned short slots = pcm->aslots;
int i, cidx;
#ifdef CONFIG_SND_AC97_POWER_SAVE
int r = pcm->cur_dbl;
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++) {
if (pcm->r[r].rslots[cidx] & (1 << i)) {
int reg = get_slot_reg(pcm, cidx, i, r);
snd_ac97_update_power(pcm->r[r].codec[cidx],
reg, 0);
}
}
}
#endif
bus = pcm->bus;
spin_lock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++)
bus->used_slots[pcm->stream][cidx] &= ~(1 << i);
}
pcm->aslots = 0;
pcm->cur_dbl = 0;
spin_unlock_irq(&pcm->bus->bus_lock);
return 0;
}
EXPORT_SYMBOL(snd_ac97_pcm_close);
static int double_rate_hw_constraint_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
if (channels->min > 2) {
static const struct snd_interval single_rates = {
.min = 1,
.max = 48000,
};
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
return snd_interval_refine(rate, &single_rates);
}
return 0;
}
static int double_rate_hw_constraint_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
if (rate->min > 48000) {
static const struct snd_interval double_rate_channels = {
.min = 2,
.max = 2,
};
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
return snd_interval_refine(channels, &double_rate_channels);
}
return 0;
}
/**
* snd_ac97_pcm_double_rate_rules - set double rate constraints
* @runtime: the runtime of the ac97 front playback pcm
*
* Installs the hardware constraint rules to prevent using double rates and
* more than two channels at the same time.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime)
{
int err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
double_rate_hw_constraint_rate, NULL,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
double_rate_hw_constraint_channels, NULL,
SNDRV_PCM_HW_PARAM_RATE, -1);
return err;
}
EXPORT_SYMBOL(snd_ac97_pcm_double_rate_rules);
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Universal interface for Audio Codec '97
*
* For more details look to AC '97 component specification revision 2.2
* by Intel Corporation (http://developer.intel.com).
*/
#include <linux/mutex.h>
#include <sound/core.h>
#include <sound/ac97_codec.h>
#include <sound/asoundef.h>
#include "ac97_local.h"
#include "ac97_id.h"
/*
* proc interface
*/
static void snd_ac97_proc_read_functions(struct snd_ac97 *ac97, struct snd_info_buffer *buffer)
{
int header = 0, function;
unsigned short info, sense_info;
static const char *function_names[12] = {
"Master Out", "AUX Out", "Center/LFE Out", "SPDIF Out",
"Phone In", "Mic 1", "Mic 2", "Line In", "CD In", "Video In",
"Aux In", "Mono Out"
};
static const char *locations[8] = {
"Rear I/O Panel", "Front Panel", "Motherboard", "Dock/External",
"reserved", "reserved", "reserved", "NC/unused"
};
for (function = 0; function < 12; ++function) {
snd_ac97_write(ac97, AC97_FUNC_SELECT, function << 1);
info = snd_ac97_read(ac97, AC97_FUNC_INFO);
if (!(info & 0x0001))
continue;
if (!header) {
snd_iprintf(buffer, "\n Gain Inverted Buffer delay Location\n");
header = 1;
}
sense_info = snd_ac97_read(ac97, AC97_SENSE_INFO);
snd_iprintf(buffer, "%-17s: %3d.%d dBV %c %2d/fs %s\n",
function_names[function],
(info & 0x8000 ? -1 : 1) * ((info & 0x7000) >> 12) * 3 / 2,
((info & 0x0800) >> 11) * 5,
info & 0x0400 ? 'X' : '-',
(info & 0x03e0) >> 5,
locations[sense_info >> 13]);
}
}
static const char *snd_ac97_stereo_enhancements[] =
{
/* 0 */ "No 3D Stereo Enhancement",
/* 1 */ "Analog Devices Phat Stereo",
/* 2 */ "Creative Stereo Enhancement",
/* 3 */ "National Semi 3D Stereo Enhancement",
/* 4 */ "YAMAHA Ymersion",
/* 5 */ "BBE 3D Stereo Enhancement",
/* 6 */ "Crystal Semi 3D Stereo Enhancement",
/* 7 */ "Qsound QXpander",
/* 8 */ "Spatializer 3D Stereo Enhancement",
/* 9 */ "SRS 3D Stereo Enhancement",
/* 10 */ "Platform Tech 3D Stereo Enhancement",
/* 11 */ "AKM 3D Audio",
/* 12 */ "Aureal Stereo Enhancement",
/* 13 */ "Aztech 3D Enhancement",
/* 14 */ "Binaura 3D Audio Enhancement",
/* 15 */ "ESS Technology Stereo Enhancement",
/* 16 */ "Harman International VMAx",
/* 17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement",
/* 18 */ "Philips Incredible Sound",
/* 19 */ "Texas Instruments 3D Stereo Enhancement",
/* 20 */ "VLSI Technology 3D Stereo Enhancement",
/* 21 */ "TriTech 3D Stereo Enhancement",
/* 22 */ "Realtek 3D Stereo Enhancement",
/* 23 */ "Samsung 3D Stereo Enhancement",
/* 24 */ "Wolfson Microelectronics 3D Enhancement",
/* 25 */ "Delta Integration 3D Enhancement",
/* 26 */ "SigmaTel 3D Enhancement",
/* 27 */ "IC Ensemble/KS Waves",
/* 28 */ "Rockwell 3D Stereo Enhancement",
/* 29 */ "Reserved 29",
/* 30 */ "Reserved 30",
/* 31 */ "Reserved 31"
};
static void snd_ac97_proc_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx)
{
char name[64];
unsigned short val, tmp, ext, mext;
static const char *spdif_slots[4] = { " SPDIF=3/4", " SPDIF=7/8", " SPDIF=6/9", " SPDIF=10/11" };
static const char *spdif_rates[4] = { " Rate=44.1kHz", " Rate=res", " Rate=48kHz", " Rate=32kHz" };
static const char *spdif_rates_cs4205[4] = { " Rate=48kHz", " Rate=44.1kHz", " Rate=res", " Rate=res" };
static const char *double_rate_slots[4] = { "10/11", "7/8", "reserved", "reserved" };
snd_ac97_get_name(NULL, ac97->id, name, 0);
snd_iprintf(buffer, "%d-%d/%d: %s\n\n", ac97->addr, ac97->num, subidx, name);
if ((ac97->scaps & AC97_SCAP_AUDIO) == 0)
goto __modem;
snd_iprintf(buffer, "PCI Subsys Vendor: 0x%04x\n",
ac97->subsystem_vendor);
snd_iprintf(buffer, "PCI Subsys Device: 0x%04x\n\n",
ac97->subsystem_device);
snd_iprintf(buffer, "Flags: %x\n", ac97->flags);
if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) {
val = snd_ac97_read(ac97, AC97_INT_PAGING);
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, AC97_PAGE_1);
tmp = snd_ac97_read(ac97, AC97_CODEC_CLASS_REV);
snd_iprintf(buffer, "Revision : 0x%02x\n", tmp & 0xff);
snd_iprintf(buffer, "Compat. Class : 0x%02x\n", (tmp >> 8) & 0x1f);
snd_iprintf(buffer, "Subsys. Vendor ID: 0x%04x\n",
snd_ac97_read(ac97, AC97_PCI_SVID));
snd_iprintf(buffer, "Subsys. ID : 0x%04x\n\n",
snd_ac97_read(ac97, AC97_PCI_SID));
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, val & AC97_PAGE_MASK);
}
// val = snd_ac97_read(ac97, AC97_RESET);
val = ac97->caps;
snd_iprintf(buffer, "Capabilities :%s%s%s%s%s%s\n",
val & AC97_BC_DEDICATED_MIC ? " -dedicated MIC PCM IN channel-" : "",
val & AC97_BC_RESERVED1 ? " -reserved1-" : "",
val & AC97_BC_BASS_TREBLE ? " -bass & treble-" : "",
val & AC97_BC_SIM_STEREO ? " -simulated stereo-" : "",
val & AC97_BC_HEADPHONE ? " -headphone out-" : "",
val & AC97_BC_LOUDNESS ? " -loudness-" : "");
tmp = ac97->caps & AC97_BC_DAC_MASK;
snd_iprintf(buffer, "DAC resolution : %s%s%s%s\n",
tmp == AC97_BC_16BIT_DAC ? "16-bit" : "",
tmp == AC97_BC_18BIT_DAC ? "18-bit" : "",
tmp == AC97_BC_20BIT_DAC ? "20-bit" : "",
tmp == AC97_BC_DAC_MASK ? "???" : "");
tmp = ac97->caps & AC97_BC_ADC_MASK;
snd_iprintf(buffer, "ADC resolution : %s%s%s%s\n",
tmp == AC97_BC_16BIT_ADC ? "16-bit" : "",
tmp == AC97_BC_18BIT_ADC ? "18-bit" : "",
tmp == AC97_BC_20BIT_ADC ? "20-bit" : "",
tmp == AC97_BC_ADC_MASK ? "???" : "");
snd_iprintf(buffer, "3D enhancement : %s\n",
snd_ac97_stereo_enhancements[(val >> 10) & 0x1f]);
snd_iprintf(buffer, "\nCurrent setup\n");
val = snd_ac97_read(ac97, AC97_MIC);
snd_iprintf(buffer, "Mic gain : %s [%s]\n", val & 0x0040 ? "+20dB" : "+0dB", ac97->regs[AC97_MIC] & 0x0040 ? "+20dB" : "+0dB");
val = snd_ac97_read(ac97, AC97_GENERAL_PURPOSE);
snd_iprintf(buffer, "POP path : %s 3D\n"
"Sim. stereo : %s\n"
"3D enhancement : %s\n"
"Loudness : %s\n"
"Mono output : %s\n"
"Mic select : %s\n"
"ADC/DAC loopback : %s\n",
val & 0x8000 ? "post" : "pre",
val & 0x4000 ? "on" : "off",
val & 0x2000 ? "on" : "off",
val & 0x1000 ? "on" : "off",
val & 0x0200 ? "Mic" : "MIX",
val & 0x0100 ? "Mic2" : "Mic1",
val & 0x0080 ? "on" : "off");
if (ac97->ext_id & AC97_EI_DRA)
snd_iprintf(buffer, "Double rate slots: %s\n",
double_rate_slots[(val >> 10) & 3]);
ext = snd_ac97_read(ac97, AC97_EXTENDED_ID);
if (ext == 0)
goto __modem;
snd_iprintf(buffer, "Extended ID : codec=%i rev=%i%s%s%s%s DSA=%i%s%s%s%s\n",
(ext & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT,
(ext & AC97_EI_REV_MASK) >> AC97_EI_REV_SHIFT,
ext & AC97_EI_AMAP ? " AMAP" : "",
ext & AC97_EI_LDAC ? " LDAC" : "",
ext & AC97_EI_SDAC ? " SDAC" : "",
ext & AC97_EI_CDAC ? " CDAC" : "",
(ext & AC97_EI_DACS_SLOT_MASK) >> AC97_EI_DACS_SLOT_SHIFT,
ext & AC97_EI_VRM ? " VRM" : "",
ext & AC97_EI_SPDIF ? " SPDIF" : "",
ext & AC97_EI_DRA ? " DRA" : "",
ext & AC97_EI_VRA ? " VRA" : "");
val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
snd_iprintf(buffer, "Extended status :%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
val & AC97_EA_PRL ? " PRL" : "",
val & AC97_EA_PRK ? " PRK" : "",
val & AC97_EA_PRJ ? " PRJ" : "",
val & AC97_EA_PRI ? " PRI" : "",
val & AC97_EA_SPCV ? " SPCV" : "",
val & AC97_EA_MDAC ? " MADC" : "",
val & AC97_EA_LDAC ? " LDAC" : "",
val & AC97_EA_SDAC ? " SDAC" : "",
val & AC97_EA_CDAC ? " CDAC" : "",
ext & AC97_EI_SPDIF ? spdif_slots[(val & AC97_EA_SPSA_SLOT_MASK) >> AC97_EA_SPSA_SLOT_SHIFT] : "",
val & AC97_EA_VRM ? " VRM" : "",
val & AC97_EA_SPDIF ? " SPDIF" : "",
val & AC97_EA_DRA ? " DRA" : "",
val & AC97_EA_VRA ? " VRA" : "");
if (ext & AC97_EI_VRA) { /* VRA */
val = snd_ac97_read(ac97, AC97_PCM_FRONT_DAC_RATE);
snd_iprintf(buffer, "PCM front DAC : %iHz\n", val);
if (ext & AC97_EI_SDAC) {
val = snd_ac97_read(ac97, AC97_PCM_SURR_DAC_RATE);
snd_iprintf(buffer, "PCM Surr DAC : %iHz\n", val);
}
if (ext & AC97_EI_LDAC) {
val = snd_ac97_read(ac97, AC97_PCM_LFE_DAC_RATE);
snd_iprintf(buffer, "PCM LFE DAC : %iHz\n", val);
}
val = snd_ac97_read(ac97, AC97_PCM_LR_ADC_RATE);
snd_iprintf(buffer, "PCM ADC : %iHz\n", val);
}
if (ext & AC97_EI_VRM) {
val = snd_ac97_read(ac97, AC97_PCM_MIC_ADC_RATE);
snd_iprintf(buffer, "PCM MIC ADC : %iHz\n", val);
}
if ((ext & AC97_EI_SPDIF) || (ac97->flags & AC97_CS_SPDIF) ||
(ac97->id == AC97_ID_YMF743)) {
if (ac97->flags & AC97_CS_SPDIF)
val = snd_ac97_read(ac97, AC97_CSR_SPDIF);
else if (ac97->id == AC97_ID_YMF743) {
val = snd_ac97_read(ac97, AC97_YMF7X3_DIT_CTRL);
val = 0x2000 | (val & 0xff00) >> 4 | (val & 0x38) >> 2;
} else
val = snd_ac97_read(ac97, AC97_SPDIF);
snd_iprintf(buffer, "SPDIF Control :%s%s%s%s Category=0x%x Generation=%i%s%s%s\n",
val & AC97_SC_PRO ? " PRO" : " Consumer",
val & AC97_SC_NAUDIO ? " Non-audio" : " PCM",
val & AC97_SC_COPY ? "" : " Copyright",
val & AC97_SC_PRE ? " Preemph50/15" : "",
(val & AC97_SC_CC_MASK) >> AC97_SC_CC_SHIFT,
(val & AC97_SC_L) >> 11,
(ac97->flags & AC97_CS_SPDIF) ?
spdif_rates_cs4205[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT] :
spdif_rates[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT],
(ac97->flags & AC97_CS_SPDIF) ?
(val & AC97_SC_DRS ? " Validity" : "") :
(val & AC97_SC_DRS ? " DRS" : ""),
(ac97->flags & AC97_CS_SPDIF) ?
(val & AC97_SC_V ? " Enabled" : "") :
(val & AC97_SC_V ? " Validity" : ""));
/* ALC650 specific*/
if ((ac97->id & 0xfffffff0) == 0x414c4720 &&
(snd_ac97_read(ac97, AC97_ALC650_CLOCK) & 0x01)) {
val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2);
if (val & AC97_ALC650_CLOCK_LOCK) {
val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS1);
snd_iprintf(buffer, "SPDIF In Status :%s%s%s%s Category=0x%x Generation=%i",
val & AC97_ALC650_PRO ? " PRO" : " Consumer",
val & AC97_ALC650_NAUDIO ? " Non-audio" : " PCM",
val & AC97_ALC650_COPY ? "" : " Copyright",
val & AC97_ALC650_PRE ? " Preemph50/15" : "",
(val & AC97_ALC650_CC_MASK) >> AC97_ALC650_CC_SHIFT,
(val & AC97_ALC650_L) >> 15);
val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2);
snd_iprintf(buffer, "%s Accuracy=%i%s%s\n",
spdif_rates[(val & AC97_ALC650_SPSR_MASK) >> AC97_ALC650_SPSR_SHIFT],
(val & AC97_ALC650_CLOCK_ACCURACY) >> AC97_ALC650_CLOCK_SHIFT,
(val & AC97_ALC650_CLOCK_LOCK ? " Locked" : " Unlocked"),
(val & AC97_ALC650_V ? " Validity?" : ""));
} else {
snd_iprintf(buffer, "SPDIF In Status : Not Locked\n");
}
}
}
if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) {
val = snd_ac97_read(ac97, AC97_INT_PAGING);
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, AC97_PAGE_1);
snd_ac97_proc_read_functions(ac97, buffer);
snd_ac97_update_bits(ac97, AC97_INT_PAGING,
AC97_PAGE_MASK, val & AC97_PAGE_MASK);
}
__modem:
mext = snd_ac97_read(ac97, AC97_EXTENDED_MID);
if (mext == 0)
return;
snd_iprintf(buffer, "Extended modem ID: codec=%i%s%s%s%s%s\n",
(mext & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT,
mext & AC97_MEI_CID2 ? " CID2" : "",
mext & AC97_MEI_CID1 ? " CID1" : "",
mext & AC97_MEI_HANDSET ? " HSET" : "",
mext & AC97_MEI_LINE2 ? " LIN2" : "",
mext & AC97_MEI_LINE1 ? " LIN1" : "");
val = snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS);
snd_iprintf(buffer, "Modem status :%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
val & AC97_MEA_GPIO ? " GPIO" : "",
val & AC97_MEA_MREF ? " MREF" : "",
val & AC97_MEA_ADC1 ? " ADC1" : "",
val & AC97_MEA_DAC1 ? " DAC1" : "",
val & AC97_MEA_ADC2 ? " ADC2" : "",
val & AC97_MEA_DAC2 ? " DAC2" : "",
val & AC97_MEA_HADC ? " HADC" : "",
val & AC97_MEA_HDAC ? " HDAC" : "",
val & AC97_MEA_PRA ? " PRA(GPIO)" : "",
val & AC97_MEA_PRB ? " PRB(res)" : "",
val & AC97_MEA_PRC ? " PRC(ADC1)" : "",
val & AC97_MEA_PRD ? " PRD(DAC1)" : "",
val & AC97_MEA_PRE ? " PRE(ADC2)" : "",
val & AC97_MEA_PRF ? " PRF(DAC2)" : "",
val & AC97_MEA_PRG ? " PRG(HADC)" : "",
val & AC97_MEA_PRH ? " PRH(HDAC)" : "");
if (mext & AC97_MEI_LINE1) {
val = snd_ac97_read(ac97, AC97_LINE1_RATE);
snd_iprintf(buffer, "Line1 rate : %iHz\n", val);
}
if (mext & AC97_MEI_LINE2) {
val = snd_ac97_read(ac97, AC97_LINE2_RATE);
snd_iprintf(buffer, "Line2 rate : %iHz\n", val);
}
if (mext & AC97_MEI_HANDSET) {
val = snd_ac97_read(ac97, AC97_HANDSET_RATE);
snd_iprintf(buffer, "Headset rate : %iHz\n", val);
}
}
static void snd_ac97_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_ac97 *ac97 = entry->private_data;
mutex_lock(&ac97->page_mutex);
if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) { // Analog Devices AD1881/85/86
int idx;
for (idx = 0; idx < 3; idx++)
if (ac97->spec.ad18xx.id[idx]) {
/* select single codec */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]);
snd_ac97_proc_read_main(ac97, buffer, idx);
snd_iprintf(buffer, "\n\n");
}
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
snd_iprintf(buffer, "\nAD18XX configuration\n");
snd_iprintf(buffer, "Unchained : 0x%04x,0x%04x,0x%04x\n",
ac97->spec.ad18xx.unchained[0],
ac97->spec.ad18xx.unchained[1],
ac97->spec.ad18xx.unchained[2]);
snd_iprintf(buffer, "Chained : 0x%04x,0x%04x,0x%04x\n",
ac97->spec.ad18xx.chained[0],
ac97->spec.ad18xx.chained[1],
ac97->spec.ad18xx.chained[2]);
} else {
snd_ac97_proc_read_main(ac97, buffer, 0);
}
mutex_unlock(&ac97->page_mutex);
}
#ifdef CONFIG_SND_DEBUG
/* direct register write for debugging */
static void snd_ac97_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_ac97 *ac97 = entry->private_data;
char line[64];
unsigned int reg, val;
mutex_lock(&ac97->page_mutex);
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x", &reg, &val) != 2)
continue;
/* register must be even */
if (reg < 0x80 && (reg & 1) == 0 && val <= 0xffff)
snd_ac97_write_cache(ac97, reg, val);
}
mutex_unlock(&ac97->page_mutex);
}
#endif
static void snd_ac97_proc_regs_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx)
{
int reg, val;
for (reg = 0; reg < 0x80; reg += 2) {
val = snd_ac97_read(ac97, reg);
snd_iprintf(buffer, "%i:%02x = %04x\n", subidx, reg, val);
}
}
static void snd_ac97_proc_regs_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ac97 *ac97 = entry->private_data;
mutex_lock(&ac97->page_mutex);
if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) { // Analog Devices AD1881/85/86
int idx;
for (idx = 0; idx < 3; idx++)
if (ac97->spec.ad18xx.id[idx]) {
/* select single codec */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]);
snd_ac97_proc_regs_read_main(ac97, buffer, idx);
}
/* select all codecs */
snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
} else {
snd_ac97_proc_regs_read_main(ac97, buffer, 0);
}
mutex_unlock(&ac97->page_mutex);
}
void snd_ac97_proc_init(struct snd_ac97 * ac97)
{
struct snd_info_entry *entry;
char name[32];
const char *prefix;
if (ac97->bus->proc == NULL)
return;
prefix = ac97_is_audio(ac97) ? "ac97" : "mc97";
sprintf(name, "%s#%d-%d", prefix, ac97->addr, ac97->num);
entry = snd_info_create_card_entry(ac97->bus->card, name,
ac97->bus->proc);
if (entry)
snd_info_set_text_ops(entry, ac97, snd_ac97_proc_read);
ac97->proc = entry;
sprintf(name, "%s#%d-%d+regs", prefix, ac97->addr, ac97->num);
entry = snd_info_create_card_entry(ac97->bus->card, name,
ac97->bus->proc);
if (entry) {
snd_info_set_text_ops(entry, ac97, snd_ac97_proc_regs_read);
#ifdef CONFIG_SND_DEBUG
entry->mode |= 0200;
entry->c.text.write = snd_ac97_proc_regs_write;
#endif
}
ac97->proc_regs = entry;
}
void snd_ac97_proc_done(struct snd_ac97 * ac97)
{
snd_info_free_entry(ac97->proc_regs);
ac97->proc_regs = NULL;
snd_info_free_entry(ac97->proc);
ac97->proc = NULL;
}
void snd_ac97_bus_proc_init(struct snd_ac97_bus * bus)
{
struct snd_info_entry *entry;
char name[32];
sprintf(name, "codec97#%d", bus->num);
entry = snd_info_create_card_entry(bus->card, name,
bus->card->proc_root);
if (entry)
entry->mode = S_IFDIR | 0555;
bus->proc = entry;
}
void snd_ac97_bus_proc_done(struct snd_ac97_bus * bus)
{
snd_info_free_entry(bus->proc);
bus->proc = NULL;
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* Au1000/Au1500/Au1100 AC97C controller driver for ASoC
*
* (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
*
* based on the old ALSA driver originally written by
* Charles Eidsness <charles@cooper-street.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/mach-au1x00/au1000.h>
#include "psc.h"
/* register offsets and bits */
#define AC97_CONFIG 0x00
#define AC97_STATUS 0x04
#define AC97_DATA 0x08
#define AC97_CMDRESP 0x0c
#define AC97_ENABLE 0x10
#define CFG_RC(x) (((x) & 0x3ff) << 13) /* valid rx slots mask */
#define CFG_XS(x) (((x) & 0x3ff) << 3) /* valid tx slots mask */
#define CFG_SG (1 << 2) /* sync gate */
#define CFG_SN (1 << 1) /* sync control */
#define CFG_RS (1 << 0) /* acrst# control */
#define STAT_XU (1 << 11) /* tx underflow */
#define STAT_XO (1 << 10) /* tx overflow */
#define STAT_RU (1 << 9) /* rx underflow */
#define STAT_RO (1 << 8) /* rx overflow */
#define STAT_RD (1 << 7) /* codec ready */
#define STAT_CP (1 << 6) /* command pending */
#define STAT_TE (1 << 4) /* tx fifo empty */
#define STAT_TF (1 << 3) /* tx fifo full */
#define STAT_RE (1 << 1) /* rx fifo empty */
#define STAT_RF (1 << 0) /* rx fifo full */
#define CMD_SET_DATA(x) (((x) & 0xffff) << 16)
#define CMD_GET_DATA(x) ((x) & 0xffff)
#define CMD_READ (1 << 7)
#define CMD_WRITE (0 << 7)
#define CMD_IDX(x) ((x) & 0x7f)
#define EN_D (1 << 1) /* DISable bit */
#define EN_CE (1 << 0) /* clock enable bit */
/* how often to retry failed codec register reads/writes */
#define AC97_RW_RETRIES 5
#define AC97_RATES \
SNDRV_PCM_RATE_CONTINUOUS
#define AC97_FMTS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE)
/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only
* once AC97C on early Alchemy chips. The newer ones aren't so lucky.
*/
static struct au1xpsc_audio_data *ac97c_workdata;
#define ac97_to_ctx(x) ac97c_workdata
static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
{
return __raw_readl(ctx->mmio + reg);
}
static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
{
__raw_writel(v, ctx->mmio + reg);
wmb();
}
static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97,
unsigned short r)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
unsigned int tmo, retry;
unsigned long data;
data = ~0;
retry = AC97_RW_RETRIES;
do {
mutex_lock(&ctx->lock);
tmo = 6;
while ((RD(ctx, AC97_STATUS) & STAT_CP) && --tmo)
udelay(21); /* wait an ac97 frame time */
if (!tmo) {
pr_debug("ac97rd timeout #1\n");
goto next;
}
WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ);
/* stupid errata: data is only valid for 21us, so
* poll, Forrest, poll...
*/
tmo = 0x10000;
while ((RD(ctx, AC97_STATUS) & STAT_CP) && --tmo)
asm volatile ("nop");
data = RD(ctx, AC97_CMDRESP);
if (!tmo)
pr_debug("ac97rd timeout #2\n");
next:
mutex_unlock(&ctx->lock);
} while (--retry && !tmo);
pr_debug("AC97RD %04x %04lx %d\n", r, data, retry);
return retry ? data & 0xffff : 0xffff;
}
static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r,
unsigned short v)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
unsigned int tmo, retry;
retry = AC97_RW_RETRIES;
do {
mutex_lock(&ctx->lock);
for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
udelay(21);
if (!tmo) {
pr_debug("ac97wr timeout #1\n");
goto next;
}
WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v));
for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
udelay(21);
if (!tmo)
pr_debug("ac97wr timeout #2\n");
next:
mutex_unlock(&ctx->lock);
} while (--retry && !tmo);
pr_debug("AC97WR %04x %04x %d\n", r, v, retry);
}
static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN);
msleep(20);
WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG);
WR(ctx, AC97_CONFIG, ctx->cfg);
}
static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97)
{
struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
int i;
WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS);
msleep(500);
WR(ctx, AC97_CONFIG, ctx->cfg);
/* wait for codec ready */
i = 50;
while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i)
msleep(20);
if (!i)
printk(KERN_ERR "ac97c: codec not ready after cold reset\n");
}
/* AC97 controller operations */
static struct snd_ac97_bus_ops ac97c_bus_ops = {
.read = au1xac97c_ac97_read,
.write = au1xac97c_ac97_write,
.reset = au1xac97c_ac97_cold_reset,
.warm_reset = au1xac97c_ac97_warm_reset,
};
static int alchemy_ac97c_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
return 0;
}
static const struct snd_soc_dai_ops alchemy_ac97c_ops = {
.startup = alchemy_ac97c_startup,
};
static int au1xac97c_dai_probe(struct snd_soc_dai *dai)
{
return ac97c_workdata ? 0 : -ENODEV;
}
static struct snd_soc_dai_driver au1xac97c_dai_driver = {
.name = "alchemy-ac97c",
.probe = au1xac97c_dai_probe,
.playback = {
.rates = AC97_RATES,
.formats = AC97_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.capture = {
.rates = AC97_RATES,
.formats = AC97_FMTS,
.channels_min = 2,
.channels_max = 2,
},
.ops = &alchemy_ac97c_ops,
};
static const struct snd_soc_component_driver au1xac97c_component = {
.name = "au1xac97c",
};
static int au1xac97c_drvprobe(struct platform_device *pdev)
{
int ret;
struct resource *iores, *dmares;
struct au1xpsc_audio_data *ctx;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->lock);
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores)
return -ENODEV;
if (!devm_request_mem_region(&pdev->dev, iores->start,
resource_size(iores),
pdev->name))
return -EBUSY;
ctx->mmio = devm_ioremap(&pdev->dev, iores->start,
resource_size(iores));
if (!ctx->mmio)
return -EBUSY;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dmares)
return -EBUSY;
ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!dmares)
return -EBUSY;
ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
/* switch it on */
WR(ctx, AC97_ENABLE, EN_D | EN_CE);
WR(ctx, AC97_ENABLE, EN_CE);
ctx->cfg = CFG_RC(3) | CFG_XS(3);
WR(ctx, AC97_CONFIG, ctx->cfg);
platform_set_drvdata(pdev, ctx);
ret = snd_soc_set_ac97_ops(&ac97c_bus_ops);
if (ret)
return ret;
ret = snd_soc_register_component(&pdev->dev, &au1xac97c_component,
&au1xac97c_dai_driver, 1);
if (ret)
return ret;
ac97c_workdata = ctx;
return 0;
}
static int au1xac97c_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */
ac97c_workdata = NULL; /* MDEV */
return 0;
}
#ifdef CONFIG_PM
static int au1xac97c_drvsuspend(struct device *dev)
{
struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */
return 0;
}
static int au1xac97c_drvresume(struct device *dev)
{
struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
WR(ctx, AC97_ENABLE, EN_D | EN_CE);
WR(ctx, AC97_ENABLE, EN_CE);
WR(ctx, AC97_CONFIG, ctx->cfg);
return 0;
}
static const struct dev_pm_ops au1xpscac97_pmops = {
.suspend = au1xac97c_drvsuspend,
.resume = au1xac97c_drvresume,
};
#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops)
#else
#define AU1XPSCAC97_PMOPS NULL
#endif
static struct platform_driver au1xac97c_driver = {
.driver = {
.name = "alchemy-ac97c",
.pm = AU1XPSCAC97_PMOPS,
},
.probe = au1xac97c_drvprobe,
.remove = au1xac97c_drvremove,
};
module_platform_driver(au1xac97c_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver");
MODULE_AUTHOR("Manuel Lauss");
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
//
/* ACP machine configuration module */
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "../sof/amd/acp.h"
#include "mach-config.h"
static int acp_quirk_data;
static const struct config_entry config_table[] = {
{
.flags = FLAG_AMD_SOF,
.device = ACP_PCI_DEV_ID,
.dmi_table = (const struct dmi_system_id []) {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "AMD"),
DMI_MATCH(DMI_PRODUCT_NAME, "Majolica-CZN"),
},
},
{}
},
},
{
.flags = FLAG_AMD_SOF,
.device = ACP_PCI_DEV_ID,
.dmi_table = (const struct dmi_system_id []) {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
},
},
{}
},
},
};
int snd_amd_acp_find_config(struct pci_dev *pci)
{
const struct config_entry *table = config_table;
u16 device = pci->device;
int i;
/* Do not enable FLAGS on older platforms with Rev id zero */
if (!pci->revision)
return 0;
for (i = 0; i < ARRAY_SIZE(config_table); i++, table++) {
if (table->device != device)
continue;
if (table->dmi_table && !dmi_check_system(table->dmi_table))
continue;
acp_quirk_data = table->flags;
return table->flags;
}
return 0;
}
EXPORT_SYMBOL(snd_amd_acp_find_config);
static struct snd_soc_acpi_codecs amp_rt1019 = {
.num_codecs = 1,
.codecs = {"10EC1019"}
};
static struct snd_soc_acpi_codecs amp_max = {
.num_codecs = 1,
.codecs = {"MX98360A"}
};
struct snd_soc_acpi_mach snd_soc_acpi_amd_sof_machines[] = {
{
.id = "10EC5682",
.drv_name = "rt5682-rt1019",
.pdata = (void *)&acp_quirk_data,
.machine_quirk = snd_soc_acpi_codec_list,
.quirk_data = &amp_rt1019,
.fw_filename = "sof-rn.ri",
.sof_tplg_filename = "sof-rn-rt5682-rt1019.tplg",
},
{
.id = "10EC5682",
.drv_name = "rt5682-max",
.pdata = (void *)&acp_quirk_data,
.machine_quirk = snd_soc_acpi_codec_list,
.quirk_data = &amp_max,
.fw_filename = "sof-rn.ri",
.sof_tplg_filename = "sof-rn-rt5682-max98360.tplg",
},
{
.id = "RTL5682",
.drv_name = "rt5682s-max",
.pdata = (void *)&acp_quirk_data,
.machine_quirk = snd_soc_acpi_codec_list,
.quirk_data = &amp_max,
.fw_filename = "sof-rn.ri",
.sof_tplg_filename = "sof-rn-rt5682-max98360.tplg",
},
{
.id = "AMDI1019",
.drv_name = "renoir-dsp",
.pdata = (void *)&acp_quirk_data,
.fw_filename = "sof-rn.ri",
.sof_tplg_filename = "sof-acp.tplg",
},
{},
};
EXPORT_SYMBOL(snd_soc_acpi_amd_sof_machines);
MODULE_LICENSE("Dual BSD/GPL");
// SPDX-License-Identifier: MIT
//
// Machine driver for AMD ACP Audio engine using DA7219, RT5682 & MAX98357 codec
//
//Copyright 2017-2021 Advanced Micro Devices, Inc.
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/driver.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/acpi.h>
#include "acp.h"
#include "../codecs/da7219.h"
#include "../codecs/da7219-aad.h"
#include "../codecs/rt5682.h"
#define CZ_PLAT_CLK 48000000
#define DUAL_CHANNEL 2
#define RT5682_PLL_FREQ (48000 * 512)
static struct snd_soc_jack cz_jack;
static struct clk *da7219_dai_wclk;
static struct clk *da7219_dai_bclk;
static struct clk *rt5682_dai_wclk;
static struct clk *rt5682_dai_bclk;
void *acp_soc_is_rltk_max(struct device *dev);
static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name);
ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK,
CZ_PLAT_CLK, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL,
CZ_PLAT_CLK, DA7219_PLL_FREQ_OUT_98304);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
return ret;
}
da7219_dai_wclk = devm_clk_get(component->dev, "da7219-dai-wclk");
if (IS_ERR(da7219_dai_wclk))
return PTR_ERR(da7219_dai_wclk);
da7219_dai_bclk = devm_clk_get(component->dev, "da7219-dai-bclk");
if (IS_ERR(da7219_dai_bclk))
return PTR_ERR(da7219_dai_bclk);
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_LINEOUT |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3,
&cz_jack, NULL, 0);
if (ret) {
dev_err(card->dev, "HP jack creation failed %d\n", ret);
return ret;
}
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND);
da7219_aad_jack_det(component, &cz_jack);
return 0;
}
static int da7219_clk_enable(struct snd_pcm_substream *substream)
{
int ret = 0;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
/*
* Set wclk to 48000 because the rate constraint of this driver is
* 48000. ADAU7002 spec: "The ADAU7002 requires a BCLK rate that is
* minimum of 64x the LRCLK sample rate." DA7219 is the only clk
* source so for all codecs we have to limit bclk to 64X lrclk.
*/
clk_set_rate(da7219_dai_wclk, 48000);
clk_set_rate(da7219_dai_bclk, 48000 * 64);
ret = clk_prepare_enable(da7219_dai_bclk);
if (ret < 0) {
dev_err(rtd->dev, "can't enable master clock %d\n", ret);
return ret;
}
return ret;
}
static void da7219_clk_disable(void)
{
clk_disable_unprepare(da7219_dai_bclk);
}
static int cz_rt5682_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
dev_info(codec_dai->dev, "codec dai name = %s\n", codec_dai->name);
/* Set codec sysclk */
ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2,
RT5682_PLL_FREQ, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(codec_dai->dev,
"Failed to set rt5682 SYSCLK: %d\n", ret);
return ret;
}
/* set codec PLL */
ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK,
CZ_PLAT_CLK, RT5682_PLL_FREQ);
if (ret < 0) {
dev_err(codec_dai->dev, "can't set rt5682 PLL: %d\n", ret);
return ret;
}
rt5682_dai_wclk = devm_clk_get(component->dev, "rt5682-dai-wclk");
if (IS_ERR(rt5682_dai_wclk))
return PTR_ERR(rt5682_dai_wclk);
rt5682_dai_bclk = devm_clk_get(component->dev, "rt5682-dai-bclk");
if (IS_ERR(rt5682_dai_bclk))
return PTR_ERR(rt5682_dai_bclk);
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_LINEOUT |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3,
&cz_jack, NULL, 0);
if (ret) {
dev_err(card->dev, "HP jack creation failed %d\n", ret);
return ret;
}
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND);
ret = snd_soc_component_set_jack(component, &cz_jack, NULL);
if (ret) {
dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
return ret;
}
return 0;
}
static int rt5682_clk_enable(struct snd_pcm_substream *substream)
{
int ret;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
/*
* Set wclk to 48000 because the rate constraint of this driver is
* 48000. ADAU7002 spec: "The ADAU7002 requires a BCLK rate that is
* minimum of 64x the LRCLK sample rate." RT5682 is the only clk
* source so for all codecs we have to limit bclk to 64X lrclk.
*/
ret = clk_set_rate(rt5682_dai_wclk, 48000);
if (ret) {
dev_err(rtd->dev, "Error setting wclk rate: %d\n", ret);
return ret;
}
ret = clk_set_rate(rt5682_dai_bclk, 48000 * 64);
if (ret) {
dev_err(rtd->dev, "Error setting bclk rate: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(rt5682_dai_wclk);
if (ret < 0) {
dev_err(rtd->dev, "can't enable wclk %d\n", ret);
return ret;
}
return ret;
}
static void rt5682_clk_disable(void)
{
clk_disable_unprepare(rt5682_dai_wclk);
}
static const unsigned int channels[] = {
DUAL_CHANNEL,
};
static const unsigned int rates[] = {
48000,
};
static const struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static const struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int cz_da7219_play_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->play_i2s_instance = I2S_SP_INSTANCE;
return da7219_clk_enable(substream);
}
static int cz_da7219_cap_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->cap_i2s_instance = I2S_SP_INSTANCE;
machine->capture_channel = CAP_CHANNEL1;
return da7219_clk_enable(substream);
}
static int cz_max_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->play_i2s_instance = I2S_BT_INSTANCE;
return da7219_clk_enable(substream);
}
static int cz_dmic0_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->cap_i2s_instance = I2S_BT_INSTANCE;
return da7219_clk_enable(substream);
}
static int cz_dmic1_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->cap_i2s_instance = I2S_SP_INSTANCE;
machine->capture_channel = CAP_CHANNEL0;
return da7219_clk_enable(substream);
}
static void cz_da7219_shutdown(struct snd_pcm_substream *substream)
{
da7219_clk_disable();
}
static int cz_rt5682_play_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->play_i2s_instance = I2S_SP_INSTANCE;
return rt5682_clk_enable(substream);
}
static int cz_rt5682_cap_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->cap_i2s_instance = I2S_SP_INSTANCE;
machine->capture_channel = CAP_CHANNEL1;
return rt5682_clk_enable(substream);
}
static int cz_rt5682_max_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->play_i2s_instance = I2S_BT_INSTANCE;
return rt5682_clk_enable(substream);
}
static int cz_rt5682_dmic0_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->cap_i2s_instance = I2S_BT_INSTANCE;
return rt5682_clk_enable(substream);
}
static int cz_rt5682_dmic1_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_platform_info *machine = snd_soc_card_get_drvdata(card);
/*
* On this platform for PCM device we support stereo
*/
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
machine->cap_i2s_instance = I2S_SP_INSTANCE;
machine->capture_channel = CAP_CHANNEL0;
return rt5682_clk_enable(substream);
}
static void cz_rt5682_shutdown(struct snd_pcm_substream *substream)
{
rt5682_clk_disable();
}
static const struct snd_soc_ops cz_da7219_play_ops = {
.startup = cz_da7219_play_startup,
.shutdown = cz_da7219_shutdown,
};
static const struct snd_soc_ops cz_da7219_cap_ops = {
.startup = cz_da7219_cap_startup,
.shutdown = cz_da7219_shutdown,
};
static const struct snd_soc_ops cz_max_play_ops = {
.startup = cz_max_startup,
.shutdown = cz_da7219_shutdown,
};
static const struct snd_soc_ops cz_dmic0_cap_ops = {
.startup = cz_dmic0_startup,
.shutdown = cz_da7219_shutdown,
};
static const struct snd_soc_ops cz_dmic1_cap_ops = {
.startup = cz_dmic1_startup,
.shutdown = cz_da7219_shutdown,
};
static const struct snd_soc_ops cz_rt5682_play_ops = {
.startup = cz_rt5682_play_startup,
.shutdown = cz_rt5682_shutdown,
};
static const struct snd_soc_ops cz_rt5682_cap_ops = {
.startup = cz_rt5682_cap_startup,
.shutdown = cz_rt5682_shutdown,
};
static const struct snd_soc_ops cz_rt5682_max_play_ops = {
.startup = cz_rt5682_max_startup,
.shutdown = cz_rt5682_shutdown,
};
static const struct snd_soc_ops cz_rt5682_dmic0_cap_ops = {
.startup = cz_rt5682_dmic0_startup,
.shutdown = cz_rt5682_shutdown,
};
static const struct snd_soc_ops cz_rt5682_dmic1_cap_ops = {
.startup = cz_rt5682_dmic1_startup,
.shutdown = cz_rt5682_shutdown,
};
SND_SOC_DAILINK_DEF(designware1,
DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.1.auto")));
SND_SOC_DAILINK_DEF(designware2,
DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.2.auto")));
SND_SOC_DAILINK_DEF(designware3,
DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.3.auto")));
SND_SOC_DAILINK_DEF(dlgs,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7219:00", "da7219-hifi")));
SND_SOC_DAILINK_DEF(rt5682,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1")));
SND_SOC_DAILINK_DEF(mx,
DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", "HiFi")));
SND_SOC_DAILINK_DEF(adau,
DAILINK_COMP_ARRAY(COMP_CODEC("ADAU7002:00", "adau7002-hifi")));
SND_SOC_DAILINK_DEF(platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_audio_dma.0.auto")));
static struct snd_soc_dai_link cz_dai_7219_98357[] = {
{
.name = "amd-da7219-play",
.stream_name = "Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.init = cz_da7219_init,
.dpcm_playback = 1,
.stop_dma_first = 1,
.ops = &cz_da7219_play_ops,
SND_SOC_DAILINK_REG(designware1, dlgs, platform),
},
{
.name = "amd-da7219-cap",
.stream_name = "Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_capture = 1,
.stop_dma_first = 1,
.ops = &cz_da7219_cap_ops,
SND_SOC_DAILINK_REG(designware2, dlgs, platform),
},
{
.name = "amd-max98357-play",
.stream_name = "HiFi Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_playback = 1,
.stop_dma_first = 1,
.ops = &cz_max_play_ops,
SND_SOC_DAILINK_REG(designware3, mx, platform),
},
{
/* C panel DMIC */
.name = "dmic0",
.stream_name = "DMIC0 Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_capture = 1,
.stop_dma_first = 1,
.ops = &cz_dmic0_cap_ops,
SND_SOC_DAILINK_REG(designware3, adau, platform),
},
{
/* A/B panel DMIC */
.name = "dmic1",
.stream_name = "DMIC1 Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_capture = 1,
.stop_dma_first = 1,
.ops = &cz_dmic1_cap_ops,
SND_SOC_DAILINK_REG(designware2, adau, platform),
},
};
static struct snd_soc_dai_link cz_dai_5682_98357[] = {
{
.name = "amd-rt5682-play",
.stream_name = "Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.init = cz_rt5682_init,
.dpcm_playback = 1,
.stop_dma_first = 1,
.ops = &cz_rt5682_play_ops,
SND_SOC_DAILINK_REG(designware1, rt5682, platform),
},
{
.name = "amd-rt5682-cap",
.stream_name = "Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_capture = 1,
.stop_dma_first = 1,
.ops = &cz_rt5682_cap_ops,
SND_SOC_DAILINK_REG(designware2, rt5682, platform),
},
{
.name = "amd-max98357-play",
.stream_name = "HiFi Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_playback = 1,
.stop_dma_first = 1,
.ops = &cz_rt5682_max_play_ops,
SND_SOC_DAILINK_REG(designware3, mx, platform),
},
{
/* C panel DMIC */
.name = "dmic0",
.stream_name = "DMIC0 Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_capture = 1,
.stop_dma_first = 1,
.ops = &cz_rt5682_dmic0_cap_ops,
SND_SOC_DAILINK_REG(designware3, adau, platform),
},
{
/* A/B panel DMIC */
.name = "dmic1",
.stream_name = "DMIC1 Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.dpcm_capture = 1,
.stop_dma_first = 1,
.ops = &cz_rt5682_dmic1_cap_ops,
SND_SOC_DAILINK_REG(designware2, adau, platform),
},
};
static const struct snd_soc_dapm_widget cz_widgets[] = {
SND_SOC_DAPM_HP("Headphones", NULL),
SND_SOC_DAPM_SPK("Speakers", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
};
static const struct snd_soc_dapm_route cz_audio_route[] = {
{"Headphones", NULL, "HPL"},
{"Headphones", NULL, "HPR"},
{"MIC", NULL, "Headset Mic"},
{"Speakers", NULL, "Speaker"},
{"PDM_DAT", NULL, "Int Mic"},
};
static const struct snd_soc_dapm_route cz_rt5682_audio_route[] = {
{"Headphones", NULL, "HPOL"},
{"Headphones", NULL, "HPOR"},
{"IN1P", NULL, "Headset Mic"},
{"Speakers", NULL, "Speaker"},
{"PDM_DAT", NULL, "Int Mic"},
};
static const struct snd_kcontrol_new cz_mc_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphones"),
SOC_DAPM_PIN_SWITCH("Speakers"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
};
static struct snd_soc_card cz_card = {
.name = "acpd7219m98357",
.owner = THIS_MODULE,
.dai_link = cz_dai_7219_98357,
.num_links = ARRAY_SIZE(cz_dai_7219_98357),
.dapm_widgets = cz_widgets,
.num_dapm_widgets = ARRAY_SIZE(cz_widgets),
.dapm_routes = cz_audio_route,
.num_dapm_routes = ARRAY_SIZE(cz_audio_route),
.controls = cz_mc_controls,
.num_controls = ARRAY_SIZE(cz_mc_controls),
};
static struct snd_soc_card cz_rt5682_card = {
.name = "acpr5682m98357",
.owner = THIS_MODULE,
.dai_link = cz_dai_5682_98357,
.num_links = ARRAY_SIZE(cz_dai_5682_98357),
.dapm_widgets = cz_widgets,
.num_dapm_widgets = ARRAY_SIZE(cz_widgets),
.dapm_routes = cz_rt5682_audio_route,
.controls = cz_mc_controls,
.num_controls = ARRAY_SIZE(cz_mc_controls),
};
void *acp_soc_is_rltk_max(struct device *dev)
{
const struct acpi_device_id *match;
match = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!match)
return NULL;
return (void *)match->driver_data;
}
static struct regulator_consumer_supply acp_da7219_supplies[] = {
REGULATOR_SUPPLY("VDD", "i2c-DLGS7219:00"),
REGULATOR_SUPPLY("VDDMIC", "i2c-DLGS7219:00"),
REGULATOR_SUPPLY("VDDIO", "i2c-DLGS7219:00"),
REGULATOR_SUPPLY("IOVDD", "ADAU7002:00"),
};
static struct regulator_init_data acp_da7219_data = {
.constraints = {
.always_on = 1,
},
.num_consumer_supplies = ARRAY_SIZE(acp_da7219_supplies),
.consumer_supplies = acp_da7219_supplies,
};
static struct regulator_config acp_da7219_cfg = {
.init_data = &acp_da7219_data,
};
static struct regulator_ops acp_da7219_ops = {
};
static const struct regulator_desc acp_da7219_desc = {
.name = "reg-fixed-1.8V",
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
.ops = &acp_da7219_ops,
.fixed_uV = 1800000, /* 1.8V */
.n_voltages = 1,
};
static int cz_probe(struct platform_device *pdev)
{
int ret;
struct snd_soc_card *card;
struct acp_platform_info *machine;
struct regulator_dev *rdev;
struct device *dev = &pdev->dev;
card = (struct snd_soc_card *)acp_soc_is_rltk_max(dev);
if (!card)
return -ENODEV;
if (!strcmp(card->name, "acpd7219m98357")) {
acp_da7219_cfg.dev = &pdev->dev;
rdev = devm_regulator_register(&pdev->dev, &acp_da7219_desc,
&acp_da7219_cfg);
if (IS_ERR(rdev)) {
dev_err(&pdev->dev, "Failed to register regulator: %d\n",
(int)PTR_ERR(rdev));
return -EINVAL;
}
}
machine = devm_kzalloc(&pdev->dev, sizeof(struct acp_platform_info),
GFP_KERNEL);
if (!machine)
return -ENOMEM;
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"devm_snd_soc_register_card(%s) failed\n",
card->name);
}
acp_bt_uart_enable = !device_property_read_bool(&pdev->dev,
"bt-pad-enable");
return 0;
}
#ifdef CONFIG_ACPI
static const struct acpi_device_id cz_audio_acpi_match[] = {
{ "AMD7219", (unsigned long)&cz_card },
{ "AMDI5682", (unsigned long)&cz_rt5682_card},
{},
};
MODULE_DEVICE_TABLE(acpi, cz_audio_acpi_match);
#endif
static struct platform_driver cz_pcm_driver = {
.driver = {
.name = "cz-da7219-max98357a",
.acpi_match_table = ACPI_PTR(cz_audio_acpi_match),
.pm = &snd_soc_pm_ops,
},
.probe = cz_probe,
};
module_platform_driver(cz_pcm_driver);
MODULE_AUTHOR("akshu.agrawal@amd.com");
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("DA7219, RT5682 & MAX98357A audio support");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
//
/*
* Generic Hardware interface for ACP Audio I2S controller
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/dma-mapping.h>
#include "amd.h"
#define DRV_NAME "acp_i2s_playcap"
static int acp_i2s_hwparams(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct device *dev = dai->component->dev;
struct acp_dev_data *adata;
u32 val;
u32 xfer_resolution;
u32 reg_val;
adata = snd_soc_dai_get_drvdata(dai);
/* These values are as per Hardware Spec */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U8:
case SNDRV_PCM_FORMAT_S8:
xfer_resolution = 0x0;
break;
case SNDRV_PCM_FORMAT_S16_LE:
xfer_resolution = 0x02;
break;
case SNDRV_PCM_FORMAT_S24_LE:
xfer_resolution = 0x04;
break;
case SNDRV_PCM_FORMAT_S32_LE:
xfer_resolution = 0x05;
break;
default:
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (dai->driver->id) {
case I2S_BT_INSTANCE:
reg_val = ACP_BTTDM_ITER;
break;
case I2S_SP_INSTANCE:
reg_val = ACP_I2STDM_ITER;
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
} else {
switch (dai->driver->id) {
case I2S_BT_INSTANCE:
reg_val = ACP_BTTDM_IRER;
break;
case I2S_SP_INSTANCE:
reg_val = ACP_I2STDM_IRER;
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
}
val = readl(adata->acp_base + reg_val);
val &= ~ACP3x_ITER_IRER_SAMP_LEN_MASK;
val = val | (xfer_resolution << 3);
writel(val, adata->acp_base + reg_val);
return 0;
}
static int acp_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
struct acp_stream *stream = substream->runtime->private_data;
struct device *dev = dai->component->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
u32 val, period_bytes, reg_val, ier_val, water_val, buf_size, buf_reg;
period_bytes = frames_to_bytes(substream->runtime, substream->runtime->period_size);
buf_size = frames_to_bytes(substream->runtime, substream->runtime->buffer_size);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
stream->bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (dai->driver->id) {
case I2S_BT_INSTANCE:
water_val = ACP_BT_TX_INTR_WATERMARK_SIZE;
reg_val = ACP_BTTDM_ITER;
ier_val = ACP_BTTDM_IER;
buf_reg = ACP_BT_TX_RINGBUFSIZE;
break;
case I2S_SP_INSTANCE:
water_val = ACP_I2S_TX_INTR_WATERMARK_SIZE;
reg_val = ACP_I2STDM_ITER;
ier_val = ACP_I2STDM_IER;
buf_reg = ACP_I2S_TX_RINGBUFSIZE;
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
} else {
switch (dai->driver->id) {
case I2S_BT_INSTANCE:
water_val = ACP_BT_RX_INTR_WATERMARK_SIZE;
reg_val = ACP_BTTDM_IRER;
ier_val = ACP_BTTDM_IER;
buf_reg = ACP_BT_RX_RINGBUFSIZE;
break;
case I2S_SP_INSTANCE:
water_val = ACP_I2S_RX_INTR_WATERMARK_SIZE;
reg_val = ACP_I2STDM_IRER;
ier_val = ACP_I2STDM_IER;
buf_reg = ACP_I2S_RX_RINGBUFSIZE;
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
}
writel(period_bytes, adata->acp_base + water_val);
writel(buf_size, adata->acp_base + buf_reg);
val = readl(adata->acp_base + reg_val);
val = val | BIT(0);
writel(val, adata->acp_base + reg_val);
writel(1, adata->acp_base + ier_val);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (dai->driver->id) {
case I2S_BT_INSTANCE:
reg_val = ACP_BTTDM_ITER;
break;
case I2S_SP_INSTANCE:
reg_val = ACP_I2STDM_ITER;
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
} else {
switch (dai->driver->id) {
case I2S_BT_INSTANCE:
reg_val = ACP_BTTDM_IRER;
break;
case I2S_SP_INSTANCE:
reg_val = ACP_I2STDM_IRER;
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
}
val = readl(adata->acp_base + reg_val);
val = val & ~BIT(0);
writel(val, adata->acp_base + reg_val);
if (!(readl(adata->acp_base + ACP_BTTDM_ITER) & BIT(0)) &&
!(readl(adata->acp_base + ACP_BTTDM_IRER) & BIT(0)))
writel(0, adata->acp_base + ACP_BTTDM_IER);
if (!(readl(adata->acp_base + ACP_I2STDM_ITER) & BIT(0)) &&
!(readl(adata->acp_base + ACP_I2STDM_IRER) & BIT(0)))
writel(0, adata->acp_base + ACP_I2STDM_IER);
return 0;
default:
return -EINVAL;
}
return 0;
}
static int acp_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct device *dev = dai->component->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
struct acp_stream *stream = substream->runtime->private_data;
u32 reg_dma_size = 0, reg_fifo_size = 0, reg_fifo_addr = 0;
u32 phy_addr = 0, acp_fifo_addr = 0, ext_int_ctrl;
unsigned int dir = substream->stream;
switch (dai->driver->id) {
case I2S_SP_INSTANCE:
if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
reg_dma_size = ACP_I2S_TX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
SP_PB_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_I2S_TX_FIFOADDR;
reg_fifo_size = ACP_I2S_TX_FIFOSIZE;
phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset;
writel(phy_addr, adata->acp_base + ACP_I2S_TX_RINGBUFADDR);
} else {
reg_dma_size = ACP_I2S_RX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
SP_CAPT_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_I2S_RX_FIFOADDR;
reg_fifo_size = ACP_I2S_RX_FIFOSIZE;
phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset;
writel(phy_addr, adata->acp_base + ACP_I2S_RX_RINGBUFADDR);
}
break;
case I2S_BT_INSTANCE:
if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
reg_dma_size = ACP_BT_TX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
BT_PB_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_BT_TX_FIFOADDR;
reg_fifo_size = ACP_BT_TX_FIFOSIZE;
phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset;
writel(phy_addr, adata->acp_base + ACP_BT_TX_RINGBUFADDR);
} else {
reg_dma_size = ACP_BT_RX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
BT_CAPT_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_BT_RX_FIFOADDR;
reg_fifo_size = ACP_BT_RX_FIFOSIZE;
phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset;
writel(phy_addr, adata->acp_base + ACP_BT_RX_RINGBUFADDR);
}
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
writel(DMA_SIZE, adata->acp_base + reg_dma_size);
writel(acp_fifo_addr, adata->acp_base + reg_fifo_addr);
writel(FIFO_SIZE, adata->acp_base + reg_fifo_size);
ext_int_ctrl = readl(adata->acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl |= BIT(I2S_RX_THRESHOLD) | BIT(BT_RX_THRESHOLD)
| BIT(I2S_TX_THRESHOLD) | BIT(BT_TX_THRESHOLD);
writel(ext_int_ctrl, adata->acp_base + ACP_EXTERNAL_INTR_CNTL);
return 0;
}
static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct acp_stream *stream = substream->runtime->private_data;
struct device *dev = dai->component->dev;
unsigned int dir = substream->stream;
unsigned int irq_bit = 0;
switch (dai->driver->id) {
case I2S_SP_INSTANCE:
if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
irq_bit = BIT(I2S_TX_THRESHOLD);
stream->pte_offset = ACP_SRAM_SP_PB_PTE_OFFSET;
stream->fifo_offset = SP_PB_FIFO_ADDR_OFFSET;
} else {
irq_bit = BIT(I2S_RX_THRESHOLD);
stream->pte_offset = ACP_SRAM_SP_CP_PTE_OFFSET;
stream->fifo_offset = SP_CAPT_FIFO_ADDR_OFFSET;
}
break;
case I2S_BT_INSTANCE:
if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
irq_bit = BIT(BT_TX_THRESHOLD);
stream->pte_offset = ACP_SRAM_BT_PB_PTE_OFFSET;
stream->fifo_offset = BT_PB_FIFO_ADDR_OFFSET;
} else {
irq_bit = BIT(BT_RX_THRESHOLD);
stream->pte_offset = ACP_SRAM_BT_CP_PTE_OFFSET;
stream->fifo_offset = BT_CAPT_FIFO_ADDR_OFFSET;
}
break;
default:
dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
return -EINVAL;
}
/* Save runtime dai configuration in stream */
stream->id = dai->driver->id + dir;
stream->dai_id = dai->driver->id;
stream->irq_bit = irq_bit;
return 0;
}
const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = {
.startup = acp_i2s_startup,
.hw_params = acp_i2s_hwparams,
.prepare = acp_i2s_prepare,
.trigger = acp_i2s_trigger,
};
EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, SND_SOC_ACP_COMMON);
int asoc_acp_i2s_probe(struct snd_soc_dai *dai)
{
struct device *dev = dai->component->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
unsigned int val;
if (!adata->acp_base) {
dev_err(dev, "I2S base is NULL\n");
return -EINVAL;
}
val = readl(adata->acp_base + ACP_I2S_PIN_CONFIG);
if (val != I2S_MODE) {
dev_err(dev, "I2S Mode not supported val %x\n", val);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(asoc_acp_i2s_probe, SND_SOC_ACP_COMMON);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS(DRV_NAME);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Balakishore Pati <Balakishore.pati@amd.com>
// Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
/* ACP-specific SOF IPC code */
#include <linux/module.h>
#include "../ops.h"
#include "acp.h"
#include "acp-dsp-offset.h"
void acp_mailbox_write(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes)
{
memcpy_to_scratch(sdev, offset, message, bytes);
}
EXPORT_SYMBOL_NS(acp_mailbox_write, SND_SOC_SOF_AMD_COMMON);
void acp_mailbox_read(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes)
{
memcpy_from_scratch(sdev, offset, message, bytes);
}
EXPORT_SYMBOL_NS(acp_mailbox_read, SND_SOC_SOF_AMD_COMMON);
static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata)
{
struct snd_sof_dev *sdev = adata->dev;
u32 swintr_trigger;
swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW_INTR_TRIG);
swintr_trigger |= 0x01;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SW_INTR_TRIG, swintr_trigger);
}
static void acp_ipc_host_msg_set(struct snd_sof_dev *sdev)
{
unsigned int host_msg = offsetof(struct scratch_ipc_conf, sof_host_msg_write);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + host_msg, 1);
}
static void acp_dsp_ipc_host_done(struct snd_sof_dev *sdev)
{
unsigned int dsp_msg = offsetof(struct scratch_ipc_conf, sof_dsp_msg_write);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg, 0);
}
static void acp_dsp_ipc_dsp_done(struct snd_sof_dev *sdev)
{
unsigned int dsp_ack = offsetof(struct scratch_ipc_conf, sof_dsp_ack_write);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack, 0);
}
int acp_sof_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
struct acp_dev_data *adata = sdev->pdata->hw_pdata;
unsigned int offset = offsetof(struct scratch_ipc_conf, sof_in_box);
acp_mailbox_write(sdev, offset, msg->msg_data, msg->msg_size);
acp_ipc_host_msg_set(sdev);
/* Trigger host to dsp interrupt for the msg */
acpbus_trigger_host_to_dsp_swintr(adata);
return 0;
}
EXPORT_SYMBOL_NS(acp_sof_ipc_send_msg, SND_SOC_SOF_AMD_COMMON);
static void acp_dsp_ipc_get_reply(struct snd_sof_dev *sdev)
{
struct snd_sof_ipc_msg *msg = sdev->msg;
struct sof_ipc_reply reply;
struct sof_ipc_cmd_hdr *hdr;
unsigned int offset = offsetof(struct scratch_ipc_conf, sof_in_box);
int ret = 0;
/*
* Sometimes, there is unexpected reply ipc arriving. The reply
* ipc belongs to none of the ipcs sent from driver.
* In this case, the driver must ignore the ipc.
*/
if (!msg) {
dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n");
return;
}
hdr = msg->msg_data;
if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) ||
hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) {
/*
* memory windows are powered off before sending IPC reply,
* so we can't read the mailbox for CTX_SAVE and PM_GATE
* replies.
*/
reply.error = 0;
reply.hdr.cmd = SOF_IPC_GLB_REPLY;
reply.hdr.size = sizeof(reply);
memcpy(msg->reply_data, &reply, sizeof(reply));
goto out;
}
/* get IPC reply from DSP in the mailbox */
acp_mailbox_read(sdev, offset, &reply, sizeof(reply));
if (reply.error < 0) {
memcpy(msg->reply_data, &reply, sizeof(reply));
ret = reply.error;
} else {
/* reply correct size ? */
if (reply.hdr.size != msg->reply_size &&
!(reply.hdr.cmd & SOF_IPC_GLB_PROBE)) {
dev_err(sdev->dev, "reply expected %zu got %u bytes\n",
msg->reply_size, reply.hdr.size);
ret = -EINVAL;
}
/* read the message */
if (msg->reply_size > 0)
acp_mailbox_read(sdev, offset, msg->reply_data, msg->reply_size);
}
out:
msg->reply_error = ret;
}
irqreturn_t acp_sof_ipc_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
unsigned int dsp_msg_write = offsetof(struct scratch_ipc_conf, sof_dsp_msg_write);
unsigned int dsp_ack_write = offsetof(struct scratch_ipc_conf, sof_dsp_ack_write);
bool ipc_irq = false;
int dsp_msg, dsp_ack;
dsp_msg = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg_write);
if (dsp_msg) {
snd_sof_ipc_msgs_rx(sdev);
acp_dsp_ipc_host_done(sdev);
ipc_irq = true;
}
dsp_ack = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack_write);
if (dsp_ack) {
spin_lock_irq(&sdev->ipc_lock);
/* handle immediate reply from DSP core */
acp_dsp_ipc_get_reply(sdev);
snd_sof_ipc_reply(sdev, 0);
/* set the done bit */
acp_dsp_ipc_dsp_done(sdev);
spin_unlock_irq(&sdev->ipc_lock);
ipc_irq = true;
}
if (!ipc_irq)
dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
return IRQ_HANDLED;
}
EXPORT_SYMBOL_NS(acp_sof_ipc_irq_thread, SND_SOC_SOF_AMD_COMMON);
int acp_sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
void *p, size_t sz)
{
unsigned int offset = offsetof(struct scratch_ipc_conf, sof_out_box);
if (!substream || !sdev->stream_box.size)
acp_mailbox_read(sdev, offset, p, sz);
return 0;
}
EXPORT_SYMBOL_NS(acp_sof_ipc_msg_data, SND_SOC_SOF_AMD_COMMON);
int acp_sof_ipc_pcm_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
const struct sof_ipc_pcm_params_reply *reply)
{
/* TODO: Implement stream hw params to validate stream offset */
return 0;
}
EXPORT_SYMBOL_NS(acp_sof_ipc_pcm_params, SND_SOC_SOF_AMD_COMMON);
int acp_sof_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return ACP_SCRATCH_MEMORY_ADDRESS;
}
EXPORT_SYMBOL_NS(acp_sof_ipc_get_mailbox_offset, SND_SOC_SOF_AMD_COMMON);
MODULE_DESCRIPTION("AMD ACP sof-ipc driver");
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
//
/*
* Machine Driver Legacy Support for ACP HW block
*/
#include <sound/core.h>
#include <sound/pcm_params.h>
#include <sound/soc-acpi.h>
#include <sound/soc-dapm.h>
#include <linux/module.h>
#include "acp-mach.h"
static struct acp_card_drvdata rt5682_rt1019_data = {
.hs_cpu_id = I2S_SP,
.amp_cpu_id = I2S_SP,
.dmic_cpu_id = NONE,
.hs_codec_id = RT5682,
.amp_codec_id = RT1019,
.dmic_codec_id = NONE,
.gpio_spkr_en = EN_SPKR_GPIO_GB,
};
static const struct snd_kcontrol_new acp_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Spk"),
SOC_DAPM_PIN_SWITCH("Left Spk"),
SOC_DAPM_PIN_SWITCH("Right Spk"),
};
static const struct snd_soc_dapm_widget acp_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Spk", event_spkr_handler),
SND_SOC_DAPM_SPK("Left Spk", event_spkr_handler),
SND_SOC_DAPM_SPK("Right Spk", event_spkr_handler),
};
static int acp_asoc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = NULL;
struct device *dev = &pdev->dev;
unsigned int spkr_gpio;
int ret;
if (!pdev->id_entry)
return -EINVAL;
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card)
return -ENOMEM;
card->dev = dev;
card->owner = THIS_MODULE;
card->name = pdev->id_entry->name;
card->dapm_widgets = acp_widgets;
card->num_dapm_widgets = ARRAY_SIZE(acp_widgets);
card->controls = acp_controls;
card->num_controls = ARRAY_SIZE(acp_controls);
card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data;
spkr_gpio = ((struct acp_card_drvdata *)(card->drvdata))->gpio_spkr_en;
acp_legacy_dai_links_create(card);
if (gpio_is_valid(spkr_gpio)) {
ret = devm_gpio_request(dev, spkr_gpio, "spkren");
if (ret) {
dev_err(dev, "(%s) gpio request failed: %d\n",
__func__, ret);
return ret;
}
gpio_direction_output(spkr_gpio, 0);
}
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev,
"devm_snd_soc_register_card(%s) failed: %d\n",
card->name, ret);
return ret;
}
return 0;
}
static const struct platform_device_id board_ids[] = {
{
.name = "rn_rt5682_rt1019",
.driver_data = (kernel_ulong_t)&rt5682_rt1019_data,
},
{ }
};
static struct platform_driver acp_asoc_audio = {
.driver = {
.name = "acp_mach",
},
.probe = acp_asoc_probe,
.id_table = board_ids,
};
module_platform_driver(acp_asoc_audio);
MODULE_IMPORT_NS(SND_SOC_AMD_MACH);
MODULE_DESCRIPTION("ACP chrome audio support");
MODULE_ALIAS("platform:rn_rt5682_rt1019");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
/*
* Hardware interface for ACP DSP Firmware binaries loader
*/
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "../ops.h"
#include "acp-dsp-offset.h"
#include "acp.h"
#define FW_BIN 0
#define FW_DATA_BIN 1
#define FW_BIN_PTE_OFFSET 0x00
#define FW_DATA_BIN_PTE_OFFSET 0x08
#define ACP_DSP_RUN 0x00
int acp_dsp_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
u32 offset, void *dest, size_t size)
{
switch (blk_type) {
case SOF_FW_BLK_TYPE_SRAM:
offset = offset - ACP_SCRATCH_MEMORY_ADDRESS;
memcpy_from_scratch(sdev, offset, dest, size);
break;
default:
dev_err(sdev->dev, "bad blk type 0x%x\n", blk_type);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_NS(acp_dsp_block_read, SND_SOC_SOF_AMD_COMMON);
int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
u32 offset, void *src, size_t size)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
struct pci_dev *pci = to_pci_dev(sdev->dev);
struct acp_dev_data *adata;
void *dest;
u32 dma_size, page_count;
unsigned int size_fw;
adata = sdev->pdata->hw_pdata;
switch (blk_type) {
case SOF_FW_BLK_TYPE_IRAM:
if (!adata->bin_buf) {
size_fw = plat_data->fw->size;
page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT;
dma_size = page_count * ACP_PAGE_SIZE;
adata->bin_buf = dma_alloc_coherent(&pci->dev, dma_size,
&adata->sha_dma_addr,
GFP_ATOMIC);
if (!adata->bin_buf)
return -ENOMEM;
}
adata->fw_bin_size = size + offset;
dest = adata->bin_buf + offset;
break;
case SOF_FW_BLK_TYPE_DRAM:
if (!adata->data_buf) {
adata->data_buf = dma_alloc_coherent(&pci->dev,
ACP_DEFAULT_DRAM_LENGTH,
&adata->dma_addr,
GFP_ATOMIC);
if (!adata->data_buf)
return -ENOMEM;
}
dest = adata->data_buf + offset;
adata->fw_data_bin_size = size + offset;
break;
case SOF_FW_BLK_TYPE_SRAM:
offset = offset - ACP_SCRATCH_MEMORY_ADDRESS;
memcpy_to_scratch(sdev, offset, src, size);
return 0;
default:
dev_err(sdev->dev, "bad blk type 0x%x\n", blk_type);
return -EINVAL;
}
memcpy(dest, src, size);
return 0;
}
EXPORT_SYMBOL_NS(acp_dsp_block_write, SND_SOC_SOF_AMD_COMMON);
int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type)
{
return type;
}
EXPORT_SYMBOL_NS(acp_get_bar_index, SND_SOC_SOF_AMD_COMMON);
static void configure_pte_for_fw_loading(int type, int num_pages, struct acp_dev_data *adata)
{
struct snd_sof_dev *sdev;
unsigned int low, high;
dma_addr_t addr;
u16 page_idx;
u32 offset;
sdev = adata->dev;
switch (type) {
case FW_BIN:
offset = FW_BIN_PTE_OFFSET;
addr = adata->sha_dma_addr;
break;
case FW_DATA_BIN:
offset = adata->fw_bin_page_count * 8;
addr = adata->dma_addr;
break;
default:
dev_err(sdev->dev, "Invalid data type %x\n", type);
return;
}
for (page_idx = 0; page_idx < num_pages; page_idx++) {
low = lower_32_bits(addr);
high = upper_32_bits(addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset, low);
high |= BIT(31);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset + 4, high);
offset += 8;
addr += PAGE_SIZE;
}
}
/* pre fw run operations */
int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev)
{
struct pci_dev *pci = to_pci_dev(sdev->dev);
struct snd_sof_pdata *plat_data = sdev->pdata;
struct acp_dev_data *adata;
unsigned int src_addr, size_fw;
u32 page_count, dma_size;
int ret;
adata = sdev->pdata->hw_pdata;
size_fw = adata->fw_bin_size;
page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT;
adata->fw_bin_page_count = page_count;
configure_pte_for_fw_loading(FW_BIN, page_count, adata);
ret = configure_and_run_sha_dma(adata, adata->bin_buf, ACP_SYSTEM_MEMORY_WINDOW,
ACP_IRAM_BASE_ADDRESS, size_fw);
if (ret < 0) {
dev_err(sdev->dev, "SHA DMA transfer failed status: %d\n", ret);
return ret;
}
configure_pte_for_fw_loading(FW_DATA_BIN, ACP_DRAM_PAGE_COUNT, adata);
src_addr = ACP_SYSTEM_MEMORY_WINDOW + page_count * ACP_PAGE_SIZE;
ret = configure_and_run_dma(adata, src_addr, ACP_DATA_RAM_BASE_ADDRESS,
adata->fw_data_bin_size);
if (ret < 0) {
dev_err(sdev->dev, "acp dma configuration failed: %d\n", ret);
return ret;
}
ret = acp_dma_status(adata, 0);
if (ret < 0)
dev_err(sdev->dev, "acp dma transfer status: %d\n", ret);
/* Free memory once DMA is complete */
dma_size = (PAGE_ALIGN(plat_data->fw->size) >> PAGE_SHIFT) * ACP_PAGE_SIZE;
dma_free_coherent(&pci->dev, dma_size, adata->bin_buf, adata->sha_dma_addr);
dma_free_coherent(&pci->dev, ACP_DEFAULT_DRAM_LENGTH, adata->data_buf, adata->dma_addr);
adata->bin_buf = NULL;
adata->data_buf = NULL;
return ret;
}
EXPORT_SYMBOL_NS(acp_dsp_pre_fw_run, SND_SOC_SOF_AMD_COMMON);
int acp_sof_dsp_run(struct snd_sof_dev *sdev)
{
int val;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_RUNSTALL, ACP_DSP_RUN);
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DSP0_RUNSTALL);
dev_dbg(sdev->dev, "ACP_DSP0_RUNSTALL : 0x%0x\n", val);
return 0;
}
EXPORT_SYMBOL_NS(acp_sof_dsp_run, SND_SOC_SOF_AMD_COMMON);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
// Vijendar Mukunda <Vijendar.Mukunda@amd.com>
//
/*
* Machine Driver Interface for ACP HW block
*/
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <sound/soc.h>
#include <linux/input.h>
#include <linux/module.h>
#include "../../codecs/rt5682.h"
#include "../../codecs/rt1019.h"
#include "../../codecs/rt5682s.h"
#include "acp-mach.h"
#define PCO_PLAT_CLK 48000000
#define RT5682_PLL_FREQ (48000 * 512)
#define DUAL_CHANNEL 2
#define FOUR_CHANNEL 4
static struct snd_soc_jack pco_jack;
static const unsigned int channels[] = {
DUAL_CHANNEL,
};
static const unsigned int rates[] = {
48000,
};
static const struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static const struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int acp_clk_enable(struct acp_card_drvdata *drvdata)
{
clk_set_rate(drvdata->wclk, 48000);
clk_set_rate(drvdata->bclk, 48000 * 64);
return clk_prepare_enable(drvdata->wclk);
}
/* Declare RT5682 codec components */
SND_SOC_DAILINK_DEF(rt5682,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1")));
static const struct snd_soc_dapm_route rt5682_map[] = {
{ "Headphone Jack", NULL, "HPOL" },
{ "Headphone Jack", NULL, "HPOR" },
{ "IN1P", NULL, "Headset Mic" },
};
int event_spkr_handler(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_soc_card *card = dapm->card;
struct acp_card_drvdata *drvdata = snd_soc_card_get_drvdata(card);
if (!gpio_is_valid(drvdata->gpio_spkr_en))
return 0;
switch (event) {
case SND_SOC_DAPM_POST_PMU:
gpio_set_value(drvdata->gpio_spkr_en, 1);
break;
case SND_SOC_DAPM_PRE_PMD:
gpio_set_value(drvdata->gpio_spkr_en, 0);
break;
default:
dev_warn(card->dev, "%s invalid setting\n", __func__);
break;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(event_spkr_handler, SND_SOC_AMD_MACH);
/* Define card ops for RT5682 CODEC */
static int acp_card_rt5682_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
int ret;
dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name);
if (drvdata->hs_codec_id != RT5682)
return -EINVAL;
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP);
if (ret < 0) {
dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK,
PCO_PLAT_CLK, RT5682_PLL_FREQ);
if (ret < 0) {
dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2,
RT5682_PLL_FREQ, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret);
return ret;
}
/* Set tdm/i2s1 master bclk ratio */
ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
if (ret < 0) {
dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret);
return ret;
}
drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk");
drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk");
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_LINEOUT |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3,
&pco_jack, NULL, 0);
if (ret) {
dev_err(card->dev, "HP jack creation failed %d\n", ret);
return ret;
}
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
ret = snd_soc_component_set_jack(component, &pco_jack, NULL);
if (ret) {
dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
return ret;
}
return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682_map, ARRAY_SIZE(rt5682_map));
}
static int acp_card_hs_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
int ret;
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP);
if (ret < 0) {
dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret);
return ret;
}
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
ret = acp_clk_enable(drvdata);
if (ret < 0)
dev_err(rtd->card->dev, "Failed to enable HS clk: %d\n", ret);
return ret;
}
static void acp_card_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
clk_disable_unprepare(drvdata->wclk);
}
static const struct snd_soc_ops acp_card_rt5682_ops = {
.startup = acp_card_hs_startup,
.shutdown = acp_card_shutdown,
};
/* Define RT5682S CODEC component*/
SND_SOC_DAILINK_DEF(rt5682s,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RTL5682:00", "rt5682s-aif1")));
static const struct snd_soc_dapm_route rt5682s_map[] = {
{ "Headphone Jack", NULL, "HPOL" },
{ "Headphone Jack", NULL, "HPOR" },
{ "IN1P", NULL, "Headset Mic" },
};
static int acp_card_rt5682s_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
int ret;
dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name);
if (drvdata->hs_codec_id != RT5682S)
return -EINVAL;
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP);
if (ret < 0) {
dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, RT5682S_PLL2, RT5682S_PLL_S_MCLK,
PCO_PLAT_CLK, RT5682_PLL_FREQ);
if (ret < 0) {
dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, RT5682S_SCLK_S_PLL2,
RT5682_PLL_FREQ, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret);
return ret;
}
/* Set tdm/i2s1 master bclk ratio */
ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
if (ret < 0) {
dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret);
return ret;
}
drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk");
drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk");
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_LINEOUT |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3,
&pco_jack, NULL, 0);
if (ret) {
dev_err(card->dev, "HP jack creation failed %d\n", ret);
return ret;
}
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
ret = snd_soc_component_set_jack(component, &pco_jack, NULL);
if (ret) {
dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
return ret;
}
return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682s_map, ARRAY_SIZE(rt5682s_map));
}
static const struct snd_soc_ops acp_card_rt5682s_ops = {
.startup = acp_card_hs_startup,
.shutdown = acp_card_shutdown,
};
/* Declare RT1019 codec components */
SND_SOC_DAILINK_DEF(rt1019,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC1019:00", "rt1019-aif"),
COMP_CODEC("i2c-10EC1019:01", "rt1019-aif")));
static const struct snd_soc_dapm_route rt1019_map_lr[] = {
{ "Left Spk", NULL, "Left SPO" },
{ "Right Spk", NULL, "Right SPO" },
};
static struct snd_soc_codec_conf rt1019_conf[] = {
{
.dlc = COMP_CODEC_CONF("i2c-10EC1019:01"),
.name_prefix = "Left",
},
{
.dlc = COMP_CODEC_CONF("i2c-10EC1019:00"),
.name_prefix = "Right",
},
};
static int acp_card_rt1019_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
if (drvdata->amp_codec_id != RT1019)
return -EINVAL;
return snd_soc_dapm_add_routes(&rtd->card->dapm, rt1019_map_lr,
ARRAY_SIZE(rt1019_map_lr));
}
static int acp_card_rt1019_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
struct snd_soc_dai *codec_dai;
int srate, i, ret = 0;
srate = params_rate(params);
if (drvdata->amp_codec_id != RT1019)
return -EINVAL;
for_each_rtd_codec_dais(rtd, i, codec_dai) {
if (strcmp(codec_dai->name, "rt1019-aif"))
continue;
ret = snd_soc_dai_set_pll(codec_dai, 0, RT1019_PLL_S_BCLK,
64 * srate, 256 * srate);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, RT1019_SCLK_S_PLL,
256 * srate, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
}
return 0;
}
static int acp_card_amp_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
int ret;
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
ret = acp_clk_enable(drvdata);
if (ret < 0)
dev_err(rtd->card->dev, "Failed to enable AMP clk: %d\n", ret);
return ret;
}
static const struct snd_soc_ops acp_card_rt1019_ops = {
.startup = acp_card_amp_startup,
.shutdown = acp_card_shutdown,
.hw_params = acp_card_rt1019_hw_params,
};
/* Declare Maxim codec components */
SND_SOC_DAILINK_DEF(max98360a,
DAILINK_COMP_ARRAY(COMP_CODEC("MX98360A:00", "HiFi")));
static const struct snd_soc_dapm_route max98360a_map[] = {
{"Spk", NULL, "Speaker"},
};
static int acp_card_maxim_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct acp_card_drvdata *drvdata = card->drvdata;
if (drvdata->amp_codec_id != MAX98360A)
return -EINVAL;
return snd_soc_dapm_add_routes(&rtd->card->dapm, max98360a_map,
ARRAY_SIZE(max98360a_map));
}
static const struct snd_soc_ops acp_card_maxim_ops = {
.startup = acp_card_amp_startup,
.shutdown = acp_card_shutdown,
};
/* Declare DMIC codec components */
SND_SOC_DAILINK_DEF(dmic_codec,
DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
/* Declare ACP CPU components */
static struct snd_soc_dai_link_component dummy_codec[] = {
{
.name = "snd-soc-dummy",
.dai_name = "snd-soc-dummy-dai",
}
};
static struct snd_soc_dai_link_component platform_component[] = {
{
.name = "acp_asoc_renoir.0",
}
};
static struct snd_soc_dai_link_component sof_component[] = {
{
.name = "0000:04:00.5",
}
};
SND_SOC_DAILINK_DEF(i2s_sp,
DAILINK_COMP_ARRAY(COMP_CPU("acp-i2s-sp")));
SND_SOC_DAILINK_DEF(sof_sp,
DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-sp")));
SND_SOC_DAILINK_DEF(sof_dmic,
DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-dmic")));
int acp_sofdsp_dai_links_create(struct snd_soc_card *card)
{
struct snd_soc_dai_link *links;
struct device *dev = card->dev;
struct acp_card_drvdata *drv_data = card->drvdata;
int i = 0, num_links = 0;
if (drv_data->hs_cpu_id)
num_links++;
if (drv_data->amp_cpu_id)
num_links++;
if (drv_data->dmic_cpu_id)
num_links++;
links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_links, GFP_KERNEL);
if (!links)
return -ENOMEM;
if (drv_data->hs_cpu_id == I2S_SP) {
links[i].name = "acp-headset-codec";
links[i].id = HEADSET_BE_ID;
links[i].cpus = sof_sp;
links[i].num_cpus = ARRAY_SIZE(sof_sp);
links[i].platforms = sof_component;
links[i].num_platforms = ARRAY_SIZE(sof_component);
links[i].dpcm_playback = 1;
links[i].dpcm_capture = 1;
links[i].nonatomic = true;
links[i].no_pcm = 1;
if (!drv_data->hs_codec_id) {
/* Use dummy codec if codec id not specified */
links[i].codecs = dummy_codec;
links[i].num_codecs = ARRAY_SIZE(dummy_codec);
}
if (drv_data->hs_codec_id == RT5682) {
links[i].codecs = rt5682;
links[i].num_codecs = ARRAY_SIZE(rt5682);
links[i].init = acp_card_rt5682_init;
links[i].ops = &acp_card_rt5682_ops;
}
if (drv_data->hs_codec_id == RT5682S) {
links[i].codecs = rt5682s;
links[i].num_codecs = ARRAY_SIZE(rt5682s);
links[i].init = acp_card_rt5682s_init;
links[i].ops = &acp_card_rt5682s_ops;
}
i++;
}
if (drv_data->amp_cpu_id == I2S_SP) {
links[i].name = "acp-amp-codec";
links[i].id = AMP_BE_ID;
links[i].cpus = sof_sp;
links[i].num_cpus = ARRAY_SIZE(sof_sp);
links[i].platforms = sof_component;
links[i].num_platforms = ARRAY_SIZE(sof_component);
links[i].dpcm_playback = 1;
links[i].nonatomic = true;
links[i].no_pcm = 1;
if (!drv_data->amp_codec_id) {
/* Use dummy codec if codec id not specified */
links[i].codecs = dummy_codec;
links[i].num_codecs = ARRAY_SIZE(dummy_codec);
}
if (drv_data->amp_codec_id == RT1019) {
links[i].codecs = rt1019;
links[i].num_codecs = ARRAY_SIZE(rt1019);
links[i].ops = &acp_card_rt1019_ops;
links[i].init = acp_card_rt1019_init;
card->codec_conf = rt1019_conf;
card->num_configs = ARRAY_SIZE(rt1019_conf);
}
if (drv_data->amp_codec_id == MAX98360A) {
links[i].codecs = max98360a;
links[i].num_codecs = ARRAY_SIZE(max98360a);
links[i].ops = &acp_card_maxim_ops;
links[i].init = acp_card_maxim_init;
}
i++;
}
if (drv_data->dmic_cpu_id == DMIC) {
links[i].name = "acp-dmic-codec";
links[i].id = DMIC_BE_ID;
links[i].codecs = dmic_codec;
links[i].num_codecs = ARRAY_SIZE(dmic_codec);
links[i].cpus = sof_dmic;
links[i].num_cpus = ARRAY_SIZE(sof_dmic);
links[i].platforms = sof_component;
links[i].num_platforms = ARRAY_SIZE(sof_component);
links[i].dpcm_capture = 1;
links[i].nonatomic = true;
links[i].no_pcm = 1;
}
card->dai_link = links;
card->num_links = num_links;
return 0;
}
EXPORT_SYMBOL_NS_GPL(acp_sofdsp_dai_links_create, SND_SOC_AMD_MACH);
int acp_legacy_dai_links_create(struct snd_soc_card *card)
{
struct snd_soc_dai_link *links;
struct device *dev = card->dev;
struct acp_card_drvdata *drv_data = card->drvdata;
int i = 0, num_links = 0;
if (drv_data->hs_cpu_id)
num_links++;
if (drv_data->amp_cpu_id)
num_links++;
if (drv_data->dmic_cpu_id)
num_links++;
links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_links, GFP_KERNEL);
if (drv_data->hs_cpu_id == I2S_SP) {
links[i].name = "acp-headset-codec";
links[i].id = HEADSET_BE_ID;
links[i].cpus = i2s_sp;
links[i].num_cpus = ARRAY_SIZE(i2s_sp);
links[i].platforms = platform_component;
links[i].num_platforms = ARRAY_SIZE(platform_component);
links[i].dpcm_playback = 1;
links[i].dpcm_capture = 1;
if (!drv_data->hs_codec_id) {
/* Use dummy codec if codec id not specified */
links[i].codecs = dummy_codec;
links[i].num_codecs = ARRAY_SIZE(dummy_codec);
}
if (drv_data->hs_codec_id == RT5682) {
links[i].codecs = rt5682;
links[i].num_codecs = ARRAY_SIZE(rt5682);
links[i].init = acp_card_rt5682_init;
links[i].ops = &acp_card_rt5682_ops;
}
if (drv_data->hs_codec_id == RT5682S) {
links[i].codecs = rt5682s;
links[i].num_codecs = ARRAY_SIZE(rt5682s);
links[i].init = acp_card_rt5682s_init;
links[i].ops = &acp_card_rt5682s_ops;
}
i++;
}
if (drv_data->amp_cpu_id == I2S_SP) {
links[i].name = "acp-amp-codec";
links[i].id = AMP_BE_ID;
links[i].cpus = i2s_sp;
links[i].num_cpus = ARRAY_SIZE(i2s_sp);
links[i].platforms = platform_component;
links[i].num_platforms = ARRAY_SIZE(platform_component);
links[i].dpcm_playback = 1;
if (!drv_data->amp_codec_id) {
/* Use dummy codec if codec id not specified */
links[i].codecs = dummy_codec;
links[i].num_codecs = ARRAY_SIZE(dummy_codec);
}
if (drv_data->amp_codec_id == RT1019) {
links[i].codecs = rt1019;
links[i].num_codecs = ARRAY_SIZE(rt1019);
links[i].ops = &acp_card_rt1019_ops;
links[i].init = acp_card_rt1019_init;
card->codec_conf = rt1019_conf;
card->num_configs = ARRAY_SIZE(rt1019_conf);
}
if (drv_data->amp_codec_id == MAX98360A) {
links[i].codecs = max98360a;
links[i].num_codecs = ARRAY_SIZE(max98360a);
links[i].ops = &acp_card_maxim_ops;
links[i].init = acp_card_maxim_init;
}
}
card->dai_link = links;
card->num_links = num_links;
return 0;
}
EXPORT_SYMBOL_NS_GPL(acp_legacy_dai_links_create, SND_SOC_AMD_MACH);
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD ALSA SoC PCM Driver for ACP 2.x
*
* Copyright 2014-2015 Advanced Micro Devices, Inc.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/sizes.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <drm/amd_asic_type.h>
#include "acp.h"
#define DRV_NAME "acp_audio_dma"
#define PLAYBACK_MIN_NUM_PERIODS 2
#define PLAYBACK_MAX_NUM_PERIODS 2
#define PLAYBACK_MAX_PERIOD_SIZE 16384
#define PLAYBACK_MIN_PERIOD_SIZE 1024
#define CAPTURE_MIN_NUM_PERIODS 2
#define CAPTURE_MAX_NUM_PERIODS 2
#define CAPTURE_MAX_PERIOD_SIZE 16384
#define CAPTURE_MIN_PERIOD_SIZE 1024
#define MAX_BUFFER (PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS)
#define MIN_BUFFER MAX_BUFFER
#define ST_PLAYBACK_MAX_PERIOD_SIZE 4096
#define ST_CAPTURE_MAX_PERIOD_SIZE ST_PLAYBACK_MAX_PERIOD_SIZE
#define ST_MAX_BUFFER (ST_PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS)
#define ST_MIN_BUFFER ST_MAX_BUFFER
#define DRV_NAME "acp_audio_dma"
bool acp_bt_uart_enable = true;
EXPORT_SYMBOL(acp_bt_uart_enable);
static const struct snd_pcm_hardware acp_pcm_hardware_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.rate_min = 8000,
.rate_max = 96000,
.buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = PLAYBACK_MIN_NUM_PERIODS,
.periods_max = PLAYBACK_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp_pcm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp_st_pcm_hardware_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.rate_min = 8000,
.rate_max = 96000,
.buffer_bytes_max = ST_MAX_BUFFER,
.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = ST_PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = PLAYBACK_MIN_NUM_PERIODS,
.periods_max = PLAYBACK_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp_st_pcm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.buffer_bytes_max = ST_MAX_BUFFER,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = ST_CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static u32 acp_reg_read(void __iomem *acp_mmio, u32 reg)
{
return readl(acp_mmio + (reg * 4));
}
static void acp_reg_write(u32 val, void __iomem *acp_mmio, u32 reg)
{
writel(val, acp_mmio + (reg * 4));
}
/*
* Configure a given dma channel parameters - enable/disable,
* number of descriptors, priority
*/
static void config_acp_dma_channel(void __iomem *acp_mmio, u8 ch_num,
u16 dscr_strt_idx, u16 num_dscrs,
enum acp_dma_priority_level priority_level)
{
u32 dma_ctrl;
/* disable the channel run field */
dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK;
acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
/* program a DMA channel with first descriptor to be processed. */
acp_reg_write((ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK
& dscr_strt_idx),
acp_mmio, mmACP_DMA_DSCR_STRT_IDX_0 + ch_num);
/*
* program a DMA channel with the number of descriptors to be
* processed in the transfer
*/
acp_reg_write(ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK & num_dscrs,
acp_mmio, mmACP_DMA_DSCR_CNT_0 + ch_num);
/* set DMA channel priority */
acp_reg_write(priority_level, acp_mmio, mmACP_DMA_PRIO_0 + ch_num);
}
/* Initialize a dma descriptor in SRAM based on descriptor information passed */
static void config_dma_descriptor_in_sram(void __iomem *acp_mmio,
u16 descr_idx,
acp_dma_dscr_transfer_t *descr_info)
{
u32 sram_offset;
sram_offset = (descr_idx * sizeof(acp_dma_dscr_transfer_t));
/* program the source base address. */
acp_reg_write(sram_offset, acp_mmio, mmACP_SRBM_Targ_Idx_Addr);
acp_reg_write(descr_info->src, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
/* program the destination base address. */
acp_reg_write(sram_offset + 4, acp_mmio, mmACP_SRBM_Targ_Idx_Addr);
acp_reg_write(descr_info->dest, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
/* program the number of bytes to be transferred for this descriptor. */
acp_reg_write(sram_offset + 8, acp_mmio, mmACP_SRBM_Targ_Idx_Addr);
acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
}
static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num)
{
u32 dma_ctrl;
int ret;
/* clear the reset bit */
dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK;
acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
/* check the reset bit before programming configuration registers */
ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4),
dma_ctrl,
!(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK),
100, ACP_DMA_RESET_TIME);
if (ret < 0)
pr_err("Failed to clear reset of channel : %d\n", ch_num);
}
/*
* Initialize the DMA descriptor information for transfer between
* system memory <-> ACP SRAM
*/
static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio,
u32 size, int direction,
u32 pte_offset, u16 ch,
u32 sram_bank, u16 dma_dscr_idx,
u32 asic_type)
{
u16 i;
acp_dma_dscr_transfer_t dmadscr[NUM_DSCRS_PER_CHANNEL];
for (i = 0; i < NUM_DSCRS_PER_CHANNEL; i++) {
dmadscr[i].xfer_val = 0;
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
dma_dscr_idx = dma_dscr_idx + i;
dmadscr[i].dest = sram_bank + (i * (size / 2));
dmadscr[i].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS
+ (pte_offset * SZ_4K) + (i * (size / 2));
switch (asic_type) {
case CHIP_STONEY:
dmadscr[i].xfer_val |=
(ACP_DMA_ATTR_DAGB_GARLIC_TO_SHAREDMEM << 16) |
(size / 2);
break;
default:
dmadscr[i].xfer_val |=
(ACP_DMA_ATTR_DAGB_ONION_TO_SHAREDMEM << 16) |
(size / 2);
}
} else {
dma_dscr_idx = dma_dscr_idx + i;
dmadscr[i].src = sram_bank + (i * (size / 2));
dmadscr[i].dest =
ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS +
(pte_offset * SZ_4K) + (i * (size / 2));
switch (asic_type) {
case CHIP_STONEY:
dmadscr[i].xfer_val |=
(ACP_DMA_ATTR_SHARED_MEM_TO_DAGB_GARLIC << 16) |
(size / 2);
break;
default:
dmadscr[i].xfer_val |=
(ACP_DMA_ATTR_SHAREDMEM_TO_DAGB_ONION << 16) |
(size / 2);
}
}
config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx,
&dmadscr[i]);
}
pre_config_reset(acp_mmio, ch);
config_acp_dma_channel(acp_mmio, ch,
dma_dscr_idx - 1,
NUM_DSCRS_PER_CHANNEL,
ACP_DMA_PRIORITY_LEVEL_NORMAL);
}
/*
* Initialize the DMA descriptor information for transfer between
* ACP SRAM <-> I2S
*/
static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size,
int direction, u32 sram_bank,
u16 destination, u16 ch,
u16 dma_dscr_idx, u32 asic_type)
{
u16 i;
acp_dma_dscr_transfer_t dmadscr[NUM_DSCRS_PER_CHANNEL];
for (i = 0; i < NUM_DSCRS_PER_CHANNEL; i++) {
dmadscr[i].xfer_val = 0;
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
dma_dscr_idx = dma_dscr_idx + i;
dmadscr[i].src = sram_bank + (i * (size / 2));
/* dmadscr[i].dest is unused by hardware. */
dmadscr[i].dest = 0;
dmadscr[i].xfer_val |= BIT(22) | (destination << 16) |
(size / 2);
} else {
dma_dscr_idx = dma_dscr_idx + i;
/* dmadscr[i].src is unused by hardware. */
dmadscr[i].src = 0;
dmadscr[i].dest =
sram_bank + (i * (size / 2));
dmadscr[i].xfer_val |= BIT(22) |
(destination << 16) | (size / 2);
}
config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx,
&dmadscr[i]);
}
pre_config_reset(acp_mmio, ch);
/* Configure the DMA channel with the above descriptor */
config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1,
NUM_DSCRS_PER_CHANNEL,
ACP_DMA_PRIORITY_LEVEL_NORMAL);
}
/* Create page table entries in ACP SRAM for the allocated memory */
static void acp_pte_config(void __iomem *acp_mmio, dma_addr_t addr,
u16 num_of_pages, u32 pte_offset)
{
u16 page_idx;
u32 low;
u32 high;
u32 offset;
offset = ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET + (pte_offset * 8);
for (page_idx = 0; page_idx < (num_of_pages); page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
acp_reg_write((offset + (page_idx * 8)),
acp_mmio, mmACP_SRBM_Targ_Idx_Addr);
low = lower_32_bits(addr);
high = upper_32_bits(addr);
acp_reg_write(low, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
/* Load the High address of page int ACP SRAM through SRBM */
acp_reg_write((offset + (page_idx * 8) + 4),
acp_mmio, mmACP_SRBM_Targ_Idx_Addr);
/* page enable in ACP */
high |= BIT(31);
acp_reg_write(high, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
/* Move to next physically contiguous page */
addr += PAGE_SIZE;
}
}
static void config_acp_dma(void __iomem *acp_mmio,
struct audio_substream_data *rtd,
u32 asic_type)
{
u16 ch_acp_sysmem, ch_acp_i2s;
acp_pte_config(acp_mmio, rtd->dma_addr, rtd->num_of_pages,
rtd->pte_offset);
if (rtd->direction == SNDRV_PCM_STREAM_PLAYBACK) {
ch_acp_sysmem = rtd->ch1;
ch_acp_i2s = rtd->ch2;
} else {
ch_acp_i2s = rtd->ch1;
ch_acp_sysmem = rtd->ch2;
}
/* Configure System memory <-> ACP SRAM DMA descriptors */
set_acp_sysmem_dma_descriptors(acp_mmio, rtd->size,
rtd->direction, rtd->pte_offset,
ch_acp_sysmem, rtd->sram_bank,
rtd->dma_dscr_idx_1, asic_type);
/* Configure ACP SRAM <-> I2S DMA descriptors */
set_acp_to_i2s_dma_descriptors(acp_mmio, rtd->size,
rtd->direction, rtd->sram_bank,
rtd->destination, ch_acp_i2s,
rtd->dma_dscr_idx_2, asic_type);
}
static void acp_dma_cap_channel_enable(void __iomem *acp_mmio,
u16 cap_channel)
{
u32 val, ch_reg, imr_reg, res_reg;
switch (cap_channel) {
case CAP_CHANNEL1:
ch_reg = mmACP_I2SMICSP_RER1;
res_reg = mmACP_I2SMICSP_RCR1;
imr_reg = mmACP_I2SMICSP_IMR1;
break;
case CAP_CHANNEL0:
default:
ch_reg = mmACP_I2SMICSP_RER0;
res_reg = mmACP_I2SMICSP_RCR0;
imr_reg = mmACP_I2SMICSP_IMR0;
break;
}
val = acp_reg_read(acp_mmio,
mmACP_I2S_16BIT_RESOLUTION_EN);
if (val & ACP_I2S_MIC_16BIT_RESOLUTION_EN) {
acp_reg_write(0x0, acp_mmio, ch_reg);
/* Set 16bit resolution on capture */
acp_reg_write(0x2, acp_mmio, res_reg);
}
val = acp_reg_read(acp_mmio, imr_reg);
val &= ~ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK;
val &= ~ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK;
acp_reg_write(val, acp_mmio, imr_reg);
acp_reg_write(0x1, acp_mmio, ch_reg);
}
static void acp_dma_cap_channel_disable(void __iomem *acp_mmio,
u16 cap_channel)
{
u32 val, ch_reg, imr_reg;
switch (cap_channel) {
case CAP_CHANNEL1:
imr_reg = mmACP_I2SMICSP_IMR1;
ch_reg = mmACP_I2SMICSP_RER1;
break;
case CAP_CHANNEL0:
default:
imr_reg = mmACP_I2SMICSP_IMR0;
ch_reg = mmACP_I2SMICSP_RER0;
break;
}
val = acp_reg_read(acp_mmio, imr_reg);
val |= ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK;
val |= ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK;
acp_reg_write(val, acp_mmio, imr_reg);
acp_reg_write(0x0, acp_mmio, ch_reg);
}
/* Start a given DMA channel transfer */
static void acp_dma_start(void __iomem *acp_mmio, u16 ch_num, bool is_circular)
{
u32 dma_ctrl;
/* read the dma control register and disable the channel run field */
dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
/* Invalidating the DAGB cache */
acp_reg_write(1, acp_mmio, mmACP_DAGB_ATU_CTRL);
/*
* configure the DMA channel and start the DMA transfer
* set dmachrun bit to start the transfer and enable the
* interrupt on completion of the dma transfer
*/
dma_ctrl |= ACP_DMA_CNTL_0__DMAChRun_MASK;
switch (ch_num) {
case ACP_TO_I2S_DMA_CH_NUM:
case I2S_TO_ACP_DMA_CH_NUM:
case ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM:
case I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM:
dma_ctrl |= ACP_DMA_CNTL_0__DMAChIOCEn_MASK;
break;
default:
dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK;
break;
}
/* enable for ACP to SRAM DMA channel */
if (is_circular == true)
dma_ctrl |= ACP_DMA_CNTL_0__Circular_DMA_En_MASK;
else
dma_ctrl &= ~ACP_DMA_CNTL_0__Circular_DMA_En_MASK;
acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
}
/* Stop a given DMA channel transfer */
static int acp_dma_stop(void __iomem *acp_mmio, u8 ch_num)
{
u32 dma_ctrl;
u32 dma_ch_sts;
u32 count = ACP_DMA_RESET_TIME;
dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
/*
* clear the dma control register fields before writing zero
* in reset bit
*/
dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK;
dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK;
acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS);
if (dma_ch_sts & BIT(ch_num)) {
/*
* set the reset bit for this channel to stop the dma
* transfer
*/
dma_ctrl |= ACP_DMA_CNTL_0__DMAChRst_MASK;
acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
}
/* check the channel status bit for some time and return the status */
while (true) {
dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS);
if (!(dma_ch_sts & BIT(ch_num))) {
/*
* clear the reset flag after successfully stopping
* the dma transfer and break from the loop
*/
dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK;
acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0
+ ch_num);
break;
}
if (--count == 0) {
pr_err("Failed to stop ACP DMA channel : %d\n", ch_num);
return -ETIMEDOUT;
}
udelay(100);
}
return 0;
}
static void acp_set_sram_bank_state(void __iomem *acp_mmio, u16 bank,
bool power_on)
{
u32 val, req_reg, sts_reg, sts_reg_mask;
u32 loops = 1000;
if (bank < 32) {
req_reg = mmACP_MEM_SHUT_DOWN_REQ_LO;
sts_reg = mmACP_MEM_SHUT_DOWN_STS_LO;
sts_reg_mask = 0xFFFFFFFF;
} else {
bank -= 32;
req_reg = mmACP_MEM_SHUT_DOWN_REQ_HI;
sts_reg = mmACP_MEM_SHUT_DOWN_STS_HI;
sts_reg_mask = 0x0000FFFF;
}
val = acp_reg_read(acp_mmio, req_reg);
if (val & (1 << bank)) {
/* bank is in off state */
if (power_on == true)
/* request to on */
val &= ~(1 << bank);
else
/* request to off */
return;
} else {
/* bank is in on state */
if (power_on == false)
/* request to off */
val |= 1 << bank;
else
/* request to on */
return;
}
acp_reg_write(val, acp_mmio, req_reg);
while (acp_reg_read(acp_mmio, sts_reg) != sts_reg_mask) {
if (!loops--) {
pr_err("ACP SRAM bank %d state change failed\n", bank);
break;
}
cpu_relax();
}
}
/* Initialize and bring ACP hardware to default state. */
static int acp_init(void __iomem *acp_mmio, u32 asic_type)
{
u16 bank;
u32 val, count, sram_pte_offset;
/* Assert Soft reset of ACP */
val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET);
val |= ACP_SOFT_RESET__SoftResetAud_MASK;
acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET);
count = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE;
while (true) {
val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET);
if (ACP_SOFT_RESET__SoftResetAudDone_MASK ==
(val & ACP_SOFT_RESET__SoftResetAudDone_MASK))
break;
if (--count == 0) {
pr_err("Failed to reset ACP\n");
return -ETIMEDOUT;
}
udelay(100);
}
/* Enable clock to ACP and wait until the clock is enabled */
val = acp_reg_read(acp_mmio, mmACP_CONTROL);
val = val | ACP_CONTROL__ClkEn_MASK;
acp_reg_write(val, acp_mmio, mmACP_CONTROL);
count = ACP_CLOCK_EN_TIME_OUT_VALUE;
while (true) {
val = acp_reg_read(acp_mmio, mmACP_STATUS);
if (val & (u32)0x1)
break;
if (--count == 0) {
pr_err("Failed to reset ACP\n");
return -ETIMEDOUT;
}
udelay(100);
}
/* Deassert the SOFT RESET flags */
val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET);
val &= ~ACP_SOFT_RESET__SoftResetAud_MASK;
acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET);
/* For BT instance change pins from UART to BT */
if (!acp_bt_uart_enable) {
val = acp_reg_read(acp_mmio, mmACP_BT_UART_PAD_SEL);
val |= ACP_BT_UART_PAD_SELECT_MASK;
acp_reg_write(val, acp_mmio, mmACP_BT_UART_PAD_SEL);
}
/* initialize Onion control DAGB register */
acp_reg_write(ACP_ONION_CNTL_DEFAULT, acp_mmio,
mmACP_AXI2DAGB_ONION_CNTL);
/* initialize Garlic control DAGB registers */
acp_reg_write(ACP_GARLIC_CNTL_DEFAULT, acp_mmio,
mmACP_AXI2DAGB_GARLIC_CNTL);
sram_pte_offset = ACP_DAGB_GRP_SRAM_BASE_ADDRESS |
ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBSnoopSel_MASK |
ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBTargetMemSel_MASK |
ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBGrpEnable_MASK;
acp_reg_write(sram_pte_offset, acp_mmio, mmACP_DAGB_BASE_ADDR_GRP_1);
acp_reg_write(ACP_PAGE_SIZE_4K_ENABLE, acp_mmio,
mmACP_DAGB_PAGE_SIZE_GRP_1);
acp_reg_write(ACP_SRAM_BASE_ADDRESS, acp_mmio,
mmACP_DMA_DESC_BASE_ADDR);
/* Num of descriptors in SRAM 0x4, means 256 descriptors;(64 * 4) */
acp_reg_write(0x4, acp_mmio, mmACP_DMA_DESC_MAX_NUM_DSCR);
acp_reg_write(ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK,
acp_mmio, mmACP_EXTERNAL_INTR_CNTL);
/*
* When ACP_TILE_P1 is turned on, all SRAM banks get turned on.
* Now, turn off all of them. This can't be done in 'poweron' of
* ACP pm domain, as this requires ACP to be initialized.
* For Stoney, Memory gating is disabled,i.e SRAM Banks
* won't be turned off. The default state for SRAM banks is ON.
* Setting SRAM bank state code skipped for STONEY platform.
*/
if (asic_type != CHIP_STONEY) {
for (bank = 1; bank < 48; bank++)
acp_set_sram_bank_state(acp_mmio, bank, false);
}
return 0;
}
/* Deinitialize ACP */
static int acp_deinit(void __iomem *acp_mmio)
{
u32 val;
u32 count;
/* Assert Soft reset of ACP */
val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET);
val |= ACP_SOFT_RESET__SoftResetAud_MASK;
acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET);
count = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE;
while (true) {
val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET);
if (ACP_SOFT_RESET__SoftResetAudDone_MASK ==
(val & ACP_SOFT_RESET__SoftResetAudDone_MASK))
break;
if (--count == 0) {
pr_err("Failed to reset ACP\n");
return -ETIMEDOUT;
}
udelay(100);
}
/* Disable ACP clock */
val = acp_reg_read(acp_mmio, mmACP_CONTROL);
val &= ~ACP_CONTROL__ClkEn_MASK;
acp_reg_write(val, acp_mmio, mmACP_CONTROL);
count = ACP_CLOCK_EN_TIME_OUT_VALUE;
while (true) {
val = acp_reg_read(acp_mmio, mmACP_STATUS);
if (!(val & (u32)0x1))
break;
if (--count == 0) {
pr_err("Failed to reset ACP\n");
return -ETIMEDOUT;
}
udelay(100);
}
return 0;
}
/* ACP DMA irq handler routine for playback, capture usecases */
static irqreturn_t dma_irq_handler(int irq, void *arg)
{
u16 dscr_idx;
u32 intr_flag, ext_intr_status;
struct audio_drv_data *irq_data;
void __iomem *acp_mmio;
struct device *dev = arg;
bool valid_irq = false;
irq_data = dev_get_drvdata(dev);
acp_mmio = irq_data->acp_mmio;
ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT);
intr_flag = (((ext_intr_status &
ACP_EXTERNAL_INTR_STAT__DMAIOCStat_MASK) >>
ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT));
if ((intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)) != 0) {
valid_irq = true;
snd_pcm_period_elapsed(irq_data->play_i2ssp_stream);
acp_reg_write((intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)) << 16,
acp_mmio, mmACP_EXTERNAL_INTR_STAT);
}
if ((intr_flag & BIT(ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM)) != 0) {
valid_irq = true;
snd_pcm_period_elapsed(irq_data->play_i2sbt_stream);
acp_reg_write((intr_flag &
BIT(ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM)) << 16,
acp_mmio, mmACP_EXTERNAL_INTR_STAT);
}
if ((intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)) != 0) {
valid_irq = true;
if (acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_14) ==
CAPTURE_START_DMA_DESCR_CH15)
dscr_idx = CAPTURE_END_DMA_DESCR_CH14;
else
dscr_idx = CAPTURE_START_DMA_DESCR_CH14;
config_acp_dma_channel(acp_mmio, ACP_TO_SYSRAM_CH_NUM, dscr_idx,
1, 0);
acp_dma_start(acp_mmio, ACP_TO_SYSRAM_CH_NUM, false);
snd_pcm_period_elapsed(irq_data->capture_i2ssp_stream);
acp_reg_write((intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)) << 16,
acp_mmio, mmACP_EXTERNAL_INTR_STAT);
}
if ((intr_flag & BIT(I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM)) != 0) {
valid_irq = true;
if (acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_10) ==
CAPTURE_START_DMA_DESCR_CH11)
dscr_idx = CAPTURE_END_DMA_DESCR_CH10;
else
dscr_idx = CAPTURE_START_DMA_DESCR_CH10;
config_acp_dma_channel(acp_mmio,
ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM,
dscr_idx, 1, 0);
acp_dma_start(acp_mmio, ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM,
false);
snd_pcm_period_elapsed(irq_data->capture_i2sbt_stream);
acp_reg_write((intr_flag &
BIT(I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM)) << 16,
acp_mmio, mmACP_EXTERNAL_INTR_STAT);
}
if (valid_irq)
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static int acp_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
u16 bank;
int ret = 0;
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_drv_data *intr_data = dev_get_drvdata(component->dev);
struct audio_substream_data *adata =
kzalloc(sizeof(struct audio_substream_data), GFP_KERNEL);
if (!adata)
return -ENOMEM;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (intr_data->asic_type) {
case CHIP_STONEY:
runtime->hw = acp_st_pcm_hardware_playback;
break;
default:
runtime->hw = acp_pcm_hardware_playback;
}
} else {
switch (intr_data->asic_type) {
case CHIP_STONEY:
runtime->hw = acp_st_pcm_hardware_capture;
break;
default:
runtime->hw = acp_pcm_hardware_capture;
}
}
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(adata);
return ret;
}
adata->acp_mmio = intr_data->acp_mmio;
runtime->private_data = adata;
/*
* Enable ACP irq, when neither playback or capture streams are
* active by the time when a new stream is being opened.
* This enablement is not required for another stream, if current
* stream is not closed
*/
if (!intr_data->play_i2ssp_stream && !intr_data->capture_i2ssp_stream &&
!intr_data->play_i2sbt_stream && !intr_data->capture_i2sbt_stream)
acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/*
* For Stoney, Memory gating is disabled,i.e SRAM Banks
* won't be turned off. The default state for SRAM banks is ON.
* Setting SRAM bank state code skipped for STONEY platform.
*/
if (intr_data->asic_type != CHIP_STONEY) {
for (bank = 1; bank <= 4; bank++)
acp_set_sram_bank_state(intr_data->acp_mmio,
bank, true);
}
} else {
if (intr_data->asic_type != CHIP_STONEY) {
for (bank = 5; bank <= 8; bank++)
acp_set_sram_bank_state(intr_data->acp_mmio,
bank, true);
}
}
return 0;
}
static int acp_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
uint64_t size;
u32 val = 0;
struct snd_pcm_runtime *runtime;
struct audio_substream_data *rtd;
struct snd_soc_pcm_runtime *prtd = asoc_substream_to_rtd(substream);
struct audio_drv_data *adata = dev_get_drvdata(component->dev);
struct snd_soc_card *card = prtd->card;
struct acp_platform_info *pinfo = snd_soc_card_get_drvdata(card);
runtime = substream->runtime;
rtd = runtime->private_data;
if (WARN_ON(!rtd))
return -EINVAL;
if (pinfo) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
rtd->i2s_instance = pinfo->play_i2s_instance;
} else {
rtd->i2s_instance = pinfo->cap_i2s_instance;
rtd->capture_channel = pinfo->capture_channel;
}
}
if (adata->asic_type == CHIP_STONEY) {
val = acp_reg_read(adata->acp_mmio,
mmACP_I2S_16BIT_RESOLUTION_EN);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
val |= ACP_I2S_BT_16BIT_RESOLUTION_EN;
break;
case I2S_SP_INSTANCE:
default:
val |= ACP_I2S_SP_16BIT_RESOLUTION_EN;
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
val |= ACP_I2S_BT_16BIT_RESOLUTION_EN;
break;
case I2S_SP_INSTANCE:
default:
val |= ACP_I2S_MIC_16BIT_RESOLUTION_EN;
}
}
acp_reg_write(val, adata->acp_mmio,
mmACP_I2S_16BIT_RESOLUTION_EN);
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
rtd->pte_offset = ACP_ST_BT_PLAYBACK_PTE_OFFSET;
rtd->ch1 = SYSRAM_TO_ACP_BT_INSTANCE_CH_NUM;
rtd->ch2 = ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM;
rtd->sram_bank = ACP_SRAM_BANK_3_ADDRESS;
rtd->destination = TO_BLUETOOTH;
rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH8;
rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH9;
rtd->byte_cnt_high_reg_offset =
mmACP_I2S_BT_TRANSMIT_BYTE_CNT_HIGH;
rtd->byte_cnt_low_reg_offset =
mmACP_I2S_BT_TRANSMIT_BYTE_CNT_LOW;
adata->play_i2sbt_stream = substream;
break;
case I2S_SP_INSTANCE:
default:
switch (adata->asic_type) {
case CHIP_STONEY:
rtd->pte_offset = ACP_ST_PLAYBACK_PTE_OFFSET;
break;
default:
rtd->pte_offset = ACP_PLAYBACK_PTE_OFFSET;
}
rtd->ch1 = SYSRAM_TO_ACP_CH_NUM;
rtd->ch2 = ACP_TO_I2S_DMA_CH_NUM;
rtd->sram_bank = ACP_SRAM_BANK_1_ADDRESS;
rtd->destination = TO_ACP_I2S_1;
rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH12;
rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH13;
rtd->byte_cnt_high_reg_offset =
mmACP_I2S_TRANSMIT_BYTE_CNT_HIGH;
rtd->byte_cnt_low_reg_offset =
mmACP_I2S_TRANSMIT_BYTE_CNT_LOW;
adata->play_i2ssp_stream = substream;
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
rtd->pte_offset = ACP_ST_BT_CAPTURE_PTE_OFFSET;
rtd->ch1 = I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM;
rtd->ch2 = ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM;
rtd->sram_bank = ACP_SRAM_BANK_4_ADDRESS;
rtd->destination = FROM_BLUETOOTH;
rtd->dma_dscr_idx_1 = CAPTURE_START_DMA_DESCR_CH10;
rtd->dma_dscr_idx_2 = CAPTURE_START_DMA_DESCR_CH11;
rtd->byte_cnt_high_reg_offset =
mmACP_I2S_BT_RECEIVE_BYTE_CNT_HIGH;
rtd->byte_cnt_low_reg_offset =
mmACP_I2S_BT_RECEIVE_BYTE_CNT_LOW;
rtd->dma_curr_dscr = mmACP_DMA_CUR_DSCR_11;
adata->capture_i2sbt_stream = substream;
break;
case I2S_SP_INSTANCE:
default:
rtd->pte_offset = ACP_CAPTURE_PTE_OFFSET;
rtd->ch1 = I2S_TO_ACP_DMA_CH_NUM;
rtd->ch2 = ACP_TO_SYSRAM_CH_NUM;
switch (adata->asic_type) {
case CHIP_STONEY:
rtd->pte_offset = ACP_ST_CAPTURE_PTE_OFFSET;
rtd->sram_bank = ACP_SRAM_BANK_2_ADDRESS;
break;
default:
rtd->pte_offset = ACP_CAPTURE_PTE_OFFSET;
rtd->sram_bank = ACP_SRAM_BANK_5_ADDRESS;
}
rtd->destination = FROM_ACP_I2S_1;
rtd->dma_dscr_idx_1 = CAPTURE_START_DMA_DESCR_CH14;
rtd->dma_dscr_idx_2 = CAPTURE_START_DMA_DESCR_CH15;
rtd->byte_cnt_high_reg_offset =
mmACP_I2S_RECEIVED_BYTE_CNT_HIGH;
rtd->byte_cnt_low_reg_offset =
mmACP_I2S_RECEIVED_BYTE_CNT_LOW;
rtd->dma_curr_dscr = mmACP_DMA_CUR_DSCR_15;
adata->capture_i2ssp_stream = substream;
}
}
size = params_buffer_bytes(params);
acp_set_sram_bank_state(rtd->acp_mmio, 0, true);
/* Save for runtime private data */
rtd->dma_addr = runtime->dma_addr;
rtd->order = get_order(size);
/* Fill the page table entries in ACP SRAM */
rtd->size = size;
rtd->num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
rtd->direction = substream->stream;
config_acp_dma(rtd->acp_mmio, rtd, adata->asic_type);
return 0;
}
static u64 acp_get_byte_count(struct audio_substream_data *rtd)
{
union acp_dma_count byte_count;
byte_count.bcount.high = acp_reg_read(rtd->acp_mmio,
rtd->byte_cnt_high_reg_offset);
byte_count.bcount.low = acp_reg_read(rtd->acp_mmio,
rtd->byte_cnt_low_reg_offset);
return byte_count.bytescount;
}
static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
u32 buffersize;
u32 pos = 0;
u64 bytescount = 0;
u16 dscr;
u32 period_bytes, delay;
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_substream_data *rtd = runtime->private_data;
struct audio_drv_data *adata = dev_get_drvdata(component->dev);
if (!rtd)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
period_bytes = frames_to_bytes(runtime, runtime->period_size);
bytescount = acp_get_byte_count(rtd);
if (bytescount >= rtd->bytescount)
bytescount -= rtd->bytescount;
if (bytescount < period_bytes) {
pos = 0;
} else {
dscr = acp_reg_read(rtd->acp_mmio, rtd->dma_curr_dscr);
if (dscr == rtd->dma_dscr_idx_1)
pos = period_bytes;
else
pos = 0;
}
if (bytescount > 0) {
delay = do_div(bytescount, period_bytes);
adata->delay += bytes_to_frames(runtime, delay);
}
} else {
buffersize = frames_to_bytes(runtime, runtime->buffer_size);
bytescount = acp_get_byte_count(rtd);
if (bytescount > rtd->bytescount)
bytescount -= rtd->bytescount;
pos = do_div(bytescount, buffersize);
}
return bytes_to_frames(runtime, pos);
}
static snd_pcm_sframes_t acp_dma_delay(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct audio_drv_data *adata = dev_get_drvdata(component->dev);
snd_pcm_sframes_t delay = adata->delay;
adata->delay = 0;
return delay;
}
static int acp_dma_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_substream_data *rtd = runtime->private_data;
u16 ch_acp_sysmem, ch_acp_i2s;
if (!rtd)
return -EINVAL;
if (rtd->direction == SNDRV_PCM_STREAM_PLAYBACK) {
ch_acp_sysmem = rtd->ch1;
ch_acp_i2s = rtd->ch2;
} else {
ch_acp_i2s = rtd->ch1;
ch_acp_sysmem = rtd->ch2;
}
config_acp_dma_channel(rtd->acp_mmio,
ch_acp_sysmem,
rtd->dma_dscr_idx_1,
NUM_DSCRS_PER_CHANNEL, 0);
config_acp_dma_channel(rtd->acp_mmio,
ch_acp_i2s,
rtd->dma_dscr_idx_2,
NUM_DSCRS_PER_CHANNEL, 0);
return 0;
}
static int acp_dma_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
int ret;
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_substream_data *rtd = runtime->private_data;
if (!rtd)
return -EINVAL;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
rtd->bytescount = acp_get_byte_count(rtd);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (rtd->capture_channel == CAP_CHANNEL0) {
acp_dma_cap_channel_disable(rtd->acp_mmio,
CAP_CHANNEL1);
acp_dma_cap_channel_enable(rtd->acp_mmio,
CAP_CHANNEL0);
}
if (rtd->capture_channel == CAP_CHANNEL1) {
acp_dma_cap_channel_disable(rtd->acp_mmio,
CAP_CHANNEL0);
acp_dma_cap_channel_enable(rtd->acp_mmio,
CAP_CHANNEL1);
}
acp_dma_start(rtd->acp_mmio, rtd->ch1, true);
} else {
acp_dma_start(rtd->acp_mmio, rtd->ch1, true);
acp_dma_start(rtd->acp_mmio, rtd->ch2, true);
}
ret = 0;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
acp_dma_stop(rtd->acp_mmio, rtd->ch2);
ret = acp_dma_stop(rtd->acp_mmio, rtd->ch1);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int acp_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct audio_drv_data *adata = dev_get_drvdata(component->dev);
struct device *parent = component->dev->parent;
switch (adata->asic_type) {
case CHIP_STONEY:
snd_pcm_set_managed_buffer_all(rtd->pcm,
SNDRV_DMA_TYPE_DEV,
parent,
ST_MIN_BUFFER,
ST_MAX_BUFFER);
break;
default:
snd_pcm_set_managed_buffer_all(rtd->pcm,
SNDRV_DMA_TYPE_DEV,
parent,
MIN_BUFFER,
MAX_BUFFER);
break;
}
return 0;
}
static int acp_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
u16 bank;
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_substream_data *rtd = runtime->private_data;
struct audio_drv_data *adata = dev_get_drvdata(component->dev);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
adata->play_i2sbt_stream = NULL;
break;
case I2S_SP_INSTANCE:
default:
adata->play_i2ssp_stream = NULL;
/*
* For Stoney, Memory gating is disabled,i.e SRAM Banks
* won't be turned off. The default state for SRAM banks
* is ON.Setting SRAM bank state code skipped for STONEY
* platform. Added condition checks for Carrizo platform
* only.
*/
if (adata->asic_type != CHIP_STONEY) {
for (bank = 1; bank <= 4; bank++)
acp_set_sram_bank_state(adata->acp_mmio,
bank, false);
}
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
adata->capture_i2sbt_stream = NULL;
break;
case I2S_SP_INSTANCE:
default:
adata->capture_i2ssp_stream = NULL;
if (adata->asic_type != CHIP_STONEY) {
for (bank = 5; bank <= 8; bank++)
acp_set_sram_bank_state(adata->acp_mmio,
bank, false);
}
}
}
/*
* Disable ACP irq, when the current stream is being closed and
* another stream is also not active.
*/
if (!adata->play_i2ssp_stream && !adata->capture_i2ssp_stream &&
!adata->play_i2sbt_stream && !adata->capture_i2sbt_stream)
acp_reg_write(0, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB);
kfree(rtd);
return 0;
}
static const struct snd_soc_component_driver acp_asoc_platform = {
.name = DRV_NAME,
.open = acp_dma_open,
.close = acp_dma_close,
.hw_params = acp_dma_hw_params,
.trigger = acp_dma_trigger,
.pointer = acp_dma_pointer,
.delay = acp_dma_delay,
.prepare = acp_dma_prepare,
.pcm_construct = acp_dma_new,
};
static int acp_audio_probe(struct platform_device *pdev)
{
int status;
struct audio_drv_data *audio_drv_data;
struct resource *res;
const u32 *pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "Missing platform data\n");
return -ENODEV;
}
audio_drv_data = devm_kzalloc(&pdev->dev, sizeof(struct audio_drv_data),
GFP_KERNEL);
if (!audio_drv_data)
return -ENOMEM;
audio_drv_data->acp_mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(audio_drv_data->acp_mmio))
return PTR_ERR(audio_drv_data->acp_mmio);
/*
* The following members gets populated in device 'open'
* function. Till then interrupts are disabled in 'acp_init'
* and device doesn't generate any interrupts.
*/
audio_drv_data->play_i2ssp_stream = NULL;
audio_drv_data->capture_i2ssp_stream = NULL;
audio_drv_data->play_i2sbt_stream = NULL;
audio_drv_data->capture_i2sbt_stream = NULL;
audio_drv_data->asic_type = *pdata;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
return -ENODEV;
}
status = devm_request_irq(&pdev->dev, res->start, dma_irq_handler,
0, "ACP_IRQ", &pdev->dev);
if (status) {
dev_err(&pdev->dev, "ACP IRQ request failed\n");
return status;
}
dev_set_drvdata(&pdev->dev, audio_drv_data);
/* Initialize the ACP */
status = acp_init(audio_drv_data->acp_mmio, audio_drv_data->asic_type);
if (status) {
dev_err(&pdev->dev, "ACP Init failed status:%d\n", status);
return status;
}
status = devm_snd_soc_register_component(&pdev->dev,
&acp_asoc_platform, NULL, 0);
if (status != 0) {
dev_err(&pdev->dev, "Fail to register ALSA platform device\n");
return status;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, 10000);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return status;
}
static int acp_audio_remove(struct platform_device *pdev)
{
int status;
struct audio_drv_data *adata = dev_get_drvdata(&pdev->dev);
status = acp_deinit(adata->acp_mmio);
if (status)
dev_err(&pdev->dev, "ACP Deinit failed status:%d\n", status);
pm_runtime_disable(&pdev->dev);
return 0;
}
static int acp_pcm_resume(struct device *dev)
{
u16 bank;
int status;
struct audio_substream_data *rtd;
struct audio_drv_data *adata = dev_get_drvdata(dev);
status = acp_init(adata->acp_mmio, adata->asic_type);
if (status) {
dev_err(dev, "ACP Init failed status:%d\n", status);
return status;
}
if (adata->play_i2ssp_stream && adata->play_i2ssp_stream->runtime) {
/*
* For Stoney, Memory gating is disabled,i.e SRAM Banks
* won't be turned off. The default state for SRAM banks is ON.
* Setting SRAM bank state code skipped for STONEY platform.
*/
if (adata->asic_type != CHIP_STONEY) {
for (bank = 1; bank <= 4; bank++)
acp_set_sram_bank_state(adata->acp_mmio, bank,
true);
}
rtd = adata->play_i2ssp_stream->runtime->private_data;
config_acp_dma(adata->acp_mmio, rtd, adata->asic_type);
}
if (adata->capture_i2ssp_stream &&
adata->capture_i2ssp_stream->runtime) {
if (adata->asic_type != CHIP_STONEY) {
for (bank = 5; bank <= 8; bank++)
acp_set_sram_bank_state(adata->acp_mmio, bank,
true);
}
rtd = adata->capture_i2ssp_stream->runtime->private_data;
config_acp_dma(adata->acp_mmio, rtd, adata->asic_type);
}
if (adata->asic_type != CHIP_CARRIZO) {
if (adata->play_i2sbt_stream &&
adata->play_i2sbt_stream->runtime) {
rtd = adata->play_i2sbt_stream->runtime->private_data;
config_acp_dma(adata->acp_mmio, rtd, adata->asic_type);
}
if (adata->capture_i2sbt_stream &&
adata->capture_i2sbt_stream->runtime) {
rtd = adata->capture_i2sbt_stream->runtime->private_data;
config_acp_dma(adata->acp_mmio, rtd, adata->asic_type);
}
}
acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB);
return 0;
}
static int acp_pcm_runtime_suspend(struct device *dev)
{
int status;
struct audio_drv_data *adata = dev_get_drvdata(dev);
status = acp_deinit(adata->acp_mmio);
if (status)
dev_err(dev, "ACP Deinit failed status:%d\n", status);
acp_reg_write(0, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB);
return 0;
}
static int acp_pcm_runtime_resume(struct device *dev)
{
int status;
struct audio_drv_data *adata = dev_get_drvdata(dev);
status = acp_init(adata->acp_mmio, adata->asic_type);
if (status) {
dev_err(dev, "ACP Init failed status:%d\n", status);
return status;
}
acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB);
return 0;
}
static const struct dev_pm_ops acp_pm_ops = {
.resume = acp_pcm_resume,
.runtime_suspend = acp_pcm_runtime_suspend,
.runtime_resume = acp_pcm_runtime_resume,
};
static struct platform_driver acp_dma_driver = {
.probe = acp_audio_probe,
.remove = acp_audio_remove,
.driver = {
.name = DRV_NAME,
.pm = &acp_pm_ops,
},
};
module_platform_driver(acp_dma_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com");
MODULE_DESCRIPTION("AMD ACP PCM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:"DRV_NAME);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
/*
* PCM interface for generic AMD audio ACP DSP block
*/
#include <sound/pcm_params.h>
#include "../ops.h"
#include "acp.h"
#include "acp-dsp-offset.h"
int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct sof_ipc_stream_params *ipc_params)
{
struct acp_dsp_stream *stream = substream->runtime->private_data;
unsigned int buf_offset, index;
u32 size;
int ret;
size = ipc_params->buffer.size;
stream->num_pages = ipc_params->buffer.pages;
stream->dmab = substream->runtime->dma_buffer_p;
ret = acp_dsp_stream_config(sdev, stream);
if (ret < 0) {
dev_err(sdev->dev, "stream configuration failed\n");
return ret;
}
ipc_params->buffer.phy_addr = stream->reg_offset;
ipc_params->stream_tag = stream->stream_tag;
/* write buffer size of stream in scratch memory */
buf_offset = offsetof(struct scratch_reg_conf, buf_size);
index = stream->stream_tag - 1;
buf_offset = buf_offset + index * 4;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + buf_offset, size);
return 0;
}
EXPORT_SYMBOL_NS(acp_pcm_hw_params, SND_SOC_SOF_AMD_COMMON);
int acp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream)
{
struct acp_dsp_stream *stream;
stream = acp_dsp_stream_get(sdev, 0);
if (!stream)
return -ENODEV;
substream->runtime->private_data = stream;
stream->substream = substream;
return 0;
}
EXPORT_SYMBOL_NS(acp_pcm_open, SND_SOC_SOF_AMD_COMMON);
int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream)
{
struct acp_dsp_stream *stream;
stream = substream->runtime->private_data;
if (!stream) {
dev_err(sdev->dev, "No open stream\n");
return -EINVAL;
}
stream->substream = NULL;
substream->runtime->private_data = NULL;
return acp_dsp_stream_put(sdev, stream);
}
EXPORT_SYMBOL_NS(acp_pcm_close, SND_SOC_SOF_AMD_COMMON);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
/*
* Generic interface for ACP audio blck PCM component
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include "amd.h"
#define DRV_NAME "acp_i2s_dma"
static const struct snd_pcm_hardware acp_pcm_hardware_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.rate_min = 8000,
.rate_max = 96000,
.buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = PLAYBACK_MIN_NUM_PERIODS,
.periods_max = PLAYBACK_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp_pcm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
int acp_machine_select(struct acp_dev_data *adata)
{
struct snd_soc_acpi_mach *mach;
int size;
size = sizeof(*adata->machines);
mach = snd_soc_acpi_find_machine(adata->machines);
if (!mach) {
dev_err(adata->dev, "warning: No matching ASoC machine driver found\n");
return -EINVAL;
}
adata->mach_dev = platform_device_register_data(adata->dev, mach->drv_name,
PLATFORM_DEVID_NONE, mach, size);
if (IS_ERR(adata->mach_dev))
dev_warn(adata->dev, "Unable to register Machine device\n");
return 0;
}
EXPORT_SYMBOL_NS_GPL(acp_machine_select, SND_SOC_ACP_COMMON);
static irqreturn_t i2s_irq_handler(int irq, void *data)
{
struct acp_dev_data *adata = data;
struct acp_stream *stream;
u16 i2s_flag = 0;
u32 val, i;
if (!adata)
return IRQ_NONE;
val = readl(adata->acp_base + ACP_EXTERNAL_INTR_STAT);
for (i = 0; i < ACP_MAX_STREAM; i++) {
stream = adata->stream[i];
if (stream && (val & stream->irq_bit)) {
writel(stream->irq_bit, adata->acp_base + ACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(stream->substream);
i2s_flag = 1;
break;
}
}
if (i2s_flag)
return IRQ_HANDLED;
return IRQ_NONE;
}
static void config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream)
{
u32 pte_reg, pte_size, reg_val;
/* Use ATU base Group5 */
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5;
stream->reg_offset = 0x02000000;
/* Group Enable */
reg_val = ACP_SRAM_PTE_OFFSET;
writel(reg_val | BIT(31), adata->acp_base + pte_reg);
writel(PAGE_SIZE_4K_ENABLE, adata->acp_base + pte_size);
}
static void config_acp_dma(struct acp_dev_data *adata, int cpu_id, int size)
{
struct acp_stream *stream = adata->stream[cpu_id];
struct snd_pcm_substream *substream = stream->substream;
dma_addr_t addr = substream->dma_buffer.addr;
int num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
u32 low, high, val;
u16 page_idx;
val = stream->pte_offset;
for (page_idx = 0; page_idx < num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
writel(low, adata->acp_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
writel(high, adata->acp_base + ACP_SCRATCH_REG_0 + val + 4);
/* Move to next physically contiguous page */
val += 8;
addr += PAGE_SIZE;
}
}
static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0);
struct snd_pcm_runtime *runtime = substream->runtime;
struct device *dev = component->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
struct acp_stream *stream;
int stream_id = cpu_dai->driver->id * 2 + substream->stream;
int ret;
stream = kzalloc(sizeof(*stream), GFP_KERNEL);
if (!stream)
return -ENOMEM;
stream->substream = substream;
adata->stream[stream_id] = stream;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = acp_pcm_hardware_playback;
else
runtime->hw = acp_pcm_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(stream);
return ret;
}
runtime->private_data = stream;
writel(1, adata->acp_base + ACP_EXTERNAL_INTR_ENB);
return ret;
}
static int acp_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
struct acp_dev_data *adata = snd_soc_component_get_drvdata(component);
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0);
struct acp_stream *stream = substream->runtime->private_data;
int stream_id = cpu_dai->driver->id * 2 + substream->stream;
u64 size = params_buffer_bytes(params);
/* Configure ACP DMA block with params */
config_pte_for_stream(adata, stream);
config_acp_dma(adata, stream_id, size);
return 0;
}
static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct device *dev = component->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
struct acp_stream *stream = substream->runtime->private_data;
u32 pos, buffersize;
u64 bytescount;
buffersize = frames_to_bytes(substream->runtime,
substream->runtime->buffer_size);
bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream);
if (bytescount > stream->bytescount)
bytescount -= stream->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(substream->runtime, pos);
}
static int acp_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, MIN_BUFFER, MAX_BUFFER);
return 0;
}
static int acp_dma_mmap(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
return snd_pcm_lib_default_mmap(substream, vma);
}
static int acp_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0);
struct device *dev = component->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
struct acp_stream *stream;
int stream_id = cpu_dai->driver->id * 2 + substream->stream;
stream = adata->stream[stream_id];
kfree(stream);
adata->stream[stream_id] = NULL;
return 0;
}
static const struct snd_soc_component_driver acp_pcm_component = {
.name = DRV_NAME,
.open = acp_dma_open,
.close = acp_dma_close,
.hw_params = acp_dma_hw_params,
.pointer = acp_dma_pointer,
.mmap = acp_dma_mmap,
.pcm_construct = acp_dma_new,
};
int acp_platform_register(struct device *dev)
{
struct acp_dev_data *adata = dev_get_drvdata(dev);
struct snd_soc_dai_driver;
unsigned int status;
status = devm_request_irq(dev, adata->i2s_irq, i2s_irq_handler,
IRQF_SHARED, "ACP_I2S_IRQ", adata);
if (status) {
dev_err(dev, "ACP I2S IRQ request failed\n");
return status;
}
status = devm_snd_soc_register_component(dev, &acp_pcm_component,
adata->dai_driver,
adata->num_dai);
if (status) {
dev_err(dev, "Fail to register acp i2s component\n");
return status;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(acp_platform_register, SND_SOC_ACP_COMMON);
int acp_platform_unregister(struct device *dev)
{
struct acp_dev_data *adata = dev_get_drvdata(dev);
if (adata->mach_dev)
platform_device_unregister(adata->mach_dev);
return 0;
}
EXPORT_SYMBOL_NS_GPL(acp_platform_unregister, SND_SOC_ACP_COMMON);
MODULE_DESCRIPTION("AMD ACP PCM Driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS(DRV_NAME);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
//
/*
* Hardware interface for Renoir ACP block
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/dma-mapping.h>
#include "amd.h"
#define DRV_NAME "acp_asoc_renoir"
static struct snd_soc_acpi_codecs amp_rt1019 = {
.num_codecs = 1,
.codecs = {"10EC1019"}
};
static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp_machines[] = {
{
.id = "10EC5682",
.drv_name = "rn_rt5682_rt1019",
.machine_quirk = snd_soc_acpi_codec_list,
.quirk_data = &amp_rt1019,
},
{
.id = "AMDI1019",
.drv_name = "renoir-acp",
},
{},
};
static struct snd_soc_dai_driver acp_renoir_dai[] = {
{
.name = "acp-i2s-sp",
.id = I2S_SP_INSTANCE,
.playback = {
.stream_name = "I2S SP Playback",
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 96000,
},
.capture = {
.stream_name = "I2S SP Capture",
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.ops = &asoc_acp_cpu_dai_ops,
.probe = &asoc_acp_i2s_probe,
},
{
.name = "acp-i2s-bt",
.id = I2S_BT_INSTANCE,
.playback = {
.stream_name = "I2S BT Playback",
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 96000,
},
.capture = {
.stream_name = "I2S BT Capture",
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.ops = &asoc_acp_cpu_dai_ops,
.probe = &asoc_acp_i2s_probe,
},
};
static int renoir_audio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct acp_dev_data *adata;
struct resource *res;
adata = devm_kzalloc(dev, sizeof(struct acp_dev_data), GFP_KERNEL);
if (!adata)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acp_mem");
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!adata->acp_base)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "acp_dai_irq");
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
return -ENODEV;
}
adata->i2s_irq = res->start;
adata->dev = dev;
adata->dai_driver = acp_renoir_dai;
adata->num_dai = ARRAY_SIZE(acp_renoir_dai);
adata->machines = snd_soc_acpi_amd_acp_machines;
acp_machine_select(adata);
dev_set_drvdata(dev, adata);
acp_platform_register(dev);
return 0;
}
static int renoir_audio_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
acp_platform_unregister(dev);
return 0;
}
static struct platform_driver renoir_driver = {
.probe = renoir_audio_probe,
.remove = renoir_audio_remove,
.driver = {
.name = "acp_asoc_renoir",
},
};
module_platform_driver(renoir_driver);
MODULE_DESCRIPTION("AMD ACP Renoir Driver");
MODULE_IMPORT_NS(SND_SOC_ACP_COMMON);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS("platform:" DRV_NAME);
/*
* Machine driver for AMD ACP Audio engine using Realtek RT5645 codec
*
* Copyright 2017 Advanced Micro Devices, Inc.
*
* This file is modified from rt288 machine driver
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*
*/
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/acpi.h>
#include "../codecs/rt5645.h"
#define CZ_PLAT_CLK 24000000
static struct snd_soc_jack cz_jack;
static int cz_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
int ret = 0;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK,
CZ_PLAT_CLK, params_rate(params) * 512);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1,
params_rate(params) * 512, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
return ret;
}
return ret;
}
static int cz_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_card *card;
struct snd_soc_component *codec;
codec = asoc_rtd_to_codec(rtd, 0)->component;
card = rtd->card;
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADPHONE | SND_JACK_MICROPHONE |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3,
&cz_jack, NULL, 0);
if (ret) {
dev_err(card->dev, "HP jack creation failed %d\n", ret);
return ret;
}
rt5645_set_jack_detect(codec, &cz_jack, &cz_jack, &cz_jack);
return 0;
}
static const struct snd_soc_ops cz_aif1_ops = {
.hw_params = cz_aif1_hw_params,
};
SND_SOC_DAILINK_DEF(designware1,
DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.1.auto")));
SND_SOC_DAILINK_DEF(designware2,
DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.2.auto")));
SND_SOC_DAILINK_DEF(codec,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5650:00", "rt5645-aif1")));
SND_SOC_DAILINK_DEF(platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_audio_dma.0.auto")));
static struct snd_soc_dai_link cz_dai_rt5650[] = {
{
.name = "amd-rt5645-play",
.stream_name = "RT5645_AIF1",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.init = cz_init,
.ops = &cz_aif1_ops,
SND_SOC_DAILINK_REG(designware1, codec, platform),
},
{
.name = "amd-rt5645-cap",
.stream_name = "RT5645_AIF1",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.ops = &cz_aif1_ops,
SND_SOC_DAILINK_REG(designware2, codec, platform),
},
};
static const struct snd_soc_dapm_widget cz_widgets[] = {
SND_SOC_DAPM_HP("Headphones", NULL),
SND_SOC_DAPM_SPK("Speakers", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
};
static const struct snd_soc_dapm_route cz_audio_route[] = {
{"Headphones", NULL, "HPOL"},
{"Headphones", NULL, "HPOR"},
{"RECMIXL", NULL, "Headset Mic"},
{"RECMIXR", NULL, "Headset Mic"},
{"Speakers", NULL, "SPOL"},
{"Speakers", NULL, "SPOR"},
{"DMIC L2", NULL, "Int Mic"},
{"DMIC R2", NULL, "Int Mic"},
};
static const struct snd_kcontrol_new cz_mc_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphones"),
SOC_DAPM_PIN_SWITCH("Speakers"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
};
static struct snd_soc_card cz_card = {
.name = "acprt5650",
.owner = THIS_MODULE,
.dai_link = cz_dai_rt5650,
.num_links = ARRAY_SIZE(cz_dai_rt5650),
.dapm_widgets = cz_widgets,
.num_dapm_widgets = ARRAY_SIZE(cz_widgets),
.dapm_routes = cz_audio_route,
.num_dapm_routes = ARRAY_SIZE(cz_audio_route),
.controls = cz_mc_controls,
.num_controls = ARRAY_SIZE(cz_mc_controls),
};
static int cz_probe(struct platform_device *pdev)
{
int ret;
struct snd_soc_card *card;
card = &cz_card;
cz_card.dev = &pdev->dev;
platform_set_drvdata(pdev, card);
ret = devm_snd_soc_register_card(&pdev->dev, &cz_card);
if (ret) {
dev_err(&pdev->dev,
"devm_snd_soc_register_card(%s) failed: %d\n",
cz_card.name, ret);
return ret;
}
return 0;
}
#ifdef CONFIG_ACPI
static const struct acpi_device_id cz_audio_acpi_match[] = {
{ "AMDI1002", 0 },
{},
};
MODULE_DEVICE_TABLE(acpi, cz_audio_acpi_match);
#endif
static struct platform_driver cz_pcm_driver = {
.driver = {
.name = "cz-rt5645",
.acpi_match_table = ACPI_PTR(cz_audio_acpi_match),
.pm = &snd_soc_pm_ops,
},
.probe = cz_probe,
};
module_platform_driver(cz_pcm_driver);
MODULE_AUTHOR("akshu.agrawal@amd.com");
MODULE_DESCRIPTION("cz-rt5645 audio support");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
//
/*
* SOF Machine Driver Support for ACP HW block
*/
#include <sound/core.h>
#include <sound/pcm_params.h>
#include <sound/soc-acpi.h>
#include <sound/soc-dapm.h>
#include <linux/module.h>
#include "acp-mach.h"
static struct acp_card_drvdata sof_rt5682_rt1019_data = {
.hs_cpu_id = I2S_SP,
.amp_cpu_id = I2S_SP,
.dmic_cpu_id = DMIC,
.hs_codec_id = RT5682,
.amp_codec_id = RT1019,
.dmic_codec_id = DMIC,
.gpio_spkr_en = EN_SPKR_GPIO_GB,
};
static struct acp_card_drvdata sof_rt5682_max_data = {
.hs_cpu_id = I2S_SP,
.amp_cpu_id = I2S_SP,
.dmic_cpu_id = DMIC,
.hs_codec_id = RT5682,
.amp_codec_id = MAX98360A,
.dmic_codec_id = DMIC,
.gpio_spkr_en = EN_SPKR_GPIO_NONE,
};
static struct acp_card_drvdata sof_rt5682s_max_data = {
.hs_cpu_id = I2S_SP,
.amp_cpu_id = I2S_SP,
.dmic_cpu_id = DMIC,
.hs_codec_id = RT5682S,
.amp_codec_id = MAX98360A,
.dmic_codec_id = DMIC,
.gpio_spkr_en = EN_SPKR_GPIO_NONE,
};
static const struct snd_kcontrol_new acp_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Spk"),
SOC_DAPM_PIN_SWITCH("Left Spk"),
SOC_DAPM_PIN_SWITCH("Right Spk"),
};
static const struct snd_soc_dapm_widget acp_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Spk", event_spkr_handler),
SND_SOC_DAPM_SPK("Left Spk", event_spkr_handler),
SND_SOC_DAPM_SPK("Right Spk", event_spkr_handler),
};
static int acp_sof_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = NULL;
struct device *dev = &pdev->dev;
unsigned int spkr_gpio;
int ret;
if (!pdev->id_entry)
return -EINVAL;
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card)
return -ENOMEM;
card->dev = dev;
card->owner = THIS_MODULE;
card->name = pdev->id_entry->name;
card->dapm_widgets = acp_widgets;
card->num_dapm_widgets = ARRAY_SIZE(acp_widgets);
card->controls = acp_controls;
card->num_controls = ARRAY_SIZE(acp_controls);
card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data;
spkr_gpio = ((struct acp_card_drvdata *)(card->drvdata))->gpio_spkr_en;
acp_sofdsp_dai_links_create(card);
if (gpio_is_valid(spkr_gpio)) {
ret = devm_gpio_request(dev, spkr_gpio, "spkren");
if (ret) {
dev_err(dev, "(%s) gpio request failed: %d\n",
__func__, ret);
return ret;
}
gpio_direction_output(spkr_gpio, 0);
}
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev,
"devm_snd_soc_register_card(%s) failed: %d\n",
card->name, ret);
return ret;
}
return 0;
}
static const struct platform_device_id board_ids[] = {
{
.name = "rt5682-rt1019",
.driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data
},
{
.name = "rt5682-max",
.driver_data = (kernel_ulong_t)&sof_rt5682_max_data
},
{
.name = "rt5682s-max",
.driver_data = (kernel_ulong_t)&sof_rt5682s_max_data
},
{ }
};
static struct platform_driver acp_asoc_audio = {
.driver = {
.name = "sof_mach",
},
.probe = acp_sof_probe,
.id_table = board_ids,
};
module_platform_driver(acp_asoc_audio);
MODULE_IMPORT_NS(SND_SOC_AMD_MACH);
MODULE_DESCRIPTION("ACP chrome SOF audio support");
MODULE_ALIAS("platform:rt5682-rt1019");
MODULE_ALIAS("platform:rt5682-max");
MODULE_ALIAS("platform:rt5682s-max");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc.
//
// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
/*
* Hardware interface for generic AMD audio DSP ACP IP
*/
#include "../ops.h"
#include "acp-dsp-offset.h"
#include "acp.h"
#define PTE_GRP1_OFFSET 0x00000000
#define PTE_GRP2_OFFSET 0x00800000
#define PTE_GRP3_OFFSET 0x01000000
#define PTE_GRP4_OFFSET 0x01800000
#define PTE_GRP5_OFFSET 0x02000000
#define PTE_GRP6_OFFSET 0x02800000
#define PTE_GRP7_OFFSET 0x03000000
#define PTE_GRP8_OFFSET 0x03800000
int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream)
{
unsigned int pte_reg, pte_size, phy_addr_offset, index;
int stream_tag = stream->stream_tag;
u32 low, high, offset, reg_val;
dma_addr_t addr;
int page_idx;
switch (stream_tag) {
case 1:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_1;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1;
offset = offsetof(struct scratch_reg_conf, grp1_pte);
stream->reg_offset = PTE_GRP1_OFFSET;
break;
case 2:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_2;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2;
offset = offsetof(struct scratch_reg_conf, grp2_pte);
stream->reg_offset = PTE_GRP2_OFFSET;
break;
case 3:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_3;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3;
offset = offsetof(struct scratch_reg_conf, grp3_pte);
stream->reg_offset = PTE_GRP3_OFFSET;
break;
case 4:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_4;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4;
offset = offsetof(struct scratch_reg_conf, grp4_pte);
stream->reg_offset = PTE_GRP4_OFFSET;
break;
case 5:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5;
offset = offsetof(struct scratch_reg_conf, grp5_pte);
stream->reg_offset = PTE_GRP5_OFFSET;
break;
case 6:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_6;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6;
offset = offsetof(struct scratch_reg_conf, grp6_pte);
stream->reg_offset = PTE_GRP6_OFFSET;
break;
case 7:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_7;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7;
offset = offsetof(struct scratch_reg_conf, grp7_pte);
stream->reg_offset = PTE_GRP7_OFFSET;
break;
case 8:
pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_8;
pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8;
offset = offsetof(struct scratch_reg_conf, grp8_pte);
stream->reg_offset = PTE_GRP8_OFFSET;
break;
default:
dev_err(sdev->dev, "Invalid stream tag %d\n", stream_tag);
return -EINVAL;
}
/* write phy_addr in scratch memory */
phy_addr_offset = offsetof(struct scratch_reg_conf, reg_offset);
index = stream_tag - 1;
phy_addr_offset = phy_addr_offset + index * 4;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 +
phy_addr_offset, stream->reg_offset);
/* Group Enable */
reg_val = ACP_SRAM_PTE_OFFSET + offset;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_reg, reg_val | BIT(31));
snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_size, PAGE_SIZE_4K_ENABLE);
for (page_idx = 0; page_idx < stream->num_pages; page_idx++) {
addr = snd_sgbuf_get_addr(stream->dmab, page_idx * PAGE_SIZE);
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset, low);
high |= BIT(31);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset + 4, high);
/* Move to next physically contiguous page */
offset += 8;
}
return 0;
}
struct acp_dsp_stream *acp_dsp_stream_get(struct snd_sof_dev *sdev, int tag)
{
struct acp_dev_data *adata = sdev->pdata->hw_pdata;
struct acp_dsp_stream *stream = adata->stream_buf;
int i;
for (i = 0; i < ACP_MAX_STREAM; i++, stream++) {
if (stream->active)
continue;
/* return stream if tag not specified*/
if (!tag) {
stream->active = 1;
return stream;
}
/* check if this is the requested stream tag */
if (stream->stream_tag == tag) {
stream->active = 1;
return stream;
}
}
dev_err(sdev->dev, "stream %d active or no inactive stream\n", tag);
return NULL;
}
EXPORT_SYMBOL_NS(acp_dsp_stream_get, SND_SOC_SOF_AMD_COMMON);
int acp_dsp_stream_put(struct snd_sof_dev *sdev,
struct acp_dsp_stream *acp_stream)
{
struct acp_dev_data *adata = sdev->pdata->hw_pdata;
struct acp_dsp_stream *stream = adata->stream_buf;
int i;
/* Free an active stream */
for (i = 0; i < ACP_MAX_STREAM; i++, stream++) {
if (stream == acp_stream) {
stream->active = 0;
return 0;
}
}
dev_err(sdev->dev, "Cannot find active stream tag %d\n", acp_stream->stream_tag);
return -EINVAL;
}
EXPORT_SYMBOL_NS(acp_dsp_stream_put, SND_SOC_SOF_AMD_COMMON);
int acp_dsp_stream_init(struct snd_sof_dev *sdev)
{
struct acp_dev_data *adata = sdev->pdata->hw_pdata;
int i;
for (i = 0; i < ACP_MAX_STREAM; i++) {
adata->stream_buf[i].sdev = sdev;
adata->stream_buf[i].active = 0;
adata->stream_buf[i].stream_tag = i + 1;
}
return 0;
}
EXPORT_SYMBOL_NS(acp_dsp_stream_init, SND_SOC_SOF_AMD_COMMON);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved.
//
// Authors: Vishnuvardhanrao Ravuapati <vishnuvardhanrao.ravulapati@amd.com>
// V Sujith Kumar Reddy <Vsujithkumar.Reddy@amd.com>
/*This file support Host TRACE Logger driver callback for SOF FW */
#include "acp.h"
#define ACP_LOGGER_STREAM 8
#define NUM_PAGES 16
int acp_sof_trace_release(struct snd_sof_dev *sdev)
{
struct acp_dsp_stream *stream;
struct acp_dev_data *adata;
int ret;
adata = sdev->pdata->hw_pdata;
stream = adata->dtrace_stream;
ret = acp_dsp_stream_put(sdev, stream);
if (ret < 0) {
dev_err(sdev->dev, "Failed to release trace stream\n");
return ret;
}
adata->dtrace_stream = NULL;
return 0;
}
EXPORT_SYMBOL_NS(acp_sof_trace_release, SND_SOC_SOF_AMD_COMMON);
static int acp_sof_trace_prepare(struct snd_sof_dev *sdev,
struct sof_ipc_dma_trace_params_ext *params)
{
struct acp_dsp_stream *stream;
struct acp_dev_data *adata;
int ret;
adata = sdev->pdata->hw_pdata;
stream = adata->dtrace_stream;
stream->dmab = &sdev->dmatb;
stream->num_pages = NUM_PAGES;
ret = acp_dsp_stream_config(sdev, stream);
if (ret < 0) {
dev_err(sdev->dev, "Failed to configure trace stream\n");
return ret;
}
params->buffer.phy_addr = stream->reg_offset;
params->stream_tag = stream->stream_tag;
return 0;
}
int acp_sof_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag)
{
struct sof_ipc_dma_trace_params_ext *params;
struct acp_dsp_stream *stream;
struct acp_dev_data *adata;
int ret;
adata = sdev->pdata->hw_pdata;
stream = acp_dsp_stream_get(sdev, ACP_LOGGER_STREAM);
if (!stream)
return -ENODEV;
adata->dtrace_stream = stream;
params = container_of(stream_tag, struct sof_ipc_dma_trace_params_ext, stream_tag);
ret = acp_sof_trace_prepare(sdev, params);
if (ret < 0) {
acp_dsp_stream_put(sdev, stream);
return ret;
}
*stream_tag = stream->stream_tag;
return 0;
}
EXPORT_SYMBOL_NS(acp_sof_trace_init, SND_SOC_SOF_AMD_COMMON);
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved.
//
// Authors: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
// Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
/*
* Hardware interface for generic AMD ACP processor
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "../ops.h"
#include "acp.h"
#include "acp-dsp-offset.h"
static int smn_write(struct pci_dev *dev, u32 smn_addr, u32 data)
{
pci_write_config_dword(dev, 0x60, smn_addr);
pci_write_config_dword(dev, 0x64, data);
return 0;
}
static int smn_read(struct pci_dev *dev, u32 smn_addr, u32 *data)
{
pci_write_config_dword(dev, 0x60, smn_addr);
pci_read_config_dword(dev, 0x64, data);
return 0;
}
static void configure_acp_groupregisters(struct acp_dev_data *adata)
{
struct snd_sof_dev *sdev = adata->dev;
/* Group Enable */
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_BASE_ADDR_GRP_1,
ACP_SRAM_PTE_OFFSET | BIT(31));
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1,
PAGE_SIZE_4K_ENABLE);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID);
}
static void init_dma_descriptor(struct acp_dev_data *adata)
{
struct snd_sof_dev *sdev = adata->dev;
unsigned int addr;
addr = ACP_SRAM_PTE_OFFSET + offsetof(struct scratch_reg_conf, dma_desc);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DESC_BASE_ADDR, addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DESC_MAX_NUM_DSCR, ACP_MAX_DESC_CNT);
}
static void configure_dma_descriptor(struct acp_dev_data *adata, unsigned short idx,
struct dma_descriptor *dscr_info)
{
struct snd_sof_dev *sdev = adata->dev;
unsigned int offset;
offset = ACP_SCRATCH_REG_0 + offsetof(struct scratch_reg_conf, dma_desc) +
idx * sizeof(struct dma_descriptor);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset, dscr_info->src_addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset + 0x4, dscr_info->dest_addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset + 0x8, dscr_info->tx_cnt.u32_all);
}
static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch,
unsigned int idx, unsigned int dscr_count)
{
struct snd_sof_dev *sdev = adata->dev;
unsigned int val, status;
int ret;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32),
ACP_DMA_CH_RST | ACP_DMA_CH_GRACEFUL_RST_EN);
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_DMA_CH_RST_STS, val,
val & (1 << ch), ACP_REG_POLL_INTERVAL,
ACP_REG_POLL_TIMEOUT_US);
if (ret < 0) {
status = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_ERROR_STATUS);
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DMA_ERR_STS_0 + ch * sizeof(u32));
dev_err(sdev->dev, "ACP_DMA_ERR_STS :0x%x ACP_ERROR_STATUS :0x%x\n", val, status);
return ret;
}
snd_sof_dsp_write(sdev, ACP_DSP_BAR, (ACP_DMA_CNTL_0 + ch * sizeof(u32)), 0);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DSCR_CNT_0 + ch * sizeof(u32), dscr_count);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DSCR_STRT_IDX_0 + ch * sizeof(u32), idx);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_PRIO_0 + ch * sizeof(u32), 0);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32), ACP_DMA_CH_RUN);
return ret;
}
static int acpbus_dma_start(struct acp_dev_data *adata, unsigned int ch,
unsigned int dscr_count, struct dma_descriptor *dscr_info)
{
struct snd_sof_dev *sdev = adata->dev;
int ret;
u16 dscr;
if (!dscr_info || !dscr_count)
return -EINVAL;
for (dscr = 0; dscr < dscr_count; dscr++)
configure_dma_descriptor(adata, dscr, dscr_info++);
ret = config_dma_channel(adata, ch, 0, dscr_count);
if (ret < 0)
dev_err(sdev->dev, "config dma ch failed:%d\n", ret);
return ret;
}
int configure_and_run_dma(struct acp_dev_data *adata, unsigned int src_addr,
unsigned int dest_addr, int dsp_data_size)
{
struct snd_sof_dev *sdev = adata->dev;
unsigned int desc_count, index;
int ret;
for (desc_count = 0; desc_count < ACP_MAX_DESC && dsp_data_size >= 0;
desc_count++, dsp_data_size -= ACP_PAGE_SIZE) {
adata->dscr_info[desc_count].src_addr = src_addr + desc_count * ACP_PAGE_SIZE;
adata->dscr_info[desc_count].dest_addr = dest_addr + desc_count * ACP_PAGE_SIZE;
adata->dscr_info[desc_count].tx_cnt.bits.count = ACP_PAGE_SIZE;
if (dsp_data_size < ACP_PAGE_SIZE)
adata->dscr_info[desc_count].tx_cnt.bits.count = dsp_data_size;
}
ret = acpbus_dma_start(adata, 0, desc_count, adata->dscr_info);
if (ret)
dev_err(sdev->dev, "acpbus_dma_start failed\n");
/* Clear descriptor array */
for (index = 0; index < desc_count; index++)
memset(&adata->dscr_info[index], 0x00, sizeof(struct dma_descriptor));
return ret;
}
static int psp_fw_validate(struct acp_dev_data *adata)
{
struct snd_sof_dev *sdev = adata->dev;
int timeout;
u32 data;
smn_write(adata->smn_dev, MP0_C2PMSG_26_REG, MBOX_ACP_SHA_DMA_COMMAND);
for (timeout = ACP_PSP_TIMEOUT_COUNTER; timeout > 0; timeout--) {
msleep(20);
smn_read(adata->smn_dev, MP0_C2PMSG_26_REG, &data);
if (data & MBOX_READY_MASK)
return 0;
}
dev_err(sdev->dev, "FW validation timedout: status %x\n", data & MBOX_STATUS_MASK);
return -ETIMEDOUT;
}
int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr,
unsigned int start_addr, unsigned int dest_addr,
unsigned int image_length)
{
struct snd_sof_dev *sdev = adata->dev;
unsigned int tx_count, fw_qualifier, val;
int ret;
if (!image_addr) {
dev_err(sdev->dev, "SHA DMA image address is NULL\n");
return -EINVAL;
}
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD);
if (val & ACP_SHA_RUN) {
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET);
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD_STS,
val, val & ACP_SHA_RESET,
ACP_REG_POLL_INTERVAL,
ACP_REG_POLL_TIMEOUT_US);
if (ret < 0) {
dev_err(sdev->dev, "SHA DMA Failed to Reset\n");
return ret;
}
}
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_DESTINATION_ADDR, dest_addr);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_MSG_LENGTH, image_length);
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN);
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT,
tx_count, tx_count == image_length,
ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US);
if (ret < 0) {
dev_err(sdev->dev, "SHA DMA Failed to Transfer Length %x\n", tx_count);
return ret;
}
ret = psp_fw_validate(adata);
if (ret)
return ret;
fw_qualifier = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER);
if (!(fw_qualifier & DSP_FW_RUN_ENABLE)) {
dev_err(sdev->dev, "PSP validation failed\n");
return -EINVAL;
}
return 0;
}
int acp_dma_status(struct acp_dev_data *adata, unsigned char ch)
{
struct snd_sof_dev *sdev = adata->dev;
unsigned int val;
int ret = 0;
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32));
if (val & ACP_DMA_CH_RUN) {
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_DMA_CH_STS, val, !val,
ACP_REG_POLL_INTERVAL,
ACP_DMA_COMPLETE_TIMEOUT_US);
if (ret < 0)
dev_err(sdev->dev, "DMA_CHANNEL %d status timeout\n", ch);
}
return ret;
}
void memcpy_from_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *dst, size_t bytes)
{
unsigned int reg_offset = offset + ACP_SCRATCH_REG_0;
int i, j;
for (i = 0, j = 0; i < bytes; i = i + 4, j++)
dst[j] = snd_sof_dsp_read(sdev, ACP_DSP_BAR, reg_offset + i);
}
void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes)
{
unsigned int reg_offset = offset + ACP_SCRATCH_REG_0;
int i, j;
for (i = 0, j = 0; i < bytes; i = i + 4, j++)
snd_sof_dsp_write(sdev, ACP_DSP_BAR, reg_offset + i, src[j]);
}
static int acp_memory_init(struct snd_sof_dev *sdev)
{
struct acp_dev_data *adata = sdev->pdata->hw_pdata;
snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, ACP_DSP_SW_INTR_CNTL,
ACP_DSP_INTR_EN_MASK, ACP_DSP_INTR_EN_MASK);
configure_acp_groupregisters(adata);
init_dma_descriptor(adata);
return 0;
}
static irqreturn_t acp_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
unsigned int val;
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_EXTERNAL_INTR_STAT);
if (val & ACP_SHA_STAT) {
/* Clear SHA interrupt raised by PSP */
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_EXTERNAL_INTR_STAT, val);
return IRQ_HANDLED;
}
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DSP_SW_INTR_STAT);
if (val & ACP_DSP_TO_HOST_IRQ) {
sof_ops(sdev)->irq_thread(irq, sdev);
val |= ACP_DSP_TO_HOST_IRQ;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP_SW_INTR_STAT, val);
return IRQ_HANDLED;
}
return IRQ_NONE;
};
static irqreturn_t acp_irq_handler(int irq, void *dev_id)
{
struct snd_sof_dev *sdev = dev_id;
unsigned int val;
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DSP_SW_INTR_STAT);
if (val)
return IRQ_WAKE_THREAD;
return IRQ_NONE;
}
static int acp_power_on(struct snd_sof_dev *sdev)
{
unsigned int val;
int ret;
val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_PGFSM_STATUS);
if (val == ACP_POWERED_ON)
return 0;
if (val & ACP_PGFSM_STATUS_MASK)
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_PGFSM_CONTROL,
ACP_PGFSM_CNTL_POWER_ON_MASK);
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_PGFSM_STATUS, val, !val,
ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
if (ret < 0)
dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n");
return ret;
}
static int acp_reset(struct snd_sof_dev *sdev)
{
unsigned int val;
int ret;
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_ASSERT_RESET);
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val,
val & ACP_SOFT_RESET_DONE_MASK,
ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
if (ret < 0) {
dev_err(sdev->dev, "timeout asserting reset\n");
return ret;
}
snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_RELEASE_RESET);
ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val, !val,
ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
if (ret < 0)
dev_err(sdev->dev, "timeout in releasing reset\n");
return ret;
}
static int acp_init(struct snd_sof_dev *sdev)
{
int ret;
/* power on */
ret = acp_power_on(sdev);
if (ret) {
dev_err(sdev->dev, "ACP power on failed\n");
return ret;
}
/* Reset */
return acp_reset(sdev);
}
int amd_sof_acp_probe(struct snd_sof_dev *sdev)
{
struct pci_dev *pci = to_pci_dev(sdev->dev);
struct acp_dev_data *adata;
const struct sof_amd_acp_desc *chip;
unsigned int addr;
int ret;
adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data),
GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->dev = sdev;
addr = pci_resource_start(pci, ACP_DSP_BAR);
sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR));
if (!sdev->bar[ACP_DSP_BAR]) {
dev_err(sdev->dev, "ioremap error\n");
return -ENXIO;
}
pci_set_master(pci);
sdev->pdata->hw_pdata = adata;
chip = get_chip_info(sdev->pdata);
if (!chip) {
dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device);
return -EIO;
}
adata->smn_dev = pci_get_device(PCI_VENDOR_ID_AMD, chip->host_bridge_id, NULL);
if (!adata->smn_dev) {
dev_err(sdev->dev, "Failed to get host bridge device\n");
return -ENODEV;
}
sdev->ipc_irq = pci->irq;
ret = request_threaded_irq(sdev->ipc_irq, acp_irq_handler, acp_irq_thread,
IRQF_SHARED, "AudioDSP", sdev);
if (ret < 0) {
dev_err(sdev->dev, "failed to register IRQ %d\n",
sdev->ipc_irq);
pci_dev_put(adata->smn_dev);
return ret;
}
ret = acp_init(sdev);
if (ret < 0) {
free_irq(sdev->ipc_irq, sdev);
pci_dev_put(adata->smn_dev);
return ret;
}
acp_memory_init(sdev);
acp_dsp_stream_init(sdev);
return 0;
}
EXPORT_SYMBOL_NS(amd_sof_acp_probe, SND_SOC_SOF_AMD_COMMON);
int amd_sof_acp_remove(struct snd_sof_dev *sdev)
{
struct acp_dev_data *adata = sdev->pdata->hw_pdata;
if (adata->smn_dev)
pci_dev_put(adata->smn_dev);
if (sdev->ipc_irq)
free_irq(sdev->ipc_irq, sdev);
return acp_reset(sdev);
}
EXPORT_SYMBOL_NS(amd_sof_acp_remove, SND_SOC_SOF_AMD_COMMON);
MODULE_DESCRIPTION("AMD ACP sof driver");
MODULE_LICENSE("Dual BSD/GPL");
// SPDX-License-Identifier: GPL-2.0+
//
// AMD ALSA SoC PCM Driver
//
//Copyright 2016 Advanced Micro Devices, Inc.
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/dma-mapping.h>
#include "acp3x.h"
#define DRV_NAME "acp3x_i2s_playcap"
static int acp3x_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct i2s_dev_data *adata;
int mode;
adata = snd_soc_dai_get_drvdata(cpu_dai);
mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
switch (mode) {
case SND_SOC_DAIFMT_I2S:
adata->tdm_mode = TDM_DISABLE;
break;
case SND_SOC_DAIFMT_DSP_A:
adata->tdm_mode = TDM_ENABLE;
break;
default:
return -EINVAL;
}
return 0;
}
static int acp3x_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai,
u32 tx_mask, u32 rx_mask, int slots, int slot_width)
{
struct i2s_dev_data *adata;
u32 frm_len;
u16 slot_len;
adata = snd_soc_dai_get_drvdata(cpu_dai);
/* These values are as per Hardware Spec */
switch (slot_width) {
case SLOT_WIDTH_8:
slot_len = 8;
break;
case SLOT_WIDTH_16:
slot_len = 16;
break;
case SLOT_WIDTH_24:
slot_len = 24;
break;
case SLOT_WIDTH_32:
slot_len = 0;
break;
default:
return -EINVAL;
}
frm_len = FRM_LEN | (slots << 15) | (slot_len << 18);
adata->tdm_fmt = frm_len;
return 0;
}
static int acp3x_i2s_hwparams(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct i2s_stream_instance *rtd;
struct snd_soc_pcm_runtime *prtd;
struct snd_soc_card *card;
struct acp3x_platform_info *pinfo;
struct i2s_dev_data *adata;
u32 val;
u32 reg_val, frmt_reg;
prtd = asoc_substream_to_rtd(substream);
rtd = substream->runtime->private_data;
card = prtd->card;
adata = snd_soc_dai_get_drvdata(dai);
pinfo = snd_soc_card_get_drvdata(card);
if (pinfo) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rtd->i2s_instance = pinfo->play_i2s_instance;
else
rtd->i2s_instance = pinfo->cap_i2s_instance;
}
/* These values are as per Hardware Spec */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U8:
case SNDRV_PCM_FORMAT_S8:
rtd->xfer_resolution = 0x0;
break;
case SNDRV_PCM_FORMAT_S16_LE:
rtd->xfer_resolution = 0x02;
break;
case SNDRV_PCM_FORMAT_S24_LE:
rtd->xfer_resolution = 0x04;
break;
case SNDRV_PCM_FORMAT_S32_LE:
rtd->xfer_resolution = 0x05;
break;
default:
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_val = mmACP_BTTDM_ITER;
frmt_reg = mmACP_BTTDM_TXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = mmACP_I2STDM_ITER;
frmt_reg = mmACP_I2STDM_TXFRMT;
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_val = mmACP_BTTDM_IRER;
frmt_reg = mmACP_BTTDM_RXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = mmACP_I2STDM_IRER;
frmt_reg = mmACP_I2STDM_RXFRMT;
}
}
if (adata->tdm_mode) {
val = rv_readl(rtd->acp3x_base + reg_val);
rv_writel(val | 0x2, rtd->acp3x_base + reg_val);
rv_writel(adata->tdm_fmt, rtd->acp3x_base + frmt_reg);
}
val = rv_readl(rtd->acp3x_base + reg_val);
val &= ~ACP3x_ITER_IRER_SAMP_LEN_MASK;
val = val | (rtd->xfer_resolution << 3);
rv_writel(val, rtd->acp3x_base + reg_val);
return 0;
}
static int acp3x_i2s_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct i2s_stream_instance *rtd;
u32 ret, val, period_bytes, reg_val, ier_val, water_val;
u32 buf_size, buf_reg;
rtd = substream->runtime->private_data;
period_bytes = frames_to_bytes(substream->runtime,
substream->runtime->period_size);
buf_size = frames_to_bytes(substream->runtime,
substream->runtime->buffer_size);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
rtd->bytescount = acp_get_byte_count(rtd,
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
water_val =
mmACP_BT_TX_INTR_WATERMARK_SIZE;
reg_val = mmACP_BTTDM_ITER;
ier_val = mmACP_BTTDM_IER;
buf_reg = mmACP_BT_TX_RINGBUFSIZE;
break;
case I2S_SP_INSTANCE:
default:
water_val =
mmACP_I2S_TX_INTR_WATERMARK_SIZE;
reg_val = mmACP_I2STDM_ITER;
ier_val = mmACP_I2STDM_IER;
buf_reg = mmACP_I2S_TX_RINGBUFSIZE;
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
water_val =
mmACP_BT_RX_INTR_WATERMARK_SIZE;
reg_val = mmACP_BTTDM_IRER;
ier_val = mmACP_BTTDM_IER;
buf_reg = mmACP_BT_RX_RINGBUFSIZE;
break;
case I2S_SP_INSTANCE:
default:
water_val =
mmACP_I2S_RX_INTR_WATERMARK_SIZE;
reg_val = mmACP_I2STDM_IRER;
ier_val = mmACP_I2STDM_IER;
buf_reg = mmACP_I2S_RX_RINGBUFSIZE;
}
}
rv_writel(period_bytes, rtd->acp3x_base + water_val);
rv_writel(buf_size, rtd->acp3x_base + buf_reg);
val = rv_readl(rtd->acp3x_base + reg_val);
val = val | BIT(0);
rv_writel(val, rtd->acp3x_base + reg_val);
rv_writel(1, rtd->acp3x_base + ier_val);
ret = 0;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_val = mmACP_BTTDM_ITER;
break;
case I2S_SP_INSTANCE:
default:
reg_val = mmACP_I2STDM_ITER;
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_val = mmACP_BTTDM_IRER;
break;
case I2S_SP_INSTANCE:
default:
reg_val = mmACP_I2STDM_IRER;
}
}
val = rv_readl(rtd->acp3x_base + reg_val);
val = val & ~BIT(0);
rv_writel(val, rtd->acp3x_base + reg_val);
if (!(rv_readl(rtd->acp3x_base + mmACP_BTTDM_ITER) & BIT(0)) &&
!(rv_readl(rtd->acp3x_base + mmACP_BTTDM_IRER) & BIT(0)))
rv_writel(0, rtd->acp3x_base + mmACP_BTTDM_IER);
if (!(rv_readl(rtd->acp3x_base + mmACP_I2STDM_ITER) & BIT(0)) &&
!(rv_readl(rtd->acp3x_base + mmACP_I2STDM_IRER) & BIT(0)))
rv_writel(0, rtd->acp3x_base + mmACP_I2STDM_IER);
ret = 0;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct snd_soc_dai_ops acp3x_i2s_dai_ops = {
.hw_params = acp3x_i2s_hwparams,
.trigger = acp3x_i2s_trigger,
.set_fmt = acp3x_i2s_set_fmt,
.set_tdm_slot = acp3x_i2s_set_tdm_slot,
};
static const struct snd_soc_component_driver acp3x_dai_component = {
.name = DRV_NAME,
};
static struct snd_soc_dai_driver acp3x_i2s_dai = {
.playback = {
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 96000,
},
.capture = {
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.ops = &acp3x_i2s_dai_ops,
};
static int acp3x_dai_probe(struct platform_device *pdev)
{
struct resource *res;
struct i2s_dev_data *adata;
int ret;
adata = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dev_data),
GFP_KERNEL);
if (!adata)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENOMEM;
}
adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!adata->acp3x_base)
return -ENOMEM;
adata->i2s_irq = res->start;
dev_set_drvdata(&pdev->dev, adata);
ret = devm_snd_soc_register_component(&pdev->dev,
&acp3x_dai_component, &acp3x_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Fail to register acp i2s dai\n");
return -ENODEV;
}
return 0;
}
static int acp3x_dai_remove(struct platform_device *pdev)
{
/* As we use devm_ memory alloc there is nothing TBD here */
return 0;
}
static struct platform_driver acp3x_dai_driver = {
.probe = acp3x_dai_probe,
.remove = acp3x_dai_remove,
.driver = {
.name = "acp3x_i2s_playcap",
},
};
module_platform_driver(acp3x_dai_driver);
MODULE_AUTHOR("Vishnuvardhanrao.Ravulapati@amd.com");
MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:"DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
//
// AMD ALSA SoC PCM Driver
//
//Copyright 2016 Advanced Micro Devices, Inc.
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "acp3x.h"
#define DRV_NAME "acp3x_rv_i2s_dma"
static const struct snd_pcm_hardware acp3x_pcm_hardware_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_96000,
.rate_min = 8000,
.rate_max = 96000,
.buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = PLAYBACK_MIN_NUM_PERIODS,
.periods_max = PLAYBACK_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp3x_pcm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
{
struct i2s_dev_data *rv_i2s_data;
u16 play_flag, cap_flag;
u32 val;
rv_i2s_data = dev_id;
if (!rv_i2s_data)
return IRQ_NONE;
play_flag = 0;
cap_flag = 0;
val = rv_readl(rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
if ((val & BIT(BT_TX_THRESHOLD)) && rv_i2s_data->play_stream) {
rv_writel(BIT(BT_TX_THRESHOLD), rv_i2s_data->acp3x_base +
mmACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(rv_i2s_data->play_stream);
play_flag = 1;
}
if ((val & BIT(I2S_TX_THRESHOLD)) &&
rv_i2s_data->i2ssp_play_stream) {
rv_writel(BIT(I2S_TX_THRESHOLD),
rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(rv_i2s_data->i2ssp_play_stream);
play_flag = 1;
}
if ((val & BIT(BT_RX_THRESHOLD)) && rv_i2s_data->capture_stream) {
rv_writel(BIT(BT_RX_THRESHOLD), rv_i2s_data->acp3x_base +
mmACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(rv_i2s_data->capture_stream);
cap_flag = 1;
}
if ((val & BIT(I2S_RX_THRESHOLD)) &&
rv_i2s_data->i2ssp_capture_stream) {
rv_writel(BIT(I2S_RX_THRESHOLD),
rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(rv_i2s_data->i2ssp_capture_stream);
cap_flag = 1;
}
if (play_flag | cap_flag)
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static void config_acp3x_dma(struct i2s_stream_instance *rtd, int direction)
{
u16 page_idx;
u32 low, high, val, acp_fifo_addr, reg_fifo_addr;
u32 reg_dma_size, reg_fifo_size;
dma_addr_t addr;
addr = rtd->dma_addr;
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
val = ACP_SRAM_BT_PB_PTE_OFFSET;
break;
case I2S_SP_INSTANCE:
default:
val = ACP_SRAM_SP_PB_PTE_OFFSET;
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
val = ACP_SRAM_BT_CP_PTE_OFFSET;
break;
case I2S_SP_INSTANCE:
default:
val = ACP_SRAM_SP_CP_PTE_OFFSET;
}
}
/* Group Enable */
rv_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp3x_base +
mmACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
rv_writel(PAGE_SIZE_4K_ENABLE, rtd->acp3x_base +
mmACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);
for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
rv_writel(low, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val);
high |= BIT(31);
rv_writel(high, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val
+ 4);
/* Move to next physically contiguous page */
val += 8;
addr += PAGE_SIZE;
}
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_dma_size = mmACP_BT_TX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
BT_PB_FIFO_ADDR_OFFSET;
reg_fifo_addr = mmACP_BT_TX_FIFOADDR;
reg_fifo_size = mmACP_BT_TX_FIFOSIZE;
rv_writel(I2S_BT_TX_MEM_WINDOW_START,
rtd->acp3x_base + mmACP_BT_TX_RINGBUFADDR);
break;
case I2S_SP_INSTANCE:
default:
reg_dma_size = mmACP_I2S_TX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
SP_PB_FIFO_ADDR_OFFSET;
reg_fifo_addr = mmACP_I2S_TX_FIFOADDR;
reg_fifo_size = mmACP_I2S_TX_FIFOSIZE;
rv_writel(I2S_SP_TX_MEM_WINDOW_START,
rtd->acp3x_base + mmACP_I2S_TX_RINGBUFADDR);
}
} else {
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_dma_size = mmACP_BT_RX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
BT_CAPT_FIFO_ADDR_OFFSET;
reg_fifo_addr = mmACP_BT_RX_FIFOADDR;
reg_fifo_size = mmACP_BT_RX_FIFOSIZE;
rv_writel(I2S_BT_RX_MEM_WINDOW_START,
rtd->acp3x_base + mmACP_BT_RX_RINGBUFADDR);
break;
case I2S_SP_INSTANCE:
default:
reg_dma_size = mmACP_I2S_RX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
SP_CAPT_FIFO_ADDR_OFFSET;
reg_fifo_addr = mmACP_I2S_RX_FIFOADDR;
reg_fifo_size = mmACP_I2S_RX_FIFOSIZE;
rv_writel(I2S_SP_RX_MEM_WINDOW_START,
rtd->acp3x_base + mmACP_I2S_RX_RINGBUFADDR);
}
}
rv_writel(DMA_SIZE, rtd->acp3x_base + reg_dma_size);
rv_writel(acp_fifo_addr, rtd->acp3x_base + reg_fifo_addr);
rv_writel(FIFO_SIZE, rtd->acp3x_base + reg_fifo_size);
rv_writel(BIT(I2S_RX_THRESHOLD) | BIT(BT_RX_THRESHOLD)
| BIT(I2S_TX_THRESHOLD) | BIT(BT_TX_THRESHOLD),
rtd->acp3x_base + mmACP_EXTERNAL_INTR_CNTL);
}
static int acp3x_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
struct snd_soc_pcm_runtime *prtd;
struct i2s_dev_data *adata;
struct i2s_stream_instance *i2s_data;
int ret;
runtime = substream->runtime;
prtd = asoc_substream_to_rtd(substream);
component = snd_soc_rtdcom_lookup(prtd, DRV_NAME);
adata = dev_get_drvdata(component->dev);
i2s_data = kzalloc(sizeof(*i2s_data), GFP_KERNEL);
if (!i2s_data)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = acp3x_pcm_hardware_playback;
else
runtime->hw = acp3x_pcm_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(i2s_data);
return ret;
}
i2s_data->acp3x_base = adata->acp3x_base;
runtime->private_data = i2s_data;
return ret;
}
static int acp3x_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct i2s_stream_instance *rtd;
struct snd_soc_pcm_runtime *prtd;
struct snd_soc_card *card;
struct acp3x_platform_info *pinfo;
struct i2s_dev_data *adata;
u64 size;
prtd = asoc_substream_to_rtd(substream);
card = prtd->card;
pinfo = snd_soc_card_get_drvdata(card);
adata = dev_get_drvdata(component->dev);
rtd = substream->runtime->private_data;
if (!rtd)
return -EINVAL;
if (pinfo) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
rtd->i2s_instance = pinfo->play_i2s_instance;
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
adata->play_stream = substream;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_play_stream = substream;
}
} else {
rtd->i2s_instance = pinfo->cap_i2s_instance;
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
adata->capture_stream = substream;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_capture_stream = substream;
}
}
} else {
pr_err("pinfo failed\n");
}
size = params_buffer_bytes(params);
rtd->dma_addr = substream->runtime->dma_addr;
rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
config_acp3x_dma(rtd, substream->stream);
return 0;
}
static snd_pcm_uframes_t acp3x_dma_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct i2s_stream_instance *rtd;
u32 pos;
u32 buffersize;
u64 bytescount;
rtd = substream->runtime->private_data;
buffersize = frames_to_bytes(substream->runtime,
substream->runtime->buffer_size);
bytescount = acp_get_byte_count(rtd, substream->stream);
if (bytescount > rtd->bytescount)
bytescount -= rtd->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(substream->runtime, pos);
}
static int acp3x_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, MIN_BUFFER, MAX_BUFFER);
return 0;
}
static int acp3x_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *prtd;
struct i2s_dev_data *adata;
struct i2s_stream_instance *ins;
prtd = asoc_substream_to_rtd(substream);
component = snd_soc_rtdcom_lookup(prtd, DRV_NAME);
adata = dev_get_drvdata(component->dev);
ins = substream->runtime->private_data;
if (!ins)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (ins->i2s_instance) {
case I2S_BT_INSTANCE:
adata->play_stream = NULL;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_play_stream = NULL;
}
} else {
switch (ins->i2s_instance) {
case I2S_BT_INSTANCE:
adata->capture_stream = NULL;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_capture_stream = NULL;
}
}
return 0;
}
static const struct snd_soc_component_driver acp3x_i2s_component = {
.name = DRV_NAME,
.open = acp3x_dma_open,
.close = acp3x_dma_close,
.hw_params = acp3x_dma_hw_params,
.pointer = acp3x_dma_pointer,
.pcm_construct = acp3x_dma_new,
};
static int acp3x_audio_probe(struct platform_device *pdev)
{
struct resource *res;
struct i2s_dev_data *adata;
unsigned int irqflags;
int status;
if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
}
irqflags = *((unsigned int *)(pdev->dev.platform_data));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!adata->acp3x_base)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
return -ENODEV;
}
adata->i2s_irq = res->start;
dev_set_drvdata(&pdev->dev, adata);
status = devm_snd_soc_register_component(&pdev->dev,
&acp3x_i2s_component,
NULL, 0);
if (status) {
dev_err(&pdev->dev, "Fail to register acp i2s component\n");
return -ENODEV;
}
status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
irqflags, "ACP3x_I2S_IRQ", adata);
if (status) {
dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n");
return -ENODEV;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static int acp3x_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
static int acp3x_resume(struct device *dev)
{
struct i2s_dev_data *adata;
u32 val, reg_val, frmt_val;
reg_val = 0;
frmt_val = 0;
adata = dev_get_drvdata(dev);
if (adata->play_stream && adata->play_stream->runtime) {
struct i2s_stream_instance *rtd =
adata->play_stream->runtime->private_data;
config_acp3x_dma(rtd, SNDRV_PCM_STREAM_PLAYBACK);
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_val = mmACP_BTTDM_ITER;
frmt_val = mmACP_BTTDM_TXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = mmACP_I2STDM_ITER;
frmt_val = mmACP_I2STDM_TXFRMT;
}
rv_writel((rtd->xfer_resolution << 3),
rtd->acp3x_base + reg_val);
}
if (adata->capture_stream && adata->capture_stream->runtime) {
struct i2s_stream_instance *rtd =
adata->capture_stream->runtime->private_data;
config_acp3x_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
switch (rtd->i2s_instance) {
case I2S_BT_INSTANCE:
reg_val = mmACP_BTTDM_IRER;
frmt_val = mmACP_BTTDM_RXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = mmACP_I2STDM_IRER;
frmt_val = mmACP_I2STDM_RXFRMT;
}
rv_writel((rtd->xfer_resolution << 3),
rtd->acp3x_base + reg_val);
}
if (adata->tdm_mode == TDM_ENABLE) {
rv_writel(adata->tdm_fmt, adata->acp3x_base + frmt_val);
val = rv_readl(adata->acp3x_base + reg_val);
rv_writel(val | 0x2, adata->acp3x_base + reg_val);
}
rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
return 0;
}
static int acp3x_pcm_runtime_suspend(struct device *dev)
{
struct i2s_dev_data *adata;
adata = dev_get_drvdata(dev);
rv_writel(0, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
return 0;
}
static int acp3x_pcm_runtime_resume(struct device *dev)
{
struct i2s_dev_data *adata;
adata = dev_get_drvdata(dev);
rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
return 0;
}
static const struct dev_pm_ops acp3x_pm_ops = {
.runtime_suspend = acp3x_pcm_runtime_suspend,
.runtime_resume = acp3x_pcm_runtime_resume,
.resume = acp3x_resume,
};
static struct platform_driver acp3x_dma_driver = {
.probe = acp3x_audio_probe,
.remove = acp3x_audio_remove,
.driver = {
.name = "acp3x_rv_i2s_dma",
.pm = &acp3x_pm_ops,
},
};
module_platform_driver(acp3x_dma_driver);
MODULE_AUTHOR("Vishnuvardhanrao.Ravulapati@amd.com");
MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com");
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:"DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
//
// AMD ALSA SoC PDM Driver
//
//Copyright 2020 Advanced Micro Devices, Inc.
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "rn_acp3x.h"
#define DRV_NAME "acp_rn_pdm_dma"
static const struct snd_pcm_hardware acp_pdm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static irqreturn_t pdm_irq_handler(int irq, void *dev_id)
{
struct pdm_dev_data *rn_pdm_data;
u16 cap_flag;
u32 val;
rn_pdm_data = dev_id;
if (!rn_pdm_data)
return IRQ_NONE;
cap_flag = 0;
val = rn_readl(rn_pdm_data->acp_base + ACP_EXTERNAL_INTR_STAT);
if ((val & BIT(PDM_DMA_STAT)) && rn_pdm_data->capture_stream) {
rn_writel(BIT(PDM_DMA_STAT), rn_pdm_data->acp_base +
ACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(rn_pdm_data->capture_stream);
cap_flag = 1;
}
if (cap_flag)
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static void init_pdm_ring_buffer(u32 physical_addr,
u32 buffer_size,
u32 watermark_size,
void __iomem *acp_base)
{
rn_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR);
rn_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE);
rn_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE);
rn_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL);
}
static void enable_pdm_clock(void __iomem *acp_base)
{
u32 pdm_clk_enable, pdm_ctrl;
pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK;
rn_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL);
pdm_ctrl = rn_readl(acp_base + ACP_WOV_MISC_CTRL);
pdm_ctrl |= ACP_WOV_MISC_CTRL_MASK;
rn_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL);
}
static void enable_pdm_interrupts(void __iomem *acp_base)
{
u32 ext_int_ctrl;
ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl |= PDM_DMA_INTR_MASK;
rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL);
}
static void disable_pdm_interrupts(void __iomem *acp_base)
{
u32 ext_int_ctrl;
ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl |= ~PDM_DMA_INTR_MASK;
rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL);
}
static bool check_pdm_dma_status(void __iomem *acp_base)
{
bool pdm_dma_status;
u32 pdm_enable, pdm_dma_enable;
pdm_dma_status = false;
pdm_enable = rn_readl(acp_base + ACP_WOV_PDM_ENABLE);
pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable &
ACP_PDM_DMA_EN_STATUS))
pdm_dma_status = true;
return pdm_dma_status;
}
static int start_pdm_dma(void __iomem *acp_base)
{
u32 pdm_enable;
u32 pdm_dma_enable;
int timeout;
pdm_enable = 0x01;
pdm_dma_enable = 0x01;
enable_pdm_clock(acp_base);
rn_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
rn_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
timeout = 0;
while (++timeout < ACP_COUNTER) {
pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS)
return 0;
udelay(DELAY_US);
}
return -ETIMEDOUT;
}
static int stop_pdm_dma(void __iomem *acp_base)
{
u32 pdm_enable, pdm_dma_enable;
int timeout;
pdm_enable = rn_readl(acp_base + ACP_WOV_PDM_ENABLE);
pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if (pdm_dma_enable & 0x01) {
pdm_dma_enable = 0x02;
rn_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
timeout = 0;
while (++timeout < ACP_COUNTER) {
pdm_dma_enable = rn_readl(acp_base +
ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_dma_enable & 0x02) == 0x00)
break;
udelay(DELAY_US);
}
if (timeout == ACP_COUNTER)
return -ETIMEDOUT;
}
if (pdm_enable == ACP_PDM_ENABLE) {
pdm_enable = ACP_PDM_DISABLE;
rn_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
}
rn_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH);
return 0;
}
static void config_acp_dma(struct pdm_stream_instance *rtd, int direction)
{
u16 page_idx;
u32 low, high, val;
dma_addr_t addr;
addr = rtd->dma_addr;
val = 0;
/* Group Enable */
rn_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp_base +
ACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
rn_writel(PAGE_SIZE_4K_ENABLE, rtd->acp_base +
ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);
for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
rn_writel(low, rtd->acp_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
rn_writel(high, rtd->acp_base + ACP_SCRATCH_REG_0 + val + 4);
val += 8;
addr += PAGE_SIZE;
}
}
static int acp_pdm_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
struct pdm_dev_data *adata;
struct pdm_stream_instance *pdm_data;
int ret;
runtime = substream->runtime;
adata = dev_get_drvdata(component->dev);
pdm_data = kzalloc(sizeof(*pdm_data), GFP_KERNEL);
if (!pdm_data)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw = acp_pdm_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(pdm_data);
return ret;
}
enable_pdm_interrupts(adata->acp_base);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
adata->capture_stream = substream;
pdm_data->acp_base = adata->acp_base;
runtime->private_data = pdm_data;
return ret;
}
static int acp_pdm_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct pdm_stream_instance *rtd;
size_t size, period_bytes;
rtd = substream->runtime->private_data;
if (!rtd)
return -EINVAL;
size = params_buffer_bytes(params);
period_bytes = params_period_bytes(params);
rtd->dma_addr = substream->runtime->dma_addr;
rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
config_acp_dma(rtd, substream->stream);
init_pdm_ring_buffer(MEM_WINDOW_START, size, period_bytes,
rtd->acp_base);
return 0;
}
static u64 acp_pdm_get_byte_count(struct pdm_stream_instance *rtd,
int direction)
{
union acp_pdm_dma_count byte_count;
byte_count.bcount.high =
rn_readl(rtd->acp_base +
ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH);
byte_count.bcount.low =
rn_readl(rtd->acp_base +
ACP_WOV_RX_LINEARPOSITIONCNTR_LOW);
return byte_count.bytescount;
}
static snd_pcm_uframes_t acp_pdm_dma_pointer(struct snd_soc_component *comp,
struct snd_pcm_substream *stream)
{
struct pdm_stream_instance *rtd;
u32 pos, buffersize;
u64 bytescount;
rtd = stream->runtime->private_data;
buffersize = frames_to_bytes(stream->runtime,
stream->runtime->buffer_size);
bytescount = acp_pdm_get_byte_count(rtd, stream->stream);
if (bytescount > rtd->bytescount)
bytescount -= rtd->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(stream->runtime, pos);
}
static int acp_pdm_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, MIN_BUFFER, MAX_BUFFER);
return 0;
}
static int acp_pdm_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct pdm_dev_data *adata = dev_get_drvdata(component->dev);
disable_pdm_interrupts(adata->acp_base);
adata->capture_stream = NULL;
return 0;
}
static int acp_pdm_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct pdm_stream_instance *rtd;
int ret;
bool pdm_status;
unsigned int ch_mask;
rtd = substream->runtime->private_data;
ret = 0;
switch (substream->runtime->channels) {
case TWO_CH:
ch_mask = 0x00;
break;
default:
return -EINVAL;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
rn_writel(ch_mask, rtd->acp_base + ACP_WOV_PDM_NO_OF_CHANNELS);
rn_writel(PDM_DECIMATION_FACTOR, rtd->acp_base +
ACP_WOV_PDM_DECIMATION_FACTOR);
rtd->bytescount = acp_pdm_get_byte_count(rtd,
substream->stream);
pdm_status = check_pdm_dma_status(rtd->acp_base);
if (!pdm_status)
ret = start_pdm_dma(rtd->acp_base);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pdm_status = check_pdm_dma_status(rtd->acp_base);
if (pdm_status)
ret = stop_pdm_dma(rtd->acp_base);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct snd_soc_dai_ops acp_pdm_dai_ops = {
.trigger = acp_pdm_dai_trigger,
};
static struct snd_soc_dai_driver acp_pdm_dai_driver = {
.capture = {
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 48000,
.rate_max = 48000,
},
.ops = &acp_pdm_dai_ops,
};
static const struct snd_soc_component_driver acp_pdm_component = {
.name = DRV_NAME,
.open = acp_pdm_dma_open,
.close = acp_pdm_dma_close,
.hw_params = acp_pdm_dma_hw_params,
.pointer = acp_pdm_dma_pointer,
.pcm_construct = acp_pdm_dma_new,
};
static int acp_pdm_audio_probe(struct platform_device *pdev)
{
struct resource *res;
struct pdm_dev_data *adata;
unsigned int irqflags;
int status;
if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
}
irqflags = *((unsigned int *)(pdev->dev.platform_data));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->acp_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!adata->acp_base)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
return -ENODEV;
}
adata->pdm_irq = res->start;
adata->capture_stream = NULL;
dev_set_drvdata(&pdev->dev, adata);
status = devm_snd_soc_register_component(&pdev->dev,
&acp_pdm_component,
&acp_pdm_dai_driver, 1);
if (status) {
dev_err(&pdev->dev, "Fail to register acp pdm dai\n");
return -ENODEV;
}
status = devm_request_irq(&pdev->dev, adata->pdm_irq, pdm_irq_handler,
irqflags, "ACP_PDM_IRQ", adata);
if (status) {
dev_err(&pdev->dev, "ACP PDM IRQ request failed\n");
return -ENODEV;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static int acp_pdm_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
static int acp_pdm_resume(struct device *dev)
{
struct pdm_dev_data *adata;
struct snd_pcm_runtime *runtime;
struct pdm_stream_instance *rtd;
u32 period_bytes, buffer_len;
adata = dev_get_drvdata(dev);
if (adata->capture_stream && adata->capture_stream->runtime) {
runtime = adata->capture_stream->runtime;
rtd = runtime->private_data;
period_bytes = frames_to_bytes(runtime, runtime->period_size);
buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
config_acp_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
init_pdm_ring_buffer(MEM_WINDOW_START, buffer_len, period_bytes,
adata->acp_base);
}
enable_pdm_interrupts(adata->acp_base);
return 0;
}
static int acp_pdm_runtime_suspend(struct device *dev)
{
struct pdm_dev_data *adata;
adata = dev_get_drvdata(dev);
disable_pdm_interrupts(adata->acp_base);
return 0;
}
static int acp_pdm_runtime_resume(struct device *dev)
{
struct pdm_dev_data *adata;
adata = dev_get_drvdata(dev);
enable_pdm_interrupts(adata->acp_base);
return 0;
}
static const struct dev_pm_ops acp_pdm_pm_ops = {
.runtime_suspend = acp_pdm_runtime_suspend,
.runtime_resume = acp_pdm_runtime_resume,
.resume = acp_pdm_resume,
};
static struct platform_driver acp_pdm_dma_driver = {
.probe = acp_pdm_audio_probe,
.remove = acp_pdm_audio_remove,
.driver = {
.name = "acp_rn_pdm_dma",
.pm = &acp_pdm_pm_ops,
},
};
module_platform_driver(acp_pdm_dma_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD ACP3x Renior PDM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
//
// Machine driver for AMD Renoir platform using DMIC
//
//Copyright 2020 Advanced Micro Devices, Inc.
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/io.h>
#include "rn_acp3x.h"
#define DRV_NAME "acp_pdm_mach"
SND_SOC_DAILINK_DEF(acp_pdm,
DAILINK_COMP_ARRAY(COMP_CPU("acp_rn_pdm_dma.0")));
SND_SOC_DAILINK_DEF(dmic_codec,
DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec.0",
"dmic-hifi")));
SND_SOC_DAILINK_DEF(platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_rn_pdm_dma.0")));
static struct snd_soc_dai_link acp_dai_pdm[] = {
{
.name = "acp3x-dmic-capture",
.stream_name = "DMIC capture",
.capture_only = 1,
SND_SOC_DAILINK_REG(acp_pdm, dmic_codec, platform),
},
};
static struct snd_soc_card acp_card = {
.name = "acp",
.owner = THIS_MODULE,
.dai_link = acp_dai_pdm,
.num_links = 1,
};
static int acp_probe(struct platform_device *pdev)
{
int ret;
struct acp_pdm *machine = NULL;
struct snd_soc_card *card;
card = &acp_card;
acp_card.dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card(%s) failed\n",
card->name);
}
return 0;
}
static struct platform_driver acp_mach_driver = {
.driver = {
.name = "acp_pdm_mach",
.pm = &snd_soc_pm_ops,
},
.probe = acp_probe,
};
module_platform_driver(acp_mach_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
//
// Machine driver for AMD ACP Audio engine using DA7219 & MAX98357 codec.
//
//Copyright 2016 Advanced Micro Devices, Inc.
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/acpi.h>
#include "raven/acp3x.h"
#include "../codecs/rt5682.h"
#include "../codecs/rt1015.h"
#define PCO_PLAT_CLK 48000000
#define RT5682_PLL_FREQ (48000 * 512)
#define DUAL_CHANNEL 2
static struct snd_soc_jack pco_jack;
static struct clk *rt5682_dai_wclk;
static struct clk *rt5682_dai_bclk;
static struct gpio_desc *dmic_sel;
void *soc_is_rltk_max(struct device *dev);
enum {
RT5682 = 0,
MAX,
EC,
};
static int acp3x_5682_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = codec_dai->component;
dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name);
/* set rt5682 dai fmt */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP);
if (ret < 0) {
dev_err(rtd->card->dev,
"Failed to set rt5682 dai fmt: %d\n", ret);
return ret;
}
/* set codec PLL */
ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK,
PCO_PLAT_CLK, RT5682_PLL_FREQ);
if (ret < 0) {
dev_err(rtd->dev, "can't set rt5682 PLL: %d\n", ret);
return ret;
}
/* Set codec sysclk */
ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2,
RT5682_PLL_FREQ, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->dev,
"Failed to set rt5682 SYSCLK: %d\n", ret);
return ret;
}
/* Set tdm/i2s1 master bclk ratio */
ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
if (ret < 0) {
dev_err(rtd->dev,
"Failed to set rt5682 tdm bclk ratio: %d\n", ret);
return ret;
}
rt5682_dai_wclk = clk_get(component->dev, "rt5682-dai-wclk");
rt5682_dai_bclk = clk_get(component->dev, "rt5682-dai-bclk");
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_LINEOUT |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3,
&pco_jack, NULL, 0);
if (ret) {
dev_err(card->dev, "HP jack creation failed %d\n", ret);
return ret;
}
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
ret = snd_soc_component_set_jack(component, &pco_jack, NULL);
if (ret) {
dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
return ret;
}
return ret;
}
static int rt5682_clk_enable(struct snd_pcm_substream *substream)
{
int ret = 0;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
/* RT5682 will support only 48K output with 48M mclk */
clk_set_rate(rt5682_dai_wclk, 48000);
clk_set_rate(rt5682_dai_bclk, 48000 * 64);
ret = clk_prepare_enable(rt5682_dai_wclk);
if (ret < 0) {
dev_err(rtd->dev, "can't enable wclk %d\n", ret);
return ret;
}
return ret;
}
static int acp3x_1015_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai;
int srate, i, ret;
ret = 0;
srate = params_rate(params);
for_each_rtd_codec_dais(rtd, i, codec_dai) {
if (strcmp(codec_dai->name, "rt1015-aif"))
continue;
ret = snd_soc_dai_set_pll(codec_dai, 0, RT1015_PLL_S_BCLK,
64 * srate, 256 * srate);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, RT1015_SCLK_S_PLL,
256 * srate, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
}
return ret;
}
static void rt5682_clk_disable(void)
{
clk_disable_unprepare(rt5682_dai_wclk);
}
static const unsigned int channels[] = {
DUAL_CHANNEL,
};
static const unsigned int rates[] = {
48000,
};
static const struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static const struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int acp3x_5682_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card);
machine->play_i2s_instance = I2S_SP_INSTANCE;
machine->cap_i2s_instance = I2S_SP_INSTANCE;
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
return rt5682_clk_enable(substream);
}
static int acp3x_max_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card);
machine->play_i2s_instance = I2S_BT_INSTANCE;
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
return rt5682_clk_enable(substream);
}
static int acp3x_ec_dmic0_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card);
machine->cap_i2s_instance = I2S_BT_INSTANCE;
snd_soc_dai_set_bclk_ratio(codec_dai, 64);
return rt5682_clk_enable(substream);
}
static int dmic_switch;
static int dmic_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = dmic_switch;
return 0;
}
static int dmic_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
if (dmic_sel) {
dmic_switch = ucontrol->value.integer.value[0];
gpiod_set_value(dmic_sel, dmic_switch);
}
return 0;
}
static void rt5682_shutdown(struct snd_pcm_substream *substream)
{
rt5682_clk_disable();
}
static const struct snd_soc_ops acp3x_5682_ops = {
.startup = acp3x_5682_startup,
.shutdown = rt5682_shutdown,
};
static const struct snd_soc_ops acp3x_max_play_ops = {
.startup = acp3x_max_startup,
.shutdown = rt5682_shutdown,
.hw_params = acp3x_1015_hw_params,
};
static const struct snd_soc_ops acp3x_ec_cap0_ops = {
.startup = acp3x_ec_dmic0_startup,
.shutdown = rt5682_shutdown,
};
SND_SOC_DAILINK_DEF(acp3x_i2s,
DAILINK_COMP_ARRAY(COMP_CPU("acp3x_i2s_playcap.0")));
SND_SOC_DAILINK_DEF(acp3x_bt,
DAILINK_COMP_ARRAY(COMP_CPU("acp3x_i2s_playcap.2")));
SND_SOC_DAILINK_DEF(rt5682,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1")));
SND_SOC_DAILINK_DEF(max,
DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", "HiFi")));
SND_SOC_DAILINK_DEF(rt1015p,
DAILINK_COMP_ARRAY(COMP_CODEC("RTL1015:00", "HiFi")));
SND_SOC_DAILINK_DEF(rt1015,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC1015:00", "rt1015-aif"),
COMP_CODEC("i2c-10EC1015:01", "rt1015-aif")));
SND_SOC_DAILINK_DEF(cros_ec,
DAILINK_COMP_ARRAY(COMP_CODEC("GOOG0013:00", "EC Codec I2S RX")));
SND_SOC_DAILINK_DEF(platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp3x_rv_i2s_dma.0")));
static struct snd_soc_codec_conf rt1015_conf[] = {
{
.dlc = COMP_CODEC_CONF("i2c-10EC1015:00"),
.name_prefix = "Left",
},
{
.dlc = COMP_CODEC_CONF("i2c-10EC1015:01"),
.name_prefix = "Right",
},
};
static struct snd_soc_dai_link acp3x_dai[] = {
[RT5682] = {
.name = "acp3x-5682-play",
.stream_name = "Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.init = acp3x_5682_init,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &acp3x_5682_ops,
SND_SOC_DAILINK_REG(acp3x_i2s, rt5682, platform),
},
[MAX] = {
.name = "acp3x-max98357-play",
.stream_name = "HiFi Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBC_CFC,
.dpcm_playback = 1,
.ops = &acp3x_max_play_ops,
.cpus = acp3x_bt,
.num_cpus = ARRAY_SIZE(acp3x_bt),
.platforms = platform,
.num_platforms = ARRAY_SIZE(platform),
},
[EC] = {
.name = "acp3x-ec-dmic0-capture",
.stream_name = "Capture DMIC0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBC_CFC,
.dpcm_capture = 1,
.ops = &acp3x_ec_cap0_ops,
SND_SOC_DAILINK_REG(acp3x_bt, cros_ec, platform),
},
};
static const char * const dmic_mux_text[] = {
"Front Mic",
"Rear Mic",
};
static SOC_ENUM_SINGLE_DECL(
acp3x_dmic_enum, SND_SOC_NOPM, 0, dmic_mux_text);
static const struct snd_kcontrol_new acp3x_dmic_mux_control =
SOC_DAPM_ENUM_EXT("DMIC Select Mux", acp3x_dmic_enum,
dmic_get, dmic_set);
static const struct snd_soc_dapm_widget acp3x_5682_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Spk", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0,
&acp3x_dmic_mux_control),
};
static const struct snd_soc_dapm_route acp3x_5682_audio_route[] = {
{"Headphone Jack", NULL, "HPOL"},
{"Headphone Jack", NULL, "HPOR"},
{"IN1P", NULL, "Headset Mic"},
{"Spk", NULL, "Speaker"},
{"Dmic Mux", "Front Mic", "DMIC"},
{"Dmic Mux", "Rear Mic", "DMIC"},
};
static const struct snd_kcontrol_new acp3x_5682_mc_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Spk"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
};
static struct snd_soc_card acp3x_5682 = {
.name = "acp3xalc5682m98357",
.owner = THIS_MODULE,
.dai_link = acp3x_dai,
.num_links = ARRAY_SIZE(acp3x_dai),
.dapm_widgets = acp3x_5682_widgets,
.num_dapm_widgets = ARRAY_SIZE(acp3x_5682_widgets),
.dapm_routes = acp3x_5682_audio_route,
.num_dapm_routes = ARRAY_SIZE(acp3x_5682_audio_route),
.controls = acp3x_5682_mc_controls,
.num_controls = ARRAY_SIZE(acp3x_5682_mc_controls),
};
static const struct snd_soc_dapm_widget acp3x_1015_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0,
&acp3x_dmic_mux_control),
SND_SOC_DAPM_SPK("Left Spk", NULL),
SND_SOC_DAPM_SPK("Right Spk", NULL),
};
static const struct snd_soc_dapm_route acp3x_1015_route[] = {
{"Headphone Jack", NULL, "HPOL"},
{"Headphone Jack", NULL, "HPOR"},
{"IN1P", NULL, "Headset Mic"},
{"Dmic Mux", "Front Mic", "DMIC"},
{"Dmic Mux", "Rear Mic", "DMIC"},
{"Left Spk", NULL, "Left SPO"},
{"Right Spk", NULL, "Right SPO"},
};
static const struct snd_kcontrol_new acp3x_mc_1015_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Left Spk"),
SOC_DAPM_PIN_SWITCH("Right Spk"),
};
static struct snd_soc_card acp3x_1015 = {
.name = "acp3xalc56821015",
.owner = THIS_MODULE,
.dai_link = acp3x_dai,
.num_links = ARRAY_SIZE(acp3x_dai),
.dapm_widgets = acp3x_1015_widgets,
.num_dapm_widgets = ARRAY_SIZE(acp3x_1015_widgets),
.dapm_routes = acp3x_1015_route,
.num_dapm_routes = ARRAY_SIZE(acp3x_1015_route),
.codec_conf = rt1015_conf,
.num_configs = ARRAY_SIZE(rt1015_conf),
.controls = acp3x_mc_1015_controls,
.num_controls = ARRAY_SIZE(acp3x_mc_1015_controls),
};
static const struct snd_soc_dapm_widget acp3x_1015p_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0,
&acp3x_dmic_mux_control),
SND_SOC_DAPM_SPK("Speakers", NULL),
};
static const struct snd_soc_dapm_route acp3x_1015p_route[] = {
{"Headphone Jack", NULL, "HPOL"},
{"Headphone Jack", NULL, "HPOR"},
{"IN1P", NULL, "Headset Mic"},
{"Dmic Mux", "Front Mic", "DMIC"},
{"Dmic Mux", "Rear Mic", "DMIC"},
/* speaker */
{ "Speakers", NULL, "Speaker" },
};
static const struct snd_kcontrol_new acp3x_mc_1015p_controls[] = {
SOC_DAPM_PIN_SWITCH("Speakers"),
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
};
static struct snd_soc_card acp3x_1015p = {
.name = "acp3xalc56821015p",
.owner = THIS_MODULE,
.dai_link = acp3x_dai,
.num_links = ARRAY_SIZE(acp3x_dai),
.dapm_widgets = acp3x_1015p_widgets,
.num_dapm_widgets = ARRAY_SIZE(acp3x_1015p_widgets),
.dapm_routes = acp3x_1015p_route,
.num_dapm_routes = ARRAY_SIZE(acp3x_1015p_route),
.controls = acp3x_mc_1015p_controls,
.num_controls = ARRAY_SIZE(acp3x_mc_1015p_controls),
};
void *soc_is_rltk_max(struct device *dev)
{
const struct acpi_device_id *match;
match = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!match)
return NULL;
return (void *)match->driver_data;
}
static void card_spk_dai_link_present(struct snd_soc_dai_link *links,
const char *card_name)
{
if (!strcmp(card_name, "acp3xalc56821015")) {
links[1].codecs = rt1015;
links[1].num_codecs = ARRAY_SIZE(rt1015);
} else if (!strcmp(card_name, "acp3xalc56821015p")) {
links[1].codecs = rt1015p;
links[1].num_codecs = ARRAY_SIZE(rt1015p);
} else {
links[1].codecs = max;
links[1].num_codecs = ARRAY_SIZE(max);
}
}
static int acp3x_probe(struct platform_device *pdev)
{
int ret;
struct snd_soc_card *card;
struct acp3x_platform_info *machine;
struct device *dev = &pdev->dev;
card = (struct snd_soc_card *)soc_is_rltk_max(dev);
if (!card)
return -ENODEV;
machine = devm_kzalloc(&pdev->dev, sizeof(*machine), GFP_KERNEL);
if (!machine)
return -ENOMEM;
card_spk_dai_link_present(card->dai_link, card->name);
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
dmic_sel = devm_gpiod_get(&pdev->dev, "dmic", GPIOD_OUT_LOW);
if (IS_ERR(dmic_sel)) {
dev_err(&pdev->dev, "DMIC gpio failed err=%ld\n",
PTR_ERR(dmic_sel));
return PTR_ERR(dmic_sel);
}
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"devm_snd_soc_register_card(%s) failed\n",
card->name);
}
return 0;
}
static const struct acpi_device_id acp3x_audio_acpi_match[] = {
{ "AMDI5682", (unsigned long)&acp3x_5682},
{ "AMDI1015", (unsigned long)&acp3x_1015},
{ "10021015", (unsigned long)&acp3x_1015p},
{},
};
MODULE_DEVICE_TABLE(acpi, acp3x_audio_acpi_match);
static struct platform_driver acp3x_audio = {
.driver = {
.name = "acp3x-alc5682-max98357",
.acpi_match_table = ACPI_PTR(acp3x_audio_acpi_match),
.pm = &snd_soc_pm_ops,
},
.probe = acp3x_probe,
};
module_platform_driver(acp3x_audio);
MODULE_AUTHOR("akshu.agrawal@amd.com");
MODULE_AUTHOR("Vishnuvardhanrao.Ravulapati@amd.com");
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("ALC5682 ALC1015, ALC1015P & MAX98357 audio support");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0+
//
// AMD ALSA SoC PCM Driver
//
// Copyright (C) 2021 Advanced Micro Devices, Inc. All rights reserved.
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/dma-mapping.h>
#include "acp5x.h"
#define DRV_NAME "acp5x_i2s_playcap"
static int acp5x_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct i2s_dev_data *adata;
int mode;
adata = snd_soc_dai_get_drvdata(cpu_dai);
mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
switch (mode) {
case SND_SOC_DAIFMT_I2S:
adata->tdm_mode = TDM_DISABLE;
break;
case SND_SOC_DAIFMT_DSP_A:
adata->tdm_mode = TDM_ENABLE;
break;
default:
return -EINVAL;
}
mode = fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
switch (mode) {
case SND_SOC_DAIFMT_CBC_CFC:
adata->master_mode = I2S_MASTER_MODE_ENABLE;
break;
case SND_SOC_DAIFMT_CBP_CFP:
adata->master_mode = I2S_MASTER_MODE_DISABLE;
break;
}
return 0;
}
static int acp5x_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai,
u32 tx_mask, u32 rx_mask,
int slots, int slot_width)
{
struct i2s_dev_data *adata;
u32 frm_len;
u16 slot_len;
adata = snd_soc_dai_get_drvdata(cpu_dai);
/* These values are as per Hardware Spec */
switch (slot_width) {
case SLOT_WIDTH_8:
slot_len = 8;
break;
case SLOT_WIDTH_16:
slot_len = 16;
break;
case SLOT_WIDTH_24:
slot_len = 24;
break;
case SLOT_WIDTH_32:
slot_len = 0;
break;
default:
return -EINVAL;
}
frm_len = FRM_LEN | (slots << 15) | (slot_len << 18);
adata->tdm_fmt = frm_len;
return 0;
}
static int acp5x_i2s_hwparams(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct i2s_stream_instance *rtd;
struct snd_soc_pcm_runtime *prtd;
struct snd_soc_card *card;
struct acp5x_platform_info *pinfo;
struct i2s_dev_data *adata;
union acp_i2stdm_mstrclkgen mclkgen;
u32 val;
u32 reg_val, frmt_reg, master_reg;
u32 lrclk_div_val, bclk_div_val;
lrclk_div_val = 0;
bclk_div_val = 0;
prtd = asoc_substream_to_rtd(substream);
rtd = substream->runtime->private_data;
card = prtd->card;
adata = snd_soc_dai_get_drvdata(dai);
pinfo = snd_soc_card_get_drvdata(card);
if (pinfo) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
rtd->i2s_instance = pinfo->play_i2s_instance;
else
rtd->i2s_instance = pinfo->cap_i2s_instance;
}
/* These values are as per Hardware Spec */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U8:
case SNDRV_PCM_FORMAT_S8:
rtd->xfer_resolution = 0x0;
break;
case SNDRV_PCM_FORMAT_S16_LE:
rtd->xfer_resolution = 0x02;
break;
case SNDRV_PCM_FORMAT_S24_LE:
rtd->xfer_resolution = 0x04;
break;
case SNDRV_PCM_FORMAT_S32_LE:
rtd->xfer_resolution = 0x05;
break;
default:
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_val = ACP_HSTDM_ITER;
frmt_reg = ACP_HSTDM_TXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = ACP_I2STDM_ITER;
frmt_reg = ACP_I2STDM_TXFRMT;
}
} else {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_val = ACP_HSTDM_IRER;
frmt_reg = ACP_HSTDM_RXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = ACP_I2STDM_IRER;
frmt_reg = ACP_I2STDM_RXFRMT;
}
}
if (adata->tdm_mode) {
val = acp_readl(rtd->acp5x_base + reg_val);
acp_writel(val | 0x2, rtd->acp5x_base + reg_val);
acp_writel(adata->tdm_fmt, rtd->acp5x_base + frmt_reg);
}
val = acp_readl(rtd->acp5x_base + reg_val);
val &= ~ACP5x_ITER_IRER_SAMP_LEN_MASK;
val = val | (rtd->xfer_resolution << 3);
acp_writel(val, rtd->acp5x_base + reg_val);
if (adata->master_mode) {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
master_reg = ACP_I2STDM2_MSTRCLKGEN;
break;
case I2S_SP_INSTANCE:
default:
master_reg = ACP_I2STDM0_MSTRCLKGEN;
break;
}
mclkgen.bits.i2stdm_master_mode = 0x1;
if (adata->tdm_mode)
mclkgen.bits.i2stdm_format_mode = 0x01;
else
mclkgen.bits.i2stdm_format_mode = 0x0;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
switch (params_rate(params)) {
case 8000:
bclk_div_val = 768;
break;
case 16000:
bclk_div_val = 384;
break;
case 24000:
bclk_div_val = 256;
break;
case 32000:
bclk_div_val = 192;
break;
case 44100:
case 48000:
bclk_div_val = 128;
break;
case 88200:
case 96000:
bclk_div_val = 64;
break;
case 192000:
bclk_div_val = 32;
break;
default:
return -EINVAL;
}
lrclk_div_val = 32;
break;
case SNDRV_PCM_FORMAT_S32_LE:
switch (params_rate(params)) {
case 8000:
bclk_div_val = 384;
break;
case 16000:
bclk_div_val = 192;
break;
case 24000:
bclk_div_val = 128;
break;
case 32000:
bclk_div_val = 96;
break;
case 44100:
case 48000:
bclk_div_val = 64;
break;
case 88200:
case 96000:
bclk_div_val = 32;
break;
case 192000:
bclk_div_val = 16;
break;
default:
return -EINVAL;
}
lrclk_div_val = 64;
break;
default:
return -EINVAL;
}
mclkgen.bits.i2stdm_bclk_div_val = bclk_div_val;
mclkgen.bits.i2stdm_lrclk_div_val = lrclk_div_val;
acp_writel(mclkgen.u32_all, rtd->acp5x_base + master_reg);
}
return 0;
}
static int acp5x_i2s_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct i2s_stream_instance *rtd;
u32 ret, val, period_bytes, reg_val, ier_val, water_val;
u32 buf_size, buf_reg;
rtd = substream->runtime->private_data;
period_bytes = frames_to_bytes(substream->runtime,
substream->runtime->period_size);
buf_size = frames_to_bytes(substream->runtime,
substream->runtime->buffer_size);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
rtd->bytescount = acp_get_byte_count(rtd,
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
water_val =
ACP_HS_TX_INTR_WATERMARK_SIZE;
reg_val = ACP_HSTDM_ITER;
ier_val = ACP_HSTDM_IER;
buf_reg = ACP_HS_TX_RINGBUFSIZE;
break;
case I2S_SP_INSTANCE:
default:
water_val =
ACP_I2S_TX_INTR_WATERMARK_SIZE;
reg_val = ACP_I2STDM_ITER;
ier_val = ACP_I2STDM_IER;
buf_reg = ACP_I2S_TX_RINGBUFSIZE;
}
} else {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
water_val =
ACP_HS_RX_INTR_WATERMARK_SIZE;
reg_val = ACP_HSTDM_IRER;
ier_val = ACP_HSTDM_IER;
buf_reg = ACP_HS_RX_RINGBUFSIZE;
break;
case I2S_SP_INSTANCE:
default:
water_val =
ACP_I2S_RX_INTR_WATERMARK_SIZE;
reg_val = ACP_I2STDM_IRER;
ier_val = ACP_I2STDM_IER;
buf_reg = ACP_I2S_RX_RINGBUFSIZE;
}
}
acp_writel(period_bytes, rtd->acp5x_base + water_val);
acp_writel(buf_size, rtd->acp5x_base + buf_reg);
val = acp_readl(rtd->acp5x_base + reg_val);
val = val | BIT(0);
acp_writel(val, rtd->acp5x_base + reg_val);
acp_writel(1, rtd->acp5x_base + ier_val);
ret = 0;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_val = ACP_HSTDM_ITER;
break;
case I2S_SP_INSTANCE:
default:
reg_val = ACP_I2STDM_ITER;
}
} else {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_val = ACP_HSTDM_IRER;
break;
case I2S_SP_INSTANCE:
default:
reg_val = ACP_I2STDM_IRER;
}
}
val = acp_readl(rtd->acp5x_base + reg_val);
val = val & ~BIT(0);
acp_writel(val, rtd->acp5x_base + reg_val);
if (!(acp_readl(rtd->acp5x_base + ACP_HSTDM_ITER) & BIT(0)) &&
!(acp_readl(rtd->acp5x_base + ACP_HSTDM_IRER) & BIT(0)))
acp_writel(0, rtd->acp5x_base + ACP_HSTDM_IER);
if (!(acp_readl(rtd->acp5x_base + ACP_I2STDM_ITER) & BIT(0)) &&
!(acp_readl(rtd->acp5x_base + ACP_I2STDM_IRER) & BIT(0)))
acp_writel(0, rtd->acp5x_base + ACP_I2STDM_IER);
ret = 0;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = {
.hw_params = acp5x_i2s_hwparams,
.trigger = acp5x_i2s_trigger,
.set_fmt = acp5x_i2s_set_fmt,
.set_tdm_slot = acp5x_i2s_set_tdm_slot,
};
static const struct snd_soc_component_driver acp5x_dai_component = {
.name = "acp5x-i2s",
};
static struct snd_soc_dai_driver acp5x_i2s_dai = {
.playback = {
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 96000,
},
.capture = {
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 96000,
},
.ops = &acp5x_i2s_dai_ops,
};
static int acp5x_dai_probe(struct platform_device *pdev)
{
struct resource *res;
struct i2s_dev_data *adata;
int ret;
adata = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dev_data),
GFP_KERNEL);
if (!adata)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENOMEM;
}
adata->acp5x_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!adata->acp5x_base)
return -ENOMEM;
adata->master_mode = I2S_MASTER_MODE_ENABLE;
dev_set_drvdata(&pdev->dev, adata);
ret = devm_snd_soc_register_component(&pdev->dev,
&acp5x_dai_component,
&acp5x_i2s_dai, 1);
if (ret)
dev_err(&pdev->dev, "Fail to register acp i2s dai\n");
return ret;
}
static struct platform_driver acp5x_dai_driver = {
.probe = acp5x_dai_probe,
.driver = {
.name = "acp5x_i2s_playcap",
},
};
module_platform_driver(acp5x_dai_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD ACP5.x CPU DAI Driver");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0+
/*
* Machine driver for AMD Vangogh platform using NAU8821 & CS35L41
* codecs.
*
* Copyright 2021 Advanced Micro Devices, Inc.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <linux/module.h>
#include <linux/io.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/jack.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
#include "../../codecs/nau8821.h"
#include "../../codecs/cs35l41.h"
#include "acp5x.h"
#define DRV_NAME "acp5x_mach"
#define DUAL_CHANNEL 2
#define ACP5X_NUVOTON_CODEC_DAI "nau8821-hifi"
#define VG_JUPITER 1
static unsigned long acp5x_machine_id;
static struct snd_soc_jack vg_headset;
static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_card *card = rtd->card;
struct snd_soc_component *component =
asoc_rtd_to_codec(rtd, 0)->component;
/*
* Headset buttons map to the google Reference headset.
* These can be configured by userspace.
*/
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_BTN_0,
&vg_headset, acp5x_nau8821_jack_pins,
ARRAY_SIZE(acp5x_nau8821_jack_pins));
if (ret) {
dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
return ret;
}
snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA);
nau8821_enable_jack_detect(component, &vg_headset);
return ret;
}
static int acp5x_cs35l41_init(struct snd_soc_pcm_runtime *rtd)
{
return 0;
}
static const unsigned int rates[] = {
48000,
};
static const struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static const unsigned int channels[] = {
2,
};
static const struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int acp5x_8821_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card);
machine->play_i2s_instance = I2S_SP_INSTANCE;
machine->cap_i2s_instance = I2S_SP_INSTANCE;
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
return 0;
}
static int acp5x_nau8821_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai =
snd_soc_card_get_codec_dai(card,
ACP5X_NUVOTON_CODEC_DAI);
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev, "can't set FS clock %d\n", ret);
ret = snd_soc_dai_set_pll(codec_dai, 0, 0, snd_soc_params_to_bclk(params),
params_rate(params) * 256);
if (ret < 0)
dev_err(card->dev, "can't set FLL: %d\n", ret);
return ret;
}
static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card);
machine->play_i2s_instance = I2S_HS_INSTANCE;
runtime->hw.channels_max = DUAL_CHANNEL;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
return 0;
}
static int acp5x_cs35l41_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai;
int ret, i;
unsigned int num_codecs = rtd->num_codecs;
unsigned int bclk_val;
for (i = 0; i < num_codecs; i++) {
codec_dai = asoc_rtd_to_codec(rtd, i);
if ((strcmp(codec_dai->name, "spi-VLV1776:00") == 0) ||
(strcmp(codec_dai->name, "spi-VLV1776:01") == 0)) {
switch (params_rate(params)) {
case 48000:
bclk_val = 1536000;
break;
default:
dev_err(card->dev, "Invalid Samplerate:0x%x\n",
params_rate(params));
return -EINVAL;
}
ret = snd_soc_component_set_sysclk(codec_dai->component,
0, 0, bclk_val, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "failed to set sysclk for CS35l41 dai\n");
return ret;
}
}
}
return ret;
}
static const struct snd_soc_ops acp5x_8821_ops = {
.startup = acp5x_8821_startup,
.hw_params = acp5x_nau8821_hw_params,
};
static const struct snd_soc_ops acp5x_cs35l41_play_ops = {
.startup = acp5x_cs35l41_startup,
.hw_params = acp5x_cs35l41_hw_params,
};
static struct snd_soc_codec_conf cs35l41_conf[] = {
{
.dlc = COMP_CODEC_CONF("spi-VLV1776:00"),
.name_prefix = "Left",
},
{
.dlc = COMP_CODEC_CONF("spi-VLV1776:01"),
.name_prefix = "Right",
},
};
SND_SOC_DAILINK_DEF(acp5x_i2s,
DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0")));
SND_SOC_DAILINK_DEF(acp5x_bt,
DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1")));
SND_SOC_DAILINK_DEF(nau8821,
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-NVTN2020:00",
"nau8821-hifi")));
SND_SOC_DAILINK_DEF(cs35l41,
DAILINK_COMP_ARRAY(COMP_CODEC("spi-VLV1776:00", "cs35l41-pcm"),
COMP_CODEC("spi-VLV1776:01", "cs35l41-pcm")));
SND_SOC_DAILINK_DEF(platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0")));
static struct snd_soc_dai_link acp5x_dai[] = {
{
.name = "acp5x-8825-play",
.stream_name = "Playback/Capture",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &acp5x_8821_ops,
.init = acp5x_8821_init,
SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform),
},
{
.name = "acp5x-CS35L41-Stereo",
.stream_name = "CS35L41 Stereo Playback",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
.dpcm_playback = 1,
.playback_only = 1,
.ops = &acp5x_cs35l41_play_ops,
.init = acp5x_cs35l41_init,
SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform),
},
};
static int platform_clock_control(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_soc_card *card = dapm->card;
struct snd_soc_dai *codec_dai;
int ret = 0;
codec_dai = snd_soc_card_get_codec_dai(card, ACP5X_NUVOTON_CODEC_DAI);
if (!codec_dai) {
dev_err(card->dev, "Codec dai not found\n");
return -EIO;
}
if (SND_SOC_DAPM_EVENT_OFF(event)) {
ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_INTERNAL,
0, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "set sysclk err = %d\n", ret);
return -EIO;
}
}
return ret;
}
static const struct snd_kcontrol_new acp5x_8821_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
};
static const struct snd_soc_dapm_widget acp5x_8821_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
platform_clock_control, SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route acp5x_8821_audio_route[] = {
/* HP jack connectors - unknown if we have jack detection */
{ "Headphone", NULL, "HPOL" },
{ "Headphone", NULL, "HPOR" },
{ "MICL", NULL, "Headset Mic" },
{ "MICR", NULL, "Headset Mic" },
{ "DMIC", NULL, "Int Mic" },
{ "Headphone", NULL, "Platform Clock" },
{ "Headset Mic", NULL, "Platform Clock" },
{ "Int Mic", NULL, "Platform Clock" },
};
static struct snd_soc_card acp5x_card = {
.name = "acp5x",
.owner = THIS_MODULE,
.dai_link = acp5x_dai,
.num_links = ARRAY_SIZE(acp5x_dai),
.dapm_widgets = acp5x_8821_widgets,
.num_dapm_widgets = ARRAY_SIZE(acp5x_8821_widgets),
.dapm_routes = acp5x_8821_audio_route,
.num_dapm_routes = ARRAY_SIZE(acp5x_8821_audio_route),
.codec_conf = cs35l41_conf,
.num_configs = ARRAY_SIZE(cs35l41_conf),
.controls = acp5x_8821_controls,
.num_controls = ARRAY_SIZE(acp5x_8821_controls),
};
static int acp5x_vg_quirk_cb(const struct dmi_system_id *id)
{
acp5x_machine_id = VG_JUPITER;
return 1;
}
static const struct dmi_system_id acp5x_vg_quirk_table[] = {
{
.callback = acp5x_vg_quirk_cb,
.matches = {
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"),
}
},
{}
};
static int acp5x_probe(struct platform_device *pdev)
{
int ret;
struct acp5x_platform_info *machine;
struct snd_soc_card *card;
machine = devm_kzalloc(&pdev->dev, sizeof(struct acp5x_platform_info),
GFP_KERNEL);
if (!machine)
return -ENOMEM;
dmi_check_system(acp5x_vg_quirk_table);
switch(acp5x_machine_id) {
case VG_JUPITER:
card = &acp5x_card;
acp5x_card.dev = &pdev->dev;
break;
default:
return -ENODEV;
}
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card(%s) failed\n",
acp5x_card.name);
}
return 0;
}
static struct platform_driver acp5x_mach_driver = {
.driver = {
.name = "acp5x_mach",
.pm = &snd_soc_pm_ops,
},
.probe = acp5x_probe,
};
module_platform_driver(acp5x_mach_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("NAU8821 & CS35L41 audio support");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
//
// AMD ALSA SoC PCM Driver
//
// Copyright (C) 2021 Advanced Micro Devices, Inc. All rights reserved.
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "acp5x.h"
#define DRV_NAME "acp5x_i2s_dma"
static const struct snd_pcm_hardware acp5x_pcm_hardware_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.rate_min = 8000,
.rate_max = 96000,
.buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = PLAYBACK_MIN_NUM_PERIODS,
.periods_max = PLAYBACK_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp5x_pcm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.rate_min = 8000,
.rate_max = 96000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
{
struct i2s_dev_data *vg_i2s_data;
u16 irq_flag;
u32 val;
vg_i2s_data = dev_id;
if (!vg_i2s_data)
return IRQ_NONE;
irq_flag = 0;
val = acp_readl(vg_i2s_data->acp5x_base + ACP_EXTERNAL_INTR_STAT);
if ((val & BIT(HS_TX_THRESHOLD)) && vg_i2s_data->play_stream) {
acp_writel(BIT(HS_TX_THRESHOLD), vg_i2s_data->acp5x_base +
ACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(vg_i2s_data->play_stream);
irq_flag = 1;
}
if ((val & BIT(I2S_TX_THRESHOLD)) && vg_i2s_data->i2ssp_play_stream) {
acp_writel(BIT(I2S_TX_THRESHOLD),
vg_i2s_data->acp5x_base + ACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(vg_i2s_data->i2ssp_play_stream);
irq_flag = 1;
}
if ((val & BIT(HS_RX_THRESHOLD)) && vg_i2s_data->capture_stream) {
acp_writel(BIT(HS_RX_THRESHOLD), vg_i2s_data->acp5x_base +
ACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(vg_i2s_data->capture_stream);
irq_flag = 1;
}
if ((val & BIT(I2S_RX_THRESHOLD)) && vg_i2s_data->i2ssp_capture_stream) {
acp_writel(BIT(I2S_RX_THRESHOLD),
vg_i2s_data->acp5x_base + ACP_EXTERNAL_INTR_STAT);
snd_pcm_period_elapsed(vg_i2s_data->i2ssp_capture_stream);
irq_flag = 1;
}
if (irq_flag)
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static void config_acp5x_dma(struct i2s_stream_instance *rtd, int direction)
{
u16 page_idx;
u32 low, high, val, acp_fifo_addr, reg_fifo_addr;
u32 reg_dma_size, reg_fifo_size;
dma_addr_t addr;
addr = rtd->dma_addr;
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
val = ACP_SRAM_HS_PB_PTE_OFFSET;
break;
case I2S_SP_INSTANCE:
default:
val = ACP_SRAM_SP_PB_PTE_OFFSET;
}
} else {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
val = ACP_SRAM_HS_CP_PTE_OFFSET;
break;
case I2S_SP_INSTANCE:
default:
val = ACP_SRAM_SP_CP_PTE_OFFSET;
}
}
/* Group Enable */
acp_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp5x_base +
ACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
acp_writel(PAGE_SIZE_4K_ENABLE, rtd->acp5x_base +
ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);
for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
acp_writel(low, rtd->acp5x_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
acp_writel(high, rtd->acp5x_base + ACP_SCRATCH_REG_0 + val + 4);
/* Move to next physically contiguous page */
val += 8;
addr += PAGE_SIZE;
}
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_dma_size = ACP_HS_TX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
HS_PB_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_HS_TX_FIFOADDR;
reg_fifo_size = ACP_HS_TX_FIFOSIZE;
acp_writel(I2S_HS_TX_MEM_WINDOW_START,
rtd->acp5x_base + ACP_HS_TX_RINGBUFADDR);
break;
case I2S_SP_INSTANCE:
default:
reg_dma_size = ACP_I2S_TX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
SP_PB_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_I2S_TX_FIFOADDR;
reg_fifo_size = ACP_I2S_TX_FIFOSIZE;
acp_writel(I2S_SP_TX_MEM_WINDOW_START,
rtd->acp5x_base + ACP_I2S_TX_RINGBUFADDR);
}
} else {
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_dma_size = ACP_HS_RX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
HS_CAPT_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_HS_RX_FIFOADDR;
reg_fifo_size = ACP_HS_RX_FIFOSIZE;
acp_writel(I2S_HS_RX_MEM_WINDOW_START,
rtd->acp5x_base + ACP_HS_RX_RINGBUFADDR);
break;
case I2S_SP_INSTANCE:
default:
reg_dma_size = ACP_I2S_RX_DMA_SIZE;
acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
SP_CAPT_FIFO_ADDR_OFFSET;
reg_fifo_addr = ACP_I2S_RX_FIFOADDR;
reg_fifo_size = ACP_I2S_RX_FIFOSIZE;
acp_writel(I2S_SP_RX_MEM_WINDOW_START,
rtd->acp5x_base + ACP_I2S_RX_RINGBUFADDR);
}
}
acp_writel(DMA_SIZE, rtd->acp5x_base + reg_dma_size);
acp_writel(acp_fifo_addr, rtd->acp5x_base + reg_fifo_addr);
acp_writel(FIFO_SIZE, rtd->acp5x_base + reg_fifo_size);
acp_writel(BIT(I2S_RX_THRESHOLD) | BIT(HS_RX_THRESHOLD)
| BIT(I2S_TX_THRESHOLD) | BIT(HS_TX_THRESHOLD),
rtd->acp5x_base + ACP_EXTERNAL_INTR_CNTL);
}
static int acp5x_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
struct snd_soc_pcm_runtime *prtd;
struct i2s_dev_data *adata;
struct i2s_stream_instance *i2s_data;
int ret;
runtime = substream->runtime;
prtd = asoc_substream_to_rtd(substream);
component = snd_soc_rtdcom_lookup(prtd, DRV_NAME);
adata = dev_get_drvdata(component->dev);
i2s_data = kzalloc(sizeof(*i2s_data), GFP_KERNEL);
if (!i2s_data)
return -ENOMEM;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = acp5x_pcm_hardware_playback;
else
runtime->hw = acp5x_pcm_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(i2s_data);
return ret;
}
i2s_data->acp5x_base = adata->acp5x_base;
runtime->private_data = i2s_data;
return ret;
}
static int acp5x_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct i2s_stream_instance *rtd;
struct snd_soc_pcm_runtime *prtd;
struct snd_soc_card *card;
struct acp5x_platform_info *pinfo;
struct i2s_dev_data *adata;
u64 size;
prtd = asoc_substream_to_rtd(substream);
card = prtd->card;
pinfo = snd_soc_card_get_drvdata(card);
adata = dev_get_drvdata(component->dev);
rtd = substream->runtime->private_data;
if (!rtd)
return -EINVAL;
if (pinfo) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
rtd->i2s_instance = pinfo->play_i2s_instance;
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
adata->play_stream = substream;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_play_stream = substream;
}
} else {
rtd->i2s_instance = pinfo->cap_i2s_instance;
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
adata->capture_stream = substream;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_capture_stream = substream;
}
}
} else {
dev_err(component->dev, "pinfo failed\n");
return -EINVAL;
}
size = params_buffer_bytes(params);
rtd->dma_addr = substream->dma_buffer.addr;
rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
config_acp5x_dma(rtd, substream->stream);
return 0;
}
static snd_pcm_uframes_t acp5x_dma_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct i2s_stream_instance *rtd;
u32 pos;
u32 buffersize;
u64 bytescount;
rtd = substream->runtime->private_data;
buffersize = frames_to_bytes(substream->runtime,
substream->runtime->buffer_size);
bytescount = acp_get_byte_count(rtd, substream->stream);
if (bytescount > rtd->bytescount)
bytescount -= rtd->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(substream->runtime, pos);
}
static int acp5x_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, MIN_BUFFER, MAX_BUFFER);
return 0;
}
static int acp5x_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *prtd;
struct i2s_dev_data *adata;
struct i2s_stream_instance *ins;
prtd = asoc_substream_to_rtd(substream);
component = snd_soc_rtdcom_lookup(prtd, DRV_NAME);
adata = dev_get_drvdata(component->dev);
ins = substream->runtime->private_data;
if (!ins)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (ins->i2s_instance) {
case I2S_HS_INSTANCE:
adata->play_stream = NULL;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_play_stream = NULL;
}
} else {
switch (ins->i2s_instance) {
case I2S_HS_INSTANCE:
adata->capture_stream = NULL;
break;
case I2S_SP_INSTANCE:
default:
adata->i2ssp_capture_stream = NULL;
}
}
kfree(ins);
return 0;
}
static const struct snd_soc_component_driver acp5x_i2s_component = {
.name = DRV_NAME,
.open = acp5x_dma_open,
.close = acp5x_dma_close,
.hw_params = acp5x_dma_hw_params,
.pointer = acp5x_dma_pointer,
.pcm_construct = acp5x_dma_new,
};
static int acp5x_audio_probe(struct platform_device *pdev)
{
struct resource *res;
struct i2s_dev_data *adata;
unsigned int irqflags;
int status;
if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
}
irqflags = *((unsigned int *)(pdev->dev.platform_data));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->acp5x_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!adata->acp5x_base)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
return -ENODEV;
}
adata->i2s_irq = res->start;
dev_set_drvdata(&pdev->dev, adata);
status = devm_snd_soc_register_component(&pdev->dev,
&acp5x_i2s_component,
NULL, 0);
if (status) {
dev_err(&pdev->dev, "Fail to register acp i2s component\n");
return status;
}
status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
irqflags, "ACP5x_I2S_IRQ", adata);
if (status) {
dev_err(&pdev->dev, "ACP5x I2S IRQ request failed\n");
return status;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static int acp5x_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
static int __maybe_unused acp5x_pcm_resume(struct device *dev)
{
struct i2s_dev_data *adata;
u32 val, reg_val, frmt_val;
reg_val = 0;
frmt_val = 0;
adata = dev_get_drvdata(dev);
if (adata->play_stream && adata->play_stream->runtime) {
struct i2s_stream_instance *rtd =
adata->play_stream->runtime->private_data;
config_acp5x_dma(rtd, SNDRV_PCM_STREAM_PLAYBACK);
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_val = ACP_HSTDM_ITER;
frmt_val = ACP_HSTDM_TXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = ACP_I2STDM_ITER;
frmt_val = ACP_I2STDM_TXFRMT;
}
acp_writel((rtd->xfer_resolution << 3),
rtd->acp5x_base + reg_val);
}
if (adata->capture_stream && adata->capture_stream->runtime) {
struct i2s_stream_instance *rtd =
adata->capture_stream->runtime->private_data;
config_acp5x_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
switch (rtd->i2s_instance) {
case I2S_HS_INSTANCE:
reg_val = ACP_HSTDM_IRER;
frmt_val = ACP_HSTDM_RXFRMT;
break;
case I2S_SP_INSTANCE:
default:
reg_val = ACP_I2STDM_IRER;
frmt_val = ACP_I2STDM_RXFRMT;
}
acp_writel((rtd->xfer_resolution << 3),
rtd->acp5x_base + reg_val);
}
if (adata->tdm_mode == TDM_ENABLE) {
acp_writel(adata->tdm_fmt, adata->acp5x_base + frmt_val);
val = acp_readl(adata->acp5x_base + reg_val);
acp_writel(val | 0x2, adata->acp5x_base + reg_val);
}
acp_writel(1, adata->acp5x_base + ACP_EXTERNAL_INTR_ENB);
return 0;
}
static int __maybe_unused acp5x_pcm_suspend(struct device *dev)
{
struct i2s_dev_data *adata;
adata = dev_get_drvdata(dev);
acp_writel(0, adata->acp5x_base + ACP_EXTERNAL_INTR_ENB);
return 0;
}
static int __maybe_unused acp5x_pcm_runtime_resume(struct device *dev)
{
struct i2s_dev_data *adata;
adata = dev_get_drvdata(dev);
acp_writel(1, adata->acp5x_base + ACP_EXTERNAL_INTR_ENB);
return 0;
}
static const struct dev_pm_ops acp5x_pm_ops = {
SET_RUNTIME_PM_OPS(acp5x_pcm_suspend,
acp5x_pcm_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(acp5x_pcm_suspend, acp5x_pcm_resume)
};
static struct platform_driver acp5x_dma_driver = {
.probe = acp5x_audio_probe,
.remove = acp5x_audio_remove,
.driver = {
.name = "acp5x_i2s_dma",
.pm = &acp5x_pm_ops,
},
};
module_platform_driver(acp5x_dma_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD ACP 5.x PCM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
/*
* Machine driver for AMD Yellow Carp platform using DMIC
*
* Copyright 2021 Advanced Micro Devices, Inc.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/io.h>
#include <linux/dmi.h>
#include "acp6x.h"
#define DRV_NAME "acp_yc_mach"
SND_SOC_DAILINK_DEF(acp6x_pdm,
DAILINK_COMP_ARRAY(COMP_CPU("acp_yc_pdm_dma.0")));
SND_SOC_DAILINK_DEF(dmic_codec,
DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec.0",
"dmic-hifi")));
SND_SOC_DAILINK_DEF(pdm_platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_yc_pdm_dma.0")));
static struct snd_soc_dai_link acp6x_dai_pdm[] = {
{
.name = "acp6x-dmic-capture",
.stream_name = "DMIC capture",
.capture_only = 1,
SND_SOC_DAILINK_REG(acp6x_pdm, dmic_codec, pdm_platform),
},
};
static struct snd_soc_card acp6x_card = {
.name = "acp6x",
.owner = THIS_MODULE,
.dai_link = acp6x_dai_pdm,
.num_links = 1,
};
static const struct dmi_system_id yc_acp_quirk_table[] = {
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21D2"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21D3"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21D4"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21D5"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CF"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CG"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CQ"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CR"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21AW"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21AX"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21BN"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21BQ"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CH"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CJ"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CK"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21CL"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21D8"),
}
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21D9"),
}
},
{}
};
static int acp6x_probe(struct platform_device *pdev)
{
struct acp6x_pdm *machine = NULL;
struct snd_soc_card *card;
int ret;
const struct dmi_system_id *dmi_id;
dmi_id = dmi_first_match(yc_acp_quirk_table);
if (!dmi_id)
return -ENODEV;
card = &acp6x_card;
acp6x_card.dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card(%s) failed\n",
card->name);
}
return 0;
}
static struct platform_driver acp6x_mach_driver = {
.driver = {
.name = "acp_yc_mach",
.pm = &snd_soc_pm_ops,
},
.probe = acp6x_probe,
};
module_platform_driver(acp6x_mach_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
// SPDX-License-Identifier: GPL-2.0+
/*
* AMD ALSA SoC Yellow Carp PDM Driver
*
* Copyright 2021 Advanced Micro Devices, Inc.
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/pm_runtime.h>
#include "acp6x.h"
#define DRV_NAME "acp_yc_pdm_dma"
static const struct snd_pcm_hardware acp6x_pdm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static void acp6x_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size,
u32 watermark_size, void __iomem *acp_base)
{
acp6x_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR);
acp6x_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE);
acp6x_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE);
acp6x_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL);
}
static void acp6x_enable_pdm_clock(void __iomem *acp_base)
{
u32 pdm_clk_enable, pdm_ctrl;
pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK;
pdm_ctrl = 0x00;
acp6x_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL);
pdm_ctrl = acp6x_readl(acp_base + ACP_WOV_MISC_CTRL);
pdm_ctrl |= ACP_WOV_MISC_CTRL_MASK;
acp6x_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL);
}
static void acp6x_enable_pdm_interrupts(void __iomem *acp_base)
{
u32 ext_int_ctrl;
ext_int_ctrl = acp6x_readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl |= PDM_DMA_INTR_MASK;
acp6x_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL);
}
static void acp6x_disable_pdm_interrupts(void __iomem *acp_base)
{
u32 ext_int_ctrl;
ext_int_ctrl = acp6x_readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl &= ~PDM_DMA_INTR_MASK;
acp6x_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL);
}
static bool acp6x_check_pdm_dma_status(void __iomem *acp_base)
{
bool pdm_dma_status;
u32 pdm_enable, pdm_dma_enable;
pdm_dma_status = false;
pdm_enable = acp6x_readl(acp_base + ACP_WOV_PDM_ENABLE);
pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & ACP_PDM_DMA_EN_STATUS))
pdm_dma_status = true;
return pdm_dma_status;
}
static int acp6x_start_pdm_dma(void __iomem *acp_base)
{
u32 pdm_enable;
u32 pdm_dma_enable;
int timeout;
pdm_enable = 0x01;
pdm_dma_enable = 0x01;
acp6x_enable_pdm_clock(acp_base);
acp6x_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
acp6x_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
timeout = 0;
while (++timeout < ACP_COUNTER) {
pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS)
return 0;
udelay(DELAY_US);
}
return -ETIMEDOUT;
}
static int acp6x_stop_pdm_dma(void __iomem *acp_base)
{
u32 pdm_enable, pdm_dma_enable;
int timeout;
pdm_enable = 0x00;
pdm_dma_enable = 0x00;
pdm_enable = acp6x_readl(acp_base + ACP_WOV_PDM_ENABLE);
pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if (pdm_dma_enable & 0x01) {
pdm_dma_enable = 0x02;
acp6x_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
timeout = 0;
while (++timeout < ACP_COUNTER) {
pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_dma_enable & 0x02) == 0x00)
break;
udelay(DELAY_US);
}
if (timeout == ACP_COUNTER)
return -ETIMEDOUT;
}
if (pdm_enable == ACP_PDM_ENABLE) {
pdm_enable = ACP_PDM_DISABLE;
acp6x_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
}
acp6x_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH);
return 0;
}
static void acp6x_config_dma(struct pdm_stream_instance *rtd, int direction)
{
u16 page_idx;
u32 low, high, val;
dma_addr_t addr;
addr = rtd->dma_addr;
val = PDM_PTE_OFFSET;
/* Group Enable */
acp6x_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp6x_base +
ACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
acp6x_writel(PAGE_SIZE_4K_ENABLE, rtd->acp6x_base +
ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);
for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
acp6x_writel(low, rtd->acp6x_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
acp6x_writel(high, rtd->acp6x_base + ACP_SCRATCH_REG_0 + val + 4);
val += 8;
addr += PAGE_SIZE;
}
}
static int acp6x_pdm_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
struct pdm_dev_data *adata;
struct pdm_stream_instance *pdm_data;
int ret;
runtime = substream->runtime;
adata = dev_get_drvdata(component->dev);
pdm_data = kzalloc(sizeof(*pdm_data), GFP_KERNEL);
if (!pdm_data)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw = acp6x_pdm_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(pdm_data);
return ret;
}
acp6x_enable_pdm_interrupts(adata->acp6x_base);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
adata->capture_stream = substream;
pdm_data->acp6x_base = adata->acp6x_base;
runtime->private_data = pdm_data;
return ret;
}
static int acp6x_pdm_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct pdm_stream_instance *rtd;
size_t size, period_bytes;
rtd = substream->runtime->private_data;
if (!rtd)
return -EINVAL;
size = params_buffer_bytes(params);
period_bytes = params_period_bytes(params);
rtd->dma_addr = substream->runtime->dma_addr;
rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
acp6x_config_dma(rtd, substream->stream);
acp6x_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, size,
period_bytes, rtd->acp6x_base);
return 0;
}
static u64 acp6x_pdm_get_byte_count(struct pdm_stream_instance *rtd,
int direction)
{
union acp_pdm_dma_count byte_count;
byte_count.bcount.high =
acp6x_readl(rtd->acp6x_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH);
byte_count.bcount.low =
acp6x_readl(rtd->acp6x_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW);
return byte_count.bytescount;
}
static snd_pcm_uframes_t acp6x_pdm_dma_pointer(struct snd_soc_component *comp,
struct snd_pcm_substream *stream)
{
struct pdm_stream_instance *rtd;
u32 pos, buffersize;
u64 bytescount;
rtd = stream->runtime->private_data;
buffersize = frames_to_bytes(stream->runtime,
stream->runtime->buffer_size);
bytescount = acp6x_pdm_get_byte_count(rtd, stream->stream);
if (bytescount > rtd->bytescount)
bytescount -= rtd->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(stream->runtime, pos);
}
static int acp6x_pdm_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, MIN_BUFFER, MAX_BUFFER);
return 0;
}
static int acp6x_pdm_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct pdm_dev_data *adata = dev_get_drvdata(component->dev);
acp6x_disable_pdm_interrupts(adata->acp6x_base);
adata->capture_stream = NULL;
return 0;
}
static int acp6x_pdm_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct pdm_stream_instance *rtd;
int ret;
bool pdm_status;
unsigned int ch_mask;
rtd = substream->runtime->private_data;
ret = 0;
switch (substream->runtime->channels) {
case TWO_CH:
ch_mask = 0x00;
break;
default:
return -EINVAL;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
acp6x_writel(ch_mask, rtd->acp6x_base + ACP_WOV_PDM_NO_OF_CHANNELS);
acp6x_writel(PDM_DECIMATION_FACTOR, rtd->acp6x_base +
ACP_WOV_PDM_DECIMATION_FACTOR);
rtd->bytescount = acp6x_pdm_get_byte_count(rtd, substream->stream);
pdm_status = acp6x_check_pdm_dma_status(rtd->acp6x_base);
if (!pdm_status)
ret = acp6x_start_pdm_dma(rtd->acp6x_base);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pdm_status = acp6x_check_pdm_dma_status(rtd->acp6x_base);
if (pdm_status)
ret = acp6x_stop_pdm_dma(rtd->acp6x_base);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct snd_soc_dai_ops acp6x_pdm_dai_ops = {
.trigger = acp6x_pdm_dai_trigger,
};
static struct snd_soc_dai_driver acp6x_pdm_dai_driver = {
.capture = {
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 48000,
.rate_max = 48000,
},
.ops = &acp6x_pdm_dai_ops,
};
static const struct snd_soc_component_driver acp6x_pdm_component = {
.name = DRV_NAME,
.open = acp6x_pdm_dma_open,
.close = acp6x_pdm_dma_close,
.hw_params = acp6x_pdm_dma_hw_params,
.pointer = acp6x_pdm_dma_pointer,
.pcm_construct = acp6x_pdm_dma_new,
};
static int acp6x_pdm_audio_probe(struct platform_device *pdev)
{
struct resource *res;
struct pdm_dev_data *adata;
int status;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->acp6x_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!adata->acp6x_base)
return -ENOMEM;
adata->capture_stream = NULL;
dev_set_drvdata(&pdev->dev, adata);
status = devm_snd_soc_register_component(&pdev->dev,
&acp6x_pdm_component,
&acp6x_pdm_dai_driver, 1);
if (status) {
dev_err(&pdev->dev, "Fail to register acp pdm dai\n");
return -ENODEV;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static int acp6x_pdm_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
static int __maybe_unused acp6x_pdm_resume(struct device *dev)
{
struct pdm_dev_data *adata;
struct snd_pcm_runtime *runtime;
struct pdm_stream_instance *rtd;
u32 period_bytes, buffer_len;
adata = dev_get_drvdata(dev);
if (adata->capture_stream && adata->capture_stream->runtime) {
runtime = adata->capture_stream->runtime;
rtd = runtime->private_data;
period_bytes = frames_to_bytes(runtime, runtime->period_size);
buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
acp6x_config_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
acp6x_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_len,
period_bytes, adata->acp6x_base);
}
acp6x_enable_pdm_interrupts(adata->acp6x_base);
return 0;
}
static int __maybe_unused acp6x_pdm_suspend(struct device *dev)
{
struct pdm_dev_data *adata;
adata = dev_get_drvdata(dev);
acp6x_disable_pdm_interrupts(adata->acp6x_base);
return 0;
}
static int __maybe_unused acp6x_pdm_runtime_resume(struct device *dev)
{
struct pdm_dev_data *adata;
adata = dev_get_drvdata(dev);
acp6x_enable_pdm_interrupts(adata->acp6x_base);
return 0;
}
static const struct dev_pm_ops acp6x_pdm_pm_ops = {
SET_RUNTIME_PM_OPS(acp6x_pdm_suspend, acp6x_pdm_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(acp6x_pdm_suspend, acp6x_pdm_resume)
};
static struct platform_driver acp6x_pdm_dma_driver = {
.probe = acp6x_pdm_audio_probe,
.remove = acp6x_pdm_audio_remove,
.driver = {
.name = "acp_yc_pdm_dma",
.pm = &acp6x_pdm_pm_ops,
},
};
module_platform_driver(acp6x_pdm_dma_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD ACP6x YC PDM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
// SPDX-License-Identifier: GPL-2.0-or-later
/*
card-ad1816a.c - driver for ADI SoundPort AD1816A based soundcards.
Copyright (C) 2000 by Massimo Piccioni <dafastidio@libero.it>
*/
#include <linux/init.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/pnp.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/ad1816a.h>
#include <sound/mpu401.h>
#include <sound/opl3.h>
#define PFX "ad1816a: "
MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
MODULE_DESCRIPTION("AD1816A, AD1815");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 1-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */
static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */
static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */
static int clockfreq[SNDRV_CARDS];
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for ad1816a based soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for ad1816a based soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable ad1816a based soundcard.");
module_param_array(clockfreq, int, NULL, 0444);
MODULE_PARM_DESC(clockfreq, "Clock frequency for ad1816a driver (default = 0).");
static const struct pnp_card_device_id snd_ad1816a_pnpids[] = {
/* Analog Devices AD1815 */
{ .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } },
/* Analog Devices AD1816? */
{ .id = "ADS7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* Analog Devices AD1816A - added by Kenneth Platz <kxp@atl.hp.com> */
{ .id = "ADS7181", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* Analog Devices AD1816A - Aztech/Newcom SC-16 3D */
{ .id = "AZT1022", .devs = { { .id = "AZT1018" }, { .id = "AZT2002" } } },
/* Highscreen Sound-Boostar 16 3D - added by Stefan Behnel */
{ .id = "LWC1061", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* Highscreen Sound-Boostar 16 3D */
{ .id = "MDK1605", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* Shark Predator ISA - added by Ken Arromdee */
{ .id = "SMM7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
{ .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
{ .id = "TER1112", .devs = { { .id = "TER1100" }, { .id = "TER1101" } } },
/* Analog Devices AD1816A - Terratec Base 64 */
{ .id = "TER1411", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
/* end */
{ .id = "" }
};
MODULE_DEVICE_TABLE(pnp_card, snd_ad1816a_pnpids);
#define DRIVER_NAME "snd-card-ad1816a"
static int snd_card_ad1816a_pnp(int dev, struct pnp_card_link *card,
const struct pnp_card_device_id *id)
{
struct pnp_dev *pdev;
int err;
pdev = pnp_request_card_device(card, id->devs[0].id, NULL);
if (pdev == NULL)
return -EBUSY;
err = pnp_activate_dev(pdev);
if (err < 0) {
printk(KERN_ERR PFX "AUDIO PnP configure failure\n");
return -EBUSY;
}
port[dev] = pnp_port_start(pdev, 2);
fm_port[dev] = pnp_port_start(pdev, 1);
dma1[dev] = pnp_dma(pdev, 0);
dma2[dev] = pnp_dma(pdev, 1);
irq[dev] = pnp_irq(pdev, 0);
pdev = pnp_request_card_device(card, id->devs[1].id, NULL);
if (pdev == NULL) {
mpu_port[dev] = -1;
snd_printk(KERN_WARNING PFX "MPU401 device busy, skipping.\n");
return 0;
}
err = pnp_activate_dev(pdev);
if (err < 0) {
printk(KERN_ERR PFX "MPU401 PnP configure failure\n");
mpu_port[dev] = -1;
} else {
mpu_port[dev] = pnp_port_start(pdev, 0);
mpu_irq[dev] = pnp_irq(pdev, 0);
}
return 0;
}
static int snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard,
const struct pnp_card_device_id *pid)
{
int error;
struct snd_card *card;
struct snd_ad1816a *chip;
struct snd_opl3 *opl3;
error = snd_devm_card_new(&pcard->card->dev,
index[dev], id[dev], THIS_MODULE,
sizeof(struct snd_ad1816a), &card);
if (error < 0)
return error;
chip = card->private_data;
error = snd_card_ad1816a_pnp(dev, pcard, pid);
if (error)
return error;
error = snd_ad1816a_create(card, port[dev],
irq[dev],
dma1[dev],
dma2[dev],
chip);
if (error)
return error;
if (clockfreq[dev] >= 5000 && clockfreq[dev] <= 100000)
chip->clock_freq = clockfreq[dev];
strcpy(card->driver, "AD1816A");
strcpy(card->shortname, "ADI SoundPort AD1816A");
sprintf(card->longname, "%s, SS at 0x%lx, irq %d, dma %d&%d",
card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
error = snd_ad1816a_pcm(chip, 0);
if (error < 0)
return error;
error = snd_ad1816a_mixer(chip);
if (error < 0)
return error;
error = snd_ad1816a_timer(chip, 0);
if (error < 0)
return error;
if (mpu_port[dev] > 0) {
if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
mpu_port[dev], 0, mpu_irq[dev],
NULL) < 0)
printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", mpu_port[dev]);
}
if (fm_port[dev] > 0) {
if (snd_opl3_create(card,
fm_port[dev], fm_port[dev] + 2,
OPL3_HW_AUTO, 0, &opl3) < 0) {
printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx.\n", fm_port[dev], fm_port[dev] + 2);
} else {
error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
if (error < 0)
return error;
}
}
error = snd_card_register(card);
if (error < 0)
return error;
pnp_set_card_drvdata(pcard, card);
return 0;
}
static unsigned int ad1816a_devices;
static int snd_ad1816a_pnp_detect(struct pnp_card_link *card,
const struct pnp_card_device_id *id)
{
static int dev;
int res;
for ( ; dev < SNDRV_CARDS; dev++) {
if (!enable[dev])
continue;
res = snd_card_ad1816a_probe(dev, card, id);
if (res < 0)
return res;
dev++;
ad1816a_devices++;
return 0;
}
return -ENODEV;
}
#ifdef CONFIG_PM
static int snd_ad1816a_pnp_suspend(struct pnp_card_link *pcard,
pm_message_t state)
{
struct snd_card *card = pnp_get_card_drvdata(pcard);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
snd_ad1816a_suspend(card->private_data);
return 0;
}
static int snd_ad1816a_pnp_resume(struct pnp_card_link *pcard)
{
struct snd_card *card = pnp_get_card_drvdata(pcard);
snd_ad1816a_resume(card->private_data);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
#endif
static struct pnp_card_driver ad1816a_pnpc_driver = {
.flags = PNP_DRIVER_RES_DISABLE,
.name = "ad1816a",
.id_table = snd_ad1816a_pnpids,
.probe = snd_ad1816a_pnp_detect,
#ifdef CONFIG_PM
.suspend = snd_ad1816a_pnp_suspend,
.resume = snd_ad1816a_pnp_resume,
#endif
};
static int __init alsa_card_ad1816a_init(void)
{
int err;
err = pnp_register_card_driver(&ad1816a_pnpc_driver);
if (err)
return err;
if (!ad1816a_devices) {
pnp_unregister_card_driver(&ad1816a_pnpc_driver);
#ifdef MODULE
printk(KERN_ERR "no AD1816A based soundcards found.\n");
#endif /* MODULE */
return -ENODEV;
}
return 0;
}
static void __exit alsa_card_ad1816a_exit(void)
{
pnp_unregister_card_driver(&ad1816a_pnpc_driver);
}
module_init(alsa_card_ad1816a_init)
module_exit(alsa_card_ad1816a_exit)
// SPDX-License-Identifier: GPL-2.0-or-later
/*
ad1816a.c - lowlevel code for Analog Devices AD1816A chip.
Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/tlv.h>
#include <sound/ad1816a.h>
#include <asm/dma.h>
static inline int snd_ad1816a_busy_wait(struct snd_ad1816a *chip)
{
int timeout;
for (timeout = 1000; timeout-- > 0; udelay(10))
if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY)
return 0;
snd_printk(KERN_WARNING "chip busy.\n");
return -EBUSY;
}
static inline unsigned char snd_ad1816a_in(struct snd_ad1816a *chip, unsigned char reg)
{
snd_ad1816a_busy_wait(chip);
return inb(AD1816A_REG(reg));
}
static inline void snd_ad1816a_out(struct snd_ad1816a *chip, unsigned char reg,
unsigned char value)
{
snd_ad1816a_busy_wait(chip);
outb(value, AD1816A_REG(reg));
}
static inline void snd_ad1816a_out_mask(struct snd_ad1816a *chip, unsigned char reg,
unsigned char mask, unsigned char value)
{
snd_ad1816a_out(chip, reg,
(value & mask) | (snd_ad1816a_in(chip, reg) & ~mask));
}
static unsigned short snd_ad1816a_read(struct snd_ad1816a *chip, unsigned char reg)
{
snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
return snd_ad1816a_in(chip, AD1816A_INDIR_DATA_LOW) |
(snd_ad1816a_in(chip, AD1816A_INDIR_DATA_HIGH) << 8);
}
static void snd_ad1816a_write(struct snd_ad1816a *chip, unsigned char reg,
unsigned short value)
{
snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
snd_ad1816a_out(chip, AD1816A_INDIR_DATA_LOW, value & 0xff);
snd_ad1816a_out(chip, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff);
}
static void snd_ad1816a_write_mask(struct snd_ad1816a *chip, unsigned char reg,
unsigned short mask, unsigned short value)
{
snd_ad1816a_write(chip, reg,
(value & mask) | (snd_ad1816a_read(chip, reg) & ~mask));
}
static unsigned char snd_ad1816a_get_format(struct snd_ad1816a *chip,
snd_pcm_format_t format,
int channels)
{
unsigned char retval = AD1816A_FMT_LINEAR_8;
switch (format) {
case SNDRV_PCM_FORMAT_MU_LAW:
retval = AD1816A_FMT_ULAW_8;
break;
case SNDRV_PCM_FORMAT_A_LAW:
retval = AD1816A_FMT_ALAW_8;
break;
case SNDRV_PCM_FORMAT_S16_LE:
retval = AD1816A_FMT_LINEAR_16_LIT;
break;
case SNDRV_PCM_FORMAT_S16_BE:
retval = AD1816A_FMT_LINEAR_16_BIG;
}
return (channels > 1) ? (retval | AD1816A_FMT_STEREO) : retval;
}
static int snd_ad1816a_open(struct snd_ad1816a *chip, unsigned int mode)
{
unsigned long flags;
spin_lock_irqsave(&chip->lock, flags);
if (chip->mode & mode) {
spin_unlock_irqrestore(&chip->lock, flags);
return -EAGAIN;
}
switch ((mode &= AD1816A_MODE_OPEN)) {
case AD1816A_MODE_PLAYBACK:
snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff);
break;
case AD1816A_MODE_CAPTURE:
snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
AD1816A_CAPTURE_IRQ_PENDING, 0x00);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_CAPTURE_IRQ_ENABLE, 0xffff);
break;
case AD1816A_MODE_TIMER:
snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
AD1816A_TIMER_IRQ_PENDING, 0x00);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_TIMER_IRQ_ENABLE, 0xffff);
}
chip->mode |= mode;
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static void snd_ad1816a_close(struct snd_ad1816a *chip, unsigned int mode)
{
unsigned long flags;
spin_lock_irqsave(&chip->lock, flags);
switch ((mode &= AD1816A_MODE_OPEN)) {
case AD1816A_MODE_PLAYBACK:
snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000);
break;
case AD1816A_MODE_CAPTURE:
snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
AD1816A_CAPTURE_IRQ_PENDING, 0x00);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_CAPTURE_IRQ_ENABLE, 0x0000);
break;
case AD1816A_MODE_TIMER:
snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
AD1816A_TIMER_IRQ_PENDING, 0x00);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_TIMER_IRQ_ENABLE, 0x0000);
}
chip->mode &= ~mode;
if (!(chip->mode & AD1816A_MODE_OPEN))
chip->mode = 0;
spin_unlock_irqrestore(&chip->lock, flags);
}
static int snd_ad1816a_trigger(struct snd_ad1816a *chip, unsigned char what,
int channel, int cmd, int iscapture)
{
int error = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_STOP:
spin_lock(&chip->lock);
cmd = (cmd == SNDRV_PCM_TRIGGER_START) ? 0xff: 0x00;
/* if (what & AD1816A_PLAYBACK_ENABLE) */
/* That is not valid, because playback and capture enable
* are the same bit pattern, just to different addresses
*/
if (! iscapture)
snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
AD1816A_PLAYBACK_ENABLE, cmd);
else
snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
AD1816A_CAPTURE_ENABLE, cmd);
spin_unlock(&chip->lock);
break;
default:
snd_printk(KERN_WARNING "invalid trigger mode 0x%x.\n", what);
error = -EINVAL;
}
return error;
}
static int snd_ad1816a_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE,
SNDRV_PCM_STREAM_PLAYBACK, cmd, 0);
}
static int snd_ad1816a_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE,
SNDRV_PCM_STREAM_CAPTURE, cmd, 1);
}
static int snd_ad1816a_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
unsigned long flags;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int size, rate;
spin_lock_irqsave(&chip->lock, flags);
chip->p_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
snd_dma_program(chip->dma1, runtime->dma_addr, size,
DMA_MODE_WRITE | DMA_AUTOINIT);
rate = runtime->rate;
if (chip->clock_freq)
rate = (rate * 33000) / chip->clock_freq;
snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, rate);
snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
snd_ad1816a_get_format(chip, runtime->format,
runtime->channels));
snd_ad1816a_write(chip, AD1816A_PLAYBACK_BASE_COUNT,
snd_pcm_lib_period_bytes(substream) / 4 - 1);
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static int snd_ad1816a_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
unsigned long flags;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int size, rate;
spin_lock_irqsave(&chip->lock, flags);
chip->c_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
snd_dma_program(chip->dma2, runtime->dma_addr, size,
DMA_MODE_READ | DMA_AUTOINIT);
rate = runtime->rate;
if (chip->clock_freq)
rate = (rate * 33000) / chip->clock_freq;
snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, rate);
snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
snd_ad1816a_get_format(chip, runtime->format,
runtime->channels));
snd_ad1816a_write(chip, AD1816A_CAPTURE_BASE_COUNT,
snd_pcm_lib_period_bytes(substream) / 4 - 1);
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static snd_pcm_uframes_t snd_ad1816a_playback_pointer(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
size_t ptr;
if (!(chip->mode & AD1816A_MODE_PLAYBACK))
return 0;
ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
return bytes_to_frames(substream->runtime, ptr);
}
static snd_pcm_uframes_t snd_ad1816a_capture_pointer(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
size_t ptr;
if (!(chip->mode & AD1816A_MODE_CAPTURE))
return 0;
ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
return bytes_to_frames(substream->runtime, ptr);
}
static irqreturn_t snd_ad1816a_interrupt(int irq, void *dev_id)
{
struct snd_ad1816a *chip = dev_id;
unsigned char status;
spin_lock(&chip->lock);
status = snd_ad1816a_in(chip, AD1816A_INTERRUPT_STATUS);
spin_unlock(&chip->lock);
if ((status & AD1816A_PLAYBACK_IRQ_PENDING) && chip->playback_substream)
snd_pcm_period_elapsed(chip->playback_substream);
if ((status & AD1816A_CAPTURE_IRQ_PENDING) && chip->capture_substream)
snd_pcm_period_elapsed(chip->capture_substream);
if ((status & AD1816A_TIMER_IRQ_PENDING) && chip->timer)
snd_timer_interrupt(chip->timer, chip->timer->sticks);
spin_lock(&chip->lock);
snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
spin_unlock(&chip->lock);
return IRQ_HANDLED;
}
static const struct snd_pcm_hardware snd_ad1816a_playback = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S16_BE),
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 4000,
.rate_max = 55200,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
static const struct snd_pcm_hardware snd_ad1816a_capture = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S16_BE),
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 4000,
.rate_max = 55200,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
static int snd_ad1816a_timer_close(struct snd_timer *timer)
{
struct snd_ad1816a *chip = snd_timer_chip(timer);
snd_ad1816a_close(chip, AD1816A_MODE_TIMER);
return 0;
}
static int snd_ad1816a_timer_open(struct snd_timer *timer)
{
struct snd_ad1816a *chip = snd_timer_chip(timer);
snd_ad1816a_open(chip, AD1816A_MODE_TIMER);
return 0;
}
static unsigned long snd_ad1816a_timer_resolution(struct snd_timer *timer)
{
if (snd_BUG_ON(!timer))
return 0;
return 10000;
}
static int snd_ad1816a_timer_start(struct snd_timer *timer)
{
unsigned short bits;
unsigned long flags;
struct snd_ad1816a *chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->lock, flags);
bits = snd_ad1816a_read(chip, AD1816A_INTERRUPT_ENABLE);
if (!(bits & AD1816A_TIMER_ENABLE)) {
snd_ad1816a_write(chip, AD1816A_TIMER_BASE_COUNT,
timer->sticks & 0xffff);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_TIMER_ENABLE, 0xffff);
}
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static int snd_ad1816a_timer_stop(struct snd_timer *timer)
{
unsigned long flags;
struct snd_ad1816a *chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->lock, flags);
snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
AD1816A_TIMER_ENABLE, 0x0000);
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static const struct snd_timer_hardware snd_ad1816a_timer_table = {
.flags = SNDRV_TIMER_HW_AUTO,
.resolution = 10000,
.ticks = 65535,
.open = snd_ad1816a_timer_open,
.close = snd_ad1816a_timer_close,
.c_resolution = snd_ad1816a_timer_resolution,
.start = snd_ad1816a_timer_start,
.stop = snd_ad1816a_timer_stop,
};
static int snd_ad1816a_playback_open(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int error;
error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK);
if (error < 0)
return error;
runtime->hw = snd_ad1816a_playback;
snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
chip->playback_substream = substream;
return 0;
}
static int snd_ad1816a_capture_open(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int error;
error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE);
if (error < 0)
return error;
runtime->hw = snd_ad1816a_capture;
snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
chip->capture_substream = substream;
return 0;
}
static int snd_ad1816a_playback_close(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
chip->playback_substream = NULL;
snd_ad1816a_close(chip, AD1816A_MODE_PLAYBACK);
return 0;
}
static int snd_ad1816a_capture_close(struct snd_pcm_substream *substream)
{
struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
chip->capture_substream = NULL;
snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE);
return 0;
}
static void snd_ad1816a_init(struct snd_ad1816a *chip)
{
unsigned long flags;
spin_lock_irqsave(&chip->lock, flags);
snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
snd_ad1816a_write(chip, AD1816A_INTERRUPT_ENABLE, 0x0000);
snd_ad1816a_write_mask(chip, AD1816A_CHIP_CONFIG,
AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff);
snd_ad1816a_write(chip, AD1816A_DSP_CONFIG, 0x0000);
snd_ad1816a_write(chip, AD1816A_POWERDOWN_CTRL, 0x0000);
spin_unlock_irqrestore(&chip->lock, flags);
}
#ifdef CONFIG_PM
void snd_ad1816a_suspend(struct snd_ad1816a *chip)
{
int reg;
unsigned long flags;
spin_lock_irqsave(&chip->lock, flags);
for (reg = 0; reg < 48; reg++)
chip->image[reg] = snd_ad1816a_read(chip, reg);
spin_unlock_irqrestore(&chip->lock, flags);
}
void snd_ad1816a_resume(struct snd_ad1816a *chip)
{
int reg;
unsigned long flags;
snd_ad1816a_init(chip);
spin_lock_irqsave(&chip->lock, flags);
for (reg = 0; reg < 48; reg++)
snd_ad1816a_write(chip, reg, chip->image[reg]);
spin_unlock_irqrestore(&chip->lock, flags);
}
#endif
static int snd_ad1816a_probe(struct snd_ad1816a *chip)
{
unsigned long flags;
spin_lock_irqsave(&chip->lock, flags);
switch (chip->version = snd_ad1816a_read(chip, AD1816A_VERSION_ID)) {
case 0:
chip->hardware = AD1816A_HW_AD1815;
break;
case 1:
chip->hardware = AD1816A_HW_AD18MAX10;
break;
case 3:
chip->hardware = AD1816A_HW_AD1816A;
break;
default:
chip->hardware = AD1816A_HW_AUTO;
}
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static const char *snd_ad1816a_chip_id(struct snd_ad1816a *chip)
{
switch (chip->hardware) {
case AD1816A_HW_AD1816A: return "AD1816A";
case AD1816A_HW_AD1815: return "AD1815";
case AD1816A_HW_AD18MAX10: return "AD18max10";
default:
snd_printk(KERN_WARNING "Unknown chip version %d:%d.\n",
chip->version, chip->hardware);
return "AD1816A - unknown";
}
}
int snd_ad1816a_create(struct snd_card *card,
unsigned long port, int irq, int dma1, int dma2,
struct snd_ad1816a *chip)
{
int error;
chip->irq = -1;
chip->dma1 = -1;
chip->dma2 = -1;
chip->res_port = devm_request_region(card->dev, port, 16, "AD1816A");
if (!chip->res_port) {
snd_printk(KERN_ERR "ad1816a: can't grab port 0x%lx\n", port);
return -EBUSY;
}
if (devm_request_irq(card->dev, irq, snd_ad1816a_interrupt, 0,
"AD1816A", (void *) chip)) {
snd_printk(KERN_ERR "ad1816a: can't grab IRQ %d\n", irq);
return -EBUSY;
}
chip->irq = irq;
card->sync_irq = chip->irq;
if (snd_devm_request_dma(card->dev, dma1, "AD1816A - 1")) {
snd_printk(KERN_ERR "ad1816a: can't grab DMA1 %d\n", dma1);
return -EBUSY;
}
chip->dma1 = dma1;
if (snd_devm_request_dma(card->dev, dma2, "AD1816A - 2")) {
snd_printk(KERN_ERR "ad1816a: can't grab DMA2 %d\n", dma2);
return -EBUSY;
}
chip->dma2 = dma2;
chip->card = card;
chip->port = port;
spin_lock_init(&chip->lock);
error = snd_ad1816a_probe(chip);
if (error)
return error;
snd_ad1816a_init(chip);
return 0;
}
static const struct snd_pcm_ops snd_ad1816a_playback_ops = {
.open = snd_ad1816a_playback_open,
.close = snd_ad1816a_playback_close,
.prepare = snd_ad1816a_playback_prepare,
.trigger = snd_ad1816a_playback_trigger,
.pointer = snd_ad1816a_playback_pointer,
};
static const struct snd_pcm_ops snd_ad1816a_capture_ops = {
.open = snd_ad1816a_capture_open,
.close = snd_ad1816a_capture_close,
.prepare = snd_ad1816a_capture_prepare,
.trigger = snd_ad1816a_capture_trigger,
.pointer = snd_ad1816a_capture_pointer,
};
int snd_ad1816a_pcm(struct snd_ad1816a *chip, int device)
{
int error;
struct snd_pcm *pcm;
error = snd_pcm_new(chip->card, "AD1816A", device, 1, 1, &pcm);
if (error)
return error;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1816a_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1816a_capture_ops);
pcm->private_data = chip;
pcm->info_flags = (chip->dma1 == chip->dma2 ) ? SNDRV_PCM_INFO_JOINT_DUPLEX : 0;
strcpy(pcm->name, snd_ad1816a_chip_id(chip));
snd_ad1816a_init(chip);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, chip->card->dev,
64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
chip->pcm = pcm;
return 0;
}
int snd_ad1816a_timer(struct snd_ad1816a *chip, int device)
{
struct snd_timer *timer;
struct snd_timer_id tid;
int error;
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.card = chip->card->number;
tid.device = device;
tid.subdevice = 0;
error = snd_timer_new(chip->card, "AD1816A", &tid, &timer);
if (error < 0)
return error;
strcpy(timer->name, snd_ad1816a_chip_id(chip));
timer->private_data = chip;
chip->timer = timer;
timer->hw = snd_ad1816a_timer_table;
return 0;
}
/*
*
*/
static int snd_ad1816a_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[8] = {
"Line", "Mix", "CD", "Synth", "Video",
"Mic", "Phone",
};
return snd_ctl_enum_info(uinfo, 2, 7, texts);
}
static int snd_ad1816a_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
unsigned short val;
spin_lock_irqsave(&chip->lock, flags);
val = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL);
spin_unlock_irqrestore(&chip->lock, flags);
ucontrol->value.enumerated.item[0] = (val >> 12) & 7;
ucontrol->value.enumerated.item[1] = (val >> 4) & 7;
return 0;
}
static int snd_ad1816a_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
unsigned short val;
int change;
if (ucontrol->value.enumerated.item[0] > 6 ||
ucontrol->value.enumerated.item[1] > 6)
return -EINVAL;
val = (ucontrol->value.enumerated.item[0] << 12) |
(ucontrol->value.enumerated.item[1] << 4);
spin_lock_irqsave(&chip->lock, flags);
change = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL) != val;
snd_ad1816a_write(chip, AD1816A_ADC_SOURCE_SEL, val);
spin_unlock_irqrestore(&chip->lock, flags);
return change;
}
#define AD1816A_SINGLE_TLV(xname, reg, shift, mask, invert, xtlv) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.name = xname, .info = snd_ad1816a_info_single, \
.get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
.private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
.tlv = { .p = (xtlv) } }
#define AD1816A_SINGLE(xname, reg, shift, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_single, \
.get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
.private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
static int snd_ad1816a_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 16) & 0xff;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_ad1816a_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0xff;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
spin_lock_irqsave(&chip->lock, flags);
ucontrol->value.integer.value[0] = (snd_ad1816a_read(chip, reg) >> shift) & mask;
spin_unlock_irqrestore(&chip->lock, flags);
if (invert)
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
return 0;
}
static int snd_ad1816a_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0xff;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
int change;
unsigned short old_val, val;
val = (ucontrol->value.integer.value[0] & mask);
if (invert)
val = mask - val;
val <<= shift;
spin_lock_irqsave(&chip->lock, flags);
old_val = snd_ad1816a_read(chip, reg);
val = (old_val & ~(mask << shift)) | val;
change = val != old_val;
snd_ad1816a_write(chip, reg, val);
spin_unlock_irqrestore(&chip->lock, flags);
return change;
}
#define AD1816A_DOUBLE_TLV(xname, reg, shift_left, shift_right, mask, invert, xtlv) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.name = xname, .info = snd_ad1816a_info_double, \
.get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
.private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24), \
.tlv = { .p = (xtlv) } }
#define AD1816A_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_double, \
.get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
.private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) }
static int snd_ad1816a_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 16) & 0xff;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_ad1816a_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift_left = (kcontrol->private_value >> 8) & 0x0f;
int shift_right = (kcontrol->private_value >> 12) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
unsigned short val;
spin_lock_irqsave(&chip->lock, flags);
val = snd_ad1816a_read(chip, reg);
ucontrol->value.integer.value[0] = (val >> shift_left) & mask;
ucontrol->value.integer.value[1] = (val >> shift_right) & mask;
spin_unlock_irqrestore(&chip->lock, flags);
if (invert) {
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
}
return 0;
}
static int snd_ad1816a_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift_left = (kcontrol->private_value >> 8) & 0x0f;
int shift_right = (kcontrol->private_value >> 12) & 0x0f;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
int change;
unsigned short old_val, val1, val2;
val1 = ucontrol->value.integer.value[0] & mask;
val2 = ucontrol->value.integer.value[1] & mask;
if (invert) {
val1 = mask - val1;
val2 = mask - val2;
}
val1 <<= shift_left;
val2 <<= shift_right;
spin_lock_irqsave(&chip->lock, flags);
old_val = snd_ad1816a_read(chip, reg);
val1 = (old_val & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
change = val1 != old_val;
snd_ad1816a_write(chip, reg, val1);
spin_unlock_irqrestore(&chip->lock, flags);
return change;
}
static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
static const struct snd_kcontrol_new snd_ad1816a_controls[] = {
AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1,
db_scale_5bit),
AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1,
db_scale_6bit),
AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1,
db_scale_5bit_12db_max),
AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1,
db_scale_5bit_12db_max),
AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1,
db_scale_5bit_12db_max),
AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1,
db_scale_6bit),
AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1),
AD1816A_SINGLE_TLV("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1,
db_scale_5bit_12db_max),
AD1816A_SINGLE("Mic Boost", AD1816A_MIC_GAIN_ATT, 14, 1, 0),
AD1816A_DOUBLE("Video Playback Switch", AD1816A_VID_GAIN_ATT, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1,
db_scale_5bit_12db_max),
AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1),
AD1816A_SINGLE_TLV("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1,
db_scale_4bit),
AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1),
AD1816A_SINGLE_TLV("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1,
db_scale_5bit),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = snd_ad1816a_info_mux,
.get = snd_ad1816a_get_mux,
.put = snd_ad1816a_put_mux,
},
AD1816A_DOUBLE("Capture Switch", AD1816A_ADC_PGA, 15, 7, 1, 1),
AD1816A_DOUBLE_TLV("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0,
db_scale_rec_gain),
AD1816A_SINGLE("3D Control - Switch", AD1816A_3D_PHAT_CTRL, 15, 1, 1),
AD1816A_SINGLE("3D Control - Level", AD1816A_3D_PHAT_CTRL, 0, 15, 0),
};
int snd_ad1816a_mixer(struct snd_ad1816a *chip)
{
struct snd_card *card;
unsigned int idx;
int err;
if (snd_BUG_ON(!chip || !chip->card))
return -EINVAL;
card = chip->card;
strcpy(card->mixername, snd_ad1816a_chip_id(chip));
for (idx = 0; idx < ARRAY_SIZE(snd_ad1816a_controls); idx++) {
err = snd_ctl_add(card, snd_ctl_new1(&snd_ad1816a_controls[idx], chip));
if (err < 0)
return err;
}
return 0;
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Audio Codec driver supporting:
* AD1835A, AD1836, AD1837A, AD1838A, AD1839A
*
* Copyright 2009-2011 Analog Devices Inc.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include "ad1836.h"
enum ad1836_type {
AD1835,
AD1836,
AD1838,
};
/* codec private data */
struct ad1836_priv {
enum ad1836_type type;
struct regmap *regmap;
};
/*
* AD1836 volume/mute/de-emphasis etc. controls
*/
static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
static SOC_ENUM_SINGLE_DECL(ad1836_deemp_enum,
AD1836_DAC_CTRL1, 8, ad1836_deemp);
#define AD1836_DAC_VOLUME(x) \
SOC_DOUBLE_R("DAC" #x " Playback Volume", AD1836_DAC_L_VOL(x), \
AD1836_DAC_R_VOL(x), 0, 0x3FF, 0)
#define AD1836_DAC_SWITCH(x) \
SOC_DOUBLE("DAC" #x " Playback Switch", AD1836_DAC_CTRL2, \
AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
#define AD1836_ADC_SWITCH(x) \
SOC_DOUBLE("ADC" #x " Capture Switch", AD1836_ADC_CTRL2, \
AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
static const struct snd_kcontrol_new ad183x_dac_controls[] = {
AD1836_DAC_VOLUME(1),
AD1836_DAC_SWITCH(1),
AD1836_DAC_VOLUME(2),
AD1836_DAC_SWITCH(2),
AD1836_DAC_VOLUME(3),
AD1836_DAC_SWITCH(3),
AD1836_DAC_VOLUME(4),
AD1836_DAC_SWITCH(4),
};
static const struct snd_soc_dapm_widget ad183x_dac_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
SND_SOC_DAPM_OUTPUT("DAC4OUT"),
};
static const struct snd_soc_dapm_route ad183x_dac_routes[] = {
{ "DAC1OUT", NULL, "DAC" },
{ "DAC2OUT", NULL, "DAC" },
{ "DAC3OUT", NULL, "DAC" },
{ "DAC4OUT", NULL, "DAC" },
};
static const struct snd_kcontrol_new ad183x_adc_controls[] = {
AD1836_ADC_SWITCH(1),
AD1836_ADC_SWITCH(2),
AD1836_ADC_SWITCH(3),
};
static const struct snd_soc_dapm_widget ad183x_adc_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("ADC1IN"),
SND_SOC_DAPM_INPUT("ADC2IN"),
};
static const struct snd_soc_dapm_route ad183x_adc_routes[] = {
{ "ADC", NULL, "ADC1IN" },
{ "ADC", NULL, "ADC2IN" },
};
static const struct snd_kcontrol_new ad183x_controls[] = {
/* ADC high-pass filter */
SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
AD1836_ADC_HIGHPASS_FILTER, 1, 0),
/* DAC de-emphasis */
SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
};
static const struct snd_soc_dapm_widget ad183x_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
AD1836_DAC_POWERDOWN, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
AD1836_ADC_POWERDOWN, 1, NULL, 0),
};
static const struct snd_soc_dapm_route ad183x_dapm_routes[] = {
{ "DAC", NULL, "ADC_PWR" },
{ "ADC", NULL, "ADC_PWR" },
};
static const DECLARE_TLV_DB_SCALE(ad1836_in_tlv, 0, 300, 0);
static const struct snd_kcontrol_new ad1836_controls[] = {
SOC_DOUBLE_TLV("ADC2 Capture Volume", AD1836_ADC_CTRL1, 3, 0, 4, 0,
ad1836_in_tlv),
};
/*
* DAI ops entries
*/
static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
/* at present, we support adc aux mode to interface with
* blackfin sport tdm mode
*/
case SND_SOC_DAIFMT_DSP_A:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_IF:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
/* ALCLK,ABCLK are both output, AD1836 can only be provider */
case SND_SOC_DAIFMT_CBP_CFP:
break;
default:
return -EINVAL;
}
return 0;
}
static int ad1836_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(dai->component);
int word_len = 0;
/* bit size */
switch (params_width(params)) {
case 16:
word_len = AD1836_WORD_LEN_16;
break;
case 20:
word_len = AD1836_WORD_LEN_20;
break;
case 24:
case 32:
word_len = AD1836_WORD_LEN_24;
break;
default:
return -EINVAL;
}
regmap_update_bits(ad1836->regmap, AD1836_DAC_CTRL1,
AD1836_DAC_WORD_LEN_MASK,
word_len << AD1836_DAC_WORD_LEN_OFFSET);
regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_WORD_LEN_MASK,
word_len << AD1836_ADC_WORD_OFFSET);
return 0;
}
static const struct snd_soc_dai_ops ad1836_dai_ops = {
.hw_params = ad1836_hw_params,
.set_fmt = ad1836_set_dai_fmt,
};
#define AD183X_DAI(_name, num_dacs, num_adcs) \
{ \
.name = _name "-hifi", \
.playback = { \
.stream_name = "Playback", \
.channels_min = 2, \
.channels_max = (num_dacs) * 2, \
.rates = SNDRV_PCM_RATE_48000, \
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
}, \
.capture = { \
.stream_name = "Capture", \
.channels_min = 2, \
.channels_max = (num_adcs) * 2, \
.rates = SNDRV_PCM_RATE_48000, \
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
}, \
.ops = &ad1836_dai_ops, \
}
static struct snd_soc_dai_driver ad183x_dais[] = {
[AD1835] = AD183X_DAI("ad1835", 4, 1),
[AD1836] = AD183X_DAI("ad1836", 3, 2),
[AD1838] = AD183X_DAI("ad1838", 3, 1),
};
#ifdef CONFIG_PM
static int ad1836_suspend(struct snd_soc_component *component)
{
struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component);
/* reset clock control mode */
return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_SERFMT_MASK, 0);
}
static int ad1836_resume(struct snd_soc_component *component)
{
struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component);
/* restore clock control mode */
return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_SERFMT_MASK, AD1836_ADC_AUX);
}
#else
#define ad1836_suspend NULL
#define ad1836_resume NULL
#endif
static int ad1836_probe(struct snd_soc_component *component)
{
struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
int num_dacs, num_adcs;
int ret = 0;
int i;
num_dacs = ad183x_dais[ad1836->type].playback.channels_max / 2;
num_adcs = ad183x_dais[ad1836->type].capture.channels_max / 2;
/* default setting for ad1836 */
/* de-emphasis: 48kHz, power-on dac */
regmap_write(ad1836->regmap, AD1836_DAC_CTRL1, 0x300);
/* unmute dac channels */
regmap_write(ad1836->regmap, AD1836_DAC_CTRL2, 0x0);
/* high-pass filter enable, power-on adc */
regmap_write(ad1836->regmap, AD1836_ADC_CTRL1, 0x100);
/* unmute adc channles, adc aux mode */
regmap_write(ad1836->regmap, AD1836_ADC_CTRL2, 0x180);
/* volume */
for (i = 1; i <= num_dacs; ++i) {
regmap_write(ad1836->regmap, AD1836_DAC_L_VOL(i), 0x3FF);
regmap_write(ad1836->regmap, AD1836_DAC_R_VOL(i), 0x3FF);
}
if (ad1836->type == AD1836) {
/* left/right diff:PGA/MUX */
regmap_write(ad1836->regmap, AD1836_ADC_CTRL3, 0x3A);
ret = snd_soc_add_component_controls(component, ad1836_controls,
ARRAY_SIZE(ad1836_controls));
if (ret)
return ret;
} else {
regmap_write(ad1836->regmap, AD1836_ADC_CTRL3, 0x00);
}
ret = snd_soc_add_component_controls(component, ad183x_dac_controls, num_dacs * 2);
if (ret)
return ret;
ret = snd_soc_add_component_controls(component, ad183x_adc_controls, num_adcs);
if (ret)
return ret;
ret = snd_soc_dapm_new_controls(dapm, ad183x_dac_dapm_widgets, num_dacs);
if (ret)
return ret;
ret = snd_soc_dapm_new_controls(dapm, ad183x_adc_dapm_widgets, num_adcs);
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(dapm, ad183x_dac_routes, num_dacs);
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(dapm, ad183x_adc_routes, num_adcs);
return ret;
}
/* power down chip */
static void ad1836_remove(struct snd_soc_component *component)
{
struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component);
/* reset clock control mode */
regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2,
AD1836_ADC_SERFMT_MASK, 0);
}
static const struct snd_soc_component_driver soc_component_dev_ad1836 = {
.probe = ad1836_probe,
.remove = ad1836_remove,
.suspend = ad1836_suspend,
.resume = ad1836_resume,
.controls = ad183x_controls,
.num_controls = ARRAY_SIZE(ad183x_controls),
.dapm_widgets = ad183x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad183x_dapm_widgets),
.dapm_routes = ad183x_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ad183x_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static const struct reg_default ad1836_reg_defaults[] = {
{ AD1836_DAC_CTRL1, 0x0000 },
{ AD1836_DAC_CTRL2, 0x0000 },
{ AD1836_DAC_L_VOL(0), 0x0000 },
{ AD1836_DAC_R_VOL(0), 0x0000 },
{ AD1836_DAC_L_VOL(1), 0x0000 },
{ AD1836_DAC_R_VOL(1), 0x0000 },
{ AD1836_DAC_L_VOL(2), 0x0000 },
{ AD1836_DAC_R_VOL(2), 0x0000 },
{ AD1836_DAC_L_VOL(3), 0x0000 },
{ AD1836_DAC_R_VOL(3), 0x0000 },
{ AD1836_ADC_CTRL1, 0x0000 },
{ AD1836_ADC_CTRL2, 0x0000 },
{ AD1836_ADC_CTRL3, 0x0000 },
};
static const struct regmap_config ad1836_regmap_config = {
.val_bits = 12,
.reg_bits = 4,
.read_flag_mask = 0x08,
.max_register = AD1836_ADC_CTRL3,
.reg_defaults = ad1836_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(ad1836_reg_defaults),
.cache_type = REGCACHE_RBTREE,
};
static int ad1836_spi_probe(struct spi_device *spi)
{
struct ad1836_priv *ad1836;
int ret;
ad1836 = devm_kzalloc(&spi->dev, sizeof(struct ad1836_priv),
GFP_KERNEL);
if (ad1836 == NULL)
return -ENOMEM;
ad1836->regmap = devm_regmap_init_spi(spi, &ad1836_regmap_config);
if (IS_ERR(ad1836->regmap))
return PTR_ERR(ad1836->regmap);
ad1836->type = spi_get_device_id(spi)->driver_data;
spi_set_drvdata(spi, ad1836);
ret = devm_snd_soc_register_component(&spi->dev,
&soc_component_dev_ad1836, &ad183x_dais[ad1836->type], 1);
return ret;
}
static const struct spi_device_id ad1836_ids[] = {
{ "ad1835", AD1835 },
{ "ad1836", AD1836 },
{ "ad1837", AD1835 },
{ "ad1838", AD1838 },
{ "ad1839", AD1838 },
{ },
};
MODULE_DEVICE_TABLE(spi, ad1836_ids);
static struct spi_driver ad1836_spi_driver = {
.driver = {
.name = "ad1836",
},
.probe = ad1836_spi_probe,
.id_table = ad1836_ids,
};
module_spi_driver(ad1836_spi_driver);
MODULE_DESCRIPTION("ASoC ad1836 driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AD1843 low level driver
*
* Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org>
* Copyright 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de>
*
* inspired from vwsnd.c (SGI VW audio driver)
* Copyright 1999 Silicon Graphics, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ad1843.h>
/*
* AD1843 bitfield definitions. All are named as in the AD1843 data
* sheet, with ad1843_ prepended and individual bit numbers removed.
*
* E.g., bits LSS0 through LSS2 become ad1843_LSS.
*
* Only the bitfields we need are defined.
*/
struct ad1843_bitfield {
char reg;
char lo_bit;
char nbits;
};
static const struct ad1843_bitfield
ad1843_PDNO = { 0, 14, 1 }, /* Converter Power-Down Flag */
ad1843_INIT = { 0, 15, 1 }, /* Clock Initialization Flag */
ad1843_RIG = { 2, 0, 4 }, /* Right ADC Input Gain */
ad1843_RMGE = { 2, 4, 1 }, /* Right ADC Mic Gain Enable */
ad1843_RSS = { 2, 5, 3 }, /* Right ADC Source Select */
ad1843_LIG = { 2, 8, 4 }, /* Left ADC Input Gain */
ad1843_LMGE = { 2, 12, 1 }, /* Left ADC Mic Gain Enable */
ad1843_LSS = { 2, 13, 3 }, /* Left ADC Source Select */
ad1843_RD2M = { 3, 0, 5 }, /* Right DAC 2 Mix Gain/Atten */
ad1843_RD2MM = { 3, 7, 1 }, /* Right DAC 2 Mix Mute */
ad1843_LD2M = { 3, 8, 5 }, /* Left DAC 2 Mix Gain/Atten */
ad1843_LD2MM = { 3, 15, 1 }, /* Left DAC 2 Mix Mute */
ad1843_RX1M = { 4, 0, 5 }, /* Right Aux 1 Mix Gain/Atten */
ad1843_RX1MM = { 4, 7, 1 }, /* Right Aux 1 Mix Mute */
ad1843_LX1M = { 4, 8, 5 }, /* Left Aux 1 Mix Gain/Atten */
ad1843_LX1MM = { 4, 15, 1 }, /* Left Aux 1 Mix Mute */
ad1843_RX2M = { 5, 0, 5 }, /* Right Aux 2 Mix Gain/Atten */
ad1843_RX2MM = { 5, 7, 1 }, /* Right Aux 2 Mix Mute */
ad1843_LX2M = { 5, 8, 5 }, /* Left Aux 2 Mix Gain/Atten */
ad1843_LX2MM = { 5, 15, 1 }, /* Left Aux 2 Mix Mute */
ad1843_RMCM = { 7, 0, 5 }, /* Right Mic Mix Gain/Atten */
ad1843_RMCMM = { 7, 7, 1 }, /* Right Mic Mix Mute */
ad1843_LMCM = { 7, 8, 5 }, /* Left Mic Mix Gain/Atten */
ad1843_LMCMM = { 7, 15, 1 }, /* Left Mic Mix Mute */
ad1843_HPOS = { 8, 4, 1 }, /* Headphone Output Voltage Swing */
ad1843_HPOM = { 8, 5, 1 }, /* Headphone Output Mute */
ad1843_MPOM = { 8, 6, 1 }, /* Mono Output Mute */
ad1843_RDA1G = { 9, 0, 6 }, /* Right DAC1 Analog/Digital Gain */
ad1843_RDA1GM = { 9, 7, 1 }, /* Right DAC1 Analog Mute */
ad1843_LDA1G = { 9, 8, 6 }, /* Left DAC1 Analog/Digital Gain */
ad1843_LDA1GM = { 9, 15, 1 }, /* Left DAC1 Analog Mute */
ad1843_RDA2G = { 10, 0, 6 }, /* Right DAC2 Analog/Digital Gain */
ad1843_RDA2GM = { 10, 7, 1 }, /* Right DAC2 Analog Mute */
ad1843_LDA2G = { 10, 8, 6 }, /* Left DAC2 Analog/Digital Gain */
ad1843_LDA2GM = { 10, 15, 1 }, /* Left DAC2 Analog Mute */
ad1843_RDA1AM = { 11, 7, 1 }, /* Right DAC1 Digital Mute */
ad1843_LDA1AM = { 11, 15, 1 }, /* Left DAC1 Digital Mute */
ad1843_RDA2AM = { 12, 7, 1 }, /* Right DAC2 Digital Mute */
ad1843_LDA2AM = { 12, 15, 1 }, /* Left DAC2 Digital Mute */
ad1843_ADLC = { 15, 0, 2 }, /* ADC Left Sample Rate Source */
ad1843_ADRC = { 15, 2, 2 }, /* ADC Right Sample Rate Source */
ad1843_DA1C = { 15, 8, 2 }, /* DAC1 Sample Rate Source */
ad1843_DA2C = { 15, 10, 2 }, /* DAC2 Sample Rate Source */
ad1843_C1C = { 17, 0, 16 }, /* Clock 1 Sample Rate Select */
ad1843_C2C = { 20, 0, 16 }, /* Clock 2 Sample Rate Select */
ad1843_C3C = { 23, 0, 16 }, /* Clock 3 Sample Rate Select */
ad1843_DAADL = { 25, 4, 2 }, /* Digital ADC Left Source Select */
ad1843_DAADR = { 25, 6, 2 }, /* Digital ADC Right Source Select */
ad1843_DAMIX = { 25, 14, 1 }, /* DAC Digital Mix Enable */
ad1843_DRSFLT = { 25, 15, 1 }, /* Digital Reampler Filter Mode */
ad1843_ADLF = { 26, 0, 2 }, /* ADC Left Channel Data Format */
ad1843_ADRF = { 26, 2, 2 }, /* ADC Right Channel Data Format */
ad1843_ADTLK = { 26, 4, 1 }, /* ADC Transmit Lock Mode Select */
ad1843_SCF = { 26, 7, 1 }, /* SCLK Frequency Select */
ad1843_DA1F = { 26, 8, 2 }, /* DAC1 Data Format Select */
ad1843_DA2F = { 26, 10, 2 }, /* DAC2 Data Format Select */
ad1843_DA1SM = { 26, 14, 1 }, /* DAC1 Stereo/Mono Mode Select */
ad1843_DA2SM = { 26, 15, 1 }, /* DAC2 Stereo/Mono Mode Select */
ad1843_ADLEN = { 27, 0, 1 }, /* ADC Left Channel Enable */
ad1843_ADREN = { 27, 1, 1 }, /* ADC Right Channel Enable */
ad1843_AAMEN = { 27, 4, 1 }, /* Analog to Analog Mix Enable */
ad1843_ANAEN = { 27, 7, 1 }, /* Analog Channel Enable */
ad1843_DA1EN = { 27, 8, 1 }, /* DAC1 Enable */
ad1843_DA2EN = { 27, 9, 1 }, /* DAC2 Enable */
ad1843_DDMEN = { 27, 12, 1 }, /* DAC2 to DAC1 Mix Enable */
ad1843_C1EN = { 28, 11, 1 }, /* Clock Generator 1 Enable */
ad1843_C2EN = { 28, 12, 1 }, /* Clock Generator 2 Enable */
ad1843_C3EN = { 28, 13, 1 }, /* Clock Generator 3 Enable */
ad1843_PDNI = { 28, 15, 1 }; /* Converter Power Down */
/*
* The various registers of the AD1843 use three different formats for
* specifying gain. The ad1843_gain structure parameterizes the
* formats.
*/
struct ad1843_gain {
int negative; /* nonzero if gain is negative. */
const struct ad1843_bitfield *lfield;
const struct ad1843_bitfield *rfield;
const struct ad1843_bitfield *lmute;
const struct ad1843_bitfield *rmute;
};
static const struct ad1843_gain ad1843_gain_RECLEV = {
.negative = 0,
.lfield = &ad1843_LIG,
.rfield = &ad1843_RIG
};
static const struct ad1843_gain ad1843_gain_LINE = {
.negative = 1,
.lfield = &ad1843_LX1M,
.rfield = &ad1843_RX1M,
.lmute = &ad1843_LX1MM,
.rmute = &ad1843_RX1MM
};
static const struct ad1843_gain ad1843_gain_LINE_2 = {
.negative = 1,
.lfield = &ad1843_LDA2G,
.rfield = &ad1843_RDA2G,
.lmute = &ad1843_LDA2GM,
.rmute = &ad1843_RDA2GM
};
static const struct ad1843_gain ad1843_gain_MIC = {
.negative = 1,
.lfield = &ad1843_LMCM,
.rfield = &ad1843_RMCM,
.lmute = &ad1843_LMCMM,
.rmute = &ad1843_RMCMM
};
static const struct ad1843_gain ad1843_gain_PCM_0 = {
.negative = 1,
.lfield = &ad1843_LDA1G,
.rfield = &ad1843_RDA1G,
.lmute = &ad1843_LDA1GM,
.rmute = &ad1843_RDA1GM
};
static const struct ad1843_gain ad1843_gain_PCM_1 = {
.negative = 1,
.lfield = &ad1843_LD2M,
.rfield = &ad1843_RD2M,
.lmute = &ad1843_LD2MM,
.rmute = &ad1843_RD2MM
};
static const struct ad1843_gain *ad1843_gain[AD1843_GAIN_SIZE] =
{
&ad1843_gain_RECLEV,
&ad1843_gain_LINE,
&ad1843_gain_LINE_2,
&ad1843_gain_MIC,
&ad1843_gain_PCM_0,
&ad1843_gain_PCM_1,
};
/* read the current value of an AD1843 bitfield. */
static int ad1843_read_bits(struct snd_ad1843 *ad1843,
const struct ad1843_bitfield *field)
{
int w;
w = ad1843->read(ad1843->chip, field->reg);
return w >> field->lo_bit & ((1 << field->nbits) - 1);
}
/*
* write a new value to an AD1843 bitfield and return the old value.
*/
static int ad1843_write_bits(struct snd_ad1843 *ad1843,
const struct ad1843_bitfield *field,
int newval)
{
int w, mask, oldval, newbits;
w = ad1843->read(ad1843->chip, field->reg);
mask = ((1 << field->nbits) - 1) << field->lo_bit;
oldval = (w & mask) >> field->lo_bit;
newbits = (newval << field->lo_bit) & mask;
w = (w & ~mask) | newbits;
ad1843->write(ad1843->chip, field->reg, w);
return oldval;
}
/*
* ad1843_read_multi reads multiple bitfields from the same AD1843
* register. It uses a single read cycle to do it. (Reading the
* ad1843 requires 256 bit times at 12.288 MHz, or nearly 20
* microseconds.)
*
* Called like this.
*
* ad1843_read_multi(ad1843, nfields,
* &ad1843_FIELD1, &val1,
* &ad1843_FIELD2, &val2, ...);
*/
static void ad1843_read_multi(struct snd_ad1843 *ad1843, int argcount, ...)
{
va_list ap;
const struct ad1843_bitfield *fp;
int w = 0, mask, *value, reg = -1;
va_start(ap, argcount);
while (--argcount >= 0) {
fp = va_arg(ap, const struct ad1843_bitfield *);
value = va_arg(ap, int *);
if (reg == -1) {
reg = fp->reg;
w = ad1843->read(ad1843->chip, reg);
}
mask = (1 << fp->nbits) - 1;
*value = w >> fp->lo_bit & mask;
}
va_end(ap);
}
/*
* ad1843_write_multi stores multiple bitfields into the same AD1843
* register. It uses one read and one write cycle to do it.
*
* Called like this.
*
* ad1843_write_multi(ad1843, nfields,
* &ad1843_FIELD1, val1,
* &ad1843_FIELF2, val2, ...);
*/
static void ad1843_write_multi(struct snd_ad1843 *ad1843, int argcount, ...)
{
va_list ap;
int reg;
const struct ad1843_bitfield *fp;
int value;
int w, m, mask, bits;
mask = 0;
bits = 0;
reg = -1;
va_start(ap, argcount);
while (--argcount >= 0) {
fp = va_arg(ap, const struct ad1843_bitfield *);
value = va_arg(ap, int);
if (reg == -1)
reg = fp->reg;
else
WARN_ON(reg != fp->reg);
m = ((1 << fp->nbits) - 1) << fp->lo_bit;
mask |= m;
bits |= (value << fp->lo_bit) & m;
}
va_end(ap);
if (~mask & 0xFFFF)
w = ad1843->read(ad1843->chip, reg);
else
w = 0;
w = (w & ~mask) | bits;
ad1843->write(ad1843->chip, reg, w);
}
int ad1843_get_gain_max(struct snd_ad1843 *ad1843, int id)
{
const struct ad1843_gain *gp = ad1843_gain[id];
int ret;
ret = (1 << gp->lfield->nbits);
if (!gp->lmute)
ret -= 1;
return ret;
}
/*
* ad1843_get_gain reads the specified register and extracts the gain value
* using the supplied gain type.
*/
int ad1843_get_gain(struct snd_ad1843 *ad1843, int id)
{
int lg, rg, lm, rm;
const struct ad1843_gain *gp = ad1843_gain[id];
unsigned short mask = (1 << gp->lfield->nbits) - 1;
ad1843_read_multi(ad1843, 2, gp->lfield, &lg, gp->rfield, &rg);
if (gp->negative) {
lg = mask - lg;
rg = mask - rg;
}
if (gp->lmute) {
ad1843_read_multi(ad1843, 2, gp->lmute, &lm, gp->rmute, &rm);
if (lm)
lg = 0;
if (rm)
rg = 0;
}
return lg << 0 | rg << 8;
}
/*
* Set an audio channel's gain.
*
* Returns the new gain, which may be lower than the old gain.
*/
int ad1843_set_gain(struct snd_ad1843 *ad1843, int id, int newval)
{
const struct ad1843_gain *gp = ad1843_gain[id];
unsigned short mask = (1 << gp->lfield->nbits) - 1;
int lg = (newval >> 0) & mask;
int rg = (newval >> 8) & mask;
int lm = (lg == 0) ? 1 : 0;
int rm = (rg == 0) ? 1 : 0;
if (gp->negative) {
lg = mask - lg;
rg = mask - rg;
}
if (gp->lmute)
ad1843_write_multi(ad1843, 2, gp->lmute, lm, gp->rmute, rm);
ad1843_write_multi(ad1843, 2, gp->lfield, lg, gp->rfield, rg);
return ad1843_get_gain(ad1843, id);
}
/* Returns the current recording source */
int ad1843_get_recsrc(struct snd_ad1843 *ad1843)
{
int val = ad1843_read_bits(ad1843, &ad1843_LSS);
if (val < 0 || val > 2) {
val = 2;
ad1843_write_multi(ad1843, 2,
&ad1843_LSS, val, &ad1843_RSS, val);
}
return val;
}
/*
* Set recording source.
*
* Returns newsrc on success, -errno on failure.
*/
int ad1843_set_recsrc(struct snd_ad1843 *ad1843, int newsrc)
{
if (newsrc < 0 || newsrc > 2)
return -EINVAL;
ad1843_write_multi(ad1843, 2, &ad1843_LSS, newsrc, &ad1843_RSS, newsrc);
return newsrc;
}
/* Setup ad1843 for D/A conversion. */
void ad1843_setup_dac(struct snd_ad1843 *ad1843,
unsigned int id,
unsigned int framerate,
snd_pcm_format_t fmt,
unsigned int channels)
{
int ad_fmt = 0, ad_mode = 0;
switch (fmt) {
case SNDRV_PCM_FORMAT_S8:
ad_fmt = 0;
break;
case SNDRV_PCM_FORMAT_U8:
ad_fmt = 0;
break;
case SNDRV_PCM_FORMAT_S16_LE:
ad_fmt = 1;
break;
case SNDRV_PCM_FORMAT_MU_LAW:
ad_fmt = 2;
break;
case SNDRV_PCM_FORMAT_A_LAW:
ad_fmt = 3;
break;
default:
break;
}
switch (channels) {
case 2:
ad_mode = 0;
break;
case 1:
ad_mode = 1;
break;
default:
break;
}
if (id) {
ad1843_write_bits(ad1843, &ad1843_C2C, framerate);
ad1843_write_multi(ad1843, 2,
&ad1843_DA2SM, ad_mode,
&ad1843_DA2F, ad_fmt);
} else {
ad1843_write_bits(ad1843, &ad1843_C1C, framerate);
ad1843_write_multi(ad1843, 2,
&ad1843_DA1SM, ad_mode,
&ad1843_DA1F, ad_fmt);
}
}
void ad1843_shutdown_dac(struct snd_ad1843 *ad1843, unsigned int id)
{
if (id)
ad1843_write_bits(ad1843, &ad1843_DA2F, 1);
else
ad1843_write_bits(ad1843, &ad1843_DA1F, 1);
}
void ad1843_setup_adc(struct snd_ad1843 *ad1843,
unsigned int framerate,
snd_pcm_format_t fmt,
unsigned int channels)
{
int da_fmt = 0;
switch (fmt) {
case SNDRV_PCM_FORMAT_S8: da_fmt = 0; break;
case SNDRV_PCM_FORMAT_U8: da_fmt = 0; break;
case SNDRV_PCM_FORMAT_S16_LE: da_fmt = 1; break;
case SNDRV_PCM_FORMAT_MU_LAW: da_fmt = 2; break;
case SNDRV_PCM_FORMAT_A_LAW: da_fmt = 3; break;
default: break;
}
ad1843_write_bits(ad1843, &ad1843_C3C, framerate);
ad1843_write_multi(ad1843, 2,
&ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt);
}
void ad1843_shutdown_adc(struct snd_ad1843 *ad1843)
{
/* nothing to do */
}
/*
* Fully initialize the ad1843. As described in the AD1843 data
* sheet, section "START-UP SEQUENCE". The numbered comments are
* subsection headings from the data sheet. See the data sheet, pages
* 52-54, for more info.
*
* return 0 on success, -errno on failure. */
int ad1843_init(struct snd_ad1843 *ad1843)
{
unsigned long later;
if (ad1843_read_bits(ad1843, &ad1843_INIT) != 0) {
printk(KERN_ERR "ad1843: AD1843 won't initialize\n");
return -EIO;
}
ad1843_write_bits(ad1843, &ad1843_SCF, 1);
/* 4. Put the conversion resources into standby. */
ad1843_write_bits(ad1843, &ad1843_PDNI, 0);
later = jiffies + msecs_to_jiffies(500);
while (ad1843_read_bits(ad1843, &ad1843_PDNO)) {
if (time_after(jiffies, later)) {
printk(KERN_ERR
"ad1843: AD1843 won't power up\n");
return -EIO;
}
schedule_timeout_interruptible(5);
}
/* 5. Power up the clock generators and enable clock output pins. */
ad1843_write_multi(ad1843, 3,
&ad1843_C1EN, 1,
&ad1843_C2EN, 1,
&ad1843_C3EN, 1);
/* 6. Configure conversion resources while they are in standby. */
/* DAC1/2 use clock 1/2 as source, ADC uses clock 3. Always. */
ad1843_write_multi(ad1843, 4,
&ad1843_DA1C, 1,
&ad1843_DA2C, 2,
&ad1843_ADLC, 3,
&ad1843_ADRC, 3);
/* 7. Enable conversion resources. */
ad1843_write_bits(ad1843, &ad1843_ADTLK, 1);
ad1843_write_multi(ad1843, 7,
&ad1843_ANAEN, 1,
&ad1843_AAMEN, 1,
&ad1843_DA1EN, 1,
&ad1843_DA2EN, 1,
&ad1843_DDMEN, 1,
&ad1843_ADLEN, 1,
&ad1843_ADREN, 1);
/* 8. Configure conversion resources while they are enabled. */
/* set gain to 0 for all channels */
ad1843_set_gain(ad1843, AD1843_GAIN_RECLEV, 0);
ad1843_set_gain(ad1843, AD1843_GAIN_LINE, 0);
ad1843_set_gain(ad1843, AD1843_GAIN_LINE_2, 0);
ad1843_set_gain(ad1843, AD1843_GAIN_MIC, 0);
ad1843_set_gain(ad1843, AD1843_GAIN_PCM_0, 0);
ad1843_set_gain(ad1843, AD1843_GAIN_PCM_1, 0);
/* Unmute all channels. */
/* DAC1 */
ad1843_write_multi(ad1843, 2, &ad1843_LDA1GM, 0, &ad1843_RDA1GM, 0);
/* DAC2 */
ad1843_write_multi(ad1843, 2, &ad1843_LDA2GM, 0, &ad1843_RDA2GM, 0);
/* Set default recording source to Line In and set
* mic gain to +20 dB.
*/
ad1843_set_recsrc(ad1843, 2);
ad1843_write_multi(ad1843, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1);
/* Set Speaker Out level to +/- 4V and unmute it. */
ad1843_write_multi(ad1843, 3,
&ad1843_HPOS, 1,
&ad1843_HPOM, 0,
&ad1843_MPOM, 0);
return 0;
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha)
* Copyright (c) by Tugrul Galatali <galatalt@stuy.edu>,
* Jaroslav Kysela <perex@perex.cz>
* Based on card-4232.c by Jaroslav Kysela <perex@perex.cz>
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/isa.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/wss.h>
#include <sound/initval.h>
#define CRD_NAME "Generic AD1848/AD1847/CS4248"
#define DEV_NAME "ad1848"
MODULE_DESCRIPTION(CRD_NAME);
MODULE_AUTHOR("Tugrul Galatali <galatalt@stuy.edu>, Jaroslav Kysela <perex@perex.cz>");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */
static bool thinkpad[SNDRV_CARDS]; /* Thinkpad special case */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
module_param_hw_array(port, long, ioport, NULL, 0444);
MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
module_param_hw_array(irq, int, irq, NULL, 0444);
MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
module_param_hw_array(dma1, int, dma, NULL, 0444);
MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
module_param_array(thinkpad, bool, NULL, 0444);
MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series.");
static int snd_ad1848_match(struct device *dev, unsigned int n)
{
if (!enable[n])
return 0;
if (port[n] == SNDRV_AUTO_PORT) {
dev_err(dev, "please specify port\n");
return 0;
}
if (irq[n] == SNDRV_AUTO_IRQ) {
dev_err(dev, "please specify irq\n");
return 0;
}
if (dma1[n] == SNDRV_AUTO_DMA) {
dev_err(dev, "please specify dma1\n");
return 0;
}
return 1;
}
static int snd_ad1848_probe(struct device *dev, unsigned int n)
{
struct snd_card *card;
struct snd_wss *chip;
int error;
error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
if (error < 0)
return error;
error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], -1,
thinkpad[n] ? WSS_HW_THINKPAD : WSS_HW_DETECT,
0, &chip);
if (error < 0)
return error;
card->private_data = chip;
error = snd_wss_pcm(chip, 0);
if (error < 0)
return error;
error = snd_wss_mixer(chip);
if (error < 0)
return error;
strscpy(card->driver, "AD1848", sizeof(card->driver));
strscpy(card->shortname, chip->pcm->name, sizeof(card->shortname));
if (!thinkpad[n])
snprintf(card->longname, sizeof(card->longname),
"%s at 0x%lx, irq %d, dma %d",
chip->pcm->name, chip->port, irq[n], dma1[n]);
else
snprintf(card->longname, sizeof(card->longname),
"%s at 0x%lx, irq %d, dma %d [Thinkpad]",
chip->pcm->name, chip->port, irq[n], dma1[n]);
error = snd_card_register(card);
if (error < 0)
return error;
dev_set_drvdata(dev, card);
return 0;
}
#ifdef CONFIG_PM
static int snd_ad1848_suspend(struct device *dev, unsigned int n, pm_message_t state)
{
struct snd_card *card = dev_get_drvdata(dev);
struct snd_wss *chip = card->private_data;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
chip->suspend(chip);
return 0;
}
static int snd_ad1848_resume(struct device *dev, unsigned int n)
{
struct snd_card *card = dev_get_drvdata(dev);
struct snd_wss *chip = card->private_data;
chip->resume(chip);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
#endif
static struct isa_driver snd_ad1848_driver = {
.match = snd_ad1848_match,
.probe = snd_ad1848_probe,
#ifdef CONFIG_PM
.suspend = snd_ad1848_suspend,
.resume = snd_ad1848_resume,
#endif
.driver = {
.name = DEV_NAME
}
};
module_isa_driver(snd_ad1848_driver, SNDRV_CARDS);
// SPDX-License-Identifier: GPL-2.0-only
/* Analog Devices 1889 audio driver
*
* This is a driver for the AD1889 PCI audio chipset found
* on the HP PA-RISC [BCJ]-xxx0 workstations.
*
* Copyright (C) 2004-2005, Kyle McMartin <kyle@parisc-linux.org>
* Copyright (C) 2005, Thibaut Varene <varenet@parisc-linux.org>
* Based on the OSS AD1889 driver by Randolph Chung <tausq@debian.org>
*
* TODO:
* Do we need to take care of CCS register?
* Maybe we could use finer grained locking (separate locks for pb/cap)?
* Wishlist:
* Control Interface (mixer) support
* Better AC97 support (VSR...)?
* PM support
* MIDI support
* Game Port support
* SG DMA support (this will need *a lot* of work)
*/
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/ac97_codec.h>
#include "ad1889.h"
#include "ac97/ac97_id.h"
#define AD1889_DRVVER "Version: 1.7"
MODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>, Thibaut Varene <t-bone@parisc-linux.org>");
MODULE_DESCRIPTION("Analog Devices AD1889 ALSA sound driver");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the AD1889 soundcard.");
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the AD1889 soundcard.");
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable AD1889 soundcard.");
static char *ac97_quirk[SNDRV_CARDS];
module_param_array(ac97_quirk, charp, NULL, 0444);
MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware.");
#define DEVNAME "ad1889"
#define PFX DEVNAME ": "
/* keep track of some hw registers */
struct ad1889_register_state {
u16 reg; /* reg setup */
u32 addr; /* dma base address */
unsigned long size; /* DMA buffer size */
};
struct snd_ad1889 {
struct snd_card *card;
struct pci_dev *pci;
int irq;
unsigned long bar;
void __iomem *iobase;
struct snd_ac97 *ac97;
struct snd_ac97_bus *ac97_bus;
struct snd_pcm *pcm;
struct snd_info_entry *proc;
struct snd_pcm_substream *psubs;
struct snd_pcm_substream *csubs;
/* playback register state */
struct ad1889_register_state wave;
struct ad1889_register_state ramc;
spinlock_t lock;
};
static inline u16
ad1889_readw(struct snd_ad1889 *chip, unsigned reg)
{
return readw(chip->iobase + reg);
}
static inline void
ad1889_writew(struct snd_ad1889 *chip, unsigned reg, u16 val)
{
writew(val, chip->iobase + reg);
}
static inline u32
ad1889_readl(struct snd_ad1889 *chip, unsigned reg)
{
return readl(chip->iobase + reg);
}
static inline void
ad1889_writel(struct snd_ad1889 *chip, unsigned reg, u32 val)
{
writel(val, chip->iobase + reg);
}
static inline void
ad1889_unmute(struct snd_ad1889 *chip)
{
u16 st;
st = ad1889_readw(chip, AD_DS_WADA) &
~(AD_DS_WADA_RWAM | AD_DS_WADA_LWAM);
ad1889_writew(chip, AD_DS_WADA, st);
ad1889_readw(chip, AD_DS_WADA);
}
static inline void
ad1889_mute(struct snd_ad1889 *chip)
{
u16 st;
st = ad1889_readw(chip, AD_DS_WADA) | AD_DS_WADA_RWAM | AD_DS_WADA_LWAM;
ad1889_writew(chip, AD_DS_WADA, st);
ad1889_readw(chip, AD_DS_WADA);
}
static inline void
ad1889_load_adc_buffer_address(struct snd_ad1889 *chip, u32 address)
{
ad1889_writel(chip, AD_DMA_ADCBA, address);
ad1889_writel(chip, AD_DMA_ADCCA, address);
}
static inline void
ad1889_load_adc_buffer_count(struct snd_ad1889 *chip, u32 count)
{
ad1889_writel(chip, AD_DMA_ADCBC, count);
ad1889_writel(chip, AD_DMA_ADCCC, count);
}
static inline void
ad1889_load_adc_interrupt_count(struct snd_ad1889 *chip, u32 count)
{
ad1889_writel(chip, AD_DMA_ADCIB, count);
ad1889_writel(chip, AD_DMA_ADCIC, count);
}
static inline void
ad1889_load_wave_buffer_address(struct snd_ad1889 *chip, u32 address)
{
ad1889_writel(chip, AD_DMA_WAVBA, address);
ad1889_writel(chip, AD_DMA_WAVCA, address);
}
static inline void
ad1889_load_wave_buffer_count(struct snd_ad1889 *chip, u32 count)
{
ad1889_writel(chip, AD_DMA_WAVBC, count);
ad1889_writel(chip, AD_DMA_WAVCC, count);
}
static inline void
ad1889_load_wave_interrupt_count(struct snd_ad1889 *chip, u32 count)
{
ad1889_writel(chip, AD_DMA_WAVIB, count);
ad1889_writel(chip, AD_DMA_WAVIC, count);
}
static void
ad1889_channel_reset(struct snd_ad1889 *chip, unsigned int channel)
{
u16 reg;
if (channel & AD_CHAN_WAV) {
/* Disable wave channel */
reg = ad1889_readw(chip, AD_DS_WSMC) & ~AD_DS_WSMC_WAEN;
ad1889_writew(chip, AD_DS_WSMC, reg);
chip->wave.reg = reg;
/* disable IRQs */
reg = ad1889_readw(chip, AD_DMA_WAV);
reg &= AD_DMA_IM_DIS;
reg &= ~AD_DMA_LOOP;
ad1889_writew(chip, AD_DMA_WAV, reg);
/* clear IRQ and address counters and pointers */
ad1889_load_wave_buffer_address(chip, 0x0);
ad1889_load_wave_buffer_count(chip, 0x0);
ad1889_load_wave_interrupt_count(chip, 0x0);
/* flush */
ad1889_readw(chip, AD_DMA_WAV);
}
if (channel & AD_CHAN_ADC) {
/* Disable ADC channel */
reg = ad1889_readw(chip, AD_DS_RAMC) & ~AD_DS_RAMC_ADEN;
ad1889_writew(chip, AD_DS_RAMC, reg);
chip->ramc.reg = reg;
reg = ad1889_readw(chip, AD_DMA_ADC);
reg &= AD_DMA_IM_DIS;
reg &= ~AD_DMA_LOOP;
ad1889_writew(chip, AD_DMA_ADC, reg);
ad1889_load_adc_buffer_address(chip, 0x0);
ad1889_load_adc_buffer_count(chip, 0x0);
ad1889_load_adc_interrupt_count(chip, 0x0);
/* flush */
ad1889_readw(chip, AD_DMA_ADC);
}
}
static u16
snd_ad1889_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
struct snd_ad1889 *chip = ac97->private_data;
return ad1889_readw(chip, AD_AC97_BASE + reg);
}
static void
snd_ad1889_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
{
struct snd_ad1889 *chip = ac97->private_data;
ad1889_writew(chip, AD_AC97_BASE + reg, val);
}
static int
snd_ad1889_ac97_ready(struct snd_ad1889 *chip)
{
int retry = 400; /* average needs 352 msec */
while (!(ad1889_readw(chip, AD_AC97_ACIC) & AD_AC97_ACIC_ACRDY)
&& --retry)
usleep_range(1000, 2000);
if (!retry) {
dev_err(chip->card->dev, "[%s] Link is not ready.\n",
__func__);
return -EIO;
}
dev_dbg(chip->card->dev, "[%s] ready after %d ms\n", __func__, 400 - retry);
return 0;
}
static const struct snd_pcm_hardware snd_ad1889_playback_hw = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000, /* docs say 7000, but we're lazy */
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = BUFFER_BYTES_MAX,
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = PERIOD_BYTES_MAX,
.periods_min = PERIODS_MIN,
.periods_max = PERIODS_MAX,
/*.fifo_size = 0,*/
};
static const struct snd_pcm_hardware snd_ad1889_capture_hw = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000, /* docs say we could to VSR, but we're lazy */
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = BUFFER_BYTES_MAX,
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = PERIOD_BYTES_MAX,
.periods_min = PERIODS_MIN,
.periods_max = PERIODS_MAX,
/*.fifo_size = 0,*/
};
static int
snd_ad1889_playback_open(struct snd_pcm_substream *ss)
{
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
struct snd_pcm_runtime *rt = ss->runtime;
chip->psubs = ss;
rt->hw = snd_ad1889_playback_hw;
return 0;
}
static int
snd_ad1889_capture_open(struct snd_pcm_substream *ss)
{
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
struct snd_pcm_runtime *rt = ss->runtime;
chip->csubs = ss;
rt->hw = snd_ad1889_capture_hw;
return 0;
}
static int
snd_ad1889_playback_close(struct snd_pcm_substream *ss)
{
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
chip->psubs = NULL;
return 0;
}
static int
snd_ad1889_capture_close(struct snd_pcm_substream *ss)
{
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
chip->csubs = NULL;
return 0;
}
static int
snd_ad1889_playback_prepare(struct snd_pcm_substream *ss)
{
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
struct snd_pcm_runtime *rt = ss->runtime;
unsigned int size = snd_pcm_lib_buffer_bytes(ss);
unsigned int count = snd_pcm_lib_period_bytes(ss);
u16 reg;
ad1889_channel_reset(chip, AD_CHAN_WAV);
reg = ad1889_readw(chip, AD_DS_WSMC);
/* Mask out 16-bit / Stereo */
reg &= ~(AD_DS_WSMC_WA16 | AD_DS_WSMC_WAST);
if (snd_pcm_format_width(rt->format) == 16)
reg |= AD_DS_WSMC_WA16;
if (rt->channels > 1)
reg |= AD_DS_WSMC_WAST;
/* let's make sure we don't clobber ourselves */
spin_lock_irq(&chip->lock);
chip->wave.size = size;
chip->wave.reg = reg;
chip->wave.addr = rt->dma_addr;
ad1889_writew(chip, AD_DS_WSMC, chip->wave.reg);
/* Set sample rates on the codec */
ad1889_writew(chip, AD_DS_WAS, rt->rate);
/* Set up DMA */
ad1889_load_wave_buffer_address(chip, chip->wave.addr);
ad1889_load_wave_buffer_count(chip, size);
ad1889_load_wave_interrupt_count(chip, count);
/* writes flush */
ad1889_readw(chip, AD_DS_WSMC);
spin_unlock_irq(&chip->lock);
dev_dbg(chip->card->dev,
"prepare playback: addr = 0x%x, count = %u, size = %u, reg = 0x%x, rate = %u\n",
chip->wave.addr, count, size, reg, rt->rate);
return 0;
}
static int
snd_ad1889_capture_prepare(struct snd_pcm_substream *ss)
{
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
struct snd_pcm_runtime *rt = ss->runtime;
unsigned int size = snd_pcm_lib_buffer_bytes(ss);
unsigned int count = snd_pcm_lib_period_bytes(ss);
u16 reg;
ad1889_channel_reset(chip, AD_CHAN_ADC);
reg = ad1889_readw(chip, AD_DS_RAMC);
/* Mask out 16-bit / Stereo */
reg &= ~(AD_DS_RAMC_AD16 | AD_DS_RAMC_ADST);
if (snd_pcm_format_width(rt->format) == 16)
reg |= AD_DS_RAMC_AD16;
if (rt->channels > 1)
reg |= AD_DS_RAMC_ADST;
/* let's make sure we don't clobber ourselves */
spin_lock_irq(&chip->lock);
chip->ramc.size = size;
chip->ramc.reg = reg;
chip->ramc.addr = rt->dma_addr;
ad1889_writew(chip, AD_DS_RAMC, chip->ramc.reg);
/* Set up DMA */
ad1889_load_adc_buffer_address(chip, chip->ramc.addr);
ad1889_load_adc_buffer_count(chip, size);
ad1889_load_adc_interrupt_count(chip, count);
/* writes flush */
ad1889_readw(chip, AD_DS_RAMC);
spin_unlock_irq(&chip->lock);
dev_dbg(chip->card->dev,
"prepare capture: addr = 0x%x, count = %u, size = %u, reg = 0x%x, rate = %u\n",
chip->ramc.addr, count, size, reg, rt->rate);
return 0;
}
/* this is called in atomic context with IRQ disabled.
Must be as fast as possible and not sleep.
DMA should be *triggered* by this call.
The WSMC "WAEN" bit triggers DMA Wave On/Off */
static int
snd_ad1889_playback_trigger(struct snd_pcm_substream *ss, int cmd)
{
u16 wsmc;
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
wsmc = ad1889_readw(chip, AD_DS_WSMC);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* enable DMA loop & interrupts */
ad1889_writew(chip, AD_DMA_WAV, AD_DMA_LOOP | AD_DMA_IM_CNT);
wsmc |= AD_DS_WSMC_WAEN;
/* 1 to clear CHSS bit */
ad1889_writel(chip, AD_DMA_CHSS, AD_DMA_CHSS_WAVS);
ad1889_unmute(chip);
break;
case SNDRV_PCM_TRIGGER_STOP:
ad1889_mute(chip);
wsmc &= ~AD_DS_WSMC_WAEN;
break;
default:
snd_BUG();
return -EINVAL;
}
chip->wave.reg = wsmc;
ad1889_writew(chip, AD_DS_WSMC, wsmc);
ad1889_readw(chip, AD_DS_WSMC); /* flush */
/* reset the chip when STOP - will disable IRQs */
if (cmd == SNDRV_PCM_TRIGGER_STOP)
ad1889_channel_reset(chip, AD_CHAN_WAV);
return 0;
}
/* this is called in atomic context with IRQ disabled.
Must be as fast as possible and not sleep.
DMA should be *triggered* by this call.
The RAMC "ADEN" bit triggers DMA ADC On/Off */
static int
snd_ad1889_capture_trigger(struct snd_pcm_substream *ss, int cmd)
{
u16 ramc;
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
ramc = ad1889_readw(chip, AD_DS_RAMC);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* enable DMA loop & interrupts */
ad1889_writew(chip, AD_DMA_ADC, AD_DMA_LOOP | AD_DMA_IM_CNT);
ramc |= AD_DS_RAMC_ADEN;
/* 1 to clear CHSS bit */
ad1889_writel(chip, AD_DMA_CHSS, AD_DMA_CHSS_ADCS);
break;
case SNDRV_PCM_TRIGGER_STOP:
ramc &= ~AD_DS_RAMC_ADEN;
break;
default:
return -EINVAL;
}
chip->ramc.reg = ramc;
ad1889_writew(chip, AD_DS_RAMC, ramc);
ad1889_readw(chip, AD_DS_RAMC); /* flush */
/* reset the chip when STOP - will disable IRQs */
if (cmd == SNDRV_PCM_TRIGGER_STOP)
ad1889_channel_reset(chip, AD_CHAN_ADC);
return 0;
}
/* Called in atomic context with IRQ disabled */
static snd_pcm_uframes_t
snd_ad1889_playback_pointer(struct snd_pcm_substream *ss)
{
size_t ptr = 0;
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
if (unlikely(!(chip->wave.reg & AD_DS_WSMC_WAEN)))
return 0;
ptr = ad1889_readl(chip, AD_DMA_WAVCA);
ptr -= chip->wave.addr;
if (snd_BUG_ON(ptr >= chip->wave.size))
return 0;
return bytes_to_frames(ss->runtime, ptr);
}
/* Called in atomic context with IRQ disabled */
static snd_pcm_uframes_t
snd_ad1889_capture_pointer(struct snd_pcm_substream *ss)
{
size_t ptr = 0;
struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
if (unlikely(!(chip->ramc.reg & AD_DS_RAMC_ADEN)))
return 0;
ptr = ad1889_readl(chip, AD_DMA_ADCCA);
ptr -= chip->ramc.addr;
if (snd_BUG_ON(ptr >= chip->ramc.size))
return 0;
return bytes_to_frames(ss->runtime, ptr);
}
static const struct snd_pcm_ops snd_ad1889_playback_ops = {
.open = snd_ad1889_playback_open,
.close = snd_ad1889_playback_close,
.prepare = snd_ad1889_playback_prepare,
.trigger = snd_ad1889_playback_trigger,
.pointer = snd_ad1889_playback_pointer,
};
static const struct snd_pcm_ops snd_ad1889_capture_ops = {
.open = snd_ad1889_capture_open,
.close = snd_ad1889_capture_close,
.prepare = snd_ad1889_capture_prepare,
.trigger = snd_ad1889_capture_trigger,
.pointer = snd_ad1889_capture_pointer,
};
static irqreturn_t
snd_ad1889_interrupt(int irq, void *dev_id)
{
unsigned long st;
struct snd_ad1889 *chip = dev_id;
st = ad1889_readl(chip, AD_DMA_DISR);
/* clear ISR */
ad1889_writel(chip, AD_DMA_DISR, st);
st &= AD_INTR_MASK;
if (unlikely(!st))
return IRQ_NONE;
if (st & (AD_DMA_DISR_PMAI|AD_DMA_DISR_PTAI))
dev_dbg(chip->card->dev,
"Unexpected master or target abort interrupt!\n");
if ((st & AD_DMA_DISR_WAVI) && chip->psubs)
snd_pcm_period_elapsed(chip->psubs);
if ((st & AD_DMA_DISR_ADCI) && chip->csubs)
snd_pcm_period_elapsed(chip->csubs);
return IRQ_HANDLED;
}
static int
snd_ad1889_pcm_init(struct snd_ad1889 *chip, int device)
{
int err;
struct snd_pcm *pcm;
err = snd_pcm_new(chip->card, chip->card->driver, device, 1, 1, &pcm);
if (err < 0)
return err;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_ad1889_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_ad1889_capture_ops);
pcm->private_data = chip;
pcm->info_flags = 0;
strcpy(pcm->name, chip->card->shortname);
chip->pcm = pcm;
chip->psubs = NULL;
chip->csubs = NULL;
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &chip->pci->dev,
BUFFER_BYTES_MAX / 2, BUFFER_BYTES_MAX);
return 0;
}
static void
snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_ad1889 *chip = entry->private_data;
u16 reg;
int tmp;
reg = ad1889_readw(chip, AD_DS_WSMC);
snd_iprintf(buffer, "Wave output: %s\n",
(reg & AD_DS_WSMC_WAEN) ? "enabled" : "disabled");
snd_iprintf(buffer, "Wave Channels: %s\n",
(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
snd_iprintf(buffer, "Wave Quality: %d-bit linear\n",
(reg & AD_DS_WSMC_WA16) ? 16 : 8);
/* WARQ is at offset 12 */
tmp = (reg & AD_DS_WSMC_WARQ) ?
((((reg & AD_DS_WSMC_WARQ) >> 12) & 0x01) ? 12 : 18) : 4;
tmp /= (reg & AD_DS_WSMC_WAST) ? 2 : 1;
snd_iprintf(buffer, "Wave FIFO: %d %s words\n\n", tmp,
(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
snd_iprintf(buffer, "Synthesis output: %s\n",
reg & AD_DS_WSMC_SYEN ? "enabled" : "disabled");
/* SYRQ is at offset 4 */
tmp = (reg & AD_DS_WSMC_SYRQ) ?
((((reg & AD_DS_WSMC_SYRQ) >> 4) & 0x01) ? 12 : 18) : 4;
tmp /= (reg & AD_DS_WSMC_WAST) ? 2 : 1;
snd_iprintf(buffer, "Synthesis FIFO: %d %s words\n\n", tmp,
(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
reg = ad1889_readw(chip, AD_DS_RAMC);
snd_iprintf(buffer, "ADC input: %s\n",
(reg & AD_DS_RAMC_ADEN) ? "enabled" : "disabled");
snd_iprintf(buffer, "ADC Channels: %s\n",
(reg & AD_DS_RAMC_ADST) ? "stereo" : "mono");
snd_iprintf(buffer, "ADC Quality: %d-bit linear\n",
(reg & AD_DS_RAMC_AD16) ? 16 : 8);
/* ACRQ is at offset 4 */
tmp = (reg & AD_DS_RAMC_ACRQ) ?
((((reg & AD_DS_RAMC_ACRQ) >> 4) & 0x01) ? 12 : 18) : 4;
tmp /= (reg & AD_DS_RAMC_ADST) ? 2 : 1;
snd_iprintf(buffer, "ADC FIFO: %d %s words\n\n", tmp,
(reg & AD_DS_RAMC_ADST) ? "stereo" : "mono");
snd_iprintf(buffer, "Resampler input: %s\n",
reg & AD_DS_RAMC_REEN ? "enabled" : "disabled");
/* RERQ is at offset 12 */
tmp = (reg & AD_DS_RAMC_RERQ) ?
((((reg & AD_DS_RAMC_RERQ) >> 12) & 0x01) ? 12 : 18) : 4;
tmp /= (reg & AD_DS_RAMC_ADST) ? 2 : 1;
snd_iprintf(buffer, "Resampler FIFO: %d %s words\n\n", tmp,
(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
/* doc says LSB represents -1.5dB, but the max value (-94.5dB)
suggests that LSB is -3dB, which is more coherent with the logarithmic
nature of the dB scale */
reg = ad1889_readw(chip, AD_DS_WADA);
snd_iprintf(buffer, "Left: %s, -%d dB\n",
(reg & AD_DS_WADA_LWAM) ? "mute" : "unmute",
((reg & AD_DS_WADA_LWAA) >> 8) * 3);
reg = ad1889_readw(chip, AD_DS_WADA);
snd_iprintf(buffer, "Right: %s, -%d dB\n",
(reg & AD_DS_WADA_RWAM) ? "mute" : "unmute",
(reg & AD_DS_WADA_RWAA) * 3);
reg = ad1889_readw(chip, AD_DS_WAS);
snd_iprintf(buffer, "Wave samplerate: %u Hz\n", reg);
reg = ad1889_readw(chip, AD_DS_RES);
snd_iprintf(buffer, "Resampler samplerate: %u Hz\n", reg);
}
static void
snd_ad1889_proc_init(struct snd_ad1889 *chip)
{
snd_card_ro_proc_new(chip->card, chip->card->driver,
chip, snd_ad1889_proc_read);
}
static const struct ac97_quirk ac97_quirks[] = {
{
.subvendor = 0x11d4, /* AD */
.subdevice = 0x1889, /* AD1889 */
.codec_id = AC97_ID_AD1819,
.name = "AD1889",
.type = AC97_TUNE_HP_ONLY
},
{ } /* terminator */
};
static void
snd_ad1889_ac97_xinit(struct snd_ad1889 *chip)
{
u16 reg;
reg = ad1889_readw(chip, AD_AC97_ACIC);
reg |= AD_AC97_ACIC_ACRD; /* Reset Disable */
ad1889_writew(chip, AD_AC97_ACIC, reg);
ad1889_readw(chip, AD_AC97_ACIC); /* flush posted write */
udelay(10);
/* Interface Enable */
reg |= AD_AC97_ACIC_ACIE;
ad1889_writew(chip, AD_AC97_ACIC, reg);
snd_ad1889_ac97_ready(chip);
/* Audio Stream Output | Variable Sample Rate Mode */
reg = ad1889_readw(chip, AD_AC97_ACIC);
reg |= AD_AC97_ACIC_ASOE | AD_AC97_ACIC_VSRM;
ad1889_writew(chip, AD_AC97_ACIC, reg);
ad1889_readw(chip, AD_AC97_ACIC); /* flush posted write */
}
static int
snd_ad1889_ac97_init(struct snd_ad1889 *chip, const char *quirk_override)
{
int err;
struct snd_ac97_template ac97;
static const struct snd_ac97_bus_ops ops = {
.write = snd_ad1889_ac97_write,
.read = snd_ad1889_ac97_read,
};
/* doing that here, it works. */
snd_ad1889_ac97_xinit(chip);
err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus);
if (err < 0)
return err;
memset(&ac97, 0, sizeof(ac97));
ac97.private_data = chip;
ac97.pci = chip->pci;
err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97);
if (err < 0)
return err;
snd_ac97_tune_hardware(chip->ac97, ac97_quirks, quirk_override);
return 0;
}
static void
snd_ad1889_free(struct snd_card *card)
{
struct snd_ad1889 *chip = card->private_data;
spin_lock_irq(&chip->lock);
ad1889_mute(chip);
/* Turn off interrupt on count and zero DMA registers */
ad1889_channel_reset(chip, AD_CHAN_WAV | AD_CHAN_ADC);
/* clear DISR. If we don't, we'd better jump off the Eiffel Tower */
ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PTAI | AD_DMA_DISR_PMAI);
ad1889_readl(chip, AD_DMA_DISR); /* flush, dammit! */
spin_unlock_irq(&chip->lock);
}
static int
snd_ad1889_create(struct snd_card *card, struct pci_dev *pci)
{
struct snd_ad1889 *chip = card->private_data;
int err;
err = pcim_enable_device(pci);
if (err < 0)
return err;
/* check PCI availability (32bit DMA) */
if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) {
dev_err(card->dev, "error setting 32-bit DMA mask.\n");
return -ENXIO;
}
chip->card = card;
chip->pci = pci;
chip->irq = -1;
/* (1) PCI resource allocation */
err = pcim_iomap_regions(pci, 1 << 0, card->driver);
if (err < 0)
return err;
chip->bar = pci_resource_start(pci, 0);
chip->iobase = pcim_iomap_table(pci)[0];
pci_set_master(pci);
spin_lock_init(&chip->lock); /* only now can we call ad1889_free */
if (devm_request_irq(&pci->dev, pci->irq, snd_ad1889_interrupt,
IRQF_SHARED, KBUILD_MODNAME, chip)) {
dev_err(card->dev, "cannot obtain IRQ %d\n", pci->irq);
return -EBUSY;
}
chip->irq = pci->irq;
card->sync_irq = chip->irq;
card->private_free = snd_ad1889_free;
/* (2) initialization of the chip hardware */
ad1889_writew(chip, AD_DS_CCS, AD_DS_CCS_CLKEN); /* turn on clock */
ad1889_readw(chip, AD_DS_CCS); /* flush posted write */
usleep_range(10000, 11000);
/* enable Master and Target abort interrupts */
ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PMAE | AD_DMA_DISR_PTAE);
return 0;
}
static int
snd_ad1889_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
int err;
static int devno;
struct snd_card *card;
struct snd_ad1889 *chip;
/* (1) */
if (devno >= SNDRV_CARDS)
return -ENODEV;
if (!enable[devno]) {
devno++;
return -ENOENT;
}
/* (2) */
err = snd_devm_card_new(&pci->dev, index[devno], id[devno], THIS_MODULE,
sizeof(*chip), &card);
if (err < 0)
return err;
chip = card->private_data;
strcpy(card->driver, "AD1889");
strcpy(card->shortname, "Analog Devices AD1889");
/* (3) */
err = snd_ad1889_create(card, pci);
if (err < 0)
return err;
/* (4) */
sprintf(card->longname, "%s at 0x%lx irq %i",
card->shortname, chip->bar, chip->irq);
/* (5) */
/* register AC97 mixer */
err = snd_ad1889_ac97_init(chip, ac97_quirk[devno]);
if (err < 0)
return err;
err = snd_ad1889_pcm_init(chip, 0);
if (err < 0)
return err;
/* register proc interface */
snd_ad1889_proc_init(chip);
/* (6) */
err = snd_card_register(card);
if (err < 0)
return err;
/* (7) */
pci_set_drvdata(pci, card);
devno++;
return 0;
}
static const struct pci_device_id snd_ad1889_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, snd_ad1889_ids);
static struct pci_driver ad1889_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_ad1889_ids,
.probe = snd_ad1889_probe,
};
module_pci_driver(ad1889_pci_driver);
// SPDX-License-Identifier: GPL-2.0-only
/*
* AD1936/AD1937 audio driver
*
* Copyright 2014 Analog Devices Inc.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "ad193x.h"
static const struct i2c_device_id ad193x_id[] = {
{ "ad1936", AD193X },
{ "ad1937", AD193X },
{ }
};
MODULE_DEVICE_TABLE(i2c, ad193x_id);
static int ad193x_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct regmap_config config;
config = ad193x_regmap_config;
config.val_bits = 8;
config.reg_bits = 8;
return ad193x_probe(&client->dev,
devm_regmap_init_i2c(client, &config),
(enum ad193x_type)id->driver_data);
}
static struct i2c_driver ad193x_i2c_driver = {
.driver = {
.name = "ad193x",
},
.probe = ad193x_i2c_probe,
.id_table = ad193x_id,
};
module_i2c_driver(ad193x_i2c_driver);
MODULE_DESCRIPTION("ASoC AD1936/AD1937 audio CODEC driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-only
/*
* AD1938/AD1939 audio driver
*
* Copyright 2014 Analog Devices Inc.
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "ad193x.h"
static int ad193x_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct regmap_config config;
config = ad193x_regmap_config;
config.val_bits = 8;
config.reg_bits = 16;
config.read_flag_mask = 0x09;
config.write_flag_mask = 0x08;
return ad193x_probe(&spi->dev, devm_regmap_init_spi(spi, &config),
(enum ad193x_type)id->driver_data);
}
static const struct spi_device_id ad193x_spi_id[] = {
{ "ad193x", AD193X },
{ "ad1933", AD1933 },
{ "ad1934", AD1934 },
{ "ad1938", AD193X },
{ "ad1939", AD193X },
{ "adau1328", AD193X },
{ }
};
MODULE_DEVICE_TABLE(spi, ad193x_spi_id);
static struct spi_driver ad193x_spi_driver = {
.driver = {
.name = "ad193x",
},
.probe = ad193x_spi_probe,
.id_table = ad193x_spi_id,
};
module_spi_driver(ad193x_spi_driver);
MODULE_DESCRIPTION("ASoC AD1938/AD1939 audio CODEC driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AD193X Audio Codec driver supporting AD1936/7/8/9
*
* Copyright 2010 Analog Devices Inc.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "ad193x.h"
/* codec private data */
struct ad193x_priv {
struct regmap *regmap;
enum ad193x_type type;
int sysclk;
};
/*
* AD193X volume/mute/de-emphasis etc. controls
*/
static const char * const ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"};
static SOC_ENUM_SINGLE_DECL(ad193x_deemp_enum, AD193X_DAC_CTRL2, 1,
ad193x_deemp);
static const DECLARE_TLV_DB_MINMAX(adau193x_tlv, -9563, 0);
static const unsigned int ad193x_sb[] = {32};
static struct snd_pcm_hw_constraint_list constr = {
.list = ad193x_sb,
.count = ARRAY_SIZE(ad193x_sb),
};
static const struct snd_kcontrol_new ad193x_snd_controls[] = {
/* DAC volume control */
SOC_DOUBLE_R_TLV("DAC1 Volume", AD193X_DAC_L1_VOL,
AD193X_DAC_R1_VOL, 0, 0xFF, 1, adau193x_tlv),
SOC_DOUBLE_R_TLV("DAC2 Volume", AD193X_DAC_L2_VOL,
AD193X_DAC_R2_VOL, 0, 0xFF, 1, adau193x_tlv),
SOC_DOUBLE_R_TLV("DAC3 Volume", AD193X_DAC_L3_VOL,
AD193X_DAC_R3_VOL, 0, 0xFF, 1, adau193x_tlv),
SOC_DOUBLE_R_TLV("DAC4 Volume", AD193X_DAC_L4_VOL,
AD193X_DAC_R4_VOL, 0, 0xFF, 1, adau193x_tlv),
/* DAC switch control */
SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE,
AD193X_DACR1_MUTE, 1, 1),
SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE,
AD193X_DACR2_MUTE, 1, 1),
SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE,
AD193X_DACR3_MUTE, 1, 1),
SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE,
AD193X_DACR4_MUTE, 1, 1),
/* DAC de-emphasis */
SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum),
};
static const struct snd_kcontrol_new ad193x_adc_snd_controls[] = {
/* ADC switch control */
SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE,
AD193X_ADCR1_MUTE, 1, 1),
SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE,
AD193X_ADCR2_MUTE, 1, 1),
/* ADC high-pass filter */
SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0,
AD193X_ADC_HIGHPASS_FILTER, 1, 0),
};
static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_PGA("DAC Output", AD193X_DAC_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY("SYSCLK", AD193X_PLL_CLK_CTRL0, 7, 0, NULL, 0),
SND_SOC_DAPM_VMID("VMID"),
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
SND_SOC_DAPM_OUTPUT("DAC4OUT"),
};
static const struct snd_soc_dapm_widget ad193x_adc_widgets[] = {
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_INPUT("ADC1IN"),
SND_SOC_DAPM_INPUT("ADC2IN"),
};
static int ad193x_check_pll(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm);
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component);
return !!ad193x->sysclk;
}
static const struct snd_soc_dapm_route audio_paths[] = {
{ "DAC", NULL, "SYSCLK" },
{ "DAC Output", NULL, "DAC" },
{ "DAC Output", NULL, "VMID" },
{ "DAC1OUT", NULL, "DAC Output" },
{ "DAC2OUT", NULL, "DAC Output" },
{ "DAC3OUT", NULL, "DAC Output" },
{ "DAC4OUT", NULL, "DAC Output" },
{ "SYSCLK", NULL, "PLL_PWR", &ad193x_check_pll },
};
static const struct snd_soc_dapm_route ad193x_adc_audio_paths[] = {
{ "ADC", NULL, "SYSCLK" },
{ "ADC", NULL, "ADC_PWR" },
{ "ADC", NULL, "ADC1IN" },
{ "ADC", NULL, "ADC2IN" },
};
static inline bool ad193x_has_adc(const struct ad193x_priv *ad193x)
{
switch (ad193x->type) {
case AD1933:
case AD1934:
return false;
default:
break;
}
return true;
}
/*
* DAI ops entries
*/
static int ad193x_mute(struct snd_soc_dai *dai, int mute, int direction)
{
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(dai->component);
if (mute)
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2,
AD193X_DAC_MASTER_MUTE,
AD193X_DAC_MASTER_MUTE);
else
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2,
AD193X_DAC_MASTER_MUTE, 0);
return 0;
}
static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int width)
{
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(dai->component);
unsigned int channels;
switch (slots) {
case 2:
channels = AD193X_2_CHANNELS;
break;
case 4:
channels = AD193X_4_CHANNELS;
break;
case 8:
channels = AD193X_8_CHANNELS;
break;
case 16:
channels = AD193X_16_CHANNELS;
break;
default:
return -EINVAL;
}
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL1,
AD193X_DAC_CHAN_MASK, channels << AD193X_DAC_CHAN_SHFT);
if (ad193x_has_adc(ad193x))
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL2,
AD193X_ADC_CHAN_MASK,
channels << AD193X_ADC_CHAN_SHFT);
return 0;
}
static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(codec_dai->component);
unsigned int adc_serfmt = 0;
unsigned int dac_serfmt = 0;
unsigned int adc_fmt = 0;
unsigned int dac_fmt = 0;
/* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
* with TDM), ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) and DAC I2S mode
* (SND_SOC_DAIFMT_I2S)
*/
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
adc_serfmt |= AD193X_ADC_SERFMT_TDM;
dac_serfmt |= AD193X_DAC_SERFMT_STEREO;
break;
case SND_SOC_DAIFMT_DSP_A:
adc_serfmt |= AD193X_ADC_SERFMT_AUX;
dac_serfmt |= AD193X_DAC_SERFMT_TDM;
break;
default:
if (ad193x_has_adc(ad193x))
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
break;
case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */
adc_fmt |= AD193X_ADC_LEFT_HIGH;
dac_fmt |= AD193X_DAC_LEFT_HIGH;
break;
case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */
adc_fmt |= AD193X_ADC_BCLK_INV;
dac_fmt |= AD193X_DAC_BCLK_INV;
break;
case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */
adc_fmt |= AD193X_ADC_LEFT_HIGH;
adc_fmt |= AD193X_ADC_BCLK_INV;
dac_fmt |= AD193X_DAC_LEFT_HIGH;
dac_fmt |= AD193X_DAC_BCLK_INV;
break;
default:
return -EINVAL;
}
/* For DSP_*, LRCLK's polarity must be inverted */
if (fmt & SND_SOC_DAIFMT_DSP_A)
dac_fmt ^= AD193X_DAC_LEFT_HIGH;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
adc_fmt |= AD193X_ADC_LCR_MASTER;
adc_fmt |= AD193X_ADC_BCLK_MASTER;
dac_fmt |= AD193X_DAC_LCR_MASTER;
dac_fmt |= AD193X_DAC_BCLK_MASTER;
break;
case SND_SOC_DAIFMT_CBC_CFP:
adc_fmt |= AD193X_ADC_LCR_MASTER;
dac_fmt |= AD193X_DAC_LCR_MASTER;
break;
case SND_SOC_DAIFMT_CBP_CFC:
adc_fmt |= AD193X_ADC_BCLK_MASTER;
dac_fmt |= AD193X_DAC_BCLK_MASTER;
break;
case SND_SOC_DAIFMT_CBC_CFC:
break;
default:
return -EINVAL;
}
if (ad193x_has_adc(ad193x)) {
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL1,
AD193X_ADC_SERFMT_MASK, adc_serfmt);
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL2,
AD193X_ADC_FMT_MASK, adc_fmt);
}
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL0,
AD193X_DAC_SERFMT_MASK, dac_serfmt);
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL1,
AD193X_DAC_FMT_MASK, dac_fmt);
return 0;
}
static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_component *component = codec_dai->component;
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component);
if (clk_id == AD193X_SYSCLK_MCLK) {
/* MCLK must be 512 x fs */
if (dir == SND_SOC_CLOCK_OUT || freq != 24576000)
return -EINVAL;
regmap_update_bits(ad193x->regmap, AD193X_PLL_CLK_CTRL1,
AD193X_PLL_SRC_MASK,
AD193X_PLL_DAC_SRC_MCLK |
AD193X_PLL_CLK_SRC_MCLK);
snd_soc_dapm_sync(dapm);
return 0;
}
switch (freq) {
case 12288000:
case 18432000:
case 24576000:
case 36864000:
ad193x->sysclk = freq;
return 0;
}
return -EINVAL;
}
static int ad193x_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
int word_len = 0, master_rate = 0;
struct snd_soc_component *component = dai->component;
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component);
bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
u8 dacc0;
dev_dbg(dai->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
__func__, params_rate(params), params_format(params),
params_width(params), params_channels(params));
/* bit size */
switch (params_width(params)) {
case 16:
word_len = 3;
break;
case 20:
word_len = 1;
break;
case 24:
case 32:
word_len = 0;
break;
}
switch (ad193x->sysclk) {
case 12288000:
master_rate = AD193X_PLL_INPUT_256;
break;
case 18432000:
master_rate = AD193X_PLL_INPUT_384;
break;
case 24576000:
master_rate = AD193X_PLL_INPUT_512;
break;
case 36864000:
master_rate = AD193X_PLL_INPUT_768;
break;
}
if (is_playback) {
switch (params_rate(params)) {
case 48000:
dacc0 = AD193X_DAC_SR_48;
break;
case 96000:
dacc0 = AD193X_DAC_SR_96;
break;
case 192000:
dacc0 = AD193X_DAC_SR_192;
break;
default:
dev_err(dai->dev, "invalid sampling rate: %d\n", params_rate(params));
return -EINVAL;
}
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL0, AD193X_DAC_SR_MASK, dacc0);
}
regmap_update_bits(ad193x->regmap, AD193X_PLL_CLK_CTRL0,
AD193X_PLL_INPUT_MASK, master_rate);
regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2,
AD193X_DAC_WORD_LEN_MASK,
word_len << AD193X_DAC_WORD_LEN_SHFT);
if (ad193x_has_adc(ad193x))
regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL1,
AD193X_ADC_WORD_LEN_MASK, word_len);
return 0;
}
static int ad193x_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
&constr);
}
static const struct snd_soc_dai_ops ad193x_dai_ops = {
.startup = ad193x_startup,
.hw_params = ad193x_hw_params,
.mute_stream = ad193x_mute,
.set_tdm_slot = ad193x_set_tdm_slot,
.set_sysclk = ad193x_set_dai_sysclk,
.set_fmt = ad193x_set_dai_fmt,
.no_capture_mute = 1,
};
/* codec DAI instance */
static struct snd_soc_dai_driver ad193x_dai = {
.name = "ad193x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
},
.ops = &ad193x_dai_ops,
};
/* codec DAI instance for DAC only */
static struct snd_soc_dai_driver ad193x_no_adc_dai = {
.name = "ad193x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
},
.ops = &ad193x_dai_ops,
};
/* codec register values to set after reset */
static void ad193x_reg_default_init(struct ad193x_priv *ad193x)
{
static const struct reg_sequence reg_init[] = {
{ 0, 0x99 }, /* PLL_CLK_CTRL0: pll input: mclki/xi 12.288Mhz */
{ 1, 0x04 }, /* PLL_CLK_CTRL1: no on-chip Vref */
{ 2, 0x40 }, /* DAC_CTRL0: TDM mode */
{ 3, 0x00 }, /* DAC_CTRL1: reset */
{ 4, 0x1A }, /* DAC_CTRL2: 48kHz de-emphasis, unmute dac */
{ 5, 0x00 }, /* DAC_CHNL_MUTE: unmute DAC channels */
{ 6, 0x00 }, /* DAC_L1_VOL: no attenuation */
{ 7, 0x00 }, /* DAC_R1_VOL: no attenuation */
{ 8, 0x00 }, /* DAC_L2_VOL: no attenuation */
{ 9, 0x00 }, /* DAC_R2_VOL: no attenuation */
{ 10, 0x00 }, /* DAC_L3_VOL: no attenuation */
{ 11, 0x00 }, /* DAC_R3_VOL: no attenuation */
{ 12, 0x00 }, /* DAC_L4_VOL: no attenuation */
{ 13, 0x00 }, /* DAC_R4_VOL: no attenuation */
};
static const struct reg_sequence reg_adc_init[] = {
{ 14, 0x03 }, /* ADC_CTRL0: high-pass filter enable */
{ 15, 0x43 }, /* ADC_CTRL1: sata delay=1, adc aux mode */
{ 16, 0x00 }, /* ADC_CTRL2: reset */
};
regmap_multi_reg_write(ad193x->regmap, reg_init, ARRAY_SIZE(reg_init));
if (ad193x_has_adc(ad193x)) {
regmap_multi_reg_write(ad193x->regmap, reg_adc_init,
ARRAY_SIZE(reg_adc_init));
}
}
static int ad193x_component_probe(struct snd_soc_component *component)
{
struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
int num, ret;
/* default setting for ad193x */
ad193x_reg_default_init(ad193x);
/* adc only */
if (ad193x_has_adc(ad193x)) {
/* add adc controls */
num = ARRAY_SIZE(ad193x_adc_snd_controls);
ret = snd_soc_add_component_controls(component,
ad193x_adc_snd_controls,
num);
if (ret)
return ret;
/* add adc widgets */
num = ARRAY_SIZE(ad193x_adc_widgets);
ret = snd_soc_dapm_new_controls(dapm,
ad193x_adc_widgets,
num);
if (ret)
return ret;
/* add adc routes */
num = ARRAY_SIZE(ad193x_adc_audio_paths);
ret = snd_soc_dapm_add_routes(dapm,
ad193x_adc_audio_paths,
num);
if (ret)
return ret;
}
return 0;
}
static const struct snd_soc_component_driver soc_component_dev_ad193x = {
.probe = ad193x_component_probe,
.controls = ad193x_snd_controls,
.num_controls = ARRAY_SIZE(ad193x_snd_controls),
.dapm_widgets = ad193x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad193x_dapm_widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
const struct regmap_config ad193x_regmap_config = {
.max_register = AD193X_NUM_REGS - 1,
};
EXPORT_SYMBOL_GPL(ad193x_regmap_config);
int ad193x_probe(struct device *dev, struct regmap *regmap,
enum ad193x_type type)
{
struct ad193x_priv *ad193x;
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ad193x = devm_kzalloc(dev, sizeof(*ad193x), GFP_KERNEL);
if (ad193x == NULL)
return -ENOMEM;
ad193x->regmap = regmap;
ad193x->type = type;
dev_set_drvdata(dev, ad193x);
if (ad193x_has_adc(ad193x))
return devm_snd_soc_register_component(dev, &soc_component_dev_ad193x,
&ad193x_dai, 1);
return devm_snd_soc_register_component(dev, &soc_component_dev_ad193x,
&ad193x_no_adc_dai, 1);
}
EXPORT_SYMBOL_GPL(ad193x_probe);
MODULE_DESCRIPTION("ASoC ad193x driver");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ad1980.c -- ALSA Soc AD1980 codec support
*
* Copyright: Analog Devices Inc.
* Author: Roy Huang <roy.huang@analog.com>
* Cliff Cai <cliff.cai@analog.com>
*/
/*
* WARNING:
*
* Because Analog Devices Inc. discontinued the ad1980 sound chip since
* Sep. 2009, this ad1980 driver is not maintained, tested and supported
* by ADI now.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
static const struct reg_default ad1980_reg_defaults[] = {
{ 0x02, 0x8000 },
{ 0x04, 0x8000 },
{ 0x06, 0x8000 },
{ 0x0c, 0x8008 },
{ 0x0e, 0x8008 },
{ 0x10, 0x8808 },
{ 0x12, 0x8808 },
{ 0x16, 0x8808 },
{ 0x18, 0x8808 },
{ 0x1a, 0x0000 },
{ 0x1c, 0x8000 },
{ 0x20, 0x0000 },
{ 0x28, 0x03c7 },
{ 0x2c, 0xbb80 },
{ 0x2e, 0xbb80 },
{ 0x30, 0xbb80 },
{ 0x32, 0xbb80 },
{ 0x36, 0x8080 },
{ 0x38, 0x8080 },
{ 0x3a, 0x2000 },
{ 0x60, 0x0000 },
{ 0x62, 0x0000 },
{ 0x72, 0x0000 },
{ 0x74, 0x1001 },
{ 0x76, 0x0000 },
};
static bool ad1980_readable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case AC97_RESET ... AC97_MASTER_MONO:
case AC97_PHONE ... AC97_CD:
case AC97_AUX ... AC97_GENERAL_PURPOSE:
case AC97_POWERDOWN ... AC97_PCM_LR_ADC_RATE:
case AC97_SPDIF:
case AC97_CODEC_CLASS_REV:
case AC97_PCI_SVID:
case AC97_AD_CODEC_CFG:
case AC97_AD_JACK_SPDIF:
case AC97_AD_SERIAL_CFG:
case AC97_VENDOR_ID1:
case AC97_VENDOR_ID2:
return true;
default:
return false;
}
}
static bool ad1980_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case AC97_VENDOR_ID1:
case AC97_VENDOR_ID2:
return false;
default:
return ad1980_readable_reg(dev, reg);
}
}
static const struct regmap_config ad1980_regmap_config = {
.reg_bits = 16,
.reg_stride = 2,
.val_bits = 16,
.max_register = 0x7e,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = regmap_ac97_default_volatile,
.readable_reg = ad1980_readable_reg,
.writeable_reg = ad1980_writeable_reg,
.reg_defaults = ad1980_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(ad1980_reg_defaults),
};
static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line",
"Stereo Mix", "Mono Mix", "Phone"};
static SOC_ENUM_DOUBLE_DECL(ad1980_cap_src,
AC97_REC_SEL, 8, 0, ad1980_rec_sel);
static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = {
SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0),
SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1),
SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1),
SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1),
SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1),
SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0),
SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0),
SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1),
SOC_DOUBLE("Center/LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 0, 31, 1),
SOC_DOUBLE("Center/LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 7, 1, 1),
SOC_ENUM("Capture Source", ad1980_cap_src),
SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
};
static const struct snd_soc_dapm_widget ad1980_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("MIC1"),
SND_SOC_DAPM_INPUT("MIC2"),
SND_SOC_DAPM_INPUT("CD_L"),
SND_SOC_DAPM_INPUT("CD_R"),
SND_SOC_DAPM_INPUT("AUX_L"),
SND_SOC_DAPM_INPUT("AUX_R"),
SND_SOC_DAPM_INPUT("LINE_IN_L"),
SND_SOC_DAPM_INPUT("LINE_IN_R"),
SND_SOC_DAPM_OUTPUT("LFE_OUT"),
SND_SOC_DAPM_OUTPUT("CENTER_OUT"),
SND_SOC_DAPM_OUTPUT("LINE_OUT_L"),
SND_SOC_DAPM_OUTPUT("LINE_OUT_R"),
SND_SOC_DAPM_OUTPUT("MONO_OUT"),
SND_SOC_DAPM_OUTPUT("HP_OUT_L"),
SND_SOC_DAPM_OUTPUT("HP_OUT_R"),
};
static const struct snd_soc_dapm_route ad1980_dapm_routes[] = {
{ "Capture", NULL, "MIC1" },
{ "Capture", NULL, "MIC2" },
{ "Capture", NULL, "CD_L" },
{ "Capture", NULL, "CD_R" },
{ "Capture", NULL, "AUX_L" },
{ "Capture", NULL, "AUX_R" },
{ "Capture", NULL, "LINE_IN_L" },
{ "Capture", NULL, "LINE_IN_R" },
{ "LFE_OUT", NULL, "Playback" },
{ "CENTER_OUT", NULL, "Playback" },
{ "LINE_OUT_L", NULL, "Playback" },
{ "LINE_OUT_R", NULL, "Playback" },
{ "MONO_OUT", NULL, "Playback" },
{ "HP_OUT_L", NULL, "Playback" },
{ "HP_OUT_R", NULL, "Playback" },
};
static struct snd_soc_dai_driver ad1980_dai = {
.name = "ad1980-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 6,
.rates = SNDRV_PCM_RATE_48000,
.formats = SND_SOC_STD_AC97_FMTS, },
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = SND_SOC_STD_AC97_FMTS, },
};
#define AD1980_VENDOR_ID 0x41445300
#define AD1980_VENDOR_MASK 0xffffff00
static int ad1980_reset(struct snd_soc_component *component, int try_warm)
{
struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
unsigned int retry_cnt = 0;
int ret;
do {
ret = snd_ac97_reset(ac97, true, AD1980_VENDOR_ID,
AD1980_VENDOR_MASK);
if (ret >= 0)
return 0;
/*
* Set bit 16slot in register 74h, then every slot will has only
* 16 bits. This command is sent out in 20bit mode, in which
* case the first nibble of data is eaten by the addr. (Tag is
* always 16 bit)
*/
snd_soc_component_write(component, AC97_AD_SERIAL_CFG, 0x9900);
} while (retry_cnt++ < 10);
dev_err(component->dev, "Failed to reset: AC97 link error\n");
return -EIO;
}
static int ad1980_soc_probe(struct snd_soc_component *component)
{
struct snd_ac97 *ac97;
struct regmap *regmap;
int ret;
u16 vendor_id2;
u16 ext_status;
ac97 = snd_soc_new_ac97_component(component, 0, 0);
if (IS_ERR(ac97)) {
ret = PTR_ERR(ac97);
dev_err(component->dev, "Failed to register AC97 component: %d\n", ret);
return ret;
}
regmap = regmap_init_ac97(ac97, &ad1980_regmap_config);
if (IS_ERR(regmap)) {
ret = PTR_ERR(regmap);
goto err_free_ac97;
}
snd_soc_component_init_regmap(component, regmap);
snd_soc_component_set_drvdata(component, ac97);
ret = ad1980_reset(component, 0);
if (ret < 0)
goto reset_err;
vendor_id2 = snd_soc_component_read(component, AC97_VENDOR_ID2);
if (vendor_id2 == 0x5374) {
dev_warn(component->dev,
"Found AD1981 - only 2/2 IN/OUT Channels supported\n");
}
/* unmute captures and playbacks volume */
snd_soc_component_write(component, AC97_MASTER, 0x0000);
snd_soc_component_write(component, AC97_PCM, 0x0000);
snd_soc_component_write(component, AC97_REC_GAIN, 0x0000);
snd_soc_component_write(component, AC97_CENTER_LFE_MASTER, 0x0000);
snd_soc_component_write(component, AC97_SURROUND_MASTER, 0x0000);
/*power on LFE/CENTER/Surround DACs*/
ext_status = snd_soc_component_read(component, AC97_EXTENDED_STATUS);
snd_soc_component_write(component, AC97_EXTENDED_STATUS, ext_status&~0x3800);
return 0;
reset_err:
snd_soc_component_exit_regmap(component);
err_free_ac97:
snd_soc_free_ac97_component(ac97);
return ret;
}
static void ad1980_soc_remove(struct snd_soc_component *component)
{
struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
snd_soc_component_exit_regmap(component);
snd_soc_free_ac97_component(ac97);
}
static const struct snd_soc_component_driver soc_component_dev_ad1980 = {
.probe = ad1980_soc_probe,
.remove = ad1980_soc_remove,
.controls = ad1980_snd_ac97_controls,
.num_controls = ARRAY_SIZE(ad1980_snd_ac97_controls),
.dapm_widgets = ad1980_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad1980_dapm_widgets),
.dapm_routes = ad1980_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ad1980_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int ad1980_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev,
&soc_component_dev_ad1980, &ad1980_dai, 1);
}
static struct platform_driver ad1980_codec_driver = {
.driver = {
.name = "ad1980",
},
.probe = ad1980_probe,
};
module_platform_driver(ad1980_codec_driver);
MODULE_DESCRIPTION("ASoC ad1980 driver (Obsolete)");
MODULE_AUTHOR("Roy Huang, Cliff Cai");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ad73311.c -- ALSA Soc AD73311 codec support
*
* Copyright: Analog Devices Inc.
* Author: Cliff Cai <cliff.cai@analog.com>
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include "ad73311.h"
static const struct snd_soc_dapm_widget ad73311_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("VINP"),
SND_SOC_DAPM_INPUT("VINN"),
SND_SOC_DAPM_OUTPUT("VOUTN"),
SND_SOC_DAPM_OUTPUT("VOUTP"),
};
static const struct snd_soc_dapm_route ad73311_dapm_routes[] = {
{ "Capture", NULL, "VINP" },
{ "Capture", NULL, "VINN" },
{ "VOUTN", NULL, "Playback" },
{ "VOUTP", NULL, "Playback" },
};
static struct snd_soc_dai_driver ad73311_dai = {
.name = "ad73311-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
};
static const struct snd_soc_component_driver soc_component_dev_ad73311 = {
.dapm_widgets = ad73311_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(ad73311_dapm_widgets),
.dapm_routes = ad73311_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(ad73311_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int ad73311_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev,
&soc_component_dev_ad73311, &ad73311_dai, 1);
}
static struct platform_driver ad73311_codec_driver = {
.driver = {
.name = "ad73311",
},
.probe = ad73311_probe,
};
module_platform_driver(ad73311_codec_driver);
MODULE_DESCRIPTION("ASoC ad73311 driver");
MODULE_AUTHOR("Cliff Cai ");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Shared helper functions for devices from the ADAU family
*
* Copyright 2011-2016 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/gcd.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "adau-utils.h"
int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out,
uint8_t regs[5])
{
unsigned int r, n, m, i, j;
unsigned int div;
if (!freq_out) {
r = 0;
n = 0;
m = 0;
div = 0;
} else {
if (freq_out % freq_in != 0) {
div = DIV_ROUND_UP(freq_in, 13500000);
freq_in /= div;
r = freq_out / freq_in;
i = freq_out % freq_in;
j = gcd(i, freq_in);
n = i / j;
m = freq_in / j;
div--;
} else {
r = freq_out / freq_in;
n = 0;
m = 0;
div = 0;
}
if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
return -EINVAL;
}
regs[0] = m >> 8;
regs[1] = m & 0xff;
regs[2] = n >> 8;
regs[3] = n & 0xff;
regs[4] = (r << 3) | (div << 1);
if (m != 0)
regs[4] |= 1; /* Fractional mode */
return 0;
}
EXPORT_SYMBOL_GPL(adau_calc_pll_cfg);
MODULE_DESCRIPTION("ASoC ADAU audio CODECs shared helper functions");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for ADAU1372 codec
*
* Copyright 2016 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "adau1372.h"
static int adau1372_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return adau1372_probe(&client->dev,
devm_regmap_init_i2c(client, &adau1372_regmap_config), NULL);
}
static const struct i2c_device_id adau1372_i2c_ids[] = {
{ "adau1372", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1372_i2c_ids);
static struct i2c_driver adau1372_i2c_driver = {
.driver = {
.name = "adau1372",
},
.probe = adau1372_i2c_probe,
.id_table = adau1372_i2c_ids,
};
module_i2c_driver(adau1372_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1372 CODEC I2C driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for ADAU1372 codec
*
* Copyright 2016 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <sound/soc.h>
#include "adau1372.h"
static void adau1372_spi_switch_mode(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/*
* To get the device into SPI mode CLATCH has to be pulled low three
* times. Do this by issuing three dummy reads.
*/
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
}
static int adau1372_spi_probe(struct spi_device *spi)
{
struct regmap_config config;
config = adau1372_regmap_config;
config.read_flag_mask = 0x1;
return adau1372_probe(&spi->dev,
devm_regmap_init_spi(spi, &config), adau1372_spi_switch_mode);
}
static const struct spi_device_id adau1372_spi_id[] = {
{ "adau1372", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, adau1372_spi_id);
static struct spi_driver adau1372_spi_driver = {
.driver = {
.name = "adau1372",
},
.probe = adau1372_spi_probe,
.id_table = adau1372_spi_id,
};
module_spi_driver(adau1372_spi_driver);
MODULE_DESCRIPTION("ASoC ADAU1372 CODEC SPI driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Analog Devices ADAU1372 Audio Codec driver
*
* Copyright 2016 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gcd.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include "adau1372.h"
#include "adau-utils.h"
struct adau1372 {
struct clk *clk;
struct regmap *regmap;
void (*switch_mode)(struct device *dev);
bool use_pll;
bool enabled;
bool clock_provider;
struct snd_pcm_hw_constraint_list rate_constraints;
unsigned int slot_width;
struct clk *mclk;
struct gpio_desc *pd_gpio;
struct device *dev;
};
#define ADAU1372_REG_CLK_CTRL 0x00
#define ADAU1372_REG_PLL(x) (0x01 + (x))
#define ADAU1372_REG_DAC_SOURCE 0x11
#define ADAU1372_REG_SOUT_SOURCE_0_1 0x13
#define ADAU1372_REG_SOUT_SOURCE_2_3 0x14
#define ADAU1372_REG_SOUT_SOURCE_4_5 0x15
#define ADAU1372_REG_SOUT_SOURCE_6_7 0x16
#define ADAU1372_REG_ADC_SDATA_CH 0x17
#define ADAU1372_REG_ASRCO_SOURCE_0_1 0x18
#define ADAU1372_REG_ASRCO_SOURCE_2_3 0x19
#define ADAU1372_REG_ASRC_MODE 0x1a
#define ADAU1372_REG_ADC_CTRL0 0x1b
#define ADAU1372_REG_ADC_CTRL1 0x1c
#define ADAU1372_REG_ADC_CTRL2 0x1d
#define ADAU1372_REG_ADC_CTRL3 0x1e
#define ADAU1372_REG_ADC_VOL(x) (0x1f + (x))
#define ADAU1372_REG_PGA_CTRL(x) (0x23 + (x))
#define ADAU1372_REG_PGA_BOOST 0x28
#define ADAU1372_REG_MICBIAS 0x2d
#define ADAU1372_REG_DAC_CTRL 0x2e
#define ADAU1372_REG_DAC_VOL(x) (0x2f + (x))
#define ADAU1372_REG_OP_STAGE_MUTE 0x31
#define ADAU1372_REG_SAI0 0x32
#define ADAU1372_REG_SAI1 0x33
#define ADAU1372_REG_SOUT_CTRL 0x34
#define ADAU1372_REG_MODE_MP(x) (0x38 + (x))
#define ADAU1372_REG_OP_STAGE_CTRL 0x43
#define ADAU1372_REG_DECIM_PWR 0x44
#define ADAU1372_REG_INTERP_PWR 0x45
#define ADAU1372_REG_BIAS_CTRL0 0x46
#define ADAU1372_REG_BIAS_CTRL1 0x47
#define ADAU1372_CLK_CTRL_PLL_EN BIT(7)
#define ADAU1372_CLK_CTRL_XTAL_DIS BIT(4)
#define ADAU1372_CLK_CTRL_CLKSRC BIT(3)
#define ADAU1372_CLK_CTRL_CC_MDIV BIT(1)
#define ADAU1372_CLK_CTRL_MCLK_EN BIT(0)
#define ADAU1372_SAI0_DELAY1 (0x0 << 6)
#define ADAU1372_SAI0_DELAY0 (0x1 << 6)
#define ADAU1372_SAI0_DELAY_MASK (0x3 << 6)
#define ADAU1372_SAI0_SAI_I2S (0x0 << 4)
#define ADAU1372_SAI0_SAI_TDM2 (0x1 << 4)
#define ADAU1372_SAI0_SAI_TDM4 (0x2 << 4)
#define ADAU1372_SAI0_SAI_TDM8 (0x3 << 4)
#define ADAU1372_SAI0_SAI_MASK (0x3 << 4)
#define ADAU1372_SAI0_FS_48 0x0
#define ADAU1372_SAI0_FS_8 0x1
#define ADAU1372_SAI0_FS_12 0x2
#define ADAU1372_SAI0_FS_16 0x3
#define ADAU1372_SAI0_FS_24 0x4
#define ADAU1372_SAI0_FS_32 0x5
#define ADAU1372_SAI0_FS_96 0x6
#define ADAU1372_SAI0_FS_192 0x7
#define ADAU1372_SAI0_FS_MASK 0xf
#define ADAU1372_SAI1_TDM_TS BIT(7)
#define ADAU1372_SAI1_BCLK_TDMC BIT(6)
#define ADAU1372_SAI1_LR_MODE BIT(5)
#define ADAU1372_SAI1_LR_POL BIT(4)
#define ADAU1372_SAI1_BCLKRATE BIT(2)
#define ADAU1372_SAI1_BCLKEDGE BIT(1)
#define ADAU1372_SAI1_MS BIT(0)
static const unsigned int adau1372_rates[] = {
[ADAU1372_SAI0_FS_8] = 8000,
[ADAU1372_SAI0_FS_12] = 12000,
[ADAU1372_SAI0_FS_16] = 16000,
[ADAU1372_SAI0_FS_24] = 24000,
[ADAU1372_SAI0_FS_32] = 32000,
[ADAU1372_SAI0_FS_48] = 48000,
[ADAU1372_SAI0_FS_96] = 96000,
[ADAU1372_SAI0_FS_192] = 192000,
};
/* 8k, 12k, 24k, 48k */
#define ADAU1372_RATE_MASK_TDM8 0x17
/* + 16k, 96k */
#define ADAU1372_RATE_MASK_TDM4_MASTER (ADAU1372_RATE_MASK_TDM8 | 0x48 | 0x20)
/* +32k */
#define ADAU1372_RATE_MASK_TDM4 (ADAU1372_RATE_MASK_TDM4_MASTER | 0x20)
/* + 192k */
#define ADAU1372_RATE_MASK_TDM2 (ADAU1372_RATE_MASK_TDM4 | 0x80)
static const DECLARE_TLV_DB_MINMAX(adau1372_digital_tlv, -9563, 0);
static const DECLARE_TLV_DB_SCALE(adau1372_pga_tlv, -1200, 75, 0);
static const DECLARE_TLV_DB_SCALE(adau1372_pga_boost_tlv, 0, 1000, 0);
static const char * const adau1372_bias_text[] = {
"Normal operation", "Extreme power saving", "Enhanced performance",
"Power saving",
};
static const unsigned int adau1372_bias_adc_values[] = {
0, 2, 3,
};
static const char * const adau1372_bias_adc_text[] = {
"Normal operation", "Enhanced performance", "Power saving",
};
static const char * const adau1372_bias_dac_text[] = {
"Normal operation", "Power saving", "Superior performance",
"Enhanced performance",
};
static SOC_ENUM_SINGLE_DECL(adau1372_bias_hp_enum,
ADAU1372_REG_BIAS_CTRL0, 6, adau1372_bias_text);
static SOC_ENUM_SINGLE_DECL(adau1372_bias_afe0_1_enum,
ADAU1372_REG_BIAS_CTRL0, 4, adau1372_bias_text);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_bias_adc2_3_enum,
ADAU1372_REG_BIAS_CTRL0, 2, 0x3, adau1372_bias_adc_text,
adau1372_bias_adc_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_bias_adc0_1_enum,
ADAU1372_REG_BIAS_CTRL0, 0, 0x3, adau1372_bias_adc_text,
adau1372_bias_adc_values);
static SOC_ENUM_SINGLE_DECL(adau1372_bias_afe2_3_enum,
ADAU1372_REG_BIAS_CTRL1, 4, adau1372_bias_text);
static SOC_ENUM_SINGLE_DECL(adau1372_bias_mic_enum,
ADAU1372_REG_BIAS_CTRL1, 2, adau1372_bias_text);
static SOC_ENUM_SINGLE_DECL(adau1372_bias_dac_enum,
ADAU1372_REG_BIAS_CTRL1, 0, adau1372_bias_dac_text);
static const char * const adau1372_hpf_text[] = {
"Off",
"1 Hz",
"4 Hz",
"8 Hz",
};
static SOC_ENUM_SINGLE_DECL(adau1372_hpf0_1_enum, ADAU1372_REG_ADC_CTRL2, 5,
adau1372_hpf_text);
static SOC_ENUM_SINGLE_DECL(adau1372_hpf2_3_enum, ADAU1372_REG_ADC_CTRL3, 5,
adau1372_hpf_text);
static const struct snd_kcontrol_new adau1372_controls[] = {
SOC_SINGLE_TLV("ADC 0 Capture Volume", ADAU1372_REG_ADC_VOL(0),
0, 0xff, 1, adau1372_digital_tlv),
SOC_SINGLE_TLV("ADC 1 Capture Volume", ADAU1372_REG_ADC_VOL(1),
0, 0xff, 1, adau1372_digital_tlv),
SOC_SINGLE_TLV("ADC 2 Capture Volume", ADAU1372_REG_ADC_VOL(2),
0, 0xff, 1, adau1372_digital_tlv),
SOC_SINGLE_TLV("ADC 3 Capture Volume", ADAU1372_REG_ADC_VOL(3),
0, 0xff, 1, adau1372_digital_tlv),
SOC_SINGLE("ADC 0 Capture Switch", ADAU1372_REG_ADC_CTRL0, 3, 1, 1),
SOC_SINGLE("ADC 1 Capture Switch", ADAU1372_REG_ADC_CTRL0, 4, 1, 1),
SOC_SINGLE("ADC 2 Capture Switch", ADAU1372_REG_ADC_CTRL1, 3, 1, 1),
SOC_SINGLE("ADC 3 Capture Switch", ADAU1372_REG_ADC_CTRL1, 4, 1, 1),
SOC_ENUM("ADC 0+1 High-Pass-Filter", adau1372_hpf0_1_enum),
SOC_ENUM("ADC 2+3 High-Pass-Filter", adau1372_hpf2_3_enum),
SOC_SINGLE_TLV("PGA 0 Capture Volume", ADAU1372_REG_PGA_CTRL(0),
0, 0x3f, 0, adau1372_pga_tlv),
SOC_SINGLE_TLV("PGA 1 Capture Volume", ADAU1372_REG_PGA_CTRL(1),
0, 0x3f, 0, adau1372_pga_tlv),
SOC_SINGLE_TLV("PGA 2 Capture Volume", ADAU1372_REG_PGA_CTRL(2),
0, 0x3f, 0, adau1372_pga_tlv),
SOC_SINGLE_TLV("PGA 3 Capture Volume", ADAU1372_REG_PGA_CTRL(3),
0, 0x3f, 0, adau1372_pga_tlv),
SOC_SINGLE_TLV("PGA 0 Boost Capture Volume", ADAU1372_REG_PGA_BOOST,
0, 1, 0, adau1372_pga_boost_tlv),
SOC_SINGLE_TLV("PGA 1 Boost Capture Volume", ADAU1372_REG_PGA_BOOST,
1, 1, 0, adau1372_pga_boost_tlv),
SOC_SINGLE_TLV("PGA 2 Boost Capture Volume", ADAU1372_REG_PGA_BOOST,
2, 1, 0, adau1372_pga_boost_tlv),
SOC_SINGLE_TLV("PGA 3 Boost Capture Volume", ADAU1372_REG_PGA_BOOST,
3, 1, 0, adau1372_pga_boost_tlv),
SOC_SINGLE("PGA 0 Capture Switch", ADAU1372_REG_PGA_CTRL(0), 7, 1, 1),
SOC_SINGLE("PGA 1 Capture Switch", ADAU1372_REG_PGA_CTRL(1), 7, 1, 1),
SOC_SINGLE("PGA 2 Capture Switch", ADAU1372_REG_PGA_CTRL(2), 7, 1, 1),
SOC_SINGLE("PGA 3 Capture Switch", ADAU1372_REG_PGA_CTRL(3), 7, 1, 1),
SOC_SINGLE_TLV("DAC 0 Playback Volume", ADAU1372_REG_DAC_VOL(0),
0, 0xff, 1, adau1372_digital_tlv),
SOC_SINGLE_TLV("DAC 1 Playback Volume", ADAU1372_REG_DAC_VOL(1),
0, 0xff, 1, adau1372_digital_tlv),
SOC_SINGLE("DAC 0 Playback Switch", ADAU1372_REG_DAC_CTRL, 3, 1, 1),
SOC_SINGLE("DAC 1 Playback Switch", ADAU1372_REG_DAC_CTRL, 4, 1, 1),
SOC_ENUM("Headphone Bias", adau1372_bias_hp_enum),
SOC_ENUM("Microphone Bias", adau1372_bias_mic_enum),
SOC_ENUM("AFE 0+1 Bias", adau1372_bias_afe0_1_enum),
SOC_ENUM("AFE 2+3 Bias", adau1372_bias_afe2_3_enum),
SOC_ENUM("ADC 0+1 Bias", adau1372_bias_adc0_1_enum),
SOC_ENUM("ADC 2+3 Bias", adau1372_bias_adc2_3_enum),
SOC_ENUM("DAC 0+1 Bias", adau1372_bias_dac_enum),
};
static const char * const adau1372_decimator_mux_text[] = {
"ADC",
"DMIC",
};
static SOC_ENUM_SINGLE_DECL(adau1372_decimator0_1_mux_enum, ADAU1372_REG_ADC_CTRL2,
2, adau1372_decimator_mux_text);
static const struct snd_kcontrol_new adau1372_decimator0_1_mux_control =
SOC_DAPM_ENUM("Decimator 0+1 Capture Mux", adau1372_decimator0_1_mux_enum);
static SOC_ENUM_SINGLE_DECL(adau1372_decimator2_3_mux_enum, ADAU1372_REG_ADC_CTRL3,
2, adau1372_decimator_mux_text);
static const struct snd_kcontrol_new adau1372_decimator2_3_mux_control =
SOC_DAPM_ENUM("Decimator 2+3 Capture Mux", adau1372_decimator2_3_mux_enum);
static const unsigned int adau1372_asrco_mux_values[] = {
4, 5, 6, 7,
};
static const char * const adau1372_asrco_mux_text[] = {
"Decimator0",
"Decimator1",
"Decimator2",
"Decimator3",
};
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_asrco0_mux_enum, ADAU1372_REG_ASRCO_SOURCE_0_1,
0, 0xf, adau1372_asrco_mux_text, adau1372_asrco_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_asrco1_mux_enum, ADAU1372_REG_ASRCO_SOURCE_0_1,
4, 0xf, adau1372_asrco_mux_text, adau1372_asrco_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_asrco2_mux_enum, ADAU1372_REG_ASRCO_SOURCE_2_3,
0, 0xf, adau1372_asrco_mux_text, adau1372_asrco_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_asrco3_mux_enum, ADAU1372_REG_ASRCO_SOURCE_2_3,
4, 0xf, adau1372_asrco_mux_text, adau1372_asrco_mux_values);
static const struct snd_kcontrol_new adau1372_asrco0_mux_control =
SOC_DAPM_ENUM("Output ASRC0 Capture Mux", adau1372_asrco0_mux_enum);
static const struct snd_kcontrol_new adau1372_asrco1_mux_control =
SOC_DAPM_ENUM("Output ASRC1 Capture Mux", adau1372_asrco1_mux_enum);
static const struct snd_kcontrol_new adau1372_asrco2_mux_control =
SOC_DAPM_ENUM("Output ASRC2 Capture Mux", adau1372_asrco2_mux_enum);
static const struct snd_kcontrol_new adau1372_asrco3_mux_control =
SOC_DAPM_ENUM("Output ASRC3 Capture Mux", adau1372_asrco3_mux_enum);
static const unsigned int adau1372_sout_mux_values[] = {
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
};
static const char * const adau1372_sout_mux_text[] = {
"Output ASRC0",
"Output ASRC1",
"Output ASRC2",
"Output ASRC3",
"Serial Input 0",
"Serial Input 1",
"Serial Input 2",
"Serial Input 3",
"Serial Input 4",
"Serial Input 5",
"Serial Input 6",
"Serial Input 7",
};
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout0_mux_enum, ADAU1372_REG_SOUT_SOURCE_0_1,
0, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout1_mux_enum, ADAU1372_REG_SOUT_SOURCE_0_1,
4, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout2_mux_enum, ADAU1372_REG_SOUT_SOURCE_2_3,
0, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout3_mux_enum, ADAU1372_REG_SOUT_SOURCE_2_3,
4, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout4_mux_enum, ADAU1372_REG_SOUT_SOURCE_4_5,
0, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout5_mux_enum, ADAU1372_REG_SOUT_SOURCE_4_5,
4, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout6_mux_enum, ADAU1372_REG_SOUT_SOURCE_6_7,
0, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_sout7_mux_enum, ADAU1372_REG_SOUT_SOURCE_6_7,
4, 0xf, adau1372_sout_mux_text, adau1372_sout_mux_values);
static const struct snd_kcontrol_new adau1372_sout0_mux_control =
SOC_DAPM_ENUM("Serial Output 0 Capture Mux", adau1372_sout0_mux_enum);
static const struct snd_kcontrol_new adau1372_sout1_mux_control =
SOC_DAPM_ENUM("Serial Output 1 Capture Mux", adau1372_sout1_mux_enum);
static const struct snd_kcontrol_new adau1372_sout2_mux_control =
SOC_DAPM_ENUM("Serial Output 2 Capture Mux", adau1372_sout2_mux_enum);
static const struct snd_kcontrol_new adau1372_sout3_mux_control =
SOC_DAPM_ENUM("Serial Output 3 Capture Mux", adau1372_sout3_mux_enum);
static const struct snd_kcontrol_new adau1372_sout4_mux_control =
SOC_DAPM_ENUM("Serial Output 4 Capture Mux", adau1372_sout4_mux_enum);
static const struct snd_kcontrol_new adau1372_sout5_mux_control =
SOC_DAPM_ENUM("Serial Output 5 Capture Mux", adau1372_sout5_mux_enum);
static const struct snd_kcontrol_new adau1372_sout6_mux_control =
SOC_DAPM_ENUM("Serial Output 6 Capture Mux", adau1372_sout6_mux_enum);
static const struct snd_kcontrol_new adau1372_sout7_mux_control =
SOC_DAPM_ENUM("Serial Output 7 Capture Mux", adau1372_sout7_mux_enum);
static const char * const adau1372_asrci_mux_text[] = {
"Serial Input 0+1",
"Serial Input 2+3",
"Serial Input 4+5",
"Serial Input 6+7",
};
static SOC_ENUM_SINGLE_DECL(adau1372_asrci_mux_enum,
ADAU1372_REG_ASRC_MODE, 2, adau1372_asrci_mux_text);
static const struct snd_kcontrol_new adau1372_asrci_mux_control =
SOC_DAPM_ENUM("Input ASRC Playback Mux", adau1372_asrci_mux_enum);
static const unsigned int adau1372_dac_mux_values[] = {
12, 13
};
static const char * const adau1372_dac_mux_text[] = {
"Input ASRC0",
"Input ASRC1",
};
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_dac0_mux_enum, ADAU1372_REG_DAC_SOURCE,
0, 0xf, adau1372_dac_mux_text, adau1372_dac_mux_values);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1372_dac1_mux_enum, ADAU1372_REG_DAC_SOURCE,
4, 0xf, adau1372_dac_mux_text, adau1372_dac_mux_values);
static const struct snd_kcontrol_new adau1372_dac0_mux_control =
SOC_DAPM_ENUM("DAC 0 Playback Mux", adau1372_dac0_mux_enum);
static const struct snd_kcontrol_new adau1372_dac1_mux_control =
SOC_DAPM_ENUM("DAC 1 Playback Mux", adau1372_dac1_mux_enum);
static const struct snd_soc_dapm_widget adau1372_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("AIN0"),
SND_SOC_DAPM_INPUT("AIN1"),
SND_SOC_DAPM_INPUT("AIN2"),
SND_SOC_DAPM_INPUT("AIN3"),
SND_SOC_DAPM_INPUT("DMIC0_1"),
SND_SOC_DAPM_INPUT("DMIC2_3"),
SND_SOC_DAPM_SUPPLY("MICBIAS0", ADAU1372_REG_MICBIAS, 4, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1372_REG_MICBIAS, 5, 0, NULL, 0),
SND_SOC_DAPM_PGA("PGA0", ADAU1372_REG_PGA_CTRL(0), 6, 0, NULL, 0),
SND_SOC_DAPM_PGA("PGA1", ADAU1372_REG_PGA_CTRL(1), 6, 0, NULL, 0),
SND_SOC_DAPM_PGA("PGA2", ADAU1372_REG_PGA_CTRL(2), 6, 0, NULL, 0),
SND_SOC_DAPM_PGA("PGA3", ADAU1372_REG_PGA_CTRL(3), 6, 0, NULL, 0),
SND_SOC_DAPM_ADC("ADC0", NULL, ADAU1372_REG_ADC_CTRL2, 0, 0),
SND_SOC_DAPM_ADC("ADC1", NULL, ADAU1372_REG_ADC_CTRL2, 1, 0),
SND_SOC_DAPM_ADC("ADC2", NULL, ADAU1372_REG_ADC_CTRL3, 0, 0),
SND_SOC_DAPM_ADC("ADC3", NULL, ADAU1372_REG_ADC_CTRL3, 1, 0),
SND_SOC_DAPM_SUPPLY("ADC0 Filter", ADAU1372_REG_DECIM_PWR, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC1 Filter", ADAU1372_REG_DECIM_PWR, 1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC2 Filter", ADAU1372_REG_DECIM_PWR, 2, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC3 Filter", ADAU1372_REG_DECIM_PWR, 3, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Output ASRC0 Decimator", ADAU1372_REG_DECIM_PWR, 4, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Output ASRC1 Decimator", ADAU1372_REG_DECIM_PWR, 5, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Output ASRC2 Decimator", ADAU1372_REG_DECIM_PWR, 6, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Output ASRC3 Decimator", ADAU1372_REG_DECIM_PWR, 7, 0, NULL, 0),
SND_SOC_DAPM_MUX("Decimator0 Mux", SND_SOC_NOPM, 0, 0, &adau1372_decimator0_1_mux_control),
SND_SOC_DAPM_MUX("Decimator1 Mux", SND_SOC_NOPM, 0, 0, &adau1372_decimator0_1_mux_control),
SND_SOC_DAPM_MUX("Decimator2 Mux", SND_SOC_NOPM, 0, 0, &adau1372_decimator2_3_mux_control),
SND_SOC_DAPM_MUX("Decimator3 Mux", SND_SOC_NOPM, 0, 0, &adau1372_decimator2_3_mux_control),
SND_SOC_DAPM_MUX("Output ASRC0 Mux", SND_SOC_NOPM, 0, 0, &adau1372_asrco0_mux_control),
SND_SOC_DAPM_MUX("Output ASRC1 Mux", SND_SOC_NOPM, 0, 0, &adau1372_asrco1_mux_control),
SND_SOC_DAPM_MUX("Output ASRC2 Mux", SND_SOC_NOPM, 0, 0, &adau1372_asrco2_mux_control),
SND_SOC_DAPM_MUX("Output ASRC3 Mux", SND_SOC_NOPM, 0, 0, &adau1372_asrco3_mux_control),
SND_SOC_DAPM_MUX("Serial Output 0 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout0_mux_control),
SND_SOC_DAPM_MUX("Serial Output 1 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout1_mux_control),
SND_SOC_DAPM_MUX("Serial Output 2 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout2_mux_control),
SND_SOC_DAPM_MUX("Serial Output 3 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout3_mux_control),
SND_SOC_DAPM_MUX("Serial Output 4 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout4_mux_control),
SND_SOC_DAPM_MUX("Serial Output 5 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout5_mux_control),
SND_SOC_DAPM_MUX("Serial Output 6 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout6_mux_control),
SND_SOC_DAPM_MUX("Serial Output 7 Capture Mux", SND_SOC_NOPM, 0, 0,
&adau1372_sout7_mux_control),
SND_SOC_DAPM_AIF_IN("Serial Input 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 1", NULL, 1, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 2", NULL, 2, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 3", NULL, 3, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 4", NULL, 4, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 5", NULL, 5, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 6", NULL, 6, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Serial Input 7", NULL, 7, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 1", NULL, 1, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 2", NULL, 2, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 3", NULL, 3, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 4", NULL, 4, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 5", NULL, 5, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 6", NULL, 6, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Serial Output 7", NULL, 7, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SUPPLY("Output ASRC Supply", ADAU1372_REG_ASRC_MODE, 1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Input ASRC Supply", ADAU1372_REG_ASRC_MODE, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("DAC1 Modulator", ADAU1372_REG_INTERP_PWR, 3, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("DAC0 Modulator", ADAU1372_REG_INTERP_PWR, 2, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Input ASRC1 Interpolator", ADAU1372_REG_INTERP_PWR, 1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Input ASRC0 Interpolator", ADAU1372_REG_INTERP_PWR, 0, 0, NULL, 0),
SND_SOC_DAPM_MUX("Input ASRC0 Mux", SND_SOC_NOPM, 0, 0, &adau1372_asrci_mux_control),
SND_SOC_DAPM_MUX("Input ASRC1 Mux", SND_SOC_NOPM, 0, 0, &adau1372_asrci_mux_control),
SND_SOC_DAPM_MUX("DAC 0 Mux", SND_SOC_NOPM, 0, 0, &adau1372_dac0_mux_control),
SND_SOC_DAPM_MUX("DAC 1 Mux", SND_SOC_NOPM, 0, 0, &adau1372_dac1_mux_control),
SND_SOC_DAPM_DAC("DAC0", NULL, ADAU1372_REG_DAC_CTRL, 0, 0),
SND_SOC_DAPM_DAC("DAC1", NULL, ADAU1372_REG_DAC_CTRL, 1, 0),
SND_SOC_DAPM_OUT_DRV("OP_STAGE_LP", ADAU1372_REG_OP_STAGE_CTRL, 0, 1, NULL, 0),
SND_SOC_DAPM_OUT_DRV("OP_STAGE_LN", ADAU1372_REG_OP_STAGE_CTRL, 1, 1, NULL, 0),
SND_SOC_DAPM_OUT_DRV("OP_STAGE_RP", ADAU1372_REG_OP_STAGE_CTRL, 2, 1, NULL, 0),
SND_SOC_DAPM_OUT_DRV("OP_STAGE_RN", ADAU1372_REG_OP_STAGE_CTRL, 3, 1, NULL, 0),
SND_SOC_DAPM_OUTPUT("HPOUTL"),
SND_SOC_DAPM_OUTPUT("HPOUTR"),
};
#define ADAU1372_SOUT_ROUTES(x) \
{ "Serial Output " #x " Capture Mux", "Output ASRC0", "Output ASRC0 Mux" }, \
{ "Serial Output " #x " Capture Mux", "Output ASRC1", "Output ASRC1 Mux" }, \
{ "Serial Output " #x " Capture Mux", "Output ASRC2", "Output ASRC2 Mux" }, \
{ "Serial Output " #x " Capture Mux", "Output ASRC3", "Output ASRC3 Mux" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 0", "Serial Input 0" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 1", "Serial Input 1" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 2", "Serial Input 2" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 3", "Serial Input 3" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 4", "Serial Input 4" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 5", "Serial Input 5" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 6", "Serial Input 6" }, \
{ "Serial Output " #x " Capture Mux", "Serial Input 7", "Serial Input 7" }, \
{ "Serial Output " #x, NULL, "Serial Output " #x " Capture Mux" }, \
{ "Capture", NULL, "Serial Output " #x }
#define ADAU1372_ASRCO_ROUTES(x) \
{ "Output ASRC" #x " Mux", "Decimator0", "Decimator0 Mux" }, \
{ "Output ASRC" #x " Mux", "Decimator1", "Decimator1 Mux" }, \
{ "Output ASRC" #x " Mux", "Decimator2", "Decimator2 Mux" }, \
{ "Output ASRC" #x " Mux", "Decimator3", "Decimator3 Mux" }
static const struct snd_soc_dapm_route adau1372_dapm_routes[] = {
{ "PGA0", NULL, "AIN0" },
{ "PGA1", NULL, "AIN1" },
{ "PGA2", NULL, "AIN2" },
{ "PGA3", NULL, "AIN3" },
{ "ADC0", NULL, "PGA0" },
{ "ADC1", NULL, "PGA1" },
{ "ADC2", NULL, "PGA2" },
{ "ADC3", NULL, "PGA3" },
{ "Decimator0 Mux", "ADC", "ADC0" },
{ "Decimator1 Mux", "ADC", "ADC1" },
{ "Decimator2 Mux", "ADC", "ADC2" },
{ "Decimator3 Mux", "ADC", "ADC3" },
{ "Decimator0 Mux", "DMIC", "DMIC0_1" },
{ "Decimator1 Mux", "DMIC", "DMIC0_1" },
{ "Decimator2 Mux", "DMIC", "DMIC2_3" },
{ "Decimator3 Mux", "DMIC", "DMIC2_3" },
{ "Decimator0 Mux", NULL, "ADC0 Filter" },
{ "Decimator1 Mux", NULL, "ADC1 Filter" },
{ "Decimator2 Mux", NULL, "ADC2 Filter" },
{ "Decimator3 Mux", NULL, "ADC3 Filter" },
{ "Output ASRC0 Mux", NULL, "Output ASRC Supply" },
{ "Output ASRC1 Mux", NULL, "Output ASRC Supply" },
{ "Output ASRC2 Mux", NULL, "Output ASRC Supply" },
{ "Output ASRC3 Mux", NULL, "Output ASRC Supply" },
{ "Output ASRC0 Mux", NULL, "Output ASRC0 Decimator" },
{ "Output ASRC1 Mux", NULL, "Output ASRC1 Decimator" },
{ "Output ASRC2 Mux", NULL, "Output ASRC2 Decimator" },
{ "Output ASRC3 Mux", NULL, "Output ASRC3 Decimator" },
ADAU1372_ASRCO_ROUTES(0),
ADAU1372_ASRCO_ROUTES(1),
ADAU1372_ASRCO_ROUTES(2),
ADAU1372_ASRCO_ROUTES(3),
ADAU1372_SOUT_ROUTES(0),
ADAU1372_SOUT_ROUTES(1),
ADAU1372_SOUT_ROUTES(2),
ADAU1372_SOUT_ROUTES(3),
ADAU1372_SOUT_ROUTES(4),
ADAU1372_SOUT_ROUTES(5),
ADAU1372_SOUT_ROUTES(6),
ADAU1372_SOUT_ROUTES(7),
{ "Serial Input 0", NULL, "Playback" },
{ "Serial Input 1", NULL, "Playback" },
{ "Serial Input 2", NULL, "Playback" },
{ "Serial Input 3", NULL, "Playback" },
{ "Serial Input 4", NULL, "Playback" },
{ "Serial Input 5", NULL, "Playback" },
{ "Serial Input 6", NULL, "Playback" },
{ "Serial Input 7", NULL, "Playback" },
{ "Input ASRC0 Mux", "Serial Input 0+1", "Serial Input 0" },
{ "Input ASRC1 Mux", "Serial Input 0+1", "Serial Input 1" },
{ "Input ASRC0 Mux", "Serial Input 2+3", "Serial Input 2" },
{ "Input ASRC1 Mux", "Serial Input 2+3", "Serial Input 3" },
{ "Input ASRC0 Mux", "Serial Input 4+5", "Serial Input 4" },
{ "Input ASRC1 Mux", "Serial Input 4+5", "Serial Input 5" },
{ "Input ASRC0 Mux", "Serial Input 6+7", "Serial Input 6" },
{ "Input ASRC1 Mux", "Serial Input 6+7", "Serial Input 7" },
{ "Input ASRC0 Mux", NULL, "Input ASRC Supply" },
{ "Input ASRC1 Mux", NULL, "Input ASRC Supply" },
{ "Input ASRC0 Mux", NULL, "Input ASRC0 Interpolator" },
{ "Input ASRC1 Mux", NULL, "Input ASRC1 Interpolator" },
{ "DAC 0 Mux", "Input ASRC0", "Input ASRC0 Mux" },
{ "DAC 0 Mux", "Input ASRC1", "Input ASRC1 Mux" },
{ "DAC 1 Mux", "Input ASRC0", "Input ASRC0 Mux" },
{ "DAC 1 Mux", "Input ASRC1", "Input ASRC1 Mux" },
{ "DAC0", NULL, "DAC 0 Mux" },
{ "DAC1", NULL, "DAC 1 Mux" },
{ "DAC0", NULL, "DAC0 Modulator" },
{ "DAC1", NULL, "DAC1 Modulator" },
{ "OP_STAGE_LP", NULL, "DAC0" },
{ "OP_STAGE_LN", NULL, "DAC0" },
{ "OP_STAGE_RP", NULL, "DAC1" },
{ "OP_STAGE_RN", NULL, "DAC1" },
{ "HPOUTL", NULL, "OP_STAGE_LP" },
{ "HPOUTL", NULL, "OP_STAGE_LN" },
{ "HPOUTR", NULL, "OP_STAGE_RP" },
{ "HPOUTR", NULL, "OP_STAGE_RN" },
};
static int adau1372_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct adau1372 *adau1372 = snd_soc_dai_get_drvdata(dai);
unsigned int sai0 = 0, sai1 = 0;
bool invert_lrclk = false;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
adau1372->clock_provider = true;
sai1 |= ADAU1372_SAI1_MS;
break;
case SND_SOC_DAIFMT_CBC_CFC:
adau1372->clock_provider = false;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
invert_lrclk = false;
break;
case SND_SOC_DAIFMT_NB_IF:
invert_lrclk = true;
break;
case SND_SOC_DAIFMT_IB_NF:
invert_lrclk = false;
sai1 |= ADAU1372_SAI1_BCLKEDGE;
break;
case SND_SOC_DAIFMT_IB_IF:
invert_lrclk = true;
sai1 |= ADAU1372_SAI1_BCLKEDGE;
break;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
sai0 |= ADAU1372_SAI0_DELAY1;
break;
case SND_SOC_DAIFMT_LEFT_J:
sai0 |= ADAU1372_SAI0_DELAY0;
invert_lrclk = !invert_lrclk;
break;
case SND_SOC_DAIFMT_DSP_A:
sai0 |= ADAU1372_SAI0_DELAY1;
sai1 |= ADAU1372_SAI1_LR_MODE;
break;
case SND_SOC_DAIFMT_DSP_B:
sai0 |= ADAU1372_SAI0_DELAY0;
sai1 |= ADAU1372_SAI1_LR_MODE;
break;
}
if (invert_lrclk)
sai1 |= ADAU1372_SAI1_LR_POL;
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI0, ADAU1372_SAI0_DELAY_MASK, sai0);
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI1,
ADAU1372_SAI1_MS | ADAU1372_SAI1_BCLKEDGE |
ADAU1372_SAI1_LR_MODE | ADAU1372_SAI1_LR_POL, sai1);
return 0;
}
static int adau1372_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct adau1372 *adau1372 = snd_soc_dai_get_drvdata(dai);
unsigned int rate = params_rate(params);
unsigned int slot_width;
unsigned int sai0, sai1;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(adau1372_rates); i++) {
if (rate == adau1372_rates[i])
break;
}
if (i == ARRAY_SIZE(adau1372_rates))
return -EINVAL;
sai0 = i;
slot_width = adau1372->slot_width;
if (slot_width == 0)
slot_width = params_width(params);
switch (slot_width) {
case 16:
sai1 = ADAU1372_SAI1_BCLKRATE;
break;
case 32:
sai1 = 0;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI0, ADAU1372_SAI0_FS_MASK, sai0);
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI1, ADAU1372_SAI1_BCLKRATE, sai1);
return 0;
}
static int adau1372_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int width)
{
struct adau1372 *adau1372 = snd_soc_dai_get_drvdata(dai);
unsigned int sai0, sai1;
/* I2S mode */
if (slots == 0) {
/* The other settings dont matter in I2S mode */
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI0,
ADAU1372_SAI0_SAI_MASK, ADAU1372_SAI0_SAI_I2S);
adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM2;
adau1372->slot_width = 0;
return 0;
}
/* We have 8 channels anything outside that is not supported */
if ((tx_mask & ~0xff) != 0 || (rx_mask & ~0xff) != 0)
return -EINVAL;
switch (width) {
case 16:
sai1 = ADAU1372_SAI1_BCLK_TDMC;
break;
case 32:
sai1 = 0;
break;
default:
return -EINVAL;
}
switch (slots) {
case 2:
sai0 = ADAU1372_SAI0_SAI_TDM2;
adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM2;
break;
case 4:
sai0 = ADAU1372_SAI0_SAI_TDM4;
if (adau1372->clock_provider)
adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM4_MASTER;
else
adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM4;
break;
case 8:
sai0 = ADAU1372_SAI0_SAI_TDM8;
adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM8;
break;
default:
return -EINVAL;
}
adau1372->slot_width = width;
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI0, ADAU1372_SAI0_SAI_MASK, sai0);
regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI1, ADAU1372_SAI1_BCLK_TDMC, sai1);
/* Mask is inverted in hardware */
regmap_write(adau1372->regmap, ADAU1372_REG_SOUT_CTRL, ~tx_mask);
return 0;
}
static int adau1372_set_tristate(struct snd_soc_dai *dai, int tristate)
{
struct adau1372 *adau1372 = snd_soc_dai_get_drvdata(dai);
unsigned int sai1;
if (tristate)
sai1 = ADAU1372_SAI1_TDM_TS;
else
sai1 = 0;
return regmap_update_bits(adau1372->regmap, ADAU1372_REG_SAI1, ADAU1372_SAI1_TDM_TS, sai1);
}
static int adau1372_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct adau1372 *adau1372 = snd_soc_dai_get_drvdata(dai);
snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&adau1372->rate_constraints);
return 0;
}
static void adau1372_enable_pll(struct adau1372 *adau1372)
{
unsigned int val, timeout = 0;
int ret;
regmap_update_bits(adau1372->regmap, ADAU1372_REG_CLK_CTRL,
ADAU1372_CLK_CTRL_PLL_EN, ADAU1372_CLK_CTRL_PLL_EN);
do {
/* Takes about 1ms to lock */
usleep_range(1000, 2000);
ret = regmap_read(adau1372->regmap, ADAU1372_REG_PLL(5), &val);
if (ret)
break;
timeout++;
} while (!(val & 1) && timeout < 3);
if (ret < 0 || !(val & 1))
dev_err(adau1372->dev, "Failed to lock PLL\n");
}
static void adau1372_set_power(struct adau1372 *adau1372, bool enable)
{
if (adau1372->enabled == enable)
return;
if (enable) {
unsigned int clk_ctrl = ADAU1372_CLK_CTRL_MCLK_EN;
clk_prepare_enable(adau1372->mclk);
if (adau1372->pd_gpio)
gpiod_set_value(adau1372->pd_gpio, 0);
if (adau1372->switch_mode)
adau1372->switch_mode(adau1372->dev);
regcache_cache_only(adau1372->regmap, false);
/*
* Clocks needs to be enabled before any other register can be
* accessed.
*/
if (adau1372->use_pll) {
adau1372_enable_pll(adau1372);
clk_ctrl |= ADAU1372_CLK_CTRL_CLKSRC;
}
regmap_update_bits(adau1372->regmap, ADAU1372_REG_CLK_CTRL,
ADAU1372_CLK_CTRL_MCLK_EN | ADAU1372_CLK_CTRL_CLKSRC, clk_ctrl);
regcache_sync(adau1372->regmap);
} else {
if (adau1372->pd_gpio) {
/*
* This will turn everything off and reset the register
* map. No need to do any register writes to manually
* turn things off.
*/
gpiod_set_value(adau1372->pd_gpio, 1);
regcache_mark_dirty(adau1372->regmap);
} else {
regmap_update_bits(adau1372->regmap, ADAU1372_REG_CLK_CTRL,
ADAU1372_CLK_CTRL_MCLK_EN | ADAU1372_CLK_CTRL_PLL_EN, 0);
}
clk_disable_unprepare(adau1372->mclk);
regcache_cache_only(adau1372->regmap, true);
}
adau1372->enabled = enable;
}
static int adau1372_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct adau1372 *adau1372 = snd_soc_component_get_drvdata(component);
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
adau1372_set_power(adau1372, true);
break;
case SND_SOC_BIAS_OFF:
adau1372_set_power(adau1372, false);
break;
}
return 0;
}
static const struct snd_soc_component_driver adau1372_driver = {
.set_bias_level = adau1372_set_bias_level,
.controls = adau1372_controls,
.num_controls = ARRAY_SIZE(adau1372_controls),
.dapm_widgets = adau1372_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1372_dapm_widgets),
.dapm_routes = adau1372_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1372_dapm_routes),
};
static const struct snd_soc_dai_ops adau1372_dai_ops = {
.set_fmt = adau1372_set_dai_fmt,
.set_tdm_slot = adau1372_set_tdm_slot,
.set_tristate = adau1372_set_tristate,
.hw_params = adau1372_hw_params,
.startup = adau1372_startup,
};
#define ADAU1372_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver adau1372_dai_driver = {
.name = "adau1372",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = ADAU1372_FORMATS,
.sig_bits = 24,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = ADAU1372_FORMATS,
.sig_bits = 24,
},
.ops = &adau1372_dai_ops,
.symmetric_rate = 1,
};
static int adau1372_setup_pll(struct adau1372 *adau1372, unsigned int rate)
{
u8 regs[5];
unsigned int i;
int ret;
ret = adau_calc_pll_cfg(rate, 49152000, regs);
if (ret < 0)
return ret;
for (i = 0; i < ARRAY_SIZE(regs); i++)
regmap_write(adau1372->regmap, ADAU1372_REG_PLL(i), regs[i]);
return 0;
}
int adau1372_probe(struct device *dev, struct regmap *regmap,
void (*switch_mode)(struct device *dev))
{
struct adau1372 *adau1372;
unsigned int clk_ctrl;
unsigned long rate;
int ret;
if (IS_ERR(regmap))
return PTR_ERR(regmap);
adau1372 = devm_kzalloc(dev, sizeof(*adau1372), GFP_KERNEL);
if (!adau1372)
return -ENOMEM;
adau1372->clk = devm_clk_get(dev, "mclk");
if (IS_ERR(adau1372->clk))
return PTR_ERR(adau1372->clk);
adau1372->pd_gpio = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
if (IS_ERR(adau1372->pd_gpio))
return PTR_ERR(adau1372->pd_gpio);
adau1372->regmap = regmap;
adau1372->switch_mode = switch_mode;
adau1372->dev = dev;
adau1372->rate_constraints.list = adau1372_rates;
adau1372->rate_constraints.count = ARRAY_SIZE(adau1372_rates);
adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM2;
dev_set_drvdata(dev, adau1372);
/*
* The datasheet says that the internal MCLK always needs to run at
* 12.288MHz. Automatically choose a valid configuration from the
* external clock.
*/
rate = clk_get_rate(adau1372->clk);
switch (rate) {
case 12288000:
clk_ctrl = ADAU1372_CLK_CTRL_CC_MDIV;
break;
case 24576000:
clk_ctrl = 0;
break;
default:
clk_ctrl = 0;
ret = adau1372_setup_pll(adau1372, rate);
if (ret < 0)
return ret;
adau1372->use_pll = true;
break;
}
/*
* Most of the registers are inaccessible unless the internal clock is
* enabled.
*/
regcache_cache_only(regmap, true);
regmap_update_bits(regmap, ADAU1372_REG_CLK_CTRL, ADAU1372_CLK_CTRL_CC_MDIV, clk_ctrl);
/*
* No pinctrl support yet, put the multi-purpose pins in the most
* sensible mode for general purpose CODEC operation.
*/
regmap_write(regmap, ADAU1372_REG_MODE_MP(1), 0x00); /* SDATA OUT */
regmap_write(regmap, ADAU1372_REG_MODE_MP(6), 0x12); /* CLOCKOUT */
regmap_write(regmap, ADAU1372_REG_OP_STAGE_MUTE, 0x0);
regmap_write(regmap, 0x7, 0x01); /* CLOCK OUT */
return devm_snd_soc_register_component(dev, &adau1372_driver, &adau1372_dai_driver, 1);
}
EXPORT_SYMBOL(adau1372_probe);
static const struct reg_default adau1372_reg_defaults[] = {
{ ADAU1372_REG_CLK_CTRL, 0x00 },
{ ADAU1372_REG_PLL(0), 0x00 },
{ ADAU1372_REG_PLL(1), 0x00 },
{ ADAU1372_REG_PLL(2), 0x00 },
{ ADAU1372_REG_PLL(3), 0x00 },
{ ADAU1372_REG_PLL(4), 0x00 },
{ ADAU1372_REG_PLL(5), 0x00 },
{ ADAU1372_REG_DAC_SOURCE, 0x10 },
{ ADAU1372_REG_SOUT_SOURCE_0_1, 0x54 },
{ ADAU1372_REG_SOUT_SOURCE_2_3, 0x76 },
{ ADAU1372_REG_SOUT_SOURCE_4_5, 0x54 },
{ ADAU1372_REG_SOUT_SOURCE_6_7, 0x76 },
{ ADAU1372_REG_ADC_SDATA_CH, 0x04 },
{ ADAU1372_REG_ASRCO_SOURCE_0_1, 0x10 },
{ ADAU1372_REG_ASRCO_SOURCE_2_3, 0x32 },
{ ADAU1372_REG_ASRC_MODE, 0x00 },
{ ADAU1372_REG_ADC_CTRL0, 0x19 },
{ ADAU1372_REG_ADC_CTRL1, 0x19 },
{ ADAU1372_REG_ADC_CTRL2, 0x00 },
{ ADAU1372_REG_ADC_CTRL3, 0x00 },
{ ADAU1372_REG_ADC_VOL(0), 0x00 },
{ ADAU1372_REG_ADC_VOL(1), 0x00 },
{ ADAU1372_REG_ADC_VOL(2), 0x00 },
{ ADAU1372_REG_ADC_VOL(3), 0x00 },
{ ADAU1372_REG_PGA_CTRL(0), 0x40 },
{ ADAU1372_REG_PGA_CTRL(1), 0x40 },
{ ADAU1372_REG_PGA_CTRL(2), 0x40 },
{ ADAU1372_REG_PGA_CTRL(3), 0x40 },
{ ADAU1372_REG_PGA_BOOST, 0x00 },
{ ADAU1372_REG_MICBIAS, 0x00 },
{ ADAU1372_REG_DAC_CTRL, 0x18 },
{ ADAU1372_REG_DAC_VOL(0), 0x00 },
{ ADAU1372_REG_DAC_VOL(1), 0x00 },
{ ADAU1372_REG_OP_STAGE_MUTE, 0x0f },
{ ADAU1372_REG_SAI0, 0x00 },
{ ADAU1372_REG_SAI1, 0x00 },
{ ADAU1372_REG_SOUT_CTRL, 0x00 },
{ ADAU1372_REG_MODE_MP(0), 0x00 },
{ ADAU1372_REG_MODE_MP(1), 0x10 },
{ ADAU1372_REG_MODE_MP(4), 0x00 },
{ ADAU1372_REG_MODE_MP(5), 0x00 },
{ ADAU1372_REG_MODE_MP(6), 0x11 },
{ ADAU1372_REG_OP_STAGE_CTRL, 0x0f },
{ ADAU1372_REG_DECIM_PWR, 0x00 },
{ ADAU1372_REG_INTERP_PWR, 0x00 },
{ ADAU1372_REG_BIAS_CTRL0, 0x00 },
{ ADAU1372_REG_BIAS_CTRL1, 0x00 },
};
static bool adau1372_volatile_register(struct device *dev, unsigned int reg)
{
if (reg == ADAU1372_REG_PLL(5))
return true;
return false;
}
const struct regmap_config adau1372_regmap_config = {
.val_bits = 8,
.reg_bits = 16,
.max_register = 0x4d,
.reg_defaults = adau1372_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(adau1372_reg_defaults),
.volatile_reg = adau1372_volatile_register,
.cache_type = REGCACHE_RBTREE,
};
EXPORT_SYMBOL_GPL(adau1372_regmap_config);
MODULE_DESCRIPTION("ASoC ADAU1372 CODEC driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Analog Devices ADAU1373 Audio Codec drive
*
* Copyright 2011 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/gcd.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <sound/adau1373.h>
#include "adau1373.h"
#include "adau-utils.h"
struct adau1373_dai {
unsigned int clk_src;
unsigned int sysclk;
bool enable_src;
bool clock_provider;
};
struct adau1373 {
struct regmap *regmap;
struct adau1373_dai dais[3];
};
#define ADAU1373_INPUT_MODE 0x00
#define ADAU1373_AINL_CTRL(x) (0x01 + (x) * 2)
#define ADAU1373_AINR_CTRL(x) (0x02 + (x) * 2)
#define ADAU1373_LLINE_OUT(x) (0x9 + (x) * 2)
#define ADAU1373_RLINE_OUT(x) (0xa + (x) * 2)
#define ADAU1373_LSPK_OUT 0x0d
#define ADAU1373_RSPK_OUT 0x0e
#define ADAU1373_LHP_OUT 0x0f
#define ADAU1373_RHP_OUT 0x10
#define ADAU1373_ADC_GAIN 0x11
#define ADAU1373_LADC_MIXER 0x12
#define ADAU1373_RADC_MIXER 0x13
#define ADAU1373_LLINE1_MIX 0x14
#define ADAU1373_RLINE1_MIX 0x15
#define ADAU1373_LLINE2_MIX 0x16
#define ADAU1373_RLINE2_MIX 0x17
#define ADAU1373_LSPK_MIX 0x18
#define ADAU1373_RSPK_MIX 0x19
#define ADAU1373_LHP_MIX 0x1a
#define ADAU1373_RHP_MIX 0x1b
#define ADAU1373_EP_MIX 0x1c
#define ADAU1373_HP_CTRL 0x1d
#define ADAU1373_HP_CTRL2 0x1e
#define ADAU1373_LS_CTRL 0x1f
#define ADAU1373_EP_CTRL 0x21
#define ADAU1373_MICBIAS_CTRL1 0x22
#define ADAU1373_MICBIAS_CTRL2 0x23
#define ADAU1373_OUTPUT_CTRL 0x24
#define ADAU1373_PWDN_CTRL1 0x25
#define ADAU1373_PWDN_CTRL2 0x26
#define ADAU1373_PWDN_CTRL3 0x27
#define ADAU1373_DPLL_CTRL(x) (0x28 + (x) * 7)
#define ADAU1373_PLL_CTRL1(x) (0x29 + (x) * 7)
#define ADAU1373_PLL_CTRL2(x) (0x2a + (x) * 7)
#define ADAU1373_PLL_CTRL3(x) (0x2b + (x) * 7)
#define ADAU1373_PLL_CTRL4(x) (0x2c + (x) * 7)
#define ADAU1373_PLL_CTRL5(x) (0x2d + (x) * 7)
#define ADAU1373_PLL_CTRL6(x) (0x2e + (x) * 7)
#define ADAU1373_HEADDECT 0x36
#define ADAU1373_ADC_DAC_STATUS 0x37
#define ADAU1373_ADC_CTRL 0x3c
#define ADAU1373_DAI(x) (0x44 + (x))
#define ADAU1373_CLK_SRC_DIV(x) (0x40 + (x) * 2)
#define ADAU1373_BCLKDIV(x) (0x47 + (x))
#define ADAU1373_SRC_RATIOA(x) (0x4a + (x) * 2)
#define ADAU1373_SRC_RATIOB(x) (0x4b + (x) * 2)
#define ADAU1373_DEEMP_CTRL 0x50
#define ADAU1373_SRC_DAI_CTRL(x) (0x51 + (x))
#define ADAU1373_DIN_MIX_CTRL(x) (0x56 + (x))
#define ADAU1373_DOUT_MIX_CTRL(x) (0x5b + (x))
#define ADAU1373_DAI_PBL_VOL(x) (0x62 + (x) * 2)
#define ADAU1373_DAI_PBR_VOL(x) (0x63 + (x) * 2)
#define ADAU1373_DAI_RECL_VOL(x) (0x68 + (x) * 2)
#define ADAU1373_DAI_RECR_VOL(x) (0x69 + (x) * 2)
#define ADAU1373_DAC1_PBL_VOL 0x6e
#define ADAU1373_DAC1_PBR_VOL 0x6f
#define ADAU1373_DAC2_PBL_VOL 0x70
#define ADAU1373_DAC2_PBR_VOL 0x71
#define ADAU1373_ADC_RECL_VOL 0x72
#define ADAU1373_ADC_RECR_VOL 0x73
#define ADAU1373_DMIC_RECL_VOL 0x74
#define ADAU1373_DMIC_RECR_VOL 0x75
#define ADAU1373_VOL_GAIN1 0x76
#define ADAU1373_VOL_GAIN2 0x77
#define ADAU1373_VOL_GAIN3 0x78
#define ADAU1373_HPF_CTRL 0x7d
#define ADAU1373_BASS1 0x7e
#define ADAU1373_BASS2 0x7f
#define ADAU1373_DRC(x) (0x80 + (x) * 0x10)
#define ADAU1373_3D_CTRL1 0xc0
#define ADAU1373_3D_CTRL2 0xc1
#define ADAU1373_FDSP_SEL1 0xdc
#define ADAU1373_FDSP_SEL2 0xdd
#define ADAU1373_FDSP_SEL3 0xde
#define ADAU1373_FDSP_SEL4 0xdf
#define ADAU1373_DIGMICCTRL 0xe2
#define ADAU1373_DIGEN 0xeb
#define ADAU1373_SOFT_RESET 0xff
#define ADAU1373_PLL_CTRL6_DPLL_BYPASS BIT(1)
#define ADAU1373_PLL_CTRL6_PLL_EN BIT(0)
#define ADAU1373_DAI_INVERT_BCLK BIT(7)
#define ADAU1373_DAI_MASTER BIT(6)
#define ADAU1373_DAI_INVERT_LRCLK BIT(4)
#define ADAU1373_DAI_WLEN_16 0x0
#define ADAU1373_DAI_WLEN_20 0x4
#define ADAU1373_DAI_WLEN_24 0x8
#define ADAU1373_DAI_WLEN_32 0xc
#define ADAU1373_DAI_WLEN_MASK 0xc
#define ADAU1373_DAI_FORMAT_RIGHT_J 0x0
#define ADAU1373_DAI_FORMAT_LEFT_J 0x1
#define ADAU1373_DAI_FORMAT_I2S 0x2
#define ADAU1373_DAI_FORMAT_DSP 0x3
#define ADAU1373_BCLKDIV_SOURCE BIT(5)
#define ADAU1373_BCLKDIV_SR_MASK (0x07 << 2)
#define ADAU1373_BCLKDIV_BCLK_MASK 0x03
#define ADAU1373_BCLKDIV_32 0x03
#define ADAU1373_BCLKDIV_64 0x02
#define ADAU1373_BCLKDIV_128 0x01
#define ADAU1373_BCLKDIV_256 0x00
#define ADAU1373_ADC_CTRL_PEAK_DETECT BIT(0)
#define ADAU1373_ADC_CTRL_RESET BIT(1)
#define ADAU1373_ADC_CTRL_RESET_FORCE BIT(2)
#define ADAU1373_OUTPUT_CTRL_LDIFF BIT(3)
#define ADAU1373_OUTPUT_CTRL_LNFBEN BIT(2)
#define ADAU1373_PWDN_CTRL3_PWR_EN BIT(0)
#define ADAU1373_EP_CTRL_MICBIAS1_OFFSET 4
#define ADAU1373_EP_CTRL_MICBIAS2_OFFSET 2
static const struct reg_default adau1373_reg_defaults[] = {
{ ADAU1373_INPUT_MODE, 0x00 },
{ ADAU1373_AINL_CTRL(0), 0x00 },
{ ADAU1373_AINR_CTRL(0), 0x00 },
{ ADAU1373_AINL_CTRL(1), 0x00 },
{ ADAU1373_AINR_CTRL(1), 0x00 },
{ ADAU1373_AINL_CTRL(2), 0x00 },
{ ADAU1373_AINR_CTRL(2), 0x00 },
{ ADAU1373_AINL_CTRL(3), 0x00 },
{ ADAU1373_AINR_CTRL(3), 0x00 },
{ ADAU1373_LLINE_OUT(0), 0x00 },
{ ADAU1373_RLINE_OUT(0), 0x00 },
{ ADAU1373_LLINE_OUT(1), 0x00 },
{ ADAU1373_RLINE_OUT(1), 0x00 },
{ ADAU1373_LSPK_OUT, 0x00 },
{ ADAU1373_RSPK_OUT, 0x00 },
{ ADAU1373_LHP_OUT, 0x00 },
{ ADAU1373_RHP_OUT, 0x00 },
{ ADAU1373_ADC_GAIN, 0x00 },
{ ADAU1373_LADC_MIXER, 0x00 },
{ ADAU1373_RADC_MIXER, 0x00 },
{ ADAU1373_LLINE1_MIX, 0x00 },
{ ADAU1373_RLINE1_MIX, 0x00 },
{ ADAU1373_LLINE2_MIX, 0x00 },
{ ADAU1373_RLINE2_MIX, 0x00 },
{ ADAU1373_LSPK_MIX, 0x00 },
{ ADAU1373_RSPK_MIX, 0x00 },
{ ADAU1373_LHP_MIX, 0x00 },
{ ADAU1373_RHP_MIX, 0x00 },
{ ADAU1373_EP_MIX, 0x00 },
{ ADAU1373_HP_CTRL, 0x00 },
{ ADAU1373_HP_CTRL2, 0x00 },
{ ADAU1373_LS_CTRL, 0x00 },
{ ADAU1373_EP_CTRL, 0x00 },
{ ADAU1373_MICBIAS_CTRL1, 0x00 },
{ ADAU1373_MICBIAS_CTRL2, 0x00 },
{ ADAU1373_OUTPUT_CTRL, 0x00 },
{ ADAU1373_PWDN_CTRL1, 0x00 },
{ ADAU1373_PWDN_CTRL2, 0x00 },
{ ADAU1373_PWDN_CTRL3, 0x00 },
{ ADAU1373_DPLL_CTRL(0), 0x00 },
{ ADAU1373_PLL_CTRL1(0), 0x00 },
{ ADAU1373_PLL_CTRL2(0), 0x00 },
{ ADAU1373_PLL_CTRL3(0), 0x00 },
{ ADAU1373_PLL_CTRL4(0), 0x00 },
{ ADAU1373_PLL_CTRL5(0), 0x00 },
{ ADAU1373_PLL_CTRL6(0), 0x02 },
{ ADAU1373_DPLL_CTRL(1), 0x00 },
{ ADAU1373_PLL_CTRL1(1), 0x00 },
{ ADAU1373_PLL_CTRL2(1), 0x00 },
{ ADAU1373_PLL_CTRL3(1), 0x00 },
{ ADAU1373_PLL_CTRL4(1), 0x00 },
{ ADAU1373_PLL_CTRL5(1), 0x00 },
{ ADAU1373_PLL_CTRL6(1), 0x02 },
{ ADAU1373_HEADDECT, 0x00 },
{ ADAU1373_ADC_CTRL, 0x00 },
{ ADAU1373_CLK_SRC_DIV(0), 0x00 },
{ ADAU1373_CLK_SRC_DIV(1), 0x00 },
{ ADAU1373_DAI(0), 0x0a },
{ ADAU1373_DAI(1), 0x0a },
{ ADAU1373_DAI(2), 0x0a },
{ ADAU1373_BCLKDIV(0), 0x00 },
{ ADAU1373_BCLKDIV(1), 0x00 },
{ ADAU1373_BCLKDIV(2), 0x00 },
{ ADAU1373_SRC_RATIOA(0), 0x00 },
{ ADAU1373_SRC_RATIOB(0), 0x00 },
{ ADAU1373_SRC_RATIOA(1), 0x00 },
{ ADAU1373_SRC_RATIOB(1), 0x00 },
{ ADAU1373_SRC_RATIOA(2), 0x00 },
{ ADAU1373_SRC_RATIOB(2), 0x00 },
{ ADAU1373_DEEMP_CTRL, 0x00 },
{ ADAU1373_SRC_DAI_CTRL(0), 0x08 },
{ ADAU1373_SRC_DAI_CTRL(1), 0x08 },
{ ADAU1373_SRC_DAI_CTRL(2), 0x08 },
{ ADAU1373_DIN_MIX_CTRL(0), 0x00 },
{ ADAU1373_DIN_MIX_CTRL(1), 0x00 },
{ ADAU1373_DIN_MIX_CTRL(2), 0x00 },
{ ADAU1373_DIN_MIX_CTRL(3), 0x00 },
{ ADAU1373_DIN_MIX_CTRL(4), 0x00 },
{ ADAU1373_DOUT_MIX_CTRL(0), 0x00 },
{ ADAU1373_DOUT_MIX_CTRL(1), 0x00 },
{ ADAU1373_DOUT_MIX_CTRL(2), 0x00 },
{ ADAU1373_DOUT_MIX_CTRL(3), 0x00 },
{ ADAU1373_DOUT_MIX_CTRL(4), 0x00 },
{ ADAU1373_DAI_PBL_VOL(0), 0x00 },
{ ADAU1373_DAI_PBR_VOL(0), 0x00 },
{ ADAU1373_DAI_PBL_VOL(1), 0x00 },
{ ADAU1373_DAI_PBR_VOL(1), 0x00 },
{ ADAU1373_DAI_PBL_VOL(2), 0x00 },
{ ADAU1373_DAI_PBR_VOL(2), 0x00 },
{ ADAU1373_DAI_RECL_VOL(0), 0x00 },
{ ADAU1373_DAI_RECR_VOL(0), 0x00 },
{ ADAU1373_DAI_RECL_VOL(1), 0x00 },
{ ADAU1373_DAI_RECR_VOL(1), 0x00 },
{ ADAU1373_DAI_RECL_VOL(2), 0x00 },
{ ADAU1373_DAI_RECR_VOL(2), 0x00 },
{ ADAU1373_DAC1_PBL_VOL, 0x00 },
{ ADAU1373_DAC1_PBR_VOL, 0x00 },
{ ADAU1373_DAC2_PBL_VOL, 0x00 },
{ ADAU1373_DAC2_PBR_VOL, 0x00 },
{ ADAU1373_ADC_RECL_VOL, 0x00 },
{ ADAU1373_ADC_RECR_VOL, 0x00 },
{ ADAU1373_DMIC_RECL_VOL, 0x00 },
{ ADAU1373_DMIC_RECR_VOL, 0x00 },
{ ADAU1373_VOL_GAIN1, 0x00 },
{ ADAU1373_VOL_GAIN2, 0x00 },
{ ADAU1373_VOL_GAIN3, 0x00 },
{ ADAU1373_HPF_CTRL, 0x00 },
{ ADAU1373_BASS1, 0x00 },
{ ADAU1373_BASS2, 0x00 },
{ ADAU1373_DRC(0) + 0x0, 0x78 },
{ ADAU1373_DRC(0) + 0x1, 0x18 },
{ ADAU1373_DRC(0) + 0x2, 0x00 },
{ ADAU1373_DRC(0) + 0x3, 0x00 },
{ ADAU1373_DRC(0) + 0x4, 0x00 },
{ ADAU1373_DRC(0) + 0x5, 0xc0 },
{ ADAU1373_DRC(0) + 0x6, 0x00 },
{ ADAU1373_DRC(0) + 0x7, 0x00 },
{ ADAU1373_DRC(0) + 0x8, 0x00 },
{ ADAU1373_DRC(0) + 0x9, 0xc0 },
{ ADAU1373_DRC(0) + 0xa, 0x88 },
{ ADAU1373_DRC(0) + 0xb, 0x7a },
{ ADAU1373_DRC(0) + 0xc, 0xdf },
{ ADAU1373_DRC(0) + 0xd, 0x20 },
{ ADAU1373_DRC(0) + 0xe, 0x00 },
{ ADAU1373_DRC(0) + 0xf, 0x00 },
{ ADAU1373_DRC(1) + 0x0, 0x78 },
{ ADAU1373_DRC(1) + 0x1, 0x18 },
{ ADAU1373_DRC(1) + 0x2, 0x00 },
{ ADAU1373_DRC(1) + 0x3, 0x00 },
{ ADAU1373_DRC(1) + 0x4, 0x00 },
{ ADAU1373_DRC(1) + 0x5, 0xc0 },
{ ADAU1373_DRC(1) + 0x6, 0x00 },
{ ADAU1373_DRC(1) + 0x7, 0x00 },
{ ADAU1373_DRC(1) + 0x8, 0x00 },
{ ADAU1373_DRC(1) + 0x9, 0xc0 },
{ ADAU1373_DRC(1) + 0xa, 0x88 },
{ ADAU1373_DRC(1) + 0xb, 0x7a },
{ ADAU1373_DRC(1) + 0xc, 0xdf },
{ ADAU1373_DRC(1) + 0xd, 0x20 },
{ ADAU1373_DRC(1) + 0xe, 0x00 },
{ ADAU1373_DRC(1) + 0xf, 0x00 },
{ ADAU1373_DRC(2) + 0x0, 0x78 },
{ ADAU1373_DRC(2) + 0x1, 0x18 },
{ ADAU1373_DRC(2) + 0x2, 0x00 },
{ ADAU1373_DRC(2) + 0x3, 0x00 },
{ ADAU1373_DRC(2) + 0x4, 0x00 },
{ ADAU1373_DRC(2) + 0x5, 0xc0 },
{ ADAU1373_DRC(2) + 0x6, 0x00 },
{ ADAU1373_DRC(2) + 0x7, 0x00 },
{ ADAU1373_DRC(2) + 0x8, 0x00 },
{ ADAU1373_DRC(2) + 0x9, 0xc0 },
{ ADAU1373_DRC(2) + 0xa, 0x88 },
{ ADAU1373_DRC(2) + 0xb, 0x7a },
{ ADAU1373_DRC(2) + 0xc, 0xdf },
{ ADAU1373_DRC(2) + 0xd, 0x20 },
{ ADAU1373_DRC(2) + 0xe, 0x00 },
{ ADAU1373_DRC(2) + 0xf, 0x00 },
{ ADAU1373_3D_CTRL1, 0x00 },
{ ADAU1373_3D_CTRL2, 0x00 },
{ ADAU1373_FDSP_SEL1, 0x00 },
{ ADAU1373_FDSP_SEL2, 0x00 },
{ ADAU1373_FDSP_SEL2, 0x00 },
{ ADAU1373_FDSP_SEL4, 0x00 },
{ ADAU1373_DIGMICCTRL, 0x00 },
{ ADAU1373_DIGEN, 0x00 },
};
static const DECLARE_TLV_DB_RANGE(adau1373_out_tlv,
0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1),
8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0),
16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0),
24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0)
);
static const DECLARE_TLV_DB_MINMAX(adau1373_digital_tlv, -9563, 0);
static const DECLARE_TLV_DB_SCALE(adau1373_in_pga_tlv, -1300, 100, 1);
static const DECLARE_TLV_DB_SCALE(adau1373_ep_tlv, -600, 600, 1);
static const DECLARE_TLV_DB_SCALE(adau1373_input_boost_tlv, 0, 2000, 0);
static const DECLARE_TLV_DB_SCALE(adau1373_gain_boost_tlv, 0, 600, 0);
static const DECLARE_TLV_DB_SCALE(adau1373_speaker_boost_tlv, 1200, 600, 0);
static const char *adau1373_fdsp_sel_text[] = {
"None",
"Channel 1",
"Channel 2",
"Channel 3",
"Channel 4",
"Channel 5",
};
static SOC_ENUM_SINGLE_DECL(adau1373_drc1_channel_enum,
ADAU1373_FDSP_SEL1, 4, adau1373_fdsp_sel_text);
static SOC_ENUM_SINGLE_DECL(adau1373_drc2_channel_enum,
ADAU1373_FDSP_SEL1, 0, adau1373_fdsp_sel_text);
static SOC_ENUM_SINGLE_DECL(adau1373_drc3_channel_enum,
ADAU1373_FDSP_SEL2, 0, adau1373_fdsp_sel_text);
static SOC_ENUM_SINGLE_DECL(adau1373_hpf_channel_enum,
ADAU1373_FDSP_SEL3, 0, adau1373_fdsp_sel_text);
static SOC_ENUM_SINGLE_DECL(adau1373_bass_channel_enum,
ADAU1373_FDSP_SEL4, 4, adau1373_fdsp_sel_text);
static const char *adau1373_hpf_cutoff_text[] = {
"3.7Hz", "50Hz", "100Hz", "150Hz", "200Hz", "250Hz", "300Hz", "350Hz",
"400Hz", "450Hz", "500Hz", "550Hz", "600Hz", "650Hz", "700Hz", "750Hz",
"800Hz",
};
static SOC_ENUM_SINGLE_DECL(adau1373_hpf_cutoff_enum,
ADAU1373_HPF_CTRL, 3, adau1373_hpf_cutoff_text);
static const char *adau1373_bass_lpf_cutoff_text[] = {
"801Hz", "1001Hz",
};
static const char *adau1373_bass_clip_level_text[] = {
"0.125", "0.250", "0.370", "0.500", "0.625", "0.750", "0.875",
};
static const unsigned int adau1373_bass_clip_level_values[] = {
1, 2, 3, 4, 5, 6, 7,
};
static const char *adau1373_bass_hpf_cutoff_text[] = {
"158Hz", "232Hz", "347Hz", "520Hz",
};
static const DECLARE_TLV_DB_RANGE(adau1373_bass_tlv,
0, 2, TLV_DB_SCALE_ITEM(-600, 600, 1),
3, 4, TLV_DB_SCALE_ITEM(950, 250, 0),
5, 7, TLV_DB_SCALE_ITEM(1400, 150, 0)
);
static SOC_ENUM_SINGLE_DECL(adau1373_bass_lpf_cutoff_enum,
ADAU1373_BASS1, 5, adau1373_bass_lpf_cutoff_text);
static SOC_VALUE_ENUM_SINGLE_DECL(adau1373_bass_clip_level_enum,
ADAU1373_BASS1, 2, 7, adau1373_bass_clip_level_text,
adau1373_bass_clip_level_values);
static SOC_ENUM_SINGLE_DECL(adau1373_bass_hpf_cutoff_enum,
ADAU1373_BASS1, 0, adau1373_bass_hpf_cutoff_text);
static const char *adau1373_3d_level_text[] = {
"0%", "6.67%", "13.33%", "20%", "26.67%", "33.33%",
"40%", "46.67%", "53.33%", "60%", "66.67%", "73.33%",
"80%", "86.67", "99.33%", "100%"
};
static const char *adau1373_3d_cutoff_text[] = {
"No 3D", "0.03125 fs", "0.04583 fs", "0.075 fs", "0.11458 fs",
"0.16875 fs", "0.27083 fs"
};
static SOC_ENUM_SINGLE_DECL(adau1373_3d_level_enum,
ADAU1373_3D_CTRL1, 4, adau1373_3d_level_text);
static SOC_ENUM_SINGLE_DECL(adau1373_3d_cutoff_enum,
ADAU1373_3D_CTRL1, 0, adau1373_3d_cutoff_text);
static const DECLARE_TLV_DB_RANGE(adau1373_3d_tlv,
0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
1, 7, TLV_DB_LINEAR_ITEM(-1800, -120)
);
static const char *adau1373_lr_mux_text[] = {
"Mute",
"Right Channel (L+R)",
"Left Channel (L+R)",
"Stereo",
};
static SOC_ENUM_SINGLE_DECL(adau1373_lineout1_lr_mux_enum,
ADAU1373_OUTPUT_CTRL, 4, adau1373_lr_mux_text);
static SOC_ENUM_SINGLE_DECL(adau1373_lineout2_lr_mux_enum,
ADAU1373_OUTPUT_CTRL, 6, adau1373_lr_mux_text);
static SOC_ENUM_SINGLE_DECL(adau1373_speaker_lr_mux_enum,
ADAU1373_LS_CTRL, 4, adau1373_lr_mux_text);
static const struct snd_kcontrol_new adau1373_controls[] = {
SOC_DOUBLE_R_TLV("AIF1 Capture Volume", ADAU1373_DAI_RECL_VOL(0),
ADAU1373_DAI_RECR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("AIF2 Capture Volume", ADAU1373_DAI_RECL_VOL(1),
ADAU1373_DAI_RECR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("AIF3 Capture Volume", ADAU1373_DAI_RECL_VOL(2),
ADAU1373_DAI_RECR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("ADC Capture Volume", ADAU1373_ADC_RECL_VOL,
ADAU1373_ADC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("DMIC Capture Volume", ADAU1373_DMIC_RECL_VOL,
ADAU1373_DMIC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("AIF1 Playback Volume", ADAU1373_DAI_PBL_VOL(0),
ADAU1373_DAI_PBR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("AIF2 Playback Volume", ADAU1373_DAI_PBL_VOL(1),
ADAU1373_DAI_PBR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("AIF3 Playback Volume", ADAU1373_DAI_PBL_VOL(2),
ADAU1373_DAI_PBR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("DAC1 Playback Volume", ADAU1373_DAC1_PBL_VOL,
ADAU1373_DAC1_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("DAC2 Playback Volume", ADAU1373_DAC2_PBL_VOL,
ADAU1373_DAC2_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
SOC_DOUBLE_R_TLV("Lineout1 Playback Volume", ADAU1373_LLINE_OUT(0),
ADAU1373_RLINE_OUT(0), 0, 0x1f, 0, adau1373_out_tlv),
SOC_DOUBLE_R_TLV("Speaker Playback Volume", ADAU1373_LSPK_OUT,
ADAU1373_RSPK_OUT, 0, 0x1f, 0, adau1373_out_tlv),
SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1373_LHP_OUT,
ADAU1373_RHP_OUT, 0, 0x1f, 0, adau1373_out_tlv),
SOC_DOUBLE_R_TLV("Input 1 Capture Volume", ADAU1373_AINL_CTRL(0),
ADAU1373_AINR_CTRL(0), 0, 0x1f, 0, adau1373_in_pga_tlv),
SOC_DOUBLE_R_TLV("Input 2 Capture Volume", ADAU1373_AINL_CTRL(1),
ADAU1373_AINR_CTRL(1), 0, 0x1f, 0, adau1373_in_pga_tlv),
SOC_DOUBLE_R_TLV("Input 3 Capture Volume", ADAU1373_AINL_CTRL(2),
ADAU1373_AINR_CTRL(2), 0, 0x1f, 0, adau1373_in_pga_tlv),
SOC_DOUBLE_R_TLV("Input 4 Capture Volume", ADAU1373_AINL_CTRL(3),
ADAU1373_AINR_CTRL(3), 0, 0x1f, 0, adau1373_in_pga_tlv),
SOC_SINGLE_TLV("Earpiece Playback Volume", ADAU1373_EP_CTRL, 0, 3, 0,
adau1373_ep_tlv),
SOC_DOUBLE_TLV("AIF3 Boost Playback Volume", ADAU1373_VOL_GAIN1, 4, 5,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("AIF2 Boost Playback Volume", ADAU1373_VOL_GAIN1, 2, 3,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("AIF1 Boost Playback Volume", ADAU1373_VOL_GAIN1, 0, 1,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("AIF3 Boost Capture Volume", ADAU1373_VOL_GAIN2, 4, 5,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("AIF2 Boost Capture Volume", ADAU1373_VOL_GAIN2, 2, 3,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("AIF1 Boost Capture Volume", ADAU1373_VOL_GAIN2, 0, 1,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("DMIC Boost Capture Volume", ADAU1373_VOL_GAIN3, 6, 7,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("ADC Boost Capture Volume", ADAU1373_VOL_GAIN3, 4, 5,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("DAC2 Boost Playback Volume", ADAU1373_VOL_GAIN3, 2, 3,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("DAC1 Boost Playback Volume", ADAU1373_VOL_GAIN3, 0, 1,
1, 0, adau1373_gain_boost_tlv),
SOC_DOUBLE_TLV("Input 1 Boost Capture Volume", ADAU1373_ADC_GAIN, 0, 4,
1, 0, adau1373_input_boost_tlv),
SOC_DOUBLE_TLV("Input 2 Boost Capture Volume", ADAU1373_ADC_GAIN, 1, 5,
1, 0, adau1373_input_boost_tlv),
SOC_DOUBLE_TLV("Input 3 Boost Capture Volume", ADAU1373_ADC_GAIN, 2, 6,
1, 0, adau1373_input_boost_tlv),
SOC_DOUBLE_TLV("Input 4 Boost Capture Volume", ADAU1373_ADC_GAIN, 3, 7,
1, 0, adau1373_input_boost_tlv),
SOC_DOUBLE_TLV("Speaker Boost Playback Volume", ADAU1373_LS_CTRL, 2, 3,
1, 0, adau1373_speaker_boost_tlv),
SOC_ENUM("Lineout1 LR Mux", adau1373_lineout1_lr_mux_enum),
SOC_ENUM("Speaker LR Mux", adau1373_speaker_lr_mux_enum),
SOC_ENUM("HPF Cutoff", adau1373_hpf_cutoff_enum),
SOC_DOUBLE("HPF Switch", ADAU1373_HPF_CTRL, 1, 0, 1, 0),
SOC_ENUM("HPF Channel", adau1373_hpf_channel_enum),
SOC_ENUM("Bass HPF Cutoff", adau1373_bass_hpf_cutoff_enum),
SOC_ENUM("Bass Clip Level Threshold", adau1373_bass_clip_level_enum),
SOC_ENUM("Bass LPF Cutoff", adau1373_bass_lpf_cutoff_enum),
SOC_DOUBLE("Bass Playback Switch", ADAU1373_BASS2, 0, 1, 1, 0),
SOC_SINGLE_TLV("Bass Playback Volume", ADAU1373_BASS2, 2, 7, 0,
adau1373_bass_tlv),
SOC_ENUM("Bass Channel", adau1373_bass_channel_enum),
SOC_ENUM("3D Freq", adau1373_3d_cutoff_enum),
SOC_ENUM("3D Level", adau1373_3d_level_enum),
SOC_SINGLE("3D Playback Switch", ADAU1373_3D_CTRL2, 0, 1, 0),
SOC_SINGLE_TLV("3D Playback Volume", ADAU1373_3D_CTRL2, 2, 7, 0,
adau1373_3d_tlv),
SOC_ENUM("3D Channel", adau1373_bass_channel_enum),
SOC_SINGLE("Zero Cross Switch", ADAU1373_PWDN_CTRL3, 7, 1, 0),
};
static const struct snd_kcontrol_new adau1373_lineout2_controls[] = {
SOC_DOUBLE_R_TLV("Lineout2 Playback Volume", ADAU1373_LLINE_OUT(1),
ADAU1373_RLINE_OUT(1), 0, 0x1f, 0, adau1373_out_tlv),
SOC_ENUM("Lineout2 LR Mux", adau1373_lineout2_lr_mux_enum),
};
static const struct snd_kcontrol_new adau1373_drc_controls[] = {
SOC_ENUM("DRC1 Channel", adau1373_drc1_channel_enum),
SOC_ENUM("DRC2 Channel", adau1373_drc2_channel_enum),
SOC_ENUM("DRC3 Channel", adau1373_drc3_channel_enum),
};
static int adau1373_pll_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
unsigned int pll_id = w->name[3] - '1';
unsigned int val;
if (SND_SOC_DAPM_EVENT_ON(event))
val = ADAU1373_PLL_CTRL6_PLL_EN;
else
val = 0;
regmap_update_bits(adau1373->regmap, ADAU1373_PLL_CTRL6(pll_id),
ADAU1373_PLL_CTRL6_PLL_EN, val);
if (SND_SOC_DAPM_EVENT_ON(event))
mdelay(5);
return 0;
}
static const char *adau1373_decimator_text[] = {
"ADC",
"DMIC1",
};
static SOC_ENUM_SINGLE_VIRT_DECL(adau1373_decimator_enum,
adau1373_decimator_text);
static const struct snd_kcontrol_new adau1373_decimator_mux =
SOC_DAPM_ENUM("Decimator Mux", adau1373_decimator_enum);
static const struct snd_kcontrol_new adau1373_left_adc_mixer_controls[] = {
SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_LADC_MIXER, 4, 1, 0),
SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_LADC_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_LADC_MIXER, 2, 1, 0),
SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_LADC_MIXER, 1, 1, 0),
SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_LADC_MIXER, 0, 1, 0),
};
static const struct snd_kcontrol_new adau1373_right_adc_mixer_controls[] = {
SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_RADC_MIXER, 4, 1, 0),
SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_RADC_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_RADC_MIXER, 2, 1, 0),
SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_RADC_MIXER, 1, 1, 0),
SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_RADC_MIXER, 0, 1, 0),
};
#define DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(_name, _reg) \
const struct snd_kcontrol_new _name[] = { \
SOC_DAPM_SINGLE("Left DAC2 Switch", _reg, 7, 1, 0), \
SOC_DAPM_SINGLE("Right DAC2 Switch", _reg, 6, 1, 0), \
SOC_DAPM_SINGLE("Left DAC1 Switch", _reg, 5, 1, 0), \
SOC_DAPM_SINGLE("Right DAC1 Switch", _reg, 4, 1, 0), \
SOC_DAPM_SINGLE("Input 4 Bypass Switch", _reg, 3, 1, 0), \
SOC_DAPM_SINGLE("Input 3 Bypass Switch", _reg, 2, 1, 0), \
SOC_DAPM_SINGLE("Input 2 Bypass Switch", _reg, 1, 1, 0), \
SOC_DAPM_SINGLE("Input 1 Bypass Switch", _reg, 0, 1, 0), \
}
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line1_mixer_controls,
ADAU1373_LLINE1_MIX);
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line1_mixer_controls,
ADAU1373_RLINE1_MIX);
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line2_mixer_controls,
ADAU1373_LLINE2_MIX);
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line2_mixer_controls,
ADAU1373_RLINE2_MIX);
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_spk_mixer_controls,
ADAU1373_LSPK_MIX);
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_spk_mixer_controls,
ADAU1373_RSPK_MIX);
static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_ep_mixer_controls,
ADAU1373_EP_MIX);
static const struct snd_kcontrol_new adau1373_left_hp_mixer_controls[] = {
SOC_DAPM_SINGLE("Left DAC1 Switch", ADAU1373_LHP_MIX, 5, 1, 0),
SOC_DAPM_SINGLE("Left DAC2 Switch", ADAU1373_LHP_MIX, 4, 1, 0),
SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_LHP_MIX, 3, 1, 0),
SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_LHP_MIX, 2, 1, 0),
SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_LHP_MIX, 1, 1, 0),
SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_LHP_MIX, 0, 1, 0),
};
static const struct snd_kcontrol_new adau1373_right_hp_mixer_controls[] = {
SOC_DAPM_SINGLE("Right DAC1 Switch", ADAU1373_RHP_MIX, 5, 1, 0),
SOC_DAPM_SINGLE("Right DAC2 Switch", ADAU1373_RHP_MIX, 4, 1, 0),
SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_RHP_MIX, 3, 1, 0),
SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_RHP_MIX, 2, 1, 0),
SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_RHP_MIX, 1, 1, 0),
SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_RHP_MIX, 0, 1, 0),
};
#define DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(_name, _reg) \
const struct snd_kcontrol_new _name[] = { \
SOC_DAPM_SINGLE("DMIC2 Swapped Switch", _reg, 6, 1, 0), \
SOC_DAPM_SINGLE("DMIC2 Switch", _reg, 5, 1, 0), \
SOC_DAPM_SINGLE("ADC/DMIC1 Swapped Switch", _reg, 4, 1, 0), \
SOC_DAPM_SINGLE("ADC/DMIC1 Switch", _reg, 3, 1, 0), \
SOC_DAPM_SINGLE("AIF3 Switch", _reg, 2, 1, 0), \
SOC_DAPM_SINGLE("AIF2 Switch", _reg, 1, 1, 0), \
SOC_DAPM_SINGLE("AIF1 Switch", _reg, 0, 1, 0), \
}
static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel1_mixer_controls,
ADAU1373_DIN_MIX_CTRL(0));
static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel2_mixer_controls,
ADAU1373_DIN_MIX_CTRL(1));
static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel3_mixer_controls,
ADAU1373_DIN_MIX_CTRL(2));
static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel4_mixer_controls,
ADAU1373_DIN_MIX_CTRL(3));
static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel5_mixer_controls,
ADAU1373_DIN_MIX_CTRL(4));
#define DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(_name, _reg) \
const struct snd_kcontrol_new _name[] = { \
SOC_DAPM_SINGLE("DSP Channel5 Switch", _reg, 4, 1, 0), \
SOC_DAPM_SINGLE("DSP Channel4 Switch", _reg, 3, 1, 0), \
SOC_DAPM_SINGLE("DSP Channel3 Switch", _reg, 2, 1, 0), \
SOC_DAPM_SINGLE("DSP Channel2 Switch", _reg, 1, 1, 0), \
SOC_DAPM_SINGLE("DSP Channel1 Switch", _reg, 0, 1, 0), \
}
static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif1_mixer_controls,
ADAU1373_DOUT_MIX_CTRL(0));
static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif2_mixer_controls,
ADAU1373_DOUT_MIX_CTRL(1));
static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif3_mixer_controls,
ADAU1373_DOUT_MIX_CTRL(2));
static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac1_mixer_controls,
ADAU1373_DOUT_MIX_CTRL(3));
static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac2_mixer_controls,
ADAU1373_DOUT_MIX_CTRL(4));
static const struct snd_soc_dapm_widget adau1373_dapm_widgets[] = {
/* Datasheet claims Left ADC is bit 6 and Right ADC is bit 7, but that
* doesn't seem to be the case. */
SND_SOC_DAPM_ADC("Left ADC", NULL, ADAU1373_PWDN_CTRL1, 7, 0),
SND_SOC_DAPM_ADC("Right ADC", NULL, ADAU1373_PWDN_CTRL1, 6, 0),
SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1373_DIGMICCTRL, 0, 0),
SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1373_DIGMICCTRL, 2, 0),
SND_SOC_DAPM_MUX("Decimator Mux", SND_SOC_NOPM, 0, 0,
&adau1373_decimator_mux),
SND_SOC_DAPM_SUPPLY("MICBIAS2", ADAU1373_PWDN_CTRL1, 5, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1373_PWDN_CTRL1, 4, 0, NULL, 0),
SND_SOC_DAPM_PGA("IN4PGA", ADAU1373_PWDN_CTRL1, 3, 0, NULL, 0),
SND_SOC_DAPM_PGA("IN3PGA", ADAU1373_PWDN_CTRL1, 2, 0, NULL, 0),
SND_SOC_DAPM_PGA("IN2PGA", ADAU1373_PWDN_CTRL1, 1, 0, NULL, 0),
SND_SOC_DAPM_PGA("IN1PGA", ADAU1373_PWDN_CTRL1, 0, 0, NULL, 0),
SND_SOC_DAPM_DAC("Left DAC2", NULL, ADAU1373_PWDN_CTRL2, 7, 0),
SND_SOC_DAPM_DAC("Right DAC2", NULL, ADAU1373_PWDN_CTRL2, 6, 0),
SND_SOC_DAPM_DAC("Left DAC1", NULL, ADAU1373_PWDN_CTRL2, 5, 0),
SND_SOC_DAPM_DAC("Right DAC1", NULL, ADAU1373_PWDN_CTRL2, 4, 0),
SOC_MIXER_ARRAY("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
adau1373_left_adc_mixer_controls),
SOC_MIXER_ARRAY("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
adau1373_right_adc_mixer_controls),
SOC_MIXER_ARRAY("Left Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 3, 0,
adau1373_left_line2_mixer_controls),
SOC_MIXER_ARRAY("Right Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 2, 0,
adau1373_right_line2_mixer_controls),
SOC_MIXER_ARRAY("Left Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 1, 0,
adau1373_left_line1_mixer_controls),
SOC_MIXER_ARRAY("Right Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 0, 0,
adau1373_right_line1_mixer_controls),
SOC_MIXER_ARRAY("Earpiece Mixer", ADAU1373_PWDN_CTRL3, 4, 0,
adau1373_ep_mixer_controls),
SOC_MIXER_ARRAY("Left Speaker Mixer", ADAU1373_PWDN_CTRL3, 3, 0,
adau1373_left_spk_mixer_controls),
SOC_MIXER_ARRAY("Right Speaker Mixer", ADAU1373_PWDN_CTRL3, 2, 0,
adau1373_right_spk_mixer_controls),
SOC_MIXER_ARRAY("Left Headphone Mixer", SND_SOC_NOPM, 0, 0,
adau1373_left_hp_mixer_controls),
SOC_MIXER_ARRAY("Right Headphone Mixer", SND_SOC_NOPM, 0, 0,
adau1373_right_hp_mixer_controls),
SND_SOC_DAPM_SUPPLY("Headphone Enable", ADAU1373_PWDN_CTRL3, 1, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF1 CLK", ADAU1373_SRC_DAI_CTRL(0), 0, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF2 CLK", ADAU1373_SRC_DAI_CTRL(1), 0, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF3 CLK", ADAU1373_SRC_DAI_CTRL(2), 0, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF1 IN SRC", ADAU1373_SRC_DAI_CTRL(0), 2, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF1 OUT SRC", ADAU1373_SRC_DAI_CTRL(0), 1, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF2 IN SRC", ADAU1373_SRC_DAI_CTRL(1), 2, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF2 OUT SRC", ADAU1373_SRC_DAI_CTRL(1), 1, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF3 IN SRC", ADAU1373_SRC_DAI_CTRL(2), 2, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("AIF3 OUT SRC", ADAU1373_SRC_DAI_CTRL(2), 1, 0,
NULL, 0),
SND_SOC_DAPM_AIF_IN("AIF1 IN", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIF1 OUT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("AIF2 IN", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIF2 OUT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("AIF3 IN", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIF3 OUT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0),
SOC_MIXER_ARRAY("DSP Channel1 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dsp_channel1_mixer_controls),
SOC_MIXER_ARRAY("DSP Channel2 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dsp_channel2_mixer_controls),
SOC_MIXER_ARRAY("DSP Channel3 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dsp_channel3_mixer_controls),
SOC_MIXER_ARRAY("DSP Channel4 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dsp_channel4_mixer_controls),
SOC_MIXER_ARRAY("DSP Channel5 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dsp_channel5_mixer_controls),
SOC_MIXER_ARRAY("AIF1 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_aif1_mixer_controls),
SOC_MIXER_ARRAY("AIF2 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_aif2_mixer_controls),
SOC_MIXER_ARRAY("AIF3 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_aif3_mixer_controls),
SOC_MIXER_ARRAY("DAC1 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dac1_mixer_controls),
SOC_MIXER_ARRAY("DAC2 Mixer", SND_SOC_NOPM, 0, 0,
adau1373_dac2_mixer_controls),
SND_SOC_DAPM_SUPPLY("DSP", ADAU1373_DIGEN, 4, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Recording Engine B", ADAU1373_DIGEN, 3, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Recording Engine A", ADAU1373_DIGEN, 2, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Playback Engine B", ADAU1373_DIGEN, 1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Playback Engine A", ADAU1373_DIGEN, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("PLL1", SND_SOC_NOPM, 0, 0, adau1373_pll_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("PLL2", SND_SOC_NOPM, 0, 0, adau1373_pll_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("SYSCLK1", ADAU1373_CLK_SRC_DIV(0), 7, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("SYSCLK2", ADAU1373_CLK_SRC_DIV(1), 7, 0, NULL, 0),
SND_SOC_DAPM_INPUT("AIN1L"),
SND_SOC_DAPM_INPUT("AIN1R"),
SND_SOC_DAPM_INPUT("AIN2L"),
SND_SOC_DAPM_INPUT("AIN2R"),
SND_SOC_DAPM_INPUT("AIN3L"),
SND_SOC_DAPM_INPUT("AIN3R"),
SND_SOC_DAPM_INPUT("AIN4L"),
SND_SOC_DAPM_INPUT("AIN4R"),
SND_SOC_DAPM_INPUT("DMIC1DAT"),
SND_SOC_DAPM_INPUT("DMIC2DAT"),
SND_SOC_DAPM_OUTPUT("LOUT1L"),
SND_SOC_DAPM_OUTPUT("LOUT1R"),
SND_SOC_DAPM_OUTPUT("LOUT2L"),
SND_SOC_DAPM_OUTPUT("LOUT2R"),
SND_SOC_DAPM_OUTPUT("HPL"),
SND_SOC_DAPM_OUTPUT("HPR"),
SND_SOC_DAPM_OUTPUT("SPKL"),
SND_SOC_DAPM_OUTPUT("SPKR"),
SND_SOC_DAPM_OUTPUT("EP"),
};
static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm);
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
unsigned int dai;
const char *clk;
dai = sink->name[3] - '1';
if (!adau1373->dais[dai].clock_provider)
return 0;
if (adau1373->dais[dai].clk_src == ADAU1373_CLK_SRC_PLL1)
clk = "SYSCLK1";
else
clk = "SYSCLK2";
return strcmp(source->name, clk) == 0;
}
static int adau1373_check_src(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm);
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
unsigned int dai;
dai = sink->name[3] - '1';
return adau1373->dais[dai].enable_src;
}
#define DSP_CHANNEL_MIXER_ROUTES(_sink) \
{ _sink, "DMIC2 Swapped Switch", "DMIC2" }, \
{ _sink, "DMIC2 Switch", "DMIC2" }, \
{ _sink, "ADC/DMIC1 Swapped Switch", "Decimator Mux" }, \
{ _sink, "ADC/DMIC1 Switch", "Decimator Mux" }, \
{ _sink, "AIF1 Switch", "AIF1 IN" }, \
{ _sink, "AIF2 Switch", "AIF2 IN" }, \
{ _sink, "AIF3 Switch", "AIF3 IN" }
#define DSP_OUTPUT_MIXER_ROUTES(_sink) \
{ _sink, "DSP Channel1 Switch", "DSP Channel1 Mixer" }, \
{ _sink, "DSP Channel2 Switch", "DSP Channel2 Mixer" }, \
{ _sink, "DSP Channel3 Switch", "DSP Channel3 Mixer" }, \
{ _sink, "DSP Channel4 Switch", "DSP Channel4 Mixer" }, \
{ _sink, "DSP Channel5 Switch", "DSP Channel5 Mixer" }
#define LEFT_OUTPUT_MIXER_ROUTES(_sink) \
{ _sink, "Right DAC2 Switch", "Right DAC2" }, \
{ _sink, "Left DAC2 Switch", "Left DAC2" }, \
{ _sink, "Right DAC1 Switch", "Right DAC1" }, \
{ _sink, "Left DAC1 Switch", "Left DAC1" }, \
{ _sink, "Input 1 Bypass Switch", "IN1PGA" }, \
{ _sink, "Input 2 Bypass Switch", "IN2PGA" }, \
{ _sink, "Input 3 Bypass Switch", "IN3PGA" }, \
{ _sink, "Input 4 Bypass Switch", "IN4PGA" }
#define RIGHT_OUTPUT_MIXER_ROUTES(_sink) \
{ _sink, "Right DAC2 Switch", "Right DAC2" }, \
{ _sink, "Left DAC2 Switch", "Left DAC2" }, \
{ _sink, "Right DAC1 Switch", "Right DAC1" }, \
{ _sink, "Left DAC1 Switch", "Left DAC1" }, \
{ _sink, "Input 1 Bypass Switch", "IN1PGA" }, \
{ _sink, "Input 2 Bypass Switch", "IN2PGA" }, \
{ _sink, "Input 3 Bypass Switch", "IN3PGA" }, \
{ _sink, "Input 4 Bypass Switch", "IN4PGA" }
static const struct snd_soc_dapm_route adau1373_dapm_routes[] = {
{ "Left ADC Mixer", "DAC1 Switch", "Left DAC1" },
{ "Left ADC Mixer", "Input 1 Switch", "IN1PGA" },
{ "Left ADC Mixer", "Input 2 Switch", "IN2PGA" },
{ "Left ADC Mixer", "Input 3 Switch", "IN3PGA" },
{ "Left ADC Mixer", "Input 4 Switch", "IN4PGA" },
{ "Right ADC Mixer", "DAC1 Switch", "Right DAC1" },
{ "Right ADC Mixer", "Input 1 Switch", "IN1PGA" },
{ "Right ADC Mixer", "Input 2 Switch", "IN2PGA" },
{ "Right ADC Mixer", "Input 3 Switch", "IN3PGA" },
{ "Right ADC Mixer", "Input 4 Switch", "IN4PGA" },
{ "Left ADC", NULL, "Left ADC Mixer" },
{ "Right ADC", NULL, "Right ADC Mixer" },
{ "Decimator Mux", "ADC", "Left ADC" },
{ "Decimator Mux", "ADC", "Right ADC" },
{ "Decimator Mux", "DMIC1", "DMIC1" },
DSP_CHANNEL_MIXER_ROUTES("DSP Channel1 Mixer"),
DSP_CHANNEL_MIXER_ROUTES("DSP Channel2 Mixer"),
DSP_CHANNEL_MIXER_ROUTES("DSP Channel3 Mixer"),
DSP_CHANNEL_MIXER_ROUTES("DSP Channel4 Mixer"),
DSP_CHANNEL_MIXER_ROUTES("DSP Channel5 Mixer"),
DSP_OUTPUT_MIXER_ROUTES("AIF1 Mixer"),
DSP_OUTPUT_MIXER_ROUTES("AIF2 Mixer"),
DSP_OUTPUT_MIXER_ROUTES("AIF3 Mixer"),
DSP_OUTPUT_MIXER_ROUTES("DAC1 Mixer"),
DSP_OUTPUT_MIXER_ROUTES("DAC2 Mixer"),
{ "AIF1 OUT", NULL, "AIF1 Mixer" },
{ "AIF2 OUT", NULL, "AIF2 Mixer" },
{ "AIF3 OUT", NULL, "AIF3 Mixer" },
{ "Left DAC1", NULL, "DAC1 Mixer" },
{ "Right DAC1", NULL, "DAC1 Mixer" },
{ "Left DAC2", NULL, "DAC2 Mixer" },
{ "Right DAC2", NULL, "DAC2 Mixer" },
LEFT_OUTPUT_MIXER_ROUTES("Left Lineout1 Mixer"),
RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout1 Mixer"),
LEFT_OUTPUT_MIXER_ROUTES("Left Lineout2 Mixer"),
RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout2 Mixer"),
LEFT_OUTPUT_MIXER_ROUTES("Left Speaker Mixer"),
RIGHT_OUTPUT_MIXER_ROUTES("Right Speaker Mixer"),
{ "Left Headphone Mixer", "Left DAC2 Switch", "Left DAC2" },
{ "Left Headphone Mixer", "Left DAC1 Switch", "Left DAC1" },
{ "Left Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" },
{ "Left Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" },
{ "Left Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" },
{ "Left Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" },
{ "Right Headphone Mixer", "Right DAC2 Switch", "Right DAC2" },
{ "Right Headphone Mixer", "Right DAC1 Switch", "Right DAC1" },
{ "Right Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" },
{ "Right Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" },
{ "Right Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" },
{ "Right Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" },
{ "Left Headphone Mixer", NULL, "Headphone Enable" },
{ "Right Headphone Mixer", NULL, "Headphone Enable" },
{ "Earpiece Mixer", "Right DAC2 Switch", "Right DAC2" },
{ "Earpiece Mixer", "Left DAC2 Switch", "Left DAC2" },
{ "Earpiece Mixer", "Right DAC1 Switch", "Right DAC1" },
{ "Earpiece Mixer", "Left DAC1 Switch", "Left DAC1" },
{ "Earpiece Mixer", "Input 1 Bypass Switch", "IN1PGA" },
{ "Earpiece Mixer", "Input 2 Bypass Switch", "IN2PGA" },
{ "Earpiece Mixer", "Input 3 Bypass Switch", "IN3PGA" },
{ "Earpiece Mixer", "Input 4 Bypass Switch", "IN4PGA" },
{ "LOUT1L", NULL, "Left Lineout1 Mixer" },
{ "LOUT1R", NULL, "Right Lineout1 Mixer" },
{ "LOUT2L", NULL, "Left Lineout2 Mixer" },
{ "LOUT2R", NULL, "Right Lineout2 Mixer" },
{ "SPKL", NULL, "Left Speaker Mixer" },
{ "SPKR", NULL, "Right Speaker Mixer" },
{ "HPL", NULL, "Left Headphone Mixer" },
{ "HPR", NULL, "Right Headphone Mixer" },
{ "EP", NULL, "Earpiece Mixer" },
{ "IN1PGA", NULL, "AIN1L" },
{ "IN2PGA", NULL, "AIN2L" },
{ "IN3PGA", NULL, "AIN3L" },
{ "IN4PGA", NULL, "AIN4L" },
{ "IN1PGA", NULL, "AIN1R" },
{ "IN2PGA", NULL, "AIN2R" },
{ "IN3PGA", NULL, "AIN3R" },
{ "IN4PGA", NULL, "AIN4R" },
{ "SYSCLK1", NULL, "PLL1" },
{ "SYSCLK2", NULL, "PLL2" },
{ "Left DAC1", NULL, "SYSCLK1" },
{ "Right DAC1", NULL, "SYSCLK1" },
{ "Left DAC2", NULL, "SYSCLK1" },
{ "Right DAC2", NULL, "SYSCLK1" },
{ "Left ADC", NULL, "SYSCLK1" },
{ "Right ADC", NULL, "SYSCLK1" },
{ "DSP", NULL, "SYSCLK1" },
{ "AIF1 Mixer", NULL, "DSP" },
{ "AIF2 Mixer", NULL, "DSP" },
{ "AIF3 Mixer", NULL, "DSP" },
{ "DAC1 Mixer", NULL, "DSP" },
{ "DAC2 Mixer", NULL, "DSP" },
{ "DAC1 Mixer", NULL, "Playback Engine A" },
{ "DAC2 Mixer", NULL, "Playback Engine B" },
{ "Left ADC Mixer", NULL, "Recording Engine A" },
{ "Right ADC Mixer", NULL, "Recording Engine A" },
{ "AIF1 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk },
{ "AIF2 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk },
{ "AIF3 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk },
{ "AIF1 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk },
{ "AIF2 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk },
{ "AIF3 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk },
{ "AIF1 IN", NULL, "AIF1 CLK" },
{ "AIF1 OUT", NULL, "AIF1 CLK" },
{ "AIF2 IN", NULL, "AIF2 CLK" },
{ "AIF2 OUT", NULL, "AIF2 CLK" },
{ "AIF3 IN", NULL, "AIF3 CLK" },
{ "AIF3 OUT", NULL, "AIF3 CLK" },
{ "AIF1 IN", NULL, "AIF1 IN SRC", adau1373_check_src },
{ "AIF1 OUT", NULL, "AIF1 OUT SRC", adau1373_check_src },
{ "AIF2 IN", NULL, "AIF2 IN SRC", adau1373_check_src },
{ "AIF2 OUT", NULL, "AIF2 OUT SRC", adau1373_check_src },
{ "AIF3 IN", NULL, "AIF3 IN SRC", adau1373_check_src },
{ "AIF3 OUT", NULL, "AIF3 OUT SRC", adau1373_check_src },
{ "DMIC1", NULL, "DMIC1DAT" },
{ "DMIC1", NULL, "SYSCLK1" },
{ "DMIC1", NULL, "Recording Engine A" },
{ "DMIC2", NULL, "DMIC2DAT" },
{ "DMIC2", NULL, "SYSCLK1" },
{ "DMIC2", NULL, "Recording Engine B" },
};
static int adau1373_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id];
unsigned int div;
unsigned int freq;
unsigned int ctrl;
freq = adau1373_dai->sysclk;
if (freq % params_rate(params) != 0)
return -EINVAL;
switch (freq / params_rate(params)) {
case 1024: /* sysclk / 256 */
div = 0;
break;
case 1536: /* 2/3 sysclk / 256 */
div = 1;
break;
case 2048: /* 1/2 sysclk / 256 */
div = 2;
break;
case 3072: /* 1/3 sysclk / 256 */
div = 3;
break;
case 4096: /* 1/4 sysclk / 256 */
div = 4;
break;
case 6144: /* 1/6 sysclk / 256 */
div = 5;
break;
case 5632: /* 2/11 sysclk / 256 */
div = 6;
break;
default:
return -EINVAL;
}
adau1373_dai->enable_src = (div != 0);
regmap_update_bits(adau1373->regmap, ADAU1373_BCLKDIV(dai->id),
ADAU1373_BCLKDIV_SR_MASK | ADAU1373_BCLKDIV_BCLK_MASK,
(div << 2) | ADAU1373_BCLKDIV_64);
switch (params_width(params)) {
case 16:
ctrl = ADAU1373_DAI_WLEN_16;
break;
case 20:
ctrl = ADAU1373_DAI_WLEN_20;
break;
case 24:
ctrl = ADAU1373_DAI_WLEN_24;
break;
case 32:
ctrl = ADAU1373_DAI_WLEN_32;
break;
default:
return -EINVAL;
}
return regmap_update_bits(adau1373->regmap, ADAU1373_DAI(dai->id),
ADAU1373_DAI_WLEN_MASK, ctrl);
}
static int adau1373_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id];
unsigned int ctrl;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
ctrl = ADAU1373_DAI_MASTER;
adau1373_dai->clock_provider = true;
break;
case SND_SOC_DAIFMT_CBC_CFC:
ctrl = 0;
adau1373_dai->clock_provider = false;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
ctrl |= ADAU1373_DAI_FORMAT_I2S;
break;
case SND_SOC_DAIFMT_LEFT_J:
ctrl |= ADAU1373_DAI_FORMAT_LEFT_J;
break;
case SND_SOC_DAIFMT_RIGHT_J:
ctrl |= ADAU1373_DAI_FORMAT_RIGHT_J;
break;
case SND_SOC_DAIFMT_DSP_B:
ctrl |= ADAU1373_DAI_FORMAT_DSP;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_NF:
ctrl |= ADAU1373_DAI_INVERT_BCLK;
break;
case SND_SOC_DAIFMT_NB_IF:
ctrl |= ADAU1373_DAI_INVERT_LRCLK;
break;
case SND_SOC_DAIFMT_IB_IF:
ctrl |= ADAU1373_DAI_INVERT_LRCLK | ADAU1373_DAI_INVERT_BCLK;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1373->regmap, ADAU1373_DAI(dai->id),
~ADAU1373_DAI_WLEN_MASK, ctrl);
return 0;
}
static int adau1373_set_dai_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(dai->component);
struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id];
switch (clk_id) {
case ADAU1373_CLK_SRC_PLL1:
case ADAU1373_CLK_SRC_PLL2:
break;
default:
return -EINVAL;
}
adau1373_dai->sysclk = freq;
adau1373_dai->clk_src = clk_id;
regmap_update_bits(adau1373->regmap, ADAU1373_BCLKDIV(dai->id),
ADAU1373_BCLKDIV_SOURCE, clk_id << 5);
return 0;
}
static const struct snd_soc_dai_ops adau1373_dai_ops = {
.hw_params = adau1373_hw_params,
.set_sysclk = adau1373_set_dai_sysclk,
.set_fmt = adau1373_set_dai_fmt,
};
#define ADAU1373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver adau1373_dai_driver[] = {
{
.id = 0,
.name = "adau1373-aif1",
.playback = {
.stream_name = "AIF1 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ADAU1373_FORMATS,
},
.capture = {
.stream_name = "AIF1 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ADAU1373_FORMATS,
},
.ops = &adau1373_dai_ops,
.symmetric_rate = 1,
},
{
.id = 1,
.name = "adau1373-aif2",
.playback = {
.stream_name = "AIF2 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ADAU1373_FORMATS,
},
.capture = {
.stream_name = "AIF2 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ADAU1373_FORMATS,
},
.ops = &adau1373_dai_ops,
.symmetric_rate = 1,
},
{
.id = 2,
.name = "adau1373-aif3",
.playback = {
.stream_name = "AIF3 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ADAU1373_FORMATS,
},
.capture = {
.stream_name = "AIF3 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ADAU1373_FORMATS,
},
.ops = &adau1373_dai_ops,
.symmetric_rate = 1,
},
};
static int adau1373_set_pll(struct snd_soc_component *component, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out)
{
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
unsigned int dpll_div = 0;
uint8_t pll_regs[5];
int ret;
switch (pll_id) {
case ADAU1373_PLL1:
case ADAU1373_PLL2:
break;
default:
return -EINVAL;
}
switch (source) {
case ADAU1373_PLL_SRC_BCLK1:
case ADAU1373_PLL_SRC_BCLK2:
case ADAU1373_PLL_SRC_BCLK3:
case ADAU1373_PLL_SRC_LRCLK1:
case ADAU1373_PLL_SRC_LRCLK2:
case ADAU1373_PLL_SRC_LRCLK3:
case ADAU1373_PLL_SRC_MCLK1:
case ADAU1373_PLL_SRC_MCLK2:
case ADAU1373_PLL_SRC_GPIO1:
case ADAU1373_PLL_SRC_GPIO2:
case ADAU1373_PLL_SRC_GPIO3:
case ADAU1373_PLL_SRC_GPIO4:
break;
default:
return -EINVAL;
}
if (freq_in < 7813 || freq_in > 27000000)
return -EINVAL;
if (freq_out < 45158000 || freq_out > 49152000)
return -EINVAL;
/* APLL input needs to be >= 8Mhz, so in case freq_in is less we use the
* DPLL to get it there. DPLL_out = (DPLL_in / div) * 1024 */
while (freq_in < 8000000) {
freq_in *= 2;
dpll_div++;
}
ret = adau_calc_pll_cfg(freq_in, freq_out, pll_regs);
if (ret)
return -EINVAL;
if (dpll_div) {
dpll_div = 11 - dpll_div;
regmap_update_bits(adau1373->regmap, ADAU1373_PLL_CTRL6(pll_id),
ADAU1373_PLL_CTRL6_DPLL_BYPASS, 0);
} else {
regmap_update_bits(adau1373->regmap, ADAU1373_PLL_CTRL6(pll_id),
ADAU1373_PLL_CTRL6_DPLL_BYPASS,
ADAU1373_PLL_CTRL6_DPLL_BYPASS);
}
regmap_write(adau1373->regmap, ADAU1373_DPLL_CTRL(pll_id),
(source << 4) | dpll_div);
regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), pll_regs[0]);
regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), pll_regs[1]);
regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), pll_regs[2]);
regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), pll_regs[3]);
regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), pll_regs[4]);
/* Set sysclk to pll_rate / 4 */
regmap_update_bits(adau1373->regmap, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09);
return 0;
}
static void adau1373_load_drc_settings(struct adau1373 *adau1373,
unsigned int nr, uint8_t *drc)
{
unsigned int i;
for (i = 0; i < ADAU1373_DRC_SIZE; ++i)
regmap_write(adau1373->regmap, ADAU1373_DRC(nr) + i, drc[i]);
}
static bool adau1373_valid_micbias(enum adau1373_micbias_voltage micbias)
{
switch (micbias) {
case ADAU1373_MICBIAS_2_9V:
case ADAU1373_MICBIAS_2_2V:
case ADAU1373_MICBIAS_2_6V:
case ADAU1373_MICBIAS_1_8V:
return true;
default:
break;
}
return false;
}
static int adau1373_probe(struct snd_soc_component *component)
{
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
struct adau1373_platform_data *pdata = component->dev->platform_data;
bool lineout_differential = false;
unsigned int val;
int i;
if (pdata) {
if (pdata->num_drc > ARRAY_SIZE(pdata->drc_setting))
return -EINVAL;
if (!adau1373_valid_micbias(pdata->micbias1) ||
!adau1373_valid_micbias(pdata->micbias2))
return -EINVAL;
for (i = 0; i < pdata->num_drc; ++i) {
adau1373_load_drc_settings(adau1373, i,
pdata->drc_setting[i]);
}
snd_soc_add_component_controls(component, adau1373_drc_controls,
pdata->num_drc);
val = 0;
for (i = 0; i < 4; ++i) {
if (pdata->input_differential[i])
val |= BIT(i);
}
regmap_write(adau1373->regmap, ADAU1373_INPUT_MODE, val);
val = 0;
if (pdata->lineout_differential)
val |= ADAU1373_OUTPUT_CTRL_LDIFF;
if (pdata->lineout_ground_sense)
val |= ADAU1373_OUTPUT_CTRL_LNFBEN;
regmap_write(adau1373->regmap, ADAU1373_OUTPUT_CTRL, val);
lineout_differential = pdata->lineout_differential;
regmap_write(adau1373->regmap, ADAU1373_EP_CTRL,
(pdata->micbias1 << ADAU1373_EP_CTRL_MICBIAS1_OFFSET) |
(pdata->micbias2 << ADAU1373_EP_CTRL_MICBIAS2_OFFSET));
}
if (!lineout_differential) {
snd_soc_add_component_controls(component, adau1373_lineout2_controls,
ARRAY_SIZE(adau1373_lineout2_controls));
}
regmap_write(adau1373->regmap, ADAU1373_ADC_CTRL,
ADAU1373_ADC_CTRL_RESET_FORCE | ADAU1373_ADC_CTRL_PEAK_DETECT);
return 0;
}
static int adau1373_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
regmap_update_bits(adau1373->regmap, ADAU1373_PWDN_CTRL3,
ADAU1373_PWDN_CTRL3_PWR_EN, ADAU1373_PWDN_CTRL3_PWR_EN);
break;
case SND_SOC_BIAS_OFF:
regmap_update_bits(adau1373->regmap, ADAU1373_PWDN_CTRL3,
ADAU1373_PWDN_CTRL3_PWR_EN, 0);
break;
}
return 0;
}
static int adau1373_resume(struct snd_soc_component *component)
{
struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component);
regcache_sync(adau1373->regmap);
return 0;
}
static bool adau1373_register_volatile(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU1373_SOFT_RESET:
case ADAU1373_ADC_DAC_STATUS:
return true;
default:
return false;
}
}
static const struct regmap_config adau1373_regmap_config = {
.val_bits = 8,
.reg_bits = 8,
.volatile_reg = adau1373_register_volatile,
.max_register = ADAU1373_SOFT_RESET,
.cache_type = REGCACHE_RBTREE,
.reg_defaults = adau1373_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(adau1373_reg_defaults),
};
static const struct snd_soc_component_driver adau1373_component_driver = {
.probe = adau1373_probe,
.resume = adau1373_resume,
.set_bias_level = adau1373_set_bias_level,
.set_pll = adau1373_set_pll,
.controls = adau1373_controls,
.num_controls = ARRAY_SIZE(adau1373_controls),
.dapm_widgets = adau1373_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1373_dapm_widgets),
.dapm_routes = adau1373_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1373_dapm_routes),
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int adau1373_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct adau1373 *adau1373;
int ret;
adau1373 = devm_kzalloc(&client->dev, sizeof(*adau1373), GFP_KERNEL);
if (!adau1373)
return -ENOMEM;
adau1373->regmap = devm_regmap_init_i2c(client,
&adau1373_regmap_config);
if (IS_ERR(adau1373->regmap))
return PTR_ERR(adau1373->regmap);
regmap_write(adau1373->regmap, ADAU1373_SOFT_RESET, 0x00);
dev_set_drvdata(&client->dev, adau1373);
ret = devm_snd_soc_register_component(&client->dev,
&adau1373_component_driver,
adau1373_dai_driver, ARRAY_SIZE(adau1373_dai_driver));
return ret;
}
static const struct i2c_device_id adau1373_i2c_id[] = {
{ "adau1373", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1373_i2c_id);
static struct i2c_driver adau1373_i2c_driver = {
.driver = {
.name = "adau1373",
},
.probe = adau1373_i2c_probe,
.id_table = adau1373_i2c_id,
};
module_i2c_driver(adau1373_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1373 driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for ADAU1701 SigmaDSP processor
*
* Copyright 2011 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
* based on an inital version by Cliff Cai <cliff.cai@analog.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <asm/unaligned.h>
#include "sigmadsp.h"
#include "adau1701.h"
#define ADAU1701_SAFELOAD_DATA(i) (0x0810 + (i))
#define ADAU1701_SAFELOAD_ADDR(i) (0x0815 + (i))
#define ADAU1701_DSPCTRL 0x081c
#define ADAU1701_SEROCTL 0x081e
#define ADAU1701_SERICTL 0x081f
#define ADAU1701_AUXNPOW 0x0822
#define ADAU1701_PINCONF_0 0x0820
#define ADAU1701_PINCONF_1 0x0821
#define ADAU1701_AUXNPOW 0x0822
#define ADAU1701_OSCIPOW 0x0826
#define ADAU1701_DACSET 0x0827
#define ADAU1701_MAX_REGISTER 0x0828
#define ADAU1701_DSPCTRL_CR (1 << 2)
#define ADAU1701_DSPCTRL_DAM (1 << 3)
#define ADAU1701_DSPCTRL_ADM (1 << 4)
#define ADAU1701_DSPCTRL_IST (1 << 5)
#define ADAU1701_DSPCTRL_SR_48 0x00
#define ADAU1701_DSPCTRL_SR_96 0x01
#define ADAU1701_DSPCTRL_SR_192 0x02
#define ADAU1701_DSPCTRL_SR_MASK 0x03
#define ADAU1701_SEROCTL_INV_LRCLK 0x2000
#define ADAU1701_SEROCTL_INV_BCLK 0x1000
#define ADAU1701_SEROCTL_MASTER 0x0800
#define ADAU1701_SEROCTL_OBF16 0x0000
#define ADAU1701_SEROCTL_OBF8 0x0200
#define ADAU1701_SEROCTL_OBF4 0x0400
#define ADAU1701_SEROCTL_OBF2 0x0600
#define ADAU1701_SEROCTL_OBF_MASK 0x0600
#define ADAU1701_SEROCTL_OLF1024 0x0000
#define ADAU1701_SEROCTL_OLF512 0x0080
#define ADAU1701_SEROCTL_OLF256 0x0100
#define ADAU1701_SEROCTL_OLF_MASK 0x0180
#define ADAU1701_SEROCTL_MSB_DEALY1 0x0000
#define ADAU1701_SEROCTL_MSB_DEALY0 0x0004
#define ADAU1701_SEROCTL_MSB_DEALY8 0x0008
#define ADAU1701_SEROCTL_MSB_DEALY12 0x000c
#define ADAU1701_SEROCTL_MSB_DEALY16 0x0010
#define ADAU1701_SEROCTL_MSB_DEALY_MASK 0x001c
#define ADAU1701_SEROCTL_WORD_LEN_24 0x0000
#define ADAU1701_SEROCTL_WORD_LEN_20 0x0001
#define ADAU1701_SEROCTL_WORD_LEN_16 0x0002
#define ADAU1701_SEROCTL_WORD_LEN_MASK 0x0003
#define ADAU1701_AUXNPOW_VBPD 0x40
#define ADAU1701_AUXNPOW_VRPD 0x20
#define ADAU1701_SERICTL_I2S 0
#define ADAU1701_SERICTL_LEFTJ 1
#define ADAU1701_SERICTL_TDM 2
#define ADAU1701_SERICTL_RIGHTJ_24 3
#define ADAU1701_SERICTL_RIGHTJ_20 4
#define ADAU1701_SERICTL_RIGHTJ_18 5
#define ADAU1701_SERICTL_RIGHTJ_16 6
#define ADAU1701_SERICTL_MODE_MASK 7
#define ADAU1701_SERICTL_INV_BCLK BIT(3)
#define ADAU1701_SERICTL_INV_LRCLK BIT(4)
#define ADAU1701_OSCIPOW_OPD 0x04
#define ADAU1701_DACSET_DACINIT 1
#define ADAU1707_CLKDIV_UNSET (-1U)
#define ADAU1701_FIRMWARE "adau1701.bin"
static const char * const supply_names[] = {
"dvdd", "avdd"
};
struct adau1701 {
struct gpio_desc *gpio_nreset;
struct gpio_descs *gpio_pll_mode;
unsigned int dai_fmt;
unsigned int pll_clkdiv;
unsigned int sysclk;
struct regmap *regmap;
struct i2c_client *client;
u8 pin_config[12];
struct sigmadsp *sigmadsp;
struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
};
static const struct snd_kcontrol_new adau1701_controls[] = {
SOC_SINGLE("Master Capture Switch", ADAU1701_DSPCTRL, 4, 1, 0),
};
static const struct snd_soc_dapm_widget adau1701_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC0", "Playback", ADAU1701_AUXNPOW, 3, 1),
SND_SOC_DAPM_DAC("DAC1", "Playback", ADAU1701_AUXNPOW, 2, 1),
SND_SOC_DAPM_DAC("DAC2", "Playback", ADAU1701_AUXNPOW, 1, 1),
SND_SOC_DAPM_DAC("DAC3", "Playback", ADAU1701_AUXNPOW, 0, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", ADAU1701_AUXNPOW, 7, 1),
SND_SOC_DAPM_OUTPUT("OUT0"),
SND_SOC_DAPM_OUTPUT("OUT1"),
SND_SOC_DAPM_OUTPUT("OUT2"),
SND_SOC_DAPM_OUTPUT("OUT3"),
SND_SOC_DAPM_INPUT("IN0"),
SND_SOC_DAPM_INPUT("IN1"),
};
static const struct snd_soc_dapm_route adau1701_dapm_routes[] = {
{ "OUT0", NULL, "DAC0" },
{ "OUT1", NULL, "DAC1" },
{ "OUT2", NULL, "DAC2" },
{ "OUT3", NULL, "DAC3" },
{ "ADC", NULL, "IN0" },
{ "ADC", NULL, "IN1" },
};
static unsigned int adau1701_register_size(struct device *dev,
unsigned int reg)
{
switch (reg) {
case ADAU1701_PINCONF_0:
case ADAU1701_PINCONF_1:
return 3;
case ADAU1701_DSPCTRL:
case ADAU1701_SEROCTL:
case ADAU1701_AUXNPOW:
case ADAU1701_OSCIPOW:
case ADAU1701_DACSET:
return 2;
case ADAU1701_SERICTL:
return 1;
}
dev_err(dev, "Unsupported register address: %d\n", reg);
return 0;
}
static bool adau1701_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU1701_DACSET:
case ADAU1701_DSPCTRL:
return true;
default:
return false;
}
}
static int adau1701_reg_write(void *context, unsigned int reg,
unsigned int value)
{
struct i2c_client *client = context;
unsigned int i;
unsigned int size;
uint8_t buf[5];
int ret;
size = adau1701_register_size(&client->dev, reg);
if (size == 0)
return -EINVAL;
buf[0] = reg >> 8;
buf[1] = reg & 0xff;
for (i = size + 1; i >= 2; --i) {
buf[i] = value;
value >>= 8;
}
ret = i2c_master_send(client, buf, size + 2);
if (ret == size + 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
static int adau1701_reg_read(void *context, unsigned int reg,
unsigned int *value)
{
int ret;
unsigned int i;
unsigned int size;
uint8_t send_buf[2], recv_buf[3];
struct i2c_client *client = context;
struct i2c_msg msgs[2];
size = adau1701_register_size(&client->dev, reg);
if (size == 0)
return -EINVAL;
send_buf[0] = reg >> 8;
send_buf[1] = reg & 0xff;
msgs[0].addr = client->addr;
msgs[0].len = sizeof(send_buf);
msgs[0].buf = send_buf;
msgs[0].flags = 0;
msgs[1].addr = client->addr;
msgs[1].len = size;
msgs[1].buf = recv_buf;
msgs[1].flags = I2C_M_RD;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
return ret;
else if (ret != ARRAY_SIZE(msgs))
return -EIO;
*value = 0;
for (i = 0; i < size; i++) {
*value <<= 8;
*value |= recv_buf[i];
}
return 0;
}
static int adau1701_safeload(struct sigmadsp *sigmadsp, unsigned int addr,
const uint8_t bytes[], size_t len)
{
struct i2c_client *client = to_i2c_client(sigmadsp->dev);
struct adau1701 *adau1701 = i2c_get_clientdata(client);
unsigned int val;
unsigned int i;
uint8_t buf[10];
int ret;
ret = regmap_read(adau1701->regmap, ADAU1701_DSPCTRL, &val);
if (ret)
return ret;
if (val & ADAU1701_DSPCTRL_IST)
msleep(50);
for (i = 0; i < len / 4; i++) {
put_unaligned_le16(ADAU1701_SAFELOAD_DATA(i), buf);
buf[2] = 0x00;
memcpy(buf + 3, bytes + i * 4, 4);
ret = i2c_master_send(client, buf, 7);
if (ret < 0)
return ret;
else if (ret != 7)
return -EIO;
put_unaligned_le16(ADAU1701_SAFELOAD_ADDR(i), buf);
put_unaligned_le16(addr + i, buf + 2);
ret = i2c_master_send(client, buf, 4);
if (ret < 0)
return ret;
else if (ret != 4)
return -EIO;
}
return regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL,
ADAU1701_DSPCTRL_IST, ADAU1701_DSPCTRL_IST);
}
static const struct sigmadsp_ops adau1701_sigmadsp_ops = {
.safeload = adau1701_safeload,
};
static int adau1701_reset(struct snd_soc_component *component, unsigned int clkdiv,
unsigned int rate)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
int ret;
DECLARE_BITMAP(values, 2);
sigmadsp_reset(adau1701->sigmadsp);
if (clkdiv != ADAU1707_CLKDIV_UNSET && adau1701->gpio_pll_mode) {
switch (clkdiv) {
case 64:
__assign_bit(0, values, 0);
__assign_bit(1, values, 0);
break;
case 256:
__assign_bit(0, values, 0);
__assign_bit(1, values, 1);
break;
case 384:
__assign_bit(0, values, 1);
__assign_bit(1, values, 0);
break;
case 0: /* fallback */
case 512:
__assign_bit(0, values, 1);
__assign_bit(1, values, 1);
break;
}
gpiod_set_array_value_cansleep(adau1701->gpio_pll_mode->ndescs,
adau1701->gpio_pll_mode->desc, adau1701->gpio_pll_mode->info,
values);
}
adau1701->pll_clkdiv = clkdiv;
if (adau1701->gpio_nreset) {
gpiod_set_value_cansleep(adau1701->gpio_nreset, 0);
/* minimum reset time is 20ns */
udelay(1);
gpiod_set_value_cansleep(adau1701->gpio_nreset, 1);
/* power-up time may be as long as 85ms */
mdelay(85);
}
/*
* Postpone the firmware download to a point in time when we
* know the correct PLL setup
*/
if (clkdiv != ADAU1707_CLKDIV_UNSET) {
ret = sigmadsp_setup(adau1701->sigmadsp, rate);
if (ret) {
dev_warn(component->dev, "Failed to load firmware\n");
return ret;
}
}
regmap_write(adau1701->regmap, ADAU1701_DACSET, ADAU1701_DACSET_DACINIT);
regmap_write(adau1701->regmap, ADAU1701_DSPCTRL, ADAU1701_DSPCTRL_CR);
regcache_mark_dirty(adau1701->regmap);
regcache_sync(adau1701->regmap);
return 0;
}
static int adau1701_set_capture_pcm_format(struct snd_soc_component *component,
struct snd_pcm_hw_params *params)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
unsigned int mask = ADAU1701_SEROCTL_WORD_LEN_MASK;
unsigned int val;
switch (params_width(params)) {
case 16:
val = ADAU1701_SEROCTL_WORD_LEN_16;
break;
case 20:
val = ADAU1701_SEROCTL_WORD_LEN_20;
break;
case 24:
val = ADAU1701_SEROCTL_WORD_LEN_24;
break;
default:
return -EINVAL;
}
if (adau1701->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) {
switch (params_width(params)) {
case 16:
val |= ADAU1701_SEROCTL_MSB_DEALY16;
break;
case 20:
val |= ADAU1701_SEROCTL_MSB_DEALY12;
break;
case 24:
val |= ADAU1701_SEROCTL_MSB_DEALY8;
break;
}
mask |= ADAU1701_SEROCTL_MSB_DEALY_MASK;
}
regmap_update_bits(adau1701->regmap, ADAU1701_SEROCTL, mask, val);
return 0;
}
static int adau1701_set_playback_pcm_format(struct snd_soc_component *component,
struct snd_pcm_hw_params *params)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
unsigned int val;
if (adau1701->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
return 0;
switch (params_width(params)) {
case 16:
val = ADAU1701_SERICTL_RIGHTJ_16;
break;
case 20:
val = ADAU1701_SERICTL_RIGHTJ_20;
break;
case 24:
val = ADAU1701_SERICTL_RIGHTJ_24;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1701->regmap, ADAU1701_SERICTL,
ADAU1701_SERICTL_MODE_MASK, val);
return 0;
}
static int adau1701_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
unsigned int clkdiv = adau1701->sysclk / params_rate(params);
unsigned int val;
int ret;
/*
* If the mclk/lrclk ratio changes, the chip needs updated PLL
* mode GPIO settings, and a full reset cycle, including a new
* firmware upload.
*/
if (clkdiv != adau1701->pll_clkdiv) {
ret = adau1701_reset(component, clkdiv, params_rate(params));
if (ret < 0)
return ret;
}
switch (params_rate(params)) {
case 192000:
val = ADAU1701_DSPCTRL_SR_192;
break;
case 96000:
val = ADAU1701_DSPCTRL_SR_96;
break;
case 48000:
val = ADAU1701_DSPCTRL_SR_48;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL,
ADAU1701_DSPCTRL_SR_MASK, val);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return adau1701_set_playback_pcm_format(component, params);
else
return adau1701_set_capture_pcm_format(component, params);
}
static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_component *component = codec_dai->component;
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
unsigned int serictl = 0x00, seroctl = 0x00;
bool invert_lrclk;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
/* master, 64-bits per sample, 1 frame per sample */
seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16
| ADAU1701_SEROCTL_OLF1024;
break;
case SND_SOC_DAIFMT_CBC_CFC:
break;
default:
return -EINVAL;
}
/* clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
invert_lrclk = false;
break;
case SND_SOC_DAIFMT_NB_IF:
invert_lrclk = true;
break;
case SND_SOC_DAIFMT_IB_NF:
invert_lrclk = false;
serictl |= ADAU1701_SERICTL_INV_BCLK;
seroctl |= ADAU1701_SEROCTL_INV_BCLK;
break;
case SND_SOC_DAIFMT_IB_IF:
invert_lrclk = true;
serictl |= ADAU1701_SERICTL_INV_BCLK;
seroctl |= ADAU1701_SEROCTL_INV_BCLK;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
break;
case SND_SOC_DAIFMT_LEFT_J:
serictl |= ADAU1701_SERICTL_LEFTJ;
seroctl |= ADAU1701_SEROCTL_MSB_DEALY0;
invert_lrclk = !invert_lrclk;
break;
case SND_SOC_DAIFMT_RIGHT_J:
serictl |= ADAU1701_SERICTL_RIGHTJ_24;
seroctl |= ADAU1701_SEROCTL_MSB_DEALY8;
invert_lrclk = !invert_lrclk;
break;
default:
return -EINVAL;
}
if (invert_lrclk) {
seroctl |= ADAU1701_SEROCTL_INV_LRCLK;
serictl |= ADAU1701_SERICTL_INV_LRCLK;
}
adau1701->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
regmap_write(adau1701->regmap, ADAU1701_SERICTL, serictl);
regmap_update_bits(adau1701->regmap, ADAU1701_SEROCTL,
~ADAU1701_SEROCTL_WORD_LEN_MASK, seroctl);
return 0;
}
static int adau1701_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
unsigned int mask = ADAU1701_AUXNPOW_VBPD | ADAU1701_AUXNPOW_VRPD;
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
/* Enable VREF and VREF buffer */
regmap_update_bits(adau1701->regmap,
ADAU1701_AUXNPOW, mask, 0x00);
break;
case SND_SOC_BIAS_OFF:
/* Disable VREF and VREF buffer */
regmap_update_bits(adau1701->regmap,
ADAU1701_AUXNPOW, mask, mask);
break;
}
return 0;
}
static int adau1701_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
{
struct snd_soc_component *component = dai->component;
unsigned int mask = ADAU1701_DSPCTRL_DAM;
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
unsigned int val;
if (mute)
val = 0;
else
val = mask;
regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL, mask, val);
return 0;
}
static int adau1701_set_sysclk(struct snd_soc_component *component, int clk_id,
int source, unsigned int freq, int dir)
{
unsigned int val;
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
switch (clk_id) {
case ADAU1701_CLK_SRC_OSC:
val = 0x0;
break;
case ADAU1701_CLK_SRC_MCLK:
val = ADAU1701_OSCIPOW_OPD;
break;
default:
return -EINVAL;
}
regmap_update_bits(adau1701->regmap, ADAU1701_OSCIPOW,
ADAU1701_OSCIPOW_OPD, val);
adau1701->sysclk = freq;
return 0;
}
static int adau1701_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(dai->component);
return sigmadsp_restrict_params(adau1701->sigmadsp, substream);
}
#define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
SNDRV_PCM_RATE_192000)
#define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE)
static const struct snd_soc_dai_ops adau1701_dai_ops = {
.set_fmt = adau1701_set_dai_fmt,
.hw_params = adau1701_hw_params,
.mute_stream = adau1701_mute_stream,
.startup = adau1701_startup,
.no_capture_mute = 1,
};
static struct snd_soc_dai_driver adau1701_dai = {
.name = "adau1701",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = ADAU1701_RATES,
.formats = ADAU1701_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 8,
.rates = ADAU1701_RATES,
.formats = ADAU1701_FORMATS,
},
.ops = &adau1701_dai_ops,
.symmetric_rate = 1,
};
#ifdef CONFIG_OF
static const struct of_device_id adau1701_dt_ids[] = {
{ .compatible = "adi,adau1701", },
{ }
};
MODULE_DEVICE_TABLE(of, adau1701_dt_ids);
#endif
static int adau1701_probe(struct snd_soc_component *component)
{
int i, ret;
unsigned int val;
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
ret = sigmadsp_attach(adau1701->sigmadsp, component);
if (ret)
return ret;
ret = regulator_bulk_enable(ARRAY_SIZE(adau1701->supplies),
adau1701->supplies);
if (ret < 0) {
dev_err(component->dev, "Failed to enable regulators: %d\n", ret);
return ret;
}
/*
* Let the pll_clkdiv variable default to something that won't happen
* at runtime. That way, we can postpone the firmware download from
* adau1701_reset() to a point in time when we know the correct PLL
* mode parameters.
*/
adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET;
/* initalize with pre-configured pll mode settings */
ret = adau1701_reset(component, adau1701->pll_clkdiv, 0);
if (ret < 0)
goto exit_regulators_disable;
/* set up pin config */
val = 0;
for (i = 0; i < 6; i++)
val |= adau1701->pin_config[i] << (i * 4);
regmap_write(adau1701->regmap, ADAU1701_PINCONF_0, val);
val = 0;
for (i = 0; i < 6; i++)
val |= adau1701->pin_config[i + 6] << (i * 4);
regmap_write(adau1701->regmap, ADAU1701_PINCONF_1, val);
return 0;
exit_regulators_disable:
regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), adau1701->supplies);
return ret;
}
static void adau1701_remove(struct snd_soc_component *component)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
if (adau1701->gpio_nreset)
gpiod_set_value_cansleep(adau1701->gpio_nreset, 0);
regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), adau1701->supplies);
}
#ifdef CONFIG_PM
static int adau1701_suspend(struct snd_soc_component *component)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies),
adau1701->supplies);
return 0;
}
static int adau1701_resume(struct snd_soc_component *component)
{
struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component);
int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(adau1701->supplies),
adau1701->supplies);
if (ret < 0) {
dev_err(component->dev, "Failed to enable regulators: %d\n", ret);
return ret;
}
return adau1701_reset(component, adau1701->pll_clkdiv, 0);
}
#else
#define adau1701_resume NULL
#define adau1701_suspend NULL
#endif /* CONFIG_PM */
static const struct snd_soc_component_driver adau1701_component_drv = {
.probe = adau1701_probe,
.remove = adau1701_remove,
.resume = adau1701_resume,
.suspend = adau1701_suspend,
.set_bias_level = adau1701_set_bias_level,
.controls = adau1701_controls,
.num_controls = ARRAY_SIZE(adau1701_controls),
.dapm_widgets = adau1701_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1701_dapm_widgets),
.dapm_routes = adau1701_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1701_dapm_routes),
.set_sysclk = adau1701_set_sysclk,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static const struct regmap_config adau1701_regmap = {
.reg_bits = 16,
.val_bits = 32,
.max_register = ADAU1701_MAX_REGISTER,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = adau1701_volatile_reg,
.reg_write = adau1701_reg_write,
.reg_read = adau1701_reg_read,
};
static int adau1701_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct adau1701 *adau1701;
struct device *dev = &client->dev;
int ret, i;
adau1701 = devm_kzalloc(dev, sizeof(*adau1701), GFP_KERNEL);
if (!adau1701)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(supply_names); i++)
adau1701->supplies[i].supply = supply_names[i];
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(adau1701->supplies),
adau1701->supplies);
if (ret < 0) {
dev_err(dev, "Failed to get regulators: %d\n", ret);
return ret;
}
ret = regulator_bulk_enable(ARRAY_SIZE(adau1701->supplies),
adau1701->supplies);
if (ret < 0) {
dev_err(dev, "Failed to enable regulators: %d\n", ret);
return ret;
}
adau1701->client = client;
adau1701->regmap = devm_regmap_init(dev, NULL, client,
&adau1701_regmap);
if (IS_ERR(adau1701->regmap)) {
ret = PTR_ERR(adau1701->regmap);
goto exit_regulators_disable;
}
if (dev->of_node) {
of_property_read_u32(dev->of_node, "adi,pll-clkdiv",
&adau1701->pll_clkdiv);
of_property_read_u8_array(dev->of_node, "adi,pin-config",
adau1701->pin_config,
ARRAY_SIZE(adau1701->pin_config));
}
adau1701->gpio_nreset = devm_gpiod_get_optional(dev, "reset", GPIOD_IN);
if (IS_ERR(adau1701->gpio_nreset)) {
ret = PTR_ERR(adau1701->gpio_nreset);
goto exit_regulators_disable;
}
adau1701->gpio_pll_mode = devm_gpiod_get_array_optional(dev, "adi,pll-mode", GPIOD_OUT_LOW);
if (IS_ERR(adau1701->gpio_pll_mode)) {
ret = PTR_ERR(adau1701->gpio_pll_mode);
goto exit_regulators_disable;
}
i2c_set_clientdata(client, adau1701);
adau1701->sigmadsp = devm_sigmadsp_init_i2c(client,
&adau1701_sigmadsp_ops, ADAU1701_FIRMWARE);
if (IS_ERR(adau1701->sigmadsp)) {
ret = PTR_ERR(adau1701->sigmadsp);
goto exit_regulators_disable;
}
ret = devm_snd_soc_register_component(&client->dev,
&adau1701_component_drv,
&adau1701_dai, 1);
exit_regulators_disable:
regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), adau1701->supplies);
return ret;
}
static const struct i2c_device_id adau1701_i2c_id[] = {
{ "adau1401", 0 },
{ "adau1401a", 0 },
{ "adau1701", 0 },
{ "adau1702", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1701_i2c_id);
static struct i2c_driver adau1701_i2c_driver = {
.driver = {
.name = "adau1701",
.of_match_table = of_match_ptr(adau1701_dt_ids),
},
.probe = adau1701_i2c_probe,
.id_table = adau1701_i2c_id,
};
module_i2c_driver(adau1701_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1701 SigmaDSP driver");
MODULE_AUTHOR("Cliff Cai <cliff.cai@analog.com>");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for ADAU1361/ADAU1461/ADAU1761/ADAU1961 codec
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "adau1761.h"
static int adau1761_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct regmap_config config;
config = adau1761_regmap_config;
config.val_bits = 8;
config.reg_bits = 16;
return adau1761_probe(&client->dev,
devm_regmap_init_i2c(client, &config),
id->driver_data, NULL);
}
static int adau1761_i2c_remove(struct i2c_client *client)
{
adau17x1_remove(&client->dev);
return 0;
}
static const struct i2c_device_id adau1761_i2c_ids[] = {
{ "adau1361", ADAU1361 },
{ "adau1461", ADAU1761 },
{ "adau1761", ADAU1761 },
{ "adau1961", ADAU1361 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adau1761_i2c_ids);
#if defined(CONFIG_OF)
static const struct of_device_id adau1761_i2c_dt_ids[] = {
{ .compatible = "adi,adau1361", },
{ .compatible = "adi,adau1461", },
{ .compatible = "adi,adau1761", },
{ .compatible = "adi,adau1961", },
{ },
};
MODULE_DEVICE_TABLE(of, adau1761_i2c_dt_ids);
#endif
static struct i2c_driver adau1761_i2c_driver = {
.driver = {
.name = "adau1761",
.of_match_table = of_match_ptr(adau1761_i2c_dt_ids),
},
.probe = adau1761_i2c_probe,
.remove = adau1761_i2c_remove,
.id_table = adau1761_i2c_ids,
};
module_i2c_driver(adau1761_i2c_driver);
MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC I2C driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for ADAU1361/ADAU1461/ADAU1761/ADAU1961 codec
*
* Copyright 2014 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <sound/soc.h>
#include "adau1761.h"
static void adau1761_spi_switch_mode(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/*
* To get the device into SPI mode CLATCH has to be pulled low three
* times. Do this by issuing three dummy reads.
*/
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
spi_w8r8(spi, 0x00);
}
static int adau1761_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct regmap_config config;
if (!id)
return -EINVAL;
config = adau1761_regmap_config;
config.val_bits = 8;
config.reg_bits = 24;
config.read_flag_mask = 0x1;
return adau1761_probe(&spi->dev,
devm_regmap_init_spi(spi, &config),
id->driver_data, adau1761_spi_switch_mode);
}
static int adau1761_spi_remove(struct spi_device *spi)
{
adau17x1_remove(&spi->dev);
return 0;
}
static const struct spi_device_id adau1761_spi_id[] = {
{ "adau1361", ADAU1361 },
{ "adau1461", ADAU1761 },
{ "adau1761", ADAU1761 },
{ "adau1961", ADAU1361 },
{ }
};
MODULE_DEVICE_TABLE(spi, adau1761_spi_id);
#if defined(CONFIG_OF)
static const struct of_device_id adau1761_spi_dt_ids[] = {
{ .compatible = "adi,adau1361", },
{ .compatible = "adi,adau1461", },
{ .compatible = "adi,adau1761", },
{ .compatible = "adi,adau1961", },
{ },
};
MODULE_DEVICE_TABLE(of, adau1761_spi_dt_ids);
#endif
static struct spi_driver adau1761_spi_driver = {
.driver = {
.name = "adau1761",
.of_match_table = of_match_ptr(adau1761_spi_dt_ids),
},
.probe = adau1761_spi_probe,
.remove = adau1761_spi_remove,
.id_table = adau1761_spi_id,
};
module_spi_driver(adau1761_spi_driver);
MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC SPI driver");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");
This file has been truncated, but you can view the full file.
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for ADAU1361/ADAU1461/ADAU1761/ADAU1961 codec
*
* Copyright 2011-2013 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/platform_data/adau17x1.h>
#include "adau17x1.h"
#include "adau1761.h"
#define ADAU1761_DIGMIC_JACKDETECT 0x4008
#define ADAU1761_REC_MIXER_LEFT0 0x400a
#define ADAU1761_REC_MIXER_LEFT1 0x400b
#define ADAU1761_REC_MIXER_RIGHT0 0x400c
#define ADAU1761_REC_MIXER_RIGHT1 0x400d
#define ADAU1761_LEFT_DIFF_INPUT_VOL 0x400e
#define ADAU1761_RIGHT_DIFF_INPUT_VOL 0x400f
#define ADAU1761_ALC_CTRL0 0x4011
#define ADAU1761_ALC_CTRL1 0x4012
#define ADAU1761_ALC_CTRL2 0x4013
#define ADAU1761_ALC_CTRL3 0x4014
#define ADAU1761_PLAY_LR_MIXER_LEFT 0x4020
#define ADAU1761_PLAY_MIXER_LEFT0 0x401c
#define ADAU1761_PLAY_MIXER_LEFT1 0x401d
#define ADAU1761_PLAY_MIXER_RIGHT0 0x401e
#define ADAU1761_PLAY_MIXER_RIGH
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment