/* Pilote pour le controleur de disquette Intel 82077AA */
/* Copyright (C) 2008 Emmanuel Varoquaux
This file is part of XOS.
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 . */
/* Le pilote supporte uniquement des disquettes de type 1.44M.
* Seul le moteur 0 est pris en charge. */
#include "floppy_motor.h"
#include "floppy_reg.h"
#include "dma.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* commandes du controleur */
#define FD_READ_DATA 0xe6 /* lecture avec MT, MFM, SK */
#define FD_WRITE_DATA 0xc5 /* ecriture avec MT, MFM */
#define FD_VERSION 0x10 /* version */
#define FD_RECALIBRATE 0x07 /* recalibrage */
#define FD_SENSE_INTERRUPT_STATUS 0x08 /* registre d'etat */
#define FD_SPECIFY 0x03 /* specifications */
#define FD_SENSE_DRIVE_STATUS 0x04 /* etat du lecteur de disquettes */
#define FD_SEEK 0x0f /* positionnement */
#define FD_CONFIGURE 0x13 /* configuration */
/* geometrie et caracteristiques de la disquette 1.44M */
#define SIZE 2880 /* capacite totale */
#define SECTORS 18 /* nombre de secteurs par piste */
#define HEADS 2 /* nombre de tetes de lecture */
#define SECTOR_SIZE 512 /* taille d'un secteur */
#define GAP 0x1b
#define RATE 0x00
#define SPEC 0xcf
/* type du controleur */
#define FD_8272A 0x80
#define FD_82077AA 0x90
#define BLOCK_COUNT (SIZE * SECTOR_SIZE / BLOCK_SIZE)
/* requete pour les lectures et ecritures */
struct rw_request_struct {
unsigned char head;
unsigned char track;
unsigned char sector;
unsigned int count;
};
/* indicateurs */
static int need_reset;
static int disk_change;
static int write_protected;
/* lecteur */
static unsigned char current_track;
static struct mutex_struct drive_mutex;
/* resultats */
#define MAX_RESULTS 7
static int result_phase;
static int result_status;
static unsigned int result_count;
static unsigned char results[MAX_RESULTS];
/* etats d'attente d'interruption */
enum {
INTERRUPT_WAITING,
INTERRUPT_RECEIVED,
INTERRUPT_TIMEOUT
};
/* interruption */
static int interrupt_status;
static struct condition_struct interrupt_condition;
static struct alarm_struct interrupt_alarm;
/* version du controleur */
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";
}
}
/* Utilitaires pour les commandes */
/* DS p39 */
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;
}
/* DS p40 */
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;
}
/* Interruption */
void do_floppy_interrupt()
{
/* Il est necessaire de lire les resultats pour qu'une nouvelle commande
puisse etre executee. */
if (result_phase) {
result_status = read_results();
result_phase = 0;
}
if (interrupt_status != INTERRUPT_WAITING)
return; /* trop tard */
interrupt_status = INTERRUPT_RECEIVED;
alarm_unset(&interrupt_alarm);
condition_signal(&interrupt_condition);
}
static void interrupt_alarm_handler()
{
if (interrupt_status != INTERRUPT_WAITING)
return; /* l'interruption a deja ete recue */
interrupt_status = INTERRUPT_TIMEOUT;
condition_signal(&interrupt_condition);
}
/* Les interruptions doivent etre desactivees, et sont reactivees par cette
* fonction. */
static void wait_for_interrupt()
{
interrupt_status = INTERRUPT_WAITING;
alarm_set(&interrupt_alarm, 3000); /* timeout de 3s */
condition_wait(&interrupt_condition);
sti();
}
/* Commandes */
/* Les commandes qui ont une phase d'execution attendent sa completion sur
* interrupt_condition. Il est necessaire de desactiver les interruptions entre
* l'envoi de la commande au controleur et l'attente sur la condition, afin que
* l'interruption ne se produise pas dans cet intervalle. */
/* DS p18,24-26,43 */
static int do_rw_data(unsigned char command, const struct rw_request_struct *request)
{
cli();
if (!send_byte(command) /* commande */
|| !send_byte(request->head << 2) /* head, drive */
|| !send_byte(request->track) /* cylinder address */
|| !send_byte(request->head) /* head address */
|| !send_byte(request->sector) /* sector address */
|| !send_byte(2) /* sector size code (512 bytes) */
|| !send_byte(SECTORS) /* end of track */
|| !send_byte(GAP) /* gap length */
|| !send_byte(0xff)) /* special sector size */
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; /* Not Writable */
return !(results[0] & 0xf8) && !(results[1] & 0xb7) && !(results[2] & 0x73);
error_sti:
sti();
return 0;
}
/* DS p19,32 */
static int do_version()
{
if (!send_byte(FD_VERSION)) /* VERSION */
return 0;
if (!read_results())
return 0;
if (result_count != 1)
return 0;
fd_version = results[0];
return 1;
}
/* DS p21,30,42 */
static int do_recalibrate()
{
cli();
if (!send_byte(FD_RECALIBRATE) /* RECALIBRATE */
|| !send_byte(0x00)) /* drive */
goto error_sti;
result_phase = 0;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
return 1;
error_sti:
sti();
return 0;
}
/* DS p31,31 */
static int do_sense_interrupt_status(int polling)
{
if (!send_byte(FD_SENSE_INTERRUPT_STATUS)) /* 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);
}
/* DS p21,31,42 */
static int do_specify()
{
if (!send_byte(FD_SPECIFY) /* SPECIFY */
|| !send_byte(SPEC) /* SRT=4ms, HUT=240ms */
|| !send_byte(0x06)) /* HLT=6ms, DMA */
return 0;
return 1;
}
/* DS p21,31 */
static int do_sense_drive_status(unsigned char head)
{
if (!send_byte(FD_SENSE_DRIVE_STATUS) /* SENSE DRIVE STATUS */
|| !send_byte(head << 2)) /* head, drive */
return 0;
if (!read_results())
return 0;
if (result_count != 1)
return 0;
write_protected = results[0] & 0x40; /* Write Protected */
return 1;
}
/* DS p21,30,42 */
static int do_seek(unsigned char head, unsigned char track)
{
cli();
if (!send_byte(FD_SEEK) /* SEEK */
|| !send_byte(head << 2) /* head, drive */
|| !send_byte(track)) /* new cylinder number */
goto error_sti;
result_phase = 0;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
return 1;
error_sti:
sti();
return 0;
}
/* DS p21,32 */
static int do_configure()
{
if (!send_byte(FD_CONFIGURE) /* CONFIGURE */
|| !send_byte(0)
|| !send_byte(0x1a) /* no implied seek, FIFO enabled, polling disabled, 10 byte threshold */
|| !send_byte(0)) /* precompensation from track 0 */
return 0;
return 1;
}
/* Utilitaires pout les operations */
static void enable_drive()
{
floppy_motor_enable();
if (!disk_change)
disk_change = inb(FD_DIR) & 0x80; /* DSKCHG */
}
/* Operations */
/* DS p40 */
static int initialize()
{
int i;
/* RESET */
cli();
outbp(inbp(FD_DOR) & ~0x04, FD_DOR);
for (i = 0; i < 100; i++)
nop();
outbp(inbp(FD_DOR) | 0x04, FD_DOR); /* reset */
outb(RATE, FD_CCR); /* taux de transfert de 500kb/s */
/* Un RESET active le mode polling, ce qui entraine une interruption (DS p32). */
result_phase = 0;
wait_for_interrupt();
if (interrupt_status == INTERRUPT_TIMEOUT)
return 0;
/* DS p31,41 */
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;
}
/* DS p42 */
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;
}
/* DS p42 */
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;
}
/* DS p43-44 */
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;
}
/* Utilitaires pour l'interface */
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;
}
/* Interface */
int floppy_init()
{
need_reset = 1;
disk_change = 0;
/* write_protected est indetermine pour le moment. */
/* current_track est indetermine jusqu'au premier initialize(). */
mutex_init(&drive_mutex);
floppy_motor_init();
result_phase = 0; /* pas de phase de resultats par defaut */
/* result_status, result_count et results sont indetermines pour le moment. */
/* interrupt_status est indetermine jusqu'a la premiere attente d'interruption. */
condition_init(&interrupt_condition);
alarm_init(&interrupt_alarm, interrupt_alarm_handler);
outbp(0x0c, FD_DOR); /* RESET, DMA */
/* Remarque : RESET devrait provoquer une interruption (qu'on peut ignorer)
lorsque les interruptions seront demasquees, mais il semble que la
commande VERSION l'annule. */
if (!do_version()) /* ok, pas de cli()..sti() */
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(); /* met a jour disk_change */
if (disk_change) {
/* effacement de l'indicateur DSKCHG */
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)); /* MT active, lecture multi-piste (par cylindre) */
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;
}
/* Lit 1 bloc. */
static int floppy_read(unsigned long block, void *buffer)
{
return floppy_rw(FD_READ_DATA, block, buffer);
}
/* Ecrit 1 bloc. */
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
};