| // SPDX-License-Identifier: GPL-2.0 |
| // Copyright (c) 2021, Linaro Limited |
| |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/device.h> |
| #include <linux/kernel.h> |
| #include <linux/component.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/irq.h> |
| #include <linux/irqdomain.h> |
| #include <linux/of.h> |
| #include <linux/soundwire/sdw.h> |
| #include <linux/soundwire/sdw_type.h> |
| #include <linux/soundwire/sdw_registers.h> |
| #include <linux/regmap.h> |
| #include <sound/soc.h> |
| #include <sound/soc-dapm.h> |
| #include "wcd938x.h" |
| |
| #define SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(m) (0xE0 + 0x10 * (m)) |
| |
| static struct wcd938x_sdw_ch_info wcd938x_sdw_rx_ch_info[] = { |
| WCD_SDW_CH(WCD938X_HPH_L, WCD938X_HPH_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_HPH_R, WCD938X_HPH_PORT, BIT(1)), |
| WCD_SDW_CH(WCD938X_CLSH, WCD938X_CLSH_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_COMP_L, WCD938X_COMP_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_COMP_R, WCD938X_COMP_PORT, BIT(1)), |
| WCD_SDW_CH(WCD938X_LO, WCD938X_LO_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_DSD_L, WCD938X_DSD_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_DSD_R, WCD938X_DSD_PORT, BIT(1)), |
| }; |
| |
| static struct wcd938x_sdw_ch_info wcd938x_sdw_tx_ch_info[] = { |
| WCD_SDW_CH(WCD938X_ADC1, WCD938X_ADC_1_2_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_ADC2, WCD938X_ADC_1_2_PORT, BIT(1)), |
| WCD_SDW_CH(WCD938X_ADC3, WCD938X_ADC_3_4_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_ADC4, WCD938X_ADC_3_4_PORT, BIT(1)), |
| WCD_SDW_CH(WCD938X_DMIC0, WCD938X_DMIC_0_3_MBHC_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_DMIC1, WCD938X_DMIC_0_3_MBHC_PORT, BIT(1)), |
| WCD_SDW_CH(WCD938X_MBHC, WCD938X_DMIC_0_3_MBHC_PORT, BIT(2)), |
| WCD_SDW_CH(WCD938X_DMIC2, WCD938X_DMIC_0_3_MBHC_PORT, BIT(2)), |
| WCD_SDW_CH(WCD938X_DMIC3, WCD938X_DMIC_0_3_MBHC_PORT, BIT(3)), |
| WCD_SDW_CH(WCD938X_DMIC4, WCD938X_DMIC_4_7_PORT, BIT(0)), |
| WCD_SDW_CH(WCD938X_DMIC5, WCD938X_DMIC_4_7_PORT, BIT(1)), |
| WCD_SDW_CH(WCD938X_DMIC6, WCD938X_DMIC_4_7_PORT, BIT(2)), |
| WCD_SDW_CH(WCD938X_DMIC7, WCD938X_DMIC_4_7_PORT, BIT(3)), |
| }; |
| |
| static struct sdw_dpn_prop wcd938x_dpn_prop[WCD938X_MAX_SWR_PORTS] = { |
| { |
| .num = 1, |
| .type = SDW_DPN_SIMPLE, |
| .min_ch = 1, |
| .max_ch = 8, |
| .simple_ch_prep_sm = true, |
| }, { |
| .num = 2, |
| .type = SDW_DPN_SIMPLE, |
| .min_ch = 1, |
| .max_ch = 4, |
| .simple_ch_prep_sm = true, |
| }, { |
| .num = 3, |
| .type = SDW_DPN_SIMPLE, |
| .min_ch = 1, |
| .max_ch = 4, |
| .simple_ch_prep_sm = true, |
| }, { |
| .num = 4, |
| .type = SDW_DPN_SIMPLE, |
| .min_ch = 1, |
| .max_ch = 4, |
| .simple_ch_prep_sm = true, |
| }, { |
| .num = 5, |
| .type = SDW_DPN_SIMPLE, |
| .min_ch = 1, |
| .max_ch = 4, |
| .simple_ch_prep_sm = true, |
| } |
| }; |
| |
| struct device *wcd938x_sdw_device_get(struct device_node *np) |
| { |
| return bus_find_device_by_of_node(&sdw_bus_type, np); |
| |
| } |
| EXPORT_SYMBOL_GPL(wcd938x_sdw_device_get); |
| |
| int wcd938x_swr_get_current_bank(struct sdw_slave *sdev) |
| { |
| int bank; |
| |
| bank = sdw_read(sdev, SDW_SCP_CTRL); |
| |
| return ((bank & 0x40) ? 1 : 0); |
| } |
| EXPORT_SYMBOL_GPL(wcd938x_swr_get_current_bank); |
| |
| int wcd938x_sdw_hw_params(struct wcd938x_sdw_priv *wcd, |
| struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *dai) |
| { |
| struct sdw_port_config port_config[WCD938X_MAX_SWR_PORTS]; |
| unsigned long ch_mask; |
| int i, j; |
| |
| wcd->sconfig.ch_count = 1; |
| wcd->active_ports = 0; |
| for (i = 0; i < WCD938X_MAX_SWR_PORTS; i++) { |
| ch_mask = wcd->port_config[i].ch_mask; |
| |
| if (!ch_mask) |
| continue; |
| |
| for_each_set_bit(j, &ch_mask, 4) |
| wcd->sconfig.ch_count++; |
| |
| port_config[wcd->active_ports] = wcd->port_config[i]; |
| wcd->active_ports++; |
| } |
| |
| wcd->sconfig.bps = 1; |
| wcd->sconfig.frame_rate = params_rate(params); |
| if (wcd->is_tx) |
| wcd->sconfig.direction = SDW_DATA_DIR_TX; |
| else |
| wcd->sconfig.direction = SDW_DATA_DIR_RX; |
| |
| wcd->sconfig.type = SDW_STREAM_PCM; |
| |
| return sdw_stream_add_slave(wcd->sdev, &wcd->sconfig, |
| &port_config[0], wcd->active_ports, |
| wcd->sruntime); |
| } |
| EXPORT_SYMBOL_GPL(wcd938x_sdw_hw_params); |
| |
| int wcd938x_sdw_free(struct wcd938x_sdw_priv *wcd, |
| struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| sdw_stream_remove_slave(wcd->sdev, wcd->sruntime); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wcd938x_sdw_free); |
| |
| int wcd938x_sdw_set_sdw_stream(struct wcd938x_sdw_priv *wcd, |
| struct snd_soc_dai *dai, |
| void *stream, int direction) |
| { |
| wcd->sruntime = stream; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wcd938x_sdw_set_sdw_stream); |
| |
| static int wcd9380_update_status(struct sdw_slave *slave, |
| enum sdw_slave_status status) |
| { |
| return 0; |
| } |
| |
| static int wcd9380_bus_config(struct sdw_slave *slave, |
| struct sdw_bus_params *params) |
| { |
| sdw_write(slave, SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(params->next_bank), 0x01); |
| |
| return 0; |
| } |
| |
| static int wcd9380_interrupt_callback(struct sdw_slave *slave, |
| struct sdw_slave_intr_status *status) |
| { |
| struct wcd938x_sdw_priv *wcd = dev_get_drvdata(&slave->dev); |
| struct irq_domain *slave_irq = wcd->slave_irq; |
| struct regmap *regmap = dev_get_regmap(&slave->dev, NULL); |
| u32 sts1, sts2, sts3; |
| |
| do { |
| handle_nested_irq(irq_find_mapping(slave_irq, 0)); |
| regmap_read(regmap, WCD938X_DIGITAL_INTR_STATUS_0, &sts1); |
| regmap_read(regmap, WCD938X_DIGITAL_INTR_STATUS_1, &sts2); |
| regmap_read(regmap, WCD938X_DIGITAL_INTR_STATUS_2, &sts3); |
| |
| } while (sts1 || sts2 || sts3); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct sdw_slave_ops wcd9380_slave_ops = { |
| .update_status = wcd9380_update_status, |
| .interrupt_callback = wcd9380_interrupt_callback, |
| .bus_config = wcd9380_bus_config, |
| }; |
| |
| static int wcd938x_sdw_component_bind(struct device *dev, |
| struct device *master, void *data) |
| { |
| return 0; |
| } |
| |
| static void wcd938x_sdw_component_unbind(struct device *dev, |
| struct device *master, void *data) |
| { |
| } |
| |
| static const struct component_ops wcd938x_sdw_component_ops = { |
| .bind = wcd938x_sdw_component_bind, |
| .unbind = wcd938x_sdw_component_unbind, |
| }; |
| |
| static int wcd9380_probe(struct sdw_slave *pdev, |
| const struct sdw_device_id *id) |
| { |
| struct device *dev = &pdev->dev; |
| struct wcd938x_sdw_priv *wcd; |
| int ret; |
| |
| wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL); |
| if (!wcd) |
| return -ENOMEM; |
| |
| /** |
| * Port map index starts with 0, however the data port for this codec |
| * are from index 1 |
| */ |
| if (of_property_read_bool(dev->of_node, "qcom,tx-port-mapping")) { |
| wcd->is_tx = true; |
| ret = of_property_read_u32_array(dev->of_node, "qcom,tx-port-mapping", |
| &pdev->m_port_map[1], |
| WCD938X_MAX_TX_SWR_PORTS); |
| } else { |
| ret = of_property_read_u32_array(dev->of_node, "qcom,rx-port-mapping", |
| &pdev->m_port_map[1], |
| WCD938X_MAX_SWR_PORTS); |
| } |
| |
| if (ret < 0) |
| dev_info(dev, "Static Port mapping not specified\n"); |
| |
| wcd->sdev = pdev; |
| dev_set_drvdata(dev, wcd); |
| |
| 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]; |
| } |
| |
| pm_runtime_set_autosuspend_delay(dev, 3000); |
| pm_runtime_use_autosuspend(dev); |
| pm_runtime_mark_last_busy(dev); |
| pm_runtime_set_active(dev); |
| pm_runtime_enable(dev); |
| |
| return component_add(dev, &wcd938x_sdw_component_ops); |
| } |
| |
| static const struct sdw_device_id wcd9380_slave_id[] = { |
| SDW_SLAVE_ENTRY(0x0217, 0x10d, 0), |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(sdw, wcd9380_slave_id); |
| |
| static int __maybe_unused wcd938x_sdw_runtime_suspend(struct device *dev) |
| { |
| struct regmap *regmap = dev_get_regmap(dev, NULL); |
| |
| if (regmap) { |
| regcache_cache_only(regmap, true); |
| regcache_mark_dirty(regmap); |
| } |
| return 0; |
| } |
| |
| static int __maybe_unused wcd938x_sdw_runtime_resume(struct device *dev) |
| { |
| struct regmap *regmap = dev_get_regmap(dev, NULL); |
| |
| if (regmap) { |
| regcache_cache_only(regmap, false); |
| regcache_sync(regmap); |
| } |
| |
| pm_runtime_mark_last_busy(dev); |
| |
| return 0; |
| } |
| |
| static const struct dev_pm_ops wcd938x_sdw_pm_ops = { |
| SET_RUNTIME_PM_OPS(wcd938x_sdw_runtime_suspend, wcd938x_sdw_runtime_resume, NULL) |
| }; |
| |
| |
| static struct sdw_driver wcd9380_codec_driver = { |
| .probe = wcd9380_probe, |
| .ops = &wcd9380_slave_ops, |
| .id_table = wcd9380_slave_id, |
| .driver = { |
| .name = "wcd9380-codec", |
| .pm = &wcd938x_sdw_pm_ops, |
| } |
| }; |
| module_sdw_driver(wcd9380_codec_driver); |
| |
| MODULE_DESCRIPTION("WCD938X SDW codec driver"); |
| MODULE_LICENSE("GPL"); |