diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..63cdd31 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +OUT := debugcc + +CC=aarch64-linux-gnu-gcc + +CFLAGS := -O2 -Wall -g +LDFLAGS := +prefix := /usr/local + +SRCS := debugcc.c +OBJS := $(SRCS:.c=.o) + +$(OUT): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) + +clean: + rm -f $(OUT) $(OBJS) *-debugcc diff --git a/debugcc.c b/debugcc.c new file mode 100644 index 0000000..da34a7d --- /dev/null +++ b/debugcc.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2019, Linaro Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debugcc.h" + +static const struct debugcc_platform *platforms[] = { + &qcs404_debugcc, + NULL +}; + +static uint32_t readl(void *ptr) +{ + return *((volatile uint32_t*)ptr); +} + +static void writel(uint32_t val, void *ptr) +{ + *((volatile uint32_t*)ptr) = val; +} + +static unsigned int measure_ticks(struct debug_mux *gcc, unsigned int ticks) +{ + uint32_t val; + + writel(ticks, gcc->base + gcc->debug_ctl_reg); + do { + val = readl(gcc->base + gcc->debug_status_reg); + } while (val & BIT(25)); + + writel(ticks | BIT(20), gcc->base + gcc->debug_ctl_reg); + do { + val = readl(gcc->base + gcc->debug_status_reg); + } while (!(val & BIT(25))); + + val &= 0x1ffffff; + + writel(ticks, gcc->base + gcc->debug_ctl_reg); + + return val; +} + +static void mux_enable(struct debug_mux *mux, int selector, int div) +{ + uint32_t val; + + val = readl(mux->base + mux->mux_reg); + val &= ~mux->mux_mask; + val |= selector << mux->mux_shift; + writel(val, mux->base + mux->mux_reg); + + if (mux->div_mask) { + val = readl(mux->base + mux->div_reg); + val &= ~mux->div_mask; + val |= (div - 1) << mux->div_shift; + writel(val, mux->base + mux->div_reg); + } + + val = readl(mux->base + mux->enable_reg); + val |= mux->enable_mask; + writel(val, mux->base + mux->enable_reg); +} + +static void mux_disable(struct debug_mux *mux) +{ + uint32_t val; + + val = readl(mux->base + mux->enable_reg); + val &= ~mux->enable_mask; + writel(val, mux->base + mux->enable_reg); +} + +static bool leaf_enabled(struct debug_mux *mux, struct debug_mux *leaf) +{ + uint32_t val; + + /* If no AHB clock is specified, we assume it's clocked */ + if (!leaf || !leaf->ahb_mask) + return true; + + val = readl(mux->base + leaf->ahb_reg); + val &= leaf->ahb_mask; + + /* CLK_OFF will be set if block is not clocked, so inverse */ + return !val; +} + +static void measure(const struct measure_clk *clk) +{ + unsigned long raw_count_short; + unsigned long raw_count_full; + struct debug_mux *gcc = clk->primary; + + if (!leaf_enabled(gcc, clk->leaf)) { + printf("%50s: skipping\n", clk->name); + return; + } + + if (clk->leaf) + mux_enable(clk->leaf, clk->leaf_mux, clk->leaf_div); + + mux_enable(clk->primary, clk->mux, clk->post_div); + + writel(1, gcc->base + gcc->xo_div4_reg); + + raw_count_short = measure_ticks(gcc, 0x1000); + raw_count_full = measure_ticks(gcc, 0x10000); + + writel(0, gcc->base + gcc->xo_div4_reg); + + if (raw_count_full == raw_count_short) { + printf("%50s: off\n", clk->name); + return; + } + + raw_count_full = ((raw_count_full * 10) + 15) * 4800000; + raw_count_full = raw_count_full / ((0x10000 * 10) + 35); + + mux_disable(clk->primary); + + if (clk->leaf) + mux_disable(clk->leaf); + + if (clk->leaf) + raw_count_full *= clk->leaf_div; + + raw_count_full *= clk->post_div; + + if (clk->fixed_div) + raw_count_full *= clk->fixed_div; + + printf("%50s: %fMHz (%ldHz)\n", clk->name, raw_count_full / 1000000.0, raw_count_full); +} + +static const struct debugcc_platform *find_platform(const char *name) +{ + const struct debugcc_platform **p; + + for (p = platforms; *p; p++) { + if (!strcmp((*p)->name, name)) + return *p; + } + + return NULL; +} + +/** + * match_platform() - match platform with executable name + * @argv: argv[0] of the executable + * + * Return: A debugcc_platform when a match is found, otherwise NULL + * + * Matches %s-debugcc against the registered platforms + */ +static const struct debugcc_platform *match_platform(const char *argv) +{ + const struct debugcc_platform **p; + const char *name; + size_t len; + + for (p = platforms; *p; p++) { + name = (*p)->name; + + len = strlen(name); + + if (!strncmp(name, argv, len) && !strcmp(argv + len, "-debugcc")) + return *p; + } + + return NULL; +} + +static const struct measure_clk *find_clock(const struct debugcc_platform *platform, + const char *name) +{ + const struct measure_clk *clk; + + for (clk = platform->clocks; clk->name; clk++) { + if (!strcmp(clk->name, name)) + return clk; + } + + return NULL; +} + +static void list_clocks(const struct debugcc_platform *platform) +{ + const struct measure_clk *clk; + + for (clk = platform->clocks; clk->name; clk++) + printf("%s\n", clk->name); +} + +static int mmap_mux(int devmem, struct debug_mux *mux) +{ + /* Do nothing if this mux has already been mapped */ + if (!mux || mux->base) + return 0; + + mux->base = mmap(0, mux->size, PROT_READ | PROT_WRITE, MAP_SHARED, devmem, mux->phys); + if (mux->base == (void *)-1) { + warn("failed to map %#zx", mux->phys); + return -1; + } + + return 0; +} + +/** + * mmap_hardware() - loop over all clock and make sure hardware is mmapped + * @devmem: file descriptor to an opened /dev/mem + * @platform: debugcc_platform with list of clocks to mmap + * + * Return: 0 on succees, -1 on failure + */ +static int mmap_hardware(int devmem, const struct debugcc_platform *platform) +{ + const struct measure_clk *clk; + int ret; + + for (clk = platform->clocks; clk->name; clk++) { + ret = mmap_mux(devmem, clk->primary); + if (ret < 0) + return ret; + + ret = mmap_mux(devmem, clk->leaf); + if (ret < 0) + return ret; + } + + return 0; +} + +static void usage(void) +{ + const struct debugcc_platform **p; + + fprintf(stderr, "debugcc <-p platform> <-a | -l | clk>\n"); + fprintf(stderr, "-debugcc <-a | -l | clk>\n"); + + fprintf(stderr, "available platforms:"); + for (p = platforms; *p; p++) + fprintf(stderr, " %s", (*p)->name); + fprintf(stderr, "\n"); + + exit(0); +} + +int main(int argc, char **argv) +{ + const struct debugcc_platform *platform = NULL; + const struct measure_clk *clk = NULL; + bool do_list_clocks = false; + bool all_clocks = false; + int devmem; + int opt; + int ret; + + while ((opt = getopt(argc, argv, "alp:")) != -1) { + switch (opt) { + case 'a': + all_clocks = true; + break; + case 'l': + do_list_clocks = true; + break; + case 'p': + platform = find_platform(optarg); + break; + default: + usage(); + /* NOTREACHED */ + } + } + + if (!platform) { + platform = match_platform(argv[0]); + if (!platform) + usage(); + } + + if (do_list_clocks) { + list_clocks(platform); + exit(0); + } + + if (!all_clocks) { + if (optind >= argc) + usage(); + + clk = find_clock(platform, argv[optind]); + if (!clk) { + fprintf(stderr, "no clock named \"%s\"\n", argv[optind]); + exit(1); + } + } + + devmem = open("/dev/mem", O_RDWR | O_SYNC); + if (devmem < 0) + err(1, "failed to open /dev/mem"); + + ret = mmap_hardware(devmem, platform); + if (ret < 0) + exit(1); + + if (clk) { + measure(clk); + } else { + for (clk = platform->clocks; clk->name; clk++) + measure(clk); + } + + return 0; +} diff --git a/debugcc.h b/debugcc.h new file mode 100644 index 0000000..91e9e9d --- /dev/null +++ b/debugcc.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019, Linaro Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DEBUGCC_H__ +#define __DEBUGCC_H__ + +#define BIT(x) (1 << (x)) + +struct debug_mux { + unsigned long phys; + void *base; + size_t size; + + unsigned int enable_reg; + unsigned int enable_mask; + + unsigned int mux_reg; + unsigned int mux_mask; + unsigned int mux_shift; + + unsigned int div_reg; + unsigned int div_shift; + unsigned int div_mask; + + unsigned int xo_div4_reg; + unsigned int debug_ctl_reg; + unsigned int debug_status_reg; + + unsigned int ahb_reg; + unsigned int ahb_mask; +}; + +struct measure_clk { + char *name; + struct debug_mux *primary; + int mux; + int post_div; + + struct debug_mux *leaf; + int leaf_mux; + int leaf_div; + + unsigned int fixed_div; +}; + +struct debugcc_platform { + const char *name; + const struct measure_clk *clocks; +}; + +extern struct debugcc_platform qcs404_debugcc; + +#endif