2005-04-17 00:20:36 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) 1996-2004 Russell King.
|
|
|
|
*
|
|
|
|
* Please note that this platform does not support 32-bit IDE IO.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/ioport.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/hdreg.h>
|
|
|
|
#include <linux/ide.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/scatterlist.h>
|
2007-07-15 22:01:32 +02:00
|
|
|
#include <linux/io.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#include <asm/dma.h>
|
|
|
|
#include <asm/ecard.h>
|
|
|
|
|
|
|
|
#define ICS_IDENT_OFFSET 0x2280
|
|
|
|
|
|
|
|
#define ICS_ARCIN_V5_INTRSTAT 0x0000
|
|
|
|
#define ICS_ARCIN_V5_INTROFFSET 0x0004
|
|
|
|
#define ICS_ARCIN_V5_IDEOFFSET 0x2800
|
|
|
|
#define ICS_ARCIN_V5_IDEALTOFFSET 0x2b80
|
|
|
|
#define ICS_ARCIN_V5_IDESTEPPING 6
|
|
|
|
|
|
|
|
#define ICS_ARCIN_V6_IDEOFFSET_1 0x2000
|
|
|
|
#define ICS_ARCIN_V6_INTROFFSET_1 0x2200
|
|
|
|
#define ICS_ARCIN_V6_INTRSTAT_1 0x2290
|
|
|
|
#define ICS_ARCIN_V6_IDEALTOFFSET_1 0x2380
|
|
|
|
#define ICS_ARCIN_V6_IDEOFFSET_2 0x3000
|
|
|
|
#define ICS_ARCIN_V6_INTROFFSET_2 0x3200
|
|
|
|
#define ICS_ARCIN_V6_INTRSTAT_2 0x3290
|
|
|
|
#define ICS_ARCIN_V6_IDEALTOFFSET_2 0x3380
|
|
|
|
#define ICS_ARCIN_V6_IDESTEPPING 6
|
|
|
|
|
|
|
|
struct cardinfo {
|
|
|
|
unsigned int dataoffset;
|
|
|
|
unsigned int ctrloffset;
|
|
|
|
unsigned int stepping;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct cardinfo icside_cardinfo_v5 = {
|
|
|
|
.dataoffset = ICS_ARCIN_V5_IDEOFFSET,
|
|
|
|
.ctrloffset = ICS_ARCIN_V5_IDEALTOFFSET,
|
|
|
|
.stepping = ICS_ARCIN_V5_IDESTEPPING,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct cardinfo icside_cardinfo_v6_1 = {
|
|
|
|
.dataoffset = ICS_ARCIN_V6_IDEOFFSET_1,
|
|
|
|
.ctrloffset = ICS_ARCIN_V6_IDEALTOFFSET_1,
|
|
|
|
.stepping = ICS_ARCIN_V6_IDESTEPPING,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct cardinfo icside_cardinfo_v6_2 = {
|
|
|
|
.dataoffset = ICS_ARCIN_V6_IDEOFFSET_2,
|
|
|
|
.ctrloffset = ICS_ARCIN_V6_IDEALTOFFSET_2,
|
|
|
|
.stepping = ICS_ARCIN_V6_IDESTEPPING,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct icside_state {
|
|
|
|
unsigned int channel;
|
|
|
|
unsigned int enabled;
|
|
|
|
void __iomem *irq_port;
|
|
|
|
void __iomem *ioc_base;
|
|
|
|
unsigned int type;
|
|
|
|
ide_hwif_t *hwif[2];
|
|
|
|
};
|
|
|
|
|
|
|
|
#define ICS_TYPE_A3IN 0
|
|
|
|
#define ICS_TYPE_A3USER 1
|
|
|
|
#define ICS_TYPE_V6 3
|
|
|
|
#define ICS_TYPE_V5 15
|
|
|
|
#define ICS_TYPE_NOTYPE ((unsigned int)-1)
|
|
|
|
|
|
|
|
/* ---------------- Version 5 PCB Support Functions --------------------- */
|
|
|
|
/* Prototype: icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr)
|
|
|
|
* Purpose : enable interrupts from card
|
|
|
|
*/
|
|
|
|
static void icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ec->irq_data;
|
|
|
|
|
|
|
|
writeb(0, state->irq_port + ICS_ARCIN_V5_INTROFFSET);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prototype: icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr)
|
|
|
|
* Purpose : disable interrupts from card
|
|
|
|
*/
|
|
|
|
static void icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ec->irq_data;
|
|
|
|
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V5_INTROFFSET);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const expansioncard_ops_t icside_ops_arcin_v5 = {
|
|
|
|
.irqenable = icside_irqenable_arcin_v5,
|
|
|
|
.irqdisable = icside_irqdisable_arcin_v5,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------------- Version 6 PCB Support Functions --------------------- */
|
|
|
|
/* Prototype: icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr)
|
|
|
|
* Purpose : enable interrupts from card
|
|
|
|
*/
|
|
|
|
static void icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ec->irq_data;
|
|
|
|
void __iomem *base = state->irq_port;
|
|
|
|
|
|
|
|
state->enabled = 1;
|
|
|
|
|
|
|
|
switch (state->channel) {
|
|
|
|
case 0:
|
|
|
|
writeb(0, base + ICS_ARCIN_V6_INTROFFSET_1);
|
|
|
|
readb(base + ICS_ARCIN_V6_INTROFFSET_2);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
writeb(0, base + ICS_ARCIN_V6_INTROFFSET_2);
|
|
|
|
readb(base + ICS_ARCIN_V6_INTROFFSET_1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prototype: icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr)
|
|
|
|
* Purpose : disable interrupts from card
|
|
|
|
*/
|
|
|
|
static void icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ec->irq_data;
|
|
|
|
|
|
|
|
state->enabled = 0;
|
|
|
|
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prototype: icside_irqprobe(struct expansion_card *ec)
|
|
|
|
* Purpose : detect an active interrupt from card
|
|
|
|
*/
|
|
|
|
static int icside_irqpending_arcin_v6(struct expansion_card *ec)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ec->irq_data;
|
|
|
|
|
|
|
|
return readb(state->irq_port + ICS_ARCIN_V6_INTRSTAT_1) & 1 ||
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTRSTAT_2) & 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const expansioncard_ops_t icside_ops_arcin_v6 = {
|
|
|
|
.irqenable = icside_irqenable_arcin_v6,
|
|
|
|
.irqdisable = icside_irqdisable_arcin_v6,
|
|
|
|
.irqpending = icside_irqpending_arcin_v6,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle routing of interrupts. This is called before
|
|
|
|
* we write the command to the drive.
|
|
|
|
*/
|
|
|
|
static void icside_maskproc(ide_drive_t *drive, int mask)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif = HWIF(drive);
|
|
|
|
struct icside_state *state = hwif->hwif_data;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
local_irq_save(flags);
|
|
|
|
|
|
|
|
state->channel = hwif->channel;
|
|
|
|
|
|
|
|
if (state->enabled && !mask) {
|
|
|
|
switch (hwif->channel) {
|
|
|
|
case 0:
|
|
|
|
writeb(0, state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
writeb(0, state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
|
|
|
|
readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
|
|
|
|
}
|
|
|
|
|
|
|
|
local_irq_restore(flags);
|
|
|
|
}
|
|
|
|
|
2008-04-26 22:25:14 +02:00
|
|
|
static const struct ide_port_ops icside_v6_no_dma_port_ops = {
|
|
|
|
.maskproc = icside_maskproc,
|
|
|
|
};
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
|
|
|
|
/*
|
|
|
|
* SG-DMA support.
|
|
|
|
*
|
|
|
|
* Similar to the BM-DMA, but we use the RiscPCs IOMD DMA controllers.
|
|
|
|
* There is only one DMA controller per card, which means that only
|
|
|
|
* one drive can be accessed at one time. NOTE! We do not enforce that
|
|
|
|
* here, but we rely on the main IDE driver spotting that both
|
|
|
|
* interfaces use the same IRQ, which should guarantee this.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Configure the IOMD to give the appropriate timings for the transfer
|
|
|
|
* mode being requested. We take the advice of the ATA standards, and
|
|
|
|
* calculate the cycle time based on the transfer mode, and the EIDE
|
|
|
|
* MW DMA specs that the drive provides in the IDENTIFY command.
|
|
|
|
*
|
|
|
|
* We have the following IOMD DMA modes to choose from:
|
|
|
|
*
|
|
|
|
* Type Active Recovery Cycle
|
|
|
|
* A 250 (250) 312 (550) 562 (800)
|
|
|
|
* B 187 250 437
|
|
|
|
* C 125 (125) 125 (375) 250 (500)
|
|
|
|
* D 62 125 187
|
|
|
|
*
|
|
|
|
* (figures in brackets are actual measured timings)
|
|
|
|
*
|
|
|
|
* However, we also need to take care of the read/write active and
|
|
|
|
* recovery timings:
|
|
|
|
*
|
|
|
|
* Read Write
|
|
|
|
* Mode Active -- Recovery -- Cycle IOMD type
|
|
|
|
* MW0 215 50 215 480 A
|
|
|
|
* MW1 80 50 50 150 C
|
|
|
|
* MW2 70 25 25 120 C
|
|
|
|
*/
|
ide: move ide_config_drive_speed() calls to upper layers (take 2)
* Convert {ide_hwif_t,ide_pci_device_t}->host_flag to be u16.
* Add IDE_HFLAG_POST_SET_MODE host flag to indicate the need to program
the host for the transfer mode after programming the device. Set it
in au1xxx-ide, amd74xx, cs5530, cs5535, pdc202xx_new, sc1200, pmac
and via82cxxx host drivers.
* Add IDE_HFLAG_NO_SET_MODE host flag to indicate the need to completely
skip programming of host/device for the transfer mode ("smart" hosts).
Set it in it821x host driver and check it in ide_tune_dma().
* Add ide_set_pio_mode()/ide_set_dma_mode() helpers and convert all
direct ->set_pio_mode/->speedproc users to use these helpers.
* Move ide_config_drive_speed() calls from ->set_pio_mode/->speedproc
methods to callers.
* Rename ->speedproc method to ->set_dma_mode, make it void and update
all implementations accordingly.
* Update ide_set_xfer_rate() comments.
* Unexport ide_config_drive_speed().
v2:
* Fix issues noticed by Sergei:
- export ide_set_dma_mode() instead of moving ->set_pio_mode abuse wrt
to setting DMA modes from sc1200_set_pio_mode() to do_special()
- check IDE_HFLAG_NO_SET_MODE in ide_tune_dma()
- check for (hwif->set_pio_mode) == NULL in ide_set_pio_mode()
- check for (hwif->set_dma_mode) == NULL in ide_set_dma_mode()
- return -1 from ide_set_{pio,dma}_mode() if ->set_{pio,dma}_mode == NULL
- don't set ->set_{pio,dma}_mode on it821x in "smart" mode
- fix build problem in pmac.c
- minor fixes in au1xxx-ide.c/cs5530.c/siimage.c
- improve patch description
Changes in behavior caused by this patch:
- HDIO_SET_PIO_MODE ioctl would now return -ENOSYS for attempts to change
PIO mode if it821x controller is in "smart" mode
- removal of two debugging printk-s (from cs5530.c and sc1200.c)
- transfer modes 0x00-0x07 passed from user space may be programmed twice on
the device (not really an issue since 0x00 is not supported correctly by
any host driver ATM, 0x01 is not supported at all and 0x02-0x07 are invalid)
Acked-by: Sergei Shtylyov <sshtylyov@ru.mvista.com>
Signed-off-by: Bartlomiej Zolnierkiewicz <bzolnier@gmail.com>
2007-10-13 17:47:51 +02:00
|
|
|
static void icside_set_dma_mode(ide_drive_t *drive, const u8 xfer_mode)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2007-10-11 23:54:00 +02:00
|
|
|
int cycle_time, use_dma_info = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
switch (xfer_mode) {
|
|
|
|
case XFER_MW_DMA_2:
|
|
|
|
cycle_time = 250;
|
|
|
|
use_dma_info = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XFER_MW_DMA_1:
|
|
|
|
cycle_time = 250;
|
|
|
|
use_dma_info = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XFER_MW_DMA_0:
|
|
|
|
cycle_time = 480;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XFER_SW_DMA_2:
|
|
|
|
case XFER_SW_DMA_1:
|
|
|
|
case XFER_SW_DMA_0:
|
|
|
|
cycle_time = 480;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we're going to be doing MW_DMA_1 or MW_DMA_2, we should
|
|
|
|
* take care to note the values in the ID...
|
|
|
|
*/
|
|
|
|
if (use_dma_info && drive->id->eide_dma_time > cycle_time)
|
|
|
|
cycle_time = drive->id->eide_dma_time;
|
|
|
|
|
|
|
|
drive->drive_data = cycle_time;
|
|
|
|
|
|
|
|
printk("%s: %s selected (peak %dMB/s)\n", drive->name,
|
|
|
|
ide_xfer_verbose(xfer_mode), 2000 / drive->drive_data);
|
|
|
|
}
|
|
|
|
|
2008-04-26 22:25:14 +02:00
|
|
|
static const struct ide_port_ops icside_v6_port_ops = {
|
|
|
|
.set_dma_mode = icside_set_dma_mode,
|
|
|
|
.maskproc = icside_maskproc,
|
|
|
|
};
|
|
|
|
|
2008-01-26 20:13:03 +01:00
|
|
|
static void icside_dma_host_set(ide_drive_t *drive, int on)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static int icside_dma_end(ide_drive_t *drive)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif = HWIF(drive);
|
2008-02-01 23:09:32 +01:00
|
|
|
struct expansion_card *ec = ECARD_DEV(hwif->dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
drive->waiting_for_dma = 0;
|
|
|
|
|
2008-02-01 23:09:32 +01:00
|
|
|
disable_dma(ec->dma);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Teardown mappings after DMA has completed. */
|
2008-02-01 23:09:32 +01:00
|
|
|
ide_destroy_dmatable(drive);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2008-02-01 23:09:32 +01:00
|
|
|
return get_dma_residue(ec->dma) != 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void icside_dma_start(ide_drive_t *drive)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif = HWIF(drive);
|
2008-02-01 23:09:32 +01:00
|
|
|
struct expansion_card *ec = ECARD_DEV(hwif->dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* We can not enable DMA on both channels simultaneously. */
|
2008-02-01 23:09:32 +01:00
|
|
|
BUG_ON(dma_channel_active(ec->dma));
|
|
|
|
enable_dma(ec->dma);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int icside_dma_setup(ide_drive_t *drive)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif = HWIF(drive);
|
2008-02-01 23:09:32 +01:00
|
|
|
struct expansion_card *ec = ECARD_DEV(hwif->dev);
|
2005-04-17 00:20:36 +02:00
|
|
|
struct request *rq = hwif->hwgroup->rq;
|
|
|
|
unsigned int dma_mode;
|
|
|
|
|
|
|
|
if (rq_data_dir(rq))
|
|
|
|
dma_mode = DMA_MODE_WRITE;
|
|
|
|
else
|
|
|
|
dma_mode = DMA_MODE_READ;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We can not enable DMA on both channels.
|
|
|
|
*/
|
2008-02-01 23:09:32 +01:00
|
|
|
BUG_ON(dma_channel_active(ec->dma));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2008-02-01 23:09:32 +01:00
|
|
|
hwif->sg_nents = ide_build_sglist(drive, rq);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Ensure that we have the right interrupt routed.
|
|
|
|
*/
|
|
|
|
icside_maskproc(drive, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Route the DMA signals to the correct interface.
|
|
|
|
*/
|
|
|
|
writeb(hwif->select_data, hwif->config_data);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Select the correct timing for this drive.
|
|
|
|
*/
|
2008-02-01 23:09:32 +01:00
|
|
|
set_dma_speed(ec->dma, drive->drive_data);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Tell the DMA engine about the SG table and
|
|
|
|
* data direction.
|
|
|
|
*/
|
2008-02-01 23:09:32 +01:00
|
|
|
set_dma_sg(ec->dma, hwif->sg_table, hwif->sg_nents);
|
|
|
|
set_dma_mode(ec->dma, dma_mode);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
drive->waiting_for_dma = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void icside_dma_exec_cmd(ide_drive_t *drive, u8 cmd)
|
|
|
|
{
|
|
|
|
/* issue cmd to drive */
|
|
|
|
ide_execute_command(drive, cmd, ide_dma_intr, 2 * WAIT_CMD, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int icside_dma_test_irq(ide_drive_t *drive)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif = HWIF(drive);
|
|
|
|
struct icside_state *state = hwif->hwif_data;
|
|
|
|
|
|
|
|
return readb(state->irq_port +
|
|
|
|
(hwif->channel ?
|
|
|
|
ICS_ARCIN_V6_INTRSTAT_2 :
|
|
|
|
ICS_ARCIN_V6_INTRSTAT_1)) & 1;
|
|
|
|
}
|
|
|
|
|
2007-07-09 23:17:54 +02:00
|
|
|
static void icside_dma_timeout(ide_drive_t *drive)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
printk(KERN_ERR "%s: DMA timeout occurred: ", drive->name);
|
|
|
|
|
|
|
|
if (icside_dma_test_irq(drive))
|
2007-07-09 23:17:54 +02:00
|
|
|
return;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2008-02-06 02:57:51 +01:00
|
|
|
ide_dump_status(drive, "DMA timeout", ide_read_status(drive));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-07-09 23:17:54 +02:00
|
|
|
icside_dma_end(drive);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2007-07-09 23:17:54 +02:00
|
|
|
static void icside_dma_lost_irq(ide_drive_t *drive)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
printk(KERN_ERR "%s: IRQ lost\n", drive->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void icside_dma_init(ide_hwif_t *hwif)
|
|
|
|
{
|
|
|
|
hwif->dmatable_cpu = NULL;
|
|
|
|
hwif->dmatable_dma = 0;
|
|
|
|
|
2008-01-26 20:13:03 +01:00
|
|
|
hwif->dma_host_set = icside_dma_host_set;
|
2005-04-17 00:20:36 +02:00
|
|
|
hwif->dma_setup = icside_dma_setup;
|
|
|
|
hwif->dma_exec_cmd = icside_dma_exec_cmd;
|
|
|
|
hwif->dma_start = icside_dma_start;
|
|
|
|
hwif->ide_dma_end = icside_dma_end;
|
|
|
|
hwif->ide_dma_test_irq = icside_dma_test_irq;
|
2007-07-09 23:17:54 +02:00
|
|
|
hwif->dma_timeout = icside_dma_timeout;
|
2007-07-09 23:17:54 +02:00
|
|
|
hwif->dma_lost_irq = icside_dma_lost_irq;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define icside_dma_init(hwif) (0)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static ide_hwif_t *
|
|
|
|
icside_setup(void __iomem *base, struct cardinfo *info, struct expansion_card *ec)
|
|
|
|
{
|
|
|
|
unsigned long port = (unsigned long)base + info->dataoffset;
|
|
|
|
ide_hwif_t *hwif;
|
|
|
|
|
2008-04-26 17:36:32 +02:00
|
|
|
hwif = ide_find_port();
|
2005-04-17 00:20:36 +02:00
|
|
|
if (hwif) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ensure we're using MMIO
|
|
|
|
*/
|
|
|
|
default_hwif_mmiops(hwif);
|
|
|
|
|
|
|
|
for (i = IDE_DATA_OFFSET; i <= IDE_STATUS_OFFSET; i++) {
|
|
|
|
hwif->io_ports[i] = port;
|
|
|
|
port += 1 << info->stepping;
|
|
|
|
}
|
|
|
|
hwif->io_ports[IDE_CONTROL_OFFSET] = (unsigned long)base + info->ctrloffset;
|
|
|
|
hwif->irq = ec->irq;
|
|
|
|
hwif->chipset = ide_acorn;
|
|
|
|
hwif->gendev.parent = &ec->dev;
|
2008-02-01 23:09:32 +01:00
|
|
|
hwif->dev = &ec->dev;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return hwif;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init
|
|
|
|
icside_register_v5(struct icside_state *state, struct expansion_card *ec)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif;
|
|
|
|
void __iomem *base;
|
2007-10-20 00:32:31 +02:00
|
|
|
u8 idx[4] = { 0xff, 0xff, 0xff, 0xff };
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-05-10 19:40:51 +02:00
|
|
|
base = ecardm_iomap(ec, ECARD_RES_MEMC, 0, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!base)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
state->irq_port = base;
|
|
|
|
|
|
|
|
ec->irqaddr = base + ICS_ARCIN_V5_INTRSTAT;
|
|
|
|
ec->irqmask = 1;
|
2007-05-10 17:46:13 +02:00
|
|
|
|
|
|
|
ecard_setirq(ec, &icside_ops_arcin_v5, state);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Be on the safe side - disable interrupts
|
|
|
|
*/
|
|
|
|
icside_irqdisable_arcin_v5(ec, 0);
|
|
|
|
|
|
|
|
hwif = icside_setup(base, &icside_cardinfo_v5, ec);
|
2007-05-10 19:40:51 +02:00
|
|
|
if (!hwif)
|
2005-04-17 00:20:36 +02:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
state->hwif[0] = hwif;
|
|
|
|
|
2007-10-20 00:32:31 +02:00
|
|
|
idx[0] = hwif->index;
|
2007-05-10 00:01:11 +02:00
|
|
|
|
2008-02-02 19:56:31 +01:00
|
|
|
ide_device_add(idx, NULL);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-02-02 19:56:31 +01:00
|
|
|
static const struct ide_port_info icside_v6_port_info __initdata = {
|
2008-04-26 22:25:14 +02:00
|
|
|
.port_ops = &icside_v6_no_dma_port_ops,
|
2008-02-02 19:56:31 +01:00
|
|
|
.host_flags = IDE_HFLAG_SERIALIZE |
|
|
|
|
IDE_HFLAG_NO_DMA | /* no SFF-style DMA */
|
|
|
|
IDE_HFLAG_NO_AUTOTUNE,
|
|
|
|
.mwdma_mask = ATA_MWDMA2,
|
|
|
|
.swdma_mask = ATA_SWDMA2,
|
|
|
|
};
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static int __init
|
|
|
|
icside_register_v6(struct icside_state *state, struct expansion_card *ec)
|
|
|
|
{
|
|
|
|
ide_hwif_t *hwif, *mate;
|
|
|
|
void __iomem *ioc_base, *easi_base;
|
|
|
|
unsigned int sel = 0;
|
|
|
|
int ret;
|
2007-10-20 00:32:31 +02:00
|
|
|
u8 idx[4] = { 0xff, 0xff, 0xff, 0xff };
|
2008-02-02 19:56:31 +01:00
|
|
|
struct ide_port_info d = icside_v6_port_info;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-05-10 19:40:51 +02:00
|
|
|
ioc_base = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!ioc_base) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
easi_base = ioc_base;
|
|
|
|
|
|
|
|
if (ecard_resource_flags(ec, ECARD_RES_EASI)) {
|
2007-05-10 19:40:51 +02:00
|
|
|
easi_base = ecardm_iomap(ec, ECARD_RES_EASI, 0, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!easi_base) {
|
|
|
|
ret = -ENOMEM;
|
2007-05-10 19:40:51 +02:00
|
|
|
goto out;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Enable access to the EASI region.
|
|
|
|
*/
|
|
|
|
sel = 1 << 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeb(sel, ioc_base);
|
|
|
|
|
2007-05-10 17:46:13 +02:00
|
|
|
ecard_setirq(ec, &icside_ops_arcin_v6, state);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
state->irq_port = easi_base;
|
|
|
|
state->ioc_base = ioc_base;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Be on the safe side - disable interrupts
|
|
|
|
*/
|
|
|
|
icside_irqdisable_arcin_v6(ec, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find and register the interfaces.
|
|
|
|
*/
|
|
|
|
hwif = icside_setup(easi_base, &icside_cardinfo_v6_1, ec);
|
|
|
|
mate = icside_setup(easi_base, &icside_cardinfo_v6_2, ec);
|
|
|
|
|
|
|
|
if (!hwif || !mate) {
|
|
|
|
ret = -ENODEV;
|
2007-05-10 19:40:51 +02:00
|
|
|
goto out;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
state->hwif[0] = hwif;
|
|
|
|
state->hwif[1] = mate;
|
|
|
|
|
|
|
|
hwif->hwif_data = state;
|
|
|
|
hwif->config_data = (unsigned long)ioc_base;
|
|
|
|
hwif->select_data = sel;
|
|
|
|
|
|
|
|
mate->maskproc = icside_maskproc;
|
|
|
|
mate->hwif_data = state;
|
|
|
|
mate->config_data = (unsigned long)ioc_base;
|
|
|
|
mate->select_data = sel | 1;
|
|
|
|
|
|
|
|
if (ec->dma != NO_DMA && !request_dma(ec->dma, hwif->name)) {
|
|
|
|
icside_dma_init(hwif);
|
|
|
|
icside_dma_init(mate);
|
2008-04-26 22:25:14 +02:00
|
|
|
d.port_ops = &icside_v6_dma_port_ops;
|
2008-02-02 19:56:31 +01:00
|
|
|
} else
|
|
|
|
d.mwdma_mask = d.swdma_mask = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-10-20 00:32:31 +02:00
|
|
|
idx[0] = hwif->index;
|
|
|
|
idx[1] = mate->index;
|
2007-05-10 00:01:11 +02:00
|
|
|
|
2008-02-02 19:56:31 +01:00
|
|
|
ide_device_add(idx, &d);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __devinit
|
|
|
|
icside_probe(struct expansion_card *ec, const struct ecard_id *id)
|
|
|
|
{
|
|
|
|
struct icside_state *state;
|
|
|
|
void __iomem *idmem;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = ecard_request_resources(ec);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2007-08-01 23:46:44 +02:00
|
|
|
state = kzalloc(sizeof(struct icside_state), GFP_KERNEL);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!state) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
state->type = ICS_TYPE_NOTYPE;
|
|
|
|
|
2007-05-10 19:40:51 +02:00
|
|
|
idmem = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (idmem) {
|
|
|
|
unsigned int type;
|
|
|
|
|
|
|
|
type = readb(idmem + ICS_IDENT_OFFSET) & 1;
|
|
|
|
type |= (readb(idmem + ICS_IDENT_OFFSET + 4) & 1) << 1;
|
|
|
|
type |= (readb(idmem + ICS_IDENT_OFFSET + 8) & 1) << 2;
|
|
|
|
type |= (readb(idmem + ICS_IDENT_OFFSET + 12) & 1) << 3;
|
2007-05-10 19:40:51 +02:00
|
|
|
ecardm_iounmap(ec, idmem);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
state->type = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (state->type) {
|
|
|
|
case ICS_TYPE_A3IN:
|
|
|
|
dev_warn(&ec->dev, "A3IN unsupported\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ICS_TYPE_A3USER:
|
|
|
|
dev_warn(&ec->dev, "A3USER unsupported\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ICS_TYPE_V5:
|
|
|
|
ret = icside_register_v5(state, ec);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ICS_TYPE_V6:
|
|
|
|
ret = icside_register_v6(state, ec);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
dev_warn(&ec->dev, "unknown interface type\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret == 0) {
|
|
|
|
ecard_set_drvdata(ec, state);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(state);
|
|
|
|
release:
|
|
|
|
ecard_release_resources(ec);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __devexit icside_remove(struct expansion_card *ec)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ecard_get_drvdata(ec);
|
|
|
|
|
|
|
|
switch (state->type) {
|
|
|
|
case ICS_TYPE_V5:
|
|
|
|
/* FIXME: tell IDE to stop using the interface */
|
|
|
|
|
|
|
|
/* Disable interrupts */
|
|
|
|
icside_irqdisable_arcin_v5(ec, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ICS_TYPE_V6:
|
|
|
|
/* FIXME: tell IDE to stop using the interface */
|
|
|
|
if (ec->dma != NO_DMA)
|
|
|
|
free_dma(ec->dma);
|
|
|
|
|
|
|
|
/* Disable interrupts */
|
|
|
|
icside_irqdisable_arcin_v6(ec, 0);
|
|
|
|
|
|
|
|
/* Reset the ROM pointer/EASI selection */
|
|
|
|
writeb(0, state->ioc_base);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecard_set_drvdata(ec, NULL);
|
|
|
|
|
|
|
|
kfree(state);
|
|
|
|
ecard_release_resources(ec);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void icside_shutdown(struct expansion_card *ec)
|
|
|
|
{
|
|
|
|
struct icside_state *state = ecard_get_drvdata(ec);
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disable interrupts from this card. We need to do
|
|
|
|
* this before disabling EASI since we may be accessing
|
|
|
|
* this register via that region.
|
|
|
|
*/
|
|
|
|
local_irq_save(flags);
|
|
|
|
ec->ops->irqdisable(ec, 0);
|
|
|
|
local_irq_restore(flags);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reset the ROM pointer so that we can read the ROM
|
|
|
|
* after a soft reboot. This also disables access to
|
|
|
|
* the IDE taskfile via the EASI region.
|
|
|
|
*/
|
|
|
|
if (state->ioc_base)
|
|
|
|
writeb(0, state->ioc_base);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct ecard_id icside_ids[] = {
|
|
|
|
{ MANU_ICS, PROD_ICS_IDE },
|
|
|
|
{ MANU_ICS2, PROD_ICS2_IDE },
|
|
|
|
{ 0xffff, 0xffff }
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct ecard_driver icside_driver = {
|
|
|
|
.probe = icside_probe,
|
|
|
|
.remove = __devexit_p(icside_remove),
|
|
|
|
.shutdown = icside_shutdown,
|
|
|
|
.id_table = icside_ids,
|
|
|
|
.drv = {
|
|
|
|
.name = "icside",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init icside_init(void)
|
|
|
|
{
|
|
|
|
return ecard_register_driver(&icside_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("ICS IDE driver");
|
|
|
|
|
|
|
|
module_init(icside_init);
|