WIP: Support parallel port logging with Arduino Uno
This commit is contained in:
		| @@ -1,46 +1,25 @@ | ||||
| #include <stdio.h> | ||||
|  | ||||
| #include <arch/gpio.h> | ||||
| #include <arch/i2c_slave.h> | ||||
| #include <arch/uart.h> | ||||
| #include <board/battery.h> | ||||
| #include <board/i2c.h> | ||||
|  | ||||
| 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 (;;) {} | ||||
| } | ||||
|   | ||||
							
								
								
									
										538
									
								
								src/board/arduino/uno/parallel.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										538
									
								
								src/board/arduino/uno/parallel.c
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <board/cpu.h> | ||||
| #include <util/delay.h> | ||||
|  | ||||
| #include <arch/gpio.h> | ||||
| #include <arch/uart.h> | ||||
|  | ||||
| // 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; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user