nuclear@3: /* nuclear@3: 72pin SIMM DRAM tester. nuclear@3: Copyright (C) 2017 John Tsiombikas nuclear@3: nuclear@3: This program is free software: you can redistribute it and/or modify nuclear@3: it under the terms of the GNU General Public License as published by nuclear@3: the Free Software Foundation, either version 3 of the License, or nuclear@3: (at your option) any later version. nuclear@3: nuclear@3: This program is distributed in the hope that it will be useful, nuclear@3: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@3: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@3: GNU General Public License for more details. nuclear@3: nuclear@3: You should have received a copy of the GNU General Public License nuclear@3: along with this program. If not, see . nuclear@3: */ nuclear@2: #define F_CPU XTAL nuclear@2: nuclear@0: #include nuclear@1: #include nuclear@0: #include nuclear@0: #include nuclear@2: #include nuclear@0: #include nuclear@0: #include nuclear@2: #include nuclear@0: #include "serial.h" nuclear@0: nuclear@1: /* pin assignments: nuclear@1: * A[0,7] data nuclear@1: * B[0,7] A0 - A7 nuclear@1: * C[0,3] A8 - A11 nuclear@1: * D7 CAS3 nuclear@1: * D6 RAS2 nuclear@1: * D5 RAS3 nuclear@1: */ nuclear@1: nuclear@1: #define CAS3_BIT 0x80 nuclear@1: #define RAS2_BIT 0x40 nuclear@1: #define RAS3_BIT 0x20 nuclear@1: #define WE_BIT 0x04 nuclear@1: nuclear@1: void proc_cmd(char *cmd); nuclear@1: void cmd_read(char *buf); nuclear@1: void cmd_write(char *buf); nuclear@1: void cmd_setcfg(char *buf); nuclear@2: void cmd_test(char *buf); nuclear@2: void cmd_detect(void); nuclear@1: nuclear@1: void dram_init(void); nuclear@1: void dram_detect(void); nuclear@2: int memtest(uint32_t addr); nuclear@1: void dram_refresh(void); nuclear@2: void dram_write(uint32_t addr, unsigned char val); nuclear@2: unsigned char dram_read(uint32_t addr); nuclear@0: nuclear@0: #define MAX_INPUT_SIZE 128 nuclear@0: static char input[MAX_INPUT_SIZE]; nuclear@0: static unsigned char inp_cidx; nuclear@0: nuclear@1: /* SIMM access config */ nuclear@1: static int addr_bits; nuclear@2: static uint32_t addr_mask; nuclear@1: static int ras_lines = 1; nuclear@1: static long memsize_kb; /* derived from the above */ nuclear@1: nuclear@0: int main(void) nuclear@0: { nuclear@1: dram_init(); nuclear@0: init_serial(38400); nuclear@0: sei(); nuclear@0: nuclear@2: printf("\n72pin SIMM DRAM tester by John Tsiombikas \n"); nuclear@1: nuclear@2: cmd_detect(); nuclear@1: nuclear@1: fputs("> ", stdout); nuclear@0: nuclear@0: for(;;) { nuclear@2: if(have_input()) { nuclear@0: int c = getchar(); nuclear@0: putchar(c); nuclear@0: nuclear@0: if(c == '\r' || c == '\n') { nuclear@0: input[inp_cidx] = 0; nuclear@1: proc_cmd(input); nuclear@0: inp_cidx = 0; nuclear@1: nuclear@1: fputs("> ", stdout); nuclear@0: } else if(inp_cidx < sizeof input - 1) { nuclear@0: input[inp_cidx++] = c; nuclear@0: } nuclear@0: } nuclear@1: nuclear@1: dram_refresh(); nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: void proc_cmd(char *cmd) nuclear@1: { nuclear@1: switch(cmd[0]) { nuclear@1: case 'w': nuclear@1: cmd_write(cmd + 1); nuclear@1: break; nuclear@1: nuclear@1: case 'r': nuclear@1: cmd_read(cmd + 1); nuclear@1: break; nuclear@1: nuclear@1: case 's': nuclear@1: cmd_setcfg(cmd + 1); nuclear@1: break; nuclear@1: nuclear@2: case 't': nuclear@2: cmd_test(cmd + 1); nuclear@2: break; nuclear@2: nuclear@2: case 'd': nuclear@2: cmd_detect(); nuclear@2: break; nuclear@2: nuclear@1: case 'h': nuclear@1: printf("commands:\n"); nuclear@1: printf(" w - write byte to address\n"); nuclear@1: printf(" r - read byte from address\n"); nuclear@1: printf(" s \n"); nuclear@2: printf(" t - test address\n"); nuclear@2: printf(" d - detect DRAM\n"); nuclear@1: printf(" h - help\n"); nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: void cmd_read(char *buf) nuclear@1: { nuclear@1: char *endp; nuclear@2: uint32_t addr; nuclear@1: unsigned char data; nuclear@1: nuclear@1: addr = strtol(buf, &endp, 0); nuclear@1: if(endp == buf) { nuclear@1: fprintf(stderr, "invalid argument to write command: %s\n", buf); nuclear@1: return; nuclear@1: } nuclear@1: buf = endp; nuclear@1: nuclear@1: data = dram_read(addr); nuclear@2: printf("%04lx: %02x (%u)\n", (unsigned long)addr, (unsigned int)data, (unsigned int)data); nuclear@1: } nuclear@1: nuclear@1: void cmd_write(char *buf) nuclear@1: { nuclear@1: char *endp; nuclear@2: uint32_t addr; nuclear@1: unsigned char data; nuclear@1: nuclear@1: addr = strtol(buf, &endp, 0); nuclear@1: if(endp == buf) { nuclear@1: fprintf(stderr, "invalid address argument to read command: %s\n", buf); nuclear@1: return; nuclear@1: } nuclear@1: buf = endp; nuclear@1: nuclear@1: data = strtol(buf, &endp, 0); nuclear@1: if(endp == buf) { nuclear@1: fprintf(stderr, "invalid data argument to read command: %s\n", buf); nuclear@1: return; nuclear@1: } nuclear@1: buf = endp; nuclear@1: nuclear@1: dram_write(addr, data); nuclear@2: printf("%04lx: %02x (%u)\n", (unsigned long)addr, (unsigned int)data, (unsigned int)data); nuclear@1: } nuclear@1: nuclear@1: void cmd_setcfg(char *buf) nuclear@1: { nuclear@1: char *endp; nuclear@1: char *name, *valstr; nuclear@1: long value; nuclear@1: nuclear@1: name = buf; nuclear@1: while(*name && isspace(*name)) ++name; nuclear@1: if(!*name) { nuclear@1: fprintf(stderr, "invalid or missing variable name\n"); nuclear@1: return; nuclear@1: } nuclear@1: endp = name; nuclear@1: while(*endp && !isspace(*endp)) ++endp; nuclear@1: *endp = 0; nuclear@1: nuclear@1: valstr = endp + 1; nuclear@1: value = strtol(valstr, &endp, 0); nuclear@1: if(endp == valstr) { nuclear@1: fprintf(stderr, "invalid or missing variable value: %s\n", valstr); nuclear@1: return; nuclear@1: } nuclear@1: nuclear@1: if(strcmp(name, "addr_bits") == 0) { nuclear@1: if(value > 0 && value <= 12) { nuclear@1: addr_bits = value; nuclear@2: addr_mask = ((uint32_t)1 << addr_bits) - 1; nuclear@2: printf("Address bits: %ld (mask: %lx)\n", value, (unsigned long)addr_mask); nuclear@1: } else { nuclear@1: fprintf(stderr, "invalid address bits value: %ld\n", value); nuclear@1: } nuclear@1: } else if(strcmp(name, "ras_lines") == 0) { nuclear@1: if(value > 0 && value <= 2) { nuclear@1: ras_lines = value; nuclear@1: printf("RAS lines: %d\n", ras_lines); nuclear@1: } else { nuclear@1: fprintf(stderr, "invalid RAS lines value: %ld\n", value); nuclear@1: } nuclear@1: } else { nuclear@1: fprintf(stderr, "unknown variable: %s\n", name); nuclear@1: } nuclear@1: } nuclear@1: nuclear@2: void cmd_test(char *buf) nuclear@2: { nuclear@2: char *endp; nuclear@2: uint32_t addr; nuclear@2: nuclear@2: addr = strtol(buf, &endp, 0); nuclear@2: if(endp == buf) { nuclear@2: fprintf(stderr, "invalid argument to test command: %s\n", buf); nuclear@2: return; nuclear@2: } nuclear@2: nuclear@2: if(memtest(addr) == 0) { nuclear@2: printf("success!\n"); nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: nuclear@2: void cmd_detect(void) nuclear@2: { nuclear@2: printf("Detecting memory ...\n"); nuclear@2: dram_detect(); nuclear@2: nuclear@2: memsize_kb = ((uint32_t)1 << (addr_bits * 2)) * 4 * ras_lines; nuclear@2: nuclear@2: printf("Address lines: %d\n", addr_bits); nuclear@2: printf("RAS lines: %d\n", ras_lines); nuclear@2: printf("Memory size: %ldmb (%ldkb)\n", memsize_kb >> 20, memsize_kb >> 10); nuclear@2: } nuclear@2: nuclear@2: nuclear@1: void dram_set_data(unsigned char val) nuclear@1: { nuclear@1: DDRA = 0xff; nuclear@1: PORTA = val; nuclear@1: } nuclear@1: nuclear@1: void dram_release_data(void) nuclear@1: { nuclear@1: DDRA = 0; nuclear@1: PORTA = 0; nuclear@1: } nuclear@1: nuclear@1: void dram_set_addr(unsigned long addr) nuclear@1: { nuclear@1: PORTB = addr & 0xff; nuclear@1: PORTC = (addr >> 8) & 3; nuclear@1: } nuclear@1: nuclear@1: void dram_assert_cas(void) nuclear@1: { nuclear@1: PORTD &= ~CAS3_BIT; nuclear@1: } nuclear@1: nuclear@1: void dram_release_cas(void) nuclear@1: { nuclear@1: PORTD |= CAS3_BIT; nuclear@1: } nuclear@1: nuclear@1: void dram_assert_ras(unsigned char bits) nuclear@1: { nuclear@1: PORTD &= ~bits; nuclear@1: } nuclear@1: nuclear@1: void dram_release_ras(unsigned char bits) nuclear@1: { nuclear@1: PORTD |= bits; nuclear@1: } nuclear@1: nuclear@1: void dram_set_we(void) nuclear@1: { nuclear@1: PORTD &= ~WE_BIT; nuclear@1: } nuclear@1: nuclear@1: void dram_clear_we(void) nuclear@1: { nuclear@1: PORTD |= WE_BIT; nuclear@1: } nuclear@1: nuclear@1: void dram_init(void) nuclear@1: { nuclear@1: DDRA = 0; /* port A is the data bus */ nuclear@1: PORTA = 0; /* no pullups when A is input */ nuclear@1: DDRB = 0xff; /* port B is A0-A7 */ nuclear@1: DDRC = 0xff; /* port C (low nibble) is A8-A11 */ nuclear@1: DDRD = 0xff; /* port D are the control lines CAS/RAS/WR */ nuclear@1: nuclear@1: PORTD = 0xff; /* deassert all control signals */ nuclear@2: nuclear@2: /* it seems like nothing works until we do one refresh cycle... */ nuclear@2: dram_refresh(); nuclear@1: } nuclear@1: nuclear@1: void dram_detect(void) nuclear@1: { nuclear@2: uint32_t addr = 0; nuclear@2: nuclear@1: /* detect how many address bits we've got */ nuclear@1: addr_bits = 12; nuclear@1: while(addr_bits > 8) { nuclear@2: addr_mask = ((uint32_t)1 << (uint32_t)addr_bits) - 1; nuclear@2: addr = ((uint32_t)1 << ((uint32_t)addr_bits * 2)) - 1; nuclear@2: if(memtest(addr) == 0) { nuclear@1: break; nuclear@1: } nuclear@1: --addr_bits; nuclear@1: } nuclear@1: if(addr_bits < 1) { nuclear@1: fprintf(stderr, "Failed to detect DRAM configuration (address lines)...\n"); nuclear@1: return; nuclear@1: } nuclear@2: nuclear@2: /* now detect if there's a second ras pair */ nuclear@2: ++addr; /* addr was already the highest of the first bank, see if there's a second */ nuclear@2: ras_lines = 2; nuclear@2: if(memtest(addr) != 0) { nuclear@2: ras_lines = 1; nuclear@2: } nuclear@1: } nuclear@1: nuclear@2: int memtest(uint32_t addr) nuclear@1: { nuclear@1: int i; nuclear@1: unsigned char pat[] = { 0xf0, 0x0f, 0xaa, 0x55, 0xc0, 0x30, 0x0c, 0x03 }; nuclear@1: unsigned char val; nuclear@1: nuclear@2: printf("testing address: %lx (a:%d,r:%d)\n", (unsigned long)addr, addr_bits, ras_lines); nuclear@1: nuclear@1: for(i=0; i> addr_bits) & addr_mask; nuclear@2: uint32_t col_addr = addr & addr_mask; nuclear@2: unsigned char ras = (addr >> (addr_bits * 2)) ? RAS3_BIT : RAS2_BIT; nuclear@2: nuclear@1: dram_set_data(val); nuclear@1: dram_set_we(); nuclear@1: /* set row address */ nuclear@2: dram_set_addr(row_addr); nuclear@2: dram_assert_ras(ras); nuclear@1: /* set column address */ nuclear@2: dram_set_addr(col_addr); nuclear@1: dram_assert_cas(); nuclear@2: dram_release_ras(ras); nuclear@1: dram_release_cas(); nuclear@1: dram_release_data(); nuclear@1: dram_clear_we(); nuclear@1: } nuclear@1: nuclear@2: unsigned char dram_read(uint32_t addr) nuclear@1: { nuclear@1: unsigned char val; nuclear@2: uint32_t row_addr = (addr >> addr_bits) & addr_mask; nuclear@2: uint32_t col_addr = addr & addr_mask; nuclear@2: unsigned char ras = (addr >> (addr_bits * 2)) ? RAS3_BIT : RAS2_BIT; nuclear@1: nuclear@1: dram_clear_we(); nuclear@2: /* this is necessary to remove previous data from the lines when no-one is driving them nuclear@2: * in case we're trying to detect the presence of a RAS line which doesn't exist nuclear@2: */ nuclear@2: dram_set_data(0); nuclear@1: dram_release_data(); nuclear@1: nuclear@1: /* set row address */ nuclear@2: dram_set_addr(row_addr); nuclear@2: dram_assert_ras(ras); nuclear@1: /* set column address */ nuclear@2: dram_set_addr(col_addr); nuclear@1: dram_assert_cas(); nuclear@1: nuclear@1: val = PINA; nuclear@1: nuclear@2: dram_release_ras(ras); nuclear@1: dram_release_cas(); nuclear@1: nuclear@1: return val; nuclear@1: }