Commit c41788c0 authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branch 'asoc/topic/samsung' into asoc-next

parents 3f151cbf 06b10ff9
...@@ -2,7 +2,15 @@ ...@@ -2,7 +2,15 @@
Required SoC Specific Properties: Required SoC Specific Properties:
- compatible : "samsung,i2s-v5" - compatible : should be one of the following.
- samsung,s3c6410-i2s: for 8/16/24bit stereo I2S.
- samsung,s5pv210-i2s: for 8/16/24bit multichannel(5.1) I2S with
secondary fifo, s/w reset control and internal mux for root clk src.
- samsung,exynos5420-i2s: for 8/16/24bit multichannel(7.1) I2S with
secondary fifo, s/w reset control, internal mux for root clk src and
TDM support. TDM (Time division multiplexing) is to allow transfer of
multiple channel audio data on single data line.
- reg: physical base address of the controller and length of memory mapped - reg: physical base address of the controller and length of memory mapped
region. region.
- dmas: list of DMA controller phandle and DMA request line ordered pairs. - dmas: list of DMA controller phandle and DMA request line ordered pairs.
...@@ -21,13 +29,6 @@ Required SoC Specific Properties: ...@@ -21,13 +29,6 @@ Required SoC Specific Properties:
Optional SoC Specific Properties: Optional SoC Specific Properties:
- samsung,supports-6ch: If the I2S Primary sound source has 5.1 Channel
support, this flag is enabled.
- samsung,supports-rstclr: This flag should be set if I2S software reset bit
control is required. When this flag is set I2S software reset bit will be
enabled or disabled based on need.
- samsung,supports-secdai:If I2S block has a secondary FIFO and internal DMA,
then this flag is enabled.
- samsung,idma-addr: Internal DMA register base address of the audio - samsung,idma-addr: Internal DMA register base address of the audio
sub system(used in secondary sound source). sub system(used in secondary sound source).
- pinctrl-0: Should specify pin control groups used for this controller. - pinctrl-0: Should specify pin control groups used for this controller.
...@@ -36,7 +37,7 @@ Optional SoC Specific Properties: ...@@ -36,7 +37,7 @@ Optional SoC Specific Properties:
Example: Example:
i2s0: i2s@03830000 { i2s0: i2s@03830000 {
compatible = "samsung,i2s-v5"; compatible = "samsung,s5pv210-i2s";
reg = <0x03830000 0x100>; reg = <0x03830000 0x100>;
dmas = <&pdma0 10 dmas = <&pdma0 10
&pdma0 9 &pdma0 9
...@@ -46,9 +47,6 @@ i2s0: i2s@03830000 { ...@@ -46,9 +47,6 @@ i2s0: i2s@03830000 {
<&clock_audss EXYNOS_I2S_BUS>, <&clock_audss EXYNOS_I2S_BUS>,
<&clock_audss EXYNOS_SCLK_I2S>; <&clock_audss EXYNOS_SCLK_I2S>;
clock-names = "iis", "i2s_opclk0", "i2s_opclk1"; clock-names = "iis", "i2s_opclk0", "i2s_opclk1";
samsung,supports-6ch;
samsung,supports-rstclr;
samsung,supports-secdai;
samsung,idma-addr = <0x03000000>; samsung,idma-addr = <0x03000000>;
pinctrl-names = "default"; pinctrl-names = "default";
pinctrl-0 = <&i2s0_bus>; pinctrl-0 = <&i2s0_bus>;
......
...@@ -405,7 +405,7 @@ dwmmc_3: dwmmc3@12230000 { ...@@ -405,7 +405,7 @@ dwmmc_3: dwmmc3@12230000 {
}; };
i2s0: i2s@03830000 { i2s0: i2s@03830000 {
compatible = "samsung,i2s-v5"; compatible = "samsung,s5pv210-i2s";
reg = <0x03830000 0x100>; reg = <0x03830000 0x100>;
dmas = <&pdma0 10 dmas = <&pdma0 10
&pdma0 9 &pdma0 9
...@@ -415,16 +415,13 @@ &pdma0 9 ...@@ -415,16 +415,13 @@ &pdma0 9
<&clock_audss EXYNOS_I2S_BUS>, <&clock_audss EXYNOS_I2S_BUS>,
<&clock_audss EXYNOS_SCLK_I2S>; <&clock_audss EXYNOS_SCLK_I2S>;
clock-names = "iis", "i2s_opclk0", "i2s_opclk1"; clock-names = "iis", "i2s_opclk0", "i2s_opclk1";
samsung,supports-6ch;
samsung,supports-rstclr;
samsung,supports-secdai;
samsung,idma-addr = <0x03000000>; samsung,idma-addr = <0x03000000>;
pinctrl-names = "default"; pinctrl-names = "default";
pinctrl-0 = <&i2s0_bus>; pinctrl-0 = <&i2s0_bus>;
}; };
i2s1: i2s@12D60000 { i2s1: i2s@12D60000 {
compatible = "samsung,i2s-v5"; compatible = "samsung,s3c6410-i2s";
reg = <0x12D60000 0x100>; reg = <0x12D60000 0x100>;
dmas = <&pdma1 12 dmas = <&pdma1 12
&pdma1 11>; &pdma1 11>;
...@@ -436,7 +433,7 @@ i2s1: i2s@12D60000 { ...@@ -436,7 +433,7 @@ i2s1: i2s@12D60000 {
}; };
i2s2: i2s@12D70000 { i2s2: i2s@12D70000 {
compatible = "samsung,i2s-v5"; compatible = "samsung,s3c6410-i2s";
reg = <0x12D70000 0x100>; reg = <0x12D70000 0x100>;
dmas = <&pdma0 12 dmas = <&pdma0 12
&pdma0 11>; &pdma0 11>;
......
...@@ -36,6 +36,7 @@ struct samsung_i2s { ...@@ -36,6 +36,7 @@ struct samsung_i2s {
*/ */
#define QUIRK_NO_MUXPSR (1 << 2) #define QUIRK_NO_MUXPSR (1 << 2)
#define QUIRK_NEED_RSTCLR (1 << 3) #define QUIRK_NEED_RSTCLR (1 << 3)
#define QUIRK_SUPPORTS_TDM (1 << 4)
/* Quirks of the I2S controller */ /* Quirks of the I2S controller */
u32 quirks; u32 quirks;
dma_addr_t idma_addr; dma_addr_t idma_addr;
......
...@@ -404,18 +404,13 @@ static int s3c_ac97_probe(struct platform_device *pdev) ...@@ -404,18 +404,13 @@ static int s3c_ac97_probe(struct platform_device *pdev)
return -ENXIO; return -ENXIO;
} }
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {
dev_err(&pdev->dev, "Unable to get register resource\n");
return -ENXIO;
}
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq_res) { if (!irq_res) {
dev_err(&pdev->dev, "AC97 IRQ not provided!\n"); dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
return -ENXIO; return -ENXIO;
} }
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
s3c_ac97.regs = devm_ioremap_resource(&pdev->dev, mem_res); s3c_ac97.regs = devm_ioremap_resource(&pdev->dev, mem_res);
if (IS_ERR(s3c_ac97.regs)) if (IS_ERR(s3c_ac97.regs))
return PTR_ERR(s3c_ac97.regs); return PTR_ERR(s3c_ac97.regs);
...@@ -462,7 +457,7 @@ static int s3c_ac97_probe(struct platform_device *pdev) ...@@ -462,7 +457,7 @@ static int s3c_ac97_probe(struct platform_device *pdev)
if (ret) if (ret)
goto err5; goto err5;
ret = asoc_dma_platform_register(&pdev->dev); ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) { if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err6; goto err6;
...@@ -485,7 +480,7 @@ static int s3c_ac97_remove(struct platform_device *pdev) ...@@ -485,7 +480,7 @@ static int s3c_ac97_remove(struct platform_device *pdev)
{ {
struct resource *irq_res; struct resource *irq_res;
asoc_dma_platform_unregister(&pdev->dev); samsung_asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_component(&pdev->dev);
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
......
...@@ -176,6 +176,10 @@ static int dma_hw_params(struct snd_pcm_substream *substream, ...@@ -176,6 +176,10 @@ static int dma_hw_params(struct snd_pcm_substream *substream,
prtd->params->ch = prtd->params->ops->request( prtd->params->ch = prtd->params->ops->request(
prtd->params->channel, &req, rtd->cpu_dai->dev, prtd->params->channel, &req, rtd->cpu_dai->dev,
prtd->params->ch_name); prtd->params->ch_name);
if (!prtd->params->ch) {
pr_err("Failed to allocate DMA channel\n");
return -ENXIO;
}
prtd->params->ops->config(prtd->params->ch, &config); prtd->params->ops->config(prtd->params->ch, &config);
} }
...@@ -433,17 +437,17 @@ static struct snd_soc_platform_driver samsung_asoc_platform = { ...@@ -433,17 +437,17 @@ static struct snd_soc_platform_driver samsung_asoc_platform = {
.pcm_free = dma_free_dma_buffers, .pcm_free = dma_free_dma_buffers,
}; };
int asoc_dma_platform_register(struct device *dev) int samsung_asoc_dma_platform_register(struct device *dev)
{ {
return snd_soc_register_platform(dev, &samsung_asoc_platform); return snd_soc_register_platform(dev, &samsung_asoc_platform);
} }
EXPORT_SYMBOL_GPL(asoc_dma_platform_register); EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register);
void asoc_dma_platform_unregister(struct device *dev) void samsung_asoc_dma_platform_unregister(struct device *dev)
{ {
snd_soc_unregister_platform(dev); snd_soc_unregister_platform(dev);
} }
EXPORT_SYMBOL_GPL(asoc_dma_platform_unregister); EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_unregister);
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung ASoC DMA Driver"); MODULE_DESCRIPTION("Samsung ASoC DMA Driver");
......
...@@ -22,7 +22,7 @@ struct s3c_dma_params { ...@@ -22,7 +22,7 @@ struct s3c_dma_params {
char *ch_name; char *ch_name;
}; };
int asoc_dma_platform_register(struct device *dev); int samsung_asoc_dma_platform_register(struct device *dev);
void asoc_dma_platform_unregister(struct device *dev); void samsung_asoc_dma_platform_unregister(struct device *dev);
#endif #endif
...@@ -31,6 +31,10 @@ ...@@ -31,6 +31,10 @@
#define I2SLVL1ADDR 0x34 #define I2SLVL1ADDR 0x34
#define I2SLVL2ADDR 0x38 #define I2SLVL2ADDR 0x38
#define I2SLVL3ADDR 0x3c #define I2SLVL3ADDR 0x3c
#define I2SSTR1 0x40
#define I2SVER 0x44
#define I2SFIC2 0x48
#define I2STDM 0x4c
#define CON_RSTCLR (1 << 31) #define CON_RSTCLR (1 << 31)
#define CON_FRXOFSTATUS (1 << 26) #define CON_FRXOFSTATUS (1 << 26)
...@@ -95,24 +99,39 @@ ...@@ -95,24 +99,39 @@
#define MOD_RXONLY (1 << 8) #define MOD_RXONLY (1 << 8)
#define MOD_TXRX (2 << 8) #define MOD_TXRX (2 << 8)
#define MOD_MASK (3 << 8) #define MOD_MASK (3 << 8)
#define MOD_LR_LLOW (0 << 7) #define MOD_LRP_SHIFT 7
#define MOD_LR_RLOW (1 << 7) #define MOD_LR_LLOW 0
#define MOD_SDF_IIS (0 << 5) #define MOD_LR_RLOW 1
#define MOD_SDF_MSB (1 << 5) #define MOD_SDF_SHIFT 5
#define MOD_SDF_LSB (2 << 5) #define MOD_SDF_IIS 0
#define MOD_SDF_MASK (3 << 5) #define MOD_SDF_MSB 1
#define MOD_RCLK_256FS (0 << 3) #define MOD_SDF_LSB 2
#define MOD_RCLK_512FS (1 << 3) #define MOD_SDF_MASK 3
#define MOD_RCLK_384FS (2 << 3) #define MOD_RCLK_SHIFT 3
#define MOD_RCLK_768FS (3 << 3) #define MOD_RCLK_256FS 0
#define MOD_RCLK_MASK (3 << 3) #define MOD_RCLK_512FS 1
#define MOD_BCLK_32FS (0 << 1) #define MOD_RCLK_384FS 2
#define MOD_BCLK_48FS (1 << 1) #define MOD_RCLK_768FS 3
#define MOD_BCLK_16FS (2 << 1) #define MOD_RCLK_MASK 3
#define MOD_BCLK_24FS (3 << 1) #define MOD_BCLK_SHIFT 1
#define MOD_BCLK_MASK (3 << 1) #define MOD_BCLK_32FS 0
#define MOD_BCLK_48FS 1
#define MOD_BCLK_16FS 2
#define MOD_BCLK_24FS 3
#define MOD_BCLK_MASK 3
#define MOD_8BIT (1 << 0) #define MOD_8BIT (1 << 0)
#define EXYNOS5420_MOD_LRP_SHIFT 15
#define EXYNOS5420_MOD_SDF_SHIFT 6
#define EXYNOS5420_MOD_RCLK_SHIFT 4
#define EXYNOS5420_MOD_BCLK_SHIFT 0
#define EXYNOS5420_MOD_BCLK_64FS 4
#define EXYNOS5420_MOD_BCLK_96FS 5
#define EXYNOS5420_MOD_BCLK_128FS 6
#define EXYNOS5420_MOD_BCLK_192FS 7
#define EXYNOS5420_MOD_BCLK_256FS 8
#define EXYNOS5420_MOD_BCLK_MASK 0xf
#define MOD_CDCLKCON (1 << 12) #define MOD_CDCLKCON (1 << 12)
#define PSR_PSREN (1 << 15) #define PSR_PSREN (1 << 15)
......
...@@ -40,6 +40,7 @@ enum samsung_dai_type { ...@@ -40,6 +40,7 @@ enum samsung_dai_type {
struct samsung_i2s_dai_data { struct samsung_i2s_dai_data {
int dai_type; int dai_type;
u32 quirks;
}; };
struct i2s_dai { struct i2s_dai {
...@@ -198,7 +199,13 @@ static inline bool is_manager(struct i2s_dai *i2s) ...@@ -198,7 +199,13 @@ static inline bool is_manager(struct i2s_dai *i2s)
/* Read RCLK of I2S (in multiples of LRCLK) */ /* Read RCLK of I2S (in multiples of LRCLK) */
static inline unsigned get_rfs(struct i2s_dai *i2s) static inline unsigned get_rfs(struct i2s_dai *i2s)
{ {
u32 rfs = (readl(i2s->addr + I2SMOD) >> 3) & 0x3; u32 rfs;
if (i2s->quirks & QUIRK_SUPPORTS_TDM)
rfs = readl(i2s->addr + I2SMOD) >> EXYNOS5420_MOD_RCLK_SHIFT;
else
rfs = (readl(i2s->addr + I2SMOD) >> MOD_RCLK_SHIFT);
rfs &= MOD_RCLK_MASK;
switch (rfs) { switch (rfs) {
case 3: return 768; case 3: return 768;
...@@ -212,21 +219,26 @@ static inline unsigned get_rfs(struct i2s_dai *i2s) ...@@ -212,21 +219,26 @@ static inline unsigned get_rfs(struct i2s_dai *i2s)
static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs)
{ {
u32 mod = readl(i2s->addr + I2SMOD); u32 mod = readl(i2s->addr + I2SMOD);
int rfs_shift;
mod &= ~MOD_RCLK_MASK; if (i2s->quirks & QUIRK_SUPPORTS_TDM)
rfs_shift = EXYNOS5420_MOD_RCLK_SHIFT;
else
rfs_shift = MOD_RCLK_SHIFT;
mod &= ~(MOD_RCLK_MASK << rfs_shift);
switch (rfs) { switch (rfs) {
case 768: case 768:
mod |= MOD_RCLK_768FS; mod |= (MOD_RCLK_768FS << rfs_shift);
break; break;
case 512: case 512:
mod |= MOD_RCLK_512FS; mod |= (MOD_RCLK_512FS << rfs_shift);
break; break;
case 384: case 384:
mod |= MOD_RCLK_384FS; mod |= (MOD_RCLK_384FS << rfs_shift);
break; break;
default: default:
mod |= MOD_RCLK_256FS; mod |= (MOD_RCLK_256FS << rfs_shift);
break; break;
} }
...@@ -236,9 +248,22 @@ static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) ...@@ -236,9 +248,22 @@ static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs)
/* Read Bit-Clock of I2S (in multiples of LRCLK) */ /* Read Bit-Clock of I2S (in multiples of LRCLK) */
static inline unsigned get_bfs(struct i2s_dai *i2s) static inline unsigned get_bfs(struct i2s_dai *i2s)
{ {
u32 bfs = (readl(i2s->addr + I2SMOD) >> 1) & 0x3; u32 bfs;
if (i2s->quirks & QUIRK_SUPPORTS_TDM) {
bfs = readl(i2s->addr + I2SMOD) >> EXYNOS5420_MOD_BCLK_SHIFT;
bfs &= EXYNOS5420_MOD_BCLK_MASK;
} else {
bfs = readl(i2s->addr + I2SMOD) >> MOD_BCLK_SHIFT;
bfs &= MOD_BCLK_MASK;
}
switch (bfs) { switch (bfs) {
case 8: return 256;
case 7: return 192;
case 6: return 128;
case 5: return 96;
case 4: return 64;
case 3: return 24; case 3: return 24;
case 2: return 16; case 2: return 16;
case 1: return 48; case 1: return 48;
...@@ -250,21 +275,50 @@ static inline unsigned get_bfs(struct i2s_dai *i2s) ...@@ -250,21 +275,50 @@ static inline unsigned get_bfs(struct i2s_dai *i2s)
static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs)
{ {
u32 mod = readl(i2s->addr + I2SMOD); u32 mod = readl(i2s->addr + I2SMOD);
int bfs_shift;
int tdm = i2s->quirks & QUIRK_SUPPORTS_TDM;
mod &= ~MOD_BCLK_MASK; if (i2s->quirks & QUIRK_SUPPORTS_TDM) {
bfs_shift = EXYNOS5420_MOD_BCLK_SHIFT;
mod &= ~(EXYNOS5420_MOD_BCLK_MASK << bfs_shift);
} else {
bfs_shift = MOD_BCLK_SHIFT;
mod &= ~(MOD_BCLK_MASK << bfs_shift);
}
/* Non-TDM I2S controllers do not support BCLK > 48 * FS */
if (!tdm && bfs > 48) {
dev_err(&i2s->pdev->dev, "Unsupported BCLK divider\n");
return;
}
switch (bfs) { switch (bfs) {
case 48: case 48:
mod |= MOD_BCLK_48FS; mod |= (MOD_BCLK_48FS << bfs_shift);
break; break;
case 32: case 32:
mod |= MOD_BCLK_32FS; mod |= (MOD_BCLK_32FS << bfs_shift);
break; break;
case 24: case 24:
mod |= MOD_BCLK_24FS; mod |= (MOD_BCLK_24FS << bfs_shift);
break; break;
case 16: case 16:
mod |= MOD_BCLK_16FS; mod |= (MOD_BCLK_16FS << bfs_shift);
break;
case 64:
mod |= (EXYNOS5420_MOD_BCLK_64FS << bfs_shift);
break;
case 96:
mod |= (EXYNOS5420_MOD_BCLK_96FS << bfs_shift);
break;
case 128:
mod |= (EXYNOS5420_MOD_BCLK_128FS << bfs_shift);
break;
case 192:
mod |= (EXYNOS5420_MOD_BCLK_192FS << bfs_shift);
break;
case 256:
mod |= (EXYNOS5420_MOD_BCLK_256FS << bfs_shift);
break; break;
default: default:
dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n"); dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n");
...@@ -491,20 +545,32 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, ...@@ -491,20 +545,32 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
{ {
struct i2s_dai *i2s = to_info(dai); struct i2s_dai *i2s = to_info(dai);
u32 mod = readl(i2s->addr + I2SMOD); u32 mod = readl(i2s->addr + I2SMOD);
int lrp_shift, sdf_shift, sdf_mask, lrp_rlow;
u32 tmp = 0; u32 tmp = 0;
if (i2s->quirks & QUIRK_SUPPORTS_TDM) {
lrp_shift = EXYNOS5420_MOD_LRP_SHIFT;
sdf_shift = EXYNOS5420_MOD_SDF_SHIFT;
} else {
lrp_shift = MOD_LRP_SHIFT;
sdf_shift = MOD_SDF_SHIFT;
}
sdf_mask = MOD_SDF_MASK << sdf_shift;
lrp_rlow = MOD_LR_RLOW << lrp_shift;
/* Format is priority */ /* Format is priority */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_RIGHT_J:
tmp |= MOD_LR_RLOW; tmp |= lrp_rlow;
tmp |= MOD_SDF_MSB; tmp |= (MOD_SDF_MSB << sdf_shift);
break; break;
case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_LEFT_J:
tmp |= MOD_LR_RLOW; tmp |= lrp_rlow;
tmp |= MOD_SDF_LSB; tmp |= (MOD_SDF_LSB << sdf_shift);
break; break;
case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_I2S:
tmp |= MOD_SDF_IIS; tmp |= (MOD_SDF_IIS << sdf_shift);
break; break;
default: default:
dev_err(&i2s->pdev->dev, "Format not supported\n"); dev_err(&i2s->pdev->dev, "Format not supported\n");
...@@ -519,10 +585,10 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, ...@@ -519,10 +585,10 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
case SND_SOC_DAIFMT_NB_NF: case SND_SOC_DAIFMT_NB_NF:
break; break;
case SND_SOC_DAIFMT_NB_IF: case SND_SOC_DAIFMT_NB_IF:
if (tmp & MOD_LR_RLOW) if (tmp & lrp_rlow)
tmp &= ~MOD_LR_RLOW; tmp &= ~lrp_rlow;
else else
tmp |= MOD_LR_RLOW; tmp |= lrp_rlow;
break; break;
default: default:
dev_err(&i2s->pdev->dev, "Polarity not supported\n"); dev_err(&i2s->pdev->dev, "Polarity not supported\n");
...@@ -544,15 +610,18 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, ...@@ -544,15 +610,18 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
return -EINVAL; return -EINVAL;
} }
/*
* Don't change the I2S mode if any controller is active on this
* channel.
*/
if (any_active(i2s) && if (any_active(i2s) &&
((mod & (MOD_SDF_MASK | MOD_LR_RLOW ((mod & (sdf_mask | lrp_rlow | MOD_SLAVE)) != tmp)) {
| MOD_SLAVE)) != tmp)) {
dev_err(&i2s->pdev->dev, dev_err(&i2s->pdev->dev,
"%s:%d Other DAI busy\n", __func__, __LINE__); "%s:%d Other DAI busy\n", __func__, __LINE__);
return -EAGAIN; return -EAGAIN;
} }
mod &= ~(MOD_SDF_MASK | MOD_LR_RLOW | MOD_SLAVE); mod &= ~(sdf_mask | lrp_rlow | MOD_SLAVE);
mod |= tmp; mod |= tmp;
writel(mod, i2s->addr + I2SMOD); writel(mod, i2s->addr + I2SMOD);
...@@ -1007,6 +1076,8 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) ...@@ -1007,6 +1076,8 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec)
if (IS_ERR(i2s->pdev)) if (IS_ERR(i2s->pdev))
return NULL; return NULL;
i2s->pdev->dev.parent = &pdev->dev;
platform_set_drvdata(i2s->pdev, i2s); platform_set_drvdata(i2s->pdev, i2s);
ret = platform_device_add(i2s->pdev); ret = platform_device_add(i2s->pdev);
if (ret < 0) if (ret < 0)
...@@ -1018,18 +1089,18 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) ...@@ -1018,18 +1089,18 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec)
static const struct of_device_id exynos_i2s_match[]; static const struct of_device_id exynos_i2s_match[];
static inline int samsung_i2s_get_driver_data(struct platform_device *pdev) static inline const struct samsung_i2s_dai_data *samsung_i2s_get_driver_data(
struct platform_device *pdev)
{ {
#ifdef CONFIG_OF #ifdef CONFIG_OF
struct samsung_i2s_dai_data *data;
if (pdev->dev.of_node) { if (pdev->dev.of_node) {
const struct of_device_id *match; const struct of_device_id *match;
match = of_match_node(exynos_i2s_match, pdev->dev.of_node); match = of_match_node(exynos_i2s_match, pdev->dev.of_node);
data = (struct samsung_i2s_dai_data *) match->data; return match->data;
return data->dai_type;
} else } else
#endif #endif
return platform_get_device_id(pdev)->driver_data; return (struct samsung_i2s_dai_data *)
platform_get_device_id(pdev)->driver_data;
} }
#ifdef CONFIG_PM_RUNTIME #ifdef CONFIG_PM_RUNTIME
...@@ -1060,13 +1131,13 @@ static int samsung_i2s_probe(struct platform_device *pdev) ...@@ -1060,13 +1131,13 @@ static int samsung_i2s_probe(struct platform_device *pdev)
struct resource *res; struct resource *res;
u32 regs_base, quirks = 0, idma_addr = 0; u32 regs_base, quirks = 0, idma_addr = 0;
struct device_node *np = pdev->dev.of_node; struct device_node *np = pdev->dev.of_node;
enum samsung_dai_type samsung_dai_type; const struct samsung_i2s_dai_data *i2s_dai_data;
int ret = 0; int ret = 0;
/* Call during Seconday interface registration */ /* Call during Seconday interface registration */
samsung_dai_type = samsung_i2s_get_driver_data(pdev); i2s_dai_data = samsung_i2s_get_driver_data(pdev);
if (samsung_dai_type == TYPE_SEC) { if (i2s_dai_data->dai_type == TYPE_SEC) {
sec_dai = dev_get_drvdata(&pdev->dev); sec_dai = dev_get_drvdata(&pdev->dev);
if (!sec_dai) { if (!sec_dai) {
dev_err(&pdev->dev, "Unable to get drvdata\n"); dev_err(&pdev->dev, "Unable to get drvdata\n");
...@@ -1075,7 +1146,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) ...@@ -1075,7 +1146,7 @@ static int samsung_i2s_probe(struct platform_device *pdev)
snd_soc_register_component(&sec_dai->pdev->dev, snd_soc_register_component(&sec_dai->pdev->dev,
&samsung_i2s_component, &samsung_i2s_component,
&sec_dai->i2s_dai_drv, 1); &sec_dai->i2s_dai_drv, 1);
asoc_dma_platform_register(&pdev->dev); samsung_asoc_dma_platform_register(&pdev->dev);
return 0; return 0;
} }
...@@ -1115,15 +1186,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) ...@@ -1115,15 +1186,7 @@ static int samsung_i2s_probe(struct platform_device *pdev)
idma_addr = i2s_cfg->idma_addr; idma_addr = i2s_cfg->idma_addr;
} }
} else { } else {
if (of_find_property(np, "samsung,supports-6ch", NULL)) quirks = i2s_dai_data->quirks;
quirks |= QUIRK_PRI_6CHAN;
if (of_find_property(np, "samsung,supports-secdai", NULL))
quirks |= QUIRK_SEC_DAI;
if (of_find_property(np, "samsung,supports-rstclr", NULL))
quirks |= QUIRK_NEED_RSTCLR;
if (of_property_read_u32(np, "samsung,idma-addr", if (of_property_read_u32(np, "samsung,idma-addr",
&idma_addr)) { &idma_addr)) {
if (quirks & QUIRK_SEC_DAI) { if (quirks & QUIRK_SEC_DAI) {
...@@ -1200,7 +1263,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) ...@@ -1200,7 +1263,7 @@ static int samsung_i2s_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
asoc_dma_platform_register(&pdev->dev); samsung_asoc_dma_platform_register(&pdev->dev);
return 0; return 0;
err: err:
...@@ -1230,33 +1293,59 @@ static int samsung_i2s_remove(struct platform_device *pdev) ...@@ -1230,33 +1293,59 @@ static int samsung_i2s_remove(struct platform_device *pdev)
i2s->pri_dai = NULL; i2s->pri_dai = NULL;
i2s->sec_dai = NULL; i2s->sec_dai = NULL;
asoc_dma_platform_unregister(&pdev->dev); samsung_asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_component(&pdev->dev);
return 0; return 0;
} }
static const struct samsung_i2s_dai_data i2sv3_dai_type = {
.dai_type = TYPE_PRI,
.quirks = QUIRK_NO_MUXPSR,
};
static const struct samsung_i2s_dai_data i2sv5_dai_type = {
.dai_type = TYPE_PRI,
.quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR,
};
static const struct samsung_i2s_dai_data i2sv6_dai_type = {
.dai_type = TYPE_PRI,
.quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
QUIRK_SUPPORTS_TDM,
};
static const struct samsung_i2s_dai_data samsung_dai_type_pri = {
.dai_type = TYPE_PRI,
};
static const struct samsung_i2s_dai_data samsung_dai_type_sec = {
.dai_type = TYPE_SEC,
};
static struct platform_device_id samsung_i2s_driver_ids[] = { static struct platform_device_id samsung_i2s_driver_ids[] = {
{ {
.name = "samsung-i2s", .name = "samsung-i2s",
.driver_data = TYPE_PRI, .driver_data = (kernel_ulong_t)&samsung_dai_type_pri,
}, { }, {
.name = "samsung-i2s-sec", .name = "samsung-i2s-sec",
.driver_data = TYPE_SEC, .driver_data = (kernel_ulong_t)&samsung_dai_type_sec,
}, },
{}, {},
}; };
MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids);
#ifdef CONFIG_OF #ifdef CONFIG_OF
static struct samsung_i2s_dai_data samsung_i2s_dai_data_array[] = {
[TYPE_PRI] = { TYPE_PRI },
[TYPE_SEC] = { TYPE_SEC },
};
static const struct of_device_id exynos_i2s_match[] = { static const struct of_device_id exynos_i2s_match[] = {
{ .compatible = "samsung,i2s-v5", {
.data = &samsung_i2s_dai_data_array[TYPE_PRI], .compatible = "samsung,s3c6410-i2s",
.data = &i2sv3_dai_type,
}, {
.compatible = "samsung,s5pv210-i2s",
.data = &i2sv5_dai_type,
}, {
.compatible = "samsung,exynos5420-i2s",
.data = &i2sv6_dai_type,
}, },
{}, {},
}; };
......
...@@ -594,7 +594,7 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) ...@@ -594,7 +594,7 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev)
goto err5; goto err5;
} }
ret = asoc_dma_platform_register(&pdev->dev); ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) { if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err6; goto err6;
...@@ -623,7 +623,7 @@ static int s3c_pcm_dev_remove(struct platform_device *pdev) ...@@ -623,7 +623,7 @@ static int s3c_pcm_dev_remove(struct platform_device *pdev)
struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id]; struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
struct resource *mem_res; struct resource *mem_res;
asoc_dma_platform_unregister(&pdev->dev); samsung_asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
......
...@@ -176,7 +176,7 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev) ...@@ -176,7 +176,7 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev)
return ret; return ret;
} }
ret = asoc_dma_platform_register(&pdev->dev); ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) { if (ret) {
pr_err("failed to register the DMA: %d\n", ret); pr_err("failed to register the DMA: %d\n", ret);
goto err; goto err;
...@@ -190,7 +190,7 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev) ...@@ -190,7 +190,7 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev)
static int s3c2412_iis_dev_remove(struct platform_device *pdev) static int s3c2412_iis_dev_remove(struct platform_device *pdev)
{ {
asoc_dma_platform_unregister(&pdev->dev); samsung_asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_component(&pdev->dev);
return 0; return 0;
} }
......
...@@ -480,7 +480,7 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev) ...@@ -480,7 +480,7 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
return ret; return ret;
} }
ret = asoc_dma_platform_register(&pdev->dev); ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) { if (ret) {
pr_err("failed to register the dma: %d\n", ret); pr_err("failed to register the dma: %d\n", ret);
goto err; goto err;
...@@ -494,7 +494,7 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev) ...@@ -494,7 +494,7 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
static int s3c24xx_iis_dev_remove(struct platform_device *pdev) static int s3c24xx_iis_dev_remove(struct platform_device *pdev)
{ {
asoc_dma_platform_unregister(&pdev->dev); samsung_asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_component(&pdev->dev);
return 0; return 0;
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h>
/* /*
* Default CFG switch settings to use this driver: * Default CFG switch settings to use this driver:
...@@ -37,11 +38,19 @@ ...@@ -37,11 +38,19 @@
/* SMDK has a 16.934MHZ crystal attached to WM8994 */ /* SMDK has a 16.934MHZ crystal attached to WM8994 */
#define SMDK_WM8994_FREQ 16934000 #define SMDK_WM8994_FREQ 16934000
struct smdk_wm8994_data {
int mclk1_rate;
};
/* Default SMDKs */
static struct smdk_wm8994_data smdk_board_data = {
.mclk1_rate = SMDK_WM8994_FREQ,
};
static int smdk_hw_params(struct snd_pcm_substream *substream, static int smdk_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params) struct snd_pcm_hw_params *params)
{ {
struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int pll_out; unsigned int pll_out;
int ret; int ret;
...@@ -54,18 +63,6 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, ...@@ -54,18 +63,6 @@ static int smdk_hw_params(struct snd_pcm_substream *substream,
else else
pll_out = params_rate(params) * 256; pll_out = params_rate(params) * 256;
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
SMDK_WM8994_FREQ, pll_out); SMDK_WM8994_FREQ, pll_out);
if (ret < 0) if (ret < 0)
...@@ -131,6 +128,8 @@ static struct snd_soc_dai_link smdk_dai[] = { ...@@ -131,6 +128,8 @@ static struct snd_soc_dai_link smdk_dai[] = {
.platform_name = "samsung-i2s.0", .platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec", .codec_name = "wm8994-codec",
.init = smdk_wm8994_init_paiftx, .init = smdk_wm8994_init_paiftx,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &smdk_ops, .ops = &smdk_ops,
}, { /* Sec_Fifo Playback i/f */ }, { /* Sec_Fifo Playback i/f */
.name = "Sec_FIFO TX", .name = "Sec_FIFO TX",
...@@ -139,6 +138,8 @@ static struct snd_soc_dai_link smdk_dai[] = { ...@@ -139,6 +138,8 @@ static struct snd_soc_dai_link smdk_dai[] = {
.codec_dai_name = "wm8994-aif1", .codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s-sec", .platform_name = "samsung-i2s-sec",
.codec_name = "wm8994-codec", .codec_name = "wm8994-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &smdk_ops, .ops = &smdk_ops,
}, },
}; };
...@@ -150,15 +151,28 @@ static struct snd_soc_card smdk = { ...@@ -150,15 +151,28 @@ static struct snd_soc_card smdk = {
.num_links = ARRAY_SIZE(smdk_dai), .num_links = ARRAY_SIZE(smdk_dai),
}; };
#ifdef CONFIG_OF
static const struct of_device_id samsung_wm8994_of_match[] = {
{ .compatible = "samsung,smdk-wm8994", .data = &smdk_board_data },
{},
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
#endif /* CONFIG_OF */
static int smdk_audio_probe(struct platform_device *pdev) static int smdk_audio_probe(struct platform_device *pdev)
{ {
int ret; int ret;
struct device_node *np = pdev->dev.of_node; struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &smdk; struct snd_soc_card *card = &smdk;
struct smdk_wm8994_data *board;
const struct of_device_id *id;
card->dev = &pdev->dev; card->dev = &pdev->dev;
board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);
if (!board)
return -ENOMEM;
if (np) { if (np) {
smdk_dai[0].cpu_dai_name = NULL; smdk_dai[0].cpu_dai_name = NULL;
smdk_dai[0].cpu_of_node = of_parse_phandle(np, smdk_dai[0].cpu_of_node = of_parse_phandle(np,
...@@ -173,6 +187,12 @@ static int smdk_audio_probe(struct platform_device *pdev) ...@@ -173,6 +187,12 @@ static int smdk_audio_probe(struct platform_device *pdev)
smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node; smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node;
} }
id = of_match_device(samsung_wm8994_of_match, &pdev->dev);
if (id)
*board = *((struct smdk_wm8994_data *)id->data);
platform_set_drvdata(pdev, board);
ret = snd_soc_register_card(card); ret = snd_soc_register_card(card);
if (ret) if (ret)
...@@ -190,17 +210,9 @@ static int smdk_audio_remove(struct platform_device *pdev) ...@@ -190,17 +210,9 @@ static int smdk_audio_remove(struct platform_device *pdev)
return 0; return 0;
} }
#ifdef CONFIG_OF
static const struct of_device_id samsung_wm8994_of_match[] = {
{ .compatible = "samsung,smdk-wm8994", },
{},
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
#endif /* CONFIG_OF */
static struct platform_driver smdk_audio_driver = { static struct platform_driver smdk_audio_driver = {
.driver = { .driver = {
.name = "smdk-audio", .name = "smdk-audio-wm8894",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_wm8994_of_match), .of_match_table = of_match_ptr(samsung_wm8994_of_match),
}, },
...@@ -212,4 +224,4 @@ module_platform_driver(smdk_audio_driver); ...@@ -212,4 +224,4 @@ module_platform_driver(smdk_audio_driver);
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994"); MODULE_DESCRIPTION("ALSA SoC SMDK WM8994");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:smdk-audio"); MODULE_ALIAS("platform:smdk-audio-wm8994");
...@@ -395,7 +395,7 @@ static int spdif_probe(struct platform_device *pdev) ...@@ -395,7 +395,7 @@ static int spdif_probe(struct platform_device *pdev)
spin_lock_init(&spdif->lock); spin_lock_init(&spdif->lock);
spdif->pclk = clk_get(&pdev->dev, "spdif"); spdif->pclk = devm_clk_get(&pdev->dev, "spdif");
if (IS_ERR(spdif->pclk)) { if (IS_ERR(spdif->pclk)) {
dev_err(&pdev->dev, "failed to get peri-clock\n"); dev_err(&pdev->dev, "failed to get peri-clock\n");
ret = -ENOENT; ret = -ENOENT;
...@@ -403,7 +403,7 @@ static int spdif_probe(struct platform_device *pdev) ...@@ -403,7 +403,7 @@ static int spdif_probe(struct platform_device *pdev)
} }
clk_prepare_enable(spdif->pclk); clk_prepare_enable(spdif->pclk);
spdif->sclk = clk_get(&pdev->dev, "sclk_spdif"); spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif");
if (IS_ERR(spdif->sclk)) { if (IS_ERR(spdif->sclk)) {
dev_err(&pdev->dev, "failed to get internal source clock\n"); dev_err(&pdev->dev, "failed to get internal source clock\n");
ret = -ENOENT; ret = -ENOENT;
...@@ -442,7 +442,7 @@ static int spdif_probe(struct platform_device *pdev) ...@@ -442,7 +442,7 @@ static int spdif_probe(struct platform_device *pdev)
spdif->dma_playback = &spdif_stereo_out; spdif->dma_playback = &spdif_stereo_out;
ret = asoc_dma_platform_register(&pdev->dev); ret = samsung_asoc_dma_platform_register(&pdev->dev);
if (ret) { if (ret) {
dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
goto err5; goto err5;
...@@ -457,10 +457,8 @@ static int spdif_probe(struct platform_device *pdev) ...@@ -457,10 +457,8 @@ static int spdif_probe(struct platform_device *pdev)
release_mem_region(mem_res->start, resource_size(mem_res)); release_mem_region(mem_res->start, resource_size(mem_res));
err2: err2:
clk_disable_unprepare(spdif->sclk); clk_disable_unprepare(spdif->sclk);
clk_put(spdif->sclk);
err1: err1:
clk_disable_unprepare(spdif->pclk); clk_disable_unprepare(spdif->pclk);
clk_put(spdif->pclk);
err0: err0:
return ret; return ret;
} }
...@@ -470,7 +468,7 @@ static int spdif_remove(struct platform_device *pdev) ...@@ -470,7 +468,7 @@ static int spdif_remove(struct platform_device *pdev)
struct samsung_spdif_info *spdif = &spdif_info; struct samsung_spdif_info *spdif = &spdif_info;
struct resource *mem_res; struct resource *mem_res;
asoc_dma_platform_unregister(&pdev->dev); samsung_asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_component(&pdev->dev);
iounmap(spdif->regs); iounmap(spdif->regs);
...@@ -480,9 +478,7 @@ static int spdif_remove(struct platform_device *pdev) ...@@ -480,9 +478,7 @@ static int spdif_remove(struct platform_device *pdev)
release_mem_region(mem_res->start, resource_size(mem_res)); release_mem_region(mem_res->start, resource_size(mem_res));
clk_disable_unprepare(spdif->sclk); clk_disable_unprepare(spdif->sclk);
clk_put(spdif->sclk);
clk_disable_unprepare(spdif->pclk); clk_disable_unprepare(spdif->pclk);
clk_put(spdif->pclk);
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment