Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
caaaf123
Commit
caaaf123
authored
Oct 27, 2002
by
Alan Cox
Committed by
Linus Torvalds
Oct 27, 2002
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[PATCH] ARM ide driver updates
parent
8ac3a026
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
529 additions
and
392 deletions
+529
-392
drivers/ide/Config.in
drivers/ide/Config.in
+5
-5
drivers/ide/arm/icside.c
drivers/ide/arm/icside.c
+481
-333
drivers/ide/arm/rapide.c
drivers/ide/arm/rapide.c
+43
-54
No files found.
drivers/ide/Config.in
View file @
caaaf123
...
...
@@ -99,12 +99,12 @@ if [ "$CONFIG_BLK_DEV_IDE" != "n" ]; then
if [ "$CONFIG_SIBYTE_SWARM" = "y" ]; then
bool ' SWARM onboard IDE support' CONFIG_BLK_DEV_IDE_SWARM
fi
if [ "$CONFIG_AR
CH_ACORN
" = "y" ]; then
dep_
bool
' ICS IDE interface support' CONFIG_BLK_DEV_IDE_ICSIDE $CONFIG_ARCH_ACORN
dep_bool ' ICS DMA support' CONFIG_BLK_DEV_IDEDMA_ICS $CONFIG_BLK_DEV_IDE_ICSIDE
dep_bool ' Use ICS DMA by default' CONFIG_IDEDMA_ICS_AUTO $CONFIG_BLK_DEV_IDEDMA_ICS
if [ "$CONFIG_AR
M
" = "y" ]; then
dep_
tristate
' ICS IDE interface support' CONFIG_BLK_DEV_IDE_ICSIDE $CONFIG_ARCH_ACORN
dep_
m
bool ' ICS DMA support' CONFIG_BLK_DEV_IDEDMA_ICS $CONFIG_BLK_DEV_IDE_ICSIDE
dep_
m
bool ' Use ICS DMA by default' CONFIG_IDEDMA_ICS_AUTO $CONFIG_BLK_DEV_IDEDMA_ICS
define_bool CONFIG_BLK_DEV_IDEDMA $CONFIG_BLK_DEV_IDEDMA_ICS
dep_
bool
' RapIDE interface support' CONFIG_BLK_DEV_IDE_RAPIDE $CONFIG_ARCH_ACORN
dep_
tristate
' RapIDE interface support' CONFIG_BLK_DEV_IDE_RAPIDE $CONFIG_ARCH_ACORN
fi
if [ "$CONFIG_AMIGA" = "y" ]; then
dep_bool ' Amiga Gayle IDE interface support' CONFIG_BLK_DEV_GAYLE $CONFIG_AMIGA
...
...
drivers/ide/arm/icside.c
View file @
caaaf123
/*
* linux/drivers/ide/icside.c
*
* Copyright (c) 1996
,1997
Russell King.
* Copyright (c) 1996
-2002
Russell King.
*
* Changelog:
* 08-Jun-1996 RMK Created
...
...
@@ -20,25 +20,13 @@
#include <linux/hdreg.h>
#include <linux/ide.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/init.h>
#include <asm/dma.h>
#include <asm/ecard.h>
#include <asm/io.h>
#include "ide-noise.h"
/*
* FIXME: We want to drop the the MACRO CRAP!
*
* ec->iops->in{b/w/l}
* ec->iops->in{b/w/l}_p
* ec->iops->out{b/w/l}
* ec->iops->out{b/w/l}_p
*
* the new core supports clean MMIO calls and other goodies
*/
/*
* Maximum number of interfaces per card
*/
...
...
@@ -86,10 +74,10 @@ static struct cardinfo icside_cardinfo_v6_2 = {
ICS_ARCIN_V6_IDESTEPPING
};
st
atic
const
card_ids
icside_cids
[]
=
{
{
MANU_ICS
,
PROD_ICS_IDE
},
{
MANU_ICS2
,
PROD_ICS2_IDE
},
{
0xffff
,
0xffff
}
st
ruct
icside_state
{
unsigned
int
channel
;
unsigned
int
enabled
;
unsigned
int
irq_port
;
};
typedef
enum
{
...
...
@@ -133,10 +121,21 @@ static const expansioncard_ops_t icside_ops_arcin_v5 = {
*/
static
void
icside_irqenable_arcin_v6
(
struct
expansion_card
*
ec
,
int
irqnr
)
{
unsigned
int
ide_base_port
=
(
unsigned
int
)
ec
->
irq_data
;
struct
icside_state
*
state
=
ec
->
irq_data
;
unsigned
int
base
=
state
->
irq_port
;
state
->
enabled
=
1
;
outb
(
0
,
ide_base_port
+
ICS_ARCIN_V6_INTROFFSET_1
);
outb
(
0
,
ide_base_port
+
ICS_ARCIN_V6_INTROFFSET_2
);
switch
(
state
->
channel
)
{
case
0
:
outb
(
0
,
base
+
ICS_ARCIN_V6_INTROFFSET_1
);
inb
(
base
+
ICS_ARCIN_V6_INTROFFSET_2
);
break
;
case
1
:
outb
(
0
,
base
+
ICS_ARCIN_V6_INTROFFSET_2
);
inb
(
base
+
ICS_ARCIN_V6_INTROFFSET_1
);
break
;
}
}
/* Prototype: icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr)
...
...
@@ -144,10 +143,12 @@ static void icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr)
*/
static
void
icside_irqdisable_arcin_v6
(
struct
expansion_card
*
ec
,
int
irqnr
)
{
unsigned
int
ide_base_port
=
(
unsigned
int
)
ec
->
irq_data
;
struct
icside_state
*
state
=
ec
->
irq_data
;
inb
(
ide_base_port
+
ICS_ARCIN_V6_INTROFFSET_1
);
inb
(
ide_base_port
+
ICS_ARCIN_V6_INTROFFSET_2
);
state
->
enabled
=
0
;
inb
(
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_1
);
inb
(
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_2
);
}
/* Prototype: icside_irqprobe(struct expansion_card *ec)
...
...
@@ -155,10 +156,10 @@ static void icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr)
*/
static
int
icside_irqpending_arcin_v6
(
struct
expansion_card
*
ec
)
{
unsigned
int
ide_base_port
=
(
unsigned
int
)
ec
->
irq_data
;
struct
icside_state
*
state
=
ec
->
irq_data
;
return
inb
(
ide_base
_port
+
ICS_ARCIN_V6_INTRSTAT_1
)
&
1
||
inb
(
ide_base
_port
+
ICS_ARCIN_V6_INTRSTAT_2
)
&
1
;
return
inb
(
state
->
irq
_port
+
ICS_ARCIN_V6_INTRSTAT_1
)
&
1
||
inb
(
state
->
irq
_port
+
ICS_ARCIN_V6_INTRSTAT_2
)
&
1
;
}
static
const
expansioncard_ops_t
icside_ops_arcin_v6
=
{
...
...
@@ -221,6 +222,39 @@ static iftype_t __init icside_identifyif (struct expansion_card *ec)
return
iftype
;
}
/*
* 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
->
hw
.
priv
;
unsigned
long
flags
;
local_irq_save
(
flags
);
state
->
channel
=
hwif
->
channel
;
if
(
state
->
enabled
&&
!
mask
)
{
switch
(
hwif
->
channel
)
{
case
0
:
outb
(
0
,
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_1
);
inb
(
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_2
);
break
;
case
1
:
outb
(
0
,
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_2
);
inb
(
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_1
);
break
;
}
}
else
{
inb
(
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_2
);
inb
(
state
->
irq_port
+
ICS_ARCIN_V6_INTROFFSET_1
);
}
local_irq_restore
(
flags
);
}
#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
/*
* SG-DMA support.
...
...
@@ -234,125 +268,119 @@ static iftype_t __init icside_identifyif (struct expansion_card *ec)
#define NR_ENTRIES 256
#define TABLE_SIZE (NR_ENTRIES * 8)
static
int
ide_build_sglist
(
ide_hwif_t
*
hwif
,
struct
request
*
rq
)
static
void
ide_build_sglist
(
ide_drive_t
*
drive
,
struct
request
*
rq
)
{
struct
buffer_head
*
bh
;
ide_hwif_t
*
hwif
=
drive
->
hwif
;
struct
scatterlist
*
sg
=
hwif
->
sg_table
;
int
nents
=
0
;
int
nents
;
if
(
rq
->
cmd
==
READ
)
BUG_ON
(
hwif
->
sg_dma_active
);
if
(
rq
->
flags
&
REQ_DRIVE_TASKFILE
)
{
ide_task_t
*
args
=
rq
->
special
;
if
(
args
->
command_type
==
IDE_DRIVE_TASK_RAW_WRITE
)
hwif
->
sg_dma_direction
=
PCI_DMA_TODEVICE
;
else
hwif
->
sg_dma_direction
=
PCI_DMA_FROMDEVICE
;
memset
(
sg
,
0
,
sizeof
(
*
sg
));
sg
->
page
=
virt_to_page
(
rq
->
buffer
);
sg
->
offset
=
((
unsigned
long
)
rq
->
buffer
)
&
~
PAGE_MASK
;
sg
->
length
=
rq
->
nr_sectors
*
SECTOR_SIZE
;
nents
=
1
;
}
else
{
nents
=
blk_rq_map_sg
(
&
drive
->
queue
,
rq
,
sg
);
if
(
rq_data_dir
(
rq
)
==
READ
)
hwif
->
sg_dma_direction
=
PCI_DMA_FROMDEVICE
;
else
hwif
->
sg_dma_direction
=
PCI_DMA_TODEVICE
;
bh
=
rq
->
bh
;
do
{
unsigned
char
*
virt_addr
=
bh
->
b_data
;
unsigned
int
size
=
bh
->
b_size
;
while
((
bh
=
bh
->
b_reqnext
)
!=
NULL
)
{
if
((
virt_addr
+
size
)
!=
(
unsigned
char
*
)
bh
->
b_data
)
break
;
size
+=
bh
->
b_size
;
}
memset
(
&
sg
[
nents
],
0
,
sizeof
(
*
sg
));
sg
[
nents
].
address
=
virt_addr
;
sg
[
nents
].
length
=
size
;
nents
++
;
}
while
(
bh
!=
NULL
);
return
pci_map_sg
(
NULL
,
sg
,
nents
,
hwif
->
sg_dma_direction
);
}
nents
=
pci_map_sg
(
NULL
,
sg
,
nents
,
hwif
->
sg_dma_direction
);
static
int
icside_build_dmatable
(
ide_drive_t
*
drive
,
int
reading
)
{
return
HWIF
(
drive
)
->
sg_nents
=
ide_build_sglist
(
HWIF
(
drive
),
HWGROUP
(
drive
)
->
rq
);
hwif
->
sg_nents
=
nents
;
}
/* Teardown mappings after DMA has completed. */
static
void
icside_destroy_dmatable
(
ide_drive_t
*
drive
)
{
struct
scatterlist
*
sg
=
HWIF
(
drive
)
->
sg_table
;
int
nents
=
HWIF
(
drive
)
->
sg_nents
;
pci_unmap_sg
(
NULL
,
sg
,
nents
,
HWIF
(
drive
)
->
sg_dma_direction
);
}
static
int
icside_config_if
(
ide_drive_t
*
drive
,
int
xfer_mode
)
/*
* 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
*/
static
int
icside_set_speed
(
ide_drive_t
*
drive
,
u8
xfer_mode
)
{
int
func
=
ide_dma_off
;
int
on
=
0
,
cycle_time
=
0
,
use_dma_info
=
0
;
switch
(
xfer_mode
)
{
case
XFER_MW_DMA_2
:
/*
* The cycle time is limited to 250ns by the r/w
* pulse width (90ns), however we should still
* have a maximum burst transfer rate of 8MB/s.
* Limit the transfer speed to MW_DMA_2.
*/
drive
->
drive_data
=
250
;
if
(
xfer_mode
>
XFER_MW_DMA_2
)
xfer_mode
=
XFER_MW_DMA_2
;
switch
(
xfer_mode
)
{
case
XFER_MW_DMA_2
:
cycle_time
=
250
;
use_dma_info
=
1
;
break
;
case
XFER_MW_DMA_1
:
drive
->
drive_data
=
250
;
cycle_time
=
250
;
use_dma_info
=
1
;
break
;
case
XFER_MW_DMA_0
:
drive
->
drive_data
=
480
;
cycle_time
=
480
;
break
;
default:
drive
->
drive_data
=
0
;
case
XFER_SW_DMA_2
:
case
XFER_SW_DMA_1
:
case
XFER_SW_DMA_0
:
cycle_time
=
480
;
break
;
}
if
(
!
drive
->
init_speed
)
drive
->
init_speed
=
(
u8
)
xfer_mode
;
/*
* 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
;
if
(
drive
->
drive_data
&&
ide_config_drive_speed
(
drive
,
(
u8
)
xfer_mode
)
==
0
)
func
=
ide_dma_on
;
if
(
cycle_time
&&
ide_config_drive_speed
(
drive
,
xfer_mode
)
==
0
)
on
=
1
;
else
drive
->
drive_data
=
480
;
printk
(
"%s: %s selected (peak %dMB/s)
\n
"
,
drive
->
name
,
ide_xfer_verbose
(
xfer_mode
),
2000
/
drive
->
drive_data
);
drive
->
current_speed
=
(
u8
)
xfer_mode
;
return
func
;
}
drive
->
current_speed
=
xfer_mode
;
static
int
icside_set_speed
(
ide_drive_t
*
drive
,
u8
speed
)
{
return
icside_config_if
(
drive
,
speed
);
}
/*
* dma_intr() is the handler for disk read/write DMA interrupts
*/
static
ide_startstop_t
icside_dmaintr
(
ide_drive_t
*
drive
)
{
u8
dma_stat
=
HWIF
(
drive
)
->
ide_dma_end
(
drive
);
/* get drive status */
u8
stat
=
HWIF
(
drive
)
->
INB
(
IDE_STATUS_REG
);
int
i
;
if
(
OK_STAT
(
stat
,
DRIVE_READY
,
drive
->
bad_wstat
|
DRQ_STAT
))
{
if
(
!
dma_stat
)
{
struct
request
*
rq
=
HWGROUP
(
drive
)
->
rq
;
rq
=
HWGROUP
(
drive
)
->
rq
;
for
(
i
=
rq
->
nr_sectors
;
i
>
0
;)
{
i
-=
rq
->
current_nr_sectors
;
DRIVER
(
drive
)
->
end_request
(
drive
,
1
);
}
return
ide_stopped
;
}
printk
(
"%s: dma_intr: bad DMA status (dma_stat=%x)
\n
"
,
drive
->
name
,
dma_stat
);
}
return
DRIVER
(
drive
)
->
error
(
drive
,
"dma_intr"
,
stat
);
return
on
;
}
/*
...
...
@@ -361,16 +389,16 @@ static ide_startstop_t icside_dmaintr(ide_drive_t *drive)
* This should be defined in one place only.
*/
struct
drive_list_entry
{
char
*
id_model
;
char
*
id_firmware
;
c
onst
c
har
*
id_model
;
c
onst
c
har
*
id_firmware
;
};
static
struct
drive_list_entry
drive_whitelist
[]
=
{
static
const
struct
drive_list_entry
drive_whitelist
[]
=
{
{
"Micropolis 2112A"
,
"ALL"
},
{
"CONNER CTMA 4000"
,
"ALL"
},
{
"CONNER CTT8000-A"
,
"ALL"
},
{
"ST34342A"
,
"ALL"
},
{
NULL
,
0
}
{
NULL
,
NULL
}
};
static
struct
drive_list_entry
drive_blacklist
[]
=
{
...
...
@@ -407,10 +435,11 @@ static struct drive_list_entry drive_blacklist [] = {
{
"PLEXTOR CD-R PX-W8432T"
,
"ALL"
},
{
"ATAPI CD-ROM DRIVE 40X MAXIMUM"
,
"ALL"
},
{
"_NEC DV5800A"
,
"ALL"
},
{
NULL
,
0
}
{
NULL
,
NULL
}
};
static
int
in_drive_list
(
struct
hd_driveid
*
id
,
struct
drive_list_entry
*
drive_table
)
static
int
in_drive_list
(
struct
hd_driveid
*
id
,
const
struct
drive_list_entry
*
drive_table
)
{
for
(
;
drive_table
->
id_model
;
drive_table
++
)
if
((
!
strcmp
(
drive_table
->
id_model
,
id
->
model
))
&&
...
...
@@ -420,41 +449,52 @@ static int in_drive_list(struct hd_driveid *id, struct drive_list_entry * drive_
return
0
;
}
/*
* For both Blacklisted and Whitelisted drives.
* This is setup to be called as an extern for future support
* to other special driver code.
*/
int
check_drive_good_lists
(
ide_drive_t
*
drive
)
static
int
icside_dma_host_off
(
ide_drive_t
*
drive
)
{
struct
hd_driveid
*
id
=
drive
->
id
;
return
in_drive_list
(
id
,
drive_whitelist
);
return
0
;
}
int
check_drive_bad_lists
(
ide_drive_t
*
drive
)
static
int
icside_dma_off_quietly
(
ide_drive_t
*
drive
)
{
struct
hd_driveid
*
id
=
drive
->
id
;
int
blacklist
=
in_drive_list
(
id
,
drive_blacklist
);
if
(
blacklist
)
printk
(
"%s: Disabling DMA for %s
\n
"
,
drive
->
name
,
id
->
model
);
return
(
blacklist
);
drive
->
using_dma
=
0
;
return
icside_dma_host_off
(
drive
);
}
int
icside_dma_check
(
ide_drive_t
*
drive
)
static
int
icside_dma_off
(
ide_drive_t
*
drive
)
{
printk
(
"%s: DMA disabled
\n
"
,
drive
->
name
);
return
icside_dma_off_quietly
(
drive
);
}
static
int
icside_dma_host_on
(
ide_drive_t
*
drive
)
{
return
0
;
}
static
int
icside_dma_on
(
ide_drive_t
*
drive
)
{
drive
->
using_dma
=
1
;
return
icside_dma_host_on
(
drive
);
}
static
int
icside_dma_check
(
ide_drive_t
*
drive
)
{
struct
hd_driveid
*
id
=
drive
->
id
;
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
int
autodma
=
hwif
->
autodma
;
int
xfer_mode
=
XFER_PIO_2
;
int
on
;
if
(
!
id
||
!
(
id
->
capability
&
1
)
||
!
autodma
)
return
hwif
->
ide_dma_off_quietly
(
drive
)
;
if
(
!
id
||
!
(
id
->
capability
&
1
)
||
!
hwif
->
autodma
)
goto
out
;
/*
* Consult the list of known "bad" drives
*/
if
(
check_drive_bad_lists
(
drive
))
return
hwif
->
ide_dma_off
(
drive
);
if
(
in_drive_list
(
id
,
drive_blacklist
))
{
printk
(
"%s: Disabling DMA for %s (blacklisted)
\n
"
,
drive
->
name
,
id
->
model
);
goto
out
;
}
/*
* Enable DMA on any drive that has multiword DMA
...
...
@@ -473,192 +513,249 @@ int icside_dma_check(ide_drive_t *drive)
/*
* Consult the list of known "good" drives
*/
if
(
check_drive_good_lists
(
drive
))
{
if
(
in_drive_list
(
id
,
drive_whitelist
))
{
if
(
id
->
eide_dma_time
>
150
)
goto
out
;
xfer_mode
=
XFER_MW_DMA_1
;
}
out:
if
(
icside_config_if
(
drive
,
xfer_mode
))
return
hwif
->
ide_dma_on
(
drive
);
return
hwif
->
ide_dma_off
(
drive
);
}
on
=
icside_set_speed
(
drive
,
xfer_mode
);
int
icside_dma_verbose
(
ide_drive_t
*
drive
)
{
printk
(
", DMA"
);
return
1
;
if
(
on
)
return
icside_dma_on
(
drive
);
else
return
icside_dma_off_quietly
(
drive
)
;
}
int
icside_dma_test_irq
(
ide_drive_t
*
drive
)
static
int
icside_dma_end
(
ide_drive_t
*
drive
)
{
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
return
inb
((
unsigned
long
)
hwif
->
hw
.
priv
)
&
1
;
}
int
icside_dma_host_off
(
ide_drive_t
*
drive
)
{
return
0
;
}
drive
->
waiting_for_dma
=
0
;
int
icside_dma_off_quietly
(
ide_drive_t
*
drive
)
{
drive
->
using_dma
=
0
;
return
icside_dma_host_off
(
drive
);
}
disable_dma
(
hwif
->
hw
.
dma
);
int
icside_dma_off
(
ide_drive_t
*
drive
)
{
printk
(
"%s: DMA disabled
\n
"
,
drive
->
name
);
return
icside_dma_off_quietly
(
drive
);
}
/* Teardown mappings after DMA has completed. */
pci_unmap_sg
(
NULL
,
hwif
->
sg_table
,
hwif
->
sg_nents
,
hwif
->
sg_dma_direction
);
int
icside_dma_host_on
(
ide_drive_t
*
drive
)
{
return
0
;
}
hwif
->
sg_dma_active
=
0
;
int
icside_dma_on
(
ide_drive_t
*
drive
)
{
drive
->
using_dma
=
1
;
return
icside_dma_host_on
(
drive
);
return
get_dma_residue
(
hwif
->
hw
.
dma
)
!=
0
;
}
int
icside_dma_begin
(
ide_drive_t
*
drive
)
static
int
icside_dma_begin
(
ide_drive_t
*
drive
)
{
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
/* We can not enable DMA on both channels simultaneously. */
BUG_ON
(
dma_channel_active
(
hwif
->
hw
.
dma
));
enable_dma
(
hwif
->
hw
.
dma
);
return
0
;
}
int
icside_dma_end
(
ide_drive_t
*
drive
)
static
int
icside_dma_count
(
ide_drive_t
*
drive
)
{
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
drive
->
waiting_for_dma
=
0
;
disable_dma
(
hwif
->
hw
.
dma
);
icside_destroy_dmatable
(
drive
);
return
get_dma_residue
(
hwif
->
hw
.
dma
)
!=
0
;
return
icside_dma_begin
(
drive
);
}
int
icside_dma_count
(
ide_drive_t
*
drive
)
/*
* dma_intr() is the handler for disk read/write DMA interrupts
*/
static
ide_startstop_t
icside_dmaintr
(
ide_drive_t
*
drive
)
{
return
icside_dma_begin
(
drive
);
unsigned
int
stat
;
int
dma_stat
;
dma_stat
=
icside_dma_end
(
drive
);
stat
=
HWIF
(
drive
)
->
INB
(
IDE_STATUS_REG
);
if
(
OK_STAT
(
stat
,
DRIVE_READY
,
drive
->
bad_wstat
|
DRQ_STAT
))
{
if
(
!
dma_stat
)
{
struct
request
*
rq
=
HWGROUP
(
drive
)
->
rq
;
int
i
;
for
(
i
=
rq
->
nr_sectors
;
i
>
0
;
)
{
i
-=
rq
->
current_nr_sectors
;
DRIVER
(
drive
)
->
end_request
(
drive
,
1
,
rq
->
nr_sectors
);
}
return
ide_stopped
;
}
printk
(
KERN_ERR
"%s: bad DMA status (dma_stat=%x)
\n
"
,
drive
->
name
,
dma_stat
);
}
return
DRIVER
(
drive
)
->
error
(
drive
,
__FUNCTION__
,
stat
);
}
int
icside_dma_read
(
ide_drive_t
*
drive
)
static
int
icside_dma_common
(
ide_drive_t
*
drive
,
struct
request
*
rq
,
unsigned
int
dma_mode
)
{
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
// ide_task_t *args = HWGROUP(drive)->rq->special;
int
count
=
0
;
u8
lba48
=
(
drive
->
addressing
==
1
)
?
1
:
0
;
task_ioreg_t
command
=
WIN_NOP
;
count
=
icside_build_dmatable
(
drive
,
1
);
if
(
!
count
)
return
1
;
disable_dma
(
hwif
->
hw
.
dma
);
/*
* We can not enable DMA on both channels.
*/
BUG_ON
(
hwif
->
sg_dma_active
);
BUG_ON
(
dma_channel_active
(
hwif
->
hw
.
dma
));
ide_build_sglist
(
drive
,
rq
);
/*
Route the DMA signals to
*
to the correct interface
.
/*
*
Ensure that we have the right interrupt routed
.
*/
HWIF
(
drive
)
->
OUTB
(
hwif
->
select_data
,
hwif
->
config_data
);
icside_maskproc
(
drive
,
0
);
/* Select the correct timing
* for this drive
/*
* Route the DMA signals to the correct interface.
*/
outb
(
hwif
->
select_data
,
hwif
->
config_data
);
/*
* Select the correct timing for this drive.
*/
set_dma_speed
(
hwif
->
hw
.
dma
,
drive
->
drive_data
);
set_dma_sg
(
hwif
->
hw
.
dma
,
HWIF
(
drive
)
->
sg_table
,
count
);
set_dma_mode
(
hwif
->
hw
.
dma
,
DMA_MODE_READ
);
/*
* Tell the DMA engine about the SG table and
* data direction.
*/
set_dma_sg
(
hwif
->
hw
.
dma
,
hwif
->
sg_table
,
hwif
->
sg_nents
);
set_dma_mode
(
hwif
->
hw
.
dma
,
dma_mode
);
return
0
;
}
static
int
icside_dma_read
(
ide_drive_t
*
drive
)
{
struct
request
*
rq
=
HWGROUP
(
drive
)
->
rq
;
task_ioreg_t
cmd
=
WIN_NOP
;
if
(
icside_dma_common
(
drive
,
rq
,
DMA_MODE_READ
))
return
1
;
drive
->
waiting_for_dma
=
1
;
if
(
drive
->
media
!=
ide_disk
)
return
0
;
if
(
HWGROUP
(
drive
)
->
handler
!=
NULL
)
/* paranoia check */
BUG
();
ide_set_handler
(
drive
,
&
icside_dmaintr
,
WAIT_CMD
,
NULL
);
BUG_ON
(
HWGROUP
(
drive
)
->
handler
!=
NULL
);
ide_set_handler
(
drive
,
icside_dmaintr
,
2
*
WAIT_CMD
,
NULL
);
/*
* FIX ME to use only ACB ide_task_t args Struct
*/
#if 0
{
ide_task_t *args =
HWGROUP(drive)->
rq->special;
ide_task_t *args = rq->special;
command = args->tfRegister[IDE_COMMAND_OFFSET];
}
#else
command
=
(
lba48
)
?
WIN_READDMA_EXT
:
WIN_READDMA
;
if
(
HWGROUP
(
drive
)
->
rq
->
flags
&
REQ_DRIVE_TASKFILE
)
{
ide_task_t
*
args
=
HWGROUP
(
drive
)
->
rq
->
special
;
command
=
args
->
tfRegister
[
IDE_COMMAND_OFFSET
];
if
(
rq
->
flags
&
REQ_DRIVE_TASKFILE
)
{
ide_task_t
*
args
=
rq
->
special
;
cmd
=
args
->
tfRegister
[
IDE_COMMAND_OFFSET
];
}
else
if
(
drive
->
addressing
==
1
)
{
cmd
=
WIN_READDMA_EXT
;
}
else
{
cmd
=
WIN_READDMA
;
}
#endif
/* issue cmd to drive */
HWIF
(
drive
)
->
OUTB
(
c
omman
d
,
IDE_COMMAND_REG
);
HWIF
(
drive
)
->
OUTB
(
c
m
d
,
IDE_COMMAND_REG
);
return
icside_dma_
count
(
drive
);
return
icside_dma_
begin
(
drive
);
}
int
icside_dma_write
(
ide_drive_t
*
drive
)
{
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
// ide_task_t *args = HWGROUP(drive)->rq->special;
int
count
=
0
;
u8
lba48
=
(
drive
->
addressing
==
1
)
?
1
:
0
;
task_ioreg_t
command
=
WIN_NOP
;
struct
request
*
rq
=
HWGROUP
(
drive
)
->
rq
;
task_ioreg_t
cmd
=
WIN_NOP
;
count
=
icside_build_dmatable
(
drive
,
0
);
if
(
!
count
)
if
(
icside_dma_common
(
drive
,
rq
,
DMA_MODE_WRITE
))
return
1
;
disable_dma
(
hwif
->
hw
.
dma
);
/* Route the DMA signals to
* to the correct interface.
*/
HWIF
(
drive
)
->
OUTB
(
hwif
->
select_data
,
hwif
->
config_data
);
/* Select the correct timing
* for this drive
*/
set_dma_speed
(
hwif
->
hw
.
dma
,
drive
->
drive_data
);
set_dma_sg
(
hwif
->
hw
.
dma
,
HWIF
(
drive
)
->
sg_table
,
count
);
set_dma_mode
(
hwif
->
hw
.
dma
,
DMA_MODE_WRITE
);
drive
->
waiting_for_dma
=
1
;
if
(
drive
->
media
!=
ide_disk
)
return
0
;
if
(
HWGROUP
(
drive
)
->
handler
!=
NULL
)
BUG
();
ide_set_handler
(
drive
,
&
icside_dmaintr
,
WAIT_CMD
,
NULL
);
BUG_ON
(
HWGROUP
(
drive
)
->
handler
!=
NULL
);
ide_set_handler
(
drive
,
icside_dmaintr
,
2
*
WAIT_CMD
,
NULL
);
/*
* FIX ME to use only ACB ide_task_t args Struct
*/
#if 0
{
ide_task_t *args =
HWGROUP(drive)->
rq->special;
ide_task_t *args = rq->special;
command = args->tfRegister[IDE_COMMAND_OFFSET];
}
#else
command
=
(
lba48
)
?
WIN_WRITEDMA_EXT
:
WIN_WRITEDMA
;
if
(
HWGROUP
(
drive
)
->
rq
->
flags
&
REQ_DRIVE_TASKFILE
)
{
ide_task_t
*
args
=
HWGROUP
(
drive
)
->
rq
->
special
;
command
=
args
->
tfRegister
[
IDE_COMMAND_OFFSET
];
if
(
rq
->
flags
&
REQ_DRIVE_TASKFILE
)
{
ide_task_t
*
args
=
rq
->
special
;
cmd
=
args
->
tfRegister
[
IDE_COMMAND_OFFSET
];
}
else
if
(
drive
->
addressing
==
1
)
{
cmd
=
WIN_WRITEDMA_EXT
;
}
else
{
cmd
=
WIN_WRITEDMA
;
}
#endif
/* issue cmd to drive */
HWIF
(
drive
)
->
OUTB
(
c
omman
d
,
IDE_COMMAND_REG
);
HWIF
(
drive
)
->
OUTB
(
c
m
d
,
IDE_COMMAND_REG
);
return
icside_dma_
count
(
drive
);
return
icside_dma_
begin
(
drive
);
}
static
int
icside_setup_dma
(
ide_hwif_t
*
hwif
,
int
autodma
)
static
int
icside_dma_test_irq
(
ide_drive_t
*
drive
)
{
ide_hwif_t
*
hwif
=
HWIF
(
drive
);
struct
icside_state
*
state
=
hwif
->
hw
.
priv
;
return
inb
(
state
->
irq_port
+
(
hwif
->
channel
?
ICS_ARCIN_V6_INTRSTAT_2
:
ICS_ARCIN_V6_INTRSTAT_1
))
&
1
;
}
static
int
icside_dma_verbose
(
ide_drive_t
*
drive
)
{
printk
(
", %s (peak %dMB/s)"
,
ide_xfer_verbose
(
drive
->
current_speed
),
2000
/
drive
->
drive_data
);
return
1
;
}
static
int
icside_dma_timeout
(
ide_drive_t
*
drive
)
{
printk
(
KERN_ERR
"%s: DMA timeout occured: "
,
drive
->
name
);
if
(
icside_dma_test_irq
(
drive
))
return
0
;
ide_dump_status
(
drive
,
"DMA timeout"
,
HWIF
(
drive
)
->
INB
(
IDE_STATUS_REG
));
return
icside_dma_end
(
drive
);
}
static
int
icside_dma_lostirq
(
ide_drive_t
*
drive
)
{
printk
(
KERN_ERR
"%s: IRQ lost
\n
"
,
drive
->
name
);
return
1
;
}
static
int
icside_setup_dma
(
ide_hwif_t
*
hwif
)
{
int
autodma
=
0
;
#ifdef CONFIG_IDEDMA_ICS_AUTO
autodma
=
1
;
#endif
printk
(
" %s: SG-DMA"
,
hwif
->
name
);
hwif
->
sg_table
=
kmalloc
(
sizeof
(
struct
scatterlist
)
*
NR_ENTRIES
,
...
...
@@ -682,24 +779,34 @@ icside_setup_dma(ide_hwif_t *hwif, int autodma)
hwif
->
ide_dma_count
=
icside_dma_count
;
hwif
->
ide_dma_begin
=
icside_dma_begin
;
hwif
->
ide_dma_end
=
icside_dma_end
;
hwif
->
ide_dma_verbose
=
icside_dma_verbose
;
hwif
->
ide_dma_bad_drive
=
check_drive_bad_lists
;
hwif
->
ide_dma_good_drive
=
check_drive_good_lists
;
hwif
->
ide_dma_test_irq
=
icside_dma_test_irq
;
hwif
->
ide_dma_verbose
=
icside_dma_verbose
;
hwif
->
ide_dma_timeout
=
icside_dma_timeout
;
hwif
->
ide_dma_lostirq
=
icside_dma_lostirq
;
printk
(
" capable%s
\n
"
,
autodma
?
", auto-enable"
:
""
);
hwif
->
drives
[
0
].
autodma
=
autodma
;
hwif
->
drives
[
1
].
autodma
=
autodma
;
printk
(
" capable%s
\n
"
,
autodma
?
", auto-enable"
:
""
);
return
1
;
failed:
printk
(
"
-- ERROR
, unable to allocate DMA table
\n
"
);
printk
(
"
disabled
, unable to allocate DMA table
\n
"
);
return
0
;
}
int
ide_release_dma
(
ide_hwif_t
*
hwif
)
{
if
(
hwif
->
sg_table
)
{
kfree
(
hwif
->
sg_table
);
hwif
->
sg_table
=
NULL
;
}
return
1
;
}
#endif
static
ide_hwif_t
*
icside_find_hwif
(
unsigned
long
dataport
)
static
ide_hwif_t
*
icside_find_hwif
(
unsigned
long
dataport
)
{
ide_hwif_t
*
hwif
;
int
index
;
...
...
@@ -716,7 +823,7 @@ icside_find_hwif(unsigned long dataport)
goto
found
;
}
return
NULL
;
hwif
=
NULL
;
found:
return
hwif
;
}
...
...
@@ -750,7 +857,7 @@ icside_setup(unsigned long base, struct cardinfo *info, int irq)
return
hwif
;
}
static
int
__init
icside_register_v5
(
struct
expansion_card
*
ec
,
int
autodma
)
static
int
__init
icside_register_v5
(
struct
expansion_card
*
ec
)
{
unsigned
long
slot_port
;
ide_hwif_t
*
hwif
;
...
...
@@ -769,14 +876,15 @@ static int __init icside_register_v5(struct expansion_card *ec, int autodma)
hwif
=
icside_setup
(
slot_port
,
&
icside_cardinfo_v5
,
ec
->
irq
);
return
hwif
?
0
:
-
1
;
return
hwif
?
0
:
-
ENODEV
;
}
static
int
__init
icside_register_v6
(
struct
expansion_card
*
ec
,
int
autodma
)
static
int
__init
icside_register_v6
(
struct
expansion_card
*
ec
)
{
unsigned
long
slot_port
,
port
;
struct
icside_state
*
state
;
ide_hwif_t
*
hwif
,
*
mate
;
int
sel
=
0
;
unsigned
int
sel
=
0
;
slot_port
=
ecard_address
(
ec
,
ECARD_IOC
,
ECARD_FAST
);
port
=
ecard_address
(
ec
,
ECARD_EASI
,
ECARD_FAST
);
...
...
@@ -788,88 +896,128 @@ static int __init icside_register_v6(struct expansion_card *ec, int autodma)
outb
(
sel
,
slot_port
);
ec
->
irq_data
=
(
void
*
)
port
;
ec
->
ops
=
(
expansioncard_ops_t
*
)
&
icside_ops_arcin_v6
;
/*
* Be on the safe side - disable interrupts
*/
inb
(
port
+
ICS_ARCIN_V6_INTROFFSET_1
);
inb
(
port
+
ICS_ARCIN_V6_INTROFFSET_2
);
/*
* Find and register the interfaces.
*/
hwif
=
icside_setup
(
port
,
&
icside_cardinfo_v6_1
,
ec
->
irq
);
mate
=
icside_setup
(
port
,
&
icside_cardinfo_v6_2
,
ec
->
irq
);
#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
if
(
ec
->
dma
!=
NO_DMA
)
{
if
(
request_dma
(
ec
->
dma
,
hwif
->
name
))
goto
no_dma
;
if
(
!
hwif
||
!
mate
)
return
-
ENODEV
;
if
(
hwif
)
{
state
=
kmalloc
(
sizeof
(
struct
icside_state
),
GFP_KERNEL
);
if
(
!
state
)
return
-
ENOMEM
;
state
->
channel
=
0
;
state
->
enabled
=
0
;
state
->
irq_port
=
port
;
ec
->
irq_data
=
state
;
ec
->
ops
=
(
expansioncard_ops_t
*
)
&
icside_ops_arcin_v6
;
hwif
->
maskproc
=
icside_maskproc
;
hwif
->
channel
=
0
;
hwif
->
hw
.
priv
=
state
;
hwif
->
mate
=
mate
;
hwif
->
serialized
=
1
;
hwif
->
config_data
=
slot_port
;
hwif
->
select_data
=
sel
;
hwif
->
hw
.
dma
=
ec
->
dma
;
hwif
->
hw
.
priv
=
(
void
*
)
(
port
+
ICS_ARCIN_V6_INTRSTAT_1
);
hwif
->
channel
=
0
;
icside_setup_dma
(
hwif
,
autodma
);
hwif
->
drives
[
0
].
autodma
=
autodma
;
hwif
->
drives
[
1
].
autodma
=
autodma
;
}
if
(
mate
)
{
mate
->
maskproc
=
icside_maskproc
;
mate
->
channel
=
1
;
mate
->
hw
.
priv
=
state
;
mate
->
mate
=
hwif
;
mate
->
serialized
=
1
;
mate
->
config_data
=
slot_port
;
mate
->
select_data
=
sel
|
1
;
mate
->
hw
.
dma
=
ec
->
dma
;
mate
->
hw
.
priv
=
(
void
*
)
(
port
+
ICS_ARCIN_V6_INTRSTAT_2
);
mate
->
channel
=
1
;
icside_setup_dma
(
mate
,
autodma
);
mate
->
drives
[
0
].
autodma
=
autodma
;
mate
->
drives
[
1
].
autodma
=
autodma
;
}
#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
if
(
ec
->
dma
!=
NO_DMA
&&
!
request_dma
(
ec
->
dma
,
hwif
->
name
))
{
icside_setup_dma
(
hwif
);
icside_setup_dma
(
mate
);
}
no_dma:
#endif
return
hwif
||
mate
?
0
:
-
1
;
return
0
;
}
int
__init
icside_init
(
void
)
static
int
__devinit
icside_probe
(
struct
expansion_card
*
ec
,
const
struct
ecard_id
*
id
)
{
int
autodma
=
0
;
#ifdef CONFIG_IDEDMA_ICS_AUTO
autodma
=
1
;
#endif
ecard_startfind
();
do
{
struct
expansion_card
*
ec
;
int
result
;
ec
=
ecard_find
(
0
,
icside_cids
);
if
(
ec
==
NULL
)
break
;
ecard_claim
(
ec
);
switch
(
icside_identifyif
(
ec
))
{
case
ics_if_arcin_v5
:
result
=
icside_register_v5
(
ec
,
autodma
);
result
=
icside_register_v5
(
ec
);
break
;
case
ics_if_arcin_v6
:
result
=
icside_register_v6
(
ec
,
autodma
);
result
=
icside_register_v6
(
ec
);
break
;
default:
result
=
-
1
;
result
=
-
ENODEV
;
break
;
}
if
(
result
)
ecard_release
(
ec
);
}
while
(
1
);
/*
* this locks the driver in-core - remove this
* comment and the two lines below when we can
* safely remove interfaces.
*/
else
MOD_INC_USE_COUNT
;
return
0
;
return
result
;
}
static
void
__devexit
icside_remove
(
struct
expansion_card
*
ec
)
{
/* need to do more */
ecard_release
(
ec
);
}
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
),
.
id_table
=
icside_ids
,
.
drv
=
{
.
name
=
"icside"
,
},
};
static
int
__init
icside_init
(
void
)
{
return
ecard_register_driver
(
&
icside_driver
);
}
static
void
__exit
icside_exit
(
void
)
{
ecard_remove_driver
(
&
icside_driver
);
}
MODULE_AUTHOR
(
"Russell King <rmk@arm.linux.org.uk>"
);
MODULE_LICENSE
(
"GPL"
);
MODULE_DESCRIPTION
(
"ICS IDE driver"
);
module_init
(
icside_init
);
module_exit
(
icside_exit
);
drivers/ide/arm/rapide.c
View file @
caaaf123
/*
* linux/drivers/ide/rapide.c
*
* Copyright (c) 1996-1998 Russell King.
*
* Changelog:
* 08-06-1996 RMK Created
* 13-04-1998 RMK Added manufacturer and product IDs
* Copyright (c) 1996-2002 Russell King.
*/
#include <linux/module.h>
...
...
@@ -17,20 +13,14 @@
#include <asm/ecard.h>
static
card_ids
__init
rapide_cids
[]
=
{
{
MANU_YELLOWSTONE
,
PROD_YELLOWSTONE_RAPIDE32
},
{
0xffff
,
0xffff
}
};
static
struct
expansion_card
*
ec
[
MAX_ECARDS
];
static
int
result
[
MAX_ECARDS
];
static
inline
int
rapide_register
(
struct
expansion_card
*
ec
)
static
int
__devinit
rapide_probe
(
struct
expansion_card
*
ec
,
const
struct
ecard_id
*
id
)
{
unsigned
long
port
=
ecard_address
(
ec
,
ECARD_MEMC
,
0
);
hw_regs_t
hw
;
int
i
,
ret
;
int
i
;
ecard_claim
(
ec
)
;
memset
(
&
hw
,
0
,
sizeof
(
hw
));
...
...
@@ -41,54 +31,53 @@ static inline int rapide_register(struct expansion_card *ec)
hw
.
io_ports
[
IDE_CONTROL_OFFSET
]
=
port
+
0x206
;
hw
.
irq
=
ec
->
irq
;
return
ide_register_hw
(
&
hw
,
NULL
);
}
ret
=
ide_register_hw
(
&
hw
,
NULL
);
int
__init
rapide_init
(
void
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_ECARDS
;
i
++
)
ec
[
i
]
=
NULL
;
ecard_startfind
();
if
(
ret
)
ecard_release
(
ec
);
/*
* this locks the driver in-core - remove this
* comment and the two lines below when we can
* safely remove interfaces.
*/
else
MOD_INC_USE_COUNT
;
for
(
i
=
0
;
;
i
++
)
{
if
((
ec
[
i
]
=
ecard_find
(
0
,
rapide_cids
))
==
NULL
)
break
;
return
ret
;
}
ecard_claim
(
ec
[
i
]);
result
[
i
]
=
rapide_register
(
ec
[
i
]);
}
for
(
i
=
0
;
i
<
MAX_ECARDS
;
i
++
)
if
(
ec
[
i
]
&&
result
[
i
]
<
0
)
{
ecard_release
(
ec
[
i
]);
ec
[
i
]
=
NULL
;
}
return
0
;
static
void
__devexit
rapide_remove
(
struct
expansion_card
*
ec
)
{
/* need to do more */
ecard_release
(
ec
);
}
#ifdef MODULE
MODULE_LICENSE
(
"GPL"
);
static
struct
ecard_id
rapide_ids
[]
=
{
{
MANU_YELLOWSTONE
,
PROD_YELLOWSTONE_RAPIDE32
},
{
0xffff
,
0xffff
}
};
int
init_module
(
void
)
static
struct
ecard_driver
rapide_driver
=
{
.
probe
=
rapide_probe
,
.
remove
=
__devexit_p
(
rapide_remove
),
.
id_table
=
rapide_ids
,
.
drv
=
{
.
name
=
"rapide"
,
},
};
static
int
__init
rapide_init
(
void
)
{
return
rapide_init
(
);
return
ecard_register_driver
(
&
rapide_driver
);
}
void
cleanup_module
(
void
)
static
void
__exit
rapide_exit
(
void
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_ECARDS
;
i
++
)
if
(
ec
[
i
])
{
unsigned
long
port
;
port
=
ecard_address
(
ec
[
i
],
ECARD_MEMC
,
0
);
ide_unregister_port
(
port
,
ec
[
i
]
->
irq
,
16
);
ecard_release
(
ec
[
i
]);
ec
[
i
]
=
NULL
;
}
ecard_remove_driver
(
&
rapide_driver
);
}
#endif
MODULE_LICENSE
(
"GPL"
);
MODULE_DESCRIPTION
(
"Yellowstone RAPIDE driver"
);
module_init
(
rapide_init
);
module_exit
(
rapide_exit
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment