#include "floppy_motor.h"
#include "floppy_reg.h"
#include "dma.h"
#include <block_driver_struct.h>
#include <alarm.h>
#include <alarm_struct.h>
#include <printk.h>
#include <mutex.h>
#include <mutex_struct.h>
#include <condition.h>
#include <areas.h>
#include <asm.h>
#include <config.h>
#include <util.h>
#include <string.h>
#define FD_READ_DATA 0xe6
#define FD_WRITE_DATA 0xc5
#define FD_VERSION 0x10
#define FD_RECALIBRATE 0x07
#define FD_SENSE_INTERRUPT_STATUS 0x08
#define FD_SPECIFY 0x03
#define FD_SENSE_DRIVE_STATUS 0x04
#define FD_SEEK 0x0f
#define FD_CONFIGURE 0x13
#define SIZE 2880
#define SECTORS 18
#define HEADS 2
#define SECTOR_SIZE 512
#define GAP 0x1b
#define RATE 0x00
#define SPEC 0xcf
#define FD_8272A 0x80
#define FD_82077AA 0x90
#define BLOCK_COUNT (SIZE * SECTOR_SIZE / BLOCK_SIZE)
struct rw_request_struct {
unsigned char head;
unsigned char track;
unsigned char sector;
unsigned int count;
};
static int need_reset;
static int disk_change;
static int write_protected;
static unsigned char current_track;
static struct mutex_struct drive_mutex;
#define MAX_RESULTS 7
static int result_phase;
static int result_status;
static unsigned int result_count;
static unsigned char results[MAX_RESULTS];
enum {
INTERRUPT_WAITING,
INTERRUPT_RECEIVED,
INTERRUPT_TIMEOUT
};
static int interrupt_status;
static struct condition_struct interrupt_condition;
static struct alarm_struct interrupt_alarm;
static unsigned char fd_version;
static inline const char *get_version_str(unsigned char version)
{
switch (version) {
case FD_82077AA:
return "82077AA";
case FD_8272A:
default:
return "8272A";
}
}
static int send_byte(unsigned char byte)
{
int counter;
for (counter = 0; counter < 10000; counter++)
if ((inbp(FD_MSR) & 0xc0) == 0x80) {
outbp(byte, FD_FIFO);
return 1;
}
return 0;
}
static int get_byte(unsigned char *byte)
{
int counter;
for (counter = 0; counter < 10000; counter++)
if ((inbp(FD_MSR) & 0xc0) == 0xc0) {
*byte = inbp(FD_FIFO);
return 1;
}
return 0;
}
static int read_results()
{
int counter;
int status;
result_count = 0;
for (counter = 0; counter < 10000; counter++) {
status = inbp(FD_MSR) & 0xd0;
if (status == 0x80)
return 1;
if (status == 0xd0) {
if (result_count == MAX_RESULTS)
return 0;
if (!get_byte(&results[result_count++]))
return 0;
}
}
return 0;
}
void do_floppy_interrupt()
{
if (result_phase) {
result_status = read_results();
result_phase = 0;
}
if (interrupt_status != INTERRUPT_WAITING)
return;
interrupt_status = INTERRUPT_RECEIVED;
alarm_unset(&interrupt_alarm);
condition_signal(&interrupt_condition);
}
static void interrupt_alarm_handler()
{
if (interrupt_status != INTERRUPT_WAITING)
return;
interrupt_status = INTERRUPT_TIMEOUT;
condition_signal(&interrupt_condition);
}
static void wait_for_interrupt()
{
interrupt_status = INTERRUPT_WAITING;
alarm_set(&interrupt_alarm, 3000);
condition_wait(&interrupt_condition);
sti();
}
static int do_rw_data(unsigned char command, const struct rw_request_struct *request)
{
cli();
if (!send_byte(command)
|| !send_byte(request->head << 2)
|| !send_byte(request->track)
|| !send_byte(request->head)
|| !send_byte(request->sector)
|| !send_byte(2)
|| !send_byte(SECTORS)
|| !send_byte(GAP)
|| !send_byte(0xff))
goto error_sti;
result_phase = 1;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
if (!result_status)
return 0;
if (result_count != 7)
return 0;
write_protected |= results[1] & 0x2;
return !(results[0] & 0xf8) && !(results[1] & 0xb7) && !(results[2] & 0x73);
error_sti:
sti();
return 0;
}
static int do_version()
{
if (!send_byte(FD_VERSION))
return 0;
if (!read_results())
return 0;
if (result_count != 1)
return 0;
fd_version = results[0];
return 1;
}
static int do_recalibrate()
{
cli();
if (!send_byte(FD_RECALIBRATE)
|| !send_byte(0x00))
goto error_sti;
result_phase = 0;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
return 1;
error_sti:
sti();
return 0;
}
static int do_sense_interrupt_status(int polling)
{
if (!send_byte(FD_SENSE_INTERRUPT_STATUS))
return 0;
if (!read_results())
return 0;
if (result_count != 2)
return 0;
current_track = results[1];
return (results[0] & 0xf8) == (polling ? 0xc0 : 0x20);
}
static int do_specify()
{
if (!send_byte(FD_SPECIFY)
|| !send_byte(SPEC)
|| !send_byte(0x06))
return 0;
return 1;
}
static int do_sense_drive_status(unsigned char head)
{
if (!send_byte(FD_SENSE_DRIVE_STATUS)
|| !send_byte(head << 2))
return 0;
if (!read_results())
return 0;
if (result_count != 1)
return 0;
write_protected = results[0] & 0x40;
return 1;
}
static int do_seek(unsigned char head, unsigned char track)
{
cli();
if (!send_byte(FD_SEEK)
|| !send_byte(head << 2)
|| !send_byte(track))
goto error_sti;
result_phase = 0;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
return 1;
error_sti:
sti();
return 0;
}
static int do_configure()
{
if (!send_byte(FD_CONFIGURE)
|| !send_byte(0)
|| !send_byte(0x1a)
|| !send_byte(0))
return 0;
return 1;
}
static void enable_drive()
{
floppy_motor_enable();
if (!disk_change)
disk_change = inb(FD_DIR) & 0x80;
}
static int initialize()
{
int i;
cli();
outbp(inbp(FD_DOR) & ~0x04, FD_DOR);
for (i = 0; i < 100; i++)
nop();
outbp(inbp(FD_DOR) | 0x04, FD_DOR);
outb(RATE, FD_CCR);
result_phase = 0;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
for (i = 0; i < 4; i++)
if (!do_sense_interrupt_status(1))
return 0;
if (fd_version == FD_82077AA)
if (!do_configure())
return 0;
if (!do_specify())
return 0;
return 1;
}
static int recalibrate()
{
enable_drive();
if (!do_recalibrate())
return 0;
if (!do_sense_interrupt_status(0))
return 0;
if (current_track)
return 0;
return 1;
}
static int seek(unsigned char head, unsigned char track)
{
enable_drive();
if (!do_seek(head, track))
return 0;
if (!do_sense_interrupt_status(0))
return 0;
if (current_track != track)
return 0;
return 1;
}
static int rw(unsigned char command, const struct rw_request_struct *request)
{
int seek_try, rw_try;
enable_drive();
outb(RATE, FD_CCR);
for (seek_try = 0; seek_try < 3; seek_try++) {
if (request->track != current_track)
seek(request->head, request->track);
floppy_motor_wait_until_started();
for (rw_try = 0; rw_try < 3; rw_try++) {
dma_setup(DMA_DISKETTE_ADAPTER, command == FD_READ_DATA ? DMA_WRITE : DMA_READ, (unsigned long)floppy_dma_buffer_start, request->count);
if (!do_rw_data(command, request)) {
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
else if (write_protected)
return 0;
else
continue;
}
return 1;
}
recalibrate();
}
return 0;
}
static void unlock_drive(int status)
{
if (!status)
need_reset = 1;
floppy_motor_unlock();
mutex_unlock(&drive_mutex);
}
static int lock_drive()
{
if (!mutex_lock(&drive_mutex))
return 0;
floppy_motor_lock();
if (need_reset) {
if (!initialize())
goto error;
if (!recalibrate())
goto error;
need_reset = 0;
}
return 1;
error:
unlock_drive(0);
return 0;
}
int floppy_init()
{
need_reset = 1;
disk_change = 0;
mutex_init(&drive_mutex);
floppy_motor_init();
result_phase = 0;
condition_init(&interrupt_condition);
alarm_init(&interrupt_alarm, interrupt_alarm_handler);
outbp(0x0c, FD_DOR);
if (!do_version())
return 0;
return 1;
}
void floppy_print_info()
{
printk("Floppy disk controller: Intel %s\n", get_version_str(fd_version));
}
static unsigned long floppy_get_block_count()
{
return BLOCK_COUNT;
}
static int floppy_is_writable()
{
int retval;
if (!lock_drive())
return 0;
if (!do_sense_drive_status(0))
goto error;
retval = !write_protected;
unlock_drive(1);
return retval;
error:
unlock_drive(0);
return 0;
}
static int floppy_has_changed()
{
int retval;
if (!lock_drive())
return 0;
enable_drive();
if (disk_change) {
if (!recalibrate())
goto error;
disk_change = 0;
retval = 1;
}
else
retval = 0;
unlock_drive(1);
return retval;
error:
unlock_drive(0);
return 1;
}
static int floppy_rw(unsigned char command, unsigned long block, void *buffer)
{
unsigned long sector;
unsigned int count;
struct rw_request_struct request;
if (block >= BLOCK_COUNT)
return 0;
if (!lock_drive())
return 0;
sector = block * BLOCK_SIZE / SECTOR_SIZE;
count = BLOCK_SIZE;
do {
request.head = sector / SECTORS % HEADS;
request.track = sector / (SECTORS * HEADS);
request.sector = sector % SECTORS + 1;
request.count = min(count, (unsigned int)((HEADS * SECTORS - (request.head * SECTORS + request.sector - 1)) * SECTOR_SIZE));
if (request.count > (unsigned int)(floppy_dma_buffer_end - floppy_dma_buffer_start))
request.count = floppy_dma_buffer_end - floppy_dma_buffer_start;
if (command == FD_WRITE_DATA)
memcpy(floppy_dma_buffer_start, buffer, request.count);
if (!rw(command, &request))
goto error;
if (command == FD_READ_DATA)
memcpy(buffer, floppy_dma_buffer_start, request.count);
buffer += request.count;
sector += request.count / SECTOR_SIZE;
count -= request.count;
}
while (count);
unlock_drive(1);
return 1;
error:
unlock_drive(0);
return 0;
}
static int floppy_read(unsigned long block, void *buffer)
{
return floppy_rw(FD_READ_DATA, block, buffer);
}
static int floppy_write(unsigned long block, void *buffer)
{
return floppy_rw(FD_WRITE_DATA, block, buffer);
}
const struct block_driver_struct floppy_driver = {
.get_block_count = floppy_get_block_count,
.is_writable = floppy_is_writable,
.has_changed = floppy_has_changed,
.read = floppy_read,
.write = floppy_write
};