1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2024 Intel Corporation
/*
* The MIPI SDCA specification is available for public downloads at
* https://www.mipi.org/mipi-sdca-v1-0-download
*/
#define dev_fmt(fmt) "%s: " fmt, __func__
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/soundwire/sdw.h>
#include <linux/types.h>
#include <sound/sdca.h>
#include <sound/sdca_function.h>
static int patch_sdca_function_type(u32 interface_revision, u32 *function_type)
{
/*
* Unfortunately early SDCA specifications used different indices for Functions,
* for backwards compatibility we have to reorder the values found
*/
if (interface_revision < 0x0801) {
switch (*function_type) {
case 1:
*function_type = SDCA_FUNCTION_TYPE_SMART_AMP;
break;
case 2:
*function_type = SDCA_FUNCTION_TYPE_SMART_MIC;
break;
case 3:
*function_type = SDCA_FUNCTION_TYPE_SPEAKER_MIC;
break;
case 4:
*function_type = SDCA_FUNCTION_TYPE_UAJ;
break;
case 5:
*function_type = SDCA_FUNCTION_TYPE_RJ;
break;
case 6:
*function_type = SDCA_FUNCTION_TYPE_HID;
break;
default:
return -EINVAL;
}
}
return 0;
}
static const char *get_sdca_function_name(u32 function_type)
{
switch (function_type) {
case SDCA_FUNCTION_TYPE_SMART_AMP:
return SDCA_FUNCTION_TYPE_SMART_AMP_NAME;
case SDCA_FUNCTION_TYPE_SMART_MIC:
return SDCA_FUNCTION_TYPE_SMART_MIC_NAME;
case SDCA_FUNCTION_TYPE_UAJ:
return SDCA_FUNCTION_TYPE_UAJ_NAME;
case SDCA_FUNCTION_TYPE_HID:
return SDCA_FUNCTION_TYPE_HID_NAME;
case SDCA_FUNCTION_TYPE_SIMPLE_AMP:
return SDCA_FUNCTION_TYPE_SIMPLE_AMP_NAME;
case SDCA_FUNCTION_TYPE_SIMPLE_MIC:
return SDCA_FUNCTION_TYPE_SIMPLE_MIC_NAME;
case SDCA_FUNCTION_TYPE_SPEAKER_MIC:
return SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME;
case SDCA_FUNCTION_TYPE_RJ:
return SDCA_FUNCTION_TYPE_RJ_NAME;
case SDCA_FUNCTION_TYPE_IMP_DEF:
return SDCA_FUNCTION_TYPE_IMP_DEF_NAME;
default:
return NULL;
}
}
static int find_sdca_function(struct acpi_device *adev, void *data)
{
struct fwnode_handle *function_node = acpi_fwnode_handle(adev);
struct sdca_device_data *sdca_data = data;
struct device *dev = &adev->dev;
struct fwnode_handle *control5; /* used to identify function type */
const char *function_name;
u32 function_type;
int func_index;
u64 addr;
int ret;
if (sdca_data->num_functions >= SDCA_MAX_FUNCTION_COUNT) {
dev_err(dev, "maximum number of functions exceeded\n");
return -EINVAL;
}
ret = acpi_get_local_u64_address(adev->handle, &addr);
if (ret < 0)
return ret;
if (!addr || addr > 0x7) {
dev_err(dev, "invalid addr: 0x%llx\n", addr);
return -ENODEV;
}
/*
* Extracting the topology type for an SDCA function is a
* convoluted process.
* The Function type is only visible as a result of a read
* from a control. In theory this would mean reading from the hardware,
* but the SDCA/DisCo specs defined the notion of "DC value" - a constant
* represented with a DSD subproperty.
* Drivers have to query the properties for the control
* SDCA_CONTROL_ENTITY_0_FUNCTION_TOPOLOGY (0x05)
*/
control5 = fwnode_get_named_child_node(function_node,
"mipi-sdca-control-0x5-subproperties");
if (!control5)
return -ENODEV;
ret = fwnode_property_read_u32(control5, "mipi-sdca-control-dc-value",
&function_type);
fwnode_handle_put(control5);
if (ret < 0) {
dev_err(dev, "function type only supported as DisCo constant\n");
return ret;
}
ret = patch_sdca_function_type(sdca_data->interface_revision, &function_type);
if (ret < 0) {
dev_err(dev, "SDCA version %#x invalid function type %d\n",
sdca_data->interface_revision, function_type);
return ret;
}
function_name = get_sdca_function_name(function_type);
if (!function_name) {
dev_err(dev, "invalid SDCA function type %d\n", function_type);
return -EINVAL;
}
dev_info(dev, "SDCA function %s (type %d) at 0x%llx\n",
function_name, function_type, addr);
/* store results */
func_index = sdca_data->num_functions;
sdca_data->sdca_func[func_index].adr = addr;
sdca_data->sdca_func[func_index].type = function_type;
sdca_data->sdca_func[func_index].name = function_name;
sdca_data->num_functions++;
return 0;
}
void sdca_lookup_functions(struct sdw_slave *slave)
{
struct device *dev = &slave->dev;
struct acpi_device *adev = to_acpi_device_node(dev->fwnode);
if (!adev) {
dev_info(dev, "No matching ACPI device found, ignoring peripheral\n");
return;
}
acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data);
}
EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SDCA library");
|