[PATCH v3 3/7] ASoC: codecs: wcd938x: add basic driver
Srinivas Kandagatla
srinivas.kandagatla at linaro.org
Mon Mar 22 11:27:17 CET 2021
On 19/03/2021 15:59, Pierre-Louis Bossart wrote:
>
>
> On 3/19/21 4:29 AM, Srinivas Kandagatla wrote:
>> This patch adds basic SoundWire codec driver to support for
>> WCD938X TX and RX devices.
>
> It took me a while to figure out that you are adding support for a codec
> that has 2 Slave interfaces internally, one for TX and one for RX dais.
>
> Each of the interfaces will appear as a separate Linux device, even
> though they are physically in the same hardware component.
>
> That perfectly legit from a SoundWire standpoint, but I wonder how you
> are going to handle pm_runtime and regmap access if the two parts are
> joined at the hip for imp-def register access (described in the cover
> letter as "Even though this device has two SoundWire devices, only tx
> device has access to main codec Control/Status Registers!").
>
> I was clearly unable to figure out how regmap/gpios/regulator were
> handled between the two TX and TX parts.
>
tx regmap is shared between both the devices.
Regulators are also handled in common code for tx and rx device.
I added more detailed reply in cover letter reply.
> See more below.
>
>> diff --git a/sound/soc/codecs/wcd938x-sdw.c
>> b/sound/soc/codecs/wcd938x-sdw.c
>> new file mode 100644
>> index 000000000000..ca29793b0972
>> --- /dev/null
>> +++ b/sound/soc/codecs/wcd938x-sdw.c
>> @@ -0,0 +1,291 @@
>> +// SPDX-License-Identifier: GPL-2.0
>
> GPL-2.0-only for consistency with the other additions below?
>
>
Sure will fix that!
>> +static int wcd9380_probe(struct sdw_slave *pdev,
>> + const struct sdw_device_id *id)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct wcd938x_sdw_priv *wcd;
>> + const char *dir = "rx";
>> + int ret;
>> +
>> + wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL);
>> + if (!wcd)
>> + return -ENOMEM;
>> +
>> + of_property_read_string(dev->of_node, "direction", &dir);
>> + if (!strcmp(dir, "tx"))
>> + wcd->is_tx = true;
>> + else
>> + wcd->is_tx = false;
>> +
>> +
>
> extra line
>
>> + ret = of_property_read_variable_u32_array(dev->of_node,
>> "qcom,port-mapping",
>> + wcd->port_map,
>> + WCD938X_MAX_TX_SWR_PORTS,
>> + WCD938X_MAX_SWR_PORTS);
>> + if (ret)
>> + dev_info(dev, "Static Port mapping not specified\n");
>> +
>> + wcd->sdev = pdev;
>> + dev_set_drvdata(dev, wcd);
>> + ret = wcd938x_init(wcd);
>> + if (ret)
>> + return ret;
>> +
>> + pdev->prop.scp_int1_mask = SDW_SCP_INT1_IMPL_DEF |
>> + SDW_SCP_INT1_BUS_CLASH |
>> + SDW_SCP_INT1_PARITY;
>> + pdev->prop.lane_control_support = true;
>> + if (wcd->is_tx) {
>> + pdev->prop.source_ports = GENMASK(WCD938X_MAX_SWR_PORTS, 0);
>> + pdev->prop.src_dpn_prop = wcd938x_dpn_prop;
>> + wcd->ch_info = &wcd938x_sdw_tx_ch_info[0];
>> + pdev->prop.wake_capable = true;
>> + } else {
>> + pdev->prop.sink_ports = GENMASK(WCD938X_MAX_SWR_PORTS, 0);
>> + pdev->prop.sink_dpn_prop = wcd938x_dpn_prop;
>> + wcd->ch_info = &wcd938x_sdw_rx_ch_info[0];
>> + }
>> +
>> + if (wcd->is_tx)
>> + return wcd938x_register_component(wcd, dev, &wcd938x_tx_dai);
>> + else
>> + return wcd938x_register_component(wcd, dev, &wcd938x_rx_dai);
>> +
>> +}
>> +
>> +static const struct sdw_device_id wcd9380_slave_id[] = {
>> + SDW_SLAVE_ENTRY(0x0217, 0x10d, 0),
>
> does this mean you cannot determine the functionality of the device by
> looking at the devId registers?
>
No, we can not, they have same device id.
> I would have expected each interface to have a different part ID to show
> that they are different...such tricks would not work in the ACPI world
> at the moment, the expectation was really that different part numbers
> are unique enough to know what to expect.
>
>
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(sdw, wcd9380_slave_id);
>> +
>> +static struct sdw_driver wcd9380_codec_driver = {
>> + .probe = wcd9380_probe,
>> + .ops = &wcd9380_slave_ops,
>> + .id_table = wcd9380_slave_id,
>> + .driver = {
>> + .name = "wcd9380-codec",
>> + }
>> +};
>> +module_sdw_driver(wcd9380_codec_driver);
>
> [...]
>
>> +static bool wcd938x_readable_register(struct device *dev, unsigned
>> int reg)
>> +{
>> + bool ret;
>> +
>> + ret = wcd938x_readonly_register(dev, reg);
>> + if (!ret)
>> + return wcd938x_rdwr_register(dev, reg);
>> +
>> + return ret;
>> +}
>> +
>> +static bool wcd938x_writeable_register(struct device *dev, unsigned
>> int reg)
>> +{
>> + return wcd938x_rdwr_register(dev, reg);
>> +}
>> +
>> +static bool wcd938x_volatile_register(struct device *dev, unsigned
>> int reg)
>> +{
>> + if (reg <= WCD938X_BASE_ADDRESS)
>> + return 0;
>> +
>> + if (reg == WCD938X_DIGITAL_SWR_TX_CLK_RATE)
>> + return true;
>> +
>> + if (wcd938x_readonly_register(dev, reg))
>> + return true;
>> +
>> + return false;
>> +}
>
> this part is quite confusing, you mentioned that only the TX interface
> has access to registers, but here you seem to expose regmap registers
> for both TX and RX?
No, this regmap is only for tx.
Am happy to prefix them with tx if it helps.
>> +
>> +static struct regmap_config wcd938x_regmap_config = {
>> + .name = "wcd938x_csr",
>> + .reg_bits = 32,
>> + .val_bits = 8,
>> + .cache_type = REGCACHE_RBTREE,
>> + .reg_defaults = wcd938x_defaults,
>> + .num_reg_defaults = ARRAY_SIZE(wcd938x_defaults),
>> + .max_register = WCD938X_MAX_REGISTER,
>> + .readable_reg = wcd938x_readable_register,
>> + .writeable_reg = wcd938x_writeable_register,
>> + .volatile_reg = wcd938x_volatile_register,
>> + .can_multi_write = true,
>> +};
>> +
>> +static const struct regmap_irq wcd938x_irqs[WCD938X_NUM_IRQS] = {
>> + REGMAP_IRQ_REG(WCD938X_IRQ_MBHC_BUTTON_PRESS_DET, 0, 0x01),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_MBHC_BUTTON_RELEASE_DET, 0, 0x02),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_MBHC_ELECT_INS_REM_DET, 0, 0x04),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, 0, 0x08),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_MBHC_SW_DET, 0, 0x10),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHR_OCP_INT, 0, 0x20),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHR_CNP_INT, 0, 0x40),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHL_OCP_INT, 0, 0x80),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHL_CNP_INT, 1, 0x01),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_EAR_CNP_INT, 1, 0x02),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_EAR_SCD_INT, 1, 0x04),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_AUX_CNP_INT, 1, 0x08),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_AUX_SCD_INT, 1, 0x10),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHL_PDM_WD_INT, 1, 0x20),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHR_PDM_WD_INT, 1, 0x40),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_AUX_PDM_WD_INT, 1, 0x80),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_LDORT_SCD_INT, 2, 0x01),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_MBHC_MOISTURE_INT, 2, 0x02),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHL_SURGE_DET_INT, 2, 0x04),
>> + REGMAP_IRQ_REG(WCD938X_IRQ_HPHR_SURGE_DET_INT, 2, 0x08),
>> +};
>> +
>> +static struct regmap_irq_chip wcd938x_regmap_irq_chip = {
>> + .name = "wcd938x",
>> + .irqs = wcd938x_irqs,
>> + .num_irqs = ARRAY_SIZE(wcd938x_irqs),
>> + .num_regs = 3,
>> + .status_base = WCD938X_DIGITAL_INTR_STATUS_0,
>> + .mask_base = WCD938X_DIGITAL_INTR_MASK_0,
>> + .type_base = WCD938X_DIGITAL_INTR_LEVEL_0,
>> + .ack_base = WCD938X_DIGITAL_INTR_CLEAR_0,
>> + .use_ack = 1,
>> + .runtime_pm = true,
>> + .irq_drv_data = NULL,
>> +};
>> +
>> +int wcd938x_io_init(struct wcd938x_sdw_priv *wcd)
>> +{
>> + struct regmap *rm = wcd->wcd938x->regmap;
>> +
>> + if (!wcd->is_tx)
>> + return 0;
>
> and here you only initialize the registers for the tx case, so what
> happens you you dump the rx registers or try to set them with
> /sys/kernel/debug/regmap?
wcd->wcd938x->regmap is regmap for tx device.
There should be no regmap for rx device at all!
There will be only one entry for wcd938x in /sys/kernel/debug/regmap
which is
/sys/kernel/debug/regmap/sdw:0:217:10d:0:3-wcd938x_csr
>
>> +
>> + regmap_update_bits(rm, WCD938X_SLEEP_CTL, 0x0E, 0x0E);
>> + regmap_update_bits(rm, WCD938X_SLEEP_CTL, 0x80, 0x80);
>> + /* 1 msec delay as per HW requirement */
>> + usleep_range(1000, 1010);
>> + regmap_update_bits(rm, WCD938X_SLEEP_CTL, 0x40, 0x40);
>> + /* 1 msec delay as per HW requirement */
>> + usleep_range(1000, 1010);
>> + regmap_update_bits(rm, WCD938X_LDORXTX_CONFIG, 0x10, 0x00);
>> + regmap_update_bits(rm, WCD938X_BIAS_VBG_FINE_ADJ,
>> + 0xF0, 0x80);
>> + regmap_update_bits(rm, WCD938X_ANA_BIAS, 0x80, 0x80);
>> + regmap_update_bits(rm, WCD938X_ANA_BIAS, 0x40, 0x40);
>> + /* 10 msec delay as per HW requirement */
>> + usleep_range(10000, 10010);
>> +
>> + regmap_update_bits(rm, WCD938X_ANA_BIAS, 0x40, 0x00);
>> + regmap_update_bits(rm, WCD938X_HPH_NEW_INT_RDAC_GAIN_CTL,
>> + 0xF0, 0x00);
>> + regmap_update_bits(rm, WCD938X_HPH_NEW_INT_RDAC_HD2_CTL_L_NEW,
>> + 0x1F, 0x15);
>> + regmap_update_bits(rm, WCD938X_HPH_NEW_INT_RDAC_HD2_CTL_R_NEW,
>> + 0x1F, 0x15);
>> + regmap_update_bits(rm, WCD938X_HPH_REFBUFF_UHQA_CTL,
>> + 0xC0, 0x80);
>> + regmap_update_bits(rm, WCD938X_DIGITAL_CDC_DMIC_CTL,
>> + 0x02, 0x02);
>> +
>> + regmap_update_bits(rm,
>> WCD938X_TX_COM_NEW_INT_TXFE_ICTRL_STG2CASC_ULP,
>> + 0xFF, 0x14);
>> + regmap_update_bits(rm,
>> WCD938X_TX_COM_NEW_INT_TXFE_ICTRL_STG2MAIN_ULP,
>> + 0x1F, 0x08);
>> +
>> + regmap_update_bits(rm, WCD938X_DIGITAL_TX_REQ_FB_CTL_0, 0xFF, 0x55);
>> + regmap_update_bits(rm, WCD938X_DIGITAL_TX_REQ_FB_CTL_1, 0xFF, 0x44);
>> + regmap_update_bits(rm, WCD938X_DIGITAL_TX_REQ_FB_CTL_2, 0xFF, 0x11);
>> + regmap_update_bits(rm, WCD938X_DIGITAL_TX_REQ_FB_CTL_3, 0xFF, 0x00);
>> + regmap_update_bits(rm, WCD938X_DIGITAL_TX_REQ_FB_CTL_4, 0xFF, 0x00);
>> +
>> + /* Set Noise Filter Resistor value */
>> + regmap_update_bits(rm, WCD938X_MICB1_TEST_CTL_1, 0xE0, 0xE0);
>> + regmap_update_bits(rm, WCD938X_MICB2_TEST_CTL_1, 0xE0, 0xE0);
>> + regmap_update_bits(rm, WCD938X_MICB3_TEST_CTL_1, 0xE0, 0xE0);
>> + regmap_update_bits(rm, WCD938X_MICB4_TEST_CTL_1, 0xE0, 0xE0);
>> +
>> + regmap_update_bits(rm, WCD938X_TX_3_4_TEST_BLK_EN2, 0x01, 0x00);
>> + regmap_update_bits(rm, WCD938X_HPH_SURGE_HPHLR_SURGE_EN, 0xC0,
>> 0xC0);
>> +
>> + return 0;
>> +
>> +}
>> +
>> +static int wcd938x_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)
>> + return -EINVAL;
>> +
>> + return (micb_mv - 1000) / 50;
>> +}
>> +
>> +static int wcd938x_set_micbias_data(struct wcd938x_priv *wcd938x)
>> +{
>> + int vout_ctl_1, vout_ctl_2, vout_ctl_3, vout_ctl_4;
>> +
>> + /* set micbias voltage */
>> + vout_ctl_1 = wcd938x_get_micb_vout_ctl_val(wcd938x->micb1_mv);
>> + vout_ctl_2 = wcd938x_get_micb_vout_ctl_val(wcd938x->micb2_mv);
>> + vout_ctl_3 = wcd938x_get_micb_vout_ctl_val(wcd938x->micb3_mv);
>> + vout_ctl_4 = wcd938x_get_micb_vout_ctl_val(wcd938x->micb4_mv);
>> + if (vout_ctl_1 < 0 || vout_ctl_2 < 0 || vout_ctl_3 < 0 ||
>> vout_ctl_4 < 0)
>> + return -EINVAL;
>> +
>> + regmap_update_bits(wcd938x->regmap, WCD938X_ANA_MICB1,
>> + WCD938X_MICB_VOUT_MASK, vout_ctl_1);
>> + regmap_update_bits(wcd938x->regmap, WCD938X_ANA_MICB2,
>> + WCD938X_MICB_VOUT_MASK, vout_ctl_2);
>> + regmap_update_bits(wcd938x->regmap, WCD938X_ANA_MICB3,
>> + WCD938X_MICB_VOUT_MASK, vout_ctl_3);
>> + regmap_update_bits(wcd938x->regmap, WCD938X_ANA_MICB4,
>> + WCD938X_MICB_VOUT_MASK, vout_ctl_4);
>> +
>> + return 0;
>> +}
>> +
>> +static int wcd938x_soc_codec_rx_probe(struct snd_soc_component
>> *component)
>> +{
>> + struct wcd938x_sdw_priv *wcd =
>> snd_soc_component_get_drvdata(component);
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> +
>> + snd_soc_component_init_regmap(component, wcd938x->regmap);
>> +
>> + wcd938x->clsh_info = wcd_clsh_ctrl_alloc(component, WCD938X);
>> +
>> + return 0;
>> +}
>> +
>> +static irqreturn_t wcd938x_wd_handle_irq(int irq, void *data)
>> +{
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static struct irq_chip wcd_irq_chip = {
>> + .name = "WCD938x",
>> +};
>> +
>> +static int wcd_irq_chip_map(struct irq_domain *irqd, unsigned int virq,
>> + irq_hw_number_t hw)
>> +{
>> + irq_set_chip_and_handler(virq, &wcd_irq_chip, handle_simple_irq);
>> + irq_set_nested_thread(virq, 1);
>> + irq_set_noprobe(virq);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct irq_domain_ops wcd_domain_ops = {
>> + .map = wcd_irq_chip_map,
>> +};
>> +
>> +static int wcd938x_irq_init(struct wcd938x_sdw_priv *data, struct
>> device *dev)
>> +{
>> + struct wcd938x_priv *wcd = data->wcd938x;
>> +
>> + wcd->virq = irq_domain_add_linear(NULL, 1, &wcd_domain_ops, NULL);
>> + if (!(wcd->virq)) {
>> + dev_err(dev, "%s: Failed to add IRQ domain\n", __func__);
>> + return -EINVAL;
>> + }
>> +
>> + return devm_regmap_add_irq_chip(dev, wcd->regmap,
>> + irq_create_mapping(wcd->virq, 0),
>> + IRQF_ONESHOT, 0, &wcd938x_regmap_irq_chip,
>> + &wcd->irq_chip);
>> +}
>> +
>> +static int wcd938x_soc_codec_probe(struct snd_soc_component *component)
>
> is this supposed to be soc_tx_codec_probe()?
>
Yes, I will rename such fuctions explictly in next version to avoid
confusion.
>> +{
>> + struct wcd938x_sdw_priv *wcd =
>> snd_soc_component_get_drvdata(component);
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> + struct device *dev = component->dev;
>> + int ret, i;
>> +
>> + wait_for_completion_timeout(&wcd->sdev->initialization_complete,
>> + msecs_to_jiffies(3000));
>> +
>> + snd_soc_component_init_regmap(component, wcd938x->regmap);
>> +
>> + wcd938x->variant = snd_soc_component_read_field(component,
>> + WCD938X_DIGITAL_EFUSE_REG_0,
>> + WCD938X_ID_MASK);
>> +
>> + /* Set all interrupts as edge triggered */
>> + for (i = 0; i < wcd938x_regmap_irq_chip.num_regs; i++) {
>> + regmap_write(wcd938x->regmap,
>> + (WCD938X_DIGITAL_INTR_LEVEL_0 + i), 0);
>> + }
>> +
>> + ret = wcd938x_irq_init(wcd, component->dev);
>> + if (ret) {
>> + dev_err(component->dev, "%s: IRQ init failed: %d\n",
>> + __func__, ret);
>> + return ret;
>> + }
>> +
>> + wcd938x->hphr_pdm_wd_int = regmap_irq_get_virq(wcd938x->irq_chip,
>> + WCD938X_IRQ_HPHR_PDM_WD_INT);
>> + wcd938x->hphl_pdm_wd_int = regmap_irq_get_virq(wcd938x->irq_chip,
>> + WCD938X_IRQ_HPHL_PDM_WD_INT);
>> + wcd938x->aux_pdm_wd_int = regmap_irq_get_virq(wcd938x->irq_chip,
>> + WCD938X_IRQ_AUX_PDM_WD_INT);
>> +
>> + /* Request for watchdog interrupt */
>> + ret = request_threaded_irq(wcd938x->hphr_pdm_wd_int, NULL,
>> wcd938x_wd_handle_irq,
>> + IRQF_ONESHOT | IRQF_TRIGGER_RISING,
>> + "HPHR PDM WD INT", wcd938x);
>> + if (ret)
>> + dev_err(dev, "Failed to request HPHR WD interrupt (%d)\n", ret);
>> +
>> + ret = request_threaded_irq(wcd938x->hphl_pdm_wd_int, NULL,
>> wcd938x_wd_handle_irq,
>> + IRQF_ONESHOT | IRQF_TRIGGER_RISING,
>> + "HPHL PDM WD INT", wcd938x);
>> + if (ret)
>> + dev_err(dev, "Failed to request HPHL WD interrupt (%d)\n", ret);
>> +
>> + ret = request_threaded_irq(wcd938x->aux_pdm_wd_int, NULL,
>> wcd938x_wd_handle_irq,
>> + IRQF_ONESHOT | IRQF_TRIGGER_RISING,
>> + "AUX PDM WD INT", wcd938x);
>> + if (ret)
>> + dev_err(dev, "Failed to request Aux WD interrupt (%d)\n", ret);
>> +
>> + /* Disable watchdog interrupt for HPH and AUX */
>> + disable_irq_nosync(wcd938x->hphr_pdm_wd_int);
>> + disable_irq_nosync(wcd938x->hphl_pdm_wd_int);
>> + disable_irq_nosync(wcd938x->aux_pdm_wd_int);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct snd_soc_component_driver
>> soc_codec_dev_wcd938x_sdw_rx = {
>> + .name = "wcd938x_codec_rx",
>> + .probe = wcd938x_soc_codec_rx_probe,
>> +};
>> +
>> +static const struct snd_soc_component_driver
>> soc_codec_dev_wcd938x_sdw_tx = {
>> + .name = "wcd938x_codec_tx",
>> + .probe = wcd938x_soc_codec_probe,
>> +};
>> +
>> +static void wcd938x_dt_parse_micbias_info(struct device *dev, struct
>> wcd938x_priv *wcd)
>> +{
>> + struct device_node *np = dev->of_node;
>> + u32 prop_val = 0;
>> + int rc = 0;
>> +
>> + rc = of_property_read_u32(np, "qcom,micbias1-microvolt",
>> &prop_val);
>> + if (!rc)
>> + wcd->micb1_mv = prop_val/1000;
>> + else
>> + dev_info(dev, "%s: Micbias1 DT property not found\n", __func__);
>> +
>> + rc = of_property_read_u32(np, "qcom,micbias2-microvolt",
>> &prop_val);
>> + if (!rc)
>> + wcd->micb2_mv = prop_val/1000;
>> + else
>> + dev_info(dev, "%s: Micbias2 DT property not found\n", __func__);
>> +
>> + rc = of_property_read_u32(np, "qcom,micbias3-microvolt", &prop_val);
>> + if (!rc)
>> + wcd->micb3_mv = prop_val/1000;
>> + else
>> + dev_info(dev, "%s: Micbias3 DT property not found\n", __func__);
>> +
>> + rc = of_property_read_u32(np, "qcom,micbias4-microvolt",
>> &prop_val);
>> + if (!rc)
>> + wcd->micb4_mv = prop_val/1000;
>> + else
>> + dev_info(dev, "%s: Micbias4 DT property not found\n", __func__);
>> +}
>> +
>> +static int wcd938x_populate_dt_data(struct wcd938x_sdw_priv *wcd,
>> struct device *dev)
>> +{
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> + int ret;
>> +
>> +
>
> extra lines
>
>> + wcd938x->reset_gpio = of_get_named_gpio(dev->of_node,
>> "reset-gpios", 0);
>> + if (wcd938x->reset_gpio < 0) {
>> + dev_err(dev, "Failed to get reset gpio: err = %d\n",
>> + wcd938x->reset_gpio);
>> + return wcd938x->reset_gpio;
>> + }
>> +
>> + wcd938x->supplies[0].supply = "vdd-rxtx";
>> + wcd938x->supplies[1].supply = "vdd-io";
>> + wcd938x->supplies[2].supply = "vdd-buck";
>> + wcd938x->supplies[3].supply = "vdd-mic-bias";
>> +
>> + ret = regulator_bulk_get(dev, WCD938X_MAX_SUPPLY,
>> wcd938x->supplies);
>> + if (ret) {
>> + dev_err(dev, "Failed to get supplies: err = %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = regulator_bulk_enable(WCD938X_MAX_SUPPLY, wcd938x->supplies);
>> + if (ret) {
>> + dev_err(dev, "Failed to enable supplies: err = %d\n", ret);
>> + return ret;
>> + }
>> +
>> + wcd938x_dt_parse_micbias_info(dev, wcd938x);
>> + return 0;
>> +}
>> +
>> +static int wcd938x_reset(struct wcd938x_sdw_priv *wcd, struct device
>> *dev)
>> +{
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> +
>> + gpio_direction_output(wcd938x->reset_gpio, 0);
>> + /* 20us sleep required after pulling the reset gpio to LOW */
>> + usleep_range(20, 30);
>> + gpio_set_value(wcd938x->reset_gpio, 1);
>> + /* 20us sleep required after pulling the reset gpio to HIGH */
>> + usleep_range(20, 30);
>
> so each Slave device has its own GPIOs and regulators?
>
unfortunately No, reset gpio and regulators are common for the whole codec.
>> +
>> + return 0;
>> +}
>> +
>> +static int __wcd938x_init(struct wcd938x_sdw_priv *wcd, struct device
>> *dev)
>> +{
>> + int ret;
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> +
>> + wcd938x_reset(wcd, dev);
>> +
>> + wcd938x->regmap = devm_regmap_init_sdw(wcd->sdev,
>> &wcd938x_regmap_config);
>> + if (IS_ERR(wcd938x->regmap)) {
>> + dev_err(dev, "%s: Regmap init failed\n", __func__);
>> + return PTR_ERR(wcd938x->regmap);
>> + }
>> +
>> + ret = wcd938x_set_micbias_data(wcd938x);
>> + if (ret < 0) {
>> + dev_err(dev, "%s: bad micbias data\n", __func__);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +int wcd938x_register_component(struct wcd938x_sdw_priv *wcd, struct
>> device *dev,
>> + struct snd_soc_dai_driver *dai_driver)
>> +{
>> + if (wcd->is_tx)
>> + return snd_soc_register_component(dev,
>> + &soc_codec_dev_wcd938x_sdw_tx,
>> + dai_driver, 1);
>> + else
>> + return snd_soc_register_component(dev,
>> + &soc_codec_dev_wcd938x_sdw_rx,
>> + dai_driver, 1);
>> +}
>> +
>> +int wcd938x_handle_sdw_irq(struct wcd938x_sdw_priv *wcd)
>> +{
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> + struct irq_domain *slave_irq = wcd938x->virq;
>> + u32 sts1, sts2, sts3;
>> +
>> + do {
>> + handle_nested_irq(irq_find_mapping(slave_irq, 0));
>> + regmap_read(wcd938x->regmap, WCD938X_DIGITAL_INTR_STATUS_0,
>> &sts1);
>> + regmap_read(wcd938x->regmap, WCD938X_DIGITAL_INTR_STATUS_1,
>> &sts2);
>> + regmap_read(wcd938x->regmap, WCD938X_DIGITAL_INTR_STATUS_2,
>> &sts3);
>> +
>> + } while (sts1 || sts2 || sts3);
> i
> no timeout in case the hardware is stuck?
>
Even if you timeout here that means the interrupt source is not cleared
yet, so we can potentially re-enter to this function subsequently.
But I agree with you on the timeout for hardware stuck case, I will try
to add that in next version.
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +int wcd938x_init(struct wcd938x_sdw_priv *data)
>> +{
>> + struct wcd938x_priv *wcd938x = NULL;
>
> initialization doesn't seem needed
>
Ack!
>> + struct device *dev = &data->sdev->dev;
>> + bool is_tx = data->is_tx;
>> + int ret;
>> +
>> + if (!is_tx) {
>> + if (g_wcd938x) {
>> + data->wcd938x = g_wcd938x;
>> + return 0;
>> + } else {
>> + return -EPROBE_DEFER;
>> + }
>> + }
>> +
>> + wcd938x = devm_kzalloc(dev, sizeof(struct wcd938x_priv),
>> + GFP_KERNEL);
>> + if (!wcd938x)
>> + return -ENOMEM;
>> +
>> + data->wcd938x = wcd938x;
>> + g_wcd938x = wcd938x;
>> + wcd938x->dev = dev;
>> + ret = wcd938x_populate_dt_data(data, dev);
>> + if (ret) {
>> + dev_err(dev, "%s: Fail to obtain platform data\n", __func__);
>> + return -EINVAL;
>> + }
>> +
>> + ret = __wcd938x_init(data, dev);
>> + if (ret) {
>> + dev_err(dev, "%s: Fail to init\n", __func__);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +int wcd938x_deinit(struct wcd938x_sdw_priv *wcd)
>> +{
>> + struct wcd938x_priv *wcd938x = wcd->wcd938x;
>> +
>> + if (!wcd->is_tx)
>> + wcd_clsh_ctrl_free(wcd938x->clsh_info);
>> +
>> + g_wcd938x = NULL;
>> +
>> + return 0;
>> +}
>> diff --git a/sound/soc/codecs/wcd938x.h b/sound/soc/codecs/wcd938x.h
>> new file mode 100644
>> index 000000000000..c373850f209e
>> --- /dev/null
>> +++ b/sound/soc/codecs/wcd938x.h
>> @@ -0,0 +1,676 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>
> GPL-2.0-only
>
>> +#define WCD938X_MAX_SWR_CH_IDS 15
>
> what is this value? SoundWire supports up to 8 channels.
This is the virtual channel IDs given to group of channels on a port for
a particular function. ex:
Channel ID WCD938X_HPH_L is for Channel 0 on port WCD938X_HPH_PORT
Channel ID WCD938X_HPH_R is for Channel 1 on port WCD938X_HPH_PORT
These IDs are internally used by the codec for mixer settings.
they do not exactly reflect number of channels, they are just enums for
internal use.
--srini
More information about the Alsa-devel
mailing list