mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ASoC: Qualcomm Tambora (WCD9378) SDCA codec
Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> says: Add support for the Qualcomm Tambora (WCD9378) headset codec in SDCA-compliant mode over SoundWire. On ARM/DT platforms without ACPI/DisCo firmware, the SDCA topology and SoundWire port properties are supplied as static data through the codec driver. Patches 1-5 extend the ASoC SDCA class framework so that a class-based codec driver can be built on ARM/DT: allow builds without ACPI, expose the SoundWire probe/remove library entry points, add class_ops with a populate_function hook, and translate sound-dai cells by entity index. Patches 6-7 add the WCD9378 dt-binding and driver. The same silicon can be fused for a mobile (non-SDCA) or an SDCA-compliant mode; the two share the SoundWire class-ID compatible (sdw20217011000) and are selected apart by the qcom,sdca-compliant boolean property. Tested with HPH playback and headset-mic capture on the Snapdragon X2 Elite Glymur CRD and Lenovo T14s Gen7. Jack detection is not wired up in this series. Link: https://patch.msgid.link/20260925161049.758913-1-srinivas.kandagatla@oss.qualcomm.com
This commit is contained in:
commit
22a310fa92
105
Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
Normal file
105
Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/qcom,wcd9378-sdw.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm WCD9378 (Tambora) SoundWire codec
|
||||
|
||||
maintainers:
|
||||
- Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
|
||||
|
||||
description:
|
||||
Qualcomm WCD9378 (Tambora) SoundWire codec. The same silicon can be
|
||||
fused for a mobile (non-SDCA) mode or an SDCA-compliant mode; the
|
||||
qcom,sdca-compliant property selects the latter and is required by
|
||||
this driver.
|
||||
|
||||
allOf:
|
||||
- $ref: dai-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: sdw20217011000
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
qcom,sdca-compliant:
|
||||
description:
|
||||
Present when the codec is fused as SDCA-compliant. The mobile
|
||||
(non-SDCA) variant carries the same class-ID compatible and is
|
||||
handled by a different driver.
|
||||
type: boolean
|
||||
|
||||
qcom,port-mapping:
|
||||
description:
|
||||
Each entry maps a slave data port to a master data port.
|
||||
Entries are in order starting from slave port 1; slave port 0
|
||||
is reserved and not represented. SDCA-compliant mode uses 8
|
||||
entries covering DP1..DP8.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
minItems: 8
|
||||
maxItems: 8
|
||||
|
||||
reset-gpios:
|
||||
description: GPIO used to release the codec from reset.
|
||||
maxItems: 1
|
||||
|
||||
vdd-buck-supply:
|
||||
description: 1.8 V analog buck supply.
|
||||
|
||||
vdd-rxtx-supply:
|
||||
description: 1.8 V RX/TX supply.
|
||||
|
||||
vdd-io-supply:
|
||||
description: Digital I/O supply.
|
||||
|
||||
vdd-mic-bias-supply:
|
||||
description: Mic bias supply.
|
||||
|
||||
'#sound-dai-cells':
|
||||
const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- qcom,sdca-compliant
|
||||
- qcom,port-mapping
|
||||
- reset-gpios
|
||||
- vdd-buck-supply
|
||||
- vdd-rxtx-supply
|
||||
- vdd-io-supply
|
||||
- vdd-mic-bias-supply
|
||||
- '#sound-dai-cells'
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
soundwire@7630000 {
|
||||
reg = <0x07630000 0x10000>;
|
||||
#address-cells = <2>;
|
||||
#size-cells = <0>;
|
||||
|
||||
wcd9378c_sdw: audio-codec@0,3 {
|
||||
compatible = "sdw20217011000";
|
||||
reg = <0 3>;
|
||||
|
||||
qcom,sdca-compliant;
|
||||
|
||||
qcom,port-mapping = <2 2 3 4 5 6 7 8>;
|
||||
|
||||
reset-gpios = <&tlmm 191 GPIO_ACTIVE_LOW>;
|
||||
|
||||
vdd-buck-supply = <&vreg_l15b_1p8>;
|
||||
vdd-rxtx-supply = <&vreg_l15b_1p8>;
|
||||
vdd-io-supply = <&vreg_l18b_1p2>;
|
||||
vdd-mic-bias-supply = <&vreg_bob1>;
|
||||
|
||||
#sound-dai-cells = <1>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
|
@ -60,20 +60,16 @@ enum sdca_quirk {
|
|||
SDCA_QUIRKS_SKIP_FUNC_TYPE_PATCHING,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA)
|
||||
#if IS_ENABLED(CONFIG_SND_SOC_SDCA)
|
||||
|
||||
void sdca_lookup_functions(struct sdw_slave *slave);
|
||||
void sdca_lookup_swft(struct sdw_slave *slave);
|
||||
void sdca_lookup_interface_revision(struct sdw_slave *slave);
|
||||
bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk);
|
||||
int sdca_dev_register_functions(struct sdw_slave *slave);
|
||||
void sdca_dev_unregister_functions(struct sdw_slave *slave);
|
||||
|
||||
#else
|
||||
|
||||
static inline void sdca_lookup_functions(struct sdw_slave *slave) {}
|
||||
static inline void sdca_lookup_swft(struct sdw_slave *slave) {}
|
||||
static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {}
|
||||
static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -88,4 +84,21 @@ static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {}
|
|||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Called from the SoundWire bus during peripheral enumeration; gated on
|
||||
* ACPI to avoid a soundwire_bus <-> snd_soc_sdca module cycle on DT builds
|
||||
* (where the bodies are stubs anyway).
|
||||
*/
|
||||
#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA)
|
||||
|
||||
void sdca_lookup_functions(struct sdw_slave *slave);
|
||||
void sdca_lookup_interface_revision(struct sdw_slave *slave);
|
||||
|
||||
#else
|
||||
|
||||
static inline void sdca_lookup_functions(struct sdw_slave *slave) {}
|
||||
static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
70
include/sound/sdca_class.h
Normal file
70
include/sound/sdca_class.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* The MIPI SDCA specification is available for public downloads at
|
||||
* https://www.mipi.org/mipi-sdca-v1-0-download
|
||||
*
|
||||
* Copyright (C) 2025 Cirrus Logic, Inc. and
|
||||
* Cirrus Logic International Semiconductor Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __SDCA_CLASS_H__
|
||||
#define __SDCA_CLASS_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct device;
|
||||
struct regmap;
|
||||
struct sdw_slave;
|
||||
struct sdca_function_data;
|
||||
|
||||
/**
|
||||
* struct sdca_class_ops - optional codec-provided class callbacks
|
||||
* @populate_function: fill @function (entities, clusters, init_table, ...)
|
||||
* from static tables in place of sdca_parse_function() on
|
||||
* DT/non-DisCo platforms. Called once per SDCA function with
|
||||
* @dev pointing at that function's device. Pure data source;
|
||||
* performs no bus I/O. Return 0 on success or a negative errno.
|
||||
*/
|
||||
struct sdca_class_ops {
|
||||
int (*populate_function)(struct device *dev,
|
||||
struct sdca_function_data *function);
|
||||
};
|
||||
|
||||
struct sdca_class_drv {
|
||||
struct device *dev;
|
||||
struct regmap *dev_regmap;
|
||||
struct sdw_slave *sdw;
|
||||
|
||||
struct sdca_interrupt_info *irq_info;
|
||||
|
||||
const struct sdca_class_ops *ops;
|
||||
|
||||
struct mutex regmap_lock;
|
||||
/* Serialise function initialisations */
|
||||
struct mutex init_lock;
|
||||
struct work_struct boot_work;
|
||||
};
|
||||
|
||||
/* Library helpers used by codec-specific SDCA SoundWire drivers. */
|
||||
int sdca_class_probe(struct sdw_slave *sdw,
|
||||
struct sdca_class_drv *drv,
|
||||
const struct sdca_class_ops *ops);
|
||||
void sdca_class_remove(struct sdca_class_drv *drv);
|
||||
|
||||
/*
|
||||
* PM helpers. Codec drivers embed sdca_class_drv in their own priv,
|
||||
* own dev_set_drvdata(), and compose these into their own dev_pm_ops:
|
||||
*
|
||||
* static int wcd_runtime_suspend(struct device *dev) {
|
||||
* struct wcd_priv *priv = dev_get_drvdata(dev);
|
||||
* return sdca_class_runtime_suspend(&priv->class);
|
||||
* }
|
||||
*/
|
||||
int sdca_class_runtime_suspend(struct sdca_class_drv *drv);
|
||||
int sdca_class_runtime_resume(struct sdca_class_drv *drv);
|
||||
int sdca_class_system_suspend(struct sdca_class_drv *drv);
|
||||
int sdca_class_system_resume(struct sdca_class_drv *drv);
|
||||
|
||||
#endif /* __SDCA_CLASS_H__ */
|
||||
|
|
@ -2552,6 +2552,17 @@ config SND_SOC_WCD939X_SDW
|
|||
The WCD9390/9395 is a audio codec IC Integrated in
|
||||
Qualcomm SoCs like SM8650.
|
||||
|
||||
config SND_SOC_WCD9378_SDCA
|
||||
tristate "Qualcomm Tambora (WCD9378) SDCA codec"
|
||||
depends on SOUNDWIRE
|
||||
depends on SND_SOC_SDCA_CLASS
|
||||
help
|
||||
This enables support for the Qualcomm Tambora (WCD9378) headset
|
||||
codec when driven via the SDCA class driver on ARM platforms
|
||||
without ACPI/DisCo tables. It provides the static SDCA topology
|
||||
and SoundWire data port properties transcribed from the factory
|
||||
ACPI tables.
|
||||
|
||||
config SND_SOC_WM0010
|
||||
tristate
|
||||
depends on SPI_MASTER
|
||||
|
|
|
|||
|
|
@ -369,6 +369,7 @@ snd-soc-wcd938x-y := wcd938x.o
|
|||
snd-soc-wcd938x-sdw-y := wcd938x-sdw.o
|
||||
snd-soc-wcd939x-y := wcd939x.o
|
||||
snd-soc-wcd939x-sdw-y := wcd939x-sdw.o
|
||||
snd-soc-wcd9378-y := wcd9378-sdw.o wcd9378-sdca.o
|
||||
snd-soc-wm-adsp-y := wm_adsp.o
|
||||
snd-soc-wm-adsp-test-y := wm_adsp_fw_find_test.o
|
||||
snd-soc-wm0010-y := wm0010.o
|
||||
|
|
@ -825,6 +826,7 @@ ifdef CONFIG_SND_SOC_WCD939X_SDW
|
|||
# avoid link failure by forcing sdw code built-in when needed
|
||||
obj-$(CONFIG_SND_SOC_WCD939X) += snd-soc-wcd939x-sdw.o
|
||||
endif
|
||||
obj-$(CONFIG_SND_SOC_WCD9378_SDCA) += snd-soc-wcd9378.o
|
||||
obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o
|
||||
obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o
|
||||
obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
|
||||
|
|
|
|||
1080
sound/soc/codecs/wcd9378-sdca.c
Normal file
1080
sound/soc/codecs/wcd9378-sdca.c
Normal file
File diff suppressed because it is too large
Load Diff
20
sound/soc/codecs/wcd9378-sdca.h
Normal file
20
sound/soc/codecs/wcd9378-sdca.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
|
||||
/* Copyright (c) 2025 Qualcomm Technologies, Inc. */
|
||||
|
||||
#ifndef _WCD9378_SDCA_H
|
||||
#define _WCD9378_SDCA_H
|
||||
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_type.h>
|
||||
|
||||
int wcd9378_sdca_probe(struct sdw_slave *slave,
|
||||
const struct sdw_device_id *id);
|
||||
void wcd9378_sdca_remove(struct sdw_slave *slave);
|
||||
int wcd9378_sdca_read_prop(struct sdw_slave *slave);
|
||||
|
||||
int wcd9378_sdca_runtime_suspend(struct device *dev);
|
||||
int wcd9378_sdca_runtime_resume(struct device *dev);
|
||||
int wcd9378_sdca_system_suspend(struct device *dev);
|
||||
int wcd9378_sdca_system_resume(struct device *dev);
|
||||
|
||||
#endif /* _WCD9378_SDCA_H */
|
||||
51
sound/soc/codecs/wcd9378-sdw.c
Normal file
51
sound/soc/codecs/wcd9378-sdw.c
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
|
||||
// Copyright (c) 2025 Qualcomm Technologies, Inc.
|
||||
|
||||
/*
|
||||
* WCD9378 (Tambora) SoundWire driver glue for the SDCA compute-mode
|
||||
* codec. The SDCA topology and hardware-specific probe logic live in
|
||||
* wcd9378-sdca.c; this file carries the SoundWire slave driver plumbing
|
||||
* and the module boilerplate.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_type.h>
|
||||
#include "wcd9378-sdca.h"
|
||||
|
||||
static const struct dev_pm_ops wcd9378_sdw_pm_ops = {
|
||||
SYSTEM_SLEEP_PM_OPS(wcd9378_sdca_system_suspend,
|
||||
wcd9378_sdca_system_resume)
|
||||
RUNTIME_PM_OPS(wcd9378_sdca_runtime_suspend,
|
||||
wcd9378_sdca_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct sdw_slave_ops wcd9378_sdw_ops = {
|
||||
.read_prop = wcd9378_sdca_read_prop,
|
||||
};
|
||||
|
||||
static const struct sdw_device_id wcd9378_sdw_id[] = {
|
||||
SDW_SLAVE_ENTRY(0x0217, 0x0110, 0),
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id);
|
||||
|
||||
static struct sdw_driver wcd9378_sdw_driver = {
|
||||
.driver = {
|
||||
.name = "wcd9378",
|
||||
.pm = pm_ptr(&wcd9378_sdw_pm_ops),
|
||||
},
|
||||
.probe = wcd9378_sdca_probe,
|
||||
.remove = wcd9378_sdca_remove,
|
||||
.id_table = wcd9378_sdw_id,
|
||||
.ops = &wcd9378_sdw_ops,
|
||||
};
|
||||
module_sdw_driver(wcd9378_sdw_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Qualcomm WCD9378 (Tambora) SoundWire codec");
|
||||
MODULE_AUTHOR("Qualcomm Technologies, Inc.");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_IMPORT_NS("SND_SOC_SDCA");
|
||||
MODULE_IMPORT_NS("SND_SOC_SDCA_CLASS");
|
||||
|
|
@ -3,11 +3,13 @@ menu "SoundWire (SDCA)"
|
|||
|
||||
config SND_SOC_SDCA
|
||||
tristate "SDCA core support"
|
||||
depends on ACPI
|
||||
select AUXILIARY_BUS
|
||||
help
|
||||
This option enables support for the MIPI SoundWire Device
|
||||
Class for Audio (SDCA).
|
||||
Class for Audio (SDCA). The ACPI-specific enumeration
|
||||
helpers are only built when CONFIG_ACPI is enabled; on
|
||||
non-ACPI platforms the SDCA function data is supplied
|
||||
statically by the codec driver.
|
||||
|
||||
config SND_SOC_SDCA_HID
|
||||
bool "SDCA HID support"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include <sound/sdca_function.h>
|
||||
#include <sound/sdca_interrupts.h>
|
||||
#include <sound/sdca_regmap.h>
|
||||
#include "sdca_class.h"
|
||||
#include <sound/sdca_class.h>
|
||||
|
||||
#define CLASS_SDW_ATTACH_TIMEOUT_MS 5000
|
||||
|
||||
|
|
@ -136,18 +136,35 @@ static void class_boot_work(struct work_struct *work)
|
|||
pm_runtime_put_sync(drv->dev);
|
||||
}
|
||||
|
||||
static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
|
||||
/**
|
||||
* sdca_class_probe - SDCA class SoundWire slave probe helper
|
||||
* @sdw: SoundWire slave
|
||||
* @drv: caller-allocated sdca_class_drv storage. The caller (a codec
|
||||
* driver, or the built-in class_sdw_driver in this file) owns the
|
||||
* allocation and sets its own dev_set_drvdata() -- the framework
|
||||
* does not touch drvdata. Typically embedded in the codec's own
|
||||
* priv struct so codec drivers can keep per-slave state.
|
||||
* @ops: optional codec-provided class callbacks (may be NULL for
|
||||
* pure-generic SDCA parts that need no quirks)
|
||||
*
|
||||
* Codec-specific SoundWire drivers call this from their .probe after
|
||||
* allocating a struct sdca_class_drv (usually embedded in their own
|
||||
* priv) and setting drvdata to their priv. The framework fills in the
|
||||
* sdca_class_drv fields, sets up the class regmap, and queues the
|
||||
* deferred boot work.
|
||||
*/
|
||||
int sdca_class_probe(struct sdw_slave *sdw,
|
||||
struct sdca_class_drv *drv,
|
||||
const struct sdca_class_ops *ops)
|
||||
{
|
||||
struct device *dev = &sdw->dev;
|
||||
struct regmap_config *dev_config;
|
||||
struct sdca_class_drv *drv;
|
||||
int ret;
|
||||
|
||||
sdca_lookup_swft(sdw);
|
||||
|
||||
drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
|
||||
if (!drv)
|
||||
return -ENOMEM;
|
||||
return -EINVAL;
|
||||
|
||||
dev_config = devm_kmemdup(dev, &class_dev_regmap_config,
|
||||
sizeof(*dev_config), GFP_KERNEL);
|
||||
|
|
@ -156,11 +173,10 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
|
|||
|
||||
drv->dev = dev;
|
||||
drv->sdw = sdw;
|
||||
drv->ops = ops;
|
||||
mutex_init(&drv->regmap_lock);
|
||||
mutex_init(&drv->init_lock);
|
||||
|
||||
dev_set_drvdata(drv->dev, drv);
|
||||
|
||||
INIT_WORK(&drv->boot_work, class_boot_work);
|
||||
|
||||
dev_config->lock_arg = &drv->regmap_lock;
|
||||
|
|
@ -185,39 +201,83 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(sdca_class_probe, "SND_SOC_SDCA_CLASS");
|
||||
|
||||
static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
|
||||
{
|
||||
struct sdca_class_drv *drv;
|
||||
|
||||
/*
|
||||
* Pure-generic SDCA parts: no codec priv to embed, so allocate a
|
||||
* bare sdca_class_drv here and stash it in drvdata for the
|
||||
* built-in PM ops to fetch.
|
||||
*/
|
||||
drv = devm_kzalloc(&sdw->dev, sizeof(*drv), GFP_KERNEL);
|
||||
if (!drv)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(&sdw->dev, drv);
|
||||
|
||||
return sdca_class_probe(sdw, drv, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* sdca_class_remove - SDCA class SoundWire slave remove helper
|
||||
* @drv: caller-owned sdca_class_drv (the one handed to sdca_class_probe()).
|
||||
*
|
||||
* Cancels the deferred boot work so devres can safely free @drv and the
|
||||
* embedding codec priv without racing class_boot_work. Codec-specific
|
||||
* SoundWire drivers that call sdca_class_probe() must call this from
|
||||
* their .remove with the same drv pointer they passed to probe.
|
||||
*/
|
||||
void sdca_class_remove(struct sdca_class_drv *drv)
|
||||
{
|
||||
cancel_work_sync(&drv->boot_work);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(sdca_class_remove, "SND_SOC_SDCA_CLASS");
|
||||
|
||||
static void class_sdw_remove(struct sdw_slave *sdw)
|
||||
{
|
||||
struct device *dev = &sdw->dev;
|
||||
struct sdca_class_drv *drv = dev_get_drvdata(dev);
|
||||
struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev);
|
||||
|
||||
cancel_work_sync(&drv->boot_work);
|
||||
sdca_class_remove(drv);
|
||||
}
|
||||
|
||||
static int class_suspend(struct device *dev)
|
||||
/**
|
||||
* sdca_class_system_suspend - SDCA class system suspend helper
|
||||
* @drv: caller-owned sdca_class_drv.
|
||||
*
|
||||
* Codec drivers compose this into their own dev_pm_ops. Disables the
|
||||
* SoundWire interrupt and forces runtime suspend of the underlying
|
||||
* class regmap.
|
||||
*/
|
||||
int sdca_class_system_suspend(struct sdca_class_drv *drv)
|
||||
{
|
||||
struct sdca_class_drv *drv = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
disable_irq(drv->sdw->irq);
|
||||
|
||||
ret = pm_runtime_force_suspend(dev);
|
||||
ret = pm_runtime_force_suspend(drv->dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to force suspend: %d\n", ret);
|
||||
dev_err(drv->dev, "failed to force suspend: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(sdca_class_system_suspend, "SND_SOC_SDCA_CLASS");
|
||||
|
||||
static int class_resume(struct device *dev)
|
||||
/**
|
||||
* sdca_class_system_resume - SDCA class system resume helper
|
||||
* @drv: caller-owned sdca_class_drv.
|
||||
*/
|
||||
int sdca_class_system_resume(struct sdca_class_drv *drv)
|
||||
{
|
||||
struct sdca_class_drv *drv = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_force_resume(dev);
|
||||
ret = pm_runtime_force_resume(drv->dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to force resume: %d\n", ret);
|
||||
dev_err(drv->dev, "failed to force resume: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -225,11 +285,14 @@ static int class_resume(struct device *dev)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(sdca_class_system_resume, "SND_SOC_SDCA_CLASS");
|
||||
|
||||
static int class_runtime_suspend(struct device *dev)
|
||||
/**
|
||||
* sdca_class_runtime_suspend - SDCA class runtime suspend helper
|
||||
* @drv: caller-owned sdca_class_drv.
|
||||
*/
|
||||
int sdca_class_runtime_suspend(struct sdca_class_drv *drv)
|
||||
{
|
||||
struct sdca_class_drv *drv = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* Whilst the driver doesn't power the chip down here, going into runtime
|
||||
* suspend lets the SoundWire bus power down, which means the driver
|
||||
|
|
@ -239,10 +302,14 @@ static int class_runtime_suspend(struct device *dev)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_suspend, "SND_SOC_SDCA_CLASS");
|
||||
|
||||
static int class_runtime_resume(struct device *dev)
|
||||
/**
|
||||
* sdca_class_runtime_resume - SDCA class runtime resume helper
|
||||
* @drv: caller-owned sdca_class_drv.
|
||||
*/
|
||||
int sdca_class_runtime_resume(struct sdca_class_drv *drv)
|
||||
{
|
||||
struct sdca_class_drv *drv = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = sdw_slave_wait_for_init(drv->sdw, CLASS_SDW_ATTACH_TIMEOUT_MS);
|
||||
|
|
@ -265,10 +332,37 @@ static int class_runtime_resume(struct device *dev)
|
|||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_resume, "SND_SOC_SDCA_CLASS");
|
||||
|
||||
static const struct dev_pm_ops class_pm_ops = {
|
||||
SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume)
|
||||
RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL)
|
||||
/*
|
||||
* Convenience dev_pm_ops used by the built-in class_sdw_driver, which
|
||||
* stashes its sdca_class_drv in drvdata directly. Codec drivers that
|
||||
* embed sdca_class_drv in their own priv compose their own dev_pm_ops
|
||||
* using the sdca_class_*_suspend/resume helpers above.
|
||||
*/
|
||||
static int class_pm_system_suspend(struct device *dev)
|
||||
{
|
||||
return sdca_class_system_suspend(dev_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static int class_pm_system_resume(struct device *dev)
|
||||
{
|
||||
return sdca_class_system_resume(dev_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static int class_pm_runtime_suspend(struct device *dev)
|
||||
{
|
||||
return sdca_class_runtime_suspend(dev_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static int class_pm_runtime_resume(struct device *dev)
|
||||
{
|
||||
return sdca_class_runtime_resume(dev_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops sdca_class_pm_ops = {
|
||||
SYSTEM_SLEEP_PM_OPS(class_pm_system_suspend, class_pm_system_resume)
|
||||
RUNTIME_PM_OPS(class_pm_runtime_suspend, class_pm_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct sdw_device_id class_sdw_id[] = {
|
||||
|
|
@ -282,7 +376,7 @@ MODULE_DEVICE_TABLE(sdw, class_sdw_id);
|
|||
static struct sdw_driver class_sdw_driver = {
|
||||
.driver = {
|
||||
.name = "sdca_class",
|
||||
.pm = pm_ptr(&class_pm_ops),
|
||||
.pm = pm_ptr(&sdca_class_pm_ops),
|
||||
},
|
||||
|
||||
.probe = class_sdw_probe,
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* The MIPI SDCA specification is available for public downloads at
|
||||
* https://www.mipi.org/mipi-sdca-v1-0-download
|
||||
*
|
||||
* Copyright (C) 2025 Cirrus Logic, Inc. and
|
||||
* Cirrus Logic International Semiconductor Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __SDCA_CLASS_H__
|
||||
#define __SDCA_CLASS_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct device;
|
||||
struct regmap;
|
||||
struct sdw_slave;
|
||||
struct sdca_function_data;
|
||||
|
||||
struct sdca_class_drv {
|
||||
struct device *dev;
|
||||
struct regmap *dev_regmap;
|
||||
struct sdw_slave *sdw;
|
||||
|
||||
struct sdca_interrupt_info *irq_info;
|
||||
|
||||
struct mutex regmap_lock;
|
||||
/* Serialise function initialisations */
|
||||
struct mutex init_lock;
|
||||
struct work_struct boot_work;
|
||||
};
|
||||
|
||||
#endif /* __SDCA_CLASS_H__ */
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
#include <sound/soc-component.h>
|
||||
#include <sound/soc-dai.h>
|
||||
#include <sound/soc.h>
|
||||
#include "sdca_class.h"
|
||||
#include <sound/sdca_class.h>
|
||||
#include "sdca_function_device.h"
|
||||
|
||||
struct class_function_drv {
|
||||
|
|
@ -216,9 +216,43 @@ static int class_function_set_jack(struct snd_soc_component *component,
|
|||
return sdca_jack_set_jack(core->irq_info, jack);
|
||||
}
|
||||
|
||||
/*
|
||||
* DT phandle cell is the SDCA entity index (matches dais[].id), not the
|
||||
* positional DAI index the default xlate assumes.
|
||||
*/
|
||||
static int class_function_of_xlate_dai_name(struct snd_soc_component *component,
|
||||
const struct of_phandle_args *args,
|
||||
const char **dai_name)
|
||||
{
|
||||
struct class_function_drv *drv = snd_soc_component_get_drvdata(component);
|
||||
struct sdca_function_data *function = drv->function;
|
||||
struct sdca_entity *entity;
|
||||
u32 target;
|
||||
|
||||
if (args->args_count != 1)
|
||||
return -EINVAL;
|
||||
|
||||
target = args->args[0];
|
||||
if (target >= function->num_entities)
|
||||
goto err;
|
||||
|
||||
entity = &function->entities[target];
|
||||
if ((entity->type != SDCA_ENTITY_TYPE_IT &&
|
||||
entity->type != SDCA_ENTITY_TYPE_OT) || !entity->iot.is_dataport)
|
||||
goto err;
|
||||
|
||||
*dai_name = entity->label;
|
||||
return 0;
|
||||
err:
|
||||
dev_err(component->dev, "xlate: no dataport entity at index %u (num_entities=%d)\n",
|
||||
target, function->num_entities);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct snd_soc_component_driver class_function_component_drv = {
|
||||
.fixup_controls = class_function_component_fixup_controls,
|
||||
.remove = class_function_component_remove,
|
||||
.of_xlate_dai_name = class_function_of_xlate_dai_name,
|
||||
.endianness = 1,
|
||||
};
|
||||
|
||||
|
|
@ -329,7 +363,14 @@ static int class_function_probe(struct auxiliary_device *auxdev,
|
|||
drv->core = core;
|
||||
drv->function = &sdev->function;
|
||||
|
||||
ret = sdca_parse_function(dev, drv->function);
|
||||
if (core->ops && core->ops->populate_function) {
|
||||
ret = core->ops->populate_function(dev, drv->function);
|
||||
} else if (drv->function->desc->node) {
|
||||
ret = sdca_parse_function(dev, drv->function);
|
||||
} else {
|
||||
dev_err(dev, "no firmware node and no populate_function hook\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <sound/sdca.h>
|
||||
#include <sound/sdca_function.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI)
|
||||
void sdca_lookup_interface_revision(struct sdw_slave *slave)
|
||||
{
|
||||
struct fwnode_handle *fwnode = slave->dev.fwnode;
|
||||
|
|
@ -45,6 +46,9 @@ void sdca_lookup_swft(struct sdw_slave *slave)
|
|||
devm_add_action_or_reset(&slave->dev, devm_acpi_table_put,
|
||||
slave->sdca_data.swft);
|
||||
}
|
||||
#else
|
||||
void sdca_lookup_swft(struct sdw_slave *slave) { }
|
||||
#endif
|
||||
EXPORT_SYMBOL_NS(sdca_lookup_swft, "SND_SOC_SDCA");
|
||||
|
||||
static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
*/
|
||||
#define SDCA_PROPERTY_LENGTH 64
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI)
|
||||
static int patch_sdca_function_type(u32 interface_revision, u32 *function_type)
|
||||
{
|
||||
/*
|
||||
|
|
@ -201,6 +202,7 @@ void sdca_lookup_functions(struct sdw_slave *slave)
|
|||
acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data);
|
||||
}
|
||||
EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA");
|
||||
#endif
|
||||
|
||||
struct raw_init_write {
|
||||
__le32 addr;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user