diff options
author | Ola Lilja <ola.o.lilja@stericsson.com> | 2011-12-07 13:29:06 +0100 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@stericsson.com> | 2012-05-22 11:05:03 +0200 |
commit | 89becb08b9dffd67d543c086254c53c1a98bb909 (patch) | |
tree | a560c7b756523ff8c0d7725901a1c7d2d5e400b8 /sound/soc/ux500 | |
parent | 600afc22730e30beda77fed6b16722b86b00908a (diff) |
ASoC: Ux500: Move i2s into ASoC and remove old i2s-layer
The old multi-client i2s driver-layer located in drivers/misc/i2s
is removed and the MSP-operation for i2s is moved into the file
ux500_msp_i2s.c in the Ux500 ASoC-folder.
ST-Ericsson ID: -
ST-Ericsson Linux next: NA
ST-Ericsson FOSS-OUT ID: Trivial
Change-Id: I11c9021bb7b2385afba9a3e658b5bef7fe9fdb68
Signed-off-by: Ola Lilja <ola.o.lilja@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/41704
Diffstat (limited to 'sound/soc/ux500')
-rw-r--r-- | sound/soc/ux500/Kconfig | 2 | ||||
-rw-r--r-- | sound/soc/ux500/Makefile | 35 | ||||
-rwxr-xr-x | sound/soc/ux500/u5500.c | 10 | ||||
-rw-r--r-- | sound/soc/ux500/u8500.c | 12 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_dai.c | 141 | ||||
-rw-r--r--[-rwxr-xr-x] | sound/soc/ux500/ux500_msp_dai.h | 5 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_i2s.c | 1013 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_i2s.h | 41 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_pcm.c | 2 |
9 files changed, 1158 insertions, 103 deletions
diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig index 0078beb72ec..1a25fb6a0d6 100644 --- a/sound/soc/ux500/Kconfig +++ b/sound/soc/ux500/Kconfig @@ -4,7 +4,7 @@ menuconfig SND_SOC_UX500 bool "SoC Audio support for Ux500 platform" - depends on SND_SOC && STM_I2S && STM_MSP_I2S + depends on SND_SOC default n help Say Y if you want to add support for the Ux500 platform. diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile index 255c0ecefdb..262e44a2812 100644 --- a/sound/soc/ux500/Makefile +++ b/sound/soc/ux500/Makefile @@ -1,47 +1,46 @@ # Ux500 Platform Support ifdef CONFIG_SND_SOC_UX500_DEBUG +CFLAGS_u8500.o := -DDEBUG CFLAGS_ux500_pcm.o := -DDEBUG CFLAGS_ux500_msp_dai.o := -DDEBUG CFLAGS_ux500_ab3550.o := -DDEBUG CFLAGS_ux500_ab8500.o := -DDEBUG CFLAGS_ux500_av8100.o := -DDEBUG CFLAGS_ux500_cg29xx.o := -DDEBUG +CFLAGS_ux500_msp_i2s.o := -DDEBUG endif -ifdef CONFIG_SND_SOC_UX500_AB3550 -snd-soc-ux500-ab3550-objs := ux500_ab3550.o -obj-$(CONFIG_SND_SOC_UX500_AB3550) += ux500_ab3550.o -endif - -ifdef CONFIG_SND_SOC_UX500_AB5500 -snd-soc-ux500-ab5500-objs := ux500_ab5500.o -obj-$(CONFIG_SND_SOC_UX500_AB5500) += ux500_ab5500.o +ifdef CONFIG_UX500_SOC_DBX500 +snd-soc-ux500-platform-objs := ux500_pcm.o ux500_msp_dai.o ux500_msp_i2s.o +obj-y += snd-soc-ux500-platform.o endif ifdef CONFIG_SND_SOC_UX500_AB8500 -snd-soc-ux500-ab8500-objs := ux500_ab8500.o -obj-$(CONFIG_SND_SOC_UX500_AB8500) += ux500_ab8500.o +snd-soc-ux500-machine-objs += ux500_ab8500.o endif ifdef CONFIG_SND_SOC_UX500_AV8100 -snd-soc-ux500-av8100-objs := ux500_av8100.o -obj-$(CONFIG_SND_SOC_UX500_AV8100) += ux500_av8100.o +snd-soc-ux500-machine-objs += ux500_av8100.o endif ifdef CONFIG_SND_SOC_UX500_CG29XX -snd-soc-ux500-cg29xx-objs := ux500_cg29xx.o -obj-$(CONFIG_SND_SOC_UX500_CG29XX) += ux500_cg29xx.o +snd-soc-ux500-machine-objs += ux500_cg29xx.o +endif + +ifdef CONFIG_SND_SOC_UX500_AB5500 +snd-soc-ux500-machine-objs += ux500_ab5500.o endif -snd-soc-ux500-objs := ux500_pcm.o ux500_msp_dai.o +obj-y += snd-soc-ux500-machine.o ifdef CONFIG_UX500_SOC_DB8500 -snd-soc-ux500-objs += u8500.o +snd-soc-u8500-objs := u8500.o +obj-y += snd-soc-u8500.o endif ifdef CONFIG_UX500_SOC_DB5500 -snd-soc-ux500-objs += u5500.o +snd-soc-u5500-objs := u5500.o +obj-y += snd-soc-u5500.o endif -obj-$(CONFIG_UX500_SOC_DBX500) += snd-soc-ux500.o diff --git a/sound/soc/ux500/u5500.c b/sound/soc/ux500/u5500.c index 1d17830c1c7..6787daa9de5 100755 --- a/sound/soc/ux500/u5500.c +++ b/sound/soc/ux500/u5500.c @@ -66,7 +66,7 @@ struct snd_soc_dai_link u5500_dai_links[] = { { .name = "ab5500_0", .stream_name = "ab5500_0", - .cpu_dai_name = "i2s.0", + .cpu_dai_name = "ux500-msp-i2s.0", .codec_dai_name = "ab5500-codec-dai.0", .platform_name = "ux500-pcm.0", .codec_name = "ab5500-codec.0", @@ -83,7 +83,7 @@ struct snd_soc_dai_link u5500_dai_links[] = { { .name = "cg29xx_0", .stream_name = "cg29xx_0", - .cpu_dai_name = "i2s.1", + .cpu_dai_name = "ux500-msp-i2s.1", .codec_dai_name = "cg29xx-codec-dai.0", .platform_name = "ux500-pcm.0", .codec_name = "cg29xx-codec.0", @@ -93,7 +93,7 @@ struct snd_soc_dai_link u5500_dai_links[] = { { .name = "cg29xx_1", .stream_name = "cg29xx_1", - .cpu_dai_name = "i2s.0", + .cpu_dai_name = "ux500-msp-i2s.0", .codec_dai_name = "cg29xx-codec-dai.1", .platform_name = "ux500-pcm.0", .codec_name = "cg29xx-codec.0", @@ -104,7 +104,7 @@ struct snd_soc_dai_link u5500_dai_links[] = { { .name = "ab5500_1", .stream_name = "ab5500_1", - .cpu_dai_name = "i2s.1", + .cpu_dai_name = "ux500-msp-i2s.1", .codec_dai_name = "ab5500-codec-dai.1", .platform_name = "ux500-pcm.0", .codec_name = "ab5500-codec.0", @@ -121,7 +121,7 @@ struct snd_soc_dai_link u5500_dai_links[] = { { .name = "hdmi", .stream_name = "hdmi", - .cpu_dai_name = "i2s.2", + .cpu_dai_name = "ux500-msp-i2s.2", .codec_dai_name = "av8100-codec-dai", .platform_name = "ux500-pcm.0", .codec_name = "av8100-codec.0", diff --git a/sound/soc/ux500/u8500.c b/sound/soc/ux500/u8500.c index c27409f1009..516008fcc6a 100644 --- a/sound/soc/ux500/u8500.c +++ b/sound/soc/ux500/u8500.c @@ -77,7 +77,7 @@ struct snd_soc_dai_link u8500_dai_links[] = { { .name = "hdmi", .stream_name = "hdmi", - .cpu_dai_name = "i2s.2", + .cpu_dai_name = "ux500-msp-i2s.2", .codec_dai_name = "av8100-codec-dai", .platform_name = "ux500-pcm.0", .codec_name = "av8100-codec.0", @@ -89,7 +89,7 @@ struct snd_soc_dai_link u8500_dai_links[] = { { .name = "ab3550_0", .stream_name = "ab3550_0", - .cpu_dai_name = "i2s.0", + .cpu_dai_name = "ux500-msp-i2s.0", .codec_dai_name = "ab3550-codec-dai.0", .platform_name = "ux500-pcm.0", .codec_name = "ab3550-codec.11", @@ -99,7 +99,7 @@ struct snd_soc_dai_link u8500_dai_links[] = { { .name = "ab3550_1", .stream_name = "ab3550_1", - .cpu_dai_name = "i2s.1", + .cpu_dai_name = "ux500-msp-i2s.1", .codec_dai_name = "ab3550-codec-dai.1", .platform_name = "ux500-pcm.0", .codec_name = "ab3550-codec.11", @@ -111,7 +111,7 @@ struct snd_soc_dai_link u8500_dai_links[] = { { .name = "ab8500_0", .stream_name = "ab8500_0", - .cpu_dai_name = "i2s.1", + .cpu_dai_name = "ux500-msp-i2s.1", .codec_dai_name = "ab8500-codec-dai.0", .platform_name = "ux500-pcm.0", .codec_name = "ab8500-codec.0", @@ -121,7 +121,7 @@ struct snd_soc_dai_link u8500_dai_links[] = { { .name = "ab8500_1", .stream_name = "ab8500_1", - .cpu_dai_name = "i2s.3", + .cpu_dai_name = "ux500-msp-i2s.3", .codec_dai_name = "ab8500-codec-dai.1", .platform_name = "ux500-pcm.0", .codec_name = "ab8500-codec.0", @@ -133,7 +133,7 @@ struct snd_soc_dai_link u8500_dai_links[] = { { .name = "cg29xx_0", .stream_name = "cg29xx_0", - .cpu_dai_name = "i2s.0", + .cpu_dai_name = "ux500-msp-i2s.0", .codec_dai_name = "cg29xx-codec-dai.1", .platform_name = "ux500-pcm.0", .codec_name = "cg29xx-codec.0", diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 6cdc96734ed..2af5bf14193 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -1,5 +1,5 @@ /* - * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) ST-Ericsson SA 2011 * * Author: Ola Lilja <ola.o.lilja@stericsson.com>, * Roger Nilsson <roger.xr.nilsson@stericsson.com> @@ -14,21 +14,22 @@ #include <linux/module.h> #include <linux/slab.h> -#include <asm/dma.h> #include <linux/bitops.h> +#include <linux/platform_device.h> + #include <mach/hardware.h> #include <mach/msp.h> -#include <linux/i2s/i2s.h> -#include <asm/mach-types.h> #include <sound/soc.h> #include <sound/soc-dai.h> + +#include "ux500_msp_i2s.h" #include "ux500_msp_dai.h" #include "ux500_pcm.h" static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { { - .i2s = NULL, + .msp_i2s_drvdata = NULL, .fmt = 0, .slots = 1, .tx_mask = 0x01, @@ -41,7 +42,7 @@ static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ, }, { - .i2s = NULL, + .msp_i2s_drvdata = NULL, .fmt = 0, .slots = 1, .tx_mask = 0x01, @@ -54,7 +55,7 @@ static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ, }, { - .i2s = NULL, + .msp_i2s_drvdata = NULL, .fmt = 0, .slots = 1, .tx_mask = 0x01, @@ -67,7 +68,7 @@ static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ, }, { - .i2s = NULL, + .msp_i2s_drvdata = NULL, .fmt = 0, .slots = 1, .tx_mask = 0x01, @@ -84,14 +85,14 @@ static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { bool ux500_msp_dai_i2s_get_underrun_status(int dai_idx) { struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; - int status = i2s_hw_status(drvdata->i2s->controller); + int status = ux500_msp_i2s_hw_status(drvdata->msp_i2s_drvdata); return (bool)(status & TRANSMIT_UNDERRUN_ERR_INT); } dma_addr_t ux500_msp_dai_i2s_get_pointer(int dai_idx, int stream_id) { struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; - return i2s_get_pointer(drvdata->i2s->controller, + return ux500_msp_i2s_get_pointer(drvdata->msp_i2s_drvdata, (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? I2S_DIRECTION_TX : I2S_DIRECTION_RX); @@ -105,7 +106,6 @@ int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, { struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; struct i2s_message message; - struct i2s_device *i2s_dev; int ret = 0; bool playback_req_valid = (drvdata->playback_active && @@ -128,9 +128,6 @@ int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, return ret; } - i2s_dev = drvdata->i2s; - - message.i2s_transfer_mode = I2S_TRANSFER_MODE_CYCLIC_DMA; message.i2s_direction = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? I2S_DIRECTION_TX : I2S_DIRECTION_RX; @@ -138,7 +135,7 @@ int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, message.buf_len = period_cnt * period_len; message.period_len = period_len; - ret = i2s_transfer(i2s_dev->controller, &message); + ret = ux500_msp_i2s_transfer(drvdata->msp_i2s_drvdata, &message); if (ret < 0) { pr_err("%s: Error: i2s_transfer failed. MSP index: %d\n", __func__, @@ -197,7 +194,7 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, else drvdata->capture_active = false; - if (i2s_cleanup(drvdata->i2s->controller, + if (ux500_msp_i2s_close(drvdata->msp_i2s_drvdata, mode_playback ? DISABLE_TRANSMIT : DISABLE_RECEIVE)) { pr_err("%s: Error: MSP %d (%s): Unable to close i2s.\n", __func__, @@ -468,7 +465,7 @@ static void ux500_msp_dai_compile_msp_config(struct snd_pcm_substream *substream msp_config->work_mode = MSP_DMA_MODE; msp_config->frame_freq = rate; - printk(KERN_INFO "%s: input_clock_freq = %u, frame_freq = %u.\n", + pr_debug("%s: input_clock_freq = %u, frame_freq = %u.\n", __func__, msp_config->input_clock_freq, msp_config->frame_freq); /* To avoid division by zero in I2S-driver (i2s_setup) */ prot_desc->total_clocks_for_one_frame = 1; @@ -548,9 +545,9 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, runtime->rate, &msp_config); - ret = i2s_setup(drvdata->i2s->controller, &msp_config); + ret = ux500_msp_i2s_open(drvdata->msp_i2s_drvdata, &msp_config); if (ret < 0) { - pr_err("%s: Error: i2s_setup failed (ret = %d)!\n", __func__, ret); + pr_err("%s: Error: msp_setup failed (ret = %d)!\n", __func__, ret); goto cleanup; } @@ -764,11 +761,11 @@ static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream, int ret = 0; struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; - pr_debug("%s: MSP %d (%s): Enter (chip_select = %d, cmd = %d).\n", + pr_debug("%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n", __func__, dai->id, stream_str(substream), - (int)drvdata->i2s->chip_select, + (int)drvdata->msp_i2s_drvdata->id, cmd); switch (cmd) { @@ -796,7 +793,7 @@ static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream, static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { { - .name = "ux500-msp.0", + .name = "ux500-msp-i2s.0", .id = 0, .suspend = NULL, .resume = NULL, @@ -826,7 +823,7 @@ static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { }, }, { - .name = "ux500-msp.1", + .name = "ux500-msp-i2s.1", .id = 1, .suspend = NULL, .resume = NULL, @@ -856,7 +853,7 @@ static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { }, }, { - .name = "ux500-msp.2", + .name = "ux500-msp-i2s.2", .id = 2, .suspend = NULL, .resume = NULL, @@ -886,7 +883,7 @@ static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { }, }, { - .name = "ux500-msp.3", + .name = "ux500-msp-i2s.3", .id = 3, .suspend = NULL, .resume = NULL, @@ -918,84 +915,90 @@ static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { }; EXPORT_SYMBOL(ux500_msp_dai_drv); -static int ux500_msp_drv_probe(struct i2s_device *i2s_dev) +static int ux500_msp_drv_probe(struct platform_device *pdev) { - int ret = 0; + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; struct ux500_platform_drvdata *drvdata; - int msp_idx = i2s_dev->chip_select; + struct msp_i2s_platform_data *platform_data; + int id; + int ret = 0; - pr_info("%s: Enter (idx: %d, dev-name: %s, drv-name: %s).\n", - __func__, - msp_idx, - dev_name(&i2s_dev->dev), - i2s_dev->dev.driver->name); + pr_err("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); - drvdata = &platform_drvdata[msp_idx]; - drvdata->i2s = i2s_dev; + platform_data = (struct msp_i2s_platform_data *)pdev->dev.platform_data; + msp_i2s_drvdata = ux500_msp_i2s_init(pdev, platform_data); + if (!msp_i2s_drvdata) { + pr_err("%s: ERROR: ux500_msp_i2s_init failed!", __func__); + return -EINVAL; + } - try_module_get(i2s_dev->controller->dev.parent->driver->owner); - i2s_set_drvdata(i2s_dev, drvdata); + id = msp_i2s_drvdata->id; + drvdata = &platform_drvdata[id]; + drvdata->msp_i2s_drvdata = msp_i2s_drvdata; - pr_debug("%s: Register MSP %d.\n", __func__, msp_idx); - ret = snd_soc_register_dai(&i2s_dev->dev, &ux500_msp_dai_drv[msp_idx]); + pr_info("%s: Registering ux500-msp-dai SoC CPU-DAI.\n", __func__); + ret = snd_soc_register_dai(&pdev->dev, &ux500_msp_dai_drv[id]); if (ret < 0) { - pr_err("Error: %s: Failed to register MSP %d.\n", __func__, msp_idx); + pr_err("Error: %s: Failed to register MSP %d.\n", __func__, id); return ret; } return ret; } -static int ux500_msp_drv_remove(struct i2s_device *i2s_dev) +static int ux500_msp_drv_remove(struct platform_device *pdev) { - struct ux500_platform_drvdata *drvdata = i2s_get_drvdata(i2s_dev); - int msp_idx = i2s_dev->chip_select; + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + struct ux500_platform_drvdata *drvdata = &platform_drvdata[msp_i2s_drvdata->id]; - pr_info("%s: Enter (idx: %d, dev-name: %s, drv-name: %s).\n", - __func__, - msp_idx, - dev_name(&i2s_dev->dev), - i2s_dev->dev.driver->name); + pr_info("%s: Unregister ux500-msp-dai ASoC CPU-DAI.\n", __func__); + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(ux500_msp_dai_drv)); - drvdata->i2s = NULL; - i2s_set_drvdata(i2s_dev, NULL); + ux500_msp_i2s_exit(msp_i2s_drvdata); + drvdata->msp_i2s_drvdata = NULL; - pr_debug("%s: Calling module_put.\n", __func__); - module_put(i2s_dev->controller->dev.parent->driver->owner); + return 0; +} - pr_debug("%s: Unregister ux500-pcm SoC platform driver.\n", __func__); - snd_soc_unregister_dais(&i2s_dev->dev, ARRAY_SIZE(ux500_msp_dai_drv)); +int ux500_msp_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); - return 0; + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + return ux500_msp_i2s_suspend(msp_i2s_drvdata); } -static const struct i2s_device_id dev_id_table[] = { - { "i2s_device.0", 0, 0 }, - { "i2s_device.1", 1, 0 }, - { "i2s_device.2", 2, 0 }, - { "i2s_device.3", 3, 0 }, - { }, -}; -MODULE_DEVICE_TABLE(i2s, dev_id_table); +int ux500_msp_drv_resume(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + return ux500_msp_i2s_resume(msp_i2s_drvdata); +} -static struct i2s_driver i2sdrv_i2s = { +static struct platform_driver msp_i2s_driver = { .driver = { - .name = "i2s", + .name = "ux500-msp-i2s", .owner = THIS_MODULE, }, .probe = ux500_msp_drv_probe, - .remove = __devexit_p(ux500_msp_drv_remove), - .id_table = dev_id_table, + .remove = ux500_msp_drv_remove, + .suspend = ux500_msp_drv_suspend, + .resume = ux500_msp_drv_resume, }; static int __init ux500_msp_init(void) { - return i2s_register_driver(&i2sdrv_i2s); + pr_info("%s: Register ux500-msp-dai platform driver.\n", __func__); + return platform_driver_register(&msp_i2s_driver); } static void __exit ux500_msp_exit(void) { - i2s_unregister_driver(&i2sdrv_i2s); + pr_info("%s: Unregister ux500-msp-dai platform driver.\n", __func__); + platform_driver_unregister(&msp_i2s_driver); } module_init(ux500_msp_init); diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index cff6749760d..c44894526f2 100755..100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -1,5 +1,5 @@ /* - * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) ST-Ericsson SA 2011 * * Author: Ola Lilja <ola.o.lilja@stericsson.com>, * Roger Nilsson <roger.xr.nilsson@stericsson.com> @@ -17,7 +17,6 @@ #include <linux/types.h> #include <linux/spinlock.h> -#include <linux/i2s/i2s.h> #include <mach/msp.h> #define UX500_NBR_OF_DAI 4 @@ -54,7 +53,7 @@ enum ux500_msp_clock_id { }; struct ux500_platform_drvdata { - struct i2s_device *i2s; + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; unsigned int fmt; unsigned int tx_mask; unsigned int rx_mask; diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c new file mode 100644 index 00000000000..1fa7a947fe1 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -0,0 +1,1013 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Sandeep Kaushik <sandeep.kaushik@st.com> + * for ST-Ericsson. + * + * License terms: + * + * 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. + */ + +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/hardware.h> +#include <mach/msp.h> + +#include "ux500_msp_i2s.h" + + /* Protocol desciptors */ +static const struct msp_protocol_desc prot_descs[] = { + I2S_PROTOCOL_DESC, + PCM_PROTOCOL_DESC, + PCM_COMPAND_PROTOCOL_DESC, + AC97_PROTOCOL_DESC, + SPI_MASTER_PROTOCOL_DESC, + SPI_SLAVE_PROTOCOL_DESC, +}; + +static void ux500_msp_i2s_set_prot_desc_tx(struct msp *msp, + struct msp_protocol_desc *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->tx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->tx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->tx_element_length_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->tx_element_length_2); + if (protocol_desc->tx_element_length_1 == + protocol_desc->tx_element_length_2) { + msp->actual_data_size = protocol_desc->tx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->tx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->tx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->tx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->compression_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_TCF); +} + +static void ux500_msp_i2s_set_prot_desc_rx(struct msp *msp, + struct msp_protocol_desc *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->rx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->rx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->rx_element_length_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->rx_element_length_2); + if (protocol_desc->rx_element_length_1 == + protocol_desc->rx_element_length_2) { + msp->actual_data_size = protocol_desc->rx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->rx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->rx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->rx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->expansion_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_RCF); + +} + +static int ux500_msp_i2s_configure_protocol(struct msp *msp, + struct msp_config *config) +{ + int direction; + struct msp_protocol_desc *protocol_desc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + direction = config->direction; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + pr_err("%s: ERROR: Invalid protocol!\n", __func__); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&prot_descs[config->protocol]; + } else { + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) { + pr_err("%s: ERROR: Invalid data-size requested (data_size = %d)!\n", + __func__, data_size); + return -EINVAL; + } + + switch (direction) { + case MSP_TRANSMIT_MODE: + ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size); + break; + case MSP_RECEIVE_MODE: + ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size); + break; + case MSP_BOTH_T_R_MODE: + ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size); + ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size); + break; + default: + pr_err("%s: ERROR: Invalid direction requested (direction = %d)!\n", + __func__, direction); + return -EINVAL; + } + + /* The below code is needed for both Rx and Tx path. Can't separate them. */ + temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(~protocol_desc->tx_clock_pol); + writel(temp_reg, msp->registers + MSP_GCR); + temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protocol_desc->rx_clock_pol); + writel(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +static int ux500_msp_i2s_configure_clock(struct msp *msp, struct msp_config *config) +{ + u32 reg_val_GCR; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + u32 bit_clock = 0; + struct msp_protocol_desc *protocol_desc = NULL; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); + + if (config->default_protocol_desc) + protocol_desc = + (struct msp_protocol_desc *)&prot_descs[config->protocol]; + else + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = config->input_clock_freq / (config->frame_freq * + (protocol_desc->total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = config->input_clock_freq / (config->frame_freq * + (protocol_desc->total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_AC97_PROTOCOL: + /* Not supported */ + pr_err("%s: ERROR: AC97 protocol not supported!\n", __func__); + return -ENOSYS; + default: + pr_err("%s: ERROR: Unknown protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + writel(temp_reg, msp->registers + MSP_SRG); + + bit_clock = (config->input_clock_freq)/(sck_div + 1); + /* If the bit clock is higher than 19.2MHz, Vape should be run in 100% OPP + * Only consider OPP 100% when bit-clock is used, i.e. MSP master mode + */ + if ((bit_clock > 19200000) && ((config->tx_clock_sel != 0) || (config->rx_clock_sel != 0))) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 100); + msp->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 50); + msp->vape_opp_constraint = 0; + } + + /* Enable clock */ + udelay(100); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR); + udelay(100); + + return 0; +} + +static int ux500_msp_i2s_configure_multichannel(struct msp *msp, struct msp_config *config) +{ + struct msp_protocol_desc *protocol_desc; + struct msp_multichannel_config *mcfg; + u32 reg_val_MCR; + + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + pr_err("%s: ERROR: Invalid protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + protocol_desc = (struct msp_protocol_desc *) + &prot_descs[config->protocol]; + } else { + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + } + + mcfg = &config->multichannel_config; + if (mcfg->tx_multichannel_enable) { + if (protocol_desc->tx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->tx_multichannel_enable ? 1 << TMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->tx_channel_0_enable, + msp->registers + MSP_TCE0); + writel(mcfg->tx_channel_1_enable, + msp->registers + MSP_TCE1); + writel(mcfg->tx_channel_2_enable, + msp->registers + MSP_TCE2); + writel(mcfg->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + pr_err("%s: ERROR: Only single-phase supported (TX-mode: %d)!\n", + __func__, protocol_desc->tx_phase_mode); + return -EINVAL; + } + } + if (mcfg->rx_multichannel_enable) { + if (protocol_desc->rx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_multichannel_enable ? 1 << RMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->rx_channel_0_enable, + msp->registers + MSP_RCE0); + writel(mcfg->rx_channel_1_enable, + msp->registers + MSP_RCE1); + writel(mcfg->rx_channel_2_enable, + msp->registers + MSP_RCE2); + writel(mcfg->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + pr_err("%s: ERROR: Only single-phase supported (RX-mode: %d)!\n", + __func__, protocol_desc->rx_phase_mode); + return -EINVAL; + } + if (mcfg->rx_comparison_enable_mode) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_comparison_enable_mode << RCMPM_BIT), + msp->registers + MSP_MCR); + + writel(mcfg->comparison_mask, + msp->registers + MSP_RCM); + writel(mcfg->comparison_value, + msp->registers + MSP_RCV); + + } + } + + return 0; +} + +void ux500_msp_i2s_configure_dma(struct msp *msp, struct msp_config *config) +{ + struct stedma40_chan_cfg *rx_dma_info = msp->dma_cfg_rx; + struct stedma40_chan_cfg *tx_dma_info = msp->dma_cfg_tx; + dma_cap_mask_t mask; + u16 word_width; + bool rx_active, tx_active; + + if (msp->tx_pipeid != NULL) { + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + + switch (config->data_size) { + case MSP_DATA_BITS_32: + word_width = STEDMA40_WORD_WIDTH; + break; + case MSP_DATA_BITS_16: + word_width = STEDMA40_HALFWORD_WIDTH; + break; + case MSP_DATA_BITS_8: + word_width = STEDMA40_BYTE_WIDTH; + break; + default: + word_width = STEDMA40_WORD_WIDTH; + pr_warn("%s: Unknown data-size (%d)! Assuming 32 bits.\n", + __func__, config->data_size); + } + + rx_active = (config->direction == MSP_RECEIVE_MODE || + config->direction == MSP_BOTH_T_R_MODE); + tx_active = (config->direction == MSP_TRANSMIT_MODE || + config->direction == MSP_BOTH_T_R_MODE); + + if (rx_active) { + rx_dma_info->src_info.data_width = word_width; + rx_dma_info->dst_info.data_width = word_width; + } + if (tx_active) { + tx_dma_info->src_info.data_width = word_width; + tx_dma_info->dst_info.data_width = word_width; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + if (rx_active) + msp->rx_pipeid = dma_request_channel(mask, stedma40_filter, rx_dma_info); + + if (tx_active) + msp->tx_pipeid = dma_request_channel(mask, stedma40_filter, tx_dma_info); +} + +static int ux500_msp_i2s_dma_xfer(struct msp *msp, struct i2s_message *msg) +{ + dma_cookie_t status_submit; + int direction, enable_bit; + u32 reg_val_GCR; + struct dma_chan *pipeid; + struct dma_async_tx_descriptor *cdesc; + + if (msg->i2s_direction == I2S_DIRECTION_TX) { + direction = DMA_TO_DEVICE; + pipeid = msp->tx_pipeid; + enable_bit = TX_ENABLE; + pr_debug("%s: Direction: TX\n", __func__); + } else { + direction = DMA_FROM_DEVICE; + pipeid = msp->rx_pipeid; + enable_bit = RX_ENABLE; + pr_debug("%s: Direction: RX\n", __func__); + } + + pr_debug("%s: msg->buf_addr = %p\n", __func__, (void *)msg->buf_addr); + pr_debug("%s: buf_len = %d\n", __func__, msg->buf_len); + pr_debug("%s: perios_len = %d\n", __func__, msg->period_len); + + /* setup the cyclic description */ + cdesc = pipeid->device->device_prep_dma_cyclic(pipeid, + msg->buf_addr, + msg->buf_len, + msg->period_len, + direction); + if (IS_ERR(cdesc)) { + pr_err("%s: ERROR: device_prep_dma_cyclic failed (%ld)!\n", + __func__, + PTR_ERR(cdesc)); + return -EINVAL; + } + + /* Submit to the dma */ + if (msg->i2s_direction == I2S_DIRECTION_TX) { + cdesc->callback = msp->xfer_data.tx_handler; + cdesc->callback_param = msp->xfer_data.tx_callback_data; + } else { + cdesc->callback = msp->xfer_data.rx_handler; + cdesc->callback_param = msp->xfer_data.rx_callback_data; + } + status_submit = dmaengine_submit(cdesc); + if (dma_submit_error(status_submit)) { + pr_err("%s: ERROR: dmaengine_submit failed!\n", __func__); + return -EINVAL; + } + + /* Start the dma */ + dma_async_issue_pending(pipeid); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + + return 0; +} + +static int ux500_msp_i2s_enable(struct msp *msp, struct msp_config *config) +{ + int status = 0; + u32 reg_val_DMACR, reg_val_GCR; + + if (config->work_mode != MSP_DMA_MODE) { + pr_err("%s: ERROR: Only DMA-mode is supported (msp->work_mode = %d)\n", + __func__, + msp->work_mode); + return -EINVAL; + } + msp->work_mode = config->work_mode; + + /* Check msp state whether in RUN or CONFIGURED Mode */ + if (msp->msp_state == MSP_STATE_IDLE) { + if (msp->plat_init) { + status = msp->plat_init(); + if (status) { + pr_err("%s: ERROR: Failed to init MSP (%d)!\n", + __func__, + status); + return status; + } + } + } + + /* Configure msp with protocol dependent settings */ + ux500_msp_i2s_configure_protocol(msp, config); + ux500_msp_i2s_configure_clock(msp, config); + if (config->multichannel_configured == 1) { + status = ux500_msp_i2s_configure_multichannel(msp, config); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_configure_multichannel failed (%d)!\n", + __func__, status); + } + + /* Make sure the correct DMA-directions are configured */ + if ((config->direction == MSP_RECEIVE_MODE) || + (config->direction == MSP_BOTH_T_R_MODE)) + if (!msp->dma_cfg_rx) { + pr_err("%s: ERROR: MSP RX-mode is not configured!", __func__); + return -EINVAL; + } + if ((config->direction == MSP_TRANSMIT_MODE) || + (config->direction == MSP_BOTH_T_R_MODE)) + if (!msp->dma_cfg_tx) { + pr_err("%s: ERROR: MSP TX-mode is not configured!", __func__); + return -EINVAL; + } + + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + switch (config->direction) { + case MSP_TRANSMIT_MODE: + writel(reg_val_DMACR | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = config->tx_callback_data; + + break; + case MSP_RECEIVE_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.rx_callback_data = config->rx_callback_data; + + break; + case MSP_BOTH_T_R_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = config->tx_callback_data; + msp->xfer_data.rx_callback_data = config->rx_callback_data; + + break; + default: + pr_err("%s: ERROR: Illegal MSP direction (config->direction = %d)!", + __func__, + config->direction); + if (msp->plat_exit) + msp->plat_exit(); + return -EINVAL; + } + ux500_msp_i2s_configure_dma(msp, config); + + msp->transfer = ux500_msp_i2s_dma_xfer; + + writel(config->iodelay, msp->registers + MSP_IODLY); + + /* Enable frame generation logic */ + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); + + return status; +} + +static void flush_fifo_rx(struct msp *msp) +{ + u32 reg_val_DR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) { + reg_val_DR = readl(msp->registers + MSP_DR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +static void flush_fifo_tx(struct msp *msp) +{ + u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR); + writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) { + reg_val_TSTDR = readl(msp->registers + MSP_TSTDR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + writel(0x0, msp->registers + MSP_ITCR); + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config) +{ + struct msp *msp = drvdata->msp; + u32 old_reg, new_reg, mask; + int res; + + if (in_interrupt()) { + pr_err("%s: ERROR: Open called in interrupt context!\n", __func__); + return -1; + } + + /* Two simultanous configuring msp is avoidable */ + down(&msp->lock); + + /* Don't enable regulator if its msp1 or msp3 */ + if (!(msp->reg_enabled) && msp->id != MSP_1_I2S_CONTROLLER + && msp->id != MSP_3_I2S_CONTROLLER) { + res = regulator_enable(drvdata->reg_vape); + if (res != 0) { + pr_err("%s: Failed to enable regulator!\n", __func__); + up(&msp->lock); + return res; + } + msp->reg_enabled = 1; + } + + switch (msp->users) { + case 0: + clk_enable(msp->clk); + msp->direction = msp_config->direction; + break; + case 1: + if (msp->direction == MSP_BOTH_T_R_MODE || + msp_config->direction == msp->direction || + msp_config->direction == MSP_BOTH_T_R_MODE) { + pr_warn("%s: WARN: MSP is in use (direction = %d)!\n", + __func__, msp_config->direction); + up(&msp->lock); + return -EBUSY; + } + msp->direction = MSP_BOTH_T_R_MODE; + break; + default: + pr_warn("%s: MSP in use in (both directions)!\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->users++; + + /* First do the global config register */ + mask = + RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FRAME_SYNC_MASK | + TX_FRAME_SYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = (msp_config->tx_clock_sel | msp_config->rx_clock_sel | + msp_config->rx_frame_sync_pol | msp_config->tx_frame_sync_pol | + msp_config->rx_frame_sync_sel | msp_config->tx_frame_sync_sel | + msp_config->rx_fifo_config | msp_config->tx_fifo_config | + msp_config->srg_clock_sel | msp_config->loopback_enable | + msp_config->tx_data_enable); + + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + + if (ux500_msp_i2s_enable(msp, msp_config) != 0) { + pr_err("%s: ERROR: ux500_msp_i2s_enable failed!\n", __func__); + return -EBUSY; + } + if (msp_config->loopback_enable & 0x80) + msp->loopback_enable = 1; + + /* Flush FIFOs */ + flush_fifo_tx(msp); + flush_fifo_rx(msp); + + msp->msp_state = MSP_STATE_CONFIGURED; + up(&msp->lock); + return 0; +} + +static void func_notify_timer(unsigned long data) +{ + struct msp *msp = (struct msp *)data; + if (msp->polling_flag) { + msp->msp_io_error = 1; + pr_err("%s: ERROR: Polling timeout!\n", __func__); + del_timer(&msp->notify_timer); + } +} + +int ux500_msp_i2s_transfer(struct ux500_msp_i2s_drvdata *drvdata, struct i2s_message *message) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + if (!message || (msp->msp_state == MSP_STATE_IDLE)) { + pr_err("%s: ERROR: i2s_message == NULL!\n", __func__); + return -EINVAL; + } + if (msp->msp_state == MSP_STATE_IDLE) { + pr_err("%s: ERROR: MSP in idle-state!\n", __func__); + return -EPERM; + } + + msp->msp_state = MSP_STATE_RUN; + if (msp->transfer) + status = msp->transfer(msp, message); + + if (msp->msp_state == MSP_STATE_RUN) + msp->msp_state = MSP_STATE_CONFIGURED; + + return status; +} + +static void ux500_msp_i2s_disable_rx(struct msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(RECEIVE_SERVICE_INT | RECEIVE_OVERRUN_ERROR_INT), + msp->registers + MSP_IMSC); + msp->xfer_data.message.rxbytes = 0; + msp->xfer_data.message.rx_offset = 0; + msp->xfer_data.message.rxdata = NULL; + msp->read = NULL; +} + +static void ux500_msp_i2s_disable_tx(struct msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(TRANSMIT_SERVICE_INT | TRANSMIT_UNDERRUN_ERR_INT), + msp->registers + MSP_IMSC); + msp->xfer_data.message.txbytes = 0; + msp->xfer_data.message.tx_offset = 0; + msp->xfer_data.message.txdata = NULL; + msp->write = NULL; +} + +static int ux500_msp_i2s_disable(struct msp *msp, int direction, enum i2s_flag flag) +{ + u32 reg_val_GCR; + int status = 0; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + if (!(reg_val_GCR & (TX_ENABLE | RX_ENABLE))) + return 0; + + if (flag == DISABLE_ALL || flag == DISABLE_TRANSMIT) { + if (msp->tx_pipeid != NULL) { + dmaengine_terminate_all(msp->tx_pipeid); + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + } + if ((flag == DISABLE_ALL || flag == DISABLE_RECEIVE)) { + if (msp->rx_pipeid != NULL) { + dmaengine_terminate_all(msp->rx_pipeid); + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + } + + if (flag == DISABLE_TRANSMIT) + ux500_msp_i2s_disable_tx(msp); + else if (flag == DISABLE_RECEIVE) + ux500_msp_i2s_disable_rx(msp); + else { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | LOOPBACK_MASK, + msp->registers + MSP_GCR); + + /* Flush TX-FIFO */ + flush_fifo_tx(msp); + + /* Disable TX-channel */ + writel((readl(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + + /* Flush RX-FIFO */ + flush_fifo_rx(msp); + + /* Disable Loopback and Receive channel */ + writel((readl(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + + ux500_msp_i2s_disable_tx(msp); + ux500_msp_i2s_disable_rx(msp); + + } + + /* disable sample rate and frame generators */ + if (flag == DISABLE_ALL) { + msp->msp_state = MSP_STATE_IDLE; + writel((readl(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + memset(&msp->xfer_data, 0, sizeof(struct trans_data)); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_exit failed (%d)!\n", + __func__, status); + msp->transfer = NULL; + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_TCF); + writel(0, msp->registers + MSP_RCF); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + writel(0, msp->registers + MSP_RCM); + writel(0, msp->registers + MSP_RCV); + writel(0, msp->registers + MSP_TCE0); + writel(0, msp->registers + MSP_TCE1); + writel(0, msp->registers + MSP_TCE2); + writel(0, msp->registers + MSP_TCE3); + writel(0, msp->registers + MSP_RCE0); + writel(0, msp->registers + MSP_RCE1); + writel(0, msp->registers + MSP_RCE2); + writel(0, msp->registers + MSP_RCE3); + } + + return status; +} + +int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + pr_debug("%s: Enter.\n", __func__); + + down(&msp->lock); + + if (msp->users == 0) { + pr_err("%s: ERROR: MSP already closed!\n", __func__); + status = -EINVAL; + goto end; + } + pr_debug("%s: msp->users = %d, flag = %d\n", __func__, msp->users, flag); + + /* We need to call it twice for DISABLE_ALL*/ + msp->users = flag == DISABLE_ALL ? 0 : msp->users - 1; + if (msp->users) + status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, flag); + else { + status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, DISABLE_ALL); + clk_disable(msp->clk); + if (msp->reg_enabled) { + status = regulator_disable(drvdata->reg_vape); + msp->reg_enabled = 0; + } + if (status != 0) { + pr_err("%s: ERROR: Failed to disable regulator (%d)!\n", + __func__, status); + clk_enable(msp->clk); + goto end; + } + } + if (status) + goto end; + if (msp->users) + msp->direction = flag == DISABLE_TRANSMIT ? + MSP_RECEIVE_MODE : MSP_TRANSMIT_MODE; + + if (msp->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s", 50); + msp->vape_opp_constraint = 0; + } +end: + up(&msp->lock); + return status; + +} + +int ux500_msp_i2s_hw_status(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + int status; + + pr_debug("%s: Enter.\n", __func__); + + status = readl(msp->registers + MSP_RIS) & 0xee; + if (status) + writel(status, msp->registers + MSP_ICR); + + return status; +} + +dma_addr_t ux500_msp_i2s_get_pointer(struct ux500_msp_i2s_drvdata *drvdata, + enum i2s_direction_t i2s_direction) +{ + struct msp *msp = drvdata->msp; + + pr_debug("%s: Enter.\n", __func__); + + return (i2s_direction == I2S_DIRECTION_TX) ? + stedma40_get_src_addr(msp->tx_pipeid) : + stedma40_get_dst_addr(msp->rx_pipeid); +} + +struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev, + struct msp_i2s_platform_data *platform_data) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + int irq; + struct resource *res = NULL; + struct i2s_controller *i2s_cont; + struct msp *msp; + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + msp_i2s_drvdata = kzalloc(sizeof(struct ux500_msp_i2s_drvdata), GFP_KERNEL); + msp_i2s_drvdata->msp = kzalloc(sizeof(struct msp), GFP_KERNEL); + msp = msp_i2s_drvdata->msp; + + msp->id = platform_data->id; + msp_i2s_drvdata->id = msp->id; + pr_debug("msp_i2s_drvdata->id = %d\n", msp_i2s_drvdata->id); + + msp->plat_init = platform_data->msp_i2s_init; + msp->plat_exit = platform_data->msp_i2s_exit; + msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; + msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx; + + sema_init(&msp->lock, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + pr_err("%s: ERROR: Unable to get resource!\n", __func__); + goto free_msp; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto free_msp; + msp->irq = irq; + + msp->registers = ioremap(res->start, (res->end - res->start + 1)); + if (msp->registers == NULL) + goto free_msp; + + msp_i2s_drvdata->reg_vape = regulator_get(NULL, "v-ape"); + if (IS_ERR(msp_i2s_drvdata->reg_vape)) { + pr_err("%s: ERROR: Failed to get Vape supply (%d)!\n", + __func__, (int)PTR_ERR(msp_i2s_drvdata->reg_vape)); + goto free_irq; + } + dev_set_drvdata(&pdev->dev, msp_i2s_drvdata); + + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50); + msp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(msp->clk)) { + pr_err("%s: ERROR: clk_get failed (%d)!\n", + __func__, (int)PTR_ERR(msp->clk)); + goto free_irq; + } + + init_timer(&msp->notify_timer); + msp->notify_timer.expires = jiffies + msecs_to_jiffies(1000); + msp->notify_timer.function = func_notify_timer; + msp->notify_timer.data = (unsigned long)msp; + + msp->rx_pipeid = NULL; + msp->tx_pipeid = NULL; + msp->read = NULL; + msp->write = NULL; + msp->transfer = NULL; + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + /* I2S Controller is allocated and added in I2S controller class. */ + i2s_cont = kzalloc(sizeof(*i2s_cont), GFP_KERNEL); + if (!i2s_cont) { + pr_err("%s: ERROR: Failed to allocate struct i2s_cont (kzalloc)!\n", + __func__); + goto del_timer; + } + i2s_cont->dev.parent = &pdev->dev; + i2s_cont->data = (void *)msp; + i2s_cont->id = (s16)msp->id; + snprintf(i2s_cont->name, + sizeof(i2s_cont->name), + "ux500-msp-i2s.%04x", + msp->id); + pr_debug("I2S device-name :%s\n", i2s_cont->name); + msp->i2s_cont = i2s_cont; + + return msp_i2s_drvdata; + +del_timer: + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); +free_irq: + iounmap(msp->registers); +free_msp: + kfree(msp); + return NULL; +} + +int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + device_unregister(&msp->i2s_cont->dev); + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); + iounmap(msp->registers); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); + regulator_put(drvdata->reg_vape); + kfree(msp); + + return status; +} + +int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + down(&msp->lock); + if (msp->users > 0) { + up(&msp->lock); + return -EBUSY; + } + up(&msp->lock); + + return 0; +} + +int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata) +{ + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + return 0; +} + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h new file mode 100644 index 00000000000..db88d0ca5de --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * for ST-Ericsson. + * + * License terms: + * + * 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. + */ + + +#ifndef UX500_MSP_I2S_H +#define UX500_MSP_I2S_H + +#include <linux/platform_device.h> +#include <mach/msp.h> + +struct ux500_msp_i2s_drvdata { + int id; + struct msp *msp; + struct regulator *reg_vape; +}; + +struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev, + struct msp_i2s_platform_data *platform_data); +int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata); +int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config); +int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag); +int ux500_msp_i2s_transfer(struct ux500_msp_i2s_drvdata *drvdata, struct i2s_message *message); +int ux500_msp_i2s_hw_status(struct ux500_msp_i2s_drvdata *drvdata); +dma_addr_t ux500_msp_i2s_get_pointer(struct ux500_msp_i2s_drvdata *drvdata, + enum i2s_direction_t i2s_direction); + +int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata); +int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata); + +#endif + diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c index 0161ffdf3f4..29b3f5e0ffb 100644 --- a/sound/soc/ux500/ux500_pcm.c +++ b/sound/soc/ux500/ux500_pcm.c @@ -272,7 +272,7 @@ static int ux500_pcm_trigger(struct snd_pcm_substream *substream, int cmd) private->msp_id, stream_id); if (ret) { - pr_err("%s: Failed to configure sg-list!\n", __func__); + pr_err("%s: Failed to configure I2S!\n", __func__); return -EINVAL; } break; |