2013-01-07 22:01:51 +02:00
|
|
|
/*
|
|
|
|
* 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>
|
2013-01-15 22:05:33 +02:00
|
|
|
#include <ctype.h>
|
2013-01-14 08:55:51 +02:00
|
|
|
#include <string.h>
|
2013-01-07 22:01:51 +02:00
|
|
|
#include <math.h>
|
|
|
|
#include <time.h>
|
2013-01-16 08:56:17 +02:00
|
|
|
#include <sched.h>
|
2013-01-14 08:55:51 +02:00
|
|
|
#include <assert.h>
|
2013-01-15 04:32:37 +02:00
|
|
|
#include <sys/mman.h>
|
2013-01-07 22:01:51 +02:00
|
|
|
|
|
|
|
#include <ubb/ubb.h>
|
2013-01-14 08:55:51 +02:00
|
|
|
#include <ubb/regs4740.h>
|
2013-01-07 22:01:51 +02:00
|
|
|
#include <ubb/mmcclk.h>
|
2013-01-14 08:55:51 +02:00
|
|
|
#include <ubb/physmem.h>
|
|
|
|
|
|
|
|
|
|
|
|
#define DMA 5
|
2013-01-07 22:01:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* ----- 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-29 02:14:53 +02:00
|
|
|
static struct mmcclk *frequencies(int *n, int all)
|
2013-01-07 22:01:51 +02:00
|
|
|
{
|
|
|
|
struct mmcclk mmc;
|
|
|
|
struct mmcclk *clks = malloc(sizeof(struct mmcclk));
|
|
|
|
int n_clks = 1;
|
|
|
|
|
|
|
|
if (!clks) {
|
|
|
|
perror("malloc");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2013-01-29 02:14:53 +02:00
|
|
|
mmcclk_first(&mmc, 0,
|
|
|
|
MMCCLK_FLAG_WR_ONLY | (all ? MMCCLK_FLAG_ALL : 0));
|
2013-01-07 22:01:51 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-15 21:10:14 +02:00
|
|
|
static void show_frequencies(int quiet)
|
2013-01-07 22:01:51 +02:00
|
|
|
{
|
|
|
|
const struct mmcclk *clks;
|
|
|
|
int n, i;
|
2013-01-16 04:14:25 +02:00
|
|
|
double last = 0;
|
2013-01-07 22:01:51 +02:00
|
|
|
|
2013-01-29 02:14:53 +02:00
|
|
|
clks = frequencies(&n, 0);
|
2013-01-07 22:01:51 +02:00
|
|
|
for (i = 0; i != n; i++) {
|
2013-01-15 21:10:14 +02:00
|
|
|
if (quiet) {
|
2013-01-16 04:14:25 +02:00
|
|
|
if (clks[i].bus_clk_hz != last)
|
|
|
|
printf("%f\n", clks[i].bus_clk_hz);
|
|
|
|
last = clks[i].bus_clk_hz;
|
2013-01-15 21:10:14 +02:00
|
|
|
} else {
|
|
|
|
printf("clkdiv = %u, clkrt = %u, bus_clk = ",
|
|
|
|
clks[i].clkdiv, clks[i].clkrt);
|
|
|
|
print_freq(stdout, clks[i].bus_clk_hz);
|
|
|
|
putchar('\n');
|
|
|
|
}
|
2013-01-07 22:01:51 +02:00
|
|
|
}
|
|
|
|
free((void *) clks);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-29 02:14:53 +02:00
|
|
|
static int select_freq(struct mmcclk *res, int hz, int rel, int quiet, int all)
|
2013-01-07 22:01:51 +02:00
|
|
|
{
|
|
|
|
const struct mmcclk *clks, *p, *best = NULL;
|
|
|
|
double d, best_d = 0;
|
|
|
|
int n;
|
2013-01-16 04:48:57 +02:00
|
|
|
double err;
|
2013-01-07 22:01:51 +02:00
|
|
|
|
2013-01-29 02:14:53 +02:00
|
|
|
clks = frequencies(&n, all);
|
2013-01-07 22:01:51 +02:00
|
|
|
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);
|
2013-01-16 04:48:57 +02:00
|
|
|
|
|
|
|
if (quiet)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (res->bus_clk_hz != hz) {
|
|
|
|
fprintf(stderr, "bus clk = ");
|
|
|
|
print_freq(stderr, res->bus_clk_hz);
|
|
|
|
err = (res->bus_clk_hz-hz)/hz;
|
|
|
|
if (err <= -0.0001 || err >= 0.0001)
|
|
|
|
fprintf(stderr, " (%+.2g%%)\n", err*100);
|
|
|
|
else
|
|
|
|
fprintf(stderr, " (%+d ppm)\n", (int) (err*1000000));
|
|
|
|
}
|
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-15 18:02:19 +02:00
|
|
|
/* ----- Pattern parser ---------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-15 22:05:33 +02:00
|
|
|
static const char *load_pattern(const char *s)
|
|
|
|
{
|
|
|
|
static char buf[20000]; /* more than enough :) */
|
|
|
|
FILE *file;
|
|
|
|
char *p = buf;
|
|
|
|
int comment = 0;
|
|
|
|
int c;
|
|
|
|
|
|
|
|
if (!strcmp(s, "-")) {
|
|
|
|
file = stdin;
|
|
|
|
} else {
|
|
|
|
file = fopen(s, "r");
|
|
|
|
if (!file)
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
while ((c = fgetc(file)) != EOF) {
|
|
|
|
if (comment) {
|
|
|
|
comment = c != '\n';
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (c == '#') {
|
|
|
|
comment = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (isspace(c))
|
|
|
|
continue;
|
|
|
|
if (buf+sizeof(buf)-1 == p) {
|
|
|
|
fprintf(stderr, "%s: file is too big\n", s);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
*p++ = c;
|
|
|
|
}
|
|
|
|
if (file != stdin)
|
|
|
|
fclose(file);
|
|
|
|
*p = 0;
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-16 08:56:17 +02:00
|
|
|
/* ----- Real-time mode ---------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
void realtimize(void)
|
|
|
|
{
|
|
|
|
struct sched_param prm;
|
|
|
|
|
|
|
|
prm.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
|
|
|
if (prm.sched_priority < 0) {
|
|
|
|
perror("sched_get_priority_max SCHED_FIFO");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (sched_setscheduler(0, SCHED_FIFO, &prm) < 0) {
|
|
|
|
perror("sched_setscheduler SCHED_FIFO");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void unrealtime(void)
|
|
|
|
{
|
|
|
|
struct sched_param prm = { .sched_priority = 0 };
|
|
|
|
|
|
|
|
if (sched_setscheduler(0, SCHED_OTHER, &prm) < 0) {
|
|
|
|
perror("sched_setscheduler SCHED_OTHER");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-15 18:02:19 +02:00
|
|
|
/* ----- DMA control ------------------------------------------------------- */
|
2013-01-14 08:55:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
static uint32_t old_dmac;
|
|
|
|
|
|
|
|
|
|
|
|
static void dma_stop(void)
|
|
|
|
{
|
2013-01-21 00:16:36 +02:00
|
|
|
DCS(DMA) =
|
|
|
|
DCS_TT | /* Transfer terminated */
|
|
|
|
DCS_HLT; /* DMA halt */
|
2013-01-14 08:55:51 +02:00
|
|
|
DCS(DMA) = 0; /* reset DMA channel */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dma_init(void)
|
|
|
|
{
|
|
|
|
old_dmac = DMAC;
|
|
|
|
|
2013-01-21 00:16:36 +02:00
|
|
|
DMAC = DMAC_DMAE; /* activate the DMA controller (in case it's off) */
|
2013-01-14 08:55:51 +02:00
|
|
|
dma_stop();
|
|
|
|
|
|
|
|
DCM(DMA) =
|
2013-01-21 00:16:36 +02:00
|
|
|
DCM_SAI | /* source address increment */
|
|
|
|
(DCM_TSZ_32BYTE << DCM_TSZ_SHIFT);
|
|
|
|
/* transfer size is 32 bytes */
|
|
|
|
DRT(DMA) = DRT_MSC_TX; /* MSC transmit-fifo-empty transfer request */
|
2013-01-14 08:55:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dma_cleanup(void)
|
|
|
|
{
|
|
|
|
DMAC = old_dmac;
|
|
|
|
dma_stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dma_setup(unsigned long buf, int nibbles)
|
|
|
|
{
|
|
|
|
assert(!(nibbles & 63));
|
|
|
|
|
2013-01-21 00:16:36 +02:00
|
|
|
DCS(DMA) = DCS_NDES; /* no-descriptor transfer */
|
2013-01-14 08:55:51 +02:00
|
|
|
DSA(DMA) = buf; /* source */
|
|
|
|
DTA(DMA) = REG_PADDR(MSC_TXFIFO); /* MUST set this each time */
|
|
|
|
DTC(DMA) = nibbles >> 6; /* 32 bytes per transfer */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-14 21:44:41 +02:00
|
|
|
static void wait_dma_done(void)
|
|
|
|
{
|
2013-01-21 00:16:36 +02:00
|
|
|
while (!(DCS(DMA) & DCS_TT));
|
2013-01-14 21:44:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-15 18:02:19 +02:00
|
|
|
/* ----- Send pattern using MSC and DMA ------------------------------------ */
|
|
|
|
|
|
|
|
|
2013-01-14 21:44:41 +02:00
|
|
|
static void wait_response(void)
|
|
|
|
{
|
2013-01-20 22:34:39 +02:00
|
|
|
while (!(MSC_STAT & MSC_STAT_END_CMD_RES));
|
2013-01-14 21:44:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void wait_fifo_empty(void)
|
|
|
|
{
|
2013-01-20 22:34:39 +02:00
|
|
|
while (!(MSC_STAT & MSC_STAT_DATA_FIFO_EMPTY));
|
2013-01-14 21:44:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void wait_shifted(const struct mmcclk *clk)
|
|
|
|
{
|
|
|
|
/* 8 nibbles */
|
|
|
|
double us = 8*1000000.0/clk->bus_clk_hz;
|
|
|
|
|
|
|
|
usleep((int) us+1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-16 09:45:49 +02:00
|
|
|
static void wait_trigger(const char *trigger, int debounce,
|
2013-01-16 08:30:04 +02:00
|
|
|
const struct timespec *debounce_ns)
|
|
|
|
{
|
|
|
|
struct timespec end, now;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @@@ could also try to use POSIX per-process timers here. May be
|
|
|
|
* slightly cleaner but could increase deviations.
|
|
|
|
*/
|
2013-01-16 09:45:49 +02:00
|
|
|
while (*trigger) {
|
|
|
|
while (PIN(UBB_CLK) != *trigger-'0');
|
2013-01-16 08:30:04 +02:00
|
|
|
if (!debounce)
|
2013-01-16 09:45:49 +02:00
|
|
|
goto next;
|
|
|
|
again:
|
2013-01-16 08:30:04 +02:00
|
|
|
if (clock_gettime(CLOCK_REALTIME, &end)) {
|
|
|
|
perror("clock_gettime");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
end.tv_sec += debounce_ns->tv_sec;
|
|
|
|
end.tv_nsec += debounce_ns->tv_nsec;
|
|
|
|
if (end.tv_nsec > 999999999) {
|
|
|
|
end.tv_nsec -= 1000000000;
|
|
|
|
end.tv_sec++;
|
|
|
|
}
|
2013-01-16 09:45:49 +02:00
|
|
|
while (PIN(UBB_CLK) == *trigger-'0') {
|
2013-01-16 08:30:04 +02:00
|
|
|
if (clock_gettime(CLOCK_REALTIME, &now)) {
|
|
|
|
perror("clock_gettime");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (now.tv_sec > end.tv_sec)
|
2013-01-16 09:45:49 +02:00
|
|
|
goto next;
|
2013-01-16 08:30:04 +02:00
|
|
|
if (now.tv_sec == end.tv_sec &&
|
|
|
|
now.tv_nsec >= end.tv_nsec)
|
2013-01-16 09:45:49 +02:00
|
|
|
goto next;
|
2013-01-16 08:30:04 +02:00
|
|
|
}
|
2013-01-16 09:45:49 +02:00
|
|
|
goto again;
|
|
|
|
next:
|
|
|
|
trigger++;
|
2013-01-16 08:30:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-14 08:55:51 +02:00
|
|
|
static void mmc_buffer(const struct mmcclk *clk,
|
2013-01-16 08:30:04 +02:00
|
|
|
uint8_t first, unsigned long buf, int nibbles, uint32_t mask,
|
2013-01-16 09:45:49 +02:00
|
|
|
const char *trigger, int debounce, const struct timespec *debounce_ns,
|
2013-01-16 08:44:30 +02:00
|
|
|
const struct timespec *wait_ns)
|
2013-01-14 08:55:51 +02:00
|
|
|
{
|
2013-01-14 22:01:13 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2013-01-14 08:55:51 +02:00
|
|
|
dma_setup(buf, nibbles);
|
|
|
|
|
2013-01-20 22:34:39 +02:00
|
|
|
MSC_STRPCL = MSC_STRPCRL_START_CLOCK; /* start the bus clock */
|
|
|
|
MSC_RESTO = MSC_RESTO_MASK; /* maximum response time-out */
|
|
|
|
MSC_BLKLEN = MSC_BLKLEN_MASK; /* never reach the end (with CRC) */
|
2013-01-14 08:55:51 +02:00
|
|
|
|
|
|
|
MSC_CMDAT =
|
2013-01-20 22:34:39 +02:00
|
|
|
MSC_CMDAT_BUS_WIDTH_4 << MSC_CMDAT_BUS_WIDTH_SHIFT |
|
|
|
|
MSC_CMDAT_DMA_EN | /* DMA */
|
|
|
|
MSC_CMDAT_WRITE_READ | /* write */
|
|
|
|
MSC_CMDAT_DATA_EN | /* with data transfer */
|
|
|
|
MSC_CMDAT_RESPONSE_FORMAT_R1; /* R1 response */
|
2013-01-14 08:55:51 +02:00
|
|
|
|
2013-01-20 22:34:39 +02:00
|
|
|
MSC_STRPCL = MSC_STRPCRL_START_OP;
|
2013-01-14 08:55:51 +02:00
|
|
|
|
2013-01-14 22:01:13 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2013-01-14 21:44:41 +02:00
|
|
|
wait_response();
|
|
|
|
|
|
|
|
MSC_TXFIFO = first*0x11111111;
|
|
|
|
|
|
|
|
wait_fifo_empty();
|
|
|
|
wait_shifted(clk);
|
|
|
|
|
2013-01-14 22:01:13 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2013-01-14 21:44:41 +02:00
|
|
|
PDFUNS = mask;
|
|
|
|
|
2013-01-16 08:56:17 +02:00
|
|
|
realtimize();
|
|
|
|
|
2013-01-16 09:45:49 +02:00
|
|
|
if (trigger)
|
2013-01-16 08:30:04 +02:00
|
|
|
wait_trigger(trigger, debounce, debounce_ns);
|
2013-01-16 08:44:30 +02:00
|
|
|
if (wait_ns->tv_sec || wait_ns->tv_nsec)
|
|
|
|
if (nanosleep(wait_ns, NULL))
|
|
|
|
perror("nanosleep");
|
2013-01-15 23:40:16 +02:00
|
|
|
|
2013-01-14 22:01:13 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2013-01-14 08:55:51 +02:00
|
|
|
DCS(DMA) =
|
2013-01-21 00:16:36 +02:00
|
|
|
DCS_NDES | /* no descriptor */
|
|
|
|
DCS_CTE; /* enable channel */
|
2013-01-14 08:55:51 +02:00
|
|
|
|
2013-01-16 08:56:17 +02:00
|
|
|
unrealtime();
|
|
|
|
|
2013-01-14 21:44:41 +02:00
|
|
|
wait_dma_done();
|
|
|
|
wait_fifo_empty();
|
|
|
|
wait_shifted(clk);
|
2013-01-14 22:01:13 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2013-01-14 08:55:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void send_buffer(const struct mmcclk *clk,
|
2013-01-16 08:30:04 +02:00
|
|
|
const uint8_t *buf, int nibbles, uint32_t mask,
|
2013-01-16 09:45:49 +02:00
|
|
|
const char *trigger, int debounce, const struct timespec *debounce_ns,
|
2013-01-16 08:44:30 +02:00
|
|
|
const struct timespec *wait_ns)
|
2013-01-14 08:55:51 +02:00
|
|
|
{
|
2013-01-16 15:16:09 +02:00
|
|
|
struct physmem_vec vec;
|
|
|
|
int n;
|
2013-01-14 08:55:51 +02:00
|
|
|
|
2013-01-16 04:31:30 +02:00
|
|
|
if (physmem_flush(buf, nibbles)) {
|
|
|
|
perror("physmem_flush");
|
2013-01-15 04:25:31 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2013-01-27 01:48:21 +02:00
|
|
|
n = physmem_xlat(buf, nibbles >> 1, &vec, 1);
|
2013-01-16 15:16:09 +02:00
|
|
|
if (n < 0) {
|
|
|
|
perror("physmem_xlat_vec");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (n != 1) {
|
|
|
|
fprintf(stderr, "physmem_xlat_vec: expected 1, got %d\n", n);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
mmc_buffer(clk, buf[0] >> 4, vec.addr, nibbles, mask,
|
2013-01-16 08:44:30 +02:00
|
|
|
trigger, debounce, debounce_ns, wait_ns);
|
2013-01-14 08:55:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-15 00:26:15 +02:00
|
|
|
static void dma_pattern(const struct mmcclk *clk,
|
2013-01-16 09:45:49 +02:00
|
|
|
const char *pattern, uint32_t mask, const char *trigger,
|
2013-01-16 08:44:30 +02:00
|
|
|
int debounce, const struct timespec *debounce_ns,
|
|
|
|
const struct timespec *wait_ns)
|
2013-01-15 00:26:15 +02:00
|
|
|
{
|
|
|
|
const uint8_t *buf;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
if (!*pattern) {
|
|
|
|
fprintf(stderr, "pattern is empty\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
buf = parse_pattern(pattern, &n);
|
2013-01-15 04:32:37 +02:00
|
|
|
|
|
|
|
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
|
|
|
|
perror("mlockall");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2013-01-16 09:45:49 +02:00
|
|
|
if (trigger) {
|
2013-01-15 23:40:16 +02:00
|
|
|
PDFUNC = UBB_CLK;
|
|
|
|
IN(UBB_CLK);
|
|
|
|
}
|
|
|
|
|
2013-01-14 08:55:51 +02:00
|
|
|
dma_init();
|
|
|
|
|
2013-01-14 22:01:13 +02:00
|
|
|
/* Initial static state: the first pattern. */
|
|
|
|
|
2013-01-14 21:44:41 +02:00
|
|
|
PDFUNS = UBB_CMD;
|
2013-01-14 08:55:51 +02:00
|
|
|
PDDATC = ~((buf[0] >> 4) << 10) & mask;
|
|
|
|
PDDATS = (buf[0] >> 4) << 10;
|
|
|
|
PDDIRS = mask;
|
|
|
|
|
2013-01-16 08:44:30 +02:00
|
|
|
send_buffer(clk, buf, n, mask,
|
|
|
|
trigger, debounce, debounce_ns, wait_ns);
|
2013-01-14 08:55:51 +02:00
|
|
|
|
2013-01-14 22:01:13 +02:00
|
|
|
/* Final static state: the last pattern. */
|
|
|
|
|
2013-01-15 00:26:15 +02:00
|
|
|
PDDATC = ~((buf[(n >> 1)-1] & 0xf) << 10) & mask;
|
|
|
|
PDDATS = (buf[(n >> 1)-1] & 0xf) << 10;
|
2013-01-14 08:55:51 +02:00
|
|
|
|
|
|
|
PDFUNC = mask;
|
|
|
|
|
|
|
|
dma_cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
/* ----- Command-line processing ------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
static int frequency(const char *s, int *hz, int *rel)
|
|
|
|
{
|
|
|
|
char *end;
|
|
|
|
double f;
|
|
|
|
|
|
|
|
f = strtod(s, &end);
|
|
|
|
|
2013-01-16 06:11:57 +02:00
|
|
|
if (end == s || f < 0)
|
|
|
|
return 0;
|
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-16 06:58:16 +02:00
|
|
|
static int duration(const char *s, double *res, int *rel)
|
2013-01-16 06:11:57 +02:00
|
|
|
{
|
|
|
|
char *end;
|
|
|
|
double d;
|
|
|
|
|
|
|
|
d = strtod(s, &end);
|
|
|
|
|
|
|
|
if (end == s || d < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
switch (*end) {
|
|
|
|
case 'M':
|
|
|
|
case 'm':
|
|
|
|
d /= 1e3;
|
|
|
|
end++;
|
|
|
|
break;
|
|
|
|
case 'U':
|
|
|
|
case 'u':
|
|
|
|
d /= 1e6;
|
|
|
|
end++;
|
|
|
|
break;
|
|
|
|
case 'N':
|
|
|
|
case 'n':
|
|
|
|
d /= 1e9;
|
|
|
|
end++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*end == 'S' || *end == 's')
|
|
|
|
end++;
|
|
|
|
|
2013-01-16 06:58:16 +02:00
|
|
|
switch (*end) {
|
|
|
|
case '+':
|
|
|
|
*rel = 1;
|
|
|
|
end++;
|
|
|
|
break;
|
|
|
|
case '-':
|
|
|
|
*rel = -1;
|
|
|
|
end++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
*rel = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-01-16 06:11:57 +02:00
|
|
|
if (*end)
|
|
|
|
return 0;
|
|
|
|
|
2013-01-16 06:58:16 +02:00
|
|
|
*res = d;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int duration_timespec(const char *s, struct timespec *res, int *rel)
|
|
|
|
{
|
|
|
|
double d;
|
|
|
|
|
|
|
|
if (!duration(s, &d, rel))
|
|
|
|
return 0;
|
2013-01-16 06:11:57 +02:00
|
|
|
res->tv_sec = d;
|
|
|
|
res->tv_nsec = (d-res->tv_sec)*1e9;
|
2013-01-16 06:58:16 +02:00
|
|
|
return 1;
|
|
|
|
}
|
2013-01-16 06:11:57 +02:00
|
|
|
|
2013-01-16 06:58:16 +02:00
|
|
|
|
|
|
|
static int interval(const char *s, int *hz, int *rel)
|
|
|
|
{
|
|
|
|
double d;
|
|
|
|
|
|
|
|
if (!duration(s, &d, rel))
|
|
|
|
return 0;
|
|
|
|
*hz = 1/d;
|
|
|
|
*rel = -*rel;
|
2013-01-16 06:11:57 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
static void usage(const char *name)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"usage: %s\n"
|
2013-01-29 02:20:25 +02:00
|
|
|
" %s [-q] (-f|-F) freq_hz|(-i|-I) interval_s\n"
|
|
|
|
" %s [-q] [(-f|-F) freq_hz|(-i|-I) interval_s] -c [active_s]\n"
|
|
|
|
" %s [-q] [(-f|-F) freq_hz|(-i|-I) interval_s]\n"
|
2013-01-29 02:14:53 +02:00
|
|
|
" [-C|-t 0|1... [-d debounce_s]] [-w wait_s] [-m mask] [-p]\n"
|
|
|
|
" file|pattern\n\n"
|
2013-01-15 19:08:38 +02:00
|
|
|
" -c output bus clock on CLK without sending a pattern\n"
|
|
|
|
" -C temporarily output bus clock on CLK (for debugging)\n"
|
2013-01-16 08:30:04 +02:00
|
|
|
" -d deb_s trigger debounce time (default: no debouncing)\n"
|
2013-01-15 19:24:00 +02:00
|
|
|
" -f freq_hz set bus clock to the specified frequency (default: 1 MHz)\n"
|
2013-01-29 02:14:53 +02:00
|
|
|
" -F freq_hz like -f, but also allow \"overclocking\"\n"
|
2013-01-16 06:58:16 +02:00
|
|
|
" -i inter_s set bus clock such that one cycle equals the specified "
|
|
|
|
"interval\n"
|
2013-01-29 02:20:25 +02:00
|
|
|
" -I inter_s like -i, but also allow \"overclocking\"\n"
|
2013-01-15 22:40:49 +02:00
|
|
|
" -m mask use only the DATx lines specified in the mask (default: 0xf)\n"
|
2013-01-15 22:09:08 +02:00
|
|
|
" -p force interpretation of argument as pattern (and not file)\n"
|
2013-01-15 21:10:14 +02:00
|
|
|
" -q quiet. Don't pretty-print frequencies; don't report clock\n"
|
2013-01-15 23:40:16 +02:00
|
|
|
" differences.\n"
|
2013-01-16 09:45:49 +02:00
|
|
|
" -t 0|1... start pattern when trigger/CLK has passed through the sequence\n"
|
2013-01-16 08:44:30 +02:00
|
|
|
" (default: start pattern immediately)\n"
|
|
|
|
" -w wait_s wait between trigger and sending the pattern\n\n"
|
2013-01-14 08:55:51 +02:00
|
|
|
" active_s keep running that many seconds after setting the clock\n"
|
2013-01-15 19:08:38 +02:00
|
|
|
" (default: exit immediately but leave the clock on)\n"
|
2013-01-15 22:05:33 +02:00
|
|
|
" file file containing the pattern\n"
|
2013-01-14 08:55:51 +02:00
|
|
|
" pattern send the specified pattern on DAT0 through DAT3\n\n"
|
2013-01-16 06:11:57 +02:00
|
|
|
"Frequency: the frequency in hertz, optionally followed by \"M\" or \"k\",\n"
|
2013-01-07 22:01:51 +02:00
|
|
|
" 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"
|
2013-01-16 06:11:57 +02:00
|
|
|
"Duration: the duration in seconds, optionally followed by \"m\", \"u\", or\n"
|
2013-01-16 06:58:16 +02:00
|
|
|
" \"n\", optionally followed by \"s\", optionally followed by \"+\" or \"-\"."
|
|
|
|
"\n"
|
2013-01-14 08:55:51 +02:00
|
|
|
"Pattern: hex digits corresponding to 1 for DAT0, 2 for DAT1, etc.\n"
|
2013-01-15 00:26:15 +02:00
|
|
|
" {n} repeats the preceding digit n times, e.g., 1{3} is equivalent to 111.\n"
|
2013-01-15 19:42:00 +02:00
|
|
|
, name, name, name, name);
|
2013-01-07 22:01:51 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
struct mmcclk clk;
|
2013-01-29 02:14:53 +02:00
|
|
|
int all = 0;
|
2013-01-15 19:08:38 +02:00
|
|
|
int bus_hz = 0, clk_only = 0, clkout = 0, bus_rel = 0;
|
2013-01-14 08:55:51 +02:00
|
|
|
const char *pattern = NULL;
|
2013-01-15 22:09:08 +02:00
|
|
|
int quiet = 0, force_pattern = 0;
|
2013-01-07 22:01:51 +02:00
|
|
|
struct timespec active_ns;
|
2013-01-16 08:30:04 +02:00
|
|
|
int active_rel;
|
2013-01-15 19:08:38 +02:00
|
|
|
int keep_clk = 1;
|
2013-01-15 22:40:49 +02:00
|
|
|
uint8_t mask = 0xf;
|
2013-01-16 09:45:49 +02:00
|
|
|
const char *trigger = NULL;
|
2013-01-16 08:30:04 +02:00
|
|
|
struct timespec debounce_ns;
|
|
|
|
int debounce = 0, debounce_rel;
|
2013-01-16 08:44:30 +02:00
|
|
|
struct timespec wait_ns = { 0, 0 };
|
|
|
|
int wait_rel;
|
2013-01-07 22:01:51 +02:00
|
|
|
char *end;
|
|
|
|
int c;
|
2013-01-15 22:40:49 +02:00
|
|
|
unsigned long tmp;
|
2013-01-16 09:45:49 +02:00
|
|
|
const char *p;
|
2013-01-07 22:01:51 +02:00
|
|
|
|
2013-01-29 02:20:25 +02:00
|
|
|
while ((c = getopt(argc, argv, "cCd:f:F:i:I:m:pqt:w:")) != EOF)
|
2013-01-07 22:01:51 +02:00
|
|
|
switch (c) {
|
|
|
|
case 'c':
|
2013-01-15 19:08:38 +02:00
|
|
|
clk_only = 1;
|
2013-01-15 19:42:00 +02:00
|
|
|
break;
|
2013-01-15 19:08:38 +02:00
|
|
|
case 'C':
|
2013-01-07 22:01:51 +02:00
|
|
|
clkout = 1;
|
|
|
|
break;
|
2013-01-16 08:30:04 +02:00
|
|
|
case 'd':
|
|
|
|
if (!duration_timespec(optarg,
|
|
|
|
&debounce_ns, &debounce_rel))
|
|
|
|
usage(*argv);
|
|
|
|
if (debounce_rel < 0)
|
|
|
|
usage(*argv);
|
|
|
|
debounce = 1;
|
|
|
|
break;
|
2013-01-29 02:14:53 +02:00
|
|
|
case 'F':
|
|
|
|
all = 1;
|
|
|
|
/* fall through */
|
2013-01-16 08:45:22 +02:00
|
|
|
case 'f':
|
|
|
|
if (!frequency(optarg, &bus_hz, &bus_rel))
|
|
|
|
usage(*argv);
|
|
|
|
break;
|
2013-01-29 02:20:25 +02:00
|
|
|
case 'I':
|
|
|
|
all = 1;
|
|
|
|
/* fall through */
|
2013-01-16 08:45:22 +02:00
|
|
|
case 'i':
|
|
|
|
if (!interval(optarg, &bus_hz, &bus_rel))
|
|
|
|
usage(*argv);
|
|
|
|
break;
|
2013-01-15 22:40:49 +02:00
|
|
|
case 'm':
|
|
|
|
tmp = strtoul(optarg, &end, 0);
|
|
|
|
if (*end)
|
|
|
|
usage(*argv);
|
|
|
|
if (tmp & ~0xfUL) {
|
|
|
|
fprintf(stderr, "mask is too large\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
mask = tmp;
|
|
|
|
break;
|
2013-01-15 22:09:08 +02:00
|
|
|
case 'p':
|
|
|
|
force_pattern = 1;
|
|
|
|
break;
|
2013-01-07 22:01:51 +02:00
|
|
|
case 'q':
|
|
|
|
quiet = 1;
|
|
|
|
break;
|
2013-01-15 23:40:16 +02:00
|
|
|
case 't':
|
2013-01-16 09:45:49 +02:00
|
|
|
trigger = optarg;
|
|
|
|
if (!*trigger)
|
2013-01-15 23:40:16 +02:00
|
|
|
usage(*argv);
|
2013-01-16 09:45:49 +02:00
|
|
|
for (p = trigger; *p; p++)
|
|
|
|
if (*p != '0' && *p != '1')
|
|
|
|
usage(*argv);
|
2013-01-15 23:40:16 +02:00
|
|
|
break;
|
2013-01-16 08:44:30 +02:00
|
|
|
case 'w':
|
|
|
|
if (!duration_timespec(optarg,
|
|
|
|
&wait_ns, &wait_rel))
|
|
|
|
usage(*argv);
|
|
|
|
if (wait_rel < 0)
|
|
|
|
usage(*argv);
|
|
|
|
break;
|
2013-01-07 22:01:51 +02:00
|
|
|
default:
|
|
|
|
usage(*argv);
|
|
|
|
}
|
|
|
|
|
2013-01-15 19:42:00 +02:00
|
|
|
if (clkout && clk_only)
|
|
|
|
usage(*argv);
|
2013-01-16 09:45:49 +02:00
|
|
|
if ((clkout || clk_only) && trigger)
|
2013-01-15 23:40:16 +02:00
|
|
|
usage(*argv);
|
2013-01-15 19:42:00 +02:00
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
switch (argc-optind) {
|
|
|
|
case 0:
|
2013-01-15 19:08:38 +02:00
|
|
|
if (clk_only)
|
|
|
|
break;
|
2013-01-16 09:45:49 +02:00
|
|
|
if (clkout || force_pattern || trigger)
|
2013-01-07 22:01:51 +02:00
|
|
|
usage(*argv);
|
2013-01-15 19:42:00 +02:00
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
ubb_open(UBB_ALL);
|
2013-01-15 19:42:00 +02:00
|
|
|
if (bus_hz) {
|
2013-01-29 02:14:53 +02:00
|
|
|
if (!select_freq(&clk, bus_hz, bus_rel, quiet, all)) {
|
2013-01-15 19:42:00 +02:00
|
|
|
fprintf(stderr,
|
|
|
|
"no suitable frequency found\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
printf("%f\n", clk.bus_clk_hz);
|
|
|
|
} else {
|
2013-01-15 21:10:14 +02:00
|
|
|
show_frequencies(quiet);
|
2013-01-15 19:42:00 +02:00
|
|
|
}
|
|
|
|
return 0;
|
2013-01-07 22:01:51 +02:00
|
|
|
case 1:
|
2013-01-15 19:08:38 +02:00
|
|
|
if (clk_only) {
|
2013-01-15 22:09:08 +02:00
|
|
|
if (force_pattern)
|
|
|
|
usage(*argv);
|
2013-01-16 06:58:16 +02:00
|
|
|
if (!duration_timespec(argv[optind],
|
|
|
|
&active_ns, &active_rel))
|
|
|
|
usage(*argv);
|
|
|
|
if (active_rel < 0)
|
2013-01-15 19:08:38 +02:00
|
|
|
usage(*argv);
|
|
|
|
keep_clk = 0;
|
|
|
|
} else {
|
|
|
|
pattern = argv[optind];
|
|
|
|
}
|
2013-01-07 22:01:51 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage(*argv);
|
|
|
|
}
|
|
|
|
|
2013-01-15 22:09:08 +02:00
|
|
|
if (pattern && !force_pattern)
|
2013-01-15 22:05:33 +02:00
|
|
|
pattern = load_pattern(pattern);
|
|
|
|
|
2013-01-07 22:01:51 +02:00
|
|
|
ubb_open(UBB_ALL);
|
|
|
|
|
2013-01-14 08:55:51 +02:00
|
|
|
PDFUNS = UBB_CMD;
|
|
|
|
|
|
|
|
if (!bus_hz)
|
|
|
|
bus_hz = 1000000;
|
|
|
|
|
2013-01-29 02:14:53 +02:00
|
|
|
if (!select_freq(&clk, bus_hz, bus_rel, quiet, all)) {
|
2013-01-07 22:01:51 +02:00
|
|
|
fprintf(stderr, "no suitable frequency found\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2013-01-15 19:42:00 +02:00
|
|
|
if (clkout || clk_only)
|
2013-01-07 22:01:51 +02:00
|
|
|
PDFUNS = UBB_CLK;
|
|
|
|
mmcclk_start(&clk);
|
|
|
|
|
2013-01-14 08:55:51 +02:00
|
|
|
if (pattern)
|
2013-01-16 08:30:04 +02:00
|
|
|
dma_pattern(&clk, pattern, mask << 10,
|
2013-01-16 08:44:30 +02:00
|
|
|
trigger, debounce, &debounce_ns, &wait_ns);
|
2013-01-14 08:55:51 +02:00
|
|
|
|
2013-01-16 06:11:57 +02:00
|
|
|
if (!keep_clk)
|
2013-01-15 19:08:38 +02:00
|
|
|
if (nanosleep(&active_ns, NULL))
|
|
|
|
perror("nanosleep");
|
|
|
|
if (pattern) {
|
|
|
|
mmcclk_stop();
|
2013-01-16 09:45:49 +02:00
|
|
|
ubb_close(mask << 10 | (trigger ? UBB_CLK : 0));
|
2013-01-15 19:08:38 +02:00
|
|
|
} else if (keep_clk) {
|
|
|
|
ubb_close(UBB_CLK);
|
|
|
|
} else {
|
|
|
|
mmcclk_stop();
|
|
|
|
ubb_close(0);
|
|
|
|
}
|
2013-01-07 22:01:51 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|