/*
* 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 .
*/
#ifndef CH375_H
#define CH375_H
#include
#include
#include
#include
#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