Commit 90f6a2a2 authored by Richard Fitzgerald's avatar Richard Fitzgerald Committed by Mark Brown

ASoC: cs42l42: Add SoundWire support

This adds support for using CS42L42 as a SoundWire device.

SoundWire-specifics are kept separate from the I2S implementation as
much as possible, aiming to limit the risk of breaking the I2C+I2S
support.

There are some important differences in the silicon behaviour between
I2S and SoundWire mode that are reflected in the implementation:

- ASP (I2S) most not be used in SoundWire mode because the two interfaces
  share pins.

- The SoundWire capture (record) port only supports 1 channel. It does
  not have left-to-right duplication like the ASP.

- DP2 can only be prepared if the HP has powered-up. DP1 can only be
  prepared if the ADC has powered-up. (This ordering restriction does
  not exist for ASPs.) The SoundWire core port-prepare step is
  triggered by the DAI-link prepare(). This happens before the
  codec DAI prepare() or the DAPM sequence so these cannot be used
  to enable HP/ADC. Instead the HP/ADC enable/disable are done during
  the port_prep callback.

- The SRCs are an integral part of the audio chain but in silicon their
  power control is linked to the ASP. There is no equivalent power link
  to SoundWire DPs so the driver must take "manual" control of SRC power.

- The SoundWire control registers occupy the lower part of the SoundWire
  address space so cs42l42 registers are offset by 0x8000 (non-paged) in
  SoundWire mode.

- Register addresses are 8-bit paged in I2C mode but 16-bit unpaged in
  SoundWire.

- Special procedures are needed on register read/writes to (a) ensure
  that the previous internal bus transaction has completed, and
  (b) handle delayed read results, when the read value could not be
  returned within the SoundWire read command.

There are also some differences in driver implementation between I2S
and SoundWire operation:

- CS42L42 I2S does not runtime_suspend, but runtime_suspend/resume support
  has been added into the driver in SoundWire mode as the most convenient
  way to power-up the bus manager and to handle the unattach_request
  condition, though the CS42L42 chip does not itself suspend or resume.

- Intel SoundWire host controllers have a low-power clock-stop mode that
  requires resetting all peripherals when resuming. This means that the
  interrupt registers will be reset in between the interrupt being
  generated and the interrupt being handled, and since the interrupt
  status is debounced, these values may not be accurate immediately,
  and may cause spurious unplug events before settling.

- As in I2S mode, the PLL is only used while audio is active because
  of clocking quirks in the silicon. For SoundWire the cs42l42_pll_config()
  is deferred until the DAI prepare(), to allow the cs42l42_bus_config()
  callback to set the SCLK.
Signed-off-by: default avatarRichard Fitzgerald <rf@opensource.cirrus.com>
Signed-off-by: default avatarStefan Binding <sbinding@opensource.cirrus.com>
Reviewed-by: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20230127165111.3010960-7-sbinding@opensource.cirrus.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent 0998816a
...@@ -68,6 +68,7 @@ config SND_SOC_ALL_CODECS ...@@ -68,6 +68,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_CS35L45_I2C imply SND_SOC_CS35L45_I2C
imply SND_SOC_CS35L45_SPI imply SND_SOC_CS35L45_SPI
imply SND_SOC_CS42L42 imply SND_SOC_CS42L42
imply SND_SOC_CS42L42_SDW
imply SND_SOC_CS42L51_I2C imply SND_SOC_CS42L51_I2C
imply SND_SOC_CS42L52 imply SND_SOC_CS42L52
imply SND_SOC_CS42L56 imply SND_SOC_CS42L56
...@@ -704,6 +705,13 @@ config SND_SOC_CS42L42 ...@@ -704,6 +705,13 @@ config SND_SOC_CS42L42
select REGMAP_I2C select REGMAP_I2C
select SND_SOC_CS42L42_CORE select SND_SOC_CS42L42_CORE
config SND_SOC_CS42L42_SDW
tristate "Cirrus Logic CS42L42 CODEC on Soundwire"
depends on SOUNDWIRE
select SND_SOC_CS42L42_CORE
help
Enable support for Cirrus Logic CS42L42 codec with Soundwire control
config SND_SOC_CS42L51 config SND_SOC_CS42L51
tristate tristate
......
...@@ -66,6 +66,7 @@ snd-soc-cs35l45-spi-objs := cs35l45-spi.o ...@@ -66,6 +66,7 @@ snd-soc-cs35l45-spi-objs := cs35l45-spi.o
snd-soc-cs35l45-i2c-objs := cs35l45-i2c.o snd-soc-cs35l45-i2c-objs := cs35l45-i2c.o
snd-soc-cs42l42-objs := cs42l42.o snd-soc-cs42l42-objs := cs42l42.o
snd-soc-cs42l42-i2c-objs := cs42l42-i2c.o snd-soc-cs42l42-i2c-objs := cs42l42-i2c.o
snd-soc-cs42l42-sdw-objs := cs42l42-sdw.o
snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
snd-soc-cs42l52-objs := cs42l52.o snd-soc-cs42l52-objs := cs42l52.o
...@@ -428,6 +429,7 @@ obj-$(CONFIG_SND_SOC_CS35L45_SPI) += snd-soc-cs35l45-spi.o ...@@ -428,6 +429,7 @@ obj-$(CONFIG_SND_SOC_CS35L45_SPI) += snd-soc-cs35l45-spi.o
obj-$(CONFIG_SND_SOC_CS35L45_I2C) += snd-soc-cs35l45-i2c.o obj-$(CONFIG_SND_SOC_CS35L45_I2C) += snd-soc-cs35l45-i2c.o
obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o
obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o
obj-$(CONFIG_SND_SOC_CS42L42_SDW) += snd-soc-cs42l42-sdw.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o
obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o
......
This diff is collapsed.
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/acpi.h> #include <linux/acpi.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h>
...@@ -525,6 +526,10 @@ static const struct snd_soc_dapm_widget cs42l42_dapm_widgets[] = { ...@@ -525,6 +526,10 @@ static const struct snd_soc_dapm_widget cs42l42_dapm_widgets[] = {
/* Playback/Capture Requirements */ /* Playback/Capture Requirements */
SND_SOC_DAPM_SUPPLY("SCLK", CS42L42_ASP_CLK_CFG, CS42L42_ASP_SCLK_EN_SHIFT, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("SCLK", CS42L42_ASP_CLK_CFG, CS42L42_ASP_SCLK_EN_SHIFT, 0, NULL, 0),
/* Soundwire SRC power control */
SND_SOC_DAPM_PGA("DACSRC", CS42L42_PWR_CTL2, CS42L42_DAC_SRC_PDNB_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_PGA("ADCSRC", CS42L42_PWR_CTL2, CS42L42_ADC_SRC_PDNB_SHIFT, 0, NULL, 0),
}; };
static const struct snd_soc_dapm_route cs42l42_audio_map[] = { static const struct snd_soc_dapm_route cs42l42_audio_map[] = {
...@@ -1660,9 +1665,11 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) ...@@ -1660,9 +1665,11 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data)
unsigned int current_button_status; unsigned int current_button_status;
unsigned int i; unsigned int i;
pm_runtime_get_sync(cs42l42->dev);
mutex_lock(&cs42l42->irq_lock); mutex_lock(&cs42l42->irq_lock);
if (cs42l42->suspended || !cs42l42->init_done) { if (cs42l42->suspended || !cs42l42->init_done) {
mutex_unlock(&cs42l42->irq_lock); mutex_unlock(&cs42l42->irq_lock);
pm_runtime_put_autosuspend(cs42l42->dev);
return IRQ_NONE; return IRQ_NONE;
} }
...@@ -1765,6 +1772,8 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) ...@@ -1765,6 +1772,8 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data)
} }
mutex_unlock(&cs42l42->irq_lock); mutex_unlock(&cs42l42->irq_lock);
pm_runtime_mark_last_busy(cs42l42->dev);
pm_runtime_put_autosuspend(cs42l42->dev);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -2144,6 +2153,9 @@ int cs42l42_suspend(struct device *dev) ...@@ -2144,6 +2153,9 @@ int cs42l42_suspend(struct device *dev)
u8 save_regs[ARRAY_SIZE(cs42l42_shutdown_seq)]; u8 save_regs[ARRAY_SIZE(cs42l42_shutdown_seq)];
int i, ret; int i, ret;
if (!cs42l42->init_done)
return 0;
/* /*
* Wait for threaded irq handler to be idle and stop it processing * Wait for threaded irq handler to be idle and stop it processing
* future interrupts. This ensures a safe disable if the interrupt * future interrupts. This ensures a safe disable if the interrupt
...@@ -2204,6 +2216,9 @@ int cs42l42_resume(struct device *dev) ...@@ -2204,6 +2216,9 @@ int cs42l42_resume(struct device *dev)
struct cs42l42_private *cs42l42 = dev_get_drvdata(dev); struct cs42l42_private *cs42l42 = dev_get_drvdata(dev);
int ret; int ret;
if (!cs42l42->init_done)
return 0;
/* /*
* If jack was unplugged and re-plugged during suspend it could * If jack was unplugged and re-plugged during suspend it could
* have changed type but the tip-sense state hasn't changed. * have changed type but the tip-sense state hasn't changed.
...@@ -2388,6 +2403,18 @@ int cs42l42_init(struct cs42l42_private *cs42l42) ...@@ -2388,6 +2403,18 @@ int cs42l42_init(struct cs42l42_private *cs42l42)
if (ret != 0) if (ret != 0)
goto err_shutdown; goto err_shutdown;
/*
* SRC power is linked to ASP power so doesn't work in Soundwire mode.
* Override it and use DAPM to control SRC power for Soundwire.
*/
if (cs42l42->sdw_peripheral) {
regmap_update_bits(cs42l42->regmap, CS42L42_PWR_CTL2,
CS42L42_SRC_PDN_OVERRIDE_MASK |
CS42L42_DAC_SRC_PDNB_MASK |
CS42L42_ADC_SRC_PDNB_MASK,
CS42L42_SRC_PDN_OVERRIDE_MASK);
}
/* Setup headset detection */ /* Setup headset detection */
cs42l42_setup_hs_type_detect(cs42l42); cs42l42_setup_hs_type_detect(cs42l42);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/soundwire/sdw.h>
#include <sound/jack.h> #include <sound/jack.h>
#include <sound/cs42l42.h> #include <sound/cs42l42.h>
#include <sound/soc-component.h> #include <sound/soc-component.h>
...@@ -30,11 +31,13 @@ struct cs42l42_private { ...@@ -30,11 +31,13 @@ struct cs42l42_private {
struct gpio_desc *reset_gpio; struct gpio_desc *reset_gpio;
struct completion pdn_done; struct completion pdn_done;
struct snd_soc_jack *jack; struct snd_soc_jack *jack;
struct sdw_slave *sdw_peripheral;
struct mutex irq_lock; struct mutex irq_lock;
int devid; int devid;
int irq; int irq;
int pll_config; int pll_config;
u32 sclk; u32 sclk;
u32 sample_rate;
u32 bclk_ratio; u32 bclk_ratio;
u8 plug_state; u8 plug_state;
u8 hs_type; u8 hs_type;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment