mirror of
https://github.com/torvalds/linux.git
synced 2026-10-28 09:35:02 +01:00
gpio: kunit: add test cases for software node hogs
Add a test suite containing cases verifying that GPIO hogs described with software nodes are correctly setup by GPIO core. Reviewed-by: Linus Walleij <linusw@kernel.org> Link: https://patch.msgid.link/20260610-gpio-kunit-swnode-hog-v1-2-37b3bf37096c@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
This commit is contained in:
parent
069547f9d8
commit
a05cfd98c5
|
|
@ -18,19 +18,67 @@
|
||||||
#define GPIO_TEST_PROVIDER "gpio-test-provider"
|
#define GPIO_TEST_PROVIDER "gpio-test-provider"
|
||||||
#define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer"
|
#define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer"
|
||||||
#define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer"
|
#define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer"
|
||||||
|
#define GPIO_CONSUMER_NAME "gpio-swnode-consumer-test-device"
|
||||||
|
|
||||||
|
#define GPIO_TEST_PROVIDER_NGPIO 4
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The test provider tracks per-line direction and value so that lines can be
|
||||||
|
* driven as both inputs and outputs - this is needed to exercise input as well
|
||||||
|
* as output GPIO hogs.
|
||||||
|
*/
|
||||||
|
struct gpio_test_provider_data {
|
||||||
|
DECLARE_BITMAP(is_output, GPIO_TEST_PROVIDER_NGPIO);
|
||||||
|
DECLARE_BITMAP(values, GPIO_TEST_PROVIDER_NGPIO);
|
||||||
|
};
|
||||||
|
|
||||||
static int gpio_test_provider_get_direction(struct gpio_chip *gc, unsigned int offset)
|
static int gpio_test_provider_get_direction(struct gpio_chip *gc, unsigned int offset)
|
||||||
{
|
{
|
||||||
return GPIO_LINE_DIRECTION_OUT;
|
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||||
|
|
||||||
|
return test_bit(offset, data->is_output) ?
|
||||||
|
GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gpio_test_provider_direction_input(struct gpio_chip *gc, unsigned int offset)
|
||||||
|
{
|
||||||
|
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||||
|
|
||||||
|
clear_bit(offset, data->is_output);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gpio_test_provider_direction_output(struct gpio_chip *gc, unsigned int offset,
|
||||||
|
int value)
|
||||||
|
{
|
||||||
|
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||||
|
|
||||||
|
set_bit(offset, data->is_output);
|
||||||
|
__assign_bit(offset, data->values, value);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gpio_test_provider_get(struct gpio_chip *gc, unsigned int offset)
|
||||||
|
{
|
||||||
|
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||||
|
|
||||||
|
return test_bit(offset, data->values);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gpio_test_provider_set(struct gpio_chip *gc, unsigned int offset, int value)
|
static int gpio_test_provider_set(struct gpio_chip *gc, unsigned int offset, int value)
|
||||||
{
|
{
|
||||||
|
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||||
|
|
||||||
|
__assign_bit(offset, data->values, value);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gpio_test_provider_probe(struct platform_device *pdev)
|
static int gpio_test_provider_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
struct gpio_test_provider_data *data;
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
struct gpio_chip *gc;
|
struct gpio_chip *gc;
|
||||||
|
|
||||||
|
|
@ -38,16 +86,26 @@ static int gpio_test_provider_probe(struct platform_device *pdev)
|
||||||
if (!gc)
|
if (!gc)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Lines start as outputs to preserve the default for lookup tests. */
|
||||||
|
bitmap_fill(data->is_output, GPIO_TEST_PROVIDER_NGPIO);
|
||||||
|
|
||||||
gc->base = -1;
|
gc->base = -1;
|
||||||
gc->ngpio = 4;
|
gc->ngpio = GPIO_TEST_PROVIDER_NGPIO;
|
||||||
gc->label = "gpio-swnode-consumer-test-device";
|
gc->label = GPIO_CONSUMER_NAME;
|
||||||
gc->parent = dev;
|
gc->parent = dev;
|
||||||
gc->owner = THIS_MODULE;
|
gc->owner = THIS_MODULE;
|
||||||
|
|
||||||
gc->get_direction = gpio_test_provider_get_direction;
|
gc->get_direction = gpio_test_provider_get_direction;
|
||||||
|
gc->direction_input = gpio_test_provider_direction_input;
|
||||||
|
gc->direction_output = gpio_test_provider_direction_output;
|
||||||
|
gc->get = gpio_test_provider_get;
|
||||||
gc->set = gpio_test_provider_set;
|
gc->set = gpio_test_provider_set;
|
||||||
|
|
||||||
return devm_gpiochip_add_data(dev, gc, NULL);
|
return devm_gpiochip_add_data(dev, gc, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver gpio_test_provider_driver = {
|
static struct platform_driver gpio_test_provider_driver = {
|
||||||
|
|
@ -63,10 +121,12 @@ static const struct software_node gpio_test_provider_swnode = {
|
||||||
|
|
||||||
struct gpio_swnode_consumer_pdata {
|
struct gpio_swnode_consumer_pdata {
|
||||||
bool gpio_ok;
|
bool gpio_ok;
|
||||||
|
int errno;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = {
|
static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = {
|
||||||
.gpio_ok = false,
|
.gpio_ok = false,
|
||||||
|
.errno = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int gpio_swnode_consumer_probe(struct platform_device *pdev)
|
static int gpio_swnode_consumer_probe(struct platform_device *pdev)
|
||||||
|
|
@ -76,8 +136,10 @@ static int gpio_swnode_consumer_probe(struct platform_device *pdev)
|
||||||
struct gpio_desc *desc;
|
struct gpio_desc *desc;
|
||||||
|
|
||||||
desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH);
|
desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH);
|
||||||
if (IS_ERR(desc))
|
if (IS_ERR(desc)) {
|
||||||
|
pdata->errno = PTR_ERR(desc);
|
||||||
return PTR_ERR(desc);
|
return PTR_ERR(desc);
|
||||||
|
}
|
||||||
|
|
||||||
pdata->gpio_ok = true;
|
pdata->gpio_ok = true;
|
||||||
|
|
||||||
|
|
@ -348,9 +410,191 @@ static struct kunit_suite gpio_unbind_with_consumers_test_suite = {
|
||||||
.test_cases = gpio_unbind_with_consumers_tests,
|
.test_cases = gpio_unbind_with_consumers_tests,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GPIO line hogs are described by child software nodes of the provider
|
||||||
|
* carrying the "gpio-hog" property. They are picked up automatically when the
|
||||||
|
* gpiochip is registered. Each hog below sits on a distinct line of the
|
||||||
|
* provider.
|
||||||
|
*/
|
||||||
|
#define GPIO_HOG_OUTPUT_HIGH_OFFSET 0
|
||||||
|
#define GPIO_HOG_OUTPUT_LOW_OFFSET 1
|
||||||
|
#define GPIO_HOG_INPUT_OFFSET 2
|
||||||
|
|
||||||
|
static const u32 gpio_hog_output_high_gpios[] = {
|
||||||
|
GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_ACTIVE_HIGH,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct property_entry gpio_hog_output_high_properties[] = {
|
||||||
|
PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_high_gpios),
|
||||||
|
PROPERTY_ENTRY_STRING("line-name", "hog-output-high"),
|
||||||
|
PROPERTY_ENTRY_BOOL("output-high"),
|
||||||
|
PROPERTY_ENTRY_BOOL("gpio-hog"),
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct software_node gpio_hog_output_high_swnode =
|
||||||
|
SOFTWARE_NODE("hog-output-high", gpio_hog_output_high_properties,
|
||||||
|
&gpio_test_provider_swnode);
|
||||||
|
|
||||||
|
static const u32 gpio_hog_output_low_gpios[] = {
|
||||||
|
GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_ACTIVE_HIGH,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct property_entry gpio_hog_output_low_properties[] = {
|
||||||
|
PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_low_gpios),
|
||||||
|
PROPERTY_ENTRY_STRING("line-name", "hog-output-low"),
|
||||||
|
PROPERTY_ENTRY_BOOL("output-low"),
|
||||||
|
PROPERTY_ENTRY_BOOL("gpio-hog"),
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct software_node gpio_hog_output_low_swnode =
|
||||||
|
SOFTWARE_NODE("hog-output-low", gpio_hog_output_low_properties,
|
||||||
|
&gpio_test_provider_swnode);
|
||||||
|
|
||||||
|
static const u32 gpio_hog_input_gpios[] = {
|
||||||
|
GPIO_HOG_INPUT_OFFSET, GPIO_ACTIVE_HIGH,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct property_entry gpio_hog_input_properties[] = {
|
||||||
|
PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_input_gpios),
|
||||||
|
PROPERTY_ENTRY_STRING("line-name", "hog-input"),
|
||||||
|
PROPERTY_ENTRY_BOOL("input"),
|
||||||
|
PROPERTY_ENTRY_BOOL("gpio-hog"),
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct software_node gpio_hog_input_swnode =
|
||||||
|
SOFTWARE_NODE("hog-input", gpio_hog_input_properties,
|
||||||
|
&gpio_test_provider_swnode);
|
||||||
|
|
||||||
|
static const struct software_node *const gpio_hog_swnodes[] = {
|
||||||
|
&gpio_test_provider_swnode,
|
||||||
|
&gpio_hog_output_high_swnode,
|
||||||
|
&gpio_hog_output_low_swnode,
|
||||||
|
&gpio_hog_input_swnode,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bring up the provider with a single hog child registered and verify both
|
||||||
|
* that the line was configured with the expected direction and that it is now
|
||||||
|
* exclusively owned (a consumer asking for the same line fails to bind).
|
||||||
|
*
|
||||||
|
* The provider node is referenced by the device through its fwnode rather than
|
||||||
|
* being handed to .swnode, so the device takes no software node reference of
|
||||||
|
* its own. Both the provider and the hog child are therefore test-managed and
|
||||||
|
* torn down (child first) once the test case completes.
|
||||||
|
*/
|
||||||
|
static void gpio_hog_assert(struct kunit *test, unsigned int offset,
|
||||||
|
int expected_direction)
|
||||||
|
{
|
||||||
|
struct gpio_swnode_consumer_pdata *pdata;
|
||||||
|
struct platform_device_info pdevinfo;
|
||||||
|
struct property_entry properties[2];
|
||||||
|
struct platform_device *pdev;
|
||||||
|
struct fwnode_handle *fwnode;
|
||||||
|
struct gpio_desc *desc;
|
||||||
|
bool bound = true;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
fwnode = software_node_fwnode(&gpio_test_provider_swnode);
|
||||||
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
|
||||||
|
|
||||||
|
pdevinfo = (struct platform_device_info){
|
||||||
|
.name = GPIO_TEST_PROVIDER,
|
||||||
|
.id = PLATFORM_DEVID_NONE,
|
||||||
|
.fwnode = fwnode,
|
||||||
|
};
|
||||||
|
|
||||||
|
pdev = kunit_platform_device_register_full(test, &pdevinfo);
|
||||||
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
|
||||||
|
|
||||||
|
wait_for_device_probe();
|
||||||
|
|
||||||
|
/* The hog must have configured the line with the expected direction. */
|
||||||
|
struct gpio_device *gdev __free(gpio_device_put) =
|
||||||
|
gpio_device_find_by_label(GPIO_CONSUMER_NAME);
|
||||||
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gdev);
|
||||||
|
|
||||||
|
desc = gpio_device_get_desc(gdev, offset);
|
||||||
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc);
|
||||||
|
|
||||||
|
ret = gpiod_get_direction(desc);
|
||||||
|
KUNIT_ASSERT_EQ(test, ret, expected_direction);
|
||||||
|
|
||||||
|
/* A hogged line is owned exclusively, so a consumer must fail to bind. */
|
||||||
|
properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios",
|
||||||
|
&gpio_test_provider_swnode,
|
||||||
|
offset, GPIO_ACTIVE_HIGH);
|
||||||
|
properties[1] = (struct property_entry){ };
|
||||||
|
|
||||||
|
pdevinfo = (struct platform_device_info){
|
||||||
|
.name = GPIO_SWNODE_TEST_CONSUMER,
|
||||||
|
.id = PLATFORM_DEVID_NONE,
|
||||||
|
.data = &gpio_swnode_pdata_template,
|
||||||
|
.size_data = sizeof(gpio_swnode_pdata_template),
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
|
||||||
|
pdev = kunit_platform_device_register_full(test, &pdevinfo);
|
||||||
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
|
||||||
|
|
||||||
|
wait_for_device_probe();
|
||||||
|
scoped_guard(device, &pdev->dev)
|
||||||
|
bound = device_is_bound(&pdev->dev);
|
||||||
|
|
||||||
|
KUNIT_ASSERT_FALSE(test, bound);
|
||||||
|
|
||||||
|
pdata = dev_get_platdata(&pdev->dev);
|
||||||
|
KUNIT_ASSERT_FALSE(test, pdata->gpio_ok);
|
||||||
|
KUNIT_ASSERT_EQ(test, pdata->errno, -EBUSY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpio_hog_output_high(struct kunit *test)
|
||||||
|
{
|
||||||
|
gpio_hog_assert(test, GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_LINE_DIRECTION_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpio_hog_output_low(struct kunit *test)
|
||||||
|
{
|
||||||
|
gpio_hog_assert(test, GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_LINE_DIRECTION_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpio_hog_input(struct kunit *test)
|
||||||
|
{
|
||||||
|
gpio_hog_assert(test, GPIO_HOG_INPUT_OFFSET, GPIO_LINE_DIRECTION_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gpio_hog_suite_init(struct kunit_suite *suite)
|
||||||
|
{
|
||||||
|
return software_node_register_node_group(gpio_hog_swnodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpio_hog_suite_exit(struct kunit_suite *suite)
|
||||||
|
{
|
||||||
|
software_node_unregister_node_group(gpio_hog_swnodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct kunit_case gpio_swnode_hog_tests[] = {
|
||||||
|
KUNIT_CASE(gpio_hog_output_high),
|
||||||
|
KUNIT_CASE(gpio_hog_output_low),
|
||||||
|
KUNIT_CASE(gpio_hog_input),
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct kunit_suite gpio_swnode_hog_test_suite = {
|
||||||
|
.name = "gpio-swnode-hog",
|
||||||
|
.test_cases = gpio_swnode_hog_tests,
|
||||||
|
.suite_init = gpio_hog_suite_init,
|
||||||
|
.suite_exit = gpio_hog_suite_exit,
|
||||||
|
.init = gpio_swnode_register_drivers,
|
||||||
|
};
|
||||||
|
|
||||||
kunit_test_suites(
|
kunit_test_suites(
|
||||||
&gpio_swnode_lookup_test_suite,
|
&gpio_swnode_lookup_test_suite,
|
||||||
&gpio_unbind_with_consumers_test_suite,
|
&gpio_unbind_with_consumers_test_suite,
|
||||||
|
&gpio_swnode_hog_test_suite,
|
||||||
);
|
);
|
||||||
|
|
||||||
MODULE_DESCRIPTION("Test module for the GPIO subsystem");
|
MODULE_DESCRIPTION("Test module for the GPIO subsystem");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user