mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02: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_SWNODE_TEST_CONSUMER "gpio-swnode-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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
struct gpio_test_provider_data *data = gpiochip_get_data(gc);
|
||||
|
||||
__assign_bit(offset, data->values, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_test_provider_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_test_provider_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gpio_chip *gc;
|
||||
|
||||
|
|
@ -38,16 +86,26 @@ static int gpio_test_provider_probe(struct platform_device *pdev)
|
|||
if (!gc)
|
||||
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->ngpio = 4;
|
||||
gc->label = "gpio-swnode-consumer-test-device";
|
||||
gc->ngpio = GPIO_TEST_PROVIDER_NGPIO;
|
||||
gc->label = GPIO_CONSUMER_NAME;
|
||||
gc->parent = dev;
|
||||
gc->owner = THIS_MODULE;
|
||||
|
||||
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;
|
||||
|
||||
return devm_gpiochip_add_data(dev, gc, NULL);
|
||||
return devm_gpiochip_add_data(dev, gc, data);
|
||||
}
|
||||
|
||||
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 {
|
||||
bool gpio_ok;
|
||||
int errno;
|
||||
};
|
||||
|
||||
static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = {
|
||||
.gpio_ok = false,
|
||||
.errno = 0,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
/*
|
||||
* 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(
|
||||
&gpio_swnode_lookup_test_suite,
|
||||
&gpio_unbind_with_consumers_test_suite,
|
||||
&gpio_swnode_hog_test_suite,
|
||||
);
|
||||
|
||||
MODULE_DESCRIPTION("Test module for the GPIO subsystem");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user