/* ------------------------------------------------------------------------ * * i2c-floppy.c I2C bus over floppy controller * * ------------------------------------------------------------------------ * Copyright (C) 2008 Herbert Poetzl Somewhat based on i2c-parport-light.c driver Copyright (C) 2003-2007 Jean Delvare 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. 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * ------------------------------------------------------------------------ */ #include #include #include #include #include #include #include #include static struct platform_device *pdev; static unsigned char dor; static u16 base; module_param(base, ushort, 0); MODULE_PARM_DESC(base, "Base I/O address"); static u16 delay; module_param(delay, ushort, 0); MODULE_PARM_DESC(base, "Signal delay"); #define DEFAULT_BASE 0x3F0 /* for PC style hardware */ #define DRVNAME "i2c-floppy" #define FOFF_DOR 0x02 #define FOFF_MSR 0x04 #define FOFF_DATA 0x05 #define FOFF_CCR 0x07 #define FOFF_DIR 0x07 #define FDOR_MOA 0x10 #define FDOR_MOB 0x20 #define FDOR_DS0 0x01 #define FDOR_DS1 0x02 #define FDOR_RESET 0x04 #define FDIR_CHG 0x80 #define FMSR_RQM 0x80 #define FMSR_DIO 0x40 #define FMSR_CMD 0x10 #define FCMD_SENSE 0x04 #define FCMS_HDS 0x04 #define FST3_T0 0x10 #define FST3_WP 0x40 /* ----- Low-level floppy access ------------------------------------------ */ static inline void port_dor_out(unsigned char d) { outb(d, base + FOFF_DOR); } static inline unsigned char port_dir_in(void) { return inb(base + FOFF_DIR); } static inline unsigned char port_msr_in(void) { return inb(base + FOFF_MSR); } static inline void port_data_out(unsigned char d) { outb(d, base + FOFF_DATA); } static inline unsigned char port_data_in(void) { return inb(base + FOFF_DATA); } /* ----- Floppy controller sense ------------------------------------------ */ static inline unsigned char msr_wait( unsigned mask, unsigned val, int timeout) { unsigned char st; /* wait for controller */ while (((st = port_msr_in()) & mask) != val) if (!--timeout) break; if (timeout) return st; pr_err(DRVNAME ": timeout reached ... %02x/%02x\n", dor, st); return 0; } unsigned char cmd_sense(unsigned char head) { unsigned char st; /* wait for controller */ msr_wait(FMSR_RQM|FMSR_CMD, FMSR_RQM, 5000); port_data_out(FCMD_SENSE); st = msr_wait(FMSR_RQM, FMSR_RQM, 5000); if (!(st & FMSR_CMD)) return 0; port_data_out(head ? FCMS_HDS : 0); st = msr_wait(FMSR_RQM, FMSR_RQM, 5000); if (!(st & FMSR_CMD)) return 0; return port_data_in(); } /* ----- Output and input lines ------------------------------------------- */ #define LINE_OFF 0x00 /* output lines */ #define LINE_MOA FDOR_MOA #define LINE_MOB FDOR_MOB #define LINE_DS0 FDOR_DS0 #define LINE_DS1 FDOR_DS1 #define LINE_MASK_DOR (LINE_MOA | LINE_MOB | LINE_DS0 | LINE_DS1) #define LINE_HDS 0x08 struct i2c_floppy_lines { unsigned ena:8; unsigned sda:8; unsigned scl:8; }; static struct i2c_floppy_lines i2c_floppy_lines_out[] = { { LINE_OFF, LINE_OFF, LINE_OFF }, /* 0 */ { LINE_OFF, LINE_MOA, LINE_MOB }, /* 1 */ { LINE_HDS, LINE_MOA, LINE_MOB }, /* 2 */ { LINE_OFF, LINE_MOA, LINE_DS1 }, /* 3 */ { LINE_MOB, LINE_MOA, LINE_DS1 }, /* 4 */ { LINE_HDS, LINE_MOA, LINE_DS1 }, /* 5 */ { LINE_OFF, LINE_MOA, LINE_HDS }, /* 6 */ { LINE_MOB, LINE_MOA, LINE_HDS }, /* 7 */ { LINE_DS1, LINE_MOA, LINE_HDS }, /* 8 */ { LINE_OFF, LINE_MOB, LINE_DS0 }, /* 9 */ { LINE_MOA, LINE_MOB, LINE_DS0 }, /* 10 */ { LINE_HDS, LINE_MOB, LINE_DS0 }, /* 11 */ { LINE_OFF, LINE_MOB, LINE_HDS }, /* 12 */ { LINE_MOA, LINE_MOB, LINE_HDS }, /* 13 */ { LINE_DS0, LINE_MOB, LINE_HDS }, /* 14 */ { LINE_OFF, LINE_HDS, LINE_DS0 }, /* 15 */ { LINE_MOA, LINE_HDS, LINE_DS0 }, /* 16 */ { LINE_MOB, LINE_HDS, LINE_DS0 }, /* 17 */ { LINE_DS1, LINE_HDS, LINE_DS0 }, /* 18 */ { LINE_OFF, LINE_HDS, LINE_DS1 }, /* 19 */ { LINE_MOA, LINE_HDS, LINE_DS1 }, /* 20 */ { LINE_MOB, LINE_HDS, LINE_DS1 }, /* 21 */ { LINE_DS0, LINE_HDS, LINE_DS1 }, /* 22 */ }; /* input lines */ #define LINE_T0 FST3_T0 #define LINE_WP FST3_WP #define LINE_MASK_ST3 (LINE_T0 | LINE_WP) #define LINE_CHG FDIR_CHG static struct i2c_floppy_lines i2c_floppy_lines_in[] = { { LINE_OFF, LINE_OFF, LINE_OFF }, /* 0 - auto*/ { LINE_OFF, LINE_CHG, LINE_OFF }, /* 1 */ { LINE_OFF, LINE_T0, LINE_OFF }, /* 2 */ { LINE_OFF, LINE_WP, LINE_OFF }, /* 3 */ { LINE_OFF, LINE_CHG, LINE_T0 }, /* 4 */ { LINE_OFF, LINE_T0, LINE_WP }, /* 5 */ { LINE_OFF, LINE_WP, LINE_CHG }, /* 6 */ { LINE_CHG, LINE_T0, LINE_OFF }, /* 7 */ { LINE_CHG, LINE_WP, LINE_OFF }, /* 8 */ { LINE_CHG, LINE_T0, LINE_WP }, /* 9 */ { LINE_T0, LINE_CHG, LINE_OFF }, /* 10 */ { LINE_T0, LINE_WP, LINE_OFF }, /* 11 */ { LINE_T0, LINE_WP, LINE_CHG }, /* 12 */ { LINE_WP, LINE_CHG, LINE_OFF }, /* 13 */ { LINE_WP, LINE_T0, LINE_OFF }, /* 14 */ { LINE_WP, LINE_CHG, LINE_T0 }, /* 15 */ }; /* parameters */ #define LM_SWAP 0x0020 #define LM_INV_SDA 0x0040 #define LM_INV_SCL 0x0080 #define LM_INV_ENA 0x0100 static unsigned int line_mode = 0xe1; module_param(line_mode, uint, 0); MODULE_PARM_DESC(line_mode, "Output line mode"); static unsigned int line_mode_in = 0x00; module_param(line_mode_in, uint, 0); MODULE_PARM_DESC(line_mode_in, "Input line mode"); /* calculated */ static unsigned char lo_inv = 0; static unsigned char lo_mask = 0; static unsigned char li_inv = 0; static unsigned char li_mask = 0; static struct i2c_floppy_lines lines; static struct i2c_floppy_lines lines_in; /* ----- Output and input helpers ----------------------------------------- */ static unsigned char i2c_lo = 0; static void update_lo(unsigned char lo) { static unsigned char lo_prev = 0; unsigned char chg = lo ^ lo_prev; if (!chg) return; lo_prev = lo; if (chg & LINE_MASK_DOR) { dor &= ~LINE_MASK_DOR; dor |= (lo ^ lo_inv) & LINE_MASK_DOR; port_dor_out(dor); } if (chg & LINE_HDS) cmd_sense((lo ^ lo_inv) & LINE_HDS); } static unsigned char i2c_li = 0; static unsigned char update_li(void) { unsigned char li = 0; /* conditionally retrieve st3 flags */ if (li_mask & LINE_MASK_ST3) li |= cmd_sense((i2c_lo ^ lo_inv) & LINE_HDS) & LINE_MASK_ST3; /* conditionally map in disk change */ if (li_mask & LINE_CHG) li |= port_dir_in() & FDIR_CHG; i2c_li = li; return li; } /* ----- I2C algorithm call-back functions and structures ----------------- */ static void floppy_setena(void *data, int state) { if (state) i2c_lo |= lines.ena; else i2c_lo &= ~lines.ena; update_lo(i2c_lo); } static void floppy_setscl(void *data, int state) { if (state) i2c_lo |= lines.scl; else i2c_lo &= ~lines.scl; update_lo(i2c_lo); } static void floppy_setsda(void *data, int state) { if (state) i2c_lo |= lines.sda; else i2c_lo &= ~lines.sda; update_lo(i2c_lo); } static int floppy_getena(void *data) { return (update_li() ^ li_inv) & lines_in.ena; } static int floppy_getscl(void *data) { return (update_li() ^ li_inv) & lines_in.scl; } static int floppy_getsda(void *data) { return (update_li() ^ li_inv) & lines_in.sda; } static struct i2c_algo_bit_data floppy_algo_data = { .setsda = floppy_setsda, .setscl = floppy_setscl, .getsda = floppy_getsda, .getscl = floppy_getscl, .udelay = 50, .timeout = HZ, }; /* ----- Driver setup ----------------------------------------------------- */ static void swap_sda_scl(struct i2c_floppy_lines *lines) { unsigned char tmp = lines->sda; lines->sda = lines->scl; lines->scl = tmp; } static void setup_lo(void) { /* avoid undefined line modes */ if ((line_mode & 0x1F) > 22) line_mode &= ~0x1F; lines = i2c_floppy_lines_out[line_mode & 0x1F]; /* swap scl and sda */ if (line_mode & LM_SWAP) swap_sda_scl(&lines); if (line_mode & LM_INV_SDA) lo_inv ^= lines.sda; if (line_mode & LM_INV_SCL) lo_inv ^= lines.scl; if (line_mode & LM_INV_ENA) lo_inv ^= lines.ena; /* calculate line mask */ lo_mask = lines.ena | lines.sda | lines.scl; pr_info(DRVNAME ": line output config: " "%02x/%02x/%02x [%02x/%02x]\n", lines.ena, lines.scl, lines.sda, lo_mask, lo_inv); } static unsigned char lo_state(int ena, int sda, int scl) { floppy_setena(NULL, ena); floppy_setsda(NULL, sda); floppy_setscl(NULL, scl); return update_li(); } static void auto_setup_li(void) { unsigned char a, b, m; li_mask = ~0; li_inv = 0; /* reset controller */ dor = 0; port_dor_out(dor); /* enable controller */ dor |= FDOR_RESET; port_dor_out(dor); udelay(100); a = lo_state(0, 1, 1); b = lo_state(1, 1, 1); m = a ^ b; /* maybe only one bit? */ /* calculate enable input */ lines_in.ena = m; li_inv |= a & m; a = b; b = lo_state(1, 1, 0); m = a ^ b; /* maybe only one bit? */ /* calculate scl input */ lines_in.scl = m; li_inv |= b & m; a = b; b = lo_state(1, 0, 0); m = a ^ b; /* maybe only one bit? */ /* calculate sda input */ lines_in.sda = m; li_inv |= b & m; } static void setup_li(void) { /* automatic setup if selected */ if (!line_mode_in) { auto_setup_li(); goto calc_mask; } lines_in = i2c_floppy_lines_in[line_mode_in & 0x0F]; /* swap scl and sda */ if (line_mode_in & LM_SWAP) swap_sda_scl(&lines_in); if (line_mode_in & LM_INV_SDA) li_inv ^= lines_in.sda; if (line_mode_in & LM_INV_SCL) li_inv ^= lines_in.scl; if (line_mode_in & LM_INV_ENA) li_inv ^= lines_in.ena; calc_mask: /* calculate line mask */ li_mask = lines_in.ena | lines_in.sda | lines_in.scl; pr_info(DRVNAME ": line input config: " "%02x/%02x/%02x [%02x/%02x]\n", lines_in.ena, lines_in.scl, lines_in.sda, li_mask, li_inv); } /* ----- Driver registration ---------------------------------------------- */ static struct i2c_adapter floppy_adapter = { .owner = THIS_MODULE, .class = I2C_CLASS_HWMON, .algo_data = &floppy_algo_data, .name = "Floppy controller adapter", }; static int __devinit i2c_floppy_probe(struct platform_device *pdev) { int err; struct resource *res; res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (!request_region(res->start, res->end - res->start + 1, DRVNAME)) return -EBUSY; floppy_adapter.dev.parent = &pdev->dev; err = i2c_bit_add_bus(&floppy_adapter); if (err) { dev_err(&pdev->dev, "Unable to register with I2C\n"); goto exit_region; } return 0; exit_region: release_region(res->start, res->end - res->start + 1); return err; } static int __devexit i2c_floppy_remove(struct platform_device *pdev) { struct resource *res; i2c_del_adapter(&floppy_adapter); res = platform_get_resource(pdev, IORESOURCE_IO, 0); release_region(res->start, res->end - res->start + 1); return 0; } static struct platform_driver i2c_floppy_driver = { .driver = { .owner = THIS_MODULE, .name = DRVNAME, }, .probe = i2c_floppy_probe, .remove = __devexit_p(i2c_floppy_remove), }; static int __init i2c_floppy_device_add(u16 address) { struct resource res = { .start = address, .end = address + 7, .name = DRVNAME, .flags = IORESOURCE_IO, }; int err; pdev = platform_device_alloc(DRVNAME, -1); if (!pdev) { err = -ENOMEM; pr_err(DRVNAME ": Device allocation failed\n"); goto exit; } err = platform_device_add_resources(pdev, &res, 1); if (err) { pr_err(DRVNAME ": Device resource addition failed " "(%d)\n", err); goto exit_device_put; } err = platform_device_add(pdev); if (err) { pr_err(DRVNAME ": Device addition failed (%d)\n", err); goto exit_device_put; } return 0; exit_device_put: platform_device_put(pdev); exit: return err; } static int __init i2c_floppy_init(void) { int err; if (base == 0) { pr_info(DRVNAME ": using default base 0x%x\n", DEFAULT_BASE); base = DEFAULT_BASE; } /* Sets global pdev as a side effect */ err = i2c_floppy_device_add(base); if (err) goto exit; err = platform_driver_register(&i2c_floppy_driver); if (err) goto exit_device; setup_lo(); setup_li(); dor &= ~FDOR_RESET; /* do we need the controller active? */ if ((li_mask & LINE_MASK_ST3) || (lo_mask & LINE_HDS)) dor |= FDOR_RESET; port_dor_out(dor); lo_state(1,1,1); pr_info(DRVNAME ": input line check %02x/%02x/%02x\n", floppy_getena(NULL), floppy_getscl(NULL), floppy_getsda(NULL)); /* disable getscl if no mapping */ if (!lines_in.scl) floppy_algo_data.getscl = NULL; /* update udelay with param */ floppy_algo_data.udelay = delay; return 0; exit_device: platform_device_unregister(pdev); exit: return err; } static void __exit i2c_floppy_exit(void) { platform_driver_unregister(&i2c_floppy_driver); platform_device_unregister(pdev); } MODULE_AUTHOR("Herbert Poetzl "); MODULE_DESCRIPTION("I2C bus over floppy controller"); MODULE_LICENSE("GPL"); module_init(i2c_floppy_init); module_exit(i2c_floppy_exit);