diff options
author | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2015-04-21 10:23:02 +0100 |
---|---|---|
committer | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2015-04-21 10:23:02 +0100 |
commit | ec3b379a5ce34dfcd03f2487c2780f61c2715115 (patch) | |
tree | d104d47da35ffd0cbd159514524981f03d6971f3 | |
parent | 93a87e4c1d7349b3f750cb6e38baa2ffdd684990 (diff) | |
parent | b99ace5e232b6943fb07a0842ff8a9868f784db1 (diff) |
Merge branch 'tracking-qcomlt-audio' into integration-linux-qcomlt
* tracking-qcomlt-audio:
WIP: snd: qcom: Add AP8016 lpaif-cpu/platform dai support
WIP: snd: qcom: make register offsets per SOC.
WIP: snd: qcom: Add clks required for APQ8016.
snd: qcom: Add APQ8016 machine driver.
ASoC: Allow for building QCOM drivers
ASoC: qcom: Add ability to build QCOM drivers
ASoC: qcom: Add Storm machine driver
ASoC: qcom: Modify test for DSP in LPASS driver
ASoC: qcom: Add LPASS platform driver
ASoC: qcom: Add LPASS CPU DAI driver
ASoC: qcom: add LPASS header files
-rw-r--r-- | sound/soc/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/qcom/Kconfig | 36 | ||||
-rw-r--r-- | sound/soc/qcom/Makefile | 12 | ||||
-rw-r--r-- | sound/soc/qcom/apq8016.c | 320 | ||||
-rw-r--r-- | sound/soc/qcom/lpass-cpu.c | 672 | ||||
-rw-r--r-- | sound/soc/qcom/lpass-lpaif-reg.h | 187 | ||||
-rw-r--r-- | sound/soc/qcom/lpass-platform.c | 535 | ||||
-rw-r--r-- | sound/soc/qcom/lpass.h | 69 | ||||
-rw-r--r-- | sound/soc/qcom/storm.c | 162 |
10 files changed, 1995 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index dcc79aa0236b..3ba52da18bc6 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -47,6 +47,7 @@ source "sound/soc/kirkwood/Kconfig" source "sound/soc/intel/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/qcom/Kconfig" source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/sh/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 5b3c8f67c8db..974ba708b482 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ +obj-$(CONFIG_SND_SOC) += qcom/ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += sh/ diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig new file mode 100644 index 000000000000..8c1d343aff84 --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,36 @@ +config SND_SOC_QCOM + tristate "ASoC support for QCOM platforms" + help + Say Y or M if you want to add support to use audio devices + in Qualcomm Technologies SOC-based platforms. + +config SND_SOC_LPASS_CPU + tristate + depends on SND_SOC_QCOM + select REGMAP_MMIO + +config SND_SOC_LPASS_PLATFORM + tristate + depends on SND_SOC_QCOM + select REGMAP_MMIO + +config SND_SOC_STORM + tristate "ASoC I2S support for Storm boards" + depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + select SND_SOC_MAX98357A + help + Say Y or M if you want add support for SoC audio on the + Qualcomm Technologies IPQ806X-based Storm board. + +config SND_SOC_APQ8016 + tristate "SoC Audio support for APQ8016 based platforms" + depends on SND_SOC_QCOM || ARCH_QCOM || COMPILE_TEST + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + help + Support for Qualcomm Technologies LPASS audio block in + IPQ806X SOC-based systems. + Say Y if you want to use audio devices such as I2S, PCM, + S/PDIF, etc.
\ No newline at end of file diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 000000000000..3a2ca7cdb492 --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,12 @@ +# Platform +snd-soc-lpass-cpu-objs := lpass-cpu.o +snd-soc-lpass-platform-objs := lpass-platform.o + +obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o +obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o + +# Machine +snd-soc-storm-objs := storm.o + +obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o +obj-$(CONFIG_SND_SOC_APQ8016) += apq8016.o diff --git a/sound/soc/qcom/apq8016.c b/sound/soc/qcom/apq8016.c new file mode 100644 index 000000000000..057b64b64830 --- /dev/null +++ b/sound/soc/qcom/apq8016.c @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +struct msm8x16_data { + struct clk *mclk; + + /* pdm */ + struct pinctrl *pinctrl; + struct pinctrl_state *cdc_lines_sus; + struct pinctrl_state *cdc_lines_act; + + /* tlmm */ + struct pinctrl_state *tlmm_sus; + struct pinctrl_state *tlmm_act; + + void __iomem *mic_iomux; + void __iomem *spkr_iomux; +}; + +static int mi2s_rx_bit_format = SNDRV_PCM_FORMAT_S16_LE; + +static int qcom_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_card *card = soc_runtime->card; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int sysclk_freq; + int bitwidth, ret; + + snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), mi2s_rx_bit_format); + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(card->dev, "%s() invalid bit width given: %d\n", + __func__, bitwidth); + return bitwidth; + } + sysclk_freq = rate * bitwidth * 2; + ret = snd_soc_dai_set_sysclk(soc_runtime->cpu_dai, 0, sysclk_freq, 0); + if (ret) { + dev_err(card->dev, "%s() error setting sysclk to %u: %d\n", + __func__, sysclk_freq, ret); + return ret; + } + + return 0; +} + +#define MIC_CTRL_QUA_WS_SLAVE BIT(17) +#define MIC_CTRL_TLMM_SCLK_EN BIT(1) +#define SPKR_CTL_TLMM_MCLK_EN BIT(1) + +static int conf_ext_secondary_mux(struct msm8x16_data *pdata) +{ + /* Enable MCLK */ + writel(readl(pdata->spkr_iomux) | SPKR_CTL_TLMM_MCLK_EN, + pdata->spkr_iomux); + /* Configure the Quat MI2S to TLMM */ + writel(readl(pdata->mic_iomux) | 0x02020002, pdata->mic_iomux); + + return 0; +} + +static int msm_ext_mi2s_snd_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct msm8x16_data *pdata = snd_soc_card_get_drvdata(card); + int ret = 0; + + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + pr_info("%s: Secondary Mi2s does not support capture\n", + __func__); + return 0; + } + + ret = conf_ext_secondary_mux(pdata); + if (ret < 0) { + pr_err("%s: failed to conf internal codec mux\n", + __func__); + return ret; + } + + ret = pinctrl_select_state(pdata->pinctrl, pdata->tlmm_act); + if (ret < 0) { + pr_err("failed to enable codec gpios\n"); + return -EINVAL; + } + //FIXME set DAIFMT_CBS_CFS ??? + + return 0; +} +static struct snd_soc_ops qcom_internal_codec_soc_ops = { + .hw_params = qcom_ops_hw_params, +}; + +static struct snd_soc_ops qcom_external_codec_soc_ops = { + .startup = msm_ext_mi2s_snd_startup, + .hw_params = qcom_ops_hw_params, +}; + +static struct snd_soc_card qcom_soc_card = { + .name = "qcom-apq8016", + .dev = NULL, +}; + +struct qcom_card_data { + int var; + struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ +}; + +static int qcom_parse_of(struct snd_soc_card *card) +{ + int num_links; + struct device *dev = card->dev; + struct snd_soc_dai_link *dai_link; + struct device_node *np, *codec, *cpu, *node = dev->of_node; + struct msm8x16_data *data = snd_soc_card_get_drvdata(card); + struct qcom_card_data *priv; + struct pinctrl *pinctrl; + char *name; + int ret; + + data->mclk = of_clk_get_by_name(dev->of_node, "mclk"); + + //FIXME + if (IS_ERR(data->mclk)) { + dev_err(dev, "error getting mlck: %ld\n", PTR_ERR(data->mclk)); + } else { + clk_set_rate(data->mclk, 9600000); + clk_prepare_enable(data->mclk); + } + + /* Get pinctrl for both internal and external */ + pinctrl = devm_pinctrl_get(dev); + data->pinctrl = pinctrl; + + data->tlmm_sus = pinctrl_lookup_state(pinctrl, "ext_tlmm_lines_sus"); + if (IS_ERR(data->tlmm_sus)) { + dev_err(dev, "Unable to get pinctrl disable state handle\n"); + return -EINVAL; + } + data->tlmm_act = pinctrl_lookup_state(pinctrl, + "ext_tlmm_lines_act"); + if (IS_ERR(data->tlmm_act)) { + dev_err(dev, "Unable to get pinctrl disable state handle\n"); + return -EINVAL; + } + + /* Populate links */ + num_links = of_get_child_count(node); + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(*dai_link) * num_links, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card->dai_link = &priv->dai_link[0]; + card->num_links = num_links; + + dai_link = priv->dai_link; + + for_each_child_of_node(node, np) { + cpu = of_get_child_by_name(np, "cpu"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu || !codec) { + dev_err(dev, "%s: Can't find cpu/codec DT node\n", __func__); + return -EINVAL; + } + + dai_link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!dai_link->cpu_of_node) { + dev_err(card->dev, "%s() error getting cpu phandle\n", + __func__); + return -EINVAL; + } + + ret = snd_soc_of_get_dai_name(cpu, &dai_link->cpu_dai_name); + + dai_link->platform_of_node = dai_link->cpu_of_node; + + dai_link->codec_of_node = of_parse_phandle(codec, "sound-dai", 0); + if (!dai_link->codec_of_node) { + dev_err(card->dev, "%s() error getting codec phandle\n", + __func__); + return -EINVAL; + } + ret = snd_soc_of_get_dai_name(codec, &dai_link->codec_dai_name); + + /* DAI link name is created from CPU/CODEC dai name */ + name = devm_kzalloc(dev, + strlen(dai_link->cpu_dai_name) + + strlen(dai_link->codec_dai_name) + 2, + GFP_KERNEL); + sprintf(name, "%s-%s", dai_link->cpu_dai_name, + dai_link->codec_dai_name); + dai_link->name = dai_link->stream_name = name; + + dev_info(dev, "\tname : %s\n", dai_link->stream_name); + + if (of_property_read_bool(np, "external-primary-codec")) { + dai_link->ops = &qcom_external_codec_soc_ops; + } else if (of_property_read_bool(np, "external-secodary-codec")) { + dai_link->ops = &qcom_external_codec_soc_ops; + } else { + dai_link->ops = &qcom_internal_codec_soc_ops; + } + + dai_link++; + } + + return 0; +} + +static int qcom_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card = &qcom_soc_card; + struct msm8x16_data *data; + struct resource *res; + int ret; + + if (card->dev) { + dev_err(&pdev->dev, "%s() error, existing soundcard\n", + __func__); + return -ENODEV; + } + + card->dev = dev; + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); + data->mic_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->mic_iomux)) + return PTR_ERR(data->mic_iomux); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); + data->spkr_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->spkr_iomux)) + return PTR_ERR(data->spkr_iomux); + + platform_set_drvdata(pdev, data); + snd_soc_card_set_drvdata(card, data); + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "%s() error parsing card name: %d\n", + __func__, ret); + return ret; + } + + ret = qcom_parse_of(card); + if (ret) { + dev_err(&pdev->dev, "%s() error resolving dai links: %d\n", + __func__, ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret == -EPROBE_DEFER) { + card->dev = NULL; + return ret; + } else if (ret) { + dev_err(&pdev->dev, "%s() error registering soundcard: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static const struct of_device_id qcom_device_id[] = { + { .compatible = "qcom,msm8916-sndcard" }, + {}, +}; +MODULE_DEVICE_TABLE(of, qcom_device_id); + +static struct platform_driver qcom_platform_driver = { + .driver = { + .name = "qcom-apq8016", + .of_match_table = + of_match_ptr(qcom_device_id), + }, + .probe = qcom_platform_probe, +}; +module_platform_driver(qcom_platform_driver); + +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("APQ8016 Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c new file mode 100644 index 000000000000..eca0f1cb4da6 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu.c @@ -0,0 +1,672 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS + */ + +#include <linux/clk.h> +#include <linux/compiler.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + if (!drvdata->mi2s_osr_clk) + return 0; + + ret = clk_set_rate(drvdata->mi2s_osr_clk, freq); + if (ret) + dev_err(dai->dev, "%s() error setting mi2s osrclk to %u: %d\n", + __func__, freq, ret); + + return ret; +} + +static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + if (drvdata->mi2s_osr_clk) { + ret = clk_prepare_enable(drvdata->mi2s_osr_clk); + if (ret) { + dev_err(dai->dev, "%s() error in enabling mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + } + + if (drvdata->mi2s_bit_clk) { + ret = clk_prepare_enable(drvdata->mi2s_bit_clk); + if (ret) { + dev_err(dai->dev, "%s() error in enabling mi2s bit clk: %d\n", + __func__, ret); + if (drvdata->mi2s_osr_clk) + clk_disable_unprepare(drvdata->mi2s_osr_clk); + + return ret; + } + } + + return 0; +} + +static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->mi2s_bit_clk); + clk_disable_unprepare(drvdata->mi2s_osr_clk); +} + +static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + unsigned int regval; + int bitwidth, ret; + struct snd_soc_dai_driver *drv = dai->driver; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "%s() invalid bit width given: %d\n", + __func__, bitwidth); + return bitwidth; + } + + regval = LPAIF_I2SCTL_LOOPBACK_DISABLE | + LPAIF_I2SCTL_WSSRC_INTERNAL; + + switch (bitwidth) { + case 16: + regval |= LPAIF_I2SCTL_BITWIDTH_16; + break; + case 24: + regval |= LPAIF_I2SCTL_BITWIDTH_24; + break; + case 32: + regval |= LPAIF_I2SCTL_BITWIDTH_32; + break; + default: + dev_err(dai->dev, "%s() invalid bitwidth given: %d\n", + __func__, bitwidth); + return -EINVAL; + } + + switch (channels) { + case 1: + regval |= LPAIF_I2SCTL_SPKMODE_SD0; + regval |= LPAIF_I2SCTL_SPKMONO_MONO; + break; + case 2: + regval |= LPAIF_I2SCTL_SPKMODE_SD0; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 4: + regval |= LPAIF_I2SCTL_SPKMODE_QUAD01; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 6: + regval |= LPAIF_I2SCTL_SPKMODE_6CH; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 8: + regval |= LPAIF_I2SCTL_SPKMODE_8CH; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + default: + dev_err(dai->dev, "%s() invalid channels given: %u\n", + __func__, channels); + return -EINVAL; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->vdata, drv->id), regval); + if (ret) { + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + return ret; + } + + ret = clk_set_rate(drvdata->mi2s_bit_clk, rate * bitwidth * 2); + if (ret) { + dev_err(dai->dev, "%s() error setting mi2s bitclk to %u: %d\n", + __func__, rate * bitwidth * 2, ret); + return ret; + } + + return 0; +} + +static int lpass_cpu_daiops_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct snd_soc_dai_driver *drv = dai->driver; + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->vdata, drv->id), 0); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + + return ret; +} + +static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + struct snd_soc_dai_driver *drv = dai->driver; + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->vdata, drv->id), + LPAIF_I2SCTL_SPKEN_MASK, LPAIF_I2SCTL_SPKEN_ENABLE); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + + return ret; +} + +static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret = 0; + struct snd_soc_dai_driver *drv = dai->driver; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->vdata, drv->id), + LPAIF_I2SCTL_SPKEN_MASK, + LPAIF_I2SCTL_SPKEN_ENABLE); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->vdata, drv->id), + LPAIF_I2SCTL_SPKEN_MASK, + LPAIF_I2SCTL_SPKEN_DISABLE); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + break; + } + + return ret; +} + +static struct snd_soc_dai_ops lpass_cpu_dai_ops = { + .set_sysclk = lpass_cpu_daiops_set_sysclk, + .startup = lpass_cpu_daiops_startup, + .shutdown = lpass_cpu_daiops_shutdown, + .hw_params = lpass_cpu_daiops_hw_params, + .hw_free = lpass_cpu_daiops_hw_free, + .prepare = lpass_cpu_daiops_prepare, + .trigger = lpass_cpu_daiops_trigger, +}; + +static int lpass_cpu_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + struct snd_soc_dai_driver *drv = dai->driver; + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->vdata, drv->id), 0); + if (ret) + dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n", + __func__, ret); + + return ret; +} + +static struct snd_soc_dai_driver lpass_cpu_dai_driver = { + .playback = { + .stream_name = "lpass-cpu-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &lpass_cpu_dai_probe, + .ops = &lpass_cpu_dai_ops, +}; + + +static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = { + [APQ8016_LPAIF_MI2S_PRIMARY] = { + .id = APQ8016_LPAIF_MI2S_PRIMARY, + .name = "Primary MI2S", + .playback = { + .stream_name = "Primary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &lpass_cpu_dai_probe, + .ops = &lpass_cpu_dai_ops, + }, + [APQ8016_LPAIF_MI2S_SECONDARY] = { + .id = APQ8016_LPAIF_MI2S_SECONDARY, + .name = "Secondary MI2S", + .playback = { + .stream_name = "Secondary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &lpass_cpu_dai_probe, + .ops = &lpass_cpu_dai_ops, + }, + [APQ8016_LPAIF_MI2S_TERTIARY] = { + .id = APQ8016_LPAIF_MI2S_TERTIARY, + .name = "Tertiary MI2S", + .playback = { + .stream_name = "Tertiary Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &lpass_cpu_dai_probe, + .ops = &lpass_cpu_dai_ops, + }, + [APQ8016_LPAIF_MI2S_QUATERNARY] = { + .id = APQ8016_LPAIF_MI2S_QUATERNARY, + .name = "Quatenary MI2S", + .playback = { + .stream_name = "Quatenary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + }, + .probe = &lpass_cpu_dai_probe, + .ops = &lpass_cpu_dai_ops, + }, +}; + +static const struct snd_soc_component_driver lpass_cpu_comp_driver = { + .name = "lpass-cpu", +}; + +static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg) +{ + int i; + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant_data *v = drvdata->vdata; + + for (i = 0; i < v->i2s_ports; ++i) + if (reg == LPAIF_I2SCTL_REG(v, i)) + return true; + + for (i = 0; i < v->irq_ports; ++i) { + if (reg == LPAIF_IRQEN_REG(v, i)) + return true; + if (reg == LPAIF_IRQCLEAR_REG(v, i)) + return true; + } + + for (i = 0; i < v->rdma_channels; ++i) { + if (reg == LPAIF_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_RDMAPER_REG(v, i)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg) +{ + int i; + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant_data *v = drvdata->vdata; + + for (i = 0; i < v->i2s_ports; ++i) + if (reg == LPAIF_I2SCTL_REG(v, i)) + return true; + + for (i = 0; i < v->irq_ports; ++i) { + if (reg == LPAIF_IRQEN_REG(v, i)) + return true; + if (reg == LPAIF_IRQSTAT_REG(v, i)) + return true; + } + + for (i = 0; i < v->rdma_channels; ++i) { + if (reg == LPAIF_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(v,i)) + return true; + if (reg == LPAIF_RDMACURR_REG(v, i)) + return true; + if (reg == LPAIF_RDMAPER_REG(v, i)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg) +{ + int i; + + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant_data *v = drvdata->vdata; + + for (i = 0; i < v->irq_ports; ++i) + if (reg == LPAIF_IRQSTAT_REG(v, i)) + return true; + + for (i = 0; i < v->rdma_channels; ++i) + if (reg == LPAIF_RDMACURR_REG(v, i)) + return true; + + return false; +} + +static const struct regmap_config lpass_cpu_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +// .max_register = LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MAX), + .writeable_reg = lpass_cpu_regmap_writeable, + .readable_reg = lpass_cpu_regmap_readable, + .volatile_reg = lpass_cpu_regmap_volatile, + .cache_type = REGCACHE_FLAT, +}; + + +struct lpass_variant_data apq8016_data = { + .i2sctrl_reg_base = 0x1000, + .i2sctrl_reg_stride = 0x1000, + .i2s_ports = 4, + .irq_reg_base = 0x6000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x8400, + .rdma_reg_stride = 0x1000, + .rdma_channels = 2, +}; + +struct lpass_variant_data ipq8064_data = { + .i2sctrl_reg_base = 0x0010, + .i2sctrl_reg_stride = 0x04, + .i2s_ports = 5, + .irq_reg_base = 0x3000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x6000, + .rdma_reg_stride = 0x1000, + .rdma_channels = 4, +}; + +#ifdef CONFIG_OF +static const struct of_device_id lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu", .data = &ipq8064_data }, + { .compatible = "qcom,lpass-cpu-apq8016", .data = &apq8016_data }, + {} +}; +MODULE_DEVICE_TABLE(of, lpass_cpu_device_id); +#endif + +static int lpass_cpu_platform_probe(struct platform_device *pdev) +{ + struct lpass_data *drvdata; + struct device_node *dsp_of_node; + struct resource *res; + int ret; + + dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0); + if (dsp_of_node) { + dev_err(&pdev->dev, "%s() DSP exists and holds audio resources\n", + __func__); + return -EBUSY; + } + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->vdata = (struct lpass_variant_data *)of_match_node( + lpass_cpu_device_id, pdev->dev.of_node)->data; + if (!drvdata->vdata) { + dev_err(&pdev->dev, "%s() no variant data\n", __func__); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif"); + if (!res) { + dev_err(&pdev->dev, "%s() error getting resource\n", __func__); + return -ENODEV; + } + + drvdata->lpaif = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR((void const __force *)drvdata->lpaif)) { + dev_err(&pdev->dev, "%s() error mapping reg resource: %ld\n", + __func__, + PTR_ERR((void const __force *)drvdata->lpaif)); + return PTR_ERR((void const __force *)drvdata->lpaif); + } + + drvdata->lpaif_map = devm_regmap_init_mmio(&pdev->dev, drvdata->lpaif, + &lpass_cpu_regmap_config); + //FIXME set max register + if (IS_ERR(drvdata->lpaif_map)) { + dev_err(&pdev->dev, "%s() error initializing regmap: %ld\n", + __func__, PTR_ERR(drvdata->lpaif_map)); + return PTR_ERR(drvdata->lpaif_map); + } + +#if 0 +//FIXME + drvdata->mi2s_osr_clk = devm_clk_get(&pdev->dev, "mi2s-osr-clk"); + if (IS_ERR(drvdata->mi2s_osr_clk)) { + dev_err(&pdev->dev, "%s() error getting mi2s-osr-clk: %ld\n", + __func__, PTR_ERR(drvdata->mi2s_osr_clk)); + return PTR_ERR(drvdata->mi2s_osr_clk); + } +#endif + + drvdata->mi2s_bit_clk = devm_clk_get(&pdev->dev, "mi2s-bit-clk"); + if (IS_ERR(drvdata->mi2s_bit_clk)) { + dev_err(&pdev->dev, "%s() error getting mi2s-bit-clk: %ld\n", + __func__, PTR_ERR(drvdata->mi2s_bit_clk)); + return PTR_ERR(drvdata->mi2s_bit_clk); + } + + drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix-clk"); + if (IS_ERR(drvdata->ahbix_clk)) { + dev_err(&pdev->dev, "%s() error getting ahbix-clk: %ld\n", + __func__, PTR_ERR(drvdata->ahbix_clk)); + return PTR_ERR(drvdata->ahbix_clk); + } + + ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY); + if (ret) { + dev_err(&pdev->dev, "%s() error setting rate on ahbix_clk: %d\n", + __func__, ret); + return ret; + } + dev_dbg(&pdev->dev, "%s() set ahbix_clk rate to %lu\n", __func__, + clk_get_rate(drvdata->ahbix_clk)); + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(&pdev->dev, "%s() error enabling ahbix_clk: %d\n", + __func__, ret); + return ret; + } + drvdata->pcnoc_mport_clk = of_clk_get_by_name(pdev->dev.of_node, "pcnoc-mport-clk"); + if (IS_ERR(drvdata->pcnoc_mport_clk)) { + dev_err(&pdev->dev, "%s() error getting pcnoc-mport-clk: %ld\n", + __func__, PTR_ERR(drvdata->pcnoc_mport_clk)); + return PTR_ERR(drvdata->pcnoc_mport_clk); + } + + ret= clk_prepare_enable(drvdata->pcnoc_mport_clk); + if (ret) { + dev_err(&pdev->dev, "%s() Error enabling ahbix_clk: %d\n", + __func__, ret); + return ret; + } + + drvdata->pcnoc_sway_clk = of_clk_get_by_name(pdev->dev.of_node, "pcnoc-sway-clk"); + if (IS_ERR(drvdata->pcnoc_sway_clk)) { + dev_err(&pdev->dev, "%s() error getting pcnoc-sway-clk: %ld\n", + __func__, PTR_ERR(drvdata->pcnoc_sway_clk)); + return PTR_ERR(drvdata->pcnoc_sway_clk); + } + + ret = clk_prepare_enable(drvdata->pcnoc_sway_clk); + if (ret) { + dev_err(&pdev->dev, "%s() Error enabling ahbix_clk: %d\n", + __func__, ret); + return ret; + } + + if (of_device_is_compatible(pdev->dev.of_node, "qcom,lpass-cpu-apq8016")) + ret = devm_snd_soc_register_component(&pdev->dev, + &lpass_cpu_comp_driver, apq8016_lpass_cpu_dai_driver, + ARRAY_SIZE(apq8016_lpass_cpu_dai_driver)); + else + ret = devm_snd_soc_register_component(&pdev->dev, + &lpass_cpu_comp_driver, &lpass_cpu_dai_driver, 1); + if (ret) { + dev_err(&pdev->dev, "%s() error registering cpu driver: %d\n", + __func__, ret); + goto err_clk; + } + + ret = asoc_qcom_lpass_platform_register(pdev); + if (ret) { + dev_err(&pdev->dev, "%s() error registering platform driver: %d\n", + __func__, ret); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable_unprepare(drvdata->ahbix_clk); + return ret; +} + +static int lpass_cpu_platform_remove(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->ahbix_clk); + + return 0; +} + +static struct platform_driver lpass_cpu_platform_driver = { + .driver = { + .name = "lpass-cpu", + .of_match_table = of_match_ptr(lpass_cpu_device_id), + }, + .probe = lpass_cpu_platform_probe, + .remove = lpass_cpu_platform_remove, +}; +module_platform_driver(lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("QTi LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h new file mode 100644 index 000000000000..128106a3b3aa --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif-reg.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LPASS_LPAIF_REG_H__ +#define __LPASS_LPAIF_REG_H__ + + +/* LPAIF I2S */ +#define LPAIF_I2SCTL_REG_ADDR(data, addr, port) \ + ((data)->i2sctrl_reg_base + (addr) + ((data)->i2sctrl_reg_stride * (port))) + +#define LPAIF_I2SCTL_REG(v, port) LPAIF_I2SCTL_REG_ADDR(v, 0x0, (port)) + +/* IPQ8064 specific */ +enum lpaif_i2s_ports { + LPAIF_I2S_PORT_MIN = 0, + + LPAIF_I2S_PORT_CODEC_SPK = 0, + LPAIF_I2S_PORT_CODEC_MIC = 1, + LPAIF_I2S_PORT_SEC_SPK = 2, + LPAIF_I2S_PORT_SEC_MIC = 3, + LPAIF_I2S_PORT_MI2S = 4, + + LPAIF_I2S_PORT_MAX = 4, + LPAIF_I2S_PORT_NUM = 5, +}; + +/* APQ8016 specifics */ +enum apq8016_lpaif_i2s_ports { + APQ8016_LPAIF_MI2S_PRIMARY = 0, + APQ8016_LPAIF_MI2S_SECONDARY = 1, + APQ8016_LPAIF_MI2S_TERTIARY = 2, + APQ8016_LPAIF_MI2S_QUATERNARY = 3, +}; + +#define LPAIF_I2SCTL_LOOPBACK_MASK 0x8000 +#define LPAIF_I2SCTL_LOOPBACK_SHIFT 15 +#define LPAIF_I2SCTL_LOOPBACK_DISABLE (0 << LPAIF_I2SCTL_LOOPBACK_SHIFT) +#define LPAIF_I2SCTL_LOOPBACK_ENABLE (1 << LPAIF_I2SCTL_LOOPBACK_SHIFT) + +#define LPAIF_I2SCTL_SPKEN_MASK 0x4000 +#define LPAIF_I2SCTL_SPKEN_SHIFT 14 +#define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT) +#define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT) + +#define LPAIF_I2SCTL_SPKMODE_MASK 0x3C00 +#define LPAIF_I2SCTL_SPKMODE_SHIFT 10 +#define LPAIF_I2SCTL_SPKMODE_NONE (0 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD0 (1 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD1 (2 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD2 (3 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD3 (4 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_QUAD01 (5 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_QUAD23 (6 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_6CH (7 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_8CH (8 << LPAIF_I2SCTL_SPKMODE_SHIFT) + +#define LPAIF_I2SCTL_SPKMONO_MASK 0x0200 +#define LPAIF_I2SCTL_SPKMONO_SHIFT 9 +#define LPAIF_I2SCTL_SPKMONO_STEREO (0 << LPAIF_I2SCTL_SPKMONO_SHIFT) +#define LPAIF_I2SCTL_SPKMONO_MONO (1 << LPAIF_I2SCTL_SPKMONO_SHIFT) + +#define LPAIF_I2SCTL_WSSRC_MASK 0x0004 +#define LPAIF_I2SCTL_WSSRC_SHIFT 2 +#define LPAIF_I2SCTL_WSSRC_INTERNAL (0 << LPAIF_I2SCTL_WSSRC_SHIFT) +#define LPAIF_I2SCTL_WSSRC_EXTERNAL (1 << LPAIF_I2SCTL_WSSRC_SHIFT) + +#define LPAIF_I2SCTL_BITWIDTH_MASK 0x0003 +#define LPAIF_I2SCTL_BITWIDTH_SHIFT 0 +#define LPAIF_I2SCTL_BITWIDTH_16 (0 << LPAIF_I2SCTL_BITWIDTH_SHIFT) +#define LPAIF_I2SCTL_BITWIDTH_24 (1 << LPAIF_I2SCTL_BITWIDTH_SHIFT) +#define LPAIF_I2SCTL_BITWIDTH_32 (2 << LPAIF_I2SCTL_BITWIDTH_SHIFT) + +#define LPAIF_IRQ_REG_ADDR(v, addr, port) (v->irq_reg_base + (addr) + (v->irq_reg_stride * (port))) + +enum lpaif_irq_ports { + LPAIF_IRQ_PORT_MIN = 0, + + LPAIF_IRQ_PORT_HOST = 0, + LPAIF_IRQ_PORT_ADSP = 1, + + LPAIF_IRQ_PORT_MAX = 2, + LPAIF_IRQ_PORT_NUM = 3, +}; + +#define LPAIF_IRQEN_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x0, (port)) +#define LPAIF_IRQSTAT_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x4, (port)) +#define LPAIF_IRQCLEAR_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0xC, (port)) + +#define LPAIF_IRQ_BITSTRIDE 3 + +#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan))) + +#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan))) + +/* LPAIF DMA */ + +#define LPAIF_RDMA_REG_ADDR(v, addr, chan) \ + (v->rdma_reg_base + (addr) + (v->rdma_reg_stride * (chan))) + +enum lpaif_dma_channels { + LPAIF_RDMA_CHAN_MIN = 0, + + LPAIF_RDMA_CHAN_MI2S = 0, + LPAIF_RDMA_CHAN_PCM0 = 1, + LPAIF_RDMA_CHAN_PCM1 = 2, + + LPAIF_RDMA_CHAN_MAX = 4, + LPAIF_RDMA_CHAN_NUM = 5, +}; + +enum apq8061_lpaif_dma_channels { + APQ8016_LPAIF_RDMA_CHAN0 = 0, + APQ8016_LPAIF_RDMA_CHAN1 = 1, +}; + +#define APQ8016_LPAIF_RDMACTL_AUDINTF_PRI_MI2S (1 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_SEC_MI2S (2 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_TER_MI2S (3 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_QUA_MI2S (4 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_SPKR_MI2S (5 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_HDMI (6 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_PCM (7 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define APQ8016_LPAIF_RDMACTL_AUDINTF_SEC_PCM (8 << LPAIF_RDMACTL_AUDINTF_SHIFT) + +#define LPAIF_RDMACTL_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x00, (chan)) +#define LPAIF_RDMABASE_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x04, (chan)) +#define LPAIF_RDMABUFF_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x08, (chan)) +#define LPAIF_RDMACURR_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x0C, (chan)) +#define LPAIF_RDMAPER_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x10, (chan)) +#define LPAIF_RDMAPERCNT_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x14, (chan)) + +#define LPAIF_RDMACTL_BURSTEN_MASK 0x800 +#define LPAIF_RDMACTL_BURSTEN_SHIFT 11 +#define LPAIF_RDMACTL_BURSTEN_SINGLE (0 << LPAIF_RDMACTL_BURSTEN_SHIFT) +#define LPAIF_RDMACTL_BURSTEN_INCR4 (1 << LPAIF_RDMACTL_BURSTEN_SHIFT) + +#define LPAIF_RDMACTL_WPSCNT_MASK 0x700 +#define LPAIF_RDMACTL_WPSCNT_SHIFT 8 +#define LPAIF_RDMACTL_WPSCNT_ONE (0 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_TWO (1 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_THREE (2 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_FOUR (3 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_SIX (5 << LPAIF_RDMACTL_WPSCNT_SHIFT) +#define LPAIF_RDMACTL_WPSCNT_EIGHT (7 << LPAIF_RDMACTL_WPSCNT_SHIFT) + +#define LPAIF_RDMACTL_AUDINTF_MASK 0x0F0 +#define LPAIF_RDMACTL_AUDINTF_SHIFT 4 +#define LPAIF_RDMACTL_AUDINTF_NONE (0 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_PRI_MI2S (1 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SEC_MI2S (2 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_TER_MI2S (3 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_QUA_MI2S (4 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SPKR_MI2S (5 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_HDMI (6 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_PCM (7 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SEC_PCM (8 << LPAIF_RDMACTL_AUDINTF_SHIFT) + +#define LPAIF_RDMACTL_FIFOWM_MASK 0x00E +#define LPAIF_RDMACTL_FIFOWM_SHIFT 1 +#define LPAIF_RDMACTL_FIFOWM_1 (0 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_2 (1 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_3 (2 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_4 (3 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_5 (4 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_6 (5 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_7 (6 << LPAIF_RDMACTL_FIFOWM_SHIFT) +#define LPAIF_RDMACTL_FIFOWM_8 (7 << LPAIF_RDMACTL_FIFOWM_SHIFT) + +#define LPAIF_RDMACTL_ENABLE_MASK 0x1 +#define LPAIF_RDMACTL_ENABLE_SHIFT 0 +#define LPAIF_RDMACTL_ENABLE_OFF (0 << LPAIF_RDMACTL_ENABLE_SHIFT) +#define LPAIF_RDMACTL_ENABLE_ON (1 << LPAIF_RDMACTL_ENABLE_SHIFT) + +#endif /* __LPASS_LPAIF_REG_H__ */ diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c new file mode 100644 index 000000000000..29e94185566b --- /dev/null +++ b/sound/soc/qcom/lpass-platform.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass-platform.c -- ALSA SoC platform driver for QTi LPASS + */ + +#include <linux/compiler.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <sound/memalloc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +#define LPASS_PLATFORM_BUFFER_SIZE (16 * 1024) +#define LPASS_PLATFORM_PERIODS 2 + +static struct snd_pcm_hardware lpass_platform_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = LPASS_PLATFORM_BUFFER_SIZE, + .period_bytes_max = LPASS_PLATFORM_BUFFER_SIZE / + LPASS_PLATFORM_PERIODS, + .period_bytes_min = LPASS_PLATFORM_BUFFER_SIZE / + LPASS_PLATFORM_PERIODS, + .periods_min = LPASS_PLATFORM_PERIODS, + .periods_max = LPASS_PLATFORM_PERIODS, + .fifo_size = 0, +}; + +static int lpass_platform_pcmops_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + int ret; + + snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware); + + runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(soc_runtime->dev, "%s() setting constraints failed: %d\n", + __func__, ret); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int lpass_platform_pcmops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int regval; + int bitwidth; + int ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(soc_runtime->dev, "%s() invalid bit width given: %d\n", + __func__, bitwidth); + return bitwidth; + } + +//FIXME + regval = LPAIF_RDMACTL_BURSTEN_INCR4 | +// LPAIF_RDMACTL_AUDINTF_MI2S | + APQ8016_LPAIF_RDMACTL_AUDINTF_QUA_MI2S | + LPAIF_RDMACTL_FIFOWM_8; + + switch (bitwidth) { + case 16: + switch (channels) { + case 1: + case 2: + regval |= LPAIF_RDMACTL_WPSCNT_ONE; + break; + case 4: + regval |= LPAIF_RDMACTL_WPSCNT_TWO; + break; + case 6: + regval |= LPAIF_RDMACTL_WPSCNT_THREE; + break; + case 8: + regval |= LPAIF_RDMACTL_WPSCNT_FOUR; + break; + default: + dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + break; + case 24: + case 32: + switch (channels) { + case 1: + regval |= LPAIF_RDMACTL_WPSCNT_ONE; + break; + case 2: + regval |= LPAIF_RDMACTL_WPSCNT_TWO; + break; + case 4: + regval |= LPAIF_RDMACTL_WPSCNT_FOUR; + break; + case 6: + regval |= LPAIF_RDMACTL_WPSCNT_SIX; + break; + case 8: + regval |= LPAIF_RDMACTL_WPSCNT_EIGHT; + break; + default: + dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + break; + default: + dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n", + __func__, bitwidth, channels); + return -EINVAL; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(v, APQ8016_LPAIF_RDMA_CHAN0), regval); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(v, APQ8016_LPAIF_RDMA_CHAN0), 0); + if (ret) + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + + return ret; +} + +static int lpass_platform_pcmops_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMABASE_REG(v, APQ8016_LPAIF_RDMA_CHAN0), + runtime->dma_addr); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmabase reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMABUFF_REG(v, APQ8016_LPAIF_RDMA_CHAN0), + (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmabuff reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMAPER_REG(v, APQ8016_LPAIF_RDMA_CHAN0), + (snd_pcm_lib_period_bytes(substream) >> 2) - 1); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmaper reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(v, APQ8016_LPAIF_RDMA_CHAN0), + LPAIF_RDMACTL_ENABLE_MASK, LPAIF_RDMACTL_ENABLE_ON); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* clear status before enabling interrupts */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(APQ8016_LPAIF_RDMA_CHAN0)); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(APQ8016_LPAIF_RDMA_CHAN0), + LPAIF_IRQ_ALL(APQ8016_LPAIF_RDMA_CHAN0)); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(v, APQ8016_LPAIF_RDMA_CHAN0), + LPAIF_RDMACTL_ENABLE_MASK, + LPAIF_RDMACTL_ENABLE_ON); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(v, APQ8016_LPAIF_RDMA_CHAN0), + LPAIF_RDMACTL_ENABLE_MASK, + LPAIF_RDMACTL_ENABLE_OFF); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(APQ8016_LPAIF_RDMA_CHAN0), 0); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n", + __func__, ret); + return ret; + } + break; + } + + return 0; +} + +static snd_pcm_uframes_t lpass_platform_pcmops_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + unsigned int base_addr, curr_addr; + int ret; + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_RDMABASE_REG(v, APQ8016_LPAIF_RDMA_CHAN0), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, "%s() error reading from rdmabase reg: %d\n", + __func__, ret); + return ret; + } + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_RDMACURR_REG(v, APQ8016_LPAIF_RDMA_CHAN0), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, "%s() error reading from rdmacurr reg: %d\n", + __func__, ret); + return ret; + } + + return bytes_to_frames(substream->runtime, curr_addr - base_addr); +} + +static int lpass_platform_pcmops_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops lpass_platform_pcm_ops = { + .open = lpass_platform_pcmops_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = lpass_platform_pcmops_hw_params, + .hw_free = lpass_platform_pcmops_hw_free, + .prepare = lpass_platform_pcmops_prepare, + .trigger = lpass_platform_pcmops_trigger, + .pointer = lpass_platform_pcmops_pointer, + .mmap = lpass_platform_pcmops_mmap, +}; + +static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) +{ + struct snd_pcm_substream *substream = data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + unsigned int interrupts; + irqreturn_t ret = IRQ_NONE; + int rv; + + rv = regmap_read(drvdata->lpaif_map, + LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &interrupts); + if (rv) { + dev_err(soc_runtime->dev, "%s() error reading from irqstat reg: %d\n", + __func__, rv); + return IRQ_NONE; + } + interrupts &= LPAIF_IRQ_ALL(APQ8016_LPAIF_RDMA_CHAN0); + + if (interrupts & LPAIF_IRQ_PER(APQ8016_LPAIF_RDMA_CHAN0)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_PER(APQ8016_LPAIF_RDMA_CHAN0)); + if (rv) { + dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n", + __func__, rv); + return IRQ_NONE; + } + snd_pcm_period_elapsed(substream); + ret = IRQ_HANDLED; + } + + if (interrupts & LPAIF_IRQ_XRUN(APQ8016_LPAIF_RDMA_CHAN0)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_XRUN(APQ8016_LPAIF_RDMA_CHAN0)); + if (rv) { + dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n", + __func__, rv); + return IRQ_NONE; + } + dev_warn(soc_runtime->dev, "%s() xrun warning\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + ret = IRQ_HANDLED; + } + + if (interrupts & LPAIF_IRQ_ERR(APQ8016_LPAIF_RDMA_CHAN0)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ERR(APQ8016_LPAIF_RDMA_CHAN0)); + if (rv) { + dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n", + __func__, rv); + return IRQ_NONE; + } + dev_err(soc_runtime->dev, "%s() bus access error\n", __func__); + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int lpass_platform_alloc_buffer(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = lpass_platform_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = soc_runtime->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(soc_runtime->dev, size, &buf->addr, + GFP_KERNEL); + if (!buf->area) { + dev_err(soc_runtime->dev, "%s: Could not allocate DMA buffer\n", + __func__); + return -ENOMEM; + } + buf->bytes = size; + + return 0; +} + +static void lpass_platform_free_buffer(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + + if (buf->area) { + dma_free_coherent(soc_runtime->dev, buf->bytes, buf->area, + buf->addr); + } + buf->area = NULL; +} + +static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_pcm *pcm = soc_runtime->pcm; + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct lpass_data *drvdata = + snd_soc_platform_get_drvdata(soc_runtime->platform); + struct lpass_variant_data *v = drvdata->vdata; + int ret; + + soc_runtime->dev->coherent_dma_mask = DMA_BIT_MASK(32); + soc_runtime->dev->dma_mask = &soc_runtime->dev->coherent_dma_mask; + + ret = lpass_platform_alloc_buffer(substream, soc_runtime); + if (ret) + return ret; + + ret = devm_request_irq(soc_runtime->dev, drvdata->lpaif_irq, + lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING, + "lpass-irq-lpaif", substream); + if (ret) { + dev_err(soc_runtime->dev, "%s() irq request failed: %d\n", + __func__, ret); + goto err_buf; + } + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n", + __func__, ret); + return ret; + } + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(v, APQ8016_LPAIF_RDMA_CHAN0), 0); + if (ret) { + dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n", + __func__, ret); + return ret; + } + + return 0; + +err_buf: + lpass_platform_free_buffer(substream, soc_runtime); + return ret; +} + +static void lpass_platform_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + + lpass_platform_free_buffer(substream, soc_runtime); +} + +static struct snd_soc_platform_driver lpass_platform_driver = { + .pcm_new = lpass_platform_pcm_new, + .pcm_free = lpass_platform_pcm_free, + .ops = &lpass_platform_pcm_ops, +}; + +int asoc_qcom_lpass_platform_register(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif"); + if (drvdata->lpaif_irq < 0) { + dev_err(&pdev->dev, "%s() error getting irq handle: %d\n", + __func__, drvdata->lpaif_irq); + return -ENODEV; + } + + return devm_snd_soc_register_platform(&pdev->dev, + &lpass_platform_driver); +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_platform_register); + +MODULE_DESCRIPTION("QTi LPASS Platform Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h new file mode 100644 index 000000000000..9e6aa1b35d28 --- /dev/null +++ b/sound/soc/qcom/lpass.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass.h - Definitions for the QTi LPASS + */ + +#ifndef __LPASS_H__ +#define __LPASS_H__ + +#include <linux/clk.h> +#include <linux/compiler.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 + +/* Vairant data per each SOC */ +struct lpass_variant_data { + u32 i2sctrl_reg_base; + u32 i2sctrl_reg_stride; + u32 i2s_ports; + u32 irq_reg_base; + u32 irq_reg_stride; + u32 irq_ports; + u32 rdma_reg_base; + u32 rdma_reg_stride; + u32 rdma_channels; + +}; + +/* Both the CPU DAI and platform drivers will access this data */ +struct lpass_data { + + /* AHB-I/X bus clocks inside the low-power audio subsystem (LPASS) */ + struct clk *ahbix_clk; + struct clk *pcnoc_mport_clk; + struct clk *pcnoc_sway_clk; + + /* MI2S system clock */ + struct clk *mi2s_osr_clk; + + /* MI2S bit clock (derived from system clock by a divider */ + struct clk *mi2s_bit_clk; + + /* low-power audio interface (LPAIF) registers */ + void __iomem *lpaif; + + /* regmap backed by the low-power audio interface (LPAIF) registers */ + struct regmap *lpaif_map; + + /* interrupts from the low-power audio interface (LPAIF) */ + int lpaif_irq; + + struct lpass_variant_data *vdata; +}; + +/* register the platform driver from the CPU DAI driver */ +int asoc_qcom_lpass_platform_register(struct platform_device *); + +#endif /* __LPASS_H__ */ diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c new file mode 100644 index 000000000000..b8bd296190ad --- /dev/null +++ b/sound/soc/qcom/storm.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#define STORM_SYSCLK_MULT 4 + +static int storm_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_card *card = soc_runtime->card; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int sysclk_freq; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(card->dev, "%s() invalid bit width given: %d\n", + __func__, bitwidth); + return bitwidth; + } + + /* + * as the CPU DAI is the I2S bus master and no system clock is needed by + * the MAX98357a DAC, simply set the system clock to be a constant + * multiple of the bit clock for the clock divider + */ + sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT; + + ret = snd_soc_dai_set_sysclk(soc_runtime->cpu_dai, 0, sysclk_freq, 0); + if (ret) { + dev_err(card->dev, "%s() error setting sysclk to %u: %d\n", + __func__, sysclk_freq, ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops storm_soc_ops = { + .hw_params = storm_ops_hw_params, +}; + +static struct snd_soc_dai_link storm_dai_link = { + .name = "Primary", + .stream_name = "Primary", + .codec_dai_name = "HiFi", + .ops = &storm_soc_ops, +}; + +static struct snd_soc_card storm_soc_card = { + .name = "ipq806x-storm", + .dev = NULL, +}; + +static int storm_parse_of(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link = card->dai_link; + struct device_node *np = card->dev->of_node; + + dai_link->cpu_of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpu_of_node) { + dev_err(card->dev, "%s() error getting cpu phandle\n", + __func__); + return -EINVAL; + } + dai_link->platform_of_node = dai_link->cpu_of_node; + + dai_link->codec_of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codec_of_node) { + dev_err(card->dev, "%s() error getting codec phandle\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int storm_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &storm_soc_card; + int ret; + + if (card->dev) { + dev_err(&pdev->dev, "%s() error, existing soundcard\n", + __func__); + return -ENODEV; + } + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "%s() error parsing card name: %d\n", + __func__, ret); + return ret; + } + + card->dai_link = &storm_dai_link; + card->num_links = 1; + + ret = storm_parse_of(card); + if (ret) { + dev_err(&pdev->dev, "%s() error resolving dai links: %d\n", + __func__, ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret == -EPROBE_DEFER) { + card->dev = NULL; + return ret; + } else if (ret) { + dev_err(&pdev->dev, "%s() error registering soundcard: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id storm_device_id[] = { + { .compatible = "google,storm-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, storm_device_id); +#endif + +static struct platform_driver storm_platform_driver = { + .driver = { + .name = "storm-audio", + .of_match_table = + of_match_ptr(storm_device_id), + }, + .probe = storm_platform_probe, +}; +module_platform_driver(storm_platform_driver); + +MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver"); +MODULE_LICENSE("GPL v2"); |