dm: gpio: Enhance gpio command to show only active GPIOs

The GPIO list is very long in many cases and most of them are not used.
By default, show only the GPIOs that are in use, and provide a flag to show
all of them. This makes the 'gpio status' command much more pleasant.

In order to do this, driver model now exposes a method for obtaining the
'function' of a GPIO, which describes whether it is an input or output, for
example. Implementation of this method is optional.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/common/cmd_gpio.c b/common/cmd_gpio.c
index 4634f91..b97533f 100644
--- a/common/cmd_gpio.c
+++ b/common/cmd_gpio.c
@@ -24,18 +24,46 @@
 };
 
 #if defined(CONFIG_DM_GPIO) && !defined(gpio_status)
-static const char * const gpio_function[] = {
+static const char * const gpio_function[GPIOF_COUNT] = {
 	"input",
 	"output",
+	"unused",
 	"unknown",
+	"func",
 };
 
-static void show_gpio(struct udevice *dev, const char *bank_name, int offset)
+/* A few flags used by show_gpio() */
+enum {
+	FLAG_SHOW_ALL		= 1 << 0,
+	FLAG_SHOW_BANK		= 1 << 1,
+	FLAG_SHOW_NEWLINE	= 1 << 2,
+};
+
+static void show_gpio(struct udevice *dev, const char *bank_name, int offset,
+		      int *flagsp)
 {
 	struct dm_gpio_ops *ops = gpio_get_ops(dev);
+	int func = GPIOF_UNKNOWN;
 	char buf[80];
 	int ret;
 
+	BUILD_BUG_ON(GPIOF_COUNT != ARRAY_SIZE(gpio_function));
+
+	if (ops->get_function) {
+		ret = ops->get_function(dev, offset);
+		if (ret >= 0 && ret < ARRAY_SIZE(gpio_function))
+			func = ret;
+	}
+	if (!(*flagsp & FLAG_SHOW_ALL) && func == GPIOF_UNUSED)
+		return;
+	if ((*flagsp & FLAG_SHOW_BANK) && bank_name) {
+		if (*flagsp & FLAG_SHOW_NEWLINE) {
+			putc('\n');
+			*flagsp &= ~FLAG_SHOW_NEWLINE;
+		}
+		printf("Bank %s:\n", bank_name);
+		*flagsp &= ~FLAG_SHOW_BANK;
+	}
 	*buf = '\0';
 	if (ops->get_state) {
 		ret = ops->get_state(dev, offset, buf, sizeof(buf));
@@ -44,14 +72,6 @@
 			return;
 		}
 	} else {
-		int func =  GPIOF_UNKNOWN;
-		int ret;
-
-		if (ops->get_function) {
-			ret = ops->get_function(dev, offset);
-			if (ret >= 0 && ret < ARRAY_SIZE(gpio_function))
-				func = ret;
-		}
 		sprintf(buf, "%s%u: %8s %d", bank_name, offset,
 			gpio_function[func], ops->get_value(dev, offset));
 	}
@@ -60,12 +80,14 @@
 	puts("\n");
 }
 
-static int do_gpio_status(const char *gpio_name)
+static int do_gpio_status(bool all, const char *gpio_name)
 {
 	struct udevice *dev;
-	int newline = 0;
+	int banklen;
+	int flags;
 	int ret;
 
+	flags = 0;
 	if (gpio_name && !*gpio_name)
 		gpio_name = NULL;
 	for (ret = uclass_first_device(UCLASS_GPIO, &dev);
@@ -74,28 +96,33 @@
 		const char *bank_name;
 		int num_bits;
 
+		flags |= FLAG_SHOW_BANK;
+		if (all)
+			flags |= FLAG_SHOW_ALL;
 		bank_name = gpio_get_bank_info(dev, &num_bits);
+		if (!num_bits)
+			continue;
+		banklen = bank_name ? strlen(bank_name) : 0;
 
 		if (!gpio_name || !bank_name ||
-		    !strncmp(gpio_name, bank_name, strlen(bank_name))) {
+		    !strncmp(gpio_name, bank_name, banklen)) {
 			const char *p = NULL;
 			int offset;
 
-			if (bank_name) {
-				if (newline)
-					putc('\n');
-				printf("Bank %s:\n", bank_name);
-			}
-			newline = 1;
-			if (gpio_name && bank_name) {
-				p = gpio_name + strlen(bank_name);
+			p = gpio_name + banklen;
+			if (gpio_name && *p) {
 				offset = simple_strtoul(p, NULL, 10);
-				show_gpio(dev, bank_name, offset);
+				show_gpio(dev, bank_name, offset, &flags);
 			} else {
-				for (offset = 0; offset < num_bits; offset++)
-					show_gpio(dev, bank_name, offset);
+				for (offset = 0; offset < num_bits; offset++) {
+					show_gpio(dev, bank_name, offset,
+						  &flags);
+				}
 			}
 		}
+		/* Add a newline between bank names */
+		if (!(flags & FLAG_SHOW_BANK))
+			flags |= FLAG_SHOW_NEWLINE;
 	}
 
 	return ret;
@@ -109,6 +136,7 @@
 	ulong value;
 	const char *str_cmd, *str_gpio = NULL;
 #ifdef CONFIG_DM_GPIO
+	bool all = false;
 	int ret;
 #endif
 
@@ -116,15 +144,24 @@
  show_usage:
 		return CMD_RET_USAGE;
 	str_cmd = argv[1];
-	if (argc > 2)
-		str_gpio = argv[2];
+	argc -= 2;
+	argv += 2;
+#ifdef CONFIG_DM_GPIO
+	if (argc > 0 && !strcmp(*argv, "-a")) {
+		all = true;
+		argc--;
+		argv++;
+	}
+#endif
+	if (argc > 0)
+		str_gpio = *argv;
 	if (!strcmp(str_cmd, "status")) {
 		/* Support deprecated gpio_status() */
 #ifdef gpio_status
 		gpio_status();
 		return 0;
 #elif defined(CONFIG_DM_GPIO)
-		return cmd_process_error(cmdtp, do_gpio_status(str_gpio));
+		return cmd_process_error(cmdtp, do_gpio_status(all, str_gpio));
 #else
 		goto show_usage;
 #endif
@@ -186,8 +223,8 @@
 	return value;
 }
 
-U_BOOT_CMD(gpio, 3, 0, do_gpio,
-	"query and control gpio pins",
-	"<input|set|clear|toggle> <pin>\n"
-	"    - input/set/clear/toggle the specified pin\n"
-	"gpio status [<bank> | <pin>]");
+U_BOOT_CMD(gpio, 4, 0, do_gpio,
+	   "query and control gpio pins",
+	   "<input|set|clear|toggle> <pin>\n"
+	   "    - input/set/clear/toggle the specified pin\n"
+	   "gpio status [-a] [<bank> | <pin>]  - show [all/claimed] GPIOs");
diff --git a/include/asm-generic/gpio.h b/include/asm-generic/gpio.h
index a6e52a0..60539d8 100644
--- a/include/asm-generic/gpio.h
+++ b/include/asm-generic/gpio.h
@@ -79,11 +79,15 @@
  */
 int gpio_set_value(unsigned gpio, int value);
 
-/* State of a GPIO, as reported by get_state() */
+/* State of a GPIO, as reported by get_function() */
 enum {
 	GPIOF_INPUT = 0,
 	GPIOF_OUTPUT,
-	GPIOF_UNKNOWN,
+	GPIOF_UNUSED,		/* Not claimed */
+	GPIOF_UNKNOWN,		/* Not known */
+	GPIOF_FUNC,		/* Not used as a GPIO */
+
+	GPIOF_COUNT,
 };
 
 struct udevice;
@@ -123,6 +127,13 @@
 				int value);
 	int (*get_value)(struct udevice *dev, unsigned offset);
 	int (*set_value)(struct udevice *dev, unsigned offset, int value);
+	/**
+	 * get_function() Get the GPIO function
+	 *
+	 * @dev:     Device to check
+	 * @offset:  GPIO offset within that device
+	 * @return current function - GPIOF_...
+	 */
 	int (*get_function)(struct udevice *dev, unsigned offset);
 	int (*get_state)(struct udevice *dev, unsigned offset, char *state,
 			 int maxlen);