diff --git a/Documentation/devicetree/bindings/sound/adi,adau17x1.txt b/Documentation/devicetree/bindings/sound/adi,adau17x1.txt index 8dbce0e18dda266b3c824e1c34c5ad9b744ca2d2..1447dec281252cb1f71374742e54331ebebd7c5b 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau17x1.txt +++ b/Documentation/devicetree/bindings/sound/adi,adau17x1.txt @@ -13,6 +13,11 @@ Required properties: - reg: The i2c address. Value depends on the state of ADDR0 and ADDR1, as wired in hardware. +Optional properties: + - clock-names: If provided must be "mclk". + - clocks: phandle + clock-specifiers for the clock that provides + the audio master clock for the device. + Examples: #include @@ -20,5 +25,8 @@ Examples: adau1361@38 { compatible = "adi,adau1761"; reg = <0x38>; + + clock-names = "mclk"; + clocks = <&audio_clock>; }; }; diff --git a/Documentation/devicetree/bindings/sound/adi,adau7002.txt b/Documentation/devicetree/bindings/sound/adi,adau7002.txt new file mode 100644 index 0000000000000000000000000000000000000000..f144ee1abf8550faf4c088c79bb9edbd714368b7 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/adi,adau7002.txt @@ -0,0 +1,19 @@ +Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter + +Required properties: + + - compatible: Must be "adi,adau7002" + +Optional properties: + + - IOVDD-supply: Phandle and specifier for the power supply providing the IOVDD + supply as covered in Documentation/devicetree/bindings/regulator/regulator.txt + + If this property is not present it is assumed that the supply pin is + hardwired to always on. + +Example: + adau7002: pdm-to-i2s { + compatible = "adi,adau7002"; + IOVDD-supply = <&supply>; + }; diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index d55a42297d4914a6da0c86266810c9b873af9c15..58ab4c0fe761393cd614e4fa81d52a5d0fa4627f 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -14,6 +14,7 @@ #define _WM_ARIZONA_CORE_H #include +#include #include #include #include @@ -148,8 +149,17 @@ struct arizona { uint16_t dac_comp_coeff; uint8_t dac_comp_enabled; struct mutex dac_comp_lock; + + struct blocking_notifier_head notifier; }; +static inline int arizona_call_notifiers(struct arizona *arizona, + unsigned long event, + void *data) +{ + return blocking_notifier_call_chain(&arizona->notifier, event, data); +} + int arizona_clk32k_enable(struct arizona *arizona); int arizona_clk32k_disable(struct arizona *arizona); diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h index c0abcdc1147083f9faa8a036357a5468429ff00a..cee8c00f3d3e6f69c4c899f2f24bbbf6fbb30c13 100644 --- a/include/sound/compress_driver.h +++ b/include/sound/compress_driver.h @@ -68,6 +68,7 @@ struct snd_compr_runtime { * @ops: pointer to DSP callbacks * @runtime: pointer to runtime structure * @device: device pointer + * @error_work: delayed work used when closing the stream due to an error * @direction: stream direction, playback/recording * @metadata_set: metadata set flag, true when set * @next_track: has userspace signal next track transition, true when set @@ -78,6 +79,7 @@ struct snd_compr_stream { struct snd_compr_ops *ops; struct snd_compr_runtime *runtime; struct snd_compr *device; + struct delayed_work error_work; enum snd_compr_direction direction; bool metadata_set; bool next_track; @@ -187,4 +189,7 @@ static inline void snd_compr_drain_notify(struct snd_compr_stream *stream) wake_up(&stream->runtime->sleep); } +int snd_compr_stop_error(struct snd_compr_stream *stream, + snd_pcm_state_t state); + #endif diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 9b3334be9df23c8f48a8768ee8b723318252873f..2c498488af6c5aebe12ec4a2cd7b432076b788af 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -67,6 +67,8 @@ struct snd_compr_file { struct snd_compr_stream stream; }; +static void error_delayed_work(struct work_struct *work); + /* * a note on stream states used: * we use following states in the compressed core @@ -123,6 +125,9 @@ static int snd_compr_open(struct inode *inode, struct file *f) snd_card_unref(compr->card); return -ENOMEM; } + + INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); + data->stream.ops = compr->ops; data->stream.direction = dirn; data->stream.private_data = compr->private_data; @@ -153,6 +158,8 @@ static int snd_compr_free(struct inode *inode, struct file *f) struct snd_compr_file *data = f->private_data; struct snd_compr_runtime *runtime = data->stream.runtime; + cancel_delayed_work_sync(&data->stream.error_work); + switch (runtime->state) { case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_DRAINING: @@ -237,6 +244,15 @@ snd_compr_ioctl_avail(struct snd_compr_stream *stream, unsigned long arg) avail = snd_compr_calc_avail(stream, &ioctl_avail); ioctl_avail.avail = avail; + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + if (copy_to_user((__u64 __user *)arg, &ioctl_avail, sizeof(ioctl_avail))) return -EFAULT; @@ -346,11 +362,13 @@ static ssize_t snd_compr_read(struct file *f, char __user *buf, switch (stream->runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_PREPARED: - case SNDRV_PCM_STATE_XRUN: case SNDRV_PCM_STATE_SUSPENDED: case SNDRV_PCM_STATE_DISCONNECTED: retval = -EBADFD; goto out; + case SNDRV_PCM_STATE_XRUN: + retval = -EPIPE; + goto out; } avail = snd_compr_get_avail(stream); @@ -399,10 +417,16 @@ static unsigned int snd_compr_poll(struct file *f, poll_table *wait) stream = &data->stream; mutex_lock(&stream->device->lock); - if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_XRUN: retval = snd_compr_get_poll(stream) | POLLERR; goto out; + default: + break; } + poll_wait(f, &stream->runtime->sleep, wait); avail = snd_compr_get_avail(stream); @@ -697,6 +721,45 @@ static int snd_compr_stop(struct snd_compr_stream *stream) return retval; } +static void error_delayed_work(struct work_struct *work) +{ + struct snd_compr_stream *stream; + + stream = container_of(work, struct snd_compr_stream, error_work.work); + + mutex_lock(&stream->device->lock); + + stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + wake_up(&stream->runtime->sleep); + + mutex_unlock(&stream->device->lock); +} + +/* + * snd_compr_stop_error: Report a fatal error on a stream + * @stream: pointer to stream + * @state: state to transition the stream to + * + * Stop the stream and set its state. + * + * Should be called with compressed device lock held. + */ +int snd_compr_stop_error(struct snd_compr_stream *stream, + snd_pcm_state_t state) +{ + if (stream->runtime->state == state) + return 0; + + stream->runtime->state = state; + + pr_debug("Changing state to: %d\n", state); + + queue_delayed_work(system_power_efficient_wq, &stream->error_work, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_compr_stop_error); + static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) { int ret; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f3fb98f0a995bb41dd473ccc4443164e404c9883..a9faf37110648dfc096e6a20b1dfb4486cad8528 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -32,6 +32,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ADAU1977_SPI if SPI_MASTER select SND_SOC_ADAU1977_I2C if I2C select SND_SOC_ADAU1701 if I2C + select SND_SOC_ADAU7002 select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C @@ -269,8 +270,12 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate +config SND_SOC_ADAU_UTILS + tristate + config SND_SOC_ADAU1373 tristate + select SND_SOC_ADAU_UTILS config SND_SOC_ADAU1701 tristate "Analog Devices ADAU1701 CODEC" @@ -280,6 +285,7 @@ config SND_SOC_ADAU1701 config SND_SOC_ADAU17X1 tristate select SND_SOC_SIGMADSP_REGMAP + select SND_SOC_ADAU_UTILS config SND_SOC_ADAU1761 tristate @@ -322,6 +328,9 @@ config SND_SOC_ADAU1977_I2C select SND_SOC_ADAU1977 select REGMAP_I2C +config SND_SOC_ADAU7002 + tristate "Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter" + config SND_SOC_ADAV80X tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 0f548fd34ca3fd9e8a1b080cd2d38a480c3c5212..e37815d55c10ff1d30169a52d6d07bedabc79838 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -7,6 +7,7 @@ snd-soc-ad193x-spi-objs := ad193x-spi.o snd-soc-ad193x-i2c-objs := ad193x-i2c.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-adau-utils-objs := adau-utils.o snd-soc-adau1373-objs := adau1373.o snd-soc-adau1701-objs := adau1701.o snd-soc-adau17x1-objs := adau17x1.o @@ -19,6 +20,7 @@ snd-soc-adau1781-spi-objs := adau1781-spi.o snd-soc-adau1977-objs := adau1977.o snd-soc-adau1977-spi-objs := adau1977-spi.o snd-soc-adau1977-i2c-objs := adau1977-i2c.o +snd-soc-adau7002-objs := adau7002.o snd-soc-adav80x-objs := adav80x.o snd-soc-adav801-objs := adav801.o snd-soc-adav803-objs := adav803.o @@ -220,6 +222,7 @@ obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAU_UTILS) += snd-soc-adau-utils.o obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o @@ -232,6 +235,7 @@ obj-$(CONFIG_SND_SOC_ADAU1781_SPI) += snd-soc-adau1781-spi.o obj-$(CONFIG_SND_SOC_ADAU1977) += snd-soc-adau1977.o obj-$(CONFIG_SND_SOC_ADAU1977_SPI) += snd-soc-adau1977-spi.o obj-$(CONFIG_SND_SOC_ADAU1977_I2C) += snd-soc-adau1977-i2c.o +obj-$(CONFIG_SND_SOC_ADAU7002) += snd-soc-adau7002.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o diff --git a/sound/soc/codecs/adau-utils.c b/sound/soc/codecs/adau-utils.c new file mode 100644 index 0000000000000000000000000000000000000000..19d6a6f41b12797ff8cd768f68901d6e22d1a0ce --- /dev/null +++ b/sound/soc/codecs/adau-utils.c @@ -0,0 +1,61 @@ +/* + * Shared helper functions for devices from the ADAU family + * + * Copyright 2011-2016 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include + +#include "adau-utils.h" + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]) +{ + unsigned int r, n, m, i, j; + unsigned int div; + + if (!freq_out) { + r = 0; + n = 0; + m = 0; + div = 0; + } else { + if (freq_out % freq_in != 0) { + div = DIV_ROUND_UP(freq_in, 13500000); + freq_in /= div; + r = freq_out / freq_in; + i = freq_out % freq_in; + j = gcd(i, freq_in); + n = i / j; + m = freq_in / j; + div--; + } else { + r = freq_out / freq_in; + n = 0; + m = 0; + div = 0; + } + if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) + return -EINVAL; + } + + regs[0] = m >> 8; + regs[1] = m & 0xff; + regs[2] = n >> 8; + regs[3] = n & 0xff; + regs[4] = (r << 3) | (div << 1); + if (m != 0) + regs[4] |= 1; /* Fractional mode */ + + return 0; +} +EXPORT_SYMBOL_GPL(adau_calc_pll_cfg); + +MODULE_DESCRIPTION("ASoC ADAU audio CODECs shared helper functions"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/adau-utils.h b/sound/soc/codecs/adau-utils.h new file mode 100644 index 0000000000000000000000000000000000000000..939b5f37762f1fde4884738e0f11f37039214093 --- /dev/null +++ b/sound/soc/codecs/adau-utils.h @@ -0,0 +1,7 @@ +#ifndef SOUND_SOC_CODECS_ADAU_PLL_H +#define SOUND_SOC_CODECS_ADAU_PLL_H + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]); + +#endif diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index fe1353a797b9449e7b8186771c8476b2cf746021..1556b360fa1516b2c5048b980274e72d8540a228 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -23,6 +23,7 @@ #include #include "adau1373.h" +#include "adau-utils.h" struct adau1373_dai { unsigned int clk_src; @@ -1254,7 +1255,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, { struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); unsigned int dpll_div = 0; - unsigned int x, r, n, m, i, j, mode; + uint8_t pll_regs[5]; + int ret; switch (pll_id) { case ADAU1373_PLL1: @@ -1295,27 +1297,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, dpll_div++; } - if (freq_out % freq_in != 0) { - /* fout = fin * (r + (n/m)) / x */ - x = DIV_ROUND_UP(freq_in, 13500000); - freq_in /= x; - r = freq_out / freq_in; - i = freq_out % freq_in; - j = gcd(i, freq_in); - n = i / j; - m = freq_in / j; - x--; - mode = 1; - } else { - /* fout = fin / r */ - r = freq_out / freq_in; - n = 0; - m = 0; - x = 0; - mode = 0; - } - - if (r < 2 || r > 8 || x > 3 || m > 0xffff || n > 0xffff) + ret = adau_calc_pll_cfg(freq_in, freq_out, pll_regs); + if (ret) return -EINVAL; if (dpll_div) { @@ -1330,12 +1313,11 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, regmap_write(adau1373->regmap, ADAU1373_DPLL_CTRL(pll_id), (source << 4) | dpll_div); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), (m >> 8) & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), m & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), (n >> 8) & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), n & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), - (r << 3) | (x << 1) | mode); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), pll_regs[0]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), pll_regs[1]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), pll_regs[2]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), pll_regs[3]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), pll_regs[4]); /* Set sysclk to pll_rate / 4 */ regmap_update_bits(adau1373->regmap, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09); diff --git a/sound/soc/codecs/adau1761-i2c.c b/sound/soc/codecs/adau1761-i2c.c index 8de010f758cd8d5007cf661a590f6e7c473da385..9e7f257f17f87591f380152553fc4c41725b1e2e 100644 --- a/sound/soc/codecs/adau1761-i2c.c +++ b/sound/soc/codecs/adau1761-i2c.c @@ -31,7 +31,7 @@ static int adau1761_i2c_probe(struct i2c_client *client, static int adau1761_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); + adau17x1_remove(&client->dev); return 0; } diff --git a/sound/soc/codecs/adau1761-spi.c b/sound/soc/codecs/adau1761-spi.c index d9171245bd9f11a177ce2429c0cbf50ef159af9d..a0b214be759a8700d3ad81eb098ed111b4b4d6c8 100644 --- a/sound/soc/codecs/adau1761-spi.c +++ b/sound/soc/codecs/adau1761-spi.c @@ -48,7 +48,7 @@ static int adau1761_spi_probe(struct spi_device *spi) static int adau1761_spi_remove(struct spi_device *spi) { - snd_soc_unregister_codec(&spi->dev); + adau17x1_remove(&spi->dev); return 0; } diff --git a/sound/soc/codecs/adau1781-i2c.c b/sound/soc/codecs/adau1781-i2c.c index 06cbca84cf02150ab0a83bbf114955ffe6ea62d1..7b9d1802d1598e9c170d28df0d28014c06642742 100644 --- a/sound/soc/codecs/adau1781-i2c.c +++ b/sound/soc/codecs/adau1781-i2c.c @@ -31,7 +31,7 @@ static int adau1781_i2c_probe(struct i2c_client *client, static int adau1781_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); + adau17x1_remove(&client->dev); return 0; } diff --git a/sound/soc/codecs/adau1781-spi.c b/sound/soc/codecs/adau1781-spi.c index 3d965a01b99cee318da30a080baebc8a060852cc..9b233544d2e8f34a49791237162cb8f965b453d5 100644 --- a/sound/soc/codecs/adau1781-spi.c +++ b/sound/soc/codecs/adau1781-spi.c @@ -48,7 +48,7 @@ static int adau1781_spi_probe(struct spi_device *spi) static int adau1781_spi_remove(struct spi_device *spi) { - snd_soc_unregister_codec(&spi->dev); + adau17x1_remove(&spi->dev); return 0; } diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index fcf05b254ecd5c37a91dbff5a3ccc3de1069e2f0..439aa3ff1f99cd517f28988f80fe2b34c6489ab2 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "sigmadsp.h" #include "adau17x1.h" +#include "adau-utils.h" static const char * const adau17x1_capture_mixer_boost_text[] = { "Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3", @@ -302,6 +304,116 @@ bool adau17x1_has_dsp(struct adau *adau) } EXPORT_SYMBOL_GPL(adau17x1_has_dsp); +static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = dai->codec; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + int ret; + + if (freq_in < 8000000 || freq_in > 27000000) + return -EINVAL; + + ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs); + if (ret < 0) + return ret; + + /* The PLL register is 6 bytes long and can only be written at once. */ + ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + if (ret) + return ret; + + adau->pll_freq = freq_out; + + return 0; +} + +static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec); + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + bool is_pll; + bool was_pll; + + switch (clk_id) { + case ADAU17X1_CLK_SRC_MCLK: + is_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL_AUTO: + if (!adau->mclk) + return -EINVAL; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: + is_pll = true; + break; + default: + return -EINVAL; + } + + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_MCLK: + was_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL: + case ADAU17X1_CLK_SRC_PLL_AUTO: + was_pll = true; + break; + default: + return -EINVAL; + } + + adau->sysclk = freq; + + if (is_pll != was_pll) { + if (is_pll) { + snd_soc_dapm_add_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } else { + snd_soc_dapm_del_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } + } + + adau->clk_src = clk_id; + + return 0; +} + +static int adau17x1_auto_pll(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct adau *adau = snd_soc_dai_get_drvdata(dai); + unsigned int pll_rate; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + return adau17x1_set_dai_pll(dai, ADAU17X1_PLL, ADAU17X1_PLL_SRC_MCLK, + clk_get_rate(adau->mclk), pll_rate); +} + static int adau17x1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -311,10 +423,19 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, unsigned int freq; int ret; - if (adau->clk_src == ADAU17X1_CLK_SRC_PLL) + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_PLL_AUTO: + ret = adau17x1_auto_pll(dai, params); + if (ret) + return ret; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: freq = adau->pll_freq; - else + break; + default: freq = adau->sysclk; + break; + } if (freq % params_rate(params) != 0) return -EINVAL; @@ -386,93 +507,6 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, ADAU17X1_SERIAL_PORT1_DELAY_MASK, val); } -static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, - int source, unsigned int freq_in, unsigned int freq_out) -{ - struct snd_soc_codec *codec = dai->codec; - struct adau *adau = snd_soc_codec_get_drvdata(codec); - unsigned int r, n, m, i, j; - unsigned int div; - int ret; - - if (freq_in < 8000000 || freq_in > 27000000) - return -EINVAL; - - if (!freq_out) { - r = 0; - n = 0; - m = 0; - div = 0; - } else { - if (freq_out % freq_in != 0) { - div = DIV_ROUND_UP(freq_in, 13500000); - freq_in /= div; - r = freq_out / freq_in; - i = freq_out % freq_in; - j = gcd(i, freq_in); - n = i / j; - m = freq_in / j; - div--; - } else { - r = freq_out / freq_in; - n = 0; - m = 0; - div = 0; - } - if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) - return -EINVAL; - } - - adau->pll_regs[0] = m >> 8; - adau->pll_regs[1] = m & 0xff; - adau->pll_regs[2] = n >> 8; - adau->pll_regs[3] = n & 0xff; - adau->pll_regs[4] = (r << 3) | (div << 1); - if (m != 0) - adau->pll_regs[4] |= 1; /* Fractional mode */ - - /* The PLL register is 6 bytes long and can only be written at once. */ - ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, - adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); - if (ret) - return ret; - - adau->pll_freq = freq_out; - - return 0; -} - -static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, - int clk_id, unsigned int freq, int dir) -{ - struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec); - struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); - - switch (clk_id) { - case ADAU17X1_CLK_SRC_MCLK: - case ADAU17X1_CLK_SRC_PLL: - break; - default: - return -EINVAL; - } - - adau->sysclk = freq; - - if (adau->clk_src != clk_id) { - if (clk_id == ADAU17X1_CLK_SRC_PLL) { - snd_soc_dapm_add_routes(dapm, - &adau17x1_dapm_pll_route, 1); - } else { - snd_soc_dapm_del_routes(dapm, - &adau17x1_dapm_pll_route, 1); - } - } - - adau->clk_src = clk_id; - - return 0; -} - static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { @@ -857,6 +891,10 @@ int adau17x1_add_routes(struct snd_soc_codec *codec) ret = snd_soc_dapm_add_routes(dapm, adau17x1_no_dsp_dapm_routes, ARRAY_SIZE(adau17x1_no_dsp_dapm_routes)); } + + if (adau->clk_src != ADAU17X1_CLK_SRC_MCLK) + snd_soc_dapm_add_routes(dapm, &adau17x1_dapm_pll_route, 1); + return ret; } EXPORT_SYMBOL_GPL(adau17x1_add_routes); @@ -879,6 +917,7 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, const char *firmware_name) { struct adau *adau; + int ret; if (IS_ERR(regmap)) return PTR_ERR(regmap); @@ -887,6 +926,30 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, if (!adau) return -ENOMEM; + adau->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(adau->mclk)) { + if (PTR_ERR(adau->mclk) != -ENOENT) + return PTR_ERR(adau->mclk); + /* Clock is optional (for the driver) */ + adau->mclk = NULL; + } else if (adau->mclk) { + adau->clk_src = ADAU17X1_CLK_SRC_PLL_AUTO; + + /* + * Any valid PLL output rate will work at this point, use one + * that is likely to be chosen later as well. The register will + * be written when the PLL is powered up for the first time. + */ + ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024, + adau->pll_regs); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(adau->mclk); + if (ret) + return ret; + } + adau->regmap = regmap; adau->switch_mode = switch_mode; adau->type = type; @@ -910,6 +973,16 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, } EXPORT_SYMBOL_GPL(adau17x1_probe); +void adau17x1_remove(struct device *dev) +{ + struct adau *adau = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + if (adau->mclk) + clk_disable_unprepare(adau->mclk); +} +EXPORT_SYMBOL_GPL(adau17x1_remove); + MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code"); MODULE_AUTHOR("Lars-Peter Clausen "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h index 5ae87a084d97e24a6aa243ba6cb1afc45290ac43..bf04b7efee4084cf77fa934eadf6ab7a3506b1ad 100644 --- a/sound/soc/codecs/adau17x1.h +++ b/sound/soc/codecs/adau17x1.h @@ -22,13 +22,18 @@ enum adau17x1_pll_src { }; enum adau17x1_clk_src { + /* Automatically configure PLL based on the sample rate */ + ADAU17X1_CLK_SRC_PLL_AUTO, ADAU17X1_CLK_SRC_MCLK, ADAU17X1_CLK_SRC_PLL, }; +struct clk; + struct adau { unsigned int sysclk; unsigned int pll_freq; + struct clk *mclk; enum adau17x1_clk_src clk_src; enum adau17x1_type type; @@ -52,6 +57,7 @@ int adau17x1_add_routes(struct snd_soc_codec *codec); int adau17x1_probe(struct device *dev, struct regmap *regmap, enum adau17x1_type type, void (*switch_mode)(struct device *dev), const char *firmware_name); +void adau17x1_remove(struct device *dev); int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, enum adau17x1_micbias_voltage micbias); bool adau17x1_readable_register(struct device *dev, unsigned int reg); diff --git a/sound/soc/codecs/adau7002.c b/sound/soc/codecs/adau7002.c new file mode 100644 index 0000000000000000000000000000000000000000..9df72c6adcca9890cf1e9ff6f7d80e0b5a88e9a9 --- /dev/null +++ b/sound/soc/codecs/adau7002.c @@ -0,0 +1,80 @@ +/* + * ADAU7002 Stereo PDM-to-I2S/TDM converter driver + * + * Copyright 2014-2016 Analog Devices + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include + +static const struct snd_soc_dapm_widget adau7002_widgets[] = { + SND_SOC_DAPM_INPUT("PDM_DAT"), + SND_SOC_DAPM_REGULATOR_SUPPLY("IOVDD", 0, 0), +}; + +static const struct snd_soc_dapm_route adau7002_routes[] = { + { "Capture", NULL, "PDM_DAT" }, + { "Capture", NULL, "IOVDD" }, +}; + +static struct snd_soc_dai_driver adau7002_dai = { + .name = "adau7002-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 20, + }, +}; + +static const struct snd_soc_codec_driver adau7002_codec_driver = { + .dapm_widgets = adau7002_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau7002_widgets), + .dapm_routes = adau7002_routes, + .num_dapm_routes = ARRAY_SIZE(adau7002_routes), +}; + +static int adau7002_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &adau7002_codec_driver, + &adau7002_dai, 1); +} + +static int adau7002_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id adau7002_dt_ids[] = { + { .compatible = "adi,adau7002", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau7002_dt_ids); +#endif + +static struct platform_driver adau7002_driver = { + .driver = { + .name = "adau7002", + .of_match_table = of_match_ptr(adau7002_dt_ids), + }, + .probe = adau7002_probe, + .remove = adau7002_remove, +}; +module_platform_driver(adau7002_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADAU7002 Stereo PDM-to-I2S/TDM Converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c index 5013d2ba0c10a968045d83e5e5cef6e223e55436..97798d250f08689eff1093979e2ce9bb116f66eb 100644 --- a/sound/soc/codecs/ak4613.c +++ b/sound/soc/codecs/ak4613.c @@ -437,15 +437,25 @@ static struct snd_soc_dai_driver ak4613_dai = { .symmetric_rates = 1, }; -static int ak4613_resume(struct snd_soc_codec *codec) +static int ak4613_suspend(struct snd_soc_codec *codec) { struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); + return 0; +} + +static int ak4613_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_cache_only(regmap, false); return regcache_sync(regmap); } static struct snd_soc_codec_driver soc_codec_dev_ak4613 = { + .suspend = ak4613_suspend, .resume = ak4613_resume, .set_bias_level = ak4613_set_bias_level, .controls = ak4613_snd_controls, diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 4d8b9e49e8d6dcb47f7c6516561fd98d847bfdf9..cc941d66ec3d2f4d1d75104f46c1329ebb9abb8a 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -523,15 +523,23 @@ static struct snd_soc_dai_driver ak4642_dai = { .symmetric_rates = 1, }; -static int ak4642_resume(struct snd_soc_codec *codec) +static int ak4642_suspend(struct snd_soc_codec *codec) { struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); - regcache_sync(regmap); return 0; } +static int ak4642_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_cache_only(regmap, false); + regcache_sync(regmap); + return 0; +} static int ak4642_probe(struct snd_soc_codec *codec) { struct ak4642_priv *priv = snd_soc_codec_get_drvdata(codec); @@ -544,6 +552,7 @@ static int ak4642_probe(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_ak4642 = { .probe = ak4642_probe, + .suspend = ak4642_suspend, .resume = ak4642_resume, .set_bias_level = ak4642_set_bias_level, .controls = ak4642_snd_controls, diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 664a8c044ffb1c3abd58afe458ba538a775d9a47..7f9ab92ffa912b8d5b91aace43119f822f18b422 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -324,6 +324,17 @@ int arizona_init_gpio(struct snd_soc_codec *codec) } EXPORT_SYMBOL_GPL(arizona_init_gpio); +int arizona_init_notifiers(struct snd_soc_codec *codec) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + BLOCKING_INIT_NOTIFIER_HEAD(&arizona->notifier); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_notifiers); + const char * const arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS] = { "None", "Tone Generator 1", @@ -2573,6 +2584,30 @@ int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(arizona_lhpf_coeff_put); +int arizona_register_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb, + int (*notify)(struct notifier_block *nb, + unsigned long action, void *data)) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + nb->notifier_call = notify; + + return blocking_notifier_chain_register(&arizona->notifier, nb); +} +EXPORT_SYMBOL_GPL(arizona_register_notifier); + +int arizona_unregister_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + return blocking_notifier_chain_unregister(&arizona->notifier, nb); +} +EXPORT_SYMBOL_GPL(arizona_unregister_notifier); + MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support"); MODULE_AUTHOR("Mark Brown "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index ce0531b8c6329ed4af58246f5eaa115193090b66..46862af7665ec866f89ce4ca114fe13fa80ed96b 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -63,6 +63,9 @@ #define ARIZONA_DVFS_SR1_RQ 0x001 #define ARIZONA_DVFS_ADSP1_RQ 0x100 +/* Notifier events */ +#define ARIZONA_NOTIFY_VOICE_TRIGGER 0x1 + struct arizona; struct wm_adsp; @@ -95,6 +98,10 @@ struct arizona_priv { bool dvfs_cached; }; +struct arizona_voice_trigger_info { + int core; +}; + #define ARIZONA_NUM_MIXER_INPUTS 104 extern const unsigned int arizona_mixer_tlv[]; @@ -306,6 +313,7 @@ extern int arizona_set_fll(struct arizona_fll *fll, int source, extern int arizona_init_spk(struct snd_soc_codec *codec); extern int arizona_init_gpio(struct snd_soc_codec *codec); extern int arizona_init_mono(struct snd_soc_codec *codec); +extern int arizona_init_notifiers(struct snd_soc_codec *codec); extern int arizona_free_spk(struct snd_soc_codec *codec); @@ -317,4 +325,13 @@ int arizona_set_output_mode(struct snd_soc_codec *codec, int output, extern bool arizona_input_analog(struct snd_soc_codec *codec, int shift); extern const char *arizona_sample_rate_val_to_name(unsigned int rate_val); + +extern int arizona_register_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb, + int (*notify)(struct notifier_block *nb, + unsigned long action, + void *data)); +extern int arizona_unregister_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb); + #endif diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c index 5ec5a682d186971daa09d532d0b4627806eecaa3..bbc8cf18ded094067e5090f850111a9d06e2e69e 100644 --- a/sound/soc/codecs/cs47l24.c +++ b/sound/soc/codecs/cs47l24.c @@ -1067,6 +1067,7 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) { struct cs47l24_priv *priv = data; struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; int serviced = 0; int i, ret; @@ -1074,6 +1075,12 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); if (ret != -ENODEV) serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } } if (!serviced) { @@ -1096,6 +1103,7 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); arizona_init_mono(codec); + arizona_init_notifiers(codec); ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, "ADSP2 Compressed IRQ", cs47l24_adsp2_irq, diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index d54f1b46c9ec08107427ff0f6bf81bf4670103bb..1aac0e1122cf27adb6d7e7a01124a1973e753b27 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -2223,6 +2223,7 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data) { struct wm5110_priv *priv = data; struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; int serviced = 0; int i, ret; @@ -2230,6 +2231,12 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data) ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); if (ret != -ENODEV) serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } } if (!serviced) { @@ -2252,6 +2259,7 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); arizona_init_mono(codec); + arizona_init_notifiers(codec); ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, "ADSP2 Compressed IRQ", wm5110_adsp2_irq, diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index a07bd7c2c587dd9d9d0135015b1aa4b578307e93..f6eb2e5b2c077df2c7e754389fe5bcae3308b7d3 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -2366,13 +2366,15 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, dsp->running = false; regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | - ADSP2_START, 0); + 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_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); list_for_each_entry(ctl, &dsp->ctl_list, list) ctl->enabled = 0; @@ -3037,12 +3039,8 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, buf = compr->buf; - if (!compr->buf) { - ret = -ENXIO; - goto out; - } - - if (compr->buf->error) { + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); ret = -EIO; goto out; } @@ -3060,8 +3058,12 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, */ if (buf->avail < wm_adsp_compr_frag_words(compr)) { ret = wm_adsp_buffer_get_error(buf); - if (ret < 0) + if (ret < 0) { + if (compr->buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); goto out; + } ret = wm_adsp_buffer_reenable_irq(buf); if (ret < 0) { @@ -3156,11 +3158,10 @@ static int wm_adsp_compr_read(struct wm_adsp_compr *compr, adsp_dbg(dsp, "Requested read of %zu bytes\n", count); - if (!compr->buf) - return -ENXIO; - - if (compr->buf->error) + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(compr->stream, SNDRV_PCM_STATE_XRUN); return -EIO; + } count /= WM_ADSP_DATA_WORD_SIZE;