diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-04-02 15:50:04 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-04-02 15:50:04 -0700 |
commit | 848960e576dafc8ed54c691b2f70b92e1fdea9ba (patch) | |
tree | 27ea80003da03b81f0b188d3712f0194745126d9 /sound/soc/bcm/bcm63xx-i2s-whistler.c | |
parent | bc3b3f4bfbded031a11c4284106adddbfacd05bb (diff) | |
parent | 5c6cd7021a05a02fcf37f360592d7c18d4d807fb (diff) |
Merge tag 'sound-5.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai:
"This became again a busy development cycle. There are few ALSA core
updates (merely API cleanups and sparse fixes), with the majority of
other changes are found in ASoC scene.
Here are some highlights:
ALSA core:
- More helper macros for sparse warning fixes (e.g. bitwise types)
- Slight optimization of PCM OSS locks
- Make common handling for PCM / compress buffers (for SOF)
ASoC:
- Lots of code refactoring and modernization for (still ongoing)
componentization works
- Conversion of SND_SOC_ALL_CODECS to use imply
- Continued refactoring and fixing of the Intel SOF/SST support,
including the initial (but still incomplete) SoundWire support
- SoundWire and more advanced clocking support for Realtek RT5682
- Support for amlogic GX, Meson 8, Meson 8B and T9015 DAC, Broadcom
DSL/PON, Ingenic JZ4760 and JZ4770, Realtek RL6231, and TI TAS2563
and TLV320ADCX140
HD-audio:
- Optimizations in HDMI jack handling
- A few new quirks and fixups for Realtek codecs
USB-audio:
- Delayed registration support
- New quirks for Motu, Kingston, Presonus"
* tag 'sound-5.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (415 commits)
ALSA: usb-audio: Fix case when USB MIDI interface has more than one extra endpoint descriptor
Revert "ALSA: uapi: Drop asound.h inclusion from asoc.h"
ALSA: hda/realtek - Remove now-unnecessary XPS 13 headphone noise fixups
ALSA: hda/realtek - Set principled PC Beep configuration for ALC256
ALSA: doc: Document PC Beep Hidden Register on Realtek ALC256
ALSA: hda/realtek - a fake key event is triggered by running shutup
ALSA: hda: default enable CA0132 DSP support
ASoC: amd: acp3x-pcm-dma: clean up two indentation issues
ASoC: tlv320adcx140: Remove undocumented property
ASoC: Intel: sof_sdw: Add Volteer support with RT5682 SNDW helper function
ASoC: Intel: common: add match table for TGL RT5682 SoundWire driver
ASoC: Intel: boards: add sof_sdw machine driver
ASoC: Intel: soc-acpi: update topology and driver name for SoundWire platforms
ASoC: rt5682: move DAI clock registry to I2S mode
ASoC: pxa: magician: convert to use i2c_new_client_device()
ASoC: SOF: Intel: hda-ctrl: add reset cycle before parsing capabilities
Asoc: SOF: Intel: hda: check SoundWire wakeen interrupt in irq thread
ASoC: SOF: Intel: hda: add WAKEEN interrupt support for SoundWire
ASoC: SOF: Intel: hda: add parameter to control SoundWire clock stop quirks
ASoC: SOF: Intel: hda: merge IPC, stream and SoundWire interrupt handlers
...
Diffstat (limited to 'sound/soc/bcm/bcm63xx-i2s-whistler.c')
-rw-r--r-- | sound/soc/bcm/bcm63xx-i2s-whistler.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/sound/soc/bcm/bcm63xx-i2s-whistler.c b/sound/soc/bcm/bcm63xx-i2s-whistler.c new file mode 100644 index 000000000000..246a57ac6679 --- /dev/null +++ b/sound/soc/bcm/bcm63xx-i2s-whistler.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// linux/sound/bcm/bcm63xx-i2s-whistler.c +// BCM63xx whistler i2s driver +// Copyright (c) 2020 Broadcom Corporation +// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com> + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bcm63xx-i2s.h" + +#define DRV_NAME "brcm-i2s" + +static bool brcm_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TX_CFG ... I2S_TX_DESC_IFF_LEN: + case I2S_TX_CFG_2 ... I2S_RX_DESC_IFF_LEN: + case I2S_RX_CFG_2 ... I2S_REG_MAX: + return true; + default: + return false; + } +} + +static bool brcm_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TX_CFG ... I2S_REG_MAX: + return true; + default: + return false; + } +} + +static bool brcm_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TX_CFG: + case I2S_TX_IRQ_CTL: + case I2S_TX_DESC_IFF_ADDR: + case I2S_TX_DESC_IFF_LEN: + case I2S_TX_DESC_OFF_ADDR: + case I2S_TX_DESC_OFF_LEN: + case I2S_TX_CFG_2: + case I2S_RX_CFG: + case I2S_RX_IRQ_CTL: + case I2S_RX_DESC_OFF_ADDR: + case I2S_RX_DESC_OFF_LEN: + case I2S_RX_DESC_IFF_LEN: + case I2S_RX_DESC_IFF_ADDR: + case I2S_RX_CFG_2: + return true; + default: + return false; + } +} + +static const struct regmap_config brcm_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_REG_MAX, + .writeable_reg = brcm_i2s_wr_reg, + .readable_reg = brcm_i2s_rd_reg, + .volatile_reg = brcm_i2s_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int bcm63xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); + + ret = clk_set_rate(i2s_priv->i2s_clk, params_rate(params)); + if (ret < 0) + dev_err(i2s_priv->dev, + "Can't set sample rate, err: %d\n", ret); + + return ret; +} + +static int bcm63xx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + unsigned int slavemode; + struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap_i2s = i2s_priv->regmap_i2s; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap_i2s, I2S_TX_CFG, + I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | + I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, + I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | + I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE); + regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 0); + regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 0); + regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 1); + + /* TX and RX block each have an independent bit to indicate + * if it is generating the clock for the I2S bus. The bus + * clocks need to be generated from either the TX or RX block, + * but not both + */ + regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode); + if (slavemode & I2S_RX_SLAVE_MODE_MASK) + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_MASTER_MODE); + else + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_SLAVE_MODE); + } else { + regmap_update_bits(regmap_i2s, I2S_RX_CFG, + I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | + I2S_RX_CLOCK_ENABLE, + I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | + I2S_RX_CLOCK_ENABLE); + regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 0); + regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 0); + regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 1); + + regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode); + if (slavemode & I2S_TX_SLAVE_MODE_MASK) + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, 0); + else + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, + I2S_RX_SLAVE_MODE); + } + return 0; +} + +static void bcm63xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + unsigned int enabled, slavemode; + struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap_i2s = i2s_priv->regmap_i2s; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap_i2s, I2S_TX_CFG, + I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | + I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, 0); + regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 1); + regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 4); + regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 4); + + regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode); + slavemode = slavemode & I2S_TX_SLAVE_MODE_MASK; + if (!slavemode) { + regmap_read(regmap_i2s, I2S_RX_CFG, &enabled); + enabled = enabled & I2S_RX_ENABLE_MASK; + if (enabled) + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, + I2S_RX_MASTER_MODE); + } + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_SLAVE_MODE); + } else { + regmap_update_bits(regmap_i2s, I2S_RX_CFG, + I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | + I2S_RX_CLOCK_ENABLE, 0); + regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 1); + regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 4); + regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 4); + + regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode); + slavemode = slavemode & I2S_RX_SLAVE_MODE_MASK; + if (!slavemode) { + regmap_read(regmap_i2s, I2S_TX_CFG, &enabled); + enabled = enabled & I2S_TX_ENABLE_MASK; + if (enabled) + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_MASTER_MODE); + } + + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, I2S_RX_SLAVE_MODE); + } +} + +static const struct snd_soc_dai_ops bcm63xx_i2s_dai_ops = { + .startup = bcm63xx_i2s_startup, + .shutdown = bcm63xx_i2s_shutdown, + .hw_params = bcm63xx_i2s_hw_params, +}; + +static struct snd_soc_dai_driver bcm63xx_i2s_dai = { + .name = DRV_NAME, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &bcm63xx_i2s_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver bcm63xx_i2s_component = { + .name = "bcm63xx", +}; + +static int bcm63xx_i2s_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + void __iomem *regs; + struct resource *r_mem, *region; + struct bcm_i2s_priv *i2s_priv; + struct regmap *regmap_i2s; + struct clk *i2s_clk; + + i2s_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_priv), GFP_KERNEL); + if (!i2s_priv) + return -ENOMEM; + + i2s_clk = devm_clk_get(&pdev->dev, "i2sclk"); + if (IS_ERR(i2s_clk)) { + dev_err(&pdev->dev, "%s: cannot get a brcm clock: %ld\n", + __func__, PTR_ERR(i2s_clk)); + return PTR_ERR(i2s_clk); + } + + r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r_mem) { + dev_err(&pdev->dev, "Unable to get register resource.\n"); + return -ENODEV; + } + + region = devm_request_mem_region(&pdev->dev, r_mem->start, + resource_size(r_mem), DRV_NAME); + if (!region) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + return -EBUSY; + } + + regs = devm_ioremap_resource(&pdev->dev, r_mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + return ret; + } + + regmap_i2s = devm_regmap_init_mmio(&pdev->dev, + regs, &brcm_i2s_regmap_config); + if (IS_ERR(regmap_i2s)) + return PTR_ERR(regmap_i2s); + + regmap_update_bits(regmap_i2s, I2S_MISC_CFG, + I2S_PAD_LVL_LOOP_DIS_MASK, + I2S_PAD_LVL_LOOP_DIS_ENABLE); + + ret = devm_snd_soc_register_component(&pdev->dev, + &bcm63xx_i2s_component, + &bcm63xx_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register the dai\n"); + return ret; + } + + i2s_priv->dev = &pdev->dev; + i2s_priv->i2s_clk = i2s_clk; + i2s_priv->regmap_i2s = regmap_i2s; + dev_set_drvdata(&pdev->dev, i2s_priv); + + ret = bcm63xx_soc_platform_probe(pdev, i2s_priv); + if (ret) + dev_err(&pdev->dev, "failed to register the pcm\n"); + + return ret; +} + +static int bcm63xx_i2s_dev_remove(struct platform_device *pdev) +{ + bcm63xx_soc_platform_remove(pdev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id snd_soc_bcm_audio_match[] = { + {.compatible = "brcm,bcm63xx-i2s"}, + { } +}; +#endif + +static struct platform_driver bcm63xx_i2s_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(snd_soc_bcm_audio_match), + }, + .probe = bcm63xx_i2s_dev_probe, + .remove = bcm63xx_i2s_dev_remove, +}; + +module_platform_driver(bcm63xx_i2s_driver); + +MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom DSL XPON ASOC I2S Interface"); +MODULE_LICENSE("GPL v2"); |