/* * Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "../wcdcal-hwdep.h" #include "wcd937x-registers.h" #include "../msm-cdc-pinctrl.h" #include #include "../msm-cdc-supply.h" #define WCD9370_VARIANT 0 #define WCD9375_VARIANT 5 #define NUM_SWRS_DT_PARAMS 5 enum { CODEC_TX = 0, CODEC_RX, }; static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1); static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1); static int wcd937x_handle_pre_irq(void *data); static int wcd937x_handle_post_irq(void *data); static const struct regmap_irq wcd937x_irqs[WCD937X_NUM_IRQS] = { REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_BUTTON_PRESS_DET, 0, 0x01), REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_BUTTON_RELEASE_DET, 0, 0x02), REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_ELECT_INS_REM_DET, 0, 0x04), REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, 0, 0x08), REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_SW_DET, 0, 0x10), REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_OCP_INT, 0, 0x20), REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_CNP_INT, 0, 0x40), REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_OCP_INT, 0, 0x80), REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_CNP_INT, 1, 0x01), REGMAP_IRQ_REG(WCD937X_IRQ_EAR_CNP_INT, 1, 0x02), REGMAP_IRQ_REG(WCD937X_IRQ_EAR_SCD_INT, 1, 0x04), REGMAP_IRQ_REG(WCD937X_IRQ_AUX_CNP_INT, 1, 0x08), REGMAP_IRQ_REG(WCD937X_IRQ_AUX_SCD_INT, 1, 0x10), REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_PDM_WD_INT, 1, 0x20), REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_PDM_WD_INT, 1, 0x40), REGMAP_IRQ_REG(WCD937X_IRQ_AUX_PDM_WD_INT, 1, 0x80), REGMAP_IRQ_REG(WCD937X_IRQ_LDORT_SCD_INT, 2, 0x01), REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_MOISTURE_INT, 2, 0x02), REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_SURGE_DET_INT, 2, 0x04), REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_SURGE_DET_INT, 2, 0x08), }; static struct regmap_irq_chip wcd937x_regmap_irq_chip = { .name = "wcd937x", .irqs = wcd937x_irqs, .num_irqs = ARRAY_SIZE(wcd937x_irqs), .num_regs = 3, .status_base = WCD937X_DIGITAL_INTR_STATUS_0, .mask_base = WCD937X_DIGITAL_INTR_MASK_0, .type_base = WCD937X_DIGITAL_INTR_LEVEL_0, .runtime_pm = true, .handle_post_irq = wcd937x_handle_post_irq, .handle_pre_irq = wcd937x_handle_pre_irq, }; static int wcd937x_handle_pre_irq(void *data) { struct wcd937x_priv *wcd937x = data; int num_irq_regs = wcd937x->num_irq_regs; int ret = 0; u8 sts[num_irq_regs]; struct wcd937x_pdata *pdata; pdata = dev_get_platdata(wcd937x->dev); memset(sts, 0, sizeof(sts)); ret = regmap_bulk_read(wcd937x->regmap, WCD937X_DIGITAL_INTR_STATUS_0, sts, num_irq_regs); if (ret < 0) { dev_err(wcd937x->dev, "%s: Failed to read intr status: %d\n", __func__, ret); } else if (ret == 0) { dev_dbg(wcd937x->dev, "%s: clear interrupts except OCP and SCD\n", __func__); /* Do not affect OCP and SCD interrupts */ sts[0] = sts[0] & 0x5F; sts[1] = sts[1] & 0xEB; regmap_write(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_0, sts[0]); regmap_write(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_1, sts[1]); regmap_write(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_2, sts[2]); } return IRQ_HANDLED; } static int wcd937x_handle_post_irq(void *data) { struct wcd937x_priv *wcd937x = data; int val = 0; struct wcd937x_pdata *pdata = NULL; pdata = dev_get_platdata(wcd937x->dev); regmap_read(wcd937x->regmap, WCD937X_DIGITAL_INTR_STATUS_0, &val); if ((val & 0xA0) != 0) { dev_dbg(wcd937x->dev, "%s Clear OCP interupts\n", __func__); regmap_update_bits(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_0, 0xA0, 0x00); } regmap_read(wcd937x->regmap, WCD937X_DIGITAL_INTR_STATUS_1, &val); if ((val & 0x14) != 0) { dev_dbg(wcd937x->dev, "%s Clear SCD interupts\n", __func__); regmap_update_bits(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_1, 0x14, 0x00); } return IRQ_HANDLED; } static int wcd937x_init_reg(struct snd_soc_codec *codec) { snd_soc_update_bits(codec, WCD937X_SLEEP_CTL, 0x0E, 0x0E); snd_soc_update_bits(codec, WCD937X_SLEEP_CTL, 0x80, 0x80); usleep_range(1000, 1010); snd_soc_update_bits(codec, WCD937X_SLEEP_CTL, 0x40, 0x40); usleep_range(1000, 1010); snd_soc_update_bits(codec, WCD937X_LDORXTX_CONFIG, 0x10, 0x00); snd_soc_update_bits(codec, WCD937X_BIAS_VBG_FINE_ADJ, 0xF0, 0x80); snd_soc_update_bits(codec, WCD937X_ANA_BIAS, 0x80, 0x80); snd_soc_update_bits(codec, WCD937X_ANA_BIAS, 0x40, 0x40); usleep_range(10000, 10010); snd_soc_update_bits(codec, WCD937X_ANA_BIAS, 0x40, 0x00); return 0; } static int wcd937x_set_port_params(struct snd_soc_codec *codec, u8 slv_prt_type, u8 *port_id, u8 *num_ch, u8 *ch_mask, u32 *ch_rate, u8 *port_type, u8 path) { int i, j; u8 num_ports; struct codec_port_info (*map)[MAX_PORT][MAX_CH_PER_PORT]; struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); switch (path) { case CODEC_RX: map = &wcd937x->rx_port_mapping; num_ports = wcd937x->num_rx_ports; break; case CODEC_TX: map = &wcd937x->tx_port_mapping; num_ports = wcd937x->num_tx_ports; break; } for (i = 0; i <= num_ports; i++) { for (j = 0; j < MAX_CH_PER_PORT; j++) { if ((*map)[i][j].slave_port_type == slv_prt_type) goto found; } } found: if (i > num_ports || j == MAX_CH_PER_PORT) { dev_err(codec->dev, "%s Failed to find slave port for type %u\n", __func__, slv_prt_type); return -EINVAL; } *port_id = i; *num_ch = (*map)[i][j].num_ch; *ch_mask = (*map)[i][j].ch_mask; *ch_rate = (*map)[i][j].ch_rate; *port_type = (*map)[i][j].master_port_type; return 0; } static int wcd937x_parse_port_mapping(struct device *dev, char *prop, u8 path) { u32 *dt_array, map_size, map_length; u32 port_num, ch_mask, ch_rate, old_port_num = 0; u32 slave_port_type, master_port_type; u32 i, ch_iter = 0; int ret = 0; u8 *num_ports; struct codec_port_info (*map)[MAX_PORT][MAX_CH_PER_PORT]; struct wcd937x_priv *wcd937x = dev_get_drvdata(dev); switch (path) { case CODEC_RX: map = &wcd937x->rx_port_mapping; num_ports = &wcd937x->num_rx_ports; break; case CODEC_TX: map = &wcd937x->tx_port_mapping; num_ports = &wcd937x->num_tx_ports; break; } if (!of_find_property(dev->of_node, prop, &map_size)) { dev_err(dev, "missing port mapping prop %s\n", prop); goto err_pdata_fail; } map_length = map_size / (NUM_SWRS_DT_PARAMS * sizeof(u32)); dt_array = kzalloc(map_size, GFP_KERNEL); if (!dt_array) { ret = -ENOMEM; goto err_pdata_fail; } ret = of_property_read_u32_array(dev->of_node, prop, dt_array, NUM_SWRS_DT_PARAMS * map_length); if (ret) { dev_err(dev, "%s: Failed to read port mapping from prop %s\n", __func__, prop); goto err_pdata_fail; } for (i = 0; i < map_length; i++) { port_num = dt_array[NUM_SWRS_DT_PARAMS * i]; slave_port_type = dt_array[NUM_SWRS_DT_PARAMS * i + 1]; ch_mask = dt_array[NUM_SWRS_DT_PARAMS * i + 2]; ch_rate = dt_array[NUM_SWRS_DT_PARAMS * i + 3]; master_port_type = dt_array[NUM_SWRS_DT_PARAMS * i + 4]; if (port_num != old_port_num) ch_iter = 0; (*map)[port_num][ch_iter].slave_port_type = slave_port_type; (*map)[port_num][ch_iter].ch_mask = ch_mask; (*map)[port_num][ch_iter].master_port_type = master_port_type; (*map)[port_num][ch_iter].num_ch = __sw_hweight8(ch_mask); (*map)[port_num][ch_iter++].ch_rate = ch_rate; old_port_num = port_num; } *num_ports = port_num; kfree(dt_array); return 0; err_pdata_fail: kfree(dt_array); return -EINVAL; } static int wcd937x_tx_connect_port(struct snd_soc_codec *codec, u8 slv_port_type, u8 enable) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); u8 port_id; u8 num_ch; u8 ch_mask; u32 ch_rate; u8 port_type; u8 num_port = 1; int ret = 0; ret = wcd937x_set_port_params(codec, slv_port_type, &port_id, &num_ch, &ch_mask, &ch_rate, &port_type, CODEC_TX); if (ret) return ret; if (enable) ret = swr_connect_port(wcd937x->tx_swr_dev, &port_id, num_port, &ch_mask, &ch_rate, &num_ch, &port_type); else ret = swr_disconnect_port(wcd937x->tx_swr_dev, &port_id, num_port, &ch_mask, &port_type); return ret; } static int wcd937x_rx_connect_port(struct snd_soc_codec *codec, u8 slv_port_type, u8 enable) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); u8 port_id; u8 num_ch; u8 ch_mask; u32 ch_rate; u8 port_type; u8 num_port = 1; int ret = 0; ret = wcd937x_set_port_params(codec, slv_port_type, &port_id, &num_ch, &ch_mask, &ch_rate, &port_type, CODEC_RX); if (ret) return ret; if (enable) ret = swr_connect_port(wcd937x->rx_swr_dev, &port_id, num_port, &ch_mask, &ch_rate, &num_ch, &port_type); else ret = swr_disconnect_port(wcd937x->rx_swr_dev, &port_id, num_port, &ch_mask, &port_type); return ret; } static int wcd937x_rx_clk_enable(struct snd_soc_codec *codec) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); if (wcd937x->rx_clk_cnt == 0) { snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x08, 0x08); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x01, 0x01); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x01, 0x01); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_RX0_CTL, 0x40, 0x00); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x02, 0x02); } wcd937x->rx_clk_cnt++; return 0; } static int wcd937x_rx_clk_disable(struct snd_soc_codec *codec) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); wcd937x->rx_clk_cnt--; if (wcd937x->rx_clk_cnt == 0) { snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x40, 0x00); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x80, 0x00); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x01, 0x00); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x02, 0x00); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x01, 0x00); } return 0; } /* * wcd937x_soc_get_mbhc: get wcd937x_mbhc handle of corresponding codec * @codec: handle to snd_soc_codec * * * return wcd937x_mbhc handle or error code in case of failure */ struct wcd937x_mbhc *wcd937x_soc_get_mbhc(struct snd_soc_codec *codec) { struct wcd937x_priv *wcd937x; if (!codec) { pr_err("%s: Invalid params, NULL codec\n", __func__); return NULL; } wcd937x = snd_soc_codec_get_drvdata(codec); if (!wcd937x) { pr_err("%s: Invalid params, NULL tavil\n", __func__); return NULL; } return wcd937x->mbhc; } EXPORT_SYMBOL(wcd937x_soc_get_mbhc); static int wcd937x_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: wcd937x_rx_clk_enable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x01, 0x01); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_HPH_GAIN_CTL, 0x04, 0x04); snd_soc_update_bits(codec, WCD937X_HPH_RDAC_CLK_CTL1, 0x80, 0x00); break; case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, WCD937X_HPH_NEW_INT_RDAC_HD2_CTL_L, 0x0F, 0x02); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_COMP_CTL_0, 0x02, 0x02); usleep_range(5000, 5010); snd_soc_update_bits(codec, WCD937X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x00); break; case SND_SOC_DAPM_POST_PMD: ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, false); break; } return ret; } static int wcd937x_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: wcd937x_rx_clk_enable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_HPH_GAIN_CTL, 0x08, 0x08); snd_soc_update_bits(codec, WCD937X_HPH_RDAC_CLK_CTL1, 0x80, 0x00); break; case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, WCD937X_HPH_NEW_INT_RDAC_HD2_CTL_R, 0x0F, 0x02); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_COMP_CTL_0, 0x01, 0x01); usleep_range(5000, 5010); snd_soc_update_bits(codec, WCD937X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x00); break; case SND_SOC_DAPM_POST_PMD: ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, false); break; } return ret; } static int wcd937x_codec_ear_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: wcd937x_rx_clk_enable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_HPH_GAIN_CTL, 0x04, 0x04); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x01, 0x01); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_COMP_CTL_0, 0x02, 0x02); usleep_range(5000, 5010); break; case SND_SOC_DAPM_POST_PMD: ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, false); break; }; return ret; } static int wcd937x_codec_aux_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: wcd937x_rx_clk_enable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x04, 0x04); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x04, 0x04); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_AUX_GAIN_CTL, 0x01, 0x01); break; case SND_SOC_DAPM_POST_PMD: ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, false); wcd937x_rx_clk_disable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x04, 0x00); break; }; return ret; } static int wcd937x_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_ANA_HPH, 0x10, 0x10); usleep_range(100, 110); break; case SND_SOC_DAPM_POST_PMU: usleep_range(7000, 7010); snd_soc_update_bits(codec, WCD937X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x02, 0x02); ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, true); break; case SND_SOC_DAPM_POST_PMD: usleep_range(7000, 7010); snd_soc_update_bits(codec, WCD937X_ANA_HPH, 0x10, 0x00); break; }; return ret; } static int wcd937x_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_ANA_HPH, 0x0C, 0x08); snd_soc_update_bits(codec, WCD937X_ANA_HPH, 0x20, 0x20); usleep_range(100, 110); break; case SND_SOC_DAPM_POST_PMU: usleep_range(7000, 7010); snd_soc_update_bits(codec, WCD937X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x02, 0x02); ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, true); break; case SND_SOC_DAPM_POST_PMD: usleep_range(7000, 7010); snd_soc_update_bits(codec, WCD937X_ANA_HPH, 0x20, 0x00); break; }; return ret; } static int wcd937x_codec_enable_aux_pa(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x80, 0x80); usleep_range(500, 510); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x3A); usleep_range(500, 510); break; case SND_SOC_DAPM_POST_PMU: usleep_range(1000, 1010); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x20, 0x20); ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, true); break; case SND_SOC_DAPM_POST_PMD: usleep_range(1000, 1010); usleep_range(1000, 1010); break; }; return ret; } static int wcd937x_codec_enable_ear_pa(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x08, 0x08); usleep_range(500, 510); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x3A); usleep_range(500, 510); break; case SND_SOC_DAPM_POST_PMU: usleep_range(6000, 6010); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x02, 0x02); ret = swr_slvdev_datapath_control(wcd937x->rx_swr_dev, wcd937x->rx_swr_dev->dev_num, true); break; case SND_SOC_DAPM_POST_PMD: usleep_range(7000, 7010); break; }; return ret; } static int wcd937x_enable_rx1(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEG_CTRL_4, 0xF0, 0x80); snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEGDAC_CTRL_2, 0xE0, 0xA0); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_3, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x1C); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x40, 0x40); usleep_range(100, 110); snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEGDAC_CTRL_2, 0xE0, 0xE0); usleep_range(100, 110); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x80, 0x80); usleep_range(500, 510); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x3A); usleep_range(500, 510); wcd937x_rx_connect_port(codec, HPH_L, true); wcd937x_rx_connect_port(codec, COMP_L, true); break; case SND_SOC_DAPM_POST_PMD: wcd937x_rx_connect_port(codec, HPH_L, false); wcd937x_rx_connect_port(codec, COMP_L, false); wcd937x_rx_clk_disable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x01, 0x00); break; }; return 0; } static int wcd937x_enable_rx2(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEG_CTRL_4, 0xF0, 0x80); snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEGDAC_CTRL_2, 0xE0, 0xA0); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_3, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x1C); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x40, 0x40); usleep_range(100, 110); snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEGDAC_CTRL_2, 0xE0, 0xE0); usleep_range(100, 110); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x80, 0x80); usleep_range(500, 510); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x3A); usleep_range(500, 510); wcd937x_rx_connect_port(codec, HPH_R, true); wcd937x_rx_connect_port(codec, COMP_R, true); break; case SND_SOC_DAPM_POST_PMD: wcd937x_rx_connect_port(codec, HPH_R, false); wcd937x_rx_connect_port(codec, COMP_R, false); wcd937x_rx_clk_disable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x02, 0x00); break; }; return 0; } static int wcd937x_enable_rx3(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEG_CTRL_2, 0xE0, 0xA0); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_3, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_CLASSH_MODE_2, 0xFF, 0x1C); snd_soc_update_bits(codec, WCD937X_ANA_RX_SUPPLIES, 0x40, 0x40); usleep_range(100, 110); snd_soc_update_bits(codec, WCD937X_FLYBACK_VNEG_CTRL_2, 0xE0, 0xE0); usleep_range(100, 110); wcd937x_rx_connect_port(codec, LO, true); break; case SND_SOC_DAPM_POST_PMD: wcd937x_rx_connect_port(codec, LO, false); usleep_range(6000, 6010); wcd937x_rx_clk_disable(codec); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x04, 0x00); break; } return 0; } static int wcd937x_codec_enable_dmic(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); u16 dmic_clk_reg; s32 *dmic_clk_cnt; unsigned int dmic; char *wname; int ret = 0; wname = strpbrk(w->name, "012345"); if (!wname) { dev_err(codec->dev, "%s: widget not found\n", __func__); return -EINVAL; } ret = kstrtouint(wname, 10, &dmic); if (ret < 0) { dev_err(codec->dev, "%s: Invalid DMIC line on the codec\n", __func__); return -EINVAL; } dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (dmic) { case 0: case 1: dmic_clk_cnt = &(wcd937x->dmic_0_1_clk_cnt); dmic_clk_reg = WCD937X_DIGITAL_CDC_DMIC0_CTL; break; case 2: case 3: dmic_clk_cnt = &(wcd937x->dmic_2_3_clk_cnt); dmic_clk_reg = WCD937X_DIGITAL_CDC_DMIC1_CTL; break; case 4: case 5: dmic_clk_cnt = &(wcd937x->dmic_4_5_clk_cnt); dmic_clk_reg = WCD937X_DIGITAL_CDC_DMIC2_CTL; break; default: dev_err(codec->dev, "%s: Invalid DMIC Selection\n", __func__); return -EINVAL; }; dev_dbg(codec->dev, "%s: event %d DMIC%d dmic_clk_cnt %d\n", __func__, event, dmic, *dmic_clk_cnt); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x80, 0x80); snd_soc_update_bits(codec, dmic_clk_reg, 0x07, 0x02); snd_soc_update_bits(codec, dmic_clk_reg, 0x08, 0x08); snd_soc_update_bits(codec, dmic_clk_reg, 0x70, 0x20); wcd937x_tx_connect_port(codec, DMIC0 + (w->shift), true); break; case SND_SOC_DAPM_POST_PMD: wcd937x_tx_connect_port(codec, DMIC0 + (w->shift), false); break; }; return 0; } /* * wcd937x_get_micb_vout_ctl_val: converts micbias from volts to register value * @micb_mv: micbias in mv * * return register value converted */ int wcd937x_get_micb_vout_ctl_val(u32 micb_mv) { /* min micbias voltage is 1V and maximum is 2.85V */ if (micb_mv < 1000 || micb_mv > 2850) { pr_err("%s: unsupported micbias voltage\n", __func__); return -EINVAL; } return (micb_mv - 1000) / 50; } EXPORT_SYMBOL(wcd937x_get_micb_vout_ctl_val); /* * wcd937x_mbhc_micb_adjust_voltage: adjust specific micbias voltage * @codec: handle to snd_soc_codec * * @req_volt: micbias voltage to be set * @micb_num: micbias to be set, e.g. micbias1 or micbias2 * * return 0 if adjustment is success or error code in case of failure */ int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_codec *codec, int req_volt, int micb_num) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int cur_vout_ctl, req_vout_ctl; int micb_reg, micb_val, micb_en; int ret = 0; switch (micb_num) { case MIC_BIAS_1: micb_reg = WCD937X_ANA_MICB1; break; case MIC_BIAS_2: micb_reg = WCD937X_ANA_MICB2; break; case MIC_BIAS_3: micb_reg = WCD937X_ANA_MICB3; break; default: return -EINVAL; } mutex_lock(&wcd937x->micb_lock); /* * If requested micbias voltage is same as current micbias * voltage, then just return. Otherwise, adjust voltage as * per requested value. If micbias is already enabled, then * to avoid slow micbias ramp-up or down enable pull-up * momentarily, change the micbias value and then re-enable * micbias. */ micb_val = snd_soc_read(codec, micb_reg); micb_en = (micb_val & 0xC0) >> 6; cur_vout_ctl = micb_val & 0x3F; req_vout_ctl = wcd937x_get_micb_vout_ctl_val(req_volt); if (req_vout_ctl < 0) { ret = -EINVAL; goto exit; } if (cur_vout_ctl == req_vout_ctl) { ret = 0; goto exit; } dev_dbg(codec->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), req_volt, micb_en); if (micb_en == 0x1) snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); snd_soc_update_bits(codec, micb_reg, 0x3F, req_vout_ctl); if (micb_en == 0x1) { snd_soc_update_bits(codec, micb_reg, 0xC0, 0x40); /* * Add 2ms delay as per HW requirement after enabling * micbias */ usleep_range(2000, 2100); } exit: mutex_unlock(&wcd937x->micb_lock); return ret; } EXPORT_SYMBOL(wcd937x_mbhc_micb_adjust_voltage); static int wcd937x_tx_swr_ctrl(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int ret = 0; switch (event) { case SND_SOC_DAPM_PRE_PMU: ret = swr_slvdev_datapath_control(wcd937x->tx_swr_dev, wcd937x->tx_swr_dev->dev_num, true); break; case SND_SOC_DAPM_POST_PMD: ret = swr_slvdev_datapath_control(wcd937x->tx_swr_dev, wcd937x->tx_swr_dev->dev_num, false); break; }; return ret; } static int wcd937x_codec_enable_adc(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event){ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x80, 0x80); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x08, 0x08); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x10, 0x10); wcd937x_tx_connect_port(codec, ADC1 + (w->shift), true); break; case SND_SOC_DAPM_POST_PMD: wcd937x_tx_connect_port(codec, ADC1 + (w->shift), false); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x08, 0x00); break; }; return 0; } static int wcd937x_enable_req(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); dev_dbg(codec->dev, "%s wname: %s event: %d\n", __func__, w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_REQ_CTL, 0x02, 0x02); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_REQ_CTL, 0x01, 0x00); snd_soc_update_bits(codec, WCD937X_ANA_TX_CH2, 0x40, 0x40); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x10, 0x10); snd_soc_update_bits(codec, WCD937X_ANA_TX_CH1, 0x80, 0x80); snd_soc_update_bits(codec, WCD937X_ANA_TX_CH2, 0x40, 0x00); break; case SND_SOC_DAPM_POST_PMD: snd_soc_update_bits(codec, WCD937X_ANA_TX_CH1, 0x80, 0x00); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x10, 0x00); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_ANA_CLK_CTL, 0x10, 0x00); snd_soc_update_bits(codec, WCD937X_DIGITAL_CDC_DIG_CLK_CTL, 0x80, 0x00); break; }; return 0; } int wcd937x_micbias_control(struct snd_soc_codec *codec, int micb_num, int req, bool is_dapm) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); int micb_index = micb_num - 1; u16 micb_reg; int pre_off_event = 0, post_off_event = 0; int post_on_event = 0, post_dapm_off = 0; int post_dapm_on = 0; if ((micb_index < 0) || (micb_index > WCD937X_MAX_MICBIAS - 1)) { dev_err(codec->dev, "%s: Invalid micbias index, micb_ind:%d\n", __func__, micb_index); return -EINVAL; } switch (micb_num) { case MIC_BIAS_1: micb_reg = WCD937X_ANA_MICB1; break; case MIC_BIAS_2: micb_reg = WCD937X_ANA_MICB2; pre_off_event = WCD_EVENT_PRE_MICBIAS_2_OFF; post_off_event = WCD_EVENT_POST_MICBIAS_2_OFF; post_on_event = WCD_EVENT_POST_MICBIAS_2_ON; post_dapm_on = WCD_EVENT_POST_DAPM_MICBIAS_2_ON; post_dapm_off = WCD_EVENT_POST_DAPM_MICBIAS_2_OFF; break; case MIC_BIAS_3: micb_reg = WCD937X_ANA_MICB3; break; default: dev_err(codec->dev, "%s: Invalid micbias number: %d\n", __func__, micb_num); return -EINVAL; }; mutex_lock(&wcd937x->micb_lock); switch (req) { case MICB_PULLUP_ENABLE: wcd937x->pullup_ref[micb_index]++; if ((wcd937x->pullup_ref[micb_index] == 1) && (wcd937x->micb_ref[micb_index] == 0)) snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); break; case MICB_PULLUP_DISABLE: if (wcd937x->pullup_ref[micb_index] > 0) wcd937x->pullup_ref[micb_index]--; if ((wcd937x->pullup_ref[micb_index] == 0) && (wcd937x->micb_ref[micb_index] == 0)) snd_soc_update_bits(codec, micb_reg, 0xC0, 0x00); break; case MICB_ENABLE: wcd937x->micb_ref[micb_index]++; if (wcd937x->micb_ref[micb_index] == 1) { snd_soc_update_bits(codec, micb_reg, 0xC0, 0x40); if (post_on_event) blocking_notifier_call_chain(&wcd937x->notifier, post_on_event, &wcd937x->mbhc); } if (is_dapm && post_dapm_on) blocking_notifier_call_chain(&wcd937x->notifier, post_dapm_on, &wcd937x->mbhc); break; case MICB_DISABLE: if (wcd937x->micb_ref[micb_index] > 0) wcd937x->micb_ref[micb_index]--; if ((wcd937x->micb_ref[micb_index] == 0) && (wcd937x->pullup_ref[micb_index] > 0)) snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); else if ((wcd937x->micb_ref[micb_index] == 0) && (wcd937x->pullup_ref[micb_index] == 0)) { if (pre_off_event) blocking_notifier_call_chain(&wcd937x->notifier, pre_off_event, &wcd937x->mbhc); snd_soc_update_bits(codec, micb_reg, 0xC0, 0x00); if (post_off_event) blocking_notifier_call_chain(&wcd937x->notifier, post_off_event, &wcd937x->mbhc); } if (is_dapm && post_dapm_off) blocking_notifier_call_chain(&wcd937x->notifier, post_dapm_off, &wcd937x->mbhc); break; }; dev_dbg(codec->dev, "%s: micb_num:%d, micb_ref: %d, pullup_ref: %d\n", __func__, micb_num, wcd937x->micb_ref[micb_index], wcd937x->pullup_ref[micb_index]); mutex_unlock(&wcd937x->micb_lock); return 0; } EXPORT_SYMBOL(wcd937x_micbias_control); static int __wcd937x_codec_enable_micbias(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); int micb_num; dev_dbg(codec->dev, "%s: wname: %s, event: %d\n", __func__, w->name, event); if (strnstr(w->name, "MIC BIAS1", sizeof("MIC BIAS1"))) micb_num = MIC_BIAS_1; else if (strnstr(w->name, "MIC BIAS2", sizeof("MIC BIAS2"))) micb_num = MIC_BIAS_2; else if (strnstr(w->name, "MIC BIAS3", sizeof("MIC BIAS3"))) micb_num = MIC_BIAS_3; else return -EINVAL; switch (event) { case SND_SOC_DAPM_PRE_PMU: wcd937x_micbias_control(codec, micb_num, MICB_ENABLE, true); break; case SND_SOC_DAPM_POST_PMU: usleep_range(1000, 1100); break; case SND_SOC_DAPM_POST_PMD: wcd937x_micbias_control(codec, micb_num, MICB_DISABLE, true); break; }; return 0; } static int wcd937x_codec_enable_micbias(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { return __wcd937x_codec_enable_micbias(w, event); } static int wcd937x_rx_hph_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); ucontrol->value.integer.value[0] = wcd937x->hph_mode; return 0; } static int wcd937x_rx_hph_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); u32 mode_val; mode_val = ucontrol->value.enumerated.item[0]; dev_dbg(codec->dev, "%s: mode: %d\n", __func__, mode_val); if (mode_val == 0) { dev_warn(codec->dev, "%s:Invalid HPH Mode, default to class_AB\n", __func__); mode_val = 3; /* enum will be updated later */ } wcd937x->hph_mode = mode_val; return 0; } static const char * const rx_hph_mode_mux_text[] = { "CLS_H_INVALID", "CLS_H_HIFI", "CLS_H_LP", "CLS_AB", "CLS_H_LOHIFI", "CLS_H_ULP", "CLS_AB_HIFI", }; static const struct soc_enum rx_hph_mode_mux_enum = SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(rx_hph_mode_mux_text), rx_hph_mode_mux_text); static const struct snd_kcontrol_new wcd937x_snd_controls[] = { SOC_ENUM_EXT("RX HPH Mode", rx_hph_mode_mux_enum, wcd937x_rx_hph_mode_get, wcd937x_rx_hph_mode_put), SOC_SINGLE_TLV("HPHL Volume", WCD937X_HPH_L_EN, 0, 20, 1, line_gain), SOC_SINGLE_TLV("HPHR Volume", WCD937X_HPH_R_EN, 0, 20, 1, line_gain), SOC_SINGLE_TLV("ADC1 Volume", WCD937X_ANA_TX_CH1, 0, 20, 0, analog_gain), SOC_SINGLE_TLV("ADC2 Volume", WCD937X_ANA_TX_CH2, 0, 20, 0, analog_gain), SOC_SINGLE_TLV("ADC3 Volume", WCD937X_ANA_TX_CH3, 0, 20, 0, analog_gain), }; static const struct snd_kcontrol_new adc1_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new adc2_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new adc3_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new dmic1_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new dmic2_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new dmic3_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new dmic4_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new dmic5_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new dmic6_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new ear_rdac_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new aux_rdac_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new hphl_rdac_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new hphr_rdac_switch[] = { SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0) }; static const char * const adc2_mux_text[] = { "INP2", "INP3" }; static const char * const rdac3_mux_text[] = { "RX1", "RX3" }; static const struct soc_enum adc2_enum = SOC_ENUM_SINGLE(WCD937X_TX_NEW_TX_CH2_SEL, 7, ARRAY_SIZE(adc2_mux_text), adc2_mux_text); static const struct soc_enum rdac3_enum = SOC_ENUM_SINGLE(WCD937X_DIGITAL_CDC_EAR_PATH_CTL, 0, ARRAY_SIZE(rdac3_mux_text), rdac3_mux_text); static const struct snd_kcontrol_new tx_adc2_mux = SOC_DAPM_ENUM("ADC2 MUX Mux", adc2_enum); static const struct snd_kcontrol_new rx_rdac3_mux = SOC_DAPM_ENUM("RDAC3_MUX Mux", rdac3_enum); static const struct snd_soc_dapm_widget wcd937x_dapm_widgets[] = { /*input widgets*/ SND_SOC_DAPM_INPUT("AMIC1"), SND_SOC_DAPM_INPUT("AMIC2"), SND_SOC_DAPM_INPUT("AMIC3"), SND_SOC_DAPM_INPUT("IN1_HPHL"), SND_SOC_DAPM_INPUT("IN2_HPHR"), SND_SOC_DAPM_INPUT("IN3_AUX"), /*tx widgets*/ SND_SOC_DAPM_ADC_E("ADC1", NULL, SND_SOC_NOPM, 0, 0, wcd937x_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("ADC2", NULL, SND_SOC_NOPM, 0, 1, wcd937x_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("ADC1 REQ", SND_SOC_NOPM, 0, 0, NULL, 0, wcd937x_enable_req, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("ADC2 REQ", SND_SOC_NOPM, 0, 0, NULL, 0, wcd937x_enable_req, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("ADC2 MUX", SND_SOC_NOPM, 0, 0, &tx_adc2_mux), /*tx mixers*/ SND_SOC_DAPM_MIXER_E("ADC1_MIXER", SND_SOC_NOPM, 0, 0, adc1_switch, ARRAY_SIZE(adc1_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("ADC2_MIXER", SND_SOC_NOPM, 0, 0, adc2_switch, ARRAY_SIZE(adc2_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), /* micbias widgets*/ SND_SOC_DAPM_MICBIAS_E("MIC BIAS1", SND_SOC_NOPM, 0, 0, wcd937x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MICBIAS_E("MIC BIAS2", SND_SOC_NOPM, 0, 0, wcd937x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MICBIAS_E("MIC BIAS3", SND_SOC_NOPM, 0, 0, wcd937x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), /*rx widgets*/ SND_SOC_DAPM_PGA_E("EAR PGA", WCD937X_ANA_EAR, 7, 0, NULL, 0, wcd937x_codec_enable_ear_pa, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("AUX PGA", WCD937X_AUX_AUXPA, 7, 0, NULL, 0, wcd937x_codec_enable_aux_pa, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("HPHL PGA", WCD937X_ANA_HPH, 7, 0, NULL, 0, wcd937x_codec_enable_hphl_pa, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("HPHR PGA", WCD937X_ANA_HPH, 6, 0, NULL, 0, wcd937x_codec_enable_hphr_pa, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("RDAC1", NULL, SND_SOC_NOPM, 0, 0, wcd937x_codec_hphl_dac_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("RDAC2", NULL, SND_SOC_NOPM, 0, 0, wcd937x_codec_hphr_dac_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("RDAC3", NULL, SND_SOC_NOPM, 0, 0, wcd937x_codec_ear_dac_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("RDAC4", NULL, SND_SOC_NOPM, 0, 0, wcd937x_codec_aux_dac_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("RDAC3_MUX", SND_SOC_NOPM, 0, 0, &rx_rdac3_mux), SND_SOC_DAPM_MIXER_E("RX1", SND_SOC_NOPM, 0, 0, NULL, 0, wcd937x_enable_rx1, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("RX2", SND_SOC_NOPM, 0, 0, NULL, 0, wcd937x_enable_rx2, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("RX3", SND_SOC_NOPM, 0, 0, NULL, 0, wcd937x_enable_rx3, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), /* rx mixer widgets*/ SND_SOC_DAPM_MIXER("EAR_RDAC", SND_SOC_NOPM, 0, 0, ear_rdac_switch, ARRAY_SIZE(ear_rdac_switch)), SND_SOC_DAPM_MIXER("AUX_RDAC", SND_SOC_NOPM, 0, 0, aux_rdac_switch, ARRAY_SIZE(aux_rdac_switch)), SND_SOC_DAPM_MIXER("HPHL_RDAC", SND_SOC_NOPM, 0, 0, hphl_rdac_switch, ARRAY_SIZE(hphl_rdac_switch)), SND_SOC_DAPM_MIXER("HPHR_RDAC", SND_SOC_NOPM, 0, 0, hphr_rdac_switch, ARRAY_SIZE(hphr_rdac_switch)), /*output widgets tx*/ SND_SOC_DAPM_OUTPUT("ADC1_OUTPUT"), SND_SOC_DAPM_OUTPUT("ADC2_OUTPUT"), /*output widgets rx*/ SND_SOC_DAPM_OUTPUT("EAR"), SND_SOC_DAPM_OUTPUT("AUX"), SND_SOC_DAPM_OUTPUT("HPHL"), SND_SOC_DAPM_OUTPUT("HPHR"), }; static const struct snd_soc_dapm_widget wcd9375_dapm_widgets[] = { /*input widgets*/ SND_SOC_DAPM_INPUT("AMIC4"), /*tx widgets*/ SND_SOC_DAPM_ADC_E("ADC3", NULL, SND_SOC_NOPM, 0, 2, wcd937x_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("ADC3 REQ", SND_SOC_NOPM, 0, 0, NULL, 0, wcd937x_enable_req, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("DMIC1", NULL, SND_SOC_NOPM, 0, 0, wcd937x_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("DMIC2", NULL, SND_SOC_NOPM, 0, 1, wcd937x_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("DMIC3", NULL, SND_SOC_NOPM, 0, 2, wcd937x_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("DMIC4", NULL, SND_SOC_NOPM, 0, 3, wcd937x_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("DMIC5", NULL, SND_SOC_NOPM, 0, 4, wcd937x_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("DMIC6", NULL, SND_SOC_NOPM, 0, 5, wcd937x_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), /*tx mixer widgets*/ SND_SOC_DAPM_MIXER_E("DMIC1_MIXER", SND_SOC_NOPM, 0, 0, dmic1_switch, ARRAY_SIZE(dmic1_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("DMIC2_MIXER", SND_SOC_NOPM, 0, 0, dmic2_switch, ARRAY_SIZE(dmic2_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("DMIC3_MIXER", SND_SOC_NOPM, 0, 0, dmic3_switch, ARRAY_SIZE(dmic3_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("DMIC4_MIXER", SND_SOC_NOPM, 0, 0, dmic4_switch, ARRAY_SIZE(dmic4_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("DMIC5_MIXER", SND_SOC_NOPM, 0, 0, dmic5_switch, ARRAY_SIZE(dmic5_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("DMIC6_MIXER", SND_SOC_NOPM, 0, 0, dmic6_switch, ARRAY_SIZE(dmic6_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER_E("ADC3_MIXER", SND_SOC_NOPM, 0, 0, adc3_switch, ARRAY_SIZE(adc3_switch), wcd937x_tx_swr_ctrl, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), /*output widgets*/ SND_SOC_DAPM_OUTPUT("DMIC1_OUTPUT"), SND_SOC_DAPM_OUTPUT("DMIC2_OUTPUT"), SND_SOC_DAPM_OUTPUT("DMIC3_OUTPUT"), SND_SOC_DAPM_OUTPUT("DMIC4_OUTPUT"), SND_SOC_DAPM_OUTPUT("DMIC5_OUTPUT"), SND_SOC_DAPM_OUTPUT("DMIC6_OUTPUT"), SND_SOC_DAPM_OUTPUT("ADC3_OUTPUT"), }; static const struct snd_soc_dapm_route wcd937x_audio_map[] = { {"ADC2_OUTPUT", NULL, "ADC2_MIXER"}, {"ADC2_MIXER", "Switch", "ADC2 REQ"}, {"ADC2 REQ", "NULL", "ADC2"}, {"ADC2", "NULL", "ADC2 MUX"}, {"ADC2 MUX", "INP3", "AMIC3"}, {"ADC2 MUX", "INP2", "AMIC2"}, {"ADC1_OUTPUT", NULL, "ADC1_MIXER"}, {"ADC1_MIXER", "Switch", "ADC1 REQ"}, {"ADC1 REQ", NULL, "ADC1"}, {"ADC1", NULL, "AMIC1"}, {"RX1", NULL, "IN1_HPHL"}, {"RDAC1", NULL, "RX1"}, {"HPHL_RDAC", "Switch", "RDAC1"}, {"HPHL PGA", NULL, "HPHL_RDAC"}, {"HPHL", NULL, "HPHL PGA"}, {"RX2", NULL, "IN2_HPHR"}, {"RDAC2", NULL, "RX2"}, {"HPHR_RDAC", "Switch", "RDAC2"}, {"HPHR PGA", NULL, "HPHR_RDAC"}, {"HPHR", NULL, "HPHR PGA"}, {"RX3", NULL, "IN3_AUX"}, {"RDAC4", NULL, "RX3"}, {"AUX_RDAC", "Switch", "RDAC4"}, {"AUX PGA", NULL, "AUX_RDAC"}, {"AUX", NULL, "AUX PGA"}, {"RDAC3_MUX", "RX3", "RX3"}, {"RDAC3_MUX", "RX1", "RX1"}, {"RDAC3", NULL, "RDAC3_MUX"}, {"EAR_RDAC", "Switch", "RDAC3"}, {"EAR PGA", NULL, "EAR_RDAC"}, {"EAR", NULL, "EAR PGA"}, }; static const struct snd_soc_dapm_route wcd9375_audio_map[] = { {"ADC3_OUTPUT", NULL, "ADC3_MIXER"}, {"ADC3_MIXER", "Switch", "ADC3 REQ"}, {"ADC3 REQ", NULL, "ADC3"}, {"ADC3", NULL, "AMIC4"}, {"DMIC1_OUTPUT", NULL, "DMIC1_MIXER"}, {"DMIC1_MIXER", "Switch", "DMIC1"}, {"DMIC2_OUTPUT", NULL, "DMIC2_MIXER"}, {"DMIC2_MIXER", "Switch", "DMIC2"}, {"DMIC3_OUTPUT", NULL, "DMIC3_MIXER"}, {"DMIC3_MIXER", "Switch", "DMIC3"}, {"DMIC4_OUTPUT", NULL, "DMIC4_MIXER"}, {"DMIC4_MIXER", "Switch", "DMIC4"}, {"DMIC5_OUTPUT", NULL, "DMIC5_MIXER"}, {"DMIC5_MIXER", "Switch", "DMIC5"}, {"DMIC6_OUTPUT", NULL, "DMIC6_MIXER"}, {"DMIC6_MIXER", "Switch", "DMIC6"}, }; static int wcd937x_soc_codec_probe(struct snd_soc_codec *codec) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); int variant; int ret = -EINVAL; dev_info(codec->dev, "%s()\n", __func__); wcd937x = snd_soc_codec_get_drvdata(codec); if (!wcd937x) return -EINVAL; wcd937x->codec = codec; variant = (snd_soc_read(codec, WCD937X_DIGITAL_EFUSE_REG_0) & 0x0E) >> 1; wcd937x->variant = variant; wcd937x->fw_data = devm_kzalloc(codec->dev, sizeof(*(wcd937x->fw_data)), GFP_KERNEL); if (!wcd937x->fw_data) { dev_err(codec->dev, "Failed to allocate fw_data\n"); ret = -ENOMEM; goto err; } set_bit(WCD9XXX_MBHC_CAL, wcd937x->fw_data->cal_bit); ret = wcd_cal_create_hwdep(wcd937x->fw_data, WCD9XXX_CODEC_HWDEP_NODE, codec); if (ret < 0) { dev_err(codec->dev, "%s hwdep failed %d\n", __func__, ret); goto err_hwdep; } ret = wcd937x_mbhc_init(&wcd937x->mbhc, codec, wcd937x->fw_data); if (ret) { pr_err("%s: mbhc initialization failed\n", __func__); goto err_hwdep; } wcd937x_init_reg(codec); if (wcd937x->variant == WCD9375_VARIANT) { ret = snd_soc_dapm_new_controls(dapm, wcd9375_dapm_widgets, ARRAY_SIZE(wcd9375_dapm_widgets)); if (ret < 0) { dev_err(codec->dev, "%s: Failed to add snd_ctls\n", __func__); goto err_hwdep; } ret = snd_soc_dapm_add_routes(dapm, wcd9375_audio_map, ARRAY_SIZE(wcd9375_audio_map)); if (ret < 0) { dev_err(codec->dev, "%s: Failed to add routes\n", __func__); goto err_hwdep; } ret = snd_soc_dapm_new_widgets(dapm->card); if (ret < 0) { dev_err(codec->dev, "%s: Failed to add widgets\n", __func__); goto err_hwdep; } } return ret; err_hwdep: wcd937x->fw_data = NULL; err: return ret; } static int wcd937x_soc_codec_remove(struct snd_soc_codec *codec) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); if (!wcd937x) return -EINVAL; return 0; } static struct regmap *wcd937x_get_regmap(struct device *dev) { struct wcd937x_priv *wcd937x = dev_get_drvdata(dev); return wcd937x->regmap; } static struct snd_soc_codec_driver soc_codec_dev_wcd937x = { .probe = wcd937x_soc_codec_probe, .remove = wcd937x_soc_codec_remove, .get_regmap = wcd937x_get_regmap, .component_driver = { .controls = wcd937x_snd_controls, .num_controls = ARRAY_SIZE(wcd937x_snd_controls), .dapm_widgets = wcd937x_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wcd937x_dapm_widgets), .dapm_routes = wcd937x_audio_map, .num_dapm_routes = ARRAY_SIZE(wcd937x_audio_map), }, }; int wcd937x_reset(struct device *dev) { struct wcd937x_priv *wcd937x = NULL; int rc = 0; int value = 0; if (!dev) return -ENODEV; wcd937x = dev_get_drvdata(dev); if (!wcd937x) return -EINVAL; if (!wcd937x->rst_np) { dev_err(dev, "%s: reset gpio device node not specified\n", __func__); return -EINVAL; } value = msm_cdc_pinctrl_get_state(wcd937x->rst_np); if (value > 0) return 0; rc = msm_cdc_pinctrl_select_sleep_state(wcd937x->rst_np); if (rc) { dev_err(dev, "%s: wcd sleep state request fail!\n", __func__); return rc; } /* 20ms sleep required after pulling the reset gpio to LOW */ usleep_range(20, 30); rc = msm_cdc_pinctrl_select_active_state(wcd937x->rst_np); if (rc) { dev_err(dev, "%s: wcd active state request fail!\n", __func__); return rc; } /* 20ms sleep required after pulling the reset gpio to HIGH */ usleep_range(20, 30); return rc; } struct wcd937x_pdata *wcd937x_populate_dt_data(struct device *dev) { struct wcd937x_pdata *pdata = NULL; pdata = devm_kzalloc(dev, sizeof(struct wcd937x_pdata), GFP_KERNEL); if (!pdata) return NULL; pdata->rst_np = of_parse_phandle(dev->of_node, "qcom,wcd-rst-gpio-node", 0); if (!pdata->rst_np) { dev_err(dev, "%s: Looking up %s property in node %s failed\n", __func__, "qcom,wcd-rst-gpio-node", dev->of_node->full_name); return NULL; } /* Parse power supplies */ msm_cdc_get_power_supplies(dev, &pdata->regulator, &pdata->num_supplies); if (!pdata->regulator || (pdata->num_supplies <= 0)) { dev_err(dev, "%s: no power supplies defined for codec\n", __func__); return NULL; } pdata->rx_slave = of_parse_phandle(dev->of_node, "qcom,rx-slave", 0); pdata->tx_slave = of_parse_phandle(dev->of_node, "qcom,tx-slave", 0); return pdata; } static int wcd937x_bind(struct device *dev) { int ret = 0, i = 0; struct wcd937x_priv *wcd937x = NULL; struct wcd937x_pdata *pdata = NULL; wcd937x = devm_kzalloc(dev, sizeof(struct wcd937x_priv), GFP_KERNEL); if (!wcd937x) return -ENOMEM; dev_set_drvdata(dev, wcd937x); pdata = wcd937x_populate_dt_data(dev); if (!pdata) { dev_err(dev, "%s: Fail to obtain platform data\n", __func__); return -EINVAL; } wcd937x->rst_np = pdata->rst_np; ret = msm_cdc_init_supplies(dev, &wcd937x->supplies, pdata->regulator, pdata->num_supplies); if (!wcd937x->supplies) { dev_err(dev, "%s: Cannot init wcd supplies\n", __func__); return ret; } ret = msm_cdc_enable_static_supplies(dev, wcd937x->supplies, pdata->regulator, pdata->num_supplies); if (ret) { dev_err(dev, "%s: wcd static supply enable failed!\n", __func__); return ret; } wcd937x_reset(dev); /* * Add 5msec delay to provide sufficient time for * soundwire auto enumeration of slave devices as * as per HW requirement. */ usleep_range(5000, 5010); ret = component_bind_all(dev, wcd937x); if (ret) { dev_err(dev, "%s: Slave bind failed, ret = %d\n", __func__, ret); return ret; } ret = wcd937x_parse_port_mapping(dev, "qcom,rx_swr_ch_map", CODEC_RX); ret |= wcd937x_parse_port_mapping(dev, "qcom,tx_swr_ch_map", CODEC_TX); if (ret) { dev_err(dev, "Failed to read port mapping\n"); goto err; } wcd937x->rx_swr_dev = get_matching_swr_slave_device(pdata->rx_slave); if (!wcd937x->rx_swr_dev) { dev_err(dev, "%s: Could not find RX swr slave device\n", __func__); ret = -ENODEV; goto err; } wcd937x->tx_swr_dev = get_matching_swr_slave_device(pdata->tx_slave); if (!wcd937x->tx_swr_dev) { dev_err(dev, "%s: Could not find TX swr slave device\n", __func__); ret = -ENODEV; goto err; } wcd937x->regmap = devm_regmap_init_swr(wcd937x->tx_swr_dev, &wcd937x_regmap_config); if (!wcd937x->regmap) { dev_err(dev, "%s: Regmap init failed\n", __func__); goto err; } /* Set all interupts as edge triggered */ for (i = 0; i < wcd937x_regmap_irq_chip.num_regs; i++) regmap_write(wcd937x->regmap, (WCD937X_DIGITAL_INTR_LEVEL_0 + i), 0); wcd937x->irq_info->wcd_regmap_irq_chip = &wcd937x_regmap_irq_chip; wcd937x->irq_info->codec_name = "WCD937X"; wcd937x->irq_info->regmap = wcd937x->regmap; wcd937x->irq_info->dev = dev; ret = wcd_irq_init(wcd937x->irq_info, &wcd937x->virq); if (ret) { dev_err(wcd937x->dev, "%s: IRQ init failed: %d\n", __func__, ret); goto err; } wcd937x->tx_swr_dev->slave_irq = wcd937x->virq; ret = snd_soc_register_codec(dev, &soc_codec_dev_wcd937x, NULL, 0); if (ret) { dev_err(dev, "%s: Codec registration failed\n", __func__); goto err_irq; } return ret; err_irq: wcd_irq_exit(wcd937x->irq_info, wcd937x->virq); err: component_unbind_all(dev, wcd937x); return ret; } static void wcd937x_unbind(struct device *dev) { struct wcd937x_priv *wcd937x = dev_get_drvdata(dev); wcd_irq_exit(wcd937x->irq_info, wcd937x->virq); snd_soc_unregister_codec(dev); component_unbind_all(dev, wcd937x); } static const struct of_device_id wcd937x_dt_match[] = { { .compatible = "qcom,wcd937x-codec" }, {} }; static const struct component_master_ops wcd937x_comp_ops = { .bind = wcd937x_bind, .unbind = wcd937x_unbind, }; static int wcd937x_compare_of(struct device *dev, void *data) { return dev->of_node == data; } static void wcd937x_release_of(struct device *dev, void *data) { of_node_put(data); } static int wcd937x_add_slave_components(struct device *dev, struct component_match **matchptr) { struct device_node *np, *rx_node, *tx_node; np = dev->of_node; rx_node = of_parse_phandle(np, "qcom,rx-slave", 0); if (!rx_node) { dev_err(dev, "%s: Rx-slave node not defined\n", __func__); return -ENODEV; } of_node_get(rx_node); component_match_add_release(dev, matchptr, wcd937x_release_of, wcd937x_compare_of, rx_node); tx_node = of_parse_phandle(np, "qcom,tx-slave", 0); if (!tx_node) { dev_err(dev, "%s: Tx-slave node not defined\n", __func__); return -ENODEV; } of_node_get(tx_node); component_match_add_release(dev, matchptr, wcd937x_release_of, wcd937x_compare_of, tx_node); return 0; } static int wcd937x_probe(struct platform_device *pdev) { struct component_match *match = NULL; int ret; ret = wcd937x_add_slave_components(&pdev->dev, &match); if (ret) return ret; return component_master_add_with_match(&pdev->dev, &wcd937x_comp_ops, match); } static int wcd937x_remove(struct platform_device *pdev) { component_master_del(&pdev->dev, &wcd937x_comp_ops); return 0; } static struct platform_driver wcd937x_codec_driver = { .probe = wcd937x_probe, .remove = wcd937x_remove, .driver = { .name = "wcd937x_codec", .owner = THIS_MODULE, .of_match_table = of_match_ptr(wcd937x_dt_match), }, }; module_platform_driver(wcd937x_codec_driver); MODULE_DESCRIPTION("WCD937X Codec driver"); MODULE_LICENSE("GPL v2");