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
515511a7
Commit
515511a7
authored
May 13, 2016
by
Mark Brown
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'asoc/topic/hdmi' into asoc-next
parents
180bc41a
8f658815
Changes
13
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
969 additions
and
41 deletions
+969
-41
include/sound/hdmi-codec.h
include/sound/hdmi-codec.h
+100
-0
include/sound/pcm_iec958.h
include/sound/pcm_iec958.h
+2
-0
sound/core/pcm_iec958.c
sound/core/pcm_iec958.c
+48
-17
sound/hda/local.h
sound/hda/local.h
+10
-0
sound/soc/codecs/Kconfig
sound/soc/codecs/Kconfig
+6
-0
sound/soc/codecs/Makefile
sound/soc/codecs/Makefile
+2
-0
sound/soc/codecs/hdac_hdmi.c
sound/soc/codecs/hdac_hdmi.c
+154
-9
sound/soc/codecs/hdmi-codec.c
sound/soc/codecs/hdmi-codec.c
+432
-0
sound/soc/intel/boards/skl_nau88l25_max98357a.c
sound/soc/intel/boards/skl_nau88l25_max98357a.c
+71
-3
sound/soc/intel/boards/skl_nau88l25_ssm4567.c
sound/soc/intel/boards/skl_nau88l25_ssm4567.c
+70
-3
sound/soc/intel/boards/skl_rt286.c
sound/soc/intel/boards/skl_rt286.c
+47
-1
sound/soc/intel/skylake/skl-pcm.c
sound/soc/intel/skylake/skl-pcm.c
+7
-7
sound/soc/intel/skylake/skl-topology.c
sound/soc/intel/skylake/skl-topology.c
+20
-1
No files found.
include/sound/hdmi-codec.h
0 → 100644
View file @
515511a7
/*
* hdmi-codec.h - HDMI Codec driver API
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Jyri Sarha <jsarha@ti.com>
*
* 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 published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#ifndef __HDMI_CODEC_H__
#define __HDMI_CODEC_H__
#include <linux/hdmi.h>
#include <drm/drm_edid.h>
#include <sound/asoundef.h>
#include <uapi/sound/asound.h>
/*
* Protocol between ASoC cpu-dai and HDMI-encoder
*/
struct
hdmi_codec_daifmt
{
enum
{
HDMI_I2S
,
HDMI_RIGHT_J
,
HDMI_LEFT_J
,
HDMI_DSP_A
,
HDMI_DSP_B
,
HDMI_AC97
,
HDMI_SPDIF
,
}
fmt
;
int
bit_clk_inv
:
1
;
int
frame_clk_inv
:
1
;
int
bit_clk_master
:
1
;
int
frame_clk_master
:
1
;
};
/*
* HDMI audio parameters
*/
struct
hdmi_codec_params
{
struct
hdmi_audio_infoframe
cea
;
struct
snd_aes_iec958
iec
;
int
sample_rate
;
int
sample_width
;
int
channels
;
};
struct
hdmi_codec_ops
{
/*
* Called when ASoC starts an audio stream setup.
* Optional
*/
int
(
*
audio_startup
)(
struct
device
*
dev
);
/*
* Configures HDMI-encoder for audio stream.
* Mandatory
*/
int
(
*
hw_params
)(
struct
device
*
dev
,
struct
hdmi_codec_daifmt
*
fmt
,
struct
hdmi_codec_params
*
hparms
);
/*
* Shuts down the audio stream.
* Mandatory
*/
void
(
*
audio_shutdown
)(
struct
device
*
dev
);
/*
* Mute/unmute HDMI audio stream.
* Optional
*/
int
(
*
digital_mute
)(
struct
device
*
dev
,
bool
enable
);
/*
* Provides EDID-Like-Data from connected HDMI device.
* Optional
*/
int
(
*
get_eld
)(
struct
device
*
dev
,
uint8_t
*
buf
,
size_t
len
);
};
/* HDMI codec initalization data */
struct
hdmi_codec_pdata
{
const
struct
hdmi_codec_ops
*
ops
;
uint
i2s
:
1
;
uint
spdif
:
1
;
int
max_i2s_channels
;
};
#define HDMI_CODEC_DRV_NAME "hdmi-audio-codec"
#endif
/* __HDMI_CODEC_H__ */
include/sound/pcm_iec958.h
View file @
515511a7
...
...
@@ -6,4 +6,6 @@
int
snd_pcm_create_iec958_consumer
(
struct
snd_pcm_runtime
*
runtime
,
u8
*
cs
,
size_t
len
);
int
snd_pcm_create_iec958_consumer_hw_params
(
struct
snd_pcm_hw_params
*
params
,
u8
*
cs
,
size_t
len
);
#endif
sound/core/pcm_iec958.c
View file @
515511a7
...
...
@@ -9,30 +9,18 @@
#include <linux/types.h>
#include <sound/asoundef.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/pcm_iec958.h>
/**
* snd_pcm_create_iec958_consumer - create consumer format IEC958 channel status
* @runtime: pcm runtime structure with ->rate filled in
* @cs: channel status buffer, at least four bytes
* @len: length of channel status buffer
*
* Create the consumer format channel status data in @cs of maximum size
* @len corresponding to the parameters of the PCM runtime @runtime.
*
* Drivers may wish to tweak the contents of the buffer after creation.
*
* Returns: length of buffer, or negative error code if something failed.
*/
int
snd_pcm_create_iec958_consumer
(
struct
snd_pcm_runtime
*
runtime
,
u8
*
cs
,
size_t
len
)
static
int
create_iec958_consumer
(
uint
rate
,
uint
sample_width
,
u8
*
cs
,
size_t
len
)
{
unsigned
int
fs
,
ws
;
if
(
len
<
4
)
return
-
EINVAL
;
switch
(
r
untime
->
r
ate
)
{
switch
(
rate
)
{
case
32000
:
fs
=
IEC958_AES3_CON_FS_32000
;
break
;
...
...
@@ -59,7 +47,7 @@ int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
}
if
(
len
>
4
)
{
switch
(
s
nd_pcm_format_width
(
runtime
->
format
)
)
{
switch
(
s
ample_width
)
{
case
16
:
ws
=
IEC958_AES4_CON_WORDLEN_20_16
;
break
;
...
...
@@ -71,6 +59,7 @@ int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
IEC958_AES4_CON_MAX_WORDLEN_24
;
break
;
case
24
:
case
32
:
/* Assume 24-bit width for 32-bit samples. */
ws
=
IEC958_AES4_CON_WORDLEN_24_20
|
IEC958_AES4_CON_MAX_WORDLEN_24
;
break
;
...
...
@@ -92,4 +81,46 @@ int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
return
len
;
}
/**
* snd_pcm_create_iec958_consumer - create consumer format IEC958 channel status
* @runtime: pcm runtime structure with ->rate filled in
* @cs: channel status buffer, at least four bytes
* @len: length of channel status buffer
*
* Create the consumer format channel status data in @cs of maximum size
* @len corresponding to the parameters of the PCM runtime @runtime.
*
* Drivers may wish to tweak the contents of the buffer after creation.
*
* Returns: length of buffer, or negative error code if something failed.
*/
int
snd_pcm_create_iec958_consumer
(
struct
snd_pcm_runtime
*
runtime
,
u8
*
cs
,
size_t
len
)
{
return
create_iec958_consumer
(
runtime
->
rate
,
snd_pcm_format_width
(
runtime
->
format
),
cs
,
len
);
}
EXPORT_SYMBOL
(
snd_pcm_create_iec958_consumer
);
/**
* snd_pcm_create_iec958_consumer_hw_params - create IEC958 channel status
* @hw_params: the hw_params instance for extracting rate and sample format
* @cs: channel status buffer, at least four bytes
* @len: length of channel status buffer
*
* Create the consumer format channel status data in @cs of maximum size
* @len corresponding to the parameters of the PCM runtime @runtime.
*
* Drivers may wish to tweak the contents of the buffer after creation.
*
* Returns: length of buffer, or negative error code if something failed.
*/
int
snd_pcm_create_iec958_consumer_hw_params
(
struct
snd_pcm_hw_params
*
params
,
u8
*
cs
,
size_t
len
)
{
return
create_iec958_consumer
(
params_rate
(
params
),
params_width
(
params
),
cs
,
len
);
}
EXPORT_SYMBOL
(
snd_pcm_create_iec958_consumer_hw_params
);
sound/hda/local.h
View file @
515511a7
...
...
@@ -16,6 +16,16 @@ static inline int get_wcaps_type(unsigned int wcaps)
return
(
wcaps
&
AC_WCAP_TYPE
)
>>
AC_WCAP_TYPE_SHIFT
;
}
static
inline
unsigned
int
get_wcaps_channels
(
u32
wcaps
)
{
unsigned
int
chans
;
chans
=
(
wcaps
&
AC_WCAP_CHAN_CNT_EXT
)
>>
13
;
chans
=
(
chans
+
1
)
*
2
;
return
chans
;
}
extern
const
struct
attribute_group
*
hdac_dev_attr_groups
[];
int
hda_widget_sysfs_init
(
struct
hdac_device
*
codec
);
void
hda_widget_sysfs_exit
(
struct
hdac_device
*
codec
);
...
...
sound/soc/codecs/Kconfig
View file @
515511a7
...
...
@@ -88,6 +88,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_MC13783 if MFD_MC13XXX
select SND_SOC_ML26124 if I2C
select SND_SOC_NAU8825 if I2C
select SND_SOC_HDMI_CODEC
select SND_SOC_PCM1681 if I2C
select SND_SOC_PCM179X_I2C if I2C
select SND_SOC_PCM179X_SPI if SPI_MASTER
...
...
@@ -478,6 +479,11 @@ config SND_SOC_BT_SCO
config SND_SOC_DMIC
tristate
config SND_SOC_HDMI_CODEC
tristate
select SND_PCM_ELD
select SND_PCM_IEC958
config SND_SOC_ES8328
tristate "Everest Semi ES8328 CODEC"
...
...
sound/soc/codecs/Makefile
View file @
515511a7
...
...
@@ -81,6 +81,7 @@ snd-soc-max9850-objs := max9850.o
snd-soc-mc13783-objs
:=
mc13783.o
snd-soc-ml26124-objs
:=
ml26124.o
snd-soc-nau8825-objs
:=
nau8825.o
snd-soc-hdmi-codec-objs
:=
hdmi-codec.o
snd-soc-pcm1681-objs
:=
pcm1681.o
snd-soc-pcm179x-codec-objs
:=
pcm179x.o
snd-soc-pcm179x-i2c-objs
:=
pcm179x-i2c.o
...
...
@@ -291,6 +292,7 @@ obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
obj-$(CONFIG_SND_SOC_MC13783)
+=
snd-soc-mc13783.o
obj-$(CONFIG_SND_SOC_ML26124)
+=
snd-soc-ml26124.o
obj-$(CONFIG_SND_SOC_NAU8825)
+=
snd-soc-nau8825.o
obj-$(CONFIG_SND_SOC_HDMI_CODEC)
+=
snd-soc-hdmi-codec.o
obj-$(CONFIG_SND_SOC_PCM1681)
+=
snd-soc-pcm1681.o
obj-$(CONFIG_SND_SOC_PCM179X)
+=
snd-soc-pcm179x-codec.o
obj-$(CONFIG_SND_SOC_PCM179X_I2C)
+=
snd-soc-pcm179x-i2c.o
...
...
sound/soc/codecs/hdac_hdmi.c
View file @
515511a7
...
...
@@ -29,6 +29,7 @@
#include <sound/hdaudio_ext.h>
#include <sound/hda_i915.h>
#include <sound/pcm_drm_eld.h>
#include <sound/hda_chmap.h>
#include "../../hda/local.h"
#include "hdac_hdmi.h"
...
...
@@ -60,11 +61,17 @@ struct hdac_hdmi_cvt {
struct
hdac_hdmi_cvt_params
params
;
};
/* Currently only spk_alloc, more to be added */
struct
hdac_hdmi_parsed_eld
{
u8
spk_alloc
;
};
struct
hdac_hdmi_eld
{
bool
monitor_present
;
bool
eld_valid
;
int
eld_size
;
char
eld_buffer
[
ELD_MAX_SIZE
];
struct
hdac_hdmi_parsed_eld
info
;
};
struct
hdac_hdmi_pin
{
...
...
@@ -76,6 +83,10 @@ struct hdac_hdmi_pin {
struct
hdac_ext_device
*
edev
;
int
repoll_count
;
struct
delayed_work
work
;
struct
mutex
lock
;
bool
chmap_set
;
unsigned
char
chmap
[
8
];
/* ALSA API channel-map */
int
channels
;
/* current number of channels */
};
struct
hdac_hdmi_pcm
{
...
...
@@ -100,8 +111,22 @@ struct hdac_hdmi_priv {
int
num_pin
;
int
num_cvt
;
struct
mutex
pin_mutex
;
struct
hdac_chmap
chmap
;
};
static
struct
hdac_hdmi_pcm
*
get_hdmi_pcm_from_id
(
struct
hdac_hdmi_priv
*
hdmi
,
int
pcm_idx
)
{
struct
hdac_hdmi_pcm
*
pcm
;
list_for_each_entry
(
pcm
,
&
hdmi
->
pcm_list
,
head
)
{
if
(
pcm
->
pcm_id
==
pcm_idx
)
return
pcm
;
}
return
NULL
;
}
static
inline
struct
hdac_ext_device
*
to_hda_ext_device
(
struct
device
*
dev
)
{
struct
hdac_device
*
hdac
=
dev_to_hdac_dev
(
dev
);
...
...
@@ -278,26 +303,31 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
int
i
;
const
u8
*
eld_buf
;
u8
conn_type
;
int
channels
=
2
;
int
channels
,
ca
;
list_for_each_entry
(
pin
,
&
hdmi
->
pin_list
,
head
)
{
if
(
pin
->
nid
==
pin_nid
)
break
;
}
ca
=
snd_hdac_channel_allocation
(
&
hdac
->
hdac
,
pin
->
eld
.
info
.
spk_alloc
,
pin
->
channels
,
pin
->
chmap_set
,
true
,
pin
->
chmap
);
channels
=
snd_hdac_get_active_channels
(
ca
);
hdmi
->
chmap
.
ops
.
set_channel_count
(
&
hdac
->
hdac
,
cvt_nid
,
channels
);
snd_hdac_setup_channel_mapping
(
&
hdmi
->
chmap
,
pin
->
nid
,
false
,
ca
,
pin
->
channels
,
pin
->
chmap
,
pin
->
chmap_set
);
eld_buf
=
pin
->
eld
.
eld_buffer
;
conn_type
=
drm_eld_get_conn_type
(
eld_buf
);
/* setup channel count */
snd_hdac_codec_write
(
&
hdac
->
hdac
,
cvt_nid
,
0
,
AC_VERB_SET_CVT_CHAN_COUNT
,
channels
-
1
);
switch
(
conn_type
)
{
case
DRM_ELD_CONN_TYPE_HDMI
:
hdmi_audio_infoframe_init
(
&
frame
);
/* Default stereo for now */
frame
.
channels
=
channels
;
frame
.
channel_allocation
=
ca
;
ret
=
hdmi_audio_infoframe_pack
(
&
frame
,
buffer
,
sizeof
(
buffer
));
if
(
ret
<
0
)
...
...
@@ -311,7 +341,7 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
dp_ai
.
len
=
0x1b
;
dp_ai
.
ver
=
0x11
<<
2
;
dp_ai
.
CC02_CT47
=
channels
-
1
;
dp_ai
.
CA
=
0
;
dp_ai
.
CA
=
ca
;
dip
=
(
u8
*
)
&
dp_ai
;
break
;
...
...
@@ -370,17 +400,23 @@ static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream,
struct
hdac_ext_device
*
hdac
=
snd_soc_dai_get_drvdata
(
dai
);
struct
hdac_hdmi_priv
*
hdmi
=
hdac
->
private_data
;
struct
hdac_hdmi_dai_pin_map
*
dai_map
;
struct
hdac_hdmi_pin
*
pin
;
struct
hdac_ext_dma_params
*
dd
;
int
ret
;
dai_map
=
&
hdmi
->
dai_map
[
dai
->
id
];
pin
=
dai_map
->
pin
;
dd
=
(
struct
hdac_ext_dma_params
*
)
snd_soc_dai_get_dma_data
(
dai
,
substream
);
dev_dbg
(
&
hdac
->
hdac
.
dev
,
"stream tag from cpu dai %d format in cvt 0x%x
\n
"
,
dd
->
stream_tag
,
dd
->
format
);
mutex_lock
(
&
pin
->
lock
);
pin
->
channels
=
substream
->
runtime
->
channels
;
ret
=
hdac_hdmi_setup_audio_infoframe
(
hdac
,
dai_map
->
cvt
->
nid
,
dai_map
->
pin
->
nid
);
mutex_unlock
(
&
pin
->
lock
);
if
(
ret
<
0
)
return
ret
;
...
...
@@ -640,6 +676,12 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
snd_hdac_codec_write
(
&
hdac
->
hdac
,
dai_map
->
pin
->
nid
,
0
,
AC_VERB_SET_AMP_GAIN_MUTE
,
AMP_OUT_MUTE
);
mutex_lock
(
&
dai_map
->
pin
->
lock
);
dai_map
->
pin
->
chmap_set
=
false
;
memset
(
dai_map
->
pin
->
chmap
,
0
,
sizeof
(
dai_map
->
pin
->
chmap
));
dai_map
->
pin
->
channels
=
0
;
mutex_unlock
(
&
dai_map
->
pin
->
lock
);
dai_map
->
pin
=
NULL
;
}
}
...
...
@@ -647,10 +689,19 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
static
int
hdac_hdmi_query_cvt_params
(
struct
hdac_device
*
hdac
,
struct
hdac_hdmi_cvt
*
cvt
)
{
unsigned
int
chans
;
struct
hdac_ext_device
*
edev
=
to_ehdac_device
(
hdac
);
struct
hdac_hdmi_priv
*
hdmi
=
edev
->
private_data
;
int
err
;
/* Only stereo supported as of now */
cvt
->
params
.
channels_min
=
cvt
->
params
.
channels_max
=
2
;
chans
=
get_wcaps
(
hdac
,
cvt
->
nid
);
chans
=
get_wcaps_channels
(
chans
);
cvt
->
params
.
channels_min
=
2
;
cvt
->
params
.
channels_max
=
chans
;
if
(
chans
>
hdmi
->
chmap
.
channels_max
)
hdmi
->
chmap
.
channels_max
=
chans
;
err
=
snd_hdac_query_supported_pcm
(
hdac
,
cvt
->
nid
,
&
cvt
->
params
.
rates
,
...
...
@@ -1008,6 +1059,12 @@ static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid)
return
hdac_hdmi_query_cvt_params
(
&
edev
->
hdac
,
cvt
);
}
static
void
hdac_hdmi_parse_eld
(
struct
hdac_ext_device
*
edev
,
struct
hdac_hdmi_pin
*
pin
)
{
pin
->
eld
.
info
.
spk_alloc
=
pin
->
eld
.
eld_buffer
[
DRM_ELD_SPEAKER
];
}
static
void
hdac_hdmi_present_sense
(
struct
hdac_hdmi_pin
*
pin
,
int
repoll
)
{
struct
hdac_ext_device
*
edev
=
pin
->
edev
;
...
...
@@ -1065,6 +1122,7 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
snd_jack_report
(
pcm
->
jack
,
SND_JACK_AVOUT
);
}
hdac_hdmi_parse_eld
(
edev
,
pin
);
print_hex_dump_bytes
(
"ELD: "
,
DUMP_PREFIX_OFFSET
,
pin
->
eld
.
eld_buffer
,
pin
->
eld
.
eld_size
);
...
...
@@ -1123,6 +1181,7 @@ static int hdac_hdmi_add_pin(struct hdac_ext_device *edev, hda_nid_t nid)
hdmi
->
num_pin
++
;
pin
->
edev
=
edev
;
mutex_init
(
&
pin
->
lock
);
INIT_DELAYED_WORK
(
&
pin
->
work
,
hdac_hdmi_repoll_eld
);
return
0
;
...
...
@@ -1342,6 +1401,19 @@ static struct i915_audio_component_audio_ops aops = {
.
pin_eld_notify
=
hdac_hdmi_eld_notify_cb
,
};
static
struct
snd_pcm
*
hdac_hdmi_get_pcm_from_id
(
struct
snd_soc_card
*
card
,
int
device
)
{
struct
snd_soc_pcm_runtime
*
rtd
;
list_for_each_entry
(
rtd
,
&
card
->
rtd_list
,
list
)
{
if
(
rtd
->
pcm
&&
(
rtd
->
pcm
->
device
==
device
))
return
rtd
->
pcm
;
}
return
NULL
;
}
int
hdac_hdmi_jack_init
(
struct
snd_soc_dai
*
dai
,
int
device
)
{
char
jack_name
[
NAME_SIZE
];
...
...
@@ -1351,6 +1423,8 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
snd_soc_component_get_dapm
(
&
codec
->
component
);
struct
hdac_hdmi_priv
*
hdmi
=
edev
->
private_data
;
struct
hdac_hdmi_pcm
*
pcm
;
struct
snd_pcm
*
snd_pcm
;
int
err
;
/*
* this is a new PCM device, create new pcm and
...
...
@@ -1362,6 +1436,18 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
pcm
->
pcm_id
=
device
;
pcm
->
cvt
=
hdmi
->
dai_map
[
dai
->
id
].
cvt
;
snd_pcm
=
hdac_hdmi_get_pcm_from_id
(
dai
->
component
->
card
,
device
);
if
(
snd_pcm
)
{
err
=
snd_hdac_add_chmap_ctls
(
snd_pcm
,
device
,
&
hdmi
->
chmap
);
if
(
err
<
0
)
{
dev_err
(
&
edev
->
hdac
.
dev
,
"chmap control add failed with err: %d for pcm: %d
\n
"
,
err
,
device
);
kfree
(
pcm
);
return
err
;
}
}
list_add_tail
(
&
pcm
->
head
,
&
hdmi
->
pcm_list
);
sprintf
(
jack_name
,
"HDMI/DP, pcm=%d Jack"
,
device
);
...
...
@@ -1483,6 +1569,60 @@ static struct snd_soc_codec_driver hdmi_hda_codec = {
.
idle_bias_off
=
true
,
};
static
void
hdac_hdmi_get_chmap
(
struct
hdac_device
*
hdac
,
int
pcm_idx
,
unsigned
char
*
chmap
)
{
struct
hdac_ext_device
*
edev
=
to_ehdac_device
(
hdac
);
struct
hdac_hdmi_priv
*
hdmi
=
edev
->
private_data
;
struct
hdac_hdmi_pcm
*
pcm
=
get_hdmi_pcm_from_id
(
hdmi
,
pcm_idx
);
struct
hdac_hdmi_pin
*
pin
=
pcm
->
pin
;
/* chmap is already set to 0 in caller */
if
(
!
pin
)
return
;
memcpy
(
chmap
,
pin
->
chmap
,
ARRAY_SIZE
(
pin
->
chmap
));
}
static
void
hdac_hdmi_set_chmap
(
struct
hdac_device
*
hdac
,
int
pcm_idx
,
unsigned
char
*
chmap
,
int
prepared
)
{
struct
hdac_ext_device
*
edev
=
to_ehdac_device
(
hdac
);
struct
hdac_hdmi_priv
*
hdmi
=
edev
->
private_data
;
struct
hdac_hdmi_pcm
*
pcm
=
get_hdmi_pcm_from_id
(
hdmi
,
pcm_idx
);
struct
hdac_hdmi_pin
*
pin
=
pcm
->
pin
;
mutex_lock
(
&
pin
->
lock
);
pin
->
chmap_set
=
true
;
memcpy
(
pin
->
chmap
,
chmap
,
ARRAY_SIZE
(
pin
->
chmap
));
if
(
prepared
)
hdac_hdmi_setup_audio_infoframe
(
edev
,
pcm
->
cvt
->
nid
,
pin
->
nid
);
mutex_unlock
(
&
pin
->
lock
);
}
static
bool
is_hdac_hdmi_pcm_attached
(
struct
hdac_device
*
hdac
,
int
pcm_idx
)
{
struct
hdac_ext_device
*
edev
=
to_ehdac_device
(
hdac
);
struct
hdac_hdmi_priv
*
hdmi
=
edev
->
private_data
;
struct
hdac_hdmi_pcm
*
pcm
=
get_hdmi_pcm_from_id
(
hdmi
,
pcm_idx
);
struct
hdac_hdmi_pin
*
pin
=
pcm
->
pin
;
return
pin
?
true
:
false
;
}
static
int
hdac_hdmi_get_spk_alloc
(
struct
hdac_device
*
hdac
,
int
pcm_idx
)
{
struct
hdac_ext_device
*
edev
=
to_ehdac_device
(
hdac
);
struct
hdac_hdmi_priv
*
hdmi
=
edev
->
private_data
;
struct
hdac_hdmi_pcm
*
pcm
=
get_hdmi_pcm_from_id
(
hdmi
,
pcm_idx
);
struct
hdac_hdmi_pin
*
pin
=
pcm
->
pin
;
if
(
!
pin
||
!
pin
->
eld
.
eld_valid
)
return
0
;
return
pin
->
eld
.
info
.
spk_alloc
;
}
static
int
hdac_hdmi_dev_probe
(
struct
hdac_ext_device
*
edev
)
{
struct
hdac_device
*
codec
=
&
edev
->
hdac
;
...
...
@@ -1501,6 +1641,11 @@ static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
return
-
ENOMEM
;
edev
->
private_data
=
hdmi_priv
;
snd_hdac_register_chmap_ops
(
codec
,
&
hdmi_priv
->
chmap
);
hdmi_priv
->
chmap
.
ops
.
get_chmap
=
hdac_hdmi_get_chmap
;
hdmi_priv
->
chmap
.
ops
.
set_chmap
=
hdac_hdmi_set_chmap
;
hdmi_priv
->
chmap
.
ops
.
is_pcm_attached
=
is_hdac_hdmi_pcm_attached
;
hdmi_priv
->
chmap
.
ops
.
get_spk_alloc
=
hdac_hdmi_get_spk_alloc
;
dev_set_drvdata
(
&
codec
->
dev
,
edev
);
...
...
sound/soc/codecs/hdmi-codec.c
0 → 100644
View file @
515511a7
This diff is collapsed.
Click to expand it.
sound/soc/intel/boards/skl_nau88l25_max98357a.c
View file @
515511a7
...
...
@@ -30,6 +30,16 @@
static
struct
snd_soc_jack
skylake_headset
;
static
struct
snd_soc_card
skylake_audio_card
;
struct
skl_hdmi_pcm
{
struct
list_head
head
;
struct
snd_soc_dai
*
codec_dai
;
int
device
;
};
struct
skl_nau8825_private
{
struct
list_head
hdmi_pcm_list
;
};
enum
{
SKL_DPCM_AUDIO_PB
=
0
,
SKL_DPCM_AUDIO_CP
,
...
...
@@ -192,23 +202,56 @@ static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
static
int
skylake_hdmi1_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_nau8825_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI1_PB
;
pcm
->
codec_dai
=
dai
;
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI1_PB
);
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
0
;
}
static
int
skylake_hdmi2_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_nau8825_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI2_PB
);
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI2_PB
;
pcm
->
codec_dai
=
dai
;
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
0
;
}
static
int
skylake_hdmi3_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_nau8825_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI3_PB
);
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI3_PB
;
pcm
->
codec_dai
=
dai
;
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
0
;
}
static
int
skylake_nau8825_fe_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
...
...
@@ -533,6 +576,21 @@ static struct snd_soc_dai_link skylake_dais[] = {
},
};
static
int
skylake_card_late_probe
(
struct
snd_soc_card
*
card
)
{
struct
skl_nau8825_private
*
ctx
=
snd_soc_card_get_drvdata
(
card
);
struct
skl_hdmi_pcm
*
pcm
;
int
err
;
list_for_each_entry
(
pcm
,
&
ctx
->
hdmi_pcm_list
,
head
)
{
err
=
hdac_hdmi_jack_init
(
pcm
->
codec_dai
,
pcm
->
device
);
if
(
err
<
0
)
return
err
;
}
return
0
;
}
/* skylake audio machine driver for SPT + NAU88L25 */
static
struct
snd_soc_card
skylake_audio_card
=
{
.
name
=
"sklnau8825max"
,
...
...
@@ -546,11 +604,21 @@ static struct snd_soc_card skylake_audio_card = {
.
dapm_routes
=
skylake_map
,
.
num_dapm_routes
=
ARRAY_SIZE
(
skylake_map
),
.
fully_routed
=
true
,
.
late_probe
=
skylake_card_late_probe
,
};
static
int
skylake_audio_probe
(
struct
platform_device
*
pdev
)
{
struct
skl_nau8825_private
*
ctx
;
ctx
=
devm_kzalloc
(
&
pdev
->
dev
,
sizeof
(
*
ctx
),
GFP_ATOMIC
);
if
(
!
ctx
)
return
-
ENOMEM
;
INIT_LIST_HEAD
(
&
ctx
->
hdmi_pcm_list
);
skylake_audio_card
.
dev
=
&
pdev
->
dev
;
snd_soc_card_set_drvdata
(
&
skylake_audio_card
,
ctx
);
return
devm_snd_soc_register_card
(
&
pdev
->
dev
,
&
skylake_audio_card
);
}
...
...
sound/soc/intel/boards/skl_nau88l25_ssm4567.c
View file @
515511a7
...
...
@@ -34,6 +34,15 @@
static
struct
snd_soc_jack
skylake_headset
;
static
struct
snd_soc_card
skylake_audio_card
;
struct
skl_hdmi_pcm
{
struct
list_head
head
;
struct
snd_soc_dai
*
codec_dai
;
int
device
;
};
struct
skl_nau88125_private
{
struct
list_head
hdmi_pcm_list
;
};
enum
{
SKL_DPCM_AUDIO_PB
=
0
,
SKL_DPCM_AUDIO_CP
,
...
...
@@ -222,24 +231,57 @@ static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
static
int
skylake_hdmi1_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_nau88125_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI1_PB
;
pcm
->
codec_dai
=
dai
;
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI1_PB
)
;
return
0
;
}
static
int
skylake_hdmi2_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_nau88125_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI2_PB
;
pcm
->
codec_dai
=
dai
;
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI2_PB
);
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
0
;
}
static
int
skylake_hdmi3_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_nau88125_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI3_PB
);
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI3_PB
;
pcm
->
codec_dai
=
dai
;
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
0
;
}
static
int
skylake_nau8825_fe_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
...
...
@@ -584,6 +626,21 @@ static struct snd_soc_dai_link skylake_dais[] = {
},
};
static
int
skylake_card_late_probe
(
struct
snd_soc_card
*
card
)
{
struct
skl_nau88125_private
*
ctx
=
snd_soc_card_get_drvdata
(
card
);
struct
skl_hdmi_pcm
*
pcm
;
int
err
;
list_for_each_entry
(
pcm
,
&
ctx
->
hdmi_pcm_list
,
head
)
{
err
=
hdac_hdmi_jack_init
(
pcm
->
codec_dai
,
pcm
->
device
);
if
(
err
<
0
)
return
err
;
}
return
0
;
}
/* skylake audio machine driver for SPT + NAU88L25 */
static
struct
snd_soc_card
skylake_audio_card
=
{
.
name
=
"sklnau8825adi"
,
...
...
@@ -599,11 +656,21 @@ static struct snd_soc_card skylake_audio_card = {
.
codec_conf
=
ssm4567_codec_conf
,
.
num_configs
=
ARRAY_SIZE
(
ssm4567_codec_conf
),
.
fully_routed
=
true
,
.
late_probe
=
skylake_card_late_probe
,
};
static
int
skylake_audio_probe
(
struct
platform_device
*
pdev
)
{
struct
skl_nau88125_private
*
ctx
;
ctx
=
devm_kzalloc
(
&
pdev
->
dev
,
sizeof
(
*
ctx
),
GFP_ATOMIC
);
if
(
!
ctx
)
return
-
ENOMEM
;
INIT_LIST_HEAD
(
&
ctx
->
hdmi_pcm_list
);
skylake_audio_card
.
dev
=
&
pdev
->
dev
;
snd_soc_card_set_drvdata
(
&
skylake_audio_card
,
ctx
);
return
devm_snd_soc_register_card
(
&
pdev
->
dev
,
&
skylake_audio_card
);
}
...
...
sound/soc/intel/boards/skl_rt286.c
View file @
515511a7
...
...
@@ -30,6 +30,16 @@
static
struct
snd_soc_jack
skylake_headset
;
struct
skl_hdmi_pcm
{
struct
list_head
head
;
struct
snd_soc_dai
*
codec_dai
;
int
device
;
};
struct
skl_rt286_private
{
struct
list_head
hdmi_pcm_list
;
};
enum
{
SKL_DPCM_AUDIO_PB
=
0
,
SKL_DPCM_AUDIO_CP
,
...
...
@@ -142,9 +152,20 @@ static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
static
int
skylake_hdmi_init
(
struct
snd_soc_pcm_runtime
*
rtd
)
{
struct
skl_rt286_private
*
ctx
=
snd_soc_card_get_drvdata
(
rtd
->
card
);
struct
snd_soc_dai
*
dai
=
rtd
->
codec_dai
;
struct
skl_hdmi_pcm
*
pcm
;
pcm
=
devm_kzalloc
(
rtd
->
card
->
dev
,
sizeof
(
*
pcm
),
GFP_KERNEL
);
if
(
!
pcm
)
return
-
ENOMEM
;
return
hdac_hdmi_jack_init
(
dai
,
SKL_DPCM_AUDIO_HDMI1_PB
+
dai
->
id
);
pcm
->
device
=
SKL_DPCM_AUDIO_HDMI1_PB
+
dai
->
id
;
pcm
->
codec_dai
=
dai
;
list_add_tail
(
&
pcm
->
head
,
&
ctx
->
hdmi_pcm_list
);
return
0
;
}
static
unsigned
int
rates
[]
=
{
...
...
@@ -437,6 +458,21 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
},
};
static
int
skylake_card_late_probe
(
struct
snd_soc_card
*
card
)
{
struct
skl_rt286_private
*
ctx
=
snd_soc_card_get_drvdata
(
card
);
struct
skl_hdmi_pcm
*
pcm
;
int
err
;
list_for_each_entry
(
pcm
,
&
ctx
->
hdmi_pcm_list
,
head
)
{
err
=
hdac_hdmi_jack_init
(
pcm
->
codec_dai
,
pcm
->
device
);
if
(
err
<
0
)
return
err
;
}
return
0
;
}
/* skylake audio machine driver for SPT + RT286S */
static
struct
snd_soc_card
skylake_rt286
=
{
.
name
=
"skylake-rt286"
,
...
...
@@ -450,11 +486,21 @@ static struct snd_soc_card skylake_rt286 = {
.
dapm_routes
=
skylake_rt286_map
,
.
num_dapm_routes
=
ARRAY_SIZE
(
skylake_rt286_map
),
.
fully_routed
=
true
,
.
late_probe
=
skylake_card_late_probe
,
};
static
int
skylake_audio_probe
(
struct
platform_device
*
pdev
)
{
struct
skl_rt286_private
*
ctx
;
ctx
=
devm_kzalloc
(
&
pdev
->
dev
,
sizeof
(
*
ctx
),
GFP_ATOMIC
);
if
(
!
ctx
)
return
-
ENOMEM
;
INIT_LIST_HEAD
(
&
ctx
->
hdmi_pcm_list
);
skylake_rt286
.
dev
=
&
pdev
->
dev
;
snd_soc_card_set_drvdata
(
&
skylake_rt286
,
ctx
);
return
devm_snd_soc_register_card
(
&
pdev
->
dev
,
&
skylake_rt286
);
}
...
...
sound/soc/intel/skylake/skl-pcm.c
View file @
515511a7
...
...
@@ -51,7 +51,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
.
rate_min
=
8000
,
.
rate_max
=
48000
,
.
channels_min
=
1
,
.
channels_max
=
HDA_QUAD
,
.
channels_max
=
8
,
.
buffer_bytes_max
=
AZX_MAX_BUF_SIZE
,
.
period_bytes_min
=
128
,
.
period_bytes_max
=
AZX_MAX_BUF_SIZE
/
2
,
...
...
@@ -691,7 +691,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.
playback
=
{
.
stream_name
=
"HDMI1 Playback"
,
.
channels_min
=
HDA_STEREO
,
.
channels_max
=
HDA_STEREO
,
.
channels_max
=
8
,
.
rates
=
SNDRV_PCM_RATE_32000
|
SNDRV_PCM_RATE_44100
|
SNDRV_PCM_RATE_48000
|
SNDRV_PCM_RATE_88200
|
SNDRV_PCM_RATE_96000
|
SNDRV_PCM_RATE_176400
|
...
...
@@ -706,7 +706,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.
playback
=
{
.
stream_name
=
"HDMI2 Playback"
,
.
channels_min
=
HDA_STEREO
,
.
channels_max
=
HDA_STEREO
,
.
channels_max
=
8
,
.
rates
=
SNDRV_PCM_RATE_32000
|
SNDRV_PCM_RATE_44100
|
SNDRV_PCM_RATE_48000
|
SNDRV_PCM_RATE_88200
|
SNDRV_PCM_RATE_96000
|
SNDRV_PCM_RATE_176400
|
...
...
@@ -721,7 +721,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.
playback
=
{
.
stream_name
=
"HDMI3 Playback"
,
.
channels_min
=
HDA_STEREO
,
.
channels_max
=
HDA_STEREO
,
.
channels_max
=
8
,
.
rates
=
SNDRV_PCM_RATE_32000
|
SNDRV_PCM_RATE_44100
|
SNDRV_PCM_RATE_48000
|
SNDRV_PCM_RATE_88200
|
SNDRV_PCM_RATE_96000
|
SNDRV_PCM_RATE_176400
|
...
...
@@ -846,7 +846,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.
playback
=
{
.
stream_name
=
"iDisp1 Tx"
,
.
channels_min
=
HDA_STEREO
,
.
channels_max
=
HDA_STEREO
,
.
channels_max
=
8
,
.
rates
=
SNDRV_PCM_RATE_8000
|
SNDRV_PCM_RATE_16000
|
SNDRV_PCM_RATE_48000
,
.
formats
=
SNDRV_PCM_FMTBIT_S16_LE
|
SNDRV_PCM_FMTBIT_S32_LE
|
SNDRV_PCM_FMTBIT_S24_LE
,
...
...
@@ -858,7 +858,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.
playback
=
{
.
stream_name
=
"iDisp2 Tx"
,
.
channels_min
=
HDA_STEREO
,
.
channels_max
=
HDA_STEREO
,
.
channels_max
=
8
,
.
rates
=
SNDRV_PCM_RATE_8000
|
SNDRV_PCM_RATE_16000
|
SNDRV_PCM_RATE_48000
,
.
formats
=
SNDRV_PCM_FMTBIT_S16_LE
|
SNDRV_PCM_FMTBIT_S32_LE
|
...
...
@@ -871,7 +871,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.
playback
=
{
.
stream_name
=
"iDisp3 Tx"
,
.
channels_min
=
HDA_STEREO
,
.
channels_max
=
HDA_STEREO
,
.
channels_max
=
8
,
.
rates
=
SNDRV_PCM_RATE_8000
|
SNDRV_PCM_RATE_16000
|
SNDRV_PCM_RATE_48000
,
.
formats
=
SNDRV_PCM_FMTBIT_S16_LE
|
SNDRV_PCM_FMTBIT_S32_LE
|
...
...
sound/soc/intel/skylake/skl-topology.c
View file @
515511a7
...
...
@@ -154,13 +154,32 @@ static void skl_dump_mconfig(struct skl_sst *ctx,
dev_dbg
(
ctx
->
dev
,
"ch_cfg = %d
\n
"
,
mcfg
->
out_fmt
[
0
].
ch_cfg
);
}
static
void
skl_tplg_update_chmap
(
struct
skl_module_fmt
*
fmt
,
int
chs
)
{
int
slot_map
=
0xFFFFFFFF
;
int
start_slot
=
0
;
int
i
;
for
(
i
=
0
;
i
<
chs
;
i
++
)
{
/*
* For 2 channels with starting slot as 0, slot map will
* look like 0xFFFFFF10.
*/
slot_map
&=
(
~
(
0xF
<<
(
4
*
i
))
|
(
start_slot
<<
(
4
*
i
)));
start_slot
++
;
}
fmt
->
ch_map
=
slot_map
;
}
static
void
skl_tplg_update_params
(
struct
skl_module_fmt
*
fmt
,
struct
skl_pipe_params
*
params
,
int
fixup
)
{
if
(
fixup
&
SKL_RATE_FIXUP_MASK
)
fmt
->
s_freq
=
params
->
s_freq
;
if
(
fixup
&
SKL_CH_FIXUP_MASK
)
if
(
fixup
&
SKL_CH_FIXUP_MASK
)
{
fmt
->
channels
=
params
->
ch
;
skl_tplg_update_chmap
(
fmt
,
fmt
->
channels
);
}
if
(
fixup
&
SKL_FMT_FIXUP_MASK
)
{
fmt
->
valid_bit_depth
=
skl_get_bit_depth
(
params
->
s_fmt
);
...
...
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