--- linux-2.2.18pre15/drivers/block/cs5530.c.orig Wed May 3 20:16:33 2000 +++ linux-2.2.18pre15/drivers/block/cs5530.c Mon Oct 16 20:46:11 2000 @@ -1,5 +1,5 @@ /* - * linux/drivers/block/cs5530.c Version 0.4 Feb 6, 2000 + * linux/drivers/block/cs5530.c Version 0.6 Oct 16, 2000 * * Copyright (C) 2000 Mark Lord * May be copied or modified under the terms of the GNU General Public License @@ -20,6 +20,24 @@ #include #include +#ifdef CONFIG_APM +/* + * This driver now supports APM SUSPEND/RESUME actions, + * for which it saves/restores chipset/drive timings. + * + * BUT.. this code may be slightly unsafe to use since + * there is no knowlege of APM in the higher level IDE code. + * + * The risk is that there may be I/O in progress when + * our RESUME function is invoked.. and we'll basically toast it. + * The high level code will eventually time out on its IRQ wait, + * and likely report a "lost interrupt" after assuming the + * operation completed successfully. Ugh. + */ +#include +static int cs5530_apm_callback(apm_event_t ae); +#endif + #ifndef SETFEATURES_XFER #define CS5530_2_2_KERNEL #endif @@ -351,7 +369,7 @@ /* * Initialize the cs5530 bridge for reliable IDE DMA operation. */ -unsigned int __init pci_init_cs5530 (struct pci_dev *dev, const char *name) +unsigned int pci_init_cs5530 (struct pci_dev *dev, const char *name) { struct pci_dev *master_0 = NULL, *cs5530_0 = NULL; unsigned short pcicmd = 0; @@ -429,6 +447,20 @@ return 0; } +#ifdef CONFIG_APM +/* + * UGLY: There really ought to be a way to give/receive + * a device tag of some kind for APM callbacks. + * + * As it stands, we haven't a friggin' clue which hwif + * we are being invoked on behalf of. UGLY. + * + * So.. we have this UGLY global variable to keep track of it. + * There can only be two cs5530 hwifs in the hardware. + */ +static ide_hwif_t *cs5530_hwifs[2] = {NULL,NULL}; +#endif /* CONFIG_APM */ + /* * This gets invoked by the IDE driver once for each channel, * and performs channel-specific pre-initialization before drive probing. @@ -442,6 +474,17 @@ (void) pci_init_cs5530 (hwif->pci_dev, "CS5530"); } #endif /* CS5530_2_2_KERNEL */ +#ifdef CONFIG_APM + static int apm_registered = 0; + + cs5530_hwifs[hwif->index] = hwif; /* remember this HWIF for callbacks */ + if (!apm_registered) { + apm_registered = 1; + if (apm_register_callback(cs5530_apm_callback)) { + printk(KERN_WARNING "%s: apm suspend/resume might not work\n", hwif->name); + } + } +#endif /* CONFIG_APM */ if (hwif->mate) hwif->serialized = hwif->mate->serialized = 1; if (!hwif->dma_base) { @@ -465,3 +508,108 @@ } } } + +#ifdef CONFIG_APM +int cs5530_apm_callback (apm_event_t ae) +{ + static unsigned int saved_regs[2][4] = {{0,0,0,0},{0,0,0,0}}; + static int saved_state = 0; + int h, d, r; + + /* + * UGLY: There really ought to be a way to give/receive + * a device tag of some kind for APM callbacks. + * + * As it stands, we haven't a friggin' clue which hwif + * we are being invoked on behalf of. UGLY. + */ + switch(ae) { + case APM_SYS_SUSPEND: + case APM_CRITICAL_SUSPEND: + case APM_USER_SUSPEND: + for (h = 0; h < 2; ++h) { + ide_hwif_t *hwif = cs5530_hwifs[h]; + if (hwif) { + unsigned int basereg = CS5530_BASEREG(hwif); + for (r = 0; r < 4; ++r) { + saved_regs[h][r] = inl(basereg + (r<<2)); + } + printk("%s: saved chipset timings\n", hwif->name); + } + } + saved_state = 1; + break; + + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + case APM_STANDBY_RESUME: + if (saved_state) { + /* + * Restore chipset registers: + */ + int already_done = 0; + for (h = 0; h < 2; ++h) { + ide_hwif_t *hwif = cs5530_hwifs[h]; + if (hwif) { + unsigned int basereg = CS5530_BASEREG(hwif); + if (!already_done++) + (void) pci_init_cs5530 (hwif->pci_dev, "CS5530"); + for (r = 0; r < 4; ++r) { + outl(saved_regs[h][r], basereg + (r<<2)); + } + printk("%s: restored chipset timings\n", hwif->name); + } + } + /* + * Re-program drive PIO modes + */ + for (h = 0; h < 2; ++h) { + ide_hwif_t *hwif = cs5530_hwifs[h]; + if (hwif) { + unsigned int format = (saved_regs[h][1] >> 31) & 1; + for (d = 0; d < 2; ++d) { + ide_drive_t *drive = &(hwif->drives[d]); + if (drive->present) { + unsigned int pio, timings; + timings = saved_regs[h][d<<1]; + for (pio = 0; pio <= 4; ++pio) { + if (cs5530_pio_timings[format][pio] == timings) + break; + } + if (pio > 4) + pio = 255; /* autotune */ + (void)cs5530_tuneproc(drive, pio); + } + } + } + } + /* + * Re-program drive DMA modes + */ + for (h = 0; h < 2; ++h) { + ide_hwif_t *hwif = cs5530_hwifs[h]; + if (hwif && hwif->dmaproc != NULL) { + for (d = 0; d < MAX_DRIVES; ++d) { + ide_drive_t *drive = &(hwif->drives[d]); + if (drive->present) { + int was_using_dma = drive->using_dma; + (void)cs5530_dmaproc(ide_dma_off_quietly, drive); + (void)cs5530_dmaproc(ide_dma_check, drive); + if (!was_using_dma && drive->using_dma) { + (void)cs5530_dmaproc(ide_dma_off_quietly, drive); + } + } + } + } + } + saved_state = 0; + } + break; + + case APM_POWER_STATUS_CHANGE: + default: + break; + } + return 0; +} +#endif