diff --git a/src/board/arduino/uno/main.c b/src/board/arduino/uno/main.c index 8b5aaff..bd064e0 100644 --- a/src/board/arduino/uno/main.c +++ b/src/board/arduino/uno/main.c @@ -1,46 +1,25 @@ #include #include -#include #include -#include -#include void init(void) { uart_stdio_init(0, __CONSOLE_BAUD__); - i2c_init(50000); -} - -static void i2c_slave_new() {} - -static void i2c_slave_recv(uint8_t data) { - printf("%c", data); -} - -static uint8_t i2c_slave_send() { - return 0; } struct Gpio LED = GPIO(B, 5); +//TODO: .h file +void parallel_main(void); + int main(void) { init(); gpio_set_dir(&LED, true); gpio_set(&LED, false); - printf("Hello from System76 EC for the Arduino Uno!\n"); - battery_debug(); + parallel_main(); - for (;;) { - i2c_slave_init(0x76, i2c_slave_new, i2c_slave_recv, i2c_slave_send); - int c = getchar(); - i2c_slave_stop(); - if (c == '\r') { - putchar('\n'); - battery_debug(); - } else if (c > 0) { - putchar(c); - } - } + // If parallel_main exits with an error, wait for reset + for (;;) {} } diff --git a/src/board/arduino/uno/parallel.c b/src/board/arduino/uno/parallel.c new file mode 100644 index 0000000..48ecd34 --- /dev/null +++ b/src/board/arduino/uno/parallel.c @@ -0,0 +1,538 @@ +// High resolution pinout can be found here: +// https://osoyoo.com/wp-content/uploads/2017/08/arduino_mega_2560_pinout.png + +#include +#include +#include +#include + +#include +#include + +// Mapping of 24-pin ribbon cable to parallel pins. See schematic +#define PINS \ + /* Data (KSO0 - KSO7) - bi-directional */ \ + PIN(d0, 1) \ + PIN(d1, 2) \ + PIN(d2, 3) \ + PIN(d3, 4) \ + PIN(d4, 5) \ + PIN(d5, 6) \ + PIN(d6, 7) \ + PIN(d7, 8) \ + /* Wait# (KSO9) - input */ \ + /* low to indicate cycle may begin, high to indicate cycle may end */ \ + PIN(wait_n, 9) \ + /* Write# (KSI0) - output */ \ + /* low to indicate write cycle, high to indicate read cycle */ \ + PIN(write_n, 10) \ + /* DataStrobe# (KSI1) - output */ \ + /* low indicates a data cycle */ \ + PIN(data_n, 11) \ + /* Reset# (KSI2) - output */ \ + /* low requests device reset */ \ + PIN(reset_n, 12) \ + /* AddressStrobe# (KSI3) - output */ \ + /* low indicates an address cycle */ \ + PIN(addr_n, 13) \ + +#define DATA_BITS \ + DATA_BIT(0) \ + DATA_BIT(1) \ + DATA_BIT(2) \ + DATA_BIT(3) \ + DATA_BIT(4) \ + DATA_BIT(5) \ + DATA_BIT(6) \ + DATA_BIT(7) + +// Mapping of 24-pin ribbon cable to GPIOs +static struct Gpio GPIOS[13] = { + GPIO(D, 2), + GPIO(D, 3), + GPIO(D, 4), + GPIO(D, 5), + GPIO(D, 6), + GPIO(D, 7), + GPIO(B, 0), + GPIO(B, 1), + GPIO(B, 2), + GPIO(C, 3), + GPIO(C, 2), + GPIO(C, 1), + GPIO(C, 0), +}; + + +// Parallel struct definition +// See http://efplus.com/techref/io/parallel/1284/eppmode.htm +struct Parallel { + #define PIN(N, P) struct Gpio * N; + PINS + #undef PIN +}; + +// Parallel struct instance +static struct Parallel PORT = { + #define PIN(N, P) .N = &GPIOS[P - 1], + PINS + #undef PIN +}; + +// Set port to all high-impedance inputs +void parallel_hiz(struct Parallel * port) { + #define PIN(N, P) \ + gpio_set_dir(port->N, false); \ + gpio_set(port->N, false); + PINS + #undef PIN +} + +// Place all data lines in high or low impendance state +void parallel_data_dir(struct Parallel * port, bool dir) { + #define DATA_BIT(B) gpio_set_dir(port->d ## B, dir); + DATA_BITS + #undef DATA_BIT +} +#define parallel_data_forward(P) parallel_data_dir(P, true) +#define parallel_data_reverse(P) parallel_data_dir(P, false) + +void parallel_data_set_high(struct Parallel * port, uint8_t byte) { + // By convention all lines are high, so only set the ones needed + #define DATA_BIT(B) if (!(byte & (1 << B))) gpio_set(port->d ## B, true); + DATA_BITS + #undef DATA_BIT +} + +// Set port to initial state required before being able to perform cycles +void parallel_reset(struct Parallel * port, bool host) { + parallel_hiz(port); + + // nRESET: output on host, input on peripherals + gpio_set_dir(port->reset_n, host); + + _delay_us(1); + + // nDATASTB: output on host, input on peripherals + gpio_set(port->data_n, true); + gpio_set_dir(port->data_n, host); + + // nADDRSTB: output on host, input on peripherals + gpio_set(port->addr_n, true); + gpio_set_dir(port->addr_n, host); + + // nWRITE: output on host, input on peripherals + gpio_set(port->write_n, true); + gpio_set_dir(port->write_n, host); + + // nWAIT: input on host, output on peripherals + gpio_set(port->wait_n, host); + gpio_set_dir(port->wait_n, !host); + + // Pull up data lines on host, leave floating on peripherals + #define DATA_BIT(B) gpio_set(port->d ## B, host); + DATA_BITS + #undef DATA_BIT + + //TODO: something with straps + + _delay_us(1); + + if (host) { + // Set reset line high, ending reset + gpio_set(port->reset_n, true); + } +} + +uint8_t parallel_read_data(struct Parallel * port) { + uint8_t byte = 0; + #define DATA_BIT(B) if (gpio_get(port->d ## B)) byte |= (1 << B); + DATA_BITS + #undef DATA_BIT + return byte; +} + +void parallel_write_data(struct Parallel * port, uint8_t byte) { + // By convention all lines are high, so only set the ones needed + #define DATA_BIT(B) if (!(byte & (1 << B))) gpio_set(port->d ## B, false); + DATA_BITS + #undef DATA_BIT +} + +//TODO: timeout +int parallel_transaction(struct Parallel * port, uint8_t * data, int length, bool read, bool addr) { + if (!read) { + // Set write line low + gpio_set(port->write_n, false); + + // Set data lines as outputs to write to peripheral + parallel_data_forward(port); + } + + int i; + uint8_t byte = 0; + for (i = 0; i < length; i++) { + // Wait for peripheral to indicate it's ready for next cycle + while (gpio_get(port->wait_n)) {} + + if (!read) { + byte = data[i]; + parallel_write_data(port, byte); + _delay_us(1); + } + + if (addr) { + // Set address strobe low + gpio_set(port->addr_n, false); + } else { + // Set data strobe low + gpio_set(port->data_n, false); + } + _delay_us(1); + + // Wait for peripheral to indicate it's ready + while (!gpio_get(port->wait_n)) {} + + if (read) { + data[i] = parallel_read_data(port); + } + + if (addr) { + // Set address strobe high + gpio_set(port->addr_n, true); + } else { + // Set data strobe high + gpio_set(port->data_n, true); + } + // XXX: Arduino peripheral not fast enough to get the data? + _delay_us(5); + + if (!read) { + // Reset data lines to high + parallel_data_set_high(port, byte); + } + } + + if (!read) { + // Set data lines back to inputs + parallel_data_reverse(port); + + // Set write line high + gpio_set(port->write_n, true); + } + + return i; +} + +#define parallel_get_address(P, D, L) parallel_transaction(P, D, L, true, true) +#define parallel_set_address(P, D, L) parallel_transaction(P, D, L, false, true) +#define parallel_read(P, D, L) parallel_transaction(P, D, L, true, false) +#define parallel_write(P, D, L) parallel_transaction(P, D, L, false, false) + +// host write -> peripheral read +// host read -> peripheral write +bool parallel_peripheral_cycle(struct Parallel * port, uint8_t * data, bool * read, bool * addr) { + if (!gpio_get(port->reset_n)) { + // XXX: Give host some time to get ready + _delay_ms(1); + return false; + } + + while (gpio_get(port->data_n) && gpio_get(port->addr_n)) {} + + *read = gpio_get(port->write_n); + *addr = !gpio_get(port->addr_n); + + if (*read) { + // Host is reading, send the data + parallel_data_forward(port); + parallel_write_data(port, *data); + } + + gpio_set(port->wait_n, true); + + // Wait for host to finish strobe + while (!gpio_get(port->addr_n) || !gpio_get(port->data_n)) {} + + if (*read) { + // Set data lines back to inputs + parallel_data_reverse(port); + } else { + // Host is writing, read the data + *data = parallel_read_data(port); + } + + // Tell host we're ready for next cycle + gpio_set(port->wait_n, false); + + return true; +} + +static uint8_t ADDRESS_INDAR1 = 0x05; +static uint8_t ADDRESS_INDDR = 0x08; + +static uint8_t ZERO = 0x00; +static uint8_t SPI_ENABLE = 0xFE; +static uint8_t SPI_DATA = 0xFD; + +// Disable chip +int parallel_spi_reset(struct Parallel *port) { + int res; + + res = parallel_set_address(port, &ADDRESS_INDAR1, 1); + if (res < 0) return res; + + res = parallel_write(port, &SPI_ENABLE, 1); + if (res < 0) return res; + + res = parallel_set_address(port, &ADDRESS_INDDR, 1); + if (res < 0) return res; + + return parallel_write(port, &ZERO, 1); +} + +// Enable chip and read or write data +int parallel_spi_transaction(struct Parallel *port, uint8_t * data, int length, bool read) { + int res; + + res = parallel_set_address(port, &ADDRESS_INDAR1, 1); + if (res < 0) return res; + + res = parallel_write(port, &SPI_DATA, 1); + if (res < 0) return res; + + res = parallel_set_address(port, &ADDRESS_INDDR, 1); + if (res < 0) return res; + + return parallel_transaction(port, data, length, read, false); +} + +#define parallel_spi_read(P, D, L) parallel_spi_transaction(P, D, L, true) +#define parallel_spi_write(P, D, L) parallel_spi_transaction(P, D, L, false) + +// "Hardware" accelerated SPI programming, requires ECINDARs to be set +int parallel_spi_program(struct Parallel * port, uint8_t * data, int length, bool initialized) { + static uint8_t aai[6] = { 0xAD, 0, 0, 0, 0, 0 }; + int res; + int i; + uint8_t status; + + for(i = 0; (i + 1) < length; i+=2) { + // Disable chip to begin command + res = parallel_spi_reset(port); + if (res < 0) return res; + + if (!initialized) { + // If not initialized, the start address must be sent + aai[1] = 0; + aai[2] = 0; + aai[3] = 0; + + aai[4] = data[i]; + aai[5] = data[i + 1]; + + res = parallel_spi_write(port, aai, 6); + if (res < 0) return res; + + initialized = true; + } else { + aai[1] = data[i]; + aai[2] = data[i + 1]; + + res = parallel_spi_write(port, aai, 3); + if (res < 0) return res; + } + + // Wait for SPI busy flag to clear + for (;;) { + // Disable chip to begin command + res = parallel_spi_reset(port); + if (res < 0) return res; + + status = 0x05; + res = parallel_spi_write(port, &status, 1); + if (res < 0) return res; + + res = parallel_spi_read(port, &status, 1); + if (res < 0) return res; + + if (!(status & 1)) break; + } + } + + return i; +} + +int serial_transaction(uint8_t * data, int length, bool read) { + int i; + for (i = 0; i < length; i++) { + if (read) { + data[i] = (uint8_t)uart_read(uart_stdio); + } else { + uart_write(uart_stdio, (unsigned char)data[i]); + } + } + + return i; +} + +#define serial_read(D, L) serial_transaction(D, L, true) +#define serial_write(D, L) serial_transaction(D, L, false) + +int parallel_main(void) { + int res = 0; + + struct Parallel * port = &PORT; + parallel_reset(port, true); + + static uint8_t data[128]; + char command; + int length; + int i; + uint8_t address; + bool set_address = false; + bool program_aai = false; + + unsigned char console_msg[] = "Entering console mode\n"; + + for (;;) { + // Read command and length + res = serial_read(data, 2); + if (res < 0) goto err; + // Command is a character + command = (char)data[0]; + + // Special address prefix + if (command == 'A') { + // Prepare to set address on next parallel cycle + address = data[1]; + set_address = true; + continue; + } else if (command != 'P') { + // End accelerated programming + program_aai = false; + } + + // Length is received data + 1 + length = ((int)data[1]) + 1; + // Truncate length to size of data + if (length > sizeof(data)) length = sizeof(data); + + switch (command) { + // Buffer size + case 'B': + // Fill buffer size - 1 + for (i = 0; i < length; i++) { + if (i == 0) { + data[i] = (uint8_t)(sizeof(data) - 1); + } else { + data[i] = 0; + } + } + + // Write data to serial + res = serial_write(data, length); + if (res < 0) goto err; + + break; + + // Debug console + case 'C': + serial_write(console_msg, sizeof(console_msg)); + + // Reconfigure as a peripheral + parallel_reset(port, false); + + for (;;) { + bool read = false; + bool addr = false; + bool ret = parallel_peripheral_cycle(port, data, &read, &addr); + + if (ret && !read && !addr) { + res = serial_write(data, 1); + if (res < 0) goto err; + } + } + + break; + + // Echo + case 'E': + // Read data from serial + res = serial_read(data, length); + if (res < 0) goto err; + + // Write data to serial + res = serial_write(data, length); + if (res < 0) goto err; + + break; + + // Read data + case 'R': + // Update parallel address if necessary + if (set_address) { + res = parallel_set_address(port, &address, 1); + if (res < 0) goto err; + set_address = false; + } + + // Read data from parallel + res = parallel_read(port, data, length); + if (res < 0) goto err; + + // Write data to serial + res = serial_write(data, length); + if (res < 0) goto err; + + break; + + // Accelerated program function + case 'P': + // Read data from serial + res = serial_read(data, length); + if (res < 0) goto err; + + // Run accelerated programming function + res = parallel_spi_program(port, data, length, program_aai); + if (res < 0) goto err; + program_aai = true; + + // Send ACK of data length + data[0] = (uint8_t)(length - 1); + res = serial_write(data, 1); + if (res < 0) goto err; + + break; + + // Write data + case 'W': + // Read data from serial + res = serial_read(data, length); + if (res < 0) goto err; + + // Update parallel address if necessary + if (set_address) { + res = parallel_set_address(port, &address, 1); + if (res < 0) goto err; + set_address = false; + } + + // Write data to parallel + res = parallel_write(port, data, length); + if (res < 0) goto err; + + // Send ACK of data length + data[0] = (uint8_t)(length - 1); + res = serial_write(data, 1); + if (res < 0) goto err; + + break; + } + } + +err: + parallel_hiz(port); + + return res; +}