Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: cpcap: Implement jack headset detection

Merge series from Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>:

cpcap audio codec found on cpcap PMIC supports headset detection
and PTT button through its 3.5 mm jack. This series implements
support for those capabilities.

+205 -1
+6
Documentation/devicetree/bindings/mfd/motorola-cpcap.txt
··· 31 31 Required properties for the audio-codec subnode: 32 32 33 33 - #sound-dai-cells = <1>; 34 + - interrupts : should contain jack detection interrupts, with headset 35 + detect interrupt matching "hs" and microphone bias 2 36 + detect interrupt matching "mb2" in interrupt-names. 37 + - interrupt-names : Contains "hs", "mb2" 34 38 35 39 The audio-codec provides two DAIs. The first one is connected to the 36 40 Stereo HiFi DAC and the second one is connected to the Voice DAC. ··· 56 52 57 53 audio-codec { 58 54 #sound-dai-cells = <1>; 55 + interrupts-extended = <&cpcap 9 0>, <&cpcap 10 0>; 56 + interrupt-names = "hs", "mb2"; 59 57 60 58 /* HiFi */ 61 59 port@0 {
+199 -1
sound/soc/codecs/cpcap.c
··· 11 11 #include <linux/module.h> 12 12 #include <linux/regmap.h> 13 13 #include <linux/platform_device.h> 14 + #include <linux/regulator/consumer.h> 14 15 #include <linux/mfd/motorola-cpcap.h> 15 16 #include <sound/core.h> 17 + #include <linux/input.h> 18 + #include <sound/jack.h> 16 19 #include <sound/soc.h> 17 20 #include <sound/tlv.h> 21 + 22 + /* Register 8 - CPCAP_REG_INTS1 --- Interrupt Sense 1 */ 23 + #define CPCAP_BIT_HS_S 9 /* Headset */ 24 + #define CPCAP_BIT_MB2_S 10 /* Mic Bias2 */ 25 + 26 + /* Register 9 - CPCAP_REG_INTS2 --- Interrupt Sense 2 */ 27 + #define CPCAP_BIT_PTT_S 11 /* Push To Talk */ 18 28 19 29 /* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */ 20 30 #define CPCAP_BIT_AUDIO_LOW_PWR 6 ··· 270 260 int codec_clk_id; 271 261 int codec_freq; 272 262 int codec_format; 263 + struct regulator *vaudio; 264 + int hsirq; 265 + int mb2irq; 266 + struct snd_soc_jack jack; 273 267 }; 274 268 275 269 static int cpcap_st_workaround(struct snd_soc_dapm_widget *w, ··· 1640 1626 return 0; 1641 1627 } 1642 1628 1629 + static irqreturn_t cpcap_hs_irq_thread(int irq, void *data) 1630 + { 1631 + struct snd_soc_component *component = data; 1632 + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); 1633 + struct regmap *regmap = cpcap->regmap; 1634 + int status = 0; 1635 + int mask = SND_JACK_HEADSET; 1636 + int val; 1637 + 1638 + if (!regmap_test_bits(regmap, CPCAP_REG_INTS1, BIT(CPCAP_BIT_HS_S))) { 1639 + val = BIT(CPCAP_BIT_MB_ON2) | BIT(CPCAP_BIT_PTT_CMP_EN); 1640 + regmap_update_bits(regmap, CPCAP_REG_TXI, val, val); 1641 + 1642 + val = BIT(CPCAP_BIT_ST_HS_CP_EN); 1643 + regmap_update_bits(regmap, CPCAP_REG_RXOA, val, val); 1644 + 1645 + regulator_set_mode(cpcap->vaudio, REGULATOR_MODE_NORMAL); 1646 + 1647 + /* Give PTTS time to settle */ 1648 + msleep(20); 1649 + 1650 + if (!regmap_test_bits(regmap, CPCAP_REG_INTS2, 1651 + BIT(CPCAP_BIT_PTT_S))) { 1652 + /* Headphones detected. (May also be a headset with the 1653 + * MFB pressed.) 1654 + */ 1655 + status = SND_JACK_HEADPHONE; 1656 + dev_info(component->dev, "HP plugged in\n"); 1657 + } else if (regmap_test_bits(regmap, CPCAP_REG_INTS1, 1658 + BIT(CPCAP_BIT_MB2_S)) == 1) { 1659 + status = SND_JACK_HEADSET; 1660 + dev_info(component->dev, "HS plugged in\n"); 1661 + } else 1662 + dev_info(component->dev, "Unsupported HS plugged in\n"); 1663 + } else { 1664 + bool mic = cpcap->jack.status & SND_JACK_MICROPHONE; 1665 + 1666 + dev_info(component->dev, "H%s disconnect\n", mic ? "S" : "P"); 1667 + val = BIT(CPCAP_BIT_MB_ON2) | BIT(CPCAP_BIT_PTT_CMP_EN); 1668 + regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, val, 0); 1669 + 1670 + val = BIT(CPCAP_BIT_ST_HS_CP_EN); 1671 + regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, val, 0); 1672 + 1673 + regulator_set_mode(cpcap->vaudio, REGULATOR_MODE_STANDBY); 1674 + 1675 + mask |= SND_JACK_BTN_0; 1676 + } 1677 + 1678 + snd_soc_jack_report(&cpcap->jack, status, mask); 1679 + 1680 + return IRQ_HANDLED; 1681 + } 1682 + 1683 + static irqreturn_t cpcap_mb2_irq_thread(int irq, void *data) 1684 + { 1685 + struct snd_soc_component *component = data; 1686 + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); 1687 + struct regmap *regmap = cpcap->regmap; 1688 + int status = 0; 1689 + int mb2; 1690 + int ptt; 1691 + 1692 + if (regmap_test_bits(regmap, CPCAP_REG_INTS1, BIT(CPCAP_BIT_HS_S)) == 1) 1693 + return IRQ_HANDLED; 1694 + 1695 + mb2 = regmap_test_bits(regmap, CPCAP_REG_INTS1, BIT(CPCAP_BIT_MB2_S)); 1696 + ptt = regmap_test_bits(regmap, CPCAP_REG_INTS2, BIT(CPCAP_BIT_PTT_S)); 1697 + 1698 + /* Initial detection might have been with MFB pressed */ 1699 + if (!(cpcap->jack.status & SND_JACK_MICROPHONE)) { 1700 + if (ptt == 1 && mb2 == 1) { 1701 + dev_info(component->dev, "MIC plugged in\n"); 1702 + snd_soc_jack_report(&cpcap->jack, SND_JACK_MICROPHONE, 1703 + SND_JACK_MICROPHONE); 1704 + } 1705 + 1706 + return IRQ_HANDLED; 1707 + } 1708 + 1709 + if (!mb2 || !ptt) 1710 + status = SND_JACK_BTN_0; 1711 + 1712 + snd_soc_jack_report(&cpcap->jack, status, SND_JACK_BTN_0); 1713 + 1714 + return IRQ_HANDLED; 1715 + } 1716 + 1643 1717 static int cpcap_soc_probe(struct snd_soc_component *component) 1644 1718 { 1719 + struct platform_device *pdev = to_platform_device(component->dev); 1720 + struct snd_soc_card *card = component->card; 1645 1721 struct cpcap_audio *cpcap; 1646 1722 int err; 1647 1723 1648 1724 cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL); 1649 1725 if (!cpcap) 1650 1726 return -ENOMEM; 1727 + 1651 1728 snd_soc_component_set_drvdata(component, cpcap); 1652 1729 cpcap->component = component; 1730 + 1731 + cpcap->vaudio = devm_regulator_get(component->dev, "VAUDIO"); 1732 + if (IS_ERR(cpcap->vaudio)) 1733 + return dev_err_probe(component->dev, PTR_ERR(cpcap->vaudio), 1734 + "Cannot get VAUDIO regulator\n"); 1735 + 1736 + err = snd_soc_card_jack_new(card, "Headphones", 1737 + SND_JACK_HEADSET | SND_JACK_BTN_0, 1738 + &cpcap->jack); 1739 + if (err < 0) { 1740 + dev_err(component->dev, "Cannot create HS jack: %i\n", err); 1741 + return err; 1742 + } 1743 + 1744 + snd_jack_set_key(cpcap->jack.jack, SND_JACK_BTN_0, KEY_MEDIA); 1653 1745 1654 1746 cpcap->regmap = dev_get_regmap(component->dev->parent, NULL); 1655 1747 if (!cpcap->regmap) ··· 1766 1646 if (err) 1767 1647 return err; 1768 1648 1769 - return cpcap_audio_reset(component, false); 1649 + cpcap->hsirq = platform_get_irq_byname(pdev, "hs"); 1650 + if (cpcap->hsirq < 0) 1651 + return cpcap->hsirq; 1652 + 1653 + err = devm_request_threaded_irq(component->dev, cpcap->hsirq, NULL, 1654 + cpcap_hs_irq_thread, 1655 + IRQF_TRIGGER_RISING | 1656 + IRQF_TRIGGER_FALLING | 1657 + IRQF_ONESHOT, 1658 + "cpcap-codec-hs", 1659 + component); 1660 + if (err) { 1661 + dev_warn(component->dev, "no HS irq%i: %i\n", 1662 + cpcap->hsirq, err); 1663 + return err; 1664 + } 1665 + 1666 + cpcap->mb2irq = platform_get_irq_byname(pdev, "mb2"); 1667 + if (cpcap->mb2irq < 0) 1668 + return cpcap->mb2irq; 1669 + 1670 + err = devm_request_threaded_irq(component->dev, cpcap->mb2irq, NULL, 1671 + cpcap_mb2_irq_thread, 1672 + IRQF_TRIGGER_RISING | 1673 + IRQF_TRIGGER_FALLING | 1674 + IRQF_ONESHOT, 1675 + "cpcap-codec-mb2", 1676 + component); 1677 + if (err) { 1678 + dev_warn(component->dev, "no MB2 irq%i: %i\n", 1679 + cpcap->mb2irq, err); 1680 + return err; 1681 + } 1682 + 1683 + err = cpcap_audio_reset(component, false); 1684 + if (err) 1685 + return err; 1686 + 1687 + cpcap_hs_irq_thread(cpcap->hsirq, component); 1688 + 1689 + enable_irq_wake(cpcap->hsirq); 1690 + enable_irq_wake(cpcap->mb2irq); 1691 + 1692 + return 0; 1693 + } 1694 + 1695 + static void cpcap_soc_remove(struct snd_soc_component *component) 1696 + { 1697 + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); 1698 + 1699 + disable_irq_wake(cpcap->hsirq); 1700 + disable_irq_wake(cpcap->mb2irq); 1701 + } 1702 + 1703 + static int cpcap_set_bias_level(struct snd_soc_component *component, 1704 + enum snd_soc_bias_level level) 1705 + { 1706 + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); 1707 + 1708 + /* VAIDIO should be kept in normal mode in order MIC/PTT to work */ 1709 + if (cpcap->jack.status & SND_JACK_MICROPHONE) 1710 + return 0; 1711 + 1712 + switch (level) { 1713 + case SND_SOC_BIAS_OFF: 1714 + break; 1715 + case SND_SOC_BIAS_PREPARE: 1716 + regulator_set_mode(cpcap->vaudio, REGULATOR_MODE_NORMAL); 1717 + break; 1718 + case SND_SOC_BIAS_STANDBY: 1719 + regulator_set_mode(cpcap->vaudio, REGULATOR_MODE_STANDBY); 1720 + break; 1721 + case SND_SOC_BIAS_ON: 1722 + break; 1723 + } 1724 + 1725 + return 0; 1770 1726 } 1771 1727 1772 1728 static const struct snd_soc_component_driver soc_codec_dev_cpcap = { 1773 1729 .probe = cpcap_soc_probe, 1730 + .remove = cpcap_soc_remove, 1774 1731 .controls = cpcap_snd_controls, 1775 1732 .num_controls = ARRAY_SIZE(cpcap_snd_controls), 1776 1733 .dapm_widgets = cpcap_dapm_widgets, 1777 1734 .num_dapm_widgets = ARRAY_SIZE(cpcap_dapm_widgets), 1778 1735 .dapm_routes = intercon, 1779 1736 .num_dapm_routes = ARRAY_SIZE(intercon), 1737 + .set_bias_level = cpcap_set_bias_level, 1780 1738 .idle_bias_on = 1, 1781 1739 .use_pmdown_time = 1, 1782 1740 .endianness = 1,