diff --git a/ubi-utils/Makemodule.am b/ubi-utils/Makemodule.am
index 7491a8a..7183ec3 100644
--- a/ubi-utils/Makemodule.am
+++ b/ubi-utils/Makemodule.am
@@ -25,6 +25,9 @@ ubinize_LDADD = libubi.a libubigen.a libmtd.a libiniparser.a
 ubiformat_SOURCES = ubi-utils/ubiformat.c include/mtd_swab.h
 ubiformat_LDADD = libubi.a libubigen.a libmtd.a libscan.a
 
+ubiscan_SOURCES = ubi-utils/ubiscan.c include/mtd_swab.h
+ubiscan_LDADD = libubi.a libubigen.a libscan.a libmtd.a
+
 ubirename_SOURCES = ubi-utils/ubirename.c
 ubirename_LDADD = libmtd.a libubi.a
 
@@ -44,7 +47,7 @@ endif
 
 sbin_PROGRAMS += \
 	ubiupdatevol ubimkvol ubirmvol ubicrc32 ubinfo ubiattach \
-	ubidetach ubinize ubiformat ubirename mtdinfo ubirsvol ubiblock
+	ubidetach ubinize ubiformat ubirename mtdinfo ubirsvol ubiblock ubiscan
 
 if WITH_GETRANDOM
 sbin_PROGRAMS += ubihealthd
diff --git a/ubi-utils/ubiscan.c b/ubi-utils/ubiscan.c
new file mode 100644
index 0000000..e040bab
--- /dev/null
+++ b/ubi-utils/ubiscan.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2021 Diego Ismirlian
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * An utility to scan MTD devices.
+ *
+ * Author: Diego Ismirlian dismirlian (at) google's mail
+ */
+
+#define PROGRAM_NAME    "ubiscan"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <mtd/ubi-media.h>
+#include <libubi.h>
+#include <libmtd.h>
+#include <libscan.h>
+#include "common.h"
+
+#define MAX_BINS 50
+
+/* The variables below are set by command line arguments */
+struct args {
+    int verbose;
+    const char *node;
+    int node_fd;
+    int bin_thresholds[MAX_BINS - 1];
+    int nbins;
+};
+
+static struct args args = {
+    .verbose = 0,
+    .nbins = 6,
+    .bin_thresholds = {
+        10,
+        100,
+        1000,
+        10000,
+        100000,
+    },
+};
+
+static const char doc[] = PROGRAM_NAME " version " VERSION
+        " - a tool to scan MTD devices";
+
+static const char optionsstr[] =
+"-h, -?, --help               print help message\n"
+"-H, --histrogram=<list>      comma-separated list of bin thresholds\n"
+"-v, --verbose                be verbose\n"
+"-V, --version                print program version\n";
+
+static const char usage[] =
+"Usage: " PROGRAM_NAME " <MTD device node file name> "
+"\t\t\t[--help] [--version] [--verbose] [--histogram=<list>]";
+
+static const struct option long_options[] = {
+    { .name = "help",            .has_arg = 0, .flag = NULL, .val = 'h' },
+    { .name = "histogram",       .has_arg = 1, .flag = NULL, .val = 'H' },
+    { .name = "verbose",         .has_arg = 0, .flag = NULL, .val = 'v' },
+    { .name = "version",         .has_arg = 0, .flag = NULL, .val = 'V' },
+    { NULL, 0, NULL, 0},
+};
+
+static int parse_opt(int argc, char * const argv[])
+{
+    int last_bin = 0;
+    while (1) {
+        int key;
+
+        key = getopt_long(argc, argv, "h?VvH:", long_options, NULL);
+        if (key == -1)
+            break;
+
+        switch (key) {
+        case 'v':
+            args.verbose = 1;
+            break;
+        case 'H': {
+            args.nbins = 1;
+            char *token = strtok(optarg, ",");
+            while (token) {
+                if (args.nbins == MAX_BINS)
+                    return errmsg("too many bins");
+                int th = atoi(token);
+                if (th <= last_bin)
+                    return errmsg("bad bin threshold list");
+                args.bin_thresholds[args.nbins - 1] = th;
+                last_bin = th;
+                args.nbins++;
+                token = strtok(NULL, ",");
+            }
+        }   break;
+        case 'V':
+            common_print_version();
+            exit(EXIT_SUCCESS);
+        case 'h':
+            printf("%s\n\n", doc);
+            printf("%s\n\n", usage);
+            printf("%s\n", optionsstr);
+            exit(EXIT_SUCCESS);
+        case '?':
+            printf("%s\n\n", doc);
+            printf("%s\n\n", usage);
+            printf("%s\n", optionsstr);
+            return -1;
+
+        case ':':
+            return errmsg("parameter is missing");
+
+        default:
+            fprintf(stderr, "Use -h for help\n");
+            return -1;
+        }
+    }
+
+    if (optind == argc)
+        return errmsg("MTD device name was not specified (use -h for help)");
+    else if (optind != argc - 1)
+        return errmsg("more then one MTD device specified (use -h for help)");
+
+    args.node = argv[optind];
+    return 0;
+}
+
+int main(int argc, char * const argv[])
+{
+    int err;
+    libmtd_t libmtd;
+    struct mtd_info mtd_info;
+    struct mtd_dev_info mtd;
+    struct ubi_scan_info *si;
+    int max, min;
+
+    struct {
+        int min;
+        int max;
+        int cnt;
+        uint64_t mean;
+    } bins[MAX_BINS];
+
+    err = parse_opt(argc, argv);
+    if (err)
+        return -1;
+
+    libmtd = libmtd_open();
+    if (!libmtd)
+        return errmsg("MTD subsystem is not present");
+
+    err = mtd_get_info(libmtd, &mtd_info);
+    if (err) {
+        sys_errmsg("cannot get MTD information");
+        goto out_close_mtd;
+    }
+
+    err = mtd_get_dev_info(libmtd, args.node, &mtd);
+    if (err) {
+        sys_errmsg("cannot get information about \"%s\"", args.node);
+        goto out_close_mtd;
+    }
+
+    args.node_fd = open(args.node, O_RDONLY);
+    if (args.node_fd == -1) {
+        sys_errmsg("cannot open \"%s\"", args.node);
+        goto out_close_mtd;
+    }
+
+    printf("Summary\n");
+    printf("=========================================================\n");
+    printf("mtd    : %d\n", mtd.mtd_num);
+    printf("type   : %s\n", mtd.type_str);
+    printf("size   : ");
+    util_print_bytes(mtd.size, 1);
+    printf("\n");
+    printf("PEBs   : %d\n", mtd.eb_cnt);
+    printf("min I/O: %d bytes\n", mtd.min_io_size);
+
+    printf("\n");
+    printf("PEB erase counters\n");
+    printf("=========================================================\n");
+    err = ubi_scan(&mtd, args.node_fd, &si, 0);
+    if (err) {
+        errmsg("failed to scan mtd%d (%s)", mtd.mtd_num, args.node);
+        goto out_close;
+    }
+
+    memset(bins, 0, sizeof(bins));
+
+    for (int j = 0; j < args.nbins; j++)
+        bins[j].min = INT_MAX;
+
+    min = INT_MAX;
+    max = 0;
+
+    for (int eb = 0; eb < mtd.eb_cnt; eb++) {
+        uint32_t ec = si->ec[eb];
+        switch (ec) {
+        case EB_EMPTY:
+        case EB_CORRUPTED:
+        case EB_ALIEN:
+        case EB_BAD:
+        case EC_MAX:
+            break;
+        default: {
+            int bin = 0;
+
+            if (ec > max)
+                max = ec;
+            if (ec < min)
+                min = ec;
+
+            for (int j = 0; j < args.nbins - 1 && ec >= args.bin_thresholds[j]; j++, bin++);
+
+            bins[bin].cnt++;
+            bins[bin].mean += ec;
+            if (ec < bins[bin].min)
+                bins[bin].min = ec;
+            if (ec > bins[bin].max)
+                bins[bin].max = ec;
+
+            } break;
+        }
+    }
+
+    printf("valid    : %d\n", si->ok_cnt);
+    printf("empty    : %d\n", si->empty_cnt);
+    printf("corrupted: %d\n", si->corrupted_cnt);
+    printf("alien    : %d\n", si->alien_cnt);
+    printf("bad      : %d\n", si->bad_cnt);
+
+    if (si->ok_cnt == 0)
+        min = 0;
+
+    printf("\n");
+    printf("Histogram\n");
+    printf("=========================================================\n");
+    printf("from              to     count      min      avg      max\n");
+    printf("---------------------------------------------------------\n");
+    for (int j = 0; j < args.nbins; j++) {
+        if (bins[j].cnt)
+            bins[j].mean /= bins[j].cnt;
+        else
+            bins[j].min = 0;
+
+        int from = (j == 0) ? 0 : args.bin_thresholds[j - 1];
+        if (j == args.nbins - 1)
+            printf("%-8d ..      inf: %8d %8d %8llu %8d\n",
+                from, bins[j].cnt, bins[j].min, bins[j].mean, bins[j].max);
+        else
+            printf("%-8d .. %8d: %8d %8d %8llu %8d\n",
+                from, args.bin_thresholds[j] - 1,
+                bins[j].cnt, bins[j].min, bins[j].mean, bins[j].max);
+    }
+    printf("---------------------------------------------------------\n");
+    printf("Total               : %8d %8d %8llu %8d\n", si->ok_cnt, min, si->mean_ec, max);
+
+    if (args.verbose) {
+        printf("\n");
+        printf("Details\n");
+        printf("=========================================================\n");
+        for (int eb = 0; eb < mtd.eb_cnt; eb++) {
+            printf("PEB %8d: ", eb);
+            uint32_t ec = si->ec[eb];
+            switch (ec) {
+            case EB_EMPTY:
+                printf("EB_EMPTY\n");
+                break;
+            case EB_CORRUPTED:
+                printf("EB_CORRUPTED\n");
+                break;
+            case EB_ALIEN:
+                printf("EB_ALIEN\n");
+                break;
+            case EB_BAD:
+                printf("EB_BAD\n");
+                break;
+            case EC_MAX:
+                printf("EC_MAX\n");
+                break;
+            default:
+                printf("%u\n", ec);
+                break;
+            }
+        }
+    }
+
+    ubi_scan_free(si);
+    close(args.node_fd);
+    libmtd_close(libmtd);
+    return 0;
+
+out_close:
+    close(args.node_fd);
+out_close_mtd:
+    libmtd_close(libmtd);
+    return -1;
+}
+
