diff options
Diffstat (limited to 'sound/soc/samsung')
-rw-r--r-- | sound/soc/samsung/Kconfig | 6 | ||||
-rw-r--r-- | sound/soc/samsung/dma.c | 3 | ||||
-rw-r--r-- | sound/soc/samsung/dma.h | 1 | ||||
-rw-r--r-- | sound/soc/samsung/i2s.c | 267 | ||||
-rw-r--r-- | sound/soc/samsung/i2s.h | 7 | ||||
-rw-r--r-- | sound/soc/samsung/smdk_wm8580.c | 7 | ||||
-rw-r--r-- | sound/soc/samsung/smdk_wm8994.c | 30 |
7 files changed, 256 insertions, 65 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 3c7c3a5..90e7e66 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -63,7 +63,7 @@ config SND_SOC_SAMSUNG_SMDK_WM8580 config SND_SOC_SAMSUNG_SMDK_WM8994 tristate "SoC I2S Audio support for WM8994 on SMDK" - depends on SND_SOC_SAMSUNG && (MACH_SMDKV310 || MACH_SMDKC210 || MACH_SMDK4212) + depends on SND_SOC_SAMSUNG depends on I2C=y && GENERIC_HARDIRQS select MFD_WM8994 select SND_SOC_WM8994 @@ -162,7 +162,7 @@ config SND_SOC_GONI_AQUILA_WM8994 config SND_SOC_SAMSUNG_SMDK_SPDIF tristate "SoC S/PDIF Audio support for SMDK" - depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210 || MACH_SMDKV310 || MACH_SMDK4212) + depends on SND_SOC_SAMSUNG select SND_SAMSUNG_SPDIF help Say Y if you want to add support for SoC S/PDIF audio on the SMDK. @@ -177,7 +177,7 @@ config SND_SOC_SMDK_WM8580_PCM config SND_SOC_SMDK_WM8994_PCM tristate "SoC PCM Audio support for WM8994 on SMDK" - depends on SND_SOC_SAMSUNG && (MACH_SMDKC210 || MACH_SMDKV310 || MACH_SMDK4212) + depends on SND_SOC_SAMSUNG depends on I2C=y && GENERIC_HARDIRQS select MFD_WM8994 select SND_SOC_WM8994 diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c index db87628..21b7926 100644 --- a/sound/soc/samsung/dma.c +++ b/sound/soc/samsung/dma.c @@ -174,7 +174,8 @@ static int dma_hw_params(struct snd_pcm_substream *substream, config.width = prtd->params->dma_size; config.fifo = prtd->params->dma_addr; prtd->params->ch = prtd->params->ops->request( - prtd->params->channel, &req); + prtd->params->channel, &req, rtd->cpu_dai->dev, + prtd->params->ch_name); prtd->params->ops->config(prtd->params->ch, &config); } diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index 73d8c7c..189a7a6 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -19,6 +19,7 @@ struct s3c_dma_params { int dma_size; /* Size of the DMA transfer */ unsigned ch; struct samsung_dma_ops *ops; + char *ch_name; }; int asoc_dma_platform_register(struct device *dev); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index d2d124f..d7231e3 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -15,11 +15,15 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #include <linux/pm_runtime.h> #include <sound/soc.h> #include <sound/pcm_params.h> +#include <mach/dma.h> + #include <linux/platform_data/asoc-s3c.h> #include "dma.h" @@ -29,6 +33,15 @@ #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) +enum samsung_dai_type { + TYPE_PRI, + TYPE_SEC, +}; + +struct samsung_i2s_dai_data { + int dai_type; +}; + struct i2s_dai { /* Platform device for this DAI */ struct platform_device *pdev; @@ -66,6 +79,7 @@ struct i2s_dai { u32 suspend_i2smod; u32 suspend_i2scon; u32 suspend_i2spsr; + unsigned long gpios[7]; /* i2s gpio line numbers */ }; /* Lock for cross i/f checks */ @@ -651,6 +665,9 @@ static int i2s_startup(struct snd_pcm_substream *substream, /* Enforce set_sysclk in Master mode */ i2s->rclk_srcrate = 0; + if (!any_active(i2s) && (i2s->quirks & QUIRK_NEED_RSTCLR)) + writel(CON_RSTCLR, i2s->addr + I2SCON); + spin_unlock_irqrestore(&lock, flags); return 0; @@ -981,8 +998,7 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) i2s->i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS; } else { /* Create a new platform_device for Secondary */ i2s->pdev = platform_device_register_resndata(NULL, - pdev->name, pdev->id + SAMSUNG_I2S_SECOFF, - NULL, 0, NULL, 0); + "samsung-i2s-sec", -1, NULL, 0, NULL, 0); if (IS_ERR(i2s->pdev)) return NULL; } @@ -993,18 +1009,103 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) return i2s; } +#ifdef CONFIG_OF +static int samsung_i2s_parse_dt_gpio(struct i2s_dai *i2s) +{ + struct device *dev = &i2s->pdev->dev; + int index, gpio, ret; + + for (index = 0; index < 7; index++) { + gpio = of_get_gpio(dev->of_node, index); + if (!gpio_is_valid(gpio)) { + dev_err(dev, "invalid gpio[%d]: %d\n", index, gpio); + goto free_gpio; + } + + ret = gpio_request(gpio, dev_name(dev)); + if (ret) { + dev_err(dev, "gpio [%d] request failed\n", gpio); + goto free_gpio; + } + i2s->gpios[index] = gpio; + } + return 0; + +free_gpio: + while (--index >= 0) + gpio_free(i2s->gpios[index]); + return -EINVAL; +} + +static void samsung_i2s_dt_gpio_free(struct i2s_dai *i2s) +{ + unsigned int index; + for (index = 0; index < 7; index++) + gpio_free(i2s->gpios[index]); +} +#else +static int samsung_i2s_parse_dt_gpio(struct i2s_dai *dai) +{ + return -EINVAL; +} + +static void samsung_i2s_dt_gpio_free(struct i2s_dai *dai) +{ +} + +#endif + +static const struct of_device_id exynos_i2s_match[]; + +static inline int samsung_i2s_get_driver_data(struct platform_device *pdev) +{ +#ifdef CONFIG_OF + struct samsung_i2s_dai_data *data; + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(exynos_i2s_match, pdev->dev.of_node); + data = (struct samsung_i2s_dai_data *) match->data; + return data->dai_type; + } else +#endif + return platform_get_device_id(pdev)->driver_data; +} + +#ifdef CONFIG_PM_RUNTIME +static int i2s_runtime_suspend(struct device *dev) +{ + struct i2s_dai *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->clk); + + return 0; +} + +static int i2s_runtime_resume(struct device *dev) +{ + struct i2s_dai *i2s = dev_get_drvdata(dev); + + clk_prepare_enable(i2s->clk); + + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + static int samsung_i2s_probe(struct platform_device *pdev) { - u32 dma_pl_chan, dma_cp_chan, dma_pl_sec_chan; struct i2s_dai *pri_dai, *sec_dai = NULL; - struct s3c_audio_pdata *i2s_pdata; - struct samsung_i2s *i2s_cfg; + struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data; + struct samsung_i2s *i2s_cfg = NULL; struct resource *res; - u32 regs_base, quirks; + u32 regs_base, quirks = 0, idma_addr = 0; + struct device_node *np = pdev->dev.of_node; + enum samsung_dai_type samsung_dai_type; int ret = 0; /* Call during Seconday interface registration */ - if (pdev->id >= SAMSUNG_I2S_SECOFF) { + samsung_dai_type = samsung_i2s_get_driver_data(pdev); + + if (samsung_dai_type == TYPE_SEC) { sec_dai = dev_get_drvdata(&pdev->dev); snd_soc_register_dai(&sec_dai->pdev->dev, &sec_dai->i2s_dai_drv); @@ -1012,31 +1113,60 @@ static int samsung_i2s_probe(struct platform_device *pdev) return 0; } - i2s_pdata = pdev->dev.platform_data; - if (i2s_pdata == NULL) { - dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n"); - return -EINVAL; + pri_dai = i2s_alloc_dai(pdev, false); + if (!pri_dai) { + dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); + return -ENOMEM; } - res = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!res) { - dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n"); - return -ENXIO; - } - dma_pl_chan = res->start; + if (!np) { + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, + "Unable to get I2S-TX dma resource\n"); + return -ENXIO; + } + pri_dai->dma_playback.channel = res->start; - res = platform_get_resource(pdev, IORESOURCE_DMA, 1); - if (!res) { - dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n"); - return -ENXIO; - } - dma_cp_chan = res->start; + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, + "Unable to get I2S-RX dma resource\n"); + return -ENXIO; + } + pri_dai->dma_capture.channel = res->start; - res = platform_get_resource(pdev, IORESOURCE_DMA, 2); - if (res) - dma_pl_sec_chan = res->start; - else - dma_pl_sec_chan = 0; + if (i2s_pdata == NULL) { + dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n"); + return -EINVAL; + } + + if (&i2s_pdata->type) + i2s_cfg = &i2s_pdata->type.i2s; + + if (i2s_cfg) { + quirks = i2s_cfg->quirks; + idma_addr = i2s_cfg->idma_addr; + } + } else { + if (of_find_property(np, "samsung,supports-6ch", NULL)) + 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", + &idma_addr)) { + if (quirks & QUIRK_SEC_DAI) { + dev_err(&pdev->dev, "idma address is not"\ + "specified"); + return -EINVAL; + } + } + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { @@ -1051,24 +1181,14 @@ static int samsung_i2s_probe(struct platform_device *pdev) } regs_base = res->start; - i2s_cfg = &i2s_pdata->type.i2s; - quirks = i2s_cfg->quirks; - - pri_dai = i2s_alloc_dai(pdev, false); - if (!pri_dai) { - dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); - ret = -ENOMEM; - goto err; - } - pri_dai->dma_playback.dma_addr = regs_base + I2STXD; pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; pri_dai->dma_playback.client = (struct s3c2410_dma_client *)&pri_dai->dma_playback; + pri_dai->dma_playback.ch_name = "tx"; pri_dai->dma_capture.client = (struct s3c2410_dma_client *)&pri_dai->dma_capture; - pri_dai->dma_playback.channel = dma_pl_chan; - pri_dai->dma_capture.channel = dma_cp_chan; + pri_dai->dma_capture.ch_name = "rx"; pri_dai->dma_playback.dma_size = 4; pri_dai->dma_capture.dma_size = 4; pri_dai->base = regs_base; @@ -1087,20 +1207,34 @@ static int samsung_i2s_probe(struct platform_device *pdev) sec_dai->dma_playback.dma_addr = regs_base + I2STXDS; sec_dai->dma_playback.client = (struct s3c2410_dma_client *)&sec_dai->dma_playback; - /* Use iDMA always if SysDMA not provided */ - sec_dai->dma_playback.channel = dma_pl_sec_chan ? : -1; + sec_dai->dma_playback.ch_name = "tx-sec"; + + if (!np) { + res = platform_get_resource(pdev, IORESOURCE_DMA, 2); + if (res) + sec_dai->dma_playback.channel = res->start; + } + sec_dai->dma_playback.dma_size = 4; sec_dai->base = regs_base; sec_dai->quirks = quirks; - sec_dai->idma_playback.dma_addr = i2s_cfg->idma_addr; + sec_dai->idma_playback.dma_addr = idma_addr; sec_dai->pri_dai = pri_dai; pri_dai->sec_dai = sec_dai; } - if (i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { - dev_err(&pdev->dev, "Unable to configure gpio\n"); - ret = -EINVAL; - goto err; + if (np) { + if (samsung_i2s_parse_dt_gpio(pri_dai)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err; + } + } else { + if (i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err; + } } snd_soc_register_dai(&pri_dai->pdev->dev, &pri_dai->i2s_dai_drv); @@ -1120,10 +1254,14 @@ static int samsung_i2s_remove(struct platform_device *pdev) { struct i2s_dai *i2s, *other; struct resource *res; + struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data; i2s = dev_get_drvdata(&pdev->dev); other = i2s->pri_dai ? : i2s->sec_dai; + if (!i2s_pdata->cfg_gpio && pdev->dev.of_node) + samsung_i2s_dt_gpio_free(i2s->pri_dai); + if (other) { other->pri_dai = NULL; other->sec_dai = NULL; @@ -1143,12 +1281,47 @@ static int samsung_i2s_remove(struct platform_device *pdev) return 0; } +static struct platform_device_id samsung_i2s_driver_ids[] = { + { + .name = "samsung-i2s", + .driver_data = TYPE_PRI, + }, { + .name = "samsung-i2s-sec", + .driver_data = TYPE_SEC, + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, samsung-i2s-driver-ids); + +#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[] = { + { .compatible = "samsung,i2s-v5", + .data = &samsung_i2s_dai_data_array[TYPE_PRI], + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_i2s_match); +#endif + +static const struct dev_pm_ops samsung_i2s_pm = { + SET_RUNTIME_PM_OPS(i2s_runtime_suspend, + i2s_runtime_resume, NULL) +}; + static struct platform_driver samsung_i2s_driver = { .probe = samsung_i2s_probe, .remove = samsung_i2s_remove, + .id_table = samsung_i2s_driver_ids, .driver = { .name = "samsung-i2s", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(exynos_i2s_match), + .pm = &samsung_i2s_pm, }, }; diff --git a/sound/soc/samsung/i2s.h b/sound/soc/samsung/i2s.h index d420a7c..7966afc 100644 --- a/sound/soc/samsung/i2s.h +++ b/sound/soc/samsung/i2s.h @@ -13,13 +13,6 @@ #ifndef __SND_SOC_SAMSUNG_I2S_H #define __SND_SOC_SAMSUNG_I2S_H -/* - * Maximum number of I2S blocks that any SoC can have. - * The secondary interface of a CPU dai(if there exists any), - * is indexed at [cpu-dai's ID + SAMSUNG_I2S_SECOFF] - */ -#define SAMSUNG_I2S_SECOFF 4 - #define SAMSUNG_I2S_DIV_BCLK 1 #define SAMSUNG_I2S_RCLKSRC_0 0 diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c index 7e2b710..7a16b32 100644 --- a/sound/soc/samsung/smdk_wm8580.c +++ b/sound/soc/samsung/smdk_wm8580.c @@ -193,9 +193,9 @@ static struct snd_soc_dai_link smdk_dai[] = { [SEC_PLAYBACK] = { /* Sec_Fifo Playback i/f */ .name = "Sec_FIFO TX", .stream_name = "Playback", - .cpu_dai_name = "samsung-i2s.x", + .cpu_dai_name = "samsung-i2s-sec", .codec_dai_name = "wm8580-hifi-playback", - .platform_name = "samsung-i2s.x", + .platform_name = "samsung-i2s-sec", .codec_name = "wm8580.0-001b", .ops = &smdk_ops, }, @@ -223,9 +223,6 @@ static int __init smdk_audio_init(void) if (machine_is_smdkc100() || machine_is_smdkv210() || machine_is_smdkc110()) { smdk.num_links = 3; - /* Secondary is at offset SAMSUNG_I2S_SECOFF from Primary */ - str = (char *)smdk_dai[SEC_PLAYBACK].cpu_dai_name; - str[strlen(str) - 1] = '0' + SAMSUNG_I2S_SECOFF; } else if (machine_is_smdk6410()) { str = (char *)smdk_dai[PRI_PLAYBACK].cpu_dai_name; str[strlen(str) - 1] = '2'; diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c index b0d0ab8..581ea4a 100644 --- a/sound/soc/samsung/smdk_wm8994.c +++ b/sound/soc/samsung/smdk_wm8994.c @@ -10,6 +10,7 @@ #include "../codecs/wm8994.h" #include <sound/pcm_params.h> #include <linux/module.h> +#include <linux/of.h> /* * Default CFG switch settings to use this driver: @@ -134,9 +135,9 @@ static struct snd_soc_dai_link smdk_dai[] = { }, { /* Sec_Fifo Playback i/f */ .name = "Sec_FIFO TX", .stream_name = "Sec_Dai", - .cpu_dai_name = "samsung-i2s.4", + .cpu_dai_name = "samsung-i2s-sec", .codec_dai_name = "wm8994-aif1", - .platform_name = "samsung-i2s.4", + .platform_name = "samsung-i2s-sec", .codec_name = "wm8994-codec", .ops = &smdk_ops, }, @@ -153,9 +154,25 @@ static struct snd_soc_card smdk = { static int smdk_audio_probe(struct platform_device *pdev) { int ret; + struct device_node *np = pdev->dev.of_node; struct snd_soc_card *card = &smdk; card->dev = &pdev->dev; + + if (np) { + smdk_dai[0].cpu_dai_name = NULL; + smdk_dai[0].cpu_of_node = of_parse_phandle(np, + "samsung,i2s-controller", 0); + if (!smdk_dai[0].cpu_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + } + + smdk_dai[0].platform_name = NULL; + smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node; + } + ret = snd_soc_register_card(card); if (ret) @@ -173,10 +190,19 @@ static int smdk_audio_remove(struct platform_device *pdev) 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 = { .driver = { .name = "smdk-audio", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_wm8994_of_match), }, .probe = smdk_audio_probe, .remove = smdk_audio_remove, |