diff options
Diffstat (limited to 'ch375.h')
| -rw-r--r-- | ch375.h | 423 |
1 files changed, 423 insertions, 0 deletions
@@ -0,0 +1,423 @@ +/* + * CH375 hardware header + * Copyright (C) 2024 Javier S. Pedro + * + * 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 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + + +#ifndef CH375_H +#define CH375_H + +#include <stdint.h> +#include <stdbool.h> +#include <conio.h> +#include <i86.h> + +#include "usb.h" + +extern unsigned short ch375_port; + +enum { + CH375_PORT_DATA = 0, + CH375_PORT_CMD = 1, +}; + +enum ch375_command { + /** Returns chip and firmware version in bit 5-0. + * output: 1byte. + * C375B is 0x37. */ + CH375_GET_IC_VERSION = 0x01, + /** (Host mode) Set serial port baud rate. */ + CH375_SET_BAUDRATE = 0x02, + /** Enter low power mode. Wakes up with USB or host activity. */ + CH375_ENTER_SLEEP = 0x03, + /** Manualy force USB speed. + * Input: 1 byte: + * 0: Full speed + * 1: Full speed with low speed clock frequency (??) + * 2: Low speed */ + CH375_SET_USB_SPEED = 0x04, + /** Hardware reset. May take up to 40ms. */ + CH375_RESET = 0x05, + /** Simple eco function. Returns input byte negated. + * Input: 1 byte: x + * Output: 1 byte: ~x */ + CH375_CHECK_EXIST = 0x06, + + CH375_READ_REG = 0x0A, + CH375_WRITE_REG = 0x0B, + /** Sets a 100us timer. + * Output: infinite bytes. 0 while timer not expired, non-0 when expired. */ + CH375_DELAY_100US = 0x0F, + /** (Device mode) set our USB vid and PID. + * Input: 4 bytes: VID LSB, VID MSB, PID LSB, PID MSB */ + CH375_SET_USB_ID = 0x12, + /** (Device mode) set USB address. ?? */ + CH375_SET_USB_ADDR = 0x13, + /** Set USB mode. Usually takes 20µs. + * Input: 1 byte: + * 0 : Device mode disabled (i.e. device plug-in detection) + * 1 : Device mode with external firmware + * 2 : Device mode with built-in firmware + * 4 : Host mode disabled (i.e. device plug-in detection) + * 5 : Host mode without automatic SOF packets. + * 6 : Host mode with automatic SOF packets. + * 7 : Host mode, initiate bus reset + * Output: 1 byte: + * CMD_RET_SUCCESS if OK + * CMD_RET_ABORT, etc. if error + */ + CH375_SET_USB_MODE = 0x15, + /** (Host mode) Check the connection status. + * Output: 1 byte: either USB_INT_CONNECT or USB_INT_DISCONNECT or USB_INT_USB_READY. */ + CH375_TEST_CONNECT = 0x16, + /** Abandon retrying and return NAK. */ + CH375_ABORT_NAK = 0x17, + + CH375_SET_ENDP2 = 0x18, + CH375_SET_ENDP3 = 0x19, + CH375_SET_ENDP4 = 0x1A, + CH375_SET_ENDP5 = 0x1B, + CH375_SET_ENDP6 = 0x1C, + CH375_SET_ENDP7 = 0x1D, + + /** Get interrupt status and ack pending interrupts. + * Output: 1 byte */ + CH375_GET_INT_STATUS = 0x22, + /** (Device mode) Release current USB buffer. */ + CH375_UNLOCK_USB = 0x23, + /** Read buffer of endpoint of current interrupt. + * Output: length byte + data. */ + CH375_RD_USB_DATA0 = 0x27, + /** Equivalent to RD_USB_DATA0 + UNLOCK_USB. */ + CH375_RD_USB_DATA = 0x28, + CH375_WR_USB_DATA3 = 0x29, + CH375_WR_USB_DATA5 = 0x2A, + CH375_WR_USB_DATA7 = 0x2B, + /** (Host mode) Clear error state on endpoint. + * Input: 1 byte: endpoint number. + * On done: Triggers interrupt. */ + CH375_CLR_STALL = 0x41, + /** (Host mode) Set USB address. + * Input: USB address. + * On done: Triggers interrupt. */ + CH375_SET_ADDRESS = 0x45, + CH375_GET_DESCR = 0x46, + CH375_SET_CONFIG = 0x49, + CH375_AUTO_SETUP = 0x4D, + CH375_ISSUE_TKN_X = 0x4E, + CH375_ISSUE_TOKEN = 0x4F, + + CH375_DISK_BOC = 0x50, + CH375_DISK_INIT = 0x51, + CH375_DISK_RESET = 0x52, + CH375_DISK_SIZE = 0x53, + CH375_DISK_READ = 0x54, + CH375_DISK_RD_GO = 0x55, + CH375_DISK_WRITE = 0x56, + CH375_DISK_WR_GO = 0x57, + CH375_DISK_INQUIRY = 0x58, + CH375_DISK_READY = 0x59, + CH375_DISK_R_SENSE = 0x5A, +}; + +enum ch375_cmd_ret { + CH375_RET_SUCESS = 0x51, + CH375_RET_ABORT = 0x5F +}; + +enum ch375_int { + /** if (x & mask) == val, this is a USB device mode interrupt. */ + CH375_INT_DEVICE_MASK = 0xF0, + CH375_INT_DEVICE_VAL = 0x00, + + CH375_INT_HOST_MASK = 0xE0, + CH375_INT_HOST_VAL = 0x20, + + CH375_INT_BUS_RESET1 = 0x03, + CH375_INT_BUS_RESET2 = 0x07, + CH375_INT_BUS_RESET3 = 0x0B, + CH375_INT_BUS_RESET4 = 0x0F, + + /** USB bus suspend event. */ + CH375_INT_USB_SUSPEND = 0x05, + /** Wakeup from USB suspend. */ + CH375_INT_USB_WAKE_UP = 0x06, + CH375_INT_USB_SUCCESS = 0x14, + CH375_INT_USB_CONNECT = 0x15, + CH375_INT_USB_DISCONNECT = 0x16, + CH375_INT_USB_BUF_OVER = 0x17, + CH375_INT_USB_READY = 0x18, + CH375_INT_USB_DISK_READ = 0x1D, + CH375_INT_USB_DISK_WRITE = 0x1E, + CH375_INT_USB_DISK_ERR = 0x1F, +}; + +static inline void ch375_iodelay(void) +{ + // Doesn't look to be required on pocket386 + //outp(0x80, 0); +} + +static inline uint8_t ch375_read_data(void) +{ + return inp(ch375_port + CH375_PORT_DATA); +} + +static inline void ch375_write_data(uint8_t data) +{ + outp(ch375_port + CH375_PORT_DATA, data); + ch375_iodelay(); +} + +static inline void ch375_send_command(enum ch375_command cmd) +{ + outp(ch375_port + CH375_PORT_CMD, cmd); + ch375_iodelay(); +} + +/// True if interrupt line is currently active (i.e. low) +static inline bool ch375_poll_interrupt(void) +{ + return !(inp(ch375_port + CH375_PORT_CMD) & 0x80); +} + +enum CH375_REG { + /** (Host mode) Get data rate of current USB device. + * Bit 4: if 1= Low Speed. if 0 = Full Speed.*/ + CH375_REG_DEV_RATE = 0x0A, + /** (Device mode): how to check USB bus suspend status. + * 0: Do not check + * 4: Check at 50ms intervals + * 5: Check at 10ms intervals */ + CH375_REG_CHK_SUSPEND = 0x10, + CH375_REG_TOGGLE = 0x1A, + + /** ??? + * Bit 5 : set after disk is online? but never unset... */ + CH375_REG_DISK_STATUS = 0x20, + /** (Host mode) Set the number of retries for USB transactions. + * If bit 7 is 0, no retry is performed when NAK is received. + * If bit 7 is 1 and bit 6 is 0, unlimited retries are performed when NAK is received. + * If bit 7 is 1 and bit 6 is 1, the maximum retry is 2 seconds when NAK is received. + * Bits 5-0 are the number of retries after timeout. */ + CH375_REG_RETRY = 0x25, + /** (Host disk mode) Set the current LUN. */ + CH375_REG_DISK_LUN = 0x34, + CH375_REG_DISK_MAX_LUN = 0x38, + /** (Host disk mode) Set the total number of 64-byte packets per sector. + * E.g. 8 = 512 byte sectors. */ + CH375_REG_DISK_PKT_P_SEC = 0x39, + /// Drive letter (in ASCII) currently assigned. This is set by software. + CH375_REG_DISK_NAME = 0x3E + + // 0x1B what's on this register ? 0x7F , 0x80 values seen + // 0x20 ?? some type of status ? + // 0x31, 0x32, 0x33 ?? + +}; + +static uint8_t ch375_read_reg(enum CH375_REG reg) +{ + ch375_send_command(CH375_READ_REG); + ch375_write_data(reg); + return ch375_read_data(); +} + +static void ch375_write_reg(enum CH375_REG reg, uint8_t value) +{ + ch375_send_command(CH375_WRITE_REG); + ch375_write_data(reg); + ch375_write_data(value); +} + +/** Reads one transfer block (64 bytes max). */ +#if _M_IX86 >= 200 +// Watcom does not seem able to synthesize rep ins/outs. Do it in 286+ mode. +static uint8_t ch375_read_data_block(uint8_t far *buffer); +#pragma aux ch375_read_data_block = \ + "mov dx, [ch375_port]" \ + "in al, dx" \ + "movzx cx, al" \ + "rep ins byte ptr es:[di], dx" \ + __parm [es di] \ + __value [al] \ + __modify exact [cx dx di] +#else +static uint8_t ch375_read_data_block(uint8_t far *buffer) +{ + uint8_t i, n = ch375_read_data(); + + for (i = 0; i < n; i++) { + buffer[i] = ch375_read_data(); + } + + return n; +} +#endif + +#if _M_IX86 >= 200 +static void ch375_write_data_block(uint8_t far * buffer, uint8_t n); +#pragma aux ch375_write_data_block = \ + "mov dx, [ch375_port]" \ + "out dx, al" \ + "movzx cx, al" \ + "rep outs dx, byte ptr es:[si]" \ + __parm [es si] [al] \ + __modify exact [cx dx si] +#else +static void ch375_write_data_block(uint8_t far * buffer, uint8_t n) +{ + uint8_t i; + + ch375_write_data(n); + + for (i = 0; i < n; i++) { + ch375_write_data(buffer[i]); + } +} +#endif + +// GLOBAL COMMANDS + +static inline uint8_t ch375_get_version(void) +{ + ch375_send_command(CH375_GET_IC_VERSION); + return ch375_read_data() & 0x3F; +} + +static inline void ch375_enter_sleep(void) +{ + ch375_send_command(CH375_ENTER_SLEEP); +} + +static inline void ch375_reset(void) +{ + ch375_send_command(CH375_RESET); +} + +static inline uint8_t ch375_check_exists(uint8_t val) +{ + ch375_send_command(CH375_CHECK_EXIST); + ch375_write_data(val); + return ch375_read_data(); +} + +static inline uint8_t ch375_get_int_status(void) +{ + ch375_send_command(CH375_GET_INT_STATUS); + return ch375_read_data(); +} + +enum ch375_usb_mode { + CH375_USB_MODE_DEVICE_DISABLED = 0, + CH375_USB_MODE_DEVICE_EXTERNAL_FIRMWARE = 1, + CH375_USB_MODE_DEVICE_INTERNAL_FIRMWARE = 2, + CH375_USB_MODE_HOST_DISABLED = 4, + CH375_USB_MODE_HOST = 5, + CH375_USB_MODE_HOST_AUTO_SOF = 6, + CH375_USB_MODE_HOST_RESET = 7 +}; + +static void ch375_set_usb_mode(enum ch375_usb_mode mode) { + ch375_send_command(CH375_SET_USB_MODE); + ch375_write_data(mode); +} + +enum ch375_usb_speed { + CH375_USB_SPEED_FULL = 0, + CH375_USB_SPEED_LOW = 2 +}; + +// Seems to be for CH375B only. See below for CH375A alternative. +static void ch375_set_usb_speed(enum ch375_usb_speed speed) { + ch375_send_command(CH375_SET_USB_SPEED); + ch375_write_data(speed); +} + +static void ch375a_set_low_speed(void) { + // Undocumented. + ch375_write_reg(0x17, 0xd8); +} + +// HOST COMMANDS + +static void ch375_test_connect(void) { + ch375_send_command(CH375_TEST_CONNECT); +} + +static void ch375_auto_setup(void) { + ch375_send_command(CH375_AUTO_SETUP); +} + +enum ch375_retry_mode { + CH375_RETRY_NAK_INFINITE = 0x80, + CH375_RETRY_NAK_2S = 0xC0, + CH375_RETRY_NAK_NONE = 0x00, + + CH375_RETRY_TIMEOUT_MAX = 0x1F +}; + +static void ch375_set_retry(enum ch375_retry_mode mode) { + ch375_write_reg(CH375_REG_RETRY, mode); +} + +static void ch375_get_descriptor(usb_descriptor_type type) { + ch375_send_command(CH375_GET_DESCR); + ch375_write_data(type); +} + +// DISK COMMANDS + +enum ch375_reg_disk_status { + CH375_REG_DISK_STATUS_READY = 0x20 +}; + +static inline bool ch375_is_disk_ready(void) +{ + return ch375_read_reg(CH375_REG_DISK_STATUS) & CH375_REG_DISK_STATUS_READY; +} + +static void ch375_disk_init() +{ + ch375_send_command(CH375_DISK_INIT); +} + +static void ch375_disk_read(uint32_t lba, uint8_t nsectors) +{ + ch375_send_command(CH375_DISK_READ); + ch375_write_data((lba >> 0) & 0xFF); + ch375_write_data((lba >> 8) & 0xFF); + ch375_write_data((lba >> 16) & 0xFF); + ch375_write_data((lba >> 24) & 0xFF); + ch375_write_data(nsectors); +} + +static void ch375_disk_write(uint32_t lba, uint8_t nsectors) +{ + ch375_send_command(CH375_DISK_WRITE); + ch375_write_data((lba >> 0) & 0xFF); + ch375_write_data((lba >> 8) & 0xFF); + ch375_write_data((lba >> 16) & 0xFF); + ch375_write_data((lba >> 24) & 0xFF); + ch375_write_data(nsectors); +} + +static void ch375_test_disk_ready(void) +{ + ch375_send_command(CH375_DISK_READY); +} + +#endif |
