diff options
author | Takashi Iwai <tiwai@suse.de> | 2017-05-02 08:25:25 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2017-05-02 08:25:25 +0200 |
commit | a5c3b32a1146e44f6b38fdfdfffc27842953420c (patch) | |
tree | eca93f51c8deabe77ed079a3e9190717b6380009 /sound | |
parent | d7dc450d5a7162de96edbed6b1792240c2f3a55f (diff) | |
parent | 20d5c84bef067b7e804a163e2abca16c47125bad (diff) |
Merge tag 'asoc-v4.12' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linussound-4.12-rc1
ASoC: Updates for v4.12
A quiet release for the core, but lots of new drivers this time around:
- A new, generalized, API for hooking up jacks which makes it easier to
write generic machine drivers for simple cases.
- Continuing fixes for issues with the x86 CPU drivers.
- New drivers for Cirrus CS35L35, DIO DIO2125, Everest ES7132,
HiSilicon hi6210, Maxim MAX98927, MT2701 systems with WM8960, Nuvoton
NAU8824, Odroid systems, ST STM32 SAI controllers and x86 systems with
DA7213
Diffstat (limited to 'sound')
185 files changed, 12070 insertions, 1880 deletions
diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index d15b653de0bf..ee08c389b4d6 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -268,7 +268,7 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) unsigned int offset; unsigned int counter = 0; - offset = snd_hdac_chip_readl(bus, LLCH); + offset = snd_hdac_chip_readw(bus, LLCH); /* Lets walk the linked capabilities list */ do { diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 182d92efc7c8..c0abad2067e1 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -47,6 +47,7 @@ source "sound/soc/cirrus/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/dwc/Kconfig" source "sound/soc/fsl/Kconfig" +source "sound/soc/hisilicon/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" @@ -63,6 +64,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sti/Kconfig" +source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9a30f21d16ee..39c27a58158d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += cirrus/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ +obj-$(CONFIG_SND_SOC) += hisilicon/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += img/ obj-$(CONFIG_SND_SOC) += intel/ @@ -43,6 +44,7 @@ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sti/ +obj-$(CONFIG_SND_SOC) += stm/ obj-$(CONFIG_SND_SOC) += sunxi/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/blackfin/bfin-eval-adau1373.c b/sound/soc/blackfin/bfin-eval-adau1373.c index 72ac78988426..64b88fdc1f6c 100644 --- a/sound/soc/blackfin/bfin-eval-adau1373.c +++ b/sound/soc/blackfin/bfin-eval-adau1373.c @@ -119,7 +119,7 @@ static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; } -static struct snd_soc_ops bfin_eval_adau1373_ops = { +static const struct snd_soc_ops bfin_eval_adau1373_ops = { .hw_params = bfin_eval_adau1373_hw_params, }; diff --git a/sound/soc/blackfin/bfin-eval-adav80x.c b/sound/soc/blackfin/bfin-eval-adav80x.c index 1037477d10b2..99e5ecabdcda 100644 --- a/sound/soc/blackfin/bfin-eval-adav80x.c +++ b/sound/soc/blackfin/bfin-eval-adav80x.c @@ -64,7 +64,7 @@ static int bfin_eval_adav80x_codec_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_ops bfin_eval_adav80x_ops = { +static const struct snd_soc_ops bfin_eval_adav80x_ops = { .hw_params = bfin_eval_adav80x_hw_params, }; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e49e9da7f1f6..883ed4c8a551 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS35L32 if I2C select SND_SOC_CS35L33 if I2C select SND_SOC_CS35L34 if I2C + select SND_SOC_CS35L35 if I2C select SND_SOC_CS42L42 if I2C select SND_SOC_CS42L51_I2C if I2C select SND_SOC_CS42L52 if I2C && INPUT @@ -69,9 +70,11 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA7219 if I2C select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C + select SND_SOC_DIO2125 select SND_SOC_DMIC select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C + select SND_SOC_ES7134 select SND_SOC_GTM601 select SND_SOC_HDAC_HDMI select SND_SOC_ICS43432 @@ -89,6 +92,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX9867 if I2C select SND_SOC_MAX98925 if I2C select SND_SOC_MAX98926 if I2C + select SND_SOC_MAX98927 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9860 if I2C select SND_SOC_MAX9768 if I2C @@ -97,6 +101,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ML26124 if I2C select SND_SOC_NAU8540 if I2C select SND_SOC_NAU8810 if I2C + select SND_SOC_NAU8824 if I2C select SND_SOC_NAU8825 if I2C select SND_SOC_HDMI_CODEC select SND_SOC_PCM1681 if I2C @@ -303,12 +308,14 @@ config SND_SOC_ADAU1761 select SND_SOC_ADAU17X1 config SND_SOC_ADAU1761_I2C - tristate + tristate "Analog Devices AU1761 CODEC - I2C" + depends on I2C select SND_SOC_ADAU1761 select REGMAP_I2C config SND_SOC_ADAU1761_SPI - tristate + tristate "Analog Devices AU1761 CODEC - SPI" + depends on SPI select SND_SOC_ADAU1761 select REGMAP_SPI @@ -408,6 +415,10 @@ config SND_SOC_CS35L34 tristate "Cirrus Logic CS35L34 CODEC" depends on I2C +config SND_SOC_CS35L35 + tristate "Cirrus Logic CS35L35 CODEC" + depends on I2C + config SND_SOC_CS42L42 tristate "Cirrus Logic CS42L42 CODEC" depends on I2C @@ -516,6 +527,10 @@ config SND_SOC_DA732X config SND_SOC_DA9055 tristate +config SND_SOC_DIO2125 + tristate "Dioo DIO2125 Amplifier" + select GPIOLIB + config SND_SOC_DMIC tristate @@ -525,6 +540,9 @@ config SND_SOC_HDMI_CODEC select SND_PCM_IEC958 select HDMI +config SND_SOC_ES7134 + tristate "Everest Semi ES7134 CODEC" + config SND_SOC_ES8328 tristate @@ -588,6 +606,10 @@ config SND_SOC_MAX98925 config SND_SOC_MAX98926 tristate +config SND_SOC_MAX98927 + tristate "Maxim Integrated MAX98927 Speaker Amplifier" + depends on I2C + config SND_SOC_MAX9850 tristate @@ -1116,6 +1138,10 @@ config SND_SOC_NAU8810 tristate "Nuvoton Technology Corporation NAU88C10 CODEC" depends on I2C +config SND_SOC_NAU8824 + tristate "Nuvoton Technology Corporation NAU88L24 CODEC" + depends on I2C + config SND_SOC_NAU8825 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1796cb987e71..28a63fdaf982 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -39,6 +39,7 @@ snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs35l33-objs := cs35l33.o snd-soc-cs35l34-objs := cs35l34.o +snd-soc-cs35l35-objs := cs35l35.o snd-soc-cs42l42-objs := cs42l42.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o @@ -63,6 +64,7 @@ snd-soc-da7219-objs := da7219.o da7219-aad.o snd-soc-da732x-objs := da732x.o snd-soc-da9055-objs := da9055.o snd-soc-dmic-objs := dmic.o +snd-soc-es7134-objs := es7134.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o @@ -84,6 +86,7 @@ snd-soc-max98371-objs := max98371.o snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o +snd-soc-max98927-objs := max98927.o snd-soc-max9850-objs := max9850.o snd-soc-max9860-objs := max9860.o snd-soc-mc13783-objs := mc13783.o @@ -92,6 +95,7 @@ snd-soc-msm8916-analog-objs := msm8916-wcd-analog.o snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o snd-soc-nau8540-objs := nau8540.o snd-soc-nau8810-objs := nau8810.o +snd-soc-nau8824-objs := nau8824.o snd-soc-nau8825-objs := nau8825.o snd-soc-hdmi-codec-objs := hdmi-codec.o snd-soc-pcm1681-objs := pcm1681.o @@ -221,6 +225,7 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o # Amp +snd-soc-dio2125-objs := dio2125.o snd-soc-max9877-objs := max9877.o snd-soc-max98504-objs := max98504.o snd-soc-tpa6130a2-objs := tpa6130a2.o @@ -269,6 +274,7 @@ obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o +obj-$(CONFIG_SND_SOC_CS35L35) += snd-soc-cs35l35.o obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o @@ -293,6 +299,7 @@ obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o +obj-$(CONFIG_SND_SOC_ES7134) += snd-soc-es7134.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o @@ -313,6 +320,7 @@ obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o +obj-$(CONFIG_SND_SOC_MAX98927) += snd-soc-max98927.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o @@ -321,6 +329,7 @@ obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o +obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o @@ -448,6 +457,7 @@ obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp +obj-$(CONFIG_SND_SOC_DIO2125) += snd-soc-dio2125.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c index e819dd8c82fd..b2dfddead227 100644 --- a/sound/soc/codecs/ak4613.c +++ b/sound/soc/codecs/ak4613.c @@ -75,6 +75,12 @@ #define DFS_DOUBLE_SPEED (1 << 2) #define DFS_QUAD_SPEED (2 << 2) +/* ICTRL */ +#define ICTRL_MASK (0x3) + +/* OCTRL */ +#define OCTRL_MASK (0x3F) + struct ak4613_formats { unsigned int width; unsigned int fmt; @@ -365,8 +371,8 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, CTRL1, FMT_MASK, fmt_ctrl); snd_soc_update_bits(codec, CTRL2, DFS_MASK, ctrl2); - snd_soc_write(codec, ICTRL, priv->ic); - snd_soc_write(codec, OCTRL, priv->oc); + snd_soc_update_bits(codec, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_update_bits(codec, OCTRL, OCTRL_MASK, priv->oc); hw_params_end: if (ret < 0) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c new file mode 100644 index 000000000000..f8aef5869b03 --- /dev/null +++ b/sound/soc/codecs/cs35l35.c @@ -0,0 +1,1580 @@ +/* + * cs35l35.c -- CS35L35 ALSA SoC audio driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Author: Brian Austin <brian.austin@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/gpio.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/cs35l35.h> +#include <linux/of_irq.h> +#include <linux/completion.h> + +#include "cs35l35.h" + +/* + * Some fields take zero as a valid value so use a high bit flag that won't + * get written to the device to mark those. + */ +#define CS35L35_VALID_PDATA 0x80000000 + +static const struct reg_default cs35l35_reg[] = { + {CS35L35_PWRCTL1, 0x01}, + {CS35L35_PWRCTL2, 0x11}, + {CS35L35_PWRCTL3, 0x00}, + {CS35L35_CLK_CTL1, 0x04}, + {CS35L35_CLK_CTL2, 0x12}, + {CS35L35_CLK_CTL3, 0xCF}, + {CS35L35_SP_FMT_CTL1, 0x20}, + {CS35L35_SP_FMT_CTL2, 0x00}, + {CS35L35_SP_FMT_CTL3, 0x02}, + {CS35L35_MAG_COMP_CTL, 0x00}, + {CS35L35_AMP_INP_DRV_CTL, 0x01}, + {CS35L35_AMP_DIG_VOL_CTL, 0x12}, + {CS35L35_AMP_DIG_VOL, 0x00}, + {CS35L35_ADV_DIG_VOL, 0x00}, + {CS35L35_PROTECT_CTL, 0x06}, + {CS35L35_AMP_GAIN_AUD_CTL, 0x13}, + {CS35L35_AMP_GAIN_PDM_CTL, 0x00}, + {CS35L35_AMP_GAIN_ADV_CTL, 0x00}, + {CS35L35_GPI_CTL, 0x00}, + {CS35L35_BST_CVTR_V_CTL, 0x00}, + {CS35L35_BST_PEAK_I, 0x07}, + {CS35L35_BST_RAMP_CTL, 0x85}, + {CS35L35_BST_CONV_COEF_1, 0x24}, + {CS35L35_BST_CONV_COEF_2, 0x24}, + {CS35L35_BST_CONV_SLOPE_COMP, 0x4E}, + {CS35L35_BST_CONV_SW_FREQ, 0x04}, + {CS35L35_CLASS_H_CTL, 0x0B}, + {CS35L35_CLASS_H_HEADRM_CTL, 0x0B}, + {CS35L35_CLASS_H_RELEASE_RATE, 0x08}, + {CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41}, + {CS35L35_CLASS_H_VP_CTL, 0xC5}, + {CS35L35_VPBR_CTL, 0x0A}, + {CS35L35_VPBR_VOL_CTL, 0x90}, + {CS35L35_VPBR_TIMING_CTL, 0x6A}, + {CS35L35_VPBR_MODE_VOL_CTL, 0x00}, + {CS35L35_SPKR_MON_CTL, 0xC0}, + {CS35L35_IMON_SCALE_CTL, 0x30}, + {CS35L35_AUDIN_RXLOC_CTL, 0x00}, + {CS35L35_ADVIN_RXLOC_CTL, 0x80}, + {CS35L35_VMON_TXLOC_CTL, 0x00}, + {CS35L35_IMON_TXLOC_CTL, 0x80}, + {CS35L35_VPMON_TXLOC_CTL, 0x04}, + {CS35L35_VBSTMON_TXLOC_CTL, 0x84}, + {CS35L35_VPBR_STATUS_TXLOC_CTL, 0x04}, + {CS35L35_ZERO_FILL_LOC_CTL, 0x00}, + {CS35L35_AUDIN_DEPTH_CTL, 0x0F}, + {CS35L35_SPKMON_DEPTH_CTL, 0x0F}, + {CS35L35_SUPMON_DEPTH_CTL, 0x0F}, + {CS35L35_ZEROFILL_DEPTH_CTL, 0x00}, + {CS35L35_MULT_DEV_SYNCH1, 0x02}, + {CS35L35_MULT_DEV_SYNCH2, 0x80}, + {CS35L35_PROT_RELEASE_CTL, 0x00}, + {CS35L35_DIAG_MODE_REG_LOCK, 0x00}, + {CS35L35_DIAG_MODE_CTL_1, 0x40}, + {CS35L35_DIAG_MODE_CTL_2, 0x00}, + {CS35L35_INT_MASK_1, 0xFF}, + {CS35L35_INT_MASK_2, 0xFF}, + {CS35L35_INT_MASK_3, 0xFF}, + {CS35L35_INT_MASK_4, 0xFF}, + +}; + +static bool cs35l35_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_DEVID_AB ... CS35L35_PWRCTL3: + case CS35L35_CLK_CTL1 ... CS35L35_SP_FMT_CTL3: + case CS35L35_MAG_COMP_CTL ... CS35L35_AMP_GAIN_AUD_CTL: + case CS35L35_AMP_GAIN_PDM_CTL ... CS35L35_BST_PEAK_I: + case CS35L35_BST_RAMP_CTL ... CS35L35_BST_CONV_SW_FREQ: + case CS35L35_CLASS_H_CTL ... CS35L35_CLASS_H_VP_CTL: + case CS35L35_CLASS_H_STATUS: + case CS35L35_VPBR_CTL ... CS35L35_VPBR_MODE_VOL_CTL: + case CS35L35_VPBR_ATTEN_STATUS: + case CS35L35_SPKR_MON_CTL: + case CS35L35_IMON_SCALE_CTL ... CS35L35_ZEROFILL_DEPTH_CTL: + case CS35L35_MULT_DEV_SYNCH1 ... CS35L35_PROT_RELEASE_CTL: + case CS35L35_DIAG_MODE_REG_LOCK ... CS35L35_DIAG_MODE_CTL_2: + case CS35L35_INT_MASK_1 ... CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static int cs35l35_wait_for_pdn(struct cs35l35_private *cs35l35) +{ + int ret; + + if (cs35l35->pdata.ext_bst) { + usleep_range(5000, 5500); + return 0; + } + + reinit_completion(&cs35l35->pdn_done); + + ret = wait_for_completion_timeout(&cs35l35->pdn_done, + msecs_to_jiffies(100)); + if (ret == 0) { + dev_err(cs35l35->dev, "PDN_DONE did not complete\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 0 << CS35L35_MCLK_DIS_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 0 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 1 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 1); + + /* Already muted, so disable volume ramp for faster shutdown */ + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, 0); + + ret = cs35l35_wait_for_pdn(cs35l35); + + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 1 << CS35L35_MCLK_DIS_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, + 1 << CS35L35_AMP_DIGSFT_SHIFT); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + return ret; +} + +static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + unsigned int reg[4]; + int i; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(5000, 5100); + /* If in PDM mode we must use VP for Voltage control */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + 0 << CS35L35_BST_CTL_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 0); + + for (i = 0; i < 2; i++) + regmap_bulk_read(cs35l35->regmap, CS35L35_INT_STATUS_1, + ®, ARRAY_SIZE(reg)); + + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, + 1 << CS35L35_AMP_MUTE_SHIFT); + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMD: + usleep_range(5000, 5100); + /* + * If PDM mode we should switch back to pdata value + * for Voltage control when we go down + */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl + << CS35L35_BST_CTL_SHIFT); + + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + } + return 0; +} + +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); +static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l35_aud_controls[] = { + SOC_SINGLE_SX_TLV("Digital Audio Volume", CS35L35_AMP_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Audio Volume", CS35L35_AMP_GAIN_AUD_CTL, 0, 19, 0, + amp_gain_tlv), + SOC_SINGLE_TLV("PDM Volume", CS35L35_AMP_GAIN_PDM_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_kcontrol_new cs35l35_adv_controls[] = { + SOC_SINGLE_SX_TLV("Digital Advisory Volume", CS35L35_ADV_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Advisory Volume", CS35L35_AMP_GAIN_ADV_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_soc_dapm_widget cs35l35_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L35_PWRCTL3, 1, 1, + cs35l35_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L35_PWRCTL3, 2, 1), + + SND_SOC_DAPM_OUTPUT("SPK"), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("VBST"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L35_PWRCTL2, 7, 1), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L35_PWRCTL2, 6, 1), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L35_PWRCTL3, 3, 1), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L35_PWRCTL3, 4, 1), + SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L35_PWRCTL2, 5, 1), + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L35_PWRCTL2, 0, 1, NULL, 0, + cs35l35_main_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l35_audio_map[] = { + {"VPMON ADC", NULL, "VP"}, + {"VBSTMON ADC", NULL, "VBST"}, + {"IMON ADC", NULL, "ISENSE"}, + {"VMON ADC", NULL, "VSENSE"}, + {"SDOUT", NULL, "IMON ADC"}, + {"SDOUT", NULL, "VMON ADC"}, + {"SDOUT", NULL, "VBSTMON ADC"}, + {"SDOUT", NULL, "VPMON ADC"}, + {"AMP Capture", NULL, "SDOUT"}, + + {"SDIN", NULL, "AMP Playback"}, + {"CLASS H", NULL, "SDIN"}, + {"Main AMP", NULL, "CLASS H"}, + {"SPK", NULL, "Main AMP"}, +}; + +static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs35l35->i2s_mode = true; + cs35l35->pdm_mode = false; + break; + case SND_SOC_DAIFMT_PDM: + cs35l35->pdm_mode = true; + cs35l35->i2s_mode = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l35_sysclk_config { + int sysclk; + int srate; + u8 clk_cfg; +}; + +static struct cs35l35_sysclk_config cs35l35_clk_ctl[] = { + + /* SYSCLK, Sample Rate, Serial Port Cfg */ + {5644800, 44100, 0x00}, + {5644800, 88200, 0x40}, + {6144000, 48000, 0x10}, + {6144000, 96000, 0x50}, + {11289600, 44100, 0x01}, + {11289600, 88200, 0x41}, + {11289600, 176400, 0x81}, + {12000000, 44100, 0x03}, + {12000000, 48000, 0x13}, + {12000000, 88200, 0x43}, + {12000000, 96000, 0x53}, + {12000000, 176400, 0x83}, + {12000000, 192000, 0x93}, + {12288000, 48000, 0x11}, + {12288000, 96000, 0x51}, + {12288000, 192000, 0x91}, + {13000000, 44100, 0x07}, + {13000000, 48000, 0x17}, + {13000000, 88200, 0x47}, + {13000000, 96000, 0x57}, + {13000000, 176400, 0x87}, + {13000000, 192000, 0x97}, + {22579200, 44100, 0x02}, + {22579200, 88200, 0x42}, + {22579200, 176400, 0x82}, + {24000000, 44100, 0x0B}, + {24000000, 48000, 0x1B}, + {24000000, 88200, 0x4B}, + {24000000, 96000, 0x5B}, + {24000000, 176400, 0x8B}, + {24000000, 192000, 0x9B}, + {24576000, 48000, 0x12}, + {24576000, 96000, 0x52}, + {24576000, 192000, 0x92}, + {26000000, 44100, 0x0F}, + {26000000, 48000, 0x1F}, + {26000000, 88200, 0x4F}, + {26000000, 96000, 0x5F}, + {26000000, 176400, 0x8F}, + {26000000, 192000, 0x9F}, +}; + +static int cs35l35_get_clk_config(int sysclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) { + if (cs35l35_clk_ctl[i].sysclk == sysclk && + cs35l35_clk_ctl[i].srate == srate) + return cs35l35_clk_ctl[i].clk_cfg; + } + return -EINVAL; +} + +static int cs35l35_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + int srate = params_rate(params); + int ret = 0; + u8 sp_sclks; + int audin_format; + int errata_chk; + + int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate); + + if (clk_ctl < 0) { + dev_err(codec->dev, "Invalid CLK:Rate %d:%d\n", + cs35l35->sysclk, srate); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2, + CS35L35_CLK_CTL2_MASK, clk_ctl); + if (ret != 0) { + dev_err(codec->dev, "Failed to set port config %d\n", ret); + return ret; + } + + /* + * Rev A0 Errata + * When configured for the weak-drive detection path (CH_WKFET_DIS = 0) + * the Class H algorithm does not enable weak-drive operation for + * nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10 + */ + errata_chk = clk_ctl & CS35L35_SP_RATE_MASK; + + if (classh->classh_wk_fet_disable == 0x00 && + (errata_chk == 0x01 || errata_chk == 0x03)) { + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + 0 << CS35L35_CH_WKFET_DEL_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set fet config %d\n", + ret); + return ret; + } + } + + /* + * You can pull more Monitor data from the SDOUT pin than going to SDIN + * Just make sure your SCLK is fast enough to fill the frame + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (params_width(params)) { + case 8: + audin_format = CS35L35_SDIN_DEPTH_8; + break; + case 16: + audin_format = CS35L35_SDIN_DEPTH_16; + break; + case 24: + audin_format = CS35L35_SDIN_DEPTH_24; + break; + default: + dev_err(codec->dev, "Unsupported Width %d\n", + params_width(params)); + return -EINVAL; + } + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_AUDIN_DEPTH_MASK, + audin_format << + CS35L35_AUDIN_DEPTH_SHIFT); + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_ADVIN_DEPTH_MASK, + audin_format << + CS35L35_ADVIN_DEPTH_SHIFT); + } + } + + if (cs35l35->i2s_mode) { + /* We have to take the SCLK to derive num sclks + * to configure the CLOCK_CTL3 register correctly + */ + if ((cs35l35->sclk / srate) % 4) { + dev_err(codec->dev, "Unsupported sclk/fs ratio %d:%d\n", + cs35l35->sclk, srate); + return -EINVAL; + } + sp_sclks = ((cs35l35->sclk / srate) / 4) - 1; + + /* Only certain ratios are supported in I2S Slave Mode */ + if (cs35l35->slave_mode) { + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_48FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(codec->dev, "ratio not supported\n"); + return -EINVAL; + } + } else { + /* Only certain ratios supported in I2S MASTER Mode */ + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(codec->dev, "ratio not supported\n"); + return -EINVAL; + } + } + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLK_CTL3, + CS35L35_SP_SCLKS_MASK, sp_sclks << + CS35L35_SP_SCLKS_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set fsclk %d\n", ret); + return ret; + } + } + + return ret; +} + +static const unsigned int cs35l35_src_rates[] = { + 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_constraints = { + .count = ARRAY_SIZE(cs35l35_src_rates), + .list = cs35l35_src_rates, +}; + +static int cs35l35_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cs35l35_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 0 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static const unsigned int cs35l35_pdm_rates[] = { + 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_pdm_constraints = { + .count = ARRAY_SIZE(cs35l35_pdm_rates), + .list = cs35l35_pdm_rates, +}; + +static int cs35l35_pdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l35_pdm_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 1 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + /* Need the SCLK Frequency regardless of sysclk source for I2S */ + cs35l35->sclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops cs35l35_ops = { + .startup = cs35l35_pcm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, + .set_sysclk = cs35l35_dai_set_sysclk, +}; + +static const struct snd_soc_dai_ops cs35l35_pdm_ops = { + .startup = cs35l35_pdm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, +}; + +static struct snd_soc_dai_driver cs35l35_dai[] = { + { + .name = "cs35l35-pcm", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_ops, + .symmetric_rates = 1, + }, + { + .name = "cs35l35-pdm", + .id = 1, + .playback = { + .stream_name = "PDM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_pdm_ops, + }, +}; + +static int cs35l35_codec_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, + int dir) +{ + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + int clksrc; + int ret = 0; + + switch (clk_id) { + case 0: + clksrc = CS35L35_CLK_SOURCE_MCLK; + break; + case 1: + clksrc = CS35L35_CLK_SOURCE_SCLK; + break; + case 2: + clksrc = CS35L35_CLK_SOURCE_PDM; + break; + default: + dev_err(codec->dev, "Invalid CLK Source\n"); + return -EINVAL; + } + + switch (freq) { + case 5644800: + case 6144000: + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 22579200: + case 24000000: + case 24576000: + case 26000000: + cs35l35->sysclk = freq; + break; + default: + dev_err(codec->dev, "Invalid CLK Frequency Input : %d\n", freq); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_CLK_SOURCE_MASK, + clksrc << CS35L35_CLK_SOURCE_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set sysclk %d\n", ret); + return ret; + } + + return ret; +} + +static int cs35l35_codec_probe(struct snd_soc_codec *codec) +{ + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg; + int ret; + + /* Set Platform Data */ + if (cs35l35->pdata.bst_vctl) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl); + + if (cs35l35->pdata.bst_ipk) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I, + CS35L35_BST_IPK_MASK, + cs35l35->pdata.bst_ipk << + CS35L35_BST_IPK_SHIFT); + + if (cs35l35->pdata.gain_zc) + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_GAIN_ZC_MASK, + cs35l35->pdata.gain_zc << + CS35L35_AMP_GAIN_ZC_SHIFT); + + if (cs35l35->pdata.aud_channel) + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_RXLOC_CTL, + CS35L35_AUD_IN_LR_MASK, + cs35l35->pdata.aud_channel << + CS35L35_AUD_IN_LR_SHIFT); + + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_ADVIN_RXLOC_CTL, + CS35L35_ADV_IN_LR_MASK, + cs35l35->pdata.adv_channel << + CS35L35_ADV_IN_LR_SHIFT); + if (cs35l35->pdata.shared_bst) + regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL, + CS35L35_CH_STEREO_MASK, + 1 << CS35L35_CH_STEREO_SHIFT); + ret = snd_soc_add_codec_controls(codec, cs35l35_adv_controls, + ARRAY_SIZE(cs35l35_adv_controls)); + if (ret) + return ret; + } + + if (cs35l35->pdata.sp_drv_str) + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_SP_DRV_MASK, + cs35l35->pdata.sp_drv_str << + CS35L35_SP_DRV_SHIFT); + if (cs35l35->pdata.sp_drv_unused) + regmap_update_bits(cs35l35->regmap, CS35L35_SP_FMT_CTL3, + CS35L35_SP_I2S_DRV_MASK, + cs35l35->pdata.sp_drv_unused << + CS35L35_SP_I2S_DRV_SHIFT); + + if (classh->classh_algo_enable) { + if (classh->classh_bst_override) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_OVR_MASK, + classh->classh_bst_override << + CS35L35_CH_BST_OVR_SHIFT); + if (classh->classh_bst_max_limit) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_LIM_MASK, + classh->classh_bst_max_limit << + CS35L35_CH_BST_LIM_SHIFT); + if (classh->classh_mem_depth) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_MEM_DEPTH_MASK, + classh->classh_mem_depth << + CS35L35_CH_MEM_DEPTH_SHIFT); + if (classh->classh_headroom) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_HEADRM_CTL, + CS35L35_CH_HDRM_CTL_MASK, + classh->classh_headroom << + CS35L35_CH_HDRM_CTL_SHIFT); + if (classh->classh_release_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_RELEASE_RATE, + CS35L35_CH_REL_RATE_MASK, + classh->classh_release_rate << + CS35L35_CH_REL_RATE_SHIFT); + if (classh->classh_wk_fet_disable) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DIS_MASK, + classh->classh_wk_fet_disable << + CS35L35_CH_WKFET_DIS_SHIFT); + if (classh->classh_wk_fet_delay) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + classh->classh_wk_fet_delay << + CS35L35_CH_WKFET_DEL_SHIFT); + if (classh->classh_wk_fet_thld) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_THLD_MASK, + classh->classh_wk_fet_thld << + CS35L35_CH_WKFET_THLD_SHIFT); + if (classh->classh_vpch_auto) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_AUTO_MASK, + classh->classh_vpch_auto << + CS35L35_CH_VP_AUTO_SHIFT); + if (classh->classh_vpch_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_RATE_MASK, + classh->classh_vpch_rate << + CS35L35_CH_VP_RATE_SHIFT); + if (classh->classh_vpch_man) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_MAN_MASK, + classh->classh_vpch_man << + CS35L35_CH_VP_MAN_SHIFT); + } + + if (monitor_config->is_present) { + if (monitor_config->vmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_VMON_DEPTH_MASK, + monitor_config->vmon_dpth << + CS35L35_VMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->imon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_IMON_DEPTH_MASK, + monitor_config->imon_dpth << + CS35L35_IMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->imon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->imon_frm << + CS35L35_MON_FRM_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_SCALE_CTL, + CS35L35_IMON_SCALE_MASK, + monitor_config->imon_scale << + CS35L35_IMON_SCALE_SHIFT); + } + if (monitor_config->vpmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VPMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vbstmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VBSTMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VBSTMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vbstmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vbstmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vpbrstat_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPBRSTAT_DEPTH_MASK, + monitor_config->vpbrstat_dpth << + CS35L35_VPBRSTAT_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpbrstat_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpbrstat_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->zerofill_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_ZEROFILL_DEPTH_MASK, + monitor_config->zerofill_dpth << + CS35L35_ZEROFILL_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->zerofill_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->zerofill_frm << + CS35L35_MON_FRM_SHIFT); + } + } + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs35l35 = { + .probe = cs35l35_codec_probe, + .set_sysclk = cs35l35_codec_set_sysclk, + .component_driver = { + .dapm_widgets = cs35l35_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l35_dapm_widgets), + + .dapm_routes = cs35l35_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l35_audio_map), + + .controls = cs35l35_aud_controls, + .num_controls = ARRAY_SIZE(cs35l35_aud_controls), + }, + +}; + +static struct regmap_config cs35l35_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L35_MAX_REGISTER, + .reg_defaults = cs35l35_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l35_reg), + .volatile_reg = cs35l35_volatile_register, + .readable_reg = cs35l35_readable_register, + .precious_reg = cs35l35_precious_register, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t cs35l35_irq(int irq, void *data) +{ + struct cs35l35_private *cs35l35 = data; + unsigned int sticky1, sticky2, sticky3, sticky4; + unsigned int mask1, mask2, mask3, mask4, current1; + + /* ack the irq by reading all status registers */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_4, &sticky4); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_3, &sticky3); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_2, &sticky2); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &sticky1); + + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_4, &mask4); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_3, &mask3); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_2, &mask2); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_1, &mask1); + + /* Check to see if unmasked bits are active */ + if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3) + && !(sticky4 & ~mask4)) + return IRQ_NONE; + + if (sticky2 & CS35L35_PDN_DONE) + complete(&cs35l35->pdn_done); + + /* read the current values */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, ¤t1); + + /* handle the interrupts */ + if (sticky1 & CS35L35_CAL_ERR) { + dev_crit(cs35l35->dev, "Calibration Error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_CAL_ERR)) { + pr_debug("%s : Cal error release\n", __func__); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, + CS35L35_CAL_ERR_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + } + } + + if (sticky1 & CS35L35_AMP_SHORT) { + dev_crit(cs35l35->dev, "AMP Short Error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_AMP_SHORT)) { + dev_dbg(cs35l35->dev, "Amp short error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, + CS35L35_SHORT_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTW) { + dev_warn(cs35l35->dev, "Over temperature warning\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTW)) { + dev_dbg(cs35l35->dev, "Over temperature warn release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, + CS35L35_OTW_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTE) { + dev_crit(cs35l35->dev, "Over temperature error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTE)) { + dev_dbg(cs35l35->dev, "Over temperature error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, + CS35L35_OTE_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + } + } + + if (sticky3 & CS35L35_BST_HIGH) { + dev_crit(cs35l35->dev, "VBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky3 & CS35L35_LBST_SHORT) { + dev_crit(cs35l35->dev, "LBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky2 & CS35L35_VPBR_ERR) + dev_dbg(cs35l35->dev, "Error: Reactive Brownout\n"); + + if (sticky4 & CS35L35_VMON_OVFL) + dev_dbg(cs35l35->dev, "Error: VMON overflow\n"); + + if (sticky4 & CS35L35_IMON_OVFL) + dev_dbg(cs35l35->dev, "Error: IMON overflow\n"); + + return IRQ_HANDLED; +} + + +static int cs35l35_handle_of_data(struct i2c_client *i2c_client, + struct cs35l35_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + struct device_node *classh, *signal_format; + struct classh_cfg *classh_config = &pdata->classh_algo; + struct monitor_cfg *monitor_config = &pdata->mon_cfg; + unsigned int val32 = 0; + u8 monitor_array[4]; + const int imon_array_size = ARRAY_SIZE(monitor_array); + const int mon_array_size = imon_array_size - 1; + int ret = 0; + + if (!np) + return 0; + + pdata->bst_pdn_fet_on = of_property_read_bool(np, + "cirrus,boost-pdn-fet-on"); + + ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val32); + if (ret >= 0) { + if (val32 < 2600 || val32 > 9000) { + dev_err(&i2c_client->dev, + "Invalid Boost Voltage %d mV\n", val32); + return -EINVAL; + } + pdata->bst_vctl = ((val32 - 2600) / 100) + 1; + } + + ret = of_property_read_u32(np, "cirrus,boost-peak-milliamp", &val32); + if (ret >= 0) { + if (val32 < 1680 || val32 > 4480) { + dev_err(&i2c_client->dev, + "Invalid Boost Peak Current %u mA\n", val32); + return -EINVAL; + } + + pdata->bst_ipk = (val32 - 1680) / 110; + } + + if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0) + pdata->sp_drv_str = val32; + if (of_property_read_u32(np, "cirrus,sp-drv-unused", &val32) >= 0) + pdata->sp_drv_unused = val32 | CS35L35_VALID_PDATA; + + pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config"); + + if (pdata->stereo) { + ret = of_property_read_u32(np, "cirrus,audio-channel", &val32); + if (ret >= 0) + pdata->aud_channel = val32; + + ret = of_property_read_u32(np, "cirrus,advisory-channel", + &val32); + if (ret >= 0) + pdata->adv_channel = val32; + + pdata->shared_bst = of_property_read_bool(np, + "cirrus,shared-boost"); + } + + pdata->ext_bst = of_property_read_bool(np, "cirrus,external-boost"); + + pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc"); + + classh = of_get_child_by_name(np, "cirrus,classh-internal-algo"); + classh_config->classh_algo_enable = classh ? true : false; + + if (classh_config->classh_algo_enable) { + classh_config->classh_bst_override = + of_property_read_bool(np, "cirrus,classh-bst-overide"); + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-mem-depth", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_mem_depth = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-release-rate", + &val32); + if (ret >= 0) + classh_config->classh_release_rate = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-headroom", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_headroom = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-wk-fet-disable", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_disable = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-delay", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_wk_fet_delay = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-thld", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_thld = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-auto", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_auto = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-rate", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_rate = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-man", + &val32); + if (ret >= 0) + classh_config->classh_vpch_man = val32; + } + of_node_put(classh); + + /* frame depth location */ + signal_format = of_get_child_by_name(np, "cirrus,monitor-signal-format"); + monitor_config->is_present = signal_format ? true : false; + if (monitor_config->is_present) { + ret = of_property_read_u8_array(signal_format, "cirrus,imon", + monitor_array, imon_array_size); + if (!ret) { + monitor_config->imon_specs = true; + monitor_config->imon_dpth = monitor_array[0]; + monitor_config->imon_loc = monitor_array[1]; + monitor_config->imon_frm = monitor_array[2]; + monitor_config->imon_scale = monitor_array[3]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vmon_specs = true; + monitor_config->vmon_dpth = monitor_array[0]; + monitor_config->vmon_loc = monitor_array[1]; + monitor_config->vmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vpmon_specs = true; + monitor_config->vpmon_dpth = monitor_array[0]; + monitor_config->vpmon_loc = monitor_array[1]; + monitor_config->vpmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vbstmon_specs = true; + monitor_config->vbstmon_dpth = monitor_array[0]; + monitor_config->vbstmon_loc = monitor_array[1]; + monitor_config->vbstmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vpbrstat_specs = true; + monitor_config->vpbrstat_dpth = monitor_array[0]; + monitor_config->vpbrstat_loc = monitor_array[1]; + monitor_config->vpbrstat_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,zerofill", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->zerofill_specs = true; + monitor_config->zerofill_dpth = monitor_array[0]; + monitor_config->zerofill_loc = monitor_array[1]; + monitor_config->zerofill_frm = monitor_array[2]; + } + } + of_node_put(signal_format); + + return 0; +} + +/* Errata Rev A0 */ +static const struct reg_sequence cs35l35_errata_patch[] = { + + { 0x7F, 0x99 }, + { 0x00, 0x99 }, + { 0x52, 0x22 }, + { 0x04, 0x14 }, + { 0x6D, 0x44 }, + { 0x24, 0x10 }, + { 0x58, 0xC4 }, + { 0x00, 0x98 }, + { 0x18, 0x08 }, + { 0x00, 0x00 }, + { 0x7F, 0x00 }, +}; + +static int cs35l35_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l35_private *cs35l35; + struct device *dev = &i2c_client->dev; + struct cs35l35_platform_data *pdata = dev_get_platdata(dev); + int i; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs35l35 = devm_kzalloc(dev, sizeof(struct cs35l35_private), GFP_KERNEL); + if (!cs35l35) + return -ENOMEM; + + cs35l35->dev = dev; + + i2c_set_clientdata(i2c_client, cs35l35); + cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap); + if (IS_ERR(cs35l35->regmap)) { + ret = PTR_ERR(cs35l35->regmap); + dev_err(dev, "regmap_init() failed: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++) + cs35l35->supplies[i].supply = cs35l35_supplies[i]; + + cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); + + ret = devm_regulator_bulk_get(dev, cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request core supplies: %d\n", ret); + return ret; + } + + if (pdata) { + cs35l35->pdata = *pdata; + } else { + pdata = devm_kzalloc(dev, sizeof(struct cs35l35_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + if (i2c_client->dev.of_node) { + ret = cs35l35_handle_of_data(i2c_client, pdata); + if (ret != 0) + return ret; + + } + cs35l35->pdata = *pdata; + } + + ret = regulator_bulk_enable(cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + /* returning NULL can be valid if in stereo mode */ + cs35l35->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs35l35->reset_gpio)) { + ret = PTR_ERR(cs35l35->reset_gpio); + cs35l35->reset_gpio = NULL; + if (ret == -EBUSY) { + dev_info(dev, + "Reset line busy, assuming shared reset\n"); + } else { + dev_err(dev, "Failed to get reset GPIO: %d\n", ret); + goto err; + } + } + + gpiod_set_value_cansleep(cs35l35->reset_gpio, 1); + + init_completion(&cs35l35->pdn_done); + + ret = devm_request_threaded_irq(dev, i2c_client->irq, NULL, cs35l35_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW | + IRQF_SHARED, "cs35l35", cs35l35); + if (ret != 0) { + dev_err(dev, "Failed to request IRQ: %d\n", ret); + goto err; + } + /* initialize codec */ + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, ®); + + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L35_CHIP_ID) { + dev_err(dev, "CS35L35 Device ID (%X). Expected ID %X\n", + devid, CS35L35_CHIP_ID); + ret = -ENODEV; + goto err; + } + + ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, ®); + if (ret < 0) { + dev_err(dev, "Get Revision ID failed: %d\n", ret); + goto err; + } + + ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch, + ARRAY_SIZE(cs35l35_errata_patch)); + if (ret < 0) { + dev_err(dev, "Failed to apply errata patch: %d\n", ret); + goto err; + } + + dev_info(dev, "Cirrus Logic CS35L35 (%x), Revision: %02X\n", + devid, reg & 0xFF); + + /* Set the INT Masks for critical errors */ + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, + CS35L35_INT1_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, + CS35L35_INT2_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, + CS35L35_INT3_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, + CS35L35_INT4_CRIT_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PWR2_PDN_MASK, + CS35L35_PWR2_PDN_MASK); + + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3, + CS35L35_PWR3_PDN_MASK, + CS35L35_PWR3_PDN_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT); + + ret = snd_soc_register_codec(dev, &soc_codec_dev_cs35l35, cs35l35_dai, + ARRAY_SIZE(cs35l35_dai)); + if (ret < 0) { + dev_err(dev, "Failed to register codec: %d\n", ret); + goto err; + } + + return 0; + +err: + regulator_bulk_disable(cs35l35->num_supplies, + cs35l35->supplies); + gpiod_set_value_cansleep(cs35l35->reset_gpio, 0); + + return ret; +} + +static int cs35l35_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct of_device_id cs35l35_of_match[] = { + {.compatible = "cirrus,cs35l35"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l35_of_match); + +static const struct i2c_device_id cs35l35_id[] = { + {"cs35l35", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l35_id); + +static struct i2c_driver cs35l35_i2c_driver = { + .driver = { + .name = "cs35l35", + .of_match_table = cs35l35_of_match, + }, + .id_table = cs35l35_id, + .probe = cs35l35_i2c_probe, + .remove = cs35l35_i2c_remove, +}; + +module_i2c_driver(cs35l35_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L35 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <brian.austin@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h new file mode 100644 index 000000000000..5a6e43a87c4d --- /dev/null +++ b/sound/soc/codecs/cs35l35.h @@ -0,0 +1,294 @@ +/* + * cs35l35.h -- CS35L35 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Brian Austin <brian.austin@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L35_H__ +#define __CS35L35_H__ + +#define CS35L35_FIRSTREG 0x01 +#define CS35L35_LASTREG 0x7E +#define CS35L35_CHIP_ID 0x00035A35 +#define CS35L35_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L35_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L35_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L35_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L35_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L35_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L35_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L35_PWRCTL3 0x08 /* Power Ctl 3 */ +#define CS35L35_CLK_CTL1 0x0A /* Clocking Ctl 1 */ +#define CS35L35_CLK_CTL2 0x0B /* Clocking Ctl 2 */ +#define CS35L35_CLK_CTL3 0x0C /* Clocking Ctl 3 */ +#define CS35L35_SP_FMT_CTL1 0x0D /* Serial Port Format CTL1 */ +#define CS35L35_SP_FMT_CTL2 0x0E /* Serial Port Format CTL2 */ +#define CS35L35_SP_FMT_CTL3 0x0F /* Serial Port Format CTL3 */ +#define CS35L35_MAG_COMP_CTL 0x13 /* Magnitude Comp CTL */ +#define CS35L35_AMP_INP_DRV_CTL 0x14 /* Amp Input Drive Ctl */ +#define CS35L35_AMP_DIG_VOL_CTL 0x15 /* Amplifier Dig Volume Ctl */ +#define CS35L35_AMP_DIG_VOL 0x16 /* Amplifier Dig Volume */ +#define CS35L35_ADV_DIG_VOL 0x17 /* Advisory Digital Volume */ +#define CS35L35_PROTECT_CTL 0x18 /* Amp Gain - Prot Ctl Param */ +#define CS35L35_AMP_GAIN_AUD_CTL 0x19 /* Amp Serial Port Gain Ctl */ +#define CS35L35_AMP_GAIN_PDM_CTL 0x1A /* Amplifier Gain PDM Ctl */ +#define CS35L35_AMP_GAIN_ADV_CTL 0x1B /* Amplifier Gain Ctl */ +#define CS35L35_GPI_CTL 0x1C /* GPI Ctl */ +#define CS35L35_BST_CVTR_V_CTL 0x1D /* Boost Conv Voltage Ctl */ +#define CS35L35_BST_PEAK_I 0x1E /* Boost Conv Peak Current */ +#define CS35L35_BST_RAMP_CTL 0x20 /* Boost Conv Soft Ramp Ctl */ +#define CS35L35_BST_CONV_COEF_1 0x21 /* Boost Conv Coefficients 1 */ +#define CS35L35_BST_CONV_COEF_2 0x22 /* Boost Conv Coefficients 2 */ +#define CS35L35_BST_CONV_SLOPE_COMP 0x23 /* Boost Conv Slope Comp */ +#define CS35L35_BST_CONV_SW_FREQ 0x24 /* Boost Conv L BST SW Freq */ +#define CS35L35_CLASS_H_CTL 0x30 /* CLS H Control */ +#define CS35L35_CLASS_H_HEADRM_CTL 0x31 /* CLS H Headroom Ctl */ +#define CS35L35_CLASS_H_RELEASE_RATE 0x32 /* CLS H Release Rate */ +#define CS35L35_CLASS_H_FET_DRIVE_CTL 0x33 /* CLS H Weak FET Drive Ctl */ +#define CS35L35_CLASS_H_VP_CTL 0x34 /* CLS H VP Ctl */ +#define CS35L35_CLASS_H_STATUS 0x38 /* CLS H Status */ +#define CS35L35_VPBR_CTL 0x3A /* VPBR Ctl */ +#define CS35L35_VPBR_VOL_CTL 0x3B /* VPBR Volume Ctl */ +#define CS35L35_VPBR_TIMING_CTL 0x3C /* VPBR Timing Ctl */ +#define CS35L35_VPBR_MODE_VOL_CTL 0x3D /* VPBR Mode/Attack Vol Ctl */ +#define CS35L35_VPBR_ATTEN_STATUS 0x4B /* VPBR Attenuation Status */ +#define CS35L35_SPKR_MON_CTL 0x4E /* Speaker Monitoring Ctl */ +#define CS35L35_IMON_SCALE_CTL 0x51 /* IMON Scale Ctl */ +#define CS35L35_AUDIN_RXLOC_CTL 0x52 /* Audio Input RX Loc Ctl */ +#define CS35L35_ADVIN_RXLOC_CTL 0x53 /* Advisory Input RX Loc Ctl */ +#define CS35L35_VMON_TXLOC_CTL 0x54 /* VMON TX Loc Ctl */ +#define CS35L35_IMON_TXLOC_CTL 0x55 /* IMON TX Loc Ctl */ +#define CS35L35_VPMON_TXLOC_CTL 0x56 /* VPMON TX Loc Ctl */ +#define CS35L35_VBSTMON_TXLOC_CTL 0x57 /* VBSTMON TX Loc Ctl */ +#define CS35L35_VPBR_STATUS_TXLOC_CTL 0x58 /* VPBR Status TX Loc Ctl */ +#define CS35L35_ZERO_FILL_LOC_CTL 0x59 /* Zero Fill Loc Ctl */ +#define CS35L35_AUDIN_DEPTH_CTL 0x5A /* Audio Input Depth Ctl */ +#define CS35L35_SPKMON_DEPTH_CTL 0x5B /* SPK Mon Output Depth Ctl */ +#define CS35L35_SUPMON_DEPTH_CTL 0x5C /* Supply Mon Out Depth Ctl */ +#define CS35L35_ZEROFILL_DEPTH_CTL 0x5D /* Zero Fill Mon Output Ctl */ +#define CS35L35_MULT_DEV_SYNCH1 0x62 /* Multidevice Synch */ +#define CS35L35_MULT_DEV_SYNCH2 0x63 /* Multidevice Synch 2 */ +#define CS35L35_PROT_RELEASE_CTL 0x64 /* Protection Release Ctl */ +#define CS35L35_DIAG_MODE_REG_LOCK 0x68 /* Diagnostic Mode Reg Lock */ +#define CS35L35_DIAG_MODE_CTL_1 0x69 /* Diagnostic Mode Ctl 1 */ +#define CS35L35_DIAG_MODE_CTL_2 0x6A /* Diagnostic Mode Ctl 2 */ +#define CS35L35_INT_MASK_1 0x70 /* Interrupt Mask 1 */ +#define CS35L35_INT_MASK_2 0x71 /* Interrupt Mask 2 */ +#define CS35L35_INT_MASK_3 0x72 /* Interrupt Mask 3 */ +#define CS35L35_INT_MASK_4 0x73 /* Interrupt Mask 4 */ +#define CS35L35_INT_STATUS_1 0x74 /* Interrupt Status 1 */ +#define CS35L35_INT_STATUS_2 0x75 /* Interrupt Status 2 */ +#define CS35L35_INT_STATUS_3 0x76 /* Interrupt Status 3 */ +#define CS35L35_INT_STATUS_4 0x77 /* Interrupt Status 4 */ +#define CS35L35_PLL_STATUS 0x78 /* PLL Status */ +#define CS35L35_OTP_TRIM_STATUS 0x7E /* OTP Trim Status */ + +#define CS35L35_MAX_REGISTER 0x7F + +/* CS35L35_PWRCTL1 */ +#define CS35L35_SFT_RST 0x80 +#define CS35L35_DISCHG_FLT 0x02 +#define CS35L35_PDN_ALL 0x01 + +/* CS35L35_PWRCTL2 */ +#define CS35L35_PDN_VMON 0x80 +#define CS35L35_PDN_IMON 0x40 +#define CS35L35_PDN_CLASSH 0x20 +#define CS35L35_PDN_VPBR 0x10 +#define CS35L35_PDN_BST 0x04 +#define CS35L35_PDN_AMP 0x01 + +/* CS35L35_PWRCTL3 */ +#define CS35L35_PDN_VBSTMON_OUT 0x10 +#define CS35L35_PDN_VMON_OUT 0x08 + +#define CS35L35_AUDIN_DEPTH_MASK 0x03 +#define CS35L35_AUDIN_DEPTH_SHIFT 0 +#define CS35L35_ADVIN_DEPTH_MASK 0x0C +#define CS35L35_ADVIN_DEPTH_SHIFT 2 +#define CS35L35_SDIN_DEPTH_8 0x01 +#define CS35L35_SDIN_DEPTH_16 0x02 +#define CS35L35_SDIN_DEPTH_24 0x03 + +#define CS35L35_SDOUT_DEPTH_8 0x01 +#define CS35L35_SDOUT_DEPTH_12 0x02 +#define CS35L35_SDOUT_DEPTH_16 0x03 + +#define CS35L35_AUD_IN_LR_MASK 0x80 +#define CS35L35_AUD_IN_LR_SHIFT 7 +#define CS35L35_ADV_IN_LR_MASK 0x80 +#define CS35L35_ADV_IN_LR_SHIFT 7 +#define CS35L35_AUD_IN_LOC_MASK 0x0F +#define CS35L35_AUD_IN_LOC_SHIFT 0 +#define CS35L35_ADV_IN_LOC_MASK 0x0F +#define CS35L35_ADV_IN_LOC_SHIFT 0 + +#define CS35L35_IMON_DEPTH_MASK 0x03 +#define CS35L35_IMON_DEPTH_SHIFT 0 +#define CS35L35_VMON_DEPTH_MASK 0x0C +#define CS35L35_VMON_DEPTH_SHIFT 2 +#define CS35L35_VBSTMON_DEPTH_MASK 0x03 +#define CS35L35_VBSTMON_DEPTH_SHIFT 0 +#define CS35L35_VPMON_DEPTH_MASK 0x0C +#define CS35L35_VPMON_DEPTH_SHIFT 2 +#define CS35L35_VPBRSTAT_DEPTH_MASK 0x30 +#define CS35L35_VPBRSTAT_DEPTH_SHIFT 4 +#define CS35L35_ZEROFILL_DEPTH_MASK 0x03 +#define CS35L35_ZEROFILL_DEPTH_SHIFT 0x00 + +#define CS35L35_MON_TXLOC_MASK 0x3F +#define CS35L35_MON_TXLOC_SHIFT 0 +#define CS35L35_MON_FRM_MASK 0x80 +#define CS35L35_MON_FRM_SHIFT 7 + +#define CS35L35_IMON_SCALE_MASK 0xF8 +#define CS35L35_IMON_SCALE_SHIFT 3 + +#define CS35L35_MS_MASK 0x80 +#define CS35L35_MS_SHIFT 7 +#define CS35L35_SPMODE_MASK 0x40 +#define CS35L35_SP_DRV_MASK 0x10 +#define CS35L35_SP_DRV_SHIFT 4 +#define CS35L35_CLK_CTL2_MASK 0xFF +#define CS35L35_PDM_MODE_MASK 0x40 +#define CS35L35_PDM_MODE_SHIFT 6 +#define CS35L35_CLK_SOURCE_MASK 0x03 +#define CS35L35_CLK_SOURCE_SHIFT 0 +#define CS35L35_CLK_SOURCE_MCLK 0 +#define CS35L35_CLK_SOURCE_SCLK 1 +#define CS35L35_CLK_SOURCE_PDM 2 + +#define CS35L35_SP_SCLKS_MASK 0x0F +#define CS35L35_SP_SCLKS_SHIFT 0x00 +#define CS35L35_SP_SCLKS_16FS 0x03 +#define CS35L35_SP_SCLKS_32FS 0x07 +#define CS35L35_SP_SCLKS_48FS 0x0B +#define CS35L35_SP_SCLKS_64FS 0x0F +#define CS35L35_SP_RATE_MASK 0xC0 + +#define CS35L35_PDN_BST_MASK 0x06 +#define CS35L35_PDN_BST_FETON_SHIFT 1 +#define CS35L35_PDN_BST_FETOFF_SHIFT 2 +#define CS35L35_PWR2_PDN_MASK 0xE0 +#define CS35L35_PWR3_PDN_MASK 0x1E +#define CS35L35_PDN_ALL_MASK 0x01 +#define CS35L35_DISCHG_FILT_MASK 0x02 +#define CS35L35_DISCHG_FILT_SHIFT 1 +#define CS35L35_MCLK_DIS_MASK 0x04 +#define CS35L35_MCLK_DIS_SHIFT 2 + +#define CS35L35_BST_CTL_MASK 0x7F +#define CS35L35_BST_CTL_SHIFT 0 +#define CS35L35_BST_IPK_MASK 0x1F +#define CS35L35_BST_IPK_SHIFT 0 +#define CS35L35_AMP_MUTE_MASK 0x20 +#define CS35L35_AMP_MUTE_SHIFT 5 +#define CS35L35_AMP_GAIN_ZC_MASK 0x10 +#define CS35L35_AMP_GAIN_ZC_SHIFT 4 + +#define CS35L35_AMP_DIGSFT_MASK 0x02 +#define CS35L35_AMP_DIGSFT_SHIFT 1 + +/* CS35L35_SP_FMT_CTL3 */ +#define CS35L35_SP_I2S_DRV_MASK 0x03 +#define CS35L35_SP_I2S_DRV_SHIFT 0 + +/* Class H Algorithm Control */ +#define CS35L35_CH_STEREO_MASK 0x40 +#define CS35L35_CH_STEREO_SHIFT 6 +#define CS35L35_CH_BST_OVR_MASK 0x04 +#define CS35L35_CH_BST_OVR_SHIFT 2 +#define CS35L35_CH_BST_LIM_MASK 0x08 +#define CS35L35_CH_BST_LIM_SHIFT 3 +#define CS35L35_CH_MEM_DEPTH_MASK 0x01 +#define CS35L35_CH_MEM_DEPTH_SHIFT 0 +#define CS35L35_CH_HDRM_CTL_MASK 0x3F +#define CS35L35_CH_HDRM_CTL_SHIFT 0 +#define CS35L35_CH_REL_RATE_MASK 0xFF +#define CS35L35_CH_REL_RATE_SHIFT 0 +#define CS35L35_CH_WKFET_DIS_MASK 0x80 +#define CS35L35_CH_WKFET_DIS_SHIFT 7 +#define CS35L35_CH_WKFET_DEL_MASK 0x70 +#define CS35L35_CH_WKFET_DEL_SHIFT 4 +#define CS35L35_CH_WKFET_THLD_MASK 0x0F +#define CS35L35_CH_WKFET_THLD_SHIFT 0 +#define CS35L35_CH_VP_AUTO_MASK 0x80 +#define CS35L35_CH_VP_AUTO_SHIFT 7 +#define CS35L35_CH_VP_RATE_MASK 0x60 +#define CS35L35_CH_VP_RATE_SHIFT 5 +#define CS35L35_CH_VP_MAN_MASK 0x1F +#define CS35L35_CH_VP_MAN_SHIFT 0 + +/* CS35L35_PROT_RELEASE_CTL */ +#define CS35L35_CAL_ERR_RLS 0x80 +#define CS35L35_SHORT_RLS 0x04 +#define CS35L35_OTW_RLS 0x02 +#define CS35L35_OTE_RLS 0x01 + +/* INT Mask Registers */ +#define CS35L35_INT1_CRIT_MASK 0x38 +#define CS35L35_INT2_CRIT_MASK 0xEF +#define CS35L35_INT3_CRIT_MASK 0xEE +#define CS35L35_INT4_CRIT_MASK 0xFF + +/* PDN DONE Masks */ +#define CS35L35_M_PDN_DONE_SHIFT 4 +#define CS35L35_M_PDN_DONE_MASK 0x10 + +/* CS35L35_INT_1 */ +#define CS35L35_CAL_ERR 0x80 +#define CS35L35_OTP_ERR 0x40 +#define CS35L35_LRCLK_ERR 0x20 +#define CS35L35_SPCLK_ERR 0x10 +#define CS35L35_MCLK_ERR 0x08 +#define CS35L35_AMP_SHORT 0x04 +#define CS35L35_OTW 0x02 +#define CS35L35_OTE 0x01 + +/* CS35L35_INT_2 */ +#define CS35L35_PDN_DONE 0x10 +#define CS35L35_VPBR_ERR 0x02 +#define CS35L35_VPBR_CLR 0x01 + +/* CS35L35_INT_3 */ +#define CS35L35_BST_HIGH 0x10 +#define CS35L35_BST_HIGH_FLAG 0x08 +#define CS35L35_BST_IPK_FLAG 0x04 +#define CS35L35_LBST_SHORT 0x01 + +/* CS35L35_INT_4 */ +#define CS35L35_VMON_OVFL 0x08 +#define CS35L35_IMON_OVFL 0x04 + +#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct cs35l35_private { + struct device *dev; + struct cs35l35_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[2]; + int num_supplies; + int sysclk; + int sclk; + bool pdm_mode; + bool i2s_mode; + bool slave_mode; + /* GPIO for /RST */ + struct gpio_desc *reset_gpio; + struct completion pdn_done; +}; + +static const char * const cs35l35_supplies[] = { + "VA", + "VP", +}; + +#endif diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index 8c0f3b89b5bc..e78b5f055f25 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -498,7 +498,7 @@ static int cs4271_reset(struct snd_soc_codec *codec) struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); if (gpio_is_valid(cs4271->gpio_nreset)) { - gpio_set_value(cs4271->gpio_nreset, 0); + gpio_direction_output(cs4271->gpio_nreset, 0); mdelay(1); gpio_set_value(cs4271->gpio_nreset, 1); mdelay(1); diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index cb47fb595ff4..1e0d5973b758 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -1130,6 +1130,7 @@ MODULE_DEVICE_TABLE(i2c, cs53l30_id); static struct i2c_driver cs53l30_i2c_driver = { .driver = { .name = "cs53l30", + .of_match_table = cs53l30_of_match, .pm = &cs53l30_runtime_pm, }, .id_table = cs53l30_id, diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 12da55882c06..6dd7578f0bb8 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -12,6 +12,7 @@ * option) any later version. */ +#include <linux/acpi.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/i2c.h> @@ -1528,12 +1529,23 @@ static int da7213_set_bias_level(struct snd_soc_codec *codec, return 0; } +#if defined(CONFIG_OF) /* DT */ static const struct of_device_id da7213_of_match[] = { { .compatible = "dlg,da7213", }, { } }; MODULE_DEVICE_TABLE(of, da7213_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id da7213_acpi_match[] = { + { "DLGS7212", 0}, + { "DLGS7213", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, da7213_acpi_match); +#endif static enum da7213_micbias_voltage da7213_of_micbias_lvl(struct snd_soc_codec *codec, u32 val) @@ -1844,6 +1856,7 @@ static struct i2c_driver da7213_i2c_driver = { .driver = { .name = "da7213", .of_match_table = of_match_ptr(da7213_of_match), + .acpi_match_table = ACPI_PTR(da7213_acpi_match), }, .probe = da7213_i2c_probe, .remove = da7213_remove, diff --git a/sound/soc/codecs/dio2125.c b/sound/soc/codecs/dio2125.c new file mode 100644 index 000000000000..09451cd44f9b --- /dev/null +++ b/sound/soc/codecs/dio2125.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <sound/soc.h> + +#define DRV_NAME "dio2125" + +struct dio2125 { + struct gpio_desc *gpiod_enable; +}; + +static int drv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct dio2125 *priv = snd_soc_component_get_drvdata(c); + int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 0; + break; + default: + WARN(1, "Unexpected event"); + return -EINVAL; + } + + gpiod_set_value_cansleep(priv->gpiod_enable, val); + + return 0; +} + +static const struct snd_soc_dapm_widget dio2125_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), + SND_SOC_DAPM_OUT_DRV_E("DRV", SND_SOC_NOPM, 0, 0, NULL, 0, drv_event, + (SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route dio2125_dapm_routes[] = { + { "DRV", NULL, "INL" }, + { "DRV", NULL, "INR" }, + { "OUTL", NULL, "DRV" }, + { "OUTR", NULL, "DRV" }, +}; + +static const struct snd_soc_component_driver dio2125_component_driver = { + .dapm_widgets = dio2125_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dio2125_dapm_widgets), + .dapm_routes = dio2125_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(dio2125_dapm_routes), +}; + +static int dio2125_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dio2125 *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_enable)) { + err = PTR_ERR(priv->gpiod_enable); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'enable' gpio: %d", err); + return err; + } + + return devm_snd_soc_register_component(dev, &dio2125_component_driver, + NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id dio2125_ids[] = { + { .compatible = "dioo,dio2125", }, + { } +}; +MODULE_DEVICE_TABLE(of, dio2125_ids); +#endif + +static struct platform_driver dio2125_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(dio2125_ids), + }, + .probe = dio2125_probe, +}; + +module_platform_driver(dio2125_driver); + +MODULE_DESCRIPTION("ASoC DIO2125 output driver"); +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es7134.c b/sound/soc/codecs/es7134.c new file mode 100644 index 000000000000..25ede825d349 --- /dev/null +++ b/sound/soc/codecs/es7134.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include <linux/module.h> +#include <sound/soc.h> + +/* + * The everest 7134 is a very simple DA converter with no register + */ + +static int es7134_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK | + SND_SOC_DAIFMT_MASTER_MASK); + + if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS)) { + dev_err(codec_dai->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops es7134_dai_ops = { + .set_fmt = es7134_set_fmt, +}; + +static struct snd_soc_dai_driver es7134_dai = { + .name = "es7134-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &es7134_dai_ops, +}; + +static const struct snd_soc_dapm_widget es7134_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route es7134_dapm_routes[] = { + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +static struct snd_soc_codec_driver es7134_codec_driver = { + .component_driver = { + .dapm_widgets = es7134_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es7134_dapm_widgets), + .dapm_routes = es7134_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es7134_dapm_routes), + }, +}; + +static int es7134_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, + &es7134_codec_driver, + &es7134_dai, 1); +} + +static int es7134_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id es7134_ids[] = { + { .compatible = "everest,es7134", }, + { .compatible = "everest,es7144", }, + { } +}; +MODULE_DEVICE_TABLE(of, es7134_ids); +#endif + +static struct platform_driver es7134_driver = { + .driver = { + .name = "es7134", + .of_match_table = of_match_ptr(es7134_ids), + }, + .probe = es7134_probe, + .remove = es7134_remove, +}; + +module_platform_driver(es7134_driver); + +MODULE_DESCRIPTION("ASoC ES7134 audio codec driver"); +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index 3f84fbd071e2..ed7cc42d1ee2 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -69,14 +69,10 @@ static const char * const supply_names[ES8328_SUPPLY_NUM] = { "HPVDD", }; -#define ES8328_RATES (SNDRV_PCM_RATE_96000 | \ - SNDRV_PCM_RATE_48000 | \ - SNDRV_PCM_RATE_44100 | \ - SNDRV_PCM_RATE_32000 | \ - SNDRV_PCM_RATE_22050 | \ - SNDRV_PCM_RATE_16000 | \ - SNDRV_PCM_RATE_11025 | \ - SNDRV_PCM_RATE_8000) +#define ES8328_RATES (SNDRV_PCM_RATE_192000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_8000_48000) #define ES8328_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S18_3LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ @@ -91,6 +87,7 @@ struct es8328_priv { int mclkdiv2; const struct snd_pcm_hw_constraint_list *sysclk_constraints; const int *mclk_ratios; + bool master; struct regulator_bulk_data supplies[ES8328_SUPPLY_NUM]; }; @@ -469,7 +466,7 @@ static int es8328_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = dai->codec; struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); - if (es8328->sysclk_constraints) + if (es8328->master && es8328->sysclk_constraints) snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, es8328->sysclk_constraints); @@ -488,27 +485,34 @@ static int es8328_hw_params(struct snd_pcm_substream *substream, int wl; int ratio; - if (!es8328->sysclk_constraints) { - dev_err(codec->dev, "No MCLK configured\n"); - return -EINVAL; - } - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) reg = ES8328_DACCONTROL2; else reg = ES8328_ADCCONTROL5; - for (i = 0; i < es8328->sysclk_constraints->count; i++) - if (es8328->sysclk_constraints->list[i] == params_rate(params)) - break; + if (es8328->master) { + if (!es8328->sysclk_constraints) { + dev_err(codec->dev, "No MCLK configured\n"); + return -EINVAL; + } - if (i == es8328->sysclk_constraints->count) { - dev_err(codec->dev, "LRCLK %d unsupported with current clock\n", - params_rate(params)); - return -EINVAL; + for (i = 0; i < es8328->sysclk_constraints->count; i++) + if (es8328->sysclk_constraints->list[i] == + params_rate(params)) + break; + + if (i == es8328->sysclk_constraints->count) { + dev_err(codec->dev, + "LRCLK %d unsupported with current clock\n", + params_rate(params)); + return -EINVAL; + } + ratio = es8328->mclk_ratios[i]; + } else { + ratio = 0; + es8328->mclkdiv2 = 0; } - ratio = es8328->mclk_ratios[i]; snd_soc_update_bits(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MCLKDIV2, es8328->mclkdiv2 ? ES8328_MASTERMODE_MCLKDIV2 : 0); @@ -586,6 +590,7 @@ static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); u8 dac_mode = 0; u8 adc_mode = 0; @@ -595,11 +600,13 @@ static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai, snd_soc_update_bits(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MSC, ES8328_MASTERMODE_MSC); + es8328->master = true; break; case SND_SOC_DAIFMT_CBS_CFS: /* Slave serial port mode */ snd_soc_update_bits(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MSC, 0); + es8328->master = false; break; default: return -EINVAL; diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index fd272a40485b..bc2e74ff3b2d 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -469,7 +469,7 @@ static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream, format = snd_hdac_calc_stream_format(params_rate(hparams), params_channels(hparams), params_format(hparams), - 24, 0); + dai->driver->playback.sig_bits, 0); pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); if (!pcm) @@ -1419,8 +1419,8 @@ static int hdac_hdmi_create_dais(struct hdac_device *hdac, hdmi_dais[i].playback.rate_min = rate_min; hdmi_dais[i].playback.channels_min = 2; hdmi_dais[i].playback.channels_max = 2; + hdmi_dais[i].playback.sig_bits = bps; hdmi_dais[i].ops = &hdmi_dai_ops; - i++; } diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index 6cdf15ab46de..0247edc9c84e 100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c @@ -516,13 +516,13 @@ static const struct i2c_device_id max9867_i2c_id[] = { { "max9867", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, max9867_i2c_id); static const struct of_device_id max9867_of_match[] = { { .compatible = "maxim,max9867", }, { } }; - -MODULE_DEVICE_TABLE(i2c, max9867_i2c_id); +MODULE_DEVICE_TABLE(of, max9867_of_match); static const struct dev_pm_ops max9867_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(max9867_suspend, max9867_resume) diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c new file mode 100644 index 000000000000..b5ee29499e16 --- /dev/null +++ b/sound/soc/codecs/max98927.c @@ -0,0 +1,841 @@ +/* + * max98927.c -- MAX98927 ALSA Soc Audio driver + * + * Copyright (C) 2016 Maxim Integrated Products + * Author: Ryan Lee <ryans.lee@maximintegrated.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/cdev.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <sound/tlv.h> +#include "max98927.h" + +static struct reg_default max98927_reg[] = { + {MAX98927_R0001_INT_RAW1, 0x00}, + {MAX98927_R0002_INT_RAW2, 0x00}, + {MAX98927_R0003_INT_RAW3, 0x00}, + {MAX98927_R0004_INT_STATE1, 0x00}, + {MAX98927_R0005_INT_STATE2, 0x00}, + {MAX98927_R0006_INT_STATE3, 0x00}, + {MAX98927_R0007_INT_FLAG1, 0x00}, + {MAX98927_R0008_INT_FLAG2, 0x00}, + {MAX98927_R0009_INT_FLAG3, 0x00}, + {MAX98927_R000A_INT_EN1, 0x00}, + {MAX98927_R000B_INT_EN2, 0x00}, + {MAX98927_R000C_INT_EN3, 0x00}, + {MAX98927_R000D_INT_FLAG_CLR1, 0x00}, + {MAX98927_R000E_INT_FLAG_CLR2, 0x00}, + {MAX98927_R000F_INT_FLAG_CLR3, 0x00}, + {MAX98927_R0010_IRQ_CTRL, 0x00}, + {MAX98927_R0011_CLK_MON, 0x00}, + {MAX98927_R0012_WDOG_CTRL, 0x00}, + {MAX98927_R0013_WDOG_RST, 0x00}, + {MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH, 0x00}, + {MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH, 0x00}, + {MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS, 0x00}, + {MAX98927_R0017_PIN_CFG, 0x55}, + {MAX98927_R0018_PCM_RX_EN_A, 0x00}, + {MAX98927_R0019_PCM_RX_EN_B, 0x00}, + {MAX98927_R001A_PCM_TX_EN_A, 0x00}, + {MAX98927_R001B_PCM_TX_EN_B, 0x00}, + {MAX98927_R001C_PCM_TX_HIZ_CTRL_A, 0x00}, + {MAX98927_R001D_PCM_TX_HIZ_CTRL_B, 0x00}, + {MAX98927_R001E_PCM_TX_CH_SRC_A, 0x00}, + {MAX98927_R001F_PCM_TX_CH_SRC_B, 0x00}, + {MAX98927_R0020_PCM_MODE_CFG, 0x40}, + {MAX98927_R0021_PCM_MASTER_MODE, 0x00}, + {MAX98927_R0022_PCM_CLK_SETUP, 0x22}, + {MAX98927_R0023_PCM_SR_SETUP1, 0x00}, + {MAX98927_R0024_PCM_SR_SETUP2, 0x00}, + {MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, 0x00}, + {MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, 0x00}, + {MAX98927_R0027_ICC_RX_EN_A, 0x00}, + {MAX98927_R0028_ICC_RX_EN_B, 0x00}, + {MAX98927_R002B_ICC_TX_EN_A, 0x00}, + {MAX98927_R002C_ICC_TX_EN_B, 0x00}, + {MAX98927_R002E_ICC_HIZ_MANUAL_MODE, 0x00}, + {MAX98927_R002F_ICC_TX_HIZ_EN_A, 0x00}, + {MAX98927_R0030_ICC_TX_HIZ_EN_B, 0x00}, + {MAX98927_R0031_ICC_LNK_EN, 0x00}, + {MAX98927_R0032_PDM_TX_EN, 0x00}, + {MAX98927_R0033_PDM_TX_HIZ_CTRL, 0x00}, + {MAX98927_R0034_PDM_TX_CTRL, 0x00}, + {MAX98927_R0035_PDM_RX_CTRL, 0x00}, + {MAX98927_R0036_AMP_VOL_CTRL, 0x00}, + {MAX98927_R0037_AMP_DSP_CFG, 0x02}, + {MAX98927_R0038_TONE_GEN_DC_CFG, 0x00}, + {MAX98927_R0039_DRE_CTRL, 0x01}, + {MAX98927_R003A_AMP_EN, 0x00}, + {MAX98927_R003B_SPK_SRC_SEL, 0x00}, + {MAX98927_R003C_SPK_GAIN, 0x00}, + {MAX98927_R003D_SSM_CFG, 0x01}, + {MAX98927_R003E_MEAS_EN, 0x00}, + {MAX98927_R003F_MEAS_DSP_CFG, 0x04}, + {MAX98927_R0040_BOOST_CTRL0, 0x00}, + {MAX98927_R0041_BOOST_CTRL3, 0x00}, + {MAX98927_R0042_BOOST_CTRL1, 0x00}, + {MAX98927_R0043_MEAS_ADC_CFG, 0x00}, + {MAX98927_R0044_MEAS_ADC_BASE_MSB, 0x00}, + {MAX98927_R0045_MEAS_ADC_BASE_LSB, 0x00}, + {MAX98927_R0046_ADC_CH0_DIVIDE, 0x00}, + {MAX98927_R0047_ADC_CH1_DIVIDE, 0x00}, + {MAX98927_R0048_ADC_CH2_DIVIDE, 0x00}, + {MAX98927_R0049_ADC_CH0_FILT_CFG, 0x00}, + {MAX98927_R004A_ADC_CH1_FILT_CFG, 0x00}, + {MAX98927_R004B_ADC_CH2_FILT_CFG, 0x00}, + {MAX98927_R004C_MEAS_ADC_CH0_READ, 0x00}, + {MAX98927_R004D_MEAS_ADC_CH1_READ, 0x00}, + {MAX98927_R004E_MEAS_ADC_CH2_READ, 0x00}, + {MAX98927_R0051_BROWNOUT_STATUS, 0x00}, + {MAX98927_R0052_BROWNOUT_EN, 0x00}, + {MAX98927_R0053_BROWNOUT_INFINITE_HOLD, 0x00}, + {MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR, 0x00}, + {MAX98927_R0055_BROWNOUT_LVL_HOLD, 0x00}, + {MAX98927_R005A_BROWNOUT_LVL1_THRESH, 0x00}, + {MAX98927_R005B_BROWNOUT_LVL2_THRESH, 0x00}, + {MAX98927_R005C_BROWNOUT_LVL3_THRESH, 0x00}, + {MAX98927_R005D_BROWNOUT_LVL4_THRESH, 0x00}, + {MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS, 0x00}, + {MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL, 0x00}, + {MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL, 0x00}, + {MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE, 0x00}, + {MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT, 0x00}, + {MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1, 0x00}, + {MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2, 0x00}, + {MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3, 0x00}, + {MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT, 0x00}, + {MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1, 0x00}, + {MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2, 0x00}, + {MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3, 0x00}, + {MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT, 0x00}, + {MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1, 0x00}, + {MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2, 0x00}, + {MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3, 0x00}, + {MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT, 0x00}, + {MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1, 0x00}, + {MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2, 0x00}, + {MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3, 0x00}, + {MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM, 0x00}, + {MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY, 0x00}, + {MAX98927_R0084_ENV_TRACK_REL_RATE, 0x00}, + {MAX98927_R0085_ENV_TRACK_HOLD_RATE, 0x00}, + {MAX98927_R0086_ENV_TRACK_CTRL, 0x00}, + {MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ, 0x00}, + {MAX98927_R00FF_GLOBAL_SHDN, 0x00}, + {MAX98927_R0100_SOFT_RESET, 0x00}, + {MAX98927_R01FF_REV_ID, 0x40}, +}; + +static int max98927_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + unsigned int mode = 0; + unsigned int format = 0; + unsigned int invert = 0; + + dev_dbg(codec->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + mode = MAX98927_PCM_MASTER_MODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + max98927->master = true; + mode = MAX98927_PCM_MASTER_MODE_MASTER; + break; + default: + dev_err(codec->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0021_PCM_MASTER_MODE, + MAX98927_PCM_MASTER_MODE_MASK, + mode); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE; + break; + default: + dev_err(codec->dev, "DAI invert mode unsupported"); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE, + invert); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + max98927->iface |= SND_SOC_DAIFMT_I2S; + format = MAX98927_PCM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + max98927->iface |= SND_SOC_DAIFMT_LEFT_J; + format = MAX98927_PCM_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_PDM: + max98927->iface |= SND_SOC_DAIFMT_PDM; + break; + default: + return -EINVAL; + } + + /* pcm channel configuration */ + if (max98927->iface & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) { + regmap_update_bits(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN); + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_FORMAT_MASK, + format << MAX98927_PCM_MODE_CFG_FORMAT_SHIFT); + + regmap_update_bits(max98927->regmap, + MAX98927_R003B_SPK_SRC_SEL, + MAX98927_SPK_SRC_MASK, 0); + + } else + regmap_update_bits(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN, 0); + + /* pdm channel configuration */ + if (max98927->iface & SND_SOC_DAIFMT_PDM) { + regmap_update_bits(max98927->regmap, + MAX98927_R0035_PDM_RX_CTRL, + MAX98927_PDM_RX_EN_MASK, 1); + + regmap_update_bits(max98927->regmap, + MAX98927_R003B_SPK_SRC_SEL, + MAX98927_SPK_SRC_MASK, 3); + } else + regmap_update_bits(max98927->regmap, + MAX98927_R0035_PDM_RX_CTRL, + MAX98927_PDM_RX_EN_MASK, 0); + return 0; +} + +/* codec MCLK rate in master mode */ +static const int rate_table[] = { + 5644800, 6000000, 6144000, 6500000, + 9600000, 11289600, 12000000, 12288000, + 13000000, 19200000, +}; + +static int max98927_set_clock(struct max98927_priv *max98927, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_codec *codec = max98927->codec; + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98927->ch_size; + int value; + + if (max98927->master) { + int i; + /* match rate to closest value */ + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i] >= max98927->sysclk) + break; + } + if (i == ARRAY_SIZE(rate_table)) { + dev_err(codec->dev, "failed to find proper clock rate.\n"); + return -EINVAL; + } + regmap_update_bits(max98927->regmap, + MAX98927_R0021_PCM_MASTER_MODE, + MAX98927_PCM_MASTER_MODE_MCLK_MASK, + i << MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT); + } + + switch (blr_clk_ratio) { + case 32: + value = 2; + break; + case 48: + value = 3; + break; + case 64: + value = 4; + break; + default: + return -EINVAL; + } + regmap_update_bits(max98927->regmap, + MAX98927_R0022_PCM_CLK_SETUP, + MAX98927_PCM_CLK_SETUP_BSEL_MASK, + value); + return 0; +} + +static int max98927_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + unsigned int sampling_rate = 0; + unsigned int chan_sz = 0; + + /* pcm mode configuration */ + switch (snd_pcm_format_width(params_format(params))) { + case 16: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(codec->dev, "format unsupported %d", + params_format(params)); + goto err; + } + + max98927->ch_size = snd_pcm_format_width(params_format(params)); + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + dev_dbg(codec->dev, "format supported %d", + params_format(params)); + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 8000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_8000; + break; + case 11025: + sampling_rate = MAX98927_PCM_SR_SET1_SR_11025; + break; + case 12000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_12000; + break; + case 16000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_16000; + break; + case 22050: + sampling_rate = MAX98927_PCM_SR_SET1_SR_22050; + break; + case 24000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_24000; + break; + case 32000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_32000; + break; + case 44100: + sampling_rate = MAX98927_PCM_SR_SET1_SR_44100; + break; + case 48000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_48000; + break; + default: + dev_err(codec->dev, "rate %d not supported\n", + params_rate(params)); + goto err; + } + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98927->regmap, + MAX98927_R0023_PCM_SR_SETUP1, + MAX98927_PCM_SR_SET1_SR_MASK, + sampling_rate); + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_SR_MASK, + sampling_rate << MAX98927_PCM_SR_SET2_SR_SHIFT); + + /* set sampling rate of IV */ + if (max98927->interleave_mode && + sampling_rate > MAX98927_PCM_SR_SET1_SR_16000) + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate - 3); + else + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate); + return max98927_set_clock(max98927, params); +err: + return -EINVAL; +} + +#define MAX98927_RATES SNDRV_PCM_RATE_8000_48000 + +#define MAX98927_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int max98927_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + + max98927->sysclk = freq; + return 0; +} + +static const struct snd_soc_dai_ops max98927_dai_ops = { + .set_sysclk = max98927_dai_set_sysclk, + .set_fmt = max98927_dai_set_fmt, + .hw_params = max98927_dai_hw_params, +}; + +static int max98927_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(max98927->regmap, + MAX98927_R003A_AMP_EN, + MAX98927_AMP_EN_MASK, 1); + /* enable VMON and IMON */ + regmap_update_bits(max98927->regmap, + MAX98927_R003E_MEAS_EN, + MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN, + MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN); + regmap_update_bits(max98927->regmap, + MAX98927_R00FF_GLOBAL_SHDN, + MAX98927_GLOBAL_EN_MASK, 1); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(max98927->regmap, + MAX98927_R00FF_GLOBAL_SHDN, + MAX98927_GLOBAL_EN_MASK, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R003A_AMP_EN, + MAX98927_AMP_EN_MASK, 0); + /* disable VMON and IMON */ + regmap_update_bits(max98927->regmap, + MAX98927_R003E_MEAS_EN, + MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN, 0); + break; + default: + return 0; + } + return 0; +} + +static const char * const max98927_switch_text[] = { + "Left", "Right", "LeftRight"}; + +static const struct soc_enum dai_sel_enum = + SOC_ENUM_SINGLE(MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, + MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT, + 3, max98927_switch_text); + +static const struct snd_kcontrol_new max98927_dai_controls = + SOC_DAPM_ENUM("DAI Sel", dai_sel_enum); + +static const struct snd_soc_dapm_widget max98927_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", MAX98927_R003A_AMP_EN, + 0, 0, max98927_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0, + &max98927_dai_controls), + SND_SOC_DAPM_OUTPUT("BE_OUT"), +}; + +static DECLARE_TLV_DB_SCALE(max98927_spk_tlv, 300, 300, 0); +static DECLARE_TLV_DB_SCALE(max98927_digital_tlv, -1600, 25, 0); + +static bool max98927_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98927_R0001_INT_RAW1 ... MAX98927_R0028_ICC_RX_EN_B: + case MAX98927_R002B_ICC_TX_EN_A ... MAX98927_R002C_ICC_TX_EN_B: + case MAX98927_R002E_ICC_HIZ_MANUAL_MODE + ... MAX98927_R004E_MEAS_ADC_CH2_READ: + case MAX98927_R0051_BROWNOUT_STATUS + ... MAX98927_R0055_BROWNOUT_LVL_HOLD: + case MAX98927_R005A_BROWNOUT_LVL1_THRESH + ... MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE: + case MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT + ... MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ: + case MAX98927_R00FF_GLOBAL_SHDN: + case MAX98927_R0100_SOFT_RESET: + case MAX98927_R01FF_REV_ID: + return true; + default: + return false; + } +}; + +static bool max98927_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98927_R0001_INT_RAW1 ... MAX98927_R0009_INT_FLAG3: + return true; + default: + return false; + } +} + +static const char * const max98927_boost_voltage_text[] = { + "6.5V", "6.625V", "6.75V", "6.875V", "7V", "7.125V", "7.25V", "7.375V", + "7.5V", "7.625V", "7.75V", "7.875V", "8V", "8.125V", "8.25V", "8.375V", + "8.5V", "8.625V", "8.75V", "8.875V", "9V", "9.125V", "9.25V", "9.375V", + "9.5V", "9.625V", "9.75V", "9.875V", "10V" +}; + +static SOC_ENUM_SINGLE_DECL(max98927_boost_voltage, + MAX98927_R0040_BOOST_CTRL0, 0, + max98927_boost_voltage_text); + +static const char * const max98927_current_limit_text[] = { + "1.00A", "1.10A", "1.20A", "1.30A", "1.40A", "1.50A", "1.60A", "1.70A", + "1.80A", "1.90A", "2.00A", "2.10A", "2.20A", "2.30A", "2.40A", "2.50A", + "2.60A", "2.70A", "2.80A", "2.90A", "3.00A", "3.10A", "3.20A", "3.30A", + "3.40A", "3.50A", "3.60A", "3.70A", "3.80A", "3.90A", "4.00A", "4.10A" +}; + +static SOC_ENUM_SINGLE_DECL(max98927_current_limit, + MAX98927_R0042_BOOST_CTRL1, 1, + max98927_current_limit_text); + +static const struct snd_kcontrol_new max98927_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98927_R003C_SPK_GAIN, + 0, 6, 0, + max98927_spk_tlv), + SOC_SINGLE_TLV("Digital Volume", MAX98927_R0036_AMP_VOL_CTRL, + 0, (1<<MAX98927_AMP_VOL_WIDTH)-1, 0, + max98927_digital_tlv), + SOC_SINGLE("Amp DSP Switch", MAX98927_R0052_BROWNOUT_EN, + MAX98927_BROWNOUT_DSP_SHIFT, 1, 0), + SOC_SINGLE("Ramp Switch", MAX98927_R0037_AMP_DSP_CFG, + MAX98927_AMP_DSP_CFG_RMP_SHIFT, 1, 0), + SOC_SINGLE("DRE Switch", MAX98927_R0039_DRE_CTRL, + MAX98927_DRE_EN_SHIFT, 1, 0), + SOC_SINGLE("Volume Location Switch", MAX98927_R0036_AMP_VOL_CTRL, + MAX98927_AMP_VOL_SEL_SHIFT, 1, 0), + SOC_ENUM("Boost Output Voltage", max98927_boost_voltage), + SOC_ENUM("Current Limit", max98927_current_limit), +}; + +static const struct snd_soc_dapm_route max98927_audio_map[] = { + {"Amp Enable", NULL, "DAI_OUT"}, + {"DAI Sel Mux", "Left", "Amp Enable"}, + {"DAI Sel Mux", "Right", "Amp Enable"}, + {"DAI Sel Mux", "LeftRight", "Amp Enable"}, + {"BE_OUT", NULL, "DAI Sel Mux"}, +}; + +static struct snd_soc_dai_driver max98927_dai[] = { + { + .name = "max98927-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98927_RATES, + .formats = MAX98927_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98927_RATES, + .formats = MAX98927_FORMATS, + }, + .ops = &max98927_dai_ops, + } +}; + +static int max98927_probe(struct snd_soc_codec *codec) +{ + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + + max98927->codec = codec; + codec->control_data = max98927->regmap; + codec->cache_bypass = 1; + + /* Software Reset */ + regmap_write(max98927->regmap, + MAX98927_R0100_SOFT_RESET, MAX98927_SOFT_RESET); + + /* IV default slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 0xFF); + regmap_write(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 0xFF); + regmap_write(max98927->regmap, + MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, + 0x80); + regmap_write(max98927->regmap, + MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, + 0x1); + /* Set inital volume (+13dB) */ + regmap_write(max98927->regmap, + MAX98927_R0036_AMP_VOL_CTRL, + 0x38); + regmap_write(max98927->regmap, + MAX98927_R003C_SPK_GAIN, + 0x05); + /* Enable DC blocker */ + regmap_write(max98927->regmap, + MAX98927_R0037_AMP_DSP_CFG, + 0x03); + /* Enable IMON VMON DC blocker */ + regmap_write(max98927->regmap, + MAX98927_R003F_MEAS_DSP_CFG, + 0xF7); + /* Boost Output Voltage & Current limit */ + regmap_write(max98927->regmap, + MAX98927_R0040_BOOST_CTRL0, + 0x1C); + regmap_write(max98927->regmap, + MAX98927_R0042_BOOST_CTRL1, + 0x3E); + /* Measurement ADC config */ + regmap_write(max98927->regmap, + MAX98927_R0043_MEAS_ADC_CFG, + 0x04); + regmap_write(max98927->regmap, + MAX98927_R0044_MEAS_ADC_BASE_MSB, + 0x00); + regmap_write(max98927->regmap, + MAX98927_R0045_MEAS_ADC_BASE_LSB, + 0x24); + /* Brownout Level */ + regmap_write(max98927->regmap, + MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1, + 0x06); + /* Envelope Tracking configuration */ + regmap_write(max98927->regmap, + MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM, + 0x08); + regmap_write(max98927->regmap, + MAX98927_R0086_ENV_TRACK_CTRL, + 0x01); + regmap_write(max98927->regmap, + MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ, + 0x10); + + /* voltage, current slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001E_PCM_TX_CH_SRC_A, + (max98927->i_l_slot<<MAX98927_PCM_TX_CH_SRC_A_I_SHIFT| + max98927->v_l_slot)&0xFF); + + if (max98927->v_l_slot < 8) { + regmap_update_bits(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 1 << max98927->v_l_slot, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + 1 << max98927->v_l_slot, + 1 << max98927->v_l_slot); + } else { + regmap_update_bits(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 1 << (max98927->v_l_slot - 8), 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + 1 << (max98927->v_l_slot - 8), + 1 << (max98927->v_l_slot - 8)); + } + + if (max98927->i_l_slot < 8) { + regmap_update_bits(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 1 << max98927->i_l_slot, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + 1 << max98927->i_l_slot, + 1 << max98927->i_l_slot); + } else { + regmap_update_bits(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 1 << (max98927->i_l_slot - 8), 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + 1 << (max98927->i_l_slot - 8), + 1 << (max98927->i_l_slot - 8)); + } + + /* Set interleave mode */ + if (max98927->interleave_mode) + regmap_update_bits(max98927->regmap, + MAX98927_R001F_PCM_TX_CH_SRC_B, + MAX98927_PCM_TX_CH_INTERLEAVE_MASK, + MAX98927_PCM_TX_CH_INTERLEAVE_MASK); + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_dev_max98927 = { + .probe = max98927_probe, + .component_driver = { + .controls = max98927_snd_controls, + .num_controls = ARRAY_SIZE(max98927_snd_controls), + .dapm_widgets = max98927_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98927_dapm_widgets), + .dapm_routes = max98927_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98927_audio_map), + }, +}; + +static const struct regmap_config max98927_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98927_R01FF_REV_ID, + .reg_defaults = max98927_reg, + .num_reg_defaults = ARRAY_SIZE(max98927_reg), + .readable_reg = max98927_readable_register, + .volatile_reg = max98927_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static void max98927_slot_config(struct i2c_client *i2c, + struct max98927_priv *max98927) +{ + int value; + + if (!of_property_read_u32(i2c->dev.of_node, + "vmon-slot-no", &value)) + max98927->v_l_slot = value & 0xF; + else + max98927->v_l_slot = 0; + if (!of_property_read_u32(i2c->dev.of_node, + "imon-slot-no", &value)) + max98927->i_l_slot = value & 0xF; + else + max98927->i_l_slot = 1; +} + +static int max98927_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + + int ret = 0, value; + int reg = 0; + struct max98927_priv *max98927 = NULL; + + max98927 = devm_kzalloc(&i2c->dev, + sizeof(*max98927), GFP_KERNEL); + + if (!max98927) { + ret = -ENOMEM; + return ret; + } + i2c_set_clientdata(i2c, max98927); + + /* update interleave mode info */ + if (!of_property_read_u32(i2c->dev.of_node, + "interleave_mode", &value)) { + if (value > 0) + max98927->interleave_mode = 1; + else + max98927->interleave_mode = 0; + } else + max98927->interleave_mode = 0; + + /* regmap initialization */ + max98927->regmap + = devm_regmap_init_i2c(i2c, &max98927_regmap); + if (IS_ERR(max98927->regmap)) { + ret = PTR_ERR(max98927->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + /* Check Revision ID */ + ret = regmap_read(max98927->regmap, + MAX98927_R01FF_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c->dev, + "Failed to read: 0x%02X\n", MAX98927_R01FF_REV_ID); + return ret; + } + dev_info(&i2c->dev, "MAX98927 revisionID: 0x%02X\n", reg); + + /* voltage/current slot configuration */ + max98927_slot_config(i2c, max98927); + + /* codec registeration */ + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98927, + max98927_dai, ARRAY_SIZE(max98927_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + + return ret; +} + +static int max98927_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id max98927_i2c_id[] = { + { "max98927", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max98927_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max98927_of_match[] = { + { .compatible = "maxim,max98927", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98927_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max98927_acpi_match[] = { + { "MX98927", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, max98927_acpi_match); +#endif + +static struct i2c_driver max98927_i2c_driver = { + .driver = { + .name = "max98927", + .of_match_table = of_match_ptr(max98927_of_match), + .acpi_match_table = ACPI_PTR(max98927_acpi_match), + .pm = NULL, + }, + .probe = max98927_i2c_probe, + .remove = max98927_i2c_remove, + .id_table = max98927_i2c_id, +}; + +module_i2c_driver(max98927_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98927 driver"); +MODULE_AUTHOR("Ryan Lee <ryans.lee@maximintegrated.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98927.h b/sound/soc/codecs/max98927.h new file mode 100644 index 000000000000..ece6a608cbe1 --- /dev/null +++ b/sound/soc/codecs/max98927.h @@ -0,0 +1,272 @@ +/* + * max98927.h -- MAX98927 ALSA Soc Audio driver + * + * Copyright 2013-15 Maxim Integrated Products + * Author: Ryan Lee <ryans.lee@maximintegrated.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#ifndef _MAX98927_H +#define _MAX98927_H + +/* Register Values */ +#define MAX98927_R0001_INT_RAW1 0x0001 +#define MAX98927_R0002_INT_RAW2 0x0002 +#define MAX98927_R0003_INT_RAW3 0x0003 +#define MAX98927_R0004_INT_STATE1 0x0004 +#define MAX98927_R0005_INT_STATE2 0x0005 +#define MAX98927_R0006_INT_STATE3 0x0006 +#define MAX98927_R0007_INT_FLAG1 0x0007 +#define MAX98927_R0008_INT_FLAG2 0x0008 +#define MAX98927_R0009_INT_FLAG3 0x0009 +#define MAX98927_R000A_INT_EN1 0x000A +#define MAX98927_R000B_INT_EN2 0x000B +#define MAX98927_R000C_INT_EN3 0x000C +#define MAX98927_R000D_INT_FLAG_CLR1 0x000D +#define MAX98927_R000E_INT_FLAG_CLR2 0x000E +#define MAX98927_R000F_INT_FLAG_CLR3 0x000F +#define MAX98927_R0010_IRQ_CTRL 0x0010 +#define MAX98927_R0011_CLK_MON 0x0011 +#define MAX98927_R0012_WDOG_CTRL 0x0012 +#define MAX98927_R0013_WDOG_RST 0x0013 +#define MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH 0x0014 +#define MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH 0x0015 +#define MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS 0x0016 +#define MAX98927_R0017_PIN_CFG 0x0017 +#define MAX98927_R0018_PCM_RX_EN_A 0x0018 +#define MAX98927_R0019_PCM_RX_EN_B 0x0019 +#define MAX98927_R001A_PCM_TX_EN_A 0x001A +#define MAX98927_R001B_PCM_TX_EN_B 0x001B +#define MAX98927_R001C_PCM_TX_HIZ_CTRL_A 0x001C +#define MAX98927_R001D_PCM_TX_HIZ_CTRL_B 0x001D +#define MAX98927_R001E_PCM_TX_CH_SRC_A 0x001E +#define MAX98927_R001F_PCM_TX_CH_SRC_B 0x001F +#define MAX98927_R0020_PCM_MODE_CFG 0x0020 +#define MAX98927_R0021_PCM_MASTER_MODE 0x0021 +#define MAX98927_R0022_PCM_CLK_SETUP 0x0022 +#define MAX98927_R0023_PCM_SR_SETUP1 0x0023 +#define MAX98927_R0024_PCM_SR_SETUP2 0x0024 +#define MAX98927_R0025_PCM_TO_SPK_MONOMIX_A 0x0025 +#define MAX98927_R0026_PCM_TO_SPK_MONOMIX_B 0x0026 +#define MAX98927_R0027_ICC_RX_EN_A 0x0027 +#define MAX98927_R0028_ICC_RX_EN_B 0x0028 +#define MAX98927_R002B_ICC_TX_EN_A 0x002B +#define MAX98927_R002C_ICC_TX_EN_B 0x002C +#define MAX98927_R002E_ICC_HIZ_MANUAL_MODE 0x002E +#define MAX98927_R002F_ICC_TX_HIZ_EN_A 0x002F +#define MAX98927_R0030_ICC_TX_HIZ_EN_B 0x0030 +#define MAX98927_R0031_ICC_LNK_EN 0x0031 +#define MAX98927_R0032_PDM_TX_EN 0x0032 +#define MAX98927_R0033_PDM_TX_HIZ_CTRL 0x0033 +#define MAX98927_R0034_PDM_TX_CTRL 0x0034 +#define MAX98927_R0035_PDM_RX_CTRL 0x0035 +#define MAX98927_R0036_AMP_VOL_CTRL 0x0036 +#define MAX98927_R0037_AMP_DSP_CFG 0x0037 +#define MAX98927_R0038_TONE_GEN_DC_CFG 0x0038 +#define MAX98927_R0039_DRE_CTRL 0x0039 +#define MAX98927_R003A_AMP_EN 0x003A +#define MAX98927_R003B_SPK_SRC_SEL 0x003B +#define MAX98927_R003C_SPK_GAIN 0x003C +#define MAX98927_R003D_SSM_CFG 0x003D +#define MAX98927_R003E_MEAS_EN 0x003E +#define MAX98927_R003F_MEAS_DSP_CFG 0x003F +#define MAX98927_R0040_BOOST_CTRL0 0x0040 +#define MAX98927_R0041_BOOST_CTRL3 0x0041 +#define MAX98927_R0042_BOOST_CTRL1 0x0042 +#define MAX98927_R0043_MEAS_ADC_CFG 0x0043 +#define MAX98927_R0044_MEAS_ADC_BASE_MSB 0x0044 +#define MAX98927_R0045_MEAS_ADC_BASE_LSB 0x0045 +#define MAX98927_R0046_ADC_CH0_DIVIDE 0x0046 +#define MAX98927_R0047_ADC_CH1_DIVIDE 0x0047 +#define MAX98927_R0048_ADC_CH2_DIVIDE 0x0048 +#define MAX98927_R0049_ADC_CH0_FILT_CFG 0x0049 +#define MAX98927_R004A_ADC_CH1_FILT_CFG 0x004A +#define MAX98927_R004B_ADC_CH2_FILT_CFG 0x004B +#define MAX98927_R004C_MEAS_ADC_CH0_READ 0x004C +#define MAX98927_R004D_MEAS_ADC_CH1_READ 0x004D +#define MAX98927_R004E_MEAS_ADC_CH2_READ 0x004E +#define MAX98927_R0051_BROWNOUT_STATUS 0x0051 +#define MAX98927_R0052_BROWNOUT_EN 0x0052 +#define MAX98927_R0053_BROWNOUT_INFINITE_HOLD 0x0053 +#define MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR 0x0054 +#define MAX98927_R0055_BROWNOUT_LVL_HOLD 0x0055 +#define MAX98927_R005A_BROWNOUT_LVL1_THRESH 0x005A +#define MAX98927_R005B_BROWNOUT_LVL2_THRESH 0x005B +#define MAX98927_R005C_BROWNOUT_LVL3_THRESH 0x005C +#define MAX98927_R005D_BROWNOUT_LVL4_THRESH 0x005D +#define MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS 0x005E +#define MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL 0x005F +#define MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL 0x0060 +#define MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE 0x0061 +#define MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT 0x0072 +#define MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1 0x0073 +#define MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2 0x0074 +#define MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3 0x0075 +#define MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT 0x0076 +#define MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1 0x0077 +#define MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2 0x0078 +#define MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3 0x0079 +#define MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT 0x007A +#define MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1 0x007B +#define MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2 0x007C +#define MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3 0x007D +#define MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT 0x007E +#define MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1 0x007F +#define MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2 0x0080 +#define MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3 0x0081 +#define MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM 0x0082 +#define MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY 0x0083 +#define MAX98927_R0084_ENV_TRACK_REL_RATE 0x0084 +#define MAX98927_R0085_ENV_TRACK_HOLD_RATE 0x0085 +#define MAX98927_R0086_ENV_TRACK_CTRL 0x0086 +#define MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ 0x0087 +#define MAX98927_R00FF_GLOBAL_SHDN 0x00FF +#define MAX98927_R0100_SOFT_RESET 0x0100 +#define MAX98927_R01FF_REV_ID 0x01FF + +/* MAX98927_R0018_PCM_RX_EN_A */ +#define MAX98927_PCM_RX_CH0_EN (0x1 << 0) +#define MAX98927_PCM_RX_CH1_EN (0x1 << 1) +#define MAX98927_PCM_RX_CH2_EN (0x1 << 2) +#define MAX98927_PCM_RX_CH3_EN (0x1 << 3) +#define MAX98927_PCM_RX_CH4_EN (0x1 << 4) +#define MAX98927_PCM_RX_CH5_EN (0x1 << 5) +#define MAX98927_PCM_RX_CH6_EN (0x1 << 6) +#define MAX98927_PCM_RX_CH7_EN (0x1 << 7) + +/* MAX98927_R001A_PCM_TX_EN_A */ +#define MAX98927_PCM_TX_CH0_EN (0x1 << 0) +#define MAX98927_PCM_TX_CH1_EN (0x1 << 1) +#define MAX98927_PCM_TX_CH2_EN (0x1 << 2) +#define MAX98927_PCM_TX_CH3_EN (0x1 << 3) +#define MAX98927_PCM_TX_CH4_EN (0x1 << 4) +#define MAX98927_PCM_TX_CH5_EN (0x1 << 5) +#define MAX98927_PCM_TX_CH6_EN (0x1 << 6) +#define MAX98927_PCM_TX_CH7_EN (0x1 << 7) + +/* MAX98927_R001E_PCM_TX_CH_SRC_A */ +#define MAX98927_PCM_TX_CH_SRC_A_V_SHIFT (0) +#define MAX98927_PCM_TX_CH_SRC_A_I_SHIFT (4) + +/* MAX98927_R001F_PCM_TX_CH_SRC_B */ +#define MAX98927_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 5) + +/* MAX98927_R0020_PCM_MODE_CFG */ +#define MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 2) +#define MAX98927_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3) +#define MAX98927_PCM_MODE_CFG_FORMAT_SHIFT (3) +#define MAX98927_PCM_FORMAT_I2S (0x0 << 0) +#define MAX98927_PCM_FORMAT_LJ (0x1 << 0) + +#define MAX98927_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6) + +/* MAX98927_R0021_PCM_MASTER_MODE */ +#define MAX98927_PCM_MASTER_MODE_MASK (0x3 << 0) +#define MAX98927_PCM_MASTER_MODE_SLAVE (0x0 << 0) +#define MAX98927_PCM_MASTER_MODE_MASTER (0x3 << 0) + +#define MAX98927_PCM_MASTER_MODE_MCLK_MASK (0xF << 2) +#define MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT (2) + +/* MAX98927_R0022_PCM_CLK_SETUP */ +#define MAX98927_PCM_CLK_SETUP_BSEL_MASK (0xF << 0) + +/* MAX98927_R0023_PCM_SR_SETUP1 */ +#define MAX98927_PCM_SR_SET1_SR_MASK (0xF << 0) + +#define MAX98927_PCM_SR_SET1_SR_8000 (0x0 << 0) +#define MAX98927_PCM_SR_SET1_SR_11025 (0x1 << 0) +#define MAX98927_PCM_SR_SET1_SR_12000 (0x2 << 0) +#define MAX98927_PCM_SR_SET1_SR_16000 (0x3 << 0) +#define MAX98927_PCM_SR_SET1_SR_22050 (0x4 << 0) +#define MAX98927_PCM_SR_SET1_SR_24000 (0x5 << 0) +#define MAX98927_PCM_SR_SET1_SR_32000 (0x6 << 0) +#define MAX98927_PCM_SR_SET1_SR_44100 (0x7 << 0) +#define MAX98927_PCM_SR_SET1_SR_48000 (0x8 << 0) + +/* MAX98927_R0024_PCM_SR_SETUP2 */ +#define MAX98927_PCM_SR_SET2_SR_MASK (0xF << 4) +#define MAX98927_PCM_SR_SET2_SR_SHIFT (4) +#define MAX98927_PCM_SR_SET2_IVADC_SR_MASK (0xf << 0) + +/* MAX98927_R0025_PCM_TO_SPK_MONOMIX_A */ +#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_MASK (0x3 << 6) +#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT (6) + +/* MAX98927_R0035_PDM_RX_CTRL */ +#define MAX98927_PDM_RX_EN_MASK (0x1 << 0) + +/* MAX98927_R0036_AMP_VOL_CTRL */ +#define MAX98927_AMP_VOL_SEL (0x1 << 7) +#define MAX98927_AMP_VOL_SEL_WIDTH (1) +#define MAX98927_AMP_VOL_SEL_SHIFT (7) +#define MAX98927_AMP_VOL_MASK (0x7f << 0) +#define MAX98927_AMP_VOL_WIDTH (7) +#define MAX98927_AMP_VOL_SHIFT (0) + +/* MAX98927_R0037_AMP_DSP_CFG */ +#define MAX98927_AMP_DSP_CFG_DCBLK_EN (0x1 << 0) +#define MAX98927_AMP_DSP_CFG_DITH_EN (0x1 << 1) +#define MAX98927_AMP_DSP_CFG_RMP_BYPASS (0x1 << 4) +#define MAX98927_AMP_DSP_CFG_DAC_INV (0x1 << 5) +#define MAX98927_AMP_DSP_CFG_RMP_SHIFT (4) + +/* MAX98927_R0039_DRE_CTRL */ +#define MAX98927_DRE_CTRL_DRE_EN (0x1 << 0) +#define MAX98927_DRE_EN_SHIFT 0x1 + +/* MAX98927_R003A_AMP_EN */ +#define MAX98927_AMP_EN_MASK (0x1 << 0) + +/* MAX98927_R003B_SPK_SRC_SEL */ +#define MAX98927_SPK_SRC_MASK (0x3 << 0) + +/* MAX98927_R003C_SPK_GAIN */ +#define MAX98927_SPK_PCM_GAIN_MASK (0x7 << 0) +#define MAX98927_SPK_PDM_GAIN_MASK (0x7 << 4) +#define MAX98927_SPK_GAIN_WIDTH (3) + +/* MAX98927_R003E_MEAS_EN */ +#define MAX98927_MEAS_V_EN (0x1 << 0) +#define MAX98927_MEAS_I_EN (0x1 << 1) + +/* MAX98927_R0040_BOOST_CTRL0 */ +#define MAX98927_BOOST_CTRL0_VOUT_MASK (0x1f << 0) +#define MAX98927_BOOST_CTRL0_PVDD_MASK (0x1 << 7) +#define MAX98927_BOOST_CTRL0_PVDD_EN_SHIFT (7) + +/* MAX98927_R0052_BROWNOUT_EN */ +#define MAX98927_BROWNOUT_BDE_EN (0x1 << 0) +#define MAX98927_BROWNOUT_AMP_EN (0x1 << 1) +#define MAX98927_BROWNOUT_DSP_EN (0x1 << 2) +#define MAX98927_BROWNOUT_DSP_SHIFT (2) + +/* MAX98927_R0100_SOFT_RESET */ +#define MAX98927_SOFT_RESET (0x1 << 0) + +/* MAX98927_R00FF_GLOBAL_SHDN */ +#define MAX98927_GLOBAL_EN_MASK (0x1 << 0) + +struct max98927_priv { + struct regmap *regmap; + struct snd_soc_codec *codec; + struct max98927_pdata *pdata; + unsigned int spk_gain; + unsigned int sysclk; + unsigned int v_l_slot; + unsigned int i_l_slot; + bool interleave_mode; + unsigned int ch_size; + unsigned int rate; + unsigned int iface; + unsigned int master; + unsigned int digital_gain; +}; +#endif diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index 9e8f0f4aa51a..c8bcb1db966d 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -39,147 +39,147 @@ /* scaling for mclk from sysclk_src output */ static const struct nau8540_fll_attr mclk_src_scaling[] = { - { 1, 0x0 }, - { 2, 0x2 }, - { 4, 0x3 }, - { 8, 0x4 }, - { 16, 0x5 }, - { 32, 0x6 }, - { 3, 0x7 }, - { 6, 0xa }, - { 12, 0xb }, - { 24, 0xc }, + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, }; /* ratio for input clk freq */ static const struct nau8540_fll_attr fll_ratio[] = { - { 512000, 0x01 }, - { 256000, 0x02 }, - { 128000, 0x04 }, - { 64000, 0x08 }, - { 32000, 0x10 }, - { 8000, 0x20 }, - { 4000, 0x40 }, + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, }; static const struct nau8540_fll_attr fll_pre_scalar[] = { - { 1, 0x0 }, - { 2, 0x1 }, - { 4, 0x2 }, - { 8, 0x3 }, + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, }; /* over sampling rate */ static const struct nau8540_osr_attr osr_adc_sel[] = { - { 32, 3 }, /* OSR 32, SRC 1/8 */ - { 64, 2 }, /* OSR 64, SRC 1/4 */ - { 128, 1 }, /* OSR 128, SRC 1/2 */ - { 256, 0 }, /* OSR 256, SRC 1 */ + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ }; static const struct reg_default nau8540_reg_defaults[] = { - {NAU8540_REG_POWER_MANAGEMENT, 0x0000}, - {NAU8540_REG_CLOCK_CTRL, 0x0000}, - {NAU8540_REG_CLOCK_SRC, 0x0000}, - {NAU8540_REG_FLL1, 0x0001}, - {NAU8540_REG_FLL2, 0x3126}, - {NAU8540_REG_FLL3, 0x0008}, - {NAU8540_REG_FLL4, 0x0010}, - {NAU8540_REG_FLL5, 0xC000}, - {NAU8540_REG_FLL6, 0x6000}, - {NAU8540_REG_FLL_VCO_RSV, 0xF13C}, - {NAU8540_REG_PCM_CTRL0, 0x000B}, - {NAU8540_REG_PCM_CTRL1, 0x3010}, - {NAU8540_REG_PCM_CTRL2, 0x0800}, - {NAU8540_REG_PCM_CTRL3, 0x0000}, - {NAU8540_REG_PCM_CTRL4, 0x000F}, - {NAU8540_REG_ALC_CONTROL_1, 0x0000}, - {NAU8540_REG_ALC_CONTROL_2, 0x700B}, - {NAU8540_REG_ALC_CONTROL_3, 0x0022}, - {NAU8540_REG_ALC_CONTROL_4, 0x1010}, - {NAU8540_REG_ALC_CONTROL_5, 0x1010}, - {NAU8540_REG_NOTCH_FIL1_CH1, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH1, 0x0000}, - {NAU8540_REG_NOTCH_FIL1_CH2, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH2, 0x0000}, - {NAU8540_REG_NOTCH_FIL1_CH3, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH3, 0x0000}, - {NAU8540_REG_NOTCH_FIL1_CH4, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH4, 0x0000}, - {NAU8540_REG_HPF_FILTER_CH12, 0x0000}, - {NAU8540_REG_HPF_FILTER_CH34, 0x0000}, - {NAU8540_REG_ADC_SAMPLE_RATE, 0x0002}, - {NAU8540_REG_DIGITAL_GAIN_CH1, 0x0400}, - {NAU8540_REG_DIGITAL_GAIN_CH2, 0x0400}, - {NAU8540_REG_DIGITAL_GAIN_CH3, 0x0400}, - {NAU8540_REG_DIGITAL_GAIN_CH4, 0x0400}, - {NAU8540_REG_DIGITAL_MUX, 0x00E4}, - {NAU8540_REG_GPIO_CTRL, 0x0000}, - {NAU8540_REG_MISC_CTRL, 0x0000}, - {NAU8540_REG_I2C_CTRL, 0xEFFF}, - {NAU8540_REG_VMID_CTRL, 0x0000}, - {NAU8540_REG_MUTE, 0x0000}, - {NAU8540_REG_ANALOG_ADC1, 0x0011}, - {NAU8540_REG_ANALOG_ADC2, 0x0020}, - {NAU8540_REG_ANALOG_PWR, 0x0000}, - {NAU8540_REG_MIC_BIAS, 0x0004}, - {NAU8540_REG_REFERENCE, 0x0000}, - {NAU8540_REG_FEPGA1, 0x0000}, - {NAU8540_REG_FEPGA2, 0x0000}, - {NAU8540_REG_FEPGA3, 0x0101}, - {NAU8540_REG_FEPGA4, 0x0101}, - {NAU8540_REG_PWR, 0x0000}, + {NAU8540_REG_POWER_MANAGEMENT, 0x0000}, + {NAU8540_REG_CLOCK_CTRL, 0x0000}, + {NAU8540_REG_CLOCK_SRC, 0x0000}, + {NAU8540_REG_FLL1, 0x0001}, + {NAU8540_REG_FLL2, 0x3126}, + {NAU8540_REG_FLL3, 0x0008}, + {NAU8540_REG_FLL4, 0x0010}, + {NAU8540_REG_FLL5, 0xC000}, + {NAU8540_REG_FLL6, 0x6000}, + {NAU8540_REG_FLL_VCO_RSV, 0xF13C}, + {NAU8540_REG_PCM_CTRL0, 0x000B}, + {NAU8540_REG_PCM_CTRL1, 0x3010}, + {NAU8540_REG_PCM_CTRL2, 0x0800}, + {NAU8540_REG_PCM_CTRL3, 0x0000}, + {NAU8540_REG_PCM_CTRL4, 0x000F}, + {NAU8540_REG_ALC_CONTROL_1, 0x0000}, + {NAU8540_REG_ALC_CONTROL_2, 0x700B}, + {NAU8540_REG_ALC_CONTROL_3, 0x0022}, + {NAU8540_REG_ALC_CONTROL_4, 0x1010}, + {NAU8540_REG_ALC_CONTROL_5, 0x1010}, + {NAU8540_REG_NOTCH_FIL1_CH1, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH1, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH2, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH2, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH3, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH3, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH4, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH4, 0x0000}, + {NAU8540_REG_HPF_FILTER_CH12, 0x0000}, + {NAU8540_REG_HPF_FILTER_CH34, 0x0000}, + {NAU8540_REG_ADC_SAMPLE_RATE, 0x0002}, + {NAU8540_REG_DIGITAL_GAIN_CH1, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH2, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH3, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH4, 0x0400}, + {NAU8540_REG_DIGITAL_MUX, 0x00E4}, + {NAU8540_REG_GPIO_CTRL, 0x0000}, + {NAU8540_REG_MISC_CTRL, 0x0000}, + {NAU8540_REG_I2C_CTRL, 0xEFFF}, + {NAU8540_REG_VMID_CTRL, 0x0000}, + {NAU8540_REG_MUTE, 0x0000}, + {NAU8540_REG_ANALOG_ADC1, 0x0011}, + {NAU8540_REG_ANALOG_ADC2, 0x0020}, + {NAU8540_REG_ANALOG_PWR, 0x0000}, + {NAU8540_REG_MIC_BIAS, 0x0004}, + {NAU8540_REG_REFERENCE, 0x0000}, + {NAU8540_REG_FEPGA1, 0x0000}, + {NAU8540_REG_FEPGA2, 0x0000}, + {NAU8540_REG_FEPGA3, 0x0101}, + {NAU8540_REG_FEPGA4, 0x0101}, + {NAU8540_REG_PWR, 0x0000}, }; static bool nau8540_readable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case NAU8540_REG_POWER_MANAGEMENT ... NAU8540_REG_FLL_VCO_RSV: - case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: - case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: - case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ADC_SAMPLE_RATE: - case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: - case NAU8540_REG_P2P_CH1 ... NAU8540_REG_I2C_CTRL: - case NAU8540_REG_I2C_DEVICE_ID: - case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: - case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: - return true; - default: - return false; - } + switch (reg) { + case NAU8540_REG_POWER_MANAGEMENT ... NAU8540_REG_FLL_VCO_RSV: + case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: + case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: + case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ADC_SAMPLE_RATE: + case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: + case NAU8540_REG_P2P_CH1 ... NAU8540_REG_I2C_CTRL: + case NAU8540_REG_I2C_DEVICE_ID: + case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: + case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: + return true; + default: + return false; + } } static bool nau8540_writeable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case NAU8540_REG_SW_RESET ... NAU8540_REG_FLL_VCO_RSV: - case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: - case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: - case NAU8540_REG_NOTCH_FIL1_CH1 ... NAU8540_REG_ADC_SAMPLE_RATE: - case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: - case NAU8540_REG_GPIO_CTRL ... NAU8540_REG_I2C_CTRL: - case NAU8540_REG_RST: - case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: - case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: - return true; - default: - return false; - } + switch (reg) { + case NAU8540_REG_SW_RESET ... NAU8540_REG_FLL_VCO_RSV: + case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: + case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: + case NAU8540_REG_NOTCH_FIL1_CH1 ... NAU8540_REG_ADC_SAMPLE_RATE: + case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: + case NAU8540_REG_GPIO_CTRL ... NAU8540_REG_I2C_CTRL: + case NAU8540_REG_RST: + case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: + case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: + return true; + default: + return false; + } } static bool nau8540_volatile_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case NAU8540_REG_SW_RESET: - case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ALC_STATUS: - case NAU8540_REG_P2P_CH1 ... NAU8540_REG_PEAK_CH4: - case NAU8540_REG_I2C_DEVICE_ID: - case NAU8540_REG_RST: - return true; - default: - return false; - } + switch (reg) { + case NAU8540_REG_SW_RESET: + case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ALC_STATUS: + case NAU8540_REG_P2P_CH1 ... NAU8540_REG_PEAK_CH4: + case NAU8540_REG_I2C_DEVICE_ID: + case NAU8540_REG_RST: + return true; + default: + return false; + } } @@ -187,255 +187,255 @@ static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, -12800, 3600); static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); static const struct snd_kcontrol_new nau8540_snd_controls[] = { - SOC_SINGLE_TLV("Mic1 Volume", NAU8540_REG_DIGITAL_GAIN_CH1, - 0, 0x520, 0, adc_vol_tlv), - SOC_SINGLE_TLV("Mic2 Volume", NAU8540_REG_DIGITAL_GAIN_CH2, - 0, 0x520, 0, adc_vol_tlv), - SOC_SINGLE_TLV("Mic3 Volume", NAU8540_REG_DIGITAL_GAIN_CH3, - 0, 0x520, 0, adc_vol_tlv), - SOC_SINGLE_TLV("Mic4 Volume", NAU8540_REG_DIGITAL_GAIN_CH4, - 0, 0x520, 0, adc_vol_tlv), - - SOC_SINGLE_TLV("Frontend PGA1 Volume", NAU8540_REG_FEPGA3, - 0, 0x25, 0, fepga_gain_tlv), - SOC_SINGLE_TLV("Frontend PGA2 Volume", NAU8540_REG_FEPGA3, - 8, 0x25, 0, fepga_gain_tlv), - SOC_SINGLE_TLV("Frontend PGA3 Volume", NAU8540_REG_FEPGA4, - 0, 0x25, 0, fepga_gain_tlv), - SOC_SINGLE_TLV("Frontend PGA4 Volume", NAU8540_REG_FEPGA4, - 8, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Mic1 Volume", NAU8540_REG_DIGITAL_GAIN_CH1, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic2 Volume", NAU8540_REG_DIGITAL_GAIN_CH2, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic3 Volume", NAU8540_REG_DIGITAL_GAIN_CH3, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic4 Volume", NAU8540_REG_DIGITAL_GAIN_CH4, + 0, 0x520, 0, adc_vol_tlv), + + SOC_SINGLE_TLV("Frontend PGA1 Volume", NAU8540_REG_FEPGA3, + 0, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA2 Volume", NAU8540_REG_FEPGA3, + 8, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA3 Volume", NAU8540_REG_FEPGA4, + 0, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA4 Volume", NAU8540_REG_FEPGA4, + 8, 0x25, 0, fepga_gain_tlv), }; static const char * const adc_channel[] = { - "ADC channel 1", "ADC channel 2", "ADC channel 3", "ADC channel 4" + "ADC channel 1", "ADC channel 2", "ADC channel 3", "ADC channel 4" }; static SOC_ENUM_SINGLE_DECL( - digital_ch4_enum, NAU8540_REG_DIGITAL_MUX, 6, adc_channel); + digital_ch4_enum, NAU8540_REG_DIGITAL_MUX, 6, adc_channel); static const struct snd_kcontrol_new digital_ch4_mux = - SOC_DAPM_ENUM("Digital CH4 Select", digital_ch4_enum); + SOC_DAPM_ENUM("Digital CH4 Select", digital_ch4_enum); static SOC_ENUM_SINGLE_DECL( - digital_ch3_enum, NAU8540_REG_DIGITAL_MUX, 4, adc_channel); + digital_ch3_enum, NAU8540_REG_DIGITAL_MUX, 4, adc_channel); static const struct snd_kcontrol_new digital_ch3_mux = - SOC_DAPM_ENUM("Digital CH3 Select", digital_ch3_enum); + SOC_DAPM_ENUM("Digital CH3 Select", digital_ch3_enum); static SOC_ENUM_SINGLE_DECL( - digital_ch2_enum, NAU8540_REG_DIGITAL_MUX, 2, adc_channel); + digital_ch2_enum, NAU8540_REG_DIGITAL_MUX, 2, adc_channel); static const struct snd_kcontrol_new digital_ch2_mux = - SOC_DAPM_ENUM("Digital CH2 Select", digital_ch2_enum); + SOC_DAPM_ENUM("Digital CH2 Select", digital_ch2_enum); static SOC_ENUM_SINGLE_DECL( - digital_ch1_enum, NAU8540_REG_DIGITAL_MUX, 0, adc_channel); + digital_ch1_enum, NAU8540_REG_DIGITAL_MUX, 0, adc_channel); static const struct snd_kcontrol_new digital_ch1_mux = - SOC_DAPM_ENUM("Digital CH1 Select", digital_ch1_enum); + SOC_DAPM_ENUM("Digital CH1 Select", digital_ch1_enum); static const struct snd_soc_dapm_widget nau8540_dapm_widgets[] = { - SND_SOC_DAPM_SUPPLY("MICBIAS2", NAU8540_REG_MIC_BIAS, 11, 0, NULL, 0), - SND_SOC_DAPM_SUPPLY("MICBIAS1", NAU8540_REG_MIC_BIAS, 10, 0, NULL, 0), - - SND_SOC_DAPM_INPUT("MIC1"), - SND_SOC_DAPM_INPUT("MIC2"), - SND_SOC_DAPM_INPUT("MIC3"), - SND_SOC_DAPM_INPUT("MIC4"), - - SND_SOC_DAPM_PGA("Frontend PGA1", NAU8540_REG_PWR, 12, 0, NULL, 0), - SND_SOC_DAPM_PGA("Frontend PGA2", NAU8540_REG_PWR, 13, 0, NULL, 0), - SND_SOC_DAPM_PGA("Frontend PGA3", NAU8540_REG_PWR, 14, 0, NULL, 0), - SND_SOC_DAPM_PGA("Frontend PGA4", NAU8540_REG_PWR, 15, 0, NULL, 0), - - SND_SOC_DAPM_ADC("ADC1", NULL, - NAU8540_REG_POWER_MANAGEMENT, 0, 0), - SND_SOC_DAPM_ADC("ADC2", NULL, - NAU8540_REG_POWER_MANAGEMENT, 1, 0), - SND_SOC_DAPM_ADC("ADC3", NULL, - NAU8540_REG_POWER_MANAGEMENT, 2, 0), - SND_SOC_DAPM_ADC("ADC4", NULL, - NAU8540_REG_POWER_MANAGEMENT, 3, 0), - - SND_SOC_DAPM_PGA("ADC CH1", NAU8540_REG_ANALOG_PWR, 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ADC CH2", NAU8540_REG_ANALOG_PWR, 1, 0, NULL, 0), - SND_SOC_DAPM_PGA("ADC CH3", NAU8540_REG_ANALOG_PWR, 2, 0, NULL, 0), - SND_SOC_DAPM_PGA("ADC CH4", NAU8540_REG_ANALOG_PWR, 3, 0, NULL, 0), - - SND_SOC_DAPM_MUX("Digital CH4 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch4_mux), - SND_SOC_DAPM_MUX("Digital CH3 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch3_mux), - SND_SOC_DAPM_MUX("Digital CH2 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch2_mux), - SND_SOC_DAPM_MUX("Digital CH1 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch1_mux), - - SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", NAU8540_REG_MIC_BIAS, 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS1", NAU8540_REG_MIC_BIAS, 10, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("MIC3"), + SND_SOC_DAPM_INPUT("MIC4"), + + SND_SOC_DAPM_PGA("Frontend PGA1", NAU8540_REG_PWR, 12, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA2", NAU8540_REG_PWR, 13, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA3", NAU8540_REG_PWR, 14, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA4", NAU8540_REG_PWR, 15, 0, NULL, 0), + + SND_SOC_DAPM_ADC("ADC1", NULL, + NAU8540_REG_POWER_MANAGEMENT, 0, 0), + SND_SOC_DAPM_ADC("ADC2", NULL, + NAU8540_REG_POWER_MANAGEMENT, 1, 0), + SND_SOC_DAPM_ADC("ADC3", NULL, + NAU8540_REG_POWER_MANAGEMENT, 2, 0), + SND_SOC_DAPM_ADC("ADC4", NULL, + NAU8540_REG_POWER_MANAGEMENT, 3, 0), + + SND_SOC_DAPM_PGA("ADC CH1", NAU8540_REG_ANALOG_PWR, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH2", NAU8540_REG_ANALOG_PWR, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH3", NAU8540_REG_ANALOG_PWR, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH4", NAU8540_REG_ANALOG_PWR, 3, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Digital CH4 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch4_mux), + SND_SOC_DAPM_MUX("Digital CH3 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch3_mux), + SND_SOC_DAPM_MUX("Digital CH2 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch2_mux), + SND_SOC_DAPM_MUX("Digital CH1 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch1_mux), + + SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0), }; static const struct snd_soc_dapm_route nau8540_dapm_routes[] = { - {"Frontend PGA1", NULL, "MIC1"}, - {"Frontend PGA2", NULL, "MIC2"}, - {"Frontend PGA3", NULL, "MIC3"}, - {"Frontend PGA4", NULL, "MIC4"}, - - {"ADC1", NULL, "Frontend PGA1"}, - {"ADC2", NULL, "Frontend PGA2"}, - {"ADC3", NULL, "Frontend PGA3"}, - {"ADC4", NULL, "Frontend PGA4"}, - - {"ADC CH1", NULL, "ADC1"}, - {"ADC CH2", NULL, "ADC2"}, - {"ADC CH3", NULL, "ADC3"}, - {"ADC CH4", NULL, "ADC4"}, - - {"ADC1", NULL, "MICBIAS1"}, - {"ADC2", NULL, "MICBIAS1"}, - {"ADC3", NULL, "MICBIAS2"}, - {"ADC4", NULL, "MICBIAS2"}, - - {"Digital CH1 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH1 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH1 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH1 Mux", "ADC channel 4", "ADC CH4"}, - - {"Digital CH2 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH2 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH2 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH2 Mux", "ADC channel 4", "ADC CH4"}, - - {"Digital CH3 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH3 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH3 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH3 Mux", "ADC channel 4", "ADC CH4"}, - - {"Digital CH4 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH4 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH4 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH4 Mux", "ADC channel 4", "ADC CH4"}, - - {"AIFTX", NULL, "Digital CH1 Mux"}, - {"AIFTX", NULL, "Digital CH2 Mux"}, - {"AIFTX", NULL, "Digital CH3 Mux"}, - {"AIFTX", NULL, "Digital CH4 Mux"}, + {"Frontend PGA1", NULL, "MIC1"}, + {"Frontend PGA2", NULL, "MIC2"}, + {"Frontend PGA3", NULL, "MIC3"}, + {"Frontend PGA4", NULL, "MIC4"}, + + {"ADC1", NULL, "Frontend PGA1"}, + {"ADC2", NULL, "Frontend PGA2"}, + {"ADC3", NULL, "Frontend PGA3"}, + {"ADC4", NULL, "Frontend PGA4"}, + + {"ADC CH1", NULL, "ADC1"}, + {"ADC CH2", NULL, "ADC2"}, + {"ADC CH3", NULL, "ADC3"}, + {"ADC CH4", NULL, "ADC4"}, + + {"ADC1", NULL, "MICBIAS1"}, + {"ADC2", NULL, "MICBIAS1"}, + {"ADC3", NULL, "MICBIAS2"}, + {"ADC4", NULL, "MICBIAS2"}, + + {"Digital CH1 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH1 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH1 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH1 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH2 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH2 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH2 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH2 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH3 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH3 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH3 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH3 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH4 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH4 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH4 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH4 Mux", "ADC channel 4", "ADC CH4"}, + + {"AIFTX", NULL, "Digital CH1 Mux"}, + {"AIFTX", NULL, "Digital CH2 Mux"}, + {"AIFTX", NULL, "Digital CH3 Mux"}, + {"AIFTX", NULL, "Digital CH4 Mux"}, }; static int nau8540_clock_check(struct nau8540 *nau8540, int rate, int osr) { - int osrate; + int osrate; - if (osr >= ARRAY_SIZE(osr_adc_sel)) - return -EINVAL; - osrate = osr_adc_sel[osr].osr; + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; - if (rate * osr > CLK_ADC_MAX) { - dev_err(nau8540->dev, "exceed the maximum frequency of CLK_ADC\n"); - return -EINVAL; - } + if (rate * osr > CLK_ADC_MAX) { + dev_err(nau8540->dev, "exceed the maximum frequency of CLK_ADC\n"); + return -EINVAL; + } - return 0; + return 0; } static int nau8540_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_codec *codec = dai->codec; - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - unsigned int val_len = 0, osr; - - /* CLK_ADC = OSR * FS - * ADC clock frequency is defined as Over Sampling Rate (OSR) - * multiplied by the audio sample rate (Fs). Note that the OSR and Fs - * values must be selected such that the maximum frequency is less - * than 6.144 MHz. - */ - regmap_read(nau8540->regmap, NAU8540_REG_ADC_SAMPLE_RATE, &osr); - osr &= NAU8540_ADC_OSR_MASK; - if (nau8540_clock_check(nau8540, params_rate(params), osr)) - return -EINVAL; - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_ADC_SRC_MASK, - osr_adc_sel[osr].clk_src << NAU8540_CLK_ADC_SRC_SFT); - - switch (params_width(params)) { - case 16: - val_len |= NAU8540_I2S_DL_16; - break; - case 20: - val_len |= NAU8540_I2S_DL_20; - break; - case 24: - val_len |= NAU8540_I2S_DL_24; - break; - case 32: - val_len |= NAU8540_I2S_DL_32; - break; - default: - return -EINVAL; - } - - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, - NAU8540_I2S_DL_MASK, val_len); - - return 0; + struct snd_soc_codec *codec = dai->codec; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + unsigned int val_len = 0, osr; + + /* CLK_ADC = OSR * FS + * ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + regmap_read(nau8540->regmap, NAU8540_REG_ADC_SAMPLE_RATE, &osr); + osr &= NAU8540_ADC_OSR_MASK; + if (nau8540_clock_check(nau8540, params_rate(params), osr)) + return -EINVAL; + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8540_CLK_ADC_SRC_SFT); + + switch (params_width(params)) { + case 16: + val_len |= NAU8540_I2S_DL_16; + break; + case 20: + val_len |= NAU8540_I2S_DL_20; + break; + case 24: + val_len |= NAU8540_I2S_DL_24; + break; + case 32: + val_len |= NAU8540_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, + NAU8540_I2S_DL_MASK, val_len); + + return 0; } static int nau8540_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - struct snd_soc_codec *codec = dai->codec; - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - unsigned int ctrl1_val = 0, ctrl2_val = 0; - - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - ctrl2_val |= NAU8540_I2S_MS_MASTER; - break; - case SND_SOC_DAIFMT_CBS_CFS: - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - break; - case SND_SOC_DAIFMT_IB_NF: - ctrl1_val |= NAU8540_I2S_BP_INV; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - ctrl1_val |= NAU8540_I2S_DF_I2S; - break; - case SND_SOC_DAIFMT_LEFT_J: - ctrl1_val |= NAU8540_I2S_DF_LEFT; - break; - case SND_SOC_DAIFMT_RIGHT_J: - ctrl1_val |= NAU8540_I2S_DF_RIGTH; - break; - case SND_SOC_DAIFMT_DSP_A: - ctrl1_val |= NAU8540_I2S_DF_PCM_AB; - break; - case SND_SOC_DAIFMT_DSP_B: - ctrl1_val |= NAU8540_I2S_DF_PCM_AB; - ctrl1_val |= NAU8540_I2S_PCMB_EN; - break; - default: - return -EINVAL; - } - - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, - NAU8540_I2S_DL_MASK | NAU8540_I2S_DF_MASK | - NAU8540_I2S_BP_INV | NAU8540_I2S_PCMB_EN, ctrl1_val); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, - NAU8540_I2S_MS_MASK | NAU8540_I2S_DO12_OE, ctrl2_val); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, - NAU8540_I2S_DO34_OE, 0); - - return 0; + struct snd_soc_codec *codec = dai->codec; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8540_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8540_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8540_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8540_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8540_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8540_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8540_I2S_DF_PCM_AB; + ctrl1_val |= NAU8540_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, + NAU8540_I2S_DL_MASK | NAU8540_I2S_DF_MASK | + NAU8540_I2S_BP_INV | NAU8540_I2S_PCMB_EN, ctrl1_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_MS_MASK | NAU8540_I2S_DO12_OE, ctrl2_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_OE, 0); + + return 0; } /** @@ -451,55 +451,55 @@ static int nau8540_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) * Configures a DAI for TDM operation. Only support 4 slots TDM. */ static int nau8540_set_tdm_slot(struct snd_soc_dai *dai, - unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { - struct snd_soc_codec *codec = dai->codec; - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - unsigned int ctrl2_val = 0, ctrl4_val = 0; - - if (slots > 4 || ((tx_mask & 0xf0) && (tx_mask & 0xf))) - return -EINVAL; - - ctrl4_val |= (NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN); - if (tx_mask & 0xf0) { - ctrl2_val = 4 * slot_width; - ctrl4_val |= (tx_mask >> 4); - } else { - ctrl4_val |= tx_mask; - } - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL4, - NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN | - NAU8540_TDM_TX_MASK, ctrl4_val); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, - NAU8540_I2S_DO12_OE, NAU8540_I2S_DO12_OE); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, - NAU8540_I2S_DO34_OE | NAU8540_I2S_TSLOT_L_MASK, - NAU8540_I2S_DO34_OE | ctrl2_val); - - return 0; + struct snd_soc_codec *codec = dai->codec; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl2_val = 0, ctrl4_val = 0; + + if (slots > 4 || ((tx_mask & 0xf0) && (tx_mask & 0xf))) + return -EINVAL; + + ctrl4_val |= (NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN); + if (tx_mask & 0xf0) { + ctrl2_val = 4 * slot_width; + ctrl4_val |= (tx_mask >> 4); + } else { + ctrl4_val |= tx_mask; + } + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL4, + NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN | + NAU8540_TDM_TX_MASK, ctrl4_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_DO12_OE, NAU8540_I2S_DO12_OE); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_OE | NAU8540_I2S_TSLOT_L_MASK, + NAU8540_I2S_DO34_OE | ctrl2_val); + + return 0; } static const struct snd_soc_dai_ops nau8540_dai_ops = { - .hw_params = nau8540_hw_params, - .set_fmt = nau8540_set_fmt, - .set_tdm_slot = nau8540_set_tdm_slot, + .hw_params = nau8540_hw_params, + .set_fmt = nau8540_set_fmt, + .set_tdm_slot = nau8540_set_tdm_slot, }; #define NAU8540_RATES SNDRV_PCM_RATE_8000_48000 #define NAU8540_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ - | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) static struct snd_soc_dai_driver nau8540_dai = { - .name = "nau8540-hifi", - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 4, - .rates = NAU8540_RATES, - .formats = NAU8540_FORMATS, - }, - .ops = &nau8540_dai_ops, + .name = "nau8540-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = NAU8540_RATES, + .formats = NAU8540_FORMATS, + }, + .ops = &nau8540_dai_ops, }; /** @@ -513,320 +513,320 @@ static struct snd_soc_dai_driver nau8540_dai = { * Returns 0 for success or negative error code. */ static int nau8540_calc_fll_param(unsigned int fll_in, - unsigned int fs, struct nau8540_fll *fll_param) + unsigned int fs, struct nau8540_fll *fll_param) { - u64 fvco, fvco_max; - unsigned int fref, i, fvco_sel; - - /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing - * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. - * FREF = freq_in / NAU8540_FLL_REF_DIV_MASK - */ - for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { - fref = fll_in / fll_pre_scalar[i].param; - if (fref <= NAU_FREF_MAX) - break; - } - if (i == ARRAY_SIZE(fll_pre_scalar)) - return -EINVAL; - fll_param->clk_ref_div = fll_pre_scalar[i].val; - - /* Choose the FLL ratio based on FREF */ - for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { - if (fref >= fll_ratio[i].param) - break; - } - if (i == ARRAY_SIZE(fll_ratio)) - return -EINVAL; - fll_param->ratio = fll_ratio[i].val; - - /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. - * FDCO must be within the 90MHz - 124MHz or the FFL cannot be - * guaranteed across the full range of operation. - * FDCO = freq_out * 2 * mclk_src_scaling - */ - fvco_max = 0; - fvco_sel = ARRAY_SIZE(mclk_src_scaling); - for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { - fvco = 256 * fs * 2 * mclk_src_scaling[i].param; - if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && - fvco_max < fvco) { - fvco_max = fvco; - fvco_sel = i; - } - } - if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) - return -EINVAL; - fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; - - /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional - * input based on FDCO, FREF and FLL ratio. - */ - fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); - fll_param->fll_int = (fvco >> 16) & 0x3FF; - fll_param->fll_frac = fvco & 0xFFFF; - return 0; + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8540_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256 * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; } static void nau8540_fll_apply(struct regmap *regmap, - struct nau8540_fll *fll_param) + struct nau8540_fll *fll_param) { - regmap_update_bits(regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK | NAU8540_CLK_MCLK_SRC_MASK, - NAU8540_CLK_SRC_MCLK | fll_param->mclk_src); - regmap_update_bits(regmap, NAU8540_REG_FLL1, - NAU8540_FLL_RATIO_MASK, fll_param->ratio); - /* FLL 16-bit fractional input */ - regmap_write(regmap, NAU8540_REG_FLL2, fll_param->fll_frac); - /* FLL 10-bit integer input */ - regmap_update_bits(regmap, NAU8540_REG_FLL3, - NAU8540_FLL_INTEGER_MASK, fll_param->fll_int); - /* FLL pre-scaler */ - regmap_update_bits(regmap, NAU8540_REG_FLL4, - NAU8540_FLL_REF_DIV_MASK, - fll_param->clk_ref_div << NAU8540_FLL_REF_DIV_SFT); - regmap_update_bits(regmap, NAU8540_REG_FLL5, - NAU8540_FLL_CLK_SW_MASK, NAU8540_FLL_CLK_SW_REF); - regmap_update_bits(regmap, - NAU8540_REG_FLL6, NAU8540_DCO_EN, 0); - if (fll_param->fll_frac) { - regmap_update_bits(regmap, NAU8540_REG_FLL5, - NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | - NAU8540_FLL_FTR_SW_MASK, - NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | - NAU8540_FLL_FTR_SW_FILTER); - regmap_update_bits(regmap, NAU8540_REG_FLL6, - NAU8540_SDM_EN, NAU8540_SDM_EN); - } else { - regmap_update_bits(regmap, NAU8540_REG_FLL5, - NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | - NAU8540_FLL_FTR_SW_MASK, NAU8540_FLL_FTR_SW_ACCU); - regmap_update_bits(regmap, - NAU8540_REG_FLL6, NAU8540_SDM_EN, 0); - } + regmap_update_bits(regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK | NAU8540_CLK_MCLK_SRC_MASK, + NAU8540_CLK_SRC_MCLK | fll_param->mclk_src); + regmap_update_bits(regmap, NAU8540_REG_FLL1, + NAU8540_FLL_RATIO_MASK, fll_param->ratio); + /* FLL 16-bit fractional input */ + regmap_write(regmap, NAU8540_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8540_REG_FLL3, + NAU8540_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8540_REG_FLL4, + NAU8540_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8540_FLL_REF_DIV_SFT); + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_CLK_SW_MASK, NAU8540_FLL_CLK_SW_REF); + regmap_update_bits(regmap, + NAU8540_REG_FLL6, NAU8540_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_MASK, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8540_REG_FLL6, + NAU8540_SDM_EN, NAU8540_SDM_EN); + } else { + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_MASK, NAU8540_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, + NAU8540_REG_FLL6, NAU8540_SDM_EN, 0); + } } /* freq_out must be 256*Fs in order to achieve the best performance */ static int nau8540_set_pll(struct snd_soc_codec *codec, int pll_id, int source, - unsigned int freq_in, unsigned int freq_out) + unsigned int freq_in, unsigned int freq_out) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - struct nau8540_fll fll_param; - int ret, fs; - - switch (pll_id) { - case NAU8540_CLK_FLL_MCLK: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, - NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_MCLK); - break; - - case NAU8540_CLK_FLL_BLK: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, - NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_BLK); - break; - - case NAU8540_CLK_FLL_FS: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, - NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_FS); - break; - - default: - dev_err(nau8540->dev, "Invalid clock id (%d)\n", pll_id); - return -EINVAL; - } - dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", - freq_out, pll_id); - - fs = freq_out / 256; - ret = nau8540_calc_fll_param(freq_in, fs, &fll_param); - if (ret < 0) { - dev_err(nau8540->dev, "Unsupported input clock %d\n", freq_in); - return ret; - } - dev_dbg(nau8540->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", - fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, - fll_param.fll_int, fll_param.clk_ref_div); - - nau8540_fll_apply(nau8540->regmap, &fll_param); - mdelay(2); - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); - - return 0; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + struct nau8540_fll fll_param; + int ret, fs; + + switch (pll_id) { + case NAU8540_CLK_FLL_MCLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_MCLK); + break; + + case NAU8540_CLK_FLL_BLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_BLK); + break; + + case NAU8540_CLK_FLL_FS: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_FS); + break; + + default: + dev_err(nau8540->dev, "Invalid clock id (%d)\n", pll_id); + return -EINVAL; + } + dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", + freq_out, pll_id); + + fs = freq_out / 256; + ret = nau8540_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(nau8540->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(nau8540->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8540_fll_apply(nau8540->regmap, &fll_param); + mdelay(2); + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); + + return 0; } static int nau8540_set_sysclk(struct snd_soc_codec *codec, - int clk_id, int source, unsigned int freq, int dir) + int clk_id, int source, unsigned int freq, int dir) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - - switch (clk_id) { - case NAU8540_CLK_DIS: - case NAU8540_CLK_MCLK: - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_MCLK); - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, - NAU8540_DCO_EN, 0); - break; - - case NAU8540_CLK_INTERNAL: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, - NAU8540_DCO_EN, NAU8540_DCO_EN); - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); - break; - - default: - dev_err(nau8540->dev, "Invalid clock id (%d)\n", clk_id); - return -EINVAL; - } - - dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", - freq, clk_id); - - return 0; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case NAU8540_CLK_DIS: + case NAU8540_CLK_MCLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_MCLK); + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, + NAU8540_DCO_EN, 0); + break; + + case NAU8540_CLK_INTERNAL: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, + NAU8540_DCO_EN, NAU8540_DCO_EN); + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); + break; + + default: + dev_err(nau8540->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; } static void nau8540_reset_chip(struct regmap *regmap) { - regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); - regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); + regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); + regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); } static void nau8540_init_regs(struct nau8540 *nau8540) { - struct regmap *regmap = nau8540->regmap; - - /* Enable Bias/VMID/VMID Tieoff */ - regmap_update_bits(regmap, NAU8540_REG_VMID_CTRL, - NAU8540_VMID_EN | NAU8540_VMID_SEL_MASK, - NAU8540_VMID_EN | (0x2 << NAU8540_VMID_SEL_SFT)); - regmap_update_bits(regmap, NAU8540_REG_REFERENCE, - NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN, - NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN); - mdelay(2); - regmap_update_bits(regmap, NAU8540_REG_MIC_BIAS, - NAU8540_PU_PRE, NAU8540_PU_PRE); - regmap_update_bits(regmap, NAU8540_REG_CLOCK_CTRL, - NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN, - NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN); - /* ADC OSR selection, CLK_ADC = Fs * OSR */ - regmap_update_bits(regmap, NAU8540_REG_ADC_SAMPLE_RATE, - NAU8540_ADC_OSR_MASK, NAU8540_ADC_OSR_64); + struct regmap *regmap = nau8540->regmap; + + /* Enable Bias/VMID/VMID Tieoff */ + regmap_update_bits(regmap, NAU8540_REG_VMID_CTRL, + NAU8540_VMID_EN | NAU8540_VMID_SEL_MASK, + NAU8540_VMID_EN | (0x2 << NAU8540_VMID_SEL_SFT)); + regmap_update_bits(regmap, NAU8540_REG_REFERENCE, + NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN, + NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN); + mdelay(2); + regmap_update_bits(regmap, NAU8540_REG_MIC_BIAS, + NAU8540_PU_PRE, NAU8540_PU_PRE); + regmap_update_bits(regmap, NAU8540_REG_CLOCK_CTRL, + NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN, + NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN); + /* ADC OSR selection, CLK_ADC = Fs * OSR */ + regmap_update_bits(regmap, NAU8540_REG_ADC_SAMPLE_RATE, + NAU8540_ADC_OSR_MASK, NAU8540_ADC_OSR_64); } static int __maybe_unused nau8540_suspend(struct snd_soc_codec *codec) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - regcache_cache_only(nau8540->regmap, true); - regcache_mark_dirty(nau8540->regmap); + regcache_cache_only(nau8540->regmap, true); + regcache_mark_dirty(nau8540->regmap); - return 0; + return 0; } static int __maybe_unused nau8540_resume(struct snd_soc_codec *codec) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - regcache_cache_only(nau8540->regmap, false); - regcache_sync(nau8540->regmap); + regcache_cache_only(nau8540->regmap, false); + regcache_sync(nau8540->regmap); - return 0; + return 0; } static struct snd_soc_codec_driver nau8540_codec_driver = { - .set_sysclk = nau8540_set_sysclk, - .set_pll = nau8540_set_pll, - .suspend = nau8540_suspend, - .resume = nau8540_resume, - .suspend_bias_off = true, - - .component_driver = { - .controls = nau8540_snd_controls, - .num_controls = ARRAY_SIZE(nau8540_snd_controls), - .dapm_widgets = nau8540_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(nau8540_dapm_widgets), - .dapm_routes = nau8540_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(nau8540_dapm_routes), - }, + .set_sysclk = nau8540_set_sysclk, + .set_pll = nau8540_set_pll, + .suspend = nau8540_suspend, + .resume = nau8540_resume, + .suspend_bias_off = true, + + .component_driver = { + .controls = nau8540_snd_controls, + .num_controls = ARRAY_SIZE(nau8540_snd_controls), + .dapm_widgets = nau8540_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8540_dapm_widgets), + .dapm_routes = nau8540_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8540_dapm_routes), + }, }; static const struct regmap_config nau8540_regmap_config = { - .val_bits = 16, - .reg_bits = 16, + .val_bits = 16, + .reg_bits = 16, - .max_register = NAU8540_REG_MAX, - .readable_reg = nau8540_readable_reg, - .writeable_reg = nau8540_writeable_reg, - .volatile_reg = nau8540_volatile_reg, + .max_register = NAU8540_REG_MAX, + .readable_reg = nau8540_readable_reg, + .writeable_reg = nau8540_writeable_reg, + .volatile_reg = nau8540_volatile_reg, - .cache_type = REGCACHE_RBTREE, - .reg_defaults = nau8540_reg_defaults, - .num_reg_defaults = ARRAY_SIZE(nau8540_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8540_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8540_reg_defaults), }; static int nau8540_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { - struct device *dev = &i2c->dev; - struct nau8540 *nau8540 = dev_get_platdata(dev); - int ret, value; - - if (!nau8540) { - nau8540 = devm_kzalloc(dev, sizeof(*nau8540), GFP_KERNEL); - if (!nau8540) - return -ENOMEM; - } - i2c_set_clientdata(i2c, nau8540); - - nau8540->regmap = devm_regmap_init_i2c(i2c, &nau8540_regmap_config); - if (IS_ERR(nau8540->regmap)) - return PTR_ERR(nau8540->regmap); - ret = regmap_read(nau8540->regmap, NAU8540_REG_I2C_DEVICE_ID, &value); - if (ret < 0) { - dev_err(dev, "Failed to read device id from the NAU85L40: %d\n", - ret); - return ret; - } - - nau8540->dev = dev; - nau8540_reset_chip(nau8540->regmap); - nau8540_init_regs(nau8540); - - return snd_soc_register_codec(dev, - &nau8540_codec_driver, &nau8540_dai, 1); + struct device *dev = &i2c->dev; + struct nau8540 *nau8540 = dev_get_platdata(dev); + int ret, value; + + if (!nau8540) { + nau8540 = devm_kzalloc(dev, sizeof(*nau8540), GFP_KERNEL); + if (!nau8540) + return -ENOMEM; + } + i2c_set_clientdata(i2c, nau8540); + + nau8540->regmap = devm_regmap_init_i2c(i2c, &nau8540_regmap_config); + if (IS_ERR(nau8540->regmap)) + return PTR_ERR(nau8540->regmap); + ret = regmap_read(nau8540->regmap, NAU8540_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU85L40: %d\n", + ret); + return ret; + } + + nau8540->dev = dev; + nau8540_reset_chip(nau8540->regmap); + nau8540_init_regs(nau8540); + + return snd_soc_register_codec(dev, + &nau8540_codec_driver, &nau8540_dai, 1); } static int nau8540_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); - return 0; + snd_soc_unregister_codec(&client->dev); + return 0; } static const struct i2c_device_id nau8540_i2c_ids[] = { - { "nau8540", 0 }, - { } + { "nau8540", 0 }, + { } }; MODULE_DEVICE_TABLE(i2c, nau8540_i2c_ids); #ifdef CONFIG_OF static const struct of_device_id nau8540_of_ids[] = { - { .compatible = "nuvoton,nau8540", }, - {} + { .compatible = "nuvoton,nau8540", }, + {} }; MODULE_DEVICE_TABLE(of, nau8540_of_ids); #endif static struct i2c_driver nau8540_i2c_driver = { - .driver = { - .name = "nau8540", - .of_match_table = of_match_ptr(nau8540_of_ids), - }, - .probe = nau8540_i2c_probe, - .remove = nau8540_i2c_remove, - .id_table = nau8540_i2c_ids, + .driver = { + .name = "nau8540", + .of_match_table = of_match_ptr(nau8540_of_ids), + }, + .probe = nau8540_i2c_probe, + .remove = nau8540_i2c_remove, + .id_table = nau8540_i2c_ids, }; module_i2c_driver(nau8540_i2c_driver); diff --git a/sound/soc/codecs/nau8540.h b/sound/soc/codecs/nau8540.h index d06e65188cd5..5db5b224944d 100644 --- a/sound/soc/codecs/nau8540.h +++ b/sound/soc/codecs/nau8540.h @@ -12,211 +12,211 @@ #ifndef __NAU8540_H__ #define __NAU8540_H__ -#define NAU8540_REG_SW_RESET 0x00 -#define NAU8540_REG_POWER_MANAGEMENT 0x01 -#define NAU8540_REG_CLOCK_CTRL 0x02 -#define NAU8540_REG_CLOCK_SRC 0x03 -#define NAU8540_REG_FLL1 0x04 -#define NAU8540_REG_FLL2 0x05 -#define NAU8540_REG_FLL3 0x06 -#define NAU8540_REG_FLL4 0x07 -#define NAU8540_REG_FLL5 0x08 -#define NAU8540_REG_FLL6 0x09 -#define NAU8540_REG_FLL_VCO_RSV 0x0A -#define NAU8540_REG_PCM_CTRL0 0x10 -#define NAU8540_REG_PCM_CTRL1 0x11 -#define NAU8540_REG_PCM_CTRL2 0x12 -#define NAU8540_REG_PCM_CTRL3 0x13 -#define NAU8540_REG_PCM_CTRL4 0x14 -#define NAU8540_REG_ALC_CONTROL_1 0x20 -#define NAU8540_REG_ALC_CONTROL_2 0x21 -#define NAU8540_REG_ALC_CONTROL_3 0x22 -#define NAU8540_REG_ALC_CONTROL_4 0x23 -#define NAU8540_REG_ALC_CONTROL_5 0x24 -#define NAU8540_REG_ALC_GAIN_CH12 0x2D -#define NAU8540_REG_ALC_GAIN_CH34 0x2E -#define NAU8540_REG_ALC_STATUS 0x2F -#define NAU8540_REG_NOTCH_FIL1_CH1 0x30 -#define NAU8540_REG_NOTCH_FIL2_CH1 0x31 -#define NAU8540_REG_NOTCH_FIL1_CH2 0x32 -#define NAU8540_REG_NOTCH_FIL2_CH2 0x33 -#define NAU8540_REG_NOTCH_FIL1_CH3 0x34 -#define NAU8540_REG_NOTCH_FIL2_CH3 0x35 -#define NAU8540_REG_NOTCH_FIL1_CH4 0x36 -#define NAU8540_REG_NOTCH_FIL2_CH4 0x37 -#define NAU8540_REG_HPF_FILTER_CH12 0x38 -#define NAU8540_REG_HPF_FILTER_CH34 0x39 -#define NAU8540_REG_ADC_SAMPLE_RATE 0x3A -#define NAU8540_REG_DIGITAL_GAIN_CH1 0x40 -#define NAU8540_REG_DIGITAL_GAIN_CH2 0x41 -#define NAU8540_REG_DIGITAL_GAIN_CH3 0x42 -#define NAU8540_REG_DIGITAL_GAIN_CH4 0x43 -#define NAU8540_REG_DIGITAL_MUX 0x44 -#define NAU8540_REG_P2P_CH1 0x48 -#define NAU8540_REG_P2P_CH2 0x49 -#define NAU8540_REG_P2P_CH3 0x4A -#define NAU8540_REG_P2P_CH4 0x4B -#define NAU8540_REG_PEAK_CH1 0x4C -#define NAU8540_REG_PEAK_CH2 0x4D -#define NAU8540_REG_PEAK_CH3 0x4E -#define NAU8540_REG_PEAK_CH4 0x4F -#define NAU8540_REG_GPIO_CTRL 0x50 -#define NAU8540_REG_MISC_CTRL 0x51 -#define NAU8540_REG_I2C_CTRL 0x52 -#define NAU8540_REG_I2C_DEVICE_ID 0x58 -#define NAU8540_REG_RST 0x5A -#define NAU8540_REG_VMID_CTRL 0x60 -#define NAU8540_REG_MUTE 0x61 -#define NAU8540_REG_ANALOG_ADC1 0x64 -#define NAU8540_REG_ANALOG_ADC2 0x65 -#define NAU8540_REG_ANALOG_PWR 0x66 -#define NAU8540_REG_MIC_BIAS 0x67 -#define NAU8540_REG_REFERENCE 0x68 -#define NAU8540_REG_FEPGA1 0x69 -#define NAU8540_REG_FEPGA2 0x6A -#define NAU8540_REG_FEPGA3 0x6B -#define NAU8540_REG_FEPGA4 0x6C -#define NAU8540_REG_PWR 0x6D -#define NAU8540_REG_MAX NAU8540_REG_PWR +#define NAU8540_REG_SW_RESET 0x00 +#define NAU8540_REG_POWER_MANAGEMENT 0x01 +#define NAU8540_REG_CLOCK_CTRL 0x02 +#define NAU8540_REG_CLOCK_SRC 0x03 +#define NAU8540_REG_FLL1 0x04 +#define NAU8540_REG_FLL2 0x05 +#define NAU8540_REG_FLL3 0x06 +#define NAU8540_REG_FLL4 0x07 +#define NAU8540_REG_FLL5 0x08 +#define NAU8540_REG_FLL6 0x09 +#define NAU8540_REG_FLL_VCO_RSV 0x0A +#define NAU8540_REG_PCM_CTRL0 0x10 +#define NAU8540_REG_PCM_CTRL1 0x11 +#define NAU8540_REG_PCM_CTRL2 0x12 +#define NAU8540_REG_PCM_CTRL3 0x13 +#define NAU8540_REG_PCM_CTRL4 0x14 +#define NAU8540_REG_ALC_CONTROL_1 0x20 +#define NAU8540_REG_ALC_CONTROL_2 0x21 +#define NAU8540_REG_ALC_CONTROL_3 0x22 +#define NAU8540_REG_ALC_CONTROL_4 0x23 +#define NAU8540_REG_ALC_CONTROL_5 0x24 +#define NAU8540_REG_ALC_GAIN_CH12 0x2D +#define NAU8540_REG_ALC_GAIN_CH34 0x2E +#define NAU8540_REG_ALC_STATUS 0x2F +#define NAU8540_REG_NOTCH_FIL1_CH1 0x30 +#define NAU8540_REG_NOTCH_FIL2_CH1 0x31 +#define NAU8540_REG_NOTCH_FIL1_CH2 0x32 +#define NAU8540_REG_NOTCH_FIL2_CH2 0x33 +#define NAU8540_REG_NOTCH_FIL1_CH3 0x34 +#define NAU8540_REG_NOTCH_FIL2_CH3 0x35 +#define NAU8540_REG_NOTCH_FIL1_CH4 0x36 +#define NAU8540_REG_NOTCH_FIL2_CH4 0x37 +#define NAU8540_REG_HPF_FILTER_CH12 0x38 +#define NAU8540_REG_HPF_FILTER_CH34 0x39 +#define NAU8540_REG_ADC_SAMPLE_RATE 0x3A +#define NAU8540_REG_DIGITAL_GAIN_CH1 0x40 +#define NAU8540_REG_DIGITAL_GAIN_CH2 0x41 +#define NAU8540_REG_DIGITAL_GAIN_CH3 0x42 +#define NAU8540_REG_DIGITAL_GAIN_CH4 0x43 +#define NAU8540_REG_DIGITAL_MUX 0x44 +#define NAU8540_REG_P2P_CH1 0x48 +#define NAU8540_REG_P2P_CH2 0x49 +#define NAU8540_REG_P2P_CH3 0x4A +#define NAU8540_REG_P2P_CH4 0x4B +#define NAU8540_REG_PEAK_CH1 0x4C +#define NAU8540_REG_PEAK_CH2 0x4D +#define NAU8540_REG_PEAK_CH3 0x4E +#define NAU8540_REG_PEAK_CH4 0x4F +#define NAU8540_REG_GPIO_CTRL 0x50 +#define NAU8540_REG_MISC_CTRL 0x51 +#define NAU8540_REG_I2C_CTRL 0x52 +#define NAU8540_REG_I2C_DEVICE_ID 0x58 +#define NAU8540_REG_RST 0x5A +#define NAU8540_REG_VMID_CTRL 0x60 +#define NAU8540_REG_MUTE 0x61 +#define NAU8540_REG_ANALOG_ADC1 0x64 +#define NAU8540_REG_ANALOG_ADC2 0x65 +#define NAU8540_REG_ANALOG_PWR 0x66 +#define NAU8540_REG_MIC_BIAS 0x67 +#define NAU8540_REG_REFERENCE 0x68 +#define NAU8540_REG_FEPGA1 0x69 +#define NAU8540_REG_FEPGA2 0x6A +#define NAU8540_REG_FEPGA3 0x6B +#define NAU8540_REG_FEPGA4 0x6C +#define NAU8540_REG_PWR 0x6D +#define NAU8540_REG_MAX NAU8540_REG_PWR /* POWER_MANAGEMENT (0x01) */ -#define NAU8540_ADC4_EN (0x1 << 3) -#define NAU8540_ADC3_EN (0x1 << 2) -#define NAU8540_ADC2_EN (0x1 << 1) -#define NAU8540_ADC1_EN 0x1 +#define NAU8540_ADC4_EN (0x1 << 3) +#define NAU8540_ADC3_EN (0x1 << 2) +#define NAU8540_ADC2_EN (0x1 << 1) +#define NAU8540_ADC1_EN 0x1 /* CLOCK_CTRL (0x02) */ -#define NAU8540_CLK_ADC_EN (0x1 << 15) -#define NAU8540_CLK_I2S_EN (0x1 << 1) +#define NAU8540_CLK_ADC_EN (0x1 << 15) +#define NAU8540_CLK_I2S_EN (0x1 << 1) /* CLOCK_SRC (0x03) */ -#define NAU8540_CLK_SRC_SFT 15 -#define NAU8540_CLK_SRC_MASK (1 << NAU8540_CLK_SRC_SFT) -#define NAU8540_CLK_SRC_VCO (1 << NAU8540_CLK_SRC_SFT) -#define NAU8540_CLK_SRC_MCLK (0 << NAU8540_CLK_SRC_SFT) -#define NAU8540_CLK_ADC_SRC_SFT 6 -#define NAU8540_CLK_ADC_SRC_MASK (0x3 << NAU8540_CLK_ADC_SRC_SFT) -#define NAU8540_CLK_MCLK_SRC_MASK 0xf +#define NAU8540_CLK_SRC_SFT 15 +#define NAU8540_CLK_SRC_MASK (1 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_SRC_VCO (1 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_SRC_MCLK (0 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_ADC_SRC_SFT 6 +#define NAU8540_CLK_ADC_SRC_MASK (0x3 << NAU8540_CLK_ADC_SRC_SFT) +#define NAU8540_CLK_MCLK_SRC_MASK 0xf /* FLL1 (0x04) */ -#define NAU8540_FLL_RATIO_MASK 0x7f +#define NAU8540_FLL_RATIO_MASK 0x7f /* FLL3 (0x06) */ -#define NAU8540_FLL_CLK_SRC_SFT 10 -#define NAU8540_FLL_CLK_SRC_MASK (0x3 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_CLK_SRC_MCLK (0 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_CLK_SRC_BLK (0x2 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_CLK_SRC_FS (0x3 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_INTEGER_MASK 0x3ff +#define NAU8540_FLL_CLK_SRC_SFT 10 +#define NAU8540_FLL_CLK_SRC_MASK (0x3 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_MCLK (0 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_BLK (0x2 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_FS (0x3 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_INTEGER_MASK 0x3ff /* FLL4 (0x07) */ -#define NAU8540_FLL_REF_DIV_SFT 10 -#define NAU8540_FLL_REF_DIV_MASK (0x3 << NAU8540_FLL_REF_DIV_SFT) +#define NAU8540_FLL_REF_DIV_SFT 10 +#define NAU8540_FLL_REF_DIV_MASK (0x3 << NAU8540_FLL_REF_DIV_SFT) /* FLL5 (0x08) */ -#define NAU8540_FLL_PDB_DAC_EN (0x1 << 15) -#define NAU8540_FLL_LOOP_FTR_EN (0x1 << 14) -#define NAU8540_FLL_CLK_SW_MASK (0x1 << 13) -#define NAU8540_FLL_CLK_SW_N2 (0x1 << 13) -#define NAU8540_FLL_CLK_SW_REF (0x0 << 13) -#define NAU8540_FLL_FTR_SW_MASK (0x1 << 12) -#define NAU8540_FLL_FTR_SW_ACCU (0x1 << 12) -#define NAU8540_FLL_FTR_SW_FILTER (0x0 << 12) +#define NAU8540_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8540_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8540_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8540_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8540_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8540_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8540_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8540_FLL_FTR_SW_FILTER (0x0 << 12) /* FLL6 (0x9) */ -#define NAU8540_DCO_EN (0x1 << 15) -#define NAU8540_SDM_EN (0x1 << 14) +#define NAU8540_DCO_EN (0x1 << 15) +#define NAU8540_SDM_EN (0x1 << 14) /* PCM_CTRL0 (0x10) */ -#define NAU8540_I2S_BP_SFT 7 -#define NAU8540_I2S_BP_INV (0x1 << NAU8540_I2S_BP_SFT) -#define NAU8540_I2S_PCMB_SFT 6 -#define NAU8540_I2S_PCMB_EN (0x1 << NAU8540_I2S_PCMB_SFT) -#define NAU8540_I2S_DL_SFT 2 -#define NAU8540_I2S_DL_MASK (0x3 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_16 (0 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_20 (0x1 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_24 (0x2 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_32 (0x3 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DF_MASK 0x3 -#define NAU8540_I2S_DF_RIGTH 0 -#define NAU8540_I2S_DF_LEFT 0x1 -#define NAU8540_I2S_DF_I2S 0x2 -#define NAU8540_I2S_DF_PCM_AB 0x3 +#define NAU8540_I2S_BP_SFT 7 +#define NAU8540_I2S_BP_INV (0x1 << NAU8540_I2S_BP_SFT) +#define NAU8540_I2S_PCMB_SFT 6 +#define NAU8540_I2S_PCMB_EN (0x1 << NAU8540_I2S_PCMB_SFT) +#define NAU8540_I2S_DL_SFT 2 +#define NAU8540_I2S_DL_MASK (0x3 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_16 (0 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_20 (0x1 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_24 (0x2 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_32 (0x3 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DF_MASK 0x3 +#define NAU8540_I2S_DF_RIGTH 0 +#define NAU8540_I2S_DF_LEFT 0x1 +#define NAU8540_I2S_DF_I2S 0x2 +#define NAU8540_I2S_DF_PCM_AB 0x3 /* PCM_CTRL1 (0x11) */ -#define NAU8540_I2S_LRC_DIV_SFT 12 -#define NAU8540_I2S_LRC_DIV_MASK (0x3 << NAU8540_I2S_LRC_DIV_SFT) -#define NAU8540_I2S_DO12_OE (0x1 << 4) -#define NAU8540_I2S_MS_SFT 3 -#define NAU8540_I2S_MS_MASK (0x1 << NAU8540_I2S_MS_SFT) -#define NAU8540_I2S_MS_MASTER (0x1 << NAU8540_I2S_MS_SFT) -#define NAU8540_I2S_MS_SLAVE (0x0 << NAU8540_I2S_MS_SFT) -#define NAU8540_I2S_BLK_DIV_MASK 0x7 +#define NAU8540_I2S_LRC_DIV_SFT 12 +#define NAU8540_I2S_LRC_DIV_MASK (0x3 << NAU8540_I2S_LRC_DIV_SFT) +#define NAU8540_I2S_DO12_OE (0x1 << 4) +#define NAU8540_I2S_MS_SFT 3 +#define NAU8540_I2S_MS_MASK (0x1 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_MS_MASTER (0x1 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_MS_SLAVE (0x0 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_BLK_DIV_MASK 0x7 /* PCM_CTRL1 (0x12) */ -#define NAU8540_I2S_DO34_OE (0x1 << 11) -#define NAU8540_I2S_TSLOT_L_MASK 0x3ff +#define NAU8540_I2S_DO34_OE (0x1 << 11) +#define NAU8540_I2S_TSLOT_L_MASK 0x3ff /* PCM_CTRL4 (0x14) */ -#define NAU8540_TDM_MODE (0x1 << 15) -#define NAU8540_TDM_OFFSET_EN (0x1 << 14) -#define NAU8540_TDM_TX_MASK 0xf +#define NAU8540_TDM_MODE (0x1 << 15) +#define NAU8540_TDM_OFFSET_EN (0x1 << 14) +#define NAU8540_TDM_TX_MASK 0xf /* ADC_SAMPLE_RATE (0x3A) */ -#define NAU8540_ADC_OSR_MASK 0x3 -#define NAU8540_ADC_OSR_256 0x3 -#define NAU8540_ADC_OSR_128 0x2 -#define NAU8540_ADC_OSR_64 0x1 -#define NAU8540_ADC_OSR_32 0x0 +#define NAU8540_ADC_OSR_MASK 0x3 +#define NAU8540_ADC_OSR_256 0x3 +#define NAU8540_ADC_OSR_128 0x2 +#define NAU8540_ADC_OSR_64 0x1 +#define NAU8540_ADC_OSR_32 0x0 /* VMID_CTRL (0x60) */ -#define NAU8540_VMID_EN (1 << 6) -#define NAU8540_VMID_SEL_SFT 4 -#define NAU8540_VMID_SEL_MASK (0x3 << NAU8540_VMID_SEL_SFT) +#define NAU8540_VMID_EN (1 << 6) +#define NAU8540_VMID_SEL_SFT 4 +#define NAU8540_VMID_SEL_MASK (0x3 << NAU8540_VMID_SEL_SFT) /* MIC_BIAS (0x67) */ -#define NAU8540_PU_PRE (0x1 << 8) +#define NAU8540_PU_PRE (0x1 << 8) /* REFERENCE (0x68) */ -#define NAU8540_PRECHARGE_DIS (0x1 << 13) -#define NAU8540_GLOBAL_BIAS_EN (0x1 << 12) +#define NAU8540_PRECHARGE_DIS (0x1 << 13) +#define NAU8540_GLOBAL_BIAS_EN (0x1 << 12) /* System Clock Source */ enum { - NAU8540_CLK_DIS, - NAU8540_CLK_MCLK, - NAU8540_CLK_INTERNAL, - NAU8540_CLK_FLL_MCLK, - NAU8540_CLK_FLL_BLK, - NAU8540_CLK_FLL_FS, + NAU8540_CLK_DIS, + NAU8540_CLK_MCLK, + NAU8540_CLK_INTERNAL, + NAU8540_CLK_FLL_MCLK, + NAU8540_CLK_FLL_BLK, + NAU8540_CLK_FLL_FS, }; struct nau8540 { - struct device *dev; - struct regmap *regmap; + struct device *dev; + struct regmap *regmap; }; struct nau8540_fll { - int mclk_src; - int ratio; - int fll_frac; - int fll_int; - int clk_ref_div; + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; }; struct nau8540_fll_attr { - unsigned int param; - unsigned int val; + unsigned int param; + unsigned int val; }; /* over sampling rate */ struct nau8540_osr_attr { - unsigned int osr; - unsigned int clk_src; + unsigned int osr; + unsigned int clk_src; }; -#endif /* __NAU8540_H__ */ +#endif /* __NAU8540_H__ */ diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c new file mode 100644 index 000000000000..cca974d26136 --- /dev/null +++ b/sound/soc/codecs/nau8824.c @@ -0,0 +1,1831 @@ +/* + * NAU88L24 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu <KCHSU0@nuvoton.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/acpi.h> +#include <linux/math64.h> +#include <linux/semaphore.h> + +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "nau8824.h" + + +static int nau8824_config_sysclk(struct nau8824 *nau8824, + int clk_id, unsigned int freq); +static bool nau8824_is_jack_inserted(struct nau8824 *nau8824); + +/* the ADC threshold of headset */ +#define DMIC_CLK 3072000 + +/* the ADC threshold of headset */ +#define HEADSET_SARADC_THD 0x80 + +/* the parameter threshold of FLL */ +#define NAU_FREF_MAX 13500000 +#define NAU_FVCO_MAX 124000000 +#define NAU_FVCO_MIN 90000000 + +/* scaling for mclk from sysclk_src output */ +static const struct nau8824_fll_attr mclk_src_scaling[] = { + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, +}; + +/* ratio for input clk freq */ +static const struct nau8824_fll_attr fll_ratio[] = { + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, +}; + +static const struct nau8824_fll_attr fll_pre_scalar[] = { + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, +}; + +/* the maximum frequency of CLK_ADC and CLK_DAC */ +#define CLK_DA_AD_MAX 6144000 + +/* over sampling rate */ +static const struct nau8824_osr_attr osr_dac_sel[] = { + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 256, 0 }, /* OSR 256, SRC 1 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 0, 0 }, + { 32, 3 }, /* OSR 32, SRC 1/8 */ +}; + +static const struct nau8824_osr_attr osr_adc_sel[] = { + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ +}; + +static const struct reg_default nau8824_reg_defaults[] = { + { NAU8824_REG_ENA_CTRL, 0x0000 }, + { NAU8824_REG_CLK_GATING_ENA, 0x0000 }, + { NAU8824_REG_CLK_DIVIDER, 0x0000 }, + { NAU8824_REG_FLL1, 0x0000 }, + { NAU8824_REG_FLL2, 0x3126 }, + { NAU8824_REG_FLL3, 0x0008 }, + { NAU8824_REG_FLL4, 0x0010 }, + { NAU8824_REG_FLL5, 0xC000 }, + { NAU8824_REG_FLL6, 0x6000 }, + { NAU8824_REG_FLL_VCO_RSV, 0xF13C }, + { NAU8824_REG_JACK_DET_CTRL, 0x0000 }, + { NAU8824_REG_INTERRUPT_SETTING_1, 0x0000 }, + { NAU8824_REG_IRQ, 0x0000 }, + { NAU8824_REG_CLEAR_INT_REG, 0x0000 }, + { NAU8824_REG_INTERRUPT_SETTING, 0x1000 }, + { NAU8824_REG_SAR_ADC, 0x0015 }, + { NAU8824_REG_VDET_COEFFICIENT, 0x0110 }, + { NAU8824_REG_VDET_THRESHOLD_1, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_2, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_3, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_4, 0x0000 }, + { NAU8824_REG_GPIO_SEL, 0x0000 }, + { NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 0x000B }, + { NAU8824_REG_PORT0_I2S_PCM_CTRL_2, 0x0010 }, + { NAU8824_REG_PORT0_LEFT_TIME_SLOT, 0x0000 }, + { NAU8824_REG_PORT0_RIGHT_TIME_SLOT, 0x0000 }, + { NAU8824_REG_TDM_CTRL, 0x0000 }, + { NAU8824_REG_ADC_HPF_FILTER, 0x0000 }, + { NAU8824_REG_ADC_FILTER_CTRL, 0x0002 }, + { NAU8824_REG_DAC_FILTER_CTRL_1, 0x0000 }, + { NAU8824_REG_DAC_FILTER_CTRL_2, 0x0000 }, + { NAU8824_REG_NOTCH_FILTER_1, 0x0000 }, + { NAU8824_REG_NOTCH_FILTER_2, 0x0000 }, + { NAU8824_REG_EQ1_LOW, 0x112C }, + { NAU8824_REG_EQ2_EQ3, 0x2C2C }, + { NAU8824_REG_EQ4_EQ5, 0x2C2C }, + { NAU8824_REG_ADC_CH0_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH1_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH2_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH3_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_DAC_MUTE_CTRL, 0x0000 }, + { NAU8824_REG_DAC_CH0_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_DAC_CH1_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_TO_DAC_ST, 0x0000 }, + { NAU8824_REG_DRC_KNEE_IP12_ADC_CH01, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_ADC_CH01, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_ADC_CH01, 0x25FF }, + { NAU8824_REG_DRC_ATKDCY_ADC_CH01, 0x3457 }, + { NAU8824_REG_DRC_KNEE_IP12_ADC_CH23, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_ADC_CH23, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_ADC_CH23, 0x25FF }, + { NAU8824_REG_DRC_ATKDCY_ADC_CH23, 0x3457 }, + { NAU8824_REG_DRC_GAINL_ADC0, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC1, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC2, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC3, 0x0200 }, + { NAU8824_REG_DRC_KNEE_IP12_DAC, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_DAC, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_DAC, 0x25F9 }, + { NAU8824_REG_DRC_ATKDCY_DAC, 0x3457 }, + { NAU8824_REG_DRC_GAIN_DAC_CH0, 0x0200 }, + { NAU8824_REG_DRC_GAIN_DAC_CH1, 0x0200 }, + { NAU8824_REG_MODE, 0x0000 }, + { NAU8824_REG_MODE1, 0x0000 }, + { NAU8824_REG_MODE2, 0x0000 }, + { NAU8824_REG_CLASSG, 0x0000 }, + { NAU8824_REG_OTP_EFUSE, 0x0000 }, + { NAU8824_REG_OTPDOUT_1, 0x0000 }, + { NAU8824_REG_OTPDOUT_2, 0x0000 }, + { NAU8824_REG_MISC_CTRL, 0x0000 }, + { NAU8824_REG_I2C_TIMEOUT, 0xEFFF }, + { NAU8824_REG_TEST_MODE, 0x0000 }, + { NAU8824_REG_I2C_DEVICE_ID, 0x1AF1 }, + { NAU8824_REG_SAR_ADC_DATA_OUT, 0x00FF }, + { NAU8824_REG_BIAS_ADJ, 0x0000 }, + { NAU8824_REG_PGA_GAIN, 0x0000 }, + { NAU8824_REG_TRIM_SETTINGS, 0x0000 }, + { NAU8824_REG_ANALOG_CONTROL_1, 0x0000 }, + { NAU8824_REG_ANALOG_CONTROL_2, 0x0000 }, + { NAU8824_REG_ENABLE_LO, 0x0000 }, + { NAU8824_REG_GAIN_LO, 0x0000 }, + { NAU8824_REG_CLASSD_GAIN_1, 0x0000 }, + { NAU8824_REG_CLASSD_GAIN_2, 0x0000 }, + { NAU8824_REG_ANALOG_ADC_1, 0x0011 }, + { NAU8824_REG_ANALOG_ADC_2, 0x0020 }, + { NAU8824_REG_RDAC, 0x0008 }, + { NAU8824_REG_MIC_BIAS, 0x0006 }, + { NAU8824_REG_HS_VOLUME_CONTROL, 0x0000 }, + { NAU8824_REG_BOOST, 0x0000 }, + { NAU8824_REG_FEPGA, 0x0000 }, + { NAU8824_REG_FEPGA_II, 0x0000 }, + { NAU8824_REG_FEPGA_SE, 0x0000 }, + { NAU8824_REG_FEPGA_ATTENUATION, 0x0000 }, + { NAU8824_REG_ATT_PORT0, 0x0000 }, + { NAU8824_REG_ATT_PORT1, 0x0000 }, + { NAU8824_REG_POWER_UP_CONTROL, 0x0000 }, + { NAU8824_REG_CHARGE_PUMP_CONTROL, 0x0300 }, + { NAU8824_REG_CHARGE_PUMP_INPUT, 0x0013 }, +}; + +static int nau8824_sema_acquire(struct nau8824 *nau8824, long timeout) +{ + int ret; + + if (timeout) { + ret = down_timeout(&nau8824->jd_sem, timeout); + if (ret < 0) + dev_warn(nau8824->dev, "Acquire semaphone timeout\n"); + } else { + ret = down_interruptible(&nau8824->jd_sem); + if (ret < 0) + dev_warn(nau8824->dev, "Acquire semaphone fail\n"); + } + + return ret; +} + +static inline void nau8824_sema_release(struct nau8824 *nau8824) +{ + up(&nau8824->jd_sem); +} + +static bool nau8824_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_ENA_CTRL ... NAU8824_REG_FLL_VCO_RSV: + case NAU8824_REG_JACK_DET_CTRL: + case NAU8824_REG_INTERRUPT_SETTING_1: + case NAU8824_REG_IRQ: + case NAU8824_REG_CLEAR_INT_REG ... NAU8824_REG_VDET_THRESHOLD_4: + case NAU8824_REG_GPIO_SEL: + case NAU8824_REG_PORT0_I2S_PCM_CTRL_1 ... NAU8824_REG_TDM_CTRL: + case NAU8824_REG_ADC_HPF_FILTER ... NAU8824_REG_EQ4_EQ5: + case NAU8824_REG_ADC_CH0_DGAIN_CTRL ... NAU8824_REG_ADC_TO_DAC_ST: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH01 ... NAU8824_REG_DRC_GAINL_ADC3: + case NAU8824_REG_DRC_KNEE_IP12_DAC ... NAU8824_REG_DRC_GAIN_DAC_CH1: + case NAU8824_REG_CLASSG ... NAU8824_REG_OTP_EFUSE: + case NAU8824_REG_OTPDOUT_1 ... NAU8824_REG_OTPDOUT_2: + case NAU8824_REG_I2C_TIMEOUT: + case NAU8824_REG_I2C_DEVICE_ID ... NAU8824_REG_SAR_ADC_DATA_OUT: + case NAU8824_REG_BIAS_ADJ ... NAU8824_REG_CLASSD_GAIN_2: + case NAU8824_REG_ANALOG_ADC_1 ... NAU8824_REG_ATT_PORT1: + case NAU8824_REG_POWER_UP_CONTROL ... NAU8824_REG_CHARGE_PUMP_INPUT: + return true; + default: + return false; + } + +} + +static bool nau8824_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_RESET ... NAU8824_REG_FLL_VCO_RSV: + case NAU8824_REG_JACK_DET_CTRL: + case NAU8824_REG_INTERRUPT_SETTING_1: + case NAU8824_REG_CLEAR_INT_REG ... NAU8824_REG_VDET_THRESHOLD_4: + case NAU8824_REG_GPIO_SEL: + case NAU8824_REG_PORT0_I2S_PCM_CTRL_1 ... NAU8824_REG_TDM_CTRL: + case NAU8824_REG_ADC_HPF_FILTER ... NAU8824_REG_EQ4_EQ5: + case NAU8824_REG_ADC_CH0_DGAIN_CTRL ... NAU8824_REG_ADC_TO_DAC_ST: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH01: + case NAU8824_REG_DRC_KNEE_IP34_ADC_CH01: + case NAU8824_REG_DRC_SLOPE_ADC_CH01: + case NAU8824_REG_DRC_ATKDCY_ADC_CH01: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH23: + case NAU8824_REG_DRC_KNEE_IP34_ADC_CH23: + case NAU8824_REG_DRC_SLOPE_ADC_CH23: + case NAU8824_REG_DRC_ATKDCY_ADC_CH23: + case NAU8824_REG_DRC_KNEE_IP12_DAC ... NAU8824_REG_DRC_ATKDCY_DAC: + case NAU8824_REG_CLASSG ... NAU8824_REG_OTP_EFUSE: + case NAU8824_REG_I2C_TIMEOUT: + case NAU8824_REG_BIAS_ADJ ... NAU8824_REG_CLASSD_GAIN_2: + case NAU8824_REG_ANALOG_ADC_1 ... NAU8824_REG_ATT_PORT1: + case NAU8824_REG_POWER_UP_CONTROL ... NAU8824_REG_CHARGE_PUMP_CONTROL: + return true; + default: + return false; + } +} + +static bool nau8824_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_RESET: + case NAU8824_REG_IRQ ... NAU8824_REG_CLEAR_INT_REG: + case NAU8824_REG_DRC_GAINL_ADC0 ... NAU8824_REG_DRC_GAINL_ADC3: + case NAU8824_REG_DRC_GAIN_DAC_CH0 ... NAU8824_REG_DRC_GAIN_DAC_CH1: + case NAU8824_REG_OTPDOUT_1 ... NAU8824_REG_OTPDOUT_2: + case NAU8824_REG_I2C_DEVICE_ID ... NAU8824_REG_SAR_ADC_DATA_OUT: + case NAU8824_REG_CHARGE_PUMP_INPUT: + return true; + default: + return false; + } +} + +static const char * const nau8824_companding[] = { + "Off", "NC", "u-law", "A-law" }; + +static const struct soc_enum nau8824_companding_adc_enum = + SOC_ENUM_SINGLE(NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 12, + ARRAY_SIZE(nau8824_companding), nau8824_companding); + +static const struct soc_enum nau8824_companding_dac_enum = + SOC_ENUM_SINGLE(NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 14, + ARRAY_SIZE(nau8824_companding), nau8824_companding); + +static const char * const nau8824_adc_decimation[] = { + "32", "64", "128", "256" }; + +static const struct soc_enum nau8824_adc_decimation_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_FILTER_CTRL, 0, + ARRAY_SIZE(nau8824_adc_decimation), nau8824_adc_decimation); + +static const char * const nau8824_dac_oversampl[] = { + "64", "256", "128", "", "32" }; + +static const struct soc_enum nau8824_dac_oversampl_enum = + SOC_ENUM_SINGLE(NAU8824_REG_DAC_FILTER_CTRL_1, 0, + ARRAY_SIZE(nau8824_dac_oversampl), nau8824_dac_oversampl); + +static const char * const nau8824_input_channel[] = { + "Input CH0", "Input CH1", "Input CH2", "Input CH3" }; + +static const struct soc_enum nau8824_adc_ch0_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH0_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch1_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH1_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch2_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH2_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch3_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH3_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const char * const nau8824_tdm_slot[] = { + "Slot 0", "Slot 1", "Slot 2", "Slot 3" }; + +static const struct soc_enum nau8824_dac_left_sel_enum = + SOC_ENUM_SINGLE(NAU8824_REG_TDM_CTRL, 6, + ARRAY_SIZE(nau8824_tdm_slot), nau8824_tdm_slot); + +static const struct soc_enum nau8824_dac_right_sel_enum = + SOC_ENUM_SINGLE(NAU8824_REG_TDM_CTRL, 4, + ARRAY_SIZE(nau8824_tdm_slot), nau8824_tdm_slot); + +static const DECLARE_TLV_DB_MINMAX_MUTE(spk_vol_tlv, 0, 2400); +static const DECLARE_TLV_DB_MINMAX(hp_vol_tlv, -3000, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 200, 0); +static const DECLARE_TLV_DB_SCALE(dmic_vol_tlv, -12800, 50, 0); + +static const struct snd_kcontrol_new nau8824_snd_controls[] = { + SOC_ENUM("ADC Companding", nau8824_companding_adc_enum), + SOC_ENUM("DAC Companding", nau8824_companding_dac_enum), + + SOC_ENUM("ADC Decimation Rate", nau8824_adc_decimation_enum), + SOC_ENUM("DAC Oversampling Rate", nau8824_dac_oversampl_enum), + + SOC_SINGLE_TLV("Speaker Right DACR Volume", + NAU8824_REG_CLASSD_GAIN_1, 8, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Left DACL Volume", + NAU8824_REG_CLASSD_GAIN_2, 0, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Left DACR Volume", + NAU8824_REG_CLASSD_GAIN_1, 0, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Right DACL Volume", + NAU8824_REG_CLASSD_GAIN_2, 8, 0x1f, 0, spk_vol_tlv), + + SOC_SINGLE_TLV("Headphone Right DACR Volume", + NAU8824_REG_ATT_PORT0, 8, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Left DACL Volume", + NAU8824_REG_ATT_PORT0, 0, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Right DACL Volume", + NAU8824_REG_ATT_PORT1, 8, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Left DACR Volume", + NAU8824_REG_ATT_PORT1, 0, 0x1f, 0, hp_vol_tlv), + + SOC_SINGLE_TLV("MIC1 Volume", NAU8824_REG_FEPGA_II, + NAU8824_FEPGA_GAINL_SFT, 0x12, 0, mic_vol_tlv), + SOC_SINGLE_TLV("MIC2 Volume", NAU8824_REG_FEPGA_II, + NAU8824_FEPGA_GAINR_SFT, 0x12, 0, mic_vol_tlv), + + SOC_SINGLE_TLV("DMIC1 Volume", NAU8824_REG_ADC_CH0_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC2 Volume", NAU8824_REG_ADC_CH1_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC3 Volume", NAU8824_REG_ADC_CH2_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC4 Volume", NAU8824_REG_ADC_CH3_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + + SOC_ENUM("ADC CH0 Select", nau8824_adc_ch0_enum), + SOC_ENUM("ADC CH1 Select", nau8824_adc_ch1_enum), + SOC_ENUM("ADC CH2 Select", nau8824_adc_ch2_enum), + SOC_ENUM("ADC CH3 Select", nau8824_adc_ch3_enum), + + SOC_SINGLE("ADC CH0 TX Switch", NAU8824_REG_TDM_CTRL, 0, 1, 0), + SOC_SINGLE("ADC CH1 TX Switch", NAU8824_REG_TDM_CTRL, 1, 1, 0), + SOC_SINGLE("ADC CH2 TX Switch", NAU8824_REG_TDM_CTRL, 2, 1, 0), + SOC_SINGLE("ADC CH3 TX Switch", NAU8824_REG_TDM_CTRL, 3, 1, 0), + + SOC_ENUM("DACL Channel Source", nau8824_dac_left_sel_enum), + SOC_ENUM("DACR Channel Source", nau8824_dac_right_sel_enum), + + SOC_SINGLE("DACL LR Mix", NAU8824_REG_DAC_MUTE_CTRL, 0, 1, 0), + SOC_SINGLE("DACR LR Mix", NAU8824_REG_DAC_MUTE_CTRL, 1, 1, 0), +}; + +static int nau8824_output_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Disables the TESTDAC to let DAC signal pass through. */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, NAU8824_TEST_DAC_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8824_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_ANALOG_CONTROL_2, + NAU8824_CLASSD_CLAMP_DIS, NAU8824_CLASSD_CLAMP_DIS); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_ANALOG_CONTROL_2, + NAU8824_CLASSD_CLAMP_DIS, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8824_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Prevent startup click by letting charge pump to ramp up */ + msleep(10); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_JAMNODCLOW, NAU8824_JAMNODCLOW); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_JAMNODCLOW, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int system_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + /* Set clock source to disable or internal clock before the + * playback or capture end. Codec needs clock for Jack + * detection and button press if jack inserted; otherwise, + * the clock should be closed. + */ + if (nau8824_is_jack_inserted(nau8824)) { + nau8824_config_sysclk(nau8824, + NAU8824_CLK_INTERNAL, 0); + } else { + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); + } + } + return 0; +} + +static int dmic_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + int src; + + /* The DMIC clock is gotten from system clock (256fs) divided by + * DMIC_SRC (1, 2, 4, 8, 16, 32). The clock has to be equal or + * less than 3.072 MHz. + */ + for (src = 0; src < 5; src++) { + if ((0x1 << (8 - src)) * nau8824->fs <= DMIC_CLK) + break; + } + dev_dbg(nau8824->dev, "dmic src %d for mclk %d\n", src, nau8824->fs * 256); + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_DMIC_SRC_MASK, (src << NAU8824_CLK_DMIC_SRC_SFT)); + + return 0; +} + +static const struct snd_kcontrol_new nau8824_adc_ch0_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH0_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch1_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH1_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch2_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH2_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch3_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH3_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_left_mixer[] = { + SOC_DAPM_SINGLE("MIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_MIC1_SFT, 1, 0), + SOC_DAPM_SINGLE("HSMIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_HSMIC_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_adc_right_mixer[] = { + SOC_DAPM_SINGLE("MIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODER_MIC2_SFT, 1, 0), + SOC_DAPM_SINGLE("HSMIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODER_HSMIC_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_hp_left_mixer[] = { + SOC_DAPM_SINGLE("DAC Right Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPL_EN_SFT, 1, 0), + SOC_DAPM_SINGLE("DAC Left Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACL_HPL_EN_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_hp_right_mixer[] = { + SOC_DAPM_SINGLE("DAC Left Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACL_HPR_EN_SFT, 1, 0), + SOC_DAPM_SINGLE("DAC Right Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPR_EN_SFT, 1, 0), +}; + +static const char * const nau8824_dac_src[] = { "DACL", "DACR" }; + +static SOC_ENUM_SINGLE_DECL( + nau8824_dacl_enum, NAU8824_REG_DAC_CH0_DGAIN_CTRL, + NAU8824_DAC_CH0_SEL_SFT, nau8824_dac_src); + +static SOC_ENUM_SINGLE_DECL( + nau8824_dacr_enum, NAU8824_REG_DAC_CH1_DGAIN_CTRL, + NAU8824_DAC_CH1_SEL_SFT, nau8824_dac_src); + +static const struct snd_kcontrol_new nau8824_dacl_mux = + SOC_DAPM_ENUM("DACL Source", nau8824_dacl_enum); + +static const struct snd_kcontrol_new nau8824_dacr_mux = + SOC_DAPM_ENUM("DACR Source", nau8824_dacr_enum); + + +static const struct snd_soc_dapm_widget nau8824_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("System Clock", SND_SOC_NOPM, 0, 0, + system_clock_control, SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("HSMIC1"), + SND_SOC_DAPM_INPUT("HSMIC2"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + + SND_SOC_DAPM_SUPPLY("SAR", NAU8824_REG_SAR_ADC, + NAU8824_SAR_ADC_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS", NAU8824_REG_MIC_BIAS, + NAU8824_MICBIAS_POWERUP_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC12 Power", NAU8824_REG_BIAS_ADJ, + NAU8824_DMIC1_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC34 Power", NAU8824_REG_BIAS_ADJ, + NAU8824_DMIC2_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Clock", SND_SOC_NOPM, 0, 0, + dmic_clock_control, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SWITCH("DMIC1 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch0_dmic), + SND_SOC_DAPM_SWITCH("DMIC2 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch1_dmic), + SND_SOC_DAPM_SWITCH("DMIC3 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch2_dmic), + SND_SOC_DAPM_SWITCH("DMIC4 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch3_dmic), + + SND_SOC_DAPM_MIXER("Left ADC", NAU8824_REG_POWER_UP_CONTROL, + 12, 0, nau8824_adc_left_mixer, + ARRAY_SIZE(nau8824_adc_left_mixer)), + SND_SOC_DAPM_MIXER("Right ADC", NAU8824_REG_POWER_UP_CONTROL, + 13, 0, nau8824_adc_right_mixer, + ARRAY_SIZE(nau8824_adc_right_mixer)), + + SND_SOC_DAPM_ADC("ADCL", NULL, NAU8824_REG_ANALOG_ADC_2, + NAU8824_ADCL_EN_SFT, 0), + SND_SOC_DAPM_ADC("ADCR", NULL, NAU8824_REG_ANALOG_ADC_2, + NAU8824_ADCR_EN_SFT, 0), + + SND_SOC_DAPM_AIF_OUT("AIFTX", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFRX", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DACL", NULL, NAU8824_REG_RDAC, + NAU8824_DACL_EN_SFT, 0), + SND_SOC_DAPM_SUPPLY("DACL Clock", NAU8824_REG_RDAC, + NAU8824_DACL_CLK_SFT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DACR", NULL, NAU8824_REG_RDAC, + NAU8824_DACR_EN_SFT, 0), + SND_SOC_DAPM_SUPPLY("DACR Clock", NAU8824_REG_RDAC, + NAU8824_DACR_CLK_SFT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &nau8824_dacl_mux), + SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &nau8824_dacr_mux), + + SND_SOC_DAPM_PGA_S("Output DACL", 0, NAU8824_REG_CHARGE_PUMP_CONTROL, + 8, 1, nau8824_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Output DACR", 0, NAU8824_REG_CHARGE_PUMP_CONTROL, + 9, 1, nau8824_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("ClassD", 0, NAU8824_REG_CLASSD_GAIN_1, + NAU8824_CLASSD_EN_SFT, 0, nau8824_spk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("Left Headphone", NAU8824_REG_CLASSG, + NAU8824_CLASSG_LDAC_EN_SFT, 0, nau8824_hp_left_mixer, + ARRAY_SIZE(nau8824_hp_left_mixer)), + SND_SOC_DAPM_MIXER("Right Headphone", NAU8824_REG_CLASSG, + NAU8824_CLASSG_RDAC_EN_SFT, 0, nau8824_hp_right_mixer, + ARRAY_SIZE(nau8824_hp_right_mixer)), + SND_SOC_DAPM_PGA_S("Charge Pump", 1, NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_CHARGE_PUMP_EN_SFT, 0, nau8824_pump_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA("Output Driver L", + NAU8824_REG_POWER_UP_CONTROL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Output Driver R", + NAU8824_REG_POWER_UP_CONTROL, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Main Driver L", + NAU8824_REG_POWER_UP_CONTROL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Main Driver R", + NAU8824_REG_POWER_UP_CONTROL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Boost Driver", NAU8824_REG_BOOST, + NAU8824_HP_BOOST_DIS_SFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Class G", NAU8824_REG_CLASSG, + NAU8824_CLASSG_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("SPKOUTL"), + SND_SOC_DAPM_OUTPUT("SPKOUTR"), + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route nau8824_dapm_routes[] = { + {"DMIC1 Enable", "Switch", "DMIC1"}, + {"DMIC2 Enable", "Switch", "DMIC2"}, + {"DMIC3 Enable", "Switch", "DMIC3"}, + {"DMIC4 Enable", "Switch", "DMIC4"}, + + {"DMIC1", NULL, "DMIC12 Power"}, + {"DMIC2", NULL, "DMIC12 Power"}, + {"DMIC3", NULL, "DMIC34 Power"}, + {"DMIC4", NULL, "DMIC34 Power"}, + {"DMIC12 Power", NULL, "DMIC Clock"}, + {"DMIC34 Power", NULL, "DMIC Clock"}, + + {"Left ADC", "MIC Switch", "MIC1"}, + {"Left ADC", "HSMIC Switch", "HSMIC1"}, + {"Right ADC", "MIC Switch", "MIC2"}, + {"Right ADC", "HSMIC Switch", "HSMIC2"}, + + {"ADCL", NULL, "Left ADC"}, + {"ADCR", NULL, "Right ADC"}, + + {"AIFTX", NULL, "MICBIAS"}, + {"AIFTX", NULL, "ADCL"}, + {"AIFTX", NULL, "ADCR"}, + {"AIFTX", NULL, "DMIC1 Enable"}, + {"AIFTX", NULL, "DMIC2 Enable"}, + {"AIFTX", NULL, "DMIC3 Enable"}, + {"AIFTX", NULL, "DMIC4 Enable"}, + + {"AIFTX", NULL, "System Clock"}, + {"AIFRX", NULL, "System Clock"}, + + {"DACL", NULL, "AIFRX"}, + {"DACL", NULL, "DACL Clock"}, + {"DACR", NULL, "AIFRX"}, + {"DACR", NULL, "DACR Clock"}, + + {"DACL Mux", "DACL", "DACL"}, + {"DACL Mux", "DACR", "DACR"}, + {"DACR Mux", "DACL", "DACL"}, + {"DACR Mux", "DACR", "DACR"}, + + {"Output DACL", NULL, "DACL Mux"}, + {"Output DACR", NULL, "DACR Mux"}, + + {"ClassD", NULL, "Output DACL"}, + {"ClassD", NULL, "Output DACR"}, + + {"Left Headphone", "DAC Left Switch", "Output DACL"}, + {"Left Headphone", "DAC Right Switch", "Output DACR"}, + {"Right Headphone", "DAC Left Switch", "Output DACL"}, + {"Right Headphone", "DAC Right Switch", "Output DACR"}, + + {"Charge Pump", NULL, "Left Headphone"}, + {"Charge Pump", NULL, "Right Headphone"}, + {"Output Driver L", NULL, "Charge Pump"}, + {"Output Driver R", NULL, "Charge Pump"}, + {"Main Driver L", NULL, "Output Driver L"}, + {"Main Driver R", NULL, "Output Driver R"}, + {"Class G", NULL, "Main Driver L"}, + {"Class G", NULL, "Main Driver R"}, + {"HP Boost Driver", NULL, "Class G"}, + + {"SPKOUTL", NULL, "ClassD"}, + {"SPKOUTR", NULL, "ClassD"}, + {"HPOL", NULL, "HP Boost Driver"}, + {"HPOR", NULL, "HP Boost Driver"}, +}; + +static bool nau8824_is_jack_inserted(struct nau8824 *nau8824) +{ + struct snd_soc_jack *jack = nau8824->jack; + bool insert = FALSE; + + if (nau8824->irq && jack) + insert = jack->status & SND_JACK_HEADPHONE; + + return insert; +} + +static void nau8824_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8824_REG_IRQ, &active_irq); + for (i = 0; i < NAU8824_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8824_REG_CLEAR_INT_REG, clear_irq); + } +} + +static void nau8824_eject_jack(struct nau8824 *nau8824) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + struct regmap *regmap = nau8824->regmap; + + /* Clear all interruption status */ + nau8824_int_status_clear_all(regmap); + + snd_soc_dapm_disable_pin(dapm, "SAR"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + snd_soc_dapm_sync(dapm); + + /* Enable the insertion interruption, disable the ejection + * interruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_KEY_RELEASE_DIS | NAU8824_IRQ_KEY_SHORT_PRESS_DIS | + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_INSERT_DIS, + NAU8824_IRQ_KEY_RELEASE_DIS | NAU8824_IRQ_KEY_SHORT_PRESS_DIS | + NAU8824_IRQ_EJECT_DIS); + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN | NAU8824_IRQ_EJECT_EN, + NAU8824_IRQ_INSERT_EN); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + + /* Close clock for jack type detection at manual mode */ + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); +} + +static void nau8824_jdet_work(struct work_struct *work) +{ + struct nau8824 *nau8824 = container_of( + work, struct nau8824, jdet_work); + struct snd_soc_dapm_context *dapm = nau8824->dapm; + struct regmap *regmap = nau8824->regmap; + int adc_value, event = 0, event_mask = 0; + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(dapm, "SAR"); + snd_soc_dapm_sync(dapm); + + msleep(100); + + regmap_read(regmap, NAU8824_REG_SAR_ADC_DATA_OUT, &adc_value); + adc_value = adc_value & NAU8824_SAR_ADC_DATA_MASK; + dev_dbg(nau8824->dev, "SAR ADC data 0x%02x\n", adc_value); + if (adc_value < HEADSET_SARADC_THD) { + event |= SND_JACK_HEADPHONE; + + snd_soc_dapm_disable_pin(dapm, "SAR"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + snd_soc_dapm_sync(dapm); + } else { + event |= SND_JACK_HEADSET; + } + event_mask |= SND_JACK_HEADSET; + snd_soc_jack_report(nau8824->jack, event, event_mask); + + nau8824_sema_release(nau8824); +} + +static void nau8824_setup_auto_irq(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + /* Enable jack ejection, short key press and release interruption. */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN | NAU8824_IRQ_EJECT_EN, + NAU8824_IRQ_EJECT_EN); + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_KEY_RELEASE_DIS | + NAU8824_IRQ_KEY_SHORT_PRESS_DIS, 0); + /* Enable internal VCO needed for interruptions */ + nau8824_config_sysclk(nau8824, NAU8824_CLK_INTERNAL, 0); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, 0); +} + +static int nau8824_button_decode(int value) +{ + int buttons = 0; + + /* The chip supports up to 8 buttons, but ALSA defines + * only 6 buttons. + */ + if (value & BIT(0)) + buttons |= SND_JACK_BTN_0; + if (value & BIT(1)) + buttons |= SND_JACK_BTN_1; + if (value & BIT(2)) + buttons |= SND_JACK_BTN_2; + if (value & BIT(3)) + buttons |= SND_JACK_BTN_3; + if (value & BIT(4)) + buttons |= SND_JACK_BTN_4; + if (value & BIT(5)) + buttons |= SND_JACK_BTN_5; + + return buttons; +} + +#define NAU8824_BUTTONS (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + +static irqreturn_t nau8824_interrupt(int irq, void *data) +{ + struct nau8824 *nau8824 = (struct nau8824 *)data; + struct regmap *regmap = nau8824->regmap; + int active_irq, clear_irq = 0, event = 0, event_mask = 0; + + if (regmap_read(regmap, NAU8824_REG_IRQ, &active_irq)) { + dev_err(nau8824->dev, "failed to read irq status\n"); + return IRQ_NONE; + } + dev_dbg(nau8824->dev, "IRQ %x\n", active_irq); + + if (active_irq & NAU8824_JACK_EJECTION_DETECTED) { + nau8824_eject_jack(nau8824); + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8824_JACK_EJECTION_DETECTED; + /* release semaphore held after resume, + * and cancel jack detection + */ + nau8824_sema_release(nau8824); + cancel_work_sync(&nau8824->jdet_work); + } else if (active_irq & NAU8824_KEY_SHORT_PRESS_IRQ) { + int key_status, button_pressed; + + regmap_read(regmap, NAU8824_REG_CLEAR_INT_REG, + &key_status); + + /* lower 8 bits of the register are for pressed keys */ + button_pressed = nau8824_button_decode(key_status); + + event |= button_pressed; + dev_dbg(nau8824->dev, "button %x pressed\n", event); + event_mask |= NAU8824_BUTTONS; + clear_irq = NAU8824_KEY_SHORT_PRESS_IRQ; + } else if (active_irq & NAU8824_KEY_RELEASE_IRQ) { + event_mask = NAU8824_BUTTONS; + clear_irq = NAU8824_KEY_RELEASE_IRQ; + } else if (active_irq & NAU8824_JACK_INSERTION_DETECTED) { + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_INSERT_DIS, + NAU8824_IRQ_INSERT_DIS); + regmap_update_bits(regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN, 0); + /* detect microphone and jack type */ + cancel_work_sync(&nau8824->jdet_work); + schedule_work(&nau8824->jdet_work); + + /* Enable interruption for jack type detection at audo + * mode which can detect microphone and jack type. + */ + nau8824_setup_auto_irq(nau8824); + } + + if (!clear_irq) + clear_irq = active_irq; + /* clears the rightmost interruption */ + regmap_write(regmap, NAU8824_REG_CLEAR_INT_REG, clear_irq); + + if (event_mask) + snd_soc_jack_report(nau8824->jack, event, event_mask); + + return IRQ_HANDLED; +} + +static int nau8824_clock_check(struct nau8824 *nau8824, + int stream, int rate, int osr) +{ + int osrate; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (osr >= ARRAY_SIZE(osr_dac_sel)) + return -EINVAL; + osrate = osr_dac_sel[osr].osr; + } else { + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; + } + + if (!osrate || rate * osr > CLK_DA_AD_MAX) { + dev_err(nau8824->dev, "exceed the maximum frequency of CLK_ADC or CLK_DAC\n"); + return -EINVAL; + } + + return 0; +} + +static int nau8824_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + unsigned int val_len = 0, osr, ctrl_val, bclk_fs, bclk_div; + + nau8824_sema_acquire(nau8824, HZ); + + /* CLK_DAC or CLK_ADC = OSR * FS + * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + nau8824->fs = params_rate(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8824->regmap, + NAU8824_REG_DAC_FILTER_CTRL_1, &osr); + osr &= NAU8824_DAC_OVERSAMPLE_MASK; + if (nau8824_clock_check(nau8824, substream->stream, + nau8824->fs, osr)) + return -EINVAL; + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_DAC_SRC_MASK, + osr_dac_sel[osr].clk_src << NAU8824_CLK_DAC_SRC_SFT); + } else { + regmap_read(nau8824->regmap, + NAU8824_REG_ADC_FILTER_CTRL, &osr); + osr &= NAU8824_ADC_SYNC_DOWN_MASK; + if (nau8824_clock_check(nau8824, substream->stream, + nau8824->fs, osr)) + return -EINVAL; + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8824_CLK_ADC_SRC_SFT); + } + + /* make BCLK and LRC divde configuration if the codec as master. */ + regmap_read(nau8824->regmap, + NAU8824_REG_PORT0_I2S_PCM_CTRL_2, &ctrl_val); + if (ctrl_val & NAU8824_I2S_MS_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / nau8824->fs; + if (bclk_fs <= 32) + bclk_div = 0x3; + else if (bclk_fs <= 64) + bclk_div = 0x2; + else if (bclk_fs <= 128) + bclk_div = 0x1; + else if (bclk_fs <= 256) + bclk_div = 0; + else + return -EINVAL; + regmap_update_bits(nau8824->regmap, + NAU8824_REG_PORT0_I2S_PCM_CTRL_2, + NAU8824_I2S_LRC_DIV_MASK | NAU8824_I2S_BLK_DIV_MASK, + (bclk_div << NAU8824_I2S_LRC_DIV_SFT) | bclk_div); + } + + switch (params_width(params)) { + case 16: + val_len |= NAU8824_I2S_DL_16; + break; + case 20: + val_len |= NAU8824_I2S_DL_20; + break; + case 24: + val_len |= NAU8824_I2S_DL_24; + break; + case 32: + val_len |= NAU8824_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_1, + NAU8824_I2S_DL_MASK, val_len); + + nau8824_sema_release(nau8824); + + return 0; +} + +static int nau8824_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + nau8824_sema_acquire(nau8824, HZ); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8824_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8824_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8824_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8824_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8824_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8824_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8824_I2S_DF_PCM_AB; + ctrl1_val |= NAU8824_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_1, + NAU8824_I2S_DF_MASK | NAU8824_I2S_BP_MASK | + NAU8824_I2S_PCMB_EN, ctrl1_val); + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_2, + NAU8824_I2S_MS_MASK, ctrl2_val); + + nau8824_sema_release(nau8824); + + return 0; +} + +/** + * nau8824_calc_fll_param - Calculate FLL parameters. + * @fll_in: external clock provided to codec. + * @fs: sampling rate. + * @fll_param: Pointer to structure of FLL parameters. + * + * Calculate FLL parameters to configure codec. + * + * Returns 0 for success or negative error code. + */ +static int nau8824_calc_fll_param(unsigned int fll_in, + unsigned int fs, struct nau8824_fll *fll_param) +{ + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8824_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256 * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; +} + +static void nau8824_fll_apply(struct regmap *regmap, + struct nau8824_fll *fll_param) +{ + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK | NAU8824_CLK_MCLK_SRC_MASK, + NAU8824_CLK_SRC_MCLK | fll_param->mclk_src); + regmap_update_bits(regmap, NAU8824_REG_FLL1, + NAU8824_FLL_RATIO_MASK, fll_param->ratio); + /* FLL 16-bit fractional input */ + regmap_write(regmap, NAU8824_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8824_REG_FLL4, + NAU8824_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8824_FLL_REF_DIV_SFT); + /* select divided VCO input */ + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_CLK_SW_MASK, NAU8824_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_MASK, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_SDM_EN, NAU8824_SDM_EN); + } else { + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_MASK, NAU8824_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_SDM_EN, 0); + } +} + +/* freq_out must be 256*Fs in order to achieve the best performance */ +static int nau8824_set_pll(struct snd_soc_codec *codec, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + struct nau8824_fll fll_param; + int ret, fs; + + fs = freq_out / 256; + ret = nau8824_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(nau8824->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(nau8824->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8824_fll_apply(nau8824->regmap, &fll_param); + mdelay(2); + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_VCO); + + return 0; +} + +static int nau8824_config_sysclk(struct nau8824 *nau8824, + int clk_id, unsigned int freq) +{ + struct regmap *regmap = nau8824->regmap; + + switch (clk_id) { + case NAU8824_CLK_DIS: + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, 0); + break; + + case NAU8824_CLK_MCLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, 0); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_INTERNAL: + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, NAU8824_DCO_EN); + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_VCO); + break; + + case NAU8824_CLK_FLL_MCLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_MCLK); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_FLL_BLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_BLK); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_FLL_FS: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_FS); + nau8824_sema_release(nau8824); + break; + + default: + dev_err(nau8824->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8824->dev, "Sysclk is %dHz and clock id is %d\n", freq, + clk_id); + + return 0; +} + +static int nau8824_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, int dir) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + return nau8824_config_sysclk(nau8824, clk_id, freq); +} + +static void nau8824_resume_setup(struct nau8824 *nau8824) +{ + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); + if (nau8824->irq) { + /* Clear all interruption status */ + nau8824_int_status_clear_all(nau8824->regmap); + /* Enable jack detection at sleep mode, insertion detection, + * and ejection detection. + */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_INSERT_DIS, 0); + } +} + +static int nau8824_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { + /* Setup codec configuration after resume */ + nau8824_resume_setup(nau8824); + } + break; + + case SND_SOC_BIAS_OFF: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, 0x3ff, 0x3ff); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, 0); + break; + } + + return 0; +} + +static int nau8824_codec_probe(struct snd_soc_codec *codec) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + nau8824->dapm = dapm; + + return 0; +} + +static int __maybe_unused nau8824_suspend(struct snd_soc_codec *codec) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + if (nau8824->irq) { + disable_irq(nau8824->irq); + snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF); + } + regcache_cache_only(nau8824->regmap, true); + regcache_mark_dirty(nau8824->regmap); + + return 0; +} + +static int __maybe_unused nau8824_resume(struct snd_soc_codec *codec) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + regcache_cache_only(nau8824->regmap, false); + regcache_sync(nau8824->regmap); + if (nau8824->irq) { + /* Hold semaphore to postpone playback happening + * until jack detection done. + */ + nau8824_sema_acquire(nau8824, 0); + enable_irq(nau8824->irq); + } + + return 0; +} + +static struct snd_soc_codec_driver nau8824_codec_driver = { + .probe = nau8824_codec_probe, + .set_sysclk = nau8824_set_sysclk, + .set_pll = nau8824_set_pll, + .set_bias_level = nau8824_set_bias_level, + .suspend = nau8824_suspend, + .resume = nau8824_resume, + .suspend_bias_off = true, + + .component_driver = { + .controls = nau8824_snd_controls, + .num_controls = ARRAY_SIZE(nau8824_snd_controls), + .dapm_widgets = nau8824_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8824_dapm_widgets), + .dapm_routes = nau8824_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8824_dapm_routes), + }, +}; + +static const struct snd_soc_dai_ops nau8824_dai_ops = { + .hw_params = nau8824_hw_params, + .set_fmt = nau8824_set_fmt, +}; + +#define NAU8824_RATES SNDRV_PCM_RATE_8000_192000 +#define NAU8824_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8824_dai = { + .name = NAU8824_CODEC_DAI, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8824_RATES, + .formats = NAU8824_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8824_RATES, + .formats = NAU8824_FORMATS, + }, + .ops = &nau8824_dai_ops, +}; + +static const struct regmap_config nau8824_regmap_config = { + .val_bits = NAU8824_REG_ADDR_LEN, + .reg_bits = NAU8824_REG_DATA_LEN, + + .max_register = NAU8824_REG_MAX, + .readable_reg = nau8824_readable_reg, + .writeable_reg = nau8824_writeable_reg, + .volatile_reg = nau8824_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8824_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8824_reg_defaults), +}; + +/** + * nau8824_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int nau8824_enable_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + int ret; + + nau8824->jack = jack; + /* Initiate jack detection work queue */ + INIT_WORK(&nau8824->jdet_work, nau8824_jdet_work); + ret = devm_request_threaded_irq(nau8824->dev, nau8824->irq, NULL, + nau8824_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "nau8824", nau8824); + if (ret) { + dev_err(nau8824->dev, "Cannot request irq %d (%d)\n", + nau8824->irq, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(nau8824_enable_jack_detect); + +static void nau8824_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8824_REG_RESET, 0x00); + regmap_write(regmap, NAU8824_REG_RESET, 0x00); +} + +static void nau8824_setup_buttons(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_TRACKING_GAIN_MASK, + nau8824->sar_voltage << NAU8824_SAR_TRACKING_GAIN_SFT); + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_COMPARE_TIME_MASK, + nau8824->sar_compare_time << NAU8824_SAR_COMPARE_TIME_SFT); + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_SAMPLING_TIME_MASK, + nau8824->sar_sampling_time << NAU8824_SAR_SAMPLING_TIME_SFT); + + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_LEVELS_NR_MASK, + (nau8824->sar_threshold_num - 1) << NAU8824_LEVELS_NR_SFT); + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_HYSTERESIS_MASK, + nau8824->sar_hysteresis << NAU8824_HYSTERESIS_SFT); + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_SHORTKEY_DEBOUNCE_MASK, + nau8824->key_debounce << NAU8824_SHORTKEY_DEBOUNCE_SFT); + + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_1, + (nau8824->sar_threshold[0] << 8) | nau8824->sar_threshold[1]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_2, + (nau8824->sar_threshold[2] << 8) | nau8824->sar_threshold[3]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_3, + (nau8824->sar_threshold[4] << 8) | nau8824->sar_threshold[5]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_4, + (nau8824->sar_threshold[6] << 8) | nau8824->sar_threshold[7]); +} + +static void nau8824_init_regs(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + /* Enable Bias/VMID/VMID Tieoff */ + regmap_update_bits(regmap, NAU8824_REG_BIAS_ADJ, + NAU8824_VMID | NAU8824_VMID_SEL_MASK, NAU8824_VMID | + (nau8824->vref_impedance << NAU8824_VMID_SEL_SFT)); + regmap_update_bits(regmap, NAU8824_REG_BOOST, + NAU8824_GLOBAL_BIAS_EN, NAU8824_GLOBAL_BIAS_EN); + mdelay(2); + regmap_update_bits(regmap, NAU8824_REG_MIC_BIAS, + NAU8824_MICBIAS_VOLTAGE_MASK, nau8824->micbias_voltage); + /* Disable Boost Driver, Automatic Short circuit protection enable */ + regmap_update_bits(regmap, NAU8824_REG_BOOST, + NAU8824_PRECHARGE_DIS | NAU8824_HP_BOOST_DIS | + NAU8824_HP_BOOST_G_DIS | NAU8824_SHORT_SHUTDOWN_EN, + NAU8824_PRECHARGE_DIS | NAU8824_HP_BOOST_DIS | + NAU8824_HP_BOOST_G_DIS | NAU8824_SHORT_SHUTDOWN_EN); + /* Scaling for ADC and DAC clock */ + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_ADC_SRC_MASK | NAU8824_CLK_DAC_SRC_MASK, + (0x1 << NAU8824_CLK_ADC_SRC_SFT) | + (0x1 << NAU8824_CLK_DAC_SRC_SFT)); + regmap_update_bits(regmap, NAU8824_REG_DAC_MUTE_CTRL, + NAU8824_DAC_ZC_EN, NAU8824_DAC_ZC_EN); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_DAC_CH1_EN | NAU8824_DAC_CH0_EN | + NAU8824_ADC_CH0_EN | NAU8824_ADC_CH1_EN | + NAU8824_ADC_CH2_EN | NAU8824_ADC_CH3_EN, + NAU8824_DAC_CH1_EN | NAU8824_DAC_CH0_EN | + NAU8824_ADC_CH0_EN | NAU8824_ADC_CH1_EN | + NAU8824_ADC_CH2_EN | NAU8824_ADC_CH3_EN); + regmap_update_bits(regmap, NAU8824_REG_CLK_GATING_ENA, + NAU8824_CLK_ADC_CH23_EN | NAU8824_CLK_ADC_CH01_EN | + NAU8824_CLK_DAC_CH1_EN | NAU8824_CLK_DAC_CH0_EN | + NAU8824_CLK_I2S_EN | NAU8824_CLK_GAIN_EN | + NAU8824_CLK_SAR_EN | NAU8824_CLK_DMIC_CH23_EN, + NAU8824_CLK_ADC_CH23_EN | NAU8824_CLK_ADC_CH01_EN | + NAU8824_CLK_DAC_CH1_EN | NAU8824_CLK_DAC_CH0_EN | + NAU8824_CLK_I2S_EN | NAU8824_CLK_GAIN_EN | + NAU8824_CLK_SAR_EN | NAU8824_CLK_DMIC_CH23_EN); + /* Class G timer 64ms */ + regmap_update_bits(regmap, NAU8824_REG_CLASSG, + NAU8824_CLASSG_TIMER_MASK, + 0x20 << NAU8824_CLASSG_TIMER_SFT); + regmap_update_bits(regmap, NAU8824_REG_TRIM_SETTINGS, + NAU8824_DRV_CURR_INC, NAU8824_DRV_CURR_INC); + /* Disable DACR/L power */ + regmap_update_bits(regmap, NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_SPKR_PULL_DOWN | NAU8824_SPKL_PULL_DOWN | + NAU8824_POWER_DOWN_DACR | NAU8824_POWER_DOWN_DACL, + NAU8824_SPKR_PULL_DOWN | NAU8824_SPKL_PULL_DOWN | + NAU8824_POWER_DOWN_DACR | NAU8824_POWER_DOWN_DACL); + /* Enable TESTDAC. This sets the analog DAC inputs to a '0' input + * signal to avoid any glitches due to power up transients in both + * the analog and digital DAC circuit. + */ + regmap_update_bits(regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, NAU8824_TEST_DAC_EN); + /* Config L/R channel */ + regmap_update_bits(regmap, NAU8824_REG_DAC_CH0_DGAIN_CTRL, + NAU8824_DAC_CH0_SEL_MASK, NAU8824_DAC_CH0_SEL_I2S0); + regmap_update_bits(regmap, NAU8824_REG_DAC_CH1_DGAIN_CTRL, + NAU8824_DAC_CH1_SEL_MASK, NAU8824_DAC_CH1_SEL_I2S1); + regmap_update_bits(regmap, NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPR_EN | NAU8824_DACL_HPL_EN, + NAU8824_DACR_HPR_EN | NAU8824_DACL_HPL_EN); + /* Default oversampling/decimations settings are unusable + * (audible hiss). Set it to something better. + */ + regmap_update_bits(regmap, NAU8824_REG_ADC_FILTER_CTRL, + NAU8824_ADC_SYNC_DOWN_MASK, NAU8824_ADC_SYNC_DOWN_64); + regmap_update_bits(regmap, NAU8824_REG_DAC_FILTER_CTRL_1, + NAU8824_DAC_CICCLP_OFF | NAU8824_DAC_OVERSAMPLE_MASK, + NAU8824_DAC_CICCLP_OFF | NAU8824_DAC_OVERSAMPLE_64); + /* DAC clock delay 2ns, VREF */ + regmap_update_bits(regmap, NAU8824_REG_RDAC, + NAU8824_RDAC_CLK_DELAY_MASK | NAU8824_RDAC_VREF_MASK, + (0x2 << NAU8824_RDAC_CLK_DELAY_SFT) | + (0x3 << NAU8824_RDAC_VREF_SFT)); + /* PGA input mode selection */ + regmap_update_bits(regmap, NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_SHORT_EN | NAU8824_FEPGA_MODER_SHORT_EN, + NAU8824_FEPGA_MODEL_SHORT_EN | NAU8824_FEPGA_MODER_SHORT_EN); + /* Digital microphone control */ + regmap_update_bits(regmap, NAU8824_REG_ANALOG_CONTROL_1, + NAU8824_DMIC_CLK_DRV_STRG | NAU8824_DMIC_CLK_SLEW_FAST, + NAU8824_DMIC_CLK_DRV_STRG | NAU8824_DMIC_CLK_SLEW_FAST); + regmap_update_bits(regmap, NAU8824_REG_JACK_DET_CTRL, + NAU8824_JACK_LOGIC, + /* jkdet_polarity - 1 is for active-low */ + nau8824->jkdet_polarity ? 0 : NAU8824_JACK_LOGIC); + regmap_update_bits(regmap, + NAU8824_REG_JACK_DET_CTRL, NAU8824_JACK_EJECT_DT_MASK, + (nau8824->jack_eject_debounce << NAU8824_JACK_EJECT_DT_SFT)); + if (nau8824->sar_threshold_num) + nau8824_setup_buttons(nau8824); +} + +static int nau8824_setup_irq(struct nau8824 *nau8824) +{ + /* Disable interruption before codec initiation done */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, 0x3ff, 0x3ff); + regmap_update_bits(nau8824->regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, 0); + + return 0; +} + +static void nau8824_print_device_properties(struct nau8824 *nau8824) +{ + struct device *dev = nau8824->dev; + int i; + + dev_dbg(dev, "jkdet-polarity: %d\n", nau8824->jkdet_polarity); + dev_dbg(dev, "micbias-voltage: %d\n", nau8824->micbias_voltage); + dev_dbg(dev, "vref-impedance: %d\n", nau8824->vref_impedance); + + dev_dbg(dev, "sar-threshold-num: %d\n", nau8824->sar_threshold_num); + for (i = 0; i < nau8824->sar_threshold_num; i++) + dev_dbg(dev, "sar-threshold[%d]=%x\n", i, + nau8824->sar_threshold[i]); + + dev_dbg(dev, "sar-hysteresis: %d\n", nau8824->sar_hysteresis); + dev_dbg(dev, "sar-voltage: %d\n", nau8824->sar_voltage); + dev_dbg(dev, "sar-compare-time: %d\n", nau8824->sar_compare_time); + dev_dbg(dev, "sar-sampling-time: %d\n", nau8824->sar_sampling_time); + dev_dbg(dev, "short-key-debounce: %d\n", nau8824->key_debounce); + dev_dbg(dev, "jack-eject-debounce: %d\n", + nau8824->jack_eject_debounce); +} + +static int nau8824_read_device_properties(struct device *dev, + struct nau8824 *nau8824) { + int ret; + + ret = device_property_read_u32(dev, "nuvoton,jkdet-polarity", + &nau8824->jkdet_polarity); + if (ret) + nau8824->jkdet_polarity = 1; + ret = device_property_read_u32(dev, "nuvoton,micbias-voltage", + &nau8824->micbias_voltage); + if (ret) + nau8824->micbias_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,vref-impedance", + &nau8824->vref_impedance); + if (ret) + nau8824->vref_impedance = 2; + ret = device_property_read_u32(dev, "nuvoton,sar-threshold-num", + &nau8824->sar_threshold_num); + if (ret) + nau8824->sar_threshold_num = 4; + ret = device_property_read_u32_array(dev, "nuvoton,sar-threshold", + nau8824->sar_threshold, nau8824->sar_threshold_num); + if (ret) { + nau8824->sar_threshold[0] = 0x0a; + nau8824->sar_threshold[1] = 0x14; + nau8824->sar_threshold[2] = 0x26; + nau8824->sar_threshold[3] = 0x73; + } + ret = device_property_read_u32(dev, "nuvoton,sar-hysteresis", + &nau8824->sar_hysteresis); + if (ret) + nau8824->sar_hysteresis = 0; + ret = device_property_read_u32(dev, "nuvoton,sar-voltage", + &nau8824->sar_voltage); + if (ret) + nau8824->sar_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,sar-compare-time", + &nau8824->sar_compare_time); + if (ret) + nau8824->sar_compare_time = 1; + ret = device_property_read_u32(dev, "nuvoton,sar-sampling-time", + &nau8824->sar_sampling_time); + if (ret) + nau8824->sar_sampling_time = 1; + ret = device_property_read_u32(dev, "nuvoton,short-key-debounce", + &nau8824->key_debounce); + if (ret) + nau8824->key_debounce = 0; + ret = device_property_read_u32(dev, "nuvoton,jack-eject-debounce", + &nau8824->jack_eject_debounce); + if (ret) + nau8824->jack_eject_debounce = 1; + + return 0; +} + +static int nau8824_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8824 *nau8824 = dev_get_platdata(dev); + int ret, value; + + if (!nau8824) { + nau8824 = devm_kzalloc(dev, sizeof(*nau8824), GFP_KERNEL); + if (!nau8824) + return -ENOMEM; + ret = nau8824_read_device_properties(dev, nau8824); + if (ret) + return ret; + } + i2c_set_clientdata(i2c, nau8824); + + nau8824->regmap = devm_regmap_init_i2c(i2c, &nau8824_regmap_config); + if (IS_ERR(nau8824->regmap)) + return PTR_ERR(nau8824->regmap); + nau8824->dev = dev; + nau8824->irq = i2c->irq; + sema_init(&nau8824->jd_sem, 1); + + nau8824_print_device_properties(nau8824); + + ret = regmap_read(nau8824->regmap, NAU8824_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU8824: %d\n", + ret); + return ret; + } + nau8824_reset_chip(nau8824->regmap); + nau8824_init_regs(nau8824); + + if (i2c->irq) + nau8824_setup_irq(nau8824); + + return snd_soc_register_codec(dev, + &nau8824_codec_driver, &nau8824_dai, 1); +} + + +static int nau8824_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id nau8824_i2c_ids[] = { + { "nau8824", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8824_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id nau8824_of_ids[] = { + { .compatible = "nuvoton,nau8824", }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8824_of_ids); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id nau8824_acpi_match[] = { + { "10508824", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, nau8824_acpi_match); +#endif + +static struct i2c_driver nau8824_i2c_driver = { + .driver = { + .name = "nau8824", + .of_match_table = of_match_ptr(nau8824_of_ids), + .acpi_match_table = ACPI_PTR(nau8824_acpi_match), + }, + .probe = nau8824_i2c_probe, + .remove = nau8824_i2c_remove, + .id_table = nau8824_i2c_ids, +}; +module_i2c_driver(nau8824_i2c_driver); + + +MODULE_DESCRIPTION("ASoC NAU88L24 driver"); +MODULE_AUTHOR("John Hsu <KCHSU0@nuvoton.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/nau8824.h b/sound/soc/codecs/nau8824.h new file mode 100644 index 000000000000..87ac9a382aed --- /dev/null +++ b/sound/soc/codecs/nau8824.h @@ -0,0 +1,466 @@ +/* + * NAU88L24 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu <KCHSU0@nuvoton.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __NAU8824_H__ +#define __NAU8824_H__ + +#define NAU8824_REG_RESET 0x00 +#define NAU8824_REG_ENA_CTRL 0x01 +#define NAU8824_REG_CLK_GATING_ENA 0x02 +#define NAU8824_REG_CLK_DIVIDER 0x03 +#define NAU8824_REG_FLL1 0x04 +#define NAU8824_REG_FLL2 0x05 +#define NAU8824_REG_FLL3 0x06 +#define NAU8824_REG_FLL4 0x07 +#define NAU8824_REG_FLL5 0x08 +#define NAU8824_REG_FLL6 0x09 +#define NAU8824_REG_FLL_VCO_RSV 0x0A +#define NAU8824_REG_JACK_DET_CTRL 0x0D +#define NAU8824_REG_INTERRUPT_SETTING_1 0x0F +#define NAU8824_REG_IRQ 0x10 +#define NAU8824_REG_CLEAR_INT_REG 0x11 +#define NAU8824_REG_INTERRUPT_SETTING 0x12 +#define NAU8824_REG_SAR_ADC 0x13 +#define NAU8824_REG_VDET_COEFFICIENT 0x14 +#define NAU8824_REG_VDET_THRESHOLD_1 0x15 +#define NAU8824_REG_VDET_THRESHOLD_2 0x16 +#define NAU8824_REG_VDET_THRESHOLD_3 0x17 +#define NAU8824_REG_VDET_THRESHOLD_4 0x18 +#define NAU8824_REG_GPIO_SEL 0x1A +#define NAU8824_REG_PORT0_I2S_PCM_CTRL_1 0x1C +#define NAU8824_REG_PORT0_I2S_PCM_CTRL_2 0x1D +#define NAU8824_REG_PORT0_LEFT_TIME_SLOT 0x1E +#define NAU8824_REG_PORT0_RIGHT_TIME_SLOT 0x1F +#define NAU8824_REG_TDM_CTRL 0x20 +#define NAU8824_REG_ADC_HPF_FILTER 0x23 +#define NAU8824_REG_ADC_FILTER_CTRL 0x24 +#define NAU8824_REG_DAC_FILTER_CTRL_1 0x25 +#define NAU8824_REG_DAC_FILTER_CTRL_2 0x26 +#define NAU8824_REG_NOTCH_FILTER_1 0x27 +#define NAU8824_REG_NOTCH_FILTER_2 0x28 +#define NAU8824_REG_EQ1_LOW 0x29 +#define NAU8824_REG_EQ2_EQ3 0x2A +#define NAU8824_REG_EQ4_EQ5 0x2B +#define NAU8824_REG_ADC_CH0_DGAIN_CTRL 0x2D +#define NAU8824_REG_ADC_CH1_DGAIN_CTRL 0x2E +#define NAU8824_REG_ADC_CH2_DGAIN_CTRL 0x2F +#define NAU8824_REG_ADC_CH3_DGAIN_CTRL 0x30 +#define NAU8824_REG_DAC_MUTE_CTRL 0x31 +#define NAU8824_REG_DAC_CH0_DGAIN_CTRL 0x32 +#define NAU8824_REG_DAC_CH1_DGAIN_CTRL 0x33 +#define NAU8824_REG_ADC_TO_DAC_ST 0x34 +#define NAU8824_REG_DRC_KNEE_IP12_ADC_CH01 0x38 +#define NAU8824_REG_DRC_KNEE_IP34_ADC_CH01 0x39 +#define NAU8824_REG_DRC_SLOPE_ADC_CH01 0x3A +#define NAU8824_REG_DRC_ATKDCY_ADC_CH01 0x3B +#define NAU8824_REG_DRC_KNEE_IP12_ADC_CH23 0x3C +#define NAU8824_REG_DRC_KNEE_IP34_ADC_CH23 0x3D +#define NAU8824_REG_DRC_SLOPE_ADC_CH23 0x3E +#define NAU8824_REG_DRC_ATKDCY_ADC_CH23 0x3F +#define NAU8824_REG_DRC_GAINL_ADC0 0x40 +#define NAU8824_REG_DRC_GAINL_ADC1 0x41 +#define NAU8824_REG_DRC_GAINL_ADC2 0x42 +#define NAU8824_REG_DRC_GAINL_ADC3 0x43 +#define NAU8824_REG_DRC_KNEE_IP12_DAC 0x45 +#define NAU8824_REG_DRC_KNEE_IP34_DAC 0x46 +#define NAU8824_REG_DRC_SLOPE_DAC 0x47 +#define NAU8824_REG_DRC_ATKDCY_DAC 0x48 +#define NAU8824_REG_DRC_GAIN_DAC_CH0 0x49 +#define NAU8824_REG_DRC_GAIN_DAC_CH1 0x4A +#define NAU8824_REG_MODE 0x4C +#define NAU8824_REG_MODE1 0x4D +#define NAU8824_REG_MODE2 0x4E +#define NAU8824_REG_CLASSG 0x50 +#define NAU8824_REG_OTP_EFUSE 0x51 +#define NAU8824_REG_OTPDOUT_1 0x53 +#define NAU8824_REG_OTPDOUT_2 0x54 +#define NAU8824_REG_MISC_CTRL 0x55 +#define NAU8824_REG_I2C_TIMEOUT 0x56 +#define NAU8824_REG_TEST_MODE 0x57 +#define NAU8824_REG_I2C_DEVICE_ID 0x58 +#define NAU8824_REG_SAR_ADC_DATA_OUT 0x59 +#define NAU8824_REG_BIAS_ADJ 0x66 +#define NAU8824_REG_PGA_GAIN 0x67 +#define NAU8824_REG_TRIM_SETTINGS 0x68 +#define NAU8824_REG_ANALOG_CONTROL_1 0x69 +#define NAU8824_REG_ANALOG_CONTROL_2 0x6A +#define NAU8824_REG_ENABLE_LO 0x6B +#define NAU8824_REG_GAIN_LO 0x6C +#define NAU8824_REG_CLASSD_GAIN_1 0x6D +#define NAU8824_REG_CLASSD_GAIN_2 0x6E +#define NAU8824_REG_ANALOG_ADC_1 0x71 +#define NAU8824_REG_ANALOG_ADC_2 0x72 +#define NAU8824_REG_RDAC 0x73 +#define NAU8824_REG_MIC_BIAS 0x74 +#define NAU8824_REG_HS_VOLUME_CONTROL 0x75 +#define NAU8824_REG_BOOST 0x76 +#define NAU8824_REG_FEPGA 0x77 +#define NAU8824_REG_FEPGA_II 0x78 +#define NAU8824_REG_FEPGA_SE 0x79 +#define NAU8824_REG_FEPGA_ATTENUATION 0x7A +#define NAU8824_REG_ATT_PORT0 0x7B +#define NAU8824_REG_ATT_PORT1 0x7C +#define NAU8824_REG_POWER_UP_CONTROL 0x7F +#define NAU8824_REG_CHARGE_PUMP_CONTROL 0x80 +#define NAU8824_REG_CHARGE_PUMP_INPUT 0x81 +#define NAU8824_REG_MAX NAU8824_REG_CHARGE_PUMP_INPUT +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8824_REG_ADDR_LEN 16 +#define NAU8824_REG_DATA_LEN 16 + + +/* ENA_CTRL (0x1) */ +#define NAU8824_DMIC_LCH_EDGE_CH23 (0x1 << 12) +#define NAU8824_DMIC_LCH_EDGE_CH01 (0x1 << 11) +#define NAU8824_JD_SLEEP_MODE (0x1 << 10) +#define NAU8824_ADC_CH3_DMIC_SFT 9 +#define NAU8824_ADC_CH3_DMIC_EN (0x1 << NAU8824_ADC_CH3_DMIC_SFT) +#define NAU8824_ADC_CH2_DMIC_SFT 8 +#define NAU8824_ADC_CH2_DMIC_EN (0x1 << NAU8824_ADC_CH2_DMIC_SFT) +#define NAU8824_ADC_CH1_DMIC_SFT 7 +#define NAU8824_ADC_CH1_DMIC_EN (0x1 << NAU8824_ADC_CH1_DMIC_SFT) +#define NAU8824_ADC_CH0_DMIC_SFT 6 +#define NAU8824_ADC_CH0_DMIC_EN (0x1 << NAU8824_ADC_CH0_DMIC_SFT) +#define NAU8824_DAC_CH1_EN (0x1 << 5) +#define NAU8824_DAC_CH0_EN (0x1 << 4) +#define NAU8824_ADC_CH3_EN (0x1 << 3) +#define NAU8824_ADC_CH2_EN (0x1 << 2) +#define NAU8824_ADC_CH1_EN (0x1 << 1) +#define NAU8824_ADC_CH0_EN 0x1 + +/* CLK_GATING_ENA (0x02) */ +#define NAU8824_CLK_ADC_CH23_EN (0x1 << 15) +#define NAU8824_CLK_ADC_CH01_EN (0x1 << 14) +#define NAU8824_CLK_DAC_CH1_EN (0x1 << 13) +#define NAU8824_CLK_DAC_CH0_EN (0x1 << 12) +#define NAU8824_CLK_I2S_EN (0x1 << 7) +#define NAU8824_CLK_GAIN_EN (0x1 << 5) +#define NAU8824_CLK_SAR_EN (0x1 << 3) +#define NAU8824_CLK_DMIC_CH23_EN (0x1 << 1) + +/* CLK_DIVIDER (0x3) */ +#define NAU8824_CLK_SRC_SFT 15 +#define NAU8824_CLK_SRC_MASK (1 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_SRC_VCO (1 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_SRC_MCLK (0 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_MCLK_SRC_MASK (0xf << 0) +#define NAU8824_CLK_DMIC_SRC_SFT 10 +#define NAU8824_CLK_DMIC_SRC_MASK (0x7 << NAU8824_CLK_DMIC_SRC_SFT) +#define NAU8824_CLK_ADC_SRC_SFT 6 +#define NAU8824_CLK_ADC_SRC_MASK (0x3 << NAU8824_CLK_ADC_SRC_SFT) +#define NAU8824_CLK_DAC_SRC_SFT 4 +#define NAU8824_CLK_DAC_SRC_MASK (0x3 << NAU8824_CLK_DAC_SRC_SFT) + +/* FLL1 (0x04) */ +#define NAU8824_FLL_RATIO_MASK (0x7f << 0) + +/* FLL3 (0x06) */ +#define NAU8824_FLL_INTEGER_MASK (0x3ff << 0) +#define NAU8824_FLL_CLK_SRC_SFT 10 +#define NAU8824_FLL_CLK_SRC_MASK (0x3 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_MCLK (0 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_BLK (0x2 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_FS (0x3 << NAU8824_FLL_CLK_SRC_SFT) + +/* FLL4 (0x07) */ +#define NAU8824_FLL_REF_DIV_SFT 10 +#define NAU8824_FLL_REF_DIV_MASK (0x3 << NAU8824_FLL_REF_DIV_SFT) + +/* FLL5 (0x08) */ +#define NAU8824_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8824_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8824_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8824_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8824_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8824_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8824_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8824_FLL_FTR_SW_FILTER (0x0 << 12) + +/* FLL6 (0x9) */ +#define NAU8824_DCO_EN (0x1 << 15) +#define NAU8824_SDM_EN (0x1 << 14) + +/* IRQ (0x10) */ +#define NAU8824_SHORT_CIRCUIT_IRQ (0x1 << 7) +#define NAU8824_IMPEDANCE_MEAS_IRQ (0x1 << 6) +#define NAU8824_KEY_RELEASE_IRQ (0x1 << 5) +#define NAU8824_KEY_LONG_PRESS_IRQ (0x1 << 4) +#define NAU8824_KEY_SHORT_PRESS_IRQ (0x1 << 3) +#define NAU8824_JACK_EJECTION_DETECTED (0x1 << 1) +#define NAU8824_JACK_INSERTION_DETECTED 0x1 + +/* JACK_DET_CTRL (0x0D) */ +#define NAU8824_JACK_EJECT_DT_SFT 2 +#define NAU8824_JACK_EJECT_DT_MASK (0x3 << NAU8824_JACK_EJECT_DT_SFT) +#define NAU8824_JACK_LOGIC 0x1 + + +/* INTERRUPT_SETTING_1 (0x0F) */ +#define NAU8824_IRQ_EJECT_EN (0x1 << 9) +#define NAU8824_IRQ_INSERT_EN (0x1 << 8) + +/* INTERRUPT_SETTING (0x12) */ +#define NAU8824_IRQ_KEY_RELEASE_DIS (0x1 << 5) +#define NAU8824_IRQ_KEY_SHORT_PRESS_DIS (0x1 << 3) +#define NAU8824_IRQ_EJECT_DIS (0x1 << 1) +#define NAU8824_IRQ_INSERT_DIS 0x1 + +/* SAR_ADC (0x13) */ +#define NAU8824_SAR_ADC_EN_SFT 12 +#define NAU8824_SAR_TRACKING_GAIN_SFT 8 +#define NAU8824_SAR_TRACKING_GAIN_MASK (0x7 << NAU8824_SAR_TRACKING_GAIN_SFT) +#define NAU8824_SAR_COMPARE_TIME_SFT 2 +#define NAU8824_SAR_COMPARE_TIME_MASK (3 << 2) +#define NAU8824_SAR_SAMPLING_TIME_SFT 0 +#define NAU8824_SAR_SAMPLING_TIME_MASK (3 << 0) + +/* VDET_COEFFICIENT (0x14) */ +#define NAU8824_SHORTKEY_DEBOUNCE_SFT 12 +#define NAU8824_SHORTKEY_DEBOUNCE_MASK (0x3 << NAU8824_SHORTKEY_DEBOUNCE_SFT) +#define NAU8824_LEVELS_NR_SFT 8 +#define NAU8824_LEVELS_NR_MASK (0x7 << 8) +#define NAU8824_HYSTERESIS_SFT 0 +#define NAU8824_HYSTERESIS_MASK 0xf + +/* PORT0_I2S_PCM_CTRL_1 (0x1C) */ +#define NAU8824_I2S_BP_SFT 7 +#define NAU8824_I2S_BP_MASK (1 << NAU8824_I2S_BP_SFT) +#define NAU8824_I2S_BP_INV (1 << NAU8824_I2S_BP_SFT) +#define NAU8824_I2S_PCMB_SFT 6 +#define NAU8824_I2S_PCMB_EN (1 << NAU8824_I2S_PCMB_SFT) +#define NAU8824_I2S_DL_SFT 2 +#define NAU8824_I2S_DL_MASK (0x3 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_16 (0 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_20 (1 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_24 (2 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_32 (3 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DF_MASK 0x3 +#define NAU8824_I2S_DF_RIGTH 0 +#define NAU8824_I2S_DF_LEFT 1 +#define NAU8824_I2S_DF_I2S 2 +#define NAU8824_I2S_DF_PCM_AB 3 + + +/* PORT0_I2S_PCM_CTRL_2 (0x1D) */ +#define NAU8824_I2S_LRC_DIV_SFT 12 +#define NAU8824_I2S_LRC_DIV_MASK (0x3 << NAU8824_I2S_LRC_DIV_SFT) +#define NAU8824_I2S_MS_SFT 3 +#define NAU8824_I2S_MS_MASK (1 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_MS_MASTER (1 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_MS_SLAVE (0 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_BLK_DIV_MASK 0x7 + +/* ADC_FILTER_CTRL (0x24) */ +#define NAU8824_ADC_SYNC_DOWN_MASK 0x3 +#define NAU8824_ADC_SYNC_DOWN_32 0 +#define NAU8824_ADC_SYNC_DOWN_64 1 +#define NAU8824_ADC_SYNC_DOWN_128 2 +#define NAU8824_ADC_SYNC_DOWN_256 3 + +/* DAC_FILTER_CTRL_1 (0x25) */ +#define NAU8824_DAC_CICCLP_OFF (0x1 << 7) +#define NAU8824_DAC_OVERSAMPLE_MASK 0x7 +#define NAU8824_DAC_OVERSAMPLE_64 0 +#define NAU8824_DAC_OVERSAMPLE_256 1 +#define NAU8824_DAC_OVERSAMPLE_128 2 +#define NAU8824_DAC_OVERSAMPLE_32 4 + +/* DAC_MUTE_CTRL (0x31) */ +#define NAU8824_DAC_CH01_MIX 0x3 +#define NAU8824_DAC_ZC_EN (0x1 << 11) + +/* DAC_CH0_DGAIN_CTRL (0x32) */ +#define NAU8824_DAC_CH0_SEL_SFT 9 +#define NAU8824_DAC_CH0_SEL_MASK (0x1 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_SEL_I2S0 (0x0 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_SEL_I2S1 (0x1 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_VOL_MASK 0x1ff + +/* DAC_CH1_DGAIN_CTRL (0x33) */ +#define NAU8824_DAC_CH1_SEL_SFT 9 +#define NAU8824_DAC_CH1_SEL_MASK (0x1 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_SEL_I2S0 (0x0 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_SEL_I2S1 (0x1 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_VOL_MASK 0x1ff + +/* CLASSG (0x50) */ +#define NAU8824_CLASSG_TIMER_SFT 8 +#define NAU8824_CLASSG_TIMER_MASK (0x3f << NAU8824_CLASSG_TIMER_SFT) +#define NAU8824_CLASSG_LDAC_EN_SFT 2 +#define NAU8824_CLASSG_RDAC_EN_SFT 1 +#define NAU8824_CLASSG_EN_SFT 0 + +/* SAR_ADC_DATA_OUT (0x59) */ +#define NAU8824_SAR_ADC_DATA_MASK 0xff + +/* BIAS_ADJ (0x66) */ +#define NAU8824_VMID (1 << 6) +#define NAU8824_VMID_SEL_SFT 4 +#define NAU8824_VMID_SEL_MASK (3 << NAU8824_VMID_SEL_SFT) +#define NAU8824_DMIC2_EN_SFT 3 +#define NAU8824_DMIC1_EN_SFT 2 + +/* TRIM_SETTINGS (0x68) */ +#define NAU8824_DRV_CURR_INC (1 << 15) + +/* ANALOG_CONTROL_1 (0x69) */ +#define NAU8824_DMIC_CLK_DRV_STRG (1 << 3) +#define NAU8824_DMIC_CLK_SLEW_FAST (0x7) + +/* ANALOG_CONTROL_2 (0x6A) */ +#define NAU8824_CLASSD_CLAMP_DIS_SFT 3 +#define NAU8824_CLASSD_CLAMP_DIS (0x1 << NAU8824_CLASSD_CLAMP_DIS_SFT) + +/* ENABLE_LO (0x6B) */ +#define NAU8824_TEST_DAC_SFT 14 +#define NAU8824_TEST_DAC_EN (0x3 << NAU8824_TEST_DAC_SFT) +#define NAU8824_DACL_HPR_EN_SFT 3 +#define NAU8824_DACL_HPR_EN (0x1 << NAU8824_DACL_HPR_EN_SFT) +#define NAU8824_DACR_HPR_EN_SFT 2 +#define NAU8824_DACR_HPR_EN (0x1 << NAU8824_DACR_HPR_EN_SFT) +#define NAU8824_DACR_HPL_EN_SFT 1 +#define NAU8824_DACR_HPL_EN (0x1 << NAU8824_DACR_HPL_EN_SFT) +#define NAU8824_DACL_HPL_EN_SFT 0 +#define NAU8824_DACL_HPL_EN 0x1 + +/* CLASSD_GAIN_1 (0x6D) */ +#define NAU8824_CLASSD_GAIN_1R_SFT 8 +#define NAU8824_CLASSD_GAIN_1R_MASK (0x1f << NAU8824_CLASSD_GAIN_1R_SFT) +#define NAU8824_CLASSD_EN_SFT 7 +#define NAU8824_CLASSD_EN (0x1 << NAU8824_CLASSD_EN_SFT) +#define NAU8824_CLASSD_GAIN_1L_MASK 0x1f + +/* CLASSD_GAIN_2 (0x6E) */ +#define NAU8824_CLASSD_GAIN_2R_SFT 8 +#define NAU8824_CLASSD_GAIN_2R_MASK (0x1f << NAU8824_CLASSD_GAIN_1R_SFT) +#define NAU8824_CLASSD_EN_SFT 7 +#define NAU8824_CLASSD_EN (0x1 << NAU8824_CLASSD_EN_SFT) +#define NAU8824_CLASSD_GAIN_2L_MASK 0x1f + +/* ANALOG_ADC_2 (0x72) */ +#define NAU8824_ADCR_EN_SFT 7 +#define NAU8824_ADCL_EN_SFT 6 + +/* RDAC (0x73) */ +#define NAU8824_DACR_EN_SFT 13 +#define NAU8824_DACL_EN_SFT 12 +#define NAU8824_DACR_CLK_SFT 9 +#define NAU8824_DACL_CLK_SFT 8 +#define NAU8824_RDAC_CLK_DELAY_SFT 4 +#define NAU8824_RDAC_CLK_DELAY_MASK (0x7 << NAU8824_RDAC_CLK_DELAY_SFT) +#define NAU8824_RDAC_VREF_SFT 2 +#define NAU8824_RDAC_VREF_MASK (0x3 << NAU8824_RDAC_VREF_SFT) + +/* MIC_BIAS (0x74) */ +#define NAU8824_MICBIAS_JKSLV (1 << 14) +#define NAU8824_MICBIAS_JKR2 (1 << 12) +#define NAU8824_MICBIAS_POWERUP_SFT 8 +#define NAU8824_MICBIAS_VOLTAGE_SFT 0 +#define NAU8824_MICBIAS_VOLTAGE_MASK 0x7 + +/* BOOST (0x76) */ +#define NAU8824_PRECHARGE_DIS (0x1 << 13) +#define NAU8824_GLOBAL_BIAS_EN (0x1 << 12) +#define NAU8824_HP_BOOST_DIS_SFT 9 +#define NAU8824_HP_BOOST_DIS (0x1 << NAU8824_HP_BOOST_DIS_SFT) +#define NAU8824_HP_BOOST_G_DIS_SFT 8 +#define NAU8824_HP_BOOST_G_DIS (0x1 << NAU8824_HP_BOOST_G_DIS_SFT) +#define NAU8824_SHORT_SHUTDOWN_DIG_EN (1 << 7) +#define NAU8824_SHORT_SHUTDOWN_EN (1 << 6) + +/* FEPGA (0x77) */ +#define NAU8824_FEPGA_MODER_SHORT_SFT 7 +#define NAU8824_FEPGA_MODER_SHORT_EN (0x1 << NAU8824_FEPGA_MODER_SHORT_SFT) +#define NAU8824_FEPGA_MODER_MIC2_SFT 5 +#define NAU8824_FEPGA_MODER_MIC2_EN (0x1 << NAU8824_FEPGA_MODER_MIC2_SFT) +#define NAU8824_FEPGA_MODER_HSMIC_SFT 4 +#define NAU8824_FEPGA_MODER_HSMIC_EN (0x1 << NAU8824_FEPGA_MODER_HSMIC_SFT) +#define NAU8824_FEPGA_MODEL_SHORT_SFT 3 +#define NAU8824_FEPGA_MODEL_SHORT_EN (0x1 << NAU8824_FEPGA_MODEL_SHORT_SFT) +#define NAU8824_FEPGA_MODEL_MIC1_SFT 1 +#define NAU8824_FEPGA_MODEL_MIC1_EN (0x1 << NAU8824_FEPGA_MODEL_MIC1_SFT) +#define NAU8824_FEPGA_MODEL_HSMIC_SFT 0 +#define NAU8824_FEPGA_MODEL_HSMIC_EN (0x1 << NAU8824_FEPGA_MODEL_HSMIC_SFT) + +/* FEPGA_II (0x78) */ +#define NAU8824_FEPGA_GAINR_SFT 5 +#define NAU8824_FEPGA_GAINR_MASK (0x1f << NAU8824_FEPGA_GAINR_SFT) +#define NAU8824_FEPGA_GAINL_SFT 0 +#define NAU8824_FEPGA_GAINL_MASK 0x1f + +/* CHARGE_PUMP_CONTROL (0x80) */ +#define NAU8824_JAMNODCLOW (0x1 << 15) +#define NAU8824_SPKR_PULL_DOWN (0x1 << 13) +#define NAU8824_SPKL_PULL_DOWN (0x1 << 12) +#define NAU8824_POWER_DOWN_DACR (0x1 << 9) +#define NAU8824_POWER_DOWN_DACL (0x1 << 8) +#define NAU8824_CHARGE_PUMP_EN_SFT 5 +#define NAU8824_CHARGE_PUMP_EN (0x1 << NAU8824_CHARGE_PUMP_EN_SFT) + + +#define NAU8824_CODEC_DAI "nau8824-hifi" + +/* System Clock Source */ +enum { + NAU8824_CLK_DIS, + NAU8824_CLK_MCLK, + NAU8824_CLK_INTERNAL, + NAU8824_CLK_FLL_MCLK, + NAU8824_CLK_FLL_BLK, + NAU8824_CLK_FLL_FS, +}; + +struct nau8824 { + struct device *dev; + struct regmap *regmap; + struct snd_soc_dapm_context *dapm; + struct snd_soc_jack *jack; + struct work_struct jdet_work; + struct semaphore jd_sem; + int fs; + int irq; + int micbias_voltage; + int vref_impedance; + int jkdet_polarity; + int sar_threshold_num; + int sar_threshold[8]; + int sar_hysteresis; + int sar_voltage; + int sar_compare_time; + int sar_sampling_time; + int key_debounce; + int jack_eject_debounce; +}; + +struct nau8824_fll { + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; +}; + +struct nau8824_fll_attr { + unsigned int param; + unsigned int val; +}; + +struct nau8824_osr_attr { + unsigned int osr; + unsigned int clk_src; +}; + + +int nau8824_enable_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack); + +#endif /* _NAU8824_H */ + diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index b281a46d769d..f91221b1ddf0 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1084,13 +1084,28 @@ static int rt5514_parse_dt(struct rt5514_priv *rt5514, struct device *dev) return 0; } +static __maybe_unused int rt5514_i2c_resume(struct device *dev) +{ + struct rt5514_priv *rt5514 = dev_get_drvdata(dev); + unsigned int val; + + /* + * Add a bogus read to avoid rt5514's confusion after s2r in case it + * saw glitches on the i2c lines and thought the other side sent a + * start bit. + */ + regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + + return 0; +} + static int rt5514_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct rt5514_platform_data *pdata = dev_get_platdata(&i2c->dev); struct rt5514_priv *rt5514; int ret; - unsigned int val; + unsigned int val = ~0; rt5514 = devm_kzalloc(&i2c->dev, sizeof(struct rt5514_priv), GFP_KERNEL); @@ -1120,8 +1135,16 @@ static int rt5514_i2c_probe(struct i2c_client *i2c, return ret; } - regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); - if (val != RT5514_DEVICE_ID) { + /* + * The rt5514 can get confused if the i2c lines glitch together, as + * can happen at bootup as regulators are turned off and on. If it's + * in this glitched state the first i2c read will fail, so we'll give + * it one change to retry. + */ + ret = regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + if (ret || val != RT5514_DEVICE_ID) + ret = regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + if (ret || val != RT5514_DEVICE_ID) { dev_err(&i2c->dev, "Device with ID register %x is not rt5514\n", val); return -ENODEV; @@ -1149,10 +1172,15 @@ static int rt5514_i2c_remove(struct i2c_client *i2c) return 0; } -struct i2c_driver rt5514_i2c_driver = { +static const struct dev_pm_ops rt5514_i2_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(NULL, rt5514_i2c_resume) +}; + +static struct i2c_driver rt5514_i2c_driver = { .driver = { .name = "rt5514", .of_match_table = of_match_ptr(rt5514_of_match), + .pm = &rt5514_i2_pm_ops, }, .probe = rt5514_i2c_probe, .remove = rt5514_i2c_remove, diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index e149f3ce5401..87844a45886a 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3542,6 +3542,15 @@ static const struct i2c_device_id rt5645_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id); +#ifdef CONFIG_OF +static const struct of_device_id rt5645_of_match[] = { + { .compatible = "realtek,rt5645", }, + { .compatible = "realtek,rt5650", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5645_of_match); +#endif + #ifdef CONFIG_ACPI static const struct acpi_device_id rt5645_acpi_match[] = { { "10EC5645", 0 }, @@ -3912,6 +3921,7 @@ static void rt5645_i2c_shutdown(struct i2c_client *i2c) static struct i2c_driver rt5645_i2c_driver = { .driver = { .name = "rt5645", + .of_match_table = of_match_ptr(rt5645_of_match), .acpi_match_table = ACPI_PTR(rt5645_acpi_match), }, .probe = rt5645_i2c_probe, diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 476135ec5726..8cd22307f5b6 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -1139,7 +1139,8 @@ static void rt5665_enable_push_button_irq(struct snd_soc_codec *codec, bool enable) { if (enable) { - snd_soc_write(codec, RT5665_4BTN_IL_CMD_1, 0x000b); + snd_soc_write(codec, RT5665_4BTN_IL_CMD_1, 0x0003); + snd_soc_update_bits(codec, RT5665_SAR_IL_CMD_9, 0x1, 0x1); snd_soc_write(codec, RT5665_IL_CMD_1, 0x0048); snd_soc_update_bits(codec, RT5665_4BTN_IL_CMD_2, RT5665_4BTN_IL_MASK | RT5665_4BTN_IL_RST_MASK, @@ -1192,10 +1193,13 @@ static int rt5665_headset_detect(struct snd_soc_codec *codec, int jack_insert) } regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, - 0x180, 0x180); + 0x1a0, 0x120); regmap_write(rt5665->regmap, RT5665_EJD_CTRL_3, 0x3424); + regmap_write(rt5665->regmap, RT5665_IL_CMD_1, 0x0048); regmap_write(rt5665->regmap, RT5665_SAR_IL_CMD_1, 0xa291); + usleep_range(10000, 15000); + rt5665->sar_adc_value = snd_soc_read(rt5665->codec, RT5665_SAR_IL_CMD_4) & 0x7ff; @@ -1256,8 +1260,8 @@ static void rt5665_jd_check_handler(struct work_struct *work) } } -int rt5665_set_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *hs_jack) +static int rt5665_set_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *hs_jack, void *data) { struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); @@ -1284,7 +1288,6 @@ int rt5665_set_jack_detect(struct snd_soc_codec *codec, return 0; } -EXPORT_SYMBOL_GPL(rt5665_set_jack_detect); static void rt5665_jack_detect_handler(struct work_struct *work) { @@ -2600,6 +2603,55 @@ static int rt5655_set_verf(struct snd_soc_dapm_widget *w, return 0; } +static int rt5665_i2s_pin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + unsigned int val1, val2, mask1, mask2 = 0; + + switch (w->shift) { + case RT5665_PWR_I2S2_1_BIT: + mask1 = RT5665_GP2_PIN_MASK | RT5665_GP3_PIN_MASK | + RT5665_GP4_PIN_MASK | RT5665_GP5_PIN_MASK; + val1 = RT5665_GP2_PIN_BCLK2 | RT5665_GP3_PIN_LRCK2 | + RT5665_GP4_PIN_DACDAT2_1 | RT5665_GP5_PIN_ADCDAT2_1; + break; + case RT5665_PWR_I2S2_2_BIT: + mask1 = RT5665_GP2_PIN_MASK | RT5665_GP3_PIN_MASK | + RT5665_GP8_PIN_MASK; + val1 = RT5665_GP2_PIN_BCLK2 | RT5665_GP3_PIN_LRCK2 | + RT5665_GP8_PIN_DACDAT2_2; + mask2 = RT5665_GP9_PIN_MASK; + val2 = RT5665_GP9_PIN_ADCDAT2_2; + break; + case RT5665_PWR_I2S3_BIT: + mask1 = RT5665_GP6_PIN_MASK | RT5665_GP7_PIN_MASK | + RT5665_GP8_PIN_MASK; + val1 = RT5665_GP6_PIN_BCLK3 | RT5665_GP7_PIN_LRCK3 | + RT5665_GP8_PIN_DACDAT3; + mask2 = RT5665_GP9_PIN_MASK; + val2 = RT5665_GP9_PIN_ADCDAT3; + break; + } + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_1, mask1, val1); + if (mask2) + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_2, + mask2, val2); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_1, mask1, 0); + if (mask2) + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_2, + mask2, 0); + break; + default: + return 0; + } + + return 0; +} static const struct snd_soc_dapm_widget rt5665_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("LDO2", RT5665_PWR_ANLG_3, RT5665_PWR_LDO2_BIT, 0, @@ -2852,11 +2904,14 @@ static const struct snd_soc_dapm_widget rt5665_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("I2S1_2", RT5665_PWR_DIG_1, RT5665_PWR_I2S1_2_BIT, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("I2S2_1", RT5665_PWR_DIG_1, RT5665_PWR_I2S2_1_BIT, - 0, NULL, 0), + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("I2S2_2", RT5665_PWR_DIG_1, RT5665_PWR_I2S2_2_BIT, - 0, NULL, 0), + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("I2S3", RT5665_PWR_DIG_1, RT5665_PWR_I2S3_BIT, - 0, NULL, 0), + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("IF1 DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -3963,12 +4018,68 @@ static const struct snd_soc_dapm_route rt5665_dapm_routes[] = { {"PDMR", NULL, "PDM R Playback"}, }; +static int rt5665_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val = 0; + + if (rx_mask || tx_mask) + val |= RT5665_I2S1_MODE_TDM; + + switch (slots) { + case 4: + val |= RT5665_TDM_IN_CH_4; + val |= RT5665_TDM_OUT_CH_4; + break; + case 6: + val |= RT5665_TDM_IN_CH_6; + val |= RT5665_TDM_OUT_CH_6; + break; + case 8: + val |= RT5665_TDM_IN_CH_8; + val |= RT5665_TDM_OUT_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + switch (slot_width) { + case 20: + val |= RT5665_TDM_IN_LEN_20; + val |= RT5665_TDM_OUT_LEN_20; + break; + case 24: + val |= RT5665_TDM_IN_LEN_24; + val |= RT5665_TDM_OUT_LEN_24; + break; + case 32: + val |= RT5665_TDM_IN_LEN_32; + val |= RT5665_TDM_OUT_LEN_32; + break; + case 16: + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, RT5665_TDM_CTRL_1, + RT5665_I2S1_MODE_MASK | RT5665_TDM_IN_CH_MASK | + RT5665_TDM_OUT_CH_MASK | RT5665_TDM_IN_LEN_MASK | + RT5665_TDM_OUT_LEN_MASK, val); + + return 0; +} + + static int rt5665_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); - unsigned int val_len = 0, val_clk, mask_clk, val_bits = 0x0100; + unsigned int val_len = 0, val_clk, reg_clk, mask_clk, val_bits = 0x0100; int pre_div, frame_size; rt5665->lrck[dai->id] = params_rate(params); @@ -4009,6 +4120,10 @@ static int rt5665_hw_params(struct snd_pcm_substream *substream, switch (dai->id) { case RT5665_AIF1_1: case RT5665_AIF1_2: + if (params_channels(params) > 2) + rt5665_set_tdm_slot(dai, 0xf, 0xf, + params_channels(params), params_width(params)); + reg_clk = RT5665_ADDA_CLK_1; mask_clk = RT5665_I2S_PD1_MASK; val_clk = pre_div << RT5665_I2S_PD1_SFT; snd_soc_update_bits(codec, RT5665_I2S1_SDP, @@ -4016,12 +4131,14 @@ static int rt5665_hw_params(struct snd_pcm_substream *substream, break; case RT5665_AIF2_1: case RT5665_AIF2_2: + reg_clk = RT5665_ADDA_CLK_2; mask_clk = RT5665_I2S_PD2_MASK; val_clk = pre_div << RT5665_I2S_PD2_SFT; snd_soc_update_bits(codec, RT5665_I2S2_SDP, RT5665_I2S_DL_MASK, val_len); break; case RT5665_AIF3: + reg_clk = RT5665_ADDA_CLK_2; mask_clk = RT5665_I2S_PD3_MASK; val_clk = pre_div << RT5665_I2S_PD3_SFT; snd_soc_update_bits(codec, RT5665_I2S3_SDP, @@ -4032,7 +4149,7 @@ static int rt5665_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - snd_soc_update_bits(codec, RT5665_ADDA_CLK_1, mask_clk, val_clk); + snd_soc_update_bits(codec, reg_clk, mask_clk, val_clk); snd_soc_update_bits(codec, RT5665_STO1_DAC_SIL_DET, 0x3700, val_bits); switch (rt5665->lrck[dai->id]) { @@ -4125,10 +4242,9 @@ static int rt5665_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } -static int rt5665_set_dai_sysclk(struct snd_soc_dai *dai, - int clk_id, unsigned int freq, int dir) +static int rt5665_set_codec_sysclk(struct snd_soc_codec *codec, int clk_id, + int source, unsigned int freq, int dir) { - struct snd_soc_codec *codec = dai->codec; struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); unsigned int reg_val = 0; @@ -4154,20 +4270,20 @@ static int rt5665_set_dai_sysclk(struct snd_soc_dai *dai, rt5665->sysclk = freq; rt5665->sysclk_src = clk_id; - dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + dev_dbg(codec->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); return 0; } -static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, - unsigned int freq_in, unsigned int freq_out) +static int rt5665_set_codec_pll(struct snd_soc_codec *codec, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) { - struct snd_soc_codec *codec = dai->codec; struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); struct rl6231_pll_code pll_code; int ret; - if (Source == rt5665->pll_src && freq_in == rt5665->pll_in && + if (source == rt5665->pll_src && freq_in == rt5665->pll_in && freq_out == rt5665->pll_out) return 0; @@ -4181,7 +4297,7 @@ static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, return 0; } - switch (Source) { + switch (source) { case RT5665_PLL1_S_MCLK: snd_soc_update_bits(codec, RT5665_GLB_CLK, RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_MCLK); @@ -4199,7 +4315,7 @@ static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_BCLK3); break; default: - dev_err(codec->dev, "Unknown PLL Source %d\n", Source); + dev_err(codec->dev, "Unknown PLL Source %d\n", source); return -EINVAL; } @@ -4221,62 +4337,7 @@ static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, rt5665->pll_in = freq_in; rt5665->pll_out = freq_out; - rt5665->pll_src = Source; - - return 0; -} - -static int rt5665_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, - unsigned int rx_mask, int slots, int slot_width) -{ - struct snd_soc_codec *codec = dai->codec; - unsigned int val = 0; - - if (rx_mask || tx_mask) - val |= RT5665_I2S1_MODE_TDM; - - switch (slots) { - case 4: - val |= RT5665_TDM_IN_CH_4; - val |= RT5665_TDM_OUT_CH_4; - break; - case 6: - val |= RT5665_TDM_IN_CH_6; - val |= RT5665_TDM_OUT_CH_6; - break; - case 8: - val |= RT5665_TDM_IN_CH_8; - val |= RT5665_TDM_OUT_CH_8; - break; - case 2: - break; - default: - return -EINVAL; - } - - switch (slot_width) { - case 20: - val |= RT5665_TDM_IN_LEN_20; - val |= RT5665_TDM_OUT_LEN_20; - break; - case 24: - val |= RT5665_TDM_IN_LEN_24; - val |= RT5665_TDM_OUT_LEN_24; - break; - case 32: - val |= RT5665_TDM_IN_LEN_32; - val |= RT5665_TDM_OUT_LEN_32; - break; - case 16: - break; - default: - return -EINVAL; - } - - snd_soc_update_bits(codec, RT5665_TDM_CTRL_1, - RT5665_I2S1_MODE_MASK | RT5665_TDM_IN_CH_MASK | - RT5665_TDM_OUT_CH_MASK | RT5665_TDM_IN_LEN_MASK | - RT5665_TDM_OUT_LEN_MASK, val); + rt5665->pll_src = source; return 0; } @@ -4393,9 +4454,7 @@ static int rt5665_resume(struct snd_soc_codec *codec) static const struct snd_soc_dai_ops rt5665_aif_dai_ops = { .hw_params = rt5665_hw_params, .set_fmt = rt5665_set_dai_fmt, - .set_sysclk = rt5665_set_dai_sysclk, .set_tdm_slot = rt5665_set_tdm_slot, - .set_pll = rt5665_set_dai_pll, .set_bclk_ratio = rt5665_set_bclk_ratio, }; @@ -4504,7 +4563,10 @@ static struct snd_soc_codec_driver soc_codec_dev_rt5665 = { .num_dapm_widgets = ARRAY_SIZE(rt5665_dapm_widgets), .dapm_routes = rt5665_dapm_routes, .num_dapm_routes = ARRAY_SIZE(rt5665_dapm_routes), - } + }, + .set_sysclk = rt5665_set_codec_sysclk, + .set_pll = rt5665_set_codec_pll, + .set_jack = rt5665_set_jack_detect, }; @@ -4783,7 +4845,7 @@ static int rt5665_i2c_probe(struct i2c_client *i2c, regmap_write(rt5665->regmap, RT5665_HP_LOGIC_CTRL_2, 0x0002); regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, - 0xf000 | RT5665_VREF_POW_MASK, 0xd000 | RT5665_VREF_POW_REG); + 0xf000 | RT5665_VREF_POW_MASK, 0xe000 | RT5665_VREF_POW_REG); /* Work around for pow_pump */ regmap_update_bits(rt5665->regmap, RT5665_STO1_DAC_SIL_DET, RT5665_DEB_STO_DAC_MASK, RT5665_DEB_80_MS); diff --git a/sound/soc/codecs/rt5665.h b/sound/soc/codecs/rt5665.h index a30f5e6d0628..1db5c6a62a8e 100644 --- a/sound/soc/codecs/rt5665.h +++ b/sound/soc/codecs/rt5665.h @@ -1984,7 +1984,5 @@ enum { int rt5665_sel_asrc_clk_src(struct snd_soc_codec *codec, unsigned int filter_mask, unsigned int clk_src); -int rt5665_set_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *hs_jack); #endif /* __RT5665_H__ */ diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 17d20b99f041..e27c5a4a0a15 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -2835,6 +2835,27 @@ static const struct dmi_system_id dmi_platform_intel_braswell[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Wyse 3040"), }, }, + { + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), + }, + }, + { + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), + }, + }, + { + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), + }, + }, {} }; diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index abc802a5a479..65ac4518ad06 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5035,6 +5035,12 @@ static const struct i2c_device_id rt5677_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, rt5677_i2c_id); +static const struct of_device_id rt5677_of_match[] = { + { .compatible = "realtek,rt5677", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5677_of_match); + static const struct acpi_gpio_params plug_det_gpio = { RT5677_GPIO_PLUG_DET, 0, false }; static const struct acpi_gpio_params mic_present_gpio = { RT5677_GPIO_MIC_PRESENT_L, 0, false }; static const struct acpi_gpio_params headphone_enable_gpio = { RT5677_GPIO_HP_AMP_SHDN_L, 0, false }; @@ -5294,6 +5300,7 @@ static int rt5677_i2c_remove(struct i2c_client *i2c) static struct i2c_driver rt5677_i2c_driver = { .driver = { .name = "rt5677", + .of_match_table = rt5677_of_match, }, .probe = rt5677_i2c_probe, .remove = rt5677_i2c_remove, diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 1589325855bc..5a2702edeb77 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -99,6 +99,13 @@ enum sgtl5000_micbias_resistor { SGTL5000_MICBIAS_8K = 8, }; +enum { + I2S_LRCLK_STRENGTH_DISABLE, + I2S_LRCLK_STRENGTH_LOW, + I2S_LRCLK_STRENGTH_MEDIUM, + I2S_LRCLK_STRENGTH_HIGH, +}; + /* sgtl5000 private structure in codec */ struct sgtl5000_priv { int sysclk; /* sysclk rate */ @@ -111,6 +118,7 @@ struct sgtl5000_priv { int revision; u8 micbias_resistor; u8 micbias_voltage; + u8 lrclk_strength; }; /* @@ -1089,6 +1097,7 @@ static int sgtl5000_enable_regulators(struct i2c_client *client) static int sgtl5000_probe(struct snd_soc_codec *codec) { int ret; + u16 reg; struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); /* power up sgtl5000 */ @@ -1118,7 +1127,8 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) SGTL5000_DAC_MUTE_RIGHT | SGTL5000_DAC_MUTE_LEFT); - snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f); + reg = ((sgtl5000->lrclk_strength) << SGTL5000_PAD_I2S_LRCLK_SHIFT | 0x5f); + snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, reg); snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL, SGTL5000_HP_ZCD_EN | @@ -1347,6 +1357,13 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, } } + sgtl5000->lrclk_strength = I2S_LRCLK_STRENGTH_LOW; + if (!of_property_read_u32(np, "lrclk-strength", &value)) { + if (value > I2S_LRCLK_STRENGTH_HIGH) + value = I2S_LRCLK_STRENGTH_LOW; + sgtl5000->lrclk_strength = value; + } + /* Ensure sgtl5000 will start with sane register values */ sgtl5000_fill_defaults(client); diff --git a/sound/soc/codecs/ssm4567.c b/sound/soc/codecs/ssm4567.c index 2bb5a11c9ba1..a622623e8558 100644 --- a/sound/soc/codecs/ssm4567.c +++ b/sound/soc/codecs/ssm4567.c @@ -485,6 +485,14 @@ static const struct i2c_device_id ssm4567_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, ssm4567_i2c_ids); +#ifdef CONFIG_OF +static const struct of_device_id ssm4567_of_match[] = { + { .compatible = "adi,ssm4567", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm4567_of_match); +#endif + #ifdef CONFIG_ACPI static const struct acpi_device_id ssm4567_acpi_match[] = { @@ -498,6 +506,7 @@ MODULE_DEVICE_TABLE(acpi, ssm4567_acpi_match); static struct i2c_driver ssm4567_driver = { .driver = { .name = "ssm4567", + .of_match_table = of_match_ptr(ssm4567_of_match), .acpi_match_table = ACPI_PTR(ssm4567_acpi_match), }, .probe = ssm4567_i2c_probe, diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index d4b384e4b266..660734359bf3 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -375,9 +375,16 @@ static const struct i2c_device_id sta529_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, sta529_i2c_id); +static const struct of_device_id sta529_of_match[] = { + { .compatible = "st,sta529", }, + { } +}; +MODULE_DEVICE_TABLE(of, sta529_of_match); + static struct i2c_driver sta529_i2c_driver = { .driver = { .name = "sta529", + .of_match_table = sta529_of_match, }, .probe = sta529_i2c_probe, .remove = sta529_i2c_remove, diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c index baf455e8c2f7..8840f72f3c4a 100644 --- a/sound/soc/codecs/tas2552.c +++ b/sound/soc/codecs/tas2552.c @@ -611,7 +611,7 @@ probe_fail: regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies), tas2552->supplies); - return -EIO; + return ret; } static int tas2552_codec_remove(struct snd_soc_codec *codec) @@ -637,7 +637,7 @@ static int tas2552_suspend(struct snd_soc_codec *codec) if (ret != 0) dev_err(codec->dev, "Failed to disable supplies: %d\n", ret); - return 0; + return ret; } static int tas2552_resume(struct snd_soc_codec *codec) @@ -653,7 +653,7 @@ static int tas2552_resume(struct snd_soc_codec *codec) ret); } - return 0; + return ret; } #else #define tas2552_suspend NULL diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 410cae0f2060..628a8eeaab68 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -174,10 +174,9 @@ static const struct snd_soc_dapm_route tlv320aic23_intercon[] = { {"ROUT", NULL, "Output Mixer"}, /* Inputs */ - {"Line Input", "NULL", "LLINEIN"}, - {"Line Input", "NULL", "RLINEIN"}, - - {"Mic Input", "NULL", "MICIN"}, + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Input", NULL, "MICIN"}, /* input mux */ {"Capture Source", "Line", "Line Input"}, diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 748036e851ea..2b6ad09e0886 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -606,6 +606,14 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { twl6040_headset_power_get_enum, twl6040_headset_power_put_enum), + /* Left HS PDM data routed to Right HSDAC */ + SOC_SINGLE("Headset Mono to Stereo Playback Switch", + TWL6040_REG_HSRCTL, 7, 1, 0), + + /* Left HF PDM data routed to Right HFDAC */ + SOC_SINGLE("Handsfree Mono to Stereo Playback Switch", + TWL6040_REG_HFRCTL, 5, 1, 0), + SOC_ENUM_EXT("PLL Selection", twl6040_power_mode_enum, twl6040_pll_get_enum, twl6040_pll_put_enum), }; diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 2918fdb95e58..61cdc79840e7 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -791,9 +791,16 @@ static const struct i2c_device_id uda1380_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); +static const struct of_device_id uda1380_of_match[] = { + { .compatible = "nxp,uda1380", }, + { } +}; +MODULE_DEVICE_TABLE(of, uda1380_of_match); + static struct i2c_driver uda1380_i2c_driver = { .driver = { .name = "uda1380-codec", + .of_match_table = uda1380_of_match, }, .probe = uda1380_i2c_probe, .remove = uda1380_i2c_remove, diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 560575000cc5..138a84efdd54 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -2014,7 +2014,7 @@ static void wm5100_micd_irq(struct wm5100_priv *wm5100) ret = regmap_read(wm5100->regmap, WM5100_MIC_DETECT_3, &val); if (ret != 0) { - dev_err(wm5100->dev, "Failed to read micropone status: %d\n", + dev_err(wm5100->dev, "Failed to read microphone status: %d\n", ret); return; } diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 6e887c2c42b1..237eeb9a8b97 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -24,6 +24,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/irq.h> #include <linux/mutex.h> @@ -115,10 +116,19 @@ static const struct reg_default wm8903_reg_defaults[] = { { 172, 0x0000 }, /* R172 - Analogue Output Bias 0 */ }; +#define WM8903_NUM_SUPPLIES 4 +static const char *wm8903_supply_names[WM8903_NUM_SUPPLIES] = { + "AVDD", + "CPVDD", + "DBVDD", + "DCVDD", +}; + struct wm8903_priv { struct wm8903_platform_data *pdata; struct device *dev; struct regmap *regmap; + struct regulator_bulk_data supplies[WM8903_NUM_SUPPLIES]; int sysclk; int irq; @@ -2030,6 +2040,23 @@ static int wm8903_i2c_probe(struct i2c_client *i2c, pdata = wm8903->pdata; + for (i = 0; i < ARRAY_SIZE(wm8903->supplies); i++) + wm8903->supplies[i].supply = wm8903_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + ret = regmap_read(wm8903->regmap, WM8903_SW_RESET_AND_ID, &val); if (ret != 0) { dev_err(&i2c->dev, "Failed to read chip ID: %d\n", ret); @@ -2160,6 +2187,8 @@ static int wm8903_i2c_probe(struct i2c_client *i2c, return 0; err: + regulator_bulk_disable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); return ret; } @@ -2167,6 +2196,8 @@ static int wm8903_i2c_remove(struct i2c_client *client) { struct wm8903_priv *wm8903 = i2c_get_clientdata(client); + regulator_bulk_disable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); if (client->irq) free_irq(client->irq, wm8903); wm8903_free_gpio(wm8903); diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 3bf081a7e450..9ed455700954 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -604,12 +604,150 @@ static const int bclk_divs[] = { 120, 160, 220, 240, 320, 320, 320 }; +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @wm8960_priv: wm8960 codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff, closest = mclk; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @codec: codec structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * < 0, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int sysclk, bclk, lrclk, freq_out; + int diff, closest, best_freq_out; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + closest = freq_in; + + best_freq_out = -EINVAL; + *sysclk_idx = *dac_idx = *bclk_idx = -1; + + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + return freq_out; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + best_freq_out = freq_out; + } + } + } + } + + return best_freq_out; +} static int wm8960_configure_clocking(struct snd_soc_codec *codec) { struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - int sysclk, bclk, lrclk, freq_out, freq_in; + int freq_out, freq_in; u16 iface1 = snd_soc_read(codec, WM8960_IFACE1); int i, j, k; + int ret; if (!(iface1 & (1<<6))) { dev_dbg(codec->dev, @@ -623,8 +761,6 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec) } freq_in = wm8960->freq_in; - bclk = wm8960->bclk; - lrclk = wm8960->lrclk; /* * If it's sysclk auto mode, check if the MCLK can provide sysclk or * not. If MCLK can provide sysclk, using MCLK to provide sysclk @@ -643,60 +779,21 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec) } if (wm8960->clk_id != WM8960_SYSCLK_PLL) { - /* check if the sysclk frequency is available. */ - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { - if (sysclk_divs[i] == -1) - continue; - sysclk = freq_out / sysclk_divs[i]; - for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { - if (sysclk != dac_divs[j] * lrclk) - continue; - for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) - if (sysclk == bclk * bclk_divs[k] / 10) - break; - if (k != ARRAY_SIZE(bclk_divs)) - break; - } - if (j != ARRAY_SIZE(dac_divs)) - break; - } - - if (i != ARRAY_SIZE(sysclk_divs)) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { goto configure_clock; } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { dev_err(codec->dev, "failed to configure clock\n"); return -EINVAL; } } - /* get a available pll out frequency and set pll */ - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { - if (sysclk_divs[i] == -1) - continue; - for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { - sysclk = lrclk * dac_divs[j]; - freq_out = sysclk * sysclk_divs[i]; - for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { - if (sysclk == bclk * bclk_divs[k] / 10 && - is_pll_freq_available(freq_in, freq_out)) { - wm8960_set_pll(codec, - freq_in, freq_out); - break; - } else { - continue; - } - } - if (k != ARRAY_SIZE(bclk_divs)) - break; - } - if (j != ARRAY_SIZE(dac_divs)) - break; - } - - if (i == ARRAY_SIZE(sysclk_divs)) { - dev_err(codec->dev, "failed to configure clock\n"); - return -EINVAL; + freq_out = wm8960_configure_pll(codec, freq_in, &i, &j, &k); + if (freq_out < 0) { + dev_err(codec->dev, "failed to configure clock via PLL\n"); + return freq_out; } + wm8960_set_pll(codec, freq_in, freq_out); configure_clock: /* configure sysclk clock */ diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 90b2d418ef60..cf761e2d7546 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -1071,9 +1071,16 @@ static const struct i2c_device_id wm8978_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id); +static const struct of_device_id wm8978_of_match[] = { + { .compatible = "wlf,wm8978", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8978_of_match); + static struct i2c_driver wm8978_i2c_driver = { .driver = { .name = "wm8978", + .of_match_table = wm8978_of_match, }, .probe = wm8978_i2c_probe, .remove = wm8978_i2c_remove, diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index bbdb72f73df1..20695b691aff 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -112,17 +112,22 @@ #define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ #define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ -#define ADSP2_CONTROL 0x0 -#define ADSP2_CLOCKING 0x1 -#define ADSP2_STATUS1 0x4 -#define ADSP2_WDMA_CONFIG_1 0x30 -#define ADSP2_WDMA_CONFIG_2 0x31 -#define ADSP2_RDMA_CONFIG_1 0x34 - -#define ADSP2_SCRATCH0 0x40 -#define ADSP2_SCRATCH1 0x41 -#define ADSP2_SCRATCH2 0x42 -#define ADSP2_SCRATCH3 0x43 +#define ADSP2_CONTROL 0x0 +#define ADSP2_CLOCKING 0x1 +#define ADSP2V2_CLOCKING 0x2 +#define ADSP2_STATUS1 0x4 +#define ADSP2_WDMA_CONFIG_1 0x30 +#define ADSP2_WDMA_CONFIG_2 0x31 +#define ADSP2V2_WDMA_CONFIG_2 0x32 +#define ADSP2_RDMA_CONFIG_1 0x34 + +#define ADSP2_SCRATCH0 0x40 +#define ADSP2_SCRATCH1 0x41 +#define ADSP2_SCRATCH2 0x42 +#define ADSP2_SCRATCH3 0x43 + +#define ADSP2V2_SCRATCH0_1 0x40 +#define ADSP2V2_SCRATCH2_3 0x42 /* * ADSP2 Control @@ -153,6 +158,17 @@ #define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ /* + * ADSP2V2 clocking + */ +#define ADSP2V2_CLK_SEL_MASK 0x70000 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_SHIFT 16 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +#define ADSP2V2_RATE_MASK 0x7800 /* DSP_RATE */ +#define ADSP2V2_RATE_SHIFT 11 /* DSP_RATE */ +#define ADSP2V2_RATE_WIDTH 4 /* DSP_RATE */ + +/* * ADSP2 Status 1 */ #define ADSP2_RAM_RDY 0x0001 @@ -160,6 +176,37 @@ #define ADSP2_RAM_RDY_SHIFT 0 #define ADSP2_RAM_RDY_WIDTH 1 +/* + * ADSP2 Lock support + */ +#define ADSP2_LOCK_CODE_0 0x5555 +#define ADSP2_LOCK_CODE_1 0xAAAA + +#define ADSP2_WATCHDOG 0x0A +#define ADSP2_BUS_ERR_ADDR 0x52 +#define ADSP2_REGION_LOCK_STATUS 0x64 +#define ADSP2_LOCK_REGION_1_LOCK_REGION_0 0x66 +#define ADSP2_LOCK_REGION_3_LOCK_REGION_2 0x68 +#define ADSP2_LOCK_REGION_5_LOCK_REGION_4 0x6A +#define ADSP2_LOCK_REGION_7_LOCK_REGION_6 0x6C +#define ADSP2_LOCK_REGION_9_LOCK_REGION_8 0x6E +#define ADSP2_LOCK_REGION_CTRL 0x7A +#define ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR 0x7C + +#define ADSP2_REGION_LOCK_ERR_MASK 0x8000 +#define ADSP2_SLAVE_ERR_MASK 0x4000 +#define ADSP2_WDT_TIMEOUT_STS_MASK 0x2000 +#define ADSP2_CTRL_ERR_PAUSE_ENA 0x0002 +#define ADSP2_CTRL_ERR_EINT 0x0001 + +#define ADSP2_BUS_ERR_ADDR_MASK 0x00FFFFFF +#define ADSP2_XMEM_ERR_ADDR_MASK 0x0000FFFF +#define ADSP2_PMEM_ERR_ADDR_MASK 0x7FFF0000 +#define ADSP2_PMEM_ERR_ADDR_SHIFT 16 +#define ADSP2_WDT_ENA_MASK 0xFFFFFFFD + +#define ADSP2_LOCK_REGION_SHIFT 16 + #define ADSP_MAX_STD_CTRL_SIZE 512 #define WM_ADSP_ACKED_CTL_TIMEOUT_MS 100 @@ -683,6 +730,9 @@ static const struct soc_enum wm_adsp_fw_enum[] = { SOC_ENUM_SINGLE(0, 1, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), SOC_ENUM_SINGLE(0, 2, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), SOC_ENUM_SINGLE(0, 3, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 4, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 5, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 6, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), }; const struct snd_kcontrol_new wm_adsp_fw_controls[] = { @@ -694,6 +744,12 @@ const struct snd_kcontrol_new wm_adsp_fw_controls[] = { wm_adsp_fw_get, wm_adsp_fw_put), SOC_ENUM_EXT("DSP4 Firmware", wm_adsp_fw_enum[3], wm_adsp_fw_get, wm_adsp_fw_put), + SOC_ENUM_EXT("DSP5 Firmware", wm_adsp_fw_enum[4], + wm_adsp_fw_get, wm_adsp_fw_put), + SOC_ENUM_EXT("DSP6 Firmware", wm_adsp_fw_enum[5], + wm_adsp_fw_get, wm_adsp_fw_put), + SOC_ENUM_EXT("DSP7 Firmware", wm_adsp_fw_enum[6], + wm_adsp_fw_get, wm_adsp_fw_put), }; EXPORT_SYMBOL_GPL(wm_adsp_fw_controls); @@ -750,6 +806,29 @@ static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) be16_to_cpu(scratch[3])); } +static void wm_adsp2v2_show_fw_status(struct wm_adsp *dsp) +{ + u32 scratch[2]; + int ret; + + ret = regmap_raw_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH0_1, + scratch, sizeof(scratch)); + + if (ret) { + adsp_err(dsp, "Failed to read SCRATCH regs: %d\n", ret); + return; + } + + scratch[0] = be32_to_cpu(scratch[0]); + scratch[1] = be32_to_cpu(scratch[1]); + + adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + scratch[0] & 0xFFFF, + scratch[0] >> 16, + scratch[1] & 0xFFFF, + scratch[1] >> 16); +} + static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) { return container_of(ext, struct wm_coeff_ctl, bytes_ext); @@ -2435,10 +2514,17 @@ static int wm_adsp2_ena(struct wm_adsp *dsp) unsigned int val; int ret, count; - ret = regmap_update_bits_async(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, ADSP2_SYS_ENA); - if (ret != 0) - return ret; + switch (dsp->rev) { + case 0: + ret = regmap_update_bits_async(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, ADSP2_SYS_ENA); + if (ret != 0) + return ret; + break; + default: + break; + } /* Wait for the RAM to start, should be near instantaneous */ for (count = 0; count < 10; ++count) { @@ -2497,11 +2583,17 @@ static void wm_adsp2_boot_work(struct work_struct *work) if (ret != 0) goto err_ena; - /* Turn DSP back off until we are ready to run */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); - if (ret != 0) - goto err_ena; + switch (dsp->rev) { + case 0: + /* Turn DSP back off until we are ready to run */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); + if (ret != 0) + goto err_ena; + break; + default: + break; + } dsp->booted = true; @@ -2523,12 +2615,21 @@ static void wm_adsp2_set_dspclk(struct wm_adsp *dsp, unsigned int freq) { int ret; - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CLOCKING, - ADSP2_CLK_SEL_MASK, - freq << ADSP2_CLK_SEL_SHIFT); - if (ret != 0) - adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + switch (dsp->rev) { + case 0: + ret = regmap_update_bits_async(dsp->regmap, + dsp->base + ADSP2_CLOCKING, + ADSP2_CLK_SEL_MASK, + freq << ADSP2_CLK_SEL_SHIFT); + if (ret) { + adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + return; + } + break; + default: + /* clock is handled by parent codec driver */ + break; + } } int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, @@ -2568,6 +2669,18 @@ int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); +static void wm_adsp_stop_watchdog(struct wm_adsp *dsp) +{ + switch (dsp->rev) { + case 0: + case 1: + return; + default: + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, + ADSP2_WDT_ENA_MASK, 0); + } +} + int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event, unsigned int freq) @@ -2640,6 +2753,8 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, if (ret != 0) goto err; + wm_adsp2_lock(dsp, dsp->lock_regions); + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, ADSP2_CORE_ENA | ADSP2_START, @@ -2663,23 +2778,49 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, /* Tell the firmware to cleanup */ wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN); + wm_adsp_stop_watchdog(dsp); + /* Log firmware state, it can be useful for analysis */ - wm_adsp2_show_fw_status(dsp); + switch (dsp->rev) { + case 0: + wm_adsp2_show_fw_status(dsp); + break; + default: + wm_adsp2v2_show_fw_status(dsp); + break; + } mutex_lock(&dsp->pwr_lock); dsp->running = false; - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, ADSP2_CORE_ENA | ADSP2_START, 0); /* Make sure DMAs are quiesced */ - regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); - - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); + switch (dsp->rev) { + case 0: + regmap_write(dsp->regmap, + dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_2, 0); + + regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); + break; + default: + regmap_write(dsp->regmap, + dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); + break; + } if (wm_adsp_fw[dsp->fw].num_caps != 0) wm_adsp_buffer_free(dsp); @@ -2732,15 +2873,22 @@ int wm_adsp2_init(struct wm_adsp *dsp) { int ret; - /* - * Disable the DSP memory by default when in reset for a small - * power saving. - */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); - if (ret != 0) { - adsp_err(dsp, "Failed to clear memory retention: %d\n", ret); - return ret; + switch (dsp->rev) { + case 0: + /* + * Disable the DSP memory by default when in reset for a small + * power saving. + */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); + if (ret) { + adsp_err(dsp, + "Failed to clear memory retention: %d\n", ret); + return ret; + } + break; + default: + break; } INIT_LIST_HEAD(&dsp->alg_regions); @@ -3523,4 +3671,94 @@ int wm_adsp_compr_copy(struct snd_compr_stream *stream, char __user *buf, } EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct regmap *regmap = dsp->regmap; + unsigned int code0, code1, lock_reg; + + if (!(lock_regions & WM_ADSP2_REGION_ALL)) + return 0; + + lock_regions &= WM_ADSP2_REGION_ALL; + lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + + while (lock_regions) { + code0 = code1 = 0; + if (lock_regions & BIT(0)) { + code0 = ADSP2_LOCK_CODE_0; + code1 = ADSP2_LOCK_CODE_1; + } + if (lock_regions & BIT(1)) { + code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; + code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + } + regmap_write(regmap, lock_reg, code0); + regmap_write(regmap, lock_reg, code1); + lock_regions >>= 2; + lock_reg += 2; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_lock); + +irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) +{ + unsigned int val; + struct regmap *regmap = dsp->regmap; + int ret = 0; + + ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); + if (ret) { + adsp_err(dsp, + "Failed to read Region Lock Ctrl register: %d\n", ret); + return IRQ_HANDLED; + } + + if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { + adsp_err(dsp, "watchdog timeout error\n"); + wm_adsp_stop_watchdog(dsp); + } + + if (val & (ADSP2_SLAVE_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { + if (val & ADSP2_SLAVE_ERR_MASK) + adsp_err(dsp, "bus error: slave error\n"); + else + adsp_err(dsp, "bus error: region lock error\n"); + + ret = regmap_read(regmap, dsp->base + ADSP2_BUS_ERR_ADDR, &val); + if (ret) { + adsp_err(dsp, + "Failed to read Bus Err Addr register: %d\n", + ret); + return IRQ_HANDLED; + } + + adsp_err(dsp, "bus error address = 0x%x\n", + val & ADSP2_BUS_ERR_ADDR_MASK); + + ret = regmap_read(regmap, + dsp->base + ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR, + &val); + if (ret) { + adsp_err(dsp, + "Failed to read Pmem Xmem Err Addr register: %d\n", + ret); + return IRQ_HANDLED; + } + + adsp_err(dsp, "xmem error address = 0x%x\n", + val & ADSP2_XMEM_ERR_ADDR_MASK); + adsp_err(dsp, "pmem error address = 0x%x\n", + (val & ADSP2_PMEM_ERR_ADDR_MASK) >> + ADSP2_PMEM_ERR_ADDR_SHIFT); + } + + regmap_update_bits(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, + ADSP2_CTRL_ERR_EINT, ADSP2_CTRL_ERR_EINT); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_adsp2_bus_error); + MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 3706b11053a3..41cc11c19b83 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -23,6 +23,23 @@ #define WM_ADSP_COMPR_OK 0 #define WM_ADSP_COMPR_VOICE_TRIGGER 1 +#define WM_ADSP2_REGION_0 BIT(0) +#define WM_ADSP2_REGION_1 BIT(1) +#define WM_ADSP2_REGION_2 BIT(2) +#define WM_ADSP2_REGION_3 BIT(3) +#define WM_ADSP2_REGION_4 BIT(4) +#define WM_ADSP2_REGION_5 BIT(5) +#define WM_ADSP2_REGION_6 BIT(6) +#define WM_ADSP2_REGION_7 BIT(7) +#define WM_ADSP2_REGION_8 BIT(8) +#define WM_ADSP2_REGION_9 BIT(9) +#define WM_ADSP2_REGION_1_9 (WM_ADSP2_REGION_1 | \ + WM_ADSP2_REGION_2 | WM_ADSP2_REGION_3 | \ + WM_ADSP2_REGION_4 | WM_ADSP2_REGION_5 | \ + WM_ADSP2_REGION_6 | WM_ADSP2_REGION_7 | \ + WM_ADSP2_REGION_8 | WM_ADSP2_REGION_9) +#define WM_ADSP2_REGION_ALL (WM_ADSP2_REGION_0 | WM_ADSP2_REGION_1_9) + struct wm_adsp_region { int type; unsigned int base; @@ -40,6 +57,7 @@ struct wm_adsp_compr_buf; struct wm_adsp { const char *part; + int rev; int num; int type; struct device *dev; @@ -75,6 +93,8 @@ struct wm_adsp { struct mutex pwr_lock; + unsigned int lock_regions; + #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_root; char *wmfw_file_name; @@ -113,6 +133,10 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w, int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event, unsigned int freq); + +int wm_adsp2_lock(struct wm_adsp *adsp, unsigned int regions); +irqreturn_t wm_adsp2_bus_error(struct wm_adsp *adsp); + int wm_adsp2_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig index c297efe43861..c6fd95fa5ca6 100644 --- a/sound/soc/dwc/Kconfig +++ b/sound/soc/dwc/Kconfig @@ -8,10 +8,10 @@ config SND_DESIGNWARE_I2S maximum of 8 channels each for play and record. config SND_DESIGNWARE_PCM - tristate "PCM PIO extension for I2S driver" + bool "PCM PIO extension for I2S driver" depends on SND_DESIGNWARE_I2S help - Say Y, M or N if you want to add a custom ALSA extension that registers + Say Y or N if you want to add a custom ALSA extension that registers a PCM and uses PIO to transfer data. This functionality is specially suited for I2S devices that don't have diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 38f1ca31c5fa..3e24c0ff95fb 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,5 +1,5 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o -ifdef CONFIG_SND_DESIGNWARE_PCM -obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o -endif + +designware_i2s-y := dwc-i2s.o +designware_i2s-$(CONFIG_SND_DESIGNWARE_PCM) += dwc-pcm.o diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/dwc-i2s.c index 9c46e4112026..9c46e4112026 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/dwc-i2s.c diff --git a/sound/soc/dwc/designware_pcm.c b/sound/soc/dwc/dwc-pcm.c index 459ec861e6b6..406fd867117b 100644 --- a/sound/soc/dwc/designware_pcm.c +++ b/sound/soc/dwc/dwc-pcm.c @@ -129,13 +129,11 @@ void dw_pcm_push_tx(struct dw_i2s_dev *dev) { dw_pcm_transfer(dev, true); } -EXPORT_SYMBOL_GPL(dw_pcm_push_tx); void dw_pcm_pop_rx(struct dw_i2s_dev *dev) { dw_pcm_transfer(dev, false); } -EXPORT_SYMBOL_GPL(dw_pcm_pop_rx); static int dw_pcm_open(struct snd_pcm_substream *substream) { @@ -281,4 +279,3 @@ int dw_pcm_register(struct platform_device *pdev) { return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform); } -EXPORT_SYMBOL_GPL(dw_pcm_register); diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index 883087f2b092..84ef6385736c 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -64,7 +64,7 @@ static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops eukrea_tlv320_snd_ops = { +static const struct snd_soc_ops eukrea_tlv320_snd_ops = { .hw_params = eukrea_tlv320_hw_params, }; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index dc30d780f874..282d841840b1 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -76,7 +76,7 @@ static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) pair->dma_chan[!dir], runtime->dma_addr, snd_pcm_lib_buffer_bytes(substream), snd_pcm_lib_period_bytes(substream), - dir == OUT ? DMA_TO_DEVICE : DMA_FROM_DEVICE, flags); + dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags); if (!pair->desc[!dir]) { dev_err(dev, "failed to prepare slave DMA for Front-End\n"); return -ENOMEM; diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 38bfd46f4ad8..809a069d490b 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -19,7 +19,6 @@ #include "fsl_esai.h" #include "imx-pcm.h" -#define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000 #define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ @@ -647,14 +646,14 @@ static struct snd_soc_dai_driver fsl_esai_dai = { .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 12, - .rates = FSL_ESAI_RATES, + .rates = SNDRV_PCM_RATE_8000_192000, .formats = FSL_ESAI_FORMATS, }, .capture = { .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 8, - .rates = FSL_ESAI_RATES, + .rates = SNDRV_PCM_RATE_8000_192000, .formats = FSL_ESAI_FORMATS, }, .ops = &fsl_esai_dai_ops, diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index fde08660b63b..173cb8496641 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -35,6 +35,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/clk.h> +#include <linux/ctype.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/slab.h> @@ -55,16 +56,6 @@ #include "imx-pcm.h" /** - * FSLSSI_I2S_RATES: sample rates supported by the I2S - * - * This driver currently only supports the SSI running in I2S slave mode, - * which means the codec determines the sample rate. Therefore, we tell - * ALSA that we support all rates and let the codec driver decide what rates - * are really supported. - */ -#define FSLSSI_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS - -/** * FSLSSI_I2S_FORMATS: audio formats supported by the SSI * * The SSI has a limitation in that the samples must be in the same byte @@ -1212,14 +1203,14 @@ static struct snd_soc_dai_driver fsl_ssi_dai_template = { .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 32, - .rates = FSLSSI_I2S_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, .formats = FSLSSI_I2S_FORMATS, }, .capture = { .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 32, - .rates = FSLSSI_I2S_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, .formats = FSLSSI_I2S_FORMATS, }, .ops = &fsl_ssi_dai_ops, @@ -1325,14 +1316,10 @@ static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = { */ static void make_lowercase(char *s) { - char *p = s; - char c; - - while ((c = *p)) { - if ((c >= 'A') && (c <= 'Z')) - *p = c + ('a' - 'A'); - p++; - } + if (!s) + return; + for (; *s; s++) + *s = tolower(*s); } static int fsl_ssi_imx_probe(struct platform_device *pdev, diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c index bb0459018b45..9d19b808f634 100644 --- a/sound/soc/fsl/imx-mc13783.c +++ b/sound/soc/fsl/imx-mc13783.c @@ -48,7 +48,7 @@ static int imx_mc13783_hifi_hw_params(struct snd_pcm_substream *substream, return snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 16); } -static struct snd_soc_ops imx_mc13783_hifi_ops = { +static const struct snd_soc_ops imx_mc13783_hifi_ops = { .hw_params = imx_mc13783_hifi_hw_params, }; diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c index f3d3d1ffa84e..314814ddd2b0 100644 --- a/sound/soc/fsl/imx-pcm-dma.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -33,48 +33,20 @@ static bool filter(struct dma_chan *chan, void *param) return true; } -static const struct snd_pcm_hardware imx_pcm_hardware = { - .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, - .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE, - .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ - .periods_min = 2, - .periods_max = 255, - .fifo_size = 0, -}; - static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = { - .pcm_hardware = &imx_pcm_hardware, .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, .compat_filter_fn = filter, - .prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE, }; int imx_pcm_dma_init(struct platform_device *pdev, size_t size) { struct snd_dmaengine_pcm_config *config; - struct snd_pcm_hardware *pcm_hardware; config = devm_kzalloc(&pdev->dev, sizeof(struct snd_dmaengine_pcm_config), GFP_KERNEL); if (!config) return -ENOMEM; *config = imx_dmaengine_pcm_config; - if (size) - config->prealloc_buffer_size = size; - - pcm_hardware = devm_kzalloc(&pdev->dev, - sizeof(struct snd_pcm_hardware), GFP_KERNEL); - *pcm_hardware = imx_pcm_hardware; - if (size) - pcm_hardware->buffer_bytes_max = size; - - config->pcm_hardware = pcm_hardware; return devm_snd_dmaengine_pcm_register(&pdev->dev, config, diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c index dac6688540dc..92410f7ca1fa 100644 --- a/sound/soc/fsl/imx-pcm-fiq.c +++ b/sound/soc/fsl/imx-pcm-fiq.c @@ -282,7 +282,7 @@ static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) return 0; } -static int ssi_irq = 0; +static int ssi_irq; static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd) { diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c index 1b60958e2080..206b898e554c 100644 --- a/sound/soc/fsl/imx-wm8962.c +++ b/sound/soc/fsl/imx-wm8962.c @@ -33,14 +33,14 @@ struct imx_wm8962_data { struct snd_soc_card card; char codec_dai_name[DAI_NAME_SIZE]; char platform_name[DAI_NAME_SIZE]; - struct clk *codec_clk; unsigned int clk_frequency; }; struct imx_priv { struct platform_device *pdev; + int sample_rate; + snd_pcm_format_t sample_format; }; -static struct imx_priv card_priv; static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), @@ -49,14 +49,14 @@ static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { SND_SOC_DAPM_MIC("DMIC", NULL), }; -static int sample_rate = 44100; -static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; - static int imx_hifi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - sample_rate = params_rate(params); - sample_format = params_format(params); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_priv *priv = snd_soc_card_get_drvdata(rtd->card); + + priv->sample_rate = params_rate(params); + priv->sample_format = params_format(params); return 0; } @@ -71,7 +71,7 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card, { struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *codec_dai; - struct imx_priv *priv = &card_priv; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); struct device *dev = &priv->pdev->dev; unsigned int pll_out; @@ -85,10 +85,10 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card, switch (level) { case SND_SOC_BIAS_PREPARE: if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { - if (sample_format == SNDRV_PCM_FORMAT_S24_LE) - pll_out = sample_rate * 384; + if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE) + pll_out = priv->sample_rate * 384; else - pll_out = sample_rate * 256; + pll_out = priv->sample_rate * 256; ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK, data->clk_frequency, @@ -140,7 +140,7 @@ static int imx_wm8962_late_probe(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *codec_dai; - struct imx_priv *priv = &card_priv; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); struct device *dev = &priv->pdev->dev; int ret; @@ -160,13 +160,20 @@ static int imx_wm8962_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; struct device_node *ssi_np, *codec_np; struct platform_device *ssi_pdev; - struct imx_priv *priv = &card_priv; struct i2c_client *codec_dev; struct imx_wm8962_data *data; + struct imx_priv *priv; + struct clk *codec_clk; int int_port, ext_port; int ret; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->pdev = pdev; + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; ret = of_property_read_u32(np, "mux-int-port", &int_port); if (ret) { @@ -231,19 +238,15 @@ static int imx_wm8962_probe(struct platform_device *pdev) goto fail; } - data->codec_clk = devm_clk_get(&codec_dev->dev, NULL); - if (IS_ERR(data->codec_clk)) { - ret = PTR_ERR(data->codec_clk); + codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(codec_clk)) { + ret = PTR_ERR(codec_clk); dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); goto fail; } - data->clk_frequency = clk_get_rate(data->codec_clk); - ret = clk_prepare_enable(data->codec_clk); - if (ret) { - dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret); - goto fail; - } + data->clk_frequency = clk_get_rate(codec_clk); + clk_put(codec_clk); data->dai.name = "HiFi"; data->dai.stream_name = "HiFi"; @@ -258,10 +261,10 @@ static int imx_wm8962_probe(struct platform_device *pdev) data->card.dev = &pdev->dev; ret = snd_soc_of_parse_card_name(&data->card, "model"); if (ret) - goto clk_fail; + goto fail; ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); if (ret) - goto clk_fail; + goto fail; data->card.num_links = 1; data->card.owner = THIS_MODULE; data->card.dai_link = &data->dai; @@ -277,16 +280,9 @@ static int imx_wm8962_probe(struct platform_device *pdev) ret = devm_snd_soc_register_card(&pdev->dev, &data->card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); - goto clk_fail; + goto fail; } - of_node_put(ssi_np); - of_node_put(codec_np); - - return 0; - -clk_fail: - clk_disable_unprepare(data->codec_clk); fail: of_node_put(ssi_np); of_node_put(codec_np); @@ -294,17 +290,6 @@ fail: return ret; } -static int imx_wm8962_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); - - if (!IS_ERR(data->codec_clk)) - clk_disable_unprepare(data->codec_clk); - - return 0; -} - static const struct of_device_id imx_wm8962_dt_ids[] = { { .compatible = "fsl,imx-audio-wm8962", }, { /* sentinel */ } @@ -318,7 +303,6 @@ static struct platform_driver imx_wm8962_driver = { .of_match_table = imx_wm8962_dt_ids, }, .probe = imx_wm8962_probe, - .remove = imx_wm8962_remove, }; module_platform_driver(imx_wm8962_driver); diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index ddf49f30b23f..a639b52c16f6 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -174,7 +174,7 @@ static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card) /** * mpc8610_hpcd_ops: ASoC machine driver operations */ -static struct snd_soc_ops mpc8610_hpcd_ops = { +static const struct snd_soc_ops mpc8610_hpcd_ops = { .startup = mpc8610_hpcd_startup, }; diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c index 198eeb3f3f7a..d7ec3d20065c 100644 --- a/sound/soc/fsl/mx27vis-aic32x4.c +++ b/sound/soc/fsl/mx27vis-aic32x4.c @@ -73,7 +73,7 @@ static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mx27vis_aic32x4_snd_ops = { +static const struct snd_soc_ops mx27vis_aic32x4_snd_ops = { .hw_params = mx27vis_aic32x4_hw_params, }; diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c index a1f780ecadf5..41c623c55c16 100644 --- a/sound/soc/fsl/p1022_ds.c +++ b/sound/soc/fsl/p1022_ds.c @@ -184,7 +184,7 @@ static int p1022_ds_machine_remove(struct snd_soc_card *card) /** * p1022_ds_ops: ASoC machine driver operations */ -static struct snd_soc_ops p1022_ds_ops = { +static const struct snd_soc_ops p1022_ds_ops = { .startup = p1022_ds_startup, }; diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c index d4d88a8cb9c0..4afbdd610bfa 100644 --- a/sound/soc/fsl/p1022_rdk.c +++ b/sound/soc/fsl/p1022_rdk.c @@ -188,7 +188,7 @@ static int p1022_rdk_machine_remove(struct snd_soc_card *card) /** * p1022_rdk_ops: ASoC machine driver operations */ -static struct snd_soc_ops p1022_rdk_ops = { +static const struct snd_soc_ops p1022_rdk_ops = { .startup = p1022_rdk_startup, }; diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c index ae403c29688f..66fb6c4614d2 100644 --- a/sound/soc/fsl/phycore-ac97.c +++ b/sound/soc/fsl/phycore-ac97.c @@ -23,7 +23,7 @@ static struct snd_soc_card imx_phycore; -static struct snd_soc_ops imx_phycore_hifi_ops = { +static const struct snd_soc_ops imx_phycore_hifi_ops = { }; static struct snd_soc_dai_link imx_phycore_dai_ac97[] = { diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c index b454972dce35..cdaf16367b47 100644 --- a/sound/soc/fsl/wm1133-ev1.c +++ b/sound/soc/fsl/wm1133-ev1.c @@ -139,7 +139,7 @@ static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops wm1133_ev1_ops = { +static const struct snd_soc_ops wm1133_ev1_ops = { .hw_params = wm1133_ev1_hw_params, }; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 85b4f1806514..2c9dedab5184 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -40,9 +40,10 @@ struct simple_card_data { struct snd_soc_dai_link *dai_link; }; -#define simple_priv_to_dev(priv) ((priv)->snd_card.dev) -#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define simple_priv_to_card(priv) (&(priv)->snd_card) #define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) #define DAI "sound-dai" #define CELL "#sound-dai-cells" @@ -323,6 +324,7 @@ static int asoc_simple_card_parse_aux_devs(struct device_node *node, { struct device *dev = simple_priv_to_dev(priv); struct device_node *aux_node; + struct snd_soc_card *card = simple_priv_to_card(priv); int i, n, len; if (!of_find_property(node, PREFIX "aux-devs", &len)) @@ -332,19 +334,19 @@ static int asoc_simple_card_parse_aux_devs(struct device_node *node, if (n <= 0) return -EINVAL; - priv->snd_card.aux_dev = devm_kzalloc(dev, - n * sizeof(*priv->snd_card.aux_dev), GFP_KERNEL); - if (!priv->snd_card.aux_dev) + card->aux_dev = devm_kzalloc(dev, + n * sizeof(*card->aux_dev), GFP_KERNEL); + if (!card->aux_dev) return -ENOMEM; for (i = 0; i < n; i++) { aux_node = of_parse_phandle(node, PREFIX "aux-devs", i); if (!aux_node) return -EINVAL; - priv->snd_card.aux_dev[i].codec_of_node = aux_node; + card->aux_dev[i].codec_of_node = aux_node; } - priv->snd_card.num_aux_devs = n; + card->num_aux_devs = n; return 0; } @@ -352,6 +354,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, struct simple_card_data *priv) { struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_card *card = simple_priv_to_card(priv); struct device_node *dai_link; int ret; @@ -362,7 +365,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, /* The off-codec widgets */ if (of_property_read_bool(node, PREFIX "widgets")) { - ret = snd_soc_of_parse_audio_simple_widgets(&priv->snd_card, + ret = snd_soc_of_parse_audio_simple_widgets(card, PREFIX "widgets"); if (ret) goto card_parse_end; @@ -370,7 +373,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, /* DAPM routes */ if (of_property_read_bool(node, PREFIX "routing")) { - ret = snd_soc_of_parse_audio_routing(&priv->snd_card, + ret = snd_soc_of_parse_audio_routing(card, PREFIX "routing"); if (ret) goto card_parse_end; @@ -401,7 +404,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, goto card_parse_end; } - ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + ret = asoc_simple_card_parse_card_name(card, PREFIX); if (ret < 0) goto card_parse_end; @@ -418,8 +421,9 @@ static int asoc_simple_card_probe(struct platform_device *pdev) struct simple_card_data *priv; struct snd_soc_dai_link *dai_link; struct simple_dai_props *dai_props; - struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct snd_soc_card *card; int num, ret; /* Get the number of DAI links */ @@ -442,10 +446,11 @@ static int asoc_simple_card_probe(struct platform_device *pdev) priv->dai_link = dai_link; /* Init snd_soc_card */ - priv->snd_card.owner = THIS_MODULE; - priv->snd_card.dev = dev; - priv->snd_card.dai_link = priv->dai_link; - priv->snd_card.num_links = num; + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; if (np && of_device_is_available(np)) { @@ -474,7 +479,7 @@ static int asoc_simple_card_probe(struct platform_device *pdev) return -EINVAL; } - priv->snd_card.name = (cinfo->card) ? cinfo->card : cinfo->name; + card->name = (cinfo->card) ? cinfo->card : cinfo->name; dai_link->name = cinfo->name; dai_link->stream_name = cinfo->name; dai_link->platform_name = cinfo->platform; @@ -489,13 +494,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) sizeof(priv->dai_props->codec_dai)); } - snd_soc_card_set_drvdata(&priv->snd_card, priv); + snd_soc_card_set_drvdata(card, priv); - ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); + ret = devm_snd_soc_register_card(dev, card); if (ret >= 0) return ret; err: - asoc_simple_card_clean_reference(&priv->snd_card); + asoc_simple_card_clean_reference(card); return ret; } diff --git a/sound/soc/generic/simple-scu-card.c b/sound/soc/generic/simple-scu-card.c index 308ff4c11a8d..dcbcab230d1b 100644 --- a/sound/soc/generic/simple-scu-card.c +++ b/sound/soc/generic/simple-scu-card.c @@ -31,9 +31,10 @@ struct simple_card_data { u32 convert_channels; }; -#define simple_priv_to_dev(priv) ((priv)->snd_card.dev) -#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define simple_priv_to_card(priv) (&(priv)->snd_card) #define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) #define DAI "sound-dai" #define CELL "#sound-dai-cells" @@ -109,6 +110,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *np, struct device *dev = simple_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx); + struct snd_soc_card *card = simple_priv_to_card(priv); int ret; if (is_fe) { @@ -163,7 +165,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *np, if (ret < 0) return ret; - snd_soc_of_parse_audio_prefix(&priv->snd_card, + snd_soc_of_parse_audio_prefix(card, &priv->codec_conf, dai_link->codec_of_node, PREFIX "prefix"); @@ -201,6 +203,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, { struct device *dev = simple_priv_to_dev(priv); struct device_node *np; + struct snd_soc_card *card = simple_priv_to_card(priv); unsigned int daifmt = 0; bool is_fe; int ret, i; @@ -208,7 +211,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, if (!node) return -EINVAL; - ret = snd_soc_of_parse_audio_routing(&priv->snd_card, PREFIX "routing"); + ret = snd_soc_of_parse_audio_routing(card, PREFIX "routing"); if (ret < 0) return ret; @@ -239,12 +242,12 @@ static int asoc_simple_card_parse_of(struct device_node *node, i++; } - ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + ret = asoc_simple_card_parse_card_name(card, PREFIX); if (ret < 0) return ret; dev_dbg(dev, "New card: %s\n", - priv->snd_card.name ? priv->snd_card.name : ""); + card->name ? card->name : ""); dev_dbg(dev, "convert_rate %d\n", priv->convert_rate); dev_dbg(dev, "convert_channels %d\n", priv->convert_channels); @@ -256,8 +259,9 @@ static int asoc_simple_card_probe(struct platform_device *pdev) struct simple_card_data *priv; struct snd_soc_dai_link *dai_link; struct asoc_simple_dai *dai_props; + struct snd_soc_card *card; struct device *dev = &pdev->dev; - struct device_node *np = pdev->dev.of_node; + struct device_node *np = dev->of_node; int num, ret; /* Allocate the private data */ @@ -276,12 +280,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) priv->dai_link = dai_link; /* Init snd_soc_card */ - priv->snd_card.owner = THIS_MODULE; - priv->snd_card.dev = dev; - priv->snd_card.dai_link = priv->dai_link; - priv->snd_card.num_links = num; - priv->snd_card.codec_conf = &priv->codec_conf; - priv->snd_card.num_configs = 1; + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; + card->codec_conf = &priv->codec_conf; + card->num_configs = 1; ret = asoc_simple_card_parse_of(np, priv); if (ret < 0) { @@ -290,13 +295,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) goto err; } - snd_soc_card_set_drvdata(&priv->snd_card, priv); + snd_soc_card_set_drvdata(card, priv); - ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); + ret = devm_snd_soc_register_card(dev, card); if (ret >= 0) return ret; err: - asoc_simple_card_clean_reference(&priv->snd_card); + asoc_simple_card_clean_reference(card); return ret; } diff --git a/sound/soc/hisilicon/Kconfig b/sound/soc/hisilicon/Kconfig new file mode 100644 index 000000000000..4356d5a1d338 --- /dev/null +++ b/sound/soc/hisilicon/Kconfig @@ -0,0 +1,5 @@ +config SND_I2S_HI6210_I2S + tristate "Hisilicon I2S controller" + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Hisilicon I2S diff --git a/sound/soc/hisilicon/Makefile b/sound/soc/hisilicon/Makefile new file mode 100644 index 000000000000..e8095e2af91a --- /dev/null +++ b/sound/soc/hisilicon/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SND_I2S_HI6210_I2S) += hi6210-i2s.o diff --git a/sound/soc/hisilicon/hi6210-i2s.c b/sound/soc/hisilicon/hi6210-i2s.c new file mode 100644 index 000000000000..45163e5202f5 --- /dev/null +++ b/sound/soc/hisilicon/hi6210-i2s.c @@ -0,0 +1,618 @@ +/* + * linux/sound/soc/m8m/hi6210_i2s.c - I2S IP driver + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green <andy.green@linaro.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver only deals with S2 interface (BT) + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/jiffies.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <linux/interrupt.h> +#include <linux/reset.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/mfd/syscon.h> +#include <linux/reset-controller.h> +#include <linux/clk.h> + +#include "hi6210-i2s.h" + +struct hi6210_i2s { + struct device *dev; + struct reset_control *rc; + struct clk *clk[8]; + int clocks; + struct snd_soc_dai_driver dai; + void __iomem *base; + struct regmap *sysctrl; + phys_addr_t base_phys; + struct snd_dmaengine_dai_dma_data dma_data[2]; + int clk_rate; + spinlock_t lock; + int rate; + int format; + u8 bits; + u8 channels; + u8 id; + u8 channel_length; + u8 use; + u32 master:1; + u32 status:1; +}; + +#define SC_PERIPH_CLKEN1 0x210 +#define SC_PERIPH_CLKDIS1 0x214 + +#define SC_PERIPH_CLKEN3 0x230 +#define SC_PERIPH_CLKDIS3 0x234 + +#define SC_PERIPH_CLKEN12 0x270 +#define SC_PERIPH_CLKDIS12 0x274 + +#define SC_PERIPH_RSTEN1 0x310 +#define SC_PERIPH_RSTDIS1 0x314 +#define SC_PERIPH_RSTSTAT1 0x318 + +#define SC_PERIPH_RSTEN2 0x320 +#define SC_PERIPH_RSTDIS2 0x324 +#define SC_PERIPH_RSTSTAT2 0x328 + +#define SOC_PMCTRL_BBPPLLALIAS 0x48 + +enum { + CLK_DACODEC, + CLK_I2S_BASE, +}; + +static inline void hi6210_write_reg(struct hi6210_i2s *i2s, int reg, u32 val) +{ + writel(val, i2s->base + reg); +} + +static inline u32 hi6210_read_reg(struct hi6210_i2s *i2s, int reg) +{ + return readl(i2s->base + reg); +} + +int hi6210_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int ret, n; + u32 val; + + /* deassert reset on ABB */ + regmap_read(i2s->sysctrl, SC_PERIPH_RSTSTAT2, &val); + if (val & BIT(4)) + regmap_write(i2s->sysctrl, SC_PERIPH_RSTDIS2, BIT(4)); + + for (n = 0; n < i2s->clocks; n++) { + ret = clk_prepare_enable(i2s->clk[n]); + if (ret) { + while (n--) + clk_disable_unprepare(i2s->clk[n]); + return ret; + } + } + + ret = clk_set_rate(i2s->clk[CLK_I2S_BASE], 49152000); + if (ret) { + dev_err(i2s->dev, "%s: setting 49.152MHz base rate failed %d\n", + __func__, ret); + return ret; + } + + /* enable clock before frequency division */ + regmap_write(i2s->sysctrl, SC_PERIPH_CLKEN12, BIT(9)); + + /* enable codec working clock / == "codec bus clock" */ + regmap_write(i2s->sysctrl, SC_PERIPH_CLKEN1, BIT(5)); + + /* deassert reset on codec / interface clock / working clock */ + regmap_write(i2s->sysctrl, SC_PERIPH_RSTEN1, BIT(5)); + regmap_write(i2s->sysctrl, SC_PERIPH_RSTDIS1, BIT(5)); + + /* not interested in i2s irqs */ + val = hi6210_read_reg(i2s, HII2S_CODEC_IRQ_MASK); + val |= 0x3f; + hi6210_write_reg(i2s, HII2S_CODEC_IRQ_MASK, val); + + + /* reset the stereo downlink fifo */ + val = hi6210_read_reg(i2s, HII2S_APB_AFIFO_CFG_1); + val |= (BIT(5) | BIT(4)); + hi6210_write_reg(i2s, HII2S_APB_AFIFO_CFG_1, val); + + val = hi6210_read_reg(i2s, HII2S_APB_AFIFO_CFG_1); + val &= ~(BIT(5) | BIT(4)); + hi6210_write_reg(i2s, HII2S_APB_AFIFO_CFG_1, val); + + + val = hi6210_read_reg(i2s, HII2S_SW_RST_N); + val &= ~(HII2S_SW_RST_N__ST_DL_WORDLEN_MASK << + HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT); + val |= (HII2S_BITS_16 << HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT); + hi6210_write_reg(i2s, HII2S_SW_RST_N, val); + + val = hi6210_read_reg(i2s, HII2S_MISC_CFG); + /* mux 11/12 = APB not i2s */ + val &= ~HII2S_MISC_CFG__ST_DL_TEST_SEL; + /* BT R ch 0 = mixer op of DACR ch */ + val &= ~HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL; + val &= ~HII2S_MISC_CFG__S2_DOUT_TEST_SEL; + + val |= HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL; + /* BT L ch = 1 = mux 7 = "mixer output of DACL */ + val |= HII2S_MISC_CFG__S2_DOUT_TEST_SEL; + hi6210_write_reg(i2s, HII2S_MISC_CFG, val); + + val = hi6210_read_reg(i2s, HII2S_SW_RST_N); + val |= HII2S_SW_RST_N__SW_RST_N; + hi6210_write_reg(i2s, HII2S_SW_RST_N, val); + + return 0; +} +void hi6210_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int n; + + for (n = 0; n < i2s->clocks; n++) + clk_disable_unprepare(i2s->clk[n]); + + regmap_write(i2s->sysctrl, SC_PERIPH_RSTEN1, BIT(5)); +} + +static void hi6210_i2s_txctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + + spin_lock(&i2s->lock); + if (on) { + /* enable S2 TX */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_IF_TX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } else { + /* disable S2 TX */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_IF_TX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } + spin_unlock(&i2s->lock); +} + +static void hi6210_i2s_rxctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + + spin_lock(&i2s->lock); + if (on) { + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_IF_RX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } else { + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_IF_RX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } + spin_unlock(&i2s->lock); +} + +static int hi6210_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + /* + * We don't actually set the hardware until the hw_params + * call, but we need to validate the user input here. + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + break; + default: + return -EINVAL; + } + + i2s->format = fmt; + i2s->master = (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS; + + return 0; +} + +static int hi6210_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 bits = 0, rate = 0, signed_data = 0, fmt = 0; + u32 val; + struct snd_dmaengine_dai_dma_data *dma_data; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U16_LE: + signed_data = HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + /* fallthru */ + case SNDRV_PCM_FORMAT_S16_LE: + bits = HII2S_BITS_16; + break; + case SNDRV_PCM_FORMAT_U24_LE: + signed_data = HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + /* fallthru */ + case SNDRV_PCM_FORMAT_S24_LE: + bits = HII2S_BITS_24; + break; + default: + dev_err(cpu_dai->dev, "Bad format\n"); + return -EINVAL; + } + + + switch (params_rate(params)) { + case 8000: + rate = HII2S_FS_RATE_8KHZ; + break; + case 16000: + rate = HII2S_FS_RATE_16KHZ; + break; + case 32000: + rate = HII2S_FS_RATE_32KHZ; + break; + case 48000: + rate = HII2S_FS_RATE_48KHZ; + break; + case 96000: + rate = HII2S_FS_RATE_96KHZ; + break; + case 192000: + rate = HII2S_FS_RATE_192KHZ; + break; + default: + dev_err(cpu_dai->dev, "Bad rate: %d\n", params_rate(params)); + return -EINVAL; + } + + if (!(params_channels(params))) { + dev_err(cpu_dai->dev, "Bad channels\n"); + return -EINVAL; + } + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + switch (bits) { + case HII2S_BITS_24: + i2s->bits = 32; + dma_data->addr_width = 3; + break; + default: + i2s->bits = 16; + dma_data->addr_width = 2; + break; + } + i2s->rate = params_rate(params); + i2s->channels = params_channels(params); + i2s->channel_length = i2s->channels * i2s->bits; + + val = hi6210_read_reg(i2s, HII2S_ST_DL_FIFO_TH_CFG); + val &= ~((HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT)); + val |= ((16 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT) | + (30 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT) | + (16 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT) | + (30 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT)); + hi6210_write_reg(i2s, HII2S_ST_DL_FIFO_TH_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_IF_CLK_EN_CFG); + val |= (BIT(19) | BIT(18) | BIT(17) | + HII2S_IF_CLK_EN_CFG__S2_IF_CLK_EN | + HII2S_IF_CLK_EN_CFG__S2_OL_MIXER_EN | + HII2S_IF_CLK_EN_CFG__S2_OL_SRC_EN | + HII2S_IF_CLK_EN_CFG__ST_DL_R_EN | + HII2S_IF_CLK_EN_CFG__ST_DL_L_EN); + hi6210_write_reg(i2s, HII2S_IF_CLK_EN_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_DIG_FILTER_CLK_EN_CFG); + val &= ~(HII2S_DIG_FILTER_CLK_EN_CFG__DACR_SDM_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACR_HBF2I_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACR_AGC_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_SDM_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_HBF2I_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_AGC_EN); + val |= (HII2S_DIG_FILTER_CLK_EN_CFG__DACR_MIXER_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_MIXER_EN); + hi6210_write_reg(i2s, HII2S_DIG_FILTER_CLK_EN_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_DIG_FILTER_MODULE_CFG); + val &= ~(HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN2_MUTE | + HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN2_MUTE); + hi6210_write_reg(i2s, HII2S_DIG_FILTER_MODULE_CFG, val); + + val = hi6210_read_reg(i2s, HII2S_MUX_TOP_MODULE_CFG); + val &= ~(HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN1_MUTE | + HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN2_MUTE | + HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN1_MUTE | + HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN2_MUTE); + hi6210_write_reg(i2s, HII2S_MUX_TOP_MODULE_CFG, val); + + + switch (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = false; + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_MST_SLV; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = true; + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_MST_SLV; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + default: + WARN_ONCE(1, "Invalid i2s->fmt MASTER_MASK. This shouldn't happen\n"); + return -EINVAL; + } + + switch (i2s->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fmt = HII2S_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + fmt = HII2S_FORMAT_LEFT_JUST; + break; + case SND_SOC_DAIFMT_RIGHT_J: + fmt = HII2S_FORMAT_RIGHT_JUST; + break; + default: + WARN_ONCE(1, "Invalid i2s->fmt FORMAT_MASK. This shouldn't happen\n"); + return -EINVAL; + } + + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~(HII2S_I2S_CFG__S2_FUNC_MODE_MASK << + HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT); + val |= fmt << HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_CLK_SEL); + val &= ~(HII2S_CLK_SEL__I2S_BT_FM_SEL | /* BT gets the I2S */ + HII2S_CLK_SEL__EXT_12_288MHZ_SEL); + hi6210_write_reg(i2s, HII2S_CLK_SEL, val); + + dma_data->maxburst = 2; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data->addr = i2s->base_phys + HII2S_ST_DL_CHANNEL; + else + dma_data->addr = i2s->base_phys + HII2S_STEREO_UPLINK_CHANNEL; + + switch (i2s->channels) { + case 1: + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_FRAME_MODE; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + default: + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_FRAME_MODE; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + } + + /* clear loopback, set signed type and word length */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + val &= ~(HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_MASK << + HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT); + val &= ~(HII2S_I2S_CFG__S2_DIRECT_LOOP_MASK << + HII2S_I2S_CFG__S2_DIRECT_LOOP_SHIFT); + val |= signed_data; + val |= (bits << HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT); + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + + + if (!i2s->master) + return 0; + + /* set DAC and related units to correct rate */ + val = hi6210_read_reg(i2s, HII2S_FS_CFG); + val &= ~(HII2S_FS_CFG__FS_S2_MASK << HII2S_FS_CFG__FS_S2_SHIFT); + val &= ~(HII2S_FS_CFG__FS_DACLR_MASK << HII2S_FS_CFG__FS_DACLR_SHIFT); + val &= ~(HII2S_FS_CFG__FS_ST_DL_R_MASK << + HII2S_FS_CFG__FS_ST_DL_R_SHIFT); + val &= ~(HII2S_FS_CFG__FS_ST_DL_L_MASK << + HII2S_FS_CFG__FS_ST_DL_L_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_S2_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_DACLR_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_ST_DL_R_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_ST_DL_L_SHIFT); + hi6210_write_reg(i2s, HII2S_FS_CFG, val); + + return 0; +} + +static int hi6210_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hi6210_i2s_rxctrl(cpu_dai, 1); + else + hi6210_i2s_txctrl(cpu_dai, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hi6210_i2s_rxctrl(cpu_dai, 0); + else + hi6210_i2s_txctrl(cpu_dai, 0); + break; + default: + dev_err(cpu_dai->dev, "uknown cmd\n"); + return -EINVAL; + } + return 0; +} + +static int hi6210_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct hi6210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, + &i2s->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &i2s->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; +} + + +static struct snd_soc_dai_ops hi6210_i2s_dai_ops = { + .trigger = hi6210_i2s_trigger, + .hw_params = hi6210_i2s_hw_params, + .set_fmt = hi6210_i2s_set_fmt, + .startup = hi6210_i2s_startup, + .shutdown = hi6210_i2s_shutdown, +}; + +struct snd_soc_dai_driver hi6210_i2s_dai_init = { + .probe = hi6210_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .ops = &hi6210_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver hi6210_i2s_i2s_comp = { + .name = "hi6210_i2s-i2s", +}; + +static int hi6210_i2s_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct hi6210_i2s *i2s; + struct resource *res; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->dev = dev; + spin_lock_init(&i2s->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(dev, res); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + + i2s->base_phys = (phys_addr_t)res->start; + i2s->dai = hi6210_i2s_dai_init; + + dev_set_drvdata(&pdev->dev, i2s); + + i2s->sysctrl = syscon_regmap_lookup_by_phandle(node, + "hisilicon,sysctrl-syscon"); + if (IS_ERR(i2s->sysctrl)) + return PTR_ERR(i2s->sysctrl); + + i2s->clk[CLK_DACODEC] = devm_clk_get(&pdev->dev, "dacodec"); + if (IS_ERR_OR_NULL(i2s->clk[CLK_DACODEC])) + return PTR_ERR(i2s->clk[CLK_DACODEC]); + i2s->clocks++; + + i2s->clk[CLK_I2S_BASE] = devm_clk_get(&pdev->dev, "i2s-base"); + if (IS_ERR_OR_NULL(i2s->clk[CLK_I2S_BASE])) + return PTR_ERR(i2s->clk[CLK_I2S_BASE]); + i2s->clocks++; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, &hi6210_i2s_i2s_comp, + &i2s->dai, 1); + return ret; +} + +static const struct of_device_id hi6210_i2s_dt_ids[] = { + { .compatible = "hisilicon,hi6210-i2s" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, hi6210_i2s_dt_ids); + +static struct platform_driver hi6210_i2s_driver = { + .probe = hi6210_i2s_probe, + .driver = { + .name = "hi6210_i2s", + .of_match_table = hi6210_i2s_dt_ids, + }, +}; + +module_platform_driver(hi6210_i2s_driver); + +MODULE_DESCRIPTION("Hisilicon HI6210 I2S driver"); +MODULE_AUTHOR("Andy Green <andy.green@linaro.org>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/hisilicon/hi6210-i2s.h b/sound/soc/hisilicon/hi6210-i2s.h new file mode 100644 index 000000000000..85cecc4939a0 --- /dev/null +++ b/sound/soc/hisilicon/hi6210-i2s.h @@ -0,0 +1,276 @@ +/* + * linux/sound/soc/hisilicon/hi6210-i2s.h + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green <andy.green@linaro.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Note at least on 6220, S2 == BT, S1 == Digital FM Radio IF + */ + +#ifndef _HI6210_I2S_H +#define _HI6210_I2S_H + +#define HII2S_SW_RST_N 0 + +#define HII2S_SW_RST_N__STEREO_UPLINK_WORDLEN_SHIFT 28 +#define HII2S_SW_RST_N__STEREO_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__THIRDMD_UPLINK_WORDLEN_SHIFT 26 +#define HII2S_SW_RST_N__THIRDMD_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__VOICE_UPLINK_WORDLEN_SHIFT 24 +#define HII2S_SW_RST_N__VOICE_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT 20 +#define HII2S_SW_RST_N__ST_DL_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__THIRDMD_DLINK_WORDLEN_SHIFT 18 +#define HII2S_SW_RST_N__THIRDMD_DLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__VOICE_DLINK_WORDLEN_SHIFT 16 +#define HII2S_SW_RST_N__VOICE_DLINK_WORDLEN_MASK 3 + +#define HII2S_SW_RST_N__SW_RST_N BIT(0) + +enum hi6210_bits { + HII2S_BITS_16, + HII2S_BITS_18, + HII2S_BITS_20, + HII2S_BITS_24, +}; + + +#define HII2S_IF_CLK_EN_CFG 4 + +#define HII2S_IF_CLK_EN_CFG__THIRDMD_UPLINK_EN BIT(25) +#define HII2S_IF_CLK_EN_CFG__THIRDMD_DLINK_EN BIT(24) +#define HII2S_IF_CLK_EN_CFG__S3_IF_CLK_EN BIT(20) +#define HII2S_IF_CLK_EN_CFG__S2_IF_CLK_EN BIT(16) +#define HII2S_IF_CLK_EN_CFG__S2_OL_MIXER_EN BIT(15) +#define HII2S_IF_CLK_EN_CFG__S2_OL_SRC_EN BIT(14) +#define HII2S_IF_CLK_EN_CFG__S2_IR_PGA_EN BIT(13) +#define HII2S_IF_CLK_EN_CFG__S2_IL_PGA_EN BIT(12) +#define HII2S_IF_CLK_EN_CFG__S1_IR_PGA_EN BIT(10) +#define HII2S_IF_CLK_EN_CFG__S1_IL_PGA_EN BIT(9) +#define HII2S_IF_CLK_EN_CFG__S1_IF_CLK_EN BIT(8) +#define HII2S_IF_CLK_EN_CFG__VOICE_DLINK_SRC_EN BIT(7) +#define HII2S_IF_CLK_EN_CFG__VOICE_DLINK_EN BIT(6) +#define HII2S_IF_CLK_EN_CFG__ST_DL_R_EN BIT(5) +#define HII2S_IF_CLK_EN_CFG__ST_DL_L_EN BIT(4) +#define HII2S_IF_CLK_EN_CFG__VOICE_UPLINK_R_EN BIT(3) +#define HII2S_IF_CLK_EN_CFG__VOICE_UPLINK_L_EN BIT(2) +#define HII2S_IF_CLK_EN_CFG__STEREO_UPLINK_R_EN BIT(1) +#define HII2S_IF_CLK_EN_CFG__STEREO_UPLINK_L_EN BIT(0) + +#define HII2S_DIG_FILTER_CLK_EN_CFG 8 +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_SDM_EN BIT(30) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_HBF2I_EN BIT(28) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_MIXER_EN BIT(25) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_AGC_EN BIT(24) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_SDM_EN BIT(22) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_HBF2I_EN BIT(20) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_MIXER_EN BIT(17) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_AGC_EN BIT(16) + +#define HII2S_FS_CFG 0xc + +#define HII2S_FS_CFG__FS_S2_SHIFT 28 +#define HII2S_FS_CFG__FS_S2_MASK 7 +#define HII2S_FS_CFG__FS_S1_SHIFT 24 +#define HII2S_FS_CFG__FS_S1_MASK 7 +#define HII2S_FS_CFG__FS_ADCLR_SHIFT 20 +#define HII2S_FS_CFG__FS_ADCLR_MASK 7 +#define HII2S_FS_CFG__FS_DACLR_SHIFT 16 +#define HII2S_FS_CFG__FS_DACLR_MASK 7 +#define HII2S_FS_CFG__FS_ST_DL_R_SHIFT 8 +#define HII2S_FS_CFG__FS_ST_DL_R_MASK 7 +#define HII2S_FS_CFG__FS_ST_DL_L_SHIFT 4 +#define HII2S_FS_CFG__FS_ST_DL_L_MASK 7 +#define HII2S_FS_CFG__FS_VOICE_DLINK_SHIFT 0 +#define HII2S_FS_CFG__FS_VOICE_DLINK_MASK 7 + +enum hi6210_i2s_rates { + HII2S_FS_RATE_8KHZ = 0, + HII2S_FS_RATE_16KHZ = 1, + HII2S_FS_RATE_32KHZ = 2, + HII2S_FS_RATE_48KHZ = 4, + HII2S_FS_RATE_96KHZ = 5, + HII2S_FS_RATE_192KHZ = 6, +}; + +#define HII2S_I2S_CFG 0x10 + +#define HII2S_I2S_CFG__S2_IF_TX_EN BIT(31) +#define HII2S_I2S_CFG__S2_IF_RX_EN BIT(30) +#define HII2S_I2S_CFG__S2_FRAME_MODE BIT(29) +#define HII2S_I2S_CFG__S2_MST_SLV BIT(28) +#define HII2S_I2S_CFG__S2_LRCK_MODE BIT(27) +#define HII2S_I2S_CFG__S2_CHNNL_MODE BIT(26) +#define HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT 24 +#define HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_MASK 3 +#define HII2S_I2S_CFG__S2_DIRECT_LOOP_SHIFT 22 +#define HII2S_I2S_CFG__S2_DIRECT_LOOP_MASK 3 +#define HII2S_I2S_CFG__S2_TX_CLK_SEL BIT(21) +#define HII2S_I2S_CFG__S2_RX_CLK_SEL BIT(20) +#define HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT BIT(19) +#define HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT 16 +#define HII2S_I2S_CFG__S2_FUNC_MODE_MASK 7 +#define HII2S_I2S_CFG__S1_IF_TX_EN BIT(15) +#define HII2S_I2S_CFG__S1_IF_RX_EN BIT(14) +#define HII2S_I2S_CFG__S1_FRAME_MODE BIT(13) +#define HII2S_I2S_CFG__S1_MST_SLV BIT(12) +#define HII2S_I2S_CFG__S1_LRCK_MODE BIT(11) +#define HII2S_I2S_CFG__S1_CHNNL_MODE BIT(10) +#define HII2S_I2S_CFG__S1_CODEC_IO_WORDLENGTH_SHIFT 8 +#define HII2S_I2S_CFG__S1_CODEC_IO_WORDLENGTH_MASK 3 +#define HII2S_I2S_CFG__S1_DIRECT_LOOP_SHIFT 6 +#define HII2S_I2S_CFG__S1_DIRECT_LOOP_MASK 3 +#define HII2S_I2S_CFG__S1_TX_CLK_SEL BIT(5) +#define HII2S_I2S_CFG__S1_RX_CLK_SEL BIT(4) +#define HII2S_I2S_CFG__S1_CODEC_DATA_FORMAT BIT(3) +#define HII2S_I2S_CFG__S1_FUNC_MODE_SHIFT 0 +#define HII2S_I2S_CFG__S1_FUNC_MODE_MASK 7 + +enum hi6210_i2s_formats { + HII2S_FORMAT_I2S, + HII2S_FORMAT_PCM_STD, + HII2S_FORMAT_PCM_USER, + HII2S_FORMAT_LEFT_JUST, + HII2S_FORMAT_RIGHT_JUST, +}; + +#define HII2S_DIG_FILTER_MODULE_CFG 0x14 + +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_GAIN_SHIFT 28 +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_GAIN_MASK 3 +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN4_MUTE BIT(27) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN3_MUTE BIT(26) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN2_MUTE BIT(25) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN1_MUTE BIT(24) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_GAIN_SHIFT 20 +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_GAIN_MASK 3 +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN4_MUTE BIT(19) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN3_MUTE BIT(18) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN2_MUTE BIT(17) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN1_MUTE BIT(16) +#define HII2S_DIG_FILTER_MODULE_CFG__SW_DACR_SDM_DITHER BIT(9) +#define HII2S_DIG_FILTER_MODULE_CFG__SW_DACL_SDM_DITHER BIT(8) +#define HII2S_DIG_FILTER_MODULE_CFG__LM_CODEC_DAC2ADC_SHIFT 4 +#define HII2S_DIG_FILTER_MODULE_CFG__LM_CODEC_DAC2ADC_MASK 7 +#define HII2S_DIG_FILTER_MODULE_CFG__RM_CODEC_DAC2ADC_SHIFT 0 +#define HII2S_DIG_FILTER_MODULE_CFG__RM_CODEC_DAC2ADC_MASK 7 + +enum hi6210_gains { + HII2S_GAIN_100PC, + HII2S_GAIN_50PC, + HII2S_GAIN_25PC, +}; + +#define HII2S_MUX_TOP_MODULE_CFG 0x18 + +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_GAIN_SHIFT 14 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_GAIN_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN2_MUTE BIT(13) +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN1_MUTE BIT(12) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_GAIN_SHIFT 10 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_GAIN_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN2_MUTE BIT(9) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN1_MUTE BIT(8) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_RDY BIT(6) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_MODE_SHIFT 4 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_MODE_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_RDY BIT(3) +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_MODE_SHIFT 0 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_MODE_MASK 7 + +enum hi6210_s2_src_mode { + HII2S_S2_SRC_MODE_3, + HII2S_S2_SRC_MODE_12, + HII2S_S2_SRC_MODE_6, + HII2S_S2_SRC_MODE_2, +}; + +enum hi6210_voice_dlink_src_mode { + HII2S_VOICE_DL_SRC_MODE_12 = 1, + HII2S_VOICE_DL_SRC_MODE_6, + HII2S_VOICE_DL_SRC_MODE_2, + HII2S_VOICE_DL_SRC_MODE_3, +}; + +#define HII2S_ADC_PGA_CFG 0x1c +#define HII2S_S1_INPUT_PGA_CFG 0x20 +#define HII2S_S2_INPUT_PGA_CFG 0x24 +#define HII2S_ST_DL_PGA_CFG 0x28 +#define HII2S_VOICE_SIDETONE_DLINK_PGA_CFG 0x2c +#define HII2S_APB_AFIFO_CFG_1 0x30 +#define HII2S_APB_AFIFO_CFG_2 0x34 +#define HII2S_ST_DL_FIFO_TH_CFG 0x38 + +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT 24 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT 16 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT 8 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT 0 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_MASK 0x1f + +#define HII2S_STEREO_UPLINK_FIFO_TH_CFG 0x3c +#define HII2S_VOICE_UPLINK_FIFO_TH_CFG 0x40 +#define HII2S_CODEC_IRQ_MASK 0x44 +#define HII2S_CODEC_IRQ 0x48 +#define HII2S_DACL_AGC_CFG_1 0x4c +#define HII2S_DACL_AGC_CFG_2 0x50 +#define HII2S_DACR_AGC_CFG_1 0x54 +#define HII2S_DACR_AGC_CFG_2 0x58 +#define HII2S_DMIC_SIF_CFG 0x5c +#define HII2S_MISC_CFG 0x60 + +#define HII2S_MISC_CFG__THIRDMD_DLINK_TEST_SEL BIT(17) +#define HII2S_MISC_CFG__THIRDMD_DLINK_DIN_SEL BIT(16) +#define HII2S_MISC_CFG__S3_DOUT_RIGHT_SEL BIT(14) +#define HII2S_MISC_CFG__S3_DOUT_LEFT_SEL BIT(13) +#define HII2S_MISC_CFG__S3_DIN_TEST_SEL BIT(12) +#define HII2S_MISC_CFG__VOICE_DLINK_SRC_UP_DOUT_VLD_SEL BIT(8) +#define HII2S_MISC_CFG__VOICE_DLINK_TEST_SEL BIT(7) +#define HII2S_MISC_CFG__VOICE_DLINK_DIN_SEL BIT(6) +#define HII2S_MISC_CFG__ST_DL_TEST_SEL BIT(4) +#define HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL BIT(3) +#define HII2S_MISC_CFG__S2_DOUT_TEST_SEL BIT(2) +#define HII2S_MISC_CFG__S1_DOUT_TEST_SEL BIT(1) +#define HII2S_MISC_CFG__S2_DOUT_LEFT_SEL BIT(0) + +#define HII2S_S2_SRC_CFG 0x64 +#define HII2S_MEM_CFG 0x68 +#define HII2S_THIRDMD_PCM_PGA_CFG 0x6c +#define HII2S_THIRD_MODEM_FIFO_TH 0x70 +#define HII2S_S3_ANTI_FREQ_JITTER_TX_INC_CNT 0x74 +#define HII2S_S3_ANTI_FREQ_JITTER_TX_DEC_CNT 0x78 +#define HII2S_S3_ANTI_FREQ_JITTER_RX_INC_CNT 0x7c +#define HII2S_S3_ANTI_FREQ_JITTER_RX_DEC_CNT 0x80 +#define HII2S_ANTI_FREQ_JITTER_EN 0x84 +#define HII2S_CLK_SEL 0x88 + +/* 0 = BT owns the i2s */ +#define HII2S_CLK_SEL__I2S_BT_FM_SEL BIT(0) +/* 0 = internal source, 1 = ext */ +#define HII2S_CLK_SEL__EXT_12_288MHZ_SEL BIT(1) + + +#define HII2S_THIRDMD_DLINK_CHANNEL 0xe8 +#define HII2S_THIRDMD_ULINK_CHANNEL 0xec +#define HII2S_VOICE_DLINK_CHANNEL 0xf0 + +/* shovel data in here for playback */ +#define HII2S_ST_DL_CHANNEL 0xf4 +#define HII2S_STEREO_UPLINK_CHANNEL 0xf8 +#define HII2S_VOICE_UPLINK_CHANNEL 0xfc + +#endif/* _HI6210_I2S_H */ diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index 526855ad479e..67968ef3bbda 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -202,6 +202,30 @@ config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH platforms with MAX98090 audio codec it also can support TI jack chip as aux device. If unsure select "N". +config SND_SOC_INTEL_BYT_CHT_DA7213_MACH + tristate "ASoC Audio driver for Intel Baytrail & Cherrytrail with DA7212/7213 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_DA7213 + select SND_SST_ATOM_HIFI2_PLATFORM + select SND_SST_IPC_ACPI + select SND_SOC_INTEL_SST_MATCH if ACPI + help + This adds support for ASoC machine driver for Intel(R) Baytrail & CherryTrail + platforms with DA7212/7213 audio codec. + If unsure select "N". + +config SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH + tristate "ASoC Audio driver for Intel Baytrail & Cherrytrail platform with no codec (MinnowBoard MAX, Up)" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SST_ATOM_HIFI2_PLATFORM + select SND_SST_IPC_ACPI + select SND_SOC_INTEL_SST_MATCH if ACPI + help + This adds support for ASoC machine driver for the MinnowBoard Max or + Up boards and provides access to I2S signals on the Low-Speed + connector + If unsure select "N". + config SND_SOC_INTEL_SKYLAKE tristate select SND_HDA_EXT_CORE diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index 747c0f393d2d..dd250b8b26f2 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -420,7 +420,21 @@ static const struct dmi_system_id byt_table[] = { .callback = byt_thinkpad10_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_NAME, "20C3001VHH"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), + }, + }, + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), + }, + }, + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), }, }, { } @@ -480,12 +494,23 @@ static struct sst_acpi_mach sst_acpi_bytcr[] = { &byt_rvp_platform_data }, {"10EC5651", "bytcr_rt5651", "intel/fw_sst_0f28.bin", "bytcr_rt5651", NULL, &byt_rvp_platform_data }, + {"DLGS7212", "bytcht_da7213", "intel/fw_sst_0f28.bin", "bytcht_da7213", NULL, + &byt_rvp_platform_data }, + {"DLGS7213", "bytcht_da7213", "intel/fw_sst_0f28.bin", "bytcht_da7213", NULL, + &byt_rvp_platform_data }, /* some Baytrail platforms rely on RT5645, use CHT machine driver */ {"10EC5645", "cht-bsw-rt5645", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, &byt_rvp_platform_data }, {"10EC5648", "cht-bsw-rt5645", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, &byt_rvp_platform_data }, - +#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) + /* + * This is always last in the table so that it is selected only when + * enabled explicitly and there is no codec-related information in SSDT + */ + {"80860F28", "bytcht_nocodec", "intel/fw_sst_0f28.bin", "bytcht_nocodec", NULL, + &byt_rvp_platform_data }, +#endif {}, }; @@ -504,6 +529,10 @@ static struct sst_acpi_mach sst_acpi_chv[] = { {"193C9890", "cht-bsw-max98090", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, &chv_platform_data }, + {"DLGS7212", "bytcht_da7213", "intel/fw_sst_22a8.bin", "bytcht_da7213", NULL, + &chv_platform_data }, + {"DLGS7213", "bytcht_da7213", "intel/fw_sst_22a8.bin", "bytcht_da7213", NULL, + &chv_platform_data }, /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ {"10EC5640", "bytcr_rt5640", "intel/fw_sst_22a8.bin", "bytcr_rt5640", cht_quirk, &chv_platform_data }, @@ -512,6 +541,14 @@ static struct sst_acpi_mach sst_acpi_chv[] = { /* some CHT-T platforms rely on RT5651, use Baytrail machine driver */ {"10EC5651", "bytcr_rt5651", "intel/fw_sst_22a8.bin", "bytcr_rt5651", NULL, &chv_platform_data }, +#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) + /* + * This is always last in the table so that it is selected only when + * enabled explicitly and there is no codec-related information in SSDT + */ + {"808622A8", "bytcht_nocodec", "intel/fw_sst_22a8.bin", "bytcht_nocodec", NULL, + &chv_platform_data }, +#endif {}, }; diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c index 14c2d9d18180..20b01e02ed8f 100644 --- a/sound/soc/intel/atom/sst/sst_ipc.c +++ b/sound/soc/intel/atom/sst/sst_ipc.c @@ -236,7 +236,9 @@ static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, retval = init->result; goto ret; } - dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n", + if (memcmp(&sst_drv_ctx->fw_version, &init->fw_version, + sizeof(init->fw_version))) + dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n", init->fw_version.type, init->fw_version.major, init->fw_version.minor, init->fw_version.build); dev_dbg(sst_drv_ctx->dev, "Build date %s Time %s\n", diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 5639f10774e6..56896e09445d 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -10,6 +10,8 @@ snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o +snd-soc-sst-byt-cht-da7213-objs := bytcht_da7213.o +snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o snd-soc-skl_rt286-objs := skl_rt286.o snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o @@ -26,6 +28,8 @@ obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_DA7213_MACH) += snd-soc-sst-byt-cht-da7213.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) += snd-soc-sst-byt-cht-nocodec.o obj-$(CONFIG_SND_SOC_INTEL_SKL_RT286_MACH) += snd-soc-skl_rt286.o obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c index 53c6b4cbb1e1..14d9693c1641 100644 --- a/sound/soc/intel/boards/bdw-rt5677.c +++ b/sound/soc/intel/boards/bdw-rt5677.c @@ -193,13 +193,12 @@ static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) RT5677_CLK_SEL_I2S1_ASRC); /* Request rt5677 GPIO for headphone amp control */ - bdw_rt5677->gpio_hp_en = devm_gpiod_get_index(codec->dev, - "headphone-enable", 0, 0); + bdw_rt5677->gpio_hp_en = devm_gpiod_get(codec->dev, "headphone-enable", + GPIOD_OUT_LOW); if (IS_ERR(bdw_rt5677->gpio_hp_en)) { dev_err(codec->dev, "Can't find HP_AMP_SHDN_L gpio\n"); return PTR_ERR(bdw_rt5677->gpio_hp_en); } - gpiod_direction_output(bdw_rt5677->gpio_hp_en, 0); /* Create and initialize headphone jack */ if (!snd_soc_card_jack_new(rtd->card, "Headphone Jack", diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c index faf865bb1765..6dcbbcefc25b 100644 --- a/sound/soc/intel/boards/broadwell.c +++ b/sound/soc/intel/boards/broadwell.c @@ -269,9 +269,6 @@ static struct snd_soc_card broadwell_rt286 = { static int broadwell_audio_probe(struct platform_device *pdev) { broadwell_rt286.dev = &pdev->dev; - - snd_soc_set_dmi_name(&broadwell_rt286, NULL); - return devm_snd_soc_register_card(&pdev->dev, &broadwell_rt286); } diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c index 2cda06cde4d1..3a8c4d954a91 100644 --- a/sound/soc/intel/boards/bxt_da7219_max98357a.c +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -55,6 +55,54 @@ enum { BXT_DPCM_AUDIO_HDMI3_PB, }; +static inline struct snd_soc_dai *bxt_get_codec_dai(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + + if (!strncmp(rtd->codec_dai->name, BXT_DIALOG_CODEC_DAI, + strlen(BXT_DIALOG_CODEC_DAI))) + return rtd->codec_dai; + } + + return NULL; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + + codec_dai = bxt_get_codec_dai(card); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if(SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, + DA7219_CLKSRC_MCLK, 19200000, SND_SOC_CLOCK_IN); + if (ret) + dev_err(card->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + static const struct snd_kcontrol_new broxton_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone Jack"), SOC_DAPM_PIN_SWITCH("Headset Mic"), @@ -69,6 +117,8 @@ static const struct snd_soc_dapm_widget broxton_widgets[] = { SND_SOC_DAPM_SPK("HDMI1", NULL), SND_SOC_DAPM_SPK("HDMI2", NULL), SND_SOC_DAPM_SPK("HDMI3", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD|SND_SOC_DAPM_PRE_PMU), }; static const struct snd_soc_dapm_route broxton_map[] = { @@ -109,6 +159,9 @@ static const struct snd_soc_dapm_route broxton_map[] = { /* DMIC */ {"dmic01_hifi", NULL, "DMIC01 Rx"}, {"DMIC01 Rx", NULL, "DMIC AIF"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, }; static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, @@ -243,49 +296,6 @@ static const struct snd_soc_ops broxton_da7219_fe_ops = { .startup = bxt_fe_startup, }; -static int broxton_da7219_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 = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, - DA7219_CLKSRC_MCLK, 19200000, SND_SOC_CLOCK_IN); - if (ret < 0) - dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); - - ret = snd_soc_dai_set_pll(codec_dai, 0, - DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); - if (ret < 0) { - dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); - return -EIO; - } - - return ret; -} - -static int broxton_da7219_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_pll(codec_dai, 0, - DA7219_SYSCLK_MCLK, 0, 0); - if (ret < 0) { - dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); - return -EIO; - } - - return ret; -} - -static const struct snd_soc_ops broxton_da7219_ops = { - .hw_params = broxton_da7219_hw_params, - .hw_free = broxton_da7219_hw_free, -}; - static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -467,7 +477,6 @@ static struct snd_soc_dai_link broxton_dais[] = { SND_SOC_DAIFMT_CBS_CFS, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp_fixup, - .ops = &broxton_da7219_ops, .dpcm_playback = 1, .dpcm_capture = 1, }, diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c index 176c080a9818..1a68d043c803 100644 --- a/sound/soc/intel/boards/bxt_rt298.c +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -274,12 +274,15 @@ static int bxt_fe_startup(struct snd_pcm_substream *substream) * on this platform for PCM device we support: * 48Khz * stereo + * 16-bit audio */ runtime->hw.channels_max = 2; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &constraints_channels); + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); diff --git a/sound/soc/intel/boards/bytcht_da7213.c b/sound/soc/intel/boards/bytcht_da7213.c new file mode 100644 index 000000000000..18873e23f404 --- /dev/null +++ b/sound/soc/intel/boards/bytcht_da7213.c @@ -0,0 +1,283 @@ +/* + * bytcht-da7213.c - ASoc Machine driver for Intel Baytrail and + * Cherrytrail-based platforms, with Dialog DA7213 codec + * + * Copyright (C) 2017 Intel Corporation + * Author: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/platform_sst_audio.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../../codecs/da7213.h" +#include "../atom/sst-atom-controls.h" +#include "../common/sst-acpi.h" + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Mic"), + SOC_DAPM_PIN_SWITCH("Aux In"), +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_LINE("Aux In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + {"AUXL", NULL, "Aux In"}, + {"AUXR", NULL, "Aux In"}, + + /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ + {"MIC1", NULL, "Headset Mic"}, + {"MIC2", NULL, "Mic"}, + + /* SOC-codec link */ + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "Capture"}, +}; + +static int codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + int ret; + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static int aif1_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 = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, + 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7213_SYSCLK_PLL_SRM, 0, DA7213_PLL_FREQ_OUT_98304000); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static int aif1_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7213_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static const struct snd_soc_ops aif1_ops = { + .startup = aif1_startup, +}; + +static const struct snd_soc_ops ssp2_ops = { + .hw_params = aif1_hw_params, + .hw_free = aif1_hw_free, + +}; + +static struct snd_soc_dai_link dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-Codec", + .id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "da7213-hifi", + .codec_name = "i2c-DLGS7213:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = codec_fixup, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card bytcht_da7213_card = { + .name = "bytcht-da7213", + .owner = THIS_MODULE, + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static char codec_name[16]; /* i2c-<HID>:00 with HID being 8 chars */ + +static int bytcht_da7213_probe(struct platform_device *pdev) +{ + int ret_val = 0; + int i; + struct snd_soc_card *card; + struct sst_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; + + mach = (&pdev->dev)->platform_data; + card = &bytcht_da7213_card; + card->dev = &pdev->dev; + + /* fix index of codec dai */ + dai_index = MERR_DPCM_COMPR + 1; + for (i = 0; i < ARRAY_SIZE(dailink); i++) { + if (!strcmp(dailink[i].codec_name, "i2c-DLGS7213:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + i2c_name = sst_acpi_find_name_from_hid(mach->id); + if (i2c_name != NULL) { + snprintf(codec_name, sizeof(codec_name), + "%s%s", "i2c-", i2c_name); + dailink[dai_index].codec_name = codec_name; + } + + ret_val = devm_snd_soc_register_card(&pdev->dev, card); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, card); + return ret_val; +} + +static struct platform_driver bytcht_da7213_driver = { + .driver = { + .name = "bytcht_da7213", + }, + .probe = bytcht_da7213_probe, +}; +module_platform_driver(bytcht_da7213_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail+DA7213 Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_da7213"); diff --git a/sound/soc/intel/boards/bytcht_nocodec.c b/sound/soc/intel/boards/bytcht_nocodec.c new file mode 100644 index 000000000000..89853eeaaf9d --- /dev/null +++ b/sound/soc/intel/boards/bytcht_nocodec.c @@ -0,0 +1,208 @@ +/* + * bytcht_nocodec.c - ASoc Machine driver for MinnowBoard Max and Up + * to make I2S signals observable on the Low-Speed connector. Audio codec + * is not managed by ASoC/DAPM + * + * Copyright (C) 2015-2017 Intel Corp + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../atom/sst-atom-controls.h" + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"ssp2 Rx", NULL, "Mic"}, + {"Speaker", NULL, "ssp2 Tx"}, +}; + +static int codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops aif1_ops = { + .startup = aif1_startup, +}; + +static struct snd_soc_dai_link dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-LowSpeed Connector", + .id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = codec_fixup, + .ignore_suspend = 1, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +/* SoC card */ +static struct snd_soc_card bytcht_nocodec_card = { + .name = "bytcht-nocodec", + .owner = THIS_MODULE, + .dai_link = dais, + .num_links = ARRAY_SIZE(dais), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .fully_routed = true, +}; + +static int snd_bytcht_nocodec_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + bytcht_nocodec_card.dev = &pdev->dev; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &bytcht_nocodec_card); + + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", + ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &bytcht_nocodec_card); + return ret_val; +} + +static struct platform_driver snd_bytcht_nocodec_mc_driver = { + .driver = { + .name = "bytcht_nocodec", + }, + .probe = snd_bytcht_nocodec_mc_probe, +}; +module_platform_driver(snd_bytcht_nocodec_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Nocodec Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_nocodec"); diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 9e2a3404a836..4a76b099a508 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -19,6 +19,7 @@ #include <linux/init.h> #include <linux/module.h> +#include <linux/moduleparam.h> #include <linux/platform_device.h> #include <linux/acpi.h> #include <linux/device.h> @@ -56,35 +57,88 @@ enum { struct byt_rt5640_private { struct clk *mclk; }; +static bool is_bytcr; static unsigned long byt_rt5640_quirk = BYT_RT5640_MCLK_EN; +static unsigned int quirk_override; +module_param_named(quirk, quirk_override, uint, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); static void log_quirks(struct device *dev) { - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_DMIC1_MAP) - dev_info(dev, "quirk DMIC1_MAP enabled"); - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_DMIC2_MAP) - dev_info(dev, "quirk DMIC2_MAP enabled"); - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_IN1_MAP) - dev_info(dev, "quirk IN1_MAP enabled"); - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_IN3_MAP) - dev_info(dev, "quirk IN3_MAP enabled"); - if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) - dev_info(dev, "quirk DMIC enabled"); + int map; + bool has_dmic = false; + bool has_mclk = false; + bool has_ssp0 = false; + bool has_ssp0_aif1 = false; + bool has_ssp0_aif2 = false; + bool has_ssp2_aif2 = false; + + map = BYT_RT5640_MAP(byt_rt5640_quirk); + switch (map) { + case BYT_RT5640_DMIC1_MAP: + dev_info(dev, "quirk DMIC1_MAP enabled\n"); + has_dmic = true; + break; + case BYT_RT5640_DMIC2_MAP: + dev_info(dev, "quirk DMIC2_MAP enabled\n"); + has_dmic = true; + break; + case BYT_RT5640_IN1_MAP: + dev_info(dev, "quirk IN1_MAP enabled\n"); + break; + case BYT_RT5640_IN3_MAP: + dev_info(dev, "quirk IN3_MAP enabled\n"); + break; + default: + dev_err(dev, "quirk map 0x%x is not supported, microphone input will not work\n", map); + break; + } + if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) { + if (has_dmic) + dev_info(dev, "quirk DMIC enabled\n"); + else + dev_err(dev, "quirk DMIC enabled but no DMIC input set, will be ignored\n"); + } if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) - dev_info(dev, "quirk MONO_SPEAKER enabled"); - if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) - dev_info(dev, "quirk DIFF_MIC enabled"); - if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) - dev_info(dev, "quirk SSP2_AIF2 enabled"); - if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) - dev_info(dev, "quirk SSP0_AIF1 enabled"); - if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) - dev_info(dev, "quirk SSP0_AIF2 enabled"); - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) - dev_info(dev, "quirk MCLK_EN enabled"); - if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) - dev_info(dev, "quirk MCLK_25MHZ enabled"); + dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) { + if (!has_dmic) + dev_info(dev, "quirk DIFF_MIC enabled\n"); + else + dev_info(dev, "quirk DIFF_MIC enabled but DMIC input selected, will be ignored\n"); + } + if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) { + dev_info(dev, "quirk SSP0_AIF1 enabled\n"); + has_ssp0 = true; + has_ssp0_aif1 = true; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) { + dev_info(dev, "quirk SSP0_AIF2 enabled\n"); + has_ssp0 = true; + has_ssp0_aif2 = true; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) { + dev_info(dev, "quirk SSP2_AIF2 enabled\n"); + has_ssp2_aif2 = true; + } + if (is_bytcr && !has_ssp0) + dev_err(dev, "Invalid routing, bytcr detected but no SSP0-based quirk, audio cannot work with SSP2 on bytcr\n"); + if (has_ssp0_aif1 && has_ssp0_aif2) + dev_err(dev, "Invalid routing, SSP0 cannot be connected to both AIF1 and AIF2\n"); + if (has_ssp0 && has_ssp2_aif2) + dev_err(dev, "Invalid routing, cannot have both SSP0 and SSP2 connected to codec\n"); + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { + dev_info(dev, "quirk MCLK_EN enabled\n"); + has_mclk = true; + } + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) { + if (has_mclk) + dev_info(dev, "quirk MCLK_25MHZ enabled\n"); + else + dev_err(dev, "quirk MCLK_25MHZ enabled but quirk MCLK not selected, will be ignored\n"); + } } @@ -128,7 +182,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, ret = clk_prepare_enable(priv->mclk); if (ret < 0) { dev_err(card->dev, - "could not configure MCLK state"); + "could not configure MCLK state\n"); return ret; } } @@ -710,8 +764,8 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) int i; int dai_index; struct byt_rt5640_private *priv; - bool is_bytcr = false; + is_bytcr = false; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); if (!priv) return -ENOMEM; @@ -806,6 +860,11 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) /* check quirks before creating card */ dmi_check_system(byt_rt5640_quirk_table); + if (quirk_override) { + dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", + (unsigned int)byt_rt5640_quirk, quirk_override); + byt_rt5640_quirk = quirk_override; + } log_quirks(&pdev->dev); if ((byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) || diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c index a3459d1682a6..d33bdaf92c57 100644 --- a/sound/soc/intel/haswell/sst-haswell-ipc.c +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -2000,10 +2000,8 @@ int sst_hsw_module_set_param(struct sst_hsw *hsw, u32 param_size, char *param) { int ret; - unsigned char *data = NULL; u32 header = 0; u32 payload_size = 0, transfer_parameter_size = 0; - dma_addr_t dma_addr = 0; struct sst_hsw_transfer_parameter *parameter; struct device *dev = hsw->dev; @@ -2047,10 +2045,6 @@ int sst_hsw_module_set_param(struct sst_hsw *hsw, kfree(parameter); - if (data) - dma_free_coherent(hsw->dsp->dma_dev, - param_size, (void *)data, dma_addr); - return ret; } diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c index 15a063a403cc..f5e7dbb1ba39 100644 --- a/sound/soc/intel/skylake/bxt-sst.c +++ b/sound/soc/intel/skylake/bxt-sst.c @@ -25,7 +25,8 @@ #include "skl-sst-ipc.h" #define BXT_BASEFW_TIMEOUT 3000 -#define BXT_INIT_TIMEOUT 500 +#define BXT_INIT_TIMEOUT 300 +#define BXT_ROM_INIT_TIMEOUT 70 #define BXT_IPC_PURGE_FW 0x01004000 #define BXT_ROM_INIT 0x5 @@ -45,6 +46,8 @@ /* Delay before scheduling D0i3 entry */ #define BXT_D0I3_DELAY 5000 +#define BXT_FW_ROM_INIT_RETRY 3 + static unsigned int bxt_get_errorcode(struct sst_dsp *ctx) { return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE); @@ -55,29 +58,15 @@ bxt_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) { struct snd_dma_buffer dmab; struct skl_sst *skl = ctx->thread_context; - const struct firmware *fw = NULL; struct firmware stripped_fw; int ret = 0, i, dma_id, stream_tag; /* library indices start from 1 to N. 0 represents base FW */ for (i = 1; i < lib_count; i++) { - ret = request_firmware(&fw, linfo[i].name, ctx->dev); - if (ret < 0) { - dev_err(ctx->dev, "Request lib %s failed:%d\n", - linfo[i].name, ret); - return ret; - } - - if (skl->is_first_boot) { - ret = snd_skl_parse_uuids(ctx, fw, + ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw, BXT_ADSP_FW_BIN_HDR_OFFSET, i); - if (ret < 0) - goto load_library_failed; - } - - stripped_fw.data = fw->data; - stripped_fw.size = fw->size; - skl_dsp_strip_extended_manifest(&stripped_fw); + if (ret < 0) + goto load_library_failed; stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, stripped_fw.size, &dmab); @@ -92,21 +81,19 @@ bxt_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) memcpy(dmab.area, stripped_fw.data, stripped_fw.size); ctx->dsp_ops.trigger(ctx->dev, true, stream_tag); - ret = skl_sst_ipc_load_library(&skl->ipc, dma_id, i); + ret = skl_sst_ipc_load_library(&skl->ipc, dma_id, i, true); if (ret < 0) dev_err(ctx->dev, "IPC Load Lib for %s fail: %d\n", linfo[i].name, ret); ctx->dsp_ops.trigger(ctx->dev, false, stream_tag); ctx->dsp_ops.cleanup(ctx->dev, &dmab, stream_tag); - release_firmware(fw); - fw = NULL; } return ret; load_library_failed: - release_firmware(fw); + skl_release_library(linfo, lib_count); return ret; } @@ -156,7 +143,7 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, SKL_ADSP_REG_HIPCIE_DONE, BXT_INIT_TIMEOUT, "HIPCIE Done"); if (ret < 0) { - dev_err(ctx->dev, "Timout for Purge Request%d\n", ret); + dev_err(ctx->dev, "Timeout for Purge Request%d\n", ret); goto base_fw_load_failed; } @@ -173,7 +160,7 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, /* Step 7: Wait for ROM init */ ret = sst_dsp_register_poll(ctx, BXT_ADSP_FW_STATUS, SKL_FW_STS_MASK, - SKL_FW_INIT, BXT_INIT_TIMEOUT, "ROM Load"); + SKL_FW_INIT, BXT_ROM_INIT_TIMEOUT, "ROM Load"); if (ret < 0) { dev_err(ctx->dev, "Timeout for ROM init, ret:%d\n", ret); goto base_fw_load_failed; @@ -206,18 +193,16 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) { struct firmware stripped_fw; struct skl_sst *skl = ctx->thread_context; - int ret; + int ret, i; - ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); - if (ret < 0) { - dev_err(ctx->dev, "Request firmware failed %d\n", ret); - goto sst_load_base_firmware_failed; + if (ctx->fw == NULL) { + ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); + if (ret < 0) { + dev_err(ctx->dev, "Request firmware failed %d\n", ret); + return ret; + } } - /* check for extended manifest */ - if (ctx->fw == NULL) - goto sst_load_base_firmware_failed; - /* prase uuids on first boot */ if (skl->is_first_boot) { ret = snd_skl_parse_uuids(ctx, ctx->fw, BXT_ADSP_FW_BIN_HDR_OFFSET, 0); @@ -229,18 +214,20 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) stripped_fw.size = ctx->fw->size; skl_dsp_strip_extended_manifest(&stripped_fw); - ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); - /* Retry Enabling core and ROM load. Retry seemed to help */ - if (ret < 0) { + + for (i = 0; i < BXT_FW_ROM_INIT_RETRY; i++) { ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); - if (ret < 0) { - dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + if (ret == 0) + break; + } + + if (ret < 0) { + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); - dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); - goto sst_load_base_firmware_failed; - } + dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); + goto sst_load_base_firmware_failed; } ret = sst_transfer_fw_host_dma(ctx); @@ -265,8 +252,11 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) } } + return ret; + sst_load_base_firmware_failed: release_firmware(ctx->fw); + ctx->fw = NULL; return ret; } @@ -428,6 +418,7 @@ static int bxt_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) return ret; } } + skl->cores.state[core_id] = SKL_DSP_RUNNING; return ret; } @@ -514,11 +505,22 @@ static int bxt_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, BXT_BASE_FW_MODULE_ID, &dx); - if (ret < 0) + if (ret < 0) { dev_err(ctx->dev, "Failed to set DSP to D3:core id = %d;Continue reset\n", core_id); + /* + * In case of D3 failure, re-download the firmware, so set + * fw_loaded to false. + */ + skl->fw_loaded = false; + } + if (core_id == SKL_DSP_CORE0_ID) { + /* disable Interrupt */ + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); + } ret = skl_dsp_disable_core(ctx, core_mask); if (ret < 0) { dev_err(ctx->dev, "Failed to disable core %d\n", ret); @@ -560,23 +562,14 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, struct sst_dsp *sst; int ret; - skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); - if (skl == NULL) - return -ENOMEM; - - skl->dev = dev; - skl_dev.thread_context = skl; - INIT_LIST_HEAD(&skl->uuid_list); - - skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); - if (!skl->dsp) { - dev_err(skl->dev, "skl_dsp_ctx_init failed\n"); - return -ENODEV; + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; } + skl = *dsp; sst = skl->dsp; - sst->fw_name = fw_name; - sst->dsp_ops = dsp_ops; sst->fw_ops = bxt_fw_ops; sst->addr.lpe = mmio_base; sst->addr.shim = mmio_base; @@ -584,24 +577,15 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, sst_dsp_mailbox_init(sst, (BXT_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), SKL_ADSP_W0_UP_SZ, BXT_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); - INIT_LIST_HEAD(&sst->module_list); - ret = skl_ipc_init(dev, skl); - if (ret) - return ret; - /* set the D0i3 check */ skl->ipc.ops.check_dsp_lp_on = skl_ipc_check_D0i0; skl->cores.count = 2; skl->boot_complete = false; init_waitqueue_head(&skl->boot_wait); - skl->is_first_boot = true; INIT_DELAYED_WORK(&skl->d0i3.work, bxt_set_dsp_D0i3); skl->d0i3.state = SKL_DSP_D0I3_NONE; - if (dsp) - *dsp = skl; - return 0; } EXPORT_SYMBOL_GPL(bxt_sst_dsp_init); @@ -635,6 +619,10 @@ EXPORT_SYMBOL_GPL(bxt_sst_init_fw); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) { + + skl_release_library(ctx->lib_info, ctx->lib_count); + if (ctx->dsp->fw) + release_firmware(ctx->dsp->fw); skl_freeup_uuid_list(ctx); skl_ipc_free(&ctx->ipc); ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp); diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c index e66870474f10..ab1adc0c9cc3 100644 --- a/sound/soc/intel/skylake/skl-messages.c +++ b/sound/soc/intel/skylake/skl-messages.c @@ -58,7 +58,7 @@ static int skl_free_dma_buf(struct device *dev, struct snd_dma_buffer *dmab) #define NOTIFICATION_MASK 0xf /* disable notfication for underruns/overruns from firmware module */ -static void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable) +void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable) { struct notification_mask mask; struct skl_ipc_large_config_msg msg = {0}; @@ -209,7 +209,7 @@ static const struct skl_dsp_ops dsp_ops[] = { { .id = 0x9d71, .loader_ops = skl_get_loader_ops, - .init = skl_sst_dsp_init, + .init = kbl_sst_dsp_init, .init_fw = skl_sst_init_fw, .cleanup = skl_sst_dsp_cleanup }, @@ -274,6 +274,7 @@ int skl_init_dsp(struct skl *skl) if (ret < 0) return ret; + skl->skl_sst->dsp_ops = ops; dev_dbg(bus->dev, "dsp registration status=%d\n", ret); return ret; @@ -284,16 +285,11 @@ int skl_free_dsp(struct skl *skl) struct hdac_ext_bus *ebus = &skl->ebus; struct hdac_bus *bus = ebus_to_hbus(ebus); struct skl_sst *ctx = skl->skl_sst; - const struct skl_dsp_ops *ops; /* disable ppcap interrupt */ snd_hdac_ext_bus_ppcap_int_enable(&skl->ebus, false); - ops = skl_get_dsp_ops(skl->pci->device); - if (!ops) - return -EIO; - - ops->cleanup(bus->dev, ctx); + ctx->dsp_ops->cleanup(bus->dev, ctx); if (ctx->dsp->addr.lpe) iounmap(ctx->dsp->addr.lpe); @@ -866,7 +862,7 @@ static void skl_clear_module_state(struct skl_module_pin *mpin, int max, } if (!found) - mcfg->m_state = SKL_MODULE_UNINIT; + mcfg->m_state = SKL_MODULE_INIT_DONE; return; } @@ -1098,7 +1094,7 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) dev_dbg(ctx->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id); /* If pipe is started, do stop the pipe in FW. */ - if (pipe->state > SKL_PIPE_STARTED) { + if (pipe->state >= SKL_PIPE_STARTED) { ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); if (ret < 0) { dev_err(ctx->dev, "Failed to stop pipeline\n"); diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c index 7eb9c419dc7f..e3f06672fd6d 100644 --- a/sound/soc/intel/skylake/skl-nhlt.c +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -24,8 +24,6 @@ static u8 OSC_UUID[16] = {0x6E, 0x88, 0x9F, 0xA6, 0xEB, 0x6C, 0x94, 0x45, 0xA4, 0x1F, 0x7B, 0x5D, 0xCE, 0x24, 0xC5, 0x53}; -#define DSDT_NHLT_PATH "\\_SB.PCI0.HDAS" - struct nhlt_acpi_table *skl_nhlt_init(struct device *dev) { acpi_handle handle; @@ -33,8 +31,9 @@ struct nhlt_acpi_table *skl_nhlt_init(struct device *dev) struct nhlt_resource_desc *nhlt_ptr = NULL; struct nhlt_acpi_table *nhlt_table = NULL; - if (ACPI_FAILURE(acpi_get_handle(NULL, DSDT_NHLT_PATH, &handle))) { - dev_err(dev, "Requested NHLT device not found\n"); + handle = ACPI_HANDLE(dev); + if (!handle) { + dev_err(dev, "Didn't find ACPI_HANDLE\n"); return NULL; } diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c index e12520e142ff..e91bbcffc856 100644 --- a/sound/soc/intel/skylake/skl-pcm.c +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -21,6 +21,7 @@ #include <linux/pci.h> #include <linux/pm_runtime.h> +#include <linux/delay.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include "skl.h" @@ -155,7 +156,7 @@ int skl_pcm_host_dma_prepare(struct device *dev, struct skl_pipe_params *params) snd_hdac_ext_stream_decouple(ebus, stream, true); format_val = snd_hdac_calc_stream_format(params->s_freq, - params->ch, params->format, 32, 0); + params->ch, params->format, params->host_bps, 0); dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", format_val, params->s_freq, params->ch, params->format); @@ -190,8 +191,8 @@ int skl_pcm_link_dma_prepare(struct device *dev, struct skl_pipe_params *params) stream = stream_to_hdac_ext_stream(hstream); snd_hdac_ext_stream_decouple(ebus, stream, true); - format_val = snd_hdac_calc_stream_format(params->s_freq, - params->ch, params->format, 24, 0); + format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, + params->format, params->link_bps, 0); dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", format_val, params->s_freq, params->ch, params->format); @@ -262,23 +263,6 @@ static int skl_pcm_open(struct snd_pcm_substream *substream, return 0; } -static int skl_be_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct skl *skl = get_skl_ctx(dai->dev); - struct skl_sst *ctx = skl->skl_sst; - struct skl_module_cfg *mconfig; - - if (dai->playback_widget->power || dai->capture_widget->power) - return 0; - - mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream); - if (mconfig == NULL) - return -EINVAL; - - return skl_dsp_set_dma_control(ctx, mconfig); -} - static int skl_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -326,6 +310,11 @@ static int skl_pcm_hw_params(struct snd_pcm_substream *substream, p_params.host_dma_id = dma_id; p_params.stream = substream->stream; p_params.format = params_format(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.host_bps = dai->driver->playback.sig_bits; + else + p_params.host_bps = dai->driver->capture.sig_bits; + m_cfg = skl_tplg_fe_get_cpr_module(dai, p_params.stream); if (m_cfg) @@ -564,6 +553,11 @@ static int skl_link_hw_params(struct snd_pcm_substream *substream, p_params.link_index = link->index; p_params.format = params_format(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.link_bps = codec_dai->driver->playback.sig_bits; + else + p_params.link_bps = codec_dai->driver->capture.sig_bits; + return skl_tplg_be_update_params(dai, &p_params); } @@ -649,7 +643,6 @@ static struct snd_soc_dai_ops skl_dmic_dai_ops = { static struct snd_soc_dai_ops skl_be_ssp_dai_ops = { .hw_params = skl_be_hw_params, - .prepare = skl_be_prepare, }; static struct snd_soc_dai_ops skl_link_dai_ops = { @@ -670,6 +663,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, .capture = { .stream_name = "System Capture", @@ -677,6 +671,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_STEREO, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -688,6 +683,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_QUAD, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -699,6 +695,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_STEREO, .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -710,6 +707,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_STEREO, .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -721,6 +719,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_QUAD, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -736,6 +735,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, }, { @@ -751,6 +751,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, }, { @@ -766,6 +767,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, }, @@ -949,14 +951,12 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { static int skl_platform_open(struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_link *dai_link = rtd->dai_link; dev_dbg(rtd->cpu_dai->dev, "In %s:%s\n", __func__, dai_link->cpu_dai_name); - runtime = substream->runtime; snd_soc_set_runtime_hwparams(substream, &azx_pcm_hw); return 0; @@ -1062,13 +1062,31 @@ static snd_pcm_uframes_t skl_platform_pcm_pointer * HAD space reflects the actual data that is transferred. * Use the position buffer for capture, as DPIB write gets * completed earlier than the actual data written to the DDR. + * + * For capture stream following workaround is required to fix the + * incorrect position reporting. + * + * 1. Wait for 20us before reading the DMA position in buffer once + * the interrupt is generated for stream completion as update happens + * on the HDA frame boundary i.e. 20.833uSec. + * 2. Read DPIB register to flush the DMA position value. This dummy + * read is required to flush DMA position value. + * 3. Read the DMA Position-in-Buffer. This value now will be equal to + * or greater than period boundary. */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { pos = readl(ebus->bus.remap_addr + AZX_REG_VS_SDXDPIB_XBASE + (AZX_REG_VS_SDXDPIB_XINTERVAL * hdac_stream(hstream)->index)); - else + } else { + udelay(20); + readl(ebus->bus.remap_addr + + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hdac_stream(hstream)->index)); pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream)); + } if (pos >= hdac_stream(hstream)->bufsize) pos = 0; @@ -1165,7 +1183,7 @@ static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) snd_dma_pci_data(skl->pci), size, MAX_PREALLOC_SIZE); if (retval) { - dev_err(dai->dev, "dma buffer allocationf fail\n"); + dev_err(dai->dev, "dma buffer allocation fail\n"); return retval; } } @@ -1173,29 +1191,52 @@ static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) return retval; } +static int skl_get_module_info(struct skl *skl, struct skl_module_cfg *mconfig) +{ + struct skl_sst *ctx = skl->skl_sst; + struct uuid_module *module; + uuid_le *uuid_mod; + + uuid_mod = (uuid_le *)mconfig->guid; + + if (list_empty(&ctx->uuid_list)) { + dev_err(ctx->dev, "Module list is empty\n"); + return -EIO; + } + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + mconfig->id.module_id = module->id; + mconfig->is_loadable = module->is_loadable; + return 0; + } + } + + return -EIO; +} + static int skl_populate_modules(struct skl *skl) { struct skl_pipeline *p; struct skl_pipe_module *m; struct snd_soc_dapm_widget *w; struct skl_module_cfg *mconfig; - int ret; + int ret = 0; list_for_each_entry(p, &skl->ppl_list, node) { list_for_each_entry(m, &p->pipe->w_list, node) { - w = m->w; mconfig = w->priv; - ret = snd_skl_get_module_info(skl->skl_sst, mconfig); + ret = skl_get_module_info(skl, mconfig); if (ret < 0) { dev_err(skl->skl_sst->dev, - "query module info failed:%d\n", ret); - goto err; + "query module info failed\n"); + return ret; } } } -err: + return ret; } @@ -1232,6 +1273,7 @@ static int skl_platform_soc_probe(struct snd_soc_platform *platform) } skl_populate_modules(skl); skl->skl_sst->update_d0i3c = skl_update_d0i3c; + skl_dsp_enable_notification(skl->skl_sst, false); } pm_runtime_mark_last_busy(platform->dev); pm_runtime_put_autosuspend(platform->dev); @@ -1256,6 +1298,7 @@ int skl_platform_register(struct device *dev) struct skl *skl = ebus_to_skl(ebus); INIT_LIST_HEAD(&skl->ppl_list); + INIT_LIST_HEAD(&skl->bind_list); ret = snd_soc_register_platform(dev, &skl_platform_drv); if (ret) { @@ -1276,6 +1319,17 @@ int skl_platform_register(struct device *dev) int skl_platform_unregister(struct device *dev) { + struct hdac_ext_bus *ebus = dev_get_drvdata(dev); + struct skl *skl = ebus_to_skl(ebus); + struct skl_module_deferred_bind *modules, *tmp; + + if (!list_empty(&skl->bind_list)) { + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + list_del(&modules->node); + kfree(modules); + } + } + snd_soc_unregister_component(dev); snd_soc_unregister_platform(dev); return 0; diff --git a/sound/soc/intel/skylake/skl-sst-cldma.c b/sound/soc/intel/skylake/skl-sst-cldma.c index c9f6d87381db..d2b1d60fec02 100644 --- a/sound/soc/intel/skylake/skl-sst-cldma.c +++ b/sound/soc/intel/skylake/skl-sst-cldma.c @@ -164,7 +164,7 @@ static void skl_cldma_cleanup(struct sst_dsp *ctx) ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_bdl); } -static int skl_cldma_wait_interruptible(struct sst_dsp *ctx) +int skl_cldma_wait_interruptible(struct sst_dsp *ctx) { int ret = 0; @@ -243,9 +243,14 @@ static void skl_cldma_fill_buffer(struct sst_dsp *ctx, unsigned int size, * 2. Polling on fw register to identify if data left to transferred doesn't * fill the ring buffer. Caller takes care of polling the required status * register to identify the transfer status. + * 3. if wait flag is set, waits for DBL interrupt to copy the next chunk till + * bytes_left is 0. + * if wait flag is not set, doesn't wait for BDL interrupt. after ccopying + * the first chunk return the no of bytes_left to be copied. */ static int -skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size) +skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, + u32 total_size, bool wait) { int ret = 0; bool start = true; @@ -272,13 +277,14 @@ skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size) size = ctx->cl_dev.bufsize; skl_cldma_fill_buffer(ctx, size, curr_pos, true, start); - start = false; - ret = skl_cldma_wait_interruptible(ctx); - if (ret < 0) { - skl_cldma_stop(ctx); - return ret; + if (wait) { + start = false; + ret = skl_cldma_wait_interruptible(ctx); + if (ret < 0) { + skl_cldma_stop(ctx); + return ret; + } } - } else { skl_cldma_int_disable(ctx); @@ -298,9 +304,11 @@ skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size) } bytes_left -= size; curr_pos = curr_pos + size; + if (!wait) + return bytes_left; } - return ret; + return bytes_left; } void skl_cldma_process_intr(struct sst_dsp *ctx) diff --git a/sound/soc/intel/skylake/skl-sst-cldma.h b/sound/soc/intel/skylake/skl-sst-cldma.h index 99e4c86b6358..5b730a1a0ae4 100644 --- a/sound/soc/intel/skylake/skl-sst-cldma.h +++ b/sound/soc/intel/skylake/skl-sst-cldma.h @@ -213,7 +213,7 @@ struct skl_cl_dev_ops { void (*cl_trigger)(struct sst_dsp *ctx, bool enable); void (*cl_cleanup_controller)(struct sst_dsp *ctx); int (*cl_copy_to_dmabuf)(struct sst_dsp *ctx, - const void *bin, u32 size); + const void *bin, u32 size, bool wait); void (*cl_stop_dma)(struct sst_dsp *ctx); }; diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c index c3deefab65d6..08332723c700 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.c +++ b/sound/soc/intel/skylake/skl-sst-dsp.c @@ -355,12 +355,13 @@ int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id) ret = ctx->fw_ops.set_state_D0(ctx, core_id); if (ret < 0) { dev_err(ctx->dev, "unable to get core%d\n", core_id); - return ret; + goto out; } } skl->cores.usage_count[core_id]++; +out: dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", core_id, skl->cores.state[core_id], skl->cores.usage_count[core_id]); @@ -379,7 +380,8 @@ int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id) return -EINVAL; } - if (--skl->cores.usage_count[core_id] == 0) { + if ((--skl->cores.usage_count[core_id] == 0) && + (skl->cores.state[core_id] != SKL_DSP_RESET)) { ret = ctx->fw_ops.set_state_D3(ctx, core_id); if (ret < 0) { dev_err(ctx->dev, "unable to put core %d: %d\n", diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h index 849410d0823e..eba20d37ba8c 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.h +++ b/sound/soc/intel/skylake/skl-sst-dsp.h @@ -17,13 +17,15 @@ #define __SKL_SST_DSP_H__ #include <linux/interrupt.h> +#include <linux/uuid.h> +#include <linux/firmware.h> #include <sound/memalloc.h> #include "skl-sst-cldma.h" -#include "skl-topology.h" struct sst_dsp; struct skl_sst; struct sst_dsp_device; +struct skl_lib_info; /* Intel HD Audio General DSP Registers */ #define SKL_ADSP_GEN_BASE 0x0 @@ -144,7 +146,7 @@ struct skl_dsp_fw_ops { int (*load_fw)(struct sst_dsp *ctx); /* FW module parser/loader */ int (*load_library)(struct sst_dsp *ctx, - struct skl_lib_info *linfo, int count); + struct skl_lib_info *linfo, int lib_count); int (*parse_fw)(struct sst_dsp *ctx); int (*set_state_D0)(struct sst_dsp *ctx, unsigned int core_id); int (*set_state_D3)(struct sst_dsp *ctx, unsigned int core_id); @@ -172,6 +174,19 @@ struct skl_dsp_loader_ops { int stream_tag); }; +#define MAX_INSTANCE_BUFF 2 + +struct uuid_module { + uuid_le uuid; + int id; + int is_loadable; + int max_instance; + u64 pvt_id[MAX_INSTANCE_BUFF]; + int *instance_id; + + struct list_head list; +}; + struct skl_load_module_info { u16 mod_id; const struct firmware *fw; @@ -186,6 +201,7 @@ struct skl_module_table { void skl_cldma_process_intr(struct sst_dsp *ctx); void skl_cldma_int_disable(struct sst_dsp *ctx); int skl_cldma_prepare(struct sst_dsp *ctx); +int skl_cldma_wait_interruptible(struct sst_dsp *ctx); void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state); struct sst_dsp *skl_dsp_ctx_init(struct device *dev, @@ -214,6 +230,9 @@ int skl_dsp_boot(struct sst_dsp *ctx); int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp); +int kbl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp); int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp); @@ -222,17 +241,22 @@ int bxt_sst_init_fw(struct device *dev, struct skl_sst *ctx); void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); -int snd_skl_get_module_info(struct skl_sst *ctx, - struct skl_module_cfg *mconfig); int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw, unsigned int offset, int index); -int skl_get_pvt_id(struct skl_sst *ctx, - struct skl_module_cfg *mconfig); -int skl_put_pvt_id(struct skl_sst *ctx, - struct skl_module_cfg *mconfig); +int skl_get_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int instance_id); +int skl_put_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int *pvt_id); int skl_get_pvt_instance_id_map(struct skl_sst *ctx, int module_id, int instance_id); void skl_freeup_uuid_list(struct skl_sst *ctx); int skl_dsp_strip_extended_manifest(struct firmware *fw); +void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable); +int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name, + struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp, + struct sst_dsp_device *skl_dev); +int skl_prepare_lib_load(struct skl_sst *skl, struct skl_lib_info *linfo, + struct firmware *stripped_fw, + unsigned int hdr_offset, int index); +void skl_release_library(struct skl_lib_info *linfo, int lib_count); + #endif /*__SKL_SST_DSP_H__*/ diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c index e1391dfbc9e9..58c525096a7c 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.c +++ b/sound/soc/intel/skylake/skl-sst-ipc.c @@ -34,6 +34,11 @@ #define IPC_GLB_REPLY_STATUS_MASK ((0x1 << IPC_GLB_REPLY_STATUS_SHIFT) - 1) #define IPC_GLB_REPLY_STATUS(x) ((x) << IPC_GLB_REPLY_STATUS_SHIFT) +#define IPC_GLB_REPLY_TYPE_SHIFT 29 +#define IPC_GLB_REPLY_TYPE_MASK 0x1F +#define IPC_GLB_REPLY_TYPE(x) (((x) >> IPC_GLB_REPLY_TYPE_SHIFT) \ + & IPC_GLB_RPLY_TYPE_MASK) + #define IPC_TIMEOUT_MSECS 3000 #define IPC_EMPTY_LIST_SIZE 8 @@ -387,12 +392,27 @@ static int skl_ipc_process_notification(struct sst_generic_ipc *ipc, return 0; } +static int skl_ipc_set_reply_error_code(u32 reply) +{ + switch (reply) { + case IPC_GLB_REPLY_OUT_OF_MEMORY: + return -ENOMEM; + + case IPC_GLB_REPLY_BUSY: + return -EBUSY; + + default: + return -EINVAL; + } +} + static void skl_ipc_process_reply(struct sst_generic_ipc *ipc, struct skl_ipc_header header) { struct ipc_message *msg; u32 reply = header.primary & IPC_GLB_REPLY_STATUS_MASK; u64 *ipc_header = (u64 *)(&header); + struct skl_sst *skl = container_of(ipc, struct skl_sst, ipc); msg = skl_ipc_reply_get_msg(ipc, *ipc_header); if (msg == NULL) { @@ -401,33 +421,39 @@ static void skl_ipc_process_reply(struct sst_generic_ipc *ipc, } /* first process the header */ - switch (reply) { - case IPC_GLB_REPLY_SUCCESS: + if (reply == IPC_GLB_REPLY_SUCCESS) { dev_dbg(ipc->dev, "ipc FW reply %x: success\n", header.primary); /* copy the rx data from the mailbox */ sst_dsp_inbox_read(ipc->dsp, msg->rx_data, msg->rx_size); - break; - - case IPC_GLB_REPLY_OUT_OF_MEMORY: - dev_err(ipc->dev, "ipc fw reply: %x: no memory\n", header.primary); - msg->errno = -ENOMEM; - break; - - case IPC_GLB_REPLY_BUSY: - dev_err(ipc->dev, "ipc fw reply: %x: Busy\n", header.primary); - msg->errno = -EBUSY; - break; + switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + case IPC_GLB_LOAD_MULTIPLE_MODS: + case IPC_GLB_LOAD_LIBRARY: + skl->mod_load_complete = true; + skl->mod_load_status = true; + wake_up(&skl->mod_load_wait); + break; - default: - dev_err(ipc->dev, "Unknown ipc reply: 0x%x\n", reply); - msg->errno = -EINVAL; - break; - } + default: + break; - if (reply != IPC_GLB_REPLY_SUCCESS) { + } + } else { + msg->errno = skl_ipc_set_reply_error_code(reply); dev_err(ipc->dev, "ipc FW reply: reply=%d\n", reply); dev_err(ipc->dev, "FW Error Code: %u\n", ipc->dsp->fw_ops.get_fw_errcode(ipc->dsp)); + switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + case IPC_GLB_LOAD_MULTIPLE_MODS: + case IPC_GLB_LOAD_LIBRARY: + skl->mod_load_complete = true; + skl->mod_load_status = false; + wake_up(&skl->mod_load_wait); + break; + + default: + break; + + } } list_del(&msg->list); @@ -811,8 +837,8 @@ int skl_ipc_load_modules(struct sst_generic_ipc *ipc, header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_MULTIPLE_MODS); header.primary |= IPC_LOAD_MODULE_CNT(module_cnt); - ret = sst_ipc_tx_message_wait(ipc, *ipc_header, data, - (sizeof(u16) * module_cnt), NULL, 0); + ret = sst_ipc_tx_message_nowait(ipc, *ipc_header, data, + (sizeof(u16) * module_cnt)); if (ret < 0) dev_err(ipc->dev, "ipc: load modules failed :%d\n", ret); @@ -947,7 +973,7 @@ int skl_ipc_get_large_config(struct sst_generic_ipc *ipc, EXPORT_SYMBOL_GPL(skl_ipc_get_large_config); int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, - u8 dma_id, u8 table_id) + u8 dma_id, u8 table_id, bool wait) { struct skl_ipc_header header = {0}; u64 *ipc_header = (u64 *)(&header); @@ -959,7 +985,11 @@ int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, header.primary |= IPC_MOD_INSTANCE_ID(table_id); header.primary |= IPC_MOD_ID(dma_id); - ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (wait) + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, + NULL, 0, NULL, 0); + else + ret = sst_ipc_tx_message_nowait(ipc, *ipc_header, NULL, 0); if (ret < 0) dev_err(ipc->dev, "ipc: load lib failed\n"); diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h index 9660ace379ab..e057da2713c6 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.h +++ b/sound/soc/intel/skylake/skl-sst-ipc.h @@ -69,6 +69,14 @@ struct skl_d0i3_data { struct delayed_work work; }; +#define SKL_LIB_NAME_LENGTH 128 +#define SKL_MAX_LIB 16 + +struct skl_lib_info { + char name[SKL_LIB_NAME_LENGTH]; + const struct firmware *fw; +}; + struct skl_sst { struct device *dev; struct sst_dsp *dsp; @@ -77,6 +85,11 @@ struct skl_sst { wait_queue_head_t boot_wait; bool boot_complete; + /* module load */ + wait_queue_head_t mod_load_wait; + bool mod_load_complete; + bool mod_load_status; + /* IPC messaging */ struct sst_generic_ipc ipc; @@ -105,6 +118,8 @@ struct skl_sst { void (*update_d0i3c)(struct device *dev, bool enable); struct skl_d0i3_data d0i3; + + const struct skl_dsp_ops *dsp_ops; }; struct skl_ipc_init_instance_msg { @@ -182,7 +197,7 @@ int skl_ipc_get_large_config(struct sst_generic_ipc *ipc, struct skl_ipc_large_config_msg *msg, u32 *param); int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, - u8 dma_id, u8 table_id); + u8 dma_id, u8 table_id, bool wait); int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc, struct skl_ipc_d0ix_msg *msg); diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c index ea162fbf68e5..81ee251881b4 100644 --- a/sound/soc/intel/skylake/skl-sst-utils.c +++ b/sound/soc/intel/skylake/skl-sst-utils.c @@ -94,19 +94,6 @@ struct adsp_fw_hdr { u32 load_offset; } __packed; -#define MAX_INSTANCE_BUFF 2 - -struct uuid_module { - uuid_le uuid; - int id; - int is_loadable; - int max_instance; - u64 pvt_id[MAX_INSTANCE_BUFF]; - int *instance_id; - - struct list_head list; -}; - struct skl_ext_manifest_hdr { u32 id; u32 len; @@ -115,32 +102,6 @@ struct skl_ext_manifest_hdr { u32 entries; }; -int snd_skl_get_module_info(struct skl_sst *ctx, - struct skl_module_cfg *mconfig) -{ - struct uuid_module *module; - uuid_le *uuid_mod; - - uuid_mod = (uuid_le *)mconfig->guid; - - if (list_empty(&ctx->uuid_list)) { - dev_err(ctx->dev, "Module list is empty\n"); - return -EINVAL; - } - - list_for_each_entry(module, &ctx->uuid_list, list) { - if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { - mconfig->id.module_id = module->id; - mconfig->is_loadable = module->is_loadable; - - return 0; - } - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(snd_skl_get_module_info); - static int skl_get_pvtid_map(struct uuid_module *module, int instance_id) { int pvt_id; @@ -222,21 +183,18 @@ static inline int skl_pvtid_128(struct uuid_module *module) * This generates a 128 bit private unique id for a module TYPE so that * module instance is unique */ -int skl_get_pvt_id(struct skl_sst *ctx, struct skl_module_cfg *mconfig) +int skl_get_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int instance_id) { struct uuid_module *module; - uuid_le *uuid_mod; int pvt_id; - uuid_mod = (uuid_le *)mconfig->guid; - list_for_each_entry(module, &ctx->uuid_list, list) { if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { pvt_id = skl_pvtid_128(module); if (pvt_id >= 0) { - module->instance_id[pvt_id] = - mconfig->id.instance_id; + module->instance_id[pvt_id] = instance_id; + return pvt_id; } } @@ -254,23 +212,21 @@ EXPORT_SYMBOL_GPL(skl_get_pvt_id); * * This frees a 128 bit private unique id previously generated */ -int skl_put_pvt_id(struct skl_sst *ctx, struct skl_module_cfg *mconfig) +int skl_put_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int *pvt_id) { int i; - uuid_le *uuid_mod; struct uuid_module *module; - uuid_mod = (uuid_le *)mconfig->guid; list_for_each_entry(module, &ctx->uuid_list, list) { if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { - if (mconfig->id.pvt_id != 0) - i = (mconfig->id.pvt_id) / 64; + if (*pvt_id != 0) + i = (*pvt_id) / 64; else i = 0; - module->pvt_id[i] &= ~(1 << (mconfig->id.pvt_id)); - mconfig->id.pvt_id = -1; + module->pvt_id[i] &= ~(1 << (*pvt_id)); + *pvt_id = -1; return 0; } } @@ -405,3 +361,83 @@ int skl_dsp_strip_extended_manifest(struct firmware *fw) return 0; } + +int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name, + struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp, + struct sst_dsp_device *skl_dev) +{ + struct skl_sst *skl; + struct sst_dsp *sst; + int ret; + + skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); + if (skl == NULL) + return -ENOMEM; + + skl->dev = dev; + skl_dev->thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); + skl->dsp = skl_dsp_ctx_init(dev, skl_dev, irq); + if (!skl->dsp) { + dev_err(skl->dev, "%s: no device\n", __func__); + return -ENODEV; + } + + sst = skl->dsp; + sst->fw_name = fw_name; + sst->dsp_ops = dsp_ops; + init_waitqueue_head(&skl->mod_load_wait); + INIT_LIST_HEAD(&sst->module_list); + ret = skl_ipc_init(dev, skl); + if (ret) + return ret; + + skl->is_first_boot = true; + if (dsp) + *dsp = skl; + + return ret; +} + +int skl_prepare_lib_load(struct skl_sst *skl, struct skl_lib_info *linfo, + struct firmware *stripped_fw, + unsigned int hdr_offset, int index) +{ + int ret; + struct sst_dsp *dsp = skl->dsp; + + if (linfo->fw == NULL) { + ret = request_firmware(&linfo->fw, linfo->name, + skl->dev); + if (ret < 0) { + dev_err(skl->dev, "Request lib %s failed:%d\n", + linfo->name, ret); + return ret; + } + } + + if (skl->is_first_boot) { + ret = snd_skl_parse_uuids(dsp, linfo->fw, hdr_offset, index); + if (ret < 0) + return ret; + } + + stripped_fw->data = linfo->fw->data; + stripped_fw->size = linfo->fw->size; + skl_dsp_strip_extended_manifest(stripped_fw); + + return 0; +} + +void skl_release_library(struct skl_lib_info *linfo, int lib_count) +{ + int i; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + if (linfo[i].fw) { + release_firmware(linfo[i].fw); + linfo[i].fw = NULL; + } + } +} diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c index b30bd384c8d3..155e456b7a3a 100644 --- a/sound/soc/intel/skylake/skl-sst.c +++ b/sound/soc/intel/skylake/skl-sst.c @@ -52,7 +52,8 @@ static int skl_transfer_firmware(struct sst_dsp *ctx, { int ret = 0; - ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size); + ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size, + true); if (ret < 0) return ret; @@ -178,6 +179,18 @@ static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) dev_err(ctx->dev, "unable to load firmware\n"); return ret; } + + /* load libs as they are also lost on D3 */ + if (skl->lib_count > 1) { + ret = ctx->fw_ops.load_library(ctx, skl->lib_info, + skl->lib_count); + if (ret < 0) { + dev_err(ctx->dev, "reload libs failed: %d\n", + ret); + return ret; + } + + } } /* @@ -203,7 +216,7 @@ static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) skl->cores.state[core_id] = SKL_DSP_RUNNING; - return ret; + return 0; } static int skl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) @@ -323,27 +336,85 @@ static struct skl_module_table *skl_module_get_from_id( return NULL; } -static int skl_transfer_module(struct sst_dsp *ctx, - struct skl_load_module_info *module) +static int skl_transfer_module(struct sst_dsp *ctx, const void *data, + u32 size, u16 mod_id, u8 table_id, bool is_module) { - int ret; + int ret, bytes_left, curr_pos; struct skl_sst *skl = ctx->thread_context; + skl->mod_load_complete = false; - ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, module->fw->data, - module->fw->size); - if (ret < 0) - return ret; + bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, data, size, false); + if (bytes_left < 0) + return bytes_left; - ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, - (void *)&module->mod_id); - if (ret < 0) - dev_err(ctx->dev, "Failed to Load module: %d\n", ret); + /* check is_module flag to load module or library */ + if (is_module) + ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, &mod_id); + else + ret = skl_sst_ipc_load_library(&skl->ipc, 0, table_id, false); + + if (ret < 0) { + dev_err(ctx->dev, "Failed to Load %s with err %d\n", + is_module ? "module" : "lib", ret); + goto out; + } + + /* + * if bytes_left > 0 then wait for BDL complete interrupt and + * copy the next chunk till bytes_left is 0. if bytes_left is + * is zero, then wait for load module IPC reply + */ + while (bytes_left > 0) { + curr_pos = size - bytes_left; + + ret = skl_cldma_wait_interruptible(ctx); + if (ret < 0) + goto out; + + bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, + data + curr_pos, + bytes_left, false); + } + ret = wait_event_timeout(skl->mod_load_wait, skl->mod_load_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0 || !skl->mod_load_status) { + dev_err(ctx->dev, "Module Load failed\n"); + ret = -EIO; + } + +out: ctx->cl_dev.ops.cl_stop_dma(ctx); return ret; } +static int +kbl_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) +{ + struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; + int ret, i; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw, + SKL_ADSP_FW_BIN_HDR_OFFSET, i); + if (ret < 0) + goto load_library_failed; + ret = skl_transfer_module(ctx, stripped_fw.data, + stripped_fw.size, 0, i, false); + if (ret < 0) + goto load_library_failed; + } + + return 0; + +load_library_failed: + skl_release_library(linfo, lib_count); + return ret; +} + static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid) { struct skl_module_table *module_entry = NULL; @@ -365,7 +436,9 @@ static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid) } if (!module_entry->usage_cnt) { - ret = skl_transfer_module(ctx, module_entry->mod_info); + ret = skl_transfer_module(ctx, module_entry->mod_info->fw->data, + module_entry->mod_info->fw->size, + mod_id, 0, true); if (ret < 0) { dev_err(ctx->dev, "Failed to Load module\n"); return ret; @@ -388,6 +461,11 @@ static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id) dev_err(ctx->dev, "Module bad usage cnt!:%d\n", usage_cnt); return -EIO; } + + /* if module is used by others return, no need to unload */ + if (usage_cnt > 0) + return 0; + ret = skl_ipc_unload_modules(&skl->ipc, SKL_NUM_MODULES, &mod_id); if (ret < 0) { @@ -434,6 +512,16 @@ static struct skl_dsp_fw_ops skl_fw_ops = { .unload_mod = skl_unload_module, }; +static struct skl_dsp_fw_ops kbl_fw_ops = { + .set_state_D0 = skl_set_dsp_D0, + .set_state_D3 = skl_set_dsp_D3, + .load_fw = skl_load_base_firmware, + .get_fw_errcode = skl_get_errorcode, + .load_library = kbl_load_library, + .load_mod = skl_load_module, + .unload_mod = skl_unload_module, +}; + static struct sst_ops skl_ops = { .irq_handler = skl_dsp_sst_interrupt, .write = sst_shim32_write, @@ -455,45 +543,47 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, struct sst_dsp *sst; int ret; - skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); - if (skl == NULL) - return -ENOMEM; - - skl->dev = dev; - skl_dev.thread_context = skl; - INIT_LIST_HEAD(&skl->uuid_list); - - skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); - if (!skl->dsp) { - dev_err(skl->dev, "%s: no device\n", __func__); - return -ENODEV; + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; } + skl = *dsp; sst = skl->dsp; - - sst->fw_name = fw_name; sst->addr.lpe = mmio_base; sst->addr.shim = mmio_base; sst_dsp_mailbox_init(sst, (SKL_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), SKL_ADSP_W0_UP_SZ, SKL_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); - INIT_LIST_HEAD(&sst->module_list); - sst->dsp_ops = dsp_ops; sst->fw_ops = skl_fw_ops; - ret = skl_ipc_init(dev, skl); - if (ret) + skl->cores.count = 2; + + return 0; +} +EXPORT_SYMBOL_GPL(skl_sst_dsp_init); + +int kbl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp) +{ + struct sst_dsp *sst; + int ret; + + ret = skl_sst_dsp_init(dev, mmio_base, irq, fw_name, dsp_ops, dsp); + if (ret < 0) { + dev_err(dev, "%s: Init failed %d\n", __func__, ret); return ret; + } - skl->cores.count = 2; - skl->is_first_boot = true; + sst = (*dsp)->dsp; + sst->fw_ops = kbl_fw_ops; - if (dsp) - *dsp = skl; + return 0; - return ret; } -EXPORT_SYMBOL_GPL(skl_sst_dsp_init); +EXPORT_SYMBOL_GPL(kbl_sst_dsp_init); int skl_sst_init_fw(struct device *dev, struct skl_sst *ctx) { @@ -507,6 +597,15 @@ int skl_sst_init_fw(struct device *dev, struct skl_sst *ctx) } skl_dsp_init_core_state(sst); + + if (ctx->lib_count > 1) { + ret = sst->fw_ops.load_library(sst, ctx->lib_info, + ctx->lib_count); + if (ret < 0) { + dev_err(dev, "Load Library failed : %x\n", ret); + return ret; + } + } ctx->is_first_boot = false; return 0; diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c index 2dbfb1b24ef4..3a99712e44a8 100644 --- a/sound/soc/intel/skylake/skl-topology.c +++ b/sound/soc/intel/skylake/skl-topology.c @@ -299,8 +299,6 @@ static void skl_tplg_update_buffer_size(struct skl_sst *ctx, { int multiplier = 1; struct skl_module_fmt *in_fmt, *out_fmt; - int in_rate, out_rate; - /* Since fixups is applied to pin 0 only, ibs, obs needs * change for pin 0 only @@ -311,22 +309,12 @@ static void skl_tplg_update_buffer_size(struct skl_sst *ctx, if (mcfg->m_type == SKL_MODULE_TYPE_SRCINT) multiplier = 5; - if (in_fmt->s_freq % 1000) - in_rate = (in_fmt->s_freq / 1000) + 1; - else - in_rate = (in_fmt->s_freq / 1000); - - mcfg->ibs = in_rate * (mcfg->in_fmt->channels) * - (mcfg->in_fmt->bit_depth >> 3) * + mcfg->ibs = DIV_ROUND_UP(in_fmt->s_freq, 1000) * + in_fmt->channels * (in_fmt->bit_depth >> 3) * multiplier; - if (mcfg->out_fmt->s_freq % 1000) - out_rate = (mcfg->out_fmt->s_freq / 1000) + 1; - else - out_rate = (mcfg->out_fmt->s_freq / 1000); - - mcfg->obs = out_rate * (mcfg->out_fmt->channels) * - (mcfg->out_fmt->bit_depth >> 3) * + mcfg->obs = DIV_ROUND_UP(out_fmt->s_freq, 1000) * + out_fmt->channels * (out_fmt->bit_depth >> 3) * multiplier; } @@ -551,6 +539,7 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) int ret = 0; list_for_each_entry(w_module, &pipe->w_list, node) { + uuid_le *uuid_mod; w = w_module->w; mconfig = w->priv; @@ -588,13 +577,15 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) * FE/BE params */ skl_tplg_update_module_params(w, ctx); - mconfig->id.pvt_id = skl_get_pvt_id(ctx, mconfig); + uuid_mod = (uuid_le *)mconfig->guid; + mconfig->id.pvt_id = skl_get_pvt_id(ctx, uuid_mod, + mconfig->id.instance_id); if (mconfig->id.pvt_id < 0) return ret; skl_tplg_set_module_init_data(w); ret = skl_init_module(ctx, mconfig); if (ret < 0) { - skl_put_pvt_id(ctx, mconfig); + skl_put_pvt_id(ctx, uuid_mod, &mconfig->id.pvt_id); return ret; } skl_tplg_alloc_pipe_mcps(skl, mconfig); @@ -614,7 +605,9 @@ static int skl_tplg_unload_pipe_modules(struct skl_sst *ctx, struct skl_module_cfg *mconfig = NULL; list_for_each_entry(w_module, &pipe->w_list, node) { + uuid_le *uuid_mod; mconfig = w_module->w->priv; + uuid_mod = (uuid_le *)mconfig->guid; if (mconfig->is_loadable && ctx->dsp->fw_ops.unload_mod && mconfig->m_state > SKL_MODULE_UNINIT) { @@ -623,7 +616,7 @@ static int skl_tplg_unload_pipe_modules(struct skl_sst *ctx, if (ret < 0) return -EIO; } - skl_put_pvt_id(ctx, mconfig); + skl_put_pvt_id(ctx, uuid_mod, &mconfig->id.pvt_id); } /* no modules to unload in this path, so return */ @@ -645,8 +638,9 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, struct skl_module_cfg *mconfig = w->priv; struct skl_pipe_module *w_module; struct skl_pipe *s_pipe = mconfig->pipe; - struct skl_module_cfg *src_module = NULL, *dst_module; + struct skl_module_cfg *src_module = NULL, *dst_module, *module; struct skl_sst *ctx = skl->skl_sst; + struct skl_module_deferred_bind *modules; /* check resource available */ if (!skl_is_pipe_mcps_avail(skl, mconfig)) @@ -687,29 +681,48 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, src_module = dst_module; } + /* + * When the destination module is initialized, check for these modules + * in deferred bind list. If found, bind them. + */ + list_for_each_entry(w_module, &s_pipe->w_list, node) { + if (list_empty(&skl->bind_list)) + break; + + list_for_each_entry(modules, &skl->bind_list, node) { + module = w_module->w->priv; + if (modules->dst == module) + skl_bind_modules(ctx, modules->src, + modules->dst); + } + } + return 0; } -static int skl_fill_sink_instance_id(struct skl_sst *ctx, - struct skl_algo_data *alg_data) +static int skl_fill_sink_instance_id(struct skl_sst *ctx, u32 *params, + int size, struct skl_module_cfg *mcfg) { - struct skl_kpb_params *params = (struct skl_kpb_params *)alg_data->params; - struct skl_mod_inst_map *inst; int i, pvt_id; - inst = params->map; + if (mcfg->m_type == SKL_MODULE_TYPE_KPB) { + struct skl_kpb_params *kpb_params = + (struct skl_kpb_params *)params; + struct skl_mod_inst_map *inst = kpb_params->map; - for (i = 0; i < params->num_modules; i++) { - pvt_id = skl_get_pvt_instance_id_map(ctx, - inst->mod_id, inst->inst_id); - if (pvt_id < 0) - return -EINVAL; - inst->inst_id = pvt_id; - inst++; + for (i = 0; i < kpb_params->num_modules; i++) { + pvt_id = skl_get_pvt_instance_id_map(ctx, inst->mod_id, + inst->inst_id); + if (pvt_id < 0) + return -EINVAL; + + inst->inst_id = pvt_id; + inst++; + } } + return 0; } - /* * Some modules require params to be set after the module is bound to * all pins connected. @@ -726,6 +739,7 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, struct soc_bytes_ext *sb; struct skl_algo_data *bc; struct skl_specific_cfg *sp_cfg; + u32 *params; /* * check all out/in pins are in bind state. @@ -758,11 +772,18 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, bc = (struct skl_algo_data *)sb->dobj.private; if (bc->set_params == SKL_PARAM_BIND) { - if (mconfig->m_type == SKL_MODULE_TYPE_KPB) - skl_fill_sink_instance_id(ctx, bc); - ret = skl_set_module_params(ctx, - (u32 *)bc->params, bc->max, - bc->param_id, mconfig); + params = kzalloc(bc->max, GFP_KERNEL); + if (!params) + return -ENOMEM; + + memcpy(params, bc->params, bc->max); + skl_fill_sink_instance_id(ctx, params, bc->max, + mconfig); + + ret = skl_set_module_params(ctx, params, + bc->max, bc->param_id, mconfig); + kfree(params); + if (ret < 0) return ret; } @@ -772,6 +793,44 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, return 0; } + +static int skl_tplg_module_add_deferred_bind(struct skl *skl, + struct skl_module_cfg *src, struct skl_module_cfg *dst) +{ + struct skl_module_deferred_bind *m_list, *modules; + int i; + + /* only supported for module with static pin connection */ + for (i = 0; i < dst->max_in_queue; i++) { + struct skl_module_pin *pin = &dst->m_in_pin[i]; + + if (pin->is_dynamic) + continue; + + if ((pin->id.module_id == src->id.module_id) && + (pin->id.instance_id == src->id.instance_id)) { + + if (!list_empty(&skl->bind_list)) { + list_for_each_entry(modules, &skl->bind_list, node) { + if (modules->src == src && modules->dst == dst) + return 0; + } + } + + m_list = kzalloc(sizeof(*m_list), GFP_KERNEL); + if (!m_list) + return -ENOMEM; + + m_list->src = src; + m_list->dst = dst; + + list_add(&m_list->node, &skl->bind_list); + } + } + + return 0; +} + static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w, struct skl *skl, struct snd_soc_dapm_widget *src_w, @@ -806,6 +865,28 @@ static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w, sink = p->sink; sink_mconfig = sink->priv; + /* + * Modules other than PGA leaf can be connected + * directly or via switch to a module in another + * pipeline. EX: reference path + * when the path is enabled, the dst module that needs + * to be bound may not be initialized. if the module is + * not initialized, add these modules in the deferred + * bind list and when the dst module is initialised, + * bind this module to the dst_module in deferred list. + */ + if (((src_mconfig->m_state == SKL_MODULE_INIT_DONE) + && (sink_mconfig->m_state == SKL_MODULE_UNINIT))) { + + ret = skl_tplg_module_add_deferred_bind(skl, + src_mconfig, sink_mconfig); + + if (ret < 0) + return ret; + + } + + if (src_mconfig->m_state == SKL_MODULE_UNINIT || sink_mconfig->m_state == SKL_MODULE_UNINIT) continue; @@ -985,15 +1066,6 @@ static int skl_tplg_mixer_dapm_pre_pmd_event(struct snd_soc_dapm_widget *w, src_mconfig = sink_mconfig->m_in_pin[i].tgt_mcfg; if (!src_mconfig) continue; - /* - * If path_found == 1, that means pmd for source - * pipe has not occurred, source is connected to - * some other sink. so its responsibility of sink - * to unbind itself from source. - */ - ret = skl_stop_pipe(ctx, src_mconfig->pipe); - if (ret < 0) - return ret; ret = skl_unbind_modules(ctx, src_mconfig, sink_mconfig); @@ -1019,6 +1091,7 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, struct skl_module_cfg *src_module = NULL, *dst_module; struct skl_sst *ctx = skl->skl_sst; struct skl_pipe *s_pipe = mconfig->pipe; + struct skl_module_deferred_bind *modules, *tmp; if (s_pipe->state == SKL_PIPE_INVALID) return -EINVAL; @@ -1027,6 +1100,35 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, skl_tplg_free_pipe_mem(skl, mconfig); list_for_each_entry(w_module, &s_pipe->w_list, node) { + if (list_empty(&skl->bind_list)) + break; + + src_module = w_module->w->priv; + + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + /* + * When the destination module is deleted, Unbind the + * modules from deferred bind list. + */ + if (modules->dst == src_module) { + skl_unbind_modules(ctx, modules->src, + modules->dst); + } + + /* + * When the source module is deleted, remove this entry + * from the deferred bind list. + */ + if (modules->src == src_module) { + list_del(&modules->node); + modules->src = NULL; + modules->dst = NULL; + kfree(modules); + } + } + } + + list_for_each_entry(w_module, &s_pipe->w_list, node) { dst_module = w_module->w->priv; if (mconfig->m_state >= SKL_MODULE_INIT_DONE) @@ -1042,6 +1144,11 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, skl_delete_pipe(ctx, mconfig->pipe); + list_for_each_entry(w_module, &s_pipe->w_list, node) { + src_module = w_module->w->priv; + src_module->m_state = SKL_MODULE_UNINIT; + } + return skl_tplg_unload_pipe_modules(ctx, s_pipe); } @@ -1083,36 +1190,6 @@ static int skl_tplg_pga_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, } /* - * In modelling, we assume there will be ONLY one mixer in a pipeline. If - * mixer is not required then it is treated as static mixer aka vmixer with - * a hard path to source module - * So we don't need to check if source is started or not as hard path puts - * dependency on each other - */ -static int skl_tplg_vmixer_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_soc_dapm_context *dapm = w->dapm; - struct skl *skl = get_skl_ctx(dapm->dev); - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - return skl_tplg_mixer_dapm_pre_pmu_event(w, skl); - - case SND_SOC_DAPM_POST_PMU: - return skl_tplg_mixer_dapm_post_pmu_event(w, skl); - - case SND_SOC_DAPM_PRE_PMD: - return skl_tplg_mixer_dapm_pre_pmd_event(w, skl); - - case SND_SOC_DAPM_POST_PMD: - return skl_tplg_mixer_dapm_post_pmd_event(w, skl); - } - - return 0; -} - -/* * In modelling, we assume there will be ONLY one mixer in a pipeline. If a * second one is required that is created as another pipe entity. * The mixer is responsible for pipe management and represent a pipeline @@ -1252,10 +1329,12 @@ static void skl_tplg_fill_dma_id(struct skl_module_cfg *mcfg, case SKL_DEVICE_HDALINK: pipe->p_params->link_dma_id = params->link_dma_id; pipe->p_params->link_index = params->link_index; + pipe->p_params->link_bps = params->link_bps; break; case SKL_DEVICE_HDAHOST: pipe->p_params->host_dma_id = params->host_dma_id; + pipe->p_params->host_bps = params->host_bps; break; default: @@ -1578,7 +1657,7 @@ int skl_tplg_be_update_params(struct snd_soc_dai *dai, static const struct snd_soc_tplg_widget_events skl_tplg_widget_ops[] = { {SKL_MIXER_EVENT, skl_tplg_mixer_event}, - {SKL_VMIXER_EVENT, skl_tplg_vmixer_event}, + {SKL_VMIXER_EVENT, skl_tplg_mixer_event}, {SKL_PGA_EVENT, skl_tplg_pga_event}, }; @@ -1632,7 +1711,7 @@ static int skl_tplg_add_pipe(struct device *dev, list_for_each_entry(ppl, &skl->ppl_list, node) { if (ppl->pipe->ppl_id == tkn_elem->value) { mconfig->pipe = ppl->pipe; - return EEXIST; + return -EEXIST; } } @@ -1924,11 +2003,13 @@ static int skl_tplg_get_token(struct device *dev, ret = skl_tplg_add_pipe(dev, mconfig, skl, tkn_elem); - if (ret < 0) + if (ret < 0) { + if (ret == -EEXIST) { + is_pipe_exists = 1; + break; + } return is_pipe_exists; - - if (ret == EEXIST) - is_pipe_exists = 1; + } break; diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h index fefab0e99a3b..cc64d6bdb4f6 100644 --- a/sound/soc/intel/skylake/skl-topology.h +++ b/sound/soc/intel/skylake/skl-topology.h @@ -257,6 +257,8 @@ struct skl_pipe_params { snd_pcm_format_t format; int link_index; int stream; + unsigned int host_bps; + unsigned int link_bps; }; struct skl_pipe { @@ -334,17 +336,10 @@ struct skl_pipeline { struct list_head node; }; -#define SKL_LIB_NAME_LENGTH 128 -#define SKL_MAX_LIB 16 - -struct skl_lib_info { - char name[SKL_LIB_NAME_LENGTH]; - const struct firmware *fw; -}; - -struct skl_manifest { - u32 lib_count; - struct skl_lib_info lib[SKL_MAX_LIB]; +struct skl_module_deferred_bind { + struct skl_module_cfg *src; + struct skl_module_cfg *dst; + struct list_head node; }; static inline struct skl *get_skl_ctx(struct device *dev) diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c index 0c57d4eaae3a..6df3b317a476 100644 --- a/sound/soc/intel/skylake/skl.c +++ b/sound/soc/intel/skylake/skl.c @@ -512,7 +512,7 @@ static int probe_codec(struct hdac_ext_bus *ebus, int addr) struct hdac_bus *bus = ebus_to_hbus(ebus); unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; - unsigned int res; + unsigned int res = -1; mutex_lock(&bus->cmd_mutex); snd_hdac_bus_send_cmd(bus, cmd); diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index 8e2878012d53..a454f6035f3e 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -56,6 +56,7 @@ struct skl { struct skl_dsp_resource resource; struct list_head ppl_list; + struct list_head bind_list; const char *fw_name; char tplg_name[64]; diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index d7013bde6f45..5c68797f36c4 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -22,6 +22,16 @@ config SND_SOC_MT2701_CS42448 Select Y if you have such device. If unsure select "N". +config SND_SOC_MT2701_WM8960 + tristate "ASoc Audio driver for MT2701 with WM8960 codec" + depends on SND_SOC_MT2701 && I2C + select SND_SOC_WM8960 + help + This adds ASoC driver for Mediatek MT2701 boards + with the WM8960 codecs. + Select Y if you have such device. + If unsure select "N". + config SND_SOC_MT8173 tristate "ASoC support for Mediatek MT8173 chip" depends on ARCH_MEDIATEK diff --git a/sound/soc/mediatek/mt2701/Makefile b/sound/soc/mediatek/mt2701/Makefile index 31c3d04d4942..c91deb6aca21 100644 --- a/sound/soc/mediatek/mt2701/Makefile +++ b/sound/soc/mediatek/mt2701/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC_MT2701) += snd-soc-mt2701-afe.o # machine driver obj-$(CONFIG_SND_SOC_MT2701_CS42448) += mt2701-cs42448.o +obj-$(CONFIG_SND_SOC_MT2701_WM8960) += mt2701-wm8960.o diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index c7fa3e663463..bc5d4db94de6 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -604,6 +604,22 @@ static struct snd_soc_dai_ops mt2701_btmrg_ops = { static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = { /* FE DAIs: memory intefaces to CPU */ { + .name = "PCMO0", + .id = MT2701_MEMIF_DL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { .name = "PCM_multi", .id = MT2701_MEMIF_DLM, .suspend = mtk_afe_dai_suspend, diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c index 1e7e8d43fd8a..aa5b31b121e3 100644 --- a/sound/soc/mediatek/mt2701/mt2701-cs42448.c +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -129,7 +129,7 @@ static int mt2701_cs42448_fe_ops_startup(struct snd_pcm_substream *substream) return 0; } -static struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { +static const struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { .startup = mt2701_cs42448_fe_ops_startup, }; diff --git a/sound/soc/mediatek/mt2701/mt2701-wm8960.c b/sound/soc/mediatek/mt2701/mt2701-wm8960.c new file mode 100644 index 000000000000..a08ce2323bdc --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-wm8960.c @@ -0,0 +1,176 @@ +/* + * mt2701-wm8960.c -- MT2701 WM8960 ALSA SoC machine driver + * + * Copyright (c) 2017 MediaTek Inc. + * Author: Ryder Lee <ryder.lee@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <sound/soc.h> + +#include "mt2701-afe-common.h" + +static const struct snd_soc_dapm_widget mt2701_wm8960_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), +}; + +static const struct snd_kcontrol_new mt2701_wm8960_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("AMIC"), +}; + +static int mt2701_wm8960_be_ops_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 = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4; + unsigned int div_bck_over_lrck = 64; + + mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck; + + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT); + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops mt2701_wm8960_be_ops = { + .hw_params = mt2701_wm8960_be_ops_hw_params +}; + +static struct snd_soc_dai_link mt2701_wm8960_dai_links[] = { + /* FE */ + { + .name = "wm8960-playback", + .stream_name = "wm8960-playback", + .cpu_dai_name = "PCMO0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "wm8960-capture", + .stream_name = "wm8960-capture", + .cpu_dai_name = "PCM0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* BE */ + { + .name = "wm8960-codec", + .cpu_dai_name = "I2S0", + .no_pcm = 1, + .codec_dai_name = "wm8960-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_wm8960_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt2701_wm8960_card = { + .name = "mt2701-wm8960", + .owner = THIS_MODULE, + .dai_link = mt2701_wm8960_dai_links, + .num_links = ARRAY_SIZE(mt2701_wm8960_dai_links), + .controls = mt2701_wm8960_controls, + .num_controls = ARRAY_SIZE(mt2701_wm8960_controls), + .dapm_widgets = mt2701_wm8960_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_wm8960_widgets), +}; + +static int mt2701_wm8960_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt2701_wm8960_card; + struct device_node *platform_node, *codec_node; + int ret, i; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_wm8960_dai_links[i].platform_name) + continue; + mt2701_wm8960_dai_links[i].platform_of_node = platform_node; + } + + card->dev = &pdev->dev; + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_wm8960_dai_links[i].codec_name) + continue; + mt2701_wm8960_dai_links[i].codec_of_node = codec_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt2701_wm8960_machine_dt_match[] = { + {.compatible = "mediatek,mt2701-wm8960-machine",}, + {} +}; +#endif + +static struct platform_driver mt2701_wm8960_machine = { + .driver = { + .name = "mt2701-wm8960", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = mt2701_wm8960_machine_dt_match, +#endif + }, + .probe = mt2701_wm8960_machine_probe, +}; + +module_platform_driver(mt2701_wm8960_machine); + +/* Module information */ +MODULE_DESCRIPTION("MT2701 WM8960 ALSA SoC machine driver"); +MODULE_AUTHOR("Ryder Lee <ryder.lee@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt2701 wm8960 soc card"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-max98090.c b/sound/soc/mediatek/mt8173/mt8173-max98090.c index 46c8e6ae00b4..e0c2b23ec711 100644 --- a/sound/soc/mediatek/mt8173/mt8173-max98090.c +++ b/sound/soc/mediatek/mt8173/mt8173-max98090.c @@ -67,7 +67,7 @@ static int mt8173_max98090_hw_params(struct snd_pcm_substream *substream, SND_SOC_CLOCK_IN); } -static struct snd_soc_ops mt8173_max98090_ops = { +static const struct snd_soc_ops mt8173_max98090_ops = { .hw_params = mt8173_max98090_hw_params, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c index 467f7049a288..5e383eb456a4 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c @@ -75,7 +75,7 @@ static int mt8173_rt5650_rt5514_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt8173_rt5650_rt5514_ops = { +static const struct snd_soc_ops mt8173_rt5650_rt5514_ops = { .hw_params = mt8173_rt5650_rt5514_hw_params, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c index 1b8b2a778845..fed1f15a39c2 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c @@ -79,7 +79,7 @@ static int mt8173_rt5650_rt5676_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt8173_rt5650_rt5676_ops = { +static const struct snd_soc_ops mt8173_rt5650_rt5676_ops = { .hw_params = mt8173_rt5650_rt5676_hw_params, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c index ba65f4157a7e..a78470839b65 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -105,7 +105,7 @@ static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt8173_rt5650_ops = { +static const struct snd_soc_ops mt8173_rt5650_ops = { .hw_params = mt8173_rt5650_hw_params, }; diff --git a/sound/soc/omap/am3517evm.c b/sound/soc/omap/am3517evm.c index 25a33e9d417a..d5651026ec10 100644 --- a/sound/soc/omap/am3517evm.c +++ b/sound/soc/omap/am3517evm.c @@ -49,7 +49,7 @@ static int am3517evm_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops am3517evm_ops = { +static const struct snd_soc_ops am3517evm_ops = { .hw_params = am3517evm_hw_params, }; diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index fdecb7043174..71e5f31fa306 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -124,7 +124,7 @@ static int n810_hw_params(struct snd_pcm_substream *substream, return err; } -static struct snd_soc_ops n810_ops = { +static const struct snd_soc_ops n810_ops = { .startup = n810_startup, .hw_params = n810_hw_params, .shutdown = n810_shutdown, diff --git a/sound/soc/omap/omap-abe-twl6040.c b/sound/soc/omap/omap-abe-twl6040.c index 89fe95e877db..614b18d2f631 100644 --- a/sound/soc/omap/omap-abe-twl6040.c +++ b/sound/soc/omap/omap-abe-twl6040.c @@ -70,7 +70,7 @@ static int omap_abe_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops omap_abe_ops = { +static const struct snd_soc_ops omap_abe_ops = { .hw_params = omap_abe_hw_params, }; diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c index 743131473056..a24b0dedabb9 100644 --- a/sound/soc/omap/omap-twl4030.c +++ b/sound/soc/omap/omap-twl4030.c @@ -73,7 +73,7 @@ static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, return snd_soc_runtime_set_dai_fmt(rtd, fmt); } -static struct snd_soc_ops omap_twl4030_ops = { +static const struct snd_soc_ops omap_twl4030_ops = { .hw_params = omap_twl4030_hw_params, }; diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index 732e749a1f8e..4e3de712159c 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -184,7 +184,7 @@ static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_ops omap3pandora_ops = { +static const struct snd_soc_ops omap3pandora_ops = { .hw_params = omap3pandora_hw_params, }; diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c index aa4053bf6710..e4096779ca05 100644 --- a/sound/soc/omap/osk5912.c +++ b/sound/soc/omap/osk5912.c @@ -68,7 +68,7 @@ static int osk_hw_params(struct snd_pcm_substream *substream, return err; } -static struct snd_soc_ops osk_ops = { +static const struct snd_soc_ops osk_ops = { .startup = osk_startup, .hw_params = osk_hw_params, .shutdown = osk_shutdown, diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index a76845748a10..3aeb65feaea1 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -123,7 +123,7 @@ static int rx51_hw_params(struct snd_pcm_substream *substream, SND_SOC_CLOCK_IN); } -static struct snd_soc_ops rx51_ops = { +static const struct snd_soc_ops rx51_ops = { .startup = rx51_startup, .hw_params = rx51_hw_params, }; @@ -433,10 +433,9 @@ static int rx51_soc_probe(struct platform_device *pdev) } pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (pdata == NULL) { - dev_err(card->dev, "failed to create private data\n"); + if (pdata == NULL) return -ENOMEM; - } + snd_soc_card_set_drvdata(card, pdata); pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, diff --git a/sound/soc/pxa/brownstone.c b/sound/soc/pxa/brownstone.c index b6cb9950f05d..9a3f5b799720 100644 --- a/sound/soc/pxa/brownstone.c +++ b/sound/soc/pxa/brownstone.c @@ -74,7 +74,7 @@ static int brownstone_wm8994_hw_params(struct snd_pcm_substream *substream, } /* machine stream operations */ -static struct snd_soc_ops brownstone_ops = { +static const struct snd_soc_ops brownstone_ops = { .hw_params = brownstone_wm8994_hw_params, }; diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index 311774e9ca46..054e0d65db9d 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -154,7 +154,7 @@ static int corgi_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops corgi_ops = { +static const struct snd_soc_ops corgi_ops = { .startup = corgi_startup, .hw_params = corgi_hw_params, .shutdown = corgi_shutdown, diff --git a/sound/soc/pxa/e750_wm9705.c b/sound/soc/pxa/e750_wm9705.c index fdcd94adee7c..82bcbbb1841b 100644 --- a/sound/soc/pxa/e750_wm9705.c +++ b/sound/soc/pxa/e750_wm9705.c @@ -81,7 +81,7 @@ static struct snd_soc_dai_link e750_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9705-aux", + .codec_dai_name = "wm9705-aux", .platform_name = "pxa-pcm-audio", .codec_name = "wm9705-codec", }, diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c index 2df714f70ec0..1ed8aa2348f1 100644 --- a/sound/soc/pxa/e800_wm9712.c +++ b/sound/soc/pxa/e800_wm9712.c @@ -81,7 +81,7 @@ static struct snd_soc_dai_link e800_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9712-aux", + .codec_dai_name = "wm9712-aux", .platform_name = "pxa-pcm-audio", .codec_name = "wm9712-codec", }, diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c index 6f2020f6c8d3..e046770ce70e 100644 --- a/sound/soc/pxa/em-x270.c +++ b/sound/soc/pxa/em-x270.c @@ -43,7 +43,7 @@ static struct snd_soc_dai_link em_x270_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9712-aux", + .codec_dai_name = "wm9712-aux", .platform_name = "pxa-pcm-audio", .codec_name = "wm9712-codec", }, diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c index 85483049b916..a9ac881c2e14 100644 --- a/sound/soc/pxa/hx4700.c +++ b/sound/soc/pxa/hx4700.c @@ -79,7 +79,7 @@ static int hx4700_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops hx4700_ops = { +static const struct snd_soc_ops hx4700_ops = { .hw_params = hx4700_hw_params, }; diff --git a/sound/soc/pxa/imote2.c b/sound/soc/pxa/imote2.c index 9d0e40771ef5..78475376f971 100644 --- a/sound/soc/pxa/imote2.c +++ b/sound/soc/pxa/imote2.c @@ -42,7 +42,7 @@ static int imote2_asoc_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops imote2_asoc_ops = { +static const struct snd_soc_ops imote2_asoc_ops = { .hw_params = imote2_asoc_hw_params, }; diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index 2d4d4455fe87..2fc012b06c43 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -255,12 +255,12 @@ static int magician_capture_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops magician_capture_ops = { +static const struct snd_soc_ops magician_capture_ops = { .startup = magician_startup, .hw_params = magician_capture_hw_params, }; -static struct snd_soc_ops magician_playback_ops = { +static const struct snd_soc_ops magician_playback_ops = { .startup = magician_startup, .hw_params = magician_playback_hw_params, }; diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c index 8760a6687885..c4c6fbedc723 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -157,7 +157,7 @@ static struct snd_soc_dai_link mioa701_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9713-aux", + .codec_dai_name = "wm9713-aux", .codec_name = "wm9713-codec", .platform_name = "pxa-pcm-audio", .ops = &mioa701_ops, diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c index 96df9b2d8fc4..5b5f1a442891 100644 --- a/sound/soc/pxa/mmp-pcm.c +++ b/sound/soc/pxa/mmp-pcm.c @@ -166,7 +166,6 @@ static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm) buf->area = NULL; } - return; } static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c index ca8b23f8c525..9cc35012e6e5 100644 --- a/sound/soc/pxa/mmp-sspa.c +++ b/sound/soc/pxa/mmp-sspa.c @@ -119,7 +119,6 @@ static void mmp_sspa_shutdown(struct snd_pcm_substream *substream, clk_disable(priv->sspa->clk); clk_disable(priv->sysclk); - return; } /* diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index a879aba0691f..b6693f32fc02 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -129,7 +129,7 @@ static int poodle_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops poodle_ops = { +static const struct snd_soc_ops poodle_ops = { .startup = poodle_startup, .hw_params = poodle_hw_params, .shutdown = poodle_shutdown, diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 3cad990dad2c..0291c7cb64eb 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -354,6 +354,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, if (ssp->type == PXA3xx_SSP) { u32 val; u64 tmp = 19968; + tmp *= 1000000; do_div(tmp, freq_out); val = tmp; @@ -590,13 +591,13 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, if ((pxa_ssp_get_scr(ssp) == 4) && (width == 16)) { /* This is a special case where the bitclk is 64fs - * and we're not dealing with 2*32 bits of audio - * samples. - * - * The SSP values used for that are all found out by - * trying and failing a lot; some of the registers - * needed for that mode are only available on PXA3xx. - */ + * and we're not dealing with 2*32 bits of audio + * samples. + * + * The SSP values used for that are all found out by + * trying and failing a lot; some of the registers + * needed for that mode are only available on PXA3xx. + */ if (ssp->type != PXA3xx_SSP) return -EINVAL; diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 2e2fb1838ec2..f49bf02e5ec2 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -140,9 +140,8 @@ static int pxa2xx_ac97_mic_startup(struct snd_pcm_substream *substream, { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -ENODEV; - else - snd_soc_dai_set_dma_data(cpu_dai, substream, - &pxa2xx_ac97_pcm_mic_mono_in); + snd_soc_dai_set_dma_data(cpu_dai, substream, + &pxa2xx_ac97_pcm_mic_mono_in); return 0; } diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 0389cf7b4b1e..3fb60baf6eab 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -46,10 +46,10 @@ #define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */ #define SACR0_EFWR (1 << 4) /* Enable EFWR Function */ #define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */ -#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */ +#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */ #define SACR0_ENB (1 << 0) /* Enable I2S Link */ #define SACR1_ENLBF (1 << 5) /* Enable Loopback */ -#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */ +#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */ #define SACR1_DREC (1 << 3) /* Disable Recording Function */ #define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */ @@ -60,7 +60,7 @@ #define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */ #define SASR0_BSY (1 << 2) /* I2S Busy */ #define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */ -#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */ +#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */ #define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */ #define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */ @@ -119,7 +119,7 @@ static int pxa_i2s_wait(void) int i; /* flush the Rx FIFO */ - for(i = 0; i < 16; i++) + for (i = 0; i < 16; i++) SADR; return 0; } diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 410d48b93031..b51d7a0755d5 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -85,7 +85,7 @@ static int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd) } static struct snd_soc_platform_driver pxa2xx_soc_platform = { - .ops = &pxa2xx_pcm_ops, + .ops = &pxa2xx_pcm_ops, .pcm_new = pxa2xx_soc_pcm_new, .pcm_free = pxa2xx_pcm_free_dma_buffers, }; diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c index 552b763005ed..111a907c4eb9 100644 --- a/sound/soc/pxa/raumfeld.c +++ b/sound/soc/pxa/raumfeld.c @@ -132,7 +132,7 @@ static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops raumfeld_cs4270_ops = { +static const struct snd_soc_ops raumfeld_cs4270_ops = { .startup = raumfeld_cs4270_startup, .shutdown = raumfeld_cs4270_shutdown, .hw_params = raumfeld_cs4270_hw_params, @@ -228,14 +228,12 @@ static struct snd_soc_ops raumfeld_ak4104_ops = { .codec_name = "spi0.0", \ } -static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = -{ +static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = { DAI_LINK_CS4270, DAI_LINK_AK4104, }; -static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = -{ +static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = { DAI_LINK_CS4270, }; diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index 07d77cddac60..1671da648e95 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -156,7 +156,7 @@ static int spitz_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops spitz_ops = { +static const struct snd_soc_ops spitz_ops = { .startup = spitz_startup, .hw_params = spitz_hw_params, }; @@ -230,8 +230,8 @@ static const struct snd_soc_dapm_route spitz_audio_map[] = { {"Headset Jack", NULL, "ROUT1"}, /* ext speaker connected to LOUT2, ROUT2 */ - {"Ext Spk", NULL , "ROUT2"}, - {"Ext Spk", NULL , "LOUT2"}, + {"Ext Spk", NULL, "ROUT2"}, + {"Ext Spk", NULL, "LOUT2"}, /* mic is connected to input 1 - with bias */ {"LINPUT1", NULL, "Mic Bias"}, diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c index e022b2a777f6..ae9c12e1ea2a 100644 --- a/sound/soc/pxa/tosa.c +++ b/sound/soc/pxa/tosa.c @@ -85,7 +85,7 @@ static int tosa_startup(struct snd_pcm_substream *substream) return 0; } -static struct snd_soc_ops tosa_ops = { +static const struct snd_soc_ops tosa_ops = { .startup = tosa_startup, }; @@ -133,7 +133,7 @@ static int tosa_set_spk(struct snd_kcontrol *kcontrol, static int tosa_hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0); + gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0); return 0; } diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c index 990b1aa6d7f6..5b0eccd2b4dd 100644 --- a/sound/soc/pxa/z2.c +++ b/sound/soc/pxa/z2.c @@ -119,8 +119,8 @@ static const struct snd_soc_dapm_route z2_audio_map[] = { {"Headphone Jack", NULL, "ROUT1"}, /* ext speaker connected to LOUT2, ROUT2 */ - {"Ext Spk", NULL , "ROUT2"}, - {"Ext Spk", NULL , "LOUT2"}, + {"Ext Spk", NULL, "ROUT2"}, + {"Ext Spk", NULL, "LOUT2"}, /* mic is connected to R input 2 - with bias */ {"RINPUT2", NULL, "Mic Bias"}, @@ -152,7 +152,7 @@ err: return ret; } -static struct snd_soc_ops z2_ops = { +static const struct snd_soc_ops z2_ops = { .hw_params = z2_hw_params, }; diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c index 6fbcdf02c88d..ba468e560dd2 100644 --- a/sound/soc/pxa/zylonite.c +++ b/sound/soc/pxa/zylonite.c @@ -132,7 +132,7 @@ static int zylonite_voice_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops zylonite_voice_ops = { +static const struct snd_soc_ops zylonite_voice_ops = { .hw_params = zylonite_voice_hw_params, }; diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c index 8aed72be3224..8a74844d99e2 100644 --- a/sound/soc/qcom/lpass-apq8016.c +++ b/sound/soc/qcom/lpass-apq8016.c @@ -231,6 +231,18 @@ static struct lpass_variant apq8016_data = { .wrdma_channels = 2, .dai_driver = apq8016_lpass_cpu_dai_driver, .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver), + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk0", + "mi2s-osr-clk1", + "mi2s-osr-clk2", + "mi2s-osr-clk3", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk0", + "mi2s-bit-clk1", + "mi2s-bit-clk2", + "mi2s-bit-clk3", + }, .init = apq8016_lpass_init, .exit = apq8016_lpass_exit, .alloc_dma_channel = apq8016_lpass_alloc_dma_channel, diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 5202a584e0c6..292b103abada 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -429,7 +429,6 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) struct lpass_variant *variant; struct device *dev = &pdev->dev; const struct of_device_id *match; - char clk_name[16]; int ret, i, dai_id; dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0); @@ -477,31 +476,24 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) for (i = 0; i < variant->num_dai; i++) { dai_id = variant->dai_driver[i].id; - if (variant->num_dai > 1) - sprintf(clk_name, "mi2s-osr-clk%d", i); - else - sprintf(clk_name, "mi2s-osr-clk"); - drvdata->mi2s_osr_clk[dai_id] = devm_clk_get(&pdev->dev, - clk_name); + variant->dai_osr_clk_names[i]); if (IS_ERR(drvdata->mi2s_osr_clk[dai_id])) { dev_warn(&pdev->dev, - "error getting optional mi2s-osr-clk: %ld\n", + "%s() error getting optional %s: %ld\n", + __func__, + variant->dai_osr_clk_names[i], PTR_ERR(drvdata->mi2s_osr_clk[dai_id])); drvdata->mi2s_osr_clk[dai_id] = NULL; } - if (variant->num_dai > 1) - sprintf(clk_name, "mi2s-bit-clk%d", i); - else - sprintf(clk_name, "mi2s-bit-clk"); - drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(&pdev->dev, - clk_name); + variant->dai_bit_clk_names[i]); if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) { dev_err(&pdev->dev, - "error getting mi2s-bit-clk: %ld\n", + "error getting %s: %ld\n", + variant->dai_bit_clk_names[i], PTR_ERR(drvdata->mi2s_bit_clk[dai_id])); return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]); } diff --git a/sound/soc/qcom/lpass-ipq806x.c b/sound/soc/qcom/lpass-ipq806x.c index 608c1a92af8a..ca1e1f2d2787 100644 --- a/sound/soc/qcom/lpass-ipq806x.c +++ b/sound/soc/qcom/lpass-ipq806x.c @@ -92,6 +92,12 @@ static struct lpass_variant ipq806x_data = { .wrdma_channels = 4, .dai_driver = &ipq806x_lpass_cpu_dai_driver, .num_dai = 1, + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk", + }, .alloc_dma_channel = ipq806x_lpass_alloc_dma_channel, .free_dma_channel = ipq806x_lpass_free_dma_channel, }; diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index 9b031352ea3c..b848db2d6c3d 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -91,6 +91,8 @@ struct lpass_variant { /* SOC specific dais */ struct snd_soc_dai_driver *dai_driver; int num_dai; + const char * const *dai_osr_clk_names; + const char * const *dai_bit_clk_names; }; /* register the platform driver from the CPU DAI driver */ diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c index b60abf322ce1..dbc53e48c52c 100644 --- a/sound/soc/rockchip/rk3288_hdmi_analog.c +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -93,6 +93,9 @@ static int rk_hw_params(struct snd_pcm_substream *substream, case 96000: mclk = 12288000; break; + case 192000: + mclk = 24576000; + break; case 11025: case 22050: case 44100: diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index f1f1d7959a1b..0520f5afd7cc 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -185,6 +185,14 @@ config SND_SOC_SNOW Say Y if you want to add audio support for various Snow boards based on Exynos5 series of SoCs. +config SND_SOC_ODROID + tristate "Audio support for Odroid XU3/XU4" + depends on SND_SOC_SAMSUNG && I2C + select SND_SOC_MAX98090 + select SND_SAMSUNG_I2S + help + Say Y here to enable audio support for the Odroid XU3/XU4. + config SND_SOC_ARNDALE_RT5631_ALC5631 tristate "Audio support for RT5631(ALC5631) on Arndale Board" depends on I2C diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index b5df5e2e3d94..b6c2ee358333 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -40,6 +40,7 @@ snd-soc-tobermory-objs := tobermory.o snd-soc-lowland-objs := lowland.o snd-soc-littlemill-objs := littlemill.o snd-soc-bells-objs := bells.o +snd-soc-odroid-objs := odroid.o snd-soc-arndale-rt5631-objs := arndale_rt5631.o snd-soc-tm2-wm5110-objs := tm2_wm5110.o @@ -62,5 +63,6 @@ obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o +obj-$(CONFIG_SND_SOC_ODROID) += snd-soc-odroid.o obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o diff --git a/sound/soc/samsung/bells.c b/sound/soc/samsung/bells.c index 3dd246fa0059..34deba461ae1 100644 --- a/sound/soc/samsung/bells.c +++ b/sound/soc/samsung/bells.c @@ -446,7 +446,6 @@ static struct snd_soc_card bells_cards[] = { }, }; - static int bells_probe(struct platform_device *pdev) { int ret; diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h index 9170c311d66e..fe6914005494 100644 --- a/sound/soc/samsung/i2s-regs.h +++ b/sound/soc/samsung/i2s-regs.h @@ -160,5 +160,3 @@ #define I2SSIZE_SHIFT (16) #endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */ - - diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 52a47ed292a4..af3ba4d4ccc5 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1242,7 +1242,6 @@ static int samsung_i2s_probe(struct platform_device *pdev) i2s_dai_data = (struct samsung_i2s_dai_data *) platform_get_device_id(pdev)->driver_data; - pri_dai = i2s_alloc_dai(pdev, false); if (!pri_dai) { dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c new file mode 100644 index 000000000000..0c0b00e40646 --- /dev/null +++ b/sound/soc/samsung/odroid.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "i2s.h" +#include "i2s-regs.h" + +struct odroid_priv { + struct snd_soc_card card; + struct snd_soc_dai_link dai_link; + + struct clk *pll; + struct clk *rclk; +}; + +static int odroid_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); + return 0; +} + +static int odroid_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card); + unsigned int pll_freq, rclk_freq; + int ret; + + switch (params_rate(params)) { + case 32000: + case 64000: + pll_freq = 131072000U; + break; + case 44100: + case 88200: + case 176400: + pll_freq = 180633600U; + break; + case 48000: + case 96000: + case 192000: + pll_freq = 196608000U; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(priv->pll, pll_freq + 1); + if (ret < 0) + return ret; + + rclk_freq = params_rate(params) * 256 * 4; + + ret = clk_set_rate(priv->rclk, rclk_freq); + if (ret < 0) + return ret; + + if (rtd->num_codecs > 1) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[1]; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct snd_soc_ops odroid_card_ops = { + .startup = odroid_card_startup, + .hw_params = odroid_card_hw_params, +}; + +static void odroid_put_codec_of_nodes(struct snd_soc_dai_link *link) +{ + struct snd_soc_dai_link_component *component = link->codecs; + int i; + + for (i = 0; i < link->num_codecs; i++, component++) { + if (!component->of_node) + break; + of_node_put(component->of_node); + } +} + +static int odroid_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *cpu, *codec; + struct odroid_priv *priv; + struct snd_soc_dai_link *link; + struct snd_soc_card *card; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card = &priv->card; + card->dev = dev; + + card->owner = THIS_MODULE; + card->fully_routed = true; + + snd_soc_card_set_drvdata(card, priv); + + priv->pll = devm_clk_get(dev, "epll"); + if (IS_ERR(priv->pll)) + return PTR_ERR(priv->pll); + + priv->rclk = devm_clk_get(dev, "i2s_rclk"); + if (IS_ERR(priv->rclk)) + return PTR_ERR(priv->rclk); + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret < 0) + return ret; + + if (of_property_read_bool(dev->of_node, "samsung,audio-widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets(card, + "samsung,audio-widgets"); + if (ret < 0) + return ret; + } + + if (of_property_read_bool(dev->of_node, "samsung,audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, + "samsung,audio-routing"); + if (ret < 0) + return ret; + } + + link = &priv->dai_link; + + link->ops = &odroid_card_ops; + link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + card->dai_link = &priv->dai_link; + card->num_links = 1; + + cpu = of_get_child_by_name(dev->of_node, "cpu"); + codec = of_get_child_by_name(dev->of_node, "codec"); + + link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!link->cpu_of_node) { + dev_err(dev, "Failed parsing cpu/sound-dai property\n"); + return -EINVAL; + } + + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) + goto err_put_codec_n; + + link->platform_of_node = link->cpu_of_node; + + link->name = "Primary"; + link->stream_name = link->name; + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + dev_err(dev, "snd_soc_register_card() failed: %d\n", ret); + goto err_put_i2s_n; + } + + return 0; + +err_put_i2s_n: + of_node_put(link->cpu_of_node); +err_put_codec_n: + odroid_put_codec_of_nodes(link); + return ret; +} + +static int odroid_audio_remove(struct platform_device *pdev) +{ + struct odroid_priv *priv = platform_get_drvdata(pdev); + + of_node_put(priv->dai_link.cpu_of_node); + odroid_put_codec_of_nodes(&priv->dai_link); + + return 0; +} + +static const struct of_device_id odroid_audio_of_match[] = { + { .compatible = "samsung,odroid-xu3-audio" }, + { .compatible = "samsung,odroid-xu4-audio"}, + { }, +}; +MODULE_DEVICE_TABLE(of, odroid_audio_of_match); + +static struct platform_driver odroid_audio_driver = { + .driver = { + .name = "odroid-audio", + .of_match_table = odroid_audio_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = odroid_audio_probe, + .remove = odroid_audio_remove, +}; +module_platform_driver(odroid_audio_driver); + +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_DESCRIPTION("Odroid XU3/XU4 audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index 644f186fd35c..8f42deaa184b 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -72,7 +72,6 @@ static inline void dbg_showcon(const char *fn, u32 con) } #endif - /* Turn on or off the transmission path. */ static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) { diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index 85a33ac0a5c4..66203d107a11 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -43,6 +43,7 @@ struct rsnd_adg { }; #define LRCLK_ASYNC (1 << 0) +#define AUDIO_OUT_48 (1 << 1) #define adg_mode_flags(adg) (adg->flags) #define for_each_rsnd_clk(pos, adg, i) \ @@ -364,7 +365,10 @@ found_clock: rsnd_adg_set_ssi_clk(ssi_mod, data); - if (!(adg_mode_flags(adg) & LRCLK_ASYNC)) { + if (adg_mode_flags(adg) & LRCLK_ASYNC) { + if (adg_mode_flags(adg) & AUDIO_OUT_48) + ckr = 0x80000000; + } else { if (0 == (rate % 8000)) ckr = 0x80000000; } @@ -427,11 +431,14 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, struct clk *clk; struct device *dev = rsnd_priv_to_dev(priv); struct device_node *np = dev->of_node; + struct property *prop; u32 ckr, rbgx, rbga, rbgb; - u32 rate, req_rate = 0, div; + u32 rate, div; +#define REQ_SIZE 2 + u32 req_rate[REQ_SIZE] = {}; uint32_t count = 0; unsigned long req_48kHz_rate, req_441kHz_rate; - int i; + int i, req_size; const char *parent_clk_name = NULL; static const char * const clkout_name[] = { [CLKOUT] = "audio_clkout", @@ -446,19 +453,32 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, [CLKI] = 0x2, }; - of_property_read_u32(np, "#clock-cells", &count); + ckr = 0; + rbga = 2; /* default 1/6 */ + rbgb = 2; /* default 1/6 */ /* * ADG supports BRRA/BRRB output only * this means all clkout0/1/2/3 will be same rate */ - of_property_read_u32(np, "clock-frequency", &req_rate); + prop = of_find_property(np, "clock-frequency", NULL); + if (!prop) + goto rsnd_adg_get_clkout_end; + + req_size = prop->length / sizeof(u32); + + of_property_read_u32_array(np, "clock-frequency", req_rate, req_size); req_48kHz_rate = 0; req_441kHz_rate = 0; - if (0 == (req_rate % 44100)) - req_441kHz_rate = req_rate; - if (0 == (req_rate % 48000)) - req_48kHz_rate = req_rate; + for (i = 0; i < req_size; i++) { + if (0 == (req_rate[i] % 44100)) + req_441kHz_rate = req_rate[i]; + if (0 == (req_rate[i] % 48000)) + req_48kHz_rate = req_rate[i]; + } + + if (req_rate[0] % 48000 == 0) + adg->flags = AUDIO_OUT_48; /* * This driver is assuming that AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC @@ -469,9 +489,6 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, * rsnd_adg_ssi_clk_try_start() * rsnd_ssi_master_clk_start() */ - ckr = 0; - rbga = 2; /* default 1/6 */ - rbgb = 2; /* default 1/6 */ adg->rbga_rate_for_441khz = 0; adg->rbgb_rate_for_48khz = 0; for_each_rsnd_clk(clk, adg, i) { @@ -505,10 +522,8 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, rbgb = rbgx; adg->rbgb_rate_for_48khz = rate / div; ckr |= brg_table[i] << 16; - if (req_48kHz_rate) { + if (req_48kHz_rate) parent_clk_name = __clk_get_name(clk); - ckr |= 0x80000000; - } } } } @@ -518,12 +533,13 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, * this means all clkout0/1/2/3 will be * same rate */ + of_property_read_u32(np, "#clock-cells", &count); /* * for clkout */ if (!count) { clk = clk_register_fixed_rate(dev, clkout_name[CLKOUT], - parent_clk_name, 0, req_rate); + parent_clk_name, 0, req_rate[0]); if (!IS_ERR(clk)) { adg->clkout[CLKOUT] = clk; of_clk_add_provider(np, of_clk_src_simple_get, clk); @@ -536,19 +552,18 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, for (i = 0; i < CLKOUTMAX; i++) { clk = clk_register_fixed_rate(dev, clkout_name[i], parent_clk_name, 0, - req_rate); - if (!IS_ERR(clk)) { - adg->onecell.clks = adg->clkout; - adg->onecell.clk_num = CLKOUTMAX; - + req_rate[0]); + adg->clkout[i] = ERR_PTR(-ENOENT); + if (!IS_ERR(clk)) adg->clkout[i] = clk; - - of_clk_add_provider(np, of_clk_src_onecell_get, - &adg->onecell); - } } + adg->onecell.clks = adg->clkout; + adg->onecell.clk_num = CLKOUTMAX; + of_clk_add_provider(np, of_clk_src_onecell_get, + &adg->onecell); } +rsnd_adg_get_clkout_end: adg->ckr = ckr; adg->rbga = rbga; adg->rbgb = rbgb; @@ -564,6 +579,7 @@ int rsnd_adg_probe(struct rsnd_priv *priv) struct rsnd_adg *adg; struct device *dev = rsnd_priv_to_dev(priv); struct device_node *np = dev->of_node; + int ret; adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL); if (!adg) { @@ -571,8 +587,10 @@ int rsnd_adg_probe(struct rsnd_priv *priv) return -ENOMEM; } - rsnd_mod_init(priv, &adg->mod, &adg_ops, + ret = rsnd_mod_init(priv, &adg->mod, &adg_ops, NULL, NULL, 0, 0); + if (ret) + return ret; rsnd_adg_get_clkin(priv, adg); rsnd_adg_get_clkout(priv, adg); @@ -589,5 +607,10 @@ int rsnd_adg_probe(struct rsnd_priv *priv) void rsnd_adg_remove(struct rsnd_priv *priv) { + struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; + + of_clk_del_provider(np); + rsnd_adg_clk_disable(priv); } diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 47b370cb2d3b..1744015408c3 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -96,7 +96,7 @@ #include <linux/pm_runtime.h> #include "rsnd.h" -#define RSND_RATES SNDRV_PCM_RATE_8000_96000 +#define RSND_RATES SNDRV_PCM_RATE_8000_192000 #define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) static const struct of_device_id rsnd_of_match[] = { @@ -110,7 +110,6 @@ MODULE_DEVICE_TABLE(of, rsnd_of_match); /* * rsnd_mod functions */ -#ifdef DEBUG void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type) { if (mod->type != type) { @@ -121,7 +120,6 @@ void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type) rsnd_mod_name(mod), rsnd_mod_id(mod)); } } -#endif char *rsnd_mod_name(struct rsnd_mod *mod) { @@ -674,12 +672,10 @@ static int rsnd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) /* set clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_IF: - rdai->bit_clk_inv = rdai->bit_clk_inv; rdai->frm_clk_inv = !rdai->frm_clk_inv; break; case SND_SOC_DAIFMT_IB_NF: rdai->bit_clk_inv = !rdai->bit_clk_inv; - rdai->frm_clk_inv = rdai->frm_clk_inv; break; case SND_SOC_DAIFMT_IB_IF: rdai->bit_clk_inv = !rdai->bit_clk_inv; @@ -1002,13 +998,30 @@ static int rsnd_kctrl_put(struct snd_kcontrol *kctrl, return change; } -static int __rsnd_kctrl_new(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - struct rsnd_kctrl_cfg *cfg, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod)) +struct rsnd_kctrl_cfg *rsnd_kctrl_init_m(struct rsnd_kctrl_cfg_m *cfg) +{ + cfg->cfg.val = cfg->val; + + return &cfg->cfg; +} + +struct rsnd_kctrl_cfg *rsnd_kctrl_init_s(struct rsnd_kctrl_cfg_s *cfg) +{ + cfg->cfg.val = &cfg->val; + + return &cfg->cfg; +} + +int rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod), + struct rsnd_kctrl_cfg *cfg, + const char * const *texts, + int size, + u32 max) { struct snd_card *card = rtd->card->snd_card; struct snd_kcontrol *kctrl; @@ -1023,6 +1036,9 @@ static int __rsnd_kctrl_new(struct rsnd_mod *mod, }; int ret; + if (size > RSND_MAX_CHANNELS) + return -EINVAL; + kctrl = snd_ctl_new1(&knew, mod); if (!kctrl) return -ENOMEM; @@ -1031,74 +1047,17 @@ static int __rsnd_kctrl_new(struct rsnd_mod *mod, if (ret < 0) return ret; - cfg->update = update; - cfg->card = card; - cfg->kctrl = kctrl; - cfg->io = io; + cfg->texts = texts; + cfg->max = max; + cfg->size = size; + cfg->update = update; + cfg->card = card; + cfg->kctrl = kctrl; + cfg->io = io; return 0; } -void _rsnd_kctrl_remove(struct rsnd_kctrl_cfg *cfg) -{ - if (cfg->card && cfg->kctrl) - snd_ctl_remove(cfg->card, cfg->kctrl); - - cfg->card = NULL; - cfg->kctrl = NULL; -} - -int rsnd_kctrl_new_m(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_m *_cfg, - int ch_size, - u32 max) -{ - if (ch_size > RSND_MAX_CHANNELS) - return -EINVAL; - - _cfg->cfg.max = max; - _cfg->cfg.size = ch_size; - _cfg->cfg.val = _cfg->val; - return __rsnd_kctrl_new(mod, io, rtd, name, &_cfg->cfg, update); -} - -int rsnd_kctrl_new_s(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_s *_cfg, - u32 max) -{ - _cfg->cfg.max = max; - _cfg->cfg.size = 1; - _cfg->cfg.val = &_cfg->val; - return __rsnd_kctrl_new(mod, io, rtd, name, &_cfg->cfg, update); -} - -int rsnd_kctrl_new_e(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - struct rsnd_kctrl_cfg_s *_cfg, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - const char * const *texts, - u32 max) -{ - _cfg->cfg.max = max; - _cfg->cfg.size = 1; - _cfg->cfg.val = &_cfg->val; - _cfg->cfg.texts = texts; - return __rsnd_kctrl_new(mod, io, rtd, name, &_cfg->cfg, update); -} - /* * snd_soc_platform */ diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index cf8f59cdd8d7..463de8360985 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -218,21 +218,6 @@ static int rsnd_dvc_probe_(struct rsnd_mod *mod, return rsnd_cmd_attach(io, rsnd_mod_id(mod)); } -static int rsnd_dvc_remove_(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct rsnd_priv *priv) -{ - struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); - - rsnd_kctrl_remove(dvc->volume); - rsnd_kctrl_remove(dvc->mute); - rsnd_kctrl_remove(dvc->ren); - rsnd_kctrl_remove(dvc->rup); - rsnd_kctrl_remove(dvc->rdown); - - return 0; -} - static int rsnd_dvc_init(struct rsnd_mod *mod, struct rsnd_dai_stream *io, struct rsnd_priv *priv) @@ -300,18 +285,18 @@ static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, ret = rsnd_kctrl_new_e(mod, io, rtd, is_play ? "DVC Out Ramp Up Rate" : "DVC In Ramp Up Rate", - &dvc->rup, rsnd_dvc_volume_update, - dvc_ramp_rate, ARRAY_SIZE(dvc_ramp_rate)); + &dvc->rup, + dvc_ramp_rate); if (ret < 0) return ret; ret = rsnd_kctrl_new_e(mod, io, rtd, is_play ? "DVC Out Ramp Down Rate" : "DVC In Ramp Down Rate", - &dvc->rdown, rsnd_dvc_volume_update, - dvc_ramp_rate, ARRAY_SIZE(dvc_ramp_rate)); + &dvc->rdown, + dvc_ramp_rate); if (ret < 0) return ret; @@ -332,7 +317,6 @@ static struct rsnd_mod_ops rsnd_dvc_ops = { .name = DVC_NAME, .dma_req = rsnd_dvc_dma_req, .probe = rsnd_dvc_probe_, - .remove = rsnd_dvc_remove_, .init = rsnd_dvc_init, .quit = rsnd_dvc_quit, .pcm_new = rsnd_dvc_pcm_new, diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 7410ec0174db..dbf4163427e8 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -611,35 +611,30 @@ struct rsnd_kctrl_cfg_s { u32 val; }; -void _rsnd_kctrl_remove(struct rsnd_kctrl_cfg *cfg); -#define rsnd_kctrl_remove(_cfg) _rsnd_kctrl_remove(&((_cfg).cfg)) - -int rsnd_kctrl_new_m(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_m *_cfg, - int ch_size, - u32 max); -int rsnd_kctrl_new_s(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_s *_cfg, - u32 max); -int rsnd_kctrl_new_e(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - struct rsnd_kctrl_cfg_s *_cfg, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - const char * const *texts, - u32 max); +struct rsnd_kctrl_cfg *rsnd_kctrl_init_m(struct rsnd_kctrl_cfg_m *cfg); +struct rsnd_kctrl_cfg *rsnd_kctrl_init_s(struct rsnd_kctrl_cfg_s *cfg); +int rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod), + struct rsnd_kctrl_cfg *cfg, + const char * const *texts, + int size, + u32 max); + +#define rsnd_kctrl_new_m(mod, io, rtd, name, update, cfg, size, max) \ + rsnd_kctrl_new(mod, io, rtd, name, update, rsnd_kctrl_init_m(cfg), \ + NULL, size, max) + +#define rsnd_kctrl_new_s(mod, io, rtd, name, update, cfg, max) \ + rsnd_kctrl_new(mod, io, rtd, name, update, rsnd_kctrl_init_s(cfg), \ + NULL, 1, max) + +#define rsnd_kctrl_new_e(mod, io, rtd, name, update, cfg, texts) \ + rsnd_kctrl_new(mod, io, rtd, name, update, rsnd_kctrl_init_s(cfg), \ + texts, 1, ARRAY_SIZE(texts)) /* * R-Car SSI @@ -732,8 +727,8 @@ void rsnd_cmd_remove(struct rsnd_priv *priv); int rsnd_cmd_attach(struct rsnd_dai_stream *io, int id); struct rsnd_mod *rsnd_cmd_mod_get(struct rsnd_priv *priv, int id); -#ifdef DEBUG void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type); +#ifdef DEBUG #define rsnd_mod_confirm_ssi(mssi) rsnd_mod_make_sure(mssi, RSND_MOD_SSI) #define rsnd_mod_confirm_src(msrc) rsnd_mod_make_sure(msrc, RSND_MOD_SRC) #define rsnd_mod_confirm_dvc(mdvc) rsnd_mod_make_sure(mdvc, RSND_MOD_DVC) diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 42db48db09ba..20b5b2ec625e 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -167,6 +167,7 @@ static int rsnd_src_hw_params(struct rsnd_mod *mod, * dpcm_fe_dai_hw_params() * dpcm_be_dai_hw_params() */ + src->convert_rate = 0; if (fe->dai_link->dynamic) { int stream = substream->stream; struct snd_soc_dpcm *dpcm; @@ -414,8 +415,6 @@ static int rsnd_src_quit(struct rsnd_mod *mod, rsnd_mod_power_off(mod); - src->convert_rate = 0; - /* reset sync convert_rate */ src->sync.val = 0; diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 411bda2387ad..135c5669f796 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -228,6 +228,15 @@ static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, for (j = 0; j < ARRAY_SIZE(ssi_clk_mul_table); j++) { /* + * It will set SSIWSR.CONT here, but SSICR.CKDV = 000 + * with it is not allowed. (SSIWSR.WS_MODE with + * SSICR.CKDV = 000 is not allowed either). + * Skip it. See SSICR.CKDV + */ + if (j == 0) + continue; + + /* * this driver is assuming that * system word is 32bit x chan * see rsnd_ssi_init() diff --git a/sound/soc/sirf/sirf-audio-port.c b/sound/soc/sirf/sirf-audio-port.c index 3f2cce03275c..be066de74aaa 100644 --- a/sound/soc/sirf/sirf-audio-port.c +++ b/sound/soc/sirf/sirf-audio-port.c @@ -19,6 +19,7 @@ struct sirf_audio_port { static int sirf_audio_port_dai_probe(struct snd_soc_dai *dai) { struct sirf_audio_port *port = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_init_dma_data(dai, &port->playback_dma_data, &port->capture_dma_data); return 0; diff --git a/sound/soc/sirf/sirf-audio.c b/sound/soc/sirf/sirf-audio.c index 94ea152e0362..f2bc50790f76 100644 --- a/sound/soc/sirf/sirf-audio.c +++ b/sound/soc/sirf/sirf-audio.c @@ -27,6 +27,7 @@ static int sirf_audio_hp_event(struct snd_soc_dapm_widget *w, struct snd_soc_card *card = dapm->card; struct sirf_audio_card *sirf_audio_card = snd_soc_card_get_drvdata(card); int on = !SND_SOC_DAPM_EVENT_OFF(event); + if (gpio_is_valid(sirf_audio_card->gpio_hp_pa)) gpio_set_value(sirf_audio_card->gpio_hp_pa, on); return 0; diff --git a/sound/soc/sirf/sirf-usp.c b/sound/soc/sirf/sirf-usp.c index 45fc06c0e0e5..77e7dcf969d0 100644 --- a/sound/soc/sirf/sirf-usp.c +++ b/sound/soc/sirf/sirf-usp.c @@ -71,6 +71,7 @@ static void sirf_usp_rx_disable(struct sirf_usp *usp) static int sirf_usp_pcm_dai_probe(struct snd_soc_dai *dai) { struct sirf_usp *usp = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_init_dma_data(dai, &usp->playback_dma_data, &usp->capture_dma_data); return 0; @@ -294,6 +295,7 @@ static struct snd_soc_dai_driver sirf_usp_pcm_dai = { static int sirf_usp_pcm_runtime_suspend(struct device *dev) { struct sirf_usp *usp = dev_get_drvdata(dev); + clk_disable_unprepare(usp->clk); return 0; } @@ -302,6 +304,7 @@ static int sirf_usp_pcm_runtime_resume(struct device *dev) { struct sirf_usp *usp = dev_get_drvdata(dev); int ret; + ret = clk_prepare_enable(usp->clk); if (ret) { dev_err(dev, "clk_enable failed: %d\n", ret); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 2722bb0c5573..525f2f397b4c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1776,7 +1776,6 @@ static int soc_bind_aux_dev(struct snd_soc_card *card, int num) } component->init = aux_dev->init; - component->auxiliary = 1; list_add(&component->card_aux_list, &card->aux_comp_list); return 0; @@ -1788,14 +1787,13 @@ err_defer: static int soc_probe_aux_devices(struct snd_soc_card *card) { - struct snd_soc_component *comp, *tmp; + struct snd_soc_component *comp; int order; int ret; for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { - list_for_each_entry_safe(comp, tmp, &card->aux_comp_list, - card_aux_list) { + list_for_each_entry(comp, &card->aux_comp_list, card_aux_list) { if (comp->driver->probe_order == order) { ret = soc_probe_component(card, comp); if (ret < 0) { @@ -1804,7 +1802,6 @@ static int soc_probe_aux_devices(struct snd_soc_card *card) comp->name, ret); return ret; } - list_del(&comp->card_aux_list); } } } @@ -1820,14 +1817,12 @@ static void soc_remove_aux_devices(struct snd_soc_card *card) for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { list_for_each_entry_safe(comp, _comp, - &card->component_dev_list, card_list) { - - if (!comp->auxiliary) - continue; + &card->aux_comp_list, card_aux_list) { if (comp->driver->remove_order == order) { soc_remove_component(comp); - comp->auxiliary = 0; + /* remove it from the card's aux_comp_list */ + list_del(&comp->card_aux_list); } } } @@ -1918,6 +1913,7 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); +#ifdef CONFIG_DMI /* Trim special characters, and replace '-' with '_' since '-' is used to * separate different DMI fields in the card long name. Only number and * alphabet characters and a few separator characters are kept. @@ -2049,6 +2045,7 @@ int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour) return 0; } EXPORT_SYMBOL_GPL(snd_soc_set_dmi_name); +#endif /* CONFIG_DMI */ static int snd_soc_instantiate_card(struct snd_soc_card *card) { @@ -2190,6 +2187,9 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, card->num_of_dapm_routes); + /* try to set some sane longname if DMI is available */ + snd_soc_set_dmi_name(card, NULL); + snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), "%s", card->name); snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), @@ -3139,7 +3139,7 @@ static int snd_soc_component_initialize(struct snd_soc_component *component, component->suspend = component->driver->suspend; component->resume = component->driver->resume; component->pcm_new = component->driver->pcm_new; - component->pcm_free= component->driver->pcm_free; + component->pcm_free = component->driver->pcm_free; dapm = &component->dapm; dapm->dev = dev; @@ -3240,6 +3240,11 @@ static void snd_soc_component_cleanup(struct snd_soc_component *component) static void snd_soc_component_del_unlocked(struct snd_soc_component *component) { + struct snd_soc_card *card = component->card; + + if (card) + snd_soc_unregister_card(card); + list_del(&component->list); } diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index fbaa1bb41102..7daf21fee355 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -19,9 +19,28 @@ #include <linux/workqueue.h> #include <linux/delay.h> #include <linux/export.h> +#include <linux/suspend.h> #include <trace/events/asoc.h> /** + * snd_soc_codec_set_jack - configure codec jack. + * @codec: CODEC + * @jack: structure to use for the jack + * @data: can be used if codec driver need extra data for configuring jack + * + * Configures and enables jack detection function. + */ +int snd_soc_codec_set_jack(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, void *data) +{ + if (codec->driver->set_jack) + return codec->driver->set_jack(codec, jack, data); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_set_jack); + +/** * snd_soc_card_jack_new - Create a new jack * @card: ASoC card * @id: an identifying string for this jack @@ -293,6 +312,27 @@ static void gpio_work(struct work_struct *work) snd_soc_jack_gpio_detect(gpio); } +static int snd_soc_jack_pm_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct snd_soc_jack_gpio *gpio = + container_of(nb, struct snd_soc_jack_gpio, pm_notifier); + + switch (action) { + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + /* + * Use workqueue so we do not have to care about running + * concurrently with work triggered by the interrupt handler. + */ + queue_delayed_work(system_power_efficient_wq, &gpio->work, 0); + break; + } + + return NOTIFY_DONE; +} + /** * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack * @@ -369,6 +409,13 @@ got_gpio: i, ret); } + /* + * Register PM notifier so we do not miss state transitions + * happening while system is asleep. + */ + gpios[i].pm_notifier.notifier_call = snd_soc_jack_pm_notifier; + register_pm_notifier(&gpios[i].pm_notifier); + /* Expose GPIO value over sysfs for diagnostic purposes */ gpiod_export(gpios[i].desc, false); @@ -428,6 +475,7 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, for (i = 0; i < count; i++) { gpiod_unexport(gpios[i].desc); + unregister_pm_notifier(&gpios[i].pm_notifier); free_irq(gpiod_to_irq(gpios[i].desc), &gpios[i]); cancel_delayed_work_sync(&gpios[i].work); gpiod_put(gpios[i].desc); diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 058bc99c6c34..002772e3ba2c 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -495,12 +495,13 @@ static void remove_widget(struct snd_soc_component *comp, struct snd_kcontrol *kcontrol = w->kcontrols[i]; struct soc_enum *se = (struct soc_enum *)kcontrol->private_value; + int j; snd_ctl_remove(card, kcontrol); kfree(se->dobj.control.dvalues); - for (i = 0; i < se->items; i++) - kfree(se->dobj.control.dtexts[i]); + for (j = 0; j < se->items; j++) + kfree(se->dobj.control.dtexts[j]); kfree(se); } diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index d7e8dd46d2cc..d8b6936e544e 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1074,7 +1074,7 @@ int uni_player_init(struct platform_device *pdev, player->clk = of_clk_get(pdev->dev.of_node, 0); if (IS_ERR(player->clk)) { dev_err(player->dev, "Failed to get clock\n"); - ret = PTR_ERR(player->clk); + return PTR_ERR(player->clk); } /* Select the frequency synthesizer clock */ diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig new file mode 100644 index 000000000000..972970f0890a --- /dev/null +++ b/sound/soc/stm/Kconfig @@ -0,0 +1,8 @@ +menuconfig SND_SOC_STM32 + tristate "STMicroelectronics STM32 SOC audio support" + depends on ARCH_STM32 || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to enable ASoC-support for STM32 diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile new file mode 100644 index 000000000000..e466a4759698 --- /dev/null +++ b/sound/soc/stm/Makefile @@ -0,0 +1,6 @@ +# SAI +snd-soc-stm32-sai-sub-objs := stm32_sai_sub.o +obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai-sub.o + +snd-soc-stm32-sai-objs := stm32_sai.o +obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai.o diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c new file mode 100644 index 000000000000..2a27a26bf7a1 --- /dev/null +++ b/sound/soc/stm/stm32_sai.c @@ -0,0 +1,115 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/reset.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/core.h> + +#include "stm32_sai.h" + +static const struct of_device_id stm32_sai_ids[] = { + { .compatible = "st,stm32f4-sai", .data = (void *)SAI_STM32F4 }, + {} +}; + +static int stm32_sai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct stm32_sai_data *sai; + struct reset_control *rst; + struct resource *res; + void __iomem *base; + const struct of_device_id *of_id; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + of_id = of_match_device(stm32_sai_ids, &pdev->dev); + if (of_id) + sai->version = (enum stm32_sai_version)of_id->data; + else + return -EINVAL; + + sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k"); + if (IS_ERR(sai->clk_x8k)) { + dev_err(&pdev->dev, "missing x8k parent clock\n"); + return PTR_ERR(sai->clk_x8k); + } + + sai->clk_x11k = devm_clk_get(&pdev->dev, "x11k"); + if (IS_ERR(sai->clk_x11k)) { + dev_err(&pdev->dev, "missing x11k parent clock\n"); + return PTR_ERR(sai->clk_x11k); + } + + /* init irqs */ + sai->irq = platform_get_irq(pdev, 0); + if (sai->irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return sai->irq; + } + + /* reset */ + rst = reset_control_get(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + sai->pdev = pdev; + platform_set_drvdata(pdev, sai); + + return of_platform_populate(np, NULL, NULL, &pdev->dev); +} + +static int stm32_sai_remove(struct platform_device *pdev) +{ + of_platform_depopulate(&pdev->dev); + + return 0; +} + +MODULE_DEVICE_TABLE(of, stm32_sai_ids); + +static struct platform_driver stm32_sai_driver = { + .driver = { + .name = "st,stm32-sai", + .of_match_table = stm32_sai_ids, + }, + .probe = stm32_sai_probe, + .remove = stm32_sai_remove, +}; + +module_platform_driver(stm32_sai_driver); + +MODULE_DESCRIPTION("STM32 Soc SAI Interface"); +MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_ALIAS("platform:st,stm32-sai"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h new file mode 100644 index 000000000000..a801fda5066f --- /dev/null +++ b/sound/soc/stm/stm32_sai.h @@ -0,0 +1,200 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +/******************** SAI Register Map **************************************/ + +/* common register */ +#define STM_SAI_GCR 0x00 + +/* Sub-block A&B registers offsets, relative to A&B sub-block addresses */ +#define STM_SAI_CR1_REGX 0x00 /* A offset: 0x04. B offset: 0x24 */ +#define STM_SAI_CR2_REGX 0x04 +#define STM_SAI_FRCR_REGX 0x08 +#define STM_SAI_SLOTR_REGX 0x0C +#define STM_SAI_IMR_REGX 0x10 +#define STM_SAI_SR_REGX 0x14 +#define STM_SAI_CLRFR_REGX 0x18 +#define STM_SAI_DR_REGX 0x1C + +/******************** Bit definition for SAI_GCR register *******************/ +#define SAI_GCR_SYNCIN_SHIFT 0 +#define SAI_GCR_SYNCIN_MASK GENMASK(1, SAI_GCR_SYNCIN_SHIFT) +#define SAI_GCR_SYNCIN_SET(x) ((x) << SAI_GCR_SYNCIN_SHIFT) + +#define SAI_GCR_SYNCOUT_SHIFT 4 +#define SAI_GCR_SYNCOUT_MASK GENMASK(5, SAI_GCR_SYNCOUT_SHIFT) +#define SAI_GCR_SYNCOUT_SET(x) ((x) << SAI_GCR_SYNCOUT_SHIFT) + +/******************* Bit definition for SAI_XCR1 register *******************/ +#define SAI_XCR1_RX_TX_SHIFT 0 +#define SAI_XCR1_RX_TX BIT(SAI_XCR1_RX_TX_SHIFT) +#define SAI_XCR1_SLAVE_SHIFT 1 +#define SAI_XCR1_SLAVE BIT(SAI_XCR1_SLAVE_SHIFT) + +#define SAI_XCR1_PRTCFG_SHIFT 2 +#define SAI_XCR1_PRTCFG_MASK GENMASK(3, SAI_XCR1_PRTCFG_SHIFT) +#define SAI_XCR1_PRTCFG_SET(x) ((x) << SAI_XCR1_PRTCFG_SHIFT) + +#define SAI_XCR1_DS_SHIFT 5 +#define SAI_XCR1_DS_MASK GENMASK(7, SAI_XCR1_DS_SHIFT) +#define SAI_XCR1_DS_SET(x) ((x) << SAI_XCR1_DS_SHIFT) + +#define SAI_XCR1_LSBFIRST_SHIFT 8 +#define SAI_XCR1_LSBFIRST BIT(SAI_XCR1_LSBFIRST_SHIFT) +#define SAI_XCR1_CKSTR_SHIFT 9 +#define SAI_XCR1_CKSTR BIT(SAI_XCR1_CKSTR_SHIFT) + +#define SAI_XCR1_SYNCEN_SHIFT 10 +#define SAI_XCR1_SYNCEN_MASK GENMASK(11, SAI_XCR1_SYNCEN_SHIFT) +#define SAI_XCR1_SYNCEN_SET(x) ((x) << SAI_XCR1_SYNCEN_SHIFT) + +#define SAI_XCR1_MONO_SHIFT 12 +#define SAI_XCR1_MONO BIT(SAI_XCR1_MONO_SHIFT) +#define SAI_XCR1_OUTDRIV_SHIFT 13 +#define SAI_XCR1_OUTDRIV BIT(SAI_XCR1_OUTDRIV_SHIFT) +#define SAI_XCR1_SAIEN_SHIFT 16 +#define SAI_XCR1_SAIEN BIT(SAI_XCR1_SAIEN_SHIFT) +#define SAI_XCR1_DMAEN_SHIFT 17 +#define SAI_XCR1_DMAEN BIT(SAI_XCR1_DMAEN_SHIFT) +#define SAI_XCR1_NODIV_SHIFT 19 +#define SAI_XCR1_NODIV BIT(SAI_XCR1_NODIV_SHIFT) + +#define SAI_XCR1_MCKDIV_SHIFT 20 +#define SAI_XCR1_MCKDIV_WIDTH 4 +#define SAI_XCR1_MCKDIV_MASK GENMASK(24, SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_SET(x) ((x) << SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_MAX ((1 << SAI_XCR1_MCKDIV_WIDTH) - 1) + +#define SAI_XCR1_OSR_SHIFT 26 +#define SAI_XCR1_OSR BIT(SAI_XCR1_OSR_SHIFT) + +/******************* Bit definition for SAI_XCR2 register *******************/ +#define SAI_XCR2_FTH_SHIFT 0 +#define SAI_XCR2_FTH_MASK GENMASK(2, SAI_XCR2_FTH_SHIFT) +#define SAI_XCR2_FTH_SET(x) ((x) << SAI_XCR2_FTH_SHIFT) + +#define SAI_XCR2_FFLUSH_SHIFT 3 +#define SAI_XCR2_FFLUSH BIT(SAI_XCR2_FFLUSH_SHIFT) +#define SAI_XCR2_TRIS_SHIFT 4 +#define SAI_XCR2_TRIS BIT(SAI_XCR2_TRIS_SHIFT) +#define SAI_XCR2_MUTE_SHIFT 5 +#define SAI_XCR2_MUTE BIT(SAI_XCR2_MUTE_SHIFT) +#define SAI_XCR2_MUTEVAL_SHIFT 6 +#define SAI_XCR2_MUTEVAL BIT(SAI_XCR2_MUTEVAL_SHIFT) + +#define SAI_XCR2_MUTECNT_SHIFT 7 +#define SAI_XCR2_MUTECNT_MASK GENMASK(12, SAI_XCR2_MUTECNT_SHIFT) +#define SAI_XCR2_MUTECNT_SET(x) ((x) << SAI_XCR2_MUTECNT_SHIFT) + +#define SAI_XCR2_CPL_SHIFT 13 +#define SAI_XCR2_CPL BIT(SAI_XCR2_CPL_SHIFT) + +#define SAI_XCR2_COMP_SHIFT 14 +#define SAI_XCR2_COMP_MASK GENMASK(15, SAI_XCR2_COMP_SHIFT) +#define SAI_XCR2_COMP_SET(x) ((x) << SAI_XCR2_COMP_SHIFT) + +/****************** Bit definition for SAI_XFRCR register *******************/ +#define SAI_XFRCR_FRL_SHIFT 0 +#define SAI_XFRCR_FRL_MASK GENMASK(7, SAI_XFRCR_FRL_SHIFT) +#define SAI_XFRCR_FRL_SET(x) ((x) << SAI_XFRCR_FRL_SHIFT) + +#define SAI_XFRCR_FSALL_SHIFT 8 +#define SAI_XFRCR_FSALL_MASK GENMASK(14, SAI_XFRCR_FSALL_SHIFT) +#define SAI_XFRCR_FSALL_SET(x) ((x) << SAI_XFRCR_FSALL_SHIFT) + +#define SAI_XFRCR_FSDEF_SHIFT 16 +#define SAI_XFRCR_FSDEF BIT(SAI_XFRCR_FSDEF_SHIFT) +#define SAI_XFRCR_FSPOL_SHIFT 17 +#define SAI_XFRCR_FSPOL BIT(SAI_XFRCR_FSPOL_SHIFT) +#define SAI_XFRCR_FSOFF_SHIFT 18 +#define SAI_XFRCR_FSOFF BIT(SAI_XFRCR_FSOFF_SHIFT) + +/****************** Bit definition for SAI_XSLOTR register ******************/ + +#define SAI_XSLOTR_FBOFF_SHIFT 0 +#define SAI_XSLOTR_FBOFF_MASK GENMASK(4, SAI_XSLOTR_FBOFF_SHIFT) +#define SAI_XSLOTR_FBOFF_SET(x) ((x) << SAI_XSLOTR_FBOFF_SHIFT) + +#define SAI_XSLOTR_SLOTSZ_SHIFT 6 +#define SAI_XSLOTR_SLOTSZ_MASK GENMASK(7, SAI_XSLOTR_SLOTSZ_SHIFT) +#define SAI_XSLOTR_SLOTSZ_SET(x) ((x) << SAI_XSLOTR_SLOTSZ_SHIFT) + +#define SAI_XSLOTR_NBSLOT_SHIFT 8 +#define SAI_XSLOTR_NBSLOT_MASK GENMASK(11, SAI_XSLOTR_NBSLOT_SHIFT) +#define SAI_XSLOTR_NBSLOT_SET(x) ((x) << SAI_XSLOTR_NBSLOT_SHIFT) + +#define SAI_XSLOTR_SLOTEN_SHIFT 16 +#define SAI_XSLOTR_SLOTEN_WIDTH 16 +#define SAI_XSLOTR_SLOTEN_MASK GENMASK(31, SAI_XSLOTR_SLOTEN_SHIFT) +#define SAI_XSLOTR_SLOTEN_SET(x) ((x) << SAI_XSLOTR_SLOTEN_SHIFT) + +/******************* Bit definition for SAI_XIMR register *******************/ +#define SAI_XIMR_OVRUDRIE BIT(0) +#define SAI_XIMR_MUTEDETIE BIT(1) +#define SAI_XIMR_WCKCFGIE BIT(2) +#define SAI_XIMR_FREQIE BIT(3) +#define SAI_XIMR_CNRDYIE BIT(4) +#define SAI_XIMR_AFSDETIE BIT(5) +#define SAI_XIMR_LFSDETIE BIT(6) + +#define SAI_XIMR_SHIFT 0 +#define SAI_XIMR_MASK GENMASK(6, SAI_XIMR_SHIFT) + +/******************** Bit definition for SAI_XSR register *******************/ +#define SAI_XSR_OVRUDR BIT(0) +#define SAI_XSR_MUTEDET BIT(1) +#define SAI_XSR_WCKCFG BIT(2) +#define SAI_XSR_FREQ BIT(3) +#define SAI_XSR_CNRDY BIT(4) +#define SAI_XSR_AFSDET BIT(5) +#define SAI_XSR_LFSDET BIT(6) + +#define SAI_XSR_SHIFT 0 +#define SAI_XSR_MASK GENMASK(6, SAI_XSR_SHIFT) + +/****************** Bit definition for SAI_XCLRFR register ******************/ +#define SAI_XCLRFR_COVRUDR BIT(0) +#define SAI_XCLRFR_CMUTEDET BIT(1) +#define SAI_XCLRFR_CWCKCFG BIT(2) +#define SAI_XCLRFR_CFREQ BIT(3) +#define SAI_XCLRFR_CCNRDY BIT(4) +#define SAI_XCLRFR_CAFSDET BIT(5) +#define SAI_XCLRFR_CLFSDET BIT(6) + +#define SAI_XCLRFR_SHIFT 0 +#define SAI_XCLRFR_MASK GENMASK(6, SAI_XCLRFR_SHIFT) + +enum stm32_sai_version { + SAI_STM32F4 +}; + +/** + * struct stm32_sai_data - private data of SAI instance driver + * @pdev: device data pointer + * @clk_x8k: SAI parent clock for sampling frequencies multiple of 8kHz + * @clk_x11k: SAI parent clock for sampling frequencies multiple of 11kHz + * @version: SOC version + * @irq: SAI interrupt line + */ +struct stm32_sai_data { + struct platform_device *pdev; + struct clk *clk_x8k; + struct clk *clk_x11k; + int version; + int irq; +}; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c new file mode 100644 index 000000000000..ae4706ca265b --- /dev/null +++ b/sound/soc/stm/stm32_sai_sub.c @@ -0,0 +1,884 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "stm32_sai.h" + +#define SAI_FREE_PROTOCOL 0x0 + +#define SAI_SLOT_SIZE_AUTO 0x0 +#define SAI_SLOT_SIZE_16 0x1 +#define SAI_SLOT_SIZE_32 0x2 + +#define SAI_DATASIZE_8 0x2 +#define SAI_DATASIZE_10 0x3 +#define SAI_DATASIZE_16 0x4 +#define SAI_DATASIZE_20 0x5 +#define SAI_DATASIZE_24 0x6 +#define SAI_DATASIZE_32 0x7 + +#define STM_SAI_FIFO_SIZE 8 +#define STM_SAI_DAI_NAME_SIZE 15 + +#define STM_SAI_IS_PLAYBACK(ip) ((ip)->dir == SNDRV_PCM_STREAM_PLAYBACK) +#define STM_SAI_IS_CAPTURE(ip) ((ip)->dir == SNDRV_PCM_STREAM_CAPTURE) + +#define STM_SAI_A_ID 0x0 +#define STM_SAI_B_ID 0x1 + +#define STM_SAI_BLOCK_NAME(x) (((x)->id == STM_SAI_A_ID) ? "A" : "B") + +/** + * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) + * @pdev: device data pointer + * @regmap: SAI register map pointer + * @dma_params: dma configuration data for rx or tx channel + * @cpu_dai_drv: DAI driver data pointer + * @cpu_dai: DAI runtime data pointer + * @substream: PCM substream data pointer + * @pdata: SAI block parent data pointer + * @sai_ck: kernel clock feeding the SAI clock generator + * @phys_addr: SAI registers physical base address + * @mclk_rate: SAI block master clock frequency (Hz). set at init + * @id: SAI sub block id corresponding to sub-block A or B + * @dir: SAI block direction (playback or capture). set at init + * @master: SAI block mode flag. (true=master, false=slave) set at init + * @fmt: SAI block format. relevant only for custom protocols. set at init + * @sync: SAI block synchronization mode. (none, internal or external) + * @fs_length: frame synchronization length. depends on protocol settings + * @slots: rx or tx slot number + * @slot_width: rx or tx slot width in bits + * @slot_mask: rx or tx active slots mask. set at init or at runtime + * @data_size: PCM data width. corresponds to PCM substream width. + */ +struct stm32_sai_sub_data { + struct platform_device *pdev; + struct regmap *regmap; + struct snd_dmaengine_dai_dma_data dma_params; + struct snd_soc_dai_driver *cpu_dai_drv; + struct snd_soc_dai *cpu_dai; + struct snd_pcm_substream *substream; + struct stm32_sai_data *pdata; + struct clk *sai_ck; + dma_addr_t phys_addr; + unsigned int mclk_rate; + unsigned int id; + int dir; + bool master; + int fmt; + int sync; + int fs_length; + int slots; + int slot_width; + int slot_mask; + int data_size; +}; + +enum stm32_sai_fifo_th { + STM_SAI_FIFO_TH_EMPTY, + STM_SAI_FIFO_TH_QUARTER, + STM_SAI_FIFO_TH_HALF, + STM_SAI_FIFO_TH_3_QUARTER, + STM_SAI_FIFO_TH_FULL, +}; + +static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_CR1_REGX: + case STM_SAI_CR2_REGX: + case STM_SAI_FRCR_REGX: + case STM_SAI_SLOTR_REGX: + case STM_SAI_IMR_REGX: + case STM_SAI_SR_REGX: + case STM_SAI_CLRFR_REGX: + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_CR1_REGX: + case STM_SAI_CR2_REGX: + case STM_SAI_FRCR_REGX: + case STM_SAI_SLOTR_REGX: + case STM_SAI_IMR_REGX: + case STM_SAI_SR_REGX: + case STM_SAI_CLRFR_REGX: + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static const struct regmap_config stm32_sai_sub_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM_SAI_DR_REGX, + .readable_reg = stm32_sai_sub_readable_reg, + .volatile_reg = stm32_sai_sub_volatile_reg, + .writeable_reg = stm32_sai_sub_writeable_reg, + .fast_io = true, +}; + +static irqreturn_t stm32_sai_isr(int irq, void *devid) +{ + struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid; + struct snd_pcm_substream *substream = sai->substream; + struct platform_device *pdev = sai->pdev; + unsigned int sr, imr, flags; + snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING; + + regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr); + regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr); + + flags = sr & imr; + if (!flags) + return IRQ_NONE; + + regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, + SAI_XCLRFR_MASK); + + if (flags & SAI_XIMR_OVRUDRIE) { + dev_err(&pdev->dev, "IT %s\n", + STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (flags & SAI_XIMR_MUTEDETIE) + dev_dbg(&pdev->dev, "IT mute detected\n"); + + if (flags & SAI_XIMR_WCKCFGIE) { + dev_err(&pdev->dev, "IT wrong clock configuration\n"); + status = SNDRV_PCM_STATE_DISCONNECTED; + } + + if (flags & SAI_XIMR_CNRDYIE) + dev_warn(&pdev->dev, "IT Codec not ready\n"); + + if (flags & SAI_XIMR_AFSDETIE) { + dev_warn(&pdev->dev, "IT Anticipated frame synchro\n"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (flags & SAI_XIMR_LFSDETIE) { + dev_warn(&pdev->dev, "IT Late frame synchro\n"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (status != SNDRV_PCM_STATE_RUNNING) { + snd_pcm_stream_lock(substream); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(substream); + } + + return IRQ_HANDLED; +} + +static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + + if ((dir == SND_SOC_CLOCK_OUT) && sai->master) { + sai->mclk_rate = freq; + dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq); + } + + return 0; +} + +static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int slotr, slotr_mask, slot_size; + + dev_dbg(cpu_dai->dev, "masks tx/rx:%#x/%#x, slots:%d, width:%d\n", + tx_mask, rx_mask, slots, slot_width); + + switch (slot_width) { + case 16: + slot_size = SAI_SLOT_SIZE_16; + break; + case 32: + slot_size = SAI_SLOT_SIZE_32; + break; + default: + slot_size = SAI_SLOT_SIZE_AUTO; + break; + } + + slotr = SAI_XSLOTR_SLOTSZ_SET(slot_size) | + SAI_XSLOTR_NBSLOT_SET(slots - 1); + slotr_mask = SAI_XSLOTR_SLOTSZ_MASK | SAI_XSLOTR_NBSLOT_MASK; + + /* tx/rx mask set in machine init, if slot number defined in DT */ + if (STM_SAI_IS_PLAYBACK(sai)) { + sai->slot_mask = tx_mask; + slotr |= SAI_XSLOTR_SLOTEN_SET(tx_mask); + } + + if (STM_SAI_IS_CAPTURE(sai)) { + sai->slot_mask = rx_mask; + slotr |= SAI_XSLOTR_SLOTEN_SET(rx_mask); + } + + slotr_mask |= SAI_XSLOTR_SLOTEN_MASK; + + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr); + + sai->slot_width = slot_width; + sai->slots = slots; + + return 0; +} + +static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1 = 0, frcr = 0; + int cr1_mask = 0, frcr_mask = 0; + int ret; + + dev_dbg(cpu_dai->dev, "fmt %x\n", fmt); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + /* SCK active high for all protocols */ + case SND_SOC_DAIFMT_I2S: + cr1 |= SAI_XCR1_CKSTR; + frcr |= SAI_XFRCR_FSOFF | SAI_XFRCR_FSDEF; + break; + /* Left justified */ + case SND_SOC_DAIFMT_MSB: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF; + break; + /* Right justified */ + case SND_SOC_DAIFMT_LSB: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF; + break; + case SND_SOC_DAIFMT_DSP_A: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF; + break; + case SND_SOC_DAIFMT_DSP_B: + frcr |= SAI_XFRCR_FSPOL; + break; + default: + dev_err(cpu_dai->dev, "Unsupported protocol %#x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + cr1_mask |= SAI_XCR1_PRTCFG_MASK | SAI_XCR1_CKSTR; + frcr_mask |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF | + SAI_XFRCR_FSDEF; + + /* DAI clock strobing. Invert setting previously set */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + cr1 ^= SAI_XCR1_CKSTR; + break; + case SND_SOC_DAIFMT_NB_IF: + frcr ^= SAI_XFRCR_FSPOL; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert fs & sck */ + cr1 ^= SAI_XCR1_CKSTR; + frcr ^= SAI_XFRCR_FSPOL; + break; + default: + dev_err(cpu_dai->dev, "Unsupported strobing %#x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + cr1_mask |= SAI_XCR1_CKSTR; + frcr_mask |= SAI_XFRCR_FSPOL; + + regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master */ + cr1 |= SAI_XCR1_SLAVE; + sai->master = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + sai->master = true; + break; + default: + dev_err(cpu_dai->dev, "Unsupported mode %#x\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + cr1_mask |= SAI_XCR1_SLAVE; + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + sai->fmt = fmt; + + return 0; +} + +static int stm32_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int imr, cr2, ret; + + sai->substream = substream; + + ret = clk_prepare_enable(sai->sai_ck); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Enable ITs */ + regmap_update_bits(sai->regmap, STM_SAI_SR_REGX, + SAI_XSR_MASK, (unsigned int)~SAI_XSR_MASK); + + regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, + SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); + + imr = SAI_XIMR_OVRUDRIE; + if (STM_SAI_IS_CAPTURE(sai)) { + regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2); + if (cr2 & SAI_XCR2_MUTECNT_MASK) + imr |= SAI_XIMR_MUTEDETIE; + } + + if (sai->master) + imr |= SAI_XIMR_WCKCFGIE; + else + imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE; + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, + SAI_XIMR_MASK, imr); + + return 0; +} + +static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, cr1_mask, ret; + int fth = STM_SAI_FIFO_TH_HALF; + + /* FIFO config */ + regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_SET(fth)); + + /* Mode, data format and channel config */ + cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_16); + break; + case SNDRV_PCM_FORMAT_S32_LE: + cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_32); + break; + default: + dev_err(cpu_dai->dev, "Data format not supported"); + return -EINVAL; + } + cr1_mask = SAI_XCR1_DS_MASK | SAI_XCR1_PRTCFG_MASK; + + cr1_mask |= SAI_XCR1_RX_TX; + if (STM_SAI_IS_CAPTURE(sai)) + cr1 |= SAI_XCR1_RX_TX; + + cr1_mask |= SAI_XCR1_MONO; + if ((sai->slots == 2) && (params_channels(params) == 1)) + cr1 |= SAI_XCR1_MONO; + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + /* DMA config */ + sai->dma_params.maxburst = STM_SAI_FIFO_SIZE * fth / sizeof(u32); + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)&sai->dma_params); + + return 0; +} + +static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int slotr, slot_sz; + + regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr); + + /* + * If SLOTSZ is set to auto in SLOTR, align slot width on data size + * By default slot width = data size, if not forced from DT + */ + slot_sz = slotr & SAI_XSLOTR_SLOTSZ_MASK; + if (slot_sz == SAI_XSLOTR_SLOTSZ_SET(SAI_SLOT_SIZE_AUTO)) + sai->slot_width = sai->data_size; + + if (sai->slot_width < sai->data_size) { + dev_err(cpu_dai->dev, + "Data size %d larger than slot width\n", + sai->data_size); + return -EINVAL; + } + + /* Slot number is set to 2, if not specified in DT */ + if (!sai->slots) + sai->slots = 2; + + /* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/ + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, + SAI_XSLOTR_NBSLOT_MASK, + SAI_XSLOTR_NBSLOT_SET((sai->slots - 1))); + + /* Set default slots mask if not already set from DT */ + if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) { + sai->slot_mask = (1 << sai->slots) - 1; + regmap_update_bits(sai->regmap, + STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK, + SAI_XSLOTR_SLOTEN_SET(sai->slot_mask)); + } + + dev_dbg(cpu_dai->dev, "slots %d, slot width %d\n", + sai->slots, sai->slot_width); + + return 0; +} + +static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int fs_active, offset, format; + int frcr, frcr_mask; + + format = sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + sai->fs_length = sai->slot_width * sai->slots; + + fs_active = sai->fs_length / 2; + if ((format == SND_SOC_DAIFMT_DSP_A) || + (format == SND_SOC_DAIFMT_DSP_B)) + fs_active = 1; + + frcr = SAI_XFRCR_FRL_SET((sai->fs_length - 1)); + frcr |= SAI_XFRCR_FSALL_SET((fs_active - 1)); + frcr_mask = SAI_XFRCR_FRL_MASK | SAI_XFRCR_FSALL_MASK; + + dev_dbg(cpu_dai->dev, "frame length %d, frame active %d\n", + sai->fs_length, fs_active); + + regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); + + if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) { + offset = sai->slot_width - sai->data_size; + + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, + SAI_XSLOTR_FBOFF_MASK, + SAI_XSLOTR_FBOFF_SET(offset)); + } +} + +static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, mask, div = 0; + int sai_clk_rate, ret; + + if (!sai->mclk_rate) { + dev_err(cpu_dai->dev, "Mclk rate is null\n"); + return -EINVAL; + } + + if (!(params_rate(params) % 11025)) + clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k); + else + clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); + sai_clk_rate = clk_get_rate(sai->sai_ck); + + /* + * mclk_rate = 256 * fs + * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate + * MCKDIV = sai_ck / (2 * mclk_rate) otherwise + */ + if (2 * sai_clk_rate >= 3 * sai->mclk_rate) + div = DIV_ROUND_CLOSEST(sai_clk_rate, 2 * sai->mclk_rate); + + if (div > SAI_XCR1_MCKDIV_MAX) { + dev_err(cpu_dai->dev, "Divider %d out of range\n", div); + return -EINVAL; + } + dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div); + + mask = SAI_XCR1_MCKDIV_MASK; + cr1 = SAI_XCR1_MCKDIV_SET(div); + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + return 0; +} + +static int stm32_sai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + sai->data_size = params_width(params); + + ret = stm32_sai_set_slots(cpu_dai); + if (ret < 0) + return ret; + stm32_sai_set_frame(cpu_dai); + + ret = stm32_sai_set_config(cpu_dai, substream, params); + if (ret) + return ret; + + if (sai->master) + ret = stm32_sai_configure_clock(cpu_dai, params); + + return ret; +} + +static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n"); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_DMAEN, SAI_XCR1_DMAEN); + + /* Enable SAI */ + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_SAIEN, SAI_XCR1_SAIEN); + if (ret < 0) + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n"); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_DMAEN, + (unsigned int)~SAI_XCR1_DMAEN); + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_SAIEN, + (unsigned int)~SAI_XCR1_SAIEN); + if (ret < 0) + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + break; + default: + return -EINVAL; + } + + return ret; +} + +static void stm32_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0); + + clk_disable_unprepare(sai->sai_ck); + sai->substream = NULL; +} + +static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + + sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX); + sai->dma_params.maxburst = 1; + /* Buswidth will be set by framework at runtime */ + sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + + if (STM_SAI_IS_PLAYBACK(sai)) + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params, NULL); + else + snd_soc_dai_init_dma_data(cpu_dai, NULL, &sai->dma_params); + + return 0; +} + +static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { + .set_sysclk = stm32_sai_set_sysclk, + .set_fmt = stm32_sai_set_dai_fmt, + .set_tdm_slot = stm32_sai_set_dai_tdm_slot, + .startup = stm32_sai_startup, + .hw_params = stm32_sai_hw_params, + .trigger = stm32_sai_trigger, + .shutdown = stm32_sai_shutdown, +}; + +static const struct snd_pcm_hardware stm32_sai_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_min = 1024, /* 5ms at 48kHz */ + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, +}; + +static struct snd_soc_dai_driver stm32_sai_playback_dai[] = { +{ + .probe = stm32_sai_dai_probe, + .id = 1, /* avoid call to fmt_single_name() */ + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + /* DMA does not support 24 bits transfers */ + .formats = + SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &stm32_sai_pcm_dai_ops, + } +}; + +static struct snd_soc_dai_driver stm32_sai_capture_dai[] = { +{ + .probe = stm32_sai_dai_probe, + .id = 1, /* avoid call to fmt_single_name() */ + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + /* DMA does not support 24 bits transfers */ + .formats = + SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &stm32_sai_pcm_dai_ops, + } +}; + +static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = { + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct snd_soc_component_driver stm32_component = { + .name = "stm32-sai", +}; + +static const struct of_device_id stm32_sai_sub_ids[] = { + { .compatible = "st,stm32-sai-sub-a", + .data = (void *)STM_SAI_A_ID}, + { .compatible = "st,stm32-sai-sub-b", + .data = (void *)STM_SAI_B_ID}, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_sai_sub_ids); + +static int stm32_sai_sub_parse_of(struct platform_device *pdev, + struct stm32_sai_sub_data *sai) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + void __iomem *base; + + if (!np) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dev_err(&pdev->dev, "res %pr\n", res); + + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->phys_addr = res->start; + sai->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &stm32_sai_sub_regmap_config); + + /* Get direction property */ + if (of_property_match_string(np, "dma-names", "tx") >= 0) { + sai->dir = SNDRV_PCM_STREAM_PLAYBACK; + } else if (of_property_match_string(np, "dma-names", "rx") >= 0) { + sai->dir = SNDRV_PCM_STREAM_CAPTURE; + } else { + dev_err(&pdev->dev, "Unsupported direction\n"); + return -EINVAL; + } + + sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck"); + if (IS_ERR(sai->sai_ck)) { + dev_err(&pdev->dev, "missing kernel clock sai_ck\n"); + return PTR_ERR(sai->sai_ck); + } + + return 0; +} + +static int stm32_sai_sub_dais_init(struct platform_device *pdev, + struct stm32_sai_sub_data *sai) +{ + sai->cpu_dai_drv = devm_kzalloc(&pdev->dev, + sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!sai->cpu_dai_drv) + return -ENOMEM; + + sai->cpu_dai_drv->name = dev_name(&pdev->dev); + if (STM_SAI_IS_PLAYBACK(sai)) { + memcpy(sai->cpu_dai_drv, &stm32_sai_playback_dai, + sizeof(stm32_sai_playback_dai)); + sai->cpu_dai_drv->playback.stream_name = sai->cpu_dai_drv->name; + } else { + memcpy(sai->cpu_dai_drv, &stm32_sai_capture_dai, + sizeof(stm32_sai_capture_dai)); + sai->cpu_dai_drv->capture.stream_name = sai->cpu_dai_drv->name; + } + + return 0; +} + +static int stm32_sai_sub_probe(struct platform_device *pdev) +{ + struct stm32_sai_sub_data *sai; + const struct of_device_id *of_id; + int ret; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + of_id = of_match_device(stm32_sai_sub_ids, &pdev->dev); + if (!of_id) + return -EINVAL; + sai->id = (uintptr_t)of_id->data; + + sai->pdev = pdev; + platform_set_drvdata(pdev, sai); + + sai->pdata = dev_get_drvdata(pdev->dev.parent); + if (!sai->pdata) { + dev_err(&pdev->dev, "Parent device data not available\n"); + return -EINVAL; + } + + ret = stm32_sai_sub_parse_of(pdev, sai); + if (ret) + return ret; + + ret = stm32_sai_sub_dais_init(pdev, sai); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); + if (ret) { + dev_err(&pdev->dev, "irq request returned %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &stm32_component, + sai->cpu_dai_drv, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &stm32_sai_pcm_config, 0); + if (ret) { + dev_err(&pdev->dev, "could not register pcm dma\n"); + return ret; + } + + return 0; +} + +static struct platform_driver stm32_sai_sub_driver = { + .driver = { + .name = "st,stm32-sai-sub", + .of_match_table = stm32_sai_sub_ids, + }, + .probe = stm32_sai_sub_probe, +}; + +module_platform_driver(stm32_sai_sub_driver); + +MODULE_DESCRIPTION("STM32 Soc SAI sub-block Interface"); +MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_ALIAS("platform:st,stm32-sai-sub"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c index 72331332b72e..6c17c99c2c8d 100644 --- a/sound/soc/sunxi/sun8i-codec-analog.c +++ b/sound/soc/sunxi/sun8i-codec-analog.c @@ -252,24 +252,15 @@ static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale, ); static const struct snd_kcontrol_new sun8i_codec_common_controls[] = { - /* Mixer pre-gains */ - SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, - SUN8I_ADDA_LINEIN_GCTRL_LINEING, - 0x7, 0, sun8i_codec_out_mixer_pregain_scale), + /* Mixer pre-gain */ SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC1G, 0x7, 0, sun8i_codec_out_mixer_pregain_scale), - SOC_SINGLE_TLV("Mic2 Playback Volume", - SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, - 0x7, 0, sun8i_codec_out_mixer_pregain_scale), - /* Microphone Amp boost gains */ + /* Microphone Amp boost gain */ SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, sun8i_codec_mic_gain_scale), - SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, - SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, - sun8i_codec_mic_gain_scale), /* ADC */ SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN, @@ -295,12 +286,8 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { * stream widgets at the card level. */ - /* Line In */ - SND_SOC_DAPM_INPUT("LINEIN"), - - /* Microphone inputs */ + /* Microphone input */ SND_SOC_DAPM_INPUT("MIC1"), - SND_SOC_DAPM_INPUT("MIC2"), /* Microphone Bias */ SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, @@ -310,8 +297,6 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { /* Mic input path */ SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), - SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, - SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), /* Mixers */ SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, @@ -335,35 +320,26 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { /* Microphone Routes */ { "Mic1 Amplifier", NULL, "MIC1"}, - { "Mic2 Amplifier", NULL, "MIC2"}, /* Left Mixer Routes */ { "Left Mixer", "DAC Playback Switch", "Left DAC" }, { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, - { "Left Mixer", "Line In Playback Switch", "LINEIN" }, { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, - { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, /* Right Mixer Routes */ { "Right Mixer", "DAC Playback Switch", "Right DAC" }, { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, - { "Right Mixer", "Line In Playback Switch", "LINEIN" }, { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, - { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, /* Left ADC Mixer Routes */ { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, - { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, - { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, /* Right ADC Mixer Routes */ { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, - { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, - { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, /* ADC Routes */ { "Left ADC", NULL, "Left ADC Mixer" }, @@ -498,6 +474,61 @@ static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) return ret; } +/* line in specific controls, widgets and rines */ +static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, + SUN8I_ADDA_LINEIN_GCTRL_LINEING, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { + /* Line input */ + SND_SOC_DAPM_INPUT("LINEIN"), +}; + +static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = { + { "Left Mixer", "Line In Playback Switch", "LINEIN" }, + + { "Right Mixer", "Line In Playback Switch", "LINEIN" }, + + { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, + + { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, +}; + +static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_linein_controls, + ARRAY_SIZE(sun8i_codec_linein_controls)); + if (ret) { + dev_err(dev, "Failed to add Line In controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_linein_widgets, + ARRAY_SIZE(sun8i_codec_linein_widgets)); + if (ret) { + dev_err(dev, "Failed to add Line In DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_linein_routes, + ARRAY_SIZE(sun8i_codec_linein_routes)); + if (ret) { + dev_err(dev, "Failed to add Line In DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + + /* line out specific controls, widgets and routes */ static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), @@ -578,19 +609,90 @@ static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) return 0; } +/* mic2 specific controls, widgets and routes */ +static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Mic2 Playback Volume", + SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), + + /* Microphone Amp boost gain */ + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, + sun8i_codec_mic_gain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = { + /* Microphone input */ + SND_SOC_DAPM_INPUT("MIC2"), + + /* Mic input path */ + SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = { + { "Mic2 Amplifier", NULL, "MIC2"}, + + { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, + + { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, +}; + +static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_mic2_controls, + ARRAY_SIZE(sun8i_codec_mic2_controls)); + if (ret) { + dev_err(dev, "Failed to add MIC2 controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mic2_widgets, + ARRAY_SIZE(sun8i_codec_mic2_widgets)); + if (ret) { + dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mic2_routes, + ARRAY_SIZE(sun8i_codec_mic2_routes)); + if (ret) { + dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + struct sun8i_codec_analog_quirks { bool has_headphone; bool has_hmic; + bool has_linein; bool has_lineout; + bool has_mic2; }; static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { .has_headphone = true, .has_hmic = true, + .has_linein = true, + .has_mic2 = true, }; static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { + .has_linein = true, .has_lineout = true, + .has_mic2 = true, }; static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) @@ -620,12 +722,24 @@ static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) return ret; } + if (quirks->has_linein) { + ret = sun8i_codec_add_linein(cmpnt); + if (ret) + return ret; + } + if (quirks->has_lineout) { ret = sun8i_codec_add_lineout(cmpnt); if (ret) return ret; } + if (quirks->has_mic2) { + ret = sun8i_codec_add_mic2(cmpnt); + if (ret) + return ret; + } + return 0; } diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c index 7527ba29a5a0..5723c3404f6b 100644 --- a/sound/soc/sunxi/sun8i-codec.c +++ b/sound/soc/sunxi/sun8i-codec.c @@ -290,12 +290,10 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0), /* DAC Mixers */ - SND_SOC_DAPM_MIXER("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0, - sun8i_dac_mixer_controls, - ARRAY_SIZE(sun8i_dac_mixer_controls)), - SND_SOC_DAPM_MIXER("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0, - sun8i_dac_mixer_controls, - ARRAY_SIZE(sun8i_dac_mixer_controls)), + SOC_MIXER_ARRAY("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_dac_mixer_controls), + SOC_MIXER_ARRAY("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_dac_mixer_controls), /* Clocks */ SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA, diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c index a68368edab9c..affad46bf188 100644 --- a/sound/soc/tegra/tegra20_ac97.c +++ b/sound/soc/tegra/tegra20_ac97.c @@ -318,7 +318,6 @@ static int tegra20_ac97_platform_probe(struct platform_device *pdev) ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97), GFP_KERNEL); if (!ac97) { - dev_err(&pdev->dev, "Can't allocate tegra20_ac97\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c index 89add13c31cf..4024e3abbeed 100644 --- a/sound/soc/tegra/tegra20_das.c +++ b/sound/soc/tegra/tegra20_das.c @@ -41,6 +41,7 @@ static inline void tegra20_das_write(u32 reg, u32 val) static inline u32 tegra20_das_read(u32 reg) { u32 val; + regmap_read(das->regmap, reg, &val); return val; } @@ -142,7 +143,6 @@ static int tegra20_das_probe(struct platform_device *pdev) das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); if (!das) { - dev_err(&pdev->dev, "Can't allocate tegra20_das\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index 14106fa82bca..26253c2849e7 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -345,7 +345,6 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL); if (!i2s) { - dev_err(&pdev->dev, "Can't allocate tegra20_i2s\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index a0c3640572b9..767c0491e11a 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -271,10 +271,9 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), GFP_KERNEL); - if (!spdif) { - dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n"); + if (!spdif) return -ENOMEM; - } + dev_set_drvdata(&pdev->dev, spdif); spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c index fef3b9a21a66..8c10ae7982ba 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -41,6 +41,7 @@ static inline void tegra30_apbif_write(u32 reg, u32 val) static inline u32 tegra30_apbif_read(u32 reg) { u32 val; + regmap_read(ahub->regmap_apbif, reg, &val); return val; } @@ -560,10 +561,8 @@ static int tegra30_ahub_probe(struct platform_device *pdev) ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), GFP_KERNEL); - if (!ahub) { - dev_err(&pdev->dev, "Can't allocate tegra30_ahub\n"); + if (!ahub) return -ENOMEM; - } dev_set_drvdata(&pdev->dev, ahub); ahub->soc_data = soc_data; diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 8e55583aa104..b2b279c96029 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -385,7 +385,6 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL); if (!i2s) { - dev_err(&pdev->dev, "Can't allocate tegra30_i2s\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c index eead6e7f205b..0509902512cc 100644 --- a/sound/soc/tegra/tegra_alc5632.c +++ b/sound/soc/tegra/tegra_alc5632.c @@ -169,10 +169,8 @@ static int tegra_alc5632_probe(struct platform_device *pdev) alc5632 = devm_kzalloc(&pdev->dev, sizeof(struct tegra_alc5632), GFP_KERNEL); - if (!alc5632) { - dev_err(&pdev->dev, "Can't allocate tegra_alc5632\n"); + if (!alc5632) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c index a403db6d563e..c34a54d6e812 100644 --- a/sound/soc/tegra/tegra_max98090.c +++ b/sound/soc/tegra/tegra_max98090.c @@ -225,10 +225,8 @@ static int tegra_max98090_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_max98090), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_max98090\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c index 25b9fc03ba62..93a356802345 100644 --- a/sound/soc/tegra/tegra_rt5640.c +++ b/sound/soc/tegra/tegra_rt5640.c @@ -170,10 +170,8 @@ static int tegra_rt5640_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_rt5640), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_rt5640\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_sgtl5000.c b/sound/soc/tegra/tegra_sgtl5000.c index 4bbab098f50b..6dda01f69983 100644 --- a/sound/soc/tegra/tegra_sgtl5000.c +++ b/sound/soc/tegra/tegra_sgtl5000.c @@ -120,10 +120,8 @@ static int tegra_sgtl5000_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_sgtl5000), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_sgtl5000 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c index bdedd1028569..d0ab0026a4cd 100644 --- a/sound/soc/tegra/tegra_wm8753.c +++ b/sound/soc/tegra/tegra_wm8753.c @@ -128,10 +128,8 @@ static int tegra_wm8753_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8753), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_wm8753 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index 2013e9c4bba0..dbfb49298ae8 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -248,10 +248,8 @@ static int tegra_wm8903_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8903), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_wm8903 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c index 6492f8143ff1..c9cd22432627 100644 --- a/sound/soc/tegra/tegra_wm9712.c +++ b/sound/soc/tegra/tegra_wm9712.c @@ -77,10 +77,8 @@ static int tegra_wm9712_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm9712), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_wm9712 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c index 870f84ab5005..c9dcad9bb931 100644 --- a/sound/soc/tegra/trimslice.c +++ b/sound/soc/tegra/trimslice.c @@ -123,10 +123,8 @@ static int tegra_snd_trimslice_probe(struct platform_device *pdev) trimslice = devm_kzalloc(&pdev->dev, sizeof(struct tegra_trimslice), GFP_KERNEL); - if (!trimslice) { - dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n"); + if (!trimslice) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c index a8f705bb60dc..7912bf09dc4d 100644 --- a/sound/soc/txx9/txx9aclc.c +++ b/sound/soc/txx9/txx9aclc.c @@ -206,7 +206,7 @@ static void txx9aclc_dma_tasklet(unsigned long data) static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; - struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; void __iomem *base = drvdata->base; unsigned long flags; int ret = 0; @@ -340,7 +340,7 @@ static bool filter(struct dma_chan *chan, void *param) static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, struct txx9aclc_dmadata *dmadata) { - struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; struct txx9dmac_slave *ds = &dmadata->dma_slave; dma_cap_mask_t mask; @@ -392,6 +392,7 @@ static int txx9aclc_pcm_remove(struct snd_soc_platform *platform) for (i = 0; i < 2; i++) { struct txx9aclc_dmadata *dmadata = &dev->dmadata[i]; struct dma_chan *chan = dmadata->dma_chan; + if (chan) { dmadata->frag_count = -1; dmaengine_terminate_all(chan); diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c index ba9fc099cf67..b50f68a439ce 100644 --- a/sound/soc/ux500/mop500.c +++ b/sound/soc/ux500/mop500.c @@ -33,7 +33,6 @@ static struct snd_soc_dai_link mop500_dai_links[] = { .stream_name = "ab8500_0", .cpu_dai_name = "ux500-msp-i2s.1", .codec_dai_name = "ab8500-codec-dai.0", - .platform_name = "ux500-msp-i2s.1", .codec_name = "ab8500-codec.0", .init = mop500_ab8500_machine_init, .ops = mop500_ab8500_ops, @@ -43,7 +42,6 @@ static struct snd_soc_dai_link mop500_dai_links[] = { .stream_name = "ab8500_1", .cpu_dai_name = "ux500-msp-i2s.3", .codec_dai_name = "ab8500-codec-dai.1", - .platform_name = "ux500-msp-i2s.3", .codec_name = "ab8500-codec.0", .init = NULL, .ops = mop500_ab8500_ops, @@ -87,8 +85,6 @@ static int mop500_of_probe(struct platform_device *pdev, for (i = 0; i < 2; i++) { mop500_dai_links[i].cpu_of_node = msp_np[i]; mop500_dai_links[i].cpu_dai_name = NULL; - mop500_dai_links[i].platform_of_node = msp_np[i]; - mop500_dai_links[i].platform_name = NULL; mop500_dai_links[i].codec_of_node = codec_np; mop500_dai_links[i].codec_name = NULL; } diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index b343efd9be5b..ec5152aa3f6e 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -133,6 +133,7 @@ static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); u32 frame_length = MSP_FRAME_LEN_1; + prot_desc->frame_width = 0; switch (drvdata->slots) { @@ -482,7 +483,8 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, if ((drvdata->fmt & SND_SOC_DAIFMT_MASTER_MASK) && (drvdata->msp->f_bitclk > 19200000)) { /* If the bit-clock is higher than 19.2MHz, Vape should be - * run in 100% OPP. Only when bit-clock is used (MSP master) */ + * run in 100% OPP. Only when bit-clock is used (MSP master) + */ prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 100); drvdata->vape_opp_constraint = 1; diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 959d7b4edf56..bd5266aca0f1 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -604,7 +604,6 @@ int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) break; default: return -EINVAL; - break; } return 0; diff --git a/sound/soc/zte/Kconfig b/sound/soc/zte/Kconfig index 6d8a90d36315..75f67a5d23ea 100644 --- a/sound/soc/zte/Kconfig +++ b/sound/soc/zte/Kconfig @@ -15,3 +15,11 @@ config ZX_I2S help Say Y or M if you want to add support for codecs attached to the ZTE ZX I2S interface + +config ZX_TDM + tristate "ZTE ZX TDM Driver Support" + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + ZTE ZX TDM interface diff --git a/sound/soc/zte/Makefile b/sound/soc/zte/Makefile index 77768f5fd10c..1fc841acdfdd 100644 --- a/sound/soc/zte/Makefile +++ b/sound/soc/zte/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_ZX_SPDIF) += zx-spdif.o obj-$(CONFIG_ZX_I2S) += zx-i2s.o +obj-$(CONFIG_ZX_TDM) += zx-tdm.o diff --git a/sound/soc/zte/zx-tdm.c b/sound/soc/zte/zx-tdm.c new file mode 100644 index 000000000000..bd632cc503b3 --- /dev/null +++ b/sound/soc/zte/zx-tdm.c @@ -0,0 +1,461 @@ +/* + * ZTE's TDM driver + * + * Copyright (C) 2017 ZTE Ltd + * + * Author: Baoyou Xie <baoyou.xie@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#define REG_TIMING_CTRL 0x04 +#define REG_TX_FIFO_CTRL 0x0C +#define REG_RX_FIFO_CTRL 0x10 +#define REG_INT_EN 0x1C +#define REG_INT_STATUS 0x20 +#define REG_DATABUF 0x24 +#define REG_TS_MASK0 0x44 +#define REG_PROCESS_CTRL 0x54 + +#define FIFO_CTRL_TX_RST BIT(0) +#define FIFO_CTRL_RX_RST BIT(0) +#define DEAGULT_FIFO_THRES GENMASK(4, 2) + +#define FIFO_CTRL_TX_DMA_EN BIT(1) +#define FIFO_CTRL_RX_DMA_EN BIT(1) + +#define TX_FIFO_RST_MASK BIT(0) +#define RX_FIFO_RST_MASK BIT(0) + +#define FIFOCTRL_TX_FIFO_RST BIT(0) +#define FIFOCTRL_RX_FIFO_RST BIT(0) + +#define TXTH_MASK GENMASK(5, 2) +#define RXTH_MASK GENMASK(5, 2) + +#define FIFOCTRL_THRESHOLD(x) ((x) << 2) + +#define TIMING_MS_MASK BIT(1) +/* + * 00: 8 clk cycles every timeslot + * 01: 16 clk cycles every timeslot + * 10: 32 clk cycles every timeslot + */ +#define TIMING_SYNC_WIDTH_MASK GENMASK(6, 5) +#define TIMING_WIDTH_SHIFT 5 +#define TIMING_DEFAULT_WIDTH 0 +#define TIMING_TS_WIDTH(x) ((x) << TIMING_WIDTH_SHIFT) +#define TIMING_WIDTH_FACTOR 8 + +#define TIMING_MASTER_MODE BIT(21) +#define TIMING_LSB_FIRST BIT(20) +#define TIMING_TS_NUM(x) (((x) - 1) << 7) +#define TIMING_CLK_SEL_MASK GENMASK(2, 0) +#define TIMING_CLK_SEL_DEF BIT(2) + +#define PROCESS_TX_EN BIT(0) +#define PROCESS_RX_EN BIT(1) +#define PROCESS_TDM_EN BIT(2) +#define PROCESS_DISABLE_ALL 0 + +#define INT_DISABLE_ALL 0 +#define INT_STATUS_MASK GENMASK(6, 0) + +struct zx_tdm_info { + struct snd_dmaengine_dai_dma_data dma_playback; + struct snd_dmaengine_dai_dma_data dma_capture; + resource_size_t phy_addr; + void __iomem *regbase; + struct clk *dai_wclk; + struct clk *dai_pclk; + int master; + struct device *dev; +}; + +static inline u32 zx_tdm_readl(struct zx_tdm_info *tdm, u16 reg) +{ + return readl_relaxed(tdm->regbase + reg); +} + +static inline void zx_tdm_writel(struct zx_tdm_info *tdm, u16 reg, u32 val) +{ + writel_relaxed(val, tdm->regbase + reg); +} + +static void zx_tdm_tx_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_PROCESS_CTRL); + if (on) + val |= PROCESS_TX_EN | PROCESS_TDM_EN; + else + val &= ~(PROCESS_TX_EN | PROCESS_TDM_EN); + zx_tdm_writel(tdm, REG_PROCESS_CTRL, val); +} + +static void zx_tdm_rx_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_PROCESS_CTRL); + if (on) + val |= PROCESS_RX_EN | PROCESS_TDM_EN; + else + val &= ~(PROCESS_RX_EN | PROCESS_TDM_EN); + zx_tdm_writel(tdm, REG_PROCESS_CTRL, val); +} + +static void zx_tdm_tx_dma_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL); + val |= FIFO_CTRL_TX_RST | DEAGULT_FIFO_THRES; + if (on) + val |= FIFO_CTRL_TX_DMA_EN; + else + val &= ~FIFO_CTRL_TX_DMA_EN; + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val); +} + +static void zx_tdm_rx_dma_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL); + val |= FIFO_CTRL_RX_RST | DEAGULT_FIFO_THRES; + if (on) + val |= FIFO_CTRL_RX_DMA_EN; + else + val &= ~FIFO_CTRL_RX_DMA_EN; + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val); +} + +#define ZX_TDM_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define ZX_TDM_FMTBIT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_MU_LAW | \ + SNDRV_PCM_FORMAT_A_LAW) + +static int zx_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_tdm); + zx_tdm->dma_playback.addr = zx_tdm->phy_addr + REG_DATABUF; + zx_tdm->dma_playback.maxburst = 16; + zx_tdm->dma_capture.addr = zx_tdm->phy_addr + REG_DATABUF; + zx_tdm->dma_capture.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &zx_tdm->dma_playback, + &zx_tdm->dma_capture); + return 0; +} + +static int zx_tdm_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long val; + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val &= ~(TIMING_SYNC_WIDTH_MASK | TIMING_MS_MASK); + val |= TIMING_DEFAULT_WIDTH << TIMING_WIDTH_SHIFT; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tdm->master = 1; + val |= TIMING_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + tdm->master = 0; + val &= ~TIMING_MASTER_MODE; + break; + default: + dev_err(cpu_dai->dev, "Unknown master/slave format\n"); + return -EINVAL; + } + + + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + + return 0; +} + +static int zx_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data; + unsigned int ts_width = TIMING_DEFAULT_WIDTH; + unsigned int ch_num = 32; + unsigned int mask = 0; + unsigned int ret = 0; + unsigned long val; + + dma_data = snd_soc_dai_get_dma_data(socdai, substream); + dma_data->addr_width = ch_num >> 3; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_MU_LAW: + case SNDRV_PCM_FORMAT_A_LAW: + case SNDRV_PCM_FORMAT_S16_LE: + ts_width = 1; + break; + default: + ts_width = 0; + dev_err(socdai->dev, "Unknown data format\n"); + return -EINVAL; + } + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val |= TIMING_TS_WIDTH(ts_width) | TIMING_TS_NUM(1); + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + zx_tdm_writel(tdm, REG_TS_MASK0, mask); + + if (tdm->master) + ret = clk_set_rate(tdm->dai_wclk, + params_rate(params) * TIMING_WIDTH_FACTOR * ch_num); + + return ret; +} + +static int zx_tdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + unsigned int val; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (capture) { + val = zx_tdm_readl(zx_tdm, REG_RX_FIFO_CTRL); + val |= FIFOCTRL_RX_FIFO_RST; + zx_tdm_writel(zx_tdm, REG_RX_FIFO_CTRL, val); + + zx_tdm_rx_dma_en(zx_tdm, true); + } else { + val = zx_tdm_readl(zx_tdm, REG_TX_FIFO_CTRL); + val |= FIFOCTRL_TX_FIFO_RST; + zx_tdm_writel(zx_tdm, REG_TX_FIFO_CTRL, val); + + zx_tdm_tx_dma_en(zx_tdm, true); + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (capture) + zx_tdm_rx_en(zx_tdm, true); + else + zx_tdm_tx_en(zx_tdm, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (capture) + zx_tdm_rx_dma_en(zx_tdm, false); + else + zx_tdm_tx_dma_en(zx_tdm, false); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (capture) + zx_tdm_rx_en(zx_tdm, false); + else + zx_tdm_tx_en(zx_tdm, false); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_tdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + int ret; + + ret = clk_prepare_enable(zx_tdm->dai_wclk); + if (ret) + return ret; + + ret = clk_prepare_enable(zx_tdm->dai_pclk); + if (ret) { + clk_disable_unprepare(zx_tdm->dai_wclk); + return ret; + } + + return 0; +} + +static void zx_tdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_tdm->dai_pclk); + clk_disable_unprepare(zx_tdm->dai_wclk); +} + +static struct snd_soc_dai_ops zx_tdm_dai_ops = { + .trigger = zx_tdm_trigger, + .hw_params = zx_tdm_hw_params, + .set_fmt = zx_tdm_set_fmt, + .startup = zx_tdm_startup, + .shutdown = zx_tdm_shutdown, +}; + +static const struct snd_soc_component_driver zx_tdm_component = { + .name = "zx-tdm", +}; + +static void zx_tdm_init_state(struct zx_tdm_info *tdm) +{ + unsigned int val; + + zx_tdm_writel(tdm, REG_PROCESS_CTRL, PROCESS_DISABLE_ALL); + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val |= TIMING_LSB_FIRST; + val &= ~TIMING_CLK_SEL_MASK; + val |= TIMING_CLK_SEL_DEF; + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + + zx_tdm_writel(tdm, REG_INT_EN, INT_DISABLE_ALL); + /* + * write INT_STATUS register to clear it. + */ + zx_tdm_writel(tdm, REG_INT_STATUS, INT_STATUS_MASK); + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, FIFOCTRL_RX_FIFO_RST); + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, FIFOCTRL_TX_FIFO_RST); + + val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL); + val &= ~(RXTH_MASK | RX_FIFO_RST_MASK); + val |= FIFOCTRL_THRESHOLD(8); + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val); + + val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL); + val &= ~(TXTH_MASK | TX_FIFO_RST_MASK); + val |= FIFOCTRL_THRESHOLD(8); + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val); +} + +static struct snd_soc_dai_driver zx_tdm_dai = { + .name = "zx-tdm-dai", + .id = 0, + .probe = zx_tdm_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 4, + .rates = ZX_TDM_RATES, + .formats = ZX_TDM_FMTBIT, + }, + .capture = { + .channels_min = 1, + .channels_max = 4, + .rates = ZX_TDM_RATES, + .formats = ZX_TDM_FMTBIT, + }, + .ops = &zx_tdm_dai_ops, +}; + +static int zx_tdm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct of_phandle_args out_args; + unsigned int dma_reg_offset; + struct zx_tdm_info *zx_tdm; + unsigned int dma_mask; + struct resource *res; + struct regmap *regmap_sysctrl; + int ret; + + zx_tdm = devm_kzalloc(&pdev->dev, sizeof(*zx_tdm), GFP_KERNEL); + if (!zx_tdm) + return -ENOMEM; + + zx_tdm->dev = dev; + + zx_tdm->dai_wclk = devm_clk_get(&pdev->dev, "wclk"); + if (IS_ERR(zx_tdm->dai_wclk)) { + dev_err(&pdev->dev, "Fail to get wclk\n"); + return PTR_ERR(zx_tdm->dai_wclk); + } + + zx_tdm->dai_pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(zx_tdm->dai_pclk)) { + dev_err(&pdev->dev, "Fail to get pclk\n"); + return PTR_ERR(zx_tdm->dai_pclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_tdm->phy_addr = res->start; + zx_tdm->regbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(zx_tdm->regbase)) + return PTR_ERR(zx_tdm->regbase); + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "zte,tdm-dma-sysctrl", 2, 0, &out_args); + if (ret) { + dev_err(&pdev->dev, "Fail to get zte,tdm-dma-sysctrl\n"); + return ret; + } + + dma_reg_offset = out_args.args[0]; + dma_mask = out_args.args[1]; + regmap_sysctrl = syscon_node_to_regmap(out_args.np); + if (IS_ERR(regmap_sysctrl)) { + of_node_put(out_args.np); + return PTR_ERR(regmap_sysctrl); + } + + regmap_update_bits(regmap_sysctrl, dma_reg_offset, dma_mask, dma_mask); + of_node_put(out_args.np); + + zx_tdm_init_state(zx_tdm); + platform_set_drvdata(pdev, zx_tdm); + + ret = devm_snd_soc_register_component(&pdev->dev, &zx_tdm_component, + &zx_tdm_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_tdm_dt_ids[] = { + { .compatible = "zte,zx296718-tdm", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_tdm_dt_ids); + +static struct platform_driver tdm_driver = { + .probe = zx_tdm_probe, + .driver = { + .name = "zx-tdm", + .of_match_table = zx_tdm_dt_ids, + }, +}; +module_platform_driver(tdm_driver); + +MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>"); +MODULE_DESCRIPTION("ZTE TDM DAI driver"); +MODULE_LICENSE("GPL v2"); |