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
nexedi
linux
Commits
49936b5e
Commit
49936b5e
authored
Jan 21, 2005
by
Linus Torvalds
Browse files
Options
Browse Files
Download
Plain Diff
Merge
bk://bk.arm.linux.org.uk/linux-2.6-mmc
into ppc970.osdl.org:/home/torvalds/v2.6/linux
parents
d02ba476
30271774
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
162 additions
and
104 deletions
+162
-104
drivers/mmc/wbsd.c
drivers/mmc/wbsd.c
+155
-98
drivers/mmc/wbsd.h
drivers/mmc/wbsd.h
+7
-6
No files found.
drivers/mmc/wbsd.c
View file @
49936b5e
/*
/*
* linux/drivers/mmc/wbsd.c
* linux/drivers/mmc/wbsd.c
- Winbond W83L51xD SD/MMC driver
*
*
* Copyright (C) 2004 Pierre Ossman, All Rights Reserved.
* Copyright (C) 2004
-2005
Pierre Ossman, All Rights Reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
* published by the Free Software Foundation.
*
*
* Warning!
*
* Changes to the FIFO system should be done with extreme care since
* the hardware is full of bugs related to the FIFO. Known issues are:
*
* - FIFO size field in FSR is always zero.
*
* - FIFO interrupts tend not to work as they should. Interrupts are
* triggered only for full/empty events, not for threshold values.
*
* - On APIC systems the FIFO empty interrupt is sometimes lost.
*/
*/
#include <linux/config.h>
#include <linux/config.h>
...
@@ -27,7 +40,7 @@
...
@@ -27,7 +40,7 @@
#include "wbsd.h"
#include "wbsd.h"
#define DRIVER_NAME "wbsd"
#define DRIVER_NAME "wbsd"
#define DRIVER_VERSION "1.
0
"
#define DRIVER_VERSION "1.
1
"
#ifdef CONFIG_MMC_DEBUG
#ifdef CONFIG_MMC_DEBUG
#define DBG(x...) \
#define DBG(x...) \
...
@@ -237,13 +250,14 @@ static inline int wbsd_next_sg(struct wbsd_host* host)
...
@@ -237,13 +250,14 @@ static inline int wbsd_next_sg(struct wbsd_host* host)
static
inline
char
*
wbsd_kmap_sg
(
struct
wbsd_host
*
host
)
static
inline
char
*
wbsd_kmap_sg
(
struct
wbsd_host
*
host
)
{
{
return
kmap_atomic
(
host
->
cur_sg
->
page
,
KM_BIO_SRC_IRQ
)
+
host
->
mapped_sg
=
kmap_atomic
(
host
->
cur_sg
->
page
,
KM_BIO_SRC_IRQ
)
+
host
->
cur_sg
->
offset
;
host
->
cur_sg
->
offset
;
return
host
->
mapped_sg
;
}
}
static
inline
void
wbsd_kunmap_sg
(
struct
wbsd_host
*
host
)
static
inline
void
wbsd_kunmap_sg
(
struct
wbsd_host
*
host
)
{
{
kunmap_atomic
(
host
->
cur_sg
->
page
,
KM_BIO_SRC_IRQ
);
kunmap_atomic
(
host
->
mapped_sg
,
KM_BIO_SRC_IRQ
);
}
}
static
inline
void
wbsd_sg_to_dma
(
struct
wbsd_host
*
host
,
struct
mmc_data
*
data
)
static
inline
void
wbsd_sg_to_dma
(
struct
wbsd_host
*
host
,
struct
mmc_data
*
data
)
...
@@ -270,7 +284,7 @@ static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data)
...
@@ -270,7 +284,7 @@ static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data)
memcpy
(
dmabuf
,
sgbuf
,
size
);
memcpy
(
dmabuf
,
sgbuf
,
size
);
else
else
memcpy
(
dmabuf
,
sgbuf
,
sg
[
i
].
length
);
memcpy
(
dmabuf
,
sgbuf
,
sg
[
i
].
length
);
kunmap_atomic
(
sg
[
i
].
page
,
KM_BIO_SRC_IRQ
);
kunmap_atomic
(
sg
buf
,
KM_BIO_SRC_IRQ
);
dmabuf
+=
sg
[
i
].
length
;
dmabuf
+=
sg
[
i
].
length
;
if
(
size
<
sg
[
i
].
length
)
if
(
size
<
sg
[
i
].
length
)
...
@@ -316,7 +330,7 @@ static inline void wbsd_dma_to_sg(struct wbsd_host* host, struct mmc_data* data)
...
@@ -316,7 +330,7 @@ static inline void wbsd_dma_to_sg(struct wbsd_host* host, struct mmc_data* data)
memcpy
(
sgbuf
,
dmabuf
,
size
);
memcpy
(
sgbuf
,
dmabuf
,
size
);
else
else
memcpy
(
sgbuf
,
dmabuf
,
sg
[
i
].
length
);
memcpy
(
sgbuf
,
dmabuf
,
sg
[
i
].
length
);
kunmap_atomic
(
sg
[
i
].
page
,
KM_BIO_SRC_IRQ
);
kunmap_atomic
(
sg
buf
,
KM_BIO_SRC_IRQ
);
dmabuf
+=
sg
[
i
].
length
;
dmabuf
+=
sg
[
i
].
length
;
if
(
size
<
sg
[
i
].
length
)
if
(
size
<
sg
[
i
].
length
)
...
@@ -398,16 +412,16 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs);
...
@@ -398,16 +412,16 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs);
static
void
wbsd_send_command
(
struct
wbsd_host
*
host
,
struct
mmc_command
*
cmd
)
static
void
wbsd_send_command
(
struct
wbsd_host
*
host
,
struct
mmc_command
*
cmd
)
{
{
int
i
;
int
i
;
u8
status
,
eir
,
isr
;
u8
status
,
isr
;
DBGF
(
"Sending cmd (%x)
\n
"
,
cmd
->
opcode
);
DBGF
(
"Sending cmd (%x)
\n
"
,
cmd
->
opcode
);
/*
/*
* Disable interrupts as the interrupt routine
* Clear accumulated ISR. The interrupt routine
* will destroy the contents of ISR.
* will fill this one with events that occur during
* transfer.
*/
*/
eir
=
inb
(
host
->
base
+
WBSD_EIR
);
host
->
isr
=
0
;
outb
(
0
,
host
->
base
+
WBSD_EIR
);
/*
/*
* Send the command (CRC calculated by host).
* Send the command (CRC calculated by host).
...
@@ -433,7 +447,7 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
...
@@ -433,7 +447,7 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
/*
/*
* Read back status.
* Read back status.
*/
*/
isr
=
inb
(
host
->
base
+
WBSD_ISR
)
;
isr
=
host
->
isr
;
/* Card removed? */
/* Card removed? */
if
(
isr
&
WBSD_INT_CARD
)
if
(
isr
&
WBSD_INT_CARD
)
...
@@ -454,17 +468,6 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
...
@@ -454,17 +468,6 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
}
}
}
}
/*
* Restore interrupt mask to previous value.
*/
outb
(
eir
,
host
->
base
+
WBSD_EIR
);
/*
* Call the interrupt routine to jump start
* interrupts.
*/
wbsd_irq
(
0
,
host
,
NULL
);
DBGF
(
"Sent cmd (%x), res %d
\n
"
,
cmd
->
opcode
,
cmd
->
error
);
DBGF
(
"Sent cmd (%x), res %d
\n
"
,
cmd
->
opcode
,
cmd
->
error
);
}
}
...
@@ -476,6 +479,7 @@ static void wbsd_empty_fifo(struct wbsd_host* host)
...
@@ -476,6 +479,7 @@ static void wbsd_empty_fifo(struct wbsd_host* host)
{
{
struct
mmc_data
*
data
=
host
->
mrq
->
cmd
->
data
;
struct
mmc_data
*
data
=
host
->
mrq
->
cmd
->
data
;
char
*
buffer
;
char
*
buffer
;
int
i
,
fsr
,
fifo
;
/*
/*
* Handle excessive data.
* Handle excessive data.
...
@@ -489,7 +493,20 @@ static void wbsd_empty_fifo(struct wbsd_host* host)
...
@@ -489,7 +493,20 @@ static void wbsd_empty_fifo(struct wbsd_host* host)
* Drain the fifo. This has a tendency to loop longer
* Drain the fifo. This has a tendency to loop longer
* than the FIFO length (usually one block).
* than the FIFO length (usually one block).
*/
*/
while
(
!
(
inb
(
host
->
base
+
WBSD_FSR
)
&
WBSD_FIFO_EMPTY
))
while
(
!
((
fsr
=
inb
(
host
->
base
+
WBSD_FSR
))
&
WBSD_FIFO_EMPTY
))
{
/*
* The size field in the FSR is broken so we have to
* do some guessing.
*/
if
(
fsr
&
WBSD_FIFO_FULL
)
fifo
=
16
;
else
if
(
fsr
&
WBSD_FIFO_FUTHRE
)
fifo
=
8
;
else
fifo
=
1
;
for
(
i
=
0
;
i
<
fifo
;
i
++
)
{
{
*
buffer
=
inb
(
host
->
base
+
WBSD_DFR
);
*
buffer
=
inb
(
host
->
base
+
WBSD_DFR
);
buffer
++
;
buffer
++
;
...
@@ -535,14 +552,24 @@ static void wbsd_empty_fifo(struct wbsd_host* host)
...
@@ -535,14 +552,24 @@ static void wbsd_empty_fifo(struct wbsd_host* host)
buffer
=
wbsd_kmap_sg
(
host
);
buffer
=
wbsd_kmap_sg
(
host
);
}
}
}
}
}
wbsd_kunmap_sg
(
host
);
wbsd_kunmap_sg
(
host
);
/*
* This is a very dirty hack to solve a
* hardware problem. The chip doesn't trigger
* FIFO threshold interrupts properly.
*/
if
((
host
->
size
-
data
->
bytes_xfered
)
<
16
)
tasklet_schedule
(
&
host
->
fifo_tasklet
);
}
}
static
void
wbsd_fill_fifo
(
struct
wbsd_host
*
host
)
static
void
wbsd_fill_fifo
(
struct
wbsd_host
*
host
)
{
{
struct
mmc_data
*
data
=
host
->
mrq
->
cmd
->
data
;
struct
mmc_data
*
data
=
host
->
mrq
->
cmd
->
data
;
char
*
buffer
;
char
*
buffer
;
int
i
,
fsr
,
fifo
;
/*
/*
* Check that we aren't being called after the
* Check that we aren't being called after the
...
@@ -557,7 +584,20 @@ static void wbsd_fill_fifo(struct wbsd_host* host)
...
@@ -557,7 +584,20 @@ static void wbsd_fill_fifo(struct wbsd_host* host)
* Fill the fifo. This has a tendency to loop longer
* Fill the fifo. This has a tendency to loop longer
* than the FIFO length (usually one block).
* than the FIFO length (usually one block).
*/
*/
while
(
!
(
inb
(
host
->
base
+
WBSD_FSR
)
&
WBSD_FIFO_FULL
))
while
(
!
((
fsr
=
inb
(
host
->
base
+
WBSD_FSR
))
&
WBSD_FIFO_FULL
))
{
/*
* The size field in the FSR is broken so we have to
* do some guessing.
*/
if
(
fsr
&
WBSD_FIFO_EMPTY
)
fifo
=
0
;
else
if
(
fsr
&
WBSD_FIFO_EMTHRE
)
fifo
=
8
;
else
fifo
=
15
;
for
(
i
=
16
;
i
>
fifo
;
i
--
)
{
{
outb
(
*
buffer
,
host
->
base
+
WBSD_DFR
);
outb
(
*
buffer
,
host
->
base
+
WBSD_DFR
);
buffer
++
;
buffer
++
;
...
@@ -603,6 +643,7 @@ static void wbsd_fill_fifo(struct wbsd_host* host)
...
@@ -603,6 +643,7 @@ static void wbsd_fill_fifo(struct wbsd_host* host)
buffer
=
wbsd_kmap_sg
(
host
);
buffer
=
wbsd_kmap_sg
(
host
);
}
}
}
}
}
wbsd_kunmap_sg
(
host
);
wbsd_kunmap_sg
(
host
);
}
}
...
@@ -687,9 +728,9 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
...
@@ -687,9 +728,9 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
disable_dma
(
host
->
dma
);
disable_dma
(
host
->
dma
);
clear_dma_ff
(
host
->
dma
);
clear_dma_ff
(
host
->
dma
);
if
(
data
->
flags
&
MMC_DATA_READ
)
if
(
data
->
flags
&
MMC_DATA_READ
)
set_dma_mode
(
host
->
dma
,
DMA_MODE_READ
);
set_dma_mode
(
host
->
dma
,
DMA_MODE_READ
&
~
0x40
);
else
else
set_dma_mode
(
host
->
dma
,
DMA_MODE_WRITE
);
set_dma_mode
(
host
->
dma
,
DMA_MODE_WRITE
&
~
0x40
);
set_dma_addr
(
host
->
dma
,
host
->
dma_addr
);
set_dma_addr
(
host
->
dma
,
host
->
dma_addr
);
set_dma_count
(
host
->
dma
,
host
->
size
);
set_dma_count
(
host
->
dma
,
host
->
size
);
...
@@ -699,8 +740,7 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
...
@@ -699,8 +740,7 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
/*
/*
* Enable DMA on the host.
* Enable DMA on the host.
*/
*/
wbsd_write_index
(
host
,
WBSD_IDX_DMA
,
wbsd_write_index
(
host
,
WBSD_IDX_DMA
,
WBSD_DMA_ENABLE
);
WBSD_DMA_SINGLE
|
WBSD_DMA_ENABLE
);
}
}
else
else
{
{
...
@@ -744,6 +784,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
...
@@ -744,6 +784,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
{
{
unsigned
long
dmaflags
;
unsigned
long
dmaflags
;
int
count
;
int
count
;
u8
status
;
WARN_ON
(
host
->
mrq
==
NULL
);
WARN_ON
(
host
->
mrq
==
NULL
);
...
@@ -753,6 +794,15 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
...
@@ -753,6 +794,15 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
if
(
data
->
stop
)
if
(
data
->
stop
)
wbsd_send_command
(
host
,
data
->
stop
);
wbsd_send_command
(
host
,
data
->
stop
);
/*
* Wait for the controller to leave data
* transfer state.
*/
do
{
status
=
wbsd_read_index
(
host
,
WBSD_IDX_STATUS
);
}
while
(
status
&
(
WBSD_BLOCK_READ
|
WBSD_BLOCK_WRITE
));
/*
/*
* DMA transfer?
* DMA transfer?
*/
*/
...
@@ -850,6 +900,12 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
...
@@ -850,6 +900,12 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
*/
*/
if
(
cmd
->
data
&&
(
cmd
->
error
==
MMC_ERR_NONE
))
if
(
cmd
->
data
&&
(
cmd
->
error
==
MMC_ERR_NONE
))
{
{
/*
* Dirty fix for hardware bug.
*/
if
(
host
->
dma
==
-
1
)
tasklet_schedule
(
&
host
->
fifo_tasklet
);
spin_unlock_bh
(
&
host
->
lock
);
spin_unlock_bh
(
&
host
->
lock
);
return
;
return
;
...
@@ -1019,7 +1075,6 @@ static void wbsd_tasklet_crc(unsigned long param)
...
@@ -1019,7 +1075,6 @@ static void wbsd_tasklet_crc(unsigned long param)
spin_lock
(
&
host
->
lock
);
spin_lock
(
&
host
->
lock
);
WARN_ON
(
!
host
->
mrq
);
if
(
!
host
->
mrq
)
if
(
!
host
->
mrq
)
goto
end
;
goto
end
;
...
@@ -1044,7 +1099,6 @@ static void wbsd_tasklet_timeout(unsigned long param)
...
@@ -1044,7 +1099,6 @@ static void wbsd_tasklet_timeout(unsigned long param)
spin_lock
(
&
host
->
lock
);
spin_lock
(
&
host
->
lock
);
WARN_ON
(
!
host
->
mrq
);
if
(
!
host
->
mrq
)
if
(
!
host
->
mrq
)
goto
end
;
goto
end
;
...
@@ -1125,13 +1179,15 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs)
...
@@ -1125,13 +1179,15 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs)
if
(
isr
==
0xff
||
isr
==
0x00
)
if
(
isr
==
0xff
||
isr
==
0x00
)
return
IRQ_NONE
;
return
IRQ_NONE
;
host
->
isr
|=
isr
;
/*
/*
* Schedule tasklets as needed.
* Schedule tasklets as needed.
*/
*/
if
(
isr
&
WBSD_INT_CARD
)
if
(
isr
&
WBSD_INT_CARD
)
tasklet_schedule
(
&
host
->
card_tasklet
);
tasklet_schedule
(
&
host
->
card_tasklet
);
if
(
isr
&
WBSD_INT_FIFO_THRE
)
if
(
isr
&
WBSD_INT_FIFO_THRE
)
tasklet_
hi_
schedule
(
&
host
->
fifo_tasklet
);
tasklet_schedule
(
&
host
->
fifo_tasklet
);
if
(
isr
&
WBSD_INT_CRC
)
if
(
isr
&
WBSD_INT_CRC
)
tasklet_hi_schedule
(
&
host
->
crc_tasklet
);
tasklet_hi_schedule
(
&
host
->
crc_tasklet
);
if
(
isr
&
WBSD_INT_TIMEOUT
)
if
(
isr
&
WBSD_INT_TIMEOUT
)
...
@@ -1390,8 +1446,8 @@ static int wbsd_probe(struct device* dev)
...
@@ -1390,8 +1446,8 @@ static int wbsd_probe(struct device* dev)
* Maximum number of segments. Worst case is one sector per segment
* Maximum number of segments. Worst case is one sector per segment
* so this will be 64kB/512.
* so this will be 64kB/512.
*/
*/
mmc
->
max_hw_segs
=
NR_SG
;
mmc
->
max_hw_segs
=
128
;
mmc
->
max_phys_segs
=
NR_SG
;
mmc
->
max_phys_segs
=
128
;
/*
/*
* Maximum number of sectors in one transfer. Also limited by 64kB
* Maximum number of sectors in one transfer. Also limited by 64kB
...
@@ -1588,6 +1644,7 @@ module_param(dma, int, 0444);
...
@@ -1588,6 +1644,7 @@ module_param(dma, int, 0444);
MODULE_LICENSE
(
"GPL"
);
MODULE_LICENSE
(
"GPL"
);
MODULE_DESCRIPTION
(
"Winbond W83L51xD SD/MMC card interface driver"
);
MODULE_DESCRIPTION
(
"Winbond W83L51xD SD/MMC card interface driver"
);
MODULE_VERSION
(
DRIVER_VERSION
);
MODULE_PARM_DESC
(
io
,
"I/O base to allocate. Must be 8 byte aligned. (default 0x248)"
);
MODULE_PARM_DESC
(
io
,
"I/O base to allocate. Must be 8 byte aligned. (default 0x248)"
);
MODULE_PARM_DESC
(
irq
,
"IRQ to allocate. (default 6)"
);
MODULE_PARM_DESC
(
irq
,
"IRQ to allocate. (default 6)"
);
...
...
drivers/mmc/wbsd.h
View file @
49936b5e
/*
/*
* linux/drivers/mmc/wbsd.h
* linux/drivers/mmc/wbsd.h
- Winbond W83L51xD SD/MMC driver
*
*
* Copyright (C) 2004 Pierre Ossman, All Rights Reserved.
* Copyright (C) 2004
-2005
Pierre Ossman, All Rights Reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* it under the terms of the GNU General Public License version 2 as
...
@@ -119,6 +119,8 @@ const int valid_ids[] = {
...
@@ -119,6 +119,8 @@ const int valid_ids[] = {
#define WBSD_FIFOEN_FULL 0x10
#define WBSD_FIFOEN_FULL 0x10
#define WBSD_FIFO_THREMASK 0x0F
#define WBSD_FIFO_THREMASK 0x0F
#define WBSD_BLOCK_READ 0x80
#define WBSD_BLOCK_WRITE 0x40
#define WBSD_BUSY 0x20
#define WBSD_BUSY 0x20
#define WBSD_CARDTRAFFIC 0x04
#define WBSD_CARDTRAFFIC 0x04
#define WBSD_SENDCMD 0x02
#define WBSD_SENDCMD 0x02
...
@@ -132,9 +134,6 @@ const int valid_ids[] = {
...
@@ -132,9 +134,6 @@ const int valid_ids[] = {
#define WBSD_CRC_FAIL 0x0B
/* S101E (01011) */
#define WBSD_CRC_FAIL 0x0B
/* S101E (01011) */
/* 64kB / 512 */
#define NR_SG 128
struct
wbsd_host
struct
wbsd_host
{
{
struct
mmc_host
*
mmc
;
/* MMC structure */
struct
mmc_host
*
mmc
;
/* MMC structure */
...
@@ -143,9 +142,11 @@ struct wbsd_host
...
@@ -143,9 +142,11 @@ struct wbsd_host
struct
mmc_request
*
mrq
;
/* Current request */
struct
mmc_request
*
mrq
;
/* Current request */
struct
scatterlist
sg
[
NR_SG
];
/* SG list */
u8
isr
;
/* Accumulated ISR */
struct
scatterlist
*
cur_sg
;
/* Current SG entry */
struct
scatterlist
*
cur_sg
;
/* Current SG entry */
unsigned
int
num_sg
;
/* Number of entries left */
unsigned
int
num_sg
;
/* Number of entries left */
void
*
mapped_sg
;
/* vaddr of mapped sg */
unsigned
int
offset
;
/* Offset into current entry */
unsigned
int
offset
;
/* Offset into current entry */
unsigned
int
remain
;
/* Data left in curren entry */
unsigned
int
remain
;
/* Data left in curren entry */
...
...
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