1 // SPDX-License-Identifier: GPL-2.0-only
2 //
3 // KUnit test for the Cirrus side-codec library.
4 //
5 // Copyright (C) 2023 Cirrus Logic, Inc. and
6 // Cirrus Logic International Semiconductor Ltd.
7
8 #include <kunit/resource.h>
9 #include <kunit/test.h>
10 #include <linux/device.h>
11 #include <linux/device/devres.h>
12 #include <linux/device/faux.h>
13 #include <linux/gpio/driver.h>
14 #include <linux/module.h>
15
16 #include "cirrus_scodec.h"
17
18 KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy,
19 struct faux_device *)
20 KUNIT_DEFINE_ACTION_WRAPPER(device_remove_software_node_wrapper,
21 device_remove_software_node,
22 struct device *)
23
24 struct cirrus_scodec_test_gpio {
25 unsigned int pin_state;
26 struct gpio_chip chip;
27 };
28
29 struct cirrus_scodec_test_priv {
30 struct faux_device *amp_dev;
31 struct faux_device *gpio_dev;
32 struct cirrus_scodec_test_gpio *gpio_priv;
33 };
34
cirrus_scodec_test_gpio_get_direction(struct gpio_chip * chip,unsigned int offset)35 static int cirrus_scodec_test_gpio_get_direction(struct gpio_chip *chip,
36 unsigned int offset)
37 {
38 return GPIO_LINE_DIRECTION_IN;
39 }
40
cirrus_scodec_test_gpio_direction_in(struct gpio_chip * chip,unsigned int offset)41 static int cirrus_scodec_test_gpio_direction_in(struct gpio_chip *chip,
42 unsigned int offset)
43 {
44 return 0;
45 }
46
cirrus_scodec_test_gpio_get(struct gpio_chip * chip,unsigned int offset)47 static int cirrus_scodec_test_gpio_get(struct gpio_chip *chip, unsigned int offset)
48 {
49 struct cirrus_scodec_test_gpio *gpio_priv = gpiochip_get_data(chip);
50
51 return !!(gpio_priv->pin_state & BIT(offset));
52 }
53
cirrus_scodec_test_gpio_direction_out(struct gpio_chip * chip,unsigned int offset,int value)54 static int cirrus_scodec_test_gpio_direction_out(struct gpio_chip *chip,
55 unsigned int offset, int value)
56 {
57 return -EOPNOTSUPP;
58 }
59
cirrus_scodec_test_gpio_set(struct gpio_chip * chip,unsigned int offset,int value)60 static int cirrus_scodec_test_gpio_set(struct gpio_chip *chip,
61 unsigned int offset, int value)
62 {
63 return -EOPNOTSUPP;
64 }
65
cirrus_scodec_test_gpio_set_config(struct gpio_chip * gc,unsigned int offset,unsigned long config)66 static int cirrus_scodec_test_gpio_set_config(struct gpio_chip *gc,
67 unsigned int offset,
68 unsigned long config)
69 {
70 switch (pinconf_to_config_param(config)) {
71 case PIN_CONFIG_LEVEL:
72 case PIN_CONFIG_OUTPUT_ENABLE:
73 return -EOPNOTSUPP;
74 default:
75 return 0;
76 }
77 }
78
79 static const struct gpio_chip cirrus_scodec_test_gpio_chip = {
80 .label = "cirrus_scodec_test_gpio",
81 .owner = THIS_MODULE,
82 .request = gpiochip_generic_request,
83 .free = gpiochip_generic_free,
84 .get_direction = cirrus_scodec_test_gpio_get_direction,
85 .direction_input = cirrus_scodec_test_gpio_direction_in,
86 .get = cirrus_scodec_test_gpio_get,
87 .direction_output = cirrus_scodec_test_gpio_direction_out,
88 .set = cirrus_scodec_test_gpio_set,
89 .set_config = cirrus_scodec_test_gpio_set_config,
90 .base = -1,
91 .ngpio = 32,
92 };
93
94 /* software_node referencing the gpio driver */
95 static const struct software_node cirrus_scodec_test_gpio_swnode = {
96 .name = "cirrus_scodec_test_gpio",
97 };
98
cirrus_scodec_test_gpio_probe(struct faux_device * fdev)99 static int cirrus_scodec_test_gpio_probe(struct faux_device *fdev)
100 {
101 struct cirrus_scodec_test_gpio *gpio_priv;
102 int ret;
103
104 gpio_priv = devm_kzalloc(&fdev->dev, sizeof(*gpio_priv), GFP_KERNEL);
105 if (!gpio_priv)
106 return -ENOMEM;
107
108 ret = device_add_software_node(&fdev->dev, &cirrus_scodec_test_gpio_swnode);
109 if (ret)
110 return ret;
111
112 ret = devm_add_action_or_reset(&fdev->dev, device_remove_software_node_wrapper,
113 &fdev->dev);
114 if (ret)
115 return ret;
116
117 /* GPIO core modifies our struct gpio_chip so use a copy */
118 gpio_priv->chip = cirrus_scodec_test_gpio_chip;
119 gpio_priv->chip.parent = &fdev->dev;
120 ret = devm_gpiochip_add_data(&fdev->dev, &gpio_priv->chip, gpio_priv);
121 if (ret)
122 return dev_err_probe(&fdev->dev, ret, "Failed to add gpiochip\n");
123
124 dev_set_drvdata(&fdev->dev, gpio_priv);
125
126 return 0;
127 }
128
129 static const struct faux_device_ops cirrus_scodec_test_gpio_driver_ops = {
130 .probe = cirrus_scodec_test_gpio_probe,
131 };
132
cirrus_scodec_test_create_gpio(struct kunit * test)133 static void cirrus_scodec_test_create_gpio(struct kunit *test)
134 {
135 struct cirrus_scodec_test_priv *priv = test->priv;
136
137 priv->gpio_dev = faux_device_create("cirrus_scodec_test_gpio_drv", NULL,
138 &cirrus_scodec_test_gpio_driver_ops);
139 KUNIT_ASSERT_NOT_NULL(test, priv->gpio_dev);
140 KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test,
141 faux_device_destroy_wrapper,
142 priv->gpio_dev));
143
144 priv->gpio_priv = dev_get_drvdata(&priv->gpio_dev->dev);
145 KUNIT_ASSERT_NOT_NULL(test, priv->gpio_priv);
146 }
147
cirrus_scodec_test_set_gpio_ref_arg(struct software_node_ref_args * arg,int gpio_num)148 static void cirrus_scodec_test_set_gpio_ref_arg(struct software_node_ref_args *arg,
149 int gpio_num)
150 {
151 struct software_node_ref_args template =
152 SOFTWARE_NODE_REFERENCE(&cirrus_scodec_test_gpio_swnode, gpio_num, 0);
153
154 *arg = template;
155 }
156
cirrus_scodec_test_set_spkid_swnode(struct kunit * test,struct device * dev,struct software_node_ref_args * args,int num_args)157 static int cirrus_scodec_test_set_spkid_swnode(struct kunit *test,
158 struct device *dev,
159 struct software_node_ref_args *args,
160 int num_args)
161 {
162 const struct property_entry props_template[] = {
163 PROPERTY_ENTRY_REF_ARRAY_LEN("spk-id-gpios", args, num_args),
164 { }
165 };
166 struct property_entry *props;
167 struct software_node *node;
168
169 node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL);
170 if (!node)
171 return -ENOMEM;
172
173 props = kunit_kzalloc(test, sizeof(props_template), GFP_KERNEL);
174 if (!props)
175 return -ENOMEM;
176
177 memcpy(props, props_template, sizeof(props_template));
178 node->properties = props;
179
180 return device_add_software_node(dev, node);
181 }
182
183 struct cirrus_scodec_test_spkid_param {
184 int num_amps;
185 int gpios_per_amp;
186 int num_amps_sharing;
187 };
188
cirrus_scodec_test_spkid_parse(struct kunit * test)189 static void cirrus_scodec_test_spkid_parse(struct kunit *test)
190 {
191 struct cirrus_scodec_test_priv *priv = test->priv;
192 const struct cirrus_scodec_test_spkid_param *param = test->param_value;
193 int num_spk_id_refs = param->num_amps * param->gpios_per_amp;
194 struct software_node_ref_args *refs;
195 struct device *dev = &priv->amp_dev->dev;
196 unsigned int v;
197 int i, ret;
198
199 refs = kunit_kcalloc(test, num_spk_id_refs, sizeof(*refs), GFP_KERNEL);
200 KUNIT_ASSERT_NOT_NULL(test, refs);
201
202 for (i = 0, v = 0; i < num_spk_id_refs; ) {
203 cirrus_scodec_test_set_gpio_ref_arg(&refs[i++], v++);
204
205 /*
206 * If amps are sharing GPIOs repeat the last set of
207 * GPIOs until we've done that number of amps.
208 * We have done all GPIOs for an amp when i is a multiple
209 * of gpios_per_amp.
210 * We have done all amps sharing the same GPIOs when i is
211 * a multiple of (gpios_per_amp * num_amps_sharing).
212 */
213 if (!(i % param->gpios_per_amp) &&
214 (i % (param->gpios_per_amp * param->num_amps_sharing)))
215 v -= param->gpios_per_amp;
216 }
217
218 ret = cirrus_scodec_test_set_spkid_swnode(test, dev, refs, num_spk_id_refs);
219 KUNIT_EXPECT_EQ_MSG(test, ret, 0, "Failed to add swnode\n");
220
221 for (i = 0; i < param->num_amps; ++i) {
222 for (v = 0; v < (1 << param->gpios_per_amp); ++v) {
223 /* Set only the GPIO bits used by this amp */
224 priv->gpio_priv->pin_state =
225 v << (param->gpios_per_amp * (i / param->num_amps_sharing));
226
227 ret = cirrus_scodec_get_speaker_id(dev, i, param->num_amps, -1);
228 KUNIT_EXPECT_EQ_MSG(test, ret, v,
229 "get_speaker_id failed amp:%d pin_state:%#x\n",
230 i, priv->gpio_priv->pin_state);
231 }
232 }
233 }
234
cirrus_scodec_test_no_spkid(struct kunit * test)235 static void cirrus_scodec_test_no_spkid(struct kunit *test)
236 {
237 struct cirrus_scodec_test_priv *priv = test->priv;
238 struct device *dev = &priv->amp_dev->dev;
239 int ret;
240
241 ret = cirrus_scodec_get_speaker_id(dev, 0, 4, -1);
242 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
243 }
244
cirrus_scodec_test_case_init(struct kunit * test)245 static int cirrus_scodec_test_case_init(struct kunit *test)
246 {
247 struct cirrus_scodec_test_priv *priv;
248
249 priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
250 if (!priv)
251 return -ENOMEM;
252
253 test->priv = priv;
254
255 /* Create dummy GPIO */
256 cirrus_scodec_test_create_gpio(test);
257
258 /* Create dummy amp driver dev */
259 priv->amp_dev = faux_device_create("cirrus_scodec_test_amp_drv", NULL, NULL);
260 KUNIT_ASSERT_NOT_NULL(test, priv->amp_dev);
261 KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test,
262 faux_device_destroy_wrapper,
263 priv->amp_dev));
264
265 return 0;
266 }
267
268 static const struct cirrus_scodec_test_spkid_param cirrus_scodec_test_spkid_param_cases[] = {
269 { .num_amps = 2, .gpios_per_amp = 1, .num_amps_sharing = 1 },
270 { .num_amps = 2, .gpios_per_amp = 2, .num_amps_sharing = 1 },
271 { .num_amps = 2, .gpios_per_amp = 3, .num_amps_sharing = 1 },
272 { .num_amps = 2, .gpios_per_amp = 4, .num_amps_sharing = 1 },
273 { .num_amps = 3, .gpios_per_amp = 1, .num_amps_sharing = 1 },
274 { .num_amps = 3, .gpios_per_amp = 2, .num_amps_sharing = 1 },
275 { .num_amps = 3, .gpios_per_amp = 3, .num_amps_sharing = 1 },
276 { .num_amps = 3, .gpios_per_amp = 4, .num_amps_sharing = 1 },
277 { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 1 },
278 { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 1 },
279 { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 1 },
280 { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 1 },
281
282 /* Same GPIO shared by all amps */
283 { .num_amps = 2, .gpios_per_amp = 1, .num_amps_sharing = 2 },
284 { .num_amps = 2, .gpios_per_amp = 2, .num_amps_sharing = 2 },
285 { .num_amps = 2, .gpios_per_amp = 3, .num_amps_sharing = 2 },
286 { .num_amps = 2, .gpios_per_amp = 4, .num_amps_sharing = 2 },
287 { .num_amps = 3, .gpios_per_amp = 1, .num_amps_sharing = 3 },
288 { .num_amps = 3, .gpios_per_amp = 2, .num_amps_sharing = 3 },
289 { .num_amps = 3, .gpios_per_amp = 3, .num_amps_sharing = 3 },
290 { .num_amps = 3, .gpios_per_amp = 4, .num_amps_sharing = 3 },
291 { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 4 },
292 { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 4 },
293 { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 4 },
294 { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 4 },
295
296 /* Two sets of shared GPIOs */
297 { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 2 },
298 { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 2 },
299 { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 2 },
300 { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 2 },
301 };
302
cirrus_scodec_test_spkid_param_desc(const struct cirrus_scodec_test_spkid_param * param,char * desc)303 static void cirrus_scodec_test_spkid_param_desc(const struct cirrus_scodec_test_spkid_param *param,
304 char *desc)
305 {
306 snprintf(desc, KUNIT_PARAM_DESC_SIZE, "amps:%d gpios_per_amp:%d num_amps_sharing:%d",
307 param->num_amps, param->gpios_per_amp, param->num_amps_sharing);
308 }
309
310 KUNIT_ARRAY_PARAM(cirrus_scodec_test_spkid, cirrus_scodec_test_spkid_param_cases,
311 cirrus_scodec_test_spkid_param_desc);
312
313 static struct kunit_case cirrus_scodec_test_cases[] = {
314 KUNIT_CASE_PARAM(cirrus_scodec_test_spkid_parse, cirrus_scodec_test_spkid_gen_params),
315 KUNIT_CASE(cirrus_scodec_test_no_spkid),
316 { } /* terminator */
317 };
318
319 static struct kunit_suite cirrus_scodec_test_suite = {
320 .name = "snd-hda-cirrus-scodec-test",
321 .init = cirrus_scodec_test_case_init,
322 .test_cases = cirrus_scodec_test_cases,
323 };
324
325 kunit_test_suite(cirrus_scodec_test_suite);
326
327 MODULE_IMPORT_NS("SND_HDA_CIRRUS_SCODEC");
328 MODULE_DESCRIPTION("KUnit test for the Cirrus side-codec library");
329 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
330 MODULE_LICENSE("GPL");
331