// SPDX-License-Identifier: GPL-2.0-only // // KUnit test for the Cirrus side-codec library. // // Copyright (C) 2023 Cirrus Logic, Inc. and // Cirrus Logic International Semiconductor Ltd. #include #include #include #include #include "cirrus_scodec.h" struct cirrus_scodec_test_gpio { unsigned int pin_state; struct gpio_chip chip; }; struct cirrus_scodec_test_priv { struct platform_device amp_pdev; struct platform_device *gpio_pdev; struct cirrus_scodec_test_gpio *gpio_priv; }; static int cirrus_scodec_test_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) { return GPIO_LINE_DIRECTION_IN; } static int cirrus_scodec_test_gpio_direction_in(struct gpio_chip *chip, unsigned int offset) { return 0; } static int cirrus_scodec_test_gpio_get(struct gpio_chip *chip, unsigned int offset) { struct cirrus_scodec_test_gpio *gpio_priv = gpiochip_get_data(chip); return !!(gpio_priv->pin_state & BIT(offset)); } static int cirrus_scodec_test_gpio_direction_out(struct gpio_chip *chip, unsigned int offset, int value) { return -EOPNOTSUPP; } static void cirrus_scodec_test_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { } static int cirrus_scodec_test_gpio_set_config(struct gpio_chip *gc, unsigned int offset, unsigned long config) { switch (pinconf_to_config_param(config)) { case PIN_CONFIG_OUTPUT: case PIN_CONFIG_OUTPUT_ENABLE: return -EOPNOTSUPP; default: return 0; } } static const struct gpio_chip cirrus_scodec_test_gpio_chip = { .label = "cirrus_scodec_test_gpio", .owner = THIS_MODULE, .request = gpiochip_generic_request, .free = gpiochip_generic_free, .get_direction = cirrus_scodec_test_gpio_get_direction, .direction_input = cirrus_scodec_test_gpio_direction_in, .get = cirrus_scodec_test_gpio_get, .direction_output = cirrus_scodec_test_gpio_direction_out, .set = cirrus_scodec_test_gpio_set, .set_config = cirrus_scodec_test_gpio_set_config, .base = -1, .ngpio = 32, }; static int cirrus_scodec_test_gpio_probe(struct platform_device *pdev) { struct cirrus_scodec_test_gpio *gpio_priv; int ret; gpio_priv = devm_kzalloc(&pdev->dev, sizeof(*gpio_priv), GFP_KERNEL); if (!gpio_priv) return -ENOMEM; /* GPIO core modifies our struct gpio_chip so use a copy */ gpio_priv->chip = cirrus_scodec_test_gpio_chip; ret = devm_gpiochip_add_data(&pdev->dev, &gpio_priv->chip, gpio_priv); if (ret) return dev_err_probe(&pdev->dev, ret, "Failed to add gpiochip\n"); dev_set_drvdata(&pdev->dev, gpio_priv); return 0; } static struct platform_driver cirrus_scodec_test_gpio_driver = { .driver.name = "cirrus_scodec_test_gpio_drv", .probe = cirrus_scodec_test_gpio_probe, }; /* software_node referencing the gpio driver */ static const struct software_node cirrus_scodec_test_gpio_swnode = { .name = "cirrus_scodec_test_gpio", }; static int cirrus_scodec_test_create_gpio(struct kunit *test) { struct cirrus_scodec_test_priv *priv = test->priv; int ret; priv->gpio_pdev = platform_device_alloc(cirrus_scodec_test_gpio_driver.driver.name, -1); if (!priv->gpio_pdev) return -ENOMEM; ret = device_add_software_node(&priv->gpio_pdev->dev, &cirrus_scodec_test_gpio_swnode); if (ret) { platform_device_put(priv->gpio_pdev); KUNIT_FAIL(test, "Failed to add swnode to gpio: %d\n", ret); return ret; } ret = platform_device_add(priv->gpio_pdev); if (ret) { platform_device_put(priv->gpio_pdev); KUNIT_FAIL(test, "Failed to add gpio platform device: %d\n", ret); return ret; } priv->gpio_priv = dev_get_drvdata(&priv->gpio_pdev->dev); if (!priv->gpio_priv) { platform_device_put(priv->gpio_pdev); KUNIT_FAIL(test, "Failed to get gpio private data\n"); return -EINVAL; } return 0; } static void cirrus_scodec_test_set_gpio_ref_arg(struct software_node_ref_args *arg, int gpio_num) { struct software_node_ref_args template = SOFTWARE_NODE_REFERENCE(&cirrus_scodec_test_gpio_swnode, gpio_num, 0); *arg = template; } static int cirrus_scodec_test_set_spkid_swnode(struct kunit *test, struct device *dev, struct software_node_ref_args *args, int num_args) { const struct property_entry props_template[] = { PROPERTY_ENTRY_REF_ARRAY_LEN("spk-id-gpios", args, num_args), { } }; struct property_entry *props; struct software_node *node; node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL); if (!node) return -ENOMEM; props = kunit_kzalloc(test, sizeof(props_template), GFP_KERNEL); if (!props) return -ENOMEM; memcpy(props, props_template, sizeof(props_template)); node->properties = props; return device_add_software_node(dev, node); } struct cirrus_scodec_test_spkid_param { int num_amps; int gpios_per_amp; int num_amps_sharing; }; static void cirrus_scodec_test_spkid_parse(struct kunit *test) { struct cirrus_scodec_test_priv *priv = test->priv; const struct cirrus_scodec_test_spkid_param *param = test->param_value; int num_spk_id_refs = param->num_amps * param->gpios_per_amp; struct software_node_ref_args *refs; struct device *dev = &priv->amp_pdev.dev; unsigned int v; int i, ret; refs = kunit_kcalloc(test, num_spk_id_refs, sizeof(*refs), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, refs); for (i = 0, v = 0; i < num_spk_id_refs; ) { cirrus_scodec_test_set_gpio_ref_arg(&refs[i++], v++); /* * If amps are sharing GPIOs repeat the last set of * GPIOs until we've done that number of amps. * We have done all GPIOs for an amp when i is a multiple * of gpios_per_amp. * We have done all amps sharing the same GPIOs when i is * a multiple of (gpios_per_amp * num_amps_sharing). */ if (!(i % param->gpios_per_amp) && (i % (param->gpios_per_amp * param->num_amps_sharing))) v -= param->gpios_per_amp; } ret = cirrus_scodec_test_set_spkid_swnode(test, dev, refs, num_spk_id_refs); KUNIT_EXPECT_EQ_MSG(test, ret, 0, "Failed to add swnode\n"); for (i = 0; i < param->num_amps; ++i) { for (v = 0; v < (1 << param->gpios_per_amp); ++v) { /* Set only the GPIO bits used by this amp */ priv->gpio_priv->pin_state = v << (param->gpios_per_amp * (i / param->num_amps_sharing)); ret = cirrus_scodec_get_speaker_id(dev, i, param->num_amps, -1); KUNIT_EXPECT_EQ_MSG(test, ret, v, "get_speaker_id failed amp:%d pin_state:%#x\n", i, priv->gpio_priv->pin_state); } } } static void cirrus_scodec_test_no_spkid(struct kunit *test) { struct cirrus_scodec_test_priv *priv = test->priv; struct device *dev = &priv->amp_pdev.dev; int ret; ret = cirrus_scodec_get_speaker_id(dev, 0, 4, -1); KUNIT_EXPECT_EQ(test, ret, -ENOENT); } static void cirrus_scodec_test_dev_release(struct device *dev) { } static int cirrus_scodec_test_case_init(struct kunit *test) { struct cirrus_scodec_test_priv *priv; int ret; priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; test->priv = priv; /* Create dummy GPIO */ ret = cirrus_scodec_test_create_gpio(test); if (ret < 0) return ret; /* Create dummy amp driver dev */ priv->amp_pdev.name = "cirrus_scodec_test_amp_drv"; priv->amp_pdev.id = -1; priv->amp_pdev.dev.release = cirrus_scodec_test_dev_release; ret = platform_device_register(&priv->amp_pdev); KUNIT_ASSERT_GE_MSG(test, ret, 0, "Failed to register amp platform device\n"); return 0; } static void cirrus_scodec_test_case_exit(struct kunit *test) { struct cirrus_scodec_test_priv *priv = test->priv; if (priv->amp_pdev.name) platform_device_unregister(&priv->amp_pdev); if (priv->gpio_pdev) { device_remove_software_node(&priv->gpio_pdev->dev); platform_device_unregister(priv->gpio_pdev); } } static int cirrus_scodec_test_suite_init(struct kunit_suite *suite) { int ret; /* Register mock GPIO driver */ ret = platform_driver_register(&cirrus_scodec_test_gpio_driver); if (ret < 0) { kunit_err(suite, "Failed to register gpio platform driver, %d\n", ret); return ret; } return 0; } static void cirrus_scodec_test_suite_exit(struct kunit_suite *suite) { platform_driver_unregister(&cirrus_scodec_test_gpio_driver); } static const struct cirrus_scodec_test_spkid_param cirrus_scodec_test_spkid_param_cases[] = { { .num_amps = 2, .gpios_per_amp = 1, .num_amps_sharing = 1 }, { .num_amps = 2, .gpios_per_amp = 2, .num_amps_sharing = 1 }, { .num_amps = 2, .gpios_per_amp = 3, .num_amps_sharing = 1 }, { .num_amps = 2, .gpios_per_amp = 4, .num_amps_sharing = 1 }, { .num_amps = 3, .gpios_per_amp = 1, .num_amps_sharing = 1 }, { .num_amps = 3, .gpios_per_amp = 2, .num_amps_sharing = 1 }, { .num_amps = 3, .gpios_per_amp = 3, .num_amps_sharing = 1 }, { .num_amps = 3, .gpios_per_amp = 4, .num_amps_sharing = 1 }, { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 1 }, { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 1 }, { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 1 }, { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 1 }, /* Same GPIO shared by all amps */ { .num_amps = 2, .gpios_per_amp = 1, .num_amps_sharing = 2 }, { .num_amps = 2, .gpios_per_amp = 2, .num_amps_sharing = 2 }, { .num_amps = 2, .gpios_per_amp = 3, .num_amps_sharing = 2 }, { .num_amps = 2, .gpios_per_amp = 4, .num_amps_sharing = 2 }, { .num_amps = 3, .gpios_per_amp = 1, .num_amps_sharing = 3 }, { .num_amps = 3, .gpios_per_amp = 2, .num_amps_sharing = 3 }, { .num_amps = 3, .gpios_per_amp = 3, .num_amps_sharing = 3 }, { .num_amps = 3, .gpios_per_amp = 4, .num_amps_sharing = 3 }, { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 4 }, { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 4 }, { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 4 }, { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 4 }, /* Two sets of shared GPIOs */ { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 2 }, { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 2 }, { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 2 }, { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 2 }, }; static void cirrus_scodec_test_spkid_param_desc(const struct cirrus_scodec_test_spkid_param *param, char *desc) { snprintf(desc, KUNIT_PARAM_DESC_SIZE, "amps:%d gpios_per_amp:%d num_amps_sharing:%d", param->num_amps, param->gpios_per_amp, param->num_amps_sharing); } KUNIT_ARRAY_PARAM(cirrus_scodec_test_spkid, cirrus_scodec_test_spkid_param_cases, cirrus_scodec_test_spkid_param_desc); static struct kunit_case cirrus_scodec_test_cases[] = { KUNIT_CASE_PARAM(cirrus_scodec_test_spkid_parse, cirrus_scodec_test_spkid_gen_params), KUNIT_CASE(cirrus_scodec_test_no_spkid), { } /* terminator */ }; static struct kunit_suite cirrus_scodec_test_suite = { .name = "snd-hda-scodec-cs35l56-test", .suite_init = cirrus_scodec_test_suite_init, .suite_exit = cirrus_scodec_test_suite_exit, .init = cirrus_scodec_test_case_init, .exit = cirrus_scodec_test_case_exit, .test_cases = cirrus_scodec_test_cases, }; kunit_test_suite(cirrus_scodec_test_suite); MODULE_IMPORT_NS("SND_HDA_CIRRUS_SCODEC"); MODULE_DESCRIPTION("KUnit test for the Cirrus side-codec library"); MODULE_AUTHOR("Richard Fitzgerald "); MODULE_LICENSE("GPL");