diff options
Diffstat (limited to 'sound/soc/sunxi')
-rw-r--r-- | sound/soc/sunxi/sun4i-codec.c | 20 | ||||
-rw-r--r-- | sound/soc/sunxi/sun4i-i2s.c | 71 | ||||
-rw-r--r-- | sound/soc/sunxi/sun4i-spdif.c | 49 | ||||
-rw-r--r-- | sound/soc/sunxi/sun50i-codec-analog.c | 50 |
4 files changed, 163 insertions, 27 deletions
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c index 9e1f00e8c32b..619073e7d972 100644 --- a/sound/soc/sunxi/sun4i-codec.c +++ b/sound/soc/sunxi/sun4i-codec.c @@ -1296,15 +1296,25 @@ static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev, { struct snd_soc_dai_link *link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL); - if (!link) + struct snd_soc_dai_link_component *dlc = devm_kzalloc(dev, + 3 * sizeof(*dlc), GFP_KERNEL); + if (!link || !dlc) return NULL; + link->cpus = &dlc[0]; + link->codecs = &dlc[1]; + link->platforms = &dlc[2]; + + link->num_cpus = 1; + link->num_codecs = 1; + link->num_platforms = 1; + link->name = "cdc"; link->stream_name = "CDC PCM"; - link->codec_dai_name = "Codec"; - link->cpu_dai_name = dev_name(dev); - link->codec_name = dev_name(dev); - link->platform_name = dev_name(dev); + link->codecs->dai_name = "Codec"; + link->cpus->dai_name = dev_name(dev); + link->codecs->name = dev_name(dev); + link->platforms->name = dev_name(dev); link->dai_fmt = SND_SOC_DAIFMT_I2S; *num_links = 1; diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index fd7c37596f21..9b2232908b65 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -114,6 +114,8 @@ #define SUN8I_I2S_RX_CHAN_SEL_REG 0x54 #define SUN8I_I2S_RX_CHAN_MAP_REG 0x58 +struct sun4i_i2s; + /** * struct sun4i_i2s_quirks - Differences between SoC variants. * @@ -127,7 +129,6 @@ * @sun4i_i2s_regmap: regmap config to use. * @mclk_offset: Value by which mclkdiv needs to be adjusted. * @bclk_offset: Value by which bclkdiv needs to be adjusted. - * @fmt_offset: Value by which wss and sr needs to be adjusted. * @field_clkdiv_mclk_en: regmap field to enable mclk output. * @field_fmt_wss: regmap field to set word select size. * @field_fmt_sr: regmap field to set sample resolution. @@ -150,7 +151,6 @@ struct sun4i_i2s_quirks { const struct regmap_config *sun4i_i2s_regmap; unsigned int mclk_offset; unsigned int bclk_offset; - unsigned int fmt_offset; /* Register fields for i2s */ struct reg_field field_clkdiv_mclk_en; @@ -163,6 +163,9 @@ struct sun4i_i2s_quirks { struct reg_field field_rxchanmap; struct reg_field field_txchansel; struct reg_field field_rxchansel; + + s8 (*get_sr)(const struct sun4i_i2s *, int); + s8 (*get_wss)(const struct sun4i_i2s *, int); }; struct sun4i_i2s { @@ -345,6 +348,39 @@ static int sun4i_i2s_set_clk_rate(struct snd_soc_dai *dai, return 0; } +static s8 sun4i_i2s_get_sr(const struct sun4i_i2s *i2s, int width) +{ + if (width < 16 || width > 24) + return -EINVAL; + + if (width % 4) + return -EINVAL; + + return (width - 16) / 4; +} + +static s8 sun4i_i2s_get_wss(const struct sun4i_i2s *i2s, int width) +{ + if (width < 16 || width > 32) + return -EINVAL; + + if (width % 4) + return -EINVAL; + + return (width - 16) / 4; +} + +static s8 sun8i_i2s_get_sr_wss(const struct sun4i_i2s *i2s, int width) +{ + if (width % 4) + return -EINVAL; + + if (width < 8 || width > 32) + return -EINVAL; + + return (width - 8) / 4 + 1; +} + static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -396,22 +432,16 @@ static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream, } i2s->playback_dma_data.addr_width = width; - switch (params_width(params)) { - case 16: - sr = 0; - wss = 0; - break; + sr = i2s->variant->get_sr(i2s, params_width(params)); + if (sr < 0) + return -EINVAL; - default: - dev_err(dai->dev, "Unsupported sample width: %d\n", - params_width(params)); + wss = i2s->variant->get_wss(i2s, params_width(params)); + if (wss < 0) return -EINVAL; - } - regmap_field_write(i2s->field_fmt_wss, - wss + i2s->variant->fmt_offset); - regmap_field_write(i2s->field_fmt_sr, - sr + i2s->variant->fmt_offset); + regmap_field_write(i2s->field_fmt_wss, wss); + regmap_field_write(i2s->field_fmt_sr, sr); return sun4i_i2s_set_clk_rate(dai, params_rate(params), params_width(params)); @@ -891,6 +921,8 @@ static const struct sun4i_i2s_quirks sun4i_a10_i2s_quirks = { .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), + .get_sr = sun4i_i2s_get_sr, + .get_wss = sun4i_i2s_get_wss, }; static const struct sun4i_i2s_quirks sun6i_a31_i2s_quirks = { @@ -908,6 +940,8 @@ static const struct sun4i_i2s_quirks sun6i_a31_i2s_quirks = { .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), + .get_sr = sun4i_i2s_get_sr, + .get_wss = sun4i_i2s_get_wss, }; static const struct sun4i_i2s_quirks sun8i_a83t_i2s_quirks = { @@ -925,6 +959,8 @@ static const struct sun4i_i2s_quirks sun8i_a83t_i2s_quirks = { .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), + .get_sr = sun8i_i2s_get_sr_wss, + .get_wss = sun8i_i2s_get_sr_wss, }; static const struct sun4i_i2s_quirks sun8i_h3_i2s_quirks = { @@ -933,7 +969,6 @@ static const struct sun4i_i2s_quirks sun8i_h3_i2s_quirks = { .sun4i_i2s_regmap = &sun8i_i2s_regmap_config, .mclk_offset = 1, .bclk_offset = 2, - .fmt_offset = 3, .has_fmt_set_lrck_period = true, .has_chcfg = true, .has_chsel_tx_chen = true, @@ -948,6 +983,8 @@ static const struct sun4i_i2s_quirks sun8i_h3_i2s_quirks = { .field_rxchanmap = REG_FIELD(SUN8I_I2S_RX_CHAN_MAP_REG, 0, 31), .field_txchansel = REG_FIELD(SUN8I_I2S_TX_CHAN_SEL_REG, 0, 2), .field_rxchansel = REG_FIELD(SUN8I_I2S_RX_CHAN_SEL_REG, 0, 2), + .get_sr = sun8i_i2s_get_sr_wss, + .get_wss = sun8i_i2s_get_sr_wss, }; static const struct sun4i_i2s_quirks sun50i_a64_codec_i2s_quirks = { @@ -965,6 +1002,8 @@ static const struct sun4i_i2s_quirks sun50i_a64_codec_i2s_quirks = { .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), + .get_sr = sun8i_i2s_get_sr_wss, + .get_wss = sun8i_i2s_get_sr_wss, }; static int sun4i_i2s_init_regmap_fields(struct device *dev, diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index 941b6a712db0..cbe598b0fb10 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -66,6 +66,18 @@ #define SUN4I_SPDIF_FCTL_RXOM(v) ((v) << 0) #define SUN4I_SPDIF_FCTL_RXOM_MASK GENMASK(1, 0) +#define SUN50I_H6_SPDIF_FCTL (0x14) + #define SUN50I_H6_SPDIF_FCTL_HUB_EN BIT(31) + #define SUN50I_H6_SPDIF_FCTL_FTX BIT(30) + #define SUN50I_H6_SPDIF_FCTL_FRX BIT(29) + #define SUN50I_H6_SPDIF_FCTL_TXTL(v) ((v) << 12) + #define SUN50I_H6_SPDIF_FCTL_TXTL_MASK GENMASK(19, 12) + #define SUN50I_H6_SPDIF_FCTL_RXTL(v) ((v) << 4) + #define SUN50I_H6_SPDIF_FCTL_RXTL_MASK GENMASK(10, 4) + #define SUN50I_H6_SPDIF_FCTL_TXIM BIT(2) + #define SUN50I_H6_SPDIF_FCTL_RXOM(v) ((v) << 0) + #define SUN50I_H6_SPDIF_FCTL_RXOM_MASK GENMASK(1, 0) + #define SUN4I_SPDIF_FSTA (0x18) #define SUN4I_SPDIF_FSTA_TXE BIT(14) #define SUN4I_SPDIF_FSTA_TXECNTSHT (8) @@ -152,6 +164,19 @@ #define SUN4I_SPDIF_SAMFREQ_176_4KHZ 0xc #define SUN4I_SPDIF_SAMFREQ_192KHZ 0xe +/** + * struct sun4i_spdif_quirks - Differences between SoC variants. + * + * @reg_dac_tx_data: TX FIFO offset for DMA config. + * @has_reset: SoC needs reset deasserted. + * @val_fctl_ftx: TX FIFO flush bitmask. + */ +struct sun4i_spdif_quirks { + unsigned int reg_dac_txdata; + bool has_reset; + unsigned int val_fctl_ftx; +}; + struct sun4i_spdif_dev { struct platform_device *pdev; struct clk *spdif_clk; @@ -160,16 +185,19 @@ struct sun4i_spdif_dev { struct snd_soc_dai_driver cpu_dai_drv; struct regmap *regmap; struct snd_dmaengine_dai_dma_data dma_params_tx; + const struct sun4i_spdif_quirks *quirks; }; static void sun4i_spdif_configure(struct sun4i_spdif_dev *host) { + const struct sun4i_spdif_quirks *quirks = host->quirks; + /* soft reset SPDIF */ regmap_write(host->regmap, SUN4I_SPDIF_CTL, SUN4I_SPDIF_CTL_RESET); /* flush TX FIFO */ regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, - SUN4I_SPDIF_FCTL_FTX, SUN4I_SPDIF_FCTL_FTX); + quirks->val_fctl_ftx, quirks->val_fctl_ftx); /* clear TX counter */ regmap_write(host->regmap, SUN4I_SPDIF_TXCNT, 0); @@ -396,25 +424,29 @@ static struct snd_soc_dai_driver sun4i_spdif_dai = { .name = "spdif", }; -struct sun4i_spdif_quirks { - unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */ - bool has_reset; -}; - static const struct sun4i_spdif_quirks sun4i_a10_spdif_quirks = { .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, + .val_fctl_ftx = SUN4I_SPDIF_FCTL_FTX, }; static const struct sun4i_spdif_quirks sun6i_a31_spdif_quirks = { .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, + .val_fctl_ftx = SUN4I_SPDIF_FCTL_FTX, .has_reset = true, }; static const struct sun4i_spdif_quirks sun8i_h3_spdif_quirks = { .reg_dac_txdata = SUN8I_SPDIF_TXFIFO, + .val_fctl_ftx = SUN4I_SPDIF_FCTL_FTX, .has_reset = true, }; +static const struct sun4i_spdif_quirks sun50i_h6_spdif_quirks = { + .reg_dac_txdata = SUN8I_SPDIF_TXFIFO, + .val_fctl_ftx = SUN50I_H6_SPDIF_FCTL_FTX, + .has_reset = true, +}; + static const struct of_device_id sun4i_spdif_of_match[] = { { .compatible = "allwinner,sun4i-a10-spdif", @@ -428,6 +460,10 @@ static const struct of_device_id sun4i_spdif_of_match[] = { .compatible = "allwinner,sun8i-h3-spdif", .data = &sun8i_h3_spdif_quirks, }, + { + .compatible = "allwinner,sun50i-h6-spdif", + .data = &sun50i_h6_spdif_quirks, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); @@ -492,6 +528,7 @@ static int sun4i_spdif_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); return -ENODEV; } + host->quirks = quirks; host->regmap = devm_regmap_init_mmio(&pdev->dev, base, &sun4i_spdif_regmap_config); diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c index d105c90c3706..6d1de565350e 100644 --- a/sound/soc/sunxi/sun50i-codec-analog.c +++ b/sound/soc/sunxi/sun50i-codec-analog.c @@ -49,6 +49,15 @@ #define SUN50I_ADDA_OR_MIX_CTRL_DACR 1 #define SUN50I_ADDA_OR_MIX_CTRL_DACL 0 +#define SUN50I_ADDA_EARPIECE_CTRL0 0x03 +#define SUN50I_ADDA_EARPIECE_CTRL0_EAR_RAMP_TIME 4 +#define SUN50I_ADDA_EARPIECE_CTRL0_ESPSR 0 + +#define SUN50I_ADDA_EARPIECE_CTRL1 0x04 +#define SUN50I_ADDA_EARPIECE_CTRL1_ESPPA_EN 7 +#define SUN50I_ADDA_EARPIECE_CTRL1_ESPPA_MUTE 6 +#define SUN50I_ADDA_EARPIECE_CTRL1_ESP_VOL 0 + #define SUN50I_ADDA_LINEOUT_CTRL0 0x05 #define SUN50I_ADDA_LINEOUT_CTRL0_LEN 7 #define SUN50I_ADDA_LINEOUT_CTRL0_REN 6 @@ -172,6 +181,10 @@ static const DECLARE_TLV_DB_RANGE(sun50i_codec_lineout_vol_scale, 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), ); +static const DECLARE_TLV_DB_RANGE(sun50i_codec_earpiece_vol_scale, + 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), +); /* volume / mute controls */ static const struct snd_kcontrol_new sun50i_a64_codec_controls[] = { @@ -225,6 +238,15 @@ static const struct snd_kcontrol_new sun50i_a64_codec_controls[] = { SUN50I_ADDA_LINEOUT_CTRL0_LEN, SUN50I_ADDA_LINEOUT_CTRL0_REN, 1, 0), + SOC_SINGLE_TLV("Earpiece Playback Volume", + SUN50I_ADDA_EARPIECE_CTRL1, + SUN50I_ADDA_EARPIECE_CTRL1_ESP_VOL, 0x1f, 0, + sun50i_codec_earpiece_vol_scale), + + SOC_SINGLE("Earpiece Playback Switch", + SUN50I_ADDA_EARPIECE_CTRL1, + SUN50I_ADDA_EARPIECE_CTRL1_ESPPA_MUTE, 1, 0), + }; static const char * const sun50i_codec_hp_src_enum_text[] = { @@ -257,6 +279,20 @@ static const struct snd_kcontrol_new sun50i_codec_lineout_src[] = { sun50i_codec_lineout_src_enum), }; +static const char * const sun50i_codec_earpiece_src_enum_text[] = { + "DACR", "DACL", "Right Mixer", "Left Mixer", +}; + +static SOC_ENUM_SINGLE_DECL(sun50i_codec_earpiece_src_enum, + SUN50I_ADDA_EARPIECE_CTRL0, + SUN50I_ADDA_EARPIECE_CTRL0_ESPSR, + sun50i_codec_earpiece_src_enum_text); + +static const struct snd_kcontrol_new sun50i_codec_earpiece_src[] = { + SOC_DAPM_ENUM("Earpiece Source Playback Route", + sun50i_codec_earpiece_src_enum), +}; + static const struct snd_soc_dapm_widget sun50i_a64_codec_widgets[] = { /* DAC */ SND_SOC_DAPM_DAC("Left DAC", NULL, SUN50I_ADDA_MIX_DAC_CTRL, @@ -285,6 +321,12 @@ static const struct snd_soc_dapm_widget sun50i_a64_codec_widgets[] = { SND_SOC_NOPM, 0, 0, sun50i_codec_lineout_src), SND_SOC_DAPM_OUTPUT("LINEOUT"), + SND_SOC_DAPM_MUX("Earpiece Source Playback Route", + SND_SOC_NOPM, 0, 0, sun50i_codec_earpiece_src), + SND_SOC_DAPM_OUT_DRV("Earpiece Amp", SUN50I_ADDA_EARPIECE_CTRL1, + SUN50I_ADDA_EARPIECE_CTRL1_ESPPA_EN, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("EARPIECE"), + /* Microphone inputs */ SND_SOC_DAPM_INPUT("MIC1"), @@ -388,6 +430,14 @@ static const struct snd_soc_dapm_route sun50i_a64_codec_routes[] = { { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, { "LINEOUT", NULL, "Line Out Source Playback Route" }, + + /* Earpiece Routes */ + { "Earpiece Source Playback Route", "DACL", "Left DAC" }, + { "Earpiece Source Playback Route", "DACR", "Right DAC" }, + { "Earpiece Source Playback Route", "Left Mixer", "Left Mixer" }, + { "Earpiece Source Playback Route", "Right Mixer", "Right Mixer" }, + { "Earpiece Amp", NULL, "Earpiece Source Playback Route" }, + { "EARPIECE", NULL, "Earpiece Amp" }, }; static const struct snd_soc_component_driver sun50i_codec_analog_cmpnt_drv = { |