Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
linux-davinci
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
linux
linux-davinci
Commits
afad17c0
Commit
afad17c0
authored
Mar 24, 2009
by
Takashi Iwai
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'topic/usb-audio' into for-linus
parents
d0807323
b1c86bb8
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
154 additions
and
105 deletions
+154
-105
sound/usb/usbaudio.c
sound/usb/usbaudio.c
+33
-46
sound/usb/usbmixer.c
sound/usb/usbmixer.c
+84
-58
sound/usb/usbmixer_maps.c
sound/usb/usbmixer_maps.c
+26
-0
sound/usb/usbquirks.h
sound/usb/usbquirks.h
+10
-0
sound/usb/usx2y/usb_stream.c
sound/usb/usx2y/usb_stream.c
+1
-1
No files found.
sound/usb/usbaudio.c
View file @
afad17c0
...
@@ -107,7 +107,7 @@ MODULE_PARM_DESC(ignore_ctl_error,
...
@@ -107,7 +107,7 @@ MODULE_PARM_DESC(ignore_ctl_error,
#define MAX_PACKS_HS (MAX_PACKS * 8)
/* in high speed mode */
#define MAX_PACKS_HS (MAX_PACKS * 8)
/* in high speed mode */
#define MAX_URBS 8
#define MAX_URBS 8
#define SYNC_URBS 4
/* always four urbs for sync */
#define SYNC_URBS 4
/* always four urbs for sync */
#define M
IN_PACKS_URB 1
/* minimum 1 packet per urb
*/
#define M
AX_QUEUE 24
/* try not to exceed this queue length, in ms
*/
struct
audioformat
{
struct
audioformat
{
struct
list_head
list
;
struct
list_head
list
;
...
@@ -525,7 +525,7 @@ static int snd_usb_audio_next_packet_size(struct snd_usb_substream *subs)
...
@@ -525,7 +525,7 @@ static int snd_usb_audio_next_packet_size(struct snd_usb_substream *subs)
/*
/*
* Prepare urb for streaming before playback starts or when paused.
* Prepare urb for streaming before playback starts or when paused.
*
*
* We don't have any data, so we send
a frame of
silence.
* We don't have any data, so we send silence.
*/
*/
static
int
prepare_nodata_playback_urb
(
struct
snd_usb_substream
*
subs
,
static
int
prepare_nodata_playback_urb
(
struct
snd_usb_substream
*
subs
,
struct
snd_pcm_runtime
*
runtime
,
struct
snd_pcm_runtime
*
runtime
,
...
@@ -537,13 +537,13 @@ static int prepare_nodata_playback_urb(struct snd_usb_substream *subs,
...
@@ -537,13 +537,13 @@ static int prepare_nodata_playback_urb(struct snd_usb_substream *subs,
offs
=
0
;
offs
=
0
;
urb
->
dev
=
ctx
->
subs
->
dev
;
urb
->
dev
=
ctx
->
subs
->
dev
;
urb
->
number_of_packets
=
subs
->
packs_per_ms
;
for
(
i
=
0
;
i
<
ctx
->
packets
;
++
i
)
{
for
(
i
=
0
;
i
<
subs
->
packs_per_ms
;
++
i
)
{
counts
=
snd_usb_audio_next_packet_size
(
subs
);
counts
=
snd_usb_audio_next_packet_size
(
subs
);
urb
->
iso_frame_desc
[
i
].
offset
=
offs
*
stride
;
urb
->
iso_frame_desc
[
i
].
offset
=
offs
*
stride
;
urb
->
iso_frame_desc
[
i
].
length
=
counts
*
stride
;
urb
->
iso_frame_desc
[
i
].
length
=
counts
*
stride
;
offs
+=
counts
;
offs
+=
counts
;
}
}
urb
->
number_of_packets
=
ctx
->
packets
;
urb
->
transfer_buffer_length
=
offs
*
stride
;
urb
->
transfer_buffer_length
=
offs
*
stride
;
memset
(
urb
->
transfer_buffer
,
memset
(
urb
->
transfer_buffer
,
subs
->
cur_audiofmt
->
format
==
SNDRV_PCM_FORMAT_U8
?
0x80
:
0
,
subs
->
cur_audiofmt
->
format
==
SNDRV_PCM_FORMAT_U8
?
0x80
:
0
,
...
@@ -1034,9 +1034,9 @@ static void release_substream_urbs(struct snd_usb_substream *subs, int force)
...
@@ -1034,9 +1034,9 @@ static void release_substream_urbs(struct snd_usb_substream *subs, int force)
static
int
init_substream_urbs
(
struct
snd_usb_substream
*
subs
,
unsigned
int
period_bytes
,
static
int
init_substream_urbs
(
struct
snd_usb_substream
*
subs
,
unsigned
int
period_bytes
,
unsigned
int
rate
,
unsigned
int
frame_bits
)
unsigned
int
rate
,
unsigned
int
frame_bits
)
{
{
unsigned
int
maxsize
,
n
,
i
;
unsigned
int
maxsize
,
i
;
int
is_playback
=
subs
->
direction
==
SNDRV_PCM_STREAM_PLAYBACK
;
int
is_playback
=
subs
->
direction
==
SNDRV_PCM_STREAM_PLAYBACK
;
unsigned
int
npacks
[
MAX_URBS
],
urb_packs
,
total_packs
,
packs_per_ms
;
unsigned
int
urb_packs
,
total_packs
,
packs_per_ms
;
/* calculate the frequency in 16.16 format */
/* calculate the frequency in 16.16 format */
if
(
snd_usb_get_speed
(
subs
->
dev
)
==
USB_SPEED_FULL
)
if
(
snd_usb_get_speed
(
subs
->
dev
)
==
USB_SPEED_FULL
)
...
@@ -1070,8 +1070,7 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
...
@@ -1070,8 +1070,7 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
subs
->
packs_per_ms
=
packs_per_ms
;
subs
->
packs_per_ms
=
packs_per_ms
;
if
(
is_playback
)
{
if
(
is_playback
)
{
urb_packs
=
nrpacks
;
urb_packs
=
max
(
nrpacks
,
1
);
urb_packs
=
max
(
urb_packs
,
(
unsigned
int
)
MIN_PACKS_URB
);
urb_packs
=
min
(
urb_packs
,
(
unsigned
int
)
MAX_PACKS
);
urb_packs
=
min
(
urb_packs
,
(
unsigned
int
)
MAX_PACKS
);
}
else
}
else
urb_packs
=
1
;
urb_packs
=
1
;
...
@@ -1079,7 +1078,7 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
...
@@ -1079,7 +1078,7 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
/* decide how many packets to be used */
/* decide how many packets to be used */
if
(
is_playback
)
{
if
(
is_playback
)
{
unsigned
int
minsize
;
unsigned
int
minsize
,
maxpacks
;
/* determine how small a packet can be */
/* determine how small a packet can be */
minsize
=
(
subs
->
freqn
>>
(
16
-
subs
->
datainterval
))
minsize
=
(
subs
->
freqn
>>
(
16
-
subs
->
datainterval
))
*
(
frame_bits
>>
3
);
*
(
frame_bits
>>
3
);
...
@@ -1092,8 +1091,13 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
...
@@ -1092,8 +1091,13 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
total_packs
=
(
total_packs
+
packs_per_ms
-
1
)
total_packs
=
(
total_packs
+
packs_per_ms
-
1
)
&
~
(
packs_per_ms
-
1
);
&
~
(
packs_per_ms
-
1
);
/* we need at least two URBs for queueing */
/* we need at least two URBs for queueing */
if
(
total_packs
<
2
*
MIN_PACKS_URB
*
packs_per_ms
)
if
(
total_packs
<
2
*
packs_per_ms
)
{
total_packs
=
2
*
MIN_PACKS_URB
*
packs_per_ms
;
total_packs
=
2
*
packs_per_ms
;
}
else
{
/* and we don't want too long a queue either */
maxpacks
=
max
(
MAX_QUEUE
*
packs_per_ms
,
urb_packs
*
2
);
total_packs
=
min
(
total_packs
,
maxpacks
);
}
}
else
{
}
else
{
total_packs
=
MAX_URBS
*
urb_packs
;
total_packs
=
MAX_URBS
*
urb_packs
;
}
}
...
@@ -1102,31 +1106,11 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
...
@@ -1102,31 +1106,11 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
/* too much... */
/* too much... */
subs
->
nurbs
=
MAX_URBS
;
subs
->
nurbs
=
MAX_URBS
;
total_packs
=
MAX_URBS
*
urb_packs
;
total_packs
=
MAX_URBS
*
urb_packs
;
}
}
else
if
(
subs
->
nurbs
<
2
)
{
n
=
total_packs
;
for
(
i
=
0
;
i
<
subs
->
nurbs
;
i
++
)
{
npacks
[
i
]
=
n
>
urb_packs
?
urb_packs
:
n
;
n
-=
urb_packs
;
}
if
(
subs
->
nurbs
<=
1
)
{
/* too little - we need at least two packets
/* too little - we need at least two packets
* to ensure contiguous playback/capture
* to ensure contiguous playback/capture
*/
*/
subs
->
nurbs
=
2
;
subs
->
nurbs
=
2
;
npacks
[
0
]
=
(
total_packs
+
1
)
/
2
;
npacks
[
1
]
=
total_packs
-
npacks
[
0
];
}
else
if
(
npacks
[
subs
->
nurbs
-
1
]
<
MIN_PACKS_URB
*
packs_per_ms
)
{
/* the last packet is too small.. */
if
(
subs
->
nurbs
>
2
)
{
/* merge to the first one */
npacks
[
0
]
+=
npacks
[
subs
->
nurbs
-
1
];
subs
->
nurbs
--
;
}
else
{
/* divide to two */
subs
->
nurbs
=
2
;
npacks
[
0
]
=
(
total_packs
+
1
)
/
2
;
npacks
[
1
]
=
total_packs
-
npacks
[
0
];
}
}
}
/* allocate and initialize data urbs */
/* allocate and initialize data urbs */
...
@@ -1134,7 +1118,8 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
...
@@ -1134,7 +1118,8 @@ static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int peri
struct
snd_urb_ctx
*
u
=
&
subs
->
dataurb
[
i
];
struct
snd_urb_ctx
*
u
=
&
subs
->
dataurb
[
i
];
u
->
index
=
i
;
u
->
index
=
i
;
u
->
subs
=
subs
;
u
->
subs
=
subs
;
u
->
packets
=
npacks
[
i
];
u
->
packets
=
(
i
+
1
)
*
total_packs
/
subs
->
nurbs
-
i
*
total_packs
/
subs
->
nurbs
;
u
->
buffer_size
=
maxsize
*
u
->
packets
;
u
->
buffer_size
=
maxsize
*
u
->
packets
;
if
(
subs
->
fmt_type
==
USB_FORMAT_TYPE_II
)
if
(
subs
->
fmt_type
==
USB_FORMAT_TYPE_II
)
u
->
packets
++
;
/* for transfer delimiter */
u
->
packets
++
;
/* for transfer delimiter */
...
@@ -1292,14 +1277,14 @@ static int init_usb_sample_rate(struct usb_device *dev, int iface,
...
@@ -1292,14 +1277,14 @@ static int init_usb_sample_rate(struct usb_device *dev, int iface,
if
((
err
=
snd_usb_ctl_msg
(
dev
,
usb_sndctrlpipe
(
dev
,
0
),
SET_CUR
,
if
((
err
=
snd_usb_ctl_msg
(
dev
,
usb_sndctrlpipe
(
dev
,
0
),
SET_CUR
,
USB_TYPE_CLASS
|
USB_RECIP_ENDPOINT
|
USB_DIR_OUT
,
USB_TYPE_CLASS
|
USB_RECIP_ENDPOINT
|
USB_DIR_OUT
,
SAMPLING_FREQ_CONTROL
<<
8
,
ep
,
data
,
3
,
1000
))
<
0
)
{
SAMPLING_FREQ_CONTROL
<<
8
,
ep
,
data
,
3
,
1000
))
<
0
)
{
snd_printk
(
KERN_ERR
"%d:%d:%d: cannot set freq %d to ep
0x%
x
\n
"
,
snd_printk
(
KERN_ERR
"%d:%d:%d: cannot set freq %d to ep
%#
x
\n
"
,
dev
->
devnum
,
iface
,
fmt
->
altsetting
,
rate
,
ep
);
dev
->
devnum
,
iface
,
fmt
->
altsetting
,
rate
,
ep
);
return
err
;
return
err
;
}
}
if
((
err
=
snd_usb_ctl_msg
(
dev
,
usb_rcvctrlpipe
(
dev
,
0
),
GET_CUR
,
if
((
err
=
snd_usb_ctl_msg
(
dev
,
usb_rcvctrlpipe
(
dev
,
0
),
GET_CUR
,
USB_TYPE_CLASS
|
USB_RECIP_ENDPOINT
|
USB_DIR_IN
,
USB_TYPE_CLASS
|
USB_RECIP_ENDPOINT
|
USB_DIR_IN
,
SAMPLING_FREQ_CONTROL
<<
8
,
ep
,
data
,
3
,
1000
))
<
0
)
{
SAMPLING_FREQ_CONTROL
<<
8
,
ep
,
data
,
3
,
1000
))
<
0
)
{
snd_printk
(
KERN_WARNING
"%d:%d:%d: cannot get freq at ep
0x%
x
\n
"
,
snd_printk
(
KERN_WARNING
"%d:%d:%d: cannot get freq at ep
%#
x
\n
"
,
dev
->
devnum
,
iface
,
fmt
->
altsetting
,
ep
);
dev
->
devnum
,
iface
,
fmt
->
altsetting
,
ep
);
return
0
;
/* some devices don't support reading */
return
0
;
/* some devices don't support reading */
}
}
...
@@ -1431,9 +1416,11 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
...
@@ -1431,9 +1416,11 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
subs
->
cur_audiofmt
=
fmt
;
subs
->
cur_audiofmt
=
fmt
;
#if 0
#if 0
printk("setting done: format = %d, rate = %d..%d, channels = %d\n",
printk(KERN_DEBUG
"setting done: format = %d, rate = %d..%d, channels = %d\n",
fmt->format, fmt->rate_min, fmt->rate_max, fmt->channels);
fmt->format, fmt->rate_min, fmt->rate_max, fmt->channels);
printk(" datapipe = 0x%0x, syncpipe = 0x%0x\n",
printk(KERN_DEBUG
" datapipe = 0x%0x, syncpipe = 0x%0x\n",
subs->datapipe, subs->syncpipe);
subs->datapipe, subs->syncpipe);
#endif
#endif
...
@@ -1468,7 +1455,7 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
...
@@ -1468,7 +1455,7 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
channels
=
params_channels
(
hw_params
);
channels
=
params_channels
(
hw_params
);
fmt
=
find_format
(
subs
,
format
,
rate
,
channels
);
fmt
=
find_format
(
subs
,
format
,
rate
,
channels
);
if
(
!
fmt
)
{
if
(
!
fmt
)
{
snd_printd
(
KERN_DEBUG
"cannot set format: format =
0x%
x, rate = %d, channels = %d
\n
"
,
snd_printd
(
KERN_DEBUG
"cannot set format: format =
%#
x, rate = %d, channels = %d
\n
"
,
format
,
rate
,
channels
);
format
,
rate
,
channels
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
...
@@ -1795,7 +1782,7 @@ static int check_hw_params_convention(struct snd_usb_substream *subs)
...
@@ -1795,7 +1782,7 @@ static int check_hw_params_convention(struct snd_usb_substream *subs)
if
(
rates
[
f
->
format
]
&&
rates
[
f
->
format
]
!=
f
->
rates
)
if
(
rates
[
f
->
format
]
&&
rates
[
f
->
format
]
!=
f
->
rates
)
goto
__out
;
goto
__out
;
}
}
channels
[
f
->
format
]
|=
(
1
<<
f
->
channels
);
channels
[
f
->
format
]
|=
1
<<
(
f
->
channels
-
1
);
rates
[
f
->
format
]
|=
f
->
rates
;
rates
[
f
->
format
]
|=
f
->
rates
;
/* needs knot? */
/* needs knot? */
if
(
f
->
rates
&
SNDRV_PCM_RATE_KNOT
)
if
(
f
->
rates
&
SNDRV_PCM_RATE_KNOT
)
...
@@ -1822,7 +1809,7 @@ static int check_hw_params_convention(struct snd_usb_substream *subs)
...
@@ -1822,7 +1809,7 @@ static int check_hw_params_convention(struct snd_usb_substream *subs)
continue
;
continue
;
for
(
i
=
0
;
i
<
32
;
i
++
)
{
for
(
i
=
0
;
i
<
32
;
i
++
)
{
if
(
f
->
rates
&
(
1
<<
i
))
if
(
f
->
rates
&
(
1
<<
i
))
channels
[
i
]
|=
(
1
<<
f
->
channels
);
channels
[
i
]
|=
1
<<
(
f
->
channels
-
1
);
}
}
}
}
cmaster
=
0
;
cmaster
=
0
;
...
@@ -1919,7 +1906,7 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
...
@@ -1919,7 +1906,7 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
* in the current code assume the 1ms period.
* in the current code assume the 1ms period.
*/
*/
snd_pcm_hw_constraint_minmax
(
runtime
,
SNDRV_PCM_HW_PARAM_PERIOD_TIME
,
snd_pcm_hw_constraint_minmax
(
runtime
,
SNDRV_PCM_HW_PARAM_PERIOD_TIME
,
1000
*
MIN_PACKS_URB
,
1000
,
/*(nrpacks * MAX_URBS) * 1000*/
UINT_MAX
);
/*(nrpacks * MAX_URBS) * 1000*/
UINT_MAX
);
err
=
check_hw_params_convention
(
subs
);
err
=
check_hw_params_convention
(
subs
);
...
@@ -2160,7 +2147,7 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s
...
@@ -2160,7 +2147,7 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s
fp
=
list_entry
(
p
,
struct
audioformat
,
list
);
fp
=
list_entry
(
p
,
struct
audioformat
,
list
);
snd_iprintf
(
buffer
,
" Interface %d
\n
"
,
fp
->
iface
);
snd_iprintf
(
buffer
,
" Interface %d
\n
"
,
fp
->
iface
);
snd_iprintf
(
buffer
,
" Altset %d
\n
"
,
fp
->
altsetting
);
snd_iprintf
(
buffer
,
" Altset %d
\n
"
,
fp
->
altsetting
);
snd_iprintf
(
buffer
,
" Format:
0x%
x
\n
"
,
fp
->
format
);
snd_iprintf
(
buffer
,
" Format:
%#
x
\n
"
,
fp
->
format
);
snd_iprintf
(
buffer
,
" Channels: %d
\n
"
,
fp
->
channels
);
snd_iprintf
(
buffer
,
" Channels: %d
\n
"
,
fp
->
channels
);
snd_iprintf
(
buffer
,
" Endpoint: %d %s (%s)
\n
"
,
snd_iprintf
(
buffer
,
" Endpoint: %d %s (%s)
\n
"
,
fp
->
endpoint
&
USB_ENDPOINT_NUMBER_MASK
,
fp
->
endpoint
&
USB_ENDPOINT_NUMBER_MASK
,
...
@@ -2180,7 +2167,7 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s
...
@@ -2180,7 +2167,7 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s
snd_iprintf
(
buffer
,
"
\n
"
);
snd_iprintf
(
buffer
,
"
\n
"
);
}
}
// snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize);
// snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize);
// snd_iprintf(buffer, " EP Attribute =
0x%
x\n", fp->attributes);
// snd_iprintf(buffer, " EP Attribute =
%#
x\n", fp->attributes);
}
}
}
}
...
@@ -2621,7 +2608,7 @@ static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat
...
@@ -2621,7 +2608,7 @@ static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat
fp
->
format
=
SNDRV_PCM_FORMAT_MPEG
;
fp
->
format
=
SNDRV_PCM_FORMAT_MPEG
;
break
;
break
;
default:
default:
snd_printd
(
KERN_INFO
"%d:%u:%d : unknown format tag
0x%
x is detected. processed as MPEG.
\n
"
,
snd_printd
(
KERN_INFO
"%d:%u:%d : unknown format tag
%#
x is detected. processed as MPEG.
\n
"
,
chip
->
dev
->
devnum
,
fp
->
iface
,
fp
->
altsetting
,
format
);
chip
->
dev
->
devnum
,
fp
->
iface
,
fp
->
altsetting
,
format
);
fp
->
format
=
SNDRV_PCM_FORMAT_MPEG
;
fp
->
format
=
SNDRV_PCM_FORMAT_MPEG
;
break
;
break
;
...
@@ -2819,7 +2806,7 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
...
@@ -2819,7 +2806,7 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
continue
;
continue
;
}
}
snd_printdd
(
KERN_INFO
"%d:%u:%d: add audio endpoint
0x%
x
\n
"
,
dev
->
devnum
,
iface_no
,
altno
,
fp
->
endpoint
);
snd_printdd
(
KERN_INFO
"%d:%u:%d: add audio endpoint
%#
x
\n
"
,
dev
->
devnum
,
iface_no
,
altno
,
fp
->
endpoint
);
err
=
add_audio_endpoint
(
chip
,
stream
,
fp
);
err
=
add_audio_endpoint
(
chip
,
stream
,
fp
);
if
(
err
<
0
)
{
if
(
err
<
0
)
{
kfree
(
fp
->
rate_table
);
kfree
(
fp
->
rate_table
);
...
@@ -3766,7 +3753,7 @@ static int usb_audio_resume(struct usb_interface *intf)
...
@@ -3766,7 +3753,7 @@ static int usb_audio_resume(struct usb_interface *intf)
static
int
__init
snd_usb_audio_init
(
void
)
static
int
__init
snd_usb_audio_init
(
void
)
{
{
if
(
nrpacks
<
MIN_PACKS_URB
||
nrpacks
>
MAX_PACKS
)
{
if
(
nrpacks
<
1
||
nrpacks
>
MAX_PACKS
)
{
printk
(
KERN_WARNING
"invalid nrpacks value.
\n
"
);
printk
(
KERN_WARNING
"invalid nrpacks value.
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
...
...
sound/usb/usbmixer.c
View file @
afad17c0
...
@@ -66,6 +66,7 @@ static const struct rc_config {
...
@@ -66,6 +66,7 @@ static const struct rc_config {
{
USB_ID
(
0x041e
,
0x3000
),
0
,
1
,
2
,
1
,
18
,
0x0013
},
/* Extigy */
{
USB_ID
(
0x041e
,
0x3000
),
0
,
1
,
2
,
1
,
18
,
0x0013
},
/* Extigy */
{
USB_ID
(
0x041e
,
0x3020
),
2
,
1
,
6
,
6
,
18
,
0x0013
},
/* Audigy 2 NX */
{
USB_ID
(
0x041e
,
0x3020
),
2
,
1
,
6
,
6
,
18
,
0x0013
},
/* Audigy 2 NX */
{
USB_ID
(
0x041e
,
0x3040
),
2
,
2
,
6
,
6
,
2
,
0x6e91
},
/* Live! 24-bit */
{
USB_ID
(
0x041e
,
0x3040
),
2
,
2
,
6
,
6
,
2
,
0x6e91
},
/* Live! 24-bit */
{
USB_ID
(
0x041e
,
0x3048
),
2
,
2
,
6
,
6
,
2
,
0x6e91
},
/* Toshiba SB0500 */
};
};
struct
usb_mixer_interface
{
struct
usb_mixer_interface
{
...
@@ -109,6 +110,8 @@ struct mixer_build {
...
@@ -109,6 +110,8 @@ struct mixer_build {
const
struct
usbmix_selector_map
*
selector_map
;
const
struct
usbmix_selector_map
*
selector_map
;
};
};
#define MAX_CHANNELS 10
/* max logical channels */
struct
usb_mixer_elem_info
{
struct
usb_mixer_elem_info
{
struct
usb_mixer_interface
*
mixer
;
struct
usb_mixer_interface
*
mixer
;
struct
usb_mixer_elem_info
*
next_id_elem
;
/* list of controls with same id */
struct
usb_mixer_elem_info
*
next_id_elem
;
/* list of controls with same id */
...
@@ -119,6 +122,8 @@ struct usb_mixer_elem_info {
...
@@ -119,6 +122,8 @@ struct usb_mixer_elem_info {
int
channels
;
int
channels
;
int
val_type
;
int
val_type
;
int
min
,
max
,
res
;
int
min
,
max
,
res
;
int
cached
;
int
cache_val
[
MAX_CHANNELS
];
u8
initialized
;
u8
initialized
;
};
};
...
@@ -180,8 +185,6 @@ enum {
...
@@ -180,8 +185,6 @@ enum {
USB_PROC_DCR_RELEASE
=
6
,
USB_PROC_DCR_RELEASE
=
6
,
};
};
#define MAX_CHANNELS 10
/* max logical channels */
/*
/*
* manual mapping of mixer names
* manual mapping of mixer names
...
@@ -218,7 +221,10 @@ static int check_ignored_ctl(struct mixer_build *state, int unitid, int control)
...
@@ -218,7 +221,10 @@ static int check_ignored_ctl(struct mixer_build *state, int unitid, int control)
for
(
p
=
state
->
map
;
p
->
id
;
p
++
)
{
for
(
p
=
state
->
map
;
p
->
id
;
p
++
)
{
if
(
p
->
id
==
unitid
&&
!
p
->
name
&&
if
(
p
->
id
==
unitid
&&
!
p
->
name
&&
(
!
control
||
!
p
->
control
||
control
==
p
->
control
))
{
(
!
control
||
!
p
->
control
||
control
==
p
->
control
))
{
// printk("ignored control %d:%d\n", unitid, control);
/*
printk(KERN_DEBUG "ignored control %d:%d\n",
unitid, control);
*/
return
1
;
return
1
;
}
}
}
}
...
@@ -375,11 +381,35 @@ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *
...
@@ -375,11 +381,35 @@ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *
}
}
/* channel = 0: master, 1 = first channel */
/* channel = 0: master, 1 = first channel */
static
inline
int
get_cur_mix_value
(
struct
usb_mixer_elem_info
*
cval
,
int
channel
,
int
*
value
)
static
inline
int
get_cur_mix_raw
(
struct
usb_mixer_elem_info
*
cval
,
int
channel
,
int
*
value
)
{
{
return
get_ctl_value
(
cval
,
GET_CUR
,
(
cval
->
control
<<
8
)
|
channel
,
value
);
return
get_ctl_value
(
cval
,
GET_CUR
,
(
cval
->
control
<<
8
)
|
channel
,
value
);
}
}
static
int
get_cur_mix_value
(
struct
usb_mixer_elem_info
*
cval
,
int
channel
,
int
index
,
int
*
value
)
{
int
err
;
if
(
cval
->
cached
&
(
1
<<
channel
))
{
*
value
=
cval
->
cache_val
[
index
];
return
0
;
}
err
=
get_cur_mix_raw
(
cval
,
channel
,
value
);
if
(
err
<
0
)
{
if
(
!
cval
->
mixer
->
ignore_ctl_error
)
snd_printd
(
KERN_ERR
"cannot get current value for "
"control %d ch %d: err = %d
\n
"
,
cval
->
control
,
channel
,
err
);
return
err
;
}
cval
->
cached
|=
1
<<
channel
;
cval
->
cache_val
[
index
]
=
*
value
;
return
0
;
}
/*
/*
* set a mixer value
* set a mixer value
*/
*/
...
@@ -411,9 +441,17 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int v
...
@@ -411,9 +441,17 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int v
return
set_ctl_value
(
cval
,
SET_CUR
,
validx
,
value
);
return
set_ctl_value
(
cval
,
SET_CUR
,
validx
,
value
);
}
}
static
inline
int
set_cur_mix_value
(
struct
usb_mixer_elem_info
*
cval
,
int
channel
,
int
value
)
static
int
set_cur_mix_value
(
struct
usb_mixer_elem_info
*
cval
,
int
channel
,
int
index
,
int
value
)
{
{
return
set_ctl_value
(
cval
,
SET_CUR
,
(
cval
->
control
<<
8
)
|
channel
,
value
);
int
err
;
err
=
set_ctl_value
(
cval
,
SET_CUR
,
(
cval
->
control
<<
8
)
|
channel
,
value
);
if
(
err
<
0
)
return
err
;
cval
->
cached
|=
1
<<
channel
;
cval
->
cache_val
[
index
]
=
value
;
return
0
;
}
}
/*
/*
...
@@ -717,7 +755,7 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
...
@@ -717,7 +755,7 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
if
(
cval
->
min
+
cval
->
res
<
cval
->
max
)
{
if
(
cval
->
min
+
cval
->
res
<
cval
->
max
)
{
int
last_valid_res
=
cval
->
res
;
int
last_valid_res
=
cval
->
res
;
int
saved
,
test
,
check
;
int
saved
,
test
,
check
;
get_cur_mix_
value
(
cval
,
minchn
,
&
saved
);
get_cur_mix_
raw
(
cval
,
minchn
,
&
saved
);
for
(;;)
{
for
(;;)
{
test
=
saved
;
test
=
saved
;
if
(
test
<
cval
->
max
)
if
(
test
<
cval
->
max
)
...
@@ -725,8 +763,8 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
...
@@ -725,8 +763,8 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
else
else
test
-=
cval
->
res
;
test
-=
cval
->
res
;
if
(
test
<
cval
->
min
||
test
>
cval
->
max
||
if
(
test
<
cval
->
min
||
test
>
cval
->
max
||
set_cur_mix_value
(
cval
,
minchn
,
test
)
||
set_cur_mix_value
(
cval
,
minchn
,
0
,
test
)
||
get_cur_mix_
value
(
cval
,
minchn
,
&
check
))
{
get_cur_mix_
raw
(
cval
,
minchn
,
&
check
))
{
cval
->
res
=
last_valid_res
;
cval
->
res
=
last_valid_res
;
break
;
break
;
}
}
...
@@ -734,7 +772,7 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
...
@@ -734,7 +772,7 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
break
;
break
;
cval
->
res
*=
2
;
cval
->
res
*=
2
;
}
}
set_cur_mix_value
(
cval
,
minchn
,
saved
);
set_cur_mix_value
(
cval
,
minchn
,
0
,
saved
);
}
}
cval
->
initialized
=
1
;
cval
->
initialized
=
1
;
...
@@ -774,35 +812,25 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_e
...
@@ -774,35 +812,25 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_e
struct
usb_mixer_elem_info
*
cval
=
kcontrol
->
private_data
;
struct
usb_mixer_elem_info
*
cval
=
kcontrol
->
private_data
;
int
c
,
cnt
,
val
,
err
;
int
c
,
cnt
,
val
,
err
;
ucontrol
->
value
.
integer
.
value
[
0
]
=
cval
->
min
;
if
(
cval
->
cmask
)
{
if
(
cval
->
cmask
)
{
cnt
=
0
;
cnt
=
0
;
for
(
c
=
0
;
c
<
MAX_CHANNELS
;
c
++
)
{
for
(
c
=
0
;
c
<
MAX_CHANNELS
;
c
++
)
{
if
(
cval
->
cmask
&
(
1
<<
c
))
{
if
(
!
(
cval
->
cmask
&
(
1
<<
c
)))
err
=
get_cur_mix_value
(
cval
,
c
+
1
,
&
val
);
continue
;
if
(
err
<
0
)
{
err
=
get_cur_mix_value
(
cval
,
c
+
1
,
cnt
,
&
val
);
if
(
cval
->
mixer
->
ignore_ctl_error
)
{
if
(
err
<
0
)
ucontrol
->
value
.
integer
.
value
[
0
]
=
cval
->
min
;
return
cval
->
mixer
->
ignore_ctl_error
?
0
:
err
;
return
0
;
val
=
get_relative_value
(
cval
,
val
);
}
ucontrol
->
value
.
integer
.
value
[
cnt
]
=
val
;
snd_printd
(
KERN_ERR
"cannot get current value for control %d ch %d: err = %d
\n
"
,
cval
->
control
,
c
+
1
,
err
);
cnt
++
;
return
err
;
}
val
=
get_relative_value
(
cval
,
val
);
ucontrol
->
value
.
integer
.
value
[
cnt
]
=
val
;
cnt
++
;
}
}
}
return
0
;
}
else
{
}
else
{
/* master channel */
/* master channel */
err
=
get_cur_mix_value
(
cval
,
0
,
&
val
);
err
=
get_cur_mix_value
(
cval
,
0
,
0
,
&
val
);
if
(
err
<
0
)
{
if
(
err
<
0
)
if
(
cval
->
mixer
->
ignore_ctl_error
)
{
return
cval
->
mixer
->
ignore_ctl_error
?
0
:
err
;
ucontrol
->
value
.
integer
.
value
[
0
]
=
cval
->
min
;
return
0
;
}
snd_printd
(
KERN_ERR
"cannot get current value for control %d master ch: err = %d
\n
"
,
cval
->
control
,
err
);
return
err
;
}
val
=
get_relative_value
(
cval
,
val
);
val
=
get_relative_value
(
cval
,
val
);
ucontrol
->
value
.
integer
.
value
[
0
]
=
val
;
ucontrol
->
value
.
integer
.
value
[
0
]
=
val
;
}
}
...
@@ -819,34 +847,28 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e
...
@@ -819,34 +847,28 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e
if
(
cval
->
cmask
)
{
if
(
cval
->
cmask
)
{
cnt
=
0
;
cnt
=
0
;
for
(
c
=
0
;
c
<
MAX_CHANNELS
;
c
++
)
{
for
(
c
=
0
;
c
<
MAX_CHANNELS
;
c
++
)
{
if
(
cval
->
cmask
&
(
1
<<
c
))
{
if
(
!
(
cval
->
cmask
&
(
1
<<
c
)))
err
=
get_cur_mix_value
(
cval
,
c
+
1
,
&
oval
);
continue
;
if
(
err
<
0
)
{
err
=
get_cur_mix_value
(
cval
,
c
+
1
,
cnt
,
&
oval
);
if
(
cval
->
mixer
->
ignore_ctl_error
)
if
(
err
<
0
)
return
0
;
return
cval
->
mixer
->
ignore_ctl_error
?
0
:
err
;
return
err
;
val
=
ucontrol
->
value
.
integer
.
value
[
cnt
];
}
val
=
get_abs_value
(
cval
,
val
);
val
=
ucontrol
->
value
.
integer
.
value
[
cnt
];
if
(
oval
!=
val
)
{
val
=
get_abs_value
(
cval
,
val
);
set_cur_mix_value
(
cval
,
c
+
1
,
cnt
,
val
);
if
(
oval
!=
val
)
{
changed
=
1
;
set_cur_mix_value
(
cval
,
c
+
1
,
val
);
changed
=
1
;
}
get_cur_mix_value
(
cval
,
c
+
1
,
&
val
);
cnt
++
;
}
}
cnt
++
;
}
}
}
else
{
}
else
{
/* master channel */
/* master channel */
err
=
get_cur_mix_value
(
cval
,
0
,
&
oval
);
err
=
get_cur_mix_value
(
cval
,
0
,
0
,
&
oval
);
if
(
err
<
0
&&
cval
->
mixer
->
ignore_ctl_error
)
return
0
;
if
(
err
<
0
)
if
(
err
<
0
)
return
err
;
return
cval
->
mixer
->
ignore_ctl_error
?
0
:
err
;
val
=
ucontrol
->
value
.
integer
.
value
[
0
];
val
=
ucontrol
->
value
.
integer
.
value
[
0
];
val
=
get_abs_value
(
cval
,
val
);
val
=
get_abs_value
(
cval
,
val
);
if
(
val
!=
oval
)
{
if
(
val
!=
oval
)
{
set_cur_mix_value
(
cval
,
0
,
val
);
set_cur_mix_value
(
cval
,
0
,
0
,
val
);
changed
=
1
;
changed
=
1
;
}
}
}
}
...
@@ -1705,7 +1727,8 @@ static void snd_usb_mixer_memory_change(struct usb_mixer_interface *mixer,
...
@@ -1705,7 +1727,8 @@ static void snd_usb_mixer_memory_change(struct usb_mixer_interface *mixer,
break
;
break
;
/* live24ext: 4 = line-in jack */
/* live24ext: 4 = line-in jack */
case
3
:
/* hp-out jack (may actuate Mute) */
case
3
:
/* hp-out jack (may actuate Mute) */
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
))
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
)
||
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3048
))
snd_usb_mixer_notify_id
(
mixer
,
mixer
->
rc_cfg
->
mute_mixer_id
);
snd_usb_mixer_notify_id
(
mixer
,
mixer
->
rc_cfg
->
mute_mixer_id
);
break
;
break
;
default:
default:
...
@@ -1936,8 +1959,9 @@ static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer)
...
@@ -1936,8 +1959,9 @@ static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer)
int
i
,
err
;
int
i
,
err
;
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
snd_audigy2nx_controls
);
++
i
)
{
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
snd_audigy2nx_controls
);
++
i
)
{
if
(
i
>
1
&&
/* Live24ext has 2 LEDs only */
if
(
i
>
1
&&
/* Live24ext has 2 LEDs only */
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
))
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
)
||
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3048
)))
break
;
break
;
err
=
snd_ctl_add
(
mixer
->
chip
->
card
,
err
=
snd_ctl_add
(
mixer
->
chip
->
card
,
snd_ctl_new1
(
&
snd_audigy2nx_controls
[
i
],
mixer
));
snd_ctl_new1
(
&
snd_audigy2nx_controls
[
i
],
mixer
));
...
@@ -1974,7 +1998,8 @@ static void snd_audigy2nx_proc_read(struct snd_info_entry *entry,
...
@@ -1974,7 +1998,8 @@ static void snd_audigy2nx_proc_read(struct snd_info_entry *entry,
snd_iprintf
(
buffer
,
"%s jacks
\n\n
"
,
mixer
->
chip
->
card
->
shortname
);
snd_iprintf
(
buffer
,
"%s jacks
\n\n
"
,
mixer
->
chip
->
card
->
shortname
);
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3020
))
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3020
))
jacks
=
jacks_audigy2nx
;
jacks
=
jacks_audigy2nx
;
else
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
))
else
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
)
||
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3048
))
jacks
=
jacks_live24ext
;
jacks
=
jacks_live24ext
;
else
else
return
;
return
;
...
@@ -2024,7 +2049,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
...
@@ -2024,7 +2049,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
goto
_error
;
goto
_error
;
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3020
)
||
if
(
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3020
)
||
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
))
{
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3040
)
||
mixer
->
chip
->
usb_id
==
USB_ID
(
0x041e
,
0x3048
))
{
struct
snd_info_entry
*
entry
;
struct
snd_info_entry
*
entry
;
if
((
err
=
snd_audigy2nx_controls_create
(
mixer
))
<
0
)
if
((
err
=
snd_audigy2nx_controls_create
(
mixer
))
<
0
)
...
...
sound/usb/usbmixer_maps.c
View file @
afad17c0
...
@@ -261,6 +261,22 @@ static struct usbmix_name_map aureon_51_2_map[] = {
...
@@ -261,6 +261,22 @@ static struct usbmix_name_map aureon_51_2_map[] = {
{}
/* terminator */
{}
/* terminator */
};
};
static
struct
usbmix_name_map
scratch_live_map
[]
=
{
/* 1: IT Line 1 (USB streaming) */
/* 2: OT Line 1 (Speaker) */
/* 3: IT Line 1 (Line connector) */
{
4
,
"Line 1 In"
},
/* FU */
/* 5: OT Line 1 (USB streaming) */
/* 6: IT Line 2 (USB streaming) */
/* 7: OT Line 2 (Speaker) */
/* 8: IT Line 2 (Line connector) */
{
9
,
"Line 2 In"
},
/* FU */
/* 10: OT Line 2 (USB streaming) */
/* 11: IT Mic (Line connector) */
/* 12: OT Mic (USB streaming) */
{
0
}
/* terminator */
};
/*
/*
* Control map entries
* Control map entries
*/
*/
...
@@ -284,6 +300,11 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = {
...
@@ -284,6 +300,11 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = {
.
id
=
USB_ID
(
0x041e
,
0x3040
),
.
id
=
USB_ID
(
0x041e
,
0x3040
),
.
map
=
live24ext_map
,
.
map
=
live24ext_map
,
},
},
{
.
id
=
USB_ID
(
0x041e
,
0x3048
),
.
map
=
audigy2nx_map
,
.
selector_map
=
audigy2nx_selectors
,
},
{
{
/* Hercules DJ Console (Windows Edition) */
/* Hercules DJ Console (Windows Edition) */
.
id
=
USB_ID
(
0x06f8
,
0xb000
),
.
id
=
USB_ID
(
0x06f8
,
0xb000
),
...
@@ -311,6 +332,11 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = {
...
@@ -311,6 +332,11 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = {
.
id
=
USB_ID
(
0x0ccd
,
0x0028
),
.
id
=
USB_ID
(
0x0ccd
,
0x0028
),
.
map
=
aureon_51_2_map
,
.
map
=
aureon_51_2_map
,
},
},
{
.
id
=
USB_ID
(
0x13e5
,
0x0001
),
.
map
=
scratch_live_map
,
.
ignore_ctl_error
=
1
,
},
{
0
}
/* terminator */
{
0
}
/* terminator */
};
};
sound/usb/usbquirks.h
View file @
afad17c0
...
@@ -39,6 +39,16 @@
...
@@ -39,6 +39,16 @@
.idProduct = prod, \
.idProduct = prod, \
.bInterfaceClass = USB_CLASS_VENDOR_SPEC
.bInterfaceClass = USB_CLASS_VENDOR_SPEC
/* Creative/Toshiba Multimedia Center SB-0500 */
{
USB_DEVICE
(
0x041e
,
0x3048
),
.
driver_info
=
(
unsigned
long
)
&
(
const
struct
snd_usb_audio_quirk
)
{
.
vendor_name
=
"Toshiba"
,
.
product_name
=
"SB-0500"
,
.
ifnum
=
QUIRK_NO_INTERFACE
}
},
/* Creative/E-Mu devices */
/* Creative/E-Mu devices */
{
{
USB_DEVICE
(
0x041e
,
0x3010
),
USB_DEVICE
(
0x041e
,
0x3010
),
...
...
sound/usb/usx2y/usb_stream.c
View file @
afad17c0
...
@@ -557,7 +557,7 @@ static void stream_start(struct usb_stream_kernel *sk,
...
@@ -557,7 +557,7 @@ static void stream_start(struct usb_stream_kernel *sk,
s
->
idle_insize
-=
max_diff
-
max_diff_0
;
s
->
idle_insize
-=
max_diff
-
max_diff_0
;
s
->
idle_insize
+=
urb_size
-
s
->
period_size
;
s
->
idle_insize
+=
urb_size
-
s
->
period_size
;
if
(
s
->
idle_insize
<
0
)
{
if
(
s
->
idle_insize
<
0
)
{
snd_printk
(
"%i %i %i
\n
"
,
snd_printk
(
KERN_WARNING
"%i %i %i
\n
"
,
s
->
idle_insize
,
urb_size
,
s
->
period_size
);
s
->
idle_insize
,
urb_size
,
s
->
period_size
);
return
;
return
;
}
else
if
(
s
->
idle_insize
==
0
)
{
}
else
if
(
s
->
idle_insize
==
0
)
{
...
...
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