This adds support for using the Soundwire interrupt mechanism to handle CS42L42 chip interrupts. Soundwire interrupts are used if a hard INT line is not declared. Wake-from-clock-stop is not used. The CS42L42 has limited wake capability, but clock-stop is already disabled when a snd_soc_jack is registered to prevent the host controller issuing a bus-reset on exit from clock stop mode, which would clear the interrupt status and break jack and button detection. Signed-off-by: Richard Fitzgerald <rf@xxxxxxxxxxxxxxxxxxxxx> --- sound/soc/codecs/cs42l42-sdw.c | 90 +++++++++++++++++++++++++++++++++- sound/soc/codecs/cs42l42.h | 3 ++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs42l42-sdw.c b/sound/soc/codecs/cs42l42-sdw.c index ed69a0a44d8c..1bdeed93587d 100644 --- a/sound/soc/codecs/cs42l42-sdw.c +++ b/sound/soc/codecs/cs42l42-sdw.c @@ -14,6 +14,7 @@ #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw_type.h> +#include <linux/workqueue.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -26,6 +27,8 @@ /* Register addresses are offset when sent over Soundwire */ #define CS42L42_SDW_ADDR_OFFSET 0x8000 +#define CS42L42_SDW_GEN_INT_STATUS_1 0xc0 +#define CS42L42_SDW_GEN_INT_MASK_1 0xc1 #define CS42L42_SDW_MEM_ACCESS_STATUS 0xd0 #define CS42L42_SDW_MEM_READ_DATA 0xd8 @@ -33,6 +36,11 @@ #define CS42L42_SDW_CMD_IN_PROGRESS BIT(2) #define CS42L42_SDW_RDATA_RDY BIT(0) +#define CS42L42_SDW_M_SCP_IMP_DEF1 BIT(0) +#define CS42L42_GEN_INT_CASCADE SDW_SCP_INT1_IMPL_DEF + +#define CS42L42_SDW_INT_MASK_CODEC_IRQ BIT(0) + #define CS42L42_DELAYED_READ_POLL_US 1 #define CS42L42_DELAYED_READ_TIMEOUT_US 100 @@ -306,6 +314,13 @@ static void cs42l42_sdw_init(struct sdw_slave *peripheral) /* Disable internal logic that makes clock-stop conditional */ regmap_clear_bits(cs42l42->regmap, CS42L42_PWR_CTL3, CS42L42_SW_CLK_STP_STAT_SEL_MASK); + /* Enable Soundwire interrupts */ + if (!cs42l42->irq) { + dev_dbg(cs42l42->dev, "Using Soundwire interrupts\n"); + sdw_write_no_pm(peripheral, CS42L42_SDW_GEN_INT_MASK_1, + CS42L42_SDW_INT_MASK_CODEC_IRQ); + } + /* * pm_runtime is needed to control bus manager suspend, and to * recover from an unattach_request when the manager suspends. @@ -319,6 +334,49 @@ static void cs42l42_sdw_init(struct sdw_slave *peripheral) pm_runtime_idle(cs42l42->dev); } +static int cs42l42_sdw_interrupt(struct sdw_slave *peripheral, + struct sdw_slave_intr_status *status) +{ + struct cs42l42_private *cs42l42 = dev_get_drvdata(&peripheral->dev); + + /* Soundwire core holds our pm_runtime when calling this function. */ + + dev_dbg(cs42l42->dev, "int control_port=0x%x\n", status->control_port); + + if ((status->control_port & CS42L42_GEN_INT_CASCADE) == 0) + return 0; + + /* + * Clear and mask until it has been handled. The read of GEN_INT_STATUS_1 + * is required as per the Soundwire spec for interrupt status bits to clear. + */ + sdw_write_no_pm(peripheral, CS42L42_SDW_GEN_INT_MASK_1, 0); + sdw_read_no_pm(peripheral, CS42L42_SDW_GEN_INT_STATUS_1); + sdw_write_no_pm(peripheral, CS42L42_SDW_GEN_INT_STATUS_1, 0xFF); + queue_work(system_power_efficient_wq, &cs42l42->sdw_irq_work); + + /* Prevent host controller suspending before we handle the interrupt */ + pm_runtime_get_noresume(cs42l42->dev); + + return 0; +} + +static void cs42l42_sdw_irq_work(struct work_struct *work) +{ + struct cs42l42_private *cs42l42 = container_of(work, + struct cs42l42_private, + sdw_irq_work); + + cs42l42_irq_thread(-1, cs42l42); + + /* unmask interrupt */ + if (!cs42l42->sdw_irq_no_unmask) + sdw_write_no_pm(cs42l42->sdw_peripheral, CS42L42_SDW_GEN_INT_MASK_1, + CS42L42_SDW_INT_MASK_CODEC_IRQ); + + pm_runtime_put_autosuspend(cs42l42->dev); +} + static int cs42l42_sdw_read_prop(struct sdw_slave *peripheral) { struct cs42l42_private *cs42l42 = dev_get_drvdata(&peripheral->dev); @@ -334,6 +392,14 @@ static int cs42l42_sdw_read_prop(struct sdw_slave *peripheral) prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + /* + * CS42L42 doesn't have a SDW_SCP_INT1_IMPL_DEF mask bit but it must be + * set in scp_int1_mask else the Soundwire framework won't notify us + * when the IMPL_DEF interrupt is asserted. + */ + if (!cs42l42->irq) + prop->scp_int1_mask |= SDW_SCP_INT1_IMPL_DEF; + /* DP1 - capture */ ports[0].num = CS42L42_SDW_CAPTURE_PORT, ports[0].type = SDW_DPN_FULL, @@ -403,6 +469,7 @@ static int __maybe_unused cs42l42_sdw_clk_stop(struct sdw_slave *peripheral, static const struct sdw_slave_ops cs42l42_sdw_ops = { .read_prop = cs42l42_sdw_read_prop, + .interrupt_callback = cs42l42_sdw_interrupt, .update_status = cs42l42_sdw_update_status, .bus_config = cs42l42_sdw_bus_config, #ifdef DEBUG @@ -473,6 +540,11 @@ static int __maybe_unused cs42l42_sdw_runtime_resume(struct device *dev) regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); regcache_sync(cs42l42->regmap); + /* Re-enable Soundwire interrupts */ + if (!cs42l42->irq) + sdw_write_no_pm(cs42l42->sdw_peripheral, CS42L42_SDW_GEN_INT_MASK_1, + CS42L42_SDW_INT_MASK_CODEC_IRQ); + return 0; } @@ -495,6 +567,11 @@ static int __maybe_unused cs42l42_sdw_resume(struct device *dev) cs42l42_resume_restore(dev); + /* Re-enable Soundwire interrupts */ + if (!cs42l42->irq) + sdw_write_no_pm(cs42l42->sdw_peripheral, CS42L42_SDW_GEN_INT_MASK_1, + CS42L42_SDW_INT_MASK_CODEC_IRQ); + return 0; } @@ -546,6 +623,7 @@ static int cs42l42_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi component_drv->dapm_routes = cs42l42_sdw_audio_map; component_drv->num_dapm_routes = ARRAY_SIZE(cs42l42_sdw_audio_map); + INIT_WORK(&cs42l42->sdw_irq_work, cs42l42_sdw_irq_work); cs42l42->dev = dev; cs42l42->regmap = regmap; cs42l42->sdw_peripheral = peripheral; @@ -562,8 +640,18 @@ static int cs42l42_sdw_remove(struct sdw_slave *peripheral) { struct cs42l42_private *cs42l42 = dev_get_drvdata(&peripheral->dev); - /* Resume so that cs42l42_remove() can access registers */ + /* Resume so that we can access registers */ pm_runtime_get_sync(cs42l42->dev); + + /* Disable Soundwire interrupts */ + if (!cs42l42->irq) { + cs42l42->sdw_irq_no_unmask = true; + cancel_work_sync(&cs42l42->sdw_irq_work); + sdw_write_no_pm(peripheral, CS42L42_SDW_GEN_INT_MASK_1, 0); + sdw_read_no_pm(peripheral, CS42L42_SDW_GEN_INT_STATUS_1); + sdw_write_no_pm(peripheral, CS42L42_SDW_GEN_INT_STATUS_1, 0xFF); + } + cs42l42_common_remove(cs42l42); pm_runtime_put(cs42l42->dev); pm_runtime_disable(cs42l42->dev); diff --git a/sound/soc/codecs/cs42l42.h b/sound/soc/codecs/cs42l42.h index 038db45d95b3..b29126d218c4 100644 --- a/sound/soc/codecs/cs42l42.h +++ b/sound/soc/codecs/cs42l42.h @@ -19,6 +19,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/soundwire/sdw.h> +#include <linux/workqueue.h> #include <sound/jack.h> #include <sound/cs42l42.h> #include <sound/soc-component.h> @@ -32,6 +33,7 @@ struct cs42l42_private { struct completion pdn_done; struct snd_soc_jack *jack; struct sdw_slave *sdw_peripheral; + struct work_struct sdw_irq_work; struct mutex irq_lock; int irq; int pll_config; @@ -52,6 +54,7 @@ struct cs42l42_private { bool hp_adc_up_pending; bool suspended; bool init_done; + bool sdw_irq_no_unmask; }; extern const struct regmap_config cs42l42_regmap; -- 2.30.2