/* * Driver for SWIM (Sander Woz Integrated Machine) floppy controller * * Copyright (C) 2004,2008 Laurent Vivier <Laurent@lvivier.info> * * based on Alastair Bridgewater SWIM analysis, 2001 * based on SWIM3 driver (c) Paul Mackerras, 1996 * based on netBSD IWM driver (c) 1997, 1998 Hauke Fath. * * 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 * 2 of the License, or (at your option) any later version. * * 2004-08-21 (lv) - Initial implementation * 2008-10-30 (lv) - Port to 2.6 */ #include <linux/module.h> #include <linux/fd.h> #include <linux/blkdev.h> #include <linux/hdreg.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <asm/macintosh.h> #include <asm/mac_via.h> #define CARDNAME "swim" struct sector_header { unsigned char side; unsigned char track; unsigned char sector; unsigned char size; unsigned char crc0; unsigned char crc1; } __attribute__((packed)); #define DRIVER_VERSION "Version 0.2 (2008-10-30)" #define REG(x) unsigned char x, x ## _pad[0x200 - 1]; struct swim { REG(write_data) REG(write_mark) REG(write_CRC) REG(write_parameter) REG(write_phase) REG(write_setup) REG(write_mode0) REG(write_mode1) REG(read_data) REG(read_mark) REG(read_error) REG(read_parameter) REG(read_phase) REG(read_setup) REG(read_status) REG(read_handshake) } __attribute__((packed)); #define swim_write(base, reg, v) out_8(&(base)->write_##reg, (v)) #define swim_read(base, reg) in_8(&(base)->read_##reg) /* IWM registers */ struct iwm { REG(ph0L) REG(ph0H) REG(ph1L) REG(ph1H) REG(ph2L) REG(ph2H) REG(ph3L) REG(ph3H) REG(mtrOff) REG(mtrOn) REG(intDrive) REG(extDrive) REG(q6L) REG(q6H) REG(q7L) REG(q7H) } __attribute__((packed)); #define iwm_write(base, reg, v) out_8(&(base)->reg, (v)) #define iwm_read(base, reg) in_8(&(base)->reg) /* bits in phase register */ #define SEEK_POSITIVE 0x070 #define SEEK_NEGATIVE 0x074 #define STEP 0x071 #define MOTOR_ON 0x072 #define MOTOR_OFF 0x076 #define INDEX 0x073 #define EJECT 0x077 #define SETMFM 0x171 #define SETGCR 0x175 #define RELAX 0x033 #define LSTRB 0x008 #define CA_MASK 0x077 /* Select values for swim_select and swim_readbit */ #define READ_DATA_0 0x074 #define TWOMEG_DRIVE 0x075 #define SINGLE_SIDED 0x076 #define DRIVE_PRESENT 0x077 #define DISK_IN 0x170 #define WRITE_PROT 0x171 #define TRACK_ZERO 0x172 #define TACHO 0x173 #define READ_DATA_1 0x174 #define MFM_MODE 0x175 #define SEEK_COMPLETE 0x176 #define ONEMEG_MEDIA 0x177 /* Bits in handshake register */ #define MARK_BYTE 0x01 #define CRC_ZERO 0x02 #define RDDATA 0x04 #define SENSE 0x08 #define MOTEN 0x10 #define ERROR 0x20 #define DAT2BYTE 0x40 #define DAT1BYTE 0x80 /* bits in setup register */ #define S_INV_WDATA 0x01 #define S_3_5_SELECT 0x02 #define S_GCR 0x04 #define S_FCLK_DIV2 0x08 #define S_ERROR_CORR 0x10 #define S_IBM_DRIVE 0x20 #define S_GCR_WRITE 0x40 #define S_TIMEOUT 0x80 /* bits in mode register */ #define CLFIFO 0x01 #define ENBL1 0x02 #define ENBL2 0x04 #define ACTION 0x08 #define WRITE_MODE 0x10 #define HEDSEL 0x20 #define MOTON 0x80 /*----------------------------------------------------------------------------*/ enum drive_location { INTERNAL_DRIVE = 0x02, EXTERNAL_DRIVE = 0x04, }; enum media_type { DD_MEDIA, HD_MEDIA, }; struct floppy_state { /* physical properties */ enum drive_location location; /* internal or external drive */ int head_number; /* single- or double-sided drive */ /* media */ int disk_in; int ejected; enum media_type type; int write_protected; int total_secs; int secpercyl; int secpertrack; /* in-use information */ int track; int ref_count; struct gendisk *disk; /* parent controller */ struct swim_priv *swd; }; enum motor_action { OFF, ON, }; enum head { LOWER_HEAD = 0, UPPER_HEAD = 1, }; #define FD_MAX_UNIT 2 struct swim_priv { struct swim __iomem *base; spinlock_t lock; struct request_queue *queue; int floppy_count; struct floppy_state unit[FD_MAX_UNIT]; }; extern int swim_read_sector_header(struct swim __iomem *base, struct sector_header *header); extern int swim_read_sector_data(struct swim __iomem *base, unsigned char *data); static inline void set_swim_mode(struct swim __iomem *base, int enable) { struct iwm __iomem *iwm_base; unsigned long flags; if (!enable) { swim_write(base, mode0, 0xf8); return; } iwm_base = (struct iwm __iomem *)base; local_irq_save(flags); iwm_read(iwm_base, q7L); iwm_read(iwm_base, mtrOff); iwm_read(iwm_base, q6H); iwm_write(iwm_base, q7H, 0x57); iwm_write(iwm_base, q7H, 0x17); iwm_write(iwm_base, q7H, 0x57); iwm_write(iwm_base, q7H, 0x57); local_irq_restore(flags); } static inline int get_swim_mode(struct swim __iomem *base) { unsigned long flags; local_irq_save(flags); swim_write(base, phase, 0xf5); if (swim_read(base, phase) != 0xf5) goto is_iwm; swim_write(base, phase, 0xf6); if (swim_read(base, phase) != 0xf6) goto is_iwm; swim_write(base, phase, 0xf7); if (swim_read(base, phase) != 0xf7) goto is_iwm; local_irq_restore(flags); return 1; is_iwm: local_irq_restore(flags); return 0; } static inline void swim_select(struct swim __iomem *base, int sel) { swim_write(base, phase, RELAX); via1_set_head(sel & 0x100); swim_write(base, phase, sel & CA_MASK); } static inline void swim_action(struct swim __iomem *base, int action) { unsigned long flags; local_irq_save(flags); swim_select(base, action); udelay(1); swim_write(base, phase, (LSTRB<<4) | LSTRB); udelay(1); swim_write(base, phase, (LSTRB<<4) | ((~LSTRB) & 0x0F)); udelay(1); local_irq_restore(flags); } static inline int swim_readbit(struct swim __iomem *base, int bit) { int stat; swim_select(base, bit); udelay(10); stat = swim_read(base, handshake); return (stat & SENSE) == 0; } static inline void swim_drive(struct swim __iomem *base, enum drive_location location) { if (location == INTERNAL_DRIVE) { swim_write(base, mode0, EXTERNAL_DRIVE); /* clear drive 1 bit */ swim_write(base, mode1, INTERNAL_DRIVE); /* set drive 0 bit */ } else if (location == EXTERNAL_DRIVE) { swim_write(base, mode0, INTERNAL_DRIVE); /* clear drive 0 bit */ swim_write(base, mode1, EXTERNAL_DRIVE); /* set drive 1 bit */ } } static inline void swim_motor(struct swim __iomem *base, enum motor_action action) { if (action == ON) { int i; swim_action(base, MOTOR_ON); for (i = 0; i < 2*HZ; i++) { swim_select(base, RELAX); if (swim_readbit(base, MOTOR_ON)) break; current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); } } else if (action == OFF) { swim_action(base, MOTOR_OFF); swim_select(base, RELAX); } } static inline void swim_eject(struct swim __iomem *base) { int i; swim_action(base, EJECT); for (i = 0; i < 2*HZ; i++) { swim_select(base, RELAX); if (!swim_readbit(base, DISK_IN)) break; current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); } swim_select(base, RELAX); } static inline void swim_head(struct swim __iomem *base, enum head head) { /* wait drive is ready */ if (head == UPPER_HEAD) swim_select(base, READ_DATA_1); else if (head == LOWER_HEAD) swim_select(base, READ_DATA_0); } static inline int swim_step(struct swim __iomem *base) { int wait; swim_action(base, STEP); for (wait = 0; wait < HZ; wait++) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); swim_select(base, RELAX); if (!swim_readbit(base, STEP)) return 0; } return -1; } static inline int swim_track00(struct swim __iomem *base) { int try; swim_action(base, SEEK_NEGATIVE); for (try = 0; try < 100; try++) { swim_select(base, RELAX); if (swim_readbit(base, TRACK_ZERO)) break; if (swim_step(base)) return -1; } if (swim_readbit(base, TRACK_ZERO)) return 0; return -1; } static inline int swim_seek(struct swim __iomem *base, int step) { if (step == 0) return 0; if (step < 0) { swim_action(base, SEEK_NEGATIVE); step = -step; } else swim_action(base, SEEK_POSITIVE); for ( ; step > 0; step--) { if (swim_step(base)) return -1; } return 0; } static inline int swim_track(struct floppy_state *fs, int track) { struct swim __iomem *base = fs->swd->base; int ret; ret = swim_seek(base, track - fs->track); if (ret == 0) fs->track = track; else { swim_track00(base); fs->track = 0; } return ret; } static int floppy_eject(struct floppy_state *fs) { struct swim __iomem *base = fs->swd->base; swim_drive(base, fs->location); swim_motor(base, OFF); swim_eject(base); fs->disk_in = 0; fs->ejected = 1; return 0; } static inline int swim_read_sector(struct floppy_state *fs, int side, int track, int sector, unsigned char *buffer) { struct swim __iomem *base = fs->swd->base; unsigned long flags; struct sector_header header; int ret = -1; short i; swim_track(fs, track); swim_write(base, mode1, MOTON); swim_head(base, side); swim_write(base, mode0, side); local_irq_save(flags); for (i = 0; i < 36; i++) { ret = swim_read_sector_header(base, &header); if (!ret && (header.sector == sector)) { /* found */ ret = swim_read_sector_data(base, buffer); break; } } local_irq_restore(flags); swim_write(base, mode0, MOTON); if ((header.side != side) || (header.track != track) || (header.sector != sector)) return 0; return ret; } static int floppy_read_sectors(struct floppy_state *fs, int req_sector, int sectors_nb, unsigned char *buffer) { struct swim __iomem *base = fs->swd->base; int ret; int side, track, sector; int i, try; swim_drive(base, fs->location); for (i = req_sector; i < req_sector + sectors_nb; i++) { int x; track = i / fs->secpercyl; x = i % fs->secpercyl; side = x / fs->secpertrack; sector = x % fs->secpertrack + 1; try = 5; do { ret = swim_read_sector(fs, side, track, sector, buffer); if (try-- == 0) return -1; } while (ret != 512); buffer += ret; } return 0; } static void redo_fd_request(struct request_queue *q) { struct request *req; struct floppy_state *fs; while ((req = elv_next_request(q))) { fs = req->rq_disk->private_data; if (req->sector < 0 || req->sector >= fs->total_secs) { end_request(req, 0); continue; } if (req->current_nr_sectors == 0) { end_request(req, 1); continue; } if (!fs->disk_in) { end_request(req, 0); continue; } if (rq_data_dir(req) == WRITE) { if (fs->write_protected) { end_request(req, 0); continue; } } switch (rq_data_dir(req)) { case WRITE: /* NOT IMPLEMENTED */ end_request(req, 0); break; case READ: if (floppy_read_sectors(fs, req->sector, req->current_nr_sectors, req->buffer)) { end_request(req, 0); continue; } req->nr_sectors -= req->current_nr_sectors; req->sector += req->current_nr_sectors; req->buffer += req->current_nr_sectors * 512; end_request(req, 1); break; } } } static void do_fd_request(struct request_queue *q) { redo_fd_request(q); } static struct floppy_struct floppy_type[4] = { { 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x00, NULL }, /* no testing */ { 720, 9, 1, 80, 0, 0x2A, 0x02, 0xDF, 0x50, NULL }, /* 360KB SS 3.5"*/ { 1440, 9, 2, 80, 0, 0x2A, 0x02, 0xDF, 0x50, NULL }, /* 720KB 3.5" */ { 2880, 18, 2, 80, 0, 0x1B, 0x00, 0xCF, 0x6C, NULL }, /* 1.44MB 3.5" */ }; static int get_floppy_geometry(struct floppy_state *fs, int type, struct floppy_struct **g) { if (type >= ARRAY_SIZE(floppy_type)) return -EINVAL; if (type) *g = &floppy_type[type]; else if (fs->type == HD_MEDIA) /* High-Density media */ *g = &floppy_type[3]; else if (fs->head_number == 2) /* double-sided */ *g = &floppy_type[2]; else *g = &floppy_type[1]; return 0; } static void setup_medium(struct floppy_state *fs) { struct swim __iomem *base = fs->swd->base; if (swim_readbit(base, DISK_IN)) { struct floppy_struct *g; fs->disk_in = 1; fs->write_protected = swim_readbit(base, WRITE_PROT); fs->type = swim_readbit(base, ONEMEG_MEDIA); if (swim_track00(base)) printk(KERN_ERR "SWIM: cannot move floppy head to track 0\n"); swim_track00(base); get_floppy_geometry(fs, 0, &g); fs->total_secs = g->size; fs->secpercyl = g->head * g->sect; fs->secpertrack = g->sect; fs->track = 0; } else { fs->disk_in = 0; } } static int floppy_open(struct block_device *bdev, fmode_t mode) { struct floppy_state *fs = bdev->bd_disk->private_data; struct swim __iomem *base = fs->swd->base; int err; if (fs->ref_count == -1 || (fs->ref_count && mode & FMODE_EXCL)) return -EBUSY; if (mode & FMODE_EXCL) fs->ref_count = -1; else fs->ref_count++; swim_write(base, setup, S_IBM_DRIVE | S_FCLK_DIV2); udelay(10); swim_drive(base, INTERNAL_DRIVE); swim_motor(base, ON); swim_action(base, SETMFM); if (fs->ejected) setup_medium(fs); if (!fs->disk_in) { err = -ENXIO; goto out; } if (mode & FMODE_NDELAY) return 0; if (mode & (FMODE_READ|FMODE_WRITE)) { check_disk_change(bdev); if ((mode & FMODE_WRITE) && fs->write_protected) { err = -EROFS; goto out; } } return 0; out: if (fs->ref_count < 0) fs->ref_count = 0; else if (fs->ref_count > 0) --fs->ref_count; if (fs->ref_count == 0) swim_motor(base, OFF); return err; } static int floppy_release(struct gendisk *disk, fmode_t mode) { struct floppy_state *fs = disk->private_data; struct swim __iomem *base = fs->swd->base; if (fs->ref_count < 0) fs->ref_count = 0; else if (fs->ref_count > 0) --fs->ref_count; if (fs->ref_count == 0) swim_motor(base, OFF); return 0; } static int floppy_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long param) { struct floppy_state *fs = bdev->bd_disk->private_data; int err; if ((cmd & 0x80) && !capable(CAP_SYS_ADMIN)) return -EPERM; switch (cmd) { case FDEJECT: if (fs->ref_count != 1) return -EBUSY; err = floppy_eject(fs); return err; case FDGETPRM: if (copy_to_user((void __user *) param, (void *) &floppy_type, sizeof(struct floppy_struct))) return -EFAULT; break; default: printk(KERN_DEBUG "SWIM floppy_ioctl: unknown cmd %d\n", cmd); return -ENOSYS; } return 0; } static int floppy_getgeo(struct block_device *bdev, struct hd_geometry *geo) { struct floppy_state *fs = bdev->bd_disk->private_data; struct floppy_struct *g; int ret; ret = get_floppy_geometry(fs, 0, &g); if (ret) return ret; geo->heads = g->head; geo->sectors = g->sect; geo->cylinders = g->track; return 0; } static int floppy_check_change(struct gendisk *disk) { struct floppy_state *fs = disk->private_data; return fs->ejected; } static int floppy_revalidate(struct gendisk *disk) { struct floppy_state *fs = disk->private_data; struct swim __iomem *base = fs->swd->base; swim_drive(base, fs->location); if (fs->ejected) setup_medium(fs); if (!fs->disk_in) swim_motor(base, OFF); else fs->ejected = 0; return !fs->disk_in; } static struct block_device_operations floppy_fops = { .owner = THIS_MODULE, .open = floppy_open, .release = floppy_release, .locked_ioctl = floppy_ioctl, .getgeo = floppy_getgeo, .media_changed = floppy_check_change, .revalidate_disk = floppy_revalidate, }; static struct kobject *floppy_find(dev_t dev, int *part, void *data) { struct swim_priv *swd = data; int drive = (*part & 3); if (drive > swd->floppy_count) return NULL; *part = 0; return get_disk(swd->unit[drive].disk); } static int __devinit swim_add_floppy(struct swim_priv *swd, enum drive_location location) { struct floppy_state *fs = &swd->unit[swd->floppy_count]; struct swim __iomem *base = swd->base; fs->location = location; swim_drive(base, location); swim_motor(base, OFF); if (swim_readbit(base, SINGLE_SIDED)) fs->head_number = 1; else fs->head_number = 2; fs->ref_count = 0; fs->ejected = 1; swd->floppy_count++; return 0; } static int __devinit swim_floppy_init(struct swim_priv *swd) { int err; int drive; struct swim __iomem *base = swd->base; /* scan floppy drives */ swim_drive(base, INTERNAL_DRIVE); if (swim_readbit(base, DRIVE_PRESENT)) swim_add_floppy(swd, INTERNAL_DRIVE); swim_drive(base, EXTERNAL_DRIVE); if (swim_readbit(base, DRIVE_PRESENT)) swim_add_floppy(swd, EXTERNAL_DRIVE); /* register floppy drives */ err = register_blkdev(FLOPPY_MAJOR, "fd"); if (err) { printk(KERN_ERR "Unable to get major %d for SWIM floppy\n", FLOPPY_MAJOR); return -EBUSY; } for (drive = 0; drive < swd->floppy_count; drive++) { swd->unit[drive].disk = alloc_disk(1); if (swd->unit[drive].disk == NULL) { err = -ENOMEM; goto exit_put_disks; } swd->unit[drive].swd = swd; } swd->queue = blk_init_queue(do_fd_request, &swd->lock); if (!swd->queue) { err = -ENOMEM; goto exit_put_disks; } for (drive = 0; drive < swd->floppy_count; drive++) { swd->unit[drive].disk->flags = GENHD_FL_REMOVABLE; swd->unit[drive].disk->major = FLOPPY_MAJOR; swd->unit[drive].disk->first_minor = drive; sprintf(swd->unit[drive].disk->disk_name, "fd%d", drive); swd->unit[drive].disk->fops = &floppy_fops; swd->unit[drive].disk->private_data = &swd->unit[drive]; swd->unit[drive].disk->queue = swd->queue; set_capacity(swd->unit[drive].disk, 2880); add_disk(swd->unit[drive].disk); } blk_register_region(MKDEV(FLOPPY_MAJOR, 0), 256, THIS_MODULE, floppy_find, NULL, swd); return 0; exit_put_disks: unregister_blkdev(FLOPPY_MAJOR, "fd"); while (drive--) put_disk(swd->unit[drive].disk); return err; } static int __devinit swim_probe(struct platform_device *dev) { struct resource *res; struct swim __iomem *swim_base; struct swim_priv *swd; int ret; res = platform_get_resource_byname(dev, IORESOURCE_MEM, "swim-regs"); if (!res) { ret = -ENODEV; goto out; } if (!request_mem_region(res->start, resource_size(res), CARDNAME)) { ret = -EBUSY; goto out; } swim_base = ioremap(res->start, resource_size(res)); if (!swim_base) { return -ENOMEM; goto out_release_io; } /* probe device */ set_swim_mode(swim_base, 1); if (!get_swim_mode(swim_base)) { printk(KERN_INFO "SWIM device not found !\n"); ret = -ENODEV; goto out_iounmap; } /* set platform driver data */ swd = kzalloc(sizeof(struct swim_priv), GFP_KERNEL); if (!swd) { ret = -ENOMEM; goto out_iounmap; } platform_set_drvdata(dev, swd); swd->base = swim_base; ret = swim_floppy_init(swd); if (ret) goto out_kfree; return 0; out_kfree: platform_set_drvdata(dev, NULL); kfree(swd); out_iounmap: iounmap(swim_base); out_release_io: release_mem_region(res->start, resource_size(res)); out: return ret; } static int __devexit swim_remove(struct platform_device *dev) { struct swim_priv *swd = platform_get_drvdata(dev); int drive; struct resource *res; blk_unregister_region(MKDEV(FLOPPY_MAJOR, 0), 256); for (drive = 0; drive < swd->floppy_count; drive++) { del_gendisk(swd->unit[drive].disk); put_disk(swd->unit[drive].disk); } unregister_blkdev(FLOPPY_MAJOR, "fd"); blk_cleanup_queue(swd->queue); /* eject floppies */ for (drive = 0; drive < swd->floppy_count; drive++) floppy_eject(&swd->unit[drive]); iounmap(swd->base); res = platform_get_resource_byname(dev, IORESOURCE_MEM, "swim-regs"); if (res) release_mem_region(res->start, resource_size(res)); platform_set_drvdata(dev, NULL); kfree(swd); return 0; } static struct platform_driver swim_driver = { .probe = swim_probe, .remove = __devexit_p(swim_remove), .driver = { .name = CARDNAME, .owner = THIS_MODULE, }, }; static int __init swim_init(void) { printk(KERN_INFO "SWIM floppy driver %s\n", DRIVER_VERSION); return platform_driver_register(&swim_driver); } module_init(swim_init); static void __exit swim_exit(void) { platform_driver_unregister(&swim_driver); } module_exit(swim_exit); MODULE_DESCRIPTION("Driver for SWIM floppy controller"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Laurent Vivier <laurent@lvivier.info>"); MODULE_ALIAS_BLOCKDEV_MAJOR(FLOPPY_MAJOR);