xref: /linux/sound/hda/codecs/side-codecs/cirrus_scodec_test.c (revision c17ee635fd3a482b2ad2bf5e269755c2eae5f25e)
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 
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 
41 static int cirrus_scodec_test_gpio_direction_in(struct gpio_chip *chip,
42 						unsigned int offset)
43 {
44 	return 0;
45 }
46 
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 
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 
60 static int cirrus_scodec_test_gpio_set(struct gpio_chip *chip,
61 				       unsigned int offset, int value)
62 {
63 	return -EOPNOTSUPP;
64 }
65 
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 
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 
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 
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 
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 
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 
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 
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 
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