1
0
mirror of git://projects.qi-hardware.com/ben-blinkenlights.git synced 2024-07-02 22:52:22 +03:00
ben-blinkenlights/ubb-patgen/ubb-patgen.c

582 lines
12 KiB
C

/*
* ubb-patgen.c - UBB pattern generator
*
* Written 2013 by Werner Almesberger
* Copyright 2013 Werner Almesberger
*
* 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.
*/
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <asm/cachectl.h>
#include <ubb/ubb.h>
#include <ubb/regs4740.h>
#include <ubb/mmcclk.h>
#include <ubb/physmem.h>
#define USE_DMA
#define DMA 5
extern int cacheflush(char *addr, int nbytes, int cache);
/* ----- List available bus clock frequencies ------------------------------ */
static int cmp(const void *a, const void *b)
{
const struct mmcclk *ma = a, *mb = b;
if (ma->bus_clk_hz < mb->bus_clk_hz)
return -1;
if (ma->bus_clk_hz > mb->bus_clk_hz)
return 1;
return mb->clkdiv-ma->clkdiv;
}
static struct mmcclk *frequencies(int *n)
{
struct mmcclk mmc;
struct mmcclk *clks = malloc(sizeof(struct mmcclk));
int n_clks = 1;
if (!clks) {
perror("malloc");
exit(1);
}
mmcclk_first(&mmc, 0);
clks[0] = mmc;
while (mmcclk_next(&mmc)) {
clks = realloc(clks, sizeof(struct mmcclk)*(n_clks+1));
if (!clks) {
perror("realloc");
exit(1);
}
clks[n_clks] = mmc;
n_clks++;
}
qsort(clks, n_clks, sizeof(*clks), cmp);
*n = n_clks;
return clks;
}
static void print_freq(FILE *file, double f)
{
const char *prefix = "";
if (f >= 1000000) {
f /= 1000000;
prefix = "M";
} else if (f >= 1000) {
f /= 1000;
prefix = "k";
}
fprintf(file, "%g %sHz", f, prefix);
}
static void show_frequencies(void)
{
const struct mmcclk *clks;
int n, i;
clks = frequencies(&n);
for (i = 0; i != n; i++) {
printf("clkdiv = %u, clkrt = %u, bus_clk = ",
clks[i].clkdiv, clks[i].clkrt);
print_freq(stdout, clks[i].bus_clk_hz);
putchar('\n');
}
free((void *) clks);
}
static int select_freq(struct mmcclk *res, int hz, int rel)
{
const struct mmcclk *clks, *p, *best = NULL;
double d, best_d = 0;
int n;
clks = frequencies(&n);
for (p = clks; p != clks+n; p++) {
if (rel > 0 && p->bus_clk_hz < hz)
continue;
if (rel < 0 && p->bus_clk_hz > hz)
continue;
d = fabs(p->bus_clk_hz-hz);
if (!best || d < best_d) {
best = p;
best_d = d;
}
}
if (!best)
return 0;
*res = *best;
free((void *) clks);
return 1;
}
/* ----- DMA the pattern --------------------------------------------------- */
#ifdef USE_DMA
static uint32_t old_dmac;
static void dma_stop(void)
{
DCS(DMA) = (1 << 3) | (1 << 2); /* halt DMA channel */
DCS(DMA) = 0; /* reset DMA channel */
}
static void dma_init(void)
{
old_dmac = DMAC;
DMAC = 1; /* activate the DMA controller (in case it's off) */
dma_stop();
DCM(DMA) =
(1 << 23) | /* source address increment */
(4 << 8); /* transfer size is 32 bytes */
DRT(DMA) = 26; /* MSC transmit-fifo-empty transfer request */
}
static void dma_cleanup(void)
{
DMAC = old_dmac;
dma_stop();
}
static void dma_setup(unsigned long buf, int nibbles)
{
assert(!(nibbles & 63));
DCS(DMA) = 1 << 31; /* no-descriptor transfer */
DSA(DMA) = buf; /* source */
DTA(DMA) = REG_PADDR(MSC_TXFIFO); /* MUST set this each time */
DTC(DMA) = nibbles >> 6; /* 32 bytes per transfer */
}
static void wait_dma_done(void)
{
while (!((DCS(DMA) >> 3) & 1)); /* DCS.TT */
}
#else /* USE_DMA */
static void dma_init(void) {}
static void dma_cleanup(void) {}
static void dma_setup(unsigned long buf, int nibbles) {}
#endif /* !USE_DMA */
static void wait_response(void)
{
while (!((MSC_STAT >> 11 ) & 1)); /* MSC_STAT.END_CMD_RES */
}
static void wait_fifo_empty(void)
{
while (!((MSC_STAT >> 6 ) & 1)); /* MSC_STAT.DATA_FIFO_EMPTY */
}
static void wait_shifted(const struct mmcclk *clk)
{
/* 8 nibbles */
double us = 8*1000000.0/clk->bus_clk_hz;
usleep((int) us+1);
}
static void mmc_buffer(const struct mmcclk *clk,
uint8_t first, unsigned long buf, int nibbles, uint32_t mask)
{
/*
* If under control of the MMC controller, DATx tri-state until we
* actually send data. That's why they have been set up as GPIOs and
* we'll only switch them to function when the MMC controller is in a
* well-defined state.
*/
MSC_STRPCL = 1 << 3; /* reset the MSC */
while (MSC_STAT & (1 << 15)); /* wait until reset finishes */
dma_setup(buf, nibbles);
MSC_CLKRT = clk->clkrt; /* cleared by MSC reset */
MSC_STRPCL = 2; /* start the bus clock */
MSC_RESTO = 0xffff; /* maximum response time-out */
MSC_BLKLEN = 0xfff; /* never reach the end (with CRC) */
MSC_CMDAT =
(2 << 9) | /* 4 bit bus */
#ifdef USE_DMA
(1 << 8) | /* DMA */
#endif
(1 << 4) | /* write */
(1 << 3) | /* with data transfer */
1; /* R1 response */
MSC_STRPCL = 4; /* START_OP */
/*
* Make sure we've reached the end of the command and then send the
* first pattern (eight times, since this is the smallest amount we
* can send.
*/
wait_response();
MSC_TXFIFO = first*0x11111111;
wait_fifo_empty();
wait_shifted(clk);
/*
* Since the transfer (of nominally 4095 bytes) is not done yet, the
* MMC controller will hold the bus at the last value sent. It's now
* safe to switch from GPIO to function.
*/
PDFUNS = mask;
#ifdef USE_DMA
/*
* Send the pattern with DMA. Note that we still have to send the first
* pattern, since the static state we begin from may not have been
* present long enough.
*/
DCS(DMA) =
(1 << 31) | /* no descriptor */
1; /* enable transfer */
wait_dma_done();
#else /* USE_DMA */
const uint32_t *p;
for (p = (void *) buf; p != (void *) buf+(nibbles >> 1); p++) {
while ((MSC_STAT >> 7) & 1);
MSC_TXFIFO = *p;
}
#endif /* !USE_DMA */
wait_fifo_empty();
wait_shifted(clk);
/*
* We're done. As far as the MMC controller is concerned, the transfer
* is still not finished (i.e., we haven't sent 4095 bytes) and will
* therefore just hold the bus. We can now return the bus to GPIO.
* This form of handover also prevents the MMC controller from sending
* a CRC, which may confuse the recipient of the pattern.
*/
}
static void send_buffer(const struct mmcclk *clk,
const uint8_t *buf, int nibbles, uint32_t mask)
{
#ifdef USE_DMA
unsigned long phys;
if (cacheflush((void *) buf, nibbles >> 1, DCACHE)) {
perror("cacheflush");
exit(1);
}
asm("sync"); /* flush the write buffer */
phys = physmem_xlat((void *) buf);
mmc_buffer(clk, buf[0] >> 4, phys, nibbles, mask);
#else
mmc_buffer(clk, buf[0] >> 4, (unsigned long) buf, nibbles, mask);
#endif
}
static void *parse_pattern(const char *s, int *nibbles)
{
uint8_t *buf = physmem_malloc(4095); /* maximum block size */
int n = 0;
uint8_t v = 0;
char *end;
unsigned long i;
memset(buf, 0, 4095);
while (*s) {
char ch[2] = { *s, 0 };
v = strtoul(ch, &end, 16);
if (*end) {
fprintf(stderr, "\"%c\" is not a hex digit\n", *s);
exit(1);
}
if (s[1] == '{') {
i = strtoul(s+2, &end, 0);
if (!*end) {
fprintf(stderr, "unterminated range\n");
exit(1);
}
if (*end != '}' || end == s+2) {
fprintf(stderr, "invalid range \"%.*s\"\n",
end-s, s+1);
exit(1);
}
s = end+1;
} else {
i = 1;
s++;
}
while (i) {
if (n == 8192-64-1) {
fprintf(stderr, "pattern is too long\n");
exit(1);
}
buf[n >> 1] |= v << 4*(~n & 1);
n++;
i--;
}
}
/* pad to multiples of 32 bytes */
while (n & 63) {
buf[n >> 1] |= v << 4*(~n & 1);
n++;
}
*nibbles = n;
return buf;
}
static void dma_pattern(const struct mmcclk *clk,
const char *pattern, uint32_t mask)
{
const uint8_t *buf;
int n;
if (!*pattern) {
fprintf(stderr, "pattern is empty\n");
exit(1);
}
buf = parse_pattern(pattern, &n);
dma_init();
/* Initial static state: the first pattern. */
PDFUNS = UBB_CMD;
PDDATC = ~((buf[0] >> 4) << 10) & mask;
PDDATS = (buf[0] >> 4) << 10;
PDDIRS = mask;
send_buffer(clk, buf, n, mask);
/* Final static state: the last pattern. */
PDDATC = ~((buf[(n >> 1)-1] & 0xf) << 10) & mask;
PDDATS = (buf[(n >> 1)-1] & 0xf) << 10;
PDFUNC = mask;
dma_cleanup();
}
/* ----- Command-line processing ------------------------------------------- */
static int frequency(const char *s, int *hz, int *rel)
{
char *end;
double f;
f = strtod(s, &end);
switch (*end) {
case 'M':
case 'm':
*hz = f*1000000;
end++;
break;
case 'K':
case 'k':
*hz = f*1000;
end++;
break;
default:
*hz = f;
break;
}
if ((end[0] == 'H' || end[0] == 'h') &&
(end[1] == 'Z' || end[1] == 'z'))
end += 2;
switch (*end) {
case '+':
*rel = 1;
end++;
break;
case '-':
*rel = -1;
end++;
break;
default:
*rel = 0;
break;
}
return !*end;
}
static void usage(const char *name)
{
fprintf(stderr,
"usage: %s\n"
"usage: %s [-b freq_hz] [-f freq_hz] [-c] [-q] [pattern] active_s\n\n"
" -b freq_hz set bus clock to the specified frequency (default: 1 MHz)\n"
" -c output bus clock on CLK\n"
" -f freq_hz set pattern rate (default: same as bus clock)\n"
" -q quiet. Don't report clock differences.\n\n"
" active_s keep running that many seconds after setting the clock\n"
" pattern send the specified pattern on DAT0 through DAT3\n\n"
"Frequency: the frequency in Hz, optionally followed by \"M\" or \"k\",\n"
" optionally followed by \"Hz\", optionally followed by \"+\" or \"-\".\n"
" \"+\" selects a frequency >= the specified one, \"-\" one <=.\n"
" Without +/-, the closest available frequency is selected.\n"
"Pattern: hex digits corresponding to 1 for DAT0, 2 for DAT1, etc.\n"
" {n} repeats the preceding digit n times, e.g., 1{3} is equivalent to 111.\n"
, name, name);
exit(1);
}
int main(int argc, char **argv)
{
struct mmcclk clk;
int bus_hz = 0, clkout = 0, bus_rel = 0;
int pattern_hz = 0, pattern_rel = 0;
const char *pattern = NULL;
int quiet = 0;
double active_s;
struct timespec active_ns;
char *end;
int c;
while ((c = getopt(argc, argv, "b:cq")) != EOF)
switch (c) {
case 'b':
if (!frequency(optarg, &bus_hz, &bus_rel))
usage(*argv);
break;
case 'f':
if (!frequency(optarg, &pattern_hz, &pattern_rel))
usage(*argv);
break;
case 'c':
clkout = 1;
break;
case 'q':
quiet = 1;
break;
default:
usage(*argv);
}
switch (argc-optind) {
case 0:
if (clkout || quiet)
usage(*argv);
ubb_open(UBB_ALL);
show_frequencies();
return 1;
case 2:
pattern = argv[optind];
/* fall through */
case 1:
active_s = strtod(argv[argc-1], &end);
if (*end)
usage(*argv);
active_ns.tv_sec = (int) active_s;
active_ns.tv_nsec = (active_s-(int) active_s)*1e9;
break;
default:
usage(*argv);
}
ubb_open(UBB_ALL);
PDFUNS = UBB_CMD;
if (!bus_hz)
bus_hz = 1000000;
if (!select_freq(&clk, bus_hz, bus_rel)) {
fprintf(stderr, "no suitable frequency found\n");
exit(1);
}
if (clk.bus_clk_hz != bus_hz && !quiet) {
double err;
fprintf(stderr, "bus clk = ");
print_freq(stderr, clk.bus_clk_hz);
err = (clk.bus_clk_hz-bus_hz)/bus_hz;
if (err <= -0.0001 || err >= 0.0001)
fprintf(stderr, " (%+.2g%%)\n", err*100);
else
fprintf(stderr, " (%+d ppm)\n", (int) (err*1000000));
}
if (clkout)
PDFUNS = UBB_CLK;
mmcclk_start(&clk);
if (pattern)
dma_pattern(&clk, pattern,
UBB_DAT0 | UBB_DAT1 | UBB_DAT2 | UBB_DAT3);
if (nanosleep(&active_ns, NULL))
perror("nanosleep");
mmcclk_stop();
ubb_close(UBB_DAT0 | UBB_DAT1 | UBB_DAT2 | UBB_DAT3);
return 0;
}