From 32c006a99799f6f3c759b77ba761c3a5981e34eb Mon Sep 17 00:00:00 2001 From: Kevin Hao Date: Thu, 12 Feb 2015 14:52:49 +0800 Subject: gpio: mpc8xxx: remove __initdata annotation for mpc8xxx_gpio_ids[] Since commit 98686d9a52ee ("gpio: mpc8xxx: Convert to platform device interface"), we get the following section mismatch warning. Remove the __initdata annotation to fix it. WARNING: vmlinux.o(.data+0xbc28): Section mismatch in reference from the variable mpc8xxx_plat_driver to the variable .init.data:mpc8xxx_gpio_ids The variable mpc8xxx_plat_driver references the variable __initdata mpc8xxx_gpio_ids If the reference is valid then annotate the variable with __init* or __refdata (see linux/init.h) or name the variable: *_template, *_timer, *_sht, *_ops, *_probe, *_probe_one, *_console Signed-off-by: Kevin Hao Signed-off-by: Linus Walleij diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index a6952ba3..a65b751 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -334,7 +334,7 @@ static struct irq_domain_ops mpc8xxx_gpio_irq_ops = { .xlate = irq_domain_xlate_twocell, }; -static struct of_device_id mpc8xxx_gpio_ids[] __initdata = { +static struct of_device_id mpc8xxx_gpio_ids[] = { { .compatible = "fsl,mpc8349-gpio", }, { .compatible = "fsl,mpc8572-gpio", }, { .compatible = "fsl,mpc8610-gpio", }, -- cgit v0.10.2 From 6afda7f5075440f6737d953ab00fc82efb6cabae Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 5 Mar 2015 16:55:21 +0200 Subject: ASoC: davinci-mcasp: Allow complete shutdown of McASP when not in use Rearrange the pm_runtime_get/put_sync calls so the IP will be turned off when it is not in use. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 0c88299..33cea07 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -62,6 +62,7 @@ struct davinci_mcasp_context { u32 config_regs[ARRAY_SIZE(context_regs)]; u32 afifo_regs[2]; /* for read/write fifo control registers */ u32 *xrsr_regs; /* for serializer configuration */ + bool pm_state; }; struct davinci_mcasp { @@ -519,7 +520,7 @@ static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); } out: - pm_runtime_put_sync(mcasp->dev); + pm_runtime_put(mcasp->dev); return ret; } @@ -528,6 +529,7 @@ static int __davinci_mcasp_set_clkdiv(struct snd_soc_dai *dai, int div_id, { struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + pm_runtime_get_sync(mcasp->dev); switch (div_id) { case 0: /* MCLK divider */ mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, @@ -553,6 +555,7 @@ static int __davinci_mcasp_set_clkdiv(struct snd_soc_dai *dai, int div_id, return -EINVAL; } + pm_runtime_put(mcasp->dev); return 0; } @@ -567,6 +570,7 @@ static int davinci_mcasp_set_sysclk(struct snd_soc_dai *dai, int clk_id, { struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + pm_runtime_get_sync(mcasp->dev); if (dir == SND_SOC_CLOCK_OUT) { mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXE); mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); @@ -579,6 +583,7 @@ static int davinci_mcasp_set_sysclk(struct snd_soc_dai *dai, int clk_id, mcasp->sysclk_freq = freq; + pm_runtime_put(mcasp->dev); return 0; } @@ -1053,6 +1058,10 @@ static int davinci_mcasp_suspend(struct snd_soc_dai *dai) u32 reg; int i; + context->pm_state = pm_runtime_enabled(mcasp->dev) + if (!context->pm_state) + pm_runtime_get_sync(mcasp->dev); + for (i = 0; i < ARRAY_SIZE(context_regs); i++) context->config_regs[i] = mcasp_get_reg(mcasp, context_regs[i]); @@ -1069,6 +1078,8 @@ static int davinci_mcasp_suspend(struct snd_soc_dai *dai) context->xrsr_regs[i] = mcasp_get_reg(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i)); + pm_runtime_put_sync(mcasp->dev); + return 0; } @@ -1079,6 +1090,8 @@ static int davinci_mcasp_resume(struct snd_soc_dai *dai) u32 reg; int i; + pm_runtime_get_sync(mcasp->dev); + for (i = 0; i < ARRAY_SIZE(context_regs); i++) mcasp_set_reg(mcasp, context_regs[i], context->config_regs[i]); @@ -1095,6 +1108,9 @@ static int davinci_mcasp_resume(struct snd_soc_dai *dai) mcasp_set_reg(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), context->xrsr_regs[i]); + if (!context->pm_state) + pm_runtime_put_sync(mcasp->dev); + return 0; } #else @@ -1398,13 +1414,6 @@ static int davinci_mcasp_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); - if (IS_ERR_VALUE(ret)) { - dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n"); - pm_runtime_disable(&pdev->dev); - return ret; - } - mcasp->base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); if (!mcasp->base) { dev_err(&pdev->dev, "ioremap failed\n"); @@ -1584,14 +1593,12 @@ static int davinci_mcasp_probe(struct platform_device *pdev) return 0; err: - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return ret; } static int davinci_mcasp_remove(struct platform_device *pdev) { - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return 0; -- cgit v0.10.2 From 046db763aaaeb987ea01ea8c7e6d618e0ad1e6b8 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 5 Mar 2015 15:39:20 +0000 Subject: regulator: core: Add devres versions of notifier registration Add devm_regulator_register_notifier, this adds the resource against the device for the consumer supply we are registering the notifier for. There seem to be few use-cases where this wouldn't be the users intention and this ensures the notifiers will always be removed at the correct time. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/drivers/regulator/devres.c b/drivers/regulator/devres.c index 8f785bc..6ec1d40 100644 --- a/drivers/regulator/devres.c +++ b/drivers/regulator/devres.c @@ -413,3 +413,88 @@ void devm_regulator_bulk_unregister_supply_alias(struct device *dev, devm_regulator_unregister_supply_alias(dev, id[i]); } EXPORT_SYMBOL_GPL(devm_regulator_bulk_unregister_supply_alias); + +struct regulator_notifier_match { + struct regulator *regulator; + struct notifier_block *nb; +}; + +static int devm_regulator_match_notifier(struct device *dev, void *res, + void *data) +{ + struct regulator_notifier_match *match = res; + struct regulator_notifier_match *target = data; + + return match->regulator == target->regulator && match->nb == target->nb; +} + +static void devm_regulator_destroy_notifier(struct device *dev, void *res) +{ + struct regulator_notifier_match *match = res; + + regulator_unregister_notifier(match->regulator, match->nb); +} + +/** + * devm_regulator_register_notifier - Resource managed + * regulator_register_notifier + * + * @regulator: regulator source + * @nb: notifier block + * + * The notifier will be registers under the consumer device and be + * automatically be unregistered when the source device is unbound. + */ +int devm_regulator_register_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + struct regulator_notifier_match *match; + int ret; + + match = devres_alloc(devm_regulator_destroy_notifier, + sizeof(struct regulator_notifier_match), + GFP_KERNEL); + if (!match) + return -ENOMEM; + + match->regulator = regulator; + match->nb = nb; + + ret = regulator_register_notifier(regulator, nb); + if (ret < 0) { + devres_free(match); + return ret; + } + + devres_add(regulator->dev, match); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_regulator_register_notifier); + +/** + * devm_regulator_unregister_notifier - Resource managed + * regulator_unregister_notifier() + * + * @regulator: regulator source + * @nb: notifier block + * + * Unregister a notifier registered with devm_regulator_register_notifier(). + * Normally this function will not need to be called and the resource + * management code will ensure that the resource is freed. + */ +void devm_regulator_unregister_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + struct regulator_notifier_match match; + int rc; + + match.regulator = regulator; + match.nb = nb; + + rc = devres_release(regulator->dev, devm_regulator_destroy_notifier, + devm_regulator_match_notifier, &match); + if (rc != 0) + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_regulator_unregister_notifier); diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index d17e1ff..bd631ee 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -252,8 +252,12 @@ int regulator_list_hardware_vsel(struct regulator *regulator, /* regulator notifier block */ int regulator_register_notifier(struct regulator *regulator, struct notifier_block *nb); +int devm_regulator_register_notifier(struct regulator *regulator, + struct notifier_block *nb); int regulator_unregister_notifier(struct regulator *regulator, struct notifier_block *nb); +void devm_regulator_unregister_notifier(struct regulator *regulator, + struct notifier_block *nb); /* driver data - core doesn't touch */ void *regulator_get_drvdata(struct regulator *regulator); @@ -515,12 +519,24 @@ static inline int regulator_register_notifier(struct regulator *regulator, return 0; } +static inline int devm_regulator_register_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + return 0; +} + static inline int regulator_unregister_notifier(struct regulator *regulator, struct notifier_block *nb) { return 0; } +static inline int devm_regulator_unregister_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + return 0; +} + static inline void *regulator_get_drvdata(struct regulator *regulator) { return NULL; -- cgit v0.10.2 From 0be9653a02830637ed385d99bf898d456e8eae8f Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 5 Mar 2015 15:39:21 +0000 Subject: ASoC: wm8804: Use new devres regulator_register_notifier This is more idiomatic and also fixes an issue where the notifiers were being leaked if probe failed. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 1bd4ace..7804ddf 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -601,8 +601,10 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) /* This should really be moved into the regulator core */ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) { - ret = regulator_register_notifier(wm8804->supplies[i].consumer, - &wm8804->disable_nb[i]); + struct regulator *regulator = wm8804->supplies[i].consumer; + + ret = devm_regulator_register_notifier(regulator, + &wm8804->disable_nb[i]); if (ret != 0) { dev_err(dev, "Failed to register regulator notifier: %d\n", @@ -662,15 +664,6 @@ EXPORT_SYMBOL_GPL(wm8804_probe); void wm8804_remove(struct device *dev) { - struct wm8804_priv *wm8804; - int i; - - wm8804 = dev_get_drvdata(dev); - - for (i = 0; i < ARRAY_SIZE(wm8804->supplies); ++i) - regulator_unregister_notifier(wm8804->supplies[i].consumer, - &wm8804->disable_nb[i]); - snd_soc_unregister_codec(dev); } EXPORT_SYMBOL_GPL(wm8804_remove); -- cgit v0.10.2 From fcf638f9953eb7f3b97fad7e970ae59dcdbd70c1 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 5 Mar 2015 15:39:22 +0000 Subject: ASoC: wm8804: Fix small issues in probe error paths This patch fixes some small issues on the probe error paths. Firstly, fail probe if we can't register the regulator notifiers as this will cause the cache to never be synchronised which will result in odd behaviour if the regulators are controllable. Secondly, we don't need to call regulator_bulk_disable if the enable fails, because the regulator core will handle this clean up for us. Finally, we need to disable the regulators if snd_soc_register_codecs fails. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 7804ddf..f44da83 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -609,6 +609,7 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) dev_err(dev, "Failed to register regulator notifier: %d\n", ret); + return ret; } } @@ -616,7 +617,7 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) wm8804->supplies); if (ret) { dev_err(dev, "Failed to enable supplies: %d\n", ret); - goto err_reg_enable; + return ret; } ret = regmap_read(regmap, WM8804_RST_DEVID1, &id1); @@ -653,8 +654,14 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) goto err_reg_enable; } - return snd_soc_register_codec(dev, &soc_codec_dev_wm8804, - &wm8804_dai, 1); + ret = snd_soc_register_codec(dev, &soc_codec_dev_wm8804, + &wm8804_dai, 1); + if (ret < 0) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + goto err_reg_enable; + } + + return 0; err_reg_enable: regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), wm8804->supplies); -- cgit v0.10.2 From 45ad3e67f663af3f7988552165e8b8ebddde0ac7 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 5 Mar 2015 15:42:51 +0000 Subject: ASoC: wm8804: Update DT binding document to cover regulator supplies Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/wm8804.txt b/Documentation/devicetree/bindings/sound/wm8804.txt index 4d3a56f..10ef076 100644 --- a/Documentation/devicetree/bindings/sound/wm8804.txt +++ b/Documentation/devicetree/bindings/sound/wm8804.txt @@ -10,6 +10,9 @@ Required properties: - reg : the I2C address of the device for I2C, the chip select number for SPI. + - PVDD-supply, DVDD-supply : Power supplies for the device, as covered + in Documentation/devicetree/bindings/regulator/regulator.txt + Example: codec: wm8804@1a { -- cgit v0.10.2 From 012baec5c1eaab7ad98b461eb7afae2faf79dbea Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Thu, 5 Mar 2015 03:37:39 +0800 Subject: ASoC: tegra: fix platform_no_drv_owner.cocci warnings sound/soc/tegra/tegra_rt5677.c:334:3-8: No need to set .owner here. The core will do it. Remove .owner field if calls are used which set it automatically Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci CC: Anatol Pomozov Signed-off-by: Fengguang Wu Signed-off-by: Mark Brown diff --git a/sound/soc/tegra/tegra_rt5677.c b/sound/soc/tegra/tegra_rt5677.c index e4cf978..4453566 100644 --- a/sound/soc/tegra/tegra_rt5677.c +++ b/sound/soc/tegra/tegra_rt5677.c @@ -331,7 +331,6 @@ static const struct of_device_id tegra_rt5677_of_match[] = { static struct platform_driver tegra_rt5677_driver = { .driver = { .name = DRV_NAME, - .owner = THIS_MODULE, .pm = &snd_soc_pm_ops, .of_match_table = tegra_rt5677_of_match, }, -- cgit v0.10.2 From cd59f13823ae65a16b7c99fdd338ebac0c6487f2 Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Tue, 3 Mar 2015 16:21:53 -0800 Subject: ASoC: qcom: add LPASS header files Add the LPASS header files for ipq806x SOC. This includes the register definitions for the ipq806x LPAIF, and the structure definition for the driver data. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/sound/soc/qcom/lpass-lpaif-ipq806x.h b/sound/soc/qcom/lpass-lpaif-ipq806x.h new file mode 100644 index 0000000..dc423b8 --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif-ipq806x.h @@ -0,0 +1,172 @@ +/* + * 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-lpaif-ipq806x.h -- Definitions for the QTi LPAIF in the ipq806x LPASS + */ + +#ifndef __LPASS_LPAIF_H__ +#define __LPASS_LPAIF_H__ + +#define LPAIF_BANK_OFFSET 0x1000 + +/* LPAIF I2S */ + +#define LPAIF_I2SCTL_REG_BASE 0x0010 +#define LPAIF_I2SCTL_REG_STRIDE 0x4 +#define LPAIF_I2SCTL_REG_ADDR(addr, port) \ + (LPAIF_I2SCTL_REG_BASE + (addr) + (LPAIF_I2SCTL_REG_STRIDE * (port))) + +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, +}; + +#define LPAIF_I2SCTL_REG(port) LPAIF_I2SCTL_REG_ADDR(0x0, (port)) + +#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) + +/* LPAIF IRQ */ + +#define LPAIF_IRQ_REG_BASE 0x3000 +#define LPAIF_IRQ_REG_STRIDE 0x1000 +#define LPAIF_IRQ_REG_ADDR(addr, port) \ + (LPAIF_IRQ_REG_BASE + (addr) + (LPAIF_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(port) LPAIF_IRQ_REG_ADDR(0x0, (port)) +#define LPAIF_IRQSTAT_REG(port) LPAIF_IRQ_REG_ADDR(0x4, (port)) +#define LPAIF_IRQCLEAR_REG(port) LPAIF_IRQ_REG_ADDR(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_BASE 0x6000 +#define LPAIF_RDMA_REG_STRIDE 0x1000 +#define LPAIF_RDMA_REG_ADDR(addr, chan) \ + (LPAIF_RDMA_REG_BASE + (addr) + (LPAIF_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, +}; + +#define LPAIF_RDMACTL_REG(chan) LPAIF_RDMA_REG_ADDR(0x00, (chan)) +#define LPAIF_RDMABASE_REG(chan) LPAIF_RDMA_REG_ADDR(0x04, (chan)) +#define LPAIF_RDMABUFF_REG(chan) LPAIF_RDMA_REG_ADDR(0x08, (chan)) +#define LPAIF_RDMACURR_REG(chan) LPAIF_RDMA_REG_ADDR(0x0C, (chan)) +#define LPAIF_RDMAPER_REG(chan) LPAIF_RDMA_REG_ADDR(0x10, (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_CODEC (1 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_PCM (2 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SEC_I2S (3 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_MI2S (4 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_HDMI (5 << LPAIF_RDMACTL_AUDINTF_SHIFT) +#define LPAIF_RDMACTL_AUDINTF_SEC_PCM (7 << 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_H__ */ diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h new file mode 100644 index 0000000..5c99b3d --- /dev/null +++ b/sound/soc/qcom/lpass.h @@ -0,0 +1,51 @@ +/* + * 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 +#include +#include +#include + +#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 + +/* 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; + + /* 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; +}; + +/* register the platform driver from the CPU DAI driver */ +int asoc_qcom_lpass_platform_register(struct platform_device *); + +#endif /* __LPASS_H__ */ -- cgit v0.10.2 From 80beab8e1d86d7da843e6c3e439bbca5320c568d Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Tue, 3 Mar 2015 16:21:54 -0800 Subject: ASoC: qcom: Add LPASS CPU DAI driver Add the CPU DAI driver for the Qualcomm Technologies low-power audio subsystem (LPASS). Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c new file mode 100644 index 0000000..d516713 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu.c @@ -0,0 +1,510 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lpass-lpaif-ipq806x.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; + + 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; + + 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; + } + + 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); + 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; + + 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(LPAIF_I2S_PORT_MI2S), 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); + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 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; + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), + 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; + + 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(LPAIF_I2S_PORT_MI2S), + 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(LPAIF_I2S_PORT_MI2S), + 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; + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 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 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; + + for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i) + if (reg == LPAIF_I2SCTL_REG(i)) + return true; + + for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) { + if (reg == LPAIF_IRQEN_REG(i)) + return true; + if (reg == LPAIF_IRQCLEAR_REG(i)) + return true; + } + + for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) { + if (reg == LPAIF_RDMACTL_REG(i)) + return true; + if (reg == LPAIF_RDMABASE_REG(i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(i)) + return true; + if (reg == LPAIF_RDMAPER_REG(i)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i) + if (reg == LPAIF_I2SCTL_REG(i)) + return true; + + for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) { + if (reg == LPAIF_IRQEN_REG(i)) + return true; + if (reg == LPAIF_IRQSTAT_REG(i)) + return true; + } + + for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) { + if (reg == LPAIF_RDMACTL_REG(i)) + return true; + if (reg == LPAIF_RDMABASE_REG(i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(i)) + return true; + if (reg == LPAIF_RDMACURR_REG(i)) + return true; + if (reg == LPAIF_RDMAPER_REG(i)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) + if (reg == LPAIF_IRQSTAT_REG(i)) + return true; + + for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) + if (reg == LPAIF_RDMACURR_REG(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, +}; + +static int lpass_cpu_parse_of(struct device *dev) +{ + struct device_node *dsp_of_node; + + dsp_of_node = of_get_child_by_name(dev->of_node, "qcom,adsp"); + if (!dsp_of_node) { + dev_err(dev, "%s() error getting qcom,adsp sub-node\n", + __func__); + return -EINVAL; + } + + if (of_device_is_available(dsp_of_node)) { + dev_err(dev, "%s() DSP exists and holds audio resources\n", + __func__); + return -EBUSY; + } + + return 0; +} + +static int lpass_cpu_platform_probe(struct platform_device *pdev) +{ + struct lpass_data *drvdata; + struct resource *res; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + + ret = lpass_cpu_parse_of(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "%s() error getting DT node info: %d\n", + __func__, ret); + return ret; + } + + 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); + 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); + } + + 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); + } + + 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; + } + + 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; +} + +#ifdef CONFIG_OF +static const struct of_device_id lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu" }, + {} +}; +MODULE_DEVICE_TABLE(of, lpass_cpu_device_id); +#endif + +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"); -- cgit v0.10.2 From c5c8635a04711c7a7aca82f90e6b1e6df1c057be Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Tue, 3 Mar 2015 16:21:55 -0800 Subject: ASoC: qcom: Add LPASS platform driver Add platform driver for the Qualcomm Technologies low-power audio subsystem (LPASS) ports. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c new file mode 100644 index 0000000..2fa6280 --- /dev/null +++ b/sound/soc/qcom/lpass-platform.c @@ -0,0 +1,526 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lpass-lpaif-ipq806x.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); + 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; + } + + regval = LPAIF_RDMACTL_BURSTEN_INCR4 | + LPAIF_RDMACTL_AUDINTF_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(LPAIF_RDMA_CHAN_MI2S), 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); + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), 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); + int ret; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S), + 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(LPAIF_RDMA_CHAN_MI2S), + (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(LPAIF_RDMA_CHAN_MI2S), + (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(LPAIF_RDMA_CHAN_MI2S), + 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); + 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(LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S)); + 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(LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S), + LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S)); + 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(LPAIF_RDMA_CHAN_MI2S), + 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(LPAIF_RDMA_CHAN_MI2S), + 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(LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S), 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); + unsigned int base_addr, curr_addr; + int ret; + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S), &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(LPAIF_RDMA_CHAN_MI2S), &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); + unsigned int interrupts; + irqreturn_t ret = IRQ_NONE; + int rv; + + rv = regmap_read(drvdata->lpaif_map, + LPAIF_IRQSTAT_REG(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(LPAIF_RDMA_CHAN_MI2S); + + if (interrupts & LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S)); + 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(LPAIF_RDMA_CHAN_MI2S)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_XRUN(LPAIF_RDMA_CHAN_MI2S)); + 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(LPAIF_RDMA_CHAN_MI2S)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ERR(LPAIF_RDMA_CHAN_MI2S)); + 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); + 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(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(LPAIF_RDMA_CHAN_MI2S), 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"); -- cgit v0.10.2 From 8dd72c42d38e62b82d7e3c47173b502d851c48ad Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Tue, 3 Mar 2015 16:21:51 -0800 Subject: ASoC: qcom: Document LPASS CPU bindings Add documentation to the sound directory of the device-tree bindings for the QTi LPASS CPU DAI device. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt new file mode 100644 index 0000000..e7c6e93 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt @@ -0,0 +1,49 @@ +* Qualcomm Technologies LPASS CPU DAI + +This node models the Qualcomm Technologies Low-Power Audio SubSystem (LPASS). + +Required properties: + +- compatible : "qcom,lpass-cpu" +- clocks : Must contain an entry for each entry in clock-names. +- clock-names : A list which must include the following entries: + * "ahbix-clk" + * "mi2s-osr-clk" + * "mi2s-bit-clk" +- interrupts : Must contain an entry for each entry in + interrupt-names. +- interrupt-names : A list which must include the following entries: + * "lpass-irq-lpaif" +- pinctrl-N : One property must exist for each entry in + pinctrl-names. See ../pinctrl/pinctrl-bindings.txt + for details of the property values. +- pinctrl-names : Must contain a "default" entry. +- reg : Must contain an address for each entry in reg-names. +- reg-names : A list which must include the following entries: + * "lpass-lpaif" + +Required subnodes: + +- qcom,adsp : Audio DSP sub-node + +Optional Audio DSP subnode properties: + +- status : "disabled" indicates the adsp is not available. + +Example: + +lpass@28100000 { + compatible = "qcom,lpass-cpu"; + clocks = <&lcc AHBIX_CLK>, <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>; + clock-names = "ahbix-clk", "mi2s-osr-clk", "mi2s-bit-clk"; + interrupts = <0 85 1>; + interrupt-names = "lpass-irq-lpaif"; + pinctrl-names = "default", "idle"; + pinctrl-0 = <&mi2s_default>; + pinctrl-1 = <&mi2s_idle>; + reg = <0x28100000 0x10000>; + reg-names = "lpass-lpaif"; + qcom,adsp { + status = "disabled"; + }; +}; -- cgit v0.10.2 From 2d800897e868ba561733ecffb30d840cbe5c86d4 Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Tue, 3 Mar 2015 16:21:50 -0800 Subject: MAINTAINERS: Add QCOM audio ASoC maintainer Add maintainers for the Qualcomm Technologies sound drivers. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/MAINTAINERS b/MAINTAINERS index ddc5a8c..9514b79 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5254,6 +5254,13 @@ F: drivers/char/ipmi/ F: include/linux/ipmi* F: include/uapi/linux/ipmi* +QCOM AUDIO (ASoC) DRIVERS +M: Patrick Lai +M: Banajit Goswami +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +S: Supported +F: sound/soc/qcom/ + IPS SCSI RAID DRIVER M: Adaptec OEM Raid Solutions L: linux-scsi@vger.kernel.org -- cgit v0.10.2 From 66e618857ca46433741bf97ceca1b425387400b1 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 6 Mar 2015 09:07:32 +0200 Subject: ASoC: davinci-mcasp: Fix compilation error Introduced by commit: 6afda7f50754 ASoC: davinci-mcasp: Allow complete shutdown of McASP when not in use I'm really sorry for this... Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 33cea07..d40b392 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -1058,7 +1058,7 @@ static int davinci_mcasp_suspend(struct snd_soc_dai *dai) u32 reg; int i; - context->pm_state = pm_runtime_enabled(mcasp->dev) + context->pm_state = pm_runtime_enabled(mcasp->dev); if (!context->pm_state) pm_runtime_get_sync(mcasp->dev); -- cgit v0.10.2 From 3d4cf65e2db58f927ca3cd13b0074b8fe5124659 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 25 Feb 2015 16:42:12 +0100 Subject: ASoC: omap: fix up SND_OMAP_SOC_OMAP_ABE_TWL6040 dependency The change to enable OMAP5 support on this platform was a little too eager in adding a 'select' for a particular clock driver that might not be enabled in all configurations, which in turn leads to a build error: warning: (SND_OMAP_SOC_OMAP_ABE_TWL6040) selects COMMON_CLK_PALMAS which has unmet direct dependencies (COMMON_CLK && MFD_PALMAS) drivers/built-in.o: In function `palmas_clks_probe': drivers/clk/clk-palmas.c:228: undefined reference to `palmas_ext_control_req_config' I do not see a strong dependency here, so it's probably better to drop this select and to avoid adding more complexity here. Fixes: 5163c1eede8e9 ("ASoC: omap: Kconfig: Support for omap5-uevm analog audio") Signed-off-by: Arnd Bergmann Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index e7c78b0..6768e4f 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -105,7 +105,7 @@ config SND_OMAP_SOC_OMAP_ABE_TWL6040 select SND_OMAP_SOC_MCPDM select SND_SOC_TWL6040 select SND_SOC_DMIC - select COMMON_CLK_PALMAS if SOC_OMAP5 + select COMMON_CLK_PALMAS if MFD_PALMAS help Say Y if you want to add support for SoC audio on OMAP boards using ABE and twl6040 codec. This driver currently supports: -- cgit v0.10.2 From 5af76d5c0882435241841186b054262407e9eabb Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Fri, 27 Feb 2015 12:52:26 +0800 Subject: ASoC: rt286: correct the OR to AND Here it should be AND(&) to check the status. Signed-off-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 16723b1..49c44a7 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -397,7 +397,7 @@ int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack) if (jack) { /* enable IRQ */ - if (rt286->jack->status | SND_JACK_HEADPHONE) + if (rt286->jack->status & SND_JACK_HEADPHONE) snd_soc_dapm_force_enable_pin(&codec->dapm, "LDO1"); regmap_update_bits(rt286->regmap, RT286_IRQ_CTRL, 0x2, 0x2); /* Send an initial empty report */ -- cgit v0.10.2 From 1a5ab21c2e0f3d6b25ee9f7ca3429fac57027f76 Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Fri, 27 Feb 2015 12:54:29 +0800 Subject: ASoC: Intel: Add suspend_pre and resume_post for Broadwell snd_soc_card For broadwell machine, we need do some machine related setting before suspend and after resume, e.g. disable/enable jack detection, here adding snd_soc_card suspend_pre and resume_post for this task. Signed-off-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/intel/broadwell.c b/sound/soc/intel/broadwell.c index fba2ef5..af5d730 100644 --- a/sound/soc/intel/broadwell.c +++ b/sound/soc/intel/broadwell.c @@ -225,6 +225,32 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { }, }; +static int broadwell_suspend(struct snd_soc_card *card){ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-INT343A:00")) { + dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n"); + rt286_mic_detect(codec, NULL); + break; + } + } + return 0; +} + +static int broadwell_resume(struct snd_soc_card *card){ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-INT343A:00")) { + dev_dbg(codec->dev, "enabling jack detect for resume.\n"); + rt286_mic_detect(codec, &broadwell_headset); + break; + } + } + return 0; +} + /* broadwell audio machine driver for WPT + RT286S */ static struct snd_soc_card broadwell_rt286 = { .name = "broadwell-rt286", @@ -238,6 +264,8 @@ static struct snd_soc_card broadwell_rt286 = { .dapm_routes = broadwell_rt286_map, .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map), .fully_routed = true, + .suspend_pre = broadwell_suspend, + .resume_post = broadwell_resume, }; static int broadwell_audio_probe(struct platform_device *pdev) -- cgit v0.10.2 From 0aed11244360c24c854a263eac0293acef2abd03 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 24 Feb 2015 00:54:16 +0000 Subject: dmaengine: export symbol of of_dma_request_slave_channel() Current DMAEngine implementation of DT bindings can't support DT subnode. This patch export symbols of of_dma_request_slave_channel() for subnode DMA DT bingings. ex) rcar_sound: rcar_sound@ec500000 { ... rcar_sound,dvc { dvc0: dvc@0 { dmas = <&audma0 0xbc>; dma-names = "tx"; }; dvc1: dvc@1 { dmas = <&audma0 0xbe>; dma-names = "tx"; }; }; ... }; Signed-off-by: Kuninori Morimoto Acked-by: Vinod Koul Signed-off-by: Mark Brown diff --git a/drivers/dma/of-dma.c b/drivers/dma/of-dma.c index ca31f1b..cbd4a8a 100644 --- a/drivers/dma/of-dma.c +++ b/drivers/dma/of-dma.c @@ -194,6 +194,7 @@ struct dma_chan *of_dma_request_slave_channel(struct device_node *np, return ERR_PTR(ret_no_channel); } +EXPORT_SYMBOL_GPL(of_dma_request_slave_channel); /** * of_dma_simple_xlate - Simple DMA engine translation function -- cgit v0.10.2 From c303cf0895ad927f5e9b8a5f8ed1c6b8c96a500f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:22:37 +0000 Subject: ASoC: rsnd: remove SH-DMA-BASE specific implementation Renesas R-Car sound had SH-DMA-BASE specific implementation before, but, now, it is no longer needed. Let's remove it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 1b53605..0b14d37 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -94,7 +94,6 @@ * */ #include -#include #include "rsnd.h" #define RSND_RATES SNDRV_PCM_RATE_8000_96000 diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index e7914bd..5ffde9b 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -171,7 +171,6 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod); * R-Car DMA */ struct rsnd_dma { - struct sh_dmae_slave slave; struct dma_chan *chan; enum dma_transfer_direction dir; dma_addr_t addr; -- cgit v0.10.2 From 4715219ecef50cf79d7784545bf5bb4664bb800d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:23:08 +0000 Subject: ASoC: rsnd: remove un-needed parameter from rsnd_dma_init() It can get DMA direction via rsnd_dai_stream. Remove un-needed is_play from rsnd_dma_init(). Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 0b14d37..1edf4dc 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -311,13 +311,15 @@ static void rsnd_dma_of_path(struct rsnd_dma *dma, } } -int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, - int is_play, int id) +int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) { struct device *dev = rsnd_priv_to_dev(priv); struct dma_slave_config cfg; + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); struct rsnd_mod *mod_from; struct rsnd_mod *mod_to; + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + int is_play = rsnd_io_is_play(io); char dma_name[DMA_NAME_SIZE]; dma_cap_mask_t mask; int ret; diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 5ffde9b..93a1a25 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -179,8 +179,7 @@ struct rsnd_dma { void rsnd_dma_start(struct rsnd_dma *dma); void rsnd_dma_stop(struct rsnd_dma *dma); int rsnd_dma_available(struct rsnd_dma *dma); -int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, - int is_play, int id); +int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id); void rsnd_dma_quit(struct rsnd_priv *priv, struct rsnd_dma *dma); @@ -413,19 +412,6 @@ struct rsnd_priv { #define rsnd_lock(priv, flags) spin_lock_irqsave(&priv->lock, flags) #define rsnd_unlock(priv, flags) spin_unlock_irqrestore(&priv->lock, flags) -#define rsnd_info_is_playback(priv, type) \ -({ \ - struct rcar_snd_info *info = rsnd_priv_to_info(priv); \ - int i, is_play = 0; \ - for (i = 0; i < info->dai_info_nr; i++) { \ - if (info->dai_info[i].playback.type == (type)->info) { \ - is_play = 1; \ - break; \ - } \ - } \ - is_play; \ -}) - /* * rsnd_kctrl */ diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 81c182b..7fb8798 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -713,7 +713,6 @@ static int rsnd_src_probe_gen2(struct rsnd_mod *mod, ret = rsnd_dma_init(priv, rsnd_mod_to_dma(mod), - rsnd_info_is_playback(priv, src), src->info->dma_id); if (ret) goto rsnd_src_probe_gen2_fail; diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 9e7b627..57e737c 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -478,7 +478,6 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, ret = rsnd_dma_init( priv, rsnd_mod_to_dma(mod), - rsnd_info_is_playback(priv, ssi), dma_id); if (ret) goto rsnd_ssi_dma_probe_fail; -- cgit v0.10.2 From 9c706ab29f33b9562f570d1e99e21955d898dc85 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:23:39 +0000 Subject: ASoC: rsnd: remove unused rsnd_dma_available() rsnd_dma_available() is not used. Let's remove it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 1edf4dc..1da94bf 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -222,11 +222,6 @@ void rsnd_dma_start(struct rsnd_dma *dma) dma_async_issue_pending(dma->chan); } -int rsnd_dma_available(struct rsnd_dma *dma) -{ - return !!dma->chan; -} - #define DMA_NAME_SIZE 16 #define MOD_MAX 4 /* MEM/SSI/SRC/DVC */ static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 93a1a25..cb12861a 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -178,7 +178,6 @@ struct rsnd_dma { void rsnd_dma_start(struct rsnd_dma *dma); void rsnd_dma_stop(struct rsnd_dma *dma); -int rsnd_dma_available(struct rsnd_dma *dma); int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id); void rsnd_dma_quit(struct rsnd_priv *priv, struct rsnd_dma *dma); diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 7fb8798..f12c8b3 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -30,8 +30,6 @@ struct rsnd_src { #define rsnd_src_convert_rate(p) ((p)->info->convert_rate) #define rsnd_mod_to_src(_mod) \ container_of((_mod), struct rsnd_src, mod) -#define rsnd_src_dma_available(src) \ - rsnd_dma_available(rsnd_mod_to_dma(&(src)->mod)) #define for_each_rsnd_src(pos, priv, i) \ for ((i) = 0; \ diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 57e737c..fcc77ea 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -80,8 +80,6 @@ struct rsnd_ssi { #define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod) #define rsnd_dma_to_ssi(dma) rsnd_mod_to_ssi(rsnd_dma_to_mod(dma)) #define rsnd_ssi_pio_available(ssi) ((ssi)->info->irq > 0) -#define rsnd_ssi_dma_available(ssi) \ - rsnd_dma_available(rsnd_mod_to_dma(&(ssi)->mod)) #define rsnd_ssi_clk_from_parent(ssi) ((ssi)->parent) #define rsnd_ssi_mode_flags(p) ((p)->info->flags) #define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id) -- cgit v0.10.2 From 8a2ff4262ca611c38b31fec0af65be656d934f52 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:24:06 +0000 Subject: ASoC: rsnd: remove un-needed parameter from rsnd_dma_quit() priv is not used on rsnd_dma_quit() Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 1da94bf..7db686d 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -358,7 +358,7 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) return 0; rsnd_dma_init_err: - rsnd_dma_quit(priv, dma); + rsnd_dma_quit(dma); rsnd_dma_channel_err: /* @@ -370,8 +370,7 @@ rsnd_dma_channel_err: return -EAGAIN; } -void rsnd_dma_quit(struct rsnd_priv *priv, - struct rsnd_dma *dma) +void rsnd_dma_quit(struct rsnd_dma *dma) { if (dma->chan) dma_release_channel(dma->chan); diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index cb12861a..6ee97e7 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -179,8 +179,7 @@ struct rsnd_dma { void rsnd_dma_start(struct rsnd_dma *dma); void rsnd_dma_stop(struct rsnd_dma *dma); int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id); -void rsnd_dma_quit(struct rsnd_priv *priv, - struct rsnd_dma *dma); +void rsnd_dma_quit(struct rsnd_dma *dma); /* diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index f12c8b3..e279205 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -730,7 +730,7 @@ rsnd_src_probe_gen2_fail: static int rsnd_src_remove_gen2(struct rsnd_mod *mod, struct rsnd_priv *priv) { - rsnd_dma_quit(priv, rsnd_mod_to_dma(mod)); + rsnd_dma_quit(rsnd_mod_to_dma(mod)); return 0; } diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index fcc77ea..a0d902a 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -499,7 +499,7 @@ static int rsnd_ssi_dma_remove(struct rsnd_mod *mod, struct device *dev = rsnd_priv_to_dev(priv); int irq = ssi->info->irq; - rsnd_dma_quit(priv, rsnd_mod_to_dma(mod)); + rsnd_dma_quit(rsnd_mod_to_dma(mod)); /* PIO will request IRQ again */ devm_free_irq(dev, irq, ssi); -- cgit v0.10.2 From 4ce3b17bd43b4f0136b1d0c7782d06fe9d3addfb Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:24:27 +0000 Subject: ASoC: rsnd: tidyup rsnd_dma_to_mod() macro declaration position Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 6ee97e7..ec77c9f 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -182,6 +182,8 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id); void rsnd_dma_quit(struct rsnd_dma *dma); +#define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma) + /* * R-Car sound mod */ @@ -253,7 +255,6 @@ struct rsnd_mod { #define rsnd_mod_to_priv(mod) (rsnd_io_to_priv(rsnd_mod_to_io(mod))) #define rsnd_mod_to_dma(mod) (&(mod)->dma) -#define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma) #define rsnd_mod_to_io(mod) ((mod)->io) #define rsnd_mod_id(mod) ((mod)->id) #define rsnd_mod_hw_start(mod) clk_prepare_enable((mod)->clk) -- cgit v0.10.2 From 7277911c87ba85436600f5b7aab15de112416795 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:24:52 +0000 Subject: ASoC: rsnd: enable to get resource by name rsnd driver needs to support Audio DMAC peri peri inside sound driver. getting resource by name is useful for it Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index de0685f..d08bcd3 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -118,11 +118,12 @@ void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, mask, data); } -#define rsnd_gen_regmap_init(priv, id_size, reg_id, conf) \ - _rsnd_gen_regmap_init(priv, id_size, reg_id, conf, ARRAY_SIZE(conf)) +#define rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf) \ + _rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf, ARRAY_SIZE(conf)) static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, int id_size, int reg_id, + const char *name, struct rsnd_regmap_field_conf *conf, int conf_size) { @@ -142,7 +143,9 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, regc.val_bits = 32; regc.reg_stride = 4; - res = platform_get_resource(pdev, IORESOURCE_MEM, reg_id); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, reg_id); if (!res) return -ENODEV; @@ -368,10 +371,10 @@ static int rsnd_gen2_probe(struct platform_device *pdev, int ret_adg; int ret_ssi; - ret_ssiu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSIU, conf_ssiu); - ret_scu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SCU, conf_scu); - ret_adg = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_ADG, conf_adg); - ret_ssi = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSI, conf_ssi); + ret_ssiu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSIU, "ssiu", conf_ssiu); + ret_scu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SCU, "scu", conf_scu); + ret_adg = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_ADG, "adg", conf_adg); + ret_ssi = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSI, "ssi", conf_ssi); if (ret_ssiu < 0 || ret_scu < 0 || ret_adg < 0 || @@ -440,9 +443,9 @@ static int rsnd_gen1_probe(struct platform_device *pdev, int ret_adg; int ret_ssi; - ret_sru = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SRU, conf_sru); - ret_adg = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_ADG, conf_adg); - ret_ssi = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SSI, conf_ssi); + ret_sru = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SRU, "sru", conf_sru); + ret_adg = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_ADG, "adg", conf_adg); + ret_ssi = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SSI, "ssi", conf_ssi); if (ret_sru < 0 || ret_adg < 0 || ret_ssi < 0) -- cgit v0.10.2 From c5212b4556b6bd120b0f4e4ae7c4a1cb9f5efe07 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:25:27 +0000 Subject: ASoC: rsnd: add rsnd_gen_get_phy_addr() to get physical address physical address is required from Audio DMAC peri peri. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index d08bcd3..0da04ed 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -28,6 +28,7 @@ struct rsnd_gen { struct regmap *regmap[RSND_BASE_MAX]; struct regmap_field *regs[RSND_REG_MAX]; + phys_addr_t res[RSND_REG_MAX]; }; #define rsnd_priv_to_gen(p) ((struct rsnd_gen *)(p)->gen) @@ -118,6 +119,13 @@ void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, mask, data); } +phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id) +{ + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); + + return gen->res[reg_id]; +} + #define rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf) \ _rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf, ARRAY_SIZE(conf)) static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, @@ -159,6 +167,7 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, gen->base[reg_id] = base; gen->regmap[reg_id] = regmap; + gen->res[reg_id] = res->start; for (i = 0; i < conf_size; i++) { @@ -216,13 +225,10 @@ rsnd_gen2_dma_addr(struct rsnd_priv *priv, struct rsnd_mod *mod, int is_play, int is_from) { - struct platform_device *pdev = rsnd_priv_to_pdev(priv); struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - dma_addr_t ssi_reg = platform_get_resource(pdev, - IORESOURCE_MEM, RSND_GEN2_SSI)->start; - dma_addr_t src_reg = platform_get_resource(pdev, - IORESOURCE_MEM, RSND_GEN2_SCU)->start; + phys_addr_t ssi_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SSI); + phys_addr_t src_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SCU); int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod); int use_src = !!rsnd_io_to_mod_src(io); int use_dvc = !!rsnd_io_to_mod_dvc(io); diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index ec77c9f..8a8a4d5 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -331,6 +331,7 @@ void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv, dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv, struct rsnd_mod *mod, int is_play, int is_from); +phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id); #define rsnd_is_gen1(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN1) #define rsnd_is_gen2(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN2) -- cgit v0.10.2 From bfe834be9525a82c8a40380c7df8ca3b149e9c93 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:25:55 +0000 Subject: ASoC: rsnd: add dma.c for Audio DMAC / Audio DMAC peri peri Renesas sound driver had been assumed that Audio DMAC / Audio DMAC peri peri are implemented by DMAEngine. But, result of DMA ML discussion, it was concluded that it is not a general purpose DMAC. And it should be moved from current DMAEngine to rsnd driver. So, Audio DMAC peri peri become non DMAEngine. OTOH, ALSA SoC has soc-generic-dmaengine-pcm implementation. but it seems difficult to use this generic implementation on rsnd driver at this point, since it needs to fallback to PIO mode if DMA can't use. and additionally it needs 2 DMAC (= Audio DMAC / Audio DMAC peri peri). These are not "generic" feature. Of course I will try to use this generic dmaengine in the future somehow, but just use current style at this point until it can formally use 2 DMACs. This patch adds new dma.c and moves current dma code to dma.c from core.c. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/Makefile b/sound/soc/sh/rcar/Makefile index 9ac5364..7b20492 100644 --- a/sound/soc/sh/rcar/Makefile +++ b/sound/soc/sh/rcar/Makefile @@ -1,2 +1,2 @@ -snd-soc-rcar-objs := core.o gen.o src.o adg.o ssi.o dvc.o +snd-soc-rcar-objs := core.o gen.o dma.o src.o adg.o ssi.o dvc.o obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o \ No newline at end of file diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 7db686d..9beea9b 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -161,224 +161,6 @@ void rsnd_mod_init(struct rsnd_mod *mod, } /* - * rsnd_dma functions - */ -void rsnd_dma_stop(struct rsnd_dma *dma) -{ - dmaengine_terminate_all(dma->chan); -} - -static void rsnd_dma_complete(void *data) -{ - struct rsnd_dma *dma = (struct rsnd_dma *)data; - struct rsnd_mod *mod = rsnd_dma_to_mod(dma); - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - - /* - * Renesas sound Gen1 needs 1 DMAC, - * Gen2 needs 2 DMAC. - * In Gen2 case, it are Audio-DMAC, and Audio-DMAC-peri-peri. - * But, Audio-DMAC-peri-peri doesn't have interrupt, - * and this driver is assuming that here. - * - * If Audio-DMAC-peri-peri has interrpt, - * rsnd_dai_pointer_update() will be called twice, - * ant it will breaks io->byte_pos - */ - - rsnd_dai_pointer_update(io, io->byte_per_period); -} - -void rsnd_dma_start(struct rsnd_dma *dma) -{ - struct rsnd_mod *mod = rsnd_dma_to_mod(dma); - struct rsnd_priv *priv = rsnd_mod_to_priv(mod); - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - struct snd_pcm_substream *substream = io->substream; - struct device *dev = rsnd_priv_to_dev(priv); - struct dma_async_tx_descriptor *desc; - - desc = dmaengine_prep_dma_cyclic(dma->chan, - (dma->addr) ? dma->addr : - substream->runtime->dma_addr, - snd_pcm_lib_buffer_bytes(substream), - snd_pcm_lib_period_bytes(substream), - dma->dir, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - - if (!desc) { - dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); - return; - } - - desc->callback = rsnd_dma_complete; - desc->callback_param = dma; - - if (dmaengine_submit(desc) < 0) { - dev_err(dev, "dmaengine_submit() fail\n"); - return; - } - - dma_async_issue_pending(dma->chan); -} - -#define DMA_NAME_SIZE 16 -#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */ -static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) -{ - if (mod) - return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d", - rsnd_mod_dma_name(mod), rsnd_mod_id(mod)); - else - return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem"); - -} - -static void rsnd_dma_of_name(struct rsnd_mod *mod_from, - struct rsnd_mod *mod_to, - char *dma_name) -{ - int index = 0; - - index = _rsnd_dma_of_name(dma_name + index, mod_from); - *(dma_name + index++) = '_'; - index = _rsnd_dma_of_name(dma_name + index, mod_to); -} - -static void rsnd_dma_of_path(struct rsnd_dma *dma, - int is_play, - struct rsnd_mod **mod_from, - struct rsnd_mod **mod_to) -{ - struct rsnd_mod *this = rsnd_dma_to_mod(dma); - struct rsnd_dai_stream *io = rsnd_mod_to_io(this); - struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io); - struct rsnd_mod *src = rsnd_io_to_mod_src(io); - struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); - struct rsnd_mod *mod[MOD_MAX]; - int i, index; - - - for (i = 0; i < MOD_MAX; i++) - mod[i] = NULL; - - /* - * in play case... - * - * src -> dst - * - * mem -> SSI - * mem -> SRC -> SSI - * mem -> SRC -> DVC -> SSI - */ - mod[0] = NULL; /* for "mem" */ - index = 1; - for (i = 1; i < MOD_MAX; i++) { - if (!src) { - mod[i] = ssi; - } else if (!dvc) { - mod[i] = src; - src = NULL; - } else { - if ((!is_play) && (this == src)) - this = dvc; - - mod[i] = (is_play) ? src : dvc; - i++; - mod[i] = (is_play) ? dvc : src; - src = NULL; - dvc = NULL; - } - - if (mod[i] == this) - index = i; - - if (mod[i] == ssi) - break; - } - - if (is_play) { - *mod_from = mod[index - 1]; - *mod_to = mod[index]; - } else { - *mod_from = mod[index]; - *mod_to = mod[index - 1]; - } -} - -int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) -{ - struct device *dev = rsnd_priv_to_dev(priv); - struct dma_slave_config cfg; - struct rsnd_mod *mod = rsnd_dma_to_mod(dma); - struct rsnd_mod *mod_from; - struct rsnd_mod *mod_to; - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - int is_play = rsnd_io_is_play(io); - char dma_name[DMA_NAME_SIZE]; - dma_cap_mask_t mask; - int ret; - - if (dma->chan) { - dev_err(dev, "it already has dma channel\n"); - return -EIO; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to); - rsnd_dma_of_name(mod_from, mod_to, dma_name); - - cfg.slave_id = id; - cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - cfg.src_addr = rsnd_gen_dma_addr(priv, mod_from, is_play, 1); - cfg.dst_addr = rsnd_gen_dma_addr(priv, mod_to, is_play, 0); - cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - - dev_dbg(dev, "dma : %s %pad -> %pad\n", - dma_name, &cfg.src_addr, &cfg.dst_addr); - - dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, - (void *)id, dev, - dma_name); - if (!dma->chan) { - dev_err(dev, "can't get dma channel\n"); - goto rsnd_dma_channel_err; - } - - ret = dmaengine_slave_config(dma->chan, &cfg); - if (ret < 0) - goto rsnd_dma_init_err; - - dma->addr = is_play ? cfg.src_addr : cfg.dst_addr; - dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - - return 0; - -rsnd_dma_init_err: - rsnd_dma_quit(dma); -rsnd_dma_channel_err: - - /* - * DMA failed. try to PIO mode - * see - * rsnd_ssi_fallback() - * rsnd_rdai_continuance_probe() - */ - return -EAGAIN; -} - -void rsnd_dma_quit(struct rsnd_dma *dma) -{ - if (dma->chan) - dma_release_channel(dma->chan); - - dma->chan = NULL; -} - -/* * settting function */ u32 rsnd_get_adinr(struct rsnd_mod *mod) diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c new file mode 100644 index 0000000..37acd40 --- /dev/null +++ b/sound/soc/sh/rcar/dma.c @@ -0,0 +1,231 @@ +/* + * Renesas R-Car Audio DMAC support + * + * Copyright (C) 2015 Renesas Electronics Corp. + * Copyright (c) 2015 Kuninori Morimoto + * + * 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 "rsnd.h" + +static void rsnd_dma_complete(void *data) +{ + struct rsnd_dma *dma = (struct rsnd_dma *)data; + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + + /* + * Renesas sound Gen1 needs 1 DMAC, + * Gen2 needs 2 DMAC. + * In Gen2 case, it are Audio-DMAC, and Audio-DMAC-peri-peri. + * But, Audio-DMAC-peri-peri doesn't have interrupt, + * and this driver is assuming that here. + * + * If Audio-DMAC-peri-peri has interrpt, + * rsnd_dai_pointer_update() will be called twice, + * ant it will breaks io->byte_pos + */ + + rsnd_dai_pointer_update(io, io->byte_per_period); +} + +#define DMA_NAME_SIZE 16 +#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */ +static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) +{ + if (mod) + return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d", + rsnd_mod_dma_name(mod), rsnd_mod_id(mod)); + else + return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem"); + +} + +static void rsnd_dma_of_name(struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to, + char *dma_name) +{ + int index = 0; + + index = _rsnd_dma_of_name(dma_name + index, mod_from); + *(dma_name + index++) = '_'; + index = _rsnd_dma_of_name(dma_name + index, mod_to); +} + +void rsnd_dma_stop(struct rsnd_dma *dma) +{ + dmaengine_terminate_all(dma->chan); +} + +void rsnd_dma_start(struct rsnd_dma *dma) +{ + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct snd_pcm_substream *substream = io->substream; + struct device *dev = rsnd_priv_to_dev(priv); + struct dma_async_tx_descriptor *desc; + + desc = dmaengine_prep_dma_cyclic(dma->chan, + (dma->addr) ? dma->addr : + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dma->dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!desc) { + dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); + return; + } + + desc->callback = rsnd_dma_complete; + desc->callback_param = dma; + + if (dmaengine_submit(desc) < 0) { + dev_err(dev, "dmaengine_submit() fail\n"); + return; + } + + dma_async_issue_pending(dma->chan); +} + +static void rsnd_dma_of_path(struct rsnd_dma *dma, + int is_play, + struct rsnd_mod **mod_from, + struct rsnd_mod **mod_to); + +int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct dma_slave_config cfg = {}; + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_mod *mod_from; + struct rsnd_mod *mod_to; + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + int is_play = rsnd_io_is_play(io); + char dma_name[DMA_NAME_SIZE]; + dma_cap_mask_t mask; + int ret; + + if (dma->chan) { + dev_err(dev, "it already has dma channel\n"); + return -EIO; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to); + rsnd_dma_of_name(mod_from, mod_to, dma_name); + + cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + cfg.src_addr = rsnd_gen_dma_addr(priv, mod_from, is_play, 1); + cfg.dst_addr = rsnd_gen_dma_addr(priv, mod_to, is_play, 0); + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + dev_dbg(dev, "dma : %s %pad -> %pad\n", + dma_name, &cfg.src_addr, &cfg.dst_addr); + + dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, + (void *)id, dev, + dma_name); + if (!dma->chan) { + dev_err(dev, "can't get dma channel\n"); + goto rsnd_dma_channel_err; + } + + ret = dmaengine_slave_config(dma->chan, &cfg); + if (ret < 0) + goto rsnd_dma_init_err; + + dma->addr = is_play ? cfg.src_addr : cfg.dst_addr; + dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + + return 0; + +rsnd_dma_init_err: + rsnd_dma_quit(dma); +rsnd_dma_channel_err: + + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + return -EAGAIN; +} + +void rsnd_dma_quit(struct rsnd_dma *dma) +{ + if (dma->chan) + dma_release_channel(dma->chan); + + dma->chan = NULL; +} + +static void rsnd_dma_of_path(struct rsnd_dma *dma, + int is_play, + struct rsnd_mod **mod_from, + struct rsnd_mod **mod_to) +{ + struct rsnd_mod *this = rsnd_dma_to_mod(dma); + struct rsnd_dai_stream *io = rsnd_mod_to_io(this); + struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + struct rsnd_mod *mod[MOD_MAX]; + int i, index; + + + for (i = 0; i < MOD_MAX; i++) + mod[i] = NULL; + + /* + * in play case... + * + * src -> dst + * + * mem -> SSI + * mem -> SRC -> SSI + * mem -> SRC -> DVC -> SSI + */ + mod[0] = NULL; /* for "mem" */ + index = 1; + for (i = 1; i < MOD_MAX; i++) { + if (!src) { + mod[i] = ssi; + } else if (!dvc) { + mod[i] = src; + src = NULL; + } else { + if ((!is_play) && (this == src)) + this = dvc; + + mod[i] = (is_play) ? src : dvc; + i++; + mod[i] = (is_play) ? dvc : src; + src = NULL; + dvc = NULL; + } + + if (mod[i] == this) + index = i; + + if (mod[i] == ssi) + break; + } + + if (is_play) { + *mod_from = mod[index - 1]; + *mod_to = mod[index]; + } else { + *mod_from = mod[index]; + *mod_to = mod[index - 1]; + } +} + -- cgit v0.10.2 From 747c71b12ee8357e73a88eb25f569e2a20e80df3 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:26:29 +0000 Subject: ASoC: rsnd: move rsnd_gen_dma_addr() from gen.c to dma.c Now, we have dma.c for Audio DMAC / Audio DMAC peri peri. rsnd_gen_dma_addr() should go there. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 37acd40..188b463 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -32,7 +32,6 @@ static void rsnd_dma_complete(void *data) } #define DMA_NAME_SIZE 16 -#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */ static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) { if (mod) @@ -97,6 +96,10 @@ static void rsnd_dma_of_path(struct rsnd_dma *dma, struct rsnd_mod **mod_from, struct rsnd_mod **mod_to); +static dma_addr_t rsnd_dma_addr(struct rsnd_priv *priv, + struct rsnd_mod *mod, + int is_play, int is_from); + int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) { struct device *dev = rsnd_priv_to_dev(priv); @@ -122,8 +125,8 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) rsnd_dma_of_name(mod_from, mod_to, dma_name); cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - cfg.src_addr = rsnd_gen_dma_addr(priv, mod_from, is_play, 1); - cfg.dst_addr = rsnd_gen_dma_addr(priv, mod_to, is_play, 0); + cfg.src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1); + cfg.dst_addr = rsnd_dma_addr(priv, mod_to, is_play, 0); cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; @@ -168,6 +171,117 @@ void rsnd_dma_quit(struct rsnd_dma *dma) dma->chan = NULL; } +/* + * DMA read/write register offset + * + * RSND_xxx_I_N for Audio DMAC input + * RSND_xxx_O_N for Audio DMAC output + * RSND_xxx_I_P for Audio DMAC peri peri input + * RSND_xxx_O_P for Audio DMAC peri peri output + * + * ex) R-Car H2 case + * mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out + * SSI : 0xec541000 / 0xec241008 / 0xec24100c + * SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000 + * SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000 + * CMD : 0xec500000 / / 0xec008000 0xec308000 + */ +#define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8) +#define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc) + +#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) +#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) + +#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) +#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) + +#define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i)) +#define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i)) + +#define RDMA_SRC_I_P(addr, i) (addr ##_reg - 0x00200000 + (0x400 * i)) +#define RDMA_SRC_O_P(addr, i) (addr ##_reg - 0x001fc000 + (0x400 * i)) + +#define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i)) +#define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i)) + +static dma_addr_t +rsnd_gen2_dma_addr(struct rsnd_priv *priv, + struct rsnd_mod *mod, + int is_play, int is_from) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + phys_addr_t ssi_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SSI); + phys_addr_t src_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SCU); + int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod); + int use_src = !!rsnd_io_to_mod_src(io); + int use_dvc = !!rsnd_io_to_mod_dvc(io); + int id = rsnd_mod_id(mod); + struct dma_addr { + dma_addr_t out_addr; + dma_addr_t in_addr; + } dma_addrs[3][2][3] = { + /* SRC */ + {{{ 0, 0 }, + /* Capture */ + { RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) }, + { RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } }, + /* Playback */ + {{ 0, 0, }, + { RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) }, + { RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } } + }, + /* SSI */ + /* Capture */ + {{{ RDMA_SSI_O_N(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 } }, + /* Playback */ + {{ 0, RDMA_SSI_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) } } + }, + /* SSIU */ + /* Capture */ + {{{ RDMA_SSIU_O_N(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 } }, + /* Playback */ + {{ 0, RDMA_SSIU_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) } } }, + }; + + /* it shouldn't happen */ + if (use_dvc && !use_src) + dev_err(dev, "DVC is selected without SRC\n"); + + /* use SSIU or SSI ? */ + if (is_ssi && (0 == strcmp(rsnd_mod_dma_name(mod), "ssiu"))) + is_ssi++; + + return (is_from) ? + dma_addrs[is_ssi][is_play][use_src + use_dvc].out_addr : + dma_addrs[is_ssi][is_play][use_src + use_dvc].in_addr; +} + +static dma_addr_t rsnd_dma_addr(struct rsnd_priv *priv, + struct rsnd_mod *mod, + int is_play, int is_from) +{ + /* + * gen1 uses default DMA addr + */ + if (rsnd_is_gen1(priv)) + return 0; + + if (!mod) + return 0; + + return rsnd_gen2_dma_addr(priv, mod, is_play, is_from); +} + +#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */ static void rsnd_dma_of_path(struct rsnd_dma *dma, int is_play, struct rsnd_mod **mod_from, diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index 0da04ed..0273724 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -188,116 +188,6 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, } /* - * DMA read/write register offset - * - * RSND_xxx_I_N for Audio DMAC input - * RSND_xxx_O_N for Audio DMAC output - * RSND_xxx_I_P for Audio DMAC peri peri input - * RSND_xxx_O_P for Audio DMAC peri peri output - * - * ex) R-Car H2 case - * mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out - * SSI : 0xec541000 / 0xec241008 / 0xec24100c - * SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000 - * SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000 - * CMD : 0xec500000 / / 0xec008000 0xec308000 - */ -#define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8) -#define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc) - -#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) -#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) - -#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) -#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) - -#define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i)) -#define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i)) - -#define RDMA_SRC_I_P(addr, i) (addr ##_reg - 0x00200000 + (0x400 * i)) -#define RDMA_SRC_O_P(addr, i) (addr ##_reg - 0x001fc000 + (0x400 * i)) - -#define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i)) -#define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i)) - -static dma_addr_t -rsnd_gen2_dma_addr(struct rsnd_priv *priv, - struct rsnd_mod *mod, - int is_play, int is_from) -{ - struct device *dev = rsnd_priv_to_dev(priv); - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - phys_addr_t ssi_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SSI); - phys_addr_t src_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SCU); - int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod); - int use_src = !!rsnd_io_to_mod_src(io); - int use_dvc = !!rsnd_io_to_mod_dvc(io); - int id = rsnd_mod_id(mod); - struct dma_addr { - dma_addr_t out_addr; - dma_addr_t in_addr; - } dma_addrs[3][2][3] = { - /* SRC */ - {{{ 0, 0 }, - /* Capture */ - { RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) }, - { RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } }, - /* Playback */ - {{ 0, 0, }, - { RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) }, - { RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } } - }, - /* SSI */ - /* Capture */ - {{{ RDMA_SSI_O_N(ssi, id), 0 }, - { RDMA_SSIU_O_P(ssi, id), 0 }, - { RDMA_SSIU_O_P(ssi, id), 0 } }, - /* Playback */ - {{ 0, RDMA_SSI_I_N(ssi, id) }, - { 0, RDMA_SSIU_I_P(ssi, id) }, - { 0, RDMA_SSIU_I_P(ssi, id) } } - }, - /* SSIU */ - /* Capture */ - {{{ RDMA_SSIU_O_N(ssi, id), 0 }, - { RDMA_SSIU_O_P(ssi, id), 0 }, - { RDMA_SSIU_O_P(ssi, id), 0 } }, - /* Playback */ - {{ 0, RDMA_SSIU_I_N(ssi, id) }, - { 0, RDMA_SSIU_I_P(ssi, id) }, - { 0, RDMA_SSIU_I_P(ssi, id) } } }, - }; - - /* it shouldn't happen */ - if (use_dvc && !use_src) - dev_err(dev, "DVC is selected without SRC\n"); - - /* use SSIU or SSI ? */ - if (is_ssi && (0 == strcmp(rsnd_mod_dma_name(mod), "ssiu"))) - is_ssi++; - - return (is_from) ? - dma_addrs[is_ssi][is_play][use_src + use_dvc].out_addr : - dma_addrs[is_ssi][is_play][use_src + use_dvc].in_addr; -} - -dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv, - struct rsnd_mod *mod, - int is_play, int is_from) -{ - /* - * gen1 uses default DMA addr - */ - if (rsnd_is_gen1(priv)) - return 0; - - if (!mod) - return 0; - - return rsnd_gen2_dma_addr(priv, mod, is_play, is_from); -} - -/* * Gen2 */ static int rsnd_gen2_probe(struct platform_device *pdev, diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 8a8a4d5..a73e94c 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -328,9 +328,6 @@ int rsnd_gen_probe(struct platform_device *pdev, void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg); -dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv, - struct rsnd_mod *mod, - int is_play, int is_from); phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id); #define rsnd_is_gen1(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN1) -- cgit v0.10.2 From 3c68565b6cb68b731b51eb21b59dce901002fc6e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:27:12 +0000 Subject: ASoC: rsnd: enable to care 1st / 2nd DMAC on rsnd_dma_xxx() rsnd driver needs to care about Audio DAMC (via DMAEngine), Audio DMAC peri peri (via local method) on rsnd driver. This patch adds new rsnd_dma_ops and care it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 188b463..c911c07 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -10,7 +10,7 @@ */ #include "rsnd.h" -static void rsnd_dma_complete(void *data) +static void rsnd_dmaen_complete(void *data) { struct rsnd_dma *dma = (struct rsnd_dma *)data; struct rsnd_mod *mod = rsnd_dma_to_mod(dma); @@ -32,7 +32,7 @@ static void rsnd_dma_complete(void *data) } #define DMA_NAME_SIZE 16 -static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) +static int _rsnd_dmaen_of_name(char *dma_name, struct rsnd_mod *mod) { if (mod) return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d", @@ -42,23 +42,23 @@ static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) } -static void rsnd_dma_of_name(struct rsnd_mod *mod_from, +static void rsnd_dmaen_of_name(struct rsnd_mod *mod_from, struct rsnd_mod *mod_to, char *dma_name) { int index = 0; - index = _rsnd_dma_of_name(dma_name + index, mod_from); + index = _rsnd_dmaen_of_name(dma_name + index, mod_from); *(dma_name + index++) = '_'; - index = _rsnd_dma_of_name(dma_name + index, mod_to); + index = _rsnd_dmaen_of_name(dma_name + index, mod_to); } -void rsnd_dma_stop(struct rsnd_dma *dma) +static void rsnd_dmaen_stop(struct rsnd_dma *dma) { dmaengine_terminate_all(dma->chan); } -void rsnd_dma_start(struct rsnd_dma *dma) +static void rsnd_dmaen_start(struct rsnd_dma *dma) { struct rsnd_mod *mod = rsnd_dma_to_mod(dma); struct rsnd_priv *priv = rsnd_mod_to_priv(mod); @@ -80,7 +80,7 @@ void rsnd_dma_start(struct rsnd_dma *dma) return; } - desc->callback = rsnd_dma_complete; + desc->callback = rsnd_dmaen_complete; desc->callback_param = dma; if (dmaengine_submit(desc) < 0) { @@ -91,22 +91,12 @@ void rsnd_dma_start(struct rsnd_dma *dma) dma_async_issue_pending(dma->chan); } -static void rsnd_dma_of_path(struct rsnd_dma *dma, - int is_play, - struct rsnd_mod **mod_from, - struct rsnd_mod **mod_to); - -static dma_addr_t rsnd_dma_addr(struct rsnd_priv *priv, - struct rsnd_mod *mod, - int is_play, int is_from); - -int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) +static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) { struct device *dev = rsnd_priv_to_dev(priv); struct dma_slave_config cfg = {}; struct rsnd_mod *mod = rsnd_dma_to_mod(dma); - struct rsnd_mod *mod_from; - struct rsnd_mod *mod_to; struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); int is_play = rsnd_io_is_play(io); char dma_name[DMA_NAME_SIZE]; @@ -121,12 +111,11 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); - rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to); - rsnd_dma_of_name(mod_from, mod_to, dma_name); + rsnd_dmaen_of_name(mod_from, mod_to, dma_name); cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - cfg.src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1); - cfg.dst_addr = rsnd_dma_addr(priv, mod_to, is_play, 0); + cfg.src_addr = dma->src_addr; + cfg.dst_addr = dma->dst_addr; cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; @@ -163,7 +152,7 @@ rsnd_dma_channel_err: return -EAGAIN; } -void rsnd_dma_quit(struct rsnd_dma *dma) +static void rsnd_dmaen_quit(struct rsnd_dma *dma) { if (dma->chan) dma_release_channel(dma->chan); @@ -171,6 +160,13 @@ void rsnd_dma_quit(struct rsnd_dma *dma) dma->chan = NULL; } +static struct rsnd_dma_ops rsnd_dmaen_ops = { + .start = rsnd_dmaen_start, + .stop = rsnd_dmaen_stop, + .init = rsnd_dmaen_init, + .quit = rsnd_dmaen_quit, +}; + /* * DMA read/write register offset * @@ -343,3 +339,35 @@ static void rsnd_dma_of_path(struct rsnd_dma *dma, } } +void rsnd_dma_stop(struct rsnd_dma *dma) +{ + dma->ops->stop(dma); +} + +void rsnd_dma_start(struct rsnd_dma *dma) +{ + dma->ops->start(dma); +} + +void rsnd_dma_quit(struct rsnd_dma *dma) +{ + dma->ops->quit(dma); +} + +int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) +{ + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_mod *mod_from; + struct rsnd_mod *mod_to; + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + int is_play = rsnd_io_is_play(io); + + rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to); + + dma->src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1); + dma->dst_addr = rsnd_dma_addr(priv, mod_to, is_play, 0); + + dma->ops = &rsnd_dmaen_ops; + + return dma->ops->init(priv, dma, id, mod_from, mod_to); +} diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index a73e94c..c7299f7 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -170,10 +170,22 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod); /* * R-Car DMA */ +struct rsnd_dma; +struct rsnd_dma_ops { + void (*start)(struct rsnd_dma *dma); + void (*stop)(struct rsnd_dma *dma); + int (*init)(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to); + void (*quit)(struct rsnd_dma *dma); +}; + struct rsnd_dma { struct dma_chan *chan; + struct rsnd_dma_ops *ops; enum dma_transfer_direction dir; dma_addr_t addr; + dma_addr_t src_addr; + dma_addr_t dst_addr; }; void rsnd_dma_start(struct rsnd_dma *dma); -- cgit v0.10.2 From 288f392e729dd4d3719c2319c7c3f8d4c820488b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:27:42 +0000 Subject: ASoC: rsnd: add Audio DMAC peri peri support rework Renesas R-Car sound (= rsnd) needs 2 DMAC which are called as Audio DMAC (= 1st DMAC) and Audio DMAC peri peri (2nd DMAC). And rsnd had assumed that 1st / 2nd DMACs are implemented as DMAEngine. But, in result of DMA ML discussion, 2nd DMAC was concluded that it is not a general purpose DMAC (2nd DMAC is for Device to Device inside sound system). Additionally, current DMAEngine can't support Device to Device, and we don't have correct DT bindings for it at this point. So the easiest solution for it is that move it from DMAEngine to rsnd driver. Audio DMAC peri peri is controlled from sound driver without DMAEngine by this patch. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 9beea9b..3b6e219 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -974,6 +974,7 @@ static int rsnd_probe(struct platform_device *pdev) const struct rsnd_of_data *of_data, struct rsnd_priv *priv) = { rsnd_gen_probe, + rsnd_dma_probe, rsnd_ssi_probe, rsnd_src_probe, rsnd_dvc_probe, diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index c911c07..a01bb8c 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -8,8 +8,29 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#include #include "rsnd.h" +/* + * Audio DMAC peri peri register + */ +#define PDMASAR 0x00 +#define PDMADAR 0x04 +#define PDMACHCR 0x0c + +/* PDMACHCR */ +#define PDMACHCR_DE (1 << 0) + +struct rsnd_dma_ctrl { + void __iomem *base; + int dmapp_num; +}; + +#define rsnd_priv_to_dmac(p) ((struct rsnd_dma_ctrl *)(p)->dma) + +/* + * Audio DMAC + */ static void rsnd_dmaen_complete(void *data) { struct rsnd_dma *dma = (struct rsnd_dma *)data; @@ -108,6 +129,8 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, return -EIO; } + dev_dbg(dev, "Audio DMAC init\n"); + dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); @@ -168,6 +191,150 @@ static struct rsnd_dma_ops rsnd_dmaen_ops = { }; /* + * Audio DMAC peri peri + */ +static const u8 gen2_id_table_ssiu[] = { + 0x00, /* SSI00 */ + 0x04, /* SSI10 */ + 0x08, /* SSI20 */ + 0x0c, /* SSI3 */ + 0x0d, /* SSI4 */ + 0x0e, /* SSI5 */ + 0x0f, /* SSI6 */ + 0x10, /* SSI7 */ + 0x11, /* SSI8 */ + 0x12, /* SSI90 */ +}; +static const u8 gen2_id_table_scu[] = { + 0x2d, /* SCU_SRCI0 */ + 0x2e, /* SCU_SRCI1 */ + 0x2f, /* SCU_SRCI2 */ + 0x30, /* SCU_SRCI3 */ + 0x31, /* SCU_SRCI4 */ + 0x32, /* SCU_SRCI5 */ + 0x33, /* SCU_SRCI6 */ + 0x34, /* SCU_SRCI7 */ + 0x35, /* SCU_SRCI8 */ + 0x36, /* SCU_SRCI9 */ +}; +static const u8 gen2_id_table_cmd[] = { + 0x37, /* SCU_CMD0 */ + 0x38, /* SCU_CMD1 */ +}; + +static u32 rsnd_dmapp_get_id(struct rsnd_mod *mod) +{ + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + const u8 *entry = NULL; + int id = rsnd_mod_id(mod); + int size = 0; + + if (mod == ssi) { + entry = gen2_id_table_ssiu; + size = ARRAY_SIZE(gen2_id_table_ssiu); + } else if (mod == src) { + entry = gen2_id_table_scu; + size = ARRAY_SIZE(gen2_id_table_scu); + } else if (mod == dvc) { + entry = gen2_id_table_cmd; + size = ARRAY_SIZE(gen2_id_table_cmd); + } + + if (!entry) + return 0xFF; + + if (size <= id) + return 0xFF; + + return entry[id]; +} + +static u32 rsnd_dmapp_get_chcr(struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to) +{ + return (rsnd_dmapp_get_id(mod_from) << 24) + + (rsnd_dmapp_get_id(mod_to) << 16); +} + +#define rsnd_dmapp_addr(dmac, dma, reg) \ + (dmac->base + 0x20 + (0x10 * dma->dmapp_id) + reg) +static void rsnd_dmapp_write(struct rsnd_dma *dma, u32 data, u32 reg) +{ + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + + dev_dbg(dev, "w %p : %08x\n", rsnd_dmapp_addr(dmac, dma, reg), data); + + iowrite32(data, rsnd_dmapp_addr(dmac, dma, reg)); +} + +static u32 rsnd_dmapp_read(struct rsnd_dma *dma, u32 reg) +{ + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + + return ioread32(rsnd_dmapp_addr(dmac, dma, reg)); +} + +static void rsnd_dmapp_stop(struct rsnd_dma *dma) +{ + int i; + + rsnd_dmapp_write(dma, 0, PDMACHCR); + + for (i = 0; i < 1024; i++) { + if (0 == rsnd_dmapp_read(dma, PDMACHCR)) + return; + udelay(1); + } +} + +static void rsnd_dmapp_start(struct rsnd_dma *dma) +{ + rsnd_dmapp_write(dma, dma->src_addr, PDMASAR); + rsnd_dmapp_write(dma, dma->dst_addr, PDMADAR); + rsnd_dmapp_write(dma, dma->chcr, PDMACHCR); +} + +static int rsnd_dmapp_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) +{ + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + + dev_dbg(dev, "Audio DMAC peri peri init\n"); + + dma->dmapp_id = dmac->dmapp_num; + dma->chcr = rsnd_dmapp_get_chcr(mod_from, mod_to) | PDMACHCR_DE; + + dmac->dmapp_num++; + + rsnd_dmapp_stop(dma); + + dev_dbg(dev, "id/src/dst/chcr = %d/%x/%x/%08x\n", + dma->dmapp_id, dma->src_addr, dma->dst_addr, dma->chcr); + + return 0; +} + +static struct rsnd_dma_ops rsnd_dmapp_ops = { + .start = rsnd_dmapp_start, + .stop = rsnd_dmapp_stop, + .init = rsnd_dmapp_init, + .quit = rsnd_dmapp_stop, +}; + +/* + * Common DMAC Interface + */ + +/* * DMA read/write register offset * * RSND_xxx_I_N for Audio DMAC input @@ -367,7 +534,49 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) dma->src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1); dma->dst_addr = rsnd_dma_addr(priv, mod_to, is_play, 0); - dma->ops = &rsnd_dmaen_ops; + /* for Gen2 */ + if (mod_from && mod_to) + dma->ops = &rsnd_dmapp_ops; + else + dma->ops = &rsnd_dmaen_ops; + + /* for Gen1, overwrite */ + if (rsnd_is_gen1(priv)) + dma->ops = &rsnd_dmaen_ops; return dma->ops->init(priv, dma, id, mod_from, mod_to); } + +int rsnd_dma_probe(struct platform_device *pdev, + const struct rsnd_of_data *of_data, + struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dma_ctrl *dmac; + struct resource *res; + + /* + * for Gen1 + */ + if (rsnd_is_gen1(priv)) + return 0; + + /* + * for Gen2 + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audmapp"); + dmac = devm_kzalloc(dev, sizeof(*dmac), GFP_KERNEL); + if (!dmac || !res) { + dev_err(dev, "dma allocate failed\n"); + return -ENOMEM; + } + + dmac->dmapp_num = 0; + dmac->base = devm_ioremap_resource(dev, res); + if (IS_ERR(dmac->base)) + return PTR_ERR(dmac->base); + + priv->dma = dmac; + + return 0; +} diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index c7299f7..9e67142 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -184,6 +184,8 @@ struct rsnd_dma { struct rsnd_dma_ops *ops; enum dma_transfer_direction dir; dma_addr_t addr; + int dmapp_id; + u32 chcr; dma_addr_t src_addr; dma_addr_t dst_addr; }; @@ -192,7 +194,9 @@ void rsnd_dma_start(struct rsnd_dma *dma); void rsnd_dma_stop(struct rsnd_dma *dma); int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id); void rsnd_dma_quit(struct rsnd_dma *dma); - +int rsnd_dma_probe(struct platform_device *pdev, + const struct rsnd_of_data *of_data, + struct rsnd_priv *priv); #define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma) @@ -396,6 +400,11 @@ struct rsnd_priv { void *adg; /* + * below value will be filled on rsnd_dma_probe() + */ + void *dma; + + /* * below value will be filled on rsnd_ssi_probe() */ void *ssi; -- cgit v0.10.2 From 56f2906ae2d0b48b64a67feef99e3be3b40c3617 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:28:07 +0000 Subject: ASoC: rsnd: remove rsnd_dma::addr DMAEngine for Renesas R-Car driver is used only for Audio DMAC now. rsnd_dma::addr was added to support Audio DMAC peri peri, but it is no longer needed. Let's remove it Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index a01bb8c..c407fd2 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -89,7 +89,6 @@ static void rsnd_dmaen_start(struct rsnd_dma *dma) struct dma_async_tx_descriptor *desc; desc = dmaengine_prep_dma_cyclic(dma->chan, - (dma->addr) ? dma->addr : substream->runtime->dma_addr, snd_pcm_lib_buffer_bytes(substream), snd_pcm_lib_period_bytes(substream), @@ -157,7 +156,6 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, if (ret < 0) goto rsnd_dma_init_err; - dma->addr = is_play ? cfg.src_addr : cfg.dst_addr; dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; return 0; diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 9e67142..a295491 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -183,7 +183,6 @@ struct rsnd_dma { struct dma_chan *chan; struct rsnd_dma_ops *ops; enum dma_transfer_direction dir; - dma_addr_t addr; int dmapp_id; u32 chcr; dma_addr_t src_addr; -- cgit v0.10.2 From aaf4fce019ecd55a2d93b6111525deeef300f751 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:28:32 +0000 Subject: ASoC: rsnd: remove rsnd_dma::dir DMAEngine direction can be calculated from rsnd_dai_stream, So, rsnd_dma::dir does not make sense now. Let's remove it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index c407fd2..3f1ea58 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -87,12 +87,13 @@ static void rsnd_dmaen_start(struct rsnd_dma *dma) struct snd_pcm_substream *substream = io->substream; struct device *dev = rsnd_priv_to_dev(priv); struct dma_async_tx_descriptor *desc; + int is_play = rsnd_io_is_play(io); desc = dmaengine_prep_dma_cyclic(dma->chan, substream->runtime->dma_addr, snd_pcm_lib_buffer_bytes(substream), snd_pcm_lib_period_bytes(substream), - dma->dir, + is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc) { @@ -156,8 +157,6 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, if (ret < 0) goto rsnd_dma_init_err; - dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - return 0; rsnd_dma_init_err: diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index a295491..5d65a4b 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -182,7 +182,6 @@ struct rsnd_dma_ops { struct rsnd_dma { struct dma_chan *chan; struct rsnd_dma_ops *ops; - enum dma_transfer_direction dir; int dmapp_id; u32 chcr; dma_addr_t src_addr; -- cgit v0.10.2 From 0d00a52182be985bfae67d407ee81fefe448a0fd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:29:13 +0000 Subject: ASoC: rsnd: use union with rsnd_dmaen / rsnd_dmapp Renesas R-Car needs 2 DMACs.1st DMAC is DMAEngine, and 2nd DMAC is implemented as local code. These 2 DMACs are never shared. We can use union for these. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 3f1ea58..b449763 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -76,11 +76,14 @@ static void rsnd_dmaen_of_name(struct rsnd_mod *mod_from, static void rsnd_dmaen_stop(struct rsnd_dma *dma) { - dmaengine_terminate_all(dma->chan); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + + dmaengine_terminate_all(dmaen->chan); } static void rsnd_dmaen_start(struct rsnd_dma *dma) { + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); struct rsnd_mod *mod = rsnd_dma_to_mod(dma); struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); @@ -89,7 +92,7 @@ static void rsnd_dmaen_start(struct rsnd_dma *dma) struct dma_async_tx_descriptor *desc; int is_play = rsnd_io_is_play(io); - desc = dmaengine_prep_dma_cyclic(dma->chan, + desc = dmaengine_prep_dma_cyclic(dmaen->chan, substream->runtime->dma_addr, snd_pcm_lib_buffer_bytes(substream), snd_pcm_lib_period_bytes(substream), @@ -109,12 +112,13 @@ static void rsnd_dmaen_start(struct rsnd_dma *dma) return; } - dma_async_issue_pending(dma->chan); + dma_async_issue_pending(dmaen->chan); } static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) { + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); struct device *dev = rsnd_priv_to_dev(priv); struct dma_slave_config cfg = {}; struct rsnd_mod *mod = rsnd_dma_to_mod(dma); @@ -124,7 +128,7 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, dma_cap_mask_t mask; int ret; - if (dma->chan) { + if (dmaen->chan) { dev_err(dev, "it already has dma channel\n"); return -EIO; } @@ -145,15 +149,15 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, dev_dbg(dev, "dma : %s %pad -> %pad\n", dma_name, &cfg.src_addr, &cfg.dst_addr); - dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, + dmaen->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, (void *)id, dev, dma_name); - if (!dma->chan) { + if (!dmaen->chan) { dev_err(dev, "can't get dma channel\n"); goto rsnd_dma_channel_err; } - ret = dmaengine_slave_config(dma->chan, &cfg); + ret = dmaengine_slave_config(dmaen->chan, &cfg); if (ret < 0) goto rsnd_dma_init_err; @@ -174,10 +178,12 @@ rsnd_dma_channel_err: static void rsnd_dmaen_quit(struct rsnd_dma *dma) { - if (dma->chan) - dma_release_channel(dma->chan); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + + if (dmaen->chan) + dma_release_channel(dmaen->chan); - dma->chan = NULL; + dmaen->chan = NULL; } static struct rsnd_dma_ops rsnd_dmaen_ops = { @@ -257,7 +263,8 @@ static u32 rsnd_dmapp_get_chcr(struct rsnd_mod *mod_from, } #define rsnd_dmapp_addr(dmac, dma, reg) \ - (dmac->base + 0x20 + (0x10 * dma->dmapp_id) + reg) + (dmac->base + 0x20 + reg + \ + (0x10 * rsnd_dma_to_dmapp(dma)->dmapp_id)) static void rsnd_dmapp_write(struct rsnd_dma *dma, u32 data, u32 reg) { struct rsnd_mod *mod = rsnd_dma_to_mod(dma); @@ -294,28 +301,31 @@ static void rsnd_dmapp_stop(struct rsnd_dma *dma) static void rsnd_dmapp_start(struct rsnd_dma *dma) { + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); + rsnd_dmapp_write(dma, dma->src_addr, PDMASAR); rsnd_dmapp_write(dma, dma->dst_addr, PDMADAR); - rsnd_dmapp_write(dma, dma->chcr, PDMACHCR); + rsnd_dmapp_write(dma, dmapp->chcr, PDMACHCR); } static int rsnd_dmapp_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) { + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); struct device *dev = rsnd_priv_to_dev(priv); dev_dbg(dev, "Audio DMAC peri peri init\n"); - dma->dmapp_id = dmac->dmapp_num; - dma->chcr = rsnd_dmapp_get_chcr(mod_from, mod_to) | PDMACHCR_DE; + dmapp->dmapp_id = dmac->dmapp_num; + dmapp->chcr = rsnd_dmapp_get_chcr(mod_from, mod_to) | PDMACHCR_DE; dmac->dmapp_num++; rsnd_dmapp_stop(dma); dev_dbg(dev, "id/src/dst/chcr = %d/%x/%x/%08x\n", - dma->dmapp_id, dma->src_addr, dma->dst_addr, dma->chcr); + dmapp->dmapp_id, dma->src_addr, dma->dst_addr, dmapp->chcr); return 0; } diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 5d65a4b..0d36e38 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -179,14 +179,26 @@ struct rsnd_dma_ops { void (*quit)(struct rsnd_dma *dma); }; -struct rsnd_dma { +struct rsnd_dmaen { struct dma_chan *chan; - struct rsnd_dma_ops *ops; +}; + +struct rsnd_dmapp { int dmapp_id; u32 chcr; +}; + +struct rsnd_dma { + struct rsnd_dma_ops *ops; dma_addr_t src_addr; dma_addr_t dst_addr; + union { + struct rsnd_dmaen en; + struct rsnd_dmapp pp; + } dma; }; +#define rsnd_dma_to_dmaen(dma) (&(dma)->dma.en) +#define rsnd_dma_to_dmapp(dma) (&(dma)->dma.pp) void rsnd_dma_start(struct rsnd_dma *dma); void rsnd_dma_stop(struct rsnd_dma *dma); -- cgit v0.10.2 From e879a9ddf41c47ccc83039e99e04b0d5c56cd0c5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:29:41 +0000 Subject: ASoC: rsnd: enable rsnd_ssi_use_busif() for DMA Renesas R-Car sound driver uses SSI, but the DMA interfaces are SSI/SSIU. This interface is based on SSI/SRC/DVC connection. And DMA function needs to know which interface is used somehow. This patch enables rsnd_ssi_use_busif() for DMA. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index b449763..3f34461 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -427,7 +427,7 @@ rsnd_gen2_dma_addr(struct rsnd_priv *priv, dev_err(dev, "DVC is selected without SRC\n"); /* use SSIU or SSI ? */ - if (is_ssi && (0 == strcmp(rsnd_mod_dma_name(mod), "ssiu"))) + if (is_ssi && rsnd_ssi_use_busif(mod)) is_ssi++; return (is_from) ? diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 0d36e38..68bc3f4 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -514,6 +514,7 @@ int rsnd_ssi_probe(struct platform_device *pdev, struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id); int rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod); int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod); +int rsnd_ssi_use_busif(struct rsnd_mod *mod); /* * R-Car DVC diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index a0d902a..7e48d56 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -84,7 +84,7 @@ struct rsnd_ssi { #define rsnd_ssi_mode_flags(p) ((p)->info->flags) #define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id) -static int rsnd_ssi_use_busif(struct rsnd_mod *mod) +int rsnd_ssi_use_busif(struct rsnd_mod *mod) { struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); -- cgit v0.10.2 From 04e627baa68a8dc42f19b68e1b46d1c6aecebfd9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:30:02 +0000 Subject: ASoC: rsnd: ssi: add rsnd_ssi_of_node() This patch adds rsnd_ssi_of_node() to get SSI subnode from DT. This is prepare for new DT bindings for 1st DMAC Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 7e48d56..2133eb3 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -83,6 +83,8 @@ struct rsnd_ssi { #define rsnd_ssi_clk_from_parent(ssi) ((ssi)->parent) #define rsnd_ssi_mode_flags(p) ((p)->info->flags) #define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id) +#define rsnd_ssi_of_node(priv) \ + of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,ssi") int rsnd_ssi_use_busif(struct rsnd_mod *mod) { @@ -633,7 +635,7 @@ static void rsnd_of_parse_ssi(struct platform_device *pdev, if (!of_data) return; - node = of_get_child_by_name(dev->of_node, "rcar_sound,ssi"); + node = rsnd_ssi_of_node(priv); if (!node) return; -- cgit v0.10.2 From 82e76ed38edbdb338d64f5f2486fcd1482c8859a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:30:22 +0000 Subject: ASoC: rsnd: src: add rsnd_src_of_node() This patch adds rsnd_src_of_node() to get SRC subnode from DT. This is prepare for new DT bindings for 1st DMAC Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index e279205..5a601be 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -28,6 +28,9 @@ struct rsnd_src { #define RSND_SRC_NAME_SIZE 16 #define rsnd_src_convert_rate(p) ((p)->info->convert_rate) +#define rsnd_src_of_node(priv) \ + of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,src") + #define rsnd_mod_to_src(_mod) \ container_of((_mod), struct rsnd_src, mod) @@ -807,7 +810,7 @@ static void rsnd_of_parse_src(struct platform_device *pdev, if (!of_data) return; - src_node = of_get_child_by_name(dev->of_node, "rcar_sound,src"); + src_node = rsnd_src_of_node(priv); if (!src_node) return; -- cgit v0.10.2 From 93b986e246248d0587acb4f073a621179a16b763 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:30:41 +0000 Subject: ASoC: rsnd: dvc: add rsnd_dvc_of_node() This patch adds rsnd_dvc_of_node() to get DVC subnode from DT. This is prepare for new DT bindings for 1st DMAC Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index d7f9ed9..e099018 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -24,6 +24,9 @@ struct rsnd_dvc { struct rsnd_kctrl_cfg_s rdown; /* Ramp Rate Down */ }; +#define rsnd_dvc_of_node(priv) \ + of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,dvc") + #define rsnd_mod_to_dvc(_mod) \ container_of((_mod), struct rsnd_dvc, mod) -- cgit v0.10.2 From 72adc61f4637aa3596c1db1129f84d768475a885 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:31:23 +0000 Subject: ASoC: rsnd: 1st DMAC dma-names cares subnode Renesas R-Car sound (= rsnd) needs 2 DMAC which are called as Audio DMAC (= 1st DMAC) and Audio DMAC peri peri (2nd DMAC). And rsnd had assumed that 1st / 2nd DMACs are implemented as DMAEngine. But, in result of DMA ML discussion, 2nd DMAC was concluded that it is not a general purpose DMAC (2nd DMAC is for Device to Device inside sound system). Additionally, current DMAEngine can't support Device to Device, and we don't have correct DT bindings for it at this point. So the easiest solution for it is that move it from DMAEngine to rsnd driver. dma-names on DT was implemented as no difference between 1st / 2nd DMAC's, since rsnd had assumed that both DMACs are implemented as DMAEngine. That style was "src_dst". But now, 2nd DMAC was implemented as non DMAEngine, and it doesn't need dma-names anymore. So, this dma-names rule is no longer needed. And additionally, dma-names was assumed that it has all (= SSI/SSIU/SRC/DVC) nodes under sound node. In upstream code, no SoC/platform is supporting DMA for rsnd driver yet. This means there is no compatible issue if this patch changes dma-names's rule of DT. This patch assumes dma-names for 1st DMAC are tx/rx base, and listed in each SSI/SRC/DVC subnode ex) rcar_sound,dvc { dvc0: dvc@0 { dmas = <&audma0 0xbc>; dma-names = "tx"; }; ... rcar_sound,src { src0: src@0 { ... dmas = <&audma0 0x85>, <&audma1 0x9a>; dma-names = "rx", "tx"; }; ... rcar_sound,ssi { ssi0: ssi@0 { ... dmas = <&audma0 0x01>, <&audma1 0x02>, <&audma0 0x15>, <&audma1 0x16>; dma-names = "rx", "tx", "rxu", "txu"; }; ... Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 3b6e219..7b995f0 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -137,15 +137,12 @@ char *rsnd_mod_name(struct rsnd_mod *mod) return mod->ops->name; } -char *rsnd_mod_dma_name(struct rsnd_mod *mod) +struct dma_chan *rsnd_mod_dma_req(struct rsnd_mod *mod) { - if (!mod || !mod->ops) - return "unknown"; - - if (!mod->ops->dma_name) - return mod->ops->name; + if (!mod || !mod->ops || !mod->ops->dma_req) + return NULL; - return mod->ops->dma_name(mod); + return mod->ops->dma_req(mod); } void rsnd_mod_init(struct rsnd_mod *mod, diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 3f34461..92fd550 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -9,6 +9,7 @@ * published by the Free Software Foundation. */ #include +#include #include "rsnd.h" /* @@ -52,28 +53,6 @@ static void rsnd_dmaen_complete(void *data) rsnd_dai_pointer_update(io, io->byte_per_period); } -#define DMA_NAME_SIZE 16 -static int _rsnd_dmaen_of_name(char *dma_name, struct rsnd_mod *mod) -{ - if (mod) - return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d", - rsnd_mod_dma_name(mod), rsnd_mod_id(mod)); - else - return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem"); - -} - -static void rsnd_dmaen_of_name(struct rsnd_mod *mod_from, - struct rsnd_mod *mod_to, - char *dma_name) -{ - int index = 0; - - index = _rsnd_dmaen_of_name(dma_name + index, mod_from); - *(dma_name + index++) = '_'; - index = _rsnd_dmaen_of_name(dma_name + index, mod_to); -} - static void rsnd_dmaen_stop(struct rsnd_dma *dma) { struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); @@ -115,6 +94,40 @@ static void rsnd_dmaen_start(struct rsnd_dma *dma) dma_async_issue_pending(dmaen->chan); } +struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node, + struct rsnd_mod *mod, char *name) +{ + struct dma_chan *chan; + struct device_node *np; + int i = 0; + + for_each_child_of_node(of_node, np) { + if (i == rsnd_mod_id(mod)) + break; + i++; + } + + chan = of_dma_request_slave_channel(np, name); + + of_node_put(np); + of_node_put(of_node); + + return chan; +} + +static struct dma_chan *rsnd_dmaen_request_channel(struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to) +{ + if ((!mod_from && !mod_to) || + (mod_from && mod_to)) + return NULL; + + if (mod_from) + return rsnd_mod_dma_req(mod_from); + else + return rsnd_mod_dma_req(mod_to); +} + static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) { @@ -124,8 +137,6 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, struct rsnd_mod *mod = rsnd_dma_to_mod(dma); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); int is_play = rsnd_io_is_play(io); - char dma_name[DMA_NAME_SIZE]; - dma_cap_mask_t mask; int ret; if (dmaen->chan) { @@ -135,10 +146,21 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, dev_dbg(dev, "Audio DMAC init\n"); - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); + if (dev->of_node) { + dmaen->chan = rsnd_dmaen_request_channel(mod_from, mod_to); + } else { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); - rsnd_dmaen_of_name(mod_from, mod_to, dma_name); + dmaen->chan = dma_request_channel(mask, shdma_chan_filter, + (void *)id); + } + if (IS_ERR_OR_NULL(dmaen->chan)) { + dev_err(dev, "can't get dma channel\n"); + goto rsnd_dma_channel_err; + } cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; cfg.src_addr = dma->src_addr; @@ -146,16 +168,8 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - dev_dbg(dev, "dma : %s %pad -> %pad\n", - dma_name, &cfg.src_addr, &cfg.dst_addr); - - dmaen->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, - (void *)id, dev, - dma_name); - if (!dmaen->chan) { - dev_err(dev, "can't get dma channel\n"); - goto rsnd_dma_channel_err; - } + dev_dbg(dev, "dma : %pad -> %pad\n", + &cfg.src_addr, &cfg.dst_addr); ret = dmaengine_slave_config(dmaen->chan, &cfg); if (ret < 0) diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index e099018..aeeef13 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -272,8 +272,17 @@ static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, return 0; } +static struct dma_chan *rsnd_dvc_dma_req(struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + + return rsnd_dma_request_channel(rsnd_dvc_of_node(priv), + mod, "tx"); +} + static struct rsnd_mod_ops rsnd_dvc_ops = { .name = DVC_NAME, + .dma_req = rsnd_dvc_dma_req, .probe = rsnd_dvc_probe_gen2, .remove = rsnd_dvc_remove_gen2, .init = rsnd_dvc_init, diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 68bc3f4..52c401c 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -207,6 +207,8 @@ void rsnd_dma_quit(struct rsnd_dma *dma); int rsnd_dma_probe(struct platform_device *pdev, const struct rsnd_of_data *of_data, struct rsnd_priv *priv); +struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node, + struct rsnd_mod *mod, char *name); #define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma) @@ -222,7 +224,7 @@ enum rsnd_mod_type { struct rsnd_mod_ops { char *name; - char* (*dma_name)(struct rsnd_mod *mod); + struct dma_chan* (*dma_req)(struct rsnd_mod *mod); int (*probe)(struct rsnd_mod *mod, struct rsnd_priv *priv); int (*remove)(struct rsnd_mod *mod, @@ -292,7 +294,7 @@ void rsnd_mod_init(struct rsnd_mod *mod, enum rsnd_mod_type type, int id); char *rsnd_mod_name(struct rsnd_mod *mod); -char *rsnd_mod_dma_name(struct rsnd_mod *mod); +struct dma_chan *rsnd_mod_dma_req(struct rsnd_mod *mod); /* * R-Car sound DAI diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 5a601be..6ce8985 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -114,6 +114,17 @@ struct rsnd_src { /* * Gen1/Gen2 common functions */ +static struct dma_chan *rsnd_src_dma_req(struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + int is_play = rsnd_io_is_play(io); + + return rsnd_dma_request_channel(rsnd_src_of_node(priv), + mod, + is_play ? "rx" : "tx"); +} + int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, int use_busif) { @@ -506,6 +517,7 @@ static int rsnd_src_stop_gen1(struct rsnd_mod *mod, static struct rsnd_mod_ops rsnd_src_gen1_ops = { .name = SRC_NAME, + .dma_req = rsnd_src_dma_req, .probe = rsnd_src_probe_gen1, .init = rsnd_src_init_gen1, .quit = rsnd_src_quit, @@ -780,6 +792,7 @@ static int rsnd_src_stop_gen2(struct rsnd_mod *mod, static struct rsnd_mod_ops rsnd_src_gen2_ops = { .name = SRC_NAME, + .dma_req = rsnd_src_dma_req, .probe = rsnd_src_probe_gen2, .remove = rsnd_src_remove_gen2, .init = rsnd_src_init_gen2, diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 2133eb3..fea4aa5 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -553,14 +553,25 @@ static int rsnd_ssi_dma_stop(struct rsnd_mod *mod, return 0; } -static char *rsnd_ssi_dma_name(struct rsnd_mod *mod) +static struct dma_chan *rsnd_ssi_dma_req(struct rsnd_mod *mod) { - return rsnd_ssi_use_busif(mod) ? "ssiu" : SSI_NAME; + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + int is_play = rsnd_io_is_play(io); + char *name; + + if (rsnd_ssi_use_busif(mod)) + name = is_play ? "rxu" : "txu"; + else + name = is_play ? "rx" : "tx"; + + return rsnd_dma_request_channel(rsnd_ssi_of_node(priv), + mod, name); } static struct rsnd_mod_ops rsnd_ssi_dma_ops = { .name = SSI_NAME, - .dma_name = rsnd_ssi_dma_name, + .dma_req = rsnd_ssi_dma_req, .probe = rsnd_ssi_dma_probe, .remove = rsnd_ssi_dma_remove, .init = rsnd_ssi_init, -- cgit v0.10.2 From 5cf4f68672856dcbca883b460aee4ee5bfbeafb0 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:31:55 +0000 Subject: ASoC: rsnd: add sample code of rcar_sound,src irq cfcefe01265cbaf5ca7209226d043b07bfa8b587 (ASoC: rsnd: add recovery support for under/over flow error on SRC) added SRC recovery support which needs IRQ. This patch adds SRC irq sample code on DT bingdings text Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt index 2dd690b..87e0fc2 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt @@ -53,16 +53,36 @@ rcar_sound: rcar_sound@ec500000 { }; rcar_sound,src { - src0: src@0 { }; - src1: src@1 { }; - src2: src@2 { }; - src3: src@3 { }; - src4: src@4 { }; - src5: src@5 { }; - src6: src@6 { }; - src7: src@7 { }; - src8: src@8 { }; - src9: src@9 { }; + src0: src@0 { + interrupts = <0 352 IRQ_TYPE_LEVEL_HIGH>; + }; + src1: src@1 { + interrupts = <0 353 IRQ_TYPE_LEVEL_HIGH>; + }; + src2: src@2 { + interrupts = <0 354 IRQ_TYPE_LEVEL_HIGH>; + }; + src3: src@3 { + interrupts = <0 355 IRQ_TYPE_LEVEL_HIGH>; + }; + src4: src@4 { + interrupts = <0 356 IRQ_TYPE_LEVEL_HIGH>; + }; + src5: src@5 { + interrupts = <0 357 IRQ_TYPE_LEVEL_HIGH>; + }; + src6: src@6 { + interrupts = <0 358 IRQ_TYPE_LEVEL_HIGH>; + }; + src7: src@7 { + interrupts = <0 359 IRQ_TYPE_LEVEL_HIGH>; + }; + src8: src@8 { + interrupts = <0 360 IRQ_TYPE_LEVEL_HIGH>; + }; + src9: src@9 { + interrupts = <0 361 IRQ_TYPE_LEVEL_HIGH>; + }; }; rcar_sound,ssi { -- cgit v0.10.2 From bb02714fe5f198c0f07a8039ebcc636c1bffbc03 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:32:15 +0000 Subject: ASoC: rsnd: add sample code of missing clocks Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt index 87e0fc2..503967b 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt @@ -47,6 +47,27 @@ rcar_sound: rcar_sound@ec500000 { <0 0xec540000 0 0x1000>, /* SSIU */ <0 0xec541000 0 0x1280>; /* SSI */ + clocks = <&mstp10_clks R8A7790_CLK_SSI_ALL>, + <&mstp10_clks R8A7790_CLK_SSI9>, <&mstp10_clks R8A7790_CLK_SSI8>, + <&mstp10_clks R8A7790_CLK_SSI7>, <&mstp10_clks R8A7790_CLK_SSI6>, + <&mstp10_clks R8A7790_CLK_SSI5>, <&mstp10_clks R8A7790_CLK_SSI4>, + <&mstp10_clks R8A7790_CLK_SSI3>, <&mstp10_clks R8A7790_CLK_SSI2>, + <&mstp10_clks R8A7790_CLK_SSI1>, <&mstp10_clks R8A7790_CLK_SSI0>, + <&mstp10_clks R8A7790_CLK_SCU_SRC9>, <&mstp10_clks R8A7790_CLK_SCU_SRC8>, + <&mstp10_clks R8A7790_CLK_SCU_SRC7>, <&mstp10_clks R8A7790_CLK_SCU_SRC6>, + <&mstp10_clks R8A7790_CLK_SCU_SRC5>, <&mstp10_clks R8A7790_CLK_SCU_SRC4>, + <&mstp10_clks R8A7790_CLK_SCU_SRC3>, <&mstp10_clks R8A7790_CLK_SCU_SRC2>, + <&mstp10_clks R8A7790_CLK_SCU_SRC1>, <&mstp10_clks R8A7790_CLK_SCU_SRC0>, + <&mstp10_clks R8A7790_CLK_SCU_DVC0>, <&mstp10_clks R8A7790_CLK_SCU_DVC1>, + <&audio_clk_a>, <&audio_clk_b>, <&audio_clk_c>, <&m2_clk>; + clock-names = "ssi-all", + "ssi.9", "ssi.8", "ssi.7", "ssi.6", "ssi.5", + "ssi.4", "ssi.3", "ssi.2", "ssi.1", "ssi.0", + "src.9", "src.8", "src.7", "src.6", "src.5", + "src.4", "src.3", "src.2", "src.1", "src.0", + "dvc.0", "dvc.1", + "clk_a", "clk_b", "clk_c", "clk_i"; + rcar_sound,dvc { dvc0: dvc@0 { }; dvc1: dvc@1 { }; -- cgit v0.10.2 From e80a2fb18bc7dca116ec623dc909f07c5e505349 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:32:49 +0000 Subject: ASoC: rsnd: add sample code of dma entry rsnd driver supports Audio DMAC (via DMAEngine) / Audio DMAC peri peri (via rsnd driver) now. This patch adds DT binding sample code on DT binging text Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt index 503967b..6e660d4 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt @@ -29,9 +29,17 @@ SSI subnode properties: - shared-pin : if shared clock pin - pio-transfer : use PIO transfer mode - no-busif : BUSIF is not ussed when [mem -> SSI] via DMA case +- dma : Should contain Audio DMAC entry +- dma-names : SSI case "rx" (=playback), "tx" (=capture) + SSIU case "rxu" (=playback), "txu" (=capture) SRC subnode properties: -no properties at this point +- dma : Should contain Audio DMAC entry +- dma-names : "rx" (=playback), "tx" (=capture) + +DVC subnode properties: +- dma : Should contain Audio DMAC entry +- dma-names : "tx" (=playback/capture) DAI subnode properties: - playback : list of playback modules @@ -69,73 +77,119 @@ rcar_sound: rcar_sound@ec500000 { "clk_a", "clk_b", "clk_c", "clk_i"; rcar_sound,dvc { - dvc0: dvc@0 { }; - dvc1: dvc@1 { }; + dvc0: dvc@0 { + dmas = <&audma0 0xbc>; + dma-names = "tx"; + }; + dvc1: dvc@1 { + dmas = <&audma0 0xbe>; + dma-names = "tx"; + }; }; rcar_sound,src { src0: src@0 { interrupts = <0 352 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x85>, <&audma1 0x9a>; + dma-names = "rx", "tx"; }; src1: src@1 { interrupts = <0 353 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x87>, <&audma1 0x9c>; + dma-names = "rx", "tx"; }; src2: src@2 { interrupts = <0 354 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x89>, <&audma1 0x9e>; + dma-names = "rx", "tx"; }; src3: src@3 { interrupts = <0 355 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x8b>, <&audma1 0xa0>; + dma-names = "rx", "tx"; }; src4: src@4 { interrupts = <0 356 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x8d>, <&audma1 0xb0>; + dma-names = "rx", "tx"; }; src5: src@5 { interrupts = <0 357 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x8f>, <&audma1 0xb2>; + dma-names = "rx", "tx"; }; src6: src@6 { interrupts = <0 358 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x91>, <&audma1 0xb4>; + dma-names = "rx", "tx"; }; src7: src@7 { interrupts = <0 359 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x93>, <&audma1 0xb6>; + dma-names = "rx", "tx"; }; src8: src@8 { interrupts = <0 360 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x95>, <&audma1 0xb8>; + dma-names = "rx", "tx"; }; src9: src@9 { interrupts = <0 361 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x97>, <&audma1 0xba>; + dma-names = "rx", "tx"; }; }; rcar_sound,ssi { ssi0: ssi@0 { interrupts = <0 370 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x01>, <&audma1 0x02>, <&audma0 0x15>, <&audma1 0x16>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi1: ssi@1 { interrupts = <0 371 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x03>, <&audma1 0x04>, <&audma0 0x49>, <&audma1 0x4a>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi2: ssi@2 { interrupts = <0 372 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x05>, <&audma1 0x06>, <&audma0 0x63>, <&audma1 0x64>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi3: ssi@3 { interrupts = <0 373 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x07>, <&audma1 0x08>, <&audma0 0x6f>, <&audma1 0x70>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi4: ssi@4 { interrupts = <0 374 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x09>, <&audma1 0x0a>, <&audma0 0x71>, <&audma1 0x72>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi5: ssi@5 { interrupts = <0 375 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x0b>, <&audma1 0x0c>, <&audma0 0x73>, <&audma1 0x74>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi6: ssi@6 { interrupts = <0 376 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x0d>, <&audma1 0x0e>, <&audma0 0x75>, <&audma1 0x76>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi7: ssi@7 { interrupts = <0 377 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x0f>, <&audma1 0x10>, <&audma0 0x79>, <&audma1 0x7a>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi8: ssi@8 { interrupts = <0 378 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x11>, <&audma1 0x12>, <&audma0 0x7b>, <&audma1 0x7c>; + dma-names = "rx", "tx", "rxu", "txu"; }; ssi9: ssi@9 { interrupts = <0 379 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&audma0 0x13>, <&audma1 0x14>, <&audma0 0x7d>, <&audma1 0x7e>; + dma-names = "rx", "tx", "rxu", "txu"; }; }; -- cgit v0.10.2 From d3b1c0badbc86c034af55704ea88c1e656b402fe Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 20 Feb 2015 10:33:09 +0000 Subject: ASoC: rsnd: add sample code of reg-names Current rsnd driver supports reg-names, and Audio DMAC peri peri. This patch adds sample code for it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt index 6e660d4..f316ce1 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.txt +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.txt @@ -53,7 +53,9 @@ rcar_sound: rcar_sound@ec500000 { reg = <0 0xec500000 0 0x1000>, /* SCU */ <0 0xec5a0000 0 0x100>, /* ADG */ <0 0xec540000 0 0x1000>, /* SSIU */ - <0 0xec541000 0 0x1280>; /* SSI */ + <0 0xec541000 0 0x1280>, /* SSI */ + <0 0xec740000 0 0x200>; /* Audio DMAC peri peri*/ + reg-names = "scu", "adg", "ssiu", "ssi", "audmapp"; clocks = <&mstp10_clks R8A7790_CLK_SSI_ALL>, <&mstp10_clks R8A7790_CLK_SSI9>, <&mstp10_clks R8A7790_CLK_SSI8>, -- cgit v0.10.2 From 70dddeee8945a0e62525a278ae7b91778f82f765 Mon Sep 17 00:00:00 2001 From: Octavian Purdila Date: Mon, 2 Mar 2015 21:03:05 +0200 Subject: iio: fix drivers that check buffer->scan_mask If the in-kernel push interface is used we may have a different masks on the device buffer and the kernel buffer and in this case the device should generate data for the reunion of the buffers, which is available at indio_dev->active_scan_mask. Compiled tested only except for bmc150-accel which was tested at runtime with the hardware. Signed-off-by: Octavian Purdila Cc: Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c index 1096da3..75c6d21 100644 --- a/drivers/iio/accel/bma180.c +++ b/drivers/iio/accel/bma180.c @@ -659,7 +659,7 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p) mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->buffer->scan_mask, + for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) { ret = bma180_get_data_reg(data, bit); if (ret < 0) { diff --git a/drivers/iio/accel/bmc150-accel.c b/drivers/iio/accel/bmc150-accel.c index 066d0c0..7d1383d 100644 --- a/drivers/iio/accel/bmc150-accel.c +++ b/drivers/iio/accel/bmc150-accel.c @@ -986,7 +986,7 @@ static irqreturn_t bmc150_accel_trigger_handler(int irq, void *p) int bit, ret, i = 0; mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->buffer->scan_mask, + for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) { ret = i2c_smbus_read_word_data(data->client, BMC150_ACCEL_AXIS_TO_REG(bit)); diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 567de26..1a63795 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -956,7 +956,7 @@ static irqreturn_t kxcjk1013_trigger_handler(int irq, void *p) mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->buffer->scan_mask, + for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) { ret = kxcjk1013_get_acc_reg(data, bit); if (ret < 0) { diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c index ff61ae5..8a0eb4a 100644 --- a/drivers/iio/adc/at91_adc.c +++ b/drivers/iio/adc/at91_adc.c @@ -544,7 +544,6 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) { struct iio_dev *idev = iio_trigger_get_drvdata(trig); struct at91_adc_state *st = iio_priv(idev); - struct iio_buffer *buffer = idev->buffer; struct at91_adc_reg_desc *reg = st->registers; u32 status = at91_adc_readl(st, reg->trigger_register); int value; @@ -564,7 +563,7 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) at91_adc_writel(st, reg->trigger_register, status | value); - for_each_set_bit(bit, buffer->scan_mask, + for_each_set_bit(bit, idev->active_scan_mask, st->num_channels) { struct iio_chan_spec const *chan = idev->channels + bit; at91_adc_writel(st, AT91_ADC_CHER, @@ -579,7 +578,7 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) at91_adc_writel(st, reg->trigger_register, status & ~value); - for_each_set_bit(bit, buffer->scan_mask, + for_each_set_bit(bit, idev->active_scan_mask, st->num_channels) { struct iio_chan_spec const *chan = idev->channels + bit; at91_adc_writel(st, AT91_ADC_CHDR, diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index 2e5cc44..a0e7161 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -188,12 +188,11 @@ static int tiadc_buffer_preenable(struct iio_dev *indio_dev) static int tiadc_buffer_postenable(struct iio_dev *indio_dev) { struct tiadc_device *adc_dev = iio_priv(indio_dev); - struct iio_buffer *buffer = indio_dev->buffer; unsigned int enb = 0; u8 bit; tiadc_step_config(indio_dev); - for_each_set_bit(bit, buffer->scan_mask, adc_dev->channels) + for_each_set_bit(bit, indio_dev->active_scan_mask, adc_dev->channels) enb |= (get_adc_step_bit(adc_dev, bit) << 1); adc_dev->buffer_en_ch_steps = enb; diff --git a/drivers/iio/gyro/bmg160.c b/drivers/iio/gyro/bmg160.c index 60451b3..ccf3ea7 100644 --- a/drivers/iio/gyro/bmg160.c +++ b/drivers/iio/gyro/bmg160.c @@ -822,7 +822,7 @@ static irqreturn_t bmg160_trigger_handler(int irq, void *p) int bit, ret, i = 0; mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->buffer->scan_mask, + for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) { ret = i2c_smbus_read_word_data(data->client, BMG160_AXIS_TO_REG(bit)); diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c index 5cc3692..b3a3637 100644 --- a/drivers/iio/imu/kmx61.c +++ b/drivers/iio/imu/kmx61.c @@ -1227,7 +1227,7 @@ static irqreturn_t kmx61_trigger_handler(int irq, void *p) base = KMX61_MAG_XOUT_L; mutex_lock(&data->lock); - for_each_set_bit(bit, indio_dev->buffer->scan_mask, + for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) { ret = kmx61_read_measurement(data, base, bit); if (ret < 0) { diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c index 74dff4e..89fca3a 100644 --- a/drivers/iio/proximity/sx9500.c +++ b/drivers/iio/proximity/sx9500.c @@ -494,7 +494,7 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private) mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->buffer->scan_mask, + for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) { ret = sx9500_read_proximity(data, &indio_dev->channels[bit], &val); -- cgit v0.10.2 From 778952598e53f09251bebe1655177b355cd9e836 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 7 Mar 2015 19:35:08 +0100 Subject: ASoC: Remove unnecessary device_remove_file() Since commit d29697dc3b92 ("ASoC: Add sysfs entries via static attribute groups") the sysfs attributes of the rtd are manged by the device core and there is no need to manually call device_remove_file() anymore. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 30579ca..acf99f1 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1410,7 +1410,6 @@ static void soc_remove_aux_dev(struct snd_soc_card *card, int num) /* unregister the rtd device */ if (rtd->dev_registered) { - device_remove_file(rtd->dev, &dev_attr_codec_reg); device_unregister(rtd->dev); rtd->dev_registered = 0; } -- cgit v0.10.2 From 967beb2e87771411e08467152e3d9f1c3ae73a67 Mon Sep 17 00:00:00 2001 From: Zubair Lutfullah Kakakhel Date: Mon, 9 Mar 2015 12:11:08 +0000 Subject: ASoC: jz4740: Add jz4780 support The jz4780 and jz4740 have very similar i2s blocks. The slight difference is in Rx/Tx fifos. And the bitclocks for input/output are different. This patch adds jz4780 support to the driver Signed-off-by: Zubair Lutfullah Kakakhel Acked-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt b/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt index b414333..b623d50 100644 --- a/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt +++ b/Documentation/devicetree/bindings/sound/ingenic,jz4740-i2s.txt @@ -1,7 +1,7 @@ Ingenic JZ4740 I2S controller Required properties: -- compatible : "ingenic,jz4740-i2s" +- compatible : "ingenic,jz4740-i2s" or "ingenic,jz4780-i2s" - reg : I2S registers location and length - clocks : AIC and I2S PLL clock specifiers. - clock-names: "aic" and "i2s" diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index 07f7781..b05fb1c 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -58,6 +58,12 @@ #define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12 #define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8 +#define JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 24 +#define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 16 +#define JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_MASK \ + (0xf << JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) +#define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_MASK \ + (0x1f << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) #define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19) #define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16) @@ -79,6 +85,7 @@ #define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16 #define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12) +#define JZ_AIC_I2S_FMT_DISABLE_BIT_ICLK BIT(13) #define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4) #define JZ_AIC_I2S_FMT_MSB BIT(0) @@ -87,6 +94,13 @@ #define JZ_AIC_CLK_DIV_MASK 0xf #define I2SDIV_DV_SHIFT 8 #define I2SDIV_DV_MASK (0xf << I2SDIV_DV_SHIFT) +#define I2SDIV_IDV_SHIFT 8 +#define I2SDIV_IDV_MASK (0xf << I2SDIV_IDV_SHIFT) + +enum jz47xx_i2s_version { + JZ_I2S_JZ4740, + JZ_I2S_JZ4780, +}; struct jz4740_i2s { struct resource *mem; @@ -98,6 +112,8 @@ struct jz4740_i2s { struct snd_dmaengine_dai_dma_data playback_dma_data; struct snd_dmaengine_dai_dma_data capture_dma_data; + + enum jz47xx_i2s_version version; }; static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s, @@ -267,13 +283,22 @@ static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO; else ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; + + div_reg &= ~I2SDIV_DV_MASK; + div_reg |= (div - 1) << I2SDIV_DV_SHIFT; } else { ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK; ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET; + + if (i2s->version >= JZ_I2S_JZ4780) { + div_reg &= ~I2SDIV_IDV_MASK; + div_reg |= (div - 1) << I2SDIV_IDV_SHIFT; + } else { + div_reg &= ~I2SDIV_DV_MASK; + div_reg |= (div - 1) << I2SDIV_DV_SHIFT; + } } - div_reg &= ~I2SDIV_DV_MASK; - div_reg |= (div - 1) << I2SDIV_DV_SHIFT; jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); jz4740_i2s_write(i2s, JZ_REG_AIC_CLK_DIV, div_reg); @@ -369,11 +394,19 @@ static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai) snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, &i2s->capture_dma_data); - conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | - (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | - JZ_AIC_CONF_OVERFLOW_PLAY_LAST | - JZ_AIC_CONF_I2S | - JZ_AIC_CONF_INTERNAL_CODEC; + if (i2s->version >= JZ_I2S_JZ4780) { + conf = (7 << JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + } else { + conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + } jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET); jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); @@ -422,13 +455,34 @@ static struct snd_soc_dai_driver jz4740_i2s_dai = { .resume = jz4740_i2s_resume, }; +static struct snd_soc_dai_driver jz4780_i2s_dai = { + .probe = jz4740_i2s_dai_probe, + .remove = jz4740_i2s_dai_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .ops = &jz4740_i2s_dai_ops, + .suspend = jz4740_i2s_suspend, + .resume = jz4740_i2s_resume, +}; + static const struct snd_soc_component_driver jz4740_i2s_component = { .name = "jz4740-i2s", }; #ifdef CONFIG_OF static const struct of_device_id jz4740_of_matches[] = { - { .compatible = "ingenic,jz4740-i2s" }, + { .compatible = "ingenic,jz4740-i2s", .data = (void *)JZ_I2S_JZ4740 }, + { .compatible = "ingenic,jz4780-i2s", .data = (void *)JZ_I2S_JZ4780 }, { /* sentinel */ } }; #endif @@ -438,11 +492,16 @@ static int jz4740_i2s_dev_probe(struct platform_device *pdev) struct jz4740_i2s *i2s; struct resource *mem; int ret; + const struct of_device_id *match; i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); if (!i2s) return -ENOMEM; + match = of_match_device(jz4740_of_matches, &pdev->dev); + if (match) + i2s->version = (enum jz47xx_i2s_version)match->data; + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); i2s->base = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(i2s->base)) @@ -460,8 +519,13 @@ static int jz4740_i2s_dev_probe(struct platform_device *pdev) platform_set_drvdata(pdev, i2s); - ret = devm_snd_soc_register_component(&pdev->dev, - &jz4740_i2s_component, &jz4740_i2s_dai, 1); + if (i2s->version == JZ_I2S_JZ4780) + ret = devm_snd_soc_register_component(&pdev->dev, + &jz4740_i2s_component, &jz4780_i2s_dai, 1); + else + ret = devm_snd_soc_register_component(&pdev->dev, + &jz4740_i2s_component, &jz4740_i2s_dai, 1); + if (ret) return ret; -- cgit v0.10.2 From bd22f9d405eb14cc074d294919d0b909e0bc6170 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Mon, 9 Mar 2015 16:55:22 +0800 Subject: ASoC: rt5670: Revert Keep sysclk on patch The "Keep sysclk on if JD func is used" patch force enable/disable pin in rt5670_set_dai_sysclk. But some machine driver call it in dapm widget event. It will cause kernel crash. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 592f961..32cd266 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -2273,13 +2273,6 @@ static int rt5670_set_dai_sysclk(struct snd_soc_dai *dai, if (freq == rt5670->sysclk && clk_id == rt5670->sysclk_src) return 0; - if (rt5670->pdata.jd_mode) { - if (clk_id == RT5670_SCLK_S_PLL1) - snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1"); - else - snd_soc_dapm_disable_pin(&codec->dapm, "PLL1"); - snd_soc_dapm_sync(&codec->dapm); - } switch (clk_id) { case RT5670_SCLK_S_MCLK: reg_val |= RT5670_SCLK_SRC_MCLK; @@ -2724,10 +2717,6 @@ static int rt5670_i2c_probe(struct i2c_client *i2c, } if (rt5670->pdata.jd_mode) { - regmap_update_bits(rt5670->regmap, RT5670_GLB_CLK, - RT5670_SCLK_SRC_MASK, RT5670_SCLK_SRC_RCCLK); - rt5670->sysclk = 0; - rt5670->sysclk_src = RT5670_SCLK_S_RCCLK; regmap_update_bits(rt5670->regmap, RT5670_PWR_ANLG1, RT5670_PWR_MB, RT5670_PWR_MB); regmap_update_bits(rt5670->regmap, RT5670_PWR_ANLG2, -- cgit v0.10.2 From 485372dc24ca2eaac18ce41a51b9dd017bc11400 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Mon, 9 Mar 2015 16:55:23 +0800 Subject: ASoC: rt5670: Check sysclk source by private data Currently, is_sys_clk_from_pll check sysclk source by reading codec register value. And it will be invoked before updating dapm widget power. In some machine driver, snd_soc_dai_set_sysclk is called in dapm event to switch codec sysclk to RC clock in idle mode. And in some use cases (such as syspend/resume) hw_params will not be called when the dapm widget is powered up. As a result, is_sys_clk_from_pll will return 0 although it is supposed to be 1. To solve this, we let is_sys_clk_from_pll check sysclk sysclk_src which is stored in private data and don't change the value of sysclk_src when codec sysclk is switched to internal clock. The internal clock can only be used in idle mode, so it sould be fine if we don't set sysclk_src to internal clock. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 32cd266..9e3bc43 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -517,11 +517,10 @@ static int set_dmic_clk(struct snd_soc_dapm_widget *w, static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { - unsigned int val; + struct snd_soc_codec *codec = source->codec; + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); - val = snd_soc_read(source->codec, RT5670_GLB_CLK); - val &= RT5670_SCLK_SRC_MASK; - if (val == RT5670_SCLK_SRC_PLL1) + if (rt5670->sysclk_src == RT5670_SCLK_S_PLL1) return 1; else return 0; @@ -2270,9 +2269,6 @@ static int rt5670_set_dai_sysclk(struct snd_soc_dai *dai, struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); unsigned int reg_val = 0; - if (freq == rt5670->sysclk && clk_id == rt5670->sysclk_src) - return 0; - switch (clk_id) { case RT5670_SCLK_S_MCLK: reg_val |= RT5670_SCLK_SRC_MCLK; @@ -2290,7 +2286,8 @@ static int rt5670_set_dai_sysclk(struct snd_soc_dai *dai, snd_soc_update_bits(codec, RT5670_GLB_CLK, RT5670_SCLK_SRC_MASK, reg_val); rt5670->sysclk = freq; - rt5670->sysclk_src = clk_id; + if (clk_id != RT5670_SCLK_S_RCCLK) + rt5670->sysclk_src = clk_id; dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); -- cgit v0.10.2 From 62c76fe2e580f6a975679e8711bade09e24c204b Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Sun, 8 Mar 2015 06:02:15 -0400 Subject: ASoC: wm8996: match wait_for_completion_timeout return type return type of wait_for_completion_timeout is unsigned long not int. An appropriately named unsigned long is added and the assignment fixed up in case of completion occurring the remaining time is >=1 so ret is set to 1 if no timeout occurred. Signed-off-by: Nicholas Mc Guire Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index dc92d5e..7a2b969 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -2009,7 +2009,7 @@ static int wm8996_set_fll(struct snd_soc_codec *codec, int fll_id, int source, struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = to_i2c_client(codec->dev); struct _fll_div fll_div; - unsigned long timeout; + unsigned long timeout, time_left; int ret, reg, retry; /* Any change? */ @@ -2113,10 +2113,11 @@ static int wm8996_set_fll(struct snd_soc_codec *codec, int fll_id, int source, timeout /= 2; for (retry = 0; retry < 10; retry++) { - ret = wait_for_completion_timeout(&wm8996->fll_lock, - timeout); - if (ret != 0) { + time_left = wait_for_completion_timeout(&wm8996->fll_lock, + timeout); + if (time_left != 0) { WARN_ON(!i2c->irq); + ret = 1; break; } -- cgit v0.10.2 From 159366ea38706402e8ebd0f55eef52931333deac Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Sun, 8 Mar 2015 06:02:38 -0400 Subject: ASoC: wm8996: ensure lower bounds of 1 for timeout wait_for_completion_timeout can be called with timeout == 0 due to msecs_to_jiffies(2) == 1 for HZ < 1000 and usecs_to_jiffies(300) == 1 for all reasonable values of HZ, thus the following timeout /= 2; sets timeout to 0. This patch simply adds a lower-bounds of 1. Signed-off-by: Nicholas Mc Guire Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index 7a2b969..308748a 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -2110,7 +2110,8 @@ static int wm8996_set_fll(struct snd_soc_codec *codec, int fll_id, int source, if (i2c->irq) timeout *= 10; else - timeout /= 2; + /* ensure timeout of atleast 1 jiffies */ + timeout = timeout/2 ? : 1; for (retry = 0; retry < 10; retry++) { time_left = wait_for_completion_timeout(&wm8996->fll_lock, -- cgit v0.10.2 From 17f4ad601d2753be7f15cd2928e89309759e4936 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Sun, 8 Mar 2015 09:16:49 -0400 Subject: ASoC: arizona: match wait_for_completion_timeout return type return type of wait_for_completion_timeout is unsigned long not int. An appropriately named unsigned long is added and the assignment fixed up. Signed-off-by: Nicholas Mc Guire Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 2920261..9015b44 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -1901,7 +1901,7 @@ static int arizona_is_enabled_fll(struct arizona_fll *fll) static int arizona_enable_fll(struct arizona_fll *fll) { struct arizona *arizona = fll->arizona; - int ret; + unsigned long time_left; bool use_sync = false; int already_enabled = arizona_is_enabled_fll(fll); struct arizona_fll_cfg cfg; @@ -1977,9 +1977,9 @@ static int arizona_enable_fll(struct arizona_fll *fll) regmap_update_bits_async(arizona->regmap, fll->base + 1, ARIZONA_FLL1_FREERUN, 0); - ret = wait_for_completion_timeout(&fll->ok, + time_left = wait_for_completion_timeout(&fll->ok, msecs_to_jiffies(250)); - if (ret == 0) + if (time_left == 0) arizona_fll_warn(fll, "Timed out waiting for lock\n"); return 0; -- cgit v0.10.2 From 905a808664402dec0ac11376833e79da4ae7b2fd Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Sun, 8 Mar 2015 06:57:07 -0400 Subject: ASoC: wm5100: match wait_for_completion_timeout return type return type of wait_for_completion_timeout is unsigned long not int. An appropriately named unsigned long is added and the assignment fixed up. Signed-off-by: Nicholas Mc Guire Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index ea09db5..9674037 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -1762,6 +1762,7 @@ static int wm5100_set_fll(struct snd_soc_codec *codec, int fll_id, int source, struct _fll_div factors; struct wm5100_fll *fll; int ret, base, lock, i, timeout; + unsigned long time_left; switch (fll_id) { case WM5100_FLL1: @@ -1842,9 +1843,9 @@ static int wm5100_set_fll(struct snd_soc_codec *codec, int fll_id, int source, /* Poll for the lock; will use interrupt when we can test */ for (i = 0; i < timeout; i++) { if (i2c->irq) { - ret = wait_for_completion_timeout(&fll->lock, - msecs_to_jiffies(25)); - if (ret > 0) + time_left = wait_for_completion_timeout(&fll->lock, + msecs_to_jiffies(25)); + if (time_left > 0) break; } else { msleep(1); -- cgit v0.10.2 From 78bb997ace926451ec8d1ed15178b161dc61b805 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Sun, 8 Mar 2015 07:06:05 -0400 Subject: ASoC: wm2200: match wait_for_completion_timeout return type return type of wait_for_completion_timeout is unsigned long not int. An appropriately named unsigned long is added and the assignment fixed up. Signed-off-by: Nicholas Mc Guire Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index 1559984..b48694a 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -1942,6 +1942,7 @@ static int wm2200_set_fll(struct snd_soc_codec *codec, int fll_id, int source, struct wm2200_priv *wm2200 = snd_soc_codec_get_drvdata(codec); struct _fll_div factors; int ret, i, timeout; + unsigned long time_left; if (!Fout) { dev_dbg(codec->dev, "FLL disabled"); @@ -2021,9 +2022,10 @@ static int wm2200_set_fll(struct snd_soc_codec *codec, int fll_id, int source, /* Poll for the lock; will use the interrupt to exit quickly */ for (i = 0; i < timeout; i++) { if (i2c->irq) { - ret = wait_for_completion_timeout(&wm2200->fll_lock, - msecs_to_jiffies(25)); - if (ret > 0) + time_left = wait_for_completion_timeout( + &wm2200->fll_lock, + msecs_to_jiffies(25)); + if (time_left > 0) break; } else { msleep(1); -- cgit v0.10.2 From 32556394501a27c02e7185c4d11a51b636b02f4b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 9 Mar 2015 11:15:28 -0700 Subject: ASoC: cx20442: remove incorerct __exit markups Even if bus is not hot-pluggable, the devices can be unbound from the driver via sysfs, so we should not be using __exit annotations on remove() methods. The only exception is drivers registered with platform_driver_probe() which specifically disables sysfs bind/unbind attributes. Signed-off-by: Dmitry Torokhov Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 0b10979..0f334bc 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -420,7 +420,7 @@ static int cx20442_platform_probe(struct platform_device *pdev) &cx20442_codec_dev, &cx20442_dai, 1); } -static int __exit cx20442_platform_remove(struct platform_device *pdev) +static int cx20442_platform_remove(struct platform_device *pdev) { snd_soc_unregister_codec(&pdev->dev); return 0; @@ -431,7 +431,7 @@ static struct platform_driver cx20442_platform_driver = { .name = "cx20442-codec", }, .probe = cx20442_platform_probe, - .remove = __exit_p(cx20442_platform_remove), + .remove = cx20442_platform_remove, }; module_platform_driver(cx20442_platform_driver); -- cgit v0.10.2 From f580f8afd0d81c3f04d8b393c9d675ef289e4d40 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 9 Mar 2015 11:18:14 -0700 Subject: ASoC: tlv320aic23: remove incorrect __exit markups Even if bus is not hot-pluggable, the devices can be unbound from the driver via sysfs, so we should not be using __exit annotations on remove() methods. The only exception is drivers registered with platform_driver_probe() which specifically disables sysfs bind/unbind attributes. Signed-off-by: Dmitry Torokhov Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/tlv320aic23-i2c.c b/sound/soc/codecs/tlv320aic23-i2c.c index f137019..78a94af 100644 --- a/sound/soc/codecs/tlv320aic23-i2c.c +++ b/sound/soc/codecs/tlv320aic23-i2c.c @@ -31,7 +31,7 @@ static int tlv320aic23_i2c_probe(struct i2c_client *i2c, return tlv320aic23_probe(&i2c->dev, regmap); } -static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c) +static int tlv320aic23_i2c_remove(struct i2c_client *i2c) { snd_soc_unregister_codec(&i2c->dev); return 0; @@ -56,7 +56,7 @@ static struct i2c_driver tlv320aic23_i2c_driver = { .of_match_table = of_match_ptr(tlv320aic23_of_match), }, .probe = tlv320aic23_i2c_probe, - .remove = __exit_p(tlv320aic23_i2c_remove), + .remove = tlv320aic23_i2c_remove, .id_table = tlv320aic23_id, }; -- cgit v0.10.2 From 8d0c38a3f2a6bb70e952f127ed817fc7a08db52c Mon Sep 17 00:00:00 2001 From: Jin Yao Date: Tue, 10 Mar 2015 09:05:38 +0800 Subject: ASoC: Intel: move sysclk source setting to platform_clock_control for balance. A playback noise happens after suspend/resume on Braswell. The issue is due to the codec PLL and codec ASRC are not enabled correctly due to the incorrect sysclk setting after resume. This patch resets the sysclk source setting in platform clock control widget handler. Signed-off-by: Bard Liao Signed-off-by: Jin Yao Signed-off-by: Mark Brown diff --git a/sound/soc/intel/cht_bsw_rt5672.c b/sound/soc/intel/cht_bsw_rt5672.c index bc8dcac..279df4c 100644 --- a/sound/soc/intel/cht_bsw_rt5672.c +++ b/sound/soc/intel/cht_bsw_rt5672.c @@ -50,6 +50,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, struct snd_soc_dapm_context *dapm = w->dapm; struct snd_soc_card *card = dapm->card; struct snd_soc_dai *codec_dai; + int ret; codec_dai = cht_get_codec_dai(card); if (!codec_dai) { @@ -57,17 +58,31 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, return -EIO; } - if (!SND_SOC_DAPM_EVENT_OFF(event)) - return 0; - - /* Set codec sysclk source to its internal clock because codec PLL will - * be off when idle and MCLK will also be off by ACPI when codec is - * runtime suspended. Codec needs clock for jack detection and button - * press. - */ - snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK, - 0, SND_SOC_CLOCK_IN); - + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, 48000 * 512); + if (ret < 0) { + dev_err(card->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + /* set codec sysclk source to PLL */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + } else { + /* Set codec sysclk source to its internal clock because codec + * PLL will be off when idle and MCLK will also be off by ACPI + * when codec is runtime suspended. Codec needs clock for jack + * detection and button press. + */ + snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + } return 0; } @@ -77,7 +92,8 @@ static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { SND_SOC_DAPM_MIC("Int Mic", NULL), SND_SOC_DAPM_SPK("Ext Spk", NULL), SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, - platform_clock_control, SND_SOC_DAPM_POST_PMD), + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), }; static const struct snd_soc_dapm_route cht_audio_map[] = { -- cgit v0.10.2 From 6ec6fb6f231547834925ff802deb4415f49f708e Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 10 Mar 2015 11:48:05 +0100 Subject: ASoC: rsnd: Use %pad to print dma_addr_t in rsnd_dmapp_init() sound/soc/sh/rcar/dma.c: In function 'rsnd_dmapp_init': sound/soc/sh/rcar/dma.c:341:2: warning: format '%x' expects argument of type 'unsigned int', but argument 5 has type 'dma_addr_t' [-Wformat=] dev_dbg(dev, "id/src/dst/chcr = %d/%x/%x/%08x\n", ^ sound/soc/sh/rcar/dma.c:341:2: warning: format '%x' expects argument of type 'unsigned int', but argument 6 has type 'dma_addr_t' [-Wformat=] Fixes: 288f392e729dd4d3 ("ASoC: rsnd: add Audio DMAC peri peri support rework") Signed-off-by: Geert Uytterhoeven Acked-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 92fd550..2b73df8 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -338,8 +338,8 @@ static int rsnd_dmapp_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, rsnd_dmapp_stop(dma); - dev_dbg(dev, "id/src/dst/chcr = %d/%x/%x/%08x\n", - dmapp->dmapp_id, dma->src_addr, dma->dst_addr, dmapp->chcr); + dev_dbg(dev, "id/src/dst/chcr = %d/%pad/%pad/%08x\n", + dmapp->dmapp_id, &dma->src_addr, &dma->dst_addr, dmapp->chcr); return 0; } -- cgit v0.10.2 From 8537483a17038f08c68c1f0a561f0fe686e5706f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 10 Mar 2015 01:25:01 +0000 Subject: ASoC: rsnd: recover PIO mode for new dma interface Renesas sound driver needs 1st/2nd DMA interface, and 1st DMA is using DMAEngine, and 2nd is using local method now. 2nd DMA had been DMAEngine, but it was moved to local method by previous patchset. But then, it lost PIO mode fallback when probe. this patch recovers it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 2b73df8..cd7b79a 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -539,6 +539,13 @@ void rsnd_dma_start(struct rsnd_dma *dma) void rsnd_dma_quit(struct rsnd_dma *dma) { + struct rsnd_mod *mod = rsnd_dma_to_mod(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + + if (!dmac) + return; + dma->ops->quit(dma); } @@ -548,8 +555,18 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id) struct rsnd_mod *mod_from; struct rsnd_mod *mod_to; struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); int is_play = rsnd_io_is_play(io); + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + if (!dmac) + return -EAGAIN; + rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to); dma->src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1); @@ -589,7 +606,7 @@ int rsnd_dma_probe(struct platform_device *pdev, dmac = devm_kzalloc(dev, sizeof(*dmac), GFP_KERNEL); if (!dmac || !res) { dev_err(dev, "dma allocate failed\n"); - return -ENOMEM; + return 0; /* it will be PIO mode */ } dmac->dmapp_num = 0; -- cgit v0.10.2 From 530b7b4a52365ce2a250669b87e964a843bdbedb Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 10 Mar 2015 01:25:36 +0000 Subject: ASoC: rsnd: add regmap_config::name for debugfs Renesas sound driver needs SSI/SRC/DVC regmaps, but it didn't have regmap_config::name for devm_regmap_init_mmio(). Thus, debugfs initialization code tried to use same driver name many times, and failed. This patch adds eacy own name for regmap_config::name Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index 0273724..a17a504 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -150,6 +150,7 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, regc.reg_bits = 32; regc.val_bits = 32; regc.reg_stride = 4; + regc.name = name; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); if (!res) -- cgit v0.10.2 From d3ef70543429b754dacc87fc68c30c2c34502337 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 11 Mar 2015 11:42:44 +0800 Subject: ASoC: rt5670: Add IRQ function This patch adds the IRQ function support of rt5670. We use a flag named dev_gpio in platform data to inform codec driver if the IRQ function is used or not. Also, we export rt5670_set_jack_detect for machine driver to pass the jack point. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/include/sound/rt5670.h b/include/sound/rt5670.h index bd31119..b7d6051 100644 --- a/include/sound/rt5670.h +++ b/include/sound/rt5670.h @@ -14,6 +14,7 @@ struct rt5670_platform_data { int jd_mode; bool in2_diff; + bool dev_gpio; bool dmic_en; unsigned int dmic1_data_pin; diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 1a6a9c4..a900db5 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -403,6 +403,171 @@ static bool rt5670_readable_register(struct device *dev, unsigned int reg) } } +/** + * rt5670_headset_detect - Detect headset. + * @codec: SoC audio codec device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ + +static int rt5670_headset_detect(struct snd_soc_codec *codec, int jack_insert) +{ + int val; + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + + if (jack_insert) { + snd_soc_dapm_force_enable_pin(&codec->dapm, + "Mic Det Power"); + snd_soc_dapm_sync(&codec->dapm); + snd_soc_update_bits(codec, RT5670_GEN_CTRL3, 0x4, 0x0); + snd_soc_update_bits(codec, RT5670_CJ_CTRL2, + RT5670_CBJ_DET_MODE | RT5670_CBJ_MN_JD, + RT5670_CBJ_MN_JD); + snd_soc_write(codec, RT5670_GPIO_CTRL2, 0x0004); + snd_soc_update_bits(codec, RT5670_GPIO_CTRL1, + RT5670_GP1_PIN_MASK, RT5670_GP1_PIN_IRQ); + snd_soc_update_bits(codec, RT5670_CJ_CTRL1, + RT5670_CBJ_BST1_EN, RT5670_CBJ_BST1_EN); + snd_soc_write(codec, RT5670_JD_CTRL3, 0x00f0); + snd_soc_update_bits(codec, RT5670_CJ_CTRL2, + RT5670_CBJ_MN_JD, RT5670_CBJ_MN_JD); + snd_soc_update_bits(codec, RT5670_CJ_CTRL2, + RT5670_CBJ_MN_JD, 0); + msleep(300); + val = snd_soc_read(codec, RT5670_CJ_CTRL3) & 0x7; + if (val == 0x1 || val == 0x2) { + rt5670->jack_type = SND_JACK_HEADSET; + /* for push button */ + snd_soc_update_bits(codec, RT5670_INT_IRQ_ST, 0x8, 0x8); + snd_soc_update_bits(codec, RT5670_IL_CMD, 0x40, 0x40); + snd_soc_read(codec, RT5670_IL_CMD); + } else { + snd_soc_update_bits(codec, RT5670_GEN_CTRL3, 0x4, 0x4); + rt5670->jack_type = SND_JACK_HEADPHONE; + snd_soc_dapm_disable_pin(&codec->dapm, "Mic Det Power"); + snd_soc_dapm_sync(&codec->dapm); + } + } else { + snd_soc_update_bits(codec, RT5670_INT_IRQ_ST, 0x8, 0x0); + snd_soc_update_bits(codec, RT5670_GEN_CTRL3, 0x4, 0x4); + rt5670->jack_type = 0; + snd_soc_dapm_disable_pin(&codec->dapm, "Mic Det Power"); + snd_soc_dapm_sync(&codec->dapm); + } + + return rt5670->jack_type; +} + +static int rt5670_button_detect(struct snd_soc_codec *codec) +{ + int btn_type, val; + + val = snd_soc_read(codec, RT5670_IL_CMD); + btn_type = val & 0xff80; + snd_soc_write(codec, RT5670_IL_CMD, val); + if (btn_type != 0) { + msleep(20); + val = snd_soc_read(codec, RT5670_IL_CMD); + snd_soc_write(codec, RT5670_IL_CMD, val); + } + + return btn_type; +} + +static int rt5670_irq_detection(void *data) +{ + struct rt5670_priv *rt5670 = (struct rt5670_priv *)data; + struct snd_soc_jack_gpio *gpio = &rt5670->hp_gpio; + struct snd_soc_jack *jack = rt5670->jack; + int val, btn_type, report = jack->status; + + if (rt5670->pdata.jd_mode == 1) /* 2 port */ + val = snd_soc_read(rt5670->codec, RT5670_A_JD_CTRL1) & 0x0070; + else + val = snd_soc_read(rt5670->codec, RT5670_A_JD_CTRL1) & 0x0020; + + switch (val) { + /* jack in */ + case 0x30: /* 2 port */ + case 0x0: /* 1 port or 2 port */ + if (rt5670->jack_type == 0) { + report = rt5670_headset_detect(rt5670->codec, 1); + /* for push button and jack out */ + gpio->debounce_time = 25; + break; + } + btn_type = 0; + if (snd_soc_read(rt5670->codec, RT5670_INT_IRQ_ST) & 0x4) { + /* button pressed */ + report = SND_JACK_HEADSET; + btn_type = rt5670_button_detect(rt5670->codec); + switch (btn_type) { + case 0x2000: /* up */ + report |= SND_JACK_BTN_1; + break; + case 0x0400: /* center */ + report |= SND_JACK_BTN_0; + break; + case 0x0080: /* down */ + report |= SND_JACK_BTN_2; + break; + default: + dev_err(rt5670->codec->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + } + if (btn_type == 0)/* button release */ + report = rt5670->jack_type; + + break; + /* jack out */ + case 0x70: /* 2 port */ + case 0x10: /* 2 port */ + case 0x20: /* 1 port */ + report = 0; + snd_soc_update_bits(rt5670->codec, RT5670_INT_IRQ_ST, 0x1, 0x0); + rt5670_headset_detect(rt5670->codec, 0); + gpio->debounce_time = 150; /* for jack in */ + break; + default: + break; + } + + return report; +} + +int rt5670_set_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack) +{ + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + int ret; + + rt5670->jack = jack; + rt5670->hp_gpio.gpiod_dev = codec->dev; + rt5670->hp_gpio.name = "headphone detect"; + rt5670->hp_gpio.report = SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2; + rt5670->hp_gpio.debounce_time = 150; + rt5670->hp_gpio.wake = true; + rt5670->hp_gpio.data = (struct rt5670_priv *)rt5670; + rt5670->hp_gpio.jack_status_check = rt5670_irq_detection; + + ret = snd_soc_jack_add_gpios(rt5670->jack, 1, + &rt5670->hp_gpio); + if (ret) { + dev_err(codec->dev, "Adding jack GPIO failed\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5670_set_jack_detect); + static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); @@ -2506,6 +2671,7 @@ static int rt5670_remove(struct snd_soc_codec *codec) struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); regmap_write(rt5670->regmap, RT5670_RESET, 0); + snd_soc_jack_free_gpios(rt5670->jack, 1, &rt5670->hp_gpio); return 0; } @@ -2665,6 +2831,7 @@ static int rt5670_i2c_probe(struct i2c_client *i2c, if (dmi_check_system(dmi_platform_intel_braswell)) { rt5670->pdata.dmic_en = true; rt5670->pdata.dmic1_data_pin = RT5670_DMIC_DATA_IN2P; + rt5670->pdata.dev_gpio = true; rt5670->pdata.jd_mode = 1; } @@ -2706,12 +2873,17 @@ static int rt5670_i2c_probe(struct i2c_client *i2c, regmap_update_bits(rt5670->regmap, RT5670_IN2, RT5670_IN_DF2, RT5670_IN_DF2); - if (i2c->irq) { + if (rt5670->pdata.dev_gpio) { + /* for push button */ + regmap_write(rt5670->regmap, RT5670_IL_CMD, 0x0000); + regmap_write(rt5670->regmap, RT5670_IL_CMD2, 0x0010); + regmap_write(rt5670->regmap, RT5670_IL_CMD3, 0x0014); + /* for irq */ regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, RT5670_GP1_PIN_MASK, RT5670_GP1_PIN_IRQ); regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL2, RT5670_GP1_PF_MASK, RT5670_GP1_PF_OUT); - + regmap_update_bits(rt5670->regmap, RT5670_DIG_MISC, 0x8, 0x8); } if (rt5670->pdata.jd_mode) { diff --git a/sound/soc/codecs/rt5670.h b/sound/soc/codecs/rt5670.h index 0a67adb..0f3255a 100644 --- a/sound/soc/codecs/rt5670.h +++ b/sound/soc/codecs/rt5670.h @@ -1988,6 +1988,8 @@ struct rt5670_priv { struct snd_soc_codec *codec; struct rt5670_platform_data pdata; struct regmap *regmap; + struct snd_soc_jack *jack; + struct snd_soc_jack_gpio hp_gpio; int sysclk; int sysclk_src; @@ -2004,4 +2006,6 @@ struct rt5670_priv { int jack_type; }; +int rt5670_set_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack); #endif /* __RT5670_H__ */ -- cgit v0.10.2 From cc3c340d28b9f730fdc6bc5caa77e3bbd1e2377c Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 11 Mar 2015 11:42:45 +0800 Subject: ASoC: rt5670: export jack suspend/resume APIs We force enable "Mic Det Power" when a jack is inserted. Also, we set codec idle_bias_off = true. As a result, codec driver will not suspend as we expect. On Braswell, we don't need the jack detection when suspend but need it after resume, so export the jack suspend/resume APIs which are provided for machine driver to control during suspend/resume. Signed-off-by: Jie Yang Signed-off-by: Jin Yao Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index a900db5..91d2069 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -461,6 +461,24 @@ static int rt5670_headset_detect(struct snd_soc_codec *codec, int jack_insert) return rt5670->jack_type; } +void rt5670_jack_suspend(struct snd_soc_codec *codec) +{ + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + + rt5670->jack_type_saved = rt5670->jack_type; + rt5670_headset_detect(codec, 0); +} +EXPORT_SYMBOL_GPL(rt5670_jack_suspend); + +void rt5670_jack_resume(struct snd_soc_codec *codec) +{ + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + + if (rt5670->jack_type_saved) + rt5670_headset_detect(codec, 1); +} +EXPORT_SYMBOL_GPL(rt5670_jack_resume); + static int rt5670_button_detect(struct snd_soc_codec *codec) { int btn_type, val; diff --git a/sound/soc/codecs/rt5670.h b/sound/soc/codecs/rt5670.h index 0f3255a..dc2b462 100644 --- a/sound/soc/codecs/rt5670.h +++ b/sound/soc/codecs/rt5670.h @@ -2004,8 +2004,11 @@ struct rt5670_priv { int dsp_sw; /* expected parameter setting */ int dsp_rate; int jack_type; + int jack_type_saved; }; +void rt5670_jack_suspend(struct snd_soc_codec *codec); +void rt5670_jack_resume(struct snd_soc_codec *codec); int rt5670_set_jack_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack); #endif /* __RT5670_H__ */ -- cgit v0.10.2 From 9449d39b990d6d3d6386fbb92f3b86c808157b47 Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Tue, 10 Mar 2015 10:41:20 +0800 Subject: ASoC: Intel: add function to load firmware image Add a general method to load firmware image, and apply to base firmware image loading. With the method, the driver will support loading multiple different modules in order to support different features. Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-dsp-priv.h b/sound/soc/intel/sst-dsp-priv.h index b9da030..396d545 100644 --- a/sound/soc/intel/sst-dsp-priv.h +++ b/sound/soc/intel/sst-dsp-priv.h @@ -173,6 +173,16 @@ struct sst_module_runtime_context { }; /* + * Audio DSP Module State + */ +enum sst_module_state { + SST_MODULE_STATE_UNLOADED = 0, /* default state */ + SST_MODULE_STATE_LOADED, + SST_MODULE_STATE_INITIALIZED, /* and inactive */ + SST_MODULE_STATE_ACTIVE, +}; + +/* * Audio DSP Generic Module. * * Each Firmware file can consist of 1..N modules. A module can span multiple @@ -203,6 +213,9 @@ struct sst_module { struct list_head list; /* DSP list of modules */ struct list_head list_fw; /* FW list of modules */ struct list_head runtime_list; /* list of runtime module objects*/ + + /* state */ + enum sst_module_state state; }; /* diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c index 5f71ef6..5e58008 100644 --- a/sound/soc/intel/sst-firmware.c +++ b/sound/soc/intel/sst-firmware.c @@ -498,6 +498,7 @@ struct sst_module *sst_module_new(struct sst_fw *sst_fw, sst_module->scratch_size = template->scratch_size; sst_module->persistent_size = template->persistent_size; sst_module->entry = template->entry; + sst_module->state = SST_MODULE_STATE_UNLOADED; INIT_LIST_HEAD(&sst_module->block_list); INIT_LIST_HEAD(&sst_module->runtime_list); diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c index 402b728..8ad733b 100644 --- a/sound/soc/intel/sst-haswell-dsp.c +++ b/sound/soc/intel/sst-haswell-dsp.c @@ -169,6 +169,7 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, block = (void *)block + sizeof(*block) + block->size; } + mod->state = SST_MODULE_STATE_LOADED; return 0; } diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index 863a9ca..ec688f5 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -1844,6 +1844,8 @@ int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) if (ret < 0) dev_err(dev, "error: audio DSP boot failure\n"); + sst_hsw_init_module_state(hsw); + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, msecs_to_jiffies(IPC_BOOT_MSECS)); if (ret == 0) { @@ -1886,6 +1888,74 @@ struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) return hsw->dsp; } +void sst_hsw_init_module_state(struct sst_hsw *hsw) +{ + struct sst_module *module; + enum sst_hsw_module_id id; + + /* the base fw contains several modules */ + for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) { + module = sst_module_get_from_id(hsw->dsp, id); + if (module) + module->state = SST_MODULE_STATE_ACTIVE; + } +} + +int sst_hsw_module_load(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, char *name) +{ + int ret = 0; + const struct firmware *fw = NULL; + struct sst_fw *hsw_sst_fw; + struct sst_module *module; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + dev_dbg(dev, "sst_hsw_module_load id=%d, name='%s'", module_id, name); + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + /* loading for the first time */ + if (module_id == SST_HSW_MODULE_BASE_FW) { + /* for base module: use fw requested in acpi probe */ + fw = dsp->pdata->fw; + if (!fw) { + dev_err(dev, "request Base fw failed\n"); + return -ENODEV; + } + } else { + /* try and load any other optional modules if they are + * available. Use dev_info instead of dev_err in case + * request firmware failed */ + ret = request_firmware(&fw, name, dev); + if (ret) { + dev_info(dev, "fw image %s not available(%d)\n", + name, ret); + return ret; + } + } + hsw_sst_fw = sst_fw_new(dsp, fw, hsw); + if (hsw_sst_fw == NULL) { + dev_err(dev, "error: failed to load firmware\n"); + ret = -ENOMEM; + goto out; + } + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "error: no module %d in firmware %s\n", + module_id, name); + } + } else + dev_info(dev, "module %d (%s) already loaded\n", + module_id, name); +out: + /* release fw, but base fw should be released by acpi driver */ + if (fw && module_id != SST_HSW_MODULE_BASE_FW) + release_firmware(fw); + + return ret; +} + static struct sst_dsp_device hsw_dev = { .thread = hsw_irq_thread, .ops = &haswell_ops, @@ -1947,12 +2017,10 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) /* keep the DSP in reset state for base FW loading */ sst_dsp_reset(hsw->dsp); - hsw->sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw); - if (hsw->sst_fw == NULL) { - ret = -ENODEV; - dev_err(dev, "error: failed to load firmware\n"); + /* load base module and other modules in base firmware image */ + ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base"); + if (ret < 0) goto fw_err; - } /* allocate scratch mem regions */ ret = sst_block_alloc_scratch(hsw->dsp); @@ -1971,6 +2039,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) goto boot_err; } + /* init module state after boot */ + sst_hsw_init_module_state(hsw); + /* get the FW version */ sst_hsw_fw_get_version(hsw, &version); @@ -1986,7 +2057,7 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) boot_err: sst_dsp_reset(hsw->dsp); - sst_fw_free(hsw->sst_fw); + sst_fw_free_all(hsw->dsp); fw_err: dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, hsw->dx_context, hsw->dx_context_paddr); diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index 8580960..e071b3a 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -467,6 +467,12 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); +/* fw module function */ +void sst_hsw_init_module_state(struct sst_hsw *hsw); + +int sst_hsw_module_load(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, char *name); + /* runtime module management */ struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, int mod_id, int offset); -- cgit v0.10.2 From 8c43fc2fdda0858879ee4dd7d7ed8e67890f699f Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Tue, 10 Mar 2015 10:41:21 +0800 Subject: ASoC: Intel: add function to load sound effect module waves Try to load module waves and allocate runtime blocks for it if the firmware image of module waves exists. Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c index 8ad733b..b3e957d 100644 --- a/sound/soc/intel/sst-haswell-dsp.c +++ b/sound/soc/intel/sst-haswell-dsp.c @@ -100,6 +100,7 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, && module->type != SST_HSW_MODULE_PCM && module->type != SST_HSW_MODULE_PCM_REFERENCE && module->type != SST_HSW_MODULE_PCM_CAPTURE + && module->type != SST_HSW_MODULE_WAVES && module->type != SST_HSW_MODULE_LPAL) return 0; @@ -139,6 +140,7 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, mod->type = SST_MEM_IRAM; break; case SST_HSW_DRAM: + case SST_HSW_REGS: ram = dsp->addr.lpe; mod->offset = block->ram_offset; mod->type = SST_MEM_DRAM; diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index ec688f5..63740e3 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -1896,11 +1896,27 @@ void sst_hsw_init_module_state(struct sst_hsw *hsw) /* the base fw contains several modules */ for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) { module = sst_module_get_from_id(hsw->dsp, id); - if (module) - module->state = SST_MODULE_STATE_ACTIVE; + if (module) { + /* module waves is active only after being enabled */ + if (id == SST_HSW_MODULE_WAVES) + module->state = SST_MODULE_STATE_INITIALIZED; + else + module->state = SST_MODULE_STATE_ACTIVE; + } } } +bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id) +{ + struct sst_module *module; + + module = sst_module_get_from_id(hsw->dsp, module_id); + if (module == NULL || module->state == SST_MODULE_STATE_UNLOADED) + return false; + else + return true; +} + int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name) { @@ -2022,6 +2038,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) if (ret < 0) goto fw_err; + /* try to load module waves */ + sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "intel/IntcPP01.bin"); + /* allocate scratch mem regions */ ret = sst_block_alloc_scratch(hsw->dsp); if (ret < 0) diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index e071b3a..208724b 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -469,6 +469,7 @@ struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); /* fw module function */ void sst_hsw_init_module_state(struct sst_hsw *hsw); +bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name); diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index 7e21e8f..a604cc4 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -807,6 +807,17 @@ static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) pcm_data->runtime->persistent_offset; } + /* create runtime blocks for module waves */ + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { + pcm_data = &pdata->pcm[HSW_PCM_COUNT-1][0]; + pcm_data->runtime = sst_hsw_runtime_module_create(hsw, + SST_HSW_MODULE_WAVES, pcm_data->persistent_offset); + if (pcm_data->runtime == NULL) + goto err; + pcm_data->persistent_offset = + pcm_data->runtime->persistent_offset; + } + return 0; err: @@ -820,12 +831,16 @@ err: static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) { + struct sst_hsw *hsw = pdata->hsw; struct hsw_pcm_data *pcm_data; int i; for (i = 0; i < ARRAY_SIZE(mod_map); i++) { pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - + sst_hsw_runtime_module_free(pcm_data->runtime); + } + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { + pcm_data = &pdata->pcm[HSW_PCM_COUNT-1][0]; sst_hsw_runtime_module_free(pcm_data->runtime); } } @@ -984,7 +999,9 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform) } /* allocate runtime modules */ - hsw_pcm_create_modules(priv_data); + ret = hsw_pcm_create_modules(priv_data); + if (ret < 0) + goto err; /* enable runtime PM with auto suspend */ pm_runtime_set_autosuspend_delay(platform->dev, -- cgit v0.10.2 From e8e79ede44ec99e09f8604c23ee99dc25065a343 Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Tue, 10 Mar 2015 10:41:22 +0800 Subject: ASoC: Intel: add function to enable/disable sound effect module waves Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index 63740e3..265d754 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -79,6 +79,15 @@ #define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT) #define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT) +/* Module Message */ +#define IPC_MODULE_OPERATION_SHIFT 20 +#define IPC_MODULE_OPERATION_MASK (0xf << IPC_MODULE_OPERATION_SHIFT) +#define IPC_MODULE_OPERATION(x) (x << IPC_MODULE_OPERATION_SHIFT) + +#define IPC_MODULE_ID_SHIFT 16 +#define IPC_MODULE_ID_MASK (0xf << IPC_MODULE_ID_SHIFT) +#define IPC_MODULE_ID(x) (x << IPC_MODULE_ID_SHIFT) + /* IPC message timeout (msecs) */ #define IPC_TIMEOUT_MSECS 300 #define IPC_BOOT_MSECS 200 @@ -115,6 +124,7 @@ enum ipc_glb_type { IPC_GLB_ENTER_DX_STATE = 12, IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */ IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */ + IPC_GLB_MODULE_OPERATION = 15, /* Message to loadable fw module */ IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */ IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */ }; @@ -133,6 +143,16 @@ enum ipc_glb_reply { IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */ }; +enum ipc_module_operation { + IPC_MODULE_NOTIFICATION = 0, + IPC_MODULE_ENABLE = 1, + IPC_MODULE_DISABLE = 2, + IPC_MODULE_GET_PARAMETER = 3, + IPC_MODULE_SET_PARAMETER = 4, + IPC_MODULE_GET_INFO = 5, + IPC_MODULE_MAX_MESSAGE +}; + /* Stream Message - Types */ enum ipc_str_operation { IPC_STR_RESET = 0, @@ -352,6 +372,16 @@ static inline u32 msg_get_notify_reason(u32 msg) return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; } +static inline u32 msg_get_module_operation(u32 msg) +{ + return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT; +} + +static inline u32 msg_get_module_id(u32 msg) +{ + return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT; +} + u32 create_channel_map(enum sst_hsw_channel_config config) { switch (config) { @@ -795,6 +825,31 @@ static int hsw_process_reply(struct sst_hsw *hsw, u32 header) return 1; } +static int hsw_module_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation, module_id; + int handled = 0; + + operation = msg_get_module_operation(header); + module_id = msg_get_module_id(header); + dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n", + header); + dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n", + operation, module_id); + + switch (operation) { + case IPC_MODULE_NOTIFICATION: + dev_dbg(hsw->dev, "module notification received"); + handled = 1; + break; + default: + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + static int hsw_stream_message(struct sst_hsw *hsw, u32 header) { u32 stream_msg, stream_id, stage_type; @@ -890,6 +945,9 @@ static int hsw_process_notification(struct sst_hsw *hsw) case IPC_GLB_DEBUG_LOG_MESSAGE: handled = hsw_log_message(hsw, header); break; + case IPC_GLB_MODULE_OPERATION: + handled = hsw_module_message(hsw, header); + break; default: dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n", type, header); @@ -1917,6 +1975,17 @@ bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id) return true; } +bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id) +{ + struct sst_module *module; + + module = sst_module_get_from_id(hsw->dsp, module_id); + if (module != NULL && module->state == SST_MODULE_STATE_ACTIVE) + return true; + else + return false; +} + int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name) { @@ -1972,6 +2041,112 @@ out: return ret; } +int sst_hsw_module_enable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id) +{ + int ret; + u32 header = 0; + struct sst_hsw_ipc_module_config config; + struct sst_module *module; + struct sst_module_runtime *runtime; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + if (!sst_hsw_is_module_loaded(hsw, module_id)) { + dev_dbg(dev, "module %d not loaded\n", module_id); + return 0; + } + + if (sst_hsw_is_module_active(hsw, module_id)) { + dev_info(dev, "module %d already enabled\n", module_id); + return 0; + } + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "module %d not valid\n", module_id); + return -ENXIO; + } + + runtime = sst_module_runtime_get_from_id(module, module_id); + if (runtime == NULL) { + dev_err(dev, "runtime %d not valid", module_id); + return -ENXIO; + } + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_ENABLE) | + IPC_MODULE_ID(module_id); + dev_dbg(dev, "module enable header: %x\n", header); + + config.map.module_entries_count = 1; + config.map.module_entries[0].module_id = module->id; + config.map.module_entries[0].entry_point = module->entry; + + config.persistent_mem.offset = + sst_dsp_get_offset(dsp, + runtime->persistent_offset, SST_MEM_DRAM); + config.persistent_mem.size = module->persistent_size; + + config.scratch_mem.offset = + sst_dsp_get_offset(dsp, + dsp->scratch_offset, SST_MEM_DRAM); + config.scratch_mem.size = module->scratch_size; + dev_dbg(dev, "mod %d enable p:%d @ %x, s:%d @ %x, ep: %x", + config.map.module_entries[0].module_id, + config.persistent_mem.size, + config.persistent_mem.offset, + config.scratch_mem.size, config.scratch_mem.offset, + config.map.module_entries[0].entry_point); + + ret = ipc_tx_message_wait(hsw, header, + &config, sizeof(config), NULL, 0); + if (ret < 0) + dev_err(dev, "ipc: module enable failed - %d\n", ret); + else + module->state = SST_MODULE_STATE_ACTIVE; + + return ret; +} + +int sst_hsw_module_disable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id) +{ + int ret; + u32 header; + struct sst_module *module; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + if (!sst_hsw_is_module_loaded(hsw, module_id)) { + dev_dbg(dev, "module %d not loaded\n", module_id); + return 0; + } + + if (!sst_hsw_is_module_active(hsw, module_id)) { + dev_info(dev, "module %d already disabled\n", module_id); + return 0; + } + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "module %d not valid\n", module_id); + return -ENXIO; + } + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) | + IPC_MODULE_ID(module_id); + + ret = ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); + if (ret < 0) + dev_err(dev, "module disable failed - %d\n", ret); + else + module->state = SST_MODULE_STATE_INITIALIZED; + + return ret; +} + static struct sst_dsp_device hsw_dev = { .thread = hsw_irq_thread, .ops = &haswell_ops, diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index 208724b..30c65b2 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -215,6 +215,12 @@ struct sst_hsw_fx_enable { struct sst_hsw_memory_info persistent_mem; } __attribute__((packed)); +struct sst_hsw_ipc_module_config { + struct sst_hsw_module_map map; + struct sst_hsw_memory_info persistent_mem; + struct sst_hsw_memory_info scratch_mem; +} __attribute__((packed)); + struct sst_hsw_get_fx_param { u32 parameter_id; u32 param_size; @@ -470,9 +476,14 @@ struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); /* fw module function */ void sst_hsw_init_module_state(struct sst_hsw *hsw); bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); +bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name); +int sst_hsw_module_enable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id); +int sst_hsw_module_disable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id); /* runtime module management */ struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, -- cgit v0.10.2 From b3f5dbec2f077be7ee392da3365d11e393a50038 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Wed, 11 Mar 2015 17:56:33 +0100 Subject: ASoC: ab8500-codec: don't export static symbol The semantic patch that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @r@ type T; identifier f; @@ static T f (...) { ... } @@ identifier r.f; declarer name EXPORT_SYMBOL_GPL; @@ -EXPORT_SYMBOL_GPL(f); // Signed-off-by: Julia Lawall Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 7895689..88ca9cb 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -2003,7 +2003,6 @@ static int ab8500_audio_setup_mics(struct snd_soc_codec *codec, return 0; } -EXPORT_SYMBOL_GPL(ab8500_audio_setup_mics); static int ab8500_audio_set_ear_cmv(struct snd_soc_codec *codec, enum ear_cm_voltage ear_cmv) @@ -2036,7 +2035,6 @@ static int ab8500_audio_set_ear_cmv(struct snd_soc_codec *codec, return 0; } -EXPORT_SYMBOL_GPL(ab8500_audio_set_ear_cmv); static int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, unsigned int delay) -- cgit v0.10.2 From 1ff2765182d1969947dfb50f4e42ebd7e83699fd Mon Sep 17 00:00:00 2001 From: Anish Kumar Date: Mon, 9 Mar 2015 15:50:34 -0700 Subject: ASoC: Add max98925 codec driver Signed-off-by: Anish Kumar Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/max98925.txt b/Documentation/devicetree/bindings/sound/max98925.txt new file mode 100644 index 0000000..27be63e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/max98925.txt @@ -0,0 +1,22 @@ +max98925 audio CODEC + +This device supports I2C. + +Required properties: + + - compatible : "maxim,max98925" + + - vmon-slot-no : slot number used to send voltage information + + - imon-slot-no : slot number used to send current information + + - reg : the I2C address of the device for I2C + +Example: + +codec: max98925@1a { + compatible = "maxim,max98925"; + vmon-slot-no = <0>; + imon-slot-no = <2>; + reg = <0x1a>; +}; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ea9f0e3..0ccce5a 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -70,6 +70,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX98357A if GPIOLIB + select SND_SOC_MAX98925 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C @@ -460,6 +461,9 @@ config SND_SOC_MAX98095 config SND_SOC_MAX98357A tristate +config SND_SOC_MAX98925 + tristate + config SND_SOC_MAX9850 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 69b8666..333ee3a 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -65,6 +65,7 @@ snd-soc-max98088-objs := max98088.o snd-soc-max98090-objs := max98090.o snd-soc-max98095-objs := max98095.o snd-soc-max98357a-objs := max98357a.o +snd-soc-max98925-objs := max98925.o snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o @@ -247,6 +248,7 @@ obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_MAX98090) += snd-soc-max98090.o obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o +obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c new file mode 100644 index 0000000..74f4f0b --- /dev/null +++ b/sound/soc/codecs/max98925.c @@ -0,0 +1,655 @@ +/* + * max98925.c -- ALSA SoC Stereo MAX98925 driver + * Copyright 2013-15 Maxim Integrated Products + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98925.h" + +static const char *const dai_text[] = { + "Left", "Right", "LeftRight", "LeftRightDiv2", +}; + +static const char * const max98925_boost_voltage_text[] = { + "8.5V", "8.25V", "8.0V", "7.75V", "7.5V", "7.25V", "7.0V", "6.75V", + "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V" +}; + +static SOC_ENUM_SINGLE_DECL(max98925_boost_voltage, + MAX98925_CONFIGURATION, M98925_BST_VOUT_SHIFT, + max98925_boost_voltage_text); + +static const char *const hpf_text[] = { + "Disable", "DC Block", "100Hz", "200Hz", "400Hz", "800Hz", +}; + +static struct reg_default max98925_reg[] = { + { 0x0B, 0x00 }, /* IRQ Enable0 */ + { 0x0C, 0x00 }, /* IRQ Enable1 */ + { 0x0D, 0x00 }, /* IRQ Enable2 */ + { 0x0E, 0x00 }, /* IRQ Clear0 */ + { 0x0F, 0x00 }, /* IRQ Clear1 */ + { 0x10, 0x00 }, /* IRQ Clear2 */ + { 0x11, 0xC0 }, /* Map0 */ + { 0x12, 0x00 }, /* Map1 */ + { 0x13, 0x00 }, /* Map2 */ + { 0x14, 0xF0 }, /* Map3 */ + { 0x15, 0x00 }, /* Map4 */ + { 0x16, 0xAB }, /* Map5 */ + { 0x17, 0x89 }, /* Map6 */ + { 0x18, 0x00 }, /* Map7 */ + { 0x19, 0x00 }, /* Map8 */ + { 0x1A, 0x06 }, /* DAI Clock Mode 1 */ + { 0x1B, 0xC0 }, /* DAI Clock Mode 2 */ + { 0x1C, 0x00 }, /* DAI Clock Divider Denominator MSBs */ + { 0x1D, 0x00 }, /* DAI Clock Divider Denominator LSBs */ + { 0x1E, 0xF0 }, /* DAI Clock Divider Numerator MSBs */ + { 0x1F, 0x00 }, /* DAI Clock Divider Numerator LSBs */ + { 0x20, 0x50 }, /* Format */ + { 0x21, 0x00 }, /* TDM Slot Select */ + { 0x22, 0x00 }, /* DOUT Configuration VMON */ + { 0x23, 0x00 }, /* DOUT Configuration IMON */ + { 0x24, 0x00 }, /* DOUT Configuration VBAT */ + { 0x25, 0x00 }, /* DOUT Configuration VBST */ + { 0x26, 0x00 }, /* DOUT Configuration FLAG */ + { 0x27, 0xFF }, /* DOUT HiZ Configuration 1 */ + { 0x28, 0xFF }, /* DOUT HiZ Configuration 2 */ + { 0x29, 0xFF }, /* DOUT HiZ Configuration 3 */ + { 0x2A, 0xFF }, /* DOUT HiZ Configuration 4 */ + { 0x2B, 0x02 }, /* DOUT Drive Strength */ + { 0x2C, 0x90 }, /* Filters */ + { 0x2D, 0x00 }, /* Gain */ + { 0x2E, 0x02 }, /* Gain Ramping */ + { 0x2F, 0x00 }, /* Speaker Amplifier */ + { 0x30, 0x0A }, /* Threshold */ + { 0x31, 0x00 }, /* ALC Attack */ + { 0x32, 0x80 }, /* ALC Atten and Release */ + { 0x33, 0x00 }, /* ALC Infinite Hold Release */ + { 0x34, 0x92 }, /* ALC Configuration */ + { 0x35, 0x01 }, /* Boost Converter */ + { 0x36, 0x00 }, /* Block Enable */ + { 0x37, 0x00 }, /* Configuration */ + { 0x38, 0x00 }, /* Global Enable */ + { 0x3A, 0x00 }, /* Boost Limiter */ +}; + +static const struct soc_enum max98925_dai_enum = + SOC_ENUM_SINGLE(MAX98925_GAIN, 5, ARRAY_SIZE(dai_text), dai_text); + +static const struct soc_enum max98925_hpf_enum = + SOC_ENUM_SINGLE(MAX98925_FILTERS, 0, ARRAY_SIZE(hpf_text), hpf_text); + +static const struct snd_kcontrol_new max98925_hpf_sel_mux = + SOC_DAPM_ENUM("Rc Filter MUX Mux", max98925_hpf_enum); + +static const struct snd_kcontrol_new max98925_dai_sel_mux = + SOC_DAPM_ENUM("DAI IN MUX Mux", max98925_dai_enum); + +static int max98925_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct max98925_priv *max98925 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(max98925->regmap, + MAX98925_BLOCK_ENABLE, + M98925_BST_EN_MASK | + M98925_ADC_IMON_EN_MASK | M98925_ADC_VMON_EN_MASK, + M98925_BST_EN_MASK | + M98925_ADC_IMON_EN_MASK | M98925_ADC_VMON_EN_MASK); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(max98925->regmap, + MAX98925_BLOCK_ENABLE, M98925_BST_EN_MASK | + M98925_ADC_IMON_EN_MASK | M98925_ADC_VMON_EN_MASK, 0); + break; + default: + return 0; + } + return 0; +} + +static const struct snd_soc_dapm_widget max98925_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("DAI IN MUX", SND_SOC_NOPM, 0, 0, + &max98925_dai_sel_mux), + SND_SOC_DAPM_MUX("Rc Filter MUX", SND_SOC_NOPM, 0, 0, + &max98925_hpf_sel_mux), + SND_SOC_DAPM_DAC_E("Amp Enable", NULL, MAX98925_BLOCK_ENABLE, + M98925_SPK_EN_SHIFT, 0, max98925_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98925_GLOBAL_ENABLE, + M98925_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("BE_OUT"), +}; + +static const struct snd_soc_dapm_route max98925_audio_map[] = { + {"DAI IN MUX", "Left", "DAI_OUT"}, + {"DAI IN MUX", "Right", "DAI_OUT"}, + {"DAI IN MUX", "LeftRight", "DAI_OUT"}, + {"DAI IN MUX", "LeftRightDiv2", "DAI_OUT"}, + {"Rc Filter MUX", "Disable", "DAI IN MUX"}, + {"Rc Filter MUX", "DC Block", "DAI IN MUX"}, + {"Rc Filter MUX", "100Hz", "DAI IN MUX"}, + {"Rc Filter MUX", "200Hz", "DAI IN MUX"}, + {"Rc Filter MUX", "400Hz", "DAI IN MUX"}, + {"Rc Filter MUX", "800Hz", "DAI IN MUX"}, + {"Amp Enable", NULL, "Rc Filter MUX"}, + {"BE_OUT", NULL, "Amp Enable"}, + {"BE_OUT", NULL, "Global Enable"}, +}; + +static bool max98925_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98925_VBAT_DATA: + case MAX98925_VBST_DATA: + case MAX98925_LIVE_STATUS0: + case MAX98925_LIVE_STATUS1: + case MAX98925_LIVE_STATUS2: + case MAX98925_STATE0: + case MAX98925_STATE1: + case MAX98925_STATE2: + case MAX98925_FLAG0: + case MAX98925_FLAG1: + case MAX98925_FLAG2: + case MAX98925_REV_VERSION: + return true; + default: + return false; + } +} + +static bool max98925_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98925_IRQ_CLEAR0: + case MAX98925_IRQ_CLEAR1: + case MAX98925_IRQ_CLEAR2: + case MAX98925_ALC_HOLD_RLS: + return false; + default: + return true; + } +} + +DECLARE_TLV_DB_SCALE(max98925_spk_tlv, -600, 100, 0); + +static const struct snd_kcontrol_new max98925_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98925_GAIN, + M98925_SPK_GAIN_SHIFT, (1<= rate) { + *value = rate_table[i].sr; + *n = rate_table[i].divisors[clock][0]; + *m = rate_table[i].divisors[clock][1]; + ret = 0; + break; + } + } + dev_dbg(codec->dev, "%s: sample rate is %d, returning %d\n", + __func__, rate_table[i].rate, *value); + return ret; +} + +static void max98925_set_sense_data(struct max98925_priv *max98925) +{ + /* set VMON slots */ + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_VMON, + M98925_DAI_VMON_EN_MASK, M98925_DAI_VMON_EN_MASK); + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_VMON, + M98925_DAI_VMON_SLOT_MASK, + max98925->v_slot << M98925_DAI_VMON_SLOT_SHIFT); + /* set IMON slots */ + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_IMON, + M98925_DAI_IMON_EN_MASK, M98925_DAI_IMON_EN_MASK); + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_IMON, + M98925_DAI_IMON_SLOT_MASK, + max98925->i_slot << M98925_DAI_IMON_SLOT_SHIFT); +} + +static int max98925_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98925_priv *max98925 = snd_soc_codec_get_drvdata(codec); + unsigned int invert = 0; + + dev_dbg(codec->dev, "%s: fmt 0x%08X\n", __func__, fmt); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* set DAI to slave mode */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_MAS_MASK, 0); + max98925_set_sense_data(max98925); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* + * set left channel DAI to master mode, + * right channel always slave + */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_MAS_MASK, M98925_DAI_MAS_MASK); + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(codec->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + invert = M98925_DAI_WCI_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + invert = M98925_DAI_BCI_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + invert = M98925_DAI_BCI_MASK | M98925_DAI_WCI_MASK; + break; + default: + dev_err(codec->dev, "DAI invert mode unsupported"); + return -EINVAL; + } + + regmap_update_bits(max98925->regmap, MAX98925_FORMAT, + M98925_DAI_BCI_MASK | M98925_DAI_BCI_MASK, invert); + return 0; +} + +static int max98925_set_clock(struct max98925_priv *max98925, + struct snd_pcm_hw_params *params) +{ + unsigned int dai_sr = 0, clock, mdll, n, m; + struct snd_soc_codec *codec = max98925->codec; + int rate = params_rate(params); + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98925->ch_size; + + switch (blr_clk_ratio) { + case 32: + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_BSEL_MASK, M98925_DAI_BSEL_32); + break; + case 48: + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_BSEL_MASK, M98925_DAI_BSEL_48); + break; + case 64: + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_BSEL_MASK, M98925_DAI_BSEL_64); + break; + default: + return -EINVAL; + } + + switch (max98925->sysclk) { + case 6000000: + clock = 0; + mdll = M98925_MDLL_MULT_MCLKx16; + break; + case 11289600: + clock = 1; + mdll = M98925_MDLL_MULT_MCLKx8; + break; + case 12000000: + clock = 0; + mdll = M98925_MDLL_MULT_MCLKx8; + break; + case 12288000: + clock = 2; + mdll = M98925_MDLL_MULT_MCLKx8; + break; + default: + dev_info(max98925->codec->dev, "unsupported sysclk %d\n", + max98925->sysclk); + return -EINVAL; + } + + if (max98925_rate_value(codec, rate, clock, &dai_sr, &n, &m)) + return -EINVAL; + + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_SR_MASK, dai_sr << M98925_DAI_SR_SHIFT); + /* set DAI m divider */ + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_M_MSBS, m >> 8); + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_M_LSBS, m & 0xFF); + /* set DAI n divider */ + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_N_MSBS, n >> 8); + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_N_LSBS, n & 0xFF); + /* set MDLL */ + regmap_update_bits(max98925->regmap, MAX98925_DAI_CLK_MODE1, + M98925_MDLL_MULT_MASK, mdll << M98925_MDLL_MULT_SHIFT); + return 0; +} + +static int max98925_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98925_priv *max98925 = snd_soc_codec_get_drvdata(codec); + + switch (snd_pcm_format_width(params_format(params))) { + case 16: + regmap_update_bits(max98925->regmap, + MAX98925_FORMAT, + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_16); + max98925->ch_size = 16; + break; + case 24: + regmap_update_bits(max98925->regmap, + MAX98925_FORMAT, + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_32); + max98925->ch_size = 32; + break; + case 32: + regmap_update_bits(max98925->regmap, + MAX98925_FORMAT, + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_32); + max98925->ch_size = 32; + break; + default: + pr_err("%s: format unsupported %d", + __func__, params_format(params)); + return -EINVAL; + } + dev_dbg(codec->dev, "%s: format supported %d", + __func__, params_format(params)); + return max98925_set_clock(max98925, params); +} + +static int max98925_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98925_priv *max98925 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case 0: + /* use MCLK for Left channel, right channel always BCLK */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE1, + M98925_DAI_CLK_SOURCE_MASK, 0); + break; + case 1: + /* configure dai clock source to BCLK instead of MCLK */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE1, + M98925_DAI_CLK_SOURCE_MASK, + M98925_DAI_CLK_SOURCE_MASK); + break; + default: + return -EINVAL; + } + max98925->sysclk = freq; + return 0; +} + +#define MAX98925_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops max98925_dai_ops = { + .set_sysclk = max98925_dai_set_sysclk, + .set_fmt = max98925_dai_set_fmt, + .hw_params = max98925_dai_hw_params, +}; + +static struct snd_soc_dai_driver max98925_dai[] = { + { + .name = "max98925-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98925_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98925_FORMATS, + }, + .ops = &max98925_dai_ops, + } +}; + +static int max98925_probe(struct snd_soc_codec *codec) +{ + struct max98925_priv *max98925 = snd_soc_codec_get_drvdata(codec); + + max98925->codec = codec; + codec->control_data = max98925->regmap; + regmap_write(max98925->regmap, MAX98925_GLOBAL_ENABLE, 0x00); + /* It's not the default but we need to set DAI_DLY */ + regmap_write(max98925->regmap, + MAX98925_FORMAT, M98925_DAI_DLY_MASK); + regmap_write(max98925->regmap, MAX98925_TDM_SLOT_SELECT, 0xC8); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG1, 0xFF); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG2, 0xFF); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG3, 0xFF); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG4, 0xF0); + regmap_write(max98925->regmap, MAX98925_FILTERS, 0xD8); + regmap_write(max98925->regmap, MAX98925_ALC_CONFIGURATION, 0xF8); + regmap_write(max98925->regmap, MAX98925_CONFIGURATION, 0xF0); + /* Disable ALC muting */ + regmap_write(max98925->regmap, MAX98925_BOOST_LIMITER, 0xF8); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_max98925 = { + .probe = max98925_probe, + .controls = max98925_snd_controls, + .num_controls = ARRAY_SIZE(max98925_snd_controls), + .dapm_routes = max98925_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98925_audio_map), + .dapm_widgets = max98925_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98925_dapm_widgets), +}; + +static struct regmap_config max98925_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX98925_REV_VERSION, + .reg_defaults = max98925_reg, + .num_reg_defaults = ARRAY_SIZE(max98925_reg), + .volatile_reg = max98925_volatile_register, + .readable_reg = max98925_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98925_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret, reg; + u32 value; + struct max98925_priv *max98925; + + max98925 = devm_kzalloc(&i2c->dev, + sizeof(*max98925), GFP_KERNEL); + if (!max98925) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98925); + max98925->regmap = devm_regmap_init_i2c(i2c, &max98925_regmap); + if (IS_ERR(max98925->regmap)) { + ret = PTR_ERR(max98925->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + goto err_out; + } + + if (!of_property_read_u32(i2c->dev.of_node, "vmon-slot-no", &value)) { + if (value > M98925_DAI_VMON_SLOT_1E_1F) { + dev_err(&i2c->dev, "vmon slot number is wrong:\n"); + return -EINVAL; + } + max98925->v_slot = value; + } + if (!of_property_read_u32(i2c->dev.of_node, "imon-slot-no", &value)) { + if (value > M98925_DAI_IMON_SLOT_1E_1F) { + dev_err(&i2c->dev, "imon slot number is wrong:\n"); + return -EINVAL; + } + max98925->i_slot = value; + } + ret = regmap_read(max98925->regmap, + MAX98925_REV_VERSION, ®); + if ((ret < 0) || + ((reg != MAX98925_VERSION) && + (reg != MAX98925_VERSION1))) { + dev_err(&i2c->dev, + "device initialization error (%d 0x%02X)\n", + ret, reg); + goto err_out; + } + dev_info(&i2c->dev, "device version 0x%02X\n", reg); + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98925, + max98925_dai, ARRAY_SIZE(max98925_dai)); + if (ret < 0) + dev_err(&i2c->dev, + "Failed to register codec: %d\n", ret); +err_out: + return ret; +} + +static int max98925_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id max98925_i2c_id[] = { + { "max98925", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98925_i2c_id); + +static const struct of_device_id max98925_of_match[] = { + { .compatible = "maxim,max98925", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98925_of_match); + +static struct i2c_driver max98925_i2c_driver = { + .driver = { + .name = "max98925", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max98925_of_match), + .pm = NULL, + }, + .probe = max98925_i2c_probe, + .remove = max98925_i2c_remove, + .id_table = max98925_i2c_id, +}; + +module_i2c_driver(max98925_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98925 driver"); +MODULE_AUTHOR("Ralph Birt , Anish kumar "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98925.h b/sound/soc/codecs/max98925.h new file mode 100644 index 0000000..3783248 --- /dev/null +++ b/sound/soc/codecs/max98925.h @@ -0,0 +1,832 @@ +/* + * max98925.h -- MAX98925 ALSA SoC Audio driver + * + * Copyright 2013-2015 Maxim Integrated Products + * + * 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 _MAX98925_H +#define _MAX98925_H + +#define MAX98925_VERSION 0x51 +#define MAX98925_VERSION1 0x80 +#define MAX98925_VBAT_DATA 0x00 +#define MAX98925_VBST_DATA 0x01 +#define MAX98925_LIVE_STATUS0 0x02 +#define MAX98925_LIVE_STATUS1 0x03 +#define MAX98925_LIVE_STATUS2 0x04 +#define MAX98925_STATE0 0x05 +#define MAX98925_STATE1 0x06 +#define MAX98925_STATE2 0x07 +#define MAX98925_FLAG0 0x08 +#define MAX98925_FLAG1 0x09 +#define MAX98925_FLAG2 0x0A +#define MAX98925_IRQ_ENABLE0 0x0B +#define MAX98925_IRQ_ENABLE1 0x0C +#define MAX98925_IRQ_ENABLE2 0x0D +#define MAX98925_IRQ_CLEAR0 0x0E +#define MAX98925_IRQ_CLEAR1 0x0F +#define MAX98925_IRQ_CLEAR2 0x10 +#define MAX98925_MAP0 0x11 +#define MAX98925_MAP1 0x12 +#define MAX98925_MAP2 0x13 +#define MAX98925_MAP3 0x14 +#define MAX98925_MAP4 0x15 +#define MAX98925_MAP5 0x16 +#define MAX98925_MAP6 0x17 +#define MAX98925_MAP7 0x18 +#define MAX98925_MAP8 0x19 +#define MAX98925_DAI_CLK_MODE1 0x1A +#define MAX98925_DAI_CLK_MODE2 0x1B +#define MAX98925_DAI_CLK_DIV_M_MSBS 0x1C +#define MAX98925_DAI_CLK_DIV_M_LSBS 0x1D +#define MAX98925_DAI_CLK_DIV_N_MSBS 0x1E +#define MAX98925_DAI_CLK_DIV_N_LSBS 0x1F +#define MAX98925_FORMAT 0x20 +#define MAX98925_TDM_SLOT_SELECT 0x21 +#define MAX98925_DOUT_CFG_VMON 0x22 +#define MAX98925_DOUT_CFG_IMON 0x23 +#define MAX98925_DOUT_CFG_VBAT 0x24 +#define MAX98925_DOUT_CFG_VBST 0x25 +#define MAX98925_DOUT_CFG_FLAG 0x26 +#define MAX98925_DOUT_HIZ_CFG1 0x27 +#define MAX98925_DOUT_HIZ_CFG2 0x28 +#define MAX98925_DOUT_HIZ_CFG3 0x29 +#define MAX98925_DOUT_HIZ_CFG4 0x2A +#define MAX98925_DOUT_DRV_STRENGTH 0x2B +#define MAX98925_FILTERS 0x2C +#define MAX98925_GAIN 0x2D +#define MAX98925_GAIN_RAMPING 0x2E +#define MAX98925_SPK_AMP 0x2F +#define MAX98925_THRESHOLD 0x30 +#define MAX98925_ALC_ATTACK 0x31 +#define MAX98925_ALC_ATTEN_RLS 0x32 +#define MAX98925_ALC_HOLD_RLS 0x33 +#define MAX98925_ALC_CONFIGURATION 0x34 +#define MAX98925_BOOST_CONVERTER 0x35 +#define MAX98925_BLOCK_ENABLE 0x36 +#define MAX98925_CONFIGURATION 0x37 +#define MAX98925_GLOBAL_ENABLE 0x38 +#define MAX98925_BOOST_LIMITER 0x3A +#define MAX98925_REV_VERSION 0xFF + +#define MAX98925_REG_CNT (MAX98925_R03A_BOOST_LIMITER+1) + +/* MAX98925 Register Bit Fields */ + +/* MAX98925_R002_LIVE_STATUS0 */ +#define M98925_THERMWARN_STATUS_MASK (1<<3) +#define M98925_THERMWARN_STATUS_SHIFT 3 +#define M98925_THERMWARN_STATUS_WIDTH 1 +#define M98925_THERMSHDN_STATUS_MASK (1<<1) +#define M98925_THERMSHDN_STATUS_SHIFT 1 +#define M98925_THERMSHDN_STATUS_WIDTH 1 + +/* MAX98925_R003_LIVE_STATUS1 */ +#define M98925_SPKCURNT_STATUS_MASK (1<<5) +#define M98925_SPKCURNT_STATUS_SHIFT 5 +#define M98925_SPKCURNT_STATUS_WIDTH 1 +#define M98925_WATCHFAIL_STATUS_MASK (1<<4) +#define M98925_WATCHFAIL_STATUS_SHIFT 4 +#define M98925_WATCHFAIL_STATUS_WIDTH 1 +#define M98925_ALCINFH_STATUS_MASK (1<<3) +#define M98925_ALCINFH_STATUS_SHIFT 3 +#define M98925_ALCINFH_STATUS_WIDTH 1 +#define M98925_ALCACT_STATUS_MASK (1<<2) +#define M98925_ALCACT_STATUS_SHIFT 2 +#define M98925_ALCACT_STATUS_WIDTH 1 +#define M98925_ALCMUT_STATUS_MASK (1<<1) +#define M98925_ALCMUT_STATUS_SHIFT 1 +#define M98925_ALCMUT_STATUS_WIDTH 1 +#define M98925_ACLP_STATUS_MASK (1<<0) +#define M98925_ACLP_STATUS_SHIFT 0 +#define M98925_ACLP_STATUS_WIDTH 1 + +/* MAX98925_R004_LIVE_STATUS2 */ +#define M98925_SLOTOVRN_STATUS_MASK (1<<6) +#define M98925_SLOTOVRN_STATUS_SHIFT 6 +#define M98925_SLOTOVRN_STATUS_WIDTH 1 +#define M98925_INVALSLOT_STATUS_MASK (1<<5) +#define M98925_INVALSLOT_STATUS_SHIFT 5 +#define M98925_INVALSLOT_STATUS_WIDTH 1 +#define M98925_SLOTCNFLT_STATUS_MASK (1<<4) +#define M98925_SLOTCNFLT_STATUS_SHIFT 4 +#define M98925_SLOTCNFLT_STATUS_WIDTH 1 +#define M98925_VBSTOVFL_STATUS_MASK (1<<3) +#define M98925_VBSTOVFL_STATUS_SHIFT 3 +#define M98925_VBSTOVFL_STATUS_WIDTH 1 +#define M98925_VBATOVFL_STATUS_MASK (1<<2) +#define M98925_VBATOVFL_STATUS_SHIFT 2 +#define M98925_VBATOVFL_STATUS_WIDTH 1 +#define M98925_IMONOVFL_STATUS_MASK (1<<1) +#define M98925_IMONOVFL_STATUS_SHIFT 1 +#define M98925_IMONOVFL_STATUS_WIDTH 1 +#define M98925_VMONOVFL_STATUS_MASK (1<<0) +#define M98925_VMONOVFL_STATUS_SHIFT 0 +#define M98925_VMONOVFL_STATUS_WIDTH 1 + +/* MAX98925_R005_STATE0 */ +#define M98925_THERMWARN_END_STATE_MASK (1<<3) +#define M98925_THERMWARN_END_STATE_SHIFT 3 +#define M98925_THERMWARN_END_STATE_WIDTH 1 +#define M98925_THERMWARN_BGN_STATE_MASK (1<<2) +#define M98925_THERMWARN_BGN_STATE_SHIFT 1 +#define M98925_THERMWARN_BGN_STATE_WIDTH 1 +#define M98925_THERMSHDN_END_STATE_MASK (1<<1) +#define M98925_THERMSHDN_END_STATE_SHIFT 1 +#define M98925_THERMSHDN_END_STATE_WIDTH 1 +#define M98925_THERMSHDN_BGN_STATE_MASK (1<<0) +#define M98925_THERMSHDN_BGN_STATE_SHIFT 0 +#define M98925_THERMSHDN_BGN_STATE_WIDTH 1 + +/* MAX98925_R006_STATE1 */ +#define M98925_SPRCURNT_STATE_MASK (1<<5) +#define M98925_SPRCURNT_STATE_SHIFT 5 +#define M98925_SPRCURNT_STATE_WIDTH 1 +#define M98925_WATCHFAIL_STATE_MASK (1<<4) +#define M98925_WATCHFAIL_STATE_SHIFT 4 +#define M98925_WATCHFAIL_STATE_WIDTH 1 +#define M98925_ALCINFH_STATE_MASK (1<<3) +#define M98925_ALCINFH_STATE_SHIFT 3 +#define M98925_ALCINFH_STATE_WIDTH 1 +#define M98925_ALCACT_STATE_MASK (1<<2) +#define M98925_ALCACT_STATE_SHIFT 2 +#define M98925_ALCACT_STATE_WIDTH 1 +#define M98925_ALCMUT_STATE_MASK (1<<1) +#define M98925_ALCMUT_STATE_SHIFT 1 +#define M98925_ALCMUT_STATE_WIDTH 1 +#define M98925_ALCP_STATE_MASK (1<<0) +#define M98925_ALCP_STATE_SHIFT 0 +#define M98925_ALCP_STATE_WIDTH 1 + +/* MAX98925_R007_STATE2 */ +#define M98925_SLOTOVRN_STATE_MASK (1<<6) +#define M98925_SLOTOVRN_STATE_SHIFT 6 +#define M98925_SLOTOVRN_STATE_WIDTH 1 +#define M98925_INVALSLOT_STATE_MASK (1<<5) +#define M98925_INVALSLOT_STATE_SHIFT 5 +#define M98925_INVALSLOT_STATE_WIDTH 1 +#define M98925_SLOTCNFLT_STATE_MASK (1<<4) +#define M98925_SLOTCNFLT_STATE_SHIFT 4 +#define M98925_SLOTCNFLT_STATE_WIDTH 1 +#define M98925_VBSTOVFL_STATE_MASK (1<<3) +#define M98925_VBSTOVFL_STATE_SHIFT 3 +#define M98925_VBSTOVFL_STATE_WIDTH 1 +#define M98925_VBATOVFL_STATE_MASK (1<<2) +#define M98925_VBATOVFL_STATE_SHIFT 2 +#define M98925_VBATOVFL_STATE_WIDTH 1 +#define M98925_IMONOVFL_STATE_MASK (1<<1) +#define M98925_IMONOVFL_STATE_SHIFT 1 +#define M98925_IMONOVFL_STATE_WIDTH 1 +#define M98925_VMONOVFL_STATE_MASK (1<<0) +#define M98925_VMONOVFL_STATE_SHIFT 0 +#define M98925_VMONOVFL_STATE_WIDTH 1 + +/* MAX98925_R008_FLAG0 */ +#define M98925_THERMWARN_END_FLAG_MASK (1<<3) +#define M98925_THERMWARN_END_FLAG_SHIFT 3 +#define M98925_THERMWARN_END_FLAG_WIDTH 1 +#define M98925_THERMWARN_BGN_FLAG_MASK (1<<2) +#define M98925_THERMWARN_BGN_FLAG_SHIFT 2 +#define M98925_THERMWARN_BGN_FLAG_WIDTH 1 +#define M98925_THERMSHDN_END_FLAG_MASK (1<<1) +#define M98925_THERMSHDN_END_FLAG_SHIFT 1 +#define M98925_THERMSHDN_END_FLAG_WIDTH 1 +#define M98925_THERMSHDN_BGN_FLAG_MASK (1<<0) +#define M98925_THERMSHDN_BGN_FLAG_SHIFT 0 +#define M98925_THERMSHDN_BGN_FLAG_WIDTH 1 + +/* MAX98925_R009_FLAG1 */ +#define M98925_SPKCURNT_FLAG_MASK (1<<5) +#define M98925_SPKCURNT_FLAG_SHIFT 5 +#define M98925_SPKCURNT_FLAG_WIDTH 1 +#define M98925_WATCHFAIL_FLAG_MASK (1<<4) +#define M98925_WATCHFAIL_FLAG_SHIFT 4 +#define M98925_WATCHFAIL_FLAG_WIDTH 1 +#define M98925_ALCINFH_FLAG_MASK (1<<3) +#define M98925_ALCINFH_FLAG_SHIFT 3 +#define M98925_ALCINFH_FLAG_WIDTH 1 +#define M98925_ALCACT_FLAG_MASK (1<<2) +#define M98925_ALCACT_FLAG_SHIFT 2 +#define M98925_ALCACT_FLAG_WIDTH 1 +#define M98925_ALCMUT_FLAG_MASK (1<<1) +#define M98925_ALCMUT_FLAG_SHIFT 1 +#define M98925_ALCMUT_FLAG_WIDTH 1 +#define M98925_ALCP_FLAG_MASK (1<<0) +#define M98925_ALCP_FLAG_SHIFT 0 +#define M98925_ALCP_FLAG_WIDTH 1 + +/* MAX98925_R00A_FLAG2 */ +#define M98925_SLOTOVRN_FLAG_MASK (1<<6) +#define M98925_SLOTOVRN_FLAG_SHIFT 6 +#define M98925_SLOTOVRN_FLAG_WIDTH 1 +#define M98925_INVALSLOT_FLAG_MASK (1<<5) +#define M98925_INVALSLOT_FLAG_SHIFT 5 +#define M98925_INVALSLOT_FLAG_WIDTH 1 +#define M98925_SLOTCNFLT_FLAG_MASK (1<<4) +#define M98925_SLOTCNFLT_FLAG_SHIFT 4 +#define M98925_SLOTCNFLT_FLAG_WIDTH 1 +#define M98925_VBSTOVFL_FLAG_MASK (1<<3) +#define M98925_VBSTOVFL_FLAG_SHIFT 3 +#define M98925_VBSTOVFL_FLAG_WIDTH 1 +#define M98925_VBATOVFL_FLAG_MASK (1<<2) +#define M98925_VBATOVFL_FLAG_SHIFT 2 +#define M98925_VBATOVFL_FLAG_WIDTH 1 +#define M98925_IMONOVFL_FLAG_MASK (1<<1) +#define M98925_IMONOVFL_FLAG_SHIFT 1 +#define M98925_IMONOVFL_FLAG_WIDTH 1 +#define M98925_VMONOVFL_FLAG_MASK (1<<0) +#define M98925_VMONOVFL_FLAG_SHIFT 0 +#define M98925_VMONOVFL_FLAG_WIDTH 1 + +/* MAX98925_R00B_IRQ_ENABLE0 */ +#define M98925_THERMWARN_END_EN_MASK (1<<3) +#define M98925_THERMWARN_END_EN_SHIFT 3 +#define M98925_THERMWARN_END_EN_WIDTH 1 +#define M98925_THERMWARN_BGN_EN_MASK (1<<2) +#define M98925_THERMWARN_BGN_EN_SHIFT 2 +#define M98925_THERMWARN_BGN_EN_WIDTH 1 +#define M98925_THERMSHDN_END_EN_MASK (1<<1) +#define M98925_THERMSHDN_END_EN_SHIFT 1 +#define M98925_THERMSHDN_END_EN_WIDTH 1 +#define M98925_THERMSHDN_BGN_EN_MASK (1<<0) +#define M98925_THERMSHDN_BGN_EN_SHIFT 0 +#define M98925_THERMSHDN_BGN_EN_WIDTH 1 + +/* MAX98925_R00C_IRQ_ENABLE1 */ +#define M98925_SPKCURNT_EN_MASK (1<<5) +#define M98925_SPKCURNT_EN_SHIFT 5 +#define M98925_SPKCURNT_EN_WIDTH 1 +#define M98925_WATCHFAIL_EN_MASK (1<<4) +#define M98925_WATCHFAIL_EN_SHIFT 4 +#define M98925_WATCHFAIL_EN_WIDTH 1 +#define M98925_ALCINFH_EN_MASK (1<<3) +#define M98925_ALCINFH_EN_SHIFT 3 +#define M98925_ALCINFH_EN_WIDTH 1 +#define M98925_ALCACT_EN_MASK (1<<2) +#define M98925_ALCACT_EN_SHIFT 2 +#define M98925_ALCACT_EN_WIDTH 1 +#define M98925_ALCMUT_EN_MASK (1<<1) +#define M98925_ALCMUT_EN_SHIFT 1 +#define M98925_ALCMUT_EN_WIDTH 1 +#define M98925_ALCP_EN_MASK (1<<0) +#define M98925_ALCP_EN_SHIFT 0 +#define M98925_ALCP_EN_WIDTH 1 + +/* MAX98925_R00D_IRQ_ENABLE2 */ +#define M98925_SLOTOVRN_EN_MASK (1<<6) +#define M98925_SLOTOVRN_EN_SHIFT 6 +#define M98925_SLOTOVRN_EN_WIDTH 1 +#define M98925_INVALSLOT_EN_MASK (1<<5) +#define M98925_INVALSLOT_EN_SHIFT 5 +#define M98925_INVALSLOT_EN_WIDTH 1 +#define M98925_SLOTCNFLT_EN_MASK (1<<4) +#define M98925_SLOTCNFLT_EN_SHIFT 4 +#define M98925_SLOTCNFLT_EN_WIDTH 1 +#define M98925_VBSTOVFL_EN_MASK (1<<3) +#define M98925_VBSTOVFL_EN_SHIFT 3 +#define M98925_VBSTOVFL_EN_WIDTH 1 +#define M98925_VBATOVFL_EN_MASK (1<<2) +#define M98925_VBATOVFL_EN_SHIFT 2 +#define M98925_VBATOVFL_EN_WIDTH 1 +#define M98925_IMONOVFL_EN_MASK (1<<1) +#define M98925_IMONOVFL_EN_SHIFT 1 +#define M98925_IMONOVFL_EN_WIDTH 1 +#define M98925_VMONOVFL_EN_MASK (1<<0) +#define M98925_VMONOVFL_EN_SHIFT 0 +#define M98925_VMONOVFL_EN_WIDTH 1 + +/* MAX98925_R00E_IRQ_CLEAR0 */ +#define M98925_THERMWARN_END_CLR_MASK (1<<3) +#define M98925_THERMWARN_END_CLR_SHIFT 3 +#define M98925_THERMWARN_END_CLR_WIDTH 1 +#define M98925_THERMWARN_BGN_CLR_MASK (1<<2) +#define M98925_THERMWARN_BGN_CLR_SHIFT 2 +#define M98925_THERMWARN_BGN_CLR_WIDTH 1 +#define M98925_THERMSHDN_END_CLR_MASK (1<<1) +#define M98925_THERMSHDN_END_CLR_SHIFT 1 +#define M98925_THERMSHDN_END_CLR_WIDTH 1 +#define M98925_THERMSHDN_BGN_CLR_MASK (1<<0) +#define M98925_THERMSHDN_BGN_CLR_SHIFT 0 +#define M98925_THERMSHDN_BGN_CLR_WIDTH 1 + +/* MAX98925_R00F_IRQ_CLEAR1 */ +#define M98925_SPKCURNT_CLR_MASK (1<<5) +#define M98925_SPKCURNT_CLR_SHIFT 5 +#define M98925_SPKCURNT_CLR_WIDTH 1 +#define M98925_WATCHFAIL_CLR_MASK (1<<4) +#define M98925_WATCHFAIL_CLR_SHIFT 4 +#define M98925_WATCHFAIL_CLR_WIDTH 1 +#define M98925_ALCINFH_CLR_MASK (1<<3) +#define M98925_ALCINFH_CLR_SHIFT 3 +#define M98925_ALCINFH_CLR_WIDTH 1 +#define M98925_ALCACT_CLR_MASK (1<<2) +#define M98925_ALCACT_CLR_SHIFT 2 +#define M98925_ALCACT_CLR_WIDTH 1 +#define M98925_ALCMUT_CLR_MASK (1<<1) +#define M98925_ALCMUT_CLR_SHIFT 1 +#define M98925_ALCMUT_CLR_WIDTH 1 +#define M98925_ALCP_CLR_MASK (1<<0) +#define M98925_ALCP_CLR_SHIFT 0 +#define M98925_ALCP_CLR_WIDTH 1 + +/* MAX98925_R010_IRQ_CLEAR2 */ +#define M98925_SLOTOVRN_CLR_MASK (1<<6) +#define M98925_SLOTOVRN_CLR_SHIFT 6 +#define M98925_SLOTOVRN_CLR_WIDTH 1 +#define M98925_INVALSLOT_CLR_MASK (1<<5) +#define M98925_INVALSLOT_CLR_SHIFT 5 +#define M98925_INVALSLOT_CLR_WIDTH 1 +#define M98925_SLOTCNFLT_CLR_MASK (1<<4) +#define M98925_SLOTCNFLT_CLR_SHIFT 4 +#define M98925_SLOTCNFLT_CLR_WIDTH 1 +#define M98925_VBSTOVFL_CLR_MASK (1<<3) +#define M98925_VBSTOVFL_CLR_SHIFT 3 +#define M98925_VBSTOVFL_CLR_WIDTH 1 +#define M98925_VBATOVFL_CLR_MASK (1<<2) +#define M98925_VBATOVFL_CLR_SHIFT 2 +#define M98925_VBATOVFL_CLR_WIDTH 1 +#define M98925_IMONOVFL_CLR_MASK (1<<1) +#define M98925_IMONOVFL_CLR_SHIFT 1 +#define M98925_IMONOVFL_CLR_WIDTH 1 +#define M98925_VMONOVFL_CLR_MASK (1<<0) +#define M98925_VMONOVFL_CLR_SHIFT 0 +#define M98925_VMONOVFL_CLR_WIDTH 1 + +/* MAX98925_R011_MAP0 */ +#define M98925_ER_THERMWARN_EN_MASK (1<<7) +#define M98925_ER_THERMWARN_EN_SHIFT 7 +#define M98925_ER_THERMWARN_EN_WIDTH 1 +#define M98925_ER_THERMWARN_MAP_MASK (0x07<<4) +#define M98925_ER_THERMWARN_MAP_SHIFT 4 +#define M98925_ER_THERMWARN_MAP_WIDTH 3 + +/* MAX98925_R012_MAP1 */ +#define M98925_ER_ALCMUT_EN_MASK (1<<7) +#define M98925_ER_ALCMUT_EN_SHIFT 7 +#define M98925_ER_ALCMUT_EN_WIDTH 1 +#define M98925_ER_ALCMUT_MAP_MASK (0x07<<4) +#define M98925_ER_ALCMUT_MAP_SHIFT 4 +#define M98925_ER_ALCMUT_MAP_WIDTH 3 +#define M98925_ER_ALCP_EN_MASK (1<<3) +#define M98925_ER_ALCP_EN_SHIFT 3 +#define M98925_ER_ALCP_EN_WIDTH 1 +#define M98925_ER_ALCP_MAP_MASK (0x07<<0) +#define M98925_ER_ALCP_MAP_SHIFT 0 +#define M98925_ER_ALCP_MAP_WIDTH 3 + +/* MAX98925_R013_MAP2 */ +#define M98925_ER_ALCINFH_EN_MASK (1<<7) +#define M98925_ER_ALCINFH_EN_SHIFT 7 +#define M98925_ER_ALCINFH_EN_WIDTH 1 +#define M98925_ER_ALCINFH_MAP_MASK (0x07<<4) +#define M98925_ER_ALCINFH_MAP_SHIFT 4 +#define M98925_ER_ALCINFH_MAP_WIDTH 3 +#define M98925_ER_ALCACT_EN_MASK (1<<3) +#define M98925_ER_ALCACT_EN_SHIFT 3 +#define M98925_ER_ALCACT_EN_WIDTH 1 +#define M98925_ER_ALCACT_MAP_MASK (0x07<<0) +#define M98925_ER_ALCACT_MAP_SHIFT 0 +#define M98925_ER_ALCACT_MAP_WIDTH 3 + +/* MAX98925_R014_MAP3 */ +#define M98925_ER_SPKCURNT_EN_MASK (1<<7) +#define M98925_ER_SPKCURNT_EN_SHIFT 7 +#define M98925_ER_SPKCURNT_EN_WIDTH 1 +#define M98925_ER_SPKCURNT_MAP_MASK (0x07<<4) +#define M98925_ER_SPKCURNT_MAP_SHIFT 4 +#define M98925_ER_SPKCURNT_MAP_WIDTH 3 + +/* MAX98925_R015_MAP4 */ +/* RESERVED */ + +/* MAX98925_R016_MAP5 */ +#define M98925_ER_IMONOVFL_EN_MASK (1<<7) +#define M98925_ER_IMONOVFL_EN_SHIFT 7 +#define M98925_ER_IMONOVFL_EN_WIDTH 1 +#define M98925_ER_IMONOVFL_MAP_MASK (0x07<<4) +#define M98925_ER_IMONOVFL_MAP_SHIFT 4 +#define M98925_ER_IMONOVFL_MAP_WIDTH 3 +#define M98925_ER_VMONOVFL_EN_MASK (1<<3) +#define M98925_ER_VMONOVFL_EN_SHIFT 3 +#define M98925_ER_VMONOVFL_EN_WIDTH 1 +#define M98925_ER_VMONOVFL_MAP_MASK (0x07<<0) +#define M98925_ER_VMONOVFL_MAP_SHIFT 0 +#define M98925_ER_VMONOVFL_MAP_WIDTH 3 + +/* MAX98925_R017_MAP6 */ +#define M98925_ER_VBSTOVFL_EN_MASK (1<<7) +#define M98925_ER_VBSTOVFL_EN_SHIFT 7 +#define M98925_ER_VBSTOVFL_EN_WIDTH 1 +#define M98925_ER_VBSTOVFL_MAP_MASK (0x07<<4) +#define M98925_ER_VBSTOVFL_MAP_SHIFT 4 +#define M98925_ER_VBSTOVFL_MAP_WIDTH 3 +#define M98925_ER_VBATOVFL_EN_MASK (1<<3) +#define M98925_ER_VBATOVFL_EN_SHIFT 3 +#define M98925_ER_VBATOVFL_EN_WIDTH 1 +#define M98925_ER_VBATOVFL_MAP_MASK (0x07<<0) +#define M98925_ER_VBATOVFL_MAP_SHIFT 0 +#define M98925_ER_VBATOVFL_MAP_WIDTH 3 + +/* MAX98925_R018_MAP7 */ +#define M98925_ER_INVALSLOT_EN_MASK (1<<7) +#define M98925_ER_INVALSLOT_EN_SHIFT 7 +#define M98925_ER_INVALSLOT_EN_WIDTH 1 +#define M98925_ER_INVALSLOT_MAP_MASK (0x07<<4) +#define M98925_ER_INVALSLOT_MAP_SHIFT 4 +#define M98925_ER_INVALSLOT_MAP_WIDTH 3 +#define M98925_ER_SLOTCNFLT_EN_MASK (1<<3) +#define M98925_ER_SLOTCNFLT_EN_SHIFT 3 +#define M98925_ER_SLOTCNFLT_EN_WIDTH 1 +#define M98925_ER_SLOTCNFLT_MAP_MASK (0x07<<0) +#define M98925_ER_SLOTCNFLT_MAP_SHIFT 0 +#define M98925_ER_SLOTCNFLT_MAP_WIDTH 3 + +/* MAX98925_R019_MAP8 */ +#define M98925_ER_SLOTOVRN_EN_MASK (1<<3) +#define M98925_ER_SLOTOVRN_EN_SHIFT 3 +#define M98925_ER_SLOTOVRN_EN_WIDTH 1 +#define M98925_ER_SLOTOVRN_MAP_MASK (0x07<<0) +#define M98925_ER_SLOTOVRN_MAP_SHIFT 0 +#define M98925_ER_SLOTOVRN_MAP_WIDTH 3 + +/* MAX98925_R01A_DAI_CLK_MODE1 */ +#define M98925_DAI_CLK_SOURCE_MASK (1<<6) +#define M98925_DAI_CLK_SOURCE_SHIFT 6 +#define M98925_DAI_CLK_SOURCE_WIDTH 1 +#define M98925_MDLL_MULT_MASK (0x0F<<0) +#define M98925_MDLL_MULT_SHIFT 0 +#define M98925_MDLL_MULT_WIDTH 4 + +#define M98925_MDLL_MULT_MCLKx8 6 +#define M98925_MDLL_MULT_MCLKx16 8 + +/* MAX98925_R01B_DAI_CLK_MODE2 */ +#define M98925_DAI_SR_MASK (0x0F<<4) +#define M98925_DAI_SR_SHIFT 4 +#define M98925_DAI_SR_WIDTH 4 +#define M98925_DAI_MAS_MASK (1<<3) +#define M98925_DAI_MAS_SHIFT 3 +#define M98925_DAI_MAS_WIDTH 1 +#define M98925_DAI_BSEL_MASK (0x07<<0) +#define M98925_DAI_BSEL_SHIFT 0 +#define M98925_DAI_BSEL_WIDTH 3 + +#define M98925_DAI_BSEL_32 (0 << M98925_DAI_BSEL_SHIFT) +#define M98925_DAI_BSEL_48 (1 << M98925_DAI_BSEL_SHIFT) +#define M98925_DAI_BSEL_64 (2 << M98925_DAI_BSEL_SHIFT) +#define M98925_DAI_BSEL_256 (6 << M98925_DAI_BSEL_SHIFT) + +/* MAX98925_R01C_DAI_CLK_DIV_M_MSBS */ +#define M98925_DAI_M_MSBS_MASK (0xFF<<0) +#define M98925_DAI_M_MSBS_SHIFT 0 +#define M98925_DAI_M_MSBS_WIDTH 8 + +/* MAX98925_R01D_DAI_CLK_DIV_M_LSBS */ +#define M98925_DAI_M_LSBS_MASK (0xFF<<0) +#define M98925_DAI_M_LSBS_SHIFT 0 +#define M98925_DAI_M_LSBS_WIDTH 8 + +/* MAX98925_R01E_DAI_CLK_DIV_N_MSBS */ +#define M98925_DAI_N_MSBS_MASK (0x7F<<0) +#define M98925_DAI_N_MSBS_SHIFT 0 +#define M98925_DAI_N_MSBS_WIDTH 7 + +/* MAX98925_R01F_DAI_CLK_DIV_N_LSBS */ +#define M98925_DAI_N_LSBS_MASK (0xFF<<0) +#define M98925_DAI_N_LSBS_SHIFT 0 +#define M98925_DAI_N_LSBS_WIDTH 8 + +/* MAX98925_R020_FORMAT */ +#define M98925_DAI_CHANSZ_MASK (0x03<<6) +#define M98925_DAI_CHANSZ_SHIFT 6 +#define M98925_DAI_CHANSZ_WIDTH 2 +#define M98925_DAI_EXTBCLK_HIZ_MASK (1<<4) +#define M98925_DAI_EXTBCLK_HIZ_SHIFT 4 +#define M98925_DAI_EXTBCLK_HIZ_WIDTH 1 +#define M98925_DAI_WCI_MASK (1<<3) +#define M98925_DAI_WCI_SHIFT 3 +#define M98925_DAI_WCI_WIDTH 1 +#define M98925_DAI_BCI_MASK (1<<2) +#define M98925_DAI_BCI_SHIFT 2 +#define M98925_DAI_BCI_WIDTH 1 +#define M98925_DAI_DLY_MASK (1<<1) +#define M98925_DAI_DLY_SHIFT 1 +#define M98925_DAI_DLY_WIDTH 1 +#define M98925_DAI_TDM_MASK (1<<0) +#define M98925_DAI_TDM_SHIFT 0 +#define M98925_DAI_TDM_WIDTH 1 + +#define M98925_DAI_CHANSZ_16 (1 << M98925_DAI_CHANSZ_SHIFT) +#define M98925_DAI_CHANSZ_24 (2 << M98925_DAI_CHANSZ_SHIFT) +#define M98925_DAI_CHANSZ_32 (3 << M98925_DAI_CHANSZ_SHIFT) + +/* MAX98925_R021_TDM_SLOT_SELECT */ +#define M98925_DAI_DO_EN_MASK (1<<7) +#define M98925_DAI_DO_EN_SHIFT 7 +#define M98925_DAI_DO_EN_WIDTH 1 +#define M98925_DAI_DIN_EN_MASK (1<<6) +#define M98925_DAI_DIN_EN_SHIFT 6 +#define M98925_DAI_DIN_EN_WIDTH 1 +#define M98925_DAI_INR_SOURCE_MASK (0x07<<3) +#define M98925_DAI_INR_SOURCE_SHIFT 3 +#define M98925_DAI_INR_SOURCE_WIDTH 3 +#define M98925_DAI_INL_SOURCE_MASK (0x07<<0) +#define M98925_DAI_INL_SOURCE_SHIFT 0 +#define M98925_DAI_INL_SOURCE_WIDTH 3 + +/* MAX98925_R022_DOUT_CFG_VMON */ +#define M98925_DAI_VMON_EN_MASK (1<<5) +#define M98925_DAI_VMON_EN_SHIFT 5 +#define M98925_DAI_VMON_EN_WIDTH 1 +#define M98925_DAI_VMON_SLOT_MASK (0x1F<<0) +#define M98925_DAI_VMON_SLOT_SHIFT 0 +#define M98925_DAI_VMON_SLOT_WIDTH 5 + +#define M98925_DAI_VMON_SLOT_00_01 (0 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_01_02 (1 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_02_03 (2 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_03_04 (3 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_04_05 (4 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_05_06 (5 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_06_07 (6 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_07_08 (7 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_08_09 (8 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_09_0A (9 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0A_0B (10 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0B_0C (11 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0C_0D (12 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0D_0E (13 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0E_0F (14 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0F_10 (15 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_10_11 (16 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_11_12 (17 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_12_13 (18 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_13_14 (19 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_14_15 (20 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_15_16 (21 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_16_17 (22 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_17_18 (23 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_18_19 (24 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_19_1A (25 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1A_1B (26 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1B_1C (27 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1C_1D (28 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1D_1E (29 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1E_1F (30 << M98925_DAI_VMON_SLOT_SHIFT) + +/* MAX98925_R023_DOUT_CFG_IMON */ +#define M98925_DAI_IMON_EN_MASK (1<<5) +#define M98925_DAI_IMON_EN_SHIFT 5 +#define M98925_DAI_IMON_EN_WIDTH 1 +#define M98925_DAI_IMON_SLOT_MASK (0x1F<<0) +#define M98925_DAI_IMON_SLOT_SHIFT 0 +#define M98925_DAI_IMON_SLOT_WIDTH 5 + +#define M98925_DAI_IMON_SLOT_00_01 (0 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_01_02 (1 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_02_03 (2 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_03_04 (3 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_04_05 (4 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_05_06 (5 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_06_07 (6 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_07_08 (7 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_08_09 (8 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_09_0A (9 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0A_0B (10 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0B_0C (11 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0C_0D (12 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0D_0E (13 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0E_0F (14 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0F_10 (15 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_10_11 (16 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_11_12 (17 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_12_13 (18 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_13_14 (19 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_14_15 (20 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_15_16 (21 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_16_17 (22 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_17_18 (23 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_18_19 (24 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_19_1A (25 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1A_1B (26 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1B_1C (27 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1C_1D (28 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1D_1E (29 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1E_1F (30 << M98925_DAI_IMON_SLOT_SHIFT) + +/* MAX98925_R024_DOUT_CFG_VBAT */ +#define M98925_DAI_VBAT_EN_MASK (1<<5) +#define M98925_DAI_VBAT_EN_SHIFT 5 +#define M98925_DAI_VBAT_EN_WIDTH 1 +#define M98925_DAI_VBAT_SLOT_MASK (0x1F<<0) +#define M98925_DAI_VBAT_SLOT_SHIFT 0 +#define M98925_DAI_VBAT_SLOT_WIDTH 5 + +/* MAX98925_R025_DOUT_CFG_VBST */ +#define M98925_DAI_VBST_EN_MASK (1<<5) +#define M98925_DAI_VBST_EN_SHIFT 5 +#define M98925_DAI_VBST_EN_WIDTH 1 +#define M98925_DAI_VBST_SLOT_MASK (0x1F<<0) +#define M98925_DAI_VBST_SLOT_SHIFT 0 +#define M98925_DAI_VBST_SLOT_WIDTH 5 + +/* MAX98925_R026_DOUT_CFG_FLAG */ +#define M98925_DAI_FLAG_EN_MASK (1<<5) +#define M98925_DAI_FLAG_EN_SHIFT 5 +#define M98925_DAI_FLAG_EN_WIDTH 1 +#define M98925_DAI_FLAG_SLOT_MASK (0x1F<<0) +#define M98925_DAI_FLAG_SLOT_SHIFT 0 +#define M98925_DAI_FLAG_SLOT_WIDTH 5 + +/* MAX98925_R027_DOUT_HIZ_CFG1 */ +#define M98925_DAI_SLOT_HIZ_CFG1_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG1_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG1_WIDTH 8 + +/* MAX98925_R028_DOUT_HIZ_CFG2 */ +#define M98925_DAI_SLOT_HIZ_CFG2_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG2_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG2_WIDTH 8 + +/* MAX98925_R029_DOUT_HIZ_CFG3 */ +#define M98925_DAI_SLOT_HIZ_CFG3_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG3_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG3_WIDTH 8 + +/* MAX98925_R02A_DOUT_HIZ_CFG4 */ +#define M98925_DAI_SLOT_HIZ_CFG4_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG4_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG4_WIDTH 8 + +/* MAX98925_R02B_DOUT_DRV_STRENGTH */ +#define M98925_DAI_OUT_DRIVE_MASK (0x03<<0) +#define M98925_DAI_OUT_DRIVE_SHIFT 0 +#define M98925_DAI_OUT_DRIVE_WIDTH 2 + +/* MAX98925_R02C_FILTERS */ +#define M98925_ADC_DITHER_EN_MASK (1<<7) +#define M98925_ADC_DITHER_EN_SHIFT 7 +#define M98925_ADC_DITHER_EN_WIDTH 1 +#define M98925_IV_DCB_EN_MASK (1<<6) +#define M98925_IV_DCB_EN_SHIFT 6 +#define M98925_IV_DCB_EN_WIDTH 1 +#define M98925_DAC_DITHER_EN_MASK (1<<4) +#define M98925_DAC_DITHER_EN_SHIFT 4 +#define M98925_DAC_DITHER_EN_WIDTH 1 +#define M98925_DAC_FILTER_MODE_MASK (1<<3) +#define M98925_DAC_FILTER_MODE_SHIFT 3 +#define M98925_DAC_FILTER_MODE_WIDTH 1 +#define M98925_DAC_HPF_MASK (0x07<<0) +#define M98925_DAC_HPF_SHIFT 0 +#define M98925_DAC_HPF_WIDTH 3 +#define M98925_DAC_HPF_DISABLE (0 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_DC_BLOCK (1 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_100 (2 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_200 (3 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_400 (4 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_800 (5 << M98925_DAC_HPF_SHIFT) + +/* MAX98925_R02D_GAIN */ +#define M98925_DAC_IN_SEL_MASK (0x03<<5) +#define M98925_DAC_IN_SEL_SHIFT 5 +#define M98925_DAC_IN_SEL_WIDTH 2 +#define M98925_SPK_GAIN_MASK (0x1F<<0) +#define M98925_SPK_GAIN_SHIFT 0 +#define M98925_SPK_GAIN_WIDTH 5 + +#define M98925_DAC_IN_SEL_LEFT_DAI (0 << M98925_DAC_IN_SEL_SHIFT) +#define M98925_DAC_IN_SEL_RIGHT_DAI (1 << M98925_DAC_IN_SEL_SHIFT) +#define M98925_DAC_IN_SEL_SUMMED_DAI (2 << M98925_DAC_IN_SEL_SHIFT) +#define M98925_DAC_IN_SEL_DIV2_SUMMED_DAI (3 << M98925_DAC_IN_SEL_SHIFT) + +/* MAX98925_R02E_GAIN_RAMPING */ +#define M98925_SPK_RMP_EN_MASK (1<<1) +#define M98925_SPK_RMP_EN_SHIFT 1 +#define M98925_SPK_RMP_EN_WIDTH 1 +#define M98925_SPK_ZCD_EN_MASK (1<<0) +#define M98925_SPK_ZCD_EN_SHIFT 0 +#define M98925_SPK_ZCD_EN_WIDTH 1 + +/* MAX98925_R02F_SPK_AMP */ +#define M98925_SPK_MODE_MASK (1<<0) +#define M98925_SPK_MODE_SHIFT 0 +#define M98925_SPK_MODE_WIDTH 1 + +/* MAX98925_R030_THRESHOLD */ +#define M98925_ALC_EN_MASK (1<<5) +#define M98925_ALC_EN_SHIFT 5 +#define M98925_ALC_EN_WIDTH 1 +#define M98925_ALC_TH_MASK (0x1F<<0) +#define M98925_ALC_TH_SHIFT 0 +#define M98925_ALC_TH_WIDTH 5 + +/* MAX98925_R031_ALC_ATTACK */ +#define M98925_ALC_ATK_STEP_MASK (0x0F<<4) +#define M98925_ALC_ATK_STEP_SHIFT 4 +#define M98925_ALC_ATK_STEP_WIDTH 4 +#define M98925_ALC_ATK_RATE_MASK (0x7<<0) +#define M98925_ALC_ATK_RATE_SHIFT 0 +#define M98925_ALC_ATK_RATE_WIDTH 3 + +/* MAX98925_R032_ALC_ATTEN_RLS */ +#define M98925_ALC_MAX_ATTEN_MASK (0x0F<<4) +#define M98925_ALC_MAX_ATTEN_SHIFT 4 +#define M98925_ALC_MAX_ATTEN_WIDTH 4 +#define M98925_ALC_RLS_RATE_MASK (0x7<<0) +#define M98925_ALC_RLS_RATE_SHIFT 0 +#define M98925_ALC_RLS_RATE_WIDTH 3 + +/* MAX98925_R033_ALC_HOLD_RLS */ +#define M98925_ALC_RLS_TGR_MASK (1<<0) +#define M98925_ALC_RLS_TGR_SHIFT 0 +#define M98925_ALC_RLS_TGR_WIDTH 1 + +/* MAX98925_R034_ALC_CONFIGURATION */ +#define M98925_ALC_MUTE_EN_MASK (1<<7) +#define M98925_ALC_MUTE_EN_SHIFT 7 +#define M98925_ALC_MUTE_EN_WIDTH 1 +#define M98925_ALC_MUTE_DLY_MASK (0x07<<4) +#define M98925_ALC_MUTE_DLY_SHIFT 4 +#define M98925_ALC_MUTE_DLY_WIDTH 3 +#define M98925_ALC_RLS_DBT_MASK (0x07<<0) +#define M98925_ALC_RLS_DBT_SHIFT 0 +#define M98925_ALC_RLS_DBT_WIDTH 3 + +/* MAX98925_R035_BOOST_CONVERTER */ +#define M98925_BST_SYNC_MASK (1<<7) +#define M98925_BST_SYNC_SHIFT 7 +#define M98925_BST_SYNC_WIDTH 1 +#define M98925_BST_PHASE_MASK (0x03<<4) +#define M98925_BST_PHASE_SHIFT 4 +#define M98925_BST_PHASE_WIDTH 2 +#define M98925_BST_SKIP_MODE_MASK (0x03<<0) +#define M98925_BST_SKIP_MODE_SHIFT 0 +#define M98925_BST_SKIP_MODE_WIDTH 2 + +/* MAX98925_R036_BLOCK_ENABLE */ +#define M98925_BST_EN_MASK (1<<7) +#define M98925_BST_EN_SHIFT 7 +#define M98925_BST_EN_WIDTH 1 +#define M98925_WATCH_EN_MASK (1<<6) +#define M98925_WATCH_EN_SHIFT 6 +#define M98925_WATCH_EN_WIDTH 1 +#define M98925_CLKMON_EN_MASK (1<<5) +#define M98925_CLKMON_EN_SHIFT 5 +#define M98925_CLKMON_EN_WIDTH 1 +#define M98925_SPK_EN_MASK (1<<4) +#define M98925_SPK_EN_SHIFT 4 +#define M98925_SPK_EN_WIDTH 1 +#define M98925_ADC_VBST_EN_MASK (1<<3) +#define M98925_ADC_VBST_EN_SHIFT 3 +#define M98925_ADC_VBST_EN_WIDTH 1 +#define M98925_ADC_VBAT_EN_MASK (1<<2) +#define M98925_ADC_VBAT_EN_SHIFT 2 +#define M98925_ADC_VBAT_EN_WIDTH 1 +#define M98925_ADC_IMON_EN_MASK (1<<1) +#define M98925_ADC_IMON_EN_SHIFT 1 +#define M98925_ADC_IMON_EN_WIDTH 1 +#define M98925_ADC_VMON_EN_MASK (1<<0) +#define M98925_ADC_VMON_EN_SHIFT 0 +#define M98925_ADC_VMON_EN_WIDTH 1 + +/* MAX98925_R037_CONFIGURATION */ +#define M98925_BST_VOUT_MASK (0x0F<<4) +#define M98925_BST_VOUT_SHIFT 4 +#define M98925_BST_VOUT_WIDTH 4 +#define M98925_THERMWARN_LEVEL_MASK (0x03<<2) +#define M98925_THERMWARN_LEVEL_SHIFT 2 +#define M98925_THERMWARN_LEVEL_WIDTH 2 +#define M98925_WATCH_TIME_MASK (0x03<<0) +#define M98925_WATCH_TIME_SHIFT 0 +#define M98925_WATCH_TIME_WIDTH 2 + +/* MAX98925_R038_GLOBAL_ENABLE */ +#define M98925_EN_MASK (1<<7) +#define M98925_EN_SHIFT 7 +#define M98925_EN_WIDTH 1 + +/* MAX98925_R03A_BOOST_LIMITER */ +#define M98925_BST_ILIM_MASK (0x1F<<3) +#define M98925_BST_ILIM_SHIFT 3 +#define M98925_BST_ILIM_WIDTH 5 + +/* MAX98925_R0FF_VERSION */ +#define M98925_REV_ID_MASK (0xFF<<0) +#define M98925_REV_ID_SHIFT 0 +#define M98925_REV_ID_WIDTH 8 + +struct max98925_priv { + struct regmap *regmap; + struct snd_soc_codec *codec; + struct max98925_pdata *pdata; + unsigned int sysclk; + unsigned int v_slot; + unsigned int i_slot; + unsigned int spk_gain; + unsigned int ch_size; +}; +#endif -- cgit v0.10.2 From 10dcc448d13071ed0d3797233187e79be258d90c Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Thu, 12 Mar 2015 03:05:44 +0800 Subject: ASoC: max98925_spk_tlv can be static Signed-off-by: Fengguang Wu Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c index 74f4f0b..34fc4d0 100644 --- a/sound/soc/codecs/max98925.c +++ b/sound/soc/codecs/max98925.c @@ -186,7 +186,7 @@ static bool max98925_readable_register(struct device *dev, unsigned int reg) } } -DECLARE_TLV_DB_SCALE(max98925_spk_tlv, -600, 100, 0); +static DECLARE_TLV_DB_SCALE(max98925_spk_tlv, -600, 100, 0); static const struct snd_kcontrol_new max98925_snd_controls[] = { SOC_SINGLE_TLV("Speaker Volume", MAX98925_GAIN, -- cgit v0.10.2 From a3a0a5992e47869232cffcb02b7d32fe5204ac7c Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 10 Mar 2015 22:42:50 +0200 Subject: iwlwifi: dvm: drop VO packets when mac80211 tells us to mac80211 now informs the driver when to drop the packets upon flush(). This will happen before disconnecting, or before we shut down the interface. We can now rely on this to drop all the packets including the VO queues. When mac80211 sets drop to false, wait for all the queues to be empty. Signed-off-by: Emmanuel Grumbach diff --git a/drivers/net/wireless/iwlwifi/dvm/mac80211.c b/drivers/net/wireless/iwlwifi/dvm/mac80211.c index 47e64e8..cceb026 100644 --- a/drivers/net/wireless/iwlwifi/dvm/mac80211.c +++ b/drivers/net/wireless/iwlwifi/dvm/mac80211.c @@ -1114,16 +1114,17 @@ static void iwlagn_mac_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, scd_queues &= ~(BIT(IWL_IPAN_CMD_QUEUE_NUM) | BIT(IWL_DEFAULT_CMD_QUEUE_NUM)); - if (vif) - scd_queues &= ~BIT(vif->hw_queue[IEEE80211_AC_VO]); - - IWL_DEBUG_TX_QUEUES(priv, "Flushing SCD queues: 0x%x\n", scd_queues); - if (iwlagn_txfifo_flush(priv, scd_queues)) { - IWL_ERR(priv, "flush request fail\n"); - goto done; + if (drop) { + IWL_DEBUG_TX_QUEUES(priv, "Flushing SCD queues: 0x%x\n", + scd_queues); + if (iwlagn_txfifo_flush(priv, scd_queues)) { + IWL_ERR(priv, "flush request fail\n"); + goto done; + } } + IWL_DEBUG_TX_QUEUES(priv, "wait transmit/flush all frames\n"); - iwl_trans_wait_tx_queue_empty(priv->trans, 0xffffffff); + iwl_trans_wait_tx_queue_empty(priv->trans, scd_queues); done: mutex_unlock(&priv->mutex); IWL_DEBUG_MAC80211(priv, "leave\n"); -- cgit v0.10.2 From 060b4460c47143440e77e6721f68ef756674d207 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 10 Mar 2015 10:47:57 +0100 Subject: iwlwifi: mvm: disconnect if CSA time event fails scheduling If this situation ever happens, the mac80211 state machine gets confused because it never clears csa_active. There was a separate bug that lead to this happening with a working connection, but it isn't very robust to try to keep the connection up in this case. When removing the time event the CSA essentially procedure stops, so the safest thing to do is to disconnect in this case. Signed-off-by: Johannes Berg Reviewed-by: Luciano Coelho Signed-off-by: Emmanuel Grumbach diff --git a/drivers/net/wireless/iwlwifi/mvm/time-event.c b/drivers/net/wireless/iwlwifi/mvm/time-event.c index f8d6f30..4b81c0b 100644 --- a/drivers/net/wireless/iwlwifi/mvm/time-event.c +++ b/drivers/net/wireless/iwlwifi/mvm/time-event.c @@ -197,6 +197,8 @@ iwl_mvm_te_handle_notify_csa(struct iwl_mvm *mvm, struct iwl_time_event_notif *notif) { if (!le32_to_cpu(notif->status)) { + if (te_data->vif->type == NL80211_IFTYPE_STATION) + ieee80211_connection_loss(te_data->vif); IWL_DEBUG_TE(mvm, "CSA time event failed to start\n"); iwl_mvm_te_clear_data(mvm, te_data); return; -- cgit v0.10.2 From 66454b3eb31e8109387606c054ae02f3cfd6543e Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 15 Jan 2015 12:52:15 +0100 Subject: ASoC: rt5670: Replace w->codec snd_soc_dapm_to_codec(w->dapm) The codec field of the snd_soc_widget struct is eventually going to be removed, use snd_soc_dapm_to_codec(w->dapm) instead. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 91d2069..cc7f84a 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -699,7 +699,7 @@ static int set_dmic_clk(struct snd_soc_dapm_widget *w, static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { - struct snd_soc_codec *codec = source->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); if (rt5670->sysclk_src == RT5670_SCLK_S_PLL1) -- cgit v0.10.2 From 76c07b8265c68d9a89fb4c0a634e373a087f11be Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Thu, 12 Mar 2015 13:53:00 +0800 Subject: ASoC: Intel: add kcontrol to enable/disable sound effect module waves Add kcontrol to enable/disable module waves. IPC is valid only when module is loaded. Also track module state over suspend so it's state can be restored on resume. Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index 265d754..ebca903 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -337,6 +337,10 @@ struct sst_hsw { /* FW log stream */ struct sst_hsw_log_stream log_stream; + + /* flags bit field to track module state when resume from RTD3, + * each bit represent state (enabled/disabled) of single module */ + u32 enabled_modules_rtd3; }; #define CREATE_TRACE_POINTS @@ -1986,6 +1990,21 @@ bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id) return false; } +void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + hsw->enabled_modules_rtd3 |= (1 << module_id); +} + +void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + hsw->enabled_modules_rtd3 &= ~(1 << module_id); +} + +bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + return hsw->enabled_modules_rtd3 & (1 << module_id); +} + int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name) { diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index 30c65b2..48290a1 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -477,6 +477,9 @@ struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); void sst_hsw_init_module_state(struct sst_hsw *hsw); bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id); +bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name); diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index a604cc4..b3de87a 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -318,6 +318,54 @@ static int hsw_volume_get(struct snd_kcontrol *kcontrol, return 0; } +static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + + ucontrol->value.integer.value[0] = + (sst_hsw_is_module_active(hsw, id) || + sst_hsw_is_module_enabled_rtd3(hsw, id)); + return 0; +} + +static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + int ret = 0; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + bool switch_on = (bool)ucontrol->value.integer.value[0]; + + /* if module is in RAM on the DSP, apply user settings to module through + * ipc. If module is not in RAM on the DSP, store user setting for + * track */ + if (sst_hsw_is_module_loaded(hsw, id)) { + if (switch_on == sst_hsw_is_module_active(hsw, id)) + return 0; + + if (switch_on) + ret = sst_hsw_module_enable(hsw, id, 0); + else + ret = sst_hsw_module_disable(hsw, id, 0); + } else { + if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id)) + return 0; + + if (switch_on) + sst_hsw_set_module_enabled_rtd3(hsw, id); + else + sst_hsw_set_module_disabled_rtd3(hsw, id); + } + + return ret; +} + /* TLV used by both global and stream volumes */ static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); @@ -339,6 +387,9 @@ static const struct snd_kcontrol_new hsw_volume_controls[] = { SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, ARRAY_SIZE(volume_map) - 1, 0, hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* enable/disable module waves */ + SOC_SINGLE_BOOL_EXT("Waves Switch", 0, + hsw_waves_switch_get, hsw_waves_switch_put), }; /* Create DMA buffer page table for DSP */ @@ -1118,10 +1169,18 @@ static int hsw_pcm_runtime_suspend(struct device *dev) { struct hsw_priv_data *pdata = dev_get_drvdata(dev); struct sst_hsw *hsw = pdata->hsw; + int ret; if (pdata->pm_state >= HSW_PM_STATE_RTD3) return 0; + /* fw modules will be unloaded on RTD3, set flag to track */ + if (sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_disable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + sst_hsw_set_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } sst_hsw_dsp_runtime_suspend(hsw); sst_hsw_dsp_runtime_sleep(hsw); pdata->pm_state = HSW_PM_STATE_RTD3; @@ -1156,6 +1215,15 @@ static int hsw_pcm_runtime_resume(struct device *dev) else if (ret == 1) /* no action required */ return 0; + /* check flag when resume */ + if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + /* unset flag */ + sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } + pdata->pm_state = HSW_PM_STATE_D0; return ret; } -- cgit v0.10.2 From 201892268b8335ae8c9dc7cc9a0d4bf6e1336f0e Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Thu, 12 Mar 2015 13:53:01 +0800 Subject: ASoC: Intel: add function to set parameter to sound effect module waves Add function to set parameters to module waves. The parameters can be set only when module is enabled, and parameter size is limited to 500 Bytes. Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index ebca903..a97324d 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -2166,6 +2166,65 @@ int sst_hsw_module_disable(struct sst_hsw *hsw, return ret; } +int sst_hsw_module_set_param(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, u32 parameter_id, + u32 param_size, char *param) +{ + int ret; + unsigned char *data = NULL; + u32 header = 0; + u32 payload_size = 0, transfer_parameter_size = 0; + dma_addr_t dma_addr = 0; + struct sst_hsw_transfer_parameter *parameter; + struct device *dev = hsw->dev; + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_SET_PARAMETER) | + IPC_MODULE_ID(module_id); + dev_dbg(dev, "sst_hsw_module_set_param header=%x\n", header); + + payload_size = param_size + + sizeof(struct sst_hsw_transfer_parameter) - + sizeof(struct sst_hsw_transfer_list); + dev_dbg(dev, "parameter size : %d\n", param_size); + dev_dbg(dev, "payload size : %d\n", payload_size); + + if (payload_size <= SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE) { + /* short parameter, mailbox can contain data */ + dev_dbg(dev, "transfer parameter size : %d\n", + transfer_parameter_size); + + transfer_parameter_size = ALIGN(payload_size, 4); + dev_dbg(dev, "transfer parameter aligned size : %d\n", + transfer_parameter_size); + + parameter = kzalloc(transfer_parameter_size, GFP_KERNEL); + if (parameter == NULL) + return -ENOMEM; + + memcpy(parameter->data, param, param_size); + } else { + dev_warn(dev, "transfer parameter size too large!"); + return 0; + } + + parameter->parameter_id = parameter_id; + parameter->data_size = param_size; + + ret = ipc_tx_message_wait(hsw, header, + parameter, transfer_parameter_size , NULL, 0); + if (ret < 0) + dev_err(dev, "ipc: module set parameter failed - %d\n", ret); + + kfree(parameter); + + if (data) + dma_free_coherent(hsw->dsp->dma_dev, + param_size, (void *)data, dma_addr); + + return ret; +} + static struct sst_dsp_device hsw_dev = { .thread = hsw_irq_thread, .ops = &haswell_ops, diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index 48290a1..16bec43 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -37,6 +37,7 @@ #define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400 #define SST_HSW_MAX_INFO_SIZE 64 #define SST_HSW_BUILD_HASH_LENGTH 40 +#define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500 struct sst_hsw; struct sst_hsw_stream; @@ -187,6 +188,28 @@ enum sst_hsw_performance_action { SST_HSW_PERF_STOP = 1, }; +struct sst_hsw_transfer_info { + uint32_t destination; /* destination address */ + uint32_t reverse:1; /* if 1 data flows from destination */ + uint32_t size:31; /* transfer size in bytes.*/ + uint16_t first_page_offset; /* offset to data in the first page. */ + uint8_t packed_pages; /* page addresses. Each occupies 20 bits */ +} __attribute__((packed)); + +struct sst_hsw_transfer_list { + uint32_t transfers_count; + struct sst_hsw_transfer_info transfers; +} __attribute__((packed)); + +struct sst_hsw_transfer_parameter { + uint32_t parameter_id; + uint32_t data_size; + union { + uint8_t data[1]; + struct sst_hsw_transfer_list transfer_list; + }; +} __attribute__((packed)); + /* SST firmware module info */ struct sst_hsw_module_info { u8 name[SST_HSW_MAX_INFO_SIZE]; @@ -487,6 +510,9 @@ int sst_hsw_module_enable(struct sst_hsw *hsw, u32 module_id, u32 instance_id); int sst_hsw_module_disable(struct sst_hsw *hsw, u32 module_id, u32 instance_id); +int sst_hsw_module_set_param(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, u32 parameter_id, + u32 param_size, char *param); /* runtime module management */ struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, -- cgit v0.10.2 From 3814c204446822cd3c82ec4e8616600732c1f94e Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Thu, 12 Mar 2015 13:53:02 +0800 Subject: ASoC: Intel: add kcontrol to set parameter to sound effect module waves Each kcontrol command includes a line of parameters up to 128 bytes. kcontrol command to set param: cset "name='Waves Set Param' <0x01,0xff,...>" or cset-bin-file "name='Waves Set Param' " The parameter lines are stored in a buffer array, so can be read back from buffer rather than from DSP, and be relaunched to DSP when resume from RTD3. The buffer size is 160 parameter lines. kcontrol command to reset the buffer: cset "name='Waves Set Param' 0xff" alsa-lib v1.0.29 or commit 6ea14c36 and f47480af are required to support the kcontrol commands. Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index a97324d..43fb5f3 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -341,6 +341,11 @@ struct sst_hsw { /* flags bit field to track module state when resume from RTD3, * each bit represent state (enabled/disabled) of single module */ u32 enabled_modules_rtd3; + + /* buffer to store parameter lines */ + u32 param_idx_w; /* write index */ + u32 param_idx_r; /* read index */ + u8 param_buf[WAVES_PARAM_LINES][WAVES_PARAM_COUNT]; }; #define CREATE_TRACE_POINTS @@ -2005,6 +2010,62 @@ bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) return hsw->enabled_modules_rtd3 & (1 << module_id); } +void sst_hsw_reset_param_buf(struct sst_hsw *hsw) +{ + hsw->param_idx_w = 0; + hsw->param_idx_r = 0; + memset((void *)hsw->param_buf, 0, sizeof(hsw->param_buf)); +} + +int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf) +{ + /* save line to the first available position of param buffer */ + if (hsw->param_idx_w > WAVES_PARAM_LINES - 1) { + dev_warn(hsw->dev, "warning: param buffer overflow!\n"); + return -EPERM; + } + memcpy(hsw->param_buf[hsw->param_idx_w], buf, WAVES_PARAM_COUNT); + hsw->param_idx_w++; + return 0; +} + +int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf) +{ + u8 id = 0; + + /* read the first matching line from param buffer */ + while (hsw->param_idx_r < WAVES_PARAM_LINES) { + id = hsw->param_buf[hsw->param_idx_r][0]; + hsw->param_idx_r++; + if (buf[0] == id) { + memcpy(buf, hsw->param_buf[hsw->param_idx_r], + WAVES_PARAM_COUNT); + break; + } + } + if (hsw->param_idx_r > WAVES_PARAM_LINES - 1) { + dev_dbg(hsw->dev, "end of buffer, roll to the beginning\n"); + hsw->param_idx_r = 0; + return 0; + } + return 0; +} + +int sst_hsw_launch_param_buf(struct sst_hsw *hsw) +{ + int ret, idx; + + /* put all param lines to DSP through ipc */ + for (idx = 0; idx < hsw->param_idx_w; idx++) { + ret = sst_hsw_module_set_param(hsw, + SST_HSW_MODULE_WAVES, 0, hsw->param_buf[idx][0], + WAVES_PARAM_COUNT, hsw->param_buf[idx]); + if (ret < 0) + return ret; + } + return 0; +} + int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name) { @@ -2299,6 +2360,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) if (ret < 0) goto boot_err; + /* init param buffer */ + sst_hsw_reset_param_buf(hsw); + /* wait for DSP boot completion */ sst_dsp_boot(hsw->dsp); ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index 16bec43..06d71ae 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -38,6 +38,8 @@ #define SST_HSW_MAX_INFO_SIZE 64 #define SST_HSW_BUILD_HASH_LENGTH 40 #define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500 +#define WAVES_PARAM_COUNT 128 +#define WAVES_PARAM_LINES 160 struct sst_hsw; struct sst_hsw_stream; @@ -503,6 +505,10 @@ bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id); bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_reset_param_buf(struct sst_hsw *hsw); +int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf); +int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf); +int sst_hsw_launch_param_buf(struct sst_hsw *hsw); int sst_hsw_module_load(struct sst_hsw *hsw, u32 module_id, u32 instance_id, char *name); diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index b3de87a..b40ec74 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -366,6 +366,49 @@ static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, return ret; } +static int hsw_waves_param_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + + /* return a matching line from param buffer */ + return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data); +} + +static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + int ret; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + int param_id = ucontrol->value.bytes.data[0]; + int param_size = WAVES_PARAM_COUNT; + + /* clear param buffer and reset buffer index */ + if (param_id == 0xFF) { + sst_hsw_reset_param_buf(hsw); + return 0; + } + + /* store params into buffer */ + ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data); + if (ret < 0) + return ret; + + if (sst_hsw_is_module_loaded(hsw, id)) { + if (!sst_hsw_is_module_active(hsw, id)) + return 0; + + ret = sst_hsw_module_set_param(hsw, id, 0, param_id, + param_size, ucontrol->value.bytes.data); + } + return ret; +} + /* TLV used by both global and stream volumes */ static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); @@ -390,6 +433,9 @@ static const struct snd_kcontrol_new hsw_volume_controls[] = { /* enable/disable module waves */ SOC_SINGLE_BOOL_EXT("Waves Switch", 0, hsw_waves_switch_get, hsw_waves_switch_put), + /* set parameters to module waves */ + SND_SOC_BYTES_EXT("Waves Set Param", WAVES_PARAM_COUNT, + hsw_waves_param_get, hsw_waves_param_put), }; /* Create DMA buffer page table for DSP */ @@ -1220,6 +1266,10 @@ static int hsw_pcm_runtime_resume(struct device *dev) ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); if (ret < 0) return ret; + /* put parameters from buffer to dsp */ + ret = sst_hsw_launch_param_buf(hsw); + if (ret < 0) + return ret; /* unset flag */ sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); } -- cgit v0.10.2 From 42ce5b8ab81e94089c79791fc682a7f46af9790a Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 12 Mar 2015 20:25:07 +0800 Subject: ASoC: rt5645: Add TDM support for rt5650 rt5650 and rt5645 use different register bits for TDM configuration. This patch modifies rt5645_set_tdm_slot to support both codecs. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index c9a4c5b..b793476 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2285,23 +2285,42 @@ static int rt5645_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - unsigned int val = 0; + struct rt5645_priv *rt5645 = snd_soc_codec_get_drvdata(codec); + unsigned int i_slot_sft, o_slot_sft, i_width_sht, o_width_sht, en_sft; + unsigned int mask, val = 0; + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5650: + en_sft = 15; + i_slot_sft = 10; + o_slot_sft = 8; + i_width_sht = 6; + o_width_sht = 4; + mask = 0x8ff0; + break; + default: + en_sft = 14; + i_slot_sft = o_slot_sft = 12; + i_width_sht = o_width_sht = 10; + mask = 0x7c00; + break; + } if (rx_mask || tx_mask) { - val |= (1 << 14); - snd_soc_update_bits(codec, RT5645_BASS_BACK, - RT5645_G_BB_BST_MASK, RT5645_G_BB_BST_25DB); + val |= (1 << en_sft); + if (rt5645->codec_type == CODEC_TYPE_RT5645) + snd_soc_update_bits(codec, RT5645_BASS_BACK, + RT5645_G_BB_BST_MASK, RT5645_G_BB_BST_25DB); } switch (slots) { case 4: - val |= (1 << 12); + val |= (1 << i_slot_sft) | (1 << o_slot_sft); break; case 6: - val |= (2 << 12); + val |= (2 << i_slot_sft) | (2 << o_slot_sft); break; case 8: - val |= (3 << 12); + val |= (3 << i_slot_sft) | (3 << o_slot_sft); break; case 2: default: @@ -2310,20 +2329,20 @@ static int rt5645_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, switch (slot_width) { case 20: - val |= (1 << 10); + val |= (1 << i_width_sht) | (1 << o_width_sht); break; case 24: - val |= (2 << 10); + val |= (2 << i_width_sht) | (2 << o_width_sht); break; case 32: - val |= (3 << 10); + val |= (3 << i_width_sht) | (3 << o_width_sht); break; case 16: default: break; } - snd_soc_update_bits(codec, RT5645_TDM_CTRL_1, 0x7c00, val); + snd_soc_update_bits(codec, RT5645_TDM_CTRL_1, mask, val); return 0; } -- cgit v0.10.2 From 718b25c803df8c1f8f2090c115911d123e34c78a Mon Sep 17 00:00:00 2001 From: Anish Kumar Date: Wed, 11 Mar 2015 15:13:16 -0700 Subject: ASoC: max98925: trivial duplicate typo fix in set_fmt Signed-off-by: Anish Kumar Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c index 34fc4d0..1f8e803 100644 --- a/sound/soc/codecs/max98925.c +++ b/sound/soc/codecs/max98925.c @@ -346,7 +346,7 @@ static int max98925_dai_set_fmt(struct snd_soc_dai *codec_dai, } regmap_update_bits(max98925->regmap, MAX98925_FORMAT, - M98925_DAI_BCI_MASK | M98925_DAI_BCI_MASK, invert); + M98925_DAI_BCI_MASK, invert); return 0; } -- cgit v0.10.2 From b52551e0d0e659db43f5cfa813ae09d4c3744761 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 13 Mar 2015 10:50:26 +0800 Subject: ASoC: rt5645: Remove adc stereo2 filter Remove adc stereo2 filter since it is not in rt5645/rt5650 codec. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index b793476..4c384a1 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -1538,8 +1538,6 @@ static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY_S("adc stereo1 filter", 1, RT5645_PWR_DIG2, RT5645_PWR_ADC_S1F_BIT, 0, NULL, 0), - SND_SOC_DAPM_SUPPLY_S("adc stereo2 filter", 1, RT5645_PWR_DIG2, - RT5645_PWR_ADC_S2F_BIT, 0, NULL, 0), SND_SOC_DAPM_MIXER_E("Sto1 ADC MIXL", SND_SOC_NOPM, 0, 0, rt5645_sto1_adc_l_mix, ARRAY_SIZE(rt5645_sto1_adc_l_mix), NULL, 0), @@ -1729,7 +1727,6 @@ static const struct snd_soc_dapm_widget rt5650_specific_dapm_widgets[] = { static const struct snd_soc_dapm_route rt5645_dapm_routes[] = { { "adc stereo1 filter", NULL, "ADC STO1 ASRC", is_using_asrc }, - { "adc stereo2 filter", NULL, "ADC STO2 ASRC", is_using_asrc }, { "adc mono left filter", NULL, "ADC MONO L ASRC", is_using_asrc }, { "adc mono right filter", NULL, "ADC MONO R ASRC", is_using_asrc }, { "dac mono left filter", NULL, "DAC MONO L ASRC", is_using_asrc }, diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h index dbfd98c..db78e94 100644 --- a/sound/soc/codecs/rt5645.h +++ b/sound/soc/codecs/rt5645.h @@ -804,8 +804,6 @@ #define RT5645_PWR_DAC_MF_L_BIT 10 #define RT5645_PWR_DAC_MF_R (0x1 << 9) #define RT5645_PWR_DAC_MF_R_BIT 9 -#define RT5645_PWR_ADC_S2F (0x1 << 8) -#define RT5645_PWR_ADC_S2F_BIT 8 #define RT5645_PWR_PDM1 (0x1 << 7) #define RT5645_PWR_PDM1_BIT 7 #define RT5645_PWR_PDM2 (0x1 << 6) -- cgit v0.10.2 From 0ba8da961bd868c67a8dae3dbbee145514515e9c Mon Sep 17 00:00:00 2001 From: Sathyanarayanan Kuppuswamy Date: Tue, 3 Mar 2015 18:17:56 +0200 Subject: iio: bmc150: change sampling frequency Currently driver reports device bandwidth list as available sampling frequency. But sampling frequency is actually twice the device bandwidth. This patch fixes this issue. Signed-off-by: Sathyanarayanan Kuppuswamy Signed-off-by: Octavian Purdila Cc: Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/accel/bmc150-accel.c b/drivers/iio/accel/bmc150-accel.c index 7d1383d..75567fd 100644 --- a/drivers/iio/accel/bmc150-accel.c +++ b/drivers/iio/accel/bmc150-accel.c @@ -168,14 +168,14 @@ static const struct { int val; int val2; u8 bw_bits; -} bmc150_accel_samp_freq_table[] = { {7, 810000, 0x08}, - {15, 630000, 0x09}, - {31, 250000, 0x0A}, - {62, 500000, 0x0B}, - {125, 0, 0x0C}, - {250, 0, 0x0D}, - {500, 0, 0x0E}, - {1000, 0, 0x0F} }; +} bmc150_accel_samp_freq_table[] = { {15, 620000, 0x08}, + {31, 260000, 0x09}, + {62, 500000, 0x0A}, + {125, 0, 0x0B}, + {250, 0, 0x0C}, + {500, 0, 0x0D}, + {1000, 0, 0x0E}, + {2000, 0, 0x0F} }; static const struct { int bw_bits; @@ -840,7 +840,7 @@ static int bmc150_accel_validate_trigger(struct iio_dev *indio_dev, } static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( - "7.810000 15.630000 31.250000 62.500000 125 250 500 1000"); + "15.620000 31.260000 62.50000 125 250 500 1000 2000"); static struct attribute *bmc150_accel_attributes[] = { &iio_const_attr_sampling_frequency_available.dev_attr.attr, -- cgit v0.10.2 From 8d09f48adfd282157f6afc94d2502b44156cb12f Mon Sep 17 00:00:00 2001 From: Marek Belisko Date: Sat, 28 Feb 2015 21:54:42 +0100 Subject: staging: iio: hmc5843: Set iio name property in sysfs Without this change file name for hmc5843 is empty in /sys/bus/iio/devices/iio\:device*/name With this change name is reported correctly: cat /sys/bus/iio/devices/iio\:device*/name hmc5843 Signed-off-by: Marek Belisko Signed-off-by: Jonathan Cameron diff --git a/drivers/staging/iio/magnetometer/hmc5843_core.c b/drivers/staging/iio/magnetometer/hmc5843_core.c index fd171d8..90cc18b 100644 --- a/drivers/staging/iio/magnetometer/hmc5843_core.c +++ b/drivers/staging/iio/magnetometer/hmc5843_core.c @@ -592,6 +592,7 @@ int hmc5843_common_probe(struct device *dev, struct regmap *regmap, mutex_init(&data->lock); indio_dev->dev.parent = dev; + indio_dev->name = dev->driver->name; indio_dev->info = &hmc5843_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = data->variant->channels; -- cgit v0.10.2 From 4dac0a8eefd55bb1f157d1a5a084531334a2d74c Mon Sep 17 00:00:00 2001 From: Viorel Suman Date: Wed, 18 Feb 2015 20:05:21 +0200 Subject: iio: inv_mpu6050: Clear timestamps fifo while resetting hardware fifo A hardware fifo reset always imply an invalidation of the existing timestamps, so we'll clear timestamps fifo on successfull hardware fifo reset. Signed-off-by: Viorel Suman Cc: Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index 0cd306a..ba27e27 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -24,6 +24,16 @@ #include #include "inv_mpu_iio.h" +static void inv_clear_kfifo(struct inv_mpu6050_state *st) +{ + unsigned long flags; + + /* take the spin lock sem to avoid interrupt kick in */ + spin_lock_irqsave(&st->time_stamp_lock, flags); + kfifo_reset(&st->timestamps); + spin_unlock_irqrestore(&st->time_stamp_lock, flags); +} + int inv_reset_fifo(struct iio_dev *indio_dev) { int result; @@ -50,6 +60,10 @@ int inv_reset_fifo(struct iio_dev *indio_dev) INV_MPU6050_BIT_FIFO_RST); if (result) goto reset_fifo_fail; + + /* clear timestamps fifo */ + inv_clear_kfifo(st); + /* enable interrupt */ if (st->chip_config.accl_fifo_enable || st->chip_config.gyro_fifo_enable) { @@ -83,16 +97,6 @@ reset_fifo_fail: return result; } -static void inv_clear_kfifo(struct inv_mpu6050_state *st) -{ - unsigned long flags; - - /* take the spin lock sem to avoid interrupt kick in */ - spin_lock_irqsave(&st->time_stamp_lock, flags); - kfifo_reset(&st->timestamps); - spin_unlock_irqrestore(&st->time_stamp_lock, flags); -} - /** * inv_mpu6050_irq_handler() - Cache a timestamp at each data ready interrupt. */ @@ -184,7 +188,6 @@ end_session: flush_fifo: /* Flush HW and SW FIFOs. */ inv_reset_fifo(indio_dev); - inv_clear_kfifo(st); mutex_unlock(&indio_dev->mlock); iio_trigger_notify_done(indio_dev->trig); -- cgit v0.10.2 From ed170dedd1ddd316c5cd17fec3dd7937981e2fb7 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Mar 2015 12:15:53 +0200 Subject: staging: iio: dummy: Fix undefined symbol build error CONFIG_SIMPLE_DUMMY_BUFFER compiles in iio_simple_dummy_buffer.c file which uses functions from industrialio-trigger.c. So, CONFIG_SIMPLE_DUMMY_BUFFER needs to select IIO_TRIGGER in order to avoid build error like this: > ERROR: "iio_trigger_notify_done" [drivers/staging/iio/iio_dummy.ko] undefined! > ERROR: "iio_triggered_buffer_postenable" [drivers/staging/iio/iio_dummy.ko] undefined! > ERROR: "iio_triggered_buffer_predisable" [drivers/staging/iio/iio_dummy.ko] undefined! > ERROR: "iio_alloc_pollfunc" [drivers/staging/iio/iio_dummy.ko] undefined! > ERROR: "iio_dealloc_pollfunc" [drivers/staging/iio/iio_dummy.ko] undefined! Cc: Arnd Bergmann Reported-by: kbuild test robot Signed-off-by: Daniel Baluta Signed-off-by: Jonathan Cameron diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig index 2418302..6d5b38d 100644 --- a/drivers/staging/iio/Kconfig +++ b/drivers/staging/iio/Kconfig @@ -38,6 +38,7 @@ config IIO_SIMPLE_DUMMY_EVENTS config IIO_SIMPLE_DUMMY_BUFFER bool "Buffered capture support" select IIO_BUFFER + select IIO_TRIGGER select IIO_KFIFO_BUF help Add buffered data capture to the simple dummy driver. -- cgit v0.10.2 From af5e1a68318e2ed2de22fc2d7a02f2882abe073c Mon Sep 17 00:00:00 2001 From: Adriana Reus Date: Mon, 23 Feb 2015 16:40:51 +0200 Subject: iio:inv-mpu6050: Fix inconsistency for the scale channel Fix inconsistency in the semantics of the scale attribute. For scale the write_raw function was considering the scale table index and writing the appropriate value into the range register, while for read_raw it was outputting the actual scale. Fix this behaviour and adhere to the iio ABI specification. Signed-off-by: Adriana Reus Reviewed-by: Viorel Suman Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index d8d5bed..ef76afe 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -410,42 +410,46 @@ error_read_raw: } } -static int inv_mpu6050_write_fsr(struct inv_mpu6050_state *st, int fsr) +static int inv_mpu6050_write_gyro_scale(struct inv_mpu6050_state *st, int val) { - int result; + int result, i; u8 d; - if (fsr < 0 || fsr > INV_MPU6050_MAX_GYRO_FS_PARAM) - return -EINVAL; - if (fsr == st->chip_config.fsr) - return 0; + for (i = 0; i < ARRAY_SIZE(gyro_scale_6050); ++i) { + if (gyro_scale_6050[i] == val) { + d = (i << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, + st->reg->gyro_config, d); + if (result) + return result; - d = (fsr << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); - result = inv_mpu6050_write_reg(st, st->reg->gyro_config, d); - if (result) - return result; - st->chip_config.fsr = fsr; + st->chip_config.fsr = i; + return 0; + } + } - return 0; + return -EINVAL; } -static int inv_mpu6050_write_accel_fs(struct inv_mpu6050_state *st, int fs) +static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val) { - int result; + int result, i; u8 d; - if (fs < 0 || fs > INV_MPU6050_MAX_ACCL_FS_PARAM) - return -EINVAL; - if (fs == st->chip_config.accl_fs) - return 0; + for (i = 0; i < ARRAY_SIZE(accel_scale); ++i) { + if (accel_scale[i] == val) { + d = (i << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, + st->reg->accl_config, d); + if (result) + return result; - d = (fs << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); - result = inv_mpu6050_write_reg(st, st->reg->accl_config, d); - if (result) - return result; - st->chip_config.accl_fs = fs; + st->chip_config.accl_fs = i; + return 0; + } + } - return 0; + return -EINVAL; } static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, @@ -471,10 +475,10 @@ static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_SCALE: switch (chan->type) { case IIO_ANGL_VEL: - result = inv_mpu6050_write_fsr(st, val); + result = inv_mpu6050_write_gyro_scale(st, val2); break; case IIO_ACCEL: - result = inv_mpu6050_write_accel_fs(st, val); + result = inv_mpu6050_write_accel_scale(st, val2); break; default: result = -EINVAL; -- cgit v0.10.2 From c1b03ab5e886760bdd38c9c7a27af149046ffe01 Mon Sep 17 00:00:00 2001 From: Martin Fuzzey Date: Thu, 19 Feb 2015 15:17:44 +0100 Subject: iio: core: Fix double free. When an error occurred during event registration memory was freed twice resulting in kernel memory corruption and a crash in unrelated code. The problem was caused by iio_device_unregister_eventset() iio_device_unregister_sysfs() being called twice, once on the error path and then again via iio_dev_release(). Fix this by making these two functions idempotent so they may be called multiple times. The problem was observed before applying 78b33216 iio:core: Handle error when mask type is not separate Signed-off-by: Martin Fuzzey Cc: Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index aaba9d3..4df97f6 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -847,8 +847,7 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, * @attr_list: List of IIO device attributes * * This function frees the memory allocated for each of the IIO device - * attributes in the list. Note: if you want to reuse the list after calling - * this function you have to reinitialize it using INIT_LIST_HEAD(). + * attributes in the list. */ void iio_free_chan_devattr_list(struct list_head *attr_list) { @@ -856,6 +855,7 @@ void iio_free_chan_devattr_list(struct list_head *attr_list) list_for_each_entry_safe(p, n, attr_list, l) { kfree(p->dev_attr.attr.name); + list_del(&p->l); kfree(p); } } @@ -936,6 +936,7 @@ static void iio_device_unregister_sysfs(struct iio_dev *indio_dev) iio_free_chan_devattr_list(&indio_dev->channel_attr_list); kfree(indio_dev->chan_attr_group.attrs); + indio_dev->chan_attr_group.attrs = NULL; } static void iio_dev_release(struct device *device) diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-event.c index a4b3970..a99692b 100644 --- a/drivers/iio/industrialio-event.c +++ b/drivers/iio/industrialio-event.c @@ -500,6 +500,7 @@ int iio_device_register_eventset(struct iio_dev *indio_dev) error_free_setup_event_lines: iio_free_chan_devattr_list(&indio_dev->event_interface->dev_attr_list); kfree(indio_dev->event_interface); + indio_dev->event_interface = NULL; return ret; } -- cgit v0.10.2 From 1cf48f22c98ae24a49a3f1b6900e4c9a9a0fcc62 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Thu, 12 Mar 2015 17:17:18 +0100 Subject: ath9k: fix tracking of enabled AP beacons sc->nbcnvifs tracks assigned beacon slots, not enabled beacons. Therefore, it cannot be used to decide if cur_conf->enable_beacon (bool) should be updated, or if beacons have been enabled already. With the current code (depending on the order of calls), beacons often do not get enabled in an AP+STA setup. To fix tracking of enabled beacons, convert cur_conf->enable_beacon to a bitmask of enabled beacon slots. Cc: stable@vger.kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Kalle Valo diff --git a/drivers/net/wireless/ath/ath9k/beacon.c b/drivers/net/wireless/ath/ath9k/beacon.c index cb366ad..f50a6bc 100644 --- a/drivers/net/wireless/ath/ath9k/beacon.c +++ b/drivers/net/wireless/ath/ath9k/beacon.c @@ -219,12 +219,15 @@ void ath9k_beacon_remove_slot(struct ath_softc *sc, struct ieee80211_vif *vif) struct ath_common *common = ath9k_hw_common(sc->sc_ah); struct ath_vif *avp = (void *)vif->drv_priv; struct ath_buf *bf = avp->av_bcbuf; + struct ath_beacon_config *cur_conf = &sc->cur_chan->beacon; ath_dbg(common, CONFIG, "Removing interface at beacon slot: %d\n", avp->av_bslot); tasklet_disable(&sc->bcon_tasklet); + cur_conf->enable_beacon &= ~BIT(avp->av_bslot); + if (bf && bf->bf_mpdu) { struct sk_buff *skb = bf->bf_mpdu; dma_unmap_single(sc->dev, bf->bf_buf_addr, @@ -521,8 +524,7 @@ static bool ath9k_allow_beacon_config(struct ath_softc *sc, } if (sc->sc_ah->opmode == NL80211_IFTYPE_AP) { - if ((vif->type != NL80211_IFTYPE_AP) || - (sc->nbcnvifs > 1)) { + if (vif->type != NL80211_IFTYPE_AP) { ath_dbg(common, CONFIG, "An AP interface is already present !\n"); return false; @@ -616,12 +618,14 @@ void ath9k_beacon_config(struct ath_softc *sc, struct ieee80211_vif *vif, * enabling/disabling SWBA. */ if (changed & BSS_CHANGED_BEACON_ENABLED) { - if (!bss_conf->enable_beacon && - (sc->nbcnvifs <= 1)) { - cur_conf->enable_beacon = false; - } else if (bss_conf->enable_beacon) { - cur_conf->enable_beacon = true; - ath9k_cache_beacon_config(sc, ctx, bss_conf); + bool enabled = cur_conf->enable_beacon; + + if (!bss_conf->enable_beacon) { + cur_conf->enable_beacon &= ~BIT(avp->av_bslot); + } else { + cur_conf->enable_beacon |= BIT(avp->av_bslot); + if (!enabled) + ath9k_cache_beacon_config(sc, ctx, bss_conf); } } diff --git a/drivers/net/wireless/ath/ath9k/common.h b/drivers/net/wireless/ath/ath9k/common.h index 2b79a56..d237373 100644 --- a/drivers/net/wireless/ath/ath9k/common.h +++ b/drivers/net/wireless/ath/ath9k/common.h @@ -54,7 +54,7 @@ struct ath_beacon_config { u16 dtim_period; u16 bmiss_timeout; u8 dtim_count; - bool enable_beacon; + u8 enable_beacon; bool ibss_creator; u32 nexttbtt; u32 intval; -- cgit v0.10.2 From c09396eb8e5a8df668174993c6400763022b2466 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sun, 15 Mar 2015 08:07:04 +0100 Subject: ath9k: disable TPC support again (for now) TPC support has been observed to cause some tx power fluctuations on some devices with at least AR934x and AR938x chips. Disable it for now until the bugs have been found and fixed Signed-off-by: Felix Fietkau Signed-off-by: Kalle Valo diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c index 60aa8d7..8529014 100644 --- a/drivers/net/wireless/ath/ath9k/hw.c +++ b/drivers/net/wireless/ath/ath9k/hw.c @@ -424,7 +424,7 @@ static void ath9k_hw_init_defaults(struct ath_hw *ah) ah->power_mode = ATH9K_PM_UNDEFINED; ah->htc_reset_init = true; - ah->tpc_enabled = true; + ah->tpc_enabled = false; ah->ani_function = ATH9K_ANI_ALL; if (!AR_SREV_9300_20_OR_LATER(ah)) -- cgit v0.10.2 From 4899c054a90439477b24da8977db8d738376fe90 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Sun, 15 Mar 2015 21:56:04 -0500 Subject: USB: ftdi_sio: Added custom PID for Synapse Wireless product Synapse Wireless uses the FTDI VID with a custom PID of 0x9090 for their SNAP Stick 200 product. Signed-off-by: Doug Goldstein Cc: stable Signed-off-by: Johan Hovold diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 3086dec..130b354 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -604,6 +604,7 @@ static const struct usb_device_id id_table_combined[] = { .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONLXM_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_SYNAPSE_SS200_PID) }, /* * ELV devices: */ diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index 56b1b55..4e4f46f 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -561,6 +561,12 @@ */ #define FTDI_NT_ORIONLXM_PID 0x7c90 /* OrionLXm Substation Automation Platform */ +/* + * Synapse Wireless product ids (FTDI_VID) + * http://www.synapse-wireless.com + */ +#define FTDI_SYNAPSE_SS200_PID 0x9090 /* SS200 - SNAP Stick 200 */ + /********************************/ /** third-party VID/PID combos **/ -- cgit v0.10.2 From 97a33ced310ab9bdb16699c2c64b28f29de0a23d Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Fri, 13 Mar 2015 00:54:16 -0700 Subject: ASoC: qcom: Change qcom,adsp in LPASS CPU bindings Change the representation of the audio DSP, in the LPASS CPU bindings description, from a required subnode to an optional phandle. Signed-off-by: Kenneth Westfield Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt index e7c6e93..e00732d 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt @@ -22,13 +22,9 @@ Required properties: - reg-names : A list which must include the following entries: * "lpass-lpaif" -Required subnodes: +Optional properties: -- qcom,adsp : Audio DSP sub-node - -Optional Audio DSP subnode properties: - -- status : "disabled" indicates the adsp is not available. +- qcom,adsp : Phandle for the audio DSP node Example: @@ -43,7 +39,5 @@ lpass@28100000 { pinctrl-1 = <&mi2s_idle>; reg = <0x28100000 0x10000>; reg-names = "lpass-lpaif"; - qcom,adsp { - status = "disabled"; - }; + qcom,adsp = <&adsp>; }; -- cgit v0.10.2 From 8ebe148be9aa12641c62a3c99c65859bf95445fe Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Fri, 13 Mar 2015 00:54:17 -0700 Subject: ASoC: qcom: Modify test for DSP in LPASS driver As the representation of the DSP in the device tree has changed from a required subnode to an optional phandle, modify the test for DSP existence in the LPASS CPU DAI driver, accordingly. Signed-off-by: Kenneth Westfield Signed-off-by: Mark Brown diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index d516713..6698d05 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -359,45 +359,26 @@ static const struct regmap_config lpass_cpu_regmap_config = { .cache_type = REGCACHE_FLAT, }; -static int lpass_cpu_parse_of(struct device *dev) +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_get_child_by_name(dev->of_node, "qcom,adsp"); - if (!dsp_of_node) { - dev_err(dev, "%s() error getting qcom,adsp sub-node\n", - __func__); - return -EINVAL; - } - - if (of_device_is_available(dsp_of_node)) { - dev_err(dev, "%s() DSP exists and holds audio resources\n", + 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; } - return 0; -} - -static int lpass_cpu_platform_probe(struct platform_device *pdev) -{ - struct lpass_data *drvdata; - struct resource *res; - int ret; - drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data), GFP_KERNEL); if (!drvdata) return -ENOMEM; platform_set_drvdata(pdev, drvdata); - ret = lpass_cpu_parse_of(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "%s() error getting DT node info: %d\n", - __func__, ret); - return ret; - } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif"); if (!res) { dev_err(&pdev->dev, "%s() error getting resource\n", __func__); -- cgit v0.10.2 From 81ddc01d56d773eed414b5c1a7f1dba1db35f538 Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Fri, 13 Mar 2015 01:01:04 -0700 Subject: ASoC: qcom: Document Storm bindings Add documentation to the sound directory of the device-tree bindings for the soundcard of the Storm board. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/storm.txt b/Documentation/devicetree/bindings/sound/storm.txt new file mode 100644 index 0000000..062a4c1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/storm.txt @@ -0,0 +1,23 @@ +* Sound complex for Storm boards + +Models a soundcard for Storm boards with the Qualcomm Technologies IPQ806x SOC +connected to a MAX98357A DAC via I2S. + +Required properties: + +- compatible : "google,storm-audio" +- cpu : Phandle of the CPU DAI +- codec : Phandle of the codec DAI + +Optional properties: + +- qcom,model : The user-visible name of this sound card. + +Example: + +sound { + compatible = "google,storm-audio"; + qcom,model = "ipq806x-storm"; + cpu = <&lpass_cpu>; + codec = <&max98357a>; +}; -- cgit v0.10.2 From 79119c798649630b3191784a708b45cea4e31daf Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Fri, 13 Mar 2015 01:01:05 -0700 Subject: ASoC: qcom: Add Storm machine driver Add machine driver for the Storm board with the IPQ806X SOC connected to the MAX98357A DAC. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c new file mode 100644 index 0000000..b8bd296 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#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"); -- cgit v0.10.2 From f380dd3f3cd77df3c5d297a635b635f72fb5a2b1 Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Fri, 13 Mar 2015 01:01:06 -0700 Subject: ASoC: qcom: Add ability to build QCOM drivers Define the LPASS platform driver, the LPASS CPU DAI driver and the Storm machine driver configurations, and how to build them. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig new file mode 100644 index 0000000..5f58e4f --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,25 @@ +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. diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 0000000..c5ce96c --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,11 @@ +# 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 -- cgit v0.10.2 From 6a328885896ef087a28173792ea93f4dde9782ef Mon Sep 17 00:00:00 2001 From: Kenneth Westfield Date: Fri, 13 Mar 2015 01:01:07 -0700 Subject: ASoC: Allow for building QCOM drivers Allow for the Qualcomm Technologies ASoC drivers to build. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami Signed-off-by: Mark Brown diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index dcc79aa..3ba52da 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 5b3c8f6..974ba70 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/ -- cgit v0.10.2 From 6b5b042d4c675cb9d3446a1cdcaca98e715ba812 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 15 Mar 2015 10:27:20 +0100 Subject: ASoC: Make snd_soc_dapm_kcontrol_codec() inline snd_soc_dapm_kcontrol_codec() is a extremely simple function and inlining it typically results in less code than necessary for calling the non-inlined version of the function. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 8d7416e..78633ef 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -440,7 +440,6 @@ void dapm_mark_endpoints_dirty(struct snd_soc_card *card); int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget_list **list); -struct snd_soc_codec *snd_soc_dapm_kcontrol_codec(struct snd_kcontrol *kcontrol); struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( struct snd_kcontrol *kcontrol); diff --git a/include/sound/soc.h b/include/sound/soc.h index 0d1ade1..85a6853 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1258,6 +1258,19 @@ static inline struct snd_soc_dapm_context *snd_soc_component_get_dapm( return component->dapm_ptr; } +/** + * snd_soc_dapm_kcontrol_codec() - Returns the codec associated to a kcontrol + * @kcontrol: The kcontrol + * + * This function must only be used on DAPM contexts that are known to be part of + * a CODEC (e.g. in a CODEC driver). Otherwise the behavior is undefined. + */ +static inline struct snd_soc_codec *snd_soc_dapm_kcontrol_codec( + struct snd_kcontrol *kcontrol) +{ + return snd_soc_dapm_to_codec(snd_soc_dapm_kcontrol_dapm(kcontrol)); +} + /* codec IO */ unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg); int snd_soc_write(struct snd_soc_codec *codec, unsigned int reg, diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b6f8820..95337c8 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -473,16 +473,6 @@ struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( } EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_dapm); -/** - * snd_soc_dapm_kcontrol_codec() - Returns the codec associated to a kcontrol - * @kcontrol: The kcontrol - */ -struct snd_soc_codec *snd_soc_dapm_kcontrol_codec(struct snd_kcontrol *kcontrol) -{ - return snd_soc_dapm_to_codec(snd_soc_dapm_kcontrol_dapm(kcontrol)); -} -EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_codec); - static void dapm_reset(struct snd_soc_card *card) { struct snd_soc_dapm_widget *w; -- cgit v0.10.2 From fa41181fe37530d78acb25b4e2c9c019241cbbf6 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 15 Mar 2015 12:15:24 +0100 Subject: ASoC: nuc900: No need to track the dma buffer in the driver state struct The DMA buffer and address can be accessed through the snd_pcm_runtime. There is no need to manually track them in the driver's state struct. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/nuc900/nuc900-audio.h b/sound/soc/nuc900/nuc900-audio.h index 59f7e8e..d0b7257 100644 --- a/sound/soc/nuc900/nuc900-audio.h +++ b/sound/soc/nuc900/nuc900-audio.h @@ -100,10 +100,7 @@ struct nuc900_audio { void __iomem *mmio; spinlock_t lock; - dma_addr_t dma_addr[2]; - unsigned long buffersize[2]; unsigned long irq_num; - struct snd_pcm_substream *substream; struct resource *res; struct clk *clk; struct device *dev; diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c index b809fa9..5ae5ca1 100644 --- a/sound/soc/nuc900/nuc900-pcm.c +++ b/sound/soc/nuc900/nuc900-pcm.c @@ -42,29 +42,10 @@ static const struct snd_pcm_hardware nuc900_pcm_hardware = { static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct nuc900_audio *nuc900_audio = runtime->private_data; - unsigned long flags; - int ret = 0; - - ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - if (ret < 0) - return ret; - - spin_lock_irqsave(&nuc900_audio->lock, flags); - - nuc900_audio->substream = substream; - nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr; - nuc900_audio->buffersize[substream->stream] = - params_buffer_bytes(params); - - spin_unlock_irqrestore(&nuc900_audio->lock, flags); - - return ret; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); } -static void nuc900_update_dma_register(struct snd_pcm_substream *substream, - dma_addr_t dma_addr, size_t count) +static void nuc900_update_dma_register(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct nuc900_audio *nuc900_audio = runtime->private_data; @@ -78,8 +59,8 @@ static void nuc900_update_dma_register(struct snd_pcm_substream *substream, mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; } - AUDIO_WRITE(mmio_addr, dma_addr); - AUDIO_WRITE(mmio_len, count); + AUDIO_WRITE(mmio_addr, runtime->dma_addr); + AUDIO_WRITE(mmio_len, runtime->dma_bytes); } static void nuc900_dma_start(struct snd_pcm_substream *substream) @@ -170,9 +151,7 @@ static int nuc900_dma_prepare(struct snd_pcm_substream *substream) spin_lock_irqsave(&nuc900_audio->lock, flags); - nuc900_update_dma_register(substream, - nuc900_audio->dma_addr[substream->stream], - nuc900_audio->buffersize[substream->stream]); + nuc900_update_dma_register(substream); val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); -- cgit v0.10.2 From c36aa0a1929a1f0f0b8c374276e49cc663e8f957 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Mon, 16 Mar 2015 14:39:57 +0800 Subject: ASoC: rt5677: add API to select ASRC clock source This patch defines an API to select the clock source for specified filters. Signed-off-by: Oder Chiou Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index c2a6e40..af18258 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -1034,6 +1034,169 @@ static int can_use_asrc(struct snd_soc_dapm_widget *source, return 0; } +/** + * rt5677_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @codec: SoC audio codec device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5677 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the codec driver will turn on ASRC + * for these filters if ASRC is selected as their clock source. + */ +int rt5677_sel_asrc_clk_src(struct snd_soc_codec *codec, + unsigned int filter_mask, unsigned int clk_src) +{ + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + unsigned int asrc3_mask = 0, asrc3_value = 0; + unsigned int asrc4_mask = 0, asrc4_value = 0; + unsigned int asrc5_mask = 0, asrc5_value = 0; + unsigned int asrc6_mask = 0, asrc6_value = 0; + unsigned int asrc7_mask = 0, asrc7_value = 0; + + switch (clk_src) { + case RT5677_CLK_SEL_SYS: + case RT5677_CLK_SEL_I2S1_ASRC: + case RT5677_CLK_SEL_I2S2_ASRC: + case RT5677_CLK_SEL_I2S3_ASRC: + case RT5677_CLK_SEL_I2S4_ASRC: + case RT5677_CLK_SEL_I2S5_ASRC: + case RT5677_CLK_SEL_I2S6_ASRC: + case RT5677_CLK_SEL_SYS2: + case RT5677_CLK_SEL_SYS3: + case RT5677_CLK_SEL_SYS4: + case RT5677_CLK_SEL_SYS5: + case RT5677_CLK_SEL_SYS6: + case RT5677_CLK_SEL_SYS7: + break; + + default: + return -EINVAL; + } + + /* ASRC 3 */ + if (filter_mask & RT5677_DA_STEREO_FILTER) { + asrc3_mask |= RT5677_DA_STO_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5677_DA_STO_CLK_SEL_MASK) + | (clk_src << RT5677_DA_STO_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO2_L_FILTER) { + asrc3_mask |= RT5677_DA_MONO2L_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5677_DA_MONO2L_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO2L_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO2_R_FILTER) { + asrc3_mask |= RT5677_DA_MONO2R_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5677_DA_MONO2R_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO2R_CLK_SEL_SFT); + } + + if (asrc3_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_3, asrc3_mask, + asrc3_value); + + /* ASRC 4 */ + if (filter_mask & RT5677_DA_MONO3_L_FILTER) { + asrc4_mask |= RT5677_DA_MONO3L_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO3L_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO3L_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO3_R_FILTER) { + asrc4_mask |= RT5677_DA_MONO3R_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO3R_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO3R_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO4_L_FILTER) { + asrc4_mask |= RT5677_DA_MONO4L_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO4L_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO4L_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO4_R_FILTER) { + asrc4_mask |= RT5677_DA_MONO4R_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO4R_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO4R_CLK_SEL_SFT); + } + + if (asrc4_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_4, asrc4_mask, + asrc4_value); + + /* ASRC 5 */ + if (filter_mask & RT5677_AD_STEREO1_FILTER) { + asrc5_mask |= RT5677_AD_STO1_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO1_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO1_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_STEREO2_FILTER) { + asrc5_mask |= RT5677_AD_STO2_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO2_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO2_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_STEREO3_FILTER) { + asrc5_mask |= RT5677_AD_STO3_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO3_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO3_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_STEREO4_FILTER) { + asrc5_mask |= RT5677_AD_STO4_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO4_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO4_CLK_SEL_SFT); + } + + if (asrc5_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_5, asrc5_mask, + asrc5_value); + + /* ASRC 6 */ + if (filter_mask & RT5677_AD_MONO_L_FILTER) { + asrc6_mask |= RT5677_AD_MONOL_CLK_SEL_MASK; + asrc6_value = (asrc6_value & ~RT5677_AD_MONOL_CLK_SEL_MASK) + | (clk_src << RT5677_AD_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_MONO_R_FILTER) { + asrc6_mask |= RT5677_AD_MONOR_CLK_SEL_MASK; + asrc6_value = (asrc6_value & ~RT5677_AD_MONOR_CLK_SEL_MASK) + | (clk_src << RT5677_AD_MONOR_CLK_SEL_SFT); + } + + if (asrc6_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_6, asrc6_mask, + asrc6_value); + + /* ASRC 7 */ + if (filter_mask & RT5677_DSP_OB_0_3_FILTER) { + asrc7_mask |= RT5677_DSP_OB_0_3_CLK_SEL_MASK; + asrc7_value = (asrc7_value & ~RT5677_DSP_OB_0_3_CLK_SEL_MASK) + | (clk_src << RT5677_DSP_OB_0_3_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DSP_OB_4_7_FILTER) { + asrc7_mask |= RT5677_DSP_OB_4_7_CLK_SEL_MASK; + asrc7_value = (asrc7_value & ~RT5677_DSP_OB_4_7_CLK_SEL_MASK) + | (clk_src << RT5677_DSP_OB_4_7_CLK_SEL_SFT); + } + + if (asrc7_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_7, asrc7_mask, + asrc7_value); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5677_sel_asrc_clk_src); + /* Digital Mixer */ static const struct snd_kcontrol_new rt5677_sto1_adc_l_mix[] = { SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO1_ADC_MIXER, diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index 07df96b..9dceb41 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -1406,6 +1406,46 @@ #define RT5677_DSP_CLK_SRC_PLL2 (0x0 << 7) #define RT5677_DSP_CLK_SRC_BYPASS (0x1 << 7) +/* ASRC Control 3 (0x85) */ +#define RT5677_DA_STO_CLK_SEL_MASK (0xf << 12) +#define RT5677_DA_STO_CLK_SEL_SFT 12 +#define RT5677_DA_MONO2L_CLK_SEL_MASK (0xf << 4) +#define RT5677_DA_MONO2L_CLK_SEL_SFT 4 +#define RT5677_DA_MONO2R_CLK_SEL_MASK (0xf << 0) +#define RT5677_DA_MONO2R_CLK_SEL_SFT 0 + +/* ASRC Control 4 (0x86) */ +#define RT5677_DA_MONO3L_CLK_SEL_MASK (0xf << 12) +#define RT5677_DA_MONO3L_CLK_SEL_SFT 12 +#define RT5677_DA_MONO3R_CLK_SEL_MASK (0xf << 8) +#define RT5677_DA_MONO3R_CLK_SEL_SFT 8 +#define RT5677_DA_MONO4L_CLK_SEL_MASK (0xf << 4) +#define RT5677_DA_MONO4L_CLK_SEL_SFT 4 +#define RT5677_DA_MONO4R_CLK_SEL_MASK (0xf << 0) +#define RT5677_DA_MONO4R_CLK_SEL_SFT 0 + +/* ASRC Control 5 (0x87) */ +#define RT5677_AD_STO1_CLK_SEL_MASK (0xf << 12) +#define RT5677_AD_STO1_CLK_SEL_SFT 12 +#define RT5677_AD_STO2_CLK_SEL_MASK (0xf << 8) +#define RT5677_AD_STO2_CLK_SEL_SFT 8 +#define RT5677_AD_STO3_CLK_SEL_MASK (0xf << 4) +#define RT5677_AD_STO3_CLK_SEL_SFT 4 +#define RT5677_AD_STO4_CLK_SEL_MASK (0xf << 0) +#define RT5677_AD_STO4_CLK_SEL_SFT 0 + +/* ASRC Control 6 (0x88) */ +#define RT5677_AD_MONOL_CLK_SEL_MASK (0xf << 12) +#define RT5677_AD_MONOL_CLK_SEL_SFT 12 +#define RT5677_AD_MONOR_CLK_SEL_MASK (0xf << 8) +#define RT5677_AD_MONOR_CLK_SEL_SFT 8 + +/* ASRC Control 7 (0x89) */ +#define RT5677_DSP_OB_0_3_CLK_SEL_MASK (0xf << 12) +#define RT5677_DSP_OB_0_3_CLK_SEL_SFT 12 +#define RT5677_DSP_OB_4_7_CLK_SEL_MASK (0xf << 8) +#define RT5677_DSP_OB_4_7_CLK_SEL_SFT 8 + /* VAD Function Control 4 (0x9f) */ #define RT5677_VAD_SRC_MASK (0x7 << 8) #define RT5677_VAD_SRC_SFT 8 @@ -1670,6 +1710,42 @@ enum rt5677_type { RT5676, }; +/* ASRC clock source selection */ +enum { + RT5677_CLK_SEL_SYS, + RT5677_CLK_SEL_I2S1_ASRC, + RT5677_CLK_SEL_I2S2_ASRC, + RT5677_CLK_SEL_I2S3_ASRC, + RT5677_CLK_SEL_I2S4_ASRC, + RT5677_CLK_SEL_I2S5_ASRC, + RT5677_CLK_SEL_I2S6_ASRC, + RT5677_CLK_SEL_SYS2, + RT5677_CLK_SEL_SYS3, + RT5677_CLK_SEL_SYS4, + RT5677_CLK_SEL_SYS5, + RT5677_CLK_SEL_SYS6, + RT5677_CLK_SEL_SYS7, +}; + +/* filter mask */ +enum { + RT5677_DA_STEREO_FILTER = 0x1, + RT5677_DA_MONO2_L_FILTER = (0x1 << 1), + RT5677_DA_MONO2_R_FILTER = (0x1 << 2), + RT5677_DA_MONO3_L_FILTER = (0x1 << 3), + RT5677_DA_MONO3_R_FILTER = (0x1 << 4), + RT5677_DA_MONO4_L_FILTER = (0x1 << 5), + RT5677_DA_MONO4_R_FILTER = (0x1 << 6), + RT5677_AD_STEREO1_FILTER = (0x1 << 7), + RT5677_AD_STEREO2_FILTER = (0x1 << 8), + RT5677_AD_STEREO3_FILTER = (0x1 << 9), + RT5677_AD_STEREO4_FILTER = (0x1 << 10), + RT5677_AD_MONO_L_FILTER = (0x1 << 11), + RT5677_AD_MONO_R_FILTER = (0x1 << 12), + RT5677_DSP_OB_0_3_FILTER = (0x1 << 13), + RT5677_DSP_OB_4_7_FILTER = (0x1 << 14), +}; + struct rt5677_priv { struct snd_soc_codec *codec; struct rt5677_platform_data pdata; @@ -1696,4 +1772,7 @@ struct rt5677_priv { bool is_vref_slow; }; +int rt5677_sel_asrc_clk_src(struct snd_soc_codec *codec, + unsigned int filter_mask, unsigned int clk_src); + #endif /* __RT5677_H__ */ -- cgit v0.10.2 From 99d422341376443a98ef22d0f0003b3381f32186 Mon Sep 17 00:00:00 2001 From: Songjun Wu Date: Thu, 12 Mar 2015 10:17:11 +0800 Subject: ASoC: wm8731: let codec to manage clock by itself Enable WM8731 to support common clock framework. Signed-off-by: Songjun Wu Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 098c143..8df1550 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ static const char *wm8731_supply_names[WM8731_NUM_SUPPLIES] = { /* codec private data */ struct wm8731_priv { struct regmap *regmap; + struct clk *mclk; struct regulator_bulk_data supplies[WM8731_NUM_SUPPLIES]; const struct snd_pcm_hw_constraint_list *constraints; unsigned int sysclk; @@ -390,6 +392,8 @@ static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, switch (clk_id) { case WM8731_SYSCLK_XTAL: case WM8731_SYSCLK_MCLK: + if (wm8731->mclk && clk_set_rate(wm8731->mclk, freq)) + return -EINVAL; wm8731->sysclk_type = clk_id; break; default: @@ -491,6 +495,8 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: + if (wm8731->mclk) + clk_prepare_enable(wm8731->mclk); break; case SND_SOC_BIAS_PREPARE: break; @@ -509,6 +515,8 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, snd_soc_write(codec, WM8731_PWR, reg | 0x0040); break; case SND_SOC_BIAS_OFF: + if (wm8731->mclk) + clk_disable_unprepare(wm8731->mclk); snd_soc_write(codec, WM8731_PWR, 0xffff); regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); @@ -667,6 +675,19 @@ static int wm8731_spi_probe(struct spi_device *spi) if (wm8731 == NULL) return -ENOMEM; + wm8731->mclk = devm_clk_get(&spi->dev, "mclk"); + if (IS_ERR(wm8731->mclk)) { + ret = PTR_ERR(wm8731->mclk); + if (ret == -ENOENT) { + wm8731->mclk = NULL; + dev_warn(&spi->dev, "Assuming static MCLK\n"); + } else { + dev_err(&spi->dev, "Failed to get MCLK: %d\n", + ret); + return ret; + } + } + mutex_init(&wm8731->lock); wm8731->regmap = devm_regmap_init_spi(spi, &wm8731_regmap); @@ -718,6 +739,19 @@ static int wm8731_i2c_probe(struct i2c_client *i2c, if (wm8731 == NULL) return -ENOMEM; + wm8731->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(wm8731->mclk)) { + ret = PTR_ERR(wm8731->mclk); + if (ret == -ENOENT) { + wm8731->mclk = NULL; + dev_warn(&i2c->dev, "Assuming static MCLK\n"); + } else { + dev_err(&i2c->dev, "Failed to get MCLK: %d\n", + ret); + return ret; + } + } + mutex_init(&wm8731->lock); wm8731->regmap = devm_regmap_init_i2c(i2c, &wm8731_regmap); -- cgit v0.10.2 From 0dd96b3e39df83265ef3f79170a623cebee50380 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 16 Mar 2015 16:39:56 +0100 Subject: ASoC: rt286: Drop unnecessary dapm bias_level initialization The default value for the bias_level is SND_SOC_BIAS_OFF when probe is being called, there is no need to initialize it explicitly. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 49c44a7..ea96797 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -1048,7 +1048,6 @@ static int rt286_probe(struct snd_soc_codec *codec) struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); rt286->codec = codec; - codec->dapm.bias_level = SND_SOC_BIAS_OFF; if (rt286->i2c->irq) { regmap_update_bits(rt286->regmap, -- cgit v0.10.2 From f263fa3e0f0b1d610f7a9d72a91fe67059d5564f Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 16 Mar 2015 16:39:57 +0100 Subject: ASoC: wm2200: Drop unnecessary dapm bias_level initialization The default value for the bias_level is SND_SOC_BIAS_OFF when probe is being called, there is no need to initialize it explicitly. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index b48694a..5a9da28 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -1554,7 +1554,6 @@ static int wm2200_probe(struct snd_soc_codec *codec) int ret; wm2200->codec = codec; - codec->dapm.bias_level = SND_SOC_BIAS_OFF; ret = snd_soc_add_codec_controls(codec, wm_adsp1_fw_controls, 2); if (ret != 0) -- cgit v0.10.2 From 066d7b87fa11213d7eca7af8fdd8447e1c62117b Mon Sep 17 00:00:00 2001 From: Jin Yao Date: Tue, 17 Mar 2015 10:23:30 +0800 Subject: ASoC: Intel: Add suspend_pre and resume_post for Braswell snd_soc_card On Braswell, we need to add some machine specific setting before suspend and after resume. For example, disable/enable jack detection in codec so use snd_soc_card suspend_pre and resume_post ops for this purpose. Signed-off-by: Jin Yao Signed-off-by: Mark Brown diff --git a/sound/soc/intel/cht_bsw_rt5672.c b/sound/soc/intel/cht_bsw_rt5672.c index 279df4c..c41fae3 100644 --- a/sound/soc/intel/cht_bsw_rt5672.c +++ b/sound/soc/intel/cht_bsw_rt5672.c @@ -267,6 +267,35 @@ static struct snd_soc_dai_link cht_dailink[] = { }, }; +static int cht_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-10EC5670:00")) { + dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n"); + rt5670_jack_suspend(codec); + break; + } + } + return 0; +} + +static int cht_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-10EC5670:00")) { + dev_dbg(codec->dev, "enabling jack detect for resume.\n"); + rt5670_jack_resume(codec); + break; + } + } + + return 0; +} + /* SoC card */ static struct snd_soc_card snd_soc_card_cht = { .name = "cherrytrailcraudio", @@ -278,6 +307,8 @@ static struct snd_soc_card snd_soc_card_cht = { .num_dapm_routes = ARRAY_SIZE(cht_audio_map), .controls = cht_mc_controls, .num_controls = ARRAY_SIZE(cht_mc_controls), + .suspend_pre = cht_suspend_pre, + .resume_post = cht_resume_post, }; static int snd_cht_mc_probe(struct platform_device *pdev) -- cgit v0.10.2 From 6b3b58d97fb2d03f8f1d009a77baece311aa0d16 Mon Sep 17 00:00:00 2001 From: Jin Yao Date: Tue, 17 Mar 2015 10:23:31 +0800 Subject: ASoC: Intel: move the jack creation to Braswell machine driver The jack creation code was in rt5670 codec driver before due to the jack resources (gpio/irq) were defined under the node of codec device in ACPI on Braswell. We used the snd_soc_jack_new() to create a jack instance. But now snd_soc_jack_new() is removed from upstream and we can't use snd_soc_card_jack_new() in codec driver, so we move the jack creation code to machine driver and pass the jack instance to codec driver for further processing. Signed-off-by: Bard Liao Signed-off-by: Jin Yao Signed-off-by: Mark Brown diff --git a/sound/soc/intel/cht_bsw_rt5672.c b/sound/soc/intel/cht_bsw_rt5672.c index c41fae3..4204fc4 100644 --- a/sound/soc/intel/cht_bsw_rt5672.c +++ b/sound/soc/intel/cht_bsw_rt5672.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "../codecs/rt5670.h" #include "sst-atom-controls.h" @@ -29,6 +30,20 @@ #define CHT_PLAT_CLK_3_HZ 19200000 #define CHT_CODEC_DAI "rt5670-aif1" +static struct snd_soc_jack cht_bsw_headset; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin cht_bsw_headset_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, +}; + static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) { int i; @@ -178,6 +193,15 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) | RT5670_AD_MONO_L_FILTER | RT5670_AD_MONO_R_FILTER, RT5670_CLK_SEL_I2S1_ASRC); + + ret = snd_soc_card_jack_new(runtime->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, &cht_bsw_headset, + cht_bsw_headset_pins, ARRAY_SIZE(cht_bsw_headset_pins)); + if (ret) + return ret; + + rt5670_set_jack_detect(codec, &cht_bsw_headset); return 0; } -- cgit v0.10.2 From 379c4b05dbfee4f8e003c9bd4c92b8c4c7146277 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2015 04:45:54 +0000 Subject: ASoC: ak4642: tidyup DAPM route for playback It needs DAC -> Playback route instead of direct settings via SND_SOC_DAPM_DAC. otherwise, it can't find correct path if sound card used prefix name Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index dde8b49..7255f69 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -187,7 +187,7 @@ static const struct snd_soc_dapm_widget ak4642_dapm_widgets[] = { ARRAY_SIZE(ak4642_lout_mixer_controls)), /* DAC */ - SND_SOC_DAPM_DAC("DAC", "HiFi Playback", PW_MGMT1, 2, 0), + SND_SOC_DAPM_DAC("DAC", NULL, PW_MGMT1, 2, 0), }; static const struct snd_soc_dapm_route ak4642_intercon[] = { @@ -205,6 +205,8 @@ static const struct snd_soc_dapm_route ak4642_intercon[] = { {"DACH", NULL, "DAC"}, {"LINEOUT Mixer", "DACL", "DAC"}, + + { "DAC", NULL, "Playback" }, }; /* -- cgit v0.10.2 From a2f4870697a5bcf4a87073ec6b32dd2928c1211d Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 17 Mar 2015 12:23:19 -0400 Subject: fs: make sure the timestamps for lazytime inodes eventually get written Jan Kara pointed out that if there is an inode which is constantly getting dirtied with I_DIRTY_PAGES, an inode with an updated timestamp will never be written since inode->dirtied_when is constantly getting updated. We fix this by adding an extra field to the inode, dirtied_time_when, so inodes with a stale dirtytime can get detected and handled. In addition, if we have a dirtytime inode caused by an atime update, and there is no write activity on the file system, we need to have a secondary system to make sure these inodes get written out. We do this by setting up a second delayed work structure which wakes up the CPU much more rarely compared to writeback_expire_centisecs. Signed-off-by: Theodore Ts'o Reviewed-by: Jan Kara diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index e907052..2cfcd74 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -53,6 +53,18 @@ struct wb_writeback_work { struct completion *done; /* set if the caller waits */ }; +/* + * If an inode is constantly having its pages dirtied, but then the + * updates stop dirtytime_expire_interval seconds in the past, it's + * possible for the worst case time between when an inode has its + * timestamps updated and when they finally get written out to be two + * dirtytime_expire_intervals. We set the default to 12 hours (in + * seconds), which means most of the time inodes will have their + * timestamps written to disk after 12 hours, but in the worst case a + * few inodes might not their timestamps updated for 24 hours. + */ +unsigned int dirtytime_expire_interval = 12 * 60 * 60; + /** * writeback_in_progress - determine whether there is writeback in progress * @bdi: the device's backing_dev_info structure. @@ -275,8 +287,8 @@ static int move_expired_inodes(struct list_head *delaying_queue, if ((flags & EXPIRE_DIRTY_ATIME) == 0) older_than_this = work->older_than_this; - else if ((work->reason == WB_REASON_SYNC) == 0) { - expire_time = jiffies - (HZ * 86400); + else if (!work->for_sync) { + expire_time = jiffies - (dirtytime_expire_interval * HZ); older_than_this = &expire_time; } while (!list_empty(delaying_queue)) { @@ -458,6 +470,7 @@ static void requeue_inode(struct inode *inode, struct bdi_writeback *wb, */ redirty_tail(inode, wb); } else if (inode->i_state & I_DIRTY_TIME) { + inode->dirtied_when = jiffies; list_move(&inode->i_wb_list, &wb->b_dirty_time); } else { /* The inode is clean. Remove from writeback lists. */ @@ -505,12 +518,17 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc) spin_lock(&inode->i_lock); dirty = inode->i_state & I_DIRTY; - if (((dirty & (I_DIRTY_SYNC | I_DIRTY_DATASYNC)) && - (inode->i_state & I_DIRTY_TIME)) || - (inode->i_state & I_DIRTY_TIME_EXPIRED)) { - dirty |= I_DIRTY_TIME | I_DIRTY_TIME_EXPIRED; - trace_writeback_lazytime(inode); - } + if (inode->i_state & I_DIRTY_TIME) { + if ((dirty & (I_DIRTY_SYNC | I_DIRTY_DATASYNC)) || + unlikely(inode->i_state & I_DIRTY_TIME_EXPIRED) || + unlikely(time_after(jiffies, + (inode->dirtied_time_when + + dirtytime_expire_interval * HZ)))) { + dirty |= I_DIRTY_TIME | I_DIRTY_TIME_EXPIRED; + trace_writeback_lazytime(inode); + } + } else + inode->i_state &= ~I_DIRTY_TIME_EXPIRED; inode->i_state &= ~dirty; /* @@ -1131,6 +1149,45 @@ void wakeup_flusher_threads(long nr_pages, enum wb_reason reason) rcu_read_unlock(); } +/* + * Wake up bdi's periodically to make sure dirtytime inodes gets + * written back periodically. We deliberately do *not* check the + * b_dirtytime list in wb_has_dirty_io(), since this would cause the + * kernel to be constantly waking up once there are any dirtytime + * inodes on the system. So instead we define a separate delayed work + * function which gets called much more rarely. (By default, only + * once every 12 hours.) + * + * If there is any other write activity going on in the file system, + * this function won't be necessary. But if the only thing that has + * happened on the file system is a dirtytime inode caused by an atime + * update, we need this infrastructure below to make sure that inode + * eventually gets pushed out to disk. + */ +static void wakeup_dirtytime_writeback(struct work_struct *w); +static DECLARE_DELAYED_WORK(dirtytime_work, wakeup_dirtytime_writeback); + +static void wakeup_dirtytime_writeback(struct work_struct *w) +{ + struct backing_dev_info *bdi; + + rcu_read_lock(); + list_for_each_entry_rcu(bdi, &bdi_list, bdi_list) { + if (list_empty(&bdi->wb.b_dirty_time)) + continue; + bdi_wakeup_thread(bdi); + } + rcu_read_unlock(); + schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); +} + +static int __init start_dirtytime_writeback(void) +{ + schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); + return 0; +} +__initcall(start_dirtytime_writeback); + static noinline void block_dump___mark_inode_dirty(struct inode *inode) { if (inode->i_ino || strcmp(inode->i_sb->s_id, "bdev")) { @@ -1269,8 +1326,13 @@ void __mark_inode_dirty(struct inode *inode, int flags) } inode->dirtied_when = jiffies; - list_move(&inode->i_wb_list, dirtytime ? - &bdi->wb.b_dirty_time : &bdi->wb.b_dirty); + if (dirtytime) + inode->dirtied_time_when = jiffies; + if (inode->i_state & (I_DIRTY_INODE | I_DIRTY_PAGES)) + list_move(&inode->i_wb_list, &bdi->wb.b_dirty); + else + list_move(&inode->i_wb_list, + &bdi->wb.b_dirty_time); spin_unlock(&bdi->wb.list_lock); trace_writeback_dirty_inode_enqueue(inode); diff --git a/include/linux/fs.h b/include/linux/fs.h index b4d71b5..f4131e8 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -604,6 +604,7 @@ struct inode { struct mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying */ + unsigned long dirtied_time_when; struct hlist_node i_hash; struct list_head i_wb_list; /* backing dev IO list */ -- cgit v0.10.2 From 1efff914afac8a965ad63817ecf8861a927c2ace Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 17 Mar 2015 12:23:32 -0400 Subject: fs: add dirtytime_expire_seconds sysctl Add a tuning knob so we can adjust the dirtytime expiration timeout, which is very useful for testing lazytime. Signed-off-by: Theodore Ts'o Reviewed-by: Jan Kara diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 2cfcd74..32a8bbd 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -1188,6 +1188,17 @@ static int __init start_dirtytime_writeback(void) } __initcall(start_dirtytime_writeback); +int dirtytime_interval_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int ret; + + ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + if (ret == 0 && write) + mod_delayed_work(system_wq, &dirtytime_work, 0); + return ret; +} + static noinline void block_dump___mark_inode_dirty(struct inode *inode) { if (inode->i_ino || strcmp(inode->i_sb->s_id, "bdev")) { diff --git a/include/linux/writeback.h b/include/linux/writeback.h index 0004833..b2dd371e 100644 --- a/include/linux/writeback.h +++ b/include/linux/writeback.h @@ -130,6 +130,7 @@ extern int vm_dirty_ratio; extern unsigned long vm_dirty_bytes; extern unsigned int dirty_writeback_interval; extern unsigned int dirty_expire_interval; +extern unsigned int dirtytime_expire_interval; extern int vm_highmem_is_dirtyable; extern int block_dump; extern int laptop_mode; @@ -146,6 +147,8 @@ extern int dirty_ratio_handler(struct ctl_table *table, int write, extern int dirty_bytes_handler(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos); +int dirtytime_interval_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos); struct ctl_table; int dirty_writeback_centisecs_handler(struct ctl_table *, int, diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 88ea2d6..ce410bb 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1228,6 +1228,14 @@ static struct ctl_table vm_table[] = { .extra1 = &zero, }, { + .procname = "dirtytime_expire_seconds", + .data = &dirtytime_expire_interval, + .maxlen = sizeof(dirty_expire_interval), + .mode = 0644, + .proc_handler = dirtytime_interval_handler, + .extra1 = &zero, + }, + { .procname = "nr_pdflush_threads", .mode = 0444 /* read-only */, .proc_handler = pdflush_proc_obsolete, -- cgit v0.10.2 From c66150824b8a809a502fd833fa9b18082cd89a39 Mon Sep 17 00:00:00 2001 From: Nikesh Oswal Date: Mon, 2 Feb 2015 17:06:44 +0000 Subject: ASoC: dapm: add code to configure dai link parameters dai-link params for codec-codec links were fixed. The fixed link between codec and another chip which may be another codec, baseband, bluetooth codec etc may require run time configuaration changes. This change provides an optional alsa control to select one of the params from a list of params. Signed-off-by: Nikesh Oswal Signed-off-by: Mark Brown diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 8d7416e..eda8814 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -378,6 +378,7 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card); void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card); int snd_soc_dapm_new_pcm(struct snd_soc_card *card, const struct snd_soc_pcm_stream *params, + unsigned int num_params, struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink); @@ -531,6 +532,8 @@ struct snd_soc_dapm_widget { void *priv; /* widget specific data */ struct regulator *regulator; /* attached regulator */ const struct snd_soc_pcm_stream *params; /* params for dai links */ + unsigned int num_params; /* number of params for dai links */ + unsigned int params_select; /* currently selected param for dai link */ /* dapm control */ int reg; /* negative reg = no direct dapm */ diff --git a/include/sound/soc.h b/include/sound/soc.h index 0d1ade1..4636a05 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -941,6 +941,7 @@ struct snd_soc_dai_link { int be_id; /* optional ID for machine driver BE identification */ const struct snd_soc_pcm_stream *params; + unsigned int num_params; unsigned int dai_fmt; /* format to set on init */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 30579ca..700ac2f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1245,7 +1245,8 @@ static int soc_link_dai_widgets(struct snd_soc_card *card, capture_w = cpu_dai->capture_widget; if (play_w && capture_w) { ret = snd_soc_dapm_new_pcm(card, dai_link->params, - capture_w, play_w); + dai_link->num_params, capture_w, + play_w); if (ret != 0) { dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n", play_w->name, capture_w->name, ret); @@ -1257,7 +1258,8 @@ static int soc_link_dai_widgets(struct snd_soc_card *card, capture_w = codec_dai->capture_widget; if (play_w && capture_w) { ret = snd_soc_dapm_new_pcm(card, dai_link->params, - capture_w, play_w); + dai_link->num_params, capture_w, + play_w); if (ret != 0) { dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n", play_w->name, capture_w->name, ret); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b6f8820..6828b4e 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -853,6 +853,36 @@ static int dapm_new_pga(struct snd_soc_dapm_widget *w) return 0; } +/* create new dapm dai link control */ +static int dapm_new_dai_link(struct snd_soc_dapm_widget *w) +{ + int i, ret; + struct snd_kcontrol *kcontrol; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_card *card = dapm->card->snd_card; + + /* create control for links with > 1 config */ + if (w->num_params <= 1) + return 0; + + /* add kcontrol */ + for (i = 0; i < w->num_kcontrols; i++) { + kcontrol = snd_soc_cnew(&w->kcontrol_news[i], w, + w->name, NULL); + ret = snd_ctl_add(card, kcontrol); + if (ret < 0) { + dev_err(dapm->dev, + "ASoC: failed to add widget %s dapm kcontrol %s: %d\n", + w->name, w->kcontrol_news[i].name, ret); + return ret; + } + kcontrol->private_data = w; + w->kcontrols[i] = kcontrol; + } + + return 0; +} + /* We implement power down on suspend by checking the power state of * the ALSA card - when we are suspending the ALSA state for the card * is set to D3. @@ -2719,6 +2749,9 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) case snd_soc_dapm_out_drv: dapm_new_pga(w); break; + case snd_soc_dapm_dai_link: + dapm_new_dai_link(w); + break; default: break; } @@ -3193,7 +3226,7 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, { struct snd_soc_dapm_path *source_p, *sink_p; struct snd_soc_dai *source, *sink; - const struct snd_soc_pcm_stream *config = w->params; + const struct snd_soc_pcm_stream *config = w->params + w->params_select; struct snd_pcm_substream substream; struct snd_pcm_hw_params *params = NULL; u64 fmt; @@ -3285,8 +3318,39 @@ out: return ret; } +static int snd_soc_dapm_dai_link_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = w->params_select; + + return 0; +} + +static int snd_soc_dapm_dai_link_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol); + + /* Can't change the config when widget is already powered */ + if (w->power) + return -EBUSY; + + if (ucontrol->value.integer.value[0] == w->params_select) + return 0; + + if (ucontrol->value.integer.value[0] >= w->num_params) + return -EINVAL; + + w->params_select = ucontrol->value.integer.value[0]; + + return 0; +} + int snd_soc_dapm_new_pcm(struct snd_soc_card *card, const struct snd_soc_pcm_stream *params, + unsigned int num_params, struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { @@ -3294,14 +3358,61 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card, struct snd_soc_dapm_widget *w; size_t len; char *link_name; - int ret; + int ret, count; + unsigned long private_value; + const char **w_param_text; + struct soc_enum w_param_enum[] = { + SOC_ENUM_SINGLE(0, 0, 0, NULL), + }; + struct snd_kcontrol_new kcontrol_dai_link[] = { + SOC_ENUM_EXT(NULL, w_param_enum[0], + snd_soc_dapm_dai_link_get, + snd_soc_dapm_dai_link_put), + }; + const struct snd_soc_pcm_stream *config = params; + + w_param_text = devm_kcalloc(card->dev, num_params, + sizeof(char *), GFP_KERNEL); + if (!w_param_text) + return -ENOMEM; len = strlen(source->name) + strlen(sink->name) + 2; link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); - if (!link_name) - return -ENOMEM; + if (!link_name) { + ret = -ENOMEM; + goto outfree_w_param; + } snprintf(link_name, len, "%s-%s", source->name, sink->name); + for (count = 0 ; count < num_params; count++) { + if (!config->stream_name) { + dev_warn(card->dapm.dev, + "ASoC: anonymous config %d for dai link %s\n", + count, link_name); + len = strlen("Anonymous Configuration ") + 3; + w_param_text[count] = + devm_kzalloc(card->dev, len, GFP_KERNEL); + if (!w_param_text[count]) { + ret = -ENOMEM; + goto outfree_link_name; + } + snprintf(w_param_text[count], len, + "Anonymous Configuration %d", count); + } else { + w_param_text[count] = devm_kmemdup(card->dev, + config->stream_name, + strlen(config->stream_name) + 1, + GFP_KERNEL); + if (!w_param_text[count]) { + ret = -ENOMEM; + goto outfree_link_name; + } + } + config++; + } + w_param_enum[0].items = num_params; + w_param_enum[0].texts = w_param_text; + memset(&template, 0, sizeof(template)); template.reg = SND_SOC_NOPM; template.id = snd_soc_dapm_dai_link; @@ -3309,6 +3420,30 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card, template.event = snd_soc_dai_link_event; template.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD; + template.num_kcontrols = 1; + /* duplicate w_param_enum on heap so that memory persists */ + private_value = + (unsigned long) devm_kmemdup(card->dev, + (void *)(kcontrol_dai_link[0].private_value), + sizeof(struct soc_enum), GFP_KERNEL); + if (!private_value) { + dev_err(card->dev, "ASoC: Failed to create control for %s widget\n", + link_name); + ret = -ENOMEM; + goto outfree_link_name; + } + kcontrol_dai_link[0].private_value = private_value; + /* duplicate kcontrol_dai_link on heap so that memory persists */ + template.kcontrol_news = + devm_kmemdup(card->dev, &kcontrol_dai_link[0], + sizeof(struct snd_kcontrol_new), + GFP_KERNEL); + if (!template.kcontrol_news) { + dev_err(card->dev, "ASoC: Failed to create control for %s widget\n", + link_name); + ret = -ENOMEM; + goto outfree_private_value; + } dev_dbg(card->dev, "ASoC: adding %s widget\n", link_name); @@ -3316,15 +3451,32 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card, if (!w) { dev_err(card->dev, "ASoC: Failed to create %s widget\n", link_name); - return -ENOMEM; + ret = -ENOMEM; + goto outfree_kcontrol_news; } w->params = params; + w->num_params = num_params; ret = snd_soc_dapm_add_path(&card->dapm, source, w, NULL, NULL); if (ret) - return ret; + goto outfree_w; return snd_soc_dapm_add_path(&card->dapm, w, sink, NULL, NULL); + +outfree_w: + devm_kfree(card->dev, w); +outfree_kcontrol_news: + devm_kfree(card->dev, (void *)template.kcontrol_news); +outfree_private_value: + devm_kfree(card->dev, (void *)private_value); +outfree_link_name: + devm_kfree(card->dev, link_name); +outfree_w_param: + for (count = 0 ; count < num_params; count++) + devm_kfree(card->dev, (void *)w_param_text[count]); + devm_kfree(card->dev, w_param_text); + + return ret; } int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, -- cgit v0.10.2 From 06ac0cd1c4e4e51fa84f866ef69b518488ffa05f Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 18 Mar 2015 10:07:19 +0200 Subject: ASoC: Intel: Remove support for Intel MID DMA from firmware loader Intel MID DMA driver is going to be removed by the coming commit 36111da7838e ("dmaengine: intel-mid-dma: remove the driver") in spi.git tree. Since there are no users for SST_DMA_TYPE_MID type the support for it can be removed from here in advance. Signed-off-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h index f291e32..3412474 100644 --- a/sound/soc/intel/sst-dsp.h +++ b/sound/soc/intel/sst-dsp.h @@ -28,7 +28,6 @@ /* Supported SST DMA Devices */ #define SST_DMA_TYPE_DW 1 -#define SST_DMA_TYPE_MID 2 /* autosuspend delay 5s*/ #define SST_RUNTIME_SUSPEND_DELAY (5 * 1000) diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c index 5e58008..38881f1 100644 --- a/sound/soc/intel/sst-firmware.c +++ b/sound/soc/intel/sst-firmware.c @@ -277,9 +277,6 @@ int sst_dma_new(struct sst_dsp *sst) case SST_DMA_TYPE_DW: dma_dev_name = "dw_dmac"; break; - case SST_DMA_TYPE_MID: - dma_dev_name = "Intel MID DMA"; - break; default: dev_err(sst->dev, "error: invalid DMA engine %d\n", sst->pdata->dma_engine); -- cgit v0.10.2 From 91b0d9aa933a2335f6f11983b19eaf9ebe3c2033 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 18 Mar 2015 10:07:20 +0200 Subject: ASoC: Intel: Remove vague commit about slave DMA config from firmware loader Intel MID DMA driver is going to be removed, commit should be a few lines down near to dmaengine_slave_config() call in order to not confuse and at quick look Synopsys DesignWare does seem to use some of the slave config structure fields (see drivers/dma/dw/core.c: dwc_config()). Signed-off-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c index 38881f1..b5659ec 100644 --- a/sound/soc/intel/sst-firmware.c +++ b/sound/soc/intel/sst-firmware.c @@ -221,8 +221,6 @@ int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) dma_cap_mask_t mask; int ret; - /* The Intel MID DMA engine driver needs the slave config set but - * Synopsis DMA engine driver safely ignores the slave config */ dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); dma_cap_set(DMA_MEMCPY, mask); -- cgit v0.10.2 From 5d5b275d727753372f0a390b4121738d073f3e94 Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Thu, 19 Mar 2015 08:38:00 +0800 Subject: Intel: ASoC: Add condition check before set param to waves Check waves state before set parameter through ipc to prevent unexpected operation. Also remove redundant check. Signed-off-by: Lu, Han Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index 43fb5f3..20b629a 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -2055,6 +2055,11 @@ int sst_hsw_launch_param_buf(struct sst_hsw *hsw) { int ret, idx; + if (!sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { + dev_dbg(hsw->dev, "module waves is not active\n"); + return 0; + } + /* put all param lines to DSP through ipc */ for (idx = 0; idx < hsw->param_idx_w; idx++) { ret = sst_hsw_module_set_param(hsw, diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index b40ec74..6c6229a 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -399,13 +399,9 @@ static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, if (ret < 0) return ret; - if (sst_hsw_is_module_loaded(hsw, id)) { - if (!sst_hsw_is_module_active(hsw, id)) - return 0; - + if (sst_hsw_is_module_active(hsw, id)) ret = sst_hsw_module_set_param(hsw, id, 0, param_id, param_size, ucontrol->value.bytes.data); - } return ret; } -- cgit v0.10.2 From bdc455b512c880292d7f145e73aed5e37a90f6e4 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Wed, 18 Mar 2015 21:31:38 +0530 Subject: ASoC: Intel: acpi_probe: fix error return path Fix the sst_acpi_probe memory allocation error path by setting right error code and initiating the cleanup insteadof just returning Reported-by: Dan Carpenter Signed-off-by: Vinod Koul Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst/sst_acpi.c b/sound/soc/intel/sst/sst_acpi.c index b336013..98c2444 100644 --- a/sound/soc/intel/sst/sst_acpi.c +++ b/sound/soc/intel/sst/sst_acpi.c @@ -309,7 +309,7 @@ int sst_acpi_probe(struct platform_device *pdev) ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64), GFP_KERNEL); if (!ctx->shim_regs64) { - return -ENOMEM; + ret = -ENOMEM; goto do_sst_cleanup; } -- cgit v0.10.2 From 4de60970abf9f49737f51ec6e7a1e3bcf6d36742 Mon Sep 17 00:00:00 2001 From: "qipeng.zha" Date: Wed, 11 Mar 2015 01:15:38 +0800 Subject: gpiolib: translate pin number in GPIO ACPI callbacks If GPIO driver use pin mapping, need to translate pin number between ACPI table and GPIO driver. This issue is found on one platform with Cherryview gpio controller, kernel is hang when executed _PS0 method of one ACPI device, since without this translation, it access invalid gpiodesc array. Verified it works again with this patch. Signed-off-by: qipeng.zha Acked-by: Mika Westerberg Signed-off-by: Linus Walleij diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index c0929d9..df990f2 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -201,6 +201,10 @@ static acpi_status acpi_gpiochip_request_interrupt(struct acpi_resource *ares, if (!handler) return AE_BAD_PARAMETER; + pin = acpi_gpiochip_pin_to_gpio_offset(chip, pin); + if (pin < 0) + return AE_BAD_PARAMETER; + desc = gpiochip_request_own_desc(chip, pin, "ACPI:Event"); if (IS_ERR(desc)) { dev_err(chip->dev, "Failed to request GPIO\n"); @@ -551,6 +555,12 @@ acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address, struct gpio_desc *desc; bool found; + pin = acpi_gpiochip_pin_to_gpio_offset(chip, pin); + if (pin < 0) { + status = AE_BAD_PARAMETER; + goto out; + } + mutex_lock(&achip->conn_lock); found = false; -- cgit v0.10.2 From 9c8928f5176766bec79f272bd47b7124e11cccbd Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Mon, 16 Mar 2015 09:08:07 +0200 Subject: iwlwifi: dvm: run INIT firmware again upon .start() The assumption before this patch was that we don't need to run again the INIT firmware after the system booted. The INIT firmware runs calibrations which impact the physical layer's behavior. Users reported that it may be helpful to run these calibrations again every time the interface is brought up. The penatly is minimal, since the calibrations run fast. This fixes: https://bugzilla.kernel.org/show_bug.cgi?id=94341 CC: Signed-off-by: Emmanuel Grumbach diff --git a/drivers/net/wireless/iwlwifi/dvm/dev.h b/drivers/net/wireless/iwlwifi/dvm/dev.h index a6f22c3..3811878 100644 --- a/drivers/net/wireless/iwlwifi/dvm/dev.h +++ b/drivers/net/wireless/iwlwifi/dvm/dev.h @@ -708,7 +708,6 @@ struct iwl_priv { unsigned long reload_jiffies; int reload_count; bool ucode_loaded; - bool init_ucode_run; /* Don't run init uCode again */ u8 plcp_delta_threshold; diff --git a/drivers/net/wireless/iwlwifi/dvm/ucode.c b/drivers/net/wireless/iwlwifi/dvm/ucode.c index 4dbef7e..5244e43 100644 --- a/drivers/net/wireless/iwlwifi/dvm/ucode.c +++ b/drivers/net/wireless/iwlwifi/dvm/ucode.c @@ -418,9 +418,6 @@ int iwl_run_init_ucode(struct iwl_priv *priv) if (!priv->fw->img[IWL_UCODE_INIT].sec[0].len) return 0; - if (priv->init_ucode_run) - return 0; - iwl_init_notification_wait(&priv->notif_wait, &calib_wait, calib_complete, ARRAY_SIZE(calib_complete), iwlagn_wait_calib, priv); @@ -440,8 +437,6 @@ int iwl_run_init_ucode(struct iwl_priv *priv) */ ret = iwl_wait_notification(&priv->notif_wait, &calib_wait, UCODE_CALIB_TIMEOUT); - if (!ret) - priv->init_ucode_run = true; goto out; -- cgit v0.10.2 From dcbc17ad7c7525ed04a45a94cfc5640d5cccf480 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 10 Mar 2015 21:24:34 +0100 Subject: iwlwifi: mvm: protect rate scaling against non-mvm IBSS stations When the driver callback returns that it's out of space for new stations, the mac80211 IBSS code still keeps the station so it doesn't try to add it over and over again. Since the rate scaling algorithm is separate in mac80211, it also invokes the rate scaling algorithm for such stations. It doesn't know that our rate scaling algorithm is tightly integrated with the MVM code and relies on those data structures, and it cannot as the abstraction doesn't allow for it. This leads to crashes when the rate scaling algorithm tries to use uninitialized data, notably the mvmsta->vif pointer. Protect against this in the rate scaling algorithm. We cannot get good rates with such peers anyway since the firmware cannot do anything with them. This should fix https://bugzilla.kernel.org/show_bug.cgi?id=93461 CC: Reported-by: Richard Taylor Signed-off-by: Johannes Berg Signed-off-by: Emmanuel Grumbach diff --git a/drivers/net/wireless/iwlwifi/mvm/rs.c b/drivers/net/wireless/iwlwifi/mvm/rs.c index efa9688..078f24c 100644 --- a/drivers/net/wireless/iwlwifi/mvm/rs.c +++ b/drivers/net/wireless/iwlwifi/mvm/rs.c @@ -1278,6 +1278,9 @@ static void rs_mac80211_tx_status(void *mvm_r, struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + if (!iwl_mvm_sta_from_mac80211(sta)->vif) + return; + if (!ieee80211_is_data(hdr->frame_control) || info->flags & IEEE80211_TX_CTL_NO_ACK) return; @@ -2511,6 +2514,14 @@ static void rs_get_rate(void *mvm_r, struct ieee80211_sta *sta, void *mvm_sta, struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct iwl_lq_sta *lq_sta = mvm_sta; + if (sta && !iwl_mvm_sta_from_mac80211(sta)->vif) { + /* if vif isn't initialized mvm doesn't know about + * this station, so don't do anything with the it + */ + sta = NULL; + mvm_sta = NULL; + } + /* TODO: handle rate_idx_mask and rate_idx_mcs_mask */ /* Treat uninitialized rate scaling data same as non-existing. */ @@ -2827,6 +2838,9 @@ static void rs_rate_update(void *mvm_r, (struct iwl_op_mode *)mvm_r; struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + if (!iwl_mvm_sta_from_mac80211(sta)->vif) + return; + /* Stop any ongoing aggregations as rs starts off assuming no agg */ for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) ieee80211_stop_tx_ba_session(sta, tid); @@ -3587,9 +3601,15 @@ static ssize_t iwl_dbgfs_ss_force_write(struct iwl_lq_sta *lq_sta, char *buf, MVM_DEBUGFS_READ_WRITE_FILE_OPS(ss_force, 32); -static void rs_add_debugfs(void *mvm, void *mvm_sta, struct dentry *dir) +static void rs_add_debugfs(void *mvm, void *priv_sta, struct dentry *dir) { - struct iwl_lq_sta *lq_sta = mvm_sta; + struct iwl_lq_sta *lq_sta = priv_sta; + struct iwl_mvm_sta *mvmsta; + + mvmsta = container_of(lq_sta, struct iwl_mvm_sta, lq_sta); + + if (!mvmsta->vif) + return; debugfs_create_file("rate_scale_table", S_IRUSR | S_IWUSR, dir, lq_sta, &rs_sta_dbgfs_scale_table_ops); -- cgit v0.10.2 From 132fee062655e90948e189c8c14ce21f115ebbda Mon Sep 17 00:00:00 2001 From: Oren Givon Date: Thu, 19 Mar 2015 12:46:06 +0200 Subject: iwlwifi: add new 3165 series PCI IDs Add new 3165 PCI IDs for new 1x1 cards. Signed-off-by: Oren Givon Signed-off-by: Emmanuel Grumbach diff --git a/drivers/net/wireless/iwlwifi/pcie/drv.c b/drivers/net/wireless/iwlwifi/pcie/drv.c index dbd6bcf..686dd30 100644 --- a/drivers/net/wireless/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/iwlwifi/pcie/drv.c @@ -368,10 +368,12 @@ static const struct pci_device_id iwl_hw_card_ids[] = { /* 3165 Series */ {IWL_PCI_DEVICE(0x3165, 0x4010, iwl3165_2ac_cfg)}, {IWL_PCI_DEVICE(0x3165, 0x4012, iwl3165_2ac_cfg)}, - {IWL_PCI_DEVICE(0x3165, 0x4110, iwl3165_2ac_cfg)}, - {IWL_PCI_DEVICE(0x3165, 0x4210, iwl3165_2ac_cfg)}, {IWL_PCI_DEVICE(0x3165, 0x4410, iwl3165_2ac_cfg)}, {IWL_PCI_DEVICE(0x3165, 0x4510, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4110, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4310, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4210, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x8010, iwl3165_2ac_cfg)}, /* 7265 Series */ {IWL_PCI_DEVICE(0x095A, 0x5010, iwl7265_2ac_cfg)}, -- cgit v0.10.2 From 1f16ea294d2ef63013740f4c8539e430b6c12dff Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 6 Mar 2015 09:17:37 +0100 Subject: iwlwifi: mvm: remove WARN_ON for invalid BA notification The firmware frequently manages to trigger this, and there's no known driver workaround, so stop warning. Signed-off-by: Johannes Berg Signed-off-by: Emmanuel Grumbach diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c index 07304e1..96a0540 100644 --- a/drivers/net/wireless/iwlwifi/mvm/tx.c +++ b/drivers/net/wireless/iwlwifi/mvm/tx.c @@ -949,8 +949,10 @@ int iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, mvmsta = iwl_mvm_sta_from_mac80211(sta); tid_data = &mvmsta->tid_data[tid]; - if (WARN_ONCE(tid_data->txq_id != scd_flow, "Q %d, tid %d, flow %d", - tid_data->txq_id, tid, scd_flow)) { + if (tid_data->txq_id != scd_flow) { + IWL_ERR(mvm, + "invalid BA notification: Q %d, tid %d, flow %d\n", + tid_data->txq_id, tid, scd_flow); rcu_read_unlock(); return 0; } -- cgit v0.10.2 From 5e71fc8629cefae5f3c1a4f498de3fe4f631924a Mon Sep 17 00:00:00 2001 From: Nathaniel W Filardo Date: Mon, 16 Mar 2015 11:19:55 -0400 Subject: USB: keyspan_pda: add new device id Add USB VID/PID for Xircom PGMFHUB USB/serial component. (The hub and SCSI bridge on that hardware are recognized out of the box by existing drivers.) Tested VID/PID using new_id and loopback connection and was met with success, but that's all the testing done. Signed-off-by: Nathaniel Wesley Filardo Cc: stable Signed-off-by: Johan Hovold diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index dd97d8b..4f7e072 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -61,6 +61,7 @@ struct keyspan_pda_private { /* For Xircom PGSDB9 and older Entrega version of the same device */ #define XIRCOM_VENDOR_ID 0x085a #define XIRCOM_FAKE_ID 0x8027 +#define XIRCOM_FAKE_ID_2 0x8025 /* "PGMFHUB" serial */ #define ENTREGA_VENDOR_ID 0x1645 #define ENTREGA_FAKE_ID 0x8093 @@ -70,6 +71,7 @@ static const struct usb_device_id id_table_combined[] = { #endif #ifdef XIRCOM { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) }, { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) }, #endif { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_ID) }, @@ -93,6 +95,7 @@ static const struct usb_device_id id_table_fake[] = { #ifdef XIRCOM static const struct usb_device_id id_table_fake_xircom[] = { { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) }, { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) }, { } }; -- cgit v0.10.2 From 9cd974bb0f7abc83da1788a17e28890819b593b8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 20 Mar 2015 12:35:45 +0100 Subject: ASoC: max98925: Constify regmap config and other codec data Constify local structures (snd_soc_dai_ops, snd_soc_codec_driver, regmap_config) and array (reg_defaults) which are not modified by the driver and passed to core as pointer to const. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c index 1f8e803..0796dfa 100644 --- a/sound/soc/codecs/max98925.c +++ b/sound/soc/codecs/max98925.c @@ -34,7 +34,7 @@ static const char *const hpf_text[] = { "Disable", "DC Block", "100Hz", "200Hz", "400Hz", "800Hz", }; -static struct reg_default max98925_reg[] = { +static const struct reg_default max98925_reg[] = { { 0x0B, 0x00 }, /* IRQ Enable0 */ { 0x0C, 0x00 }, /* IRQ Enable1 */ { 0x0D, 0x00 }, /* IRQ Enable2 */ @@ -491,7 +491,7 @@ static int max98925_dai_set_sysclk(struct snd_soc_dai *dai, #define MAX98925_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) -static struct snd_soc_dai_ops max98925_dai_ops = { +static const struct snd_soc_dai_ops max98925_dai_ops = { .set_sysclk = max98925_dai_set_sysclk, .set_fmt = max98925_dai_set_fmt, .hw_params = max98925_dai_hw_params, @@ -541,7 +541,7 @@ static int max98925_probe(struct snd_soc_codec *codec) return 0; } -static struct snd_soc_codec_driver soc_codec_dev_max98925 = { +static const struct snd_soc_codec_driver soc_codec_dev_max98925 = { .probe = max98925_probe, .controls = max98925_snd_controls, .num_controls = ARRAY_SIZE(max98925_snd_controls), @@ -551,7 +551,7 @@ static struct snd_soc_codec_driver soc_codec_dev_max98925 = { .num_dapm_widgets = ARRAY_SIZE(max98925_dapm_widgets), }; -static struct regmap_config max98925_regmap = { +static const struct regmap_config max98925_regmap = { .reg_bits = 8, .val_bits = 8, .max_register = MAX98925_REV_VERSION, -- cgit v0.10.2 From 715a03d2848275269bd4014a49212cc83452f32d Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Fri, 20 Mar 2015 15:56:40 +0800 Subject: NFSD: restore trace event lost in mismerge 31ef83dc05 "nfsd: add trace events" had a typo that dropped a trace event and replaced it by an incorrect recursive call to nfsd4_cb_layout_fail. 133d558216d9 "Subject: nfsd: don't recursively call nfsd4_cb_layout_fail" fixed the crash, this restores the tracepoint. Reviewed-by: Christoph Hellwig Signed-off-by: Kinglong Mee Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index 1028a06..0a616b5 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -587,6 +587,8 @@ nfsd4_cb_layout_fail(struct nfs4_layout_stateid *ls) rpc_ntop((struct sockaddr *)&clp->cl_addr, addr_str, sizeof(addr_str)); + trace_layout_recall_fail(&ls->ls_stid.sc_stateid); + printk(KERN_WARNING "nfsd: client %s failed to respond to layout recall. " " Fencing..\n", addr_str); -- cgit v0.10.2 From 6f8f28ec5f88715515e70dd52f16b326a5e63f81 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Thu, 19 Mar 2015 19:04:14 +0800 Subject: NFSD: Check layout type when returning client layouts According to RFC5661: " When lr_returntype is LAYOUTRETURN4_FSID, the current filehandle is used to identify the file system and all layouts matching the client ID, the fsid of the file system, lora_layout_type, and lora_iomode are returned. When lr_returntype is LAYOUTRETURN4_ALL, all layouts matching the client ID, lora_layout_type, and lora_iomode are returned and the current filehandle is not used. " When returning client layouts, always check layout type. Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index 0a616b5..6964613 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -513,6 +513,9 @@ nfsd4_return_client_layouts(struct svc_rqst *rqstp, spin_lock(&clp->cl_lock); list_for_each_entry_safe(ls, n, &clp->cl_lo_states, ls_perclnt) { + if (ls->ls_layout_type != lrp->lr_layout_type) + continue; + if (lrp->lr_return_type == RETURN_FSID && !fh_fsid_match(&ls->ls_stid.sc_file->fi_fhandle, &cstate->current_fh.fh_handle)) -- cgit v0.10.2 From db59c0ef08ca56758dcee6495d9d6a086682c5ec Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Thu, 19 Mar 2015 19:04:41 +0800 Subject: NFSD: Take care the return value from nfsd4_decode_stateid Return status after nfsd4_decode_stateid failed. Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index df5e66c..c76654c 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -1562,7 +1562,11 @@ nfsd4_decode_layoutget(struct nfsd4_compoundargs *argp, p = xdr_decode_hyper(p, &lgp->lg_seg.offset); p = xdr_decode_hyper(p, &lgp->lg_seg.length); p = xdr_decode_hyper(p, &lgp->lg_minlength); - nfsd4_decode_stateid(argp, &lgp->lg_sid); + + status = nfsd4_decode_stateid(argp, &lgp->lg_sid); + if (status) + return status; + READ_BUF(4); lgp->lg_maxcount = be32_to_cpup(p++); @@ -1580,7 +1584,11 @@ nfsd4_decode_layoutcommit(struct nfsd4_compoundargs *argp, p = xdr_decode_hyper(p, &lcp->lc_seg.offset); p = xdr_decode_hyper(p, &lcp->lc_seg.length); lcp->lc_reclaim = be32_to_cpup(p++); - nfsd4_decode_stateid(argp, &lcp->lc_sid); + + status = nfsd4_decode_stateid(argp, &lcp->lc_sid); + if (status) + return status; + READ_BUF(4); lcp->lc_newoffset = be32_to_cpup(p++); if (lcp->lc_newoffset) { @@ -1628,7 +1636,11 @@ nfsd4_decode_layoutreturn(struct nfsd4_compoundargs *argp, READ_BUF(16); p = xdr_decode_hyper(p, &lrp->lr_seg.offset); p = xdr_decode_hyper(p, &lrp->lr_seg.length); - nfsd4_decode_stateid(argp, &lrp->lr_sid); + + status = nfsd4_decode_stateid(argp, &lrp->lr_sid); + if (status) + return status; + READ_BUF(4); lrp->lrf_body_len = be32_to_cpup(p++); if (lrp->lrf_body_len > 0) { -- cgit v0.10.2 From a68465c9cb435feadb0efe6acbd0a9a7d1038465 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Thu, 19 Mar 2015 19:48:31 +0800 Subject: NFSD: Error out when register_shrinker() fail If register_shrinker() failed, nfsd will cause a NULL pointer access as, [ 9250.875465] nfsd: last server has exited, flushing export cache [ 9251.427270] BUG: unable to handle kernel NULL pointer dereference at (null) [ 9251.427393] IP: [] __list_del_entry+0x29/0xd0 [ 9251.427579] PGD 13e4d067 PUD 13e4c067 PMD 0 [ 9251.427633] Oops: 0000 [#1] SMP DEBUG_PAGEALLOC [ 9251.427706] Modules linked in: ip6t_rpfilter ip6t_REJECT bnep bluetooth xt_conntrack cfg80211 rfkill ebtable_nat ebtable_broute bridge stp llc ebtable_filter ebtables ip6table_nat nf_conntrack_ipv6 nf_defrag_ipv6 nf_nat_ipv6 ip6table_mangle ip6table_security ip6table_raw ip6table_filter ip6_tables iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack iptable_mangle iptable_security iptable_raw btrfs xfs microcode ppdev serio_raw pcspkr xor libcrc32c raid6_pq e1000 parport_pc parport i2c_piix4 i2c_core nfsd(OE-) auth_rpcgss nfs_acl lockd sunrpc(E) ata_generic pata_acpi [ 9251.428240] CPU: 0 PID: 1557 Comm: rmmod Tainted: G OE 3.16.0-rc2+ #22 [ 9251.428366] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/31/2013 [ 9251.428496] task: ffff880000849540 ti: ffff8800136f4000 task.ti: ffff8800136f4000 [ 9251.428593] RIP: 0010:[] [] __list_del_entry+0x29/0xd0 [ 9251.428696] RSP: 0018:ffff8800136f7ea0 EFLAGS: 00010207 [ 9251.428751] RAX: 0000000000000000 RBX: ffffffffa0116d48 RCX: dead000000200200 [ 9251.428814] RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffffffa0116d48 [ 9251.428876] RBP: ffff8800136f7ea0 R08: ffff8800136f4000 R09: 0000000000000001 [ 9251.428939] R10: 8080808080808080 R11: 0000000000000000 R12: ffffffffa011a5a0 [ 9251.429002] R13: 0000000000000800 R14: 0000000000000000 R15: 00000000018ac090 [ 9251.429064] FS: 00007fb9acef0740(0000) GS:ffff88003fa00000(0000) knlGS:0000000000000000 [ 9251.429164] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 9251.429221] CR2: 0000000000000000 CR3: 0000000031a17000 CR4: 00000000001407f0 [ 9251.429306] Stack: [ 9251.429410] ffff8800136f7eb8 ffffffff8136fcdd ffffffffa0116d20 ffff8800136f7ed0 [ 9251.429511] ffffffff8118a0f2 0000000000000000 ffff8800136f7ee0 ffffffffa00eb765 [ 9251.429610] ffff8800136f7ef0 ffffffffa010e93c ffff8800136f7f78 ffffffff81104ac2 [ 9251.429709] Call Trace: [ 9251.429755] [] list_del+0xd/0x30 [ 9251.429896] [] unregister_shrinker+0x22/0x40 [ 9251.430037] [] nfsd_reply_cache_shutdown+0x15/0x90 [nfsd] [ 9251.430106] [] exit_nfsd+0x9/0x6cd [nfsd] [ 9251.430192] [] SyS_delete_module+0x162/0x200 [ 9251.430280] [] ? do_notify_resume+0x59/0x90 [ 9251.430395] [] system_call_fastpath+0x16/0x1b [ 9251.430457] Code: 00 00 55 48 8b 17 48 b9 00 01 10 00 00 00 ad de 48 8b 47 08 48 89 e5 48 39 ca 74 29 48 b9 00 02 20 00 00 00 ad de 48 39 c8 74 7a <4c> 8b 00 4c 39 c7 75 53 4c 8b 42 08 4c 39 c7 75 2b 48 89 42 08 [ 9251.430691] RIP [] __list_del_entry+0x29/0xd0 [ 9251.430755] RSP [ 9251.430805] CR2: 0000000000000000 [ 9251.431033] ---[ end trace 080f3050d082b4ea ]--- Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfscache.c b/fs/nfsd/nfscache.c index 83a9694..46ec934 100644 --- a/fs/nfsd/nfscache.c +++ b/fs/nfsd/nfscache.c @@ -165,13 +165,17 @@ int nfsd_reply_cache_init(void) { unsigned int hashsize; unsigned int i; + int status = 0; max_drc_entries = nfsd_cache_size_limit(); atomic_set(&num_drc_entries, 0); hashsize = nfsd_hashsize(max_drc_entries); maskbits = ilog2(hashsize); - register_shrinker(&nfsd_reply_cache_shrinker); + status = register_shrinker(&nfsd_reply_cache_shrinker); + if (status) + return status; + drc_slab = kmem_cache_create("nfsd_drc", sizeof(struct svc_cacherep), 0, 0, NULL); if (!drc_slab) -- cgit v0.10.2 From a736775db683174269c65c7c5cc8e5ee534e7681 Mon Sep 17 00:00:00 2001 From: Charlie Mooney Date: Fri, 20 Mar 2015 09:40:17 -0700 Subject: Input: add MT_TOOL_PALM Currently there are only two "tools" that can be specified by a multi-touch driver: MT_TOOL_FINGER and MT_TOOL_PEN. In working with Elan (The touch vendor) and discussing their next-gen devices it seems that it will be useful to have more tools so that their devices can give the upper layers of the stack hints as to what is touching the sensor. In particular they have new experimental firmware that can better differentiate between palms vs fingertips and would like to plumb a patch so that we can use their hints in higher-level gesture soft- ware. The firmware on the device can reasonably do a better job of palm detection because it has access to all of the raw sensor readings as opposed to just the width/pressure/etc that are exposed by the driver. As such, the firmware can characterize what a palm looks like in much finer-grained detail and this change would allow such a device to share its findings with the kernel. Signed-off-by: Charlie Mooney Acked-by: Peter Hutterer Signed-off-by: Dmitry Torokhov diff --git a/Documentation/input/multi-touch-protocol.txt b/Documentation/input/multi-touch-protocol.txt index 7b4f59c..b85d000 100644 --- a/Documentation/input/multi-touch-protocol.txt +++ b/Documentation/input/multi-touch-protocol.txt @@ -312,9 +312,12 @@ ABS_MT_TOOL_TYPE The type of approaching tool. A lot of kernel drivers cannot distinguish between different tool types, such as a finger or a pen. In such cases, the -event should be omitted. The protocol currently supports MT_TOOL_FINGER and -MT_TOOL_PEN [2]. For type B devices, this event is handled by input core; -drivers should instead use input_mt_report_slot_state(). +event should be omitted. The protocol currently supports MT_TOOL_FINGER, +MT_TOOL_PEN, and MT_TOOL_PALM [2]. For type B devices, this event is handled +by input core; drivers should instead use input_mt_report_slot_state(). +A contact's ABS_MT_TOOL_TYPE may change over time while still touching the +device, because the firmware may not be able to determine which tool is being +used when it first appears. ABS_MT_BLOB_ID diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index a1d7e93..2320b0c 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -972,7 +972,8 @@ struct input_keymap_entry { */ #define MT_TOOL_FINGER 0 #define MT_TOOL_PEN 1 -#define MT_TOOL_MAX 1 +#define MT_TOOL_PALM 2 +#define MT_TOOL_MAX 2 /* * Values describing the status of a force-feedback effect -- cgit v0.10.2 From a1420384e3aa83b4ac8af85ae92d84f320272756 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Sun, 15 Mar 2015 23:12:15 +0800 Subject: NFSD: Put exports after nfsd4_layout_verify fail Fix commit 9cf514ccfa (nfsd: implement pNFS operations). Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index d30bea8..92b9d97 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1237,8 +1237,8 @@ nfsd4_getdeviceinfo(struct svc_rqst *rqstp, nfserr = ops->proc_getdeviceinfo(exp->ex_path.mnt->mnt_sb, gdp); gdp->gd_notify_types &= ops->notify_types; - exp_put(exp); out: + exp_put(exp); return nfserr; } -- cgit v0.10.2 From f93a25b38cbd840f26c9fd2dd8a6611a57b259b7 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 20 Mar 2015 22:18:17 +0100 Subject: brcmfmac: disable MBSS feature for BCM43362 The BCM43362 firmware falsely reports it is capable of providing MBSS. As a result AP mode no longer works for this device. Therefor disable MBSS in the driver for this chipset. Cc: stable@vger.kernel.org # 3.19.y Reported-by: Jorg Krause Reviewed-by: Hante Meuleman Reviewed-by: Pieter-Paul Giesberts Signed-off-by: Arend van Spriel Signed-off-by: Kalle Valo diff --git a/drivers/net/wireless/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/brcm80211/brcmfmac/feature.c index defb7a4..7748a1c 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/feature.c @@ -126,7 +126,8 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_MCHAN, "mchan"); if (drvr->bus_if->wowl_supported) brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_WOWL, "wowl"); - brcmf_feat_iovar_int_set(ifp, BRCMF_FEAT_MBSS, "mbss", 0); + if (drvr->bus_if->chip != BRCM_CC_43362_CHIP_ID) + brcmf_feat_iovar_int_set(ifp, BRCMF_FEAT_MBSS, "mbss", 0); /* set chip related quirks */ switch (drvr->bus_if->chip) { -- cgit v0.10.2 From 2477bc58d49edb1c0baf59df7dc093dce682af2b Mon Sep 17 00:00:00 2001 From: Sachin Prabhu Date: Wed, 4 Feb 2015 13:10:26 +0000 Subject: cifs: smb2_clone_range() - exit on unhandled error While attempting to clone a file on a samba server, we receive a STATUS_INVALID_DEVICE_REQUEST. This is mapped to -EOPNOTSUPP which isn't handled in smb2_clone_range(). We end up looping in the while loop making same call to the samba server over and over again. The proposed fix is to exit and return the error value when encountered with an unhandled error. Cc: Signed-off-by: Sachin Prabhu Signed-off-by: Steve French Signed-off-by: Steve French diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 96b5d40..eab05e1 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -684,7 +684,8 @@ smb2_clone_range(const unsigned int xid, /* No need to change MaxChunks since already set to 1 */ chunk_sizes_updated = true; - } + } else + goto cchunk_out; } cchunk_out: -- cgit v0.10.2 From e1e9bda22d7ddf88515e8fe401887e313922823e Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Fri, 13 Mar 2015 14:20:29 +0100 Subject: cifs: fix use-after-free bug in find_writable_file Under intermittent network outages, find_writable_file() is susceptible to the following race condition, which results in a user-after-free in the cifs_writepages code-path: Thread 1 Thread 2 ======== ======== inv_file = NULL refind = 0 spin_lock(&cifs_file_list_lock) // invalidHandle found on openFileList inv_file = open_file // inv_file->count currently 1 cifsFileInfo_get(inv_file) // inv_file->count = 2 spin_unlock(&cifs_file_list_lock); cifs_reopen_file() cifs_close() // fails (rc != 0) ->cifsFileInfo_put() spin_lock(&cifs_file_list_lock) // inv_file->count = 1 spin_unlock(&cifs_file_list_lock) spin_lock(&cifs_file_list_lock); list_move_tail(&inv_file->flist, &cifs_inode->openFileList); spin_unlock(&cifs_file_list_lock); cifsFileInfo_put(inv_file); ->spin_lock(&cifs_file_list_lock) // inv_file->count = 0 list_del(&cifs_file->flist); // cleanup!! kfree(cifs_file); spin_unlock(&cifs_file_list_lock); spin_lock(&cifs_file_list_lock); ++refind; // refind = 1 goto refind_writable; At this point we loop back through with an invalid inv_file pointer and a refind value of 1. On second pass, inv_file is not overwritten on openFileList traversal, and is subsequently dereferenced. Signed-off-by: David Disseldorp Reviewed-by: Jeff Layton CC: Signed-off-by: Steve French diff --git a/fs/cifs/file.c b/fs/cifs/file.c index a94b3e6..ca30c39 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -1823,6 +1823,7 @@ refind_writable: cifsFileInfo_put(inv_file); spin_lock(&cifs_file_list_lock); ++refind; + inv_file = NULL; goto refind_writable; } } -- cgit v0.10.2 From 2bd50fb3d4d31f5168ecea221f291534cd0a96e9 Mon Sep 17 00:00:00 2001 From: Taesoo Kim Date: Sat, 21 Mar 2015 19:08:30 -0400 Subject: cifs: potential memory leaks when parsing mnt opts For example, when mount opt is redundently specified (e.g., "user=A,user=B,user=C"), kernel kept allocating new key/val with kstrdup() and overwrite previous ptr (to be freed). Althouhg mount.cifs in userspace performs a bit of sanitization (e.g., forcing one user option), current implementation is not robust. Other options such as iocharset and domainanme are similarly vulnerable. Signed-off-by: Taesoo Kim Signed-off-by: Steve French diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index d3aa999..4cb8450 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -1599,6 +1599,8 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, pr_warn("CIFS: username too long\n"); goto cifs_parse_mount_err; } + + kfree(vol->username); vol->username = kstrdup(string, GFP_KERNEL); if (!vol->username) goto cifs_parse_mount_err; @@ -1700,6 +1702,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, goto cifs_parse_mount_err; } + kfree(vol->domainname); vol->domainname = kstrdup(string, GFP_KERNEL); if (!vol->domainname) { pr_warn("CIFS: no memory for domainname\n"); @@ -1731,6 +1734,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, } if (strncasecmp(string, "default", 7) != 0) { + kfree(vol->iocharset); vol->iocharset = kstrdup(string, GFP_KERNEL); if (!vol->iocharset) { -- cgit v0.10.2 From 3088ef9f5dec3d72b24dcdd33d240823ab26b432 Mon Sep 17 00:00:00 2001 From: Howard Mitchell Date: Thu, 19 Mar 2015 12:08:29 +0000 Subject: ALSA: ControlName.txt: Add 'Analogue' as a prefix for volume controls Currently 'Playback Volume' is the correct way to express an analogue volume control. However, this control name has initialisation defaults applied when using 'alsactl restore' and in some cases this is not appropriate. An example would be a control that has a selection of 0db and -6dB of gain that is intended to set the fullscale ouput voltage of a DAC. The TI pcm512x family of DAcs have such a control. In this case the device/driver reset defaults are preferred. Signed-off-by: Howard Mitchell Acked-by: Takashi Iwai Signed-off-by: Mark Brown diff --git a/Documentation/sound/alsa/ControlNames.txt b/Documentation/sound/alsa/ControlNames.txt index 79a6127..3fc1cf50 100644 --- a/Documentation/sound/alsa/ControlNames.txt +++ b/Documentation/sound/alsa/ControlNames.txt @@ -71,11 +71,11 @@ SOURCE: HDMI/DP (either HDMI or DisplayPort) Exceptions (deprecated): - [Digital] Capture Source - [Digital] Capture Switch (aka input gain switch) - [Digital] Capture Volume (aka input gain volume) - [Digital] Playback Switch (aka output gain switch) - [Digital] Playback Volume (aka output gain volume) + [Analogue|Digital] Capture Source + [Analogue|Digital] Capture Switch (aka input gain switch) + [Analogue|Digital] Capture Volume (aka input gain volume) + [Analogue|Digital] Playback Switch (aka output gain switch) + [Analogue|Digital] Playback Volume (aka output gain volume) Tone Control - Switch Tone Control - Bass Tone Control - Treble -- cgit v0.10.2 From 4d9b13c7cc803fbde59d7e998f7de2b9a2101c7e Mon Sep 17 00:00:00 2001 From: Howard Mitchell Date: Thu, 19 Mar 2015 12:08:30 +0000 Subject: ASoC: pcm512x: Add 'Analogue' prefix to analogue volume controls This is to ensure that 'alsactl restore' does not apply default initialisation as the chip reset defaults are preferred. Signed-off-by: Howard Mitchell Signed-off-by: Mark Brown Cc: stable@vger.kernel.org diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 9974f20..194f4c8 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -304,9 +304,9 @@ static const struct soc_enum pcm512x_veds = static const struct snd_kcontrol_new pcm512x_controls[] = { SOC_DOUBLE_R_TLV("Digital Playback Volume", PCM512x_DIGITAL_VOLUME_2, PCM512x_DIGITAL_VOLUME_3, 0, 255, 1, digital_tlv), -SOC_DOUBLE_TLV("Playback Volume", PCM512x_ANALOG_GAIN_CTRL, +SOC_DOUBLE_TLV("Analogue Playback Volume", PCM512x_ANALOG_GAIN_CTRL, PCM512x_LAGN_SHIFT, PCM512x_RAGN_SHIFT, 1, 1, analog_tlv), -SOC_DOUBLE_TLV("Playback Boost Volume", PCM512x_ANALOG_GAIN_BOOST, +SOC_DOUBLE_TLV("Analogue Playback Boost Volume", PCM512x_ANALOG_GAIN_BOOST, PCM512x_AGBL_SHIFT, PCM512x_AGBR_SHIFT, 1, 0, boost_tlv), SOC_DOUBLE("Digital Playback Switch", PCM512x_MUTE, PCM512x_RQML_SHIFT, PCM512x_RQMR_SHIFT, 1, 1), -- cgit v0.10.2 From be0b5e635883678bfbc695889772fed545f3427d Mon Sep 17 00:00:00 2001 From: Larry Finger Date: Sat, 21 Mar 2015 15:16:05 -0500 Subject: rtlwifi: Fix IOMMU mapping leak in AP mode Transmission of an AP beacon does not call the TX interrupt service routine, which usually does the cleanup. Instead, cleanup is handled in a tasklet completion routine. Unfortunately, this routine has a serious bug in that it does not release the DMA mapping before it frees the skb, thus one IOMMU mapping is leaked for each beacon. The test system failed with no free IOMMU mapping slots approximately one hour after hostapd was used to start an AP. This issue was reported and tested at https://github.com/lwfinger/rtlwifi_new/issues/30. Reported-and-tested-by: Kevin Mullican Cc: Kevin Mullican Signed-off-by: Shao Fu Signed-off-by: Larry Finger Cc: Stable [3.18+] Signed-off-by: Kalle Valo diff --git a/drivers/net/wireless/rtlwifi/pci.c b/drivers/net/wireless/rtlwifi/pci.c index a62170e..8c45cf4 100644 --- a/drivers/net/wireless/rtlwifi/pci.c +++ b/drivers/net/wireless/rtlwifi/pci.c @@ -1124,12 +1124,22 @@ static void _rtl_pci_prepare_bcn_tasklet(struct ieee80211_hw *hw) /*This is for new trx flow*/ struct rtl_tx_buffer_desc *pbuffer_desc = NULL; u8 temp_one = 1; + u8 *entry; memset(&tcb_desc, 0, sizeof(struct rtl_tcb_desc)); ring = &rtlpci->tx_ring[BEACON_QUEUE]; pskb = __skb_dequeue(&ring->queue); - if (pskb) + if (rtlpriv->use_new_trx_flow) + entry = (u8 *)(&ring->buffer_desc[ring->idx]); + else + entry = (u8 *)(&ring->desc[ring->idx]); + if (pskb) { + pci_unmap_single(rtlpci->pdev, + rtlpriv->cfg->ops->get_desc( + (u8 *)entry, true, HW_DESC_TXBUFF_ADDR), + pskb->len, PCI_DMA_TODEVICE); kfree_skb(pskb); + } /*NB: the beacon data buffer must be 32-bit aligned. */ pskb = ieee80211_beacon_get(hw, mac->vif); -- cgit v0.10.2 From 082d70b6142f623c10f7a032d9b63a2399fb4ded Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Sun, 15 Mar 2015 11:03:38 -0400 Subject: can: kvaser_usb: Comply with firmware max tx URBs value Current driver code arbitrarily assumes a max outstanding tx value of 16 parallel transmissions. Meanwhile, the device firmware provides its actual maximum inside its reply to the CMD_GET_SOFTWARE_INFO message. Under heavy tx traffic, if the interleaved transmissions count increases above the limit reported by firmware, the firmware breaks up badly, reports a massive list of internal errors, and the candump traces hardly matches the actual frames sent and received. On the other hand, in certain models, the firmware can support up to 48 tx URBs instead of just 16, increasing the driver throughput by two-fold and reducing the possibility of -ENOBUFs. Thus dynamically set the driver's max tx URBs value according to firmware replies. Signed-off-by: Ahmed S. Darwish Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/usb/kvaser_usb.c b/drivers/net/can/usb/kvaser_usb.c index e97a08c..2f9733a 100644 --- a/drivers/net/can/usb/kvaser_usb.c +++ b/drivers/net/can/usb/kvaser_usb.c @@ -25,7 +25,6 @@ #include #include -#define MAX_TX_URBS 16 #define MAX_RX_URBS 4 #define START_TIMEOUT 1000 /* msecs */ #define STOP_TIMEOUT 1000 /* msecs */ @@ -443,6 +442,7 @@ struct kvaser_usb_error_summary { }; }; +/* Context for an outstanding, not yet ACKed, transmission */ struct kvaser_usb_tx_urb_context { struct kvaser_usb_net_priv *priv; u32 echo_index; @@ -456,8 +456,13 @@ struct kvaser_usb { struct usb_endpoint_descriptor *bulk_in, *bulk_out; struct usb_anchor rx_submitted; + /* @max_tx_urbs: Firmware-reported maximum number of oustanding, + * not yet ACKed, transmissions on this device. This value is + * also used as a sentinel for marking free tx contexts. + */ u32 fw_version; unsigned int nchannels; + unsigned int max_tx_urbs; enum kvaser_usb_family family; bool rxinitdone; @@ -467,19 +472,18 @@ struct kvaser_usb { struct kvaser_usb_net_priv { struct can_priv can; - - spinlock_t tx_contexts_lock; - int active_tx_contexts; - struct kvaser_usb_tx_urb_context tx_contexts[MAX_TX_URBS]; - - struct usb_anchor tx_submitted; - struct completion start_comp, stop_comp; + struct can_berr_counter bec; struct kvaser_usb *dev; struct net_device *netdev; int channel; - struct can_berr_counter bec; + struct completion start_comp, stop_comp; + struct usb_anchor tx_submitted; + + spinlock_t tx_contexts_lock; + int active_tx_contexts; + struct kvaser_usb_tx_urb_context tx_contexts[]; }; static const struct usb_device_id kvaser_usb_table[] = { @@ -657,9 +661,13 @@ static int kvaser_usb_get_software_info(struct kvaser_usb *dev) switch (dev->family) { case KVASER_LEAF: dev->fw_version = le32_to_cpu(msg.u.leaf.softinfo.fw_version); + dev->max_tx_urbs = + le16_to_cpu(msg.u.leaf.softinfo.max_outstanding_tx); break; case KVASER_USBCAN: dev->fw_version = le32_to_cpu(msg.u.usbcan.softinfo.fw_version); + dev->max_tx_urbs = + le16_to_cpu(msg.u.usbcan.softinfo.max_outstanding_tx); break; } @@ -715,7 +723,7 @@ static void kvaser_usb_tx_acknowledge(const struct kvaser_usb *dev, stats = &priv->netdev->stats; - context = &priv->tx_contexts[tid % MAX_TX_URBS]; + context = &priv->tx_contexts[tid % dev->max_tx_urbs]; /* Sometimes the state change doesn't come after a bus-off event */ if (priv->can.restart_ms && @@ -744,7 +752,7 @@ static void kvaser_usb_tx_acknowledge(const struct kvaser_usb *dev, spin_lock_irqsave(&priv->tx_contexts_lock, flags); can_get_echo_skb(priv->netdev, context->echo_index); - context->echo_index = MAX_TX_URBS; + context->echo_index = dev->max_tx_urbs; --priv->active_tx_contexts; netif_wake_queue(priv->netdev); @@ -1512,11 +1520,13 @@ error: static void kvaser_usb_reset_tx_urb_contexts(struct kvaser_usb_net_priv *priv) { - int i; + int i, max_tx_urbs; + + max_tx_urbs = priv->dev->max_tx_urbs; priv->active_tx_contexts = 0; - for (i = 0; i < MAX_TX_URBS; i++) - priv->tx_contexts[i].echo_index = MAX_TX_URBS; + for (i = 0; i < max_tx_urbs; i++) + priv->tx_contexts[i].echo_index = max_tx_urbs; } /* This method might sleep. Do not call it in the atomic context @@ -1702,14 +1712,14 @@ static netdev_tx_t kvaser_usb_start_xmit(struct sk_buff *skb, *msg_tx_can_flags |= MSG_FLAG_REMOTE_FRAME; spin_lock_irqsave(&priv->tx_contexts_lock, flags); - for (i = 0; i < ARRAY_SIZE(priv->tx_contexts); i++) { - if (priv->tx_contexts[i].echo_index == MAX_TX_URBS) { + for (i = 0; i < dev->max_tx_urbs; i++) { + if (priv->tx_contexts[i].echo_index == dev->max_tx_urbs) { context = &priv->tx_contexts[i]; context->echo_index = i; can_put_echo_skb(skb, netdev, context->echo_index); ++priv->active_tx_contexts; - if (priv->active_tx_contexts >= MAX_TX_URBS) + if (priv->active_tx_contexts >= dev->max_tx_urbs) netif_stop_queue(netdev); break; @@ -1743,7 +1753,7 @@ static netdev_tx_t kvaser_usb_start_xmit(struct sk_buff *skb, spin_lock_irqsave(&priv->tx_contexts_lock, flags); can_free_echo_skb(netdev, context->echo_index); - context->echo_index = MAX_TX_URBS; + context->echo_index = dev->max_tx_urbs; --priv->active_tx_contexts; netif_wake_queue(netdev); @@ -1881,7 +1891,9 @@ static int kvaser_usb_init_one(struct usb_interface *intf, if (err) return err; - netdev = alloc_candev(sizeof(*priv), MAX_TX_URBS); + netdev = alloc_candev(sizeof(*priv) + + dev->max_tx_urbs * sizeof(*priv->tx_contexts), + dev->max_tx_urbs); if (!netdev) { dev_err(&intf->dev, "Cannot alloc candev\n"); return -ENOMEM; @@ -2009,6 +2021,13 @@ static int kvaser_usb_probe(struct usb_interface *intf, return err; } + dev_dbg(&intf->dev, "Firmware version: %d.%d.%d\n", + ((dev->fw_version >> 24) & 0xff), + ((dev->fw_version >> 16) & 0xff), + (dev->fw_version & 0xffff)); + + dev_dbg(&intf->dev, "Max oustanding tx = %d URBs\n", dev->max_tx_urbs); + err = kvaser_usb_get_card_info(dev); if (err) { dev_err(&intf->dev, @@ -2016,11 +2035,6 @@ static int kvaser_usb_probe(struct usb_interface *intf, return err; } - dev_dbg(&intf->dev, "Firmware version: %d.%d.%d\n", - ((dev->fw_version >> 24) & 0xff), - ((dev->fw_version >> 16) & 0xff), - (dev->fw_version & 0xffff)); - for (i = 0; i < dev->nchannels; i++) { err = kvaser_usb_init_one(intf, id, i); if (err) { -- cgit v0.10.2 From 7e184c28636dce594426cd7d8781d52d1dd447cd Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Sun, 15 Mar 2015 11:10:49 -0400 Subject: can: kvaser_usb: Fix sparse warning __le16 degrades to integer USB endpoint's wMaxPacketSize field is an le16 entity. Use appropriate le16_to_cpu macros to maintain endian independence. Reported-by: Marc Kleine-Budde Signed-off-by: Ahmed S. Darwish Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/usb/kvaser_usb.c b/drivers/net/can/usb/kvaser_usb.c index 2f9733a..57611fd 100644 --- a/drivers/net/can/usb/kvaser_usb.c +++ b/drivers/net/can/usb/kvaser_usb.c @@ -596,8 +596,8 @@ static int kvaser_usb_wait_msg(const struct kvaser_usb *dev, u8 id, * for further details. */ if (tmp->len == 0) { - pos = round_up(pos, - dev->bulk_in->wMaxPacketSize); + pos = round_up(pos, le16_to_cpu(dev->bulk_in-> + wMaxPacketSize)); continue; } @@ -1337,7 +1337,8 @@ static void kvaser_usb_read_bulk_callback(struct urb *urb) * number of events in case of a heavy rx load on the bus. */ if (msg->len == 0) { - pos = round_up(pos, dev->bulk_in->wMaxPacketSize); + pos = round_up(pos, le16_to_cpu(dev->bulk_in-> + wMaxPacketSize)); continue; } -- cgit v0.10.2 From 37920a74abd6fdf0696730ba80a07843a62c9c54 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 16 Mar 2015 22:42:24 +0000 Subject: can: gs_usb: check for kzalloc allocation failure smatch detected the following issue: drivers/net/can/usb/gs_usb.c:904 gs_usb_probe() error: potential null dereference 'dev'. (kzalloc returns null) Add a check for null return from kzalloc and return -ENOMEM Signed-off-by: Colin Ian King Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index 009acc8..8b4d3e6 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -901,6 +901,8 @@ static int gs_usb_probe(struct usb_interface *intf, const struct usb_device_id * } dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; init_usb_anchor(&dev->rx_submitted); atomic_set(&dev->active_channels, 0); -- cgit v0.10.2 From 3ef5247e033972ddbd16f65ec2ad8a7dfc2e4170 Mon Sep 17 00:00:00 2001 From: Stephane Grosjean Date: Mon, 16 Mar 2015 11:57:52 +0100 Subject: can: peak_usb: rename usb option cmds definition and structs The PUCAN_CMD_RX_FRAME_(ENABLE|DISABLE) command has extended its purpose and was therefore renamed to PUCAN_CMD_SET_(EN|DIS)_OPTION. Signed-off-by: Stephane Grosjean Signed-off-by: Oliver Hartkopp Tested-by: Oliver Hartkopp Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/usb/peak_usb/pcan_ucan.h b/drivers/net/can/usb/peak_usb/pcan_ucan.h index 1ba7c25..1fb3330 100644 --- a/drivers/net/can/usb/peak_usb/pcan_ucan.h +++ b/drivers/net/can/usb/peak_usb/pcan_ucan.h @@ -26,8 +26,8 @@ #define PUCAN_CMD_FILTER_STD 0x008 #define PUCAN_CMD_TX_ABORT 0x009 #define PUCAN_CMD_WR_ERR_CNT 0x00a -#define PUCAN_CMD_RX_FRAME_ENABLE 0x00b -#define PUCAN_CMD_RX_FRAME_DISABLE 0x00c +#define PUCAN_CMD_SET_EN_OPTION 0x00b +#define PUCAN_CMD_CLR_DIS_OPTION 0x00c #define PUCAN_CMD_END_OF_COLLECTION 0x3ff /* uCAN received messages list */ @@ -101,14 +101,14 @@ struct __packed pucan_wr_err_cnt { u16 unused; }; -/* uCAN RX_FRAME_ENABLE command fields */ -#define PUCAN_FLTEXT_ERROR 0x0001 -#define PUCAN_FLTEXT_BUSLOAD 0x0002 +/* uCAN SET_EN/CLR_DIS _OPTION command fields */ +#define PUCAN_OPTION_ERROR 0x0001 +#define PUCAN_OPTION_BUSLOAD 0x0002 -struct __packed pucan_filter_ext { +struct __packed pucan_options { __le16 opcode_channel; - __le16 ext_mask; + __le16 options; u32 unused; }; diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c index 0bac0f1..7506a83 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c @@ -110,13 +110,13 @@ struct __packed pcan_ufd_led { u8 unused[5]; }; -/* Extended usage of uCAN commands CMD_RX_FRAME_xxxABLE for PCAN-USB Pro FD */ +/* Extended usage of uCAN commands CMD_xxx_xx_OPTION for PCAN-USB Pro FD */ #define PCAN_UFD_FLTEXT_CALIBRATION 0x8000 -struct __packed pcan_ufd_filter_ext { +struct __packed pcan_ufd_options { __le16 opcode_channel; - __le16 ext_mask; + __le16 ucan_mask; u16 unused; __le16 usb_mask; }; @@ -321,21 +321,21 @@ static int pcan_usb_fd_set_filter_std(struct peak_usb_device *dev, int idx, return pcan_usb_fd_send_cmd(dev, cmd); } -/* set/unset notifications filter: +/* set/unset options * - * onoff sets(1)/unset(0) notifications - * mask each bit defines a kind of notification to set/unset + * onoff set(1)/unset(0) options + * mask each bit defines a kind of options to set/unset */ -static int pcan_usb_fd_set_filter_ext(struct peak_usb_device *dev, - bool onoff, u16 ext_mask, u16 usb_mask) +static int pcan_usb_fd_set_options(struct peak_usb_device *dev, + bool onoff, u16 ucan_mask, u16 usb_mask) { - struct pcan_ufd_filter_ext *cmd = pcan_usb_fd_cmd_buffer(dev); + struct pcan_ufd_options *cmd = pcan_usb_fd_cmd_buffer(dev); cmd->opcode_channel = pucan_cmd_opcode_channel(dev, - (onoff) ? PUCAN_CMD_RX_FRAME_ENABLE : - PUCAN_CMD_RX_FRAME_DISABLE); + (onoff) ? PUCAN_CMD_SET_EN_OPTION : + PUCAN_CMD_CLR_DIS_OPTION); - cmd->ext_mask = cpu_to_le16(ext_mask); + cmd->ucan_mask = cpu_to_le16(ucan_mask); cmd->usb_mask = cpu_to_le16(usb_mask); /* send the command */ @@ -770,9 +770,9 @@ static int pcan_usb_fd_start(struct peak_usb_device *dev) &pcan_usb_pro_fd); /* enable USB calibration messages */ - err = pcan_usb_fd_set_filter_ext(dev, 1, - PUCAN_FLTEXT_ERROR, - PCAN_UFD_FLTEXT_CALIBRATION); + err = pcan_usb_fd_set_options(dev, 1, + PUCAN_OPTION_ERROR, + PCAN_UFD_FLTEXT_CALIBRATION); } pdev->usb_if->dev_opened_count++; @@ -806,9 +806,9 @@ static int pcan_usb_fd_stop(struct peak_usb_device *dev) /* turn off special msgs for that interface if no other dev opened */ if (pdev->usb_if->dev_opened_count == 1) - pcan_usb_fd_set_filter_ext(dev, 0, - PUCAN_FLTEXT_ERROR, - PCAN_UFD_FLTEXT_CALIBRATION); + pcan_usb_fd_set_options(dev, 0, + PUCAN_OPTION_ERROR, + PCAN_UFD_FLTEXT_CALIBRATION); pdev->usb_if->dev_opened_count--; return 0; @@ -937,9 +937,9 @@ static void pcan_usb_fd_exit(struct peak_usb_device *dev) if (dev->ctrl_idx == 0) { /* turn off calibration message if any device were opened */ if (pdev->usb_if->dev_opened_count > 0) - pcan_usb_fd_set_filter_ext(dev, 0, - PUCAN_FLTEXT_ERROR, - PCAN_UFD_FLTEXT_CALIBRATION); + pcan_usb_fd_set_options(dev, 0, + PUCAN_OPTION_ERROR, + PCAN_UFD_FLTEXT_CALIBRATION); /* tell USB adapter that the driver is being unloaded */ pcan_usb_fd_drv_loaded(dev, 0); -- cgit v0.10.2 From 0f251e45c7cbb1a494b5600485ea8ff64f664b3f Mon Sep 17 00:00:00 2001 From: Stephane Grosjean Date: Mon, 16 Mar 2015 11:57:53 +0100 Subject: can: peak_usb_fd: add support for ISO / non-ISO mode switching The PCAN USB (pro) FD adapters with firmware versions > 2.x support the switching between ISO (default) and non-ISO conform bitstreams on the CAN bus. The setting for the 2.x firmware adapters can be modified with the 'ip' tool from the iproute2 package (option: fd-non-iso [on|off]). Signed-off-by: Stephane Grosjean Signed-off-by: Oliver Hartkopp Tested-by: Oliver Hartkopp Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/usb/peak_usb/pcan_ucan.h b/drivers/net/can/usb/peak_usb/pcan_ucan.h index 1fb3330..e8fc495 100644 --- a/drivers/net/can/usb/peak_usb/pcan_ucan.h +++ b/drivers/net/can/usb/peak_usb/pcan_ucan.h @@ -104,6 +104,7 @@ struct __packed pucan_wr_err_cnt { /* uCAN SET_EN/CLR_DIS _OPTION command fields */ #define PUCAN_OPTION_ERROR 0x0001 #define PUCAN_OPTION_BUSLOAD 0x0002 +#define PUCAN_OPTION_CANDFDISO 0x0004 struct __packed pucan_options { __le16 opcode_channel; diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c index 7506a83..a9221ad 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c @@ -251,6 +251,27 @@ static int pcan_usb_fd_build_restart_cmd(struct peak_usb_device *dev, u8 *buf) /* moves the pointer forward */ pc += sizeof(struct pucan_wr_err_cnt); + /* add command to switch from ISO to non-ISO mode, if fw allows it */ + if (dev->can.ctrlmode_supported & CAN_CTRLMODE_FD_NON_ISO) { + struct pucan_options *puo = (struct pucan_options *)pc; + + puo->opcode_channel = + (dev->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO) ? + pucan_cmd_opcode_channel(dev, + PUCAN_CMD_CLR_DIS_OPTION) : + pucan_cmd_opcode_channel(dev, PUCAN_CMD_SET_EN_OPTION); + + puo->options = cpu_to_le16(PUCAN_OPTION_CANDFDISO); + + /* to be sure that no other extended bits will be taken into + * account + */ + puo->unused = 0; + + /* moves the pointer forward */ + pc += sizeof(struct pucan_options); + } + /* next, go back to operational mode */ cmd = (struct pucan_command *)pc; cmd->opcode_channel = pucan_cmd_opcode_channel(dev, @@ -860,8 +881,14 @@ static int pcan_usb_fd_init(struct peak_usb_device *dev) pdev->usb_if->fw_info.fw_version[2], dev->adapter->ctrl_count); - /* the currently supported hw is non-ISO */ - dev->can.ctrlmode = CAN_CTRLMODE_FD_NON_ISO; + /* check for ability to switch between ISO/non-ISO modes */ + if (pdev->usb_if->fw_info.fw_version[0] >= 2) { + /* firmware >= 2.x supports ISO/non-ISO switching */ + dev->can.ctrlmode_supported |= CAN_CTRLMODE_FD_NON_ISO; + } else { + /* firmware < 2.x only supports fixed(!) non-ISO */ + dev->can.ctrlmode |= CAN_CTRLMODE_FD_NON_ISO; + } /* tell the hardware the can driver is running */ err = pcan_usb_fd_drv_loaded(dev, 1); -- cgit v0.10.2 From 258ce80e19211f06c97a562a71308ec21a9ab98f Mon Sep 17 00:00:00 2001 From: Andri Yngvason Date: Tue, 17 Mar 2015 13:03:09 +0000 Subject: can: flexcan: fix bus-off error state handling. Making sure that the bus-off state gets passed to can_change_state(). Signed-off-by: Andri Yngvason Cc: linux-stable Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index 80c46ad..ee944ae 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -592,13 +592,12 @@ static int flexcan_poll_state(struct net_device *dev, u32 reg_esr) rx_state = unlikely(reg_esr & FLEXCAN_ESR_RX_WRN) ? CAN_STATE_ERROR_WARNING : CAN_STATE_ERROR_ACTIVE; new_state = max(tx_state, rx_state); - } else if (unlikely(flt == FLEXCAN_ESR_FLT_CONF_PASSIVE)) { + } else { __flexcan_get_berr_counter(dev, &bec); - new_state = CAN_STATE_ERROR_PASSIVE; + new_state = flt == FLEXCAN_ESR_FLT_CONF_PASSIVE ? + CAN_STATE_ERROR_PASSIVE : CAN_STATE_BUS_OFF; rx_state = bec.rxerr >= bec.txerr ? new_state : 0; tx_state = bec.rxerr <= bec.txerr ? new_state : 0; - } else { - new_state = CAN_STATE_BUS_OFF; } /* state hasn't changed */ -- cgit v0.10.2 From 6ffa84df2b26b1ba78f6a55b373b9513c93017d6 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 18 Mar 2015 17:48:57 +0100 Subject: ASoC: fsl: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 08d2a80..0bab760 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -326,7 +326,7 @@ static int psc_ac97_of_remove(struct platform_device *op) } /* Match table for of_platform binding */ -static struct of_device_id psc_ac97_match[] = { +static const struct of_device_id psc_ac97_match[] = { { .compatible = "fsl,mpc5200-psc-ac97", }, { .compatible = "fsl,mpc5200b-psc-ac97", }, {} diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index 51fb0c0..d823294 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -217,7 +217,7 @@ static int psc_i2s_of_remove(struct platform_device *op) } /* Match table for of_platform binding */ -static struct of_device_id psc_i2s_match[] = { +static const struct of_device_id psc_i2s_match[] = { { .compatible = "fsl,mpc5200-psc-i2s", }, { .compatible = "fsl,mpc5200b-psc-i2s", }, {} diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c index c44459d..ec73122 100644 --- a/sound/soc/fsl/pcm030-audio-fabric.c +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -113,7 +113,7 @@ static int pcm030_fabric_remove(struct platform_device *op) return ret; } -static struct of_device_id pcm030_audio_match[] = { +static const struct of_device_id pcm030_audio_match[] = { { .compatible = "phytec,pcm030-audio-fabric", }, {} }; -- cgit v0.10.2 From 7f2c52afc02554f18db27242b62d4047bbd8df4c Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 18 Mar 2015 17:48:58 +0100 Subject: ASoC: kirkwood: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index def7d82..0a7ff0e 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -643,7 +643,7 @@ static int kirkwood_i2s_dev_remove(struct platform_device *pdev) } #ifdef CONFIG_OF -static struct of_device_id mvebu_audio_of_match[] = { +static const struct of_device_id mvebu_audio_of_match[] = { { .compatible = "marvell,kirkwood-audio" }, { .compatible = "marvell,dove-audio" }, { .compatible = "marvell,armada370-audio" }, -- cgit v0.10.2 From 261e43a35893830ff32de94443d685c8b9770f5f Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 18 Mar 2015 17:48:59 +0100 Subject: ASoC: rt5631: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c index c618527..2c10d77 100644 --- a/sound/soc/codecs/rt5631.c +++ b/sound/soc/codecs/rt5631.c @@ -1675,7 +1675,7 @@ static const struct i2c_device_id rt5631_i2c_id[] = { MODULE_DEVICE_TABLE(i2c, rt5631_i2c_id); #ifdef CONFIG_OF -static struct of_device_id rt5631_i2c_dt_ids[] = { +static const struct of_device_id rt5631_i2c_dt_ids[] = { { .compatible = "realtek,rt5631"}, { .compatible = "realtek,alc5631"}, { } -- cgit v0.10.2 From f7d4bfee667e236bc9680cfe2f17247bc007f4e3 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 18 Mar 2015 17:49:00 +0100 Subject: ASoC: ak4554: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/ak4554.c b/sound/soc/codecs/ak4554.c index 16ce9f9..298dedc 100644 --- a/sound/soc/codecs/ak4554.c +++ b/sound/soc/codecs/ak4554.c @@ -84,7 +84,7 @@ static int ak4554_soc_remove(struct platform_device *pdev) return 0; } -static struct of_device_id ak4554_of_match[] = { +static const struct of_device_id ak4554_of_match[] = { { .compatible = "asahi-kasei,ak4554" }, {}, }; -- cgit v0.10.2 From c660c0a805860e3abf22b44a2508ff46a549ffa9 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 18 Mar 2015 17:49:01 +0100 Subject: ASoC: fsi: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index b87b22e..eef7083 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -1876,7 +1876,7 @@ static void fsi_handler_init(struct fsi_priv *fsi, } } -static struct of_device_id fsi_of_match[]; +static const struct of_device_id fsi_of_match[]; static int fsi_probe(struct platform_device *pdev) { struct fsi_master *master; @@ -2092,7 +2092,7 @@ static struct fsi_core fsi2_core = { .b_mclk = B_MST_CTLR, }; -static struct of_device_id fsi_of_match[] = { +static const struct of_device_id fsi_of_match[] = { { .compatible = "renesas,sh_fsi", .data = &fsi1_core}, { .compatible = "renesas,sh_fsi2", .data = &fsi2_core}, {}, -- cgit v0.10.2 From 33187fb4a203e44dec11211f2fa86a63139615bc Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 18 Mar 2015 17:49:02 +0100 Subject: ASoC: rsnd: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 7b995f0..7be1602 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -107,7 +107,7 @@ static struct rsnd_of_data rsnd_of_data_gen2 = { .flags = RSND_GEN2, }; -static struct of_device_id rsnd_of_match[] = { +static const struct of_device_id rsnd_of_match[] = { { .compatible = "renesas,rcar_sound-gen1", .data = &rsnd_of_data_gen1 }, { .compatible = "renesas,rcar_sound-gen2", .data = &rsnd_of_data_gen2 }, {}, -- cgit v0.10.2 From 044930b4a69a6c0645b6199bec4f870e0b6e77f4 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 19 Mar 2015 04:13:47 +0000 Subject: ASoC: rsnd: no more SSI restart when unusual situation It will be SSI interrupt endless loop f unusual situation happen. This patch adds restart limit for it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index fea4aa5..060d3d2 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -416,11 +416,14 @@ static irqreturn_t rsnd_ssi_interrupt(int irq, void *data) /* * restart SSI */ - rsnd_ssi_stop(mod, priv); - rsnd_ssi_start(mod, priv); - dev_dbg(dev, "%s[%d] restart\n", rsnd_mod_name(mod), rsnd_mod_id(mod)); + + rsnd_ssi_stop(mod, priv); + if (ssi->err < 1024) + rsnd_ssi_start(mod, priv); + else + dev_warn(dev, "no more SSI restart\n"); } rsnd_ssi_record_error(ssi, status); -- cgit v0.10.2 From 639b231f866c6cc6dcefc33bcaf31e7554697186 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 19 Mar 2015 04:14:04 +0000 Subject: ASoC: rsnd: no more SRC restart when unusual situation It will be SRC interrupt endless loop f unusual situation happen. This patch adds restart limit for it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 6ce8985..cc93f32 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -620,13 +620,17 @@ static irqreturn_t rsnd_src_interrupt_gen2(int irq, void *data) if (rsnd_src_error_record_gen2(mod)) { struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_src *src = rsnd_mod_to_src(mod); struct device *dev = rsnd_priv_to_dev(priv); - _rsnd_src_stop_gen2(mod); - _rsnd_src_start_gen2(mod); - dev_dbg(dev, "%s[%d] restart\n", rsnd_mod_name(mod), rsnd_mod_id(mod)); + + _rsnd_src_stop_gen2(mod); + if (src->err < 1024) + _rsnd_src_start_gen2(mod); + else + dev_warn(dev, "no more SRC restart\n"); } return IRQ_HANDLED; -- cgit v0.10.2 From 072bd1e7e136e089e41c8cc2d2b2251ed60b5bcd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 19 Mar 2015 04:14:20 +0000 Subject: ASoC: rsnd: tidyup error message format This driver sometimes fixups debug/error message format 30cc4faf703955cd5cd07da489bd817ae43e3fec (ASoC: rsnd: tidyup debug message format and timing) 337b0b4c5f415705f1b97df57cecfac45903449a (ASoC: rsnd: error meesage indicates its port) But, it still exist un-fomated error message. This patch fixup it Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 7be1602..cd78a17 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -232,7 +232,7 @@ static int rsnd_dai_connect(struct rsnd_mod *mod, struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct device *dev = rsnd_priv_to_dev(priv); - dev_err(dev, "%s%d is not empty\n", + dev_err(dev, "%s[%d] is not empty\n", rsnd_mod_name(mod), rsnd_mod_id(mod)); return -EIO; -- cgit v0.10.2 From d2c4b80c5b392c8437f9174161a17176b9c3cd76 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 19 Mar 2015 04:14:45 +0000 Subject: ASoC: rsnd: show debug info for sampling rate convert Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index 7ac35c9..7af374b 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -183,6 +183,8 @@ int rsnd_adg_set_convert_clk_gen2(struct rsnd_mod *mod, rsnd_mod_bset(mod, DIV_EN, en, en); + dev_dbg(dev, "convert rate %d <-> %d\n", src_rate, dst_rate); + return 0; } -- cgit v0.10.2 From 555828ef45f825d6ee06559f0304163550eed380 Mon Sep 17 00:00:00 2001 From: Andreas Werner Date: Sun, 22 Mar 2015 17:35:52 +0100 Subject: can: flexcan: Deferred on Regulator return EPROBE_DEFER Return EPROBE_DEFER if Regulator returns EPROBE_DEFER If the Flexcan driver is built into kernel and a regulator is used to enable the CAN transceiver, the Flexcan driver may not use the regulator. When initializing the Flexcan device with a regulator defined in the device tree, but not initialized, the regulator subsystem returns EPROBE_DEFER, hence the Flexcan init fails. The solution for this is to return EPROBE_DEFER if regulator is not initialized and wait until the regulator is initialized. Signed-off-by: Andreas Werner Cc: linux-stable Signed-off-by: Marc Kleine-Budde diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index ee944ae..ad0a7e8 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -1157,12 +1157,19 @@ static int flexcan_probe(struct platform_device *pdev) const struct flexcan_devtype_data *devtype_data; struct net_device *dev; struct flexcan_priv *priv; + struct regulator *reg_xceiver; struct resource *mem; struct clk *clk_ipg = NULL, *clk_per = NULL; void __iomem *base; int err, irq; u32 clock_freq = 0; + reg_xceiver = devm_regulator_get(&pdev->dev, "xceiver"); + if (PTR_ERR(reg_xceiver) == -EPROBE_DEFER) + return -EPROBE_DEFER; + else if (IS_ERR(reg_xceiver)) + reg_xceiver = NULL; + if (pdev->dev.of_node) of_property_read_u32(pdev->dev.of_node, "clock-frequency", &clock_freq); @@ -1223,9 +1230,7 @@ static int flexcan_probe(struct platform_device *pdev) priv->pdata = dev_get_platdata(&pdev->dev); priv->devtype_data = devtype_data; - priv->reg_xceiver = devm_regulator_get(&pdev->dev, "xceiver"); - if (IS_ERR(priv->reg_xceiver)) - priv->reg_xceiver = NULL; + priv->reg_xceiver = reg_xceiver; netif_napi_add(dev, &priv->napi, flexcan_poll, FLEXCAN_NAPI_WEIGHT); -- cgit v0.10.2 From f8c3c3094302cb25d9720804b8100fdd37a3ace0 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 19 Mar 2015 04:15:18 +0000 Subject: ASoC: rsnd: add dai_link stream name This patch adds missing dai_link stream_name which is used when DPCM Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index cd78a17..519d856 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -648,20 +648,28 @@ static int rsnd_dai_probe(struct platform_device *pdev, drv[i].name = rdai[i].name; drv[i].ops = &rsnd_soc_dai_ops; if (pmod) { + snprintf(rdai[i].playback.name, RSND_DAI_NAME_SIZE, + "DAI%d Playback", i); + drv[i].playback.rates = RSND_RATES; drv[i].playback.formats = RSND_FMTS; drv[i].playback.channels_min = 2; drv[i].playback.channels_max = 2; + drv[i].playback.stream_name = rdai[i].playback.name; rdai[i].playback.info = &info->dai_info[i].playback; rdai[i].playback.rdai = rdai + i; rsnd_path_init(priv, &rdai[i], &rdai[i].playback); } if (cmod) { + snprintf(rdai[i].capture.name, RSND_DAI_NAME_SIZE, + "DAI%d Capture", i); + drv[i].capture.rates = RSND_RATES; drv[i].capture.formats = RSND_FMTS; drv[i].capture.channels_min = 2; drv[i].capture.channels_max = 2; + drv[i].capture.stream_name = rdai[i].capture.name; rdai[i].capture.info = &info->dai_info[i].capture; rdai[i].capture.rdai = rdai + i; diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 52c401c..5f35af7 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -301,6 +301,7 @@ struct dma_chan *rsnd_mod_dma_req(struct rsnd_mod *mod); */ #define RSND_DAI_NAME_SIZE 16 struct rsnd_dai_stream { + char name[RSND_DAI_NAME_SIZE]; struct snd_pcm_substream *substream; struct rsnd_mod *mod[RSND_MOD_MAX]; struct rsnd_dai_path_info *info; /* rcar_snd.h */ -- cgit v0.10.2 From f073faa73626f41db7050a69edd5074c53ce6d6c Mon Sep 17 00:00:00 2001 From: Howard Mitchell Date: Fri, 20 Mar 2015 21:13:45 +0000 Subject: ASoC: pcm512x: Fix divide by zero issue If den=1 and pllin_rate>20MHz then den and num are adjusted to 0 causing a divide by zero error a few lines further on. Therefore this patch correctly scales num and den such that pllin_rate/den < 20MHz as required in the device data sheet. Signed-off-by: Howard Mitchell Signed-off-by: Mark Brown Cc: stable@vger.kernel.org diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 194f4c8..0676ab8 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -576,8 +576,8 @@ static int pcm512x_find_pll_coeff(struct snd_soc_dai *dai, /* pllin_rate / P (or here, den) cannot be greater than 20 MHz */ if (pllin_rate / den > 20000000 && num < 8) { - num *= 20000000 / (pllin_rate / den); - den *= 20000000 / (pllin_rate / den); + num *= DIV_ROUND_UP(pllin_rate / den, 20000000); + den *= DIV_ROUND_UP(pllin_rate / den, 20000000); } dev_dbg(dev, "num / den = %lu / %lu\n", num, den); -- cgit v0.10.2 From 6212755eff3171e0211bb6a9f4706e115217588c Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 20 Mar 2015 15:31:34 -0500 Subject: ASoC: Intel: remove misleading DMA error messages on Baytrail platforms During probe, the Baytrail audio driver reports errors such as: [44.172040] baytrail-pcm-audio baytrail-pcm-audio: error: invalid DMA engine 0 [44.172137] baytrail-pcm-audio baytrail-pcm-audio: sst_dma_new failed Those error messages are misleading, there is no error since the DMA is explicitly not configured for Baytrail. Add a test to remove DMA error checks when DMA is not configured and return silently. Acked-by: Liam Girdwood Signed-off-by: Pierre-Louis Bossart Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-acpi.c b/sound/soc/intel/sst-acpi.c index b3d8456..42f293f 100644 --- a/sound/soc/intel/sst-acpi.c +++ b/sound/soc/intel/sst-acpi.c @@ -142,6 +142,7 @@ static int sst_acpi_probe(struct platform_device *pdev) sst_acpi->desc = desc; sst_acpi->mach = mach; + sst_pdata->resindex_dma_base = desc->resindex_dma_base; if (desc->resindex_dma_base >= 0) { sst_pdata->dma_engine = desc->dma_engine; sst_pdata->dma_base = desc->resindex_dma_base; diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h index f291e32..148d8c5 100644 --- a/sound/soc/intel/sst-dsp.h +++ b/sound/soc/intel/sst-dsp.h @@ -206,6 +206,7 @@ struct sst_pdata { const struct firmware *fw; /* DMA */ + int resindex_dma_base; /* other fields invalid if equals to -1 */ u32 dma_base; u32 dma_size; int dma_engine; diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c index b3f9489..28beceb 100644 --- a/sound/soc/intel/sst-firmware.c +++ b/sound/soc/intel/sst-firmware.c @@ -271,6 +271,10 @@ int sst_dma_new(struct sst_dsp *sst) const char *dma_dev_name; int ret = 0; + if (sst->pdata->resindex_dma_base == -1) + /* DMA is not used, return and squelsh error messages */ + return 0; + /* configure the correct platform data for whatever DMA engine * is attached to the ADSP IP. */ switch (sst->pdata->dma_engine) { -- cgit v0.10.2 From 83a60ed8f0b5ce550afd5802b60468578db4e055 Mon Sep 17 00:00:00 2001 From: Baptiste Reynal Date: Wed, 4 Mar 2015 16:51:06 +0100 Subject: iommu/arm-smmu: fix ARM_SMMU_FEAT_TRANS_OPS condition This patch is a fix to "iommu/arm-smmu: add support for iova_to_phys through ATS1PR". According to ARM documentation, translation registers are optional even in SMMUv1, so ID0_S1TS needs to be checked to verify their presence. Also, we check that the domain is a stage-1 domain. Signed-off-by: Baptiste Reynal Acked-by: Will Deacon Signed-off-by: Joerg Roedel diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index fc13dd5..a3adde6 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1288,10 +1288,13 @@ static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain, return 0; spin_lock_irqsave(&smmu_domain->pgtbl_lock, flags); - if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS) + if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS && + smmu_domain->stage == ARM_SMMU_DOMAIN_S1) { ret = arm_smmu_iova_to_phys_hard(domain, iova); - else + } else { ret = ops->iova_to_phys(ops, iova); + } + spin_unlock_irqrestore(&smmu_domain->pgtbl_lock, flags); return ret; @@ -1556,7 +1559,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return -ENODEV; } - if (smmu->version == 1 || (!(id & ID0_ATOSNS) && (id & ID0_S1TS))) { + if ((id & ID0_S1TS) && ((smmu->version == 1) || (id & ID0_ATOSNS))) { smmu->features |= ARM_SMMU_FEAT_TRANS_OPS; dev_notice(smmu->dev, "\taddress translation ops\n"); } -- cgit v0.10.2 From 71684406905f98f86a85e008b51f5c4c5d83af5a Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Wed, 4 Mar 2015 11:30:10 -0700 Subject: iommu/vt-d: Detach domain *only* from attached iommus Device domains never span IOMMU hardware units, which allows the domain ID space for each IOMMU to be an independent address space. Therefore we can have multiple, independent domains, each with the same domain->id, but attached to different hardware units. This is also why we need to do a heavy-weight search for VM domains since they can span multiple IOMMUs hardware units and we don't require a single global ID to use for all hardware units. Therefore, if we call iommu_detach_domain() across all active IOMMU hardware units for a non-VM domain, the result is that we clear domain IDs that are not associated with our domain, allowing them to be re-allocated and causing apparent coherency issues when the device cannot access IOVAs for the intended domain. This bug was introduced in commit fb170fb4c548 ("iommu/vt-d: Introduce helper functions to make code symmetric for readability"), but is significantly exacerbated by the more recent commit 62c22167dd70 ("iommu/vt-d: Fix dmar_domain leak in iommu_attach_device") which calls domain_exit() more frequently to resolve a domain leak. Fixes: fb170fb4c548 ("iommu/vt-d: Introduce helper functions to make code symmetric for readability") Signed-off-by: Alex Williamson Cc: Jiang Liu Cc: stable@vger.kernel.org # v3.17+ Signed-off-by: Joerg Roedel diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index ae4c1a8..a83c965 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -1743,8 +1743,8 @@ static int domain_init(struct dmar_domain *domain, int guest_width) static void domain_exit(struct dmar_domain *domain) { struct dmar_drhd_unit *drhd; - struct intel_iommu *iommu; struct page *freelist = NULL; + int i; /* Domain 0 is reserved, so dont process it */ if (!domain) @@ -1764,8 +1764,8 @@ static void domain_exit(struct dmar_domain *domain) /* clear attached or cached domains */ rcu_read_lock(); - for_each_active_iommu(iommu, drhd) - iommu_detach_domain(domain, iommu); + for_each_set_bit(i, domain->iommu_bmp, g_num_of_iommus) + iommu_detach_domain(domain, g_iommus[i]); rcu_read_unlock(); dma_free_pagelist(freelist); -- cgit v0.10.2 From ac04f85a733b6af1faa10f7603e98bf07d2c4203 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 17 Mar 2015 08:06:45 +0800 Subject: iommu: ipmmu-vmsa: Add terminating entry for ipmmu_of_ids The of_device_id table is supposed to be zero-terminated. Signed-off-by: Axel Lin Acked-by: Laurent Pinchart Signed-off-by: Joerg Roedel diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c index 10186ca..bc39bdf 100644 --- a/drivers/iommu/ipmmu-vmsa.c +++ b/drivers/iommu/ipmmu-vmsa.c @@ -851,6 +851,7 @@ static int ipmmu_remove(struct platform_device *pdev) static const struct of_device_id ipmmu_of_ids[] = { { .compatible = "renesas,ipmmu-vmsa", }, + { } }; static struct platform_driver ipmmu_driver = { -- cgit v0.10.2 From 633d6f17cd91ad5bf2370265946f716e42d388c6 Mon Sep 17 00:00:00 2001 From: Juergen Gross Date: Fri, 20 Mar 2015 13:55:38 +0100 Subject: x86/xen: prepare p2m list for memory hotplug Commit 054954eb051f35e74b75a566a96fe756015352c8 ("xen: switch to linear virtual mapped sparse p2m list") introduced a regression regarding to memory hotplug for a pv-domain: as the virtual space for the p2m list is allocated for the to be expected memory size of the domain only, hotplugged memory above that size will not be usable by the domain. Correct this by using a configurable size for the p2m list in case of memory hotplug enabled (default supported memory size is 512 GB for 64 bit domains and 4 GB for 32 bit domains). Signed-off-by: Juergen Gross Cc: # 3.19+ Reviewed-by: Daniel Kiper Signed-off-by: David Vrabel diff --git a/arch/x86/xen/p2m.c b/arch/x86/xen/p2m.c index 9f93af5..b47124d 100644 --- a/arch/x86/xen/p2m.c +++ b/arch/x86/xen/p2m.c @@ -91,6 +91,12 @@ EXPORT_SYMBOL_GPL(xen_p2m_size); unsigned long xen_max_p2m_pfn __read_mostly; EXPORT_SYMBOL_GPL(xen_max_p2m_pfn); +#ifdef CONFIG_XEN_BALLOON_MEMORY_HOTPLUG_LIMIT +#define P2M_LIMIT CONFIG_XEN_BALLOON_MEMORY_HOTPLUG_LIMIT +#else +#define P2M_LIMIT 0 +#endif + static DEFINE_SPINLOCK(p2m_update_lock); static unsigned long *p2m_mid_missing_mfn; @@ -385,9 +391,11 @@ static void __init xen_rebuild_p2m_list(unsigned long *p2m) void __init xen_vmalloc_p2m_tree(void) { static struct vm_struct vm; + unsigned long p2m_limit; + p2m_limit = (phys_addr_t)P2M_LIMIT * 1024 * 1024 * 1024 / PAGE_SIZE; vm.flags = VM_ALLOC; - vm.size = ALIGN(sizeof(unsigned long) * xen_max_p2m_pfn, + vm.size = ALIGN(sizeof(unsigned long) * max(xen_max_p2m_pfn, p2m_limit), PMD_SIZE * PMDS_PER_MID_PAGE); vm_area_register_early(&vm, PMD_SIZE * PMDS_PER_MID_PAGE); pr_notice("p2m virtual area at %p, size is %lx\n", vm.addr, vm.size); diff --git a/drivers/xen/Kconfig b/drivers/xen/Kconfig index b812462..94d9680 100644 --- a/drivers/xen/Kconfig +++ b/drivers/xen/Kconfig @@ -55,6 +55,23 @@ config XEN_BALLOON_MEMORY_HOTPLUG In that case step 3 should be omitted. +config XEN_BALLOON_MEMORY_HOTPLUG_LIMIT + int "Hotplugged memory limit (in GiB) for a PV guest" + default 512 if X86_64 + default 4 if X86_32 + range 0 64 if X86_32 + depends on XEN_HAVE_PVMMU + depends on XEN_BALLOON_MEMORY_HOTPLUG + help + Maxmium amount of memory (in GiB) that a PV guest can be + expanded to when using memory hotplug. + + A PV guest can have more memory than this limit if is + started with a larger maximum. + + This value is used to allocate enough space in internal + tables needed for physical memory administration. + config XEN_SCRUB_PAGES bool "Scrub pages before returning them to system" depends on XEN_BALLOON -- cgit v0.10.2 From 3c56b3a12ce52f361468cbdd2f79b2f3b8da0ea6 Mon Sep 17 00:00:00 2001 From: Juergen Gross Date: Fri, 20 Mar 2015 13:55:39 +0100 Subject: xen/balloon: before adding hotplugged memory, set frames to invalid Commit 25b884a83d487fd62c3de7ac1ab5549979188482 ("x86/xen: set regions above the end of RAM as 1:1") introduced a regression. To be able to add memory pages which were added via memory hotplug to a pv domain, the pages must be "invalid" instead of "identity" in the p2m list before they can be added. Suggested-by: David Vrabel Signed-off-by: Juergen Gross Cc: # 3.16+ Reviewed-by: Daniel Kiper Signed-off-by: David Vrabel diff --git a/drivers/xen/balloon.c b/drivers/xen/balloon.c index 0b52d92..fd93369 100644 --- a/drivers/xen/balloon.c +++ b/drivers/xen/balloon.c @@ -229,6 +229,29 @@ static enum bp_state reserve_additional_memory(long credit) balloon_hotplug = round_up(balloon_hotplug, PAGES_PER_SECTION); nid = memory_add_physaddr_to_nid(hotplug_start_paddr); +#ifdef CONFIG_XEN_HAVE_PVMMU + /* + * add_memory() will build page tables for the new memory so + * the p2m must contain invalid entries so the correct + * non-present PTEs will be written. + * + * If a failure occurs, the original (identity) p2m entries + * are not restored since this region is now known not to + * conflict with any devices. + */ + if (!xen_feature(XENFEAT_auto_translated_physmap)) { + unsigned long pfn, i; + + pfn = PFN_DOWN(hotplug_start_paddr); + for (i = 0; i < balloon_hotplug; i++) { + if (!set_phys_to_machine(pfn + i, INVALID_P2M_ENTRY)) { + pr_warn("set_phys_to_machine() failed, no memory added\n"); + return BP_ECANCELED; + } + } + } +#endif + rc = add_memory(nid, hotplug_start_paddr, balloon_hotplug << PAGE_SHIFT); if (rc) { -- cgit v0.10.2 From c164c147c9a0a371c4710186972a02b6ee2eb984 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 21 Mar 2015 20:29:34 -0700 Subject: Input: ALPS - fix max coordinates for v5 and v7 protocols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 3296f71cd2fde7a2ad52e66a27eae419f6328066 ("Input: ALPS - consolidate setting protocol parameters") inadvertently moved call to alps_dolphin_get_device_area() from v5 to v7 protocol, causing both protocols report incorrect maximum values for X and Y axes which resulted in crash in Synaptics X driver. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=94801 Reported-by: Santiago Gala Reported-by: Pali Rohár Acked-by: Hans de Goede Acked-by: Pali Rohár Signed-off-by: Dmitry Torokhov diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index 1bd15eb..33198b9 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -2281,10 +2281,12 @@ static int alps_set_protocol(struct psmouse *psmouse, priv->set_abs_params = alps_set_abs_params_mt; priv->nibble_commands = alps_v3_nibble_commands; priv->addr_command = PSMOUSE_CMD_RESET_WRAP; - priv->x_max = 1360; - priv->y_max = 660; priv->x_bits = 23; priv->y_bits = 12; + + if (alps_dolphin_get_device_area(psmouse, priv)) + return -EIO; + break; case ALPS_PROTO_V6: @@ -2303,9 +2305,8 @@ static int alps_set_protocol(struct psmouse *psmouse, priv->set_abs_params = alps_set_abs_params_mt; priv->nibble_commands = alps_v3_nibble_commands; priv->addr_command = PSMOUSE_CMD_RESET_WRAP; - - if (alps_dolphin_get_device_area(psmouse, priv)) - return -EIO; + priv->x_max = 0xfff; + priv->y_max = 0x7ff; if (priv->fw_ver[1] != 0xba) priv->flags |= ALPS_BUTTONPAD; -- cgit v0.10.2 From 98dc0703735d9cfc483522d5ffbce0c0b07c1f86 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Mon, 23 Mar 2015 10:33:07 -0700 Subject: Input: synaptics - add quirk for Thinkpad E440 Its ClickPad shares PNP ID "LEN2006" with the one in model E540 which is already handled by the driver (both are Haswell iterations of the Edge line, launched in 2014) but the dimensions it reports are different: $ sudo ./touchpad-edge-detector /dev/input/event3 Touchpad SynPS/2 Synaptics TouchPad on /dev/input/event3 Move one finger around the touchpad to detect the actual edges Kernel says: x [1472..5044], y [1408..3398] Touchpad sends: x [1024..5045], y [2457..4832] /^C Fortunately we can use the board ID, which is also different, to distinguish among them. $ dmesg | grep -i synaptics psmouse serio1: synaptics: Touchpad model: 1, fw: 8.1, id: 0x1e2b1, caps: 0xd001a3/0x940300/0x127c00, board id: 2691, fw id: 1494646 psmouse serio1: synaptics: serio: Synaptics pass-through port at isa0060/serio1/input0 input: SynPS/2 Synaptics TouchPad as /devices/platform/i8042/serio1/input/input4 Board ID in E540 is 2722: psmouse serio1: synaptics: Touchpad model: 1, fw: 8.1, id: 0x1e2b1, caps: 0xd001a3/0x940300/0x127c00, board id: 2722, fw id: 1484859 (from https://launchpadlibrarian.net/179702965/BootDmesg.txt) Signed-off-by: Ramiro Morales Reviewed-by: Benjamin Tissoires Signed-off-by: Dmitry Torokhov diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index dda6058..f6a3a7b 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -154,6 +154,11 @@ static const struct min_max_quirk min_max_pnpid_table[] = { }, { (const char * const []){"LEN2006", NULL}, + {2691, 2691}, + 1024, 5045, 2457, 4632 + }, + { + (const char * const []){"LEN2006", NULL}, {ANY_BOARD_ID, ANY_BOARD_ID}, 1264, 5675, 1171, 4688 }, @@ -189,7 +194,7 @@ static const char * const topbuttonpad_pnp_ids[] = { "LEN2003", "LEN2004", /* L440 */ "LEN2005", - "LEN2006", + "LEN2006", /* Edge E440/E540 */ "LEN2007", "LEN2008", "LEN2009", -- cgit v0.10.2 From 9425183d177aa4a2f09d01a74925124f0778b595 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 23 Mar 2015 18:27:41 +0200 Subject: usb: xhci: handle Config Error Change (CEC) in xhci driver Linux xHCI driver doesn't report and handle port cofig error change. If Port Configure Error for root hub port occurs, CEC bit in PORTSC would be set by xHC and remains 1. This happends when the root port fails to configure its link partner, e.g. the port fails to exchange port capabilities information using Port Capability LMPs. Then the Port Status Change Events will be blocked until all status change bits(CEC is one of the change bits) are cleared('0') (refer to xHCI spec 4.19.2). Otherwise, the port status change event for this root port will not be generated anymore, then root port would look like dead for user and can't be recovered until a Host Controller Reset(HCRST). This patch is to check CEC bit in PORTSC in xhci_get_port_status() and set a Config Error in the return status if CEC is set. This will cause a ClearPortFeature request, where CEC bit is cleared in xhci_clear_port_change_bit(). [The commit log is based on initial Marvell patch posted at http://marc.info/?l=linux-kernel&m=142323612321434&w=2] Reported-by: Gregory CLEMENT Signed-off-by: Lu Baolu Cc: stable # v3.2+ Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index a7865c4..0827d7c 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -387,6 +387,10 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, status = PORT_PLC; port_change_bit = "link state"; break; + case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: + status = PORT_CEC; + port_change_bit = "config error"; + break; default: /* Should never happen */ return; @@ -588,6 +592,8 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, status |= USB_PORT_STAT_C_LINK_STATE << 16; if ((raw_port_status & PORT_WRC)) status |= USB_PORT_STAT_C_BH_RESET << 16; + if ((raw_port_status & PORT_CEC)) + status |= USB_PORT_STAT_C_CONFIG_ERROR << 16; } if (hcd->speed != HCD_USB3) { @@ -1005,6 +1011,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_C_OVER_CURRENT: case USB_PORT_FEAT_C_ENABLE: case USB_PORT_FEAT_C_PORT_LINK_STATE: + case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: xhci_clear_port_change_bit(xhci, wValue, wIndex, port_array[wIndex], temp); break; @@ -1069,7 +1076,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) */ status = bus_state->resuming_ports; - mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC; + mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC | PORT_CEC; spin_lock_irqsave(&xhci->lock, flags); /* For each port, did anything change? If so, set that bit in buf. */ -- cgit v0.10.2 From 227a4fd801c8a9fa2c4700ab98ec1aec06e3b44d Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 23 Mar 2015 18:27:42 +0200 Subject: usb: xhci: apply XHCI_AVOID_BEI quirk to all Intel xHCI controllers When a device with an isochronous endpoint is plugged into the Intel xHCI host controller, and the driver submits multiple frames per URB, the xHCI driver will set the Block Event Interrupt (BEI) flag on all but the last TD for the URB. This causes the host controller to place an event on the event ring, but not send an interrupt. When the last TD for the URB completes, BEI is cleared, and we get an interrupt for the whole URB. However, under Intel xHCI host controllers, if the event ring is full of events from transfers with BEI set, an "Event Ring is Full" event will be posted to the last entry of the event ring, but no interrupt is generated. Host will cease all transfer and command executions and wait until software completes handling the pending events in the event ring. That means xHC stops, but event of "event ring is full" is not notified. As the result, the xHC looks like dead to user. This patch is to apply XHCI_AVOID_BEI quirk to Intel xHC devices. And it should be backported to kernels as old as 3.0, that contains the commit 69e848c2090a ("Intel xhci: Support EHCI/xHCI port switching."). Signed-off-by: Lu Baolu Tested-by: Alistair Grant Cc: stable@vger.kernel.org Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index fd53c9e..2af32e2 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -115,6 +115,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_INTEL) { xhci->quirks |= XHCI_LPM_SUPPORT; xhci->quirks |= XHCI_INTEL_HOST; + xhci->quirks |= XHCI_AVOID_BEI; } if (pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_XHCI) { @@ -130,7 +131,6 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) * PPT chipsets. */ xhci->quirks |= XHCI_SPURIOUS_REBOOT; - xhci->quirks |= XHCI_AVOID_BEI; } if (pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI) { -- cgit v0.10.2 From cd02e3df529eb3fbf8904ba60a617f6217ac7629 Mon Sep 17 00:00:00 2001 From: Howard Mitchell Date: Mon, 23 Mar 2015 21:17:01 +0000 Subject: ASoC: pcm512x: Remove hardcoding of pll-lock to GPIO4 Currently GPIO4 is hardcoded to output the pll-lock signal. Unfortunately this is after the pll-out GPIO is configured which is selectable in the device tree. Therefore it is not possible to use GPIO4 for pll-out. Therefore this patch removes the configuration of GPIO4. Signed-off-by: Howard Mitchell Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 0676ab8..8c09e3f 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -1156,25 +1156,6 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream, ret, pcm512x->pll_out); return ret; } - - gpio = PCM512x_G1OE << (4 - 1); - ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_EN, - gpio, gpio); - if (ret != 0) { - dev_err(codec->dev, "Failed to enable gpio %d: %d\n", - 4, ret); - return ret; - } - - gpio = PCM512x_GPIO_OUTPUT_1 + 4 - 1; - ret = regmap_update_bits(pcm512x->regmap, gpio, - PCM512x_GxSL, PCM512x_GxSL_PLLLK); - if (ret != 0) { - dev_err(codec->dev, - "Failed to output pll lock on %d: %d\n", - ret, 4); - return ret; - } } ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE, -- cgit v0.10.2 From dff173de84958a677ce0d24b1da3cdc3a32b4238 Mon Sep 17 00:00:00 2001 From: Yuval Mintz Date: Mon, 23 Mar 2015 10:56:14 +0200 Subject: bnx2x: Fix statistics locking scheme Statistics' state-machine in bnx2x driver must be synced with various driver flows, but its current locking scheme manages to be wasteful [using 2 locks + additional local variable] and prone to race-conditions at the same time, as the state-machine and 'action' are being accessed under different locks. In addition, current 'safe exec' isn't in fact safe, since the only guarantee it gives is that DMA transactions are over, but ramrods might still be running. This patch cleans up said logic, leaving us with a single lock for the entire flow and removing the possible races. Changes from v2: - Switched into mutex locking from semaphore locking. - Release locks on error flows. Changes from v1: Failure to acquire lock fails flow instead of printing a warning and allowing access to the critical section. Signed-off-by: Yuval Mintz Signed-off-by: Ariel Elior Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x.h b/drivers/net/ethernet/broadcom/bnx2x/bnx2x.h index 756053c..4085c4b 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x.h +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x.h @@ -1811,7 +1811,7 @@ struct bnx2x { int stats_state; /* used for synchronization of concurrent threads statistics handling */ - spinlock_t stats_lock; + struct mutex stats_lock; /* used by dmae command loader */ struct dmae_command stats_dmae; @@ -1935,8 +1935,6 @@ struct bnx2x { int fp_array_size; u32 dump_preset_idx; - bool stats_started; - struct semaphore stats_sema; u8 phys_port_id[ETH_ALEN]; diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c index 996e215..ae571a1 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c @@ -12037,9 +12037,8 @@ static int bnx2x_init_bp(struct bnx2x *bp) mutex_init(&bp->port.phy_mutex); mutex_init(&bp->fw_mb_mutex); mutex_init(&bp->drv_info_mutex); + mutex_init(&bp->stats_lock); bp->drv_info_mng_owner = false; - spin_lock_init(&bp->stats_lock); - sema_init(&bp->stats_sema, 1); INIT_DELAYED_WORK(&bp->sp_task, bnx2x_sp_task); INIT_DELAYED_WORK(&bp->sp_rtnl_task, bnx2x_sp_rtnl_task); @@ -13668,9 +13667,9 @@ static int bnx2x_eeh_nic_unload(struct bnx2x *bp) cancel_delayed_work_sync(&bp->sp_task); cancel_delayed_work_sync(&bp->period_task); - spin_lock_bh(&bp->stats_lock); + mutex_lock(&bp->stats_lock); bp->stats_state = STATS_STATE_DISABLED; - spin_unlock_bh(&bp->stats_lock); + mutex_unlock(&bp->stats_lock); bnx2x_save_statistics(bp); diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sriov.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sriov.c index e5aca2d..cfe3c769 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sriov.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sriov.c @@ -2238,7 +2238,9 @@ int bnx2x_vf_close(struct bnx2x *bp, struct bnx2x_virtf *vf) cookie.vf = vf; cookie.state = VF_ACQUIRED; - bnx2x_stats_safe_exec(bp, bnx2x_set_vf_state, &cookie); + rc = bnx2x_stats_safe_exec(bp, bnx2x_set_vf_state, &cookie); + if (rc) + goto op_err; } DP(BNX2X_MSG_IOV, "set state to acquired\n"); diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.c index d160829..800ab44 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.c @@ -123,36 +123,28 @@ static void bnx2x_dp_stats(struct bnx2x *bp) */ static void bnx2x_storm_stats_post(struct bnx2x *bp) { - if (!bp->stats_pending) { - int rc; + int rc; - spin_lock_bh(&bp->stats_lock); - - if (bp->stats_pending) { - spin_unlock_bh(&bp->stats_lock); - return; - } - - bp->fw_stats_req->hdr.drv_stats_counter = - cpu_to_le16(bp->stats_counter++); + if (bp->stats_pending) + return; - DP(BNX2X_MSG_STATS, "Sending statistics ramrod %d\n", - le16_to_cpu(bp->fw_stats_req->hdr.drv_stats_counter)); + bp->fw_stats_req->hdr.drv_stats_counter = + cpu_to_le16(bp->stats_counter++); - /* adjust the ramrod to include VF queues statistics */ - bnx2x_iov_adjust_stats_req(bp); - bnx2x_dp_stats(bp); + DP(BNX2X_MSG_STATS, "Sending statistics ramrod %d\n", + le16_to_cpu(bp->fw_stats_req->hdr.drv_stats_counter)); - /* send FW stats ramrod */ - rc = bnx2x_sp_post(bp, RAMROD_CMD_ID_COMMON_STAT_QUERY, 0, - U64_HI(bp->fw_stats_req_mapping), - U64_LO(bp->fw_stats_req_mapping), - NONE_CONNECTION_TYPE); - if (rc == 0) - bp->stats_pending = 1; + /* adjust the ramrod to include VF queues statistics */ + bnx2x_iov_adjust_stats_req(bp); + bnx2x_dp_stats(bp); - spin_unlock_bh(&bp->stats_lock); - } + /* send FW stats ramrod */ + rc = bnx2x_sp_post(bp, RAMROD_CMD_ID_COMMON_STAT_QUERY, 0, + U64_HI(bp->fw_stats_req_mapping), + U64_LO(bp->fw_stats_req_mapping), + NONE_CONNECTION_TYPE); + if (rc == 0) + bp->stats_pending = 1; } static void bnx2x_hw_stats_post(struct bnx2x *bp) @@ -221,7 +213,7 @@ static void bnx2x_stats_comp(struct bnx2x *bp) */ /* should be called under stats_sema */ -static void __bnx2x_stats_pmf_update(struct bnx2x *bp) +static void bnx2x_stats_pmf_update(struct bnx2x *bp) { struct dmae_command *dmae; u32 opcode; @@ -519,7 +511,7 @@ static void bnx2x_func_stats_init(struct bnx2x *bp) } /* should be called under stats_sema */ -static void __bnx2x_stats_start(struct bnx2x *bp) +static void bnx2x_stats_start(struct bnx2x *bp) { if (IS_PF(bp)) { if (bp->port.pmf) @@ -531,34 +523,13 @@ static void __bnx2x_stats_start(struct bnx2x *bp) bnx2x_hw_stats_post(bp); bnx2x_storm_stats_post(bp); } - - bp->stats_started = true; -} - -static void bnx2x_stats_start(struct bnx2x *bp) -{ - if (down_timeout(&bp->stats_sema, HZ/10)) - BNX2X_ERR("Unable to acquire stats lock\n"); - __bnx2x_stats_start(bp); - up(&bp->stats_sema); } static void bnx2x_stats_pmf_start(struct bnx2x *bp) { - if (down_timeout(&bp->stats_sema, HZ/10)) - BNX2X_ERR("Unable to acquire stats lock\n"); bnx2x_stats_comp(bp); - __bnx2x_stats_pmf_update(bp); - __bnx2x_stats_start(bp); - up(&bp->stats_sema); -} - -static void bnx2x_stats_pmf_update(struct bnx2x *bp) -{ - if (down_timeout(&bp->stats_sema, HZ/10)) - BNX2X_ERR("Unable to acquire stats lock\n"); - __bnx2x_stats_pmf_update(bp); - up(&bp->stats_sema); + bnx2x_stats_pmf_update(bp); + bnx2x_stats_start(bp); } static void bnx2x_stats_restart(struct bnx2x *bp) @@ -568,11 +539,9 @@ static void bnx2x_stats_restart(struct bnx2x *bp) */ if (IS_VF(bp)) return; - if (down_timeout(&bp->stats_sema, HZ/10)) - BNX2X_ERR("Unable to acquire stats lock\n"); + bnx2x_stats_comp(bp); - __bnx2x_stats_start(bp); - up(&bp->stats_sema); + bnx2x_stats_start(bp); } static void bnx2x_bmac_stats_update(struct bnx2x *bp) @@ -1246,18 +1215,12 @@ static void bnx2x_stats_update(struct bnx2x *bp) { u32 *stats_comp = bnx2x_sp(bp, stats_comp); - /* we run update from timer context, so give up - * if somebody is in the middle of transition - */ - if (down_trylock(&bp->stats_sema)) + if (bnx2x_edebug_stats_stopped(bp)) return; - if (bnx2x_edebug_stats_stopped(bp) || !bp->stats_started) - goto out; - if (IS_PF(bp)) { if (*stats_comp != DMAE_COMP_VAL) - goto out; + return; if (bp->port.pmf) bnx2x_hw_stats_update(bp); @@ -1267,7 +1230,7 @@ static void bnx2x_stats_update(struct bnx2x *bp) BNX2X_ERR("storm stats were not updated for 3 times\n"); bnx2x_panic(); } - goto out; + return; } } else { /* vf doesn't collect HW statistics, and doesn't get completions @@ -1281,7 +1244,7 @@ static void bnx2x_stats_update(struct bnx2x *bp) /* vf is done */ if (IS_VF(bp)) - goto out; + return; if (netif_msg_timer(bp)) { struct bnx2x_eth_stats *estats = &bp->eth_stats; @@ -1292,9 +1255,6 @@ static void bnx2x_stats_update(struct bnx2x *bp) bnx2x_hw_stats_post(bp); bnx2x_storm_stats_post(bp); - -out: - up(&bp->stats_sema); } static void bnx2x_port_stats_stop(struct bnx2x *bp) @@ -1358,12 +1318,7 @@ static void bnx2x_port_stats_stop(struct bnx2x *bp) static void bnx2x_stats_stop(struct bnx2x *bp) { - int update = 0; - - if (down_timeout(&bp->stats_sema, HZ/10)) - BNX2X_ERR("Unable to acquire stats lock\n"); - - bp->stats_started = false; + bool update = false; bnx2x_stats_comp(bp); @@ -1381,8 +1336,6 @@ static void bnx2x_stats_stop(struct bnx2x *bp) bnx2x_hw_stats_post(bp); bnx2x_stats_comp(bp); } - - up(&bp->stats_sema); } static void bnx2x_stats_do_nothing(struct bnx2x *bp) @@ -1410,18 +1363,28 @@ static const struct { void bnx2x_stats_handle(struct bnx2x *bp, enum bnx2x_stats_event event) { - enum bnx2x_stats_state state; - void (*action)(struct bnx2x *bp); + enum bnx2x_stats_state state = bp->stats_state; + if (unlikely(bp->panic)) return; - spin_lock_bh(&bp->stats_lock); - state = bp->stats_state; + /* Statistics update run from timer context, and we don't want to stop + * that context in case someone is in the middle of a transition. + * For other events, wait a bit until lock is taken. + */ + if (!mutex_trylock(&bp->stats_lock)) { + if (event == STATS_EVENT_UPDATE) + return; + + DP(BNX2X_MSG_STATS, + "Unlikely stats' lock contention [event %d]\n", event); + mutex_lock(&bp->stats_lock); + } + + bnx2x_stats_stm[state][event].action(bp); bp->stats_state = bnx2x_stats_stm[state][event].next_state; - action = bnx2x_stats_stm[state][event].action; - spin_unlock_bh(&bp->stats_lock); - action(bp); + mutex_unlock(&bp->stats_lock); if ((event != STATS_EVENT_UPDATE) || netif_msg_timer(bp)) DP(BNX2X_MSG_STATS, "state %d -> event %d -> state %d\n", @@ -1998,13 +1961,34 @@ void bnx2x_afex_collect_stats(struct bnx2x *bp, void *void_afex_stats, } } -void bnx2x_stats_safe_exec(struct bnx2x *bp, - void (func_to_exec)(void *cookie), - void *cookie){ - if (down_timeout(&bp->stats_sema, HZ/10)) - BNX2X_ERR("Unable to acquire stats lock\n"); +int bnx2x_stats_safe_exec(struct bnx2x *bp, + void (func_to_exec)(void *cookie), + void *cookie) +{ + int cnt = 10, rc = 0; + + /* Wait for statistics to end [while blocking further requests], + * then run supplied function 'safely'. + */ + mutex_lock(&bp->stats_lock); + bnx2x_stats_comp(bp); + while (bp->stats_pending && cnt--) + if (bnx2x_storm_stats_update(bp)) + usleep_range(1000, 2000); + if (bp->stats_pending) { + BNX2X_ERR("Failed to wait for stats pending to clear [possibly FW is stuck]\n"); + rc = -EBUSY; + goto out; + } + func_to_exec(cookie); - __bnx2x_stats_start(bp); - up(&bp->stats_sema); + +out: + /* No need to restart statistics - if they're enabled, the timer + * will restart the statistics. + */ + mutex_unlock(&bp->stats_lock); + + return rc; } diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.h b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.h index 2beceae..965539a 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.h +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_stats.h @@ -539,9 +539,9 @@ struct bnx2x; void bnx2x_memset_stats(struct bnx2x *bp); void bnx2x_stats_init(struct bnx2x *bp); void bnx2x_stats_handle(struct bnx2x *bp, enum bnx2x_stats_event event); -void bnx2x_stats_safe_exec(struct bnx2x *bp, - void (func_to_exec)(void *cookie), - void *cookie); +int bnx2x_stats_safe_exec(struct bnx2x *bp, + void (func_to_exec)(void *cookie), + void *cookie); /** * bnx2x_save_statistics - save statistics when unloading. -- cgit v0.10.2 From d0c294c53a771ae7e84506dfbd8c18c30f078735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kube=C4=8Dek?= Date: Mon, 23 Mar 2015 15:14:00 +0100 Subject: tcp: prevent fetching dst twice in early demux code On s390x, gcc 4.8 compiles this part of tcp_v6_early_demux() struct dst_entry *dst = sk->sk_rx_dst; if (dst) dst = dst_check(dst, inet6_sk(sk)->rx_dst_cookie); to code reading sk->sk_rx_dst twice, once for the test and once for the argument of ip6_dst_check() (dst_check() is inline). This allows ip6_dst_check() to be called with null first argument, causing a crash. Protect sk->sk_rx_dst access by READ_ONCE() both in IPv4 and IPv6 TCP early demux code. Fixes: 41063e9dd119 ("ipv4: Early TCP socket demux.") Fixes: c7109986db3c ("ipv6: Early TCP socket demux") Signed-off-by: Michal Kubecek Acked-by: Eric Dumazet Signed-off-by: David S. Miller diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 5a2dfed..f1756ee 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1518,7 +1518,7 @@ void tcp_v4_early_demux(struct sk_buff *skb) skb->sk = sk; skb->destructor = sock_edemux; if (sk->sk_state != TCP_TIME_WAIT) { - struct dst_entry *dst = sk->sk_rx_dst; + struct dst_entry *dst = READ_ONCE(sk->sk_rx_dst); if (dst) dst = dst_check(dst, 0); diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 5d46832..b283a49 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1585,7 +1585,7 @@ static void tcp_v6_early_demux(struct sk_buff *skb) skb->sk = sk; skb->destructor = sock_edemux; if (sk->sk_state != TCP_TIME_WAIT) { - struct dst_entry *dst = sk->sk_rx_dst; + struct dst_entry *dst = READ_ONCE(sk->sk_rx_dst); if (dst) dst = dst_check(dst, inet6_sk(sk)->rx_dst_cookie); -- cgit v0.10.2 From a71aaf66725e8fc02253a3bbea15d27e656434fe Mon Sep 17 00:00:00 2001 From: Larry Finger Date: Mon, 23 Mar 2015 14:07:57 -0500 Subject: iwlwifi: Fix memory leak in iwl_req_fw_callback() In this routine, kzalloc allocates a memory block. This allocation is freed in the error paths, but not in the normal exit, thus the allocation is leaked. The kmemleak facility was used to find the leak. Signed-off-by: Larry Finger Cc: Johannes Berg Cc: Emmanuel Grumbach Cc: Intel Linux Wireless diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c index 996e7f1..c7154ac 100644 --- a/drivers/net/wireless/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/iwlwifi/iwl-drv.c @@ -1257,6 +1257,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) op->name, err); #endif } + kfree(pieces); return; try_again: -- cgit v0.10.2 From 509fca899d5682a6eee3d1fb70bba7c89439034b Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Mon, 23 Mar 2015 19:50:21 -0600 Subject: iommu/vt-d: Remove unused variable Unused after commit 71684406905f ("iommu/vt-d: Detach domain *only* from attached iommus"). Reported by 0-day builder. Signed-off-by: Alex Williamson Signed-off-by: Joerg Roedel diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index a83c965..2d1e05b 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -1742,7 +1742,6 @@ static int domain_init(struct dmar_domain *domain, int guest_width) static void domain_exit(struct dmar_domain *domain) { - struct dmar_drhd_unit *drhd; struct page *freelist = NULL; int i; -- cgit v0.10.2 From 143526ee94a295ed33b9cc19e9532ab6d14a1cc0 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 24 Mar 2015 09:51:12 +0800 Subject: ASoC: rt286: check regmap_read result for ID check It is worth to check the regmap_read result for ID check since it is the first regmap_read. And we can check if there is any i2c issue. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index ea96797..842cfb9 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -1219,7 +1219,7 @@ static int rt286_i2c_probe(struct i2c_client *i2c, { struct rt286_platform_data *pdata = dev_get_platdata(&i2c->dev); struct rt286_priv *rt286; - int i, ret; + int i, ret, val; rt286 = devm_kzalloc(&i2c->dev, sizeof(*rt286), GFP_KERNEL); @@ -1234,11 +1234,15 @@ static int rt286_i2c_probe(struct i2c_client *i2c, return ret; } - regmap_read(rt286->regmap, - RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &ret); - if (ret != RT286_VENDOR_ID && ret != RT288_VENDOR_ID) { + ret = regmap_read(rt286->regmap, + RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &val); + if (ret != 0) { + dev_err(&i2c->dev, "I2C error %d\n", ret); + return ret; + } + if (val != RT286_VENDOR_ID && val != RT288_VENDOR_ID) { dev_err(&i2c->dev, - "Device with ID register %x is not rt286\n", ret); + "Device with ID register %x is not rt286\n", val); return -ENODEV; } -- cgit v0.10.2 From 65582a7f4ce5bd7a1fb61516d9c2476f4f166f4e Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 20 Mar 2015 11:42:17 +0000 Subject: usb: isp1760: fix spin unlock in the error path of isp1760_udc_start Commit a124820de5fd ("usb: isp1760: fix possible deadlock in isp1760_udc_irq") replaced spin_{un,}lock with spin_{un,}lock_irq{save,restore}. However it missed an error path resulting in the smatch warning as below: drivers/usb/isp1760/isp1760-udc.c:1230 isp1760_udc_start() warn: inconsistent returns 'irqsave:flags'. Locked on: line 1207 Unlocked on: line 1199 This patch fixes the spin unlock in the error path in isp1760_udc_start thereby removing the smatch warning mentioned above. Reported-by: Dan Carpenter Cc: Laurent Pinchart Cc: Felipe Balbi Signed-off-by: Sudeep Holla Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c index f32c292..3fc4fe7 100644 --- a/drivers/usb/isp1760/isp1760-udc.c +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -1203,7 +1203,7 @@ static int isp1760_udc_start(struct usb_gadget *gadget, if (udc->driver) { dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); - spin_unlock(&udc->lock); + spin_unlock_irqrestore(&udc->lock, flags); return -EBUSY; } -- cgit v0.10.2 From 39c26180641da983269d5f24f124f8624f2587b4 Mon Sep 17 00:00:00 2001 From: Takeshi Kihara Date: Tue, 24 Mar 2015 05:15:22 +0000 Subject: ASoC: ak4642: enable stereo line output power-save mode ak4642 has power-save mode for stereo line to reduce pop noise. This patch enables it. Signed-off-by: Takeshi Kihara Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 7255f69..24ceb36 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -97,6 +97,9 @@ #define PMMP (1 << 2) /* MPWR pin Power Management */ #define MGAIN0 (1 << 0) /* MIC amp gain*/ +/* SG_SL2 */ +#define LOPS (1 << 6) /* Stero Line-out Power Save Mode */ + /* TIMER */ #define ZTM(param) ((param & 0x3) << 4) /* ALC Zero Crossing TimeOut */ #define WTM(param) (((param & 0x4) << 4) | ((param & 0x3) << 2)) @@ -168,6 +171,29 @@ static const struct snd_kcontrol_new ak4642_lout_mixer_controls[] = { SOC_DAPM_SINGLE("DACL", SG_SL1, 4, 1, 0), }; +/* event handlers */ +static int ak4642_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + case SND_SOC_DAPM_PRE_PMU: + /* Power save mode ON */ + snd_soc_update_bits(codec, SG_SL2, LOPS, LOPS); + break; + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + /* Power save mode OFF */ + mdelay(300); + snd_soc_update_bits(codec, SG_SL2, LOPS, 0); + break; + } + + return 0; +} + static const struct snd_soc_dapm_widget ak4642_dapm_widgets[] = { /* Outputs */ @@ -182,9 +208,12 @@ static const struct snd_soc_dapm_widget ak4642_dapm_widgets[] = { SND_SOC_DAPM_PGA("DACH", MD_CTL4, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("LINEOUT Mixer", PW_MGMT1, 3, 0, + SND_SOC_DAPM_MIXER_E("LINEOUT Mixer", PW_MGMT1, 3, 0, &ak4642_lout_mixer_controls[0], - ARRAY_SIZE(ak4642_lout_mixer_controls)), + ARRAY_SIZE(ak4642_lout_mixer_controls), + ak4642_lout_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), /* DAC */ SND_SOC_DAPM_DAC("DAC", NULL, PW_MGMT1, 2, 0), -- cgit v0.10.2 From d079535d5e1bf5e2e7c856bae2483414ea21e137 Mon Sep 17 00:00:00 2001 From: WANG Cong Date: Mon, 23 Mar 2015 16:31:09 -0700 Subject: net: use for_each_netdev_safe() in rtnl_group_changelink() In case we move the whole dev group to another netns, we should call for_each_netdev_safe(), otherwise we get a soft lockup: NMI watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [ip:798] irq event stamp: 255424 hardirqs last enabled at (255423): [] restore_args+0x0/0x30 hardirqs last disabled at (255424): [] apic_timer_interrupt+0x6a/0x80 softirqs last enabled at (255422): [] __do_softirq+0x2c1/0x3a9 softirqs last disabled at (255417): [] irq_exit+0x41/0x95 CPU: 0 PID: 798 Comm: ip Not tainted 4.0.0-rc4+ #881 Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 task: ffff8800d1b88000 ti: ffff880119530000 task.ti: ffff880119530000 RIP: 0010:[] [] debug_lockdep_rcu_enabled+0x28/0x30 RSP: 0018:ffff880119533778 EFLAGS: 00000246 RAX: ffff8800d1b88000 RBX: 0000000000000002 RCX: 0000000000000038 RDX: 0000000000000000 RSI: ffff8800d1b888c8 RDI: ffff8800d1b888c8 RBP: ffff880119533778 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 000000000000b5c2 R12: 0000000000000246 R13: ffff880119533708 R14: 00000000001d5a40 R15: ffff88011a7d5a40 FS: 00007fc01315f740(0000) GS:ffff88011a600000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b CR2: 00007f367a120988 CR3: 000000011849c000 CR4: 00000000000007f0 Stack: ffff880119533798 ffffffff811ac868 ffffffff811ac831 ffffffff811ac828 ffff8801195337c8 ffffffff811ac8c9 ffff8801195339b0 ffff8801197633e0 0000000000000000 ffff8801195339b0 ffff8801195337d8 ffffffff811ad2d7 Call Trace: [] rcu_read_lock+0x37/0x6e [] ? rcu_read_unlock+0x5f/0x5f [] ? rcu_read_unlock+0x56/0x5f [] __fget+0x2a/0x7a [] fget+0x13/0x15 [] proc_ns_fget+0xe/0x38 [] get_net_ns_by_fd+0x11/0x59 [] rtnl_link_get_net+0x33/0x3e [] do_setlink+0x73/0x87b [] ? trace_hardirqs_off+0xd/0xf [] ? retint_restore_args+0xe/0xe [] rtnl_newlink+0x40c/0x699 [] ? rtnl_newlink+0xeb/0x699 [] ? _raw_spin_unlock+0x28/0x33 [] ? security_capable+0x18/0x1a [] ? ns_capable+0x4d/0x65 [] rtnetlink_rcv_msg+0x181/0x194 [] ? rtnl_lock+0x17/0x19 [] ? rtnl_lock+0x17/0x19 [] ? __rtnl_unlock+0x17/0x17 [] netlink_rcv_skb+0x4d/0x93 [] rtnetlink_rcv+0x26/0x2d [] netlink_unicast+0xcb/0x150 [] netlink_sendmsg+0x501/0x523 [] ? might_fault+0x59/0xa9 [] ? copy_from_user+0x2a/0x2c [] sock_sendmsg+0x34/0x3c [] ___sys_sendmsg+0x1b8/0x255 [] ? handle_pte_fault+0xbd5/0xd4a [] ? native_sched_clock+0x35/0x37 [] ? sched_clock_local+0x12/0x72 [] ? sched_clock_cpu+0x9e/0xb7 [] ? rcu_read_lock_held+0x3b/0x3d [] ? __fcheck_files+0x4c/0x58 [] ? __fget_light+0x2d/0x52 [] __sys_sendmsg+0x42/0x60 [] SyS_sendmsg+0x12/0x1c [] system_call_fastpath+0x12/0x17 Fixes: e7ed828f10bd8 ("netlink: support setting devgroup parameters") Signed-off-by: Cong Wang Signed-off-by: David S. Miller diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index ee0608b..7ebed55 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -1932,10 +1932,10 @@ static int rtnl_group_changelink(const struct sk_buff *skb, struct ifinfomsg *ifm, struct nlattr **tb) { - struct net_device *dev; + struct net_device *dev, *aux; int err; - for_each_netdev(net, dev) { + for_each_netdev_safe(net, dev, aux) { if (dev->group == group) { err = do_setlink(skb, dev, ifm, tb, NULL, 0); if (err < 0) -- cgit v0.10.2 From 2f4b1e6bb25899e7d21e1764abcfb23f14250535 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 24 Mar 2015 11:47:43 +0100 Subject: ASoC: rsnd: Fix duplicate const for DVC ramp rates Replace duplicated const keyword for 'dvc_ramp_rate' with proper array of const pointers to const strings. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index aeeef13..6d85e36 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -36,7 +36,7 @@ struct rsnd_dvc { ((pos) = (struct rsnd_dvc *)(priv)->dvc + i); \ i++) -static const char const *dvc_ramp_rate[] = { +static const char * const dvc_ramp_rate[] = { "128 dB/1 step", /* 00000 */ "64 dB/1 step", /* 00001 */ "32 dB/1 step", /* 00010 */ -- cgit v0.10.2 From a75a053f1eefbbbbae0f7d6bf1ed12ce012112b7 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 20 Mar 2015 13:31:08 +0200 Subject: ASoC: davinci-mcasp: Set rule constraints if implicit BCLK divider is used Set rule constraints to allow only combinations of sample-rate, sample-format, and channels counts that can be played/captured with reasonable sample-rate accuracy. The logic with tdm-slots and serializers (=i2s data wires) goes like this: The first wire will take all channels up to number of tdm-slots, before following wires (if any) are used. If the first wire is used fully, the remaining wires share the same clocks and the divider can be calculated for the first wire. Also, takes the number of tdm-slots into account when implicitly selecting the BLCK divider. Signed-off-by: Jyri Sarha Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index d40b392..76156d18 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,11 @@ struct davinci_mcasp_context { bool pm_state; }; +struct davinci_mcasp_ruledata { + struct davinci_mcasp *mcasp; + int serializers; +}; + struct davinci_mcasp { struct snd_dmaengine_dai_dma_data dma_data[2]; void __iomem *base; @@ -99,6 +105,8 @@ struct davinci_mcasp { #ifdef CONFIG_PM_SLEEP struct davinci_mcasp_context context; #endif + + struct davinci_mcasp_ruledata ruledata[2]; }; static inline void mcasp_set_bits(struct davinci_mcasp *mcasp, u32 offset, @@ -868,6 +876,30 @@ static int mcasp_dit_hw_param(struct davinci_mcasp *mcasp, return 0; } +static int davinci_mcasp_calc_clk_div(struct davinci_mcasp *mcasp, + unsigned int bclk_freq, + int *error_ppm) +{ + int div = mcasp->sysclk_freq / bclk_freq; + int rem = mcasp->sysclk_freq % bclk_freq; + + if (rem != 0) { + if (div == 0 || + ((mcasp->sysclk_freq / div) - bclk_freq) > + (bclk_freq - (mcasp->sysclk_freq / (div+1)))) { + div++; + rem = rem - bclk_freq; + } + } + if (error_ppm) + *error_ppm = + (div*1000000 + (int)div64_long(1000000LL*rem, + (int)bclk_freq)) + /div - 1000000; + + return div; +} + static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai) @@ -883,16 +915,20 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, * the machine driver, we need to calculate the ratio. */ if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { - unsigned int bclk_freq = snd_soc_params_to_bclk(params); - unsigned int div = mcasp->sysclk_freq / bclk_freq; - if (mcasp->sysclk_freq % bclk_freq != 0) { - if (((mcasp->sysclk_freq / div) - bclk_freq) > - (bclk_freq - (mcasp->sysclk_freq / (div+1)))) - div++; - dev_warn(mcasp->dev, - "Inaccurate BCLK: %u Hz / %u != %u Hz\n", - mcasp->sysclk_freq, div, bclk_freq); - } + int channels = params_channels(params); + int rate = params_rate(params); + int sbits = params_width(params); + int ppm, div; + + if (channels > mcasp->tdm_slots) + channels = mcasp->tdm_slots; + + div = davinci_mcasp_calc_clk_div(mcasp, rate*sbits*channels, + &ppm); + if (ppm) + dev_info(mcasp->dev, "Sample-rate is off by %d PPM\n", + ppm); + __davinci_mcasp_set_clkdiv(cpu_dai, 1, div, 0); } @@ -974,6 +1010,120 @@ static int davinci_mcasp_trigger(struct snd_pcm_substream *substream, return ret; } +static const unsigned int davinci_mcasp_dai_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, + 88200, 96000, 176400, 192000, +}; + +#define DAVINCI_MAX_RATE_ERROR_PPM 1000 + +static int davinci_mcasp_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct davinci_mcasp_ruledata *rd = rule->private; + struct snd_interval *ri = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + int sbits = params_width(params); + int channels = params_channels(params); + unsigned int list[ARRAY_SIZE(davinci_mcasp_dai_rates)]; + int i, count = 0; + + if (channels > rd->mcasp->tdm_slots) + channels = rd->mcasp->tdm_slots; + + for (i = 0; i < ARRAY_SIZE(davinci_mcasp_dai_rates); i++) { + if (ri->min <= davinci_mcasp_dai_rates[i] && + ri->max >= davinci_mcasp_dai_rates[i]) { + uint bclk_freq = sbits*channels* + davinci_mcasp_dai_rates[i]; + int ppm; + + davinci_mcasp_calc_clk_div(rd->mcasp, bclk_freq, &ppm); + if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) + list[count++] = davinci_mcasp_dai_rates[i]; + } + } + dev_dbg(rd->mcasp->dev, + "%d frequencies (%d-%d) for %d sbits and %d channels\n", + count, ri->min, ri->max, sbits, channels); + + return snd_interval_list(hw_param_interval(params, rule->var), + count, list, 0); +} + +static int davinci_mcasp_hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct davinci_mcasp_ruledata *rd = rule->private; + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_mask nfmt; + int rate = params_rate(params); + int channels = params_channels(params); + int i, count = 0; + + snd_mask_none(&nfmt); + + if (channels > rd->mcasp->tdm_slots) + channels = rd->mcasp->tdm_slots; + + for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) { + if (snd_mask_test(fmt, i)) { + uint bclk_freq = snd_pcm_format_width(i)*channels*rate; + int ppm; + + davinci_mcasp_calc_clk_div(rd->mcasp, bclk_freq, &ppm); + if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { + snd_mask_set(&nfmt, i); + count++; + } + } + } + dev_dbg(rd->mcasp->dev, + "%d possible sample format for %d Hz and %d channels\n", + count, rate, channels); + + return snd_mask_refine(fmt, &nfmt); +} + +static int davinci_mcasp_hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct davinci_mcasp_ruledata *rd = rule->private; + struct snd_interval *ci = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + int sbits = params_width(params); + int rate = params_rate(params); + int max_chan_per_wire = rd->mcasp->tdm_slots < ci->max ? + rd->mcasp->tdm_slots : ci->max; + unsigned int list[ci->max - ci->min + 1]; + int c1, c, count = 0; + + for (c1 = ci->min; c1 <= max_chan_per_wire; c1++) { + uint bclk_freq = c1*sbits*rate; + int ppm; + + davinci_mcasp_calc_clk_div(rd->mcasp, bclk_freq, &ppm); + if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { + /* If we can use all tdm_slots, we can put any + amount of channels to remaining wires as + long as they fit in. */ + if (c1 == rd->mcasp->tdm_slots) { + for (c = c1; c <= rd->serializers*c1 && + c <= ci->max; c++) + list[count++] = c; + } else { + list[count++] = c1; + } + } + } + dev_dbg(rd->mcasp->dev, + "%d possible channel counts (%d-%d) for %d Hz and %d sbits\n", + count, ci->min, ci->max, rate, sbits); + + return snd_interval_list(hw_param_interval(params, rule->var), + count, list, 0); +} + static int davinci_mcasp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { @@ -999,6 +1149,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, if (mcasp->serial_dir[i] == dir) max_channels++; } + mcasp->ruledata[dir].serializers = max_channels; max_channels *= mcasp->tdm_slots; /* * If the already active stream has less channels than the calculated @@ -1013,6 +1164,42 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, max_channels); + + /* + * If we rely on implicit BCLK divider setting we should + * set constraints based on what we can provide. + */ + if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { + int ret; + + mcasp->ruledata[dir].mcasp = mcasp; + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + davinci_mcasp_hw_rule_rate, + &mcasp->ruledata[dir], + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) + return ret; + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + davinci_mcasp_hw_rule_format, + &mcasp->ruledata[dir], + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) + return ret; + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + davinci_mcasp_hw_rule_channels, + &mcasp->ruledata[dir], + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) + return ret; + } + return 0; } -- cgit v0.10.2 From 4e2576bd36a12e78ac3786d05b99a820dffe687f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 24 Mar 2015 09:01:00 +0000 Subject: ASoC: soc-core: initialize debugfs in snd_soc_instantiate_card() Current soc_init_card_debugfs() is called from snd_soc_register_card() but, soc_cleanup_card_debugfs() is called from soc_cleanup_card_resources(), not from paired function. This differences don't matter for now. But if anyone wants to implement a proper hotplug/unplug, this difference would become clearer. Now, we can assume that snd_soc_instantiate_card() and soc_cleanup_card_resources() are paired function. soc_init_card_debugfs() / soc_cleanup_card_debugfs() paired function should be called from these. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index acf99f1..4443581 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1662,6 +1662,8 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) snd_soc_dapm_sync(&card->dapm); mutex_unlock(&card->mutex); + soc_init_card_debugfs(card); + return 0; probe_aux_dev_err: @@ -2352,8 +2354,6 @@ int snd_soc_register_card(struct snd_soc_card *card) snd_soc_initialize_card_lists(card); - soc_init_card_debugfs(card); - card->rtd = devm_kzalloc(card->dev, sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs), @@ -2384,7 +2384,7 @@ int snd_soc_register_card(struct snd_soc_card *card) ret = snd_soc_instantiate_card(card); if (ret != 0) - soc_cleanup_card_debugfs(card); + return ret; /* deactivate pins to sleep state */ for (i = 0; i < card->num_rtd; i++) { -- cgit v0.10.2 From a6e95cc718c8916a13f1e1e9d33cacbc5db56c0f Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Tue, 24 Mar 2015 09:31:40 +0900 Subject: rocker: handle non-bridge master change Master change notifications may occur other than when joining or leaving a bridge, for example when being added to or removed from a bond or Open vSwitch. Previously in those cases rocker_port_bridge_leave() was called which results in a null-pointer dereference as rocker_port->bridge_dev is NULL because there is no bridge device. This patch makes provision for doing nothing in such cases. Fixes: 6c7079450071f ("rocker: implement L2 bridge offloading") Acked-by: Jiri Pirko Acked-by: Scott Feldman Signed-off-by: Simon Horman Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c index 9fb6948..5cecec2 100644 --- a/drivers/net/ethernet/rocker/rocker.c +++ b/drivers/net/ethernet/rocker/rocker.c @@ -4468,10 +4468,16 @@ static int rocker_port_master_changed(struct net_device *dev) struct net_device *master = netdev_master_upper_dev_get(dev); int err = 0; + /* There are currently three cases handled here: + * 1. Joining a bridge + * 2. Leaving a previously joined bridge + * 3. Other, e.g. being added to or removed from a bond or openvswitch, + * in which case nothing is done + */ if (master && master->rtnl_link_ops && !strcmp(master->rtnl_link_ops->kind, "bridge")) err = rocker_port_bridge_join(rocker_port, master); - else + else if (rocker_port_is_bridged(rocker_port)) err = rocker_port_bridge_leave(rocker_port); return err; -- cgit v0.10.2 From e5eda89d97ec256ba14e7e861387cc0468259c18 Mon Sep 17 00:00:00 2001 From: Ido Shamay Date: Tue, 24 Mar 2015 15:18:38 +0200 Subject: net/mlx4_en: Call register_netdevice in the proper location Netdevice registration should be performed a the end of the driver initialization flow. If we don't do that, after calling register_netdevice, device callbacks may be issued by higher layers of the stack before final configuration of the device is done. For example (VXLAN configuration race), mlx4_SET_PORT_VXLAN was issued after the register_netdev command. System network scripts may configure the interface (UP) right after the registration, which also attach unicast VXLAN steering rule, before mlx4_SET_PORT_VXLAN was called, causing the firmware to fail the rule attachment. Fixes: 837052d0ccc5 ("net/mlx4_en: Add netdev support for TCP/IP offloads of vxlan tunneling") Signed-off-by: Ido Shamay Signed-off-by: Or Gerlitz Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c index ebce5bb..3485acf 100644 --- a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c +++ b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c @@ -2805,13 +2805,6 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port, netif_carrier_off(dev); mlx4_en_set_default_moderation(priv); - err = register_netdev(dev); - if (err) { - en_err(priv, "Netdev registration failed for port %d\n", port); - goto out; - } - priv->registered = 1; - en_warn(priv, "Using %d TX rings\n", prof->tx_ring_num); en_warn(priv, "Using %d RX rings\n", prof->rx_ring_num); @@ -2853,6 +2846,14 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port, mlx4_set_stats_bitmap(mdev->dev, &priv->stats_bitmap); + err = register_netdev(dev); + if (err) { + en_err(priv, "Netdev registration failed for port %d\n", port); + goto out; + } + + priv->registered = 1; + return 0; out: -- cgit v0.10.2 From bffb023ad2d44c0cfa8142ad8247391b32000998 Mon Sep 17 00:00:00 2001 From: Jack Morgenstein Date: Tue, 24 Mar 2015 15:18:39 +0200 Subject: net/mlx4_core: Fix GEN_EQE accessing uninitialixed mutex We occasionally see in procedure mlx4_GEN_EQE that the driver tries to grab an uninitialized mutex. This can occur in only one of two ways: 1. We are trying to generate an async event on an uninitialized slave. 2. We are trying to generate an async event on an illegal slave number ( < 0 or > persist->num_vfs) or an inactive slave. To deal with #1: move the mutex initialization from specific slave init sequence in procedure mlx_master_do_cmd to mlx4_multi_func_init() (so that the mutex is always initialized for all slaves). To deal with #2: check in procedure mlx4_GEN_EQE that the slave number provided is in the proper range and that the slave is active. Signed-off-by: Jack Morgenstein Signed-off-by: Or Gerlitz Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/mellanox/mlx4/cmd.c b/drivers/net/ethernet/mellanox/mlx4/cmd.c index a681d7c..3350721 100644 --- a/drivers/net/ethernet/mellanox/mlx4/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx4/cmd.c @@ -1993,7 +1993,6 @@ static void mlx4_master_do_cmd(struct mlx4_dev *dev, int slave, u8 cmd, goto reset_slave; slave_state[slave].vhcr_dma = ((u64) param) << 48; priv->mfunc.master.slave_state[slave].cookie = 0; - mutex_init(&priv->mfunc.master.gen_eqe_mutex[slave]); break; case MLX4_COMM_CMD_VHCR1: if (slave_state[slave].last_cmd != MLX4_COMM_CMD_VHCR0) @@ -2225,6 +2224,7 @@ int mlx4_multi_func_init(struct mlx4_dev *dev) for (i = 0; i < dev->num_slaves; ++i) { s_state = &priv->mfunc.master.slave_state[i]; s_state->last_cmd = MLX4_COMM_CMD_RESET; + mutex_init(&priv->mfunc.master.gen_eqe_mutex[i]); for (j = 0; j < MLX4_EVENT_TYPES_NUM; ++j) s_state->event_eq[j].eqn = -1; __raw_writel((__force u32) 0, diff --git a/drivers/net/ethernet/mellanox/mlx4/eq.c b/drivers/net/ethernet/mellanox/mlx4/eq.c index 264bc15..6e70ffe 100644 --- a/drivers/net/ethernet/mellanox/mlx4/eq.c +++ b/drivers/net/ethernet/mellanox/mlx4/eq.c @@ -153,12 +153,10 @@ void mlx4_gen_slave_eqe(struct work_struct *work) /* All active slaves need to receive the event */ if (slave == ALL_SLAVES) { - for (i = 0; i < dev->num_slaves; i++) { - if (i != dev->caps.function && - master->slave_state[i].active) - if (mlx4_GEN_EQE(dev, i, eqe)) - mlx4_warn(dev, "Failed to generate event for slave %d\n", - i); + for (i = 0; i <= dev->persist->num_vfs; i++) { + if (mlx4_GEN_EQE(dev, i, eqe)) + mlx4_warn(dev, "Failed to generate event for slave %d\n", + i); } } else { if (mlx4_GEN_EQE(dev, slave, eqe)) @@ -203,13 +201,11 @@ static void mlx4_slave_event(struct mlx4_dev *dev, int slave, struct mlx4_eqe *eqe) { struct mlx4_priv *priv = mlx4_priv(dev); - struct mlx4_slave_state *s_slave = - &priv->mfunc.master.slave_state[slave]; - if (!s_slave->active) { - /*mlx4_warn(dev, "Trying to pass event to inactive slave\n");*/ + if (slave < 0 || slave > dev->persist->num_vfs || + slave == dev->caps.function || + !priv->mfunc.master.slave_state[slave].active) return; - } slave_event(dev, slave, eqe); } diff --git a/drivers/net/ethernet/mellanox/mlx4/resource_tracker.c b/drivers/net/ethernet/mellanox/mlx4/resource_tracker.c index d97ca88..6e413ac 100644 --- a/drivers/net/ethernet/mellanox/mlx4/resource_tracker.c +++ b/drivers/net/ethernet/mellanox/mlx4/resource_tracker.c @@ -3095,6 +3095,12 @@ int mlx4_GEN_EQE(struct mlx4_dev *dev, int slave, struct mlx4_eqe *eqe) if (!priv->mfunc.master.slave_state) return -EINVAL; + /* check for slave valid, slave not PF, and slave active */ + if (slave < 0 || slave > dev->persist->num_vfs || + slave == dev->caps.function || + !priv->mfunc.master.slave_state[slave].active) + return 0; + event_eq = &priv->mfunc.master.slave_state[slave].event_eq[eqe->type]; /* Create the event only if the slave is registered */ -- cgit v0.10.2 From 1452db764c279f8b329f1b3b9469bd01d9e21afd Mon Sep 17 00:00:00 2001 From: Cliff Clark Date: Tue, 24 Mar 2015 14:07:26 -0700 Subject: ucc_geth: Intialize link state to down before register_netdev ucc_geth was indicating link up after a port is administratively enabled even when nothing is plugged in. This causes user-space tools to see a spurious link up the first time after boot. Signed-off-by: Cliff Clark Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/freescale/ucc_geth.c b/drivers/net/ethernet/freescale/ucc_geth.c index 357e8b57..56b774d 100644 --- a/drivers/net/ethernet/freescale/ucc_geth.c +++ b/drivers/net/ethernet/freescale/ucc_geth.c @@ -3893,6 +3893,9 @@ static int ucc_geth_probe(struct platform_device* ofdev) ugeth->phy_interface = phy_interface; ugeth->max_speed = max_speed; + /* Carrier starts down, phylib will bring it up */ + netif_carrier_off(dev); + err = register_netdev(dev); if (err) { if (netif_msg_probe(ugeth)) -- cgit v0.10.2 From b229a0f840f774d29d8fedbf5deb344ca36b7f1a Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Mon, 23 Mar 2015 20:34:48 -0500 Subject: USB: ftdi_sio: Use jtag quirk for SNAP Connect E10 This patch uses the existing CALAO Systems ftdi_8u2232c_probe in order to avoid attaching a TTY to the JTAG port as this board is based on the CALAO Systems reference design and needs the same fix up. Signed-off-by: Doug Goldstein CC: stable [johan: clean up probe logic ] Signed-off-by: Johan Hovold diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 130b354..8eb68a3 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1884,8 +1884,12 @@ static int ftdi_8u2232c_probe(struct usb_serial *serial) { struct usb_device *udev = serial->dev; - if ((udev->manufacturer && !strcmp(udev->manufacturer, "CALAO Systems")) || - (udev->product && !strcmp(udev->product, "BeagleBone/XDS100V2"))) + if (udev->manufacturer && !strcmp(udev->manufacturer, "CALAO Systems")) + return ftdi_jtag_probe(serial); + + if (udev->product && + (!strcmp(udev->product, "BeagleBone/XDS100V2") || + !strcmp(udev->product, "SNAP Connect E10"))) return ftdi_jtag_probe(serial); return 0; -- cgit v0.10.2 From 46172b6c26667133b9945b916b6223cc87bbf10c Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 25 Mar 2015 11:22:35 +0000 Subject: ASoC: dapm: Fix build warning commit c66150824b8a ("ASoC: dapm: add code to configure dai link parameters") introduced the following build warning: sound/soc/soc-dapm.c: In function 'snd_soc_dapm_new_pcm': sound/soc/soc-dapm.c:3389:4: warning: passing argument 1 of 'snprintf' discards 'const' qualifier from pointer target type snprintf(w_param_text[count], len, This patch fixes this by switching to using devm_kasprintf. This also saves a couple of lines of code. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 6828b4e..e5ec9d7 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3356,7 +3356,6 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card, { struct snd_soc_dapm_widget template; struct snd_soc_dapm_widget *w; - size_t len; char *link_name; int ret, count; unsigned long private_value; @@ -3376,28 +3375,26 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card, if (!w_param_text) return -ENOMEM; - len = strlen(source->name) + strlen(sink->name) + 2; - link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); + link_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-%s", + source->name, sink->name); if (!link_name) { ret = -ENOMEM; goto outfree_w_param; } - snprintf(link_name, len, "%s-%s", source->name, sink->name); for (count = 0 ; count < num_params; count++) { if (!config->stream_name) { dev_warn(card->dapm.dev, "ASoC: anonymous config %d for dai link %s\n", count, link_name); - len = strlen("Anonymous Configuration ") + 3; w_param_text[count] = - devm_kzalloc(card->dev, len, GFP_KERNEL); + devm_kasprintf(card->dev, GFP_KERNEL, + "Anonymous Configuration %d", + count); if (!w_param_text[count]) { ret = -ENOMEM; goto outfree_link_name; } - snprintf(w_param_text[count], len, - "Anonymous Configuration %d", count); } else { w_param_text[count] = devm_kmemdup(card->dev, config->stream_name, -- cgit v0.10.2 From 8787041d9bb832b9449b1eb878cedcebce42c61a Mon Sep 17 00:00:00 2001 From: Sergej Sawazki Date: Tue, 24 Mar 2015 21:13:22 +0100 Subject: ASoC: wm8741: Fix rates constraints values The WM8741 DAC supports the following typical audio sampling rates: 44.1kHz, 88.2kHz, 176.4kHz (eg: with a master clock of 22.5792MHz) 32kHz, 48kHz, 96kHz, 192kHz (eg: with a master clock of 24.576MHz) For the rates lists, we should use 82000 instead of 88235, 176400 instead of 1764000 and 192000 instead of 19200 (seems to be a typo). Signed-off-by: Sergej Sawazki Acked-by: Charles Keepax Signed-off-by: Mark Brown Cc: stable@vger.kernel.org diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index 31bb480..9e71c76 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -123,7 +123,7 @@ static struct { }; static const unsigned int rates_11289[] = { - 44100, 88235, + 44100, 88200, }; static const struct snd_pcm_hw_constraint_list constraints_11289 = { @@ -150,7 +150,7 @@ static const struct snd_pcm_hw_constraint_list constraints_16384 = { }; static const unsigned int rates_16934[] = { - 44100, 88235, + 44100, 88200, }; static const struct snd_pcm_hw_constraint_list constraints_16934 = { @@ -168,7 +168,7 @@ static const struct snd_pcm_hw_constraint_list constraints_18432 = { }; static const unsigned int rates_22579[] = { - 44100, 88235, 1764000 + 44100, 88200, 176400 }; static const struct snd_pcm_hw_constraint_list constraints_22579 = { @@ -186,7 +186,7 @@ static const struct snd_pcm_hw_constraint_list constraints_24576 = { }; static const unsigned int rates_36864[] = { - 48000, 96000, 19200 + 48000, 96000, 192000 }; static const struct snd_pcm_hw_constraint_list constraints_36864 = { -- cgit v0.10.2 From 6fd99094de2b83d1d4c8457f2c83483b2828e75a Mon Sep 17 00:00:00 2001 From: "D.S. Ljungmark" Date: Wed, 25 Mar 2015 09:28:15 +0100 Subject: ipv6: Don't reduce hop limit for an interface A local route may have a lower hop_limit set than global routes do. RFC 3756, Section 4.2.7, "Parameter Spoofing" > 1. The attacker includes a Current Hop Limit of one or another small > number which the attacker knows will cause legitimate packets to > be dropped before they reach their destination. > As an example, one possible approach to mitigate this threat is to > ignore very small hop limits. The nodes could implement a > configurable minimum hop limit, and ignore attempts to set it below > said limit. Signed-off-by: D.S. Ljungmark Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index 471ed24..14ecdaf 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -1218,7 +1218,14 @@ static void ndisc_router_discovery(struct sk_buff *skb) if (rt) rt6_set_expires(rt, jiffies + (HZ * lifetime)); if (ra_msg->icmph.icmp6_hop_limit) { - in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit; + /* Only set hop_limit on the interface if it is higher than + * the current hop_limit. + */ + if (in6_dev->cnf.hop_limit < ra_msg->icmph.icmp6_hop_limit) { + in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit; + } else { + ND_PRINTK(2, warn, "RA: Got route advertisement with lower hop_limit than current\n"); + } if (rt) dst_metric_set(&rt->dst, RTAX_HOPLIMIT, ra_msg->icmph.icmp6_hop_limit); -- cgit v0.10.2 From dde93dfea53c72b07907d9e44a6e4b1545f6bdc4 Mon Sep 17 00:00:00 2001 From: Hariprasad Shenai Date: Wed, 25 Mar 2015 20:01:26 +0530 Subject: cxgb4: Fix frame size warning for 32 bit arch Fixes below warning by dynamically allocating memory All warnings: drivers/net/ethernet/chelsio/cxgb4/cxgb4_debugfs.c: In function 'cctrl_tbl_show': >> drivers/net/ethernet/chelsio/cxgb4/cxgb4_debugfs.c:689:1: warning: the >> frame >> size of 1028 bytes is larger than 1024 bytes [-Wframe-larger-than=] Reported-by: kbuild test robot Signed-off-by: Hariprasad Shenai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_debugfs.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_debugfs.c index 78854ce..dcb0479 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_debugfs.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_debugfs.c @@ -670,9 +670,13 @@ static int cctrl_tbl_show(struct seq_file *seq, void *v) "0.9375" }; int i; - u16 incr[NMTUS][NCCTRL_WIN]; + u16 (*incr)[NCCTRL_WIN]; struct adapter *adap = seq->private; + incr = kmalloc(sizeof(*incr) * NMTUS, GFP_KERNEL); + if (!incr) + return -ENOMEM; + t4_read_cong_tbl(adap, incr); for (i = 0; i < NCCTRL_WIN; ++i) { @@ -685,6 +689,8 @@ static int cctrl_tbl_show(struct seq_file *seq, void *v) adap->params.a_wnd[i], dec_fac[adap->params.b_wnd[i]]); } + + kfree(incr); return 0; } -- cgit v0.10.2 From c5952338bfc234e54deda45b7228f610a545e28a Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Mon, 23 Mar 2015 10:53:42 -0400 Subject: nfsd: return correct openowner when there is a race to put one in the hash alloc_init_open_stateowner can return an already freed entry if there is a race to put openowners in the hashtable. In commit 7ffb588086e9, we changed it so that we allocate and initialize an openowner, and then check to see if a matching one got stuffed into the hashtable in the meantime. If it did, then we free the one we just allocated and take a reference on the one already there. There is a bug here though. The code will then return the pointer to the one that was allocated (and has now been freed). This wasn't evident before as this race almost never occurred. The Linux kernel client used to serialize requests for a single openowner. That has changed now with v4.0 kernels, and this race can now easily occur. Fixes: 7ffb588086e9 Cc: # v3.17+ Cc: Trond Myklebust Reported-by: Christoph Hellwig Reviewed-by: Christoph Hellwig Signed-off-by: Jeff Layton Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index d2f2c37..cf29d1a 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -3221,7 +3221,7 @@ alloc_init_open_stateowner(unsigned int strhashval, struct nfsd4_open *open, } else nfs4_free_openowner(&oo->oo_owner); spin_unlock(&clp->cl_lock); - return oo; + return ret; } static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) { -- cgit v0.10.2 From 340f0ba1c6c8412aa35fd6476044836b84361ea6 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 23 Mar 2015 11:02:30 -0400 Subject: nfsd: return correct lockowner when there is a race on hash insert alloc_init_lock_stateowner can return an already freed entry if there is a race to put openowners in the hashtable. Noticed by inspection after Jeff Layton fixed the same bug for open owners. Depending on client behavior, this one may be trickier to trigger in practice. Fixes: c58c6610ec24 "nfsd: Protect adding/removing lock owners using client_lock" Cc: Cc: Trond Myklebust Acked-by: Jeff Layton Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index cf29d1a..8ba1d88 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -5062,7 +5062,7 @@ alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, } else nfs4_free_lockowner(&lo->lo_owner); spin_unlock(&clp->cl_lock); - return lo; + return ret; } static void -- cgit v0.10.2 From 853695230e9847445ad42c330d023fca2324fd56 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Sun, 22 Mar 2015 22:16:40 +0800 Subject: NFSD: Printk blocklayout length and offset as format 0x%llx When testing pnfs with nfsd_debug on, nfsd print a negative number of layout length and foff in nfsd4_block_proc_layoutget as, "GET: -xxxx:-xxx 2" Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/blocklayout.c b/fs/nfsd/blocklayout.c index cdbc78c..03d647b 100644 --- a/fs/nfsd/blocklayout.c +++ b/fs/nfsd/blocklayout.c @@ -137,7 +137,7 @@ nfsd4_block_proc_layoutget(struct inode *inode, const struct svc_fh *fhp, seg->offset = iomap.offset; seg->length = iomap.length; - dprintk("GET: %lld:%lld %d\n", bex->foff, bex->len, bex->es); + dprintk("GET: 0x%llx:0x%llx %d\n", bex->foff, bex->len, bex->es); return 0; out_error: diff --git a/fs/nfsd/blocklayoutxdr.c b/fs/nfsd/blocklayoutxdr.c index 9da89fd..9aa2796 100644 --- a/fs/nfsd/blocklayoutxdr.c +++ b/fs/nfsd/blocklayoutxdr.c @@ -122,19 +122,19 @@ nfsd4_block_decode_layoutupdate(__be32 *p, u32 len, struct iomap **iomapp, p = xdr_decode_hyper(p, &bex.foff); if (bex.foff & (block_size - 1)) { - dprintk("%s: unaligned offset %lld\n", + dprintk("%s: unaligned offset 0x%llx\n", __func__, bex.foff); goto fail; } p = xdr_decode_hyper(p, &bex.len); if (bex.len & (block_size - 1)) { - dprintk("%s: unaligned length %lld\n", + dprintk("%s: unaligned length 0x%llx\n", __func__, bex.foff); goto fail; } p = xdr_decode_hyper(p, &bex.soff); if (bex.soff & (block_size - 1)) { - dprintk("%s: unaligned disk offset %lld\n", + dprintk("%s: unaligned disk offset 0x%llx\n", __func__, bex.soff); goto fail; } -- cgit v0.10.2 From 376675daeac3dc0e39b84ca90f0bdc41a9d44d46 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Sun, 22 Mar 2015 22:17:10 +0800 Subject: NFSD: Take care the return value from nfsd4_encode_stateid Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index c76654c..5fb7e78 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -4135,7 +4135,7 @@ nfsd4_encode_layoutreturn(struct nfsd4_compoundres *resp, __be32 nfserr, return nfserr_resource; *p++ = cpu_to_be32(lrp->lrs_present); if (lrp->lrs_present) - nfsd4_encode_stateid(xdr, &lrp->lr_sid); + return nfsd4_encode_stateid(xdr, &lrp->lr_sid); return nfs_ok; } #endif /* CONFIG_NFSD_PNFS */ -- cgit v0.10.2 From 7890203da2cb9b861dae9bdb88f882f3e03baec9 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Sun, 22 Mar 2015 22:17:20 +0800 Subject: NFSD: Fix bad update of layout in nfsd4_return_file_layout With return layout as, (seg is return layout, lo is record layout) seg->offset <= lo->offset and layout_end(seg) < layout_end(lo), nfsd should update lo's offset to seg's end, and, seg->offset > lo->offset and layout_end(seg) >= layout_end(lo), nfsd should update lo's end to seg's offset. Fixes: 9cf514ccfa ("nfsd: implement pNFS operations") Signed-off-by: Kinglong Mee Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index 6964613..80e236b 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -440,15 +440,14 @@ nfsd4_return_file_layout(struct nfs4_layout *lp, struct nfsd4_layout_seg *seg, list_move_tail(&lp->lo_perstate, reaplist); return; } - end = seg->offset; + lo->offset = layout_end(seg); } else { /* retain the whole layout segment on a split. */ if (layout_end(seg) < end) { dprintk("%s: split not supported\n", __func__); return; } - - lo->offset = layout_end(seg); + end = seg->offset; } layout_update_len(lo, end); -- cgit v0.10.2 From a57069e33fbc6625f39e1b09c88ea44629a35206 Mon Sep 17 00:00:00 2001 From: Manish Badarkhe Date: Thu, 26 Mar 2015 15:38:25 +0200 Subject: ASoC: davinci-evm: drop un-necessary remove function As davinci card gets registered using 'devm_' api there is no need to unregister the card in 'remove' function. Hence drop the 'remove' function. Fixes: ee2f615d6e59c (ASoC: davinci-evm: Add device tree binding) Signed-off-by: Manish Badarkhe Signed-off-by: Jyri Sarha Signed-off-by: Mark Brown Cc: stable@vger.kernel.org diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index b6bb594..8c2b9be 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -425,18 +425,8 @@ static int davinci_evm_probe(struct platform_device *pdev) return ret; } -static int davinci_evm_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - snd_soc_unregister_card(card); - - return 0; -} - static struct platform_driver davinci_evm_driver = { .probe = davinci_evm_probe, - .remove = davinci_evm_remove, .driver = { .name = "davinci_evm", .pm = &snd_soc_pm_ops, -- cgit v0.10.2 From e91d863d1aaeda0349aed42dfbd75c98e042a2c9 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 13 Mar 2015 12:38:51 +0100 Subject: serial: samsung: Clear operation mode on UART shutdown Exynos serial ports operate either in a DMA-based or interrupt-based modes. In DMA-based mode, the UART generates a transfer data request and a Transmission (Tx) interrupt in interrupt-based mode. The Tx IRQ is only unmasked in interrupt-based mode and it was done in s3c24xx_serial_start_tx(). Commit ba019a3e2ad5 ("serial: samsung: remove redundant interrupt enabling") removed the IRQ enable on that function since it is enabled when the mode is set in enable_tx_pio(). The problem is that enable_tx_pio() is only called if the port mode has not been set before but the mode was not cleared on .shutdown(). So if the UART was shutdown and then started up again, the mode set will remain and the Tx IRQ won't be unmasked. This caused a hang on at least Exynos5250, Exynos5420 and Exynos5800 when the system is rebooted or powered off. Fixes: ba019a3e2ad5 ("serial: samsung: remove redundant interrupt enabling") Signed-off-by: Javier Martinez Canillas Reviewed-by: Sylwester Nawrocki Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index af821a9..cf08876 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -963,6 +963,7 @@ static void s3c24xx_serial_shutdown(struct uart_port *port) free_irq(ourport->tx_irq, ourport); tx_enabled(port) = 0; ourport->tx_claimed = 0; + ourport->tx_mode = 0; } if (ourport->rx_claimed) { -- cgit v0.10.2 From 4e8f245937091b2c9eebf3d4909c9ceda4f0a78e Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 13 Mar 2015 14:51:50 +0100 Subject: tty: serial: fsl_lpuart: specify transmit FIFO size Specify transmit FIFO size which might be different depending on LPUART instance. This makes sure uart_wait_until_sent in serial core getting called, which in turn waits and checks if the FIFO is really empty on shutdown by using the tx_empty callback. Without the call of this callback, the last several characters might not yet be transmitted when closing the serial port. This can be reproduced by simply using echo and redirect the output to a ttyLP device. Signed-off-by: Stefan Agner Cc: stable Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index b1893f3..7ec9110 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -1076,6 +1076,8 @@ static int lpuart_startup(struct uart_port *port) sport->txfifo_size = 0x1 << (((temp >> UARTPFIFO_TXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); + sport->port.fifosize = sport->txfifo_size; + sport->rxfifo_size = 0x1 << (((temp >> UARTPFIFO_RXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); -- cgit v0.10.2 From 8e4934c6d6c659e22b1b746af4196683e77ce6ca Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 13 Mar 2015 14:51:51 +0100 Subject: tty: serial: fsl_lpuart: clear receive flag on FIFO flush When the receiver was enabled during startup, a character could have been in the FIFO when the UART get initially used. The driver configures the (receive) watermark level, and flushes the FIFO. However, the receive flag (RDRF) could still be set at that stage (as mentioned in the register description of UARTx_RWFIFO). This leads to an interrupt which won't be handled properly in interrupt mode: The receive interrupt function lpuart_rxint checks the FIFO count, which is 0 at that point (due to the flush during initialization). The problem does not manifest when using DMA to receive characters. Fix this situation by explicitly read the status register, which leads to clearing of the RDRF flag. Due to the flush just after the status flag read, a explicit data read is not to required. Signed-off-by: Stefan Agner Cc: stable Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 7ec9110..3ad1458 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -921,6 +921,9 @@ static void lpuart_setup_watermark(struct lpuart_port *sport) writeb(val | UARTPFIFO_TXFE | UARTPFIFO_RXFE, sport->port.membase + UARTPFIFO); + /* explicitly clear RDRF */ + readb(sport->port.membase + UARTSR1); + /* flush Tx and Rx FIFO */ writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH, sport->port.membase + UARTCFIFO); -- cgit v0.10.2 From dd38c1d4a17cda2486883cef7ec1bd84b5095260 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 24 Mar 2015 01:06:01 +0000 Subject: ARM: shmobile: armadillo800eva: Properly specify HDMI audio link format The DAI link format should be specified for the whole link rather than just one component on the link. So move the format specification for the HDMI audio link from the CPU component to the link itself. Since the sh-mobile-hdmi DAI driver doesn't implement the set_fmt() callback in this case there is no functional difference between only specifying the the format for the CPU side or for the whole link, but the later it will allow us to remove support for just specifying the format for one component. Signed-off-by: Lars-Peter Clausen Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Mark Brown diff --git a/arch/arm/mach-shmobile/board-armadillo800eva.c b/arch/arm/mach-shmobile/board-armadillo800eva.c index 6d949f1..75de26c 100644 --- a/arch/arm/mach-shmobile/board-armadillo800eva.c +++ b/arch/arm/mach-shmobile/board-armadillo800eva.c @@ -1040,9 +1040,9 @@ static struct asoc_simple_card_info fsi2_hdmi_info = { .card = "FSI2B-HDMI", .codec = "sh-mobile-hdmi", .platform = "sh_fsi2", + .daifmt = SND_SOC_DAIFMT_CBS_CFS, .cpu_dai = { .name = "fsib-dai", - .fmt = SND_SOC_DAIFMT_CBS_CFS, }, .codec_dai = { .name = "sh_mobile_hdmi-hifi", -- cgit v0.10.2 From 947a37cd38796f5b196a934353165a001cbcb0a9 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 24 Mar 2015 01:06:40 +0000 Subject: ARM: shmobile: armadillo800eva: fix clock inversion When operating in left-justfied mode both the frame-clock and the bit-clock need to be inverted to be standards compliant. This means that the exta clock inversion setting in the armadillo800eva machine driver for CPU component should now be removed. Signed-off-by: Lars-Peter Clausen Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Mark Brown diff --git a/arch/arm/mach-shmobile/board-armadillo800eva.c b/arch/arm/mach-shmobile/board-armadillo800eva.c index 75de26c..36aaeb1 100644 --- a/arch/arm/mach-shmobile/board-armadillo800eva.c +++ b/arch/arm/mach-shmobile/board-armadillo800eva.c @@ -1015,7 +1015,6 @@ static struct asoc_simple_card_info fsi_wm8978_info = { .platform = "sh_fsi2", .daifmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM, .cpu_dai = { - .fmt = SND_SOC_DAIFMT_IB_NF, .name = "fsia-dai", }, .codec_dai = { -- cgit v0.10.2 From 1efb53a220b78fdfdbb97b726a2156713e75bdab Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 24 Mar 2015 01:07:08 +0000 Subject: ASoC: simple-card: Remove support for setting differing DAI formats Having to set different formats on the CPU side and the CODEC side of a DAI link is usually indication that something is terribly wrong and in most cases is a result of a broken driver that implements a set_fmt() callback which does not follow the specification. In the past this feature has been used to work around broken drivers, rather than fixing them. We don't really want to encourage this, so remove support for setting different formats on both ends of the link. Along the way switch to static DAI format setup by setting the the dai_fmt field of the snd_soc_dai_link rather than calling snd_soc_dai_fmt(). Signed-off-by: Lars-Peter Clausen Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/include/sound/simple_card.h b/include/sound/simple_card.h index 1255ddb..b9b4f28 100644 --- a/include/sound/simple_card.h +++ b/include/sound/simple_card.h @@ -16,7 +16,6 @@ struct asoc_simple_dai { const char *name; - unsigned int fmt; unsigned int sysclk; int slots; int slot_width; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index f7c6734..3efd947 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -125,14 +125,6 @@ static int __asoc_simple_card_dai_init(struct snd_soc_dai *dai, { int ret; - if (set->fmt) { - ret = snd_soc_dai_set_fmt(dai, set->fmt); - if (ret && ret != -ENOTSUPP) { - dev_err(dai->dev, "simple-card: set_fmt error\n"); - goto err; - } - } - if (set->sysclk) { ret = snd_soc_dai_set_sysclk(dai, 0, set->sysclk, 0); if (ret && ret != -ENOTSUPP) { @@ -269,12 +261,10 @@ static int asoc_simple_card_parse_daifmt(struct device_node *node, struct device_node *codec, char *prefix, int idx) { + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); struct device *dev = simple_priv_to_dev(priv); struct device_node *bitclkmaster = NULL; struct device_node *framemaster = NULL; - struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx); - struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; - struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; unsigned int daifmt; daifmt = snd_soc_of_parse_daifmt(node, prefix, @@ -289,8 +279,7 @@ static int asoc_simple_card_parse_daifmt(struct device_node *node, */ dev_dbg(dev, "Revert to legacy daifmt parsing\n"); - cpu_dai->fmt = codec_dai->fmt = - snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | + daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); } else { if (codec == bitclkmaster) @@ -299,11 +288,10 @@ static int asoc_simple_card_parse_daifmt(struct device_node *node, else daifmt |= (codec == framemaster) ? SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; - - cpu_dai->fmt = daifmt; - codec_dai->fmt = daifmt; } + dai_link->dai_fmt = daifmt; + of_node_put(bitclkmaster); of_node_put(framemaster); @@ -379,13 +367,12 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, dai_link->init = asoc_simple_card_dai_init; dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); - dev_dbg(dev, "\tcpu : %s / %04x / %d\n", + dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt); + dev_dbg(dev, "\tcpu : %s / %d\n", dai_link->cpu_dai_name, - dai_props->cpu_dai.fmt, dai_props->cpu_dai.sysclk); - dev_dbg(dev, "\tcodec : %s / %04x / %d\n", + dev_dbg(dev, "\tcodec : %s / %d\n", dai_link->codec_dai_name, - dai_props->codec_dai.fmt, dai_props->codec_dai.sysclk); /* @@ -572,14 +559,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) dai_link->codec_name = cinfo->codec; dai_link->cpu_dai_name = cinfo->cpu_dai.name; dai_link->codec_dai_name = cinfo->codec_dai.name; + dai_link->dai_fmt = cinfo->daifmt; dai_link->init = asoc_simple_card_dai_init; memcpy(&priv->dai_props->cpu_dai, &cinfo->cpu_dai, sizeof(priv->dai_props->cpu_dai)); memcpy(&priv->dai_props->codec_dai, &cinfo->codec_dai, sizeof(priv->dai_props->codec_dai)); - priv->dai_props->cpu_dai.fmt |= cinfo->daifmt; - priv->dai_props->codec_dai.fmt |= cinfo->daifmt; } snd_soc_card_set_drvdata(&priv->snd_card, priv); -- cgit v0.10.2 From c6ac19dac2683a8b06aebcc10cb1c711b555a949 Mon Sep 17 00:00:00 2001 From: Grygorii Strashko Date: Tue, 24 Mar 2015 20:42:42 +0200 Subject: gpio: syscon: reduce message level when direction reg offset not in dt Now GPIO syscon driver produces bunch of warnings during the boot of Kesytone 2 SoCs: gpio-syscon soc:keystone_dsp_gpio@02620240: can't read the dir register offset! gpio-syscon soc:keystone_dsp_gpio@2620244: can't read the dir register offset! This message unintentionally was added using dev_err(), but its actual log level is debug, because third cell of "ti,syscon-dev" is optional. Hence change it to dev_dbg() as it should be. This patch fixes commit: 5a3e3f8 ("gpio: syscon: retriave syscon node and regs offsets from dt") Reported-by: Russell King Tested-by: Murali Karicheri Acked-by: Santosh Shilimkar Signed-off-by: Grygorii Strashko Signed-off-by: Linus Walleij diff --git a/drivers/gpio/gpio-syscon.c b/drivers/gpio/gpio-syscon.c index 257e298..045a952 100644 --- a/drivers/gpio/gpio-syscon.c +++ b/drivers/gpio/gpio-syscon.c @@ -219,7 +219,7 @@ static int syscon_gpio_probe(struct platform_device *pdev) ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2, &priv->dir_reg_offset); if (ret) - dev_err(dev, "can't read the dir register offset!\n"); + dev_dbg(dev, "can't read the dir register offset!\n"); priv->dir_reg_offset <<= 3; } -- cgit v0.10.2 From bfbaafae8519d82d10da6abe75f5766dd5b20475 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 20 Mar 2015 09:59:47 +0100 Subject: firmware: dmi_scan: Prevent dmi_num integer overflow dmi_num is a u16, dmi_len is a u32, so this construct: dmi_num = dmi_len / 4; would result in an integer overflow for a DMI table larger than 256 kB. I've never see such a large table so far, but SMBIOS 3.0 makes it possible so maybe we'll see such tables in the future. So instead of faking a structure count when the entry point does not provide it, adjust the loop condition in dmi_table() to properly deal with the case where dmi_num is not set. This bug was introduced with the initial SMBIOS 3.0 support in commit fc43026278b2 ("dmi: add support for SMBIOS 3.0 64-bit entry point"). Signed-off-by: Jean Delvare Cc: Matt Fleming Cc: Ivan Khoronzhuk Cc: Acked-by: Ard Biesheuvel Signed-off-by: Matt Fleming diff --git a/drivers/firmware/dmi_scan.c b/drivers/firmware/dmi_scan.c index 69fac06..2eebd28b 100644 --- a/drivers/firmware/dmi_scan.c +++ b/drivers/firmware/dmi_scan.c @@ -86,10 +86,13 @@ static void dmi_table(u8 *buf, u32 len, int num, int i = 0; /* - * Stop when we see all the items the table claimed to have - * OR we run off the end of the table (also happens) + * Stop when we have seen all the items the table claimed to have + * (SMBIOS < 3.0 only) OR we reach an end-of-table marker OR we run + * off the end of the table (should never happen but sometimes does + * on bogus implementations.) */ - while ((i < num) && (data - buf + sizeof(struct dmi_header)) <= len) { + while ((!num || i < num) && + (data - buf + sizeof(struct dmi_header)) <= len) { const struct dmi_header *dm = (const struct dmi_header *)data; /* @@ -529,21 +532,10 @@ static int __init dmi_smbios3_present(const u8 *buf) if (memcmp(buf, "_SM3_", 5) == 0 && buf[6] < 32 && dmi_checksum(buf, buf[6])) { dmi_ver = get_unaligned_be16(buf + 7); + dmi_num = 0; /* No longer specified */ dmi_len = get_unaligned_le32(buf + 12); dmi_base = get_unaligned_le64(buf + 16); - /* - * The 64-bit SMBIOS 3.0 entry point no longer has a field - * containing the number of structures present in the table. - * Instead, it defines the table size as a maximum size, and - * relies on the end-of-table structure type (#127) to be used - * to signal the end of the table. - * So let's define dmi_num as an upper bound as well: each - * structure has a 4 byte header, so dmi_len / 4 is an upper - * bound for the number of structures in the table. - */ - dmi_num = dmi_len / 4; - if (dmi_walk_early(dmi_decode) == 0) { pr_info("SMBIOS %d.%d present.\n", dmi_ver >> 8, dmi_ver & 0xFF); -- cgit v0.10.2 From a901125c65544aa05c52e1a7388c3900e8af105f Mon Sep 17 00:00:00 2001 From: "Yan, Zheng" Date: Fri, 27 Mar 2015 10:34:20 +0800 Subject: locks: fix file_lock deletion inside loop locks_delete_lock_ctx() is called inside the loop, so we should use list_for_each_entry_safe. Fixes: 8634b51f6ca2 (locks: convert lease handling to file_lock_context) Signed-off-by: "Yan, Zheng" Signed-off-by: Jeff Layton diff --git a/fs/locks.c b/fs/locks.c index 528fedf..40bc384 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1388,9 +1388,8 @@ any_leases_conflict(struct inode *inode, struct file_lock *breaker) int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) { int error = 0; - struct file_lock *new_fl; struct file_lock_context *ctx = inode->i_flctx; - struct file_lock *fl; + struct file_lock *new_fl, *fl, *tmp; unsigned long break_time; int want_write = (mode & O_ACCMODE) != O_RDONLY; LIST_HEAD(dispose); @@ -1420,7 +1419,7 @@ int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) break_time++; /* so that 0 means no break time */ } - list_for_each_entry(fl, &ctx->flc_lease, fl_list) { + list_for_each_entry_safe(fl, tmp, &ctx->flc_lease, fl_list) { if (!leases_conflict(fl, new_fl)) continue; if (want_write) { -- cgit v0.10.2 From ad692b46dbf122ef90aadce3b389ef64c90e861d Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 26 Mar 2015 10:42:00 +0200 Subject: drm/edid: set ELD for firmware and debugfs override EDIDs If the user supplies EDID through firmware or debugfs override, the driver callbacks are bypassed and the connector ELD does not get updated, and audio fails. Set ELD for firmware and debugfs EDIDs too. There should be no harm in gratuitously doing this for non HDMI/DP connectors, as it's still up to the driver to use the ELD, if any. Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=82349 Reference: https://bugs.freedesktop.org/show_bug.cgi?id=80691 Reported-by: Emil Reported-by: Rob Engle Tested-by: Jolan Luff Reviewed-by: Daniel Vetter Cc: stable@vger.kernel.org Signed-off-by: Jani Nikula diff --git a/drivers/gpu/drm/drm_edid_load.c b/drivers/gpu/drm/drm_edid_load.c index 732cb6f..4c0aa97 100644 --- a/drivers/gpu/drm/drm_edid_load.c +++ b/drivers/gpu/drm/drm_edid_load.c @@ -287,6 +287,7 @@ int drm_load_edid_firmware(struct drm_connector *connector) drm_mode_connector_update_edid_property(connector, edid); ret = drm_add_edid_modes(connector, edid); + drm_edid_to_eld(connector, edid); kfree(edid); return ret; diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index 6591d48..3fee587 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -174,6 +174,7 @@ static int drm_helper_probe_single_connector_modes_merge_bits(struct drm_connect struct edid *edid = (struct edid *) connector->edid_blob_ptr->data; count = drm_add_edid_modes(connector, edid); + drm_edid_to_eld(connector, edid); } else count = (*connector_funcs->get_modes)(connector); } -- cgit v0.10.2 From 3899ca844b82fb201fb764f56eec483acb59a29c Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 18 Mar 2015 17:05:10 -0400 Subject: drm/radeon/dpm: fix 120hz handling harder Need to expand the check to handle short circuiting if the selected state is the same as current state. bug: https://bugs.freedesktop.org/show_bug.cgi?id=87796 Signed-off-by: Alex Deucher Cc: stable@vger.kernel.org diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index 5587603..33d5a4f 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h @@ -1565,6 +1565,7 @@ struct radeon_dpm { int new_active_crtc_count; u32 current_active_crtcs; int current_active_crtc_count; + bool single_display; struct radeon_dpm_dynamic_state dyn_state; struct radeon_dpm_fan fan; u32 tdp_limit; diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c index 33cf410..c1ba83a 100644 --- a/drivers/gpu/drm/radeon/radeon_pm.c +++ b/drivers/gpu/drm/radeon/radeon_pm.c @@ -837,12 +837,8 @@ static void radeon_dpm_thermal_work_handler(struct work_struct *work) radeon_pm_compute_clocks(rdev); } -static struct radeon_ps *radeon_dpm_pick_power_state(struct radeon_device *rdev, - enum radeon_pm_state_type dpm_state) +static bool radeon_dpm_single_display(struct radeon_device *rdev) { - int i; - struct radeon_ps *ps; - u32 ui_class; bool single_display = (rdev->pm.dpm.new_active_crtc_count < 2) ? true : false; @@ -858,6 +854,17 @@ static struct radeon_ps *radeon_dpm_pick_power_state(struct radeon_device *rdev, if (single_display && (r600_dpm_get_vrefresh(rdev) >= 120)) single_display = false; + return single_display; +} + +static struct radeon_ps *radeon_dpm_pick_power_state(struct radeon_device *rdev, + enum radeon_pm_state_type dpm_state) +{ + int i; + struct radeon_ps *ps; + u32 ui_class; + bool single_display = radeon_dpm_single_display(rdev); + /* certain older asics have a separare 3D performance state, * so try that first if the user selected performance */ @@ -983,6 +990,7 @@ static void radeon_dpm_change_power_state_locked(struct radeon_device *rdev) struct radeon_ps *ps; enum radeon_pm_state_type dpm_state; int ret; + bool single_display = radeon_dpm_single_display(rdev); /* if dpm init failed */ if (!rdev->pm.dpm_enabled) @@ -1007,6 +1015,9 @@ static void radeon_dpm_change_power_state_locked(struct radeon_device *rdev) /* vce just modifies an existing state so force a change */ if (ps->vce_active != rdev->pm.dpm.vce_active) goto force; + /* user has made a display change (such as timing) */ + if (rdev->pm.dpm.single_display != single_display) + goto force; if ((rdev->family < CHIP_BARTS) || (rdev->flags & RADEON_IS_IGP)) { /* for pre-BTC and APUs if the num crtcs changed but state is the same, * all we need to do is update the display configuration. @@ -1069,6 +1080,7 @@ force: rdev->pm.dpm.current_active_crtcs = rdev->pm.dpm.new_active_crtcs; rdev->pm.dpm.current_active_crtc_count = rdev->pm.dpm.new_active_crtc_count; + rdev->pm.dpm.single_display = single_display; /* wait for the rings to drain */ for (i = 0; i < RADEON_NUM_RINGS; i++) { -- cgit v0.10.2 From f2c9e560b406f2f6b14b345c7da33467dee9cdf2 Mon Sep 17 00:00:00 2001 From: David Miller Date: Wed, 18 Mar 2015 23:18:40 -0400 Subject: radeon: Do not directly dereference pointers to BIOS area. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use readb() and memcpy_fromio() accessors instead. Reviewed-by: Christian König Signed-off-by: David S. Miller Signed-off-by: Alex Deucher Cc: stable@vger.kernel.org diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 63ccb8f..d27e4cc 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -76,7 +76,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) static bool radeon_read_bios(struct radeon_device *rdev) { - uint8_t __iomem *bios; + uint8_t __iomem *bios, val1, val2; size_t size; rdev->bios = NULL; @@ -86,15 +86,19 @@ static bool radeon_read_bios(struct radeon_device *rdev) return false; } - if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) { + val1 = readb(&bios[0]); + val2 = readb(&bios[1]); + + if (size == 0 || val1 != 0x55 || val2 != 0xaa) { pci_unmap_rom(rdev->pdev, bios); return false; } - rdev->bios = kmemdup(bios, size, GFP_KERNEL); + rdev->bios = kzalloc(size, GFP_KERNEL); if (rdev->bios == NULL) { pci_unmap_rom(rdev->pdev, bios); return false; } + memcpy_fromio(rdev->bios, bios, size); pci_unmap_rom(rdev->pdev, bios); return true; } -- cgit v0.10.2 From 1b01fc347ae7d2b76ac849f7040a32582cb5aee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Mon, 23 Mar 2015 11:32:59 +0100 Subject: drm/radeon: always dump the ring content if it's available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dumping is still possible if a ring isn't ready, only when it isn't allocated at all we need to abort here. Signed-off-by: Christian König Signed-off-by: Alex Deucher diff --git a/drivers/gpu/drm/radeon/radeon_ring.c b/drivers/gpu/drm/radeon/radeon_ring.c index 2456f69..8c78723 100644 --- a/drivers/gpu/drm/radeon/radeon_ring.c +++ b/drivers/gpu/drm/radeon/radeon_ring.c @@ -495,7 +495,7 @@ static int radeon_debugfs_ring_info(struct seq_file *m, void *data) seq_printf(m, "%u free dwords in ring\n", ring->ring_free_dw); seq_printf(m, "%u dwords in ring\n", count); - if (!ring->ready) + if (!ring->ring) return 0; /* print 8 dw before current rptr as often it's the last executed -- cgit v0.10.2 From 88f9eae4d74c1b5c35adb026d7f0d4d44d2fdf70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Mon, 23 Mar 2015 11:33:00 +0100 Subject: drm/radeon: programm the VCE fw BAR as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise the VCE firmware needs to be in the first 256MB of VRAM. Signed-off-by: Christian König Signed-off-by: Alex Deucher diff --git a/drivers/gpu/drm/radeon/cikd.h b/drivers/gpu/drm/radeon/cikd.h index c648e19..243a36c 100644 --- a/drivers/gpu/drm/radeon/cikd.h +++ b/drivers/gpu/drm/radeon/cikd.h @@ -2129,6 +2129,7 @@ #define VCE_UENC_REG_CLOCK_GATING 0x207c0 #define VCE_SYS_INT_EN 0x21300 # define VCE_SYS_INT_TRAP_INTERRUPT_EN (1 << 3) +#define VCE_LMI_VCPU_CACHE_40BIT_BAR 0x2145c #define VCE_LMI_CTRL2 0x21474 #define VCE_LMI_CTRL 0x21498 #define VCE_LMI_VM_CTRL 0x214a0 diff --git a/drivers/gpu/drm/radeon/vce_v2_0.c b/drivers/gpu/drm/radeon/vce_v2_0.c index 1ac7bb8..fbbe78f 100644 --- a/drivers/gpu/drm/radeon/vce_v2_0.c +++ b/drivers/gpu/drm/radeon/vce_v2_0.c @@ -156,6 +156,9 @@ int vce_v2_0_resume(struct radeon_device *rdev) WREG32(VCE_LMI_SWAP_CNTL1, 0); WREG32(VCE_LMI_VM_CTRL, 0); + WREG32(VCE_LMI_VCPU_CACHE_40BIT_BAR, addr >> 8); + + addr &= 0xff; size = RADEON_GPU_PAGE_ALIGN(rdev->vce_fw->size); WREG32(VCE_VCPU_CACHE_OFFSET0, addr & 0x7fffffff); WREG32(VCE_VCPU_CACHE_SIZE0, size); -- cgit v0.10.2 From 2c0ed6349287a15f7be73bba00e520106087cd1b Mon Sep 17 00:00:00 2001 From: "Lu, Han" Date: Fri, 27 Mar 2015 15:03:57 +0800 Subject: ASoC: Intel: fix warning reported by static check tool smatch The smatch tool report warning: ... CHECK sound/soc/intel/sst-haswell-pcm.c sound/soc/intel/sst-haswell-pcm.c:1110 hsw_pcm_probe() error: buffer overflow\ 'hsw_dais' 4 <= 4 sound/soc/intel/sst-haswell-pcm.c:1112 hsw_pcm_probe() error: buffer overflow\ 'hsw_dais' 4 <= 4 ... fix it by use its own struct member for post-process module, rather than sharing unused pcm member. Signed-off-by: Lu, Han Acked-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index 6c6229a..31ffc0f 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -137,6 +137,7 @@ struct hsw_priv_data { struct device *dev; enum hsw_pm_state pm_state; struct snd_soc_card *soc_card; + struct sst_module_runtime *runtime_waves; /* sound effect module */ /* page tables */ struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; @@ -902,13 +903,10 @@ static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) /* create runtime blocks for module waves */ if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { - pcm_data = &pdata->pcm[HSW_PCM_COUNT-1][0]; - pcm_data->runtime = sst_hsw_runtime_module_create(hsw, - SST_HSW_MODULE_WAVES, pcm_data->persistent_offset); - if (pcm_data->runtime == NULL) + pdata->runtime_waves = sst_hsw_runtime_module_create(hsw, + SST_HSW_MODULE_WAVES, 0); + if (pdata->runtime_waves == NULL) goto err; - pcm_data->persistent_offset = - pcm_data->runtime->persistent_offset; } return 0; @@ -933,8 +931,7 @@ static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) sst_hsw_runtime_module_free(pcm_data->runtime); } if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { - pcm_data = &pdata->pcm[HSW_PCM_COUNT-1][0]; - sst_hsw_runtime_module_free(pcm_data->runtime); + sst_hsw_runtime_module_free(pdata->runtime_waves); } } -- cgit v0.10.2 From 57bf27365c56b547097253004768904419df6c69 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 27 Mar 2015 20:19:06 +0800 Subject: ASoC: rt5645: Redefine format config for rt5650 rt5650 and rt5645 use different register bits for format configuration. This patch modifies rt5645_hw_params and rt5645_set_dai_fmt to support both codecs. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 4c384a1..0133c8c 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2049,7 +2049,7 @@ static int rt5645_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_codec *codec = dai->codec; struct rt5645_priv *rt5645 = snd_soc_codec_get_drvdata(codec); - unsigned int val_len = 0, val_clk, mask_clk; + unsigned int val_len = 0, val_clk, mask_clk, dl_sft; int pre_div, bclk_ms, frame_size; rt5645->lrck[dai->id] = params_rate(params); @@ -2063,6 +2063,16 @@ static int rt5645_hw_params(struct snd_pcm_substream *substream, dev_err(codec->dev, "Unsupported frame size: %d\n", frame_size); return -EINVAL; } + + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5650: + dl_sft = 4; + break; + default: + dl_sft = 2; + break; + } + bclk_ms = frame_size > 32; rt5645->bclk[dai->id] = rt5645->lrck[dai->id] * (32 << bclk_ms); @@ -2075,13 +2085,13 @@ static int rt5645_hw_params(struct snd_pcm_substream *substream, case 16: break; case 20: - val_len |= RT5645_I2S_DL_20; + val_len = 0x1; break; case 24: - val_len |= RT5645_I2S_DL_24; + val_len = 0x2; break; case 8: - val_len |= RT5645_I2S_DL_8; + val_len = 0x3; break; default: return -EINVAL; @@ -2093,7 +2103,7 @@ static int rt5645_hw_params(struct snd_pcm_substream *substream, val_clk = bclk_ms << RT5645_I2S_BCLK_MS1_SFT | pre_div << RT5645_I2S_PD1_SFT; snd_soc_update_bits(codec, RT5645_I2S1_SDP, - RT5645_I2S_DL_MASK, val_len); + (0x3 << dl_sft), (val_len << dl_sft)); snd_soc_update_bits(codec, RT5645_ADDA_CLK1, mask_clk, val_clk); break; case RT5645_AIF2: @@ -2101,7 +2111,7 @@ static int rt5645_hw_params(struct snd_pcm_substream *substream, val_clk = bclk_ms << RT5645_I2S_BCLK_MS2_SFT | pre_div << RT5645_I2S_PD2_SFT; snd_soc_update_bits(codec, RT5645_I2S2_SDP, - RT5645_I2S_DL_MASK, val_len); + (0x3 << dl_sft), (val_len << dl_sft)); snd_soc_update_bits(codec, RT5645_ADDA_CLK1, mask_clk, val_clk); break; default: @@ -2116,7 +2126,16 @@ static int rt5645_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; struct rt5645_priv *rt5645 = snd_soc_codec_get_drvdata(codec); - unsigned int reg_val = 0; + unsigned int reg_val = 0, pol_sft; + + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5650: + pol_sft = 8; + break; + default: + pol_sft = 7; + break; + } switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: @@ -2134,7 +2153,7 @@ static int rt5645_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_NF: - reg_val |= RT5645_I2S_BP_INV; + reg_val |= (1 << pol_sft); break; default: return -EINVAL; @@ -2158,12 +2177,12 @@ static int rt5645_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) switch (dai->id) { case RT5645_AIF1: snd_soc_update_bits(codec, RT5645_I2S1_SDP, - RT5645_I2S_MS_MASK | RT5645_I2S_BP_MASK | + RT5645_I2S_MS_MASK | (1 << pol_sft) | RT5645_I2S_DF_MASK, reg_val); break; case RT5645_AIF2: snd_soc_update_bits(codec, RT5645_I2S2_SDP, - RT5645_I2S_MS_MASK | RT5645_I2S_BP_MASK | + RT5645_I2S_MS_MASK | (1 << pol_sft) | RT5645_I2S_DF_MASK, reg_val); break; default: -- cgit v0.10.2 From afefc12801e501fea90f1d9a678e0985f47dc1bf Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 27 Mar 2015 20:19:07 +0800 Subject: ASoC: rt5645: Set use_single_rw flag for regmap RT5645 doesn't support auto incrementing writes so driver should set the use_single_rw flag for regmap. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 0133c8c..f9edf09 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2633,7 +2633,7 @@ static struct snd_soc_codec_driver soc_codec_dev_rt5645 = { static const struct regmap_config rt5645_regmap = { .reg_bits = 8, .val_bits = 16, - + .use_single_rw = true, .max_register = RT5645_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5645_ranges) * RT5645_PR_SPACING), .volatile_reg = rt5645_volatile_register, -- cgit v0.10.2 From 1b5d0160e8f17db0714016a2550d3b1d65c70c3e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 27 Mar 2015 20:19:08 +0800 Subject: ASoC: rt5645: Use update_bits for bit control In codec bias level off, we need to disable gate mode with MCLK for power saving. It is set by one bit. We don't need to write while register for that. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index f9edf09..b6d5b95 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2396,7 +2396,8 @@ static int rt5645_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_OFF: snd_soc_write(codec, RT5645_DEPOP_M2, 0x1100); - snd_soc_write(codec, RT5645_GEN_CTRL1, 0x0128); + snd_soc_update_bits(codec, RT5645_GEN_CTRL1, + RT5645_DIG_GATE_CTRL, 0); snd_soc_update_bits(codec, RT5645_PWR_ANLG1, RT5645_PWR_VREF1 | RT5645_PWR_MB | RT5645_PWR_BG | RT5645_PWR_VREF2 | -- cgit v0.10.2 From 373225510f9608150a18b3491e756fbf3f58ff24 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 27 Mar 2015 20:19:09 +0800 Subject: ASoC: rt5645: Restore HP depop setting in HP off This driver will set RT5645_DEPOP_MAN bit in headphone power up depop process. We need to restore it in headphone power down process. Otherwise, we will get headphone noise when push button function is enabled. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index b6d5b95..69528ae 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -1270,6 +1270,8 @@ static void hp_amp_power(struct snd_soc_codec *codec, int on) snd_soc_update_bits(codec, RT5645_PWR_ANLG1, RT5645_PWR_HP_L | RT5645_PWR_HP_R | RT5645_PWR_HA, 0); + snd_soc_update_bits(codec, RT5645_DEPOP_M2, + RT5645_DEPOP_MASK, 0); } } } -- cgit v0.10.2 From ff7f53fb82a7801a778e5902bdbbc5e195ab0de0 Mon Sep 17 00:00:00 2001 From: "Martin K. Petersen" Date: Fri, 27 Mar 2015 15:17:20 -0400 Subject: libata: Update Crucial/Micron blacklist Micron has released an updated firmware (MU02) for M510/M550/MX100 drives to fix the issues with queued TRIM. Queued TRIM remains broken on M500 but is working fine on later drives such as M600 and MX200. Tweak our blacklist to reflect the above. Link: https://bugzilla.kernel.org/show_bug.cgi?id=71371 Signed-off-by: Martin K. Petersen Cc: stable@vger.kernel.org Signed-off-by: Tejun Heo diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index ef150eb..86374df 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -4204,9 +4204,16 @@ static const struct ata_blacklist_entry ata_device_blacklist [] = { { "PIONEER DVD-RW DVR-216D", NULL, ATA_HORKAGE_NOSETXFER }, /* devices that don't properly handle queued TRIM commands */ - { "Micron_M[56]*", NULL, ATA_HORKAGE_NO_NCQ_TRIM | + { "Micron_M500*", NULL, ATA_HORKAGE_NO_NCQ_TRIM | + ATA_HORKAGE_ZERO_AFTER_TRIM, }, + { "Crucial_CT*M500*", NULL, ATA_HORKAGE_NO_NCQ_TRIM | + ATA_HORKAGE_ZERO_AFTER_TRIM, }, + { "Micron_M5[15]0*", "MU01", ATA_HORKAGE_NO_NCQ_TRIM | + ATA_HORKAGE_ZERO_AFTER_TRIM, }, + { "Crucial_CT*M550*", "MU01", ATA_HORKAGE_NO_NCQ_TRIM | + ATA_HORKAGE_ZERO_AFTER_TRIM, }, + { "Crucial_CT*MX100*", "MU01", ATA_HORKAGE_NO_NCQ_TRIM | ATA_HORKAGE_ZERO_AFTER_TRIM, }, - { "Crucial_CT*SSD*", NULL, ATA_HORKAGE_NO_NCQ_TRIM, }, /* * As defined, the DRAT (Deterministic Read After Trim) and RZAT @@ -4226,6 +4233,8 @@ static const struct ata_blacklist_entry ata_device_blacklist [] = { */ { "INTEL*SSDSC2MH*", NULL, 0, }, + { "Micron*", NULL, ATA_HORKAGE_ZERO_AFTER_TRIM, }, + { "Crucial*", NULL, ATA_HORKAGE_ZERO_AFTER_TRIM, }, { "INTEL*SSD*", NULL, ATA_HORKAGE_ZERO_AFTER_TRIM, }, { "SSD*INTEL*", NULL, ATA_HORKAGE_ZERO_AFTER_TRIM, }, { "Samsung*SSD*", NULL, ATA_HORKAGE_ZERO_AFTER_TRIM, }, -- cgit v0.10.2 From 6fc4d97a4987c5d247655a157a9377996626221a Mon Sep 17 00:00:00 2001 From: "Martin K. Petersen" Date: Fri, 27 Mar 2015 15:17:21 -0400 Subject: libata: Blacklist queued TRIM on Samsung SSD 850 Pro Blacklist queued TRIM on this drive for now. Reported-by: Stefan Keller Signed-off-by: Martin K. Petersen CC: stable@vger.kernel.org Signed-off-by: Tejun Heo diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 86374df..23dac3b 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -4214,6 +4214,8 @@ static const struct ata_blacklist_entry ata_device_blacklist [] = { ATA_HORKAGE_ZERO_AFTER_TRIM, }, { "Crucial_CT*MX100*", "MU01", ATA_HORKAGE_NO_NCQ_TRIM | ATA_HORKAGE_ZERO_AFTER_TRIM, }, + { "Samsung SSD 850 PRO*", NULL, ATA_HORKAGE_NO_NCQ_TRIM | + ATA_HORKAGE_ZERO_AFTER_TRIM, }, /* * As defined, the DRAT (Deterministic Read After Trim) and RZAT -- cgit v0.10.2 From 5116ede10dc92d7a11a38fc59f545c24a33809cd Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 27 Mar 2015 17:25:21 +0800 Subject: ASoC: max98925: Fix bit-width 24 settings in max98925_dai_hw_params Trivial typo fix. Signed-off-by: Axel Lin Anish Kumar Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c index 0796dfa..9b5a17d 100644 --- a/sound/soc/codecs/max98925.c +++ b/sound/soc/codecs/max98925.c @@ -442,8 +442,8 @@ static int max98925_dai_hw_params(struct snd_pcm_substream *substream, case 24: regmap_update_bits(max98925->regmap, MAX98925_FORMAT, - M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_32); - max98925->ch_size = 32; + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_24); + max98925->ch_size = 24; break; case 32: regmap_update_bits(max98925->regmap, -- cgit v0.10.2 From 415f1cb29d3be865b034b528058c7115bc262f43 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 26 Mar 2015 04:01:27 +0000 Subject: ASoC: rsrc-card: add Renesas sampling rate convert sound card support Renesas sound card has "sampling rate convert" feature which should be implemented via DPCM. But, sound card driver point of view, it is difficult to add this DPCM feature on simple-card driver. Especially, DT binding support is very difficult. This patch implements DPCM feature on DT as Renesas specific sound card. This new driver is copied from current simple-card driver. Main difference between simple-card and this driver are... 1. removed unused feature from simple-card 2. removed driver named prefix from DT property 3. CPU will be FE, CODEC will be BE with snd-soc-dummy 4. it supports sampling rate convert via .be_hw_params_fixup 5. board specific routing is implemented in driver Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt b/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt new file mode 100644 index 0000000..12e287e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt @@ -0,0 +1,66 @@ +Renesas Sampling Rate Convert Sound Card: + +Renesas Sampling Rate Convert Sound Card specifies audio DAI connections of SoC <-> codec. + +Required properties: + +- compatible : "renesas,rsrc-card," + Examples with soctypes are: + - "renesas,rsrc-card,lager" + - "renesas,rsrc-card,koelsch" +Optional properties: + +- card_name : User specified audio sound card name, one string + property. +- cpu : CPU sub-node +- codec : CODEC sub-node + +Optional subnode properties: + +- format : CPU/CODEC common audio format. + "i2s", "right_j", "left_j" , "dsp_a" + "dsp_b", "ac97", "pdm", "msb", "lsb" +- frame-master : Indicates dai-link frame master. + phandle to a cpu or codec subnode. +- bitclock-master : Indicates dai-link bit clock master. + phandle to a cpu or codec subnode. +- bitclock-inversion : bool property. Add this if the + dai-link uses bit clock inversion. +- frame-inversion : bool property. Add this if the + dai-link uses frame clock inversion. + +Required CPU/CODEC subnodes properties: + +- sound-dai : phandle and port of CPU/CODEC + +Optional CPU/CODEC subnodes properties: + +- clocks / system-clock-frequency : specify subnode's clock if needed. + it can be specified via "clocks" if system has + clock node (= common clock), or "system-clock-frequency" + (if system doens't support common clock) + If a clock is specified, it is + enabled with clk_prepare_enable() + in dai startup() and disabled with + clk_disable_unprepare() in dai + shutdown(). + +Example + +sound { + compatible = "renesas,rsrc-card,lager"; + + card-name = "rsnd-ak4643"; + format = "left_j"; + bitclock-master = <&sndcodec>; + frame-master = <&sndcodec>; + + sndcpu: cpu { + sound-dai = <&rcar_sound>; + }; + + sndcodec: codec { + sound-dai = <&ak4643>; + system-clock-frequency = <11289600>; + }; +}; diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index 80245b6..2b30304 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -41,6 +41,11 @@ config SND_SOC_RCAR help This option enables R-Car SUR/SCU/SSIU/SSI sound support +config SND_SOC_RSRC_CARD + tristate "Renesas Sampling Rate Convert Sound Card" + help + This option enables simple sound if you need sampling rate convert + ## ## Boards ## diff --git a/sound/soc/sh/rcar/Makefile b/sound/soc/sh/rcar/Makefile index 7b20492..f1b4451 100644 --- a/sound/soc/sh/rcar/Makefile +++ b/sound/soc/sh/rcar/Makefile @@ -1,2 +1,5 @@ snd-soc-rcar-objs := core.o gen.o dma.o src.o adg.o ssi.o dvc.o -obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o \ No newline at end of file +obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o + +snd-soc-rsrc-card-objs := rsrc-card.o +obj-$(CONFIG_SND_SOC_RSRC_CARD) += snd-soc-rsrc-card.o diff --git a/sound/soc/sh/rcar/rsrc-card.c b/sound/soc/sh/rcar/rsrc-card.c new file mode 100644 index 0000000..3baeab7 --- /dev/null +++ b/sound/soc/sh/rcar/rsrc-card.c @@ -0,0 +1,489 @@ +/* + * Renesas Sampling Rate Convert Sound Card for DPCM + * + * Copyright (C) 2015 Renesas Solutions Corp. + * Kuninori Morimoto + * + * based on ${LINUX}/sound/soc/generic/simple-card.c + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct rsrc_card_of_data { + const char *prefix; + const struct snd_soc_dapm_route *routes; + int num_routes; +}; + +static const struct snd_soc_dapm_route routes_ssi0_ak4642[] = { + {"ak4642 Playback", NULL, "DAI0 Playback"}, + {"DAI0 Capture", NULL, "ak4642 Capture"}, +}; + +static const struct rsrc_card_of_data routes_of_ssi0_ak4642 = { + .prefix = "ak4642", + .routes = routes_ssi0_ak4642, + .num_routes = ARRAY_SIZE(routes_ssi0_ak4642), +}; + +static const struct of_device_id rsrc_card_of_match[] = { + { .compatible = "renesas,rsrc-card,lager", .data = &routes_of_ssi0_ak4642 }, + { .compatible = "renesas,rsrc-card,koelsch", .data = &routes_of_ssi0_ak4642 }, + {}, +}; +MODULE_DEVICE_TABLE(of, rsrc_card_of_match); + +struct rsrc_card_dai { + const char *name; + unsigned int fmt; + unsigned int sysclk; + struct clk *clk; +}; + +#define RSRC_FB_NUM 2 /* FE/BE */ +#define IDX_CPU 0 +#define IDX_CODEC 1 +struct rsrc_card_priv { + struct snd_soc_card snd_card; + struct rsrc_card_dai_props { + struct rsrc_card_dai cpu_dai; + struct rsrc_card_dai codec_dai; + } dai_props[RSRC_FB_NUM]; + struct snd_soc_codec_conf codec_conf; + struct snd_soc_dai_link dai_link[RSRC_FB_NUM]; +}; + +#define rsrc_priv_to_dev(priv) ((priv)->snd_card.dev) +#define rsrc_priv_to_link(priv, i) ((priv)->snd_card.dai_link + i) +#define rsrc_priv_to_props(priv, i) ((priv)->dai_props + i) +#define rsrc_dev_to_of_data(dev) (of_match_device(rsrc_card_of_match, (dev))->data) + +static int rsrc_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct rsrc_card_dai_props *dai_props = + &priv->dai_props[rtd - rtd->card->rtd]; + int ret; + + ret = clk_prepare_enable(dai_props->cpu_dai.clk); + if (ret) + return ret; + + ret = clk_prepare_enable(dai_props->codec_dai.clk); + if (ret) + clk_disable_unprepare(dai_props->cpu_dai.clk); + + return ret; +} + +static void rsrc_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct rsrc_card_dai_props *dai_props = + &priv->dai_props[rtd - rtd->card->rtd]; + + clk_disable_unprepare(dai_props->cpu_dai.clk); + + clk_disable_unprepare(dai_props->codec_dai.clk); +} + +static struct snd_soc_ops rsrc_card_ops = { + .startup = rsrc_card_startup, + .shutdown = rsrc_card_shutdown, +}; + +static int __rsrc_card_dai_init(struct snd_soc_dai *dai, + struct rsrc_card_dai *set) +{ + int ret; + + if (set->fmt) { + ret = snd_soc_dai_set_fmt(dai, set->fmt); + if (ret && ret != -ENOTSUPP) { + dev_err(dai->dev, "set_fmt error\n"); + goto err; + } + } + + if (set->sysclk) { + ret = snd_soc_dai_set_sysclk(dai, 0, set->sysclk, 0); + if (ret && ret != -ENOTSUPP) { + dev_err(dai->dev, "set_sysclk error\n"); + goto err; + } + } + + ret = 0; + +err: + return ret; +} + +static int rsrc_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + struct rsrc_card_dai_props *dai_props; + int num, ret; + + num = rtd - rtd->card->rtd; + dai_props = &priv->dai_props[num]; + ret = __rsrc_card_dai_init(codec, &dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = __rsrc_card_dai_init(cpu, &dai_props->cpu_dai); + if (ret < 0) + return ret; + + return 0; +} + +static int +rsrc_card_sub_parse_of(struct rsrc_card_priv *priv, + struct device_node *np, + struct rsrc_card_dai *dai, + struct snd_soc_dai_link *dai_link, + int *args_count) +{ + struct device *dev = rsrc_priv_to_dev(priv); + const struct rsrc_card_of_data *of_data = rsrc_dev_to_of_data(dev); + struct of_phandle_args args; + struct device_node **p_node; + struct clk *clk; + const char **dai_name; + const char **name; + u32 val; + int ret; + + if (args_count) { + p_node = &dai_link->cpu_of_node; + dai_name = &dai_link->cpu_dai_name; + name = &dai_link->cpu_name; + } else { + p_node = &dai_link->codec_of_node; + dai_name = &dai_link->codec_dai_name; + name = &dai_link->codec_name; + } + + if (!np) { + /* use snd-soc-dummy */ + *p_node = NULL; + *dai_name = "snd-soc-dummy-dai"; + *name = "snd-soc-dummy"; + return 0; + } + + /* + * Get node via "sound-dai = <&phandle port>" + * it will be used as xxx_of_node on soc_bind_dai_link() + */ + ret = of_parse_phandle_with_args(np, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) + return ret; + + *p_node = args.np; + + /* Get dai->name */ + ret = snd_soc_of_get_dai_name(np, dai_name); + if (ret < 0) + return ret; + + /* + * FIXME + * + * rsrc assumes DPCM playback/capture + */ + dai_link->dpcm_playback = 1; + dai_link->dpcm_capture = 1; + + if (args_count) { + *args_count = args.args_count; + dai_link->dynamic = 1; + } else { + dai_link->no_pcm = 1; + priv->codec_conf.of_node = (*p_node); + priv->codec_conf.name_prefix = of_data->prefix; + } + + /* + * Parse dai->sysclk come from "clocks = <&xxx>" + * (if system has common clock) + * or "system-clock-frequency = " + * or device's module clock. + */ + if (of_property_read_bool(np, "clocks")) { + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + return ret; + } + + dai->sysclk = clk_get_rate(clk); + dai->clk = clk; + } else if (!of_property_read_u32(np, "system-clock-frequency", &val)) { + dai->sysclk = val; + } else { + clk = of_clk_get(args.np, 0); + if (!IS_ERR(clk)) + dai->sysclk = clk_get_rate(clk); + } + + return 0; +} + +static int rsrc_card_parse_daifmt(struct device_node *node, + struct rsrc_card_priv *priv, + struct device_node *codec, + int idx) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + struct rsrc_card_dai_props *dai_props = rsrc_priv_to_props(priv, idx); + struct rsrc_card_dai *cpu_dai = &dai_props->cpu_dai; + struct rsrc_card_dai *codec_dai = &dai_props->codec_dai; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, NULL, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + if (!bitclkmaster && !framemaster) + return -EINVAL; + + if (codec == bitclkmaster) + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + else + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + + cpu_dai->fmt = daifmt; + codec_dai->fmt = daifmt; + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + return 0; +} + +static int rsrc_card_dai_link_of(struct device_node *node, + struct rsrc_card_priv *priv, + int idx) +{ + struct device *dev = rsrc_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); + struct rsrc_card_dai_props *dai_props = rsrc_priv_to_props(priv, idx); + struct device_node *cpu = NULL; + struct device_node *codec = NULL; + char *name; + char prop[128]; + int ret, cpu_args; + + cpu = of_get_child_by_name(node, "cpu"); + codec = of_get_child_by_name(node, "codec"); + + if (!cpu || !codec) { + ret = -EINVAL; + dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); + goto dai_link_of_err; + } + + ret = rsrc_card_parse_daifmt(node, priv, codec, idx); + if (ret < 0) + goto dai_link_of_err; + + ret = rsrc_card_sub_parse_of(priv, (idx == IDX_CPU) ? cpu : NULL, + &dai_props->cpu_dai, + dai_link, + &cpu_args); + if (ret < 0) + goto dai_link_of_err; + + ret = rsrc_card_sub_parse_of(priv, (idx == IDX_CODEC) ? codec : NULL, + &dai_props->codec_dai, + dai_link, + NULL); + if (ret < 0) + goto dai_link_of_err; + + if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) { + ret = -EINVAL; + goto dai_link_of_err; + } + + /* Simple Card assumes platform == cpu */ + dai_link->platform_of_node = dai_link->cpu_of_node; + + /* 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); + if (!name) { + ret = -ENOMEM; + goto dai_link_of_err; + } + + sprintf(name, "%s-%s", dai_link->cpu_dai_name, + dai_link->codec_dai_name); + dai_link->name = dai_link->stream_name = name; + dai_link->ops = &rsrc_card_ops; + dai_link->init = rsrc_card_dai_init; + + dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); + dev_dbg(dev, "\tcpu : %s / %04x / %d\n", + dai_link->cpu_dai_name, + dai_props->cpu_dai.fmt, + dai_props->cpu_dai.sysclk); + dev_dbg(dev, "\tcodec : %s / %04x / %d\n", + dai_link->codec_dai_name, + dai_props->codec_dai.fmt, + dai_props->codec_dai.sysclk); + + /* + * In soc_bind_dai_link() will check cpu name after + * of_node matching if dai_link has cpu_dai_name. + * but, it will never match if name was created by + * fmt_single_name() remove cpu_dai_name if cpu_args + * was 0. See: + * fmt_single_name() + * fmt_multiple_name() + */ + if (!cpu_args) + dai_link->cpu_dai_name = NULL; + +dai_link_of_err: + of_node_put(cpu); + of_node_put(codec); + + return ret; +} + +static int rsrc_card_parse_of(struct device_node *node, + struct rsrc_card_priv *priv) +{ + struct device *dev = rsrc_priv_to_dev(priv); + const struct rsrc_card_of_data *of_data = rsrc_dev_to_of_data(dev); + int ret; + int i; + + if (!node) + return -EINVAL; + + /* Parse the card name from DT */ + snd_soc_of_parse_card_name(&priv->snd_card, "card-name"); + + /* DAPM routes */ + priv->snd_card.of_dapm_routes = of_data->routes; + priv->snd_card.num_of_dapm_routes = of_data->num_routes; + + dev_dbg(dev, "New rsrc-audio-card: %s\n", priv->snd_card.name ? + priv->snd_card.name : ""); + + /* FE/BE */ + for (i = 0; i < RSRC_FB_NUM; i++) { + ret = rsrc_card_dai_link_of(node, priv, i); + if (ret < 0) + return ret; + } + + if (!priv->snd_card.name) + priv->snd_card.name = priv->snd_card.dai_link->name; + + return 0; +} + +/* Decrease the reference count of the device nodes */ +static int rsrc_card_unref(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + int num_links; + + for (num_links = 0, dai_link = card->dai_link; + num_links < card->num_links; + num_links++, dai_link++) { + of_node_put(dai_link->cpu_of_node); + of_node_put(dai_link->codec_of_node); + } + return 0; +} + +static int rsrc_card_probe(struct platform_device *pdev) +{ + struct rsrc_card_priv *priv; + struct snd_soc_dai_link *dai_link; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + + /* Allocate the private data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Init snd_soc_card */ + priv->snd_card.owner = THIS_MODULE; + priv->snd_card.dev = dev; + dai_link = priv->dai_link; + priv->snd_card.dai_link = dai_link; + priv->snd_card.num_links = RSRC_FB_NUM; + priv->snd_card.codec_conf = &priv->codec_conf; + priv->snd_card.num_configs = 1; + + ret = rsrc_card_parse_of(np, priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + snd_soc_card_set_drvdata(&priv->snd_card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); + if (ret >= 0) + return ret; +err: + rsrc_card_unref(&priv->snd_card); + + return ret; +} + +static int rsrc_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + return rsrc_card_unref(card); +} + +static struct platform_driver rsrc_card = { + .driver = { + .name = "renesas-src-audio-card", + .of_match_table = rsrc_card_of_match, + }, + .probe = rsrc_card_probe, + .remove = rsrc_card_remove, +}; + +module_platform_driver(rsrc_card); + +MODULE_ALIAS("platform:renesas-src-audio-card"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Renesas Sampling Rate Convert Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto "); -- cgit v0.10.2 From af7e2be96623785816c1a6e521307b7af11ad016 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 26 Mar 2015 04:01:46 +0000 Subject: ASoC: rsrc-card: add .be_hw_params_fixup support for convert rate Current rsnd-dpcm-card is supporting DPCM FE/BE sound card. This patch adds .be_hw_params_fixup and enabled sampling convert rate. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt b/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt index 12e287e..c641550 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt +++ b/Documentation/devicetree/bindings/sound/renesas,rsrc-card.txt @@ -28,6 +28,7 @@ Optional subnode properties: dai-link uses bit clock inversion. - frame-inversion : bool property. Add this if the dai-link uses frame clock inversion. +- convert-rate : platform specified sampling rate convert Required CPU/CODEC subnodes properties: diff --git a/sound/soc/sh/rcar/rsrc-card.c b/sound/soc/sh/rcar/rsrc-card.c index 3baeab7..a68517a 100644 --- a/sound/soc/sh/rcar/rsrc-card.c +++ b/sound/soc/sh/rcar/rsrc-card.c @@ -63,6 +63,7 @@ struct rsrc_card_priv { } dai_props[RSRC_FB_NUM]; struct snd_soc_codec_conf codec_conf; struct snd_soc_dai_link dai_link[RSRC_FB_NUM]; + u32 convert_rate; }; #define rsrc_priv_to_dev(priv) ((priv)->snd_card.dev) @@ -154,6 +155,21 @@ static int rsrc_card_dai_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static int rsrc_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + if (!priv->convert_rate) + return 0; + + rate->min = rate->max = priv->convert_rate; + + return 0; +} + static int rsrc_card_sub_parse_of(struct rsrc_card_priv *priv, struct device_node *np, @@ -347,6 +363,9 @@ static int rsrc_card_dai_link_of(struct device_node *node, dai_link->ops = &rsrc_card_ops; dai_link->init = rsrc_card_dai_init; + if (idx == IDX_CODEC) + dai_link->be_hw_params_fixup = rsrc_card_be_hw_params_fixup; + dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); dev_dbg(dev, "\tcpu : %s / %04x / %d\n", dai_link->cpu_dai_name, @@ -394,8 +413,12 @@ static int rsrc_card_parse_of(struct device_node *node, priv->snd_card.of_dapm_routes = of_data->routes; priv->snd_card.num_of_dapm_routes = of_data->num_routes; - dev_dbg(dev, "New rsrc-audio-card: %s\n", priv->snd_card.name ? - priv->snd_card.name : ""); + /* sampling rate convert */ + of_property_read_u32(node, "convert-rate", &priv->convert_rate); + + dev_dbg(dev, "New rsrc-audio-card: %s (%d)\n", + priv->snd_card.name ? priv->snd_card.name : "", + priv->convert_rate); /* FE/BE */ for (i = 0; i < RSRC_FB_NUM; i++) { -- cgit v0.10.2 From 2f78dd7f40264697afed4c2ac0890df8f0588e49 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 26 Mar 2015 04:02:09 +0000 Subject: ASoC: rsnd: call clk_prepare/unprepare() in probe/remove clk_prepare_enable()/clk_disable_unprepare() uses mutex inside, in concretely clk_prepare()/clk_unprepare().And it uses __schedule(). Then, raw_spin_lock/unlock_irq() is called, and it breaks Renesas sound driver's spin lock irq. This patch separates thesse into clk_prepare()/clk_unprepare() and clk_enable/clk_disable. And call clk_prepare()/clk_unprepare() from probe/remove function. Special thanks to Das Biju. Reported-by: Das Biju Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 1b53605..6046c10 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -149,16 +149,29 @@ char *rsnd_mod_dma_name(struct rsnd_mod *mod) return mod->ops->dma_name(mod); } -void rsnd_mod_init(struct rsnd_mod *mod, +int rsnd_mod_init(struct rsnd_mod *mod, struct rsnd_mod_ops *ops, struct clk *clk, enum rsnd_mod_type type, int id) { + int ret = clk_prepare(clk); + + if (ret) + return ret; + mod->id = id; mod->ops = ops; mod->type = type; mod->clk = clk; + + return ret; +} + +void rsnd_mod_quit(struct rsnd_mod *mod) +{ + if (mod->clk) + clk_unprepare(mod->clk); } /* @@ -1290,6 +1303,12 @@ static int rsnd_remove(struct platform_device *pdev) { struct rsnd_priv *priv = dev_get_drvdata(&pdev->dev); struct rsnd_dai *rdai; + void (*remove_func[])(struct platform_device *pdev, + struct rsnd_priv *priv) = { + rsnd_ssi_remove, + rsnd_src_remove, + rsnd_dvc_remove, + }; int ret = 0, i; pm_runtime_disable(&pdev->dev); @@ -1299,6 +1318,9 @@ static int rsnd_remove(struct platform_device *pdev) ret |= rsnd_dai_call(remove, &rdai->capture, priv); } + for (i = 0; i < ARRAY_SIZE(remove_func); i++) + remove_func[i](pdev, priv); + snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_platform(&pdev->dev); diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index d7f9ed9..261997a 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -333,7 +333,7 @@ int rsnd_dvc_probe(struct platform_device *pdev, struct rsnd_dvc *dvc; struct clk *clk; char name[RSND_DVC_NAME_SIZE]; - int i, nr; + int i, nr, ret; rsnd_of_parse_dvc(pdev, of_data, priv); @@ -366,11 +366,24 @@ int rsnd_dvc_probe(struct platform_device *pdev, dvc->info = &info->dvc_info[i]; - rsnd_mod_init(&dvc->mod, &rsnd_dvc_ops, + ret = rsnd_mod_init(&dvc->mod, &rsnd_dvc_ops, clk, RSND_MOD_DVC, i); + if (ret) + return ret; dev_dbg(dev, "CMD%d probed\n", i); } return 0; } + +void rsnd_dvc_remove(struct platform_device *pdev, + struct rsnd_priv *priv) +{ + struct rsnd_dvc *dvc; + int i; + + for_each_rsnd_dvc(dvc, priv, i) { + rsnd_mod_quit(&dvc->mod); + } +} diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index e7914bd..1bccc55 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -260,14 +260,15 @@ struct rsnd_mod { #define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma) #define rsnd_mod_to_io(mod) ((mod)->io) #define rsnd_mod_id(mod) ((mod)->id) -#define rsnd_mod_hw_start(mod) clk_prepare_enable((mod)->clk) -#define rsnd_mod_hw_stop(mod) clk_disable_unprepare((mod)->clk) +#define rsnd_mod_hw_start(mod) clk_enable((mod)->clk) +#define rsnd_mod_hw_stop(mod) clk_disable((mod)->clk) -void rsnd_mod_init(struct rsnd_mod *mod, +int rsnd_mod_init(struct rsnd_mod *mod, struct rsnd_mod_ops *ops, struct clk *clk, enum rsnd_mod_type type, int id); +void rsnd_mod_quit(struct rsnd_mod *mod); char *rsnd_mod_name(struct rsnd_mod *mod); char *rsnd_mod_dma_name(struct rsnd_mod *mod); @@ -480,6 +481,8 @@ int rsnd_kctrl_new_e(struct rsnd_mod *mod, int rsnd_src_probe(struct platform_device *pdev, const struct rsnd_of_data *of_data, struct rsnd_priv *priv); +void rsnd_src_remove(struct platform_device *pdev, + struct rsnd_priv *priv); struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id); unsigned int rsnd_src_get_ssi_rate(struct rsnd_priv *priv, struct rsnd_dai_stream *io, @@ -498,6 +501,8 @@ int rsnd_src_ssi_irq_disable(struct rsnd_mod *ssi_mod); int rsnd_ssi_probe(struct platform_device *pdev, const struct rsnd_of_data *of_data, struct rsnd_priv *priv); +void rsnd_ssi_remove(struct platform_device *pdev, + struct rsnd_priv *priv); struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id); int rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod); int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod); diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 81c182b..c77d059 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -850,7 +850,7 @@ int rsnd_src_probe(struct platform_device *pdev, struct rsnd_mod_ops *ops; struct clk *clk; char name[RSND_SRC_NAME_SIZE]; - int i, nr; + int i, nr, ret; ops = NULL; if (rsnd_is_gen1(priv)) @@ -890,10 +890,23 @@ int rsnd_src_probe(struct platform_device *pdev, src->info = &info->src_info[i]; - rsnd_mod_init(&src->mod, ops, clk, RSND_MOD_SRC, i); + ret = rsnd_mod_init(&src->mod, ops, clk, RSND_MOD_SRC, i); + if (ret) + return ret; dev_dbg(dev, "SRC%d probed\n", i); } return 0; } + +void rsnd_src_remove(struct platform_device *pdev, + struct rsnd_priv *priv) +{ + struct rsnd_src *src; + int i; + + for_each_rsnd_src(src, priv, i) { + rsnd_mod_quit(&src->mod); + } +} diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 9e7b627..f7cb1fd 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -697,7 +697,7 @@ int rsnd_ssi_probe(struct platform_device *pdev, struct clk *clk; struct rsnd_ssi *ssi; char name[RSND_SSI_NAME_SIZE]; - int i, nr; + int i, nr, ret; rsnd_of_parse_ssi(pdev, of_data, priv); @@ -732,10 +732,23 @@ int rsnd_ssi_probe(struct platform_device *pdev, else if (rsnd_ssi_pio_available(ssi)) ops = &rsnd_ssi_pio_ops; - rsnd_mod_init(&ssi->mod, ops, clk, RSND_MOD_SSI, i); + ret = rsnd_mod_init(&ssi->mod, ops, clk, RSND_MOD_SSI, i); + if (ret) + return ret; rsnd_ssi_parent_clk_setup(priv, ssi); } return 0; } + +void rsnd_ssi_remove(struct platform_device *pdev, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi; + int i; + + for_each_rsnd_ssi(ssi, priv, i) { + rsnd_mod_quit(&ssi->mod); + } +} -- cgit v0.10.2 From b543b52a44c4e45283cd17721af1299049405136 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 26 Mar 2015 04:02:32 +0000 Subject: ASoC: rsnd: remove useless debug message This patch removes useless debug message. especially some kind of "probed" message will be printed from core.c if it has #define DEBUG Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index 7af374b..fefc881 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -434,7 +434,5 @@ int rsnd_adg_probe(struct platform_device *pdev, priv->adg = adg; - dev_dbg(dev, "adg probed\n"); - return 0; } diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index cd7b79a..ac3756f 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -144,8 +144,6 @@ static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, return -EIO; } - dev_dbg(dev, "Audio DMAC init\n"); - if (dev->of_node) { dmaen->chan = rsnd_dmaen_request_channel(mod_from, mod_to); } else { @@ -329,8 +327,6 @@ static int rsnd_dmapp_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id, struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); struct device *dev = rsnd_priv_to_dev(priv); - dev_dbg(dev, "Audio DMAC peri peri init\n"); - dmapp->dmapp_id = dmac->dmapp_num; dmapp->chcr = rsnd_dmapp_get_chcr(mod_from, mod_to) | PDMACHCR_DE; diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index aab4526..e5fcb06 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -119,17 +119,6 @@ static void rsnd_dvc_volume_update(struct rsnd_mod *mod) rsnd_mod_write(mod, DVC_DVUER, 1); } -static int rsnd_dvc_probe_gen2(struct rsnd_mod *mod, - struct rsnd_priv *priv) -{ - struct device *dev = rsnd_priv_to_dev(priv); - - dev_dbg(dev, "%s[%d] (Gen2) is probed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); - - return 0; -} - static int rsnd_dvc_remove_gen2(struct rsnd_mod *mod, struct rsnd_priv *priv) { @@ -283,7 +272,6 @@ static struct dma_chan *rsnd_dvc_dma_req(struct rsnd_mod *mod) static struct rsnd_mod_ops rsnd_dvc_ops = { .name = DVC_NAME, .dma_req = rsnd_dvc_dma_req, - .probe = rsnd_dvc_probe_gen2, .remove = rsnd_dvc_remove_gen2, .init = rsnd_dvc_init, .quit = rsnd_dvc_quit, @@ -382,8 +370,6 @@ int rsnd_dvc_probe(struct platform_device *pdev, clk, RSND_MOD_DVC, i); if (ret) return ret; - - dev_dbg(dev, "CMD%d probed\n", i); } return 0; diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index a17a504..8c7dc51 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -194,7 +194,6 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, static int rsnd_gen2_probe(struct platform_device *pdev, struct rsnd_priv *priv) { - struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_regmap_field_conf conf_ssiu[] = { RSND_GEN_S_REG(SSI_MODE0, 0x800), RSND_GEN_S_REG(SSI_MODE1, 0x804), @@ -278,8 +277,6 @@ static int rsnd_gen2_probe(struct platform_device *pdev, ret_ssi < 0) return ret_ssiu | ret_scu | ret_adg | ret_ssi; - dev_dbg(dev, "Gen2 is probed\n"); - return 0; } @@ -290,7 +287,6 @@ static int rsnd_gen2_probe(struct platform_device *pdev, static int rsnd_gen1_probe(struct platform_device *pdev, struct rsnd_priv *priv) { - struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_regmap_field_conf conf_sru[] = { RSND_GEN_S_REG(SRC_ROUTE_SEL, 0x00), RSND_GEN_S_REG(SRC_TMG_SEL0, 0x08), @@ -348,8 +344,6 @@ static int rsnd_gen1_probe(struct platform_device *pdev, ret_ssi < 0) return ret_sru | ret_adg | ret_ssi; - dev_dbg(dev, "Gen1 is probed\n"); - return 0; } diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 6099a8e..83611fa 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -460,17 +460,6 @@ static int rsnd_src_set_convert_rate_gen1(struct rsnd_mod *mod) return 0; } -static int rsnd_src_probe_gen1(struct rsnd_mod *mod, - struct rsnd_priv *priv) -{ - struct device *dev = rsnd_priv_to_dev(priv); - - dev_dbg(dev, "%s[%d] (Gen1) is probed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); - - return 0; -} - static int rsnd_src_init_gen1(struct rsnd_mod *mod, struct rsnd_priv *priv) { @@ -518,7 +507,6 @@ static int rsnd_src_stop_gen1(struct rsnd_mod *mod, static struct rsnd_mod_ops rsnd_src_gen1_ops = { .name = SRC_NAME, .dma_req = rsnd_src_dma_req, - .probe = rsnd_src_probe_gen1, .init = rsnd_src_init_gen1, .quit = rsnd_src_quit, .start = rsnd_src_start_gen1, @@ -725,23 +713,12 @@ static int rsnd_src_probe_gen2(struct rsnd_mod *mod, IRQF_SHARED, dev_name(dev), mod); if (ret) - goto rsnd_src_probe_gen2_fail; + return ret; } ret = rsnd_dma_init(priv, rsnd_mod_to_dma(mod), src->info->dma_id); - if (ret) - goto rsnd_src_probe_gen2_fail; - - dev_dbg(dev, "%s[%d] (Gen2) is probed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); - - return ret; - -rsnd_src_probe_gen2_fail: - dev_err(dev, "%s[%d] (Gen2) failed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); return ret; } @@ -910,8 +887,6 @@ int rsnd_src_probe(struct platform_device *pdev, ret = rsnd_mod_init(&src->mod, ops, clk, RSND_MOD_SRC, i); if (ret) return ret; - - dev_dbg(dev, "SRC%d probed\n", i); } return 0; diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 79dc7a3..7bb9c08 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -445,12 +445,6 @@ static int rsnd_ssi_pio_probe(struct rsnd_mod *mod, rsnd_ssi_interrupt, IRQF_SHARED, dev_name(dev), ssi); - if (ret) - dev_err(dev, "%s[%d] (PIO) request interrupt failed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); - else - dev_dbg(dev, "%s[%d] (PIO) is probed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); return ret; } @@ -477,22 +471,11 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, IRQF_SHARED, dev_name(dev), ssi); if (ret) - goto rsnd_ssi_dma_probe_fail; + return ret; ret = rsnd_dma_init( priv, rsnd_mod_to_dma(mod), dma_id); - if (ret) - goto rsnd_ssi_dma_probe_fail; - - dev_dbg(dev, "%s[%d] (DMA) is probed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); - - return ret; - -rsnd_ssi_dma_probe_fail: - dev_err(dev, "%s[%d] (DMA) is failed\n", - rsnd_mod_name(mod), rsnd_mod_id(mod)); return ret; } -- cgit v0.10.2 From 3b7843ff618f63d1776abd71de7eea9130987037 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 26 Mar 2015 04:02:51 +0000 Subject: ASoC: rsnd: add DPCM based sampling rate convert This patch supports DPCM based sampling rate convert on Renesas sound driver. It assumes... 1. SRC is implemented as FE 2. BE dai_link supports .be_hw_params_fixup Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 770247c..9b0de42 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -203,7 +203,7 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod) ({ \ struct rsnd_priv *priv = rsnd_mod_to_priv(mod); \ struct device *dev = rsnd_priv_to_dev(priv); \ - u32 mask = 1 << __rsnd_mod_shift_##func; \ + u32 mask = (1 << __rsnd_mod_shift_##func) & ~(1 << 31); \ u32 call = __rsnd_mod_call_##func << __rsnd_mod_shift_##func; \ int ret = 0; \ if ((mod->status & mask) == call) { \ @@ -728,6 +728,15 @@ static int rsnd_pcm_open(struct snd_pcm_substream *substream) static int rsnd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { + struct snd_soc_dai *dai = rsnd_substream_to_dai(substream); + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + int ret; + + ret = rsnd_dai_call(hw_params, io, substream, hw_params); + if (ret) + return ret; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index f7af0be..4e6de68 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -239,6 +239,9 @@ struct rsnd_mod_ops { struct rsnd_priv *priv); int (*pcm_new)(struct rsnd_mod *mod, struct snd_soc_pcm_runtime *rtd); + int (*hw_params)(struct rsnd_mod *mod, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params); int (*fallback)(struct rsnd_mod *mod, struct rsnd_priv *priv); }; @@ -262,6 +265,9 @@ struct rsnd_mod { * 2 0: start 1: stop * 3 0: pcm_new * 4 0: fallback + * + * 31 bit is always called (see __rsnd_mod_call) + * 31 0: hw_params */ #define __rsnd_mod_shift_probe 0 #define __rsnd_mod_shift_remove 0 @@ -271,6 +277,7 @@ struct rsnd_mod { #define __rsnd_mod_shift_stop 2 #define __rsnd_mod_shift_pcm_new 3 #define __rsnd_mod_shift_fallback 4 +#define __rsnd_mod_shift_hw_params 31 /* always called */ #define __rsnd_mod_call_probe 0 #define __rsnd_mod_call_remove 1 @@ -280,6 +287,7 @@ struct rsnd_mod { #define __rsnd_mod_call_stop 1 #define __rsnd_mod_call_pcm_new 0 #define __rsnd_mod_call_fallback 0 +#define __rsnd_mod_call_hw_params 0 #define rsnd_mod_to_priv(mod) (rsnd_io_to_priv(rsnd_mod_to_io(mod))) #define rsnd_mod_to_dma(mod) (&(mod)->dma) diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 83611fa..a0a2bda 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -22,12 +22,13 @@ struct rsnd_src { struct rsnd_src_platform_info *info; /* rcar_snd.h */ struct rsnd_mod mod; + u32 convert_rate; /* sampling rate convert */ int err; }; #define RSND_SRC_NAME_SIZE 16 -#define rsnd_src_convert_rate(p) ((p)->info->convert_rate) +#define rsnd_src_convert_rate(s) ((s)->convert_rate) #define rsnd_src_of_node(priv) \ of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,src") @@ -288,7 +289,43 @@ static int rsnd_src_set_convert_rate(struct rsnd_mod *mod) return 0; } -static int rsnd_src_init(struct rsnd_mod *mod) +static int rsnd_src_hw_params(struct rsnd_mod *mod, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *fe_params) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + struct snd_soc_pcm_runtime *fe = substream->private_data; + + /* default value (mainly for non-DT) */ + src->convert_rate = src->info->convert_rate; + + /* + * SRC assumes that it is used under DPCM if user want to use + * sampling rate convert. Then, SRC should be FE. + * And then, this function will be called *after* BE settings. + * this means, each BE already has fixuped hw_params. + * see + * dpcm_fe_dai_hw_params() + * dpcm_be_dai_hw_params() + */ + if (fe->dai_link->dynamic) { + int stream = substream->stream; + struct snd_soc_dpcm *dpcm; + struct snd_pcm_hw_params *be_params; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + be_params = &dpcm->hw_params; + + if (params_rate(fe_params) != params_rate(be_params)) + src->convert_rate = params_rate(be_params); + } + } + + return 0; +} + +static int rsnd_src_init(struct rsnd_mod *mod, + struct rsnd_priv *priv) { struct rsnd_src *src = rsnd_mod_to_src(mod); @@ -317,6 +354,8 @@ static int rsnd_src_quit(struct rsnd_mod *mod, dev_warn(dev, "%s[%d] under/over flow err = %d\n", rsnd_mod_name(mod), rsnd_mod_id(mod), src->err); + src->convert_rate = 0; + return 0; } @@ -465,7 +504,7 @@ static int rsnd_src_init_gen1(struct rsnd_mod *mod, { int ret; - ret = rsnd_src_init(mod); + ret = rsnd_src_init(mod, priv); if (ret < 0) return ret; @@ -511,6 +550,7 @@ static struct rsnd_mod_ops rsnd_src_gen1_ops = { .quit = rsnd_src_quit, .start = rsnd_src_start_gen1, .stop = rsnd_src_stop_gen1, + .hw_params = rsnd_src_hw_params, }; /* @@ -736,7 +776,7 @@ static int rsnd_src_init_gen2(struct rsnd_mod *mod, { int ret; - ret = rsnd_src_init(mod); + ret = rsnd_src_init(mod, priv); if (ret < 0) return ret; @@ -780,6 +820,7 @@ static struct rsnd_mod_ops rsnd_src_gen2_ops = { .quit = rsnd_src_quit, .start = rsnd_src_start_gen2, .stop = rsnd_src_stop_gen2, + .hw_params = rsnd_src_hw_params, }; struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id) -- cgit v0.10.2 From 41d9489319f28f06cf51731131bc353d5a6bce59 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Mon, 23 Mar 2015 14:16:38 +1100 Subject: drivers/of: Add empty ranges quirk for PA-Semi The "sdc" node is missing the ranges property, it needs to be treated as having an empty one otherwise translation fails for its children. Fixes 746c9e9f92dd, "of/base: Fix PowerPC address parsing hack" Tested-by: Steven Rostedt Signed-off-by: Benjamin Herrenschmidt Signed-off-by: Grant Likely Cc: Stable # v3.18+ diff --git a/drivers/of/address.c b/drivers/of/address.c index ad29069..78a7dcb 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -450,12 +450,17 @@ static struct of_bus *of_match_bus(struct device_node *np) return NULL; } -static int of_empty_ranges_quirk(void) +static int of_empty_ranges_quirk(struct device_node *np) { if (IS_ENABLED(CONFIG_PPC)) { - /* To save cycles, we cache the result */ + /* To save cycles, we cache the result for global "Mac" setting */ static int quirk_state = -1; + /* PA-SEMI sdc DT bug */ + if (of_device_is_compatible(np, "1682m-sdc")) + return true; + + /* Make quirk cached */ if (quirk_state < 0) quirk_state = of_machine_is_compatible("Power Macintosh") || @@ -490,7 +495,7 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus, * This code is only enabled on powerpc. --gcl */ ranges = of_get_property(parent, rprop, &rlen); - if (ranges == NULL && !of_empty_ranges_quirk()) { + if (ranges == NULL && !of_empty_ranges_quirk(parent)) { pr_debug("OF: no ranges; cannot translate\n"); return 1; } -- cgit v0.10.2 From bbc45f3ab78edb8c97e563ddd351f851da47dab1 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Mon, 16 Mar 2015 22:27:10 +0100 Subject: iio/adc/cc10001_adc.c: Fix !HAS_IOMEM build Fixes: drivers/built-in.o: In function `cc10001_adc_probe': cc10001_adc.c:(.text+0x412e92): undefined reference to `devm_ioremap_resource' Signed-off-by: Richard Weinberger Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 202daf8..46379b1 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -137,7 +137,8 @@ config AXP288_ADC config CC10001_ADC tristate "Cosmic Circuits 10001 ADC driver" - depends on HAS_IOMEM || HAVE_CLK || REGULATOR + depends on HAVE_CLK || REGULATOR + depends on HAS_IOMEM select IIO_BUFFER select IIO_TRIGGERED_BUFFER help -- cgit v0.10.2 From f54e9f2be312a4e71b54aea865b2e33ccb95ef0c Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 24 Mar 2015 13:47:47 +0100 Subject: iio: adc: vf610: use ADC clock within specification Depending on conversion mode used, the ADC clock (ADCK) needs to be below a maximum frequency. According to Vybrid's data sheet this is 20MHz for the low power conversion mode. The ADC clock is depending on input clock, which is the bus clock by default. Vybrid SoC are typically clocked at at 400MHz or 500MHz, which leads to 66MHz or 83MHz bus clock respectively. Hence, a divider of 8 is required to stay below the specified maximum clock of 20MHz. Due to the different bus clock speeds, the resulting sampling frequency is not static. Hence use the ADC clock and calculate the actual available sampling frequency dynamically. This fixes bogous values observed on some 500MHz clocked Vybrid SoC. The resulting value usually showed Bit 9 being stuck at 1, or 0, which lead to a value of +/-512. Signed-off-by: Stefan Agner Acked-by: Fugang Duan Cc: Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c index 8ec353c..e63b8e7 100644 --- a/drivers/iio/adc/vf610_adc.c +++ b/drivers/iio/adc/vf610_adc.c @@ -141,9 +141,13 @@ struct vf610_adc { struct regulator *vref; struct vf610_adc_feature adc_feature; + u32 sample_freq_avail[5]; + struct completion completion; }; +static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 }; + #define VF610_ADC_CHAN(_idx, _chan_type) { \ .type = (_chan_type), \ .indexed = 1, \ @@ -180,35 +184,47 @@ static const struct iio_chan_spec vf610_adc_iio_channels[] = { /* sentinel */ }; -/* - * ADC sample frequency, unit is ADCK cycles. - * ADC clk source is ipg clock, which is the same as bus clock. - * - * ADC conversion time = SFCAdder + AverageNum x (BCT + LSTAdder) - * SFCAdder: fixed to 6 ADCK cycles - * AverageNum: 1, 4, 8, 16, 32 samples for hardware average. - * BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode - * LSTAdder(Long Sample Time): fixed to 3 ADCK cycles - * - * By default, enable 12 bit resolution mode, clock source - * set to ipg clock, So get below frequency group: - */ -static const u32 vf610_sample_freq_avail[5] = -{1941176, 559332, 286957, 145374, 73171}; +static inline void vf610_adc_calculate_rates(struct vf610_adc *info) +{ + unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk); + int i; + + /* + * Calculate ADC sample frequencies + * Sample time unit is ADCK cycles. ADCK clk source is ipg clock, + * which is the same as bus clock. + * + * ADC conversion time = SFCAdder + AverageNum x (BCT + LSTAdder) + * SFCAdder: fixed to 6 ADCK cycles + * AverageNum: 1, 4, 8, 16, 32 samples for hardware average. + * BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode + * LSTAdder(Long Sample Time): fixed to 3 ADCK cycles + */ + adck_rate = ipg_rate / info->adc_feature.clk_div; + for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++) + info->sample_freq_avail[i] = + adck_rate / (6 + vf610_hw_avgs[i] * (25 + 3)); +} static inline void vf610_adc_cfg_init(struct vf610_adc *info) { + struct vf610_adc_feature *adc_feature = &info->adc_feature; + /* set default Configuration for ADC controller */ - info->adc_feature.clk_sel = VF610_ADCIOC_BUSCLK_SET; - info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET; + adc_feature->clk_sel = VF610_ADCIOC_BUSCLK_SET; + adc_feature->vol_ref = VF610_ADCIOC_VR_VREF_SET; + + adc_feature->calibration = true; + adc_feature->ovwren = true; + + adc_feature->res_mode = 12; + adc_feature->sample_rate = 1; + adc_feature->lpm = true; - info->adc_feature.calibration = true; - info->adc_feature.ovwren = true; + /* Use a save ADCK which is below 20MHz on all devices */ + adc_feature->clk_div = 8; - info->adc_feature.clk_div = 1; - info->adc_feature.res_mode = 12; - info->adc_feature.sample_rate = 1; - info->adc_feature.lpm = true; + vf610_adc_calculate_rates(info); } static void vf610_adc_cfg_post_set(struct vf610_adc *info) @@ -290,12 +306,10 @@ static void vf610_adc_cfg_set(struct vf610_adc *info) cfg_data = readl(info->regs + VF610_REG_ADC_CFG); - /* low power configuration */ cfg_data &= ~VF610_ADC_ADLPC_EN; if (adc_feature->lpm) cfg_data |= VF610_ADC_ADLPC_EN; - /* disable high speed */ cfg_data &= ~VF610_ADC_ADHSC_EN; writel(cfg_data, info->regs + VF610_REG_ADC_CFG); @@ -435,10 +449,27 @@ static irqreturn_t vf610_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1941176, 559332, 286957, 145374, 73171"); +static ssize_t vf610_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vf610_adc *info = iio_priv(dev_to_iio_dev(dev)); + size_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(info->sample_freq_avail); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%u ", info->sample_freq_avail[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(vf610_show_samp_freq_avail); static struct attribute *vf610_attributes[] = { - &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, NULL }; @@ -502,7 +533,7 @@ static int vf610_read_raw(struct iio_dev *indio_dev, return IIO_VAL_FRACTIONAL_LOG2; case IIO_CHAN_INFO_SAMP_FREQ: - *val = vf610_sample_freq_avail[info->adc_feature.sample_rate]; + *val = info->sample_freq_avail[info->adc_feature.sample_rate]; *val2 = 0; return IIO_VAL_INT; @@ -525,9 +556,9 @@ static int vf610_write_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: for (i = 0; - i < ARRAY_SIZE(vf610_sample_freq_avail); + i < ARRAY_SIZE(info->sample_freq_avail); i++) - if (val == vf610_sample_freq_avail[i]) { + if (val == info->sample_freq_avail[i]) { info->adc_feature.sample_rate = i; vf610_adc_sample_set(info); return 0; -- cgit v0.10.2 From 4ce7ca89d6e8eae9e201cd0e972ba323f33e2fb4 Mon Sep 17 00:00:00 2001 From: Darshana Padmadas Date: Sat, 28 Mar 2015 12:07:14 +0530 Subject: iio: imu: Use iio_trigger_get for indio_dev->trig assignment This patch uses iio_trigger_get to increment the reference count of trigger device, to avoid incorrect assignment. Can result in a null pointer dereference during removal if the trigger has been changed before removal. This patch refers to a similar situation encountered through the following discussion: http://www.spinics.net/lists/linux-iio/msg13669.html Signed-off-by: Darshana Padmadas Cc: Signed-off-by: Jonathan Cameron diff --git a/drivers/iio/imu/adis_trigger.c b/drivers/iio/imu/adis_trigger.c index e0017c2..f53e9a8 100644 --- a/drivers/iio/imu/adis_trigger.c +++ b/drivers/iio/imu/adis_trigger.c @@ -60,7 +60,7 @@ int adis_probe_trigger(struct adis *adis, struct iio_dev *indio_dev) iio_trigger_set_drvdata(adis->trig, adis); ret = iio_trigger_register(adis->trig); - indio_dev->trig = adis->trig; + indio_dev->trig = iio_trigger_get(adis->trig); if (ret) goto error_free_irq; -- cgit v0.10.2 From 1e9e39f4a29857a396ac7b669d109f697f66695e Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Thu, 26 Feb 2015 19:34:37 +0000 Subject: usbnet: Fix tx_packets stat for FLAG_MULTI_FRAME drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the usbnet core does not update the tx_packets statistic for drivers with FLAG_MULTI_PACKET and there is no hook in the TX completion path where they could do this. cdc_ncm and dependent drivers are bumping tx_packets stat on the transmit path while asix and sr9800 aren't updating it at all. Add a packet count in struct skb_data so these drivers can fill it in, initialise it to 1 for other drivers, and add the packet count to the tx_packets statistic on completion. Signed-off-by: Ben Hutchings Tested-by: Bjørn Mork Signed-off-by: David S. Miller diff --git a/drivers/net/usb/asix_common.c b/drivers/net/usb/asix_common.c index 5c55f11..724a9b5 100644 --- a/drivers/net/usb/asix_common.c +++ b/drivers/net/usb/asix_common.c @@ -188,6 +188,8 @@ struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb, memcpy(skb_tail_pointer(skb), &padbytes, sizeof(padbytes)); skb_put(skb, sizeof(padbytes)); } + + usbnet_set_skb_tx_stats(skb, 1); return skb; } diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 80a844e..70cbea5 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -1172,7 +1172,6 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) /* return skb */ ctx->tx_curr_skb = NULL; - dev->net->stats.tx_packets += ctx->tx_curr_frame_num; /* keep private stats: framing overhead and number of NTBs */ ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload; @@ -1184,6 +1183,8 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) */ dev->net->stats.tx_bytes -= skb_out->len - ctx->tx_curr_frame_payload; + usbnet_set_skb_tx_stats(skb_out, n); + return skb_out; exit_no_skb: diff --git a/drivers/net/usb/sr9800.c b/drivers/net/usb/sr9800.c index b94a0fb..7650cdc 100644 --- a/drivers/net/usb/sr9800.c +++ b/drivers/net/usb/sr9800.c @@ -144,6 +144,7 @@ static struct sk_buff *sr_tx_fixup(struct usbnet *dev, struct sk_buff *skb, skb_put(skb, sizeof(padbytes)); } + usbnet_set_skb_tx_stats(skb, 1); return skb; } diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 449835f..0f3ff28 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1188,8 +1188,7 @@ static void tx_complete (struct urb *urb) struct usbnet *dev = entry->dev; if (urb->status == 0) { - if (!(dev->driver_info->flags & FLAG_MULTI_PACKET)) - dev->net->stats.tx_packets++; + dev->net->stats.tx_packets += entry->packets; dev->net->stats.tx_bytes += entry->length; } else { dev->net->stats.tx_errors++; @@ -1348,6 +1347,8 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb, urb->transfer_flags |= URB_ZERO_PACKET; } entry->length = urb->transfer_buffer_length = length; + if (!(info->flags & FLAG_MULTI_PACKET)) + usbnet_set_skb_tx_stats(skb, 1); spin_lock_irqsave(&dev->txq.lock, flags); retval = usb_autopm_get_interface_async(dev->intf); diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index d9a4905..ff3fb2b 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -228,8 +228,20 @@ struct skb_data { /* skb->cb is one of these */ struct usbnet *dev; enum skb_state state; size_t length; + unsigned long packets; }; +/* Drivers that set FLAG_MULTI_PACKET must call this in their + * tx_fixup method before returning an skb. + */ +static inline void +usbnet_set_skb_tx_stats(struct sk_buff *skb, unsigned long packets) +{ + struct skb_data *entry = (struct skb_data *) skb->cb; + + entry->packets = packets; +} + extern int usbnet_open(struct net_device *net); extern int usbnet_stop(struct net_device *net); extern netdev_tx_t usbnet_start_xmit(struct sk_buff *skb, -- cgit v0.10.2 From 7a1e890e2168e33fb62d84528e996b8b4b478fea Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Wed, 25 Mar 2015 21:41:33 +0100 Subject: usbnet: Fix tx_bytes statistic running backward in cdc_ncm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cdc_ncm disagrees with usbnet about how much framing overhead should be counted in the tx_bytes statistics, and tries 'fix' this by decrementing tx_bytes on the transmit path. But statistics must never be decremented except due to roll-over; this will thoroughly confuse user-space. Also, tx_bytes is only incremented by usbnet in the completion path. Fix this by requiring drivers that set FLAG_MULTI_FRAME to set a tx_bytes delta along with the tx_packets count. Fixes: beeecd42c3b4 ("net: cdc_ncm/cdc_mbim: adding NCM protocol statistics") Signed-off-by: Ben Hutchings Signed-off-by: Bjørn Mork diff --git a/drivers/net/usb/asix_common.c b/drivers/net/usb/asix_common.c index 724a9b5..75d6f26 100644 --- a/drivers/net/usb/asix_common.c +++ b/drivers/net/usb/asix_common.c @@ -189,7 +189,7 @@ struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb, skb_put(skb, sizeof(padbytes)); } - usbnet_set_skb_tx_stats(skb, 1); + usbnet_set_skb_tx_stats(skb, 1, 0); return skb; } diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 70cbea5..c3e4da9 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -1177,13 +1177,12 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload; ctx->tx_ntbs++; - /* usbnet has already counted all the framing overhead. + /* usbnet will count all the framing overhead by default. * Adjust the stats so that the tx_bytes counter show real * payload data instead. */ - dev->net->stats.tx_bytes -= skb_out->len - ctx->tx_curr_frame_payload; - - usbnet_set_skb_tx_stats(skb_out, n); + usbnet_set_skb_tx_stats(skb_out, n, + ctx->tx_curr_frame_payload - skb_out->len); return skb_out; diff --git a/drivers/net/usb/sr9800.c b/drivers/net/usb/sr9800.c index 7650cdc..953de13 100644 --- a/drivers/net/usb/sr9800.c +++ b/drivers/net/usb/sr9800.c @@ -144,7 +144,7 @@ static struct sk_buff *sr_tx_fixup(struct usbnet *dev, struct sk_buff *skb, skb_put(skb, sizeof(padbytes)); } - usbnet_set_skb_tx_stats(skb, 1); + usbnet_set_skb_tx_stats(skb, 1, 0); return skb; } diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 0f3ff28..777757a 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1346,9 +1346,19 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb, } else urb->transfer_flags |= URB_ZERO_PACKET; } - entry->length = urb->transfer_buffer_length = length; - if (!(info->flags & FLAG_MULTI_PACKET)) - usbnet_set_skb_tx_stats(skb, 1); + urb->transfer_buffer_length = length; + + if (info->flags & FLAG_MULTI_PACKET) { + /* Driver has set number of packets and a length delta. + * Calculate the complete length and ensure that it's + * positive. + */ + entry->length += length; + if (WARN_ON_ONCE(entry->length <= 0)) + entry->length = length; + } else { + usbnet_set_skb_tx_stats(skb, 1, length); + } spin_lock_irqsave(&dev->txq.lock, flags); retval = usb_autopm_get_interface_async(dev->intf); diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index ff3fb2b..6e0ce8c 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -227,7 +227,7 @@ struct skb_data { /* skb->cb is one of these */ struct urb *urb; struct usbnet *dev; enum skb_state state; - size_t length; + long length; unsigned long packets; }; @@ -235,11 +235,13 @@ struct skb_data { /* skb->cb is one of these */ * tx_fixup method before returning an skb. */ static inline void -usbnet_set_skb_tx_stats(struct sk_buff *skb, unsigned long packets) +usbnet_set_skb_tx_stats(struct sk_buff *skb, + unsigned long packets, long bytes_delta) { struct skb_data *entry = (struct skb_data *) skb->cb; entry->packets = packets; + entry->length = bytes_delta; } extern int usbnet_open(struct net_device *net); -- cgit v0.10.2 From f243e5a7859a24d10975afb9a1708cac624ba6f1 Mon Sep 17 00:00:00 2001 From: WANG Cong Date: Wed, 25 Mar 2015 14:45:03 -0700 Subject: ipmr,ip6mr: call ip6mr_free_table() on failure path Signed-off-by: Cong Wang Signed-off-by: David S. Miller diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 9d78427..9282544 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -268,7 +268,7 @@ static int __net_init ipmr_rules_init(struct net *net) return 0; err2: - kfree(mrt); + ipmr_free_table(mrt); err1: fib_rules_unregister(ops); return err; diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 34b6826..52028f4 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -252,7 +252,7 @@ static int __net_init ip6mr_rules_init(struct net *net) return 0; err2: - kfree(mrt); + ip6mr_free_table(mrt); err1: fib_rules_unregister(ops); return err; -- cgit v0.10.2 From 4b8e27a86d209063d8aacfb918668bbb75e56019 Mon Sep 17 00:00:00 2001 From: Hariprasad Shenai Date: Thu, 26 Mar 2015 10:04:25 +0530 Subject: cxgb4: Allocate dynamic mem. for egress and ingress queue maps QIDs (egress/ingress) from firmware in FW_*_CMD.alloc command can be anywhere in the range from EQ(IQFLINT)_START to EQ(IQFLINT)_END. For eg, in the first load eqid can be from 100 to 300. In the next load it can be from 301 to 500 (assume eq_start is 100 and eq_end is 1000). The driver was assuming them to always start from EQ(IQFLINT)_START till MAX_EGRQ(INGQ). This was causing stack overflow and subsequent crash. Fixed it by dynamically allocating memory (of qsize (x_END - x_START + 1)) for these structures. Based on original work by Santosh Rastapur Signed-off-by: Hariprasad Shenai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h index 97842d0..8816c64 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h @@ -376,8 +376,6 @@ enum { enum { INGQ_EXTRAS = 2, /* firmware event queue and */ /* forwarded interrupts */ - MAX_EGRQ = MAX_ETH_QSETS*2 + MAX_OFLD_QSETS*2 - + MAX_CTRL_QUEUES + MAX_RDMA_QUEUES + MAX_ISCSI_QUEUES, MAX_INGQ = MAX_ETH_QSETS + MAX_OFLD_QSETS + MAX_RDMA_QUEUES + MAX_RDMA_CIQS + MAX_ISCSI_QUEUES + INGQ_EXTRAS, }; @@ -616,11 +614,13 @@ struct sge { unsigned int idma_qid[2]; /* SGE IDMA Hung Ingress Queue ID */ unsigned int egr_start; + unsigned int egr_sz; unsigned int ingr_start; - void *egr_map[MAX_EGRQ]; /* qid->queue egress queue map */ - struct sge_rspq *ingr_map[MAX_INGQ]; /* qid->queue ingress queue map */ - DECLARE_BITMAP(starving_fl, MAX_EGRQ); - DECLARE_BITMAP(txq_maperr, MAX_EGRQ); + unsigned int ingr_sz; + void **egr_map; /* qid->queue egress queue map */ + struct sge_rspq **ingr_map; /* qid->queue ingress queue map */ + unsigned long *starving_fl; + unsigned long *txq_maperr; struct timer_list rx_timer; /* refills starving FLs */ struct timer_list tx_timer; /* checks Tx queues */ }; diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index a22cf93..b9b5a7b 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -920,7 +920,7 @@ static void quiesce_rx(struct adapter *adap) { int i; - for (i = 0; i < ARRAY_SIZE(adap->sge.ingr_map); i++) { + for (i = 0; i < adap->sge.ingr_sz; i++) { struct sge_rspq *q = adap->sge.ingr_map[i]; if (q && q->handler) { @@ -941,7 +941,7 @@ static void enable_rx(struct adapter *adap) { int i; - for (i = 0; i < ARRAY_SIZE(adap->sge.ingr_map); i++) { + for (i = 0; i < adap->sge.ingr_sz; i++) { struct sge_rspq *q = adap->sge.ingr_map[i]; if (!q) @@ -970,8 +970,8 @@ static int setup_sge_queues(struct adapter *adap) int err, msi_idx, i, j; struct sge *s = &adap->sge; - bitmap_zero(s->starving_fl, MAX_EGRQ); - bitmap_zero(s->txq_maperr, MAX_EGRQ); + bitmap_zero(s->starving_fl, s->egr_sz); + bitmap_zero(s->txq_maperr, s->egr_sz); if (adap->flags & USING_MSIX) msi_idx = 1; /* vector 0 is for non-queue interrupts */ @@ -983,6 +983,19 @@ static int setup_sge_queues(struct adapter *adap) msi_idx = -((int)s->intrq.abs_id + 1); } + /* NOTE: If you add/delete any Ingress/Egress Queue allocations in here, + * don't forget to update the following which need to be + * synchronized to and changes here. + * + * 1. The calculations of MAX_INGQ in cxgb4.h. + * + * 2. Update enable_msix/name_msix_vecs/request_msix_queue_irqs + * to accommodate any new/deleted Ingress Queues + * which need MSI-X Vectors. + * + * 3. Update sge_qinfo_show() to include information on the + * new/deleted queues. + */ err = t4_sge_alloc_rxq(adap, &s->fw_evtq, true, adap->port[0], msi_idx, NULL, fwevtq_handler); if (err) { @@ -4733,8 +4746,9 @@ static int adap_init1(struct adapter *adap, struct fw_caps_config_cmd *c) if (ret < 0) return ret; - ret = t4_cfg_pfvf(adap, adap->fn, adap->fn, 0, MAX_EGRQ, 64, MAX_INGQ, - 0, 0, 4, 0xf, 0xf, 16, FW_CMD_CAP_PF, FW_CMD_CAP_PF); + ret = t4_cfg_pfvf(adap, adap->fn, adap->fn, 0, adap->sge.egr_sz, 64, + MAX_INGQ, 0, 0, 4, 0xf, 0xf, 16, FW_CMD_CAP_PF, + FW_CMD_CAP_PF); if (ret < 0) return ret; @@ -5293,6 +5307,51 @@ static int adap_init0(struct adapter *adap) adap->tids.nftids = val[4] - val[3] + 1; adap->sge.ingr_start = val[5]; + /* qids (ingress/egress) returned from firmware can be anywhere + * in the range from EQ(IQFLINT)_START to EQ(IQFLINT)_END. + * Hence driver needs to allocate memory for this range to + * store the queue info. Get the highest IQFLINT/EQ index returned + * in FW_EQ_*_CMD.alloc command. + */ + params[0] = FW_PARAM_PFVF(EQ_END); + params[1] = FW_PARAM_PFVF(IQFLINT_END); + ret = t4_query_params(adap, adap->mbox, adap->fn, 0, 2, params, val); + if (ret < 0) + goto bye; + adap->sge.egr_sz = val[0] - adap->sge.egr_start + 1; + adap->sge.ingr_sz = val[1] - adap->sge.ingr_start + 1; + + adap->sge.egr_map = kcalloc(adap->sge.egr_sz, + sizeof(*adap->sge.egr_map), GFP_KERNEL); + if (!adap->sge.egr_map) { + ret = -ENOMEM; + goto bye; + } + + adap->sge.ingr_map = kcalloc(adap->sge.ingr_sz, + sizeof(*adap->sge.ingr_map), GFP_KERNEL); + if (!adap->sge.ingr_map) { + ret = -ENOMEM; + goto bye; + } + + /* Allocate the memory for the vaious egress queue bitmaps + * ie starving_fl and txq_maperr. + */ + adap->sge.starving_fl = kcalloc(BITS_TO_LONGS(adap->sge.egr_sz), + sizeof(long), GFP_KERNEL); + if (!adap->sge.starving_fl) { + ret = -ENOMEM; + goto bye; + } + + adap->sge.txq_maperr = kcalloc(BITS_TO_LONGS(adap->sge.egr_sz), + sizeof(long), GFP_KERNEL); + if (!adap->sge.txq_maperr) { + ret = -ENOMEM; + goto bye; + } + params[0] = FW_PARAM_PFVF(CLIP_START); params[1] = FW_PARAM_PFVF(CLIP_END); ret = t4_query_params(adap, adap->mbox, adap->fn, 0, 2, params, val); @@ -5501,6 +5560,10 @@ static int adap_init0(struct adapter *adap) * happened to HW/FW, stop issuing commands. */ bye: + kfree(adap->sge.egr_map); + kfree(adap->sge.ingr_map); + kfree(adap->sge.starving_fl); + kfree(adap->sge.txq_maperr); if (ret != -ETIMEDOUT && ret != -EIO) t4_fw_bye(adap, adap->mbox); return ret; @@ -5912,6 +5975,10 @@ static void free_some_resources(struct adapter *adapter) t4_free_mem(adapter->l2t); t4_free_mem(adapter->tids.tid_tab); + kfree(adapter->sge.egr_map); + kfree(adapter->sge.ingr_map); + kfree(adapter->sge.starving_fl); + kfree(adapter->sge.txq_maperr); disable_msi(adapter); for_each_port(adapter, i) diff --git a/drivers/net/ethernet/chelsio/cxgb4/sge.c b/drivers/net/ethernet/chelsio/cxgb4/sge.c index b4b9f60..b688b32 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/sge.c +++ b/drivers/net/ethernet/chelsio/cxgb4/sge.c @@ -2171,7 +2171,7 @@ static void sge_rx_timer_cb(unsigned long data) struct adapter *adap = (struct adapter *)data; struct sge *s = &adap->sge; - for (i = 0; i < ARRAY_SIZE(s->starving_fl); i++) + for (i = 0; i < BITS_TO_LONGS(s->egr_sz); i++) for (m = s->starving_fl[i]; m; m &= m - 1) { struct sge_eth_rxq *rxq; unsigned int id = __ffs(m) + i * BITS_PER_LONG; @@ -2259,7 +2259,7 @@ static void sge_tx_timer_cb(unsigned long data) struct adapter *adap = (struct adapter *)data; struct sge *s = &adap->sge; - for (i = 0; i < ARRAY_SIZE(s->txq_maperr); i++) + for (i = 0; i < BITS_TO_LONGS(s->egr_sz); i++) for (m = s->txq_maperr[i]; m; m &= m - 1) { unsigned long id = __ffs(m) + i * BITS_PER_LONG; struct sge_ofld_txq *txq = s->egr_map[id]; @@ -2741,7 +2741,8 @@ void t4_free_sge_resources(struct adapter *adap) free_rspq_fl(adap, &adap->sge.intrq, NULL); /* clear the reverse egress queue map */ - memset(adap->sge.egr_map, 0, sizeof(adap->sge.egr_map)); + memset(adap->sge.egr_map, 0, + adap->sge.egr_sz * sizeof(*adap->sge.egr_map)); } void t4_sge_start(struct adapter *adap) -- cgit v0.10.2 From b37987e8db5faee3fc12baecc4699f9d92bc0695 Mon Sep 17 00:00:00 2001 From: Hariprasad Shenai Date: Thu, 26 Mar 2015 10:04:26 +0530 Subject: cxgb4: Disable interrupts and napi before unregistering netdev Disable interrupts and quiesce rx before unregistering net device to avoid crash while unloading driver when traffic is flowing through. Based on original work by Shameem Khalid Signed-off-by: Hariprasad Shenai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index b9b5a7b..826de7a7 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -934,6 +934,21 @@ static void quiesce_rx(struct adapter *adap) } } +/* Disable interrupt and napi handler */ +static void disable_interrupts(struct adapter *adap) +{ + if (adap->flags & FULL_INIT_DONE) { + t4_intr_disable(adap); + if (adap->flags & USING_MSIX) { + free_msix_queue_irqs(adap); + free_irq(adap->msix_info[0].vec, adap); + } else { + free_irq(adap->pdev->irq, adap); + } + quiesce_rx(adap); + } +} + /* * Enable NAPI scheduling and interrupt generation for all Rx queues. */ @@ -4257,19 +4272,12 @@ static int cxgb_up(struct adapter *adap) static void cxgb_down(struct adapter *adapter) { - t4_intr_disable(adapter); cancel_work_sync(&adapter->tid_release_task); cancel_work_sync(&adapter->db_full_task); cancel_work_sync(&adapter->db_drop_task); adapter->tid_release_task_busy = false; adapter->tid_release_head = NULL; - if (adapter->flags & USING_MSIX) { - free_msix_queue_irqs(adapter); - free_irq(adapter->msix_info[0].vec, adapter); - } else - free_irq(adapter->pdev->irq, adapter); - quiesce_rx(adapter); t4_sge_stop(adapter); t4_free_sge_resources(adapter); adapter->flags &= ~FULL_INIT_DONE; @@ -5591,6 +5599,7 @@ static pci_ers_result_t eeh_err_detected(struct pci_dev *pdev, netif_carrier_off(dev); } spin_unlock(&adap->stats_lock); + disable_interrupts(adap); if (adap->flags & FULL_INIT_DONE) cxgb_down(adap); rtnl_unlock(); @@ -6304,6 +6313,8 @@ static void remove_one(struct pci_dev *pdev) if (is_offload(adapter)) detach_ulds(adapter); + disable_interrupts(adapter); + for_each_port(adapter, i) if (adapter->port[i]->reg_state == NETREG_REGISTERED) unregister_netdev(adapter->port[i]); -- cgit v0.10.2 From 263fcd312deffb9bf10f007f958dccfa64a807f5 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 27 Mar 2015 14:15:02 +0000 Subject: irqchip: gicv3-its: Fix encoding of collection's target redistributor With a monolithic GICv3, redistributors are addressed using a linear number, while a distributed implementation uses physical addresses. When encoding a target address into a command, we strip the lower 16 bits, as redistributors are always 64kB aligned. This works perfectly well with a distributed implementation, but has the silly effect of always encoding target 0 in the monolithic case (unless you have more than 64k CPUs, of course). The obvious fix is to shift the linear target number by 16 when computing the target address, so that we don't loose any precious bit. Reported-by: Andre Przywara Tested-by: Andre Przywara Signed-off-by: Marc Zyngier Link: https://lkml.kernel.org/r/1427465705-17126-2-git-send-email-marc.zyngier@arm.com Signed-off-by: Jason Cooper diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 596b0a9..7318dba 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1026,7 +1026,7 @@ static void its_cpu_init_collection(void) * This ITS wants a linear CPU number. */ target = readq_relaxed(gic_data_rdist_rd_base() + GICR_TYPER); - target = GICR_TYPER_CPU_NUMBER(target); + target = GICR_TYPER_CPU_NUMBER(target) << 16; } /* Perform collection mapping */ -- cgit v0.10.2 From 7e195ba03738dec72fe337dcd3cb3c3c2bd66c30 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 27 Mar 2015 14:15:03 +0000 Subject: irqchip: gicv3-its: Fix device ID encoding When building ITS commands which have the device ID in it, we should mask off the whole upper 32 bits of the first command word before inserting the new value in there. Signed-off-by: Andre Przywara Signed-off-by: Marc Zyngier Link: https://lkml.kernel.org/r/1427465705-17126-3-git-send-email-marc.zyngier@arm.com Signed-off-by: Jason Cooper diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 7318dba..fa0c436 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -169,7 +169,7 @@ static void its_encode_cmd(struct its_cmd_block *cmd, u8 cmd_nr) static void its_encode_devid(struct its_cmd_block *cmd, u32 devid) { - cmd->raw_cmd[0] &= ~(0xffffUL << 32); + cmd->raw_cmd[0] &= BIT_ULL(32) - 1; cmd->raw_cmd[0] |= ((u64)devid) << 32; } -- cgit v0.10.2 From 4ad3e3634a6cbe916722c7113c5b488d52c7a3dc Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 27 Mar 2015 14:15:04 +0000 Subject: irqchip: gicv3-its: Fix PROP/PEND and BASE/CBASE confusion The ITS driver sometime mixes up the use of GICR_PROPBASE bitfields for the GICR_PENDBASE register, and GITS_BASER for GICR_CBASE. This does not lead to any observable bug because similar bits are at the same location, but this just make the code even harder to understand... This patch provides the required #defines and fixes the mixup. Signed-off-by: Marc Zyngier Link: https://lkml.kernel.org/r/1427465705-17126-4-git-send-email-marc.zyngier@arm.com Signed-off-by: Jason Cooper diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index fa0c436..56353f6 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -986,8 +986,8 @@ static void its_cpu_init_lpis(void) /* set PENDBASE */ val = (page_to_phys(pend_page) | - GICR_PROPBASER_InnerShareable | - GICR_PROPBASER_WaWb); + GICR_PENDBASER_InnerShareable | + GICR_PENDBASER_WaWb); writeq_relaxed(val, rbase + GICR_PENDBASER); @@ -1425,7 +1425,7 @@ static int its_probe(struct device_node *node, struct irq_domain *parent) writeq_relaxed(0, its->base + GITS_CWRITER); writel_relaxed(GITS_CTLR_ENABLE, its->base + GITS_CTLR); - if ((tmp ^ baser) & GITS_BASER_SHAREABILITY_MASK) { + if ((tmp ^ baser) & GITS_CBASER_SHAREABILITY_MASK) { pr_info("ITS: using cache flushing for cmd queue\n"); its->flags |= ITS_FLAGS_CMDQ_NEEDS_FLUSHING; } diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 781974a..826a4bd 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -128,6 +128,19 @@ #define GICR_PROPBASER_RaWaWb (7U << 7) #define GICR_PROPBASER_IDBITS_MASK (0x1f) +#define GICR_PENDBASER_NonShareable (0U << 10) +#define GICR_PENDBASER_InnerShareable (1U << 10) +#define GICR_PENDBASER_OuterShareable (2U << 10) +#define GICR_PENDBASER_SHAREABILITY_MASK (3UL << 10) +#define GICR_PENDBASER_nCnB (0U << 7) +#define GICR_PENDBASER_nC (1U << 7) +#define GICR_PENDBASER_RaWt (2U << 7) +#define GICR_PENDBASER_RaWb (3U << 7) +#define GICR_PENDBASER_WaWt (4U << 7) +#define GICR_PENDBASER_WaWb (5U << 7) +#define GICR_PENDBASER_RaWaWt (6U << 7) +#define GICR_PENDBASER_RaWaWb (7U << 7) + /* * Re-Distributor registers, offsets from SGI_base */ -- cgit v0.10.2 From 241a386c7dbb8b0db400a1f92f2ebe3b10eb661d Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 27 Mar 2015 14:15:05 +0000 Subject: irqchip: gicv3-its: Use non-cacheable accesses when no shareability If the ITS or the redistributors report their shareability as zero, then it is important to make sure they will no generate any cacheable traffic, as this is unlikely to produce the expected result. Signed-off-by: Marc Zyngier Link: https://lkml.kernel.org/r/1427465705-17126-5-git-send-email-marc.zyngier@arm.com Signed-off-by: Jason Cooper diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 56353f6..9687f8a 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -802,6 +802,7 @@ static int its_alloc_tables(struct its_node *its) int i; int psz = SZ_64K; u64 shr = GITS_BASER_InnerShareable; + u64 cache = GITS_BASER_WaWb; for (i = 0; i < GITS_BASER_NR_REGS; i++) { u64 val = readq_relaxed(its->base + GITS_BASER + i * 8); @@ -848,7 +849,7 @@ retry_baser: val = (virt_to_phys(base) | (type << GITS_BASER_TYPE_SHIFT) | ((entry_size - 1) << GITS_BASER_ENTRY_SIZE_SHIFT) | - GITS_BASER_WaWb | + cache | shr | GITS_BASER_VALID); @@ -874,9 +875,12 @@ retry_baser: * Shareability didn't stick. Just use * whatever the read reported, which is likely * to be the only thing this redistributor - * supports. + * supports. If that's zero, make it + * non-cacheable as well. */ shr = tmp & GITS_BASER_SHAREABILITY_MASK; + if (!shr) + cache = GITS_BASER_nC; goto retry_baser; } @@ -980,6 +984,17 @@ static void its_cpu_init_lpis(void) tmp = readq_relaxed(rbase + GICR_PROPBASER); if ((tmp ^ val) & GICR_PROPBASER_SHAREABILITY_MASK) { + if (!(tmp & GICR_PROPBASER_SHAREABILITY_MASK)) { + /* + * The HW reports non-shareable, we must + * remove the cacheability attributes as + * well. + */ + val &= ~(GICR_PROPBASER_SHAREABILITY_MASK | + GICR_PROPBASER_CACHEABILITY_MASK); + val |= GICR_PROPBASER_nC; + writeq_relaxed(val, rbase + GICR_PROPBASER); + } pr_info_once("GIC: using cache flushing for LPI property table\n"); gic_rdists->flags |= RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING; } @@ -990,6 +1005,18 @@ static void its_cpu_init_lpis(void) GICR_PENDBASER_WaWb); writeq_relaxed(val, rbase + GICR_PENDBASER); + tmp = readq_relaxed(rbase + GICR_PENDBASER); + + if (!(tmp & GICR_PENDBASER_SHAREABILITY_MASK)) { + /* + * The HW reports non-shareable, we must remove the + * cacheability attributes as well. + */ + val &= ~(GICR_PENDBASER_SHAREABILITY_MASK | + GICR_PENDBASER_CACHEABILITY_MASK); + val |= GICR_PENDBASER_nC; + writeq_relaxed(val, rbase + GICR_PENDBASER); + } /* Enable LPIs */ val = readl_relaxed(rbase + GICR_CTLR); @@ -1422,14 +1449,26 @@ static int its_probe(struct device_node *node, struct irq_domain *parent) writeq_relaxed(baser, its->base + GITS_CBASER); tmp = readq_relaxed(its->base + GITS_CBASER); - writeq_relaxed(0, its->base + GITS_CWRITER); - writel_relaxed(GITS_CTLR_ENABLE, its->base + GITS_CTLR); if ((tmp ^ baser) & GITS_CBASER_SHAREABILITY_MASK) { + if (!(tmp & GITS_CBASER_SHAREABILITY_MASK)) { + /* + * The HW reports non-shareable, we must + * remove the cacheability attributes as + * well. + */ + baser &= ~(GITS_CBASER_SHAREABILITY_MASK | + GITS_CBASER_CACHEABILITY_MASK); + baser |= GITS_CBASER_nC; + writeq_relaxed(baser, its->base + GITS_CBASER); + } pr_info("ITS: using cache flushing for cmd queue\n"); its->flags |= ITS_FLAGS_CMDQ_NEEDS_FLUSHING; } + writeq_relaxed(0, its->base + GITS_CWRITER); + writel_relaxed(GITS_CTLR_ENABLE, its->base + GITS_CTLR); + if (of_property_read_bool(its->msi_chip.of_node, "msi-controller")) { its->domain = irq_domain_add_tree(NULL, &its_domain_ops, its); if (!its->domain) { diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 826a4bd..ffbc034 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -126,6 +126,7 @@ #define GICR_PROPBASER_WaWb (5U << 7) #define GICR_PROPBASER_RaWaWt (6U << 7) #define GICR_PROPBASER_RaWaWb (7U << 7) +#define GICR_PROPBASER_CACHEABILITY_MASK (7U << 7) #define GICR_PROPBASER_IDBITS_MASK (0x1f) #define GICR_PENDBASER_NonShareable (0U << 10) @@ -140,6 +141,7 @@ #define GICR_PENDBASER_WaWb (5U << 7) #define GICR_PENDBASER_RaWaWt (6U << 7) #define GICR_PENDBASER_RaWaWb (7U << 7) +#define GICR_PENDBASER_CACHEABILITY_MASK (7U << 7) /* * Re-Distributor registers, offsets from SGI_base @@ -195,6 +197,7 @@ #define GITS_CBASER_WaWb (5UL << 59) #define GITS_CBASER_RaWaWt (6UL << 59) #define GITS_CBASER_RaWaWb (7UL << 59) +#define GITS_CBASER_CACHEABILITY_MASK (7UL << 59) #define GITS_CBASER_NonShareable (0UL << 10) #define GITS_CBASER_InnerShareable (1UL << 10) #define GITS_CBASER_OuterShareable (2UL << 10) @@ -211,6 +214,7 @@ #define GITS_BASER_WaWb (5UL << 59) #define GITS_BASER_RaWaWt (6UL << 59) #define GITS_BASER_RaWaWb (7UL << 59) +#define GITS_BASER_CACHEABILITY_MASK (7UL << 59) #define GITS_BASER_TYPE_SHIFT (56) #define GITS_BASER_TYPE(r) (((r) >> GITS_BASER_TYPE_SHIFT) & 7) #define GITS_BASER_ENTRY_SIZE_SHIFT (48) -- cgit v0.10.2 From 4217291e592da0e4258b652e82e5428639d29acc Mon Sep 17 00:00:00 2001 From: Nicolas Dichtel Date: Thu, 26 Mar 2015 17:56:38 +0100 Subject: netns: don't clear nsid too early on removal With the current code, ids are removed too early. Suppose you have an ipip interface that stands in the netns foo and its link part in the netns bar (so the netns bar has an nsid into the netns foo). Now, you remove the netns bar: - the bar nsid into the netns foo is removed - the netns exit method of ipip is called, thus our ipip iface is removed: => a netlink message is sent in the netns foo to advertise this deletion => this netlink message requests an nsid for bar, thus a new nsid is allocated for bar and never removed. We must remove nsids when we are sure that nobody will refer to netns currently cleaned. Fixes: 0c7aecd4bde4 ("netns: add rtnl cmd to add and get peer netns ids") Signed-off-by: Nicolas Dichtel Signed-off-by: David S. Miller diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index cb5290b..5221f97 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -349,7 +349,7 @@ static LIST_HEAD(cleanup_list); /* Must hold cleanup_list_lock to touch */ static void cleanup_net(struct work_struct *work) { const struct pernet_operations *ops; - struct net *net, *tmp; + struct net *net, *tmp, *peer; struct list_head net_kill_list; LIST_HEAD(net_exit_list); @@ -365,14 +365,6 @@ static void cleanup_net(struct work_struct *work) list_for_each_entry(net, &net_kill_list, cleanup_list) { list_del_rcu(&net->list); list_add_tail(&net->exit_list, &net_exit_list); - for_each_net(tmp) { - int id = __peernet2id(tmp, net, false); - - if (id >= 0) - idr_remove(&tmp->netns_ids, id); - } - idr_destroy(&net->netns_ids); - } rtnl_unlock(); @@ -398,12 +390,26 @@ static void cleanup_net(struct work_struct *work) */ rcu_barrier(); + rtnl_lock(); /* Finally it is safe to free my network namespace structure */ list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) { + /* Unreference net from all peers (no need to loop over + * net_exit_list because idr_destroy() will be called for each + * element of this list. + */ + for_each_net(peer) { + int id = __peernet2id(peer, net, false); + + if (id >= 0) + idr_remove(&peer->netns_ids, id); + } + idr_destroy(&net->netns_ids); + list_del_init(&net->exit_list); put_user_ns(net->user_ns); net_drop_ns(net); } + rtnl_unlock(); } static DECLARE_WORK(net_cleanup_work, cleanup_net); -- cgit v0.10.2 From 2ff2acf1fb65f1c83c41b67aba990c6d49a2274c Mon Sep 17 00:00:00 2001 From: Hariprasad Shenai Date: Fri, 27 Mar 2015 11:01:18 +0530 Subject: cxgb4vf: Fix sparse warnings Fixes sparse warnings introduced in commit e85c9a7abfa407ed ("cxgb4/cxgb4vf: Add code to calculate T5 BAR2 Offsets for SGE Queue Registers") and df64e4d38c904dd3 ("cxgb4/cxgb4vf: Use new interfaces to calculate BAR2 SGE Queue Register addresses") and few old ones sparse warnings: >> drivers/net/ethernet/chelsio/cxgb4vf/sge.c:1006:48: sparse: cast removes >> address space of expression >> drivers/net/ethernet/chelsio/cxgb4vf/sge.c:1006:48: sparse: incorrect type in >> initializer (different address space) >> drivers/net/ethernet/chelsio/cxgb4vf/sge.c:1020:40: sparse: incorrect type in >> argument 1 (different base types) Reported-by: Dan Carpenter Reported-by: kbuild test robot Signed-off-by: Hariprasad Shenai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/chelsio/cxgb4vf/sge.c b/drivers/net/ethernet/chelsio/cxgb4vf/sge.c index 0545f0d..e0d7110 100644 --- a/drivers/net/ethernet/chelsio/cxgb4vf/sge.c +++ b/drivers/net/ethernet/chelsio/cxgb4vf/sge.c @@ -1004,7 +1004,7 @@ static inline void ring_tx_db(struct adapter *adapter, struct sge_txq *tq, ? (tq->pidx - 1) : (tq->size - 1)); __be64 *src = (__be64 *)&tq->desc[index]; - __be64 __iomem *dst = (__be64 *)(tq->bar2_addr + + __be64 __iomem *dst = (__be64 __iomem *)(tq->bar2_addr + SGE_UDB_WCDOORBELL); unsigned int count = EQ_UNIT / sizeof(__be64); @@ -1018,7 +1018,11 @@ static inline void ring_tx_db(struct adapter *adapter, struct sge_txq *tq, * DMA. */ while (count) { - writeq(*src, dst); + /* the (__force u64) is because the compiler + * doesn't understand the endian swizzling + * going on + */ + writeq((__force u64)*src, dst); src++; dst++; count--; @@ -1252,8 +1256,8 @@ int t4vf_eth_xmit(struct sk_buff *skb, struct net_device *dev) BUG_ON(DIV_ROUND_UP(ETHTXQ_MAX_HDR, TXD_PER_EQ_UNIT) > 1); wr = (void *)&txq->q.desc[txq->q.pidx]; wr->equiq_to_len16 = cpu_to_be32(wr_mid); - wr->r3[0] = cpu_to_be64(0); - wr->r3[1] = cpu_to_be64(0); + wr->r3[0] = cpu_to_be32(0); + wr->r3[1] = cpu_to_be32(0); skb_copy_from_linear_data(skb, (void *)wr->ethmacdst, fw_hdr_copy_len); end = (u64 *)wr + flits; diff --git a/drivers/net/ethernet/chelsio/cxgb4vf/t4vf_hw.c b/drivers/net/ethernet/chelsio/cxgb4vf/t4vf_hw.c index 1b5506d..280b4a2 100644 --- a/drivers/net/ethernet/chelsio/cxgb4vf/t4vf_hw.c +++ b/drivers/net/ethernet/chelsio/cxgb4vf/t4vf_hw.c @@ -210,10 +210,10 @@ int t4vf_wr_mbox_core(struct adapter *adapter, const void *cmd, int size, if (rpl) { /* request bit in high-order BE word */ - WARN_ON((be32_to_cpu(*(const u32 *)cmd) + WARN_ON((be32_to_cpu(*(const __be32 *)cmd) & FW_CMD_REQUEST_F) == 0); get_mbox_rpl(adapter, rpl, size, mbox_data); - WARN_ON((be32_to_cpu(*(u32 *)rpl) + WARN_ON((be32_to_cpu(*(__be32 *)rpl) & FW_CMD_REQUEST_F) != 0); } t4_write_reg(adapter, mbox_ctl, @@ -484,7 +484,7 @@ int t4_bar2_sge_qregs(struct adapter *adapter, * o The BAR2 Queue ID. * o The BAR2 Queue ID Offset into the BAR2 page. */ - bar2_page_offset = ((qid >> qpp_shift) << page_shift); + bar2_page_offset = ((u64)(qid >> qpp_shift) << page_shift); bar2_qid = qid & qpp_mask; bar2_qid_offset = bar2_qid * SGE_UDB_SIZE; -- cgit v0.10.2 From 4ad19de8774e2a7b075b3e8ea48db85adcf33fa6 Mon Sep 17 00:00:00 2001 From: Alexey Kodanev Date: Fri, 27 Mar 2015 12:24:22 +0300 Subject: net: tcp6: fix double call of tcp_v6_fill_cb() tcp_v6_fill_cb() will be called twice if socket's state changes from TCP_TIME_WAIT to TCP_LISTEN. That can result in control buffer data corruption because in the second tcp_v6_fill_cb() call it's not copying IP6CB(skb) anymore, but 'seq', 'end_seq', etc., so we can get weird and unpredictable results. Performance loss of up to 1200% has been observed in LTP/vxlan03 test. This can be fixed by copying inet6_skb_parm to the beginning of 'cb' only if xfrm6_policy_check() and tcp_v6_fill_cb() are going to be called again. Fixes: 2dc49d1680b53 ("tcp6: don't move IP6CB before xfrm6_policy_check()") Signed-off-by: Alexey Kodanev Acked-by: Eric Dumazet Signed-off-by: David S. Miller diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index b283a49..1f5e622 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1411,6 +1411,15 @@ static void tcp_v6_fill_cb(struct sk_buff *skb, const struct ipv6hdr *hdr, TCP_SKB_CB(skb)->sacked = 0; } +static void tcp_v6_restore_cb(struct sk_buff *skb) +{ + /* We need to move header back to the beginning if xfrm6_policy_check() + * and tcp_v6_fill_cb() are going to be called again. + */ + memmove(IP6CB(skb), &TCP_SKB_CB(skb)->header.h6, + sizeof(struct inet6_skb_parm)); +} + static int tcp_v6_rcv(struct sk_buff *skb) { const struct tcphdr *th; @@ -1543,6 +1552,7 @@ do_time_wait: inet_twsk_deschedule(tw, &tcp_death_row); inet_twsk_put(tw); sk = sk2; + tcp_v6_restore_cb(skb); goto process; } /* Fall through to ACK */ @@ -1551,6 +1561,7 @@ do_time_wait: tcp_v6_timewait_ack(sk, skb); break; case TCP_TW_RST: + tcp_v6_restore_cb(skb); goto no_tcp_socket; case TCP_TW_SUCCESS: ; -- cgit v0.10.2 From 63c607321492c5efc7a31bc4ea734b877f8e7f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 27 Mar 2015 11:08:32 +0100 Subject: net: fec: setup right value for mdio hold time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FEC modules used on i.MX28 and newer have a register to tune the MDIO output hold time that should be at least 10 ns. Up to now this value was not explicitly set and so resulted in less hold time if the fec clock was faster than 100 MHz. This was noticed on an i.MX28 machine that uses an input clock of ~150 Mhz which resulted in unreliable communication with a Marvell switch. Signed-off-by: Uwe Kleine-König Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index 78e1ce0..f6a3a7a 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -1954,6 +1954,7 @@ static int fec_enet_mii_init(struct platform_device *pdev) struct fec_enet_private *fep = netdev_priv(ndev); struct device_node *node; int err = -ENXIO, i; + u32 mii_speed, holdtime; /* * The i.MX28 dual fec interfaces are not equal. @@ -1991,10 +1992,33 @@ static int fec_enet_mii_init(struct platform_device *pdev) * Reference Manual has an error on this, and gets fixed on i.MX6Q * document. */ - fep->phy_speed = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 5000000); + mii_speed = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 5000000); if (fep->quirks & FEC_QUIRK_ENET_MAC) - fep->phy_speed--; - fep->phy_speed <<= 1; + mii_speed--; + if (mii_speed > 63) { + dev_err(&pdev->dev, + "fec clock (%lu) to fast to get right mii speed\n", + clk_get_rate(fep->clk_ipg)); + err = -EINVAL; + goto err_out; + } + + /* + * The i.MX28 and i.MX6 types have another filed in the MSCR (aka + * MII_SPEED) register that defines the MDIO output hold time. Earlier + * versions are RAZ there, so just ignore the difference and write the + * register always. + * The minimal hold time according to IEE802.3 (clause 22) is 10 ns. + * HOLDTIME + 1 is the number of clk cycles the fec is holding the + * output. + * The HOLDTIME bitfield takes values between 0 and 7 (inclusive). + * Given that ceil(clkrate / 5000000) <= 64, the calculation for + * holdtime cannot result in a value greater than 3. + */ + holdtime = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 100000000) - 1; + + fep->phy_speed = mii_speed << 1 | holdtime << 8; + writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED); fep->mii_bus = mdiobus_alloc(); -- cgit v0.10.2 From ee73c61c0af590ff2c03fb83e68a5664bd8b38d0 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 27 Mar 2015 11:02:10 +0000 Subject: drm/i915: Skip allocating shadow batch for 0-length batches Since commit 17cabf571e50677d980e9ab2a43c5f11213003ae Author: Chris Wilson Date: Wed Jan 14 11:20:57 2015 +0000 drm/i915: Trim the command parser allocations we may then try to allocate a zero-sized object and attempt to extract its pages. Understandably this fails. Note that the real offender seems to be commit b9ffd80ed659c559152c042e74741f4f60cac691 Author: Brad Volkin Date: Thu Dec 11 12:13:10 2014 -0800 drm/i915: Use batch length instead of object size in command parser Testcase: igt/gem_exec_nop #ivb,byt,hsw Signed-off-by: Chris Wilson Signed-off-by: Daniel Vetter [cherry picked from commit 743e78c1d726d875b98ff9689cc77c4d3d5d9ae2 from drm-intel-next because 4.0 seems to be affected by this too, despite that the obvious culprit is definitely not in 4.0. Whatever, if fixes a bug. Signed-off-by: Daniel Vetter diff --git a/drivers/gpu/drm/i915/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/i915_gem_execbuffer.c index b773368..38a7425 100644 --- a/drivers/gpu/drm/i915/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/i915_gem_execbuffer.c @@ -1487,7 +1487,7 @@ i915_gem_do_execbuffer(struct drm_device *dev, void *data, goto err; } - if (i915_needs_cmd_parser(ring)) { + if (i915_needs_cmd_parser(ring) && args->batch_len) { batch_obj = i915_gem_execbuffer_parse(ring, &shadow_exec_entry, eb, -- cgit v0.10.2 From ca76683930a1c6acb8dd22867eca2bedaab998e3 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 28 Mar 2015 18:05:44 +0100 Subject: dmaengine: jz4740: Define capabilities Setup the capabilities of the device/driver, so that users of the DMAengine API can query them. Signed-off-by: Lars-Peter Clausen Signed-off-by: Vinod Koul diff --git a/drivers/dma/dma-jz4740.c b/drivers/dma/dma-jz4740.c index 4527a3e..8488441 100644 --- a/drivers/dma/dma-jz4740.c +++ b/drivers/dma/dma-jz4740.c @@ -511,6 +511,9 @@ static void jz4740_dma_desc_free(struct virt_dma_desc *vdesc) kfree(container_of(vdesc, struct jz4740_dma_desc, vdesc)); } +#define JZ4740_DMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \ + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)) + static int jz4740_dma_probe(struct platform_device *pdev) { struct jz4740_dmaengine_chan *chan; @@ -548,6 +551,10 @@ static int jz4740_dma_probe(struct platform_device *pdev) dd->device_prep_dma_cyclic = jz4740_dma_prep_dma_cyclic; dd->device_config = jz4740_dma_slave_config; dd->device_terminate_all = jz4740_dma_terminate_all; + dd->src_addr_widths = JZ4740_DMA_BUSWIDTHS; + dd->dst_addr_widths = JZ4740_DMA_BUSWIDTHS; + dd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); + dd->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; dd->dev = &pdev->dev; INIT_LIST_HEAD(&dd->channels); -- cgit v0.10.2 From 5ca9e7ce6eebec53362ff779264143860ccf68cd Mon Sep 17 00:00:00 2001 From: Petr Kulhavy Date: Fri, 27 Mar 2015 13:35:51 +0200 Subject: dmaengine: edma: fix memory leak when terminating running transfers If edma_terminate_all() was called while a transfer was running (i.e. after edma_execute() but before edma_callback()) the echan->edesc was not freed. This was due to the fact that a running transfer is on none of the vchan lists: desc_submitted, desc_issued, desc_completed (edma_execute() removes it from the desc_issued list), so the vchan_dma_desc_free_list() called at the end of edma_terminate_all() didn't find it and didn't free it. This bug was found on an AM1808 based hardware (very similar to da850evm, however using the second MMC/SD controller), where intense operations on the SD card wasted the device 128MB RAM within a couple of days. Peter Ujfalusi: The issue is even more severe since it affects cyclic (audio) transfers as well. In this case starting/stopping audio will results memory leak. Signed-off-by: Petr Kulhavy Signed-off-by: Peter Ujfalusi CC: CC: Signed-off-by: Vinod Koul diff --git a/drivers/dma/edma.c b/drivers/dma/edma.c index 276157f..53dbd3b 100644 --- a/drivers/dma/edma.c +++ b/drivers/dma/edma.c @@ -260,6 +260,13 @@ static int edma_terminate_all(struct dma_chan *chan) */ if (echan->edesc) { int cyclic = echan->edesc->cyclic; + + /* + * free the running request descriptor + * since it is not in any of the vdesc lists + */ + edma_desc_free(&echan->edesc->vdesc); + echan->edesc = NULL; edma_stop(echan->ch_num); /* Move the cyclic channel back to default queue */ -- cgit v0.10.2 From 02d88b735f5a60f04dbf6d051b76e1877a0d0844 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 27 Mar 2015 13:35:52 +0200 Subject: dmaengine: omap-dma: Fix memory leak when terminating running transfer In omap_dma_start_desc the vdesc->node is removed from the virt-dma framework managed lists (to be precise from the desc_issued list). If a terminate_all comes before the transfer finishes the omap_desc will not be freed up because it is not in any of the lists and we stopped the DMA channel so the transfer will not going to complete. There is no special sequence for leaking memory when using cyclic (audio) transfer: with every start and stop of a cyclic transfer the driver leaks struct omap_desc worth of memory. Free up the allocated memory directly in omap_dma_terminate_all() since the framework will not going to do that for us. Signed-off-by: Peter Ujfalusi CC: CC: Signed-off-by: Vinod Koul diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c index 7dd6dd1..167dbaf 100644 --- a/drivers/dma/omap-dma.c +++ b/drivers/dma/omap-dma.c @@ -981,6 +981,7 @@ static int omap_dma_terminate_all(struct dma_chan *chan) * c->desc is NULL and exit.) */ if (c->desc) { + omap_dma_desc_free(&c->desc->vd); c->desc = NULL; /* Avoid stopping the dma twice */ if (!c->paused) -- cgit v0.10.2 From f93178291712772983845700b12fc1c8b32f2eb1 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 27 Mar 2015 13:35:53 +0200 Subject: dmaengine: bcm2835-dma: Fix memory leak when stopping a running transfer The vd->node is removed from the lists when the transfer started so the vchan_get_all_descriptors() will not find it. This results memory leak. Signed-off-by: Peter Ujfalusi Acked-by: Stephen Warren Signed-off-by: Vinod Koul diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c index 0723096..c92d6a7 100644 --- a/drivers/dma/bcm2835-dma.c +++ b/drivers/dma/bcm2835-dma.c @@ -475,6 +475,7 @@ static int bcm2835_dma_terminate_all(struct dma_chan *chan) * c->desc is NULL and exit.) */ if (c->desc) { + bcm2835_dma_desc_free(&c->desc->vd); c->desc = NULL; bcm2835_dma_abort(c->chan_base); -- cgit v0.10.2 From fbef403aa7b377eb238dc6cade21213c315bc1a7 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 27 Mar 2015 13:35:55 +0200 Subject: dmaengine: moxart-dma: Fix memory leak when stopping a running transfer The vd->node is removed from the lists when the transfer started so the vchan_get_all_descriptors() will not find it. This results memory leak. Signed-off-by: Peter Ujfalusi Signed-off-by: Vinod Koul diff --git a/drivers/dma/moxart-dma.c b/drivers/dma/moxart-dma.c index 15cab7d..b463410 100644 --- a/drivers/dma/moxart-dma.c +++ b/drivers/dma/moxart-dma.c @@ -193,8 +193,10 @@ static int moxart_terminate_all(struct dma_chan *chan) spin_lock_irqsave(&ch->vc.lock, flags); - if (ch->desc) + if (ch->desc) { + moxart_dma_desc_free(&ch->desc->vd); ch->desc = NULL; + } ctrl = readl(ch->base + REG_OFF_CTRL); ctrl &= ~(APB_DMA_ENABLE | APB_DMA_FIN_INT_EN | APB_DMA_ERR_INT_EN); -- cgit v0.10.2 From f3f03330dee0526d82f2a0fd1a79d207ed1ac439 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 30 Mar 2015 18:46:29 +0200 Subject: nfsd: require an explicit option to enable pNFS Turns out sending out layouts to any client is a bad idea if they can't get at the storage device, so require explicit admin action to enable pNFS. Signed-off-by: Christoph Hellwig Signed-off-by: J. Bruce Fields diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index 80e236b..6904213 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -118,7 +118,7 @@ void nfsd4_setup_layout_type(struct svc_export *exp) { struct super_block *sb = exp->ex_path.mnt->mnt_sb; - if (exp->ex_flags & NFSEXP_NOPNFS) + if (!(exp->ex_flags & NFSEXP_PNFS)) return; if (sb->s_export_op->get_uuid && diff --git a/include/uapi/linux/nfsd/export.h b/include/uapi/linux/nfsd/export.h index 4742f2c..d3bd6ffe 100644 --- a/include/uapi/linux/nfsd/export.h +++ b/include/uapi/linux/nfsd/export.h @@ -47,7 +47,7 @@ * exported filesystem. */ #define NFSEXP_V4ROOT 0x10000 -#define NFSEXP_NOPNFS 0x20000 +#define NFSEXP_PNFS 0x20000 /* All flags that we claim to support. (Note we don't support NOACL.) */ #define NFSEXP_ALLFLAGS 0x3FE7F -- cgit v0.10.2 From ca68a525ff6b62e77dc8a8c3020a63321bfb864f Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Sun, 29 Mar 2015 15:54:13 +0200 Subject: MAINTAINERS: Change the x86 microcode loader maintainer Let's make it official - I've been doing this for a while now anyway. Signed-off-by: Borislav Petkov Cc: Andreas Herrmann Cc: Andrew Morton Cc: Greg KH Cc: H. Peter Anvin Cc: Thomas Gleixner Cc: Tigran Aivazian Signed-off-by: Ingo Molnar diff --git a/MAINTAINERS b/MAINTAINERS index 1de6afa..e043628 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -637,8 +637,7 @@ F: drivers/gpu/drm/radeon/radeon_kfd.h F: include/uapi/linux/kfd_ioctl.h AMD MICROCODE UPDATE SUPPORT -M: Andreas Herrmann -L: amd64-microcode@amd64.org +M: Borislav Petkov S: Maintained F: arch/x86/kernel/cpu/microcode/amd* @@ -5095,7 +5094,7 @@ S: Supported F: drivers/platform/x86/intel_menlow.c INTEL IA32 MICROCODE UPDATE SUPPORT -M: Tigran Aivazian +M: Borislav Petkov S: Maintained F: arch/x86/kernel/cpu/microcode/core* F: arch/x86/kernel/cpu/microcode/intel* -- cgit v0.10.2 From 1c6ae56c5d26d22e8ba9ea6d3a0afc8b22b4e207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 29 Mar 2015 21:47:16 +0200 Subject: ASoC: fsi: fix license specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the file header only GPL v2 applies to it. Fix the MODULE_LICENSE parameter accordingly. Signed-off-by: Uwe Kleine-König Signed-off-by: Mark Brown diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index eef7083..936c02d 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -2119,7 +2119,7 @@ static struct platform_driver fsi_driver = { module_platform_driver(fsi_driver); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("SuperH onchip FSI audio driver"); MODULE_AUTHOR("Kuninori Morimoto "); MODULE_ALIAS("platform:fsi-pcm-audio"); -- cgit v0.10.2 From 3a82002c7cbb166b891de515b5463aec41035c75 Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Tue, 31 Mar 2015 15:34:51 +0200 Subject: MAINTAINERS: change the Atmel audio alsa driver entry I take over the the maintainship of Atmel alsa drivers from Voice. Thanks for your work! Signed-off-by: Nicolas Ferre Acked-by: Bo Shen Signed-off-by: Mark Brown diff --git a/MAINTAINERS b/MAINTAINERS index ddc5a8c..679b2d0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1751,7 +1751,7 @@ S: Supported F: drivers/tty/serial/atmel_serial.c ATMEL Audio ALSA driver -M: Bo Shen +M: Nicolas Ferre L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Supported F: sound/soc/atmel -- cgit v0.10.2 From e9637415a92cf25ad800b7fdeddcd30cce7b44ab Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Mon, 30 Mar 2015 13:39:09 -0400 Subject: block: fix blk_stack_limits() regression due to lcm() change Linux 3.19 commit 69c953c ("lib/lcm.c: lcm(n,0)=lcm(0,n) is 0, not n") caused blk_stack_limits() to not properly stack queue_limits for stacked devices (e.g. DM). Fix this regression by establishing lcm_not_zero() and switching blk_stack_limits() over to using it. DM uses blk_set_stacking_limits() to establish the initial top-level queue_limits that are then built up based on underlying devices' limits using blk_stack_limits(). In the case of optimal_io_size (io_opt) blk_set_stacking_limits() establishes a default value of 0. With commit 69c953c, lcm(0, n) is no longer n, which compromises proper stacking of the underlying devices' io_opt. Test: $ modprobe scsi_debug dev_size_mb=10 num_tgts=1 opt_blks=1536 $ cat /sys/block/sde/queue/optimal_io_size 786432 $ dmsetup create node --table "0 100 linear /dev/sde 0" Before this fix: $ cat /sys/block/dm-5/queue/optimal_io_size 0 After this fix: $ cat /sys/block/dm-5/queue/optimal_io_size 786432 Signed-off-by: Mike Snitzer Cc: stable@vger.kernel.org # 3.19+ Acked-by: Martin K. Petersen Signed-off-by: Jens Axboe diff --git a/block/blk-settings.c b/block/blk-settings.c index 6ed2cbe..12600bf 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c @@ -585,7 +585,7 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b, b->physical_block_size); t->io_min = max(t->io_min, b->io_min); - t->io_opt = lcm(t->io_opt, b->io_opt); + t->io_opt = lcm_not_zero(t->io_opt, b->io_opt); t->cluster &= b->cluster; t->discard_zeroes_data &= b->discard_zeroes_data; @@ -616,7 +616,7 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b, b->raid_partial_stripes_expensive); /* Find lowest common alignment_offset */ - t->alignment_offset = lcm(t->alignment_offset, alignment) + t->alignment_offset = lcm_not_zero(t->alignment_offset, alignment) % max(t->physical_block_size, t->io_min); /* Verify that new alignment_offset is on a logical block boundary */ @@ -643,7 +643,7 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b, b->max_discard_sectors); t->discard_granularity = max(t->discard_granularity, b->discard_granularity); - t->discard_alignment = lcm(t->discard_alignment, alignment) % + t->discard_alignment = lcm_not_zero(t->discard_alignment, alignment) % t->discard_granularity; } diff --git a/include/linux/lcm.h b/include/linux/lcm.h index 7bf01d7..1ce79a7 100644 --- a/include/linux/lcm.h +++ b/include/linux/lcm.h @@ -4,5 +4,6 @@ #include unsigned long lcm(unsigned long a, unsigned long b) __attribute_const__; +unsigned long lcm_not_zero(unsigned long a, unsigned long b) __attribute_const__; #endif /* _LCM_H */ diff --git a/lib/lcm.c b/lib/lcm.c index e97dbd5..03d7fcb 100644 --- a/lib/lcm.c +++ b/lib/lcm.c @@ -12,3 +12,14 @@ unsigned long lcm(unsigned long a, unsigned long b) return 0; } EXPORT_SYMBOL_GPL(lcm); + +unsigned long lcm_not_zero(unsigned long a, unsigned long b) +{ + unsigned long l = lcm(a, b); + + if (l) + return l; + + return (b ? : a); +} +EXPORT_SYMBOL_GPL(lcm_not_zero); -- cgit v0.10.2 From 27705f7085ce2e124fac4c280ce824962cc90bb6 Mon Sep 17 00:00:00 2001 From: Jiri Benc Date: Sat, 28 Mar 2015 19:13:22 +0100 Subject: ipvlan: fix addr hash list corruption When ipvlan interface with IP addresses attached is brought down and then deleted, the assigned addresses are deleted twice from the address hash list, first on the interface down and second on the link deletion. Similarly, when an address is added while the interface is down, it is added second time once the interface is brought up. When the interface is down, the addresses should be kept off the hash list for performance reasons. Ensure this is true, which also fixes the double add problem. To fix the double free, check whether the address is hashed before removing it. Reported-by: Dan Williams Signed-off-by: Jiri Benc Signed-off-by: Mahesh Bandewar Signed-off-by: David S. Miller diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index 2a17500..8a542b9 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -81,12 +81,13 @@ void ipvlan_ht_addr_add(struct ipvl_dev *ipvlan, struct ipvl_addr *addr) hash = (addr->atype == IPVL_IPV6) ? ipvlan_get_v6_hash(&addr->ip6addr) : ipvlan_get_v4_hash(&addr->ip4addr); - hlist_add_head_rcu(&addr->hlnode, &port->hlhead[hash]); + if (hlist_unhashed(&addr->hlnode)) + hlist_add_head_rcu(&addr->hlnode, &port->hlhead[hash]); } void ipvlan_ht_addr_del(struct ipvl_addr *addr, bool sync) { - hlist_del_rcu(&addr->hlnode); + hlist_del_init_rcu(&addr->hlnode); if (sync) synchronize_rcu(); } diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 4f4099d..1eb3f33 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -622,7 +622,11 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) addr->atype = IPVL_IPV6; list_add_tail_rcu(&addr->anode, &ipvlan->addrs); ipvlan->ipv6cnt++; - ipvlan_ht_addr_add(ipvlan, addr); + /* If the interface is not up, the address will be added to the hash + * list by ipvlan_open. + */ + if (netif_running(ipvlan->dev)) + ipvlan_ht_addr_add(ipvlan, addr); return 0; } @@ -690,7 +694,11 @@ static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) addr->atype = IPVL_IPV4; list_add_tail_rcu(&addr->anode, &ipvlan->addrs); ipvlan->ipv4cnt++; - ipvlan_ht_addr_add(ipvlan, addr); + /* If the interface is not up, the address will be added to the hash + * list by ipvlan_open. + */ + if (netif_running(ipvlan->dev)) + ipvlan_ht_addr_add(ipvlan, addr); ipvlan_set_broadcast_mac_filter(ipvlan, true); return 0; -- cgit v0.10.2 From 2afa650ce297bc2048bc3d059774be9c39734565 Mon Sep 17 00:00:00 2001 From: Jiri Benc Date: Sat, 28 Mar 2015 19:13:23 +0100 Subject: ipvlan: protect against concurrent link removal Adding and removing to the 'ipvlans' list is already done using _rcu list operations. Signed-off-by: Jiri Benc Signed-off-by: David S. Miller diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index 8a542b9..568628f 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -193,7 +193,8 @@ static void ipvlan_multicast_frame(struct ipvl_port *port, struct sk_buff *skb, if (skb->protocol == htons(ETH_P_PAUSE)) return; - list_for_each_entry(ipvlan, &port->ipvlans, pnode) { + rcu_read_lock(); + list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) { if (local && (ipvlan == in_dev)) continue; @@ -220,6 +221,7 @@ static void ipvlan_multicast_frame(struct ipvl_port *port, struct sk_buff *skb, mcast_acct: ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true); } + rcu_read_unlock(); /* Locally generated? ...Forward a copy to the main-device as * well. On the RX side we'll ignore it (wont give it to any -- cgit v0.10.2 From 40891e8ad630558caae26788d37bf50137b6d5d2 Mon Sep 17 00:00:00 2001 From: Jiri Benc Date: Sat, 28 Mar 2015 19:13:24 +0100 Subject: ipvlan: do not use rcu operations for address list All accesses to ipvlan->addrs are under rtnl. Signed-off-by: Jiri Benc Signed-off-by: David S. Miller diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 1eb3f33..aaa005b 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -505,7 +505,7 @@ static void ipvlan_link_delete(struct net_device *dev, struct list_head *head) if (ipvlan->ipv6cnt > 0 || ipvlan->ipv4cnt > 0) { list_for_each_entry_safe(addr, next, &ipvlan->addrs, anode) { ipvlan_ht_addr_del(addr, !dev->dismantle); - list_del_rcu(&addr->anode); + list_del(&addr->anode); } } list_del_rcu(&ipvlan->pnode); @@ -620,7 +620,7 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) addr->master = ipvlan; memcpy(&addr->ip6addr, ip6_addr, sizeof(struct in6_addr)); addr->atype = IPVL_IPV6; - list_add_tail_rcu(&addr->anode, &ipvlan->addrs); + list_add_tail(&addr->anode, &ipvlan->addrs); ipvlan->ipv6cnt++; /* If the interface is not up, the address will be added to the hash * list by ipvlan_open. @@ -640,7 +640,7 @@ static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) return; ipvlan_ht_addr_del(addr, true); - list_del_rcu(&addr->anode); + list_del(&addr->anode); ipvlan->ipv6cnt--; WARN_ON(ipvlan->ipv6cnt < 0); kfree_rcu(addr, rcu); @@ -692,7 +692,7 @@ static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) addr->master = ipvlan; memcpy(&addr->ip4addr, ip4_addr, sizeof(struct in_addr)); addr->atype = IPVL_IPV4; - list_add_tail_rcu(&addr->anode, &ipvlan->addrs); + list_add_tail(&addr->anode, &ipvlan->addrs); ipvlan->ipv4cnt++; /* If the interface is not up, the address will be added to the hash * list by ipvlan_open. @@ -713,7 +713,7 @@ static void ipvlan_del_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) return; ipvlan_ht_addr_del(addr, true); - list_del_rcu(&addr->anode); + list_del(&addr->anode); ipvlan->ipv4cnt--; WARN_ON(ipvlan->ipv4cnt < 0); if (!ipvlan->ipv4cnt) -- cgit v0.10.2 From e9997c2938b23d792528da1597b3aea9b0f2a324 Mon Sep 17 00:00:00 2001 From: Jiri Benc Date: Sat, 28 Mar 2015 19:13:25 +0100 Subject: ipvlan: fix check for IP addresses in control path When an ipvlan interface is down, its addresses are not on the hash list. Fix checks for existence of addresses not to depend on the hash list, walk through all interface addresses instead. Signed-off-by: Jiri Benc Acked-by: Mahesh Bandewar Signed-off-by: David S. Miller diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 924ea98..54549a6 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -114,7 +114,9 @@ unsigned int ipvlan_mac_hash(const unsigned char *addr); rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb); int ipvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev); void ipvlan_ht_addr_add(struct ipvl_dev *ipvlan, struct ipvl_addr *addr); -bool ipvlan_addr_busy(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6); +struct ipvl_addr *ipvlan_find_addr(const struct ipvl_dev *ipvlan, + const void *iaddr, bool is_v6); +bool ipvlan_addr_busy(struct ipvl_port *port, void *iaddr, bool is_v6); struct ipvl_addr *ipvlan_ht_addr_lookup(const struct ipvl_port *port, const void *iaddr, bool is_v6); void ipvlan_ht_addr_del(struct ipvl_addr *addr, bool sync); diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index 568628f..b7877a1 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -92,9 +92,9 @@ void ipvlan_ht_addr_del(struct ipvl_addr *addr, bool sync) synchronize_rcu(); } -bool ipvlan_addr_busy(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) +struct ipvl_addr *ipvlan_find_addr(const struct ipvl_dev *ipvlan, + const void *iaddr, bool is_v6) { - struct ipvl_port *port = ipvlan->port; struct ipvl_addr *addr; list_for_each_entry(addr, &ipvlan->addrs, anode) { @@ -102,12 +102,21 @@ bool ipvlan_addr_busy(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) ipv6_addr_equal(&addr->ip6addr, iaddr)) || (!is_v6 && addr->atype == IPVL_IPV4 && addr->ip4addr.s_addr == ((struct in_addr *)iaddr)->s_addr)) - return true; + return addr; } + return NULL; +} - if (ipvlan_ht_addr_lookup(port, iaddr, is_v6)) - return true; +bool ipvlan_addr_busy(struct ipvl_port *port, void *iaddr, bool is_v6) +{ + struct ipvl_dev *ipvlan; + + ASSERT_RTNL(); + list_for_each_entry(ipvlan, &port->ipvlans, pnode) { + if (ipvlan_find_addr(ipvlan, iaddr, is_v6)) + return true; + } return false; } diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index aaa005b..4fa1420 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -607,7 +607,7 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) { struct ipvl_addr *addr; - if (ipvlan_addr_busy(ipvlan, ip6_addr, true)) { + if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) { netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv6=%pI6c addr for %s intf\n", ip6_addr, ipvlan->dev->name); @@ -635,7 +635,7 @@ static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) { struct ipvl_addr *addr; - addr = ipvlan_ht_addr_lookup(ipvlan->port, ip6_addr, true); + addr = ipvlan_find_addr(ipvlan, ip6_addr, true); if (!addr) return; @@ -679,7 +679,7 @@ static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) { struct ipvl_addr *addr; - if (ipvlan_addr_busy(ipvlan, ip4_addr, false)) { + if (ipvlan_addr_busy(ipvlan->port, ip4_addr, false)) { netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv4=%pI4 on %s intf.\n", ip4_addr, ipvlan->dev->name); @@ -708,7 +708,7 @@ static void ipvlan_del_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) { struct ipvl_addr *addr; - addr = ipvlan_ht_addr_lookup(ipvlan->port, ip4_addr, false); + addr = ipvlan_find_addr(ipvlan, ip4_addr, false); if (!addr) return; -- cgit v0.10.2 From f5e2dc5d7fe78fe4d8748d217338f4f7b6a5d7ea Mon Sep 17 00:00:00 2001 From: Anton Nayshtut Date: Sun, 29 Mar 2015 14:20:25 +0300 Subject: bonding: Bonding Overriding Configuration logic restored. Before commit 3900f29021f0bc7fe9815aa32f1a993b7dfdd402 ("bonding: slight optimizztion for bond_slave_override()") the override logic was to send packets with non-zero queue_id through the slave with corresponding queue_id, under two conditions only - if the slave can transmit and it's up. The above mentioned commit changed this logic by introducing an additional condition - whether the bond is active (indirectly, using the slave_can_tx and later - bond_is_active_slave), that prevents the user from implementing more complex policies according to the Documentation/networking/bonding.txt. Signed-off-by: Anton Nayshtut Signed-off-by: Alexey Bogoslavsky Signed-off-by: Andy Gospodarek Signed-off-by: David S. Miller diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index b979c26..089a402 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -3850,7 +3850,8 @@ static inline int bond_slave_override(struct bonding *bond, /* Find out if any slaves have the same mapping as this skb. */ bond_for_each_slave_rcu(bond, slave, iter) { if (slave->queue_id == skb->queue_mapping) { - if (bond_slave_can_tx(slave)) { + if (bond_slave_is_up(slave) && + slave->link == BOND_LINK_UP) { bond_dev_queue_xmit(bond, skb, slave->dev); return 0; } -- cgit v0.10.2 From 5899f0478528b59ea9ced201eacb3e56ca406c39 Mon Sep 17 00:00:00 2001 From: Jiri Benc Date: Sun, 29 Mar 2015 16:05:28 +0200 Subject: netlink: pad nla_memcpy dest buffer with zeroes This is especially important in cases where the kernel allocs a new structure and expects a field to be set from a netlink attribute. If such attribute is shorter than expected, the rest of the field is left containing previous data. When such field is read back by the user space, kernel memory content is leaked. Signed-off-by: Jiri Benc Acked-by: Thomas Graf Signed-off-by: David S. Miller diff --git a/lib/nlattr.c b/lib/nlattr.c index 76a1b59..f5907d2 100644 --- a/lib/nlattr.c +++ b/lib/nlattr.c @@ -279,6 +279,8 @@ int nla_memcpy(void *dest, const struct nlattr *src, int count) int minlen = min_t(int, count, nla_len(src)); memcpy(dest, nla_data(src), minlen); + if (count > minlen) + memset(dest + minlen, 0, count - minlen); return minlen; } -- cgit v0.10.2 From f9c72d10d6fbf949558cd088389a42213ed7b12d Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 31 Mar 2015 12:03:28 -0400 Subject: sunrpc: make debugfs file creation failure non-fatal We currently have a problem that SELinux policy is being enforced when creating debugfs files. If a debugfs file is created as a side effect of doing some syscall, then that creation can fail if the SELinux policy for that process prevents it. This seems wrong. We don't do that for files under /proc, for instance, so Bruce has proposed a patch to fix that. While discussing that patch however, Greg K.H. stated: "No kernel code should care / fail if a debugfs function fails, so please fix up the sunrpc code first." This patch converts all of the sunrpc debugfs setup code to be void return functins, and the callers to not look for errors from those functions. This should allow rpc_clnt and rpc_xprt creation to work, even if the kernel fails to create debugfs files for some reason. Symptoms were failing krb5 mounts on systems using gss-proxy and selinux. Fixes: 388f0c776781 "sunrpc: add a debugfs rpc_xprt directory..." Cc: stable@vger.kernel.org Signed-off-by: Jeff Layton Acked-by: Greg Kroah-Hartman Signed-off-by: J. Bruce Fields diff --git a/include/linux/sunrpc/debug.h b/include/linux/sunrpc/debug.h index c57d8ea..59a7889 100644 --- a/include/linux/sunrpc/debug.h +++ b/include/linux/sunrpc/debug.h @@ -60,17 +60,17 @@ struct rpc_xprt; #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) void rpc_register_sysctl(void); void rpc_unregister_sysctl(void); -int sunrpc_debugfs_init(void); +void sunrpc_debugfs_init(void); void sunrpc_debugfs_exit(void); -int rpc_clnt_debugfs_register(struct rpc_clnt *); +void rpc_clnt_debugfs_register(struct rpc_clnt *); void rpc_clnt_debugfs_unregister(struct rpc_clnt *); -int rpc_xprt_debugfs_register(struct rpc_xprt *); +void rpc_xprt_debugfs_register(struct rpc_xprt *); void rpc_xprt_debugfs_unregister(struct rpc_xprt *); #else -static inline int +static inline void sunrpc_debugfs_init(void) { - return 0; + return; } static inline void @@ -79,10 +79,10 @@ sunrpc_debugfs_exit(void) return; } -static inline int +static inline void rpc_clnt_debugfs_register(struct rpc_clnt *clnt) { - return 0; + return; } static inline void @@ -91,10 +91,10 @@ rpc_clnt_debugfs_unregister(struct rpc_clnt *clnt) return; } -static inline int +static inline void rpc_xprt_debugfs_register(struct rpc_xprt *xprt) { - return 0; + return; } static inline void diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c index 612aa73..e6ce151 100644 --- a/net/sunrpc/clnt.c +++ b/net/sunrpc/clnt.c @@ -303,9 +303,7 @@ static int rpc_client_register(struct rpc_clnt *clnt, struct super_block *pipefs_sb; int err; - err = rpc_clnt_debugfs_register(clnt); - if (err) - return err; + rpc_clnt_debugfs_register(clnt); pipefs_sb = rpc_get_sb_net(net); if (pipefs_sb) { diff --git a/net/sunrpc/debugfs.c b/net/sunrpc/debugfs.c index e811f39..82962f7 100644 --- a/net/sunrpc/debugfs.c +++ b/net/sunrpc/debugfs.c @@ -129,48 +129,52 @@ static const struct file_operations tasks_fops = { .release = tasks_release, }; -int +void rpc_clnt_debugfs_register(struct rpc_clnt *clnt) { - int len, err; + int len; char name[24]; /* enough for "../../rpc_xprt/ + 8 hex digits + NULL */ + struct rpc_xprt *xprt; /* Already registered? */ - if (clnt->cl_debugfs) - return 0; + if (clnt->cl_debugfs || !rpc_clnt_dir) + return; len = snprintf(name, sizeof(name), "%x", clnt->cl_clid); if (len >= sizeof(name)) - return -EINVAL; + return; /* make the per-client dir */ clnt->cl_debugfs = debugfs_create_dir(name, rpc_clnt_dir); if (!clnt->cl_debugfs) - return -ENOMEM; + return; /* make tasks file */ - err = -ENOMEM; if (!debugfs_create_file("tasks", S_IFREG | S_IRUSR, clnt->cl_debugfs, clnt, &tasks_fops)) goto out_err; - err = -EINVAL; rcu_read_lock(); + xprt = rcu_dereference(clnt->cl_xprt); + /* no "debugfs" dentry? Don't bother with the symlink. */ + if (!xprt->debugfs) { + rcu_read_unlock(); + return; + } len = snprintf(name, sizeof(name), "../../rpc_xprt/%s", - rcu_dereference(clnt->cl_xprt)->debugfs->d_name.name); + xprt->debugfs->d_name.name); rcu_read_unlock(); + if (len >= sizeof(name)) goto out_err; - err = -ENOMEM; if (!debugfs_create_symlink("xprt", clnt->cl_debugfs, name)) goto out_err; - return 0; + return; out_err: debugfs_remove_recursive(clnt->cl_debugfs); clnt->cl_debugfs = NULL; - return err; } void @@ -226,33 +230,33 @@ static const struct file_operations xprt_info_fops = { .release = xprt_info_release, }; -int +void rpc_xprt_debugfs_register(struct rpc_xprt *xprt) { int len, id; static atomic_t cur_id; char name[9]; /* 8 hex digits + NULL term */ + if (!rpc_xprt_dir) + return; + id = (unsigned int)atomic_inc_return(&cur_id); len = snprintf(name, sizeof(name), "%x", id); if (len >= sizeof(name)) - return -EINVAL; + return; /* make the per-client dir */ xprt->debugfs = debugfs_create_dir(name, rpc_xprt_dir); if (!xprt->debugfs) - return -ENOMEM; + return; /* make tasks file */ if (!debugfs_create_file("info", S_IFREG | S_IRUSR, xprt->debugfs, xprt, &xprt_info_fops)) { debugfs_remove_recursive(xprt->debugfs); xprt->debugfs = NULL; - return -ENOMEM; } - - return 0; } void @@ -266,14 +270,17 @@ void __exit sunrpc_debugfs_exit(void) { debugfs_remove_recursive(topdir); + topdir = NULL; + rpc_clnt_dir = NULL; + rpc_xprt_dir = NULL; } -int __init +void __init sunrpc_debugfs_init(void) { topdir = debugfs_create_dir("sunrpc", NULL); if (!topdir) - goto out; + return; rpc_clnt_dir = debugfs_create_dir("rpc_clnt", topdir); if (!rpc_clnt_dir) @@ -283,10 +290,9 @@ sunrpc_debugfs_init(void) if (!rpc_xprt_dir) goto out_remove; - return 0; + return; out_remove: debugfs_remove_recursive(topdir); topdir = NULL; -out: - return -ENOMEM; + rpc_clnt_dir = NULL; } diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c index e37fbed..ee5d3d2 100644 --- a/net/sunrpc/sunrpc_syms.c +++ b/net/sunrpc/sunrpc_syms.c @@ -98,10 +98,7 @@ init_sunrpc(void) if (err) goto out4; - err = sunrpc_debugfs_init(); - if (err) - goto out5; - + sunrpc_debugfs_init(); #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) rpc_register_sysctl(); #endif @@ -109,8 +106,6 @@ init_sunrpc(void) init_socket_xprt(); /* clnt sock transport */ return 0; -out5: - unregister_rpc_pipefs(); out4: unregister_pernet_subsys(&sunrpc_net_ops); out3: diff --git a/net/sunrpc/xprt.c b/net/sunrpc/xprt.c index e3015ae..9949722 100644 --- a/net/sunrpc/xprt.c +++ b/net/sunrpc/xprt.c @@ -1331,7 +1331,6 @@ static void xprt_init(struct rpc_xprt *xprt, struct net *net) */ struct rpc_xprt *xprt_create_transport(struct xprt_create *args) { - int err; struct rpc_xprt *xprt; struct xprt_class *t; @@ -1372,11 +1371,7 @@ found: return ERR_PTR(-ENOMEM); } - err = rpc_xprt_debugfs_register(xprt); - if (err) { - xprt_destroy(xprt); - return ERR_PTR(err); - } + rpc_xprt_debugfs_register(xprt); dprintk("RPC: created transport %p with %u slots\n", xprt, xprt->max_reqs); -- cgit v0.10.2 From fa2d8ff4e3522b4e05f590575d3eb8087f3a8cdc Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Mon, 30 Mar 2015 13:57:41 +0200 Subject: openvswitch: Return vport module ref before destruction Return module reference before invoking the respective vport ->destroy() function. This is needed as ovs_vport_del() is not invoked inside an RCU read side critical section so the kfree can occur immediately before returning to ovs_vport_del(). Returning the module reference before ->destroy() is safe because the module unregistration is blocked on ovs_lock which we hold while destroying the datapath. Fixes: 62b9c8d0372d ("ovs: Turn vports with dependencies into separate modules") Reported-by: Pravin Shelar Signed-off-by: Thomas Graf Acked-by: Pravin B Shelar Signed-off-by: David S. Miller diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index ec2954f..067a3ff 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -274,10 +274,8 @@ void ovs_vport_del(struct vport *vport) ASSERT_OVSL(); hlist_del_rcu(&vport->hash_node); - - vport->ops->destroy(vport); - module_put(vport->ops->owner); + vport->ops->destroy(vport); } /** -- cgit v0.10.2 From ed4ac4221776a5103faf71a4032ca00178d6e66b Mon Sep 17 00:00:00 2001 From: Eugene Crosser Date: Mon, 30 Mar 2015 15:40:42 +0200 Subject: af_iucv: fix AF_IUCV sendmsg() errno When sending over AF_IUCV socket, errno was incorrectly set to ENOMEM even when other values where appropriate, notably EAGAIN. With this patch, error indicator returned by sock_alloc_send_skb() is passed to the caller, rather than being overwritten with ENOMEM. Signed-off-by: Eugene Crosser Signed-off-by: Ursula Braun Signed-off-by: David S. Miller diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index 2e9953b..53d9311 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -1114,10 +1114,8 @@ static int iucv_sock_sendmsg(struct kiocb *iocb, struct socket *sock, noblock, &err); else skb = sock_alloc_send_skb(sk, len, noblock, &err); - if (!skb) { - err = -ENOMEM; + if (!skb) goto out; - } if (iucv->transport == AF_IUCV_TRANS_HIPER) skb_reserve(skb, sizeof(struct af_iucv_trans_hdr) + ETH_HLEN); if (memcpy_from_msg(skb_put(skb, len), msg, len)) { -- cgit v0.10.2 From 347eec348a9b0ba6968145cc7b1995d2475ae31b Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 31 Mar 2015 14:10:07 +0200 Subject: net/usb/r8152: add device id for Lenovo TP USB 3.0 Ethernet This device is sold as 'Lenovo Tinkpad USB 3.0 Ethernet 4X90E51405'. Chipset is RTL8153 and works with r8152. Signed-off-by: Christian Hesse Signed-off-by: David S. Miller diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index 9311a08..4545e78 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -522,6 +522,7 @@ static const struct driver_info wwan_info = { #define DELL_VENDOR_ID 0x413C #define REALTEK_VENDOR_ID 0x0bda #define SAMSUNG_VENDOR_ID 0x04e8 +#define LENOVO_VENDOR_ID 0x17ef static const struct usb_device_id products[] = { /* BLACKLIST !! @@ -702,6 +703,13 @@ static const struct usb_device_id products[] = { .driver_info = 0, }, +/* Lenovo Thinkpad USB 3.0 Ethernet Adapters (based on Realtek RTL8153) */ +{ + USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x7205, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + /* WHITELIST!!! * * CDC Ether uses two interfaces, not necessarily consecutive. diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c index 438fc6b..9f7c0ab 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -492,6 +492,7 @@ enum rtl8152_flags { /* Define these values to match your device */ #define VENDOR_ID_REALTEK 0x0bda #define VENDOR_ID_SAMSUNG 0x04e8 +#define VENDOR_ID_LENOVO 0x17ef #define MCU_TYPE_PLA 0x0100 #define MCU_TYPE_USB 0x0000 @@ -4037,6 +4038,7 @@ static struct usb_device_id rtl8152_table[] = { {REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8152)}, {REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8153)}, {REALTEK_USB_DEVICE(VENDOR_ID_SAMSUNG, 0xa101)}, + {REALTEK_USB_DEVICE(VENDOR_ID_LENOVO, 0x7205)}, {} }; -- cgit v0.10.2 From 85734b1a5de80341d7d01cd29bbea9cf6f77094b Mon Sep 17 00:00:00 2001 From: Filip Ayazi Date: Wed, 25 Mar 2015 15:53:04 -0700 Subject: Input: synaptics - fix min-max quirk value for E440 Commit 98dc070373 ("Input: synaptics - add quirk for Thinkpad E440") had a typo in ymax, this changes the value to the one reported by touchpad-edge-detector and mentioned in the commit. Signed-off-by: Filip Ayazi Signed-off-by: Dmitry Torokhov diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index f6a3a7b..3b06c8a 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -155,7 +155,7 @@ static const struct min_max_quirk min_max_pnpid_table[] = { { (const char * const []){"LEN2006", NULL}, {2691, 2691}, - 1024, 5045, 2457, 4632 + 1024, 5045, 2457, 4832 }, { (const char * const []){"LEN2006", NULL}, -- cgit v0.10.2 From 33096777519a66e632fed7f1e54e6480b4228827 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 27 Mar 2015 09:59:55 -0700 Subject: Input: define INPUT_PROP_ACCELEROMETER behavior Spell out what this property means to userspace. If the property is set, all directional axes must be accelerometer axes, any other axes are left as-is. This allows an accelerometer device to e.g. have an ABS_WHEEL. It is not permitted to mix normal directional axes and accelerometer axes on the same device node. Signed-off-by: Peter Hutterer Acked-by: Jiri Kosina Reviewed-by: Bastien Nocera Signed-off-by: Dmitry Torokhov diff --git a/Documentation/input/event-codes.txt b/Documentation/input/event-codes.txt index c587a96..9670561 100644 --- a/Documentation/input/event-codes.txt +++ b/Documentation/input/event-codes.txt @@ -294,6 +294,12 @@ accordingly. This property does not affect kernel behavior. The kernel does not provide button emulation for such devices but treats them as any other INPUT_PROP_BUTTONPAD device. +INPUT_PROP_ACCELEROMETER +------------------------- +Directional axes on this device (absolute and/or relative x, y, z) represent +accelerometer data. All other axes retain their meaning. A device must not mix +regular directional axes and accelerometer axes on the same event node. + Guidelines: ========== The guidelines below ensure proper single-touch and multi-finger functionality. -- cgit v0.10.2 From d52356e7f48e400ca258c6763a232a92fa82ff68 Mon Sep 17 00:00:00 2001 From: Jan Stancek Date: Tue, 31 Mar 2015 18:11:46 +0200 Subject: powerpc: fix memory corruption by pnv_alloc_idle_core_states Space allocated for paca is based off nr_cpu_ids, but pnv_alloc_idle_core_states() iterates paca with cpu_nr_cores()*threads_per_core, which is using NR_CPUS. This causes pnv_alloc_idle_core_states() to write over memory, which is outside of paca array and may later lead to various panics. Fixes: 7cba160ad789 (powernv/cpuidle: Redesign idle states management) Signed-off-by: Jan Stancek Signed-off-by: Michael Ellerman diff --git a/arch/powerpc/include/asm/cputhreads.h b/arch/powerpc/include/asm/cputhreads.h index 2bf8e93..4c8ad59 100644 --- a/arch/powerpc/include/asm/cputhreads.h +++ b/arch/powerpc/include/asm/cputhreads.h @@ -55,7 +55,7 @@ static inline cpumask_t cpu_thread_mask_to_cores(const struct cpumask *threads) static inline int cpu_nr_cores(void) { - return NR_CPUS >> threads_shift; + return nr_cpu_ids >> threads_shift; } static inline cpumask_t cpu_online_cores_map(void) -- cgit v0.10.2 From 7e436905780659d6dc12d0581944934bf91a9919 Mon Sep 17 00:00:00 2001 From: Ying Xue Date: Wed, 1 Apr 2015 09:42:50 +0800 Subject: tipc: fix a slab object leak When remove TIPC module, there is a warning to remind us that a slab object is leaked like: root@localhost:~# rmmod tipc [ 19.056226] ============================================================================= [ 19.057549] BUG TIPC (Not tainted): Objects remaining in TIPC on kmem_cache_close() [ 19.058736] ----------------------------------------------------------------------------- [ 19.058736] [ 19.060287] INFO: Slab 0xffffea0000519a00 objects=23 used=1 fp=0xffff880014668b00 flags=0x100000000004080 [ 19.061915] INFO: Object 0xffff880014668000 @offset=0 [ 19.062717] kmem_cache_destroy TIPC: Slab cache still has objects This is because the listening socket of TIPC topology server is not closed before TIPC proto handler is unregistered with proto_unregister(). However, as the socket is closed in tipc_exit_net() which is called by unregister_pernet_subsys() during unregistering TIPC namespace operation, the warning can be eliminated if calling unregister_pernet_subsys() is moved before calling proto_unregister(). Fixes: e05b31f4bf89 ("tipc: make tipc socket support net namespace") Reviewed-by: Erik Hugne Signed-off-by: Ying Xue Signed-off-by: David S. Miller diff --git a/net/tipc/core.c b/net/tipc/core.c index 935205e..be1c9fa 100644 --- a/net/tipc/core.c +++ b/net/tipc/core.c @@ -152,11 +152,11 @@ out_netlink: static void __exit tipc_exit(void) { tipc_bearer_cleanup(); + unregister_pernet_subsys(&tipc_net_ops); tipc_netlink_stop(); tipc_netlink_compat_stop(); tipc_socket_stop(); tipc_unregister_sysctl(); - unregister_pernet_subsys(&tipc_net_ops); pr_info("Deactivated\n"); } -- cgit v0.10.2 From 2f30232481ef39aa1657ac76b16c47d0c0241a69 Mon Sep 17 00:00:00 2001 From: Jeff Kirsher Date: Wed, 25 Mar 2015 17:01:03 -0700 Subject: MAINTAINERS: Update Intel Wired Ethernet Driver info Update the git tree info with a recent change in tree names. Also add our new mailing list created solely for Linux kernel patches and kernel development, as well as the new patchwork project for tracking patches. Lastly update the list of "reviewers" since a couple of developers have moved on to different projects. Made an update to the section header so that it is more manageable going forward as we add new drivers. Signed-off-by: Jeff Kirsher diff --git a/MAINTAINERS b/MAINTAINERS index 358eb01..9508870 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5128,22 +5128,21 @@ M: Deepak Saxena S: Maintained F: drivers/char/hw_random/ixp4xx-rng.c -INTEL ETHERNET DRIVERS (e100/e1000/e1000e/fm10k/igb/igbvf/ixgb/ixgbe/ixgbevf/i40e/i40evf) +INTEL ETHERNET DRIVERS M: Jeff Kirsher -M: Jesse Brandeburg -M: Bruce Allan -M: Carolyn Wyborny -M: Don Skidmore -M: Greg Rose -M: Matthew Vick -M: John Ronciak -M: Mitch Williams -M: Linux NICS -L: e1000-devel@lists.sourceforge.net +R: Jesse Brandeburg +R: Shannon Nelson +R: Carolyn Wyborny +R: Don Skidmore +R: Matthew Vick +R: John Ronciak +R: Mitch Williams +L: intel-wired-lan@lists.osuosl.org W: http://www.intel.com/support/feedback.htm W: http://e1000.sourceforge.net/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/jkirsher/net.git -T: git git://git.kernel.org/pub/scm/linux/kernel/git/jkirsher/net-next.git +Q: http://patchwork.ozlabs.org/project/intel-wired-lan/list/ +T: git git://git.kernel.org/pub/scm/linux/kernel/git/jkirsher/net-queue.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/jkirsher/next-queue.git S: Supported F: Documentation/networking/e100.txt F: Documentation/networking/e1000.txt -- cgit v0.10.2 From 75fdfc849ae0636853add4b7dbdc5753160ad0cb Mon Sep 17 00:00:00 2001 From: Steve French Date: Wed, 25 Mar 2015 18:51:57 -0500 Subject: Fix warning on uninitialized buftype Pointed out by coverity analyzer. resp_buftype is not initialized in one path which can rarely log a spurious warning (buf is null so there will not be a problem with freeing data, but if buf_type were randomly set to wrong value could log a warning) Reported by Coverity (CID 1269144) Signed-off-by: Steve French Acked-by: Shirish Pargaonkar Acked-by: Sachin Prabhu Reviewed-by: Jeff Layton diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 3417340..1b906de 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -2114,7 +2114,7 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon, struct kvec iov[2]; int rc = 0; int len; - int resp_buftype; + int resp_buftype = CIFS_NO_BUFFER; unsigned char *bufptr; struct TCP_Server_Info *server; struct cifs_ses *ses = tcon->ses; -- cgit v0.10.2 From f3a31a2bbbfe22db0313a4e270c7ba91baa7b630 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 26 Mar 2015 19:23:20 -0500 Subject: Don't ignore errors on encrypting password in SMBTcon Although unlikely to fail (and tree connect does not commonly send a password since SECMODE_USER is the default for most servers) do not ignore errors on SMBNTEncrypt in SMB Tree Connect. Reported by Coverity (CID 1226853) Signed-off-by: Steve French Acked-by: Shirish Pargaonkar Acked-by: Sachin Prabhu Reviewed-by: Jeff Layton diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 4cb8450..cdb1aaf 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3696,6 +3696,12 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, #endif /* CIFS_WEAK_PW_HASH */ rc = SMBNTencrypt(tcon->password, ses->server->cryptkey, bcc_ptr, nls_codepage); + if (rc) { + cifs_dbg(FYI, "%s Can't generate NTLM rsp. Error: %d\n", + __func__, rc); + cifs_buf_release(smb_buffer); + return rc; + } bcc_ptr += CIFS_AUTH_RESP_SIZE; if (ses->capabilities & CAP_UNICODE) { -- cgit v0.10.2 From 8e3531060556bf5e09444acd2aa5c78bce9c9f70 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 26 Mar 2015 19:47:02 -0500 Subject: Fix dereference before null check warning null tcon is not likely in these paths in current code, but obviously it does clarify the code to check for null (if at all) before derefrencing rather than after. Reported by Coverity (CID 1042666) Signed-off-by: Steve French Acked-by: Shirish Pargaonkar Acked-by: Sachin Prabhu diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 1b906de..78b329f 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -1218,7 +1218,7 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, struct smb2_ioctl_req *req; struct smb2_ioctl_rsp *rsp; struct TCP_Server_Info *server; - struct cifs_ses *ses = tcon->ses; + struct cifs_ses *ses; struct kvec iov[2]; int resp_buftype; int num_iovecs; @@ -1233,6 +1233,11 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, if (plen) *plen = 0; + if (tcon) + ses = tcon->ses; + else + return -EIO; + if (ses && (ses->server)) server = ses->server; else @@ -1296,14 +1301,12 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, rsp = (struct smb2_ioctl_rsp *)iov[0].iov_base; if ((rc != 0) && (rc != -EINVAL)) { - if (tcon) - cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE); + cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE); goto ioctl_exit; } else if (rc == -EINVAL) { if ((opcode != FSCTL_SRV_COPYCHUNK_WRITE) && (opcode != FSCTL_SRV_COPYCHUNK)) { - if (tcon) - cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE); + cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE); goto ioctl_exit; } } -- cgit v0.10.2 From 064bcc0702a83aed29220bee3b3db3f968c5d8ae Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 26 Mar 2015 22:41:59 -0500 Subject: Fix coverity warning Coverity reports a warning for referencing the beginning of the SMB2/SMB3 frame using the ProtocolId field as an array. Although it works the same either way, this patch should quiet the warning and might be a little clearer. Reported by Coverity (CID 741269) Signed-off-by: Steve French Acked-by: Shirish Pargaonkar Acked-by: Sachin Prabhu Reviewed-by: Jeff Layton diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index 689f035..22dfdf1 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -322,7 +322,7 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *hdr) /* return pointer to beginning of data area, ie offset from SMB start */ if ((*off != 0) && (*len != 0)) - return hdr->ProtocolId + *off; + return (char *)(&hdr->ProtocolId[0]) + *off; else return NULL; } -- cgit v0.10.2 From c85c35f8fc1d3f5ca60cd713f10af387de73cb94 Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 27 Mar 2015 01:15:02 -0500 Subject: Fix warning on impossible comparison workstation_RFC1001_name is part of the struct and can't be null, remove impossible comparison (array vs. null) Pointed out by Coverity (CID 140095) Signed-off-by: Steve French Reviewed-by: Jeff Layton diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index cdb1aaf..480cf9c 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2917,8 +2917,7 @@ ip_rfc1001_connect(struct TCP_Server_Info *server) * calling name ends in null (byte 16) from old smb * convention. */ - if (server->workstation_RFC1001_name && - server->workstation_RFC1001_name[0] != 0) + if (server->workstation_RFC1001_name[0] != 0) rfc1002mangle(ses_init_buf->trailer. session_req.calling_name, server->workstation_RFC1001_name, -- cgit v0.10.2 From 8b7a454443bdb73df4fd200af7f013f3319a3855 Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 30 Mar 2015 16:58:17 -0500 Subject: CIFS: session servername can't be null remove impossible check Pointed out by Coverity (CID 115422) Signed-off-by: Steve French Reviewed-by: Jeff Layton diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c index 4ac7445..aa0dc25 100644 --- a/fs/cifs/cifsencrypt.c +++ b/fs/cifs/cifsencrypt.c @@ -1,6 +1,9 @@ /* * fs/cifs/cifsencrypt.c * + * Encryption and hashing operations relating to NTLM, NTLMv2. See MS-NLMP + * for more detailed information + * * Copyright (C) International Business Machines Corp., 2005,2013 * Author(s): Steve French (sfrench@us.ibm.com) * @@ -515,7 +518,8 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, __func__); return rc; } - } else if (ses->serverName) { + } else { + /* We use ses->serverName if no domain name available */ len = strlen(ses->serverName); server = kmalloc(2 + (len * 2), GFP_KERNEL); -- cgit v0.10.2 From dfebe40076f3b4966e818eaf94021a7ff788df04 Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 27 Mar 2015 01:00:06 -0500 Subject: Fix another dereference before null check warning null tcon is not possible in these paths so remove confusing null check Reported by Coverity (CID 728519) Signed-off-by: Steve French Reviewed-by: Jeff Layton diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index 78b329f..65cd7a8 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c @@ -1632,7 +1632,7 @@ SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, rc = SendReceive2(xid, ses, iov, 1, &resp_buftype, 0); - if ((rc != 0) && tcon) + if (rc != 0) cifs_stats_fail_inc(tcon, SMB2_FLUSH_HE); free_rsp_buf(resp_buftype, iov[0].iov_base); -- cgit v0.10.2 From 4c5930e8056127e9a89bb7836bfa34318a89ab14 Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 30 Mar 2015 22:03:06 -0500 Subject: Fix warning Coverity reports a warning due to unitialized attr structure in one code path. Reported by Coverity (CID 728535) Signed-off-by: Steve French Reviewed-by: Jeff Layton diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 2d4f372..3e126d7 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -771,6 +771,8 @@ cifs_get_inode_info(struct inode **inode, const char *full_path, cifs_buf_release(srchinf->ntwrk_buf_start); } kfree(srchinf); + if (rc) + goto cgii_exit; } else goto cgii_exit; -- cgit v0.10.2 From 3b6281cf2893a3c140a37be817b0802c46af292b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 31 Mar 2015 16:48:56 +0200 Subject: ASoC: fsi: reorder code to make a forward declaration superfluous MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Uwe Kleine-König Signed-off-by: Mark Brown diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 936c02d..5d26f8c 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -1876,7 +1876,40 @@ static void fsi_handler_init(struct fsi_priv *fsi, } } -static const struct of_device_id fsi_of_match[]; +static struct fsi_core fsi1_core = { + .ver = 1, + + /* Interrupt */ + .int_st = INT_ST, + .iemsk = IEMSK, + .imsk = IMSK, +}; + +static struct fsi_core fsi2_core = { + .ver = 2, + + /* Interrupt */ + .int_st = CPU_INT_ST, + .iemsk = CPU_IEMSK, + .imsk = CPU_IMSK, + .a_mclk = A_MST_CTLR, + .b_mclk = B_MST_CTLR, +}; + +static const struct of_device_id fsi_of_match[] = { + { .compatible = "renesas,sh_fsi", .data = &fsi1_core}, + { .compatible = "renesas,sh_fsi2", .data = &fsi2_core}, + {}, +}; +MODULE_DEVICE_TABLE(of, fsi_of_match); + +static struct platform_device_id fsi_id_table[] = { + { "sh_fsi", (kernel_ulong_t)&fsi1_core }, + { "sh_fsi2", (kernel_ulong_t)&fsi2_core }, + {}, +}; +MODULE_DEVICE_TABLE(platform, fsi_id_table); + static int fsi_probe(struct platform_device *pdev) { struct fsi_master *master; @@ -2072,40 +2105,6 @@ static struct dev_pm_ops fsi_pm_ops = { .resume = fsi_resume, }; -static struct fsi_core fsi1_core = { - .ver = 1, - - /* Interrupt */ - .int_st = INT_ST, - .iemsk = IEMSK, - .imsk = IMSK, -}; - -static struct fsi_core fsi2_core = { - .ver = 2, - - /* Interrupt */ - .int_st = CPU_INT_ST, - .iemsk = CPU_IEMSK, - .imsk = CPU_IMSK, - .a_mclk = A_MST_CTLR, - .b_mclk = B_MST_CTLR, -}; - -static const struct of_device_id fsi_of_match[] = { - { .compatible = "renesas,sh_fsi", .data = &fsi1_core}, - { .compatible = "renesas,sh_fsi2", .data = &fsi2_core}, - {}, -}; -MODULE_DEVICE_TABLE(of, fsi_of_match); - -static struct platform_device_id fsi_id_table[] = { - { "sh_fsi", (kernel_ulong_t)&fsi1_core }, - { "sh_fsi2", (kernel_ulong_t)&fsi2_core }, - {}, -}; -MODULE_DEVICE_TABLE(platform, fsi_id_table); - static struct platform_driver fsi_driver = { .driver = { .name = "fsi-pcm-audio", -- cgit v0.10.2 From 9a42ab04aae96d47cd86e065b5127e472fd9eab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 31 Mar 2015 16:48:57 +0200 Subject: ASoC: fsi: mark several data structures as const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A driver's platform_device_id and device data should and can be const. Signed-off-by: Uwe Kleine-König Signed-off-by: Mark Brown diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 5d26f8c..0c2af21 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -1876,7 +1876,7 @@ static void fsi_handler_init(struct fsi_priv *fsi, } } -static struct fsi_core fsi1_core = { +static const struct fsi_core fsi1_core = { .ver = 1, /* Interrupt */ @@ -1885,7 +1885,7 @@ static struct fsi_core fsi1_core = { .imsk = IMSK, }; -static struct fsi_core fsi2_core = { +static const struct fsi_core fsi2_core = { .ver = 2, /* Interrupt */ @@ -1903,7 +1903,7 @@ static const struct of_device_id fsi_of_match[] = { }; MODULE_DEVICE_TABLE(of, fsi_of_match); -static struct platform_device_id fsi_id_table[] = { +static const struct platform_device_id fsi_id_table[] = { { "sh_fsi", (kernel_ulong_t)&fsi1_core }, { "sh_fsi2", (kernel_ulong_t)&fsi2_core }, {}, -- cgit v0.10.2 From 969b8619069f0e4da767c54481dcc10812f949a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 31 Mar 2015 20:35:09 +0200 Subject: ASoC: rcar: mark device data as constant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A driver's device data should and can be const. This is a follow-up on commit 33187fb4a203 (ASoC: rsnd: constify of_device_id array) which marked the of_device_id as const. Signed-off-by: Uwe Kleine-König Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 9b0de42..6f60149 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -99,11 +99,11 @@ #define RSND_RATES SNDRV_PCM_RATE_8000_96000 #define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) -static struct rsnd_of_data rsnd_of_data_gen1 = { +static const struct rsnd_of_data rsnd_of_data_gen1 = { .flags = RSND_GEN1, }; -static struct rsnd_of_data rsnd_of_data_gen2 = { +static const struct rsnd_of_data rsnd_of_data_gen2 = { .flags = RSND_GEN2, }; -- cgit v0.10.2 From 80313b3078fcd2ca51970880d90757f05879a193 Mon Sep 17 00:00:00 2001 From: Stefan Lippers-Hollmann Date: Mon, 30 Mar 2015 22:44:27 +0200 Subject: x86/reboot: Add ASRock Q1900DC-ITX mainboard reboot quirk The ASRock Q1900DC-ITX mainboard (Baytrail-D) hangs randomly in both BIOS and UEFI mode while rebooting unless reboot=pci is used. Add a quirk to reboot via the pci method. The problem is very intermittent and hard to debug, it might succeed rebooting just fine 40 times in a row - but fails half a dozen times the next day. It seems to be slightly less common in BIOS CSM mode than native UEFI (with the CSM disabled), but it does happen in either mode. Since I've started testing this patch in late january, rebooting has been 100% reliable. Most of the time it already hangs during POST, but occasionally it might even make it through the bootloader and the kernel might even start booting, but then hangs before the mode switch. The same symptoms occur with grub-efi, gummiboot and grub-pc, just as well as (at least) kernel 3.16-3.19 and 4.0-rc6 (I haven't tried older kernels than 3.16). Upgrading to the most current mainboard firmware of the ASRock Q1900DC-ITX, version 1.20, does not improve the situation. ( Searching the web seems to suggest that other Bay Trail-D mainboards might be affected as well. ) -- Signed-off-by: Stefan Lippers-Hollmann Cc: Cc: Matt Fleming Link: http://lkml.kernel.org/r/20150330224427.0fb58e42@mir Signed-off-by: Ingo Molnar diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c index bae6c60..86db4bc 100644 --- a/arch/x86/kernel/reboot.c +++ b/arch/x86/kernel/reboot.c @@ -183,6 +183,16 @@ static struct dmi_system_id __initdata reboot_dmi_table[] = { }, }, + /* ASRock */ + { /* Handle problems with rebooting on ASRock Q1900DC-ITX */ + .callback = set_pci_reboot, + .ident = "ASRock Q1900DC-ITX", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASRock"), + DMI_MATCH(DMI_BOARD_NAME, "Q1900DC-ITX"), + }, + }, + /* ASUS */ { /* Handle problems with rebooting on ASUS P4S800 */ .callback = set_bios_reboot, -- cgit v0.10.2 From 788211d81bfdf9b6a547d0530f206ba6ee76b107 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 1 Apr 2015 14:20:42 +0200 Subject: mac80211: fix RX A-MPDU session reorder timer deletion There's an issue with the way the RX A-MPDU reorder timer is deleted that can cause a kernel crash like this: * tid_rx is removed - call_rcu(ieee80211_free_tid_rx) * station is destroyed * reorder timer fires before ieee80211_free_tid_rx() runs, accessing the station, thus potentially crashing due to the use-after-free The station deletion is protected by synchronize_net(), but that isn't enough -- ieee80211_free_tid_rx() need not have run when that returns (it deletes the timer.) We could use rcu_barrier() instead of synchronize_net(), but that's much more expensive. Instead, to fix this, add a field tracking that the session is being deleted. In this case, the only re-arming of the timer happens with the reorder spinlock held, so make that code not rearm it if the session is being deleted and also delete the timer after setting that field. This ensures the timer cannot fire after ___ieee80211_stop_rx_ba_session() returns, which fixes the problem. Cc: stable@vger.kernel.org Signed-off-by: Johannes Berg diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c index a48bad4..7702978 100644 --- a/net/mac80211/agg-rx.c +++ b/net/mac80211/agg-rx.c @@ -49,8 +49,6 @@ static void ieee80211_free_tid_rx(struct rcu_head *h) container_of(h, struct tid_ampdu_rx, rcu_head); int i; - del_timer_sync(&tid_rx->reorder_timer); - for (i = 0; i < tid_rx->buf_size; i++) __skb_queue_purge(&tid_rx->reorder_buf[i]); kfree(tid_rx->reorder_buf); @@ -93,6 +91,12 @@ void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid, del_timer_sync(&tid_rx->session_timer); + /* make sure ieee80211_sta_reorder_release() doesn't re-arm the timer */ + spin_lock_bh(&tid_rx->reorder_lock); + tid_rx->removed = true; + spin_unlock_bh(&tid_rx->reorder_lock); + del_timer_sync(&tid_rx->reorder_timer); + call_rcu(&tid_rx->rcu_head, ieee80211_free_tid_rx); } diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 944bdc0..1eb730b 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -873,9 +873,10 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, set_release_timer: - mod_timer(&tid_agg_rx->reorder_timer, - tid_agg_rx->reorder_time[j] + 1 + - HT_RX_REORDER_BUF_TIMEOUT); + if (!tid_agg_rx->removed) + mod_timer(&tid_agg_rx->reorder_timer, + tid_agg_rx->reorder_time[j] + 1 + + HT_RX_REORDER_BUF_TIMEOUT); } else { del_timer(&tid_agg_rx->reorder_timer); } diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 925e68f..fb0fc13 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -175,6 +175,7 @@ struct tid_ampdu_tx { * @reorder_lock: serializes access to reorder buffer, see below. * @auto_seq: used for offloaded BA sessions to automatically pick head_seq_and * and ssn. + * @removed: this session is removed (but might have been found due to RCU) * * This structure's lifetime is managed by RCU, assignments to * the array holding it must hold the aggregation mutex. @@ -199,6 +200,7 @@ struct tid_ampdu_rx { u16 timeout; u8 dialog_token; bool auto_seq; + bool removed; }; /** -- cgit v0.10.2 From 3d6b72534a109be4353b161a09a5683b441d0941 Mon Sep 17 00:00:00 2001 From: Yuval Mintz Date: Wed, 1 Apr 2015 10:02:19 +0300 Subject: bnx2x: Fix kdump on 4-port device When running in a kdump kernel, it's very likely that due to sync. loss with management firmware the first PCI function to probe and reach the previous unload flow would decide it can reset the chip and continue onward. While doing so, it will only close its own Rx port. On a 4-port device where 2nd port on engine is a 1g-port, the 2nd port would allow ingress traffic after the chip is reset [assuming it was active on the first kernel]. This would later cause a HW attention. This changes driver flow to close both ports' 1g capabilities during the previous driver unload flow prior to the chip reset. Signed-off-by: Yuval Mintz Signed-off-by: Ariel Elior Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c index ae571a1..e6dec47 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c @@ -129,8 +129,8 @@ struct bnx2x_mac_vals { u32 xmac_val; u32 emac_addr; u32 emac_val; - u32 umac_addr; - u32 umac_val; + u32 umac_addr[2]; + u32 umac_val[2]; u32 bmac_addr; u32 bmac_val[2]; }; @@ -10141,6 +10141,25 @@ static u32 bnx2x_get_pretend_reg(struct bnx2x *bp) return base + (BP_ABS_FUNC(bp)) * stride; } +static bool bnx2x_prev_unload_close_umac(struct bnx2x *bp, + u8 port, u32 reset_reg, + struct bnx2x_mac_vals *vals) +{ + u32 mask = MISC_REGISTERS_RESET_REG_2_UMAC0 << port; + u32 base_addr; + + if (!(mask & reset_reg)) + return false; + + BNX2X_DEV_INFO("Disable umac Rx %02x\n", port); + base_addr = port ? GRCBASE_UMAC1 : GRCBASE_UMAC0; + vals->umac_addr[port] = base_addr + UMAC_REG_COMMAND_CONFIG; + vals->umac_val[port] = REG_RD(bp, vals->umac_addr[port]); + REG_WR(bp, vals->umac_addr[port], 0); + + return true; +} + static void bnx2x_prev_unload_close_mac(struct bnx2x *bp, struct bnx2x_mac_vals *vals) { @@ -10149,10 +10168,7 @@ static void bnx2x_prev_unload_close_mac(struct bnx2x *bp, u8 port = BP_PORT(bp); /* reset addresses as they also mark which values were changed */ - vals->bmac_addr = 0; - vals->umac_addr = 0; - vals->xmac_addr = 0; - vals->emac_addr = 0; + memset(vals, 0, sizeof(*vals)); reset_reg = REG_RD(bp, MISC_REG_RESET_REG_2); @@ -10201,15 +10217,11 @@ static void bnx2x_prev_unload_close_mac(struct bnx2x *bp, REG_WR(bp, vals->xmac_addr, 0); mac_stopped = true; } - mask = MISC_REGISTERS_RESET_REG_2_UMAC0 << port; - if (mask & reset_reg) { - BNX2X_DEV_INFO("Disable umac Rx\n"); - base_addr = BP_PORT(bp) ? GRCBASE_UMAC1 : GRCBASE_UMAC0; - vals->umac_addr = base_addr + UMAC_REG_COMMAND_CONFIG; - vals->umac_val = REG_RD(bp, vals->umac_addr); - REG_WR(bp, vals->umac_addr, 0); - mac_stopped = true; - } + + mac_stopped |= bnx2x_prev_unload_close_umac(bp, 0, + reset_reg, vals); + mac_stopped |= bnx2x_prev_unload_close_umac(bp, 1, + reset_reg, vals); } if (mac_stopped) @@ -10505,8 +10517,11 @@ static int bnx2x_prev_unload_common(struct bnx2x *bp) /* Close the MAC Rx to prevent BRB from filling up */ bnx2x_prev_unload_close_mac(bp, &mac_vals); - /* close LLH filters towards the BRB */ + /* close LLH filters for both ports towards the BRB */ + bnx2x_set_rx_filter(&bp->link_params, 0); + bp->link_params.port ^= 1; bnx2x_set_rx_filter(&bp->link_params, 0); + bp->link_params.port ^= 1; /* Check if the UNDI driver was previously loaded */ if (bnx2x_prev_is_after_undi(bp)) { @@ -10553,8 +10568,10 @@ static int bnx2x_prev_unload_common(struct bnx2x *bp) if (mac_vals.xmac_addr) REG_WR(bp, mac_vals.xmac_addr, mac_vals.xmac_val); - if (mac_vals.umac_addr) - REG_WR(bp, mac_vals.umac_addr, mac_vals.umac_val); + if (mac_vals.umac_addr[0]) + REG_WR(bp, mac_vals.umac_addr[0], mac_vals.umac_val[0]); + if (mac_vals.umac_addr[1]) + REG_WR(bp, mac_vals.umac_addr[1], mac_vals.umac_val[1]); if (mac_vals.emac_addr) REG_WR(bp, mac_vals.emac_addr, mac_vals.emac_val); if (mac_vals.bmac_addr) { -- cgit v0.10.2 From da254fbc6357a66a127e4e4e234b4f9c555d5ed1 Mon Sep 17 00:00:00 2001 From: Yuval Mintz Date: Wed, 1 Apr 2015 10:02:20 +0300 Subject: bnx2x: Fix kdump when iommu=on When IOMM-vtd is active, once main kernel crashes unfinished DMAE transactions will be blocked, putting the HW in an error state which will cause further transactions to timeout. Current employed logic uses wrong macros, causing the first function to be the only function that cleanups that error state during its probe/load. This patch allows all the functions to successfully re-load in kdump kernel. Signed-off-by: Yuval Mintz Signed-off-by: Ariel Elior Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c index e6dec47..1ec635f 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c @@ -7866,6 +7866,20 @@ int bnx2x_init_hw_func_cnic(struct bnx2x *bp) return 0; } +/* previous driver DMAE transaction may have occurred when pre-boot stage ended + * and boot began, or when kdump kernel was loaded. Either case would invalidate + * the addresses of the transaction, resulting in was-error bit set in the pci + * causing all hw-to-host pcie transactions to timeout. If this happened we want + * to clear the interrupt which detected this from the pglueb and the was done + * bit + */ +static void bnx2x_clean_pglue_errors(struct bnx2x *bp) +{ + if (!CHIP_IS_E1x(bp)) + REG_WR(bp, PGLUE_B_REG_WAS_ERROR_PF_7_0_CLR, + 1 << BP_ABS_FUNC(bp)); +} + static int bnx2x_init_hw_func(struct bnx2x *bp) { int port = BP_PORT(bp); @@ -7958,8 +7972,7 @@ static int bnx2x_init_hw_func(struct bnx2x *bp) bnx2x_init_block(bp, BLOCK_PGLUE_B, init_phase); - if (!CHIP_IS_E1x(bp)) - REG_WR(bp, PGLUE_B_REG_WAS_ERROR_PF_7_0_CLR, func); + bnx2x_clean_pglue_errors(bp); bnx2x_init_block(bp, BLOCK_ATC, init_phase); bnx2x_init_block(bp, BLOCK_DMAE, init_phase); @@ -10588,26 +10601,6 @@ static int bnx2x_prev_unload_common(struct bnx2x *bp) return bnx2x_prev_mcp_done(bp); } -/* previous driver DMAE transaction may have occurred when pre-boot stage ended - * and boot began, or when kdump kernel was loaded. Either case would invalidate - * the addresses of the transaction, resulting in was-error bit set in the pci - * causing all hw-to-host pcie transactions to timeout. If this happened we want - * to clear the interrupt which detected this from the pglueb and the was done - * bit - */ -static void bnx2x_prev_interrupted_dmae(struct bnx2x *bp) -{ - if (!CHIP_IS_E1x(bp)) { - u32 val = REG_RD(bp, PGLUE_B_REG_PGLUE_B_INT_STS); - if (val & PGLUE_B_PGLUE_B_INT_STS_REG_WAS_ERROR_ATTN) { - DP(BNX2X_MSG_SP, - "'was error' bit was found to be set in pglueb upon startup. Clearing\n"); - REG_WR(bp, PGLUE_B_REG_WAS_ERROR_PF_7_0_CLR, - 1 << BP_FUNC(bp)); - } - } -} - static int bnx2x_prev_unload(struct bnx2x *bp) { int time_counter = 10; @@ -10617,7 +10610,7 @@ static int bnx2x_prev_unload(struct bnx2x *bp) /* clear hw from errors which may have resulted from an interrupted * dmae transaction. */ - bnx2x_prev_interrupted_dmae(bp); + bnx2x_clean_pglue_errors(bp); /* Release previously held locks */ hw_lock_reg = (BP_FUNC(bp) <= 5) ? -- cgit v0.10.2 From e1b7c029a31a26e42c76f763b7cd566ecf62cbd2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 1 Apr 2015 17:03:30 +1030 Subject: lguest: now needs PCI_DIRECT. Since commit 8e7094694396 ("lguest: add a dummy PCI host bridge.") lguest uses PCI, but it needs you to frob the ports directly. Signed-off-by: Rusty Russell Acked-by: Ingo Molnar Signed-off-by: Linus Torvalds diff --git a/drivers/lguest/Kconfig b/drivers/lguest/Kconfig index ee035ec..169172d 100644 --- a/drivers/lguest/Kconfig +++ b/drivers/lguest/Kconfig @@ -1,6 +1,6 @@ config LGUEST tristate "Linux hypervisor example code" - depends on X86_32 && EVENTFD && TTY + depends on X86_32 && EVENTFD && TTY && PCI_DIRECT select HVC_DRIVER ---help--- This is a very simple module which allows you to run -- cgit v0.10.2 From 7ef65a42111270f343b26673c10e48180871326b Mon Sep 17 00:00:00 2001 From: Hariprasad Shenai Date: Wed, 1 Apr 2015 21:41:15 +0530 Subject: cxgb4: Firmware macro changes for fw verison 1.13.32.0 Adds new macro and few macro changes for fw version 1.13.32.0 also changes version string in driver to match 1.13.32.0 Signed-off-by: Hariprasad Shenai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h b/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h index 9b353a8..a4a19e0 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h +++ b/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h @@ -101,7 +101,7 @@ enum fw_wr_opcodes { FW_RI_BIND_MW_WR = 0x18, FW_RI_FR_NSMR_WR = 0x19, FW_RI_INV_LSTAG_WR = 0x1a, - FW_LASTC2E_WR = 0x40 + FW_LASTC2E_WR = 0x70 }; struct fw_wr_hdr { @@ -993,6 +993,7 @@ enum fw_memtype_cf { FW_MEMTYPE_CF_EXTMEM = 0x2, FW_MEMTYPE_CF_FLASH = 0x4, FW_MEMTYPE_CF_INTERNAL = 0x5, + FW_MEMTYPE_CF_EXTMEM1 = 0x6, }; struct fw_caps_config_cmd { @@ -1035,6 +1036,7 @@ enum fw_params_mnem { FW_PARAMS_MNEM_PFVF = 2, /* function params */ FW_PARAMS_MNEM_REG = 3, /* limited register access */ FW_PARAMS_MNEM_DMAQ = 4, /* dma queue params */ + FW_PARAMS_MNEM_CHNET = 5, /* chnet params */ FW_PARAMS_MNEM_LAST }; @@ -3102,7 +3104,8 @@ enum fw_devlog_facility { FW_DEVLOG_FACILITY_FCOE = 0x2E, FW_DEVLOG_FACILITY_FOISCSI = 0x30, FW_DEVLOG_FACILITY_FOFCOE = 0x32, - FW_DEVLOG_FACILITY_MAX = 0x32, + FW_DEVLOG_FACILITY_CHNET = 0x34, + FW_DEVLOG_FACILITY_MAX = 0x34, }; /* log message format */ @@ -3139,4 +3142,36 @@ struct fw_devlog_cmd { (((x) >> FW_DEVLOG_CMD_MEMADDR16_DEVLOG_S) & \ FW_DEVLOG_CMD_MEMADDR16_DEVLOG_M) +/* P C I E F W P F 7 R E G I S T E R */ + +/* PF7 stores the Firmware Device Log parameters which allows Host Drivers to + * access the "devlog" which needing to contact firmware. The encoding is + * mostly the same as that returned by the DEVLOG command except for the size + * which is encoded as the number of entries in multiples-1 of 128 here rather + * than the memory size as is done in the DEVLOG command. Thus, 0 means 128 + * and 15 means 2048. This of course in turn constrains the allowed values + * for the devlog size ... + */ +#define PCIE_FW_PF_DEVLOG 7 + +#define PCIE_FW_PF_DEVLOG_NENTRIES128_S 28 +#define PCIE_FW_PF_DEVLOG_NENTRIES128_M 0xf +#define PCIE_FW_PF_DEVLOG_NENTRIES128_V(x) \ + ((x) << PCIE_FW_PF_DEVLOG_NENTRIES128_S) +#define PCIE_FW_PF_DEVLOG_NENTRIES128_G(x) \ + (((x) >> PCIE_FW_PF_DEVLOG_NENTRIES128_S) & \ + PCIE_FW_PF_DEVLOG_NENTRIES128_M) + +#define PCIE_FW_PF_DEVLOG_ADDR16_S 4 +#define PCIE_FW_PF_DEVLOG_ADDR16_M 0xffffff +#define PCIE_FW_PF_DEVLOG_ADDR16_V(x) ((x) << PCIE_FW_PF_DEVLOG_ADDR16_S) +#define PCIE_FW_PF_DEVLOG_ADDR16_G(x) \ + (((x) >> PCIE_FW_PF_DEVLOG_ADDR16_S) & PCIE_FW_PF_DEVLOG_ADDR16_M) + +#define PCIE_FW_PF_DEVLOG_MEMTYPE_S 0 +#define PCIE_FW_PF_DEVLOG_MEMTYPE_M 0xf +#define PCIE_FW_PF_DEVLOG_MEMTYPE_V(x) ((x) << PCIE_FW_PF_DEVLOG_MEMTYPE_S) +#define PCIE_FW_PF_DEVLOG_MEMTYPE_G(x) \ + (((x) >> PCIE_FW_PF_DEVLOG_MEMTYPE_S) & PCIE_FW_PF_DEVLOG_MEMTYPE_M) + #endif /* _T4FW_INTERFACE_H_ */ diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4fw_version.h b/drivers/net/ethernet/chelsio/cxgb4/t4fw_version.h index e2bd3f7..b9d1cba 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4fw_version.h +++ b/drivers/net/ethernet/chelsio/cxgb4/t4fw_version.h @@ -36,13 +36,13 @@ #define __T4FW_VERSION_H__ #define T4FW_VERSION_MAJOR 0x01 -#define T4FW_VERSION_MINOR 0x0C -#define T4FW_VERSION_MICRO 0x19 +#define T4FW_VERSION_MINOR 0x0D +#define T4FW_VERSION_MICRO 0x20 #define T4FW_VERSION_BUILD 0x00 #define T5FW_VERSION_MAJOR 0x01 -#define T5FW_VERSION_MINOR 0x0C -#define T5FW_VERSION_MICRO 0x19 +#define T5FW_VERSION_MINOR 0x0D +#define T5FW_VERSION_MICRO 0x20 #define T5FW_VERSION_BUILD 0x00 #endif -- cgit v0.10.2 From ae469b68a557d1bd538c9122eaf99cf09fc242f2 Mon Sep 17 00:00:00 2001 From: Hariprasad Shenai Date: Wed, 1 Apr 2015 21:41:16 +0530 Subject: cxgb4: Fix to dump devlog, even if FW is crashed Add new Common Code routines to retrieve Firmware Device Log parameters from PCIE_FW_PF[7]. The firmware initializes its Device Log very early on and stores the parameters for its location/size in that register. Using the parameters from the register allows us to access the Firmware Device Log even when the firmware crashes very early on or we're not attached to the firmware Based on original work by Casey Leedom Signed-off-by: Hariprasad Shenai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h index 8816c64..c6ff489 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h @@ -1136,6 +1136,8 @@ int cxgb4_t4_bar2_sge_qregs(struct adapter *adapter, unsigned int qtimer_val(const struct adapter *adap, const struct sge_rspq *q); + +int t4_init_devlog_params(struct adapter *adapter); int t4_init_sge_params(struct adapter *adapter); int t4_init_tp_params(struct adapter *adap); int t4_filter_field_shift(const struct adapter *adap, int filter_sel); diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index 826de7a7..d929951 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -5110,10 +5110,15 @@ static int adap_init0(struct adapter *adap) enum dev_state state; u32 params[7], val[7]; struct fw_caps_config_cmd caps_cmd; - struct fw_devlog_cmd devlog_cmd; - u32 devlog_meminfo; int reset = 1; + /* Grab Firmware Device Log parameters as early as possible so we have + * access to it for debugging, etc. + */ + ret = t4_init_devlog_params(adap); + if (ret < 0) + return ret; + /* Contact FW, advertising Master capability */ ret = t4_fw_hello(adap, adap->mbox, adap->mbox, MASTER_MAY, &state); if (ret < 0) { @@ -5191,30 +5196,6 @@ static int adap_init0(struct adapter *adap) if (ret < 0) goto bye; - /* Read firmware device log parameters. We really need to find a way - * to get these parameters initialized with some default values (which - * are likely to be correct) for the case where we either don't - * attache to the firmware or it's crashed when we probe the adapter. - * That way we'll still be able to perform early firmware startup - * debugging ... If the request to get the Firmware's Device Log - * parameters fails, we'll live so we don't make that a fatal error. - */ - memset(&devlog_cmd, 0, sizeof(devlog_cmd)); - devlog_cmd.op_to_write = htonl(FW_CMD_OP_V(FW_DEVLOG_CMD) | - FW_CMD_REQUEST_F | FW_CMD_READ_F); - devlog_cmd.retval_len16 = htonl(FW_LEN16(devlog_cmd)); - ret = t4_wr_mbox(adap, adap->mbox, &devlog_cmd, sizeof(devlog_cmd), - &devlog_cmd); - if (ret == 0) { - devlog_meminfo = - ntohl(devlog_cmd.memtype_devlog_memaddr16_devlog); - adap->params.devlog.memtype = - FW_DEVLOG_CMD_MEMTYPE_DEVLOG_G(devlog_meminfo); - adap->params.devlog.start = - FW_DEVLOG_CMD_MEMADDR16_DEVLOG_G(devlog_meminfo) << 4; - adap->params.devlog.size = ntohl(devlog_cmd.memsize_devlog); - } - /* * Find out what ports are available to us. Note that we need to do * this before calling adap_init0_no_config() since it needs nports diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c index 1abdfa1..ee394dc 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c @@ -4459,6 +4459,59 @@ int cxgb4_t4_bar2_sge_qregs(struct adapter *adapter, } /** + * t4_init_devlog_params - initialize adapter->params.devlog + * @adap: the adapter + * + * Initialize various fields of the adapter's Firmware Device Log + * Parameters structure. + */ +int t4_init_devlog_params(struct adapter *adap) +{ + struct devlog_params *dparams = &adap->params.devlog; + u32 pf_dparams; + unsigned int devlog_meminfo; + struct fw_devlog_cmd devlog_cmd; + int ret; + + /* If we're dealing with newer firmware, the Device Log Paramerters + * are stored in a designated register which allows us to access the + * Device Log even if we can't talk to the firmware. + */ + pf_dparams = + t4_read_reg(adap, PCIE_FW_REG(PCIE_FW_PF_A, PCIE_FW_PF_DEVLOG)); + if (pf_dparams) { + unsigned int nentries, nentries128; + + dparams->memtype = PCIE_FW_PF_DEVLOG_MEMTYPE_G(pf_dparams); + dparams->start = PCIE_FW_PF_DEVLOG_ADDR16_G(pf_dparams) << 4; + + nentries128 = PCIE_FW_PF_DEVLOG_NENTRIES128_G(pf_dparams); + nentries = (nentries128 + 1) * 128; + dparams->size = nentries * sizeof(struct fw_devlog_e); + + return 0; + } + + /* Otherwise, ask the firmware for it's Device Log Parameters. + */ + memset(&devlog_cmd, 0, sizeof(devlog_cmd)); + devlog_cmd.op_to_write = htonl(FW_CMD_OP_V(FW_DEVLOG_CMD) | + FW_CMD_REQUEST_F | FW_CMD_READ_F); + devlog_cmd.retval_len16 = htonl(FW_LEN16(devlog_cmd)); + ret = t4_wr_mbox(adap, adap->mbox, &devlog_cmd, sizeof(devlog_cmd), + &devlog_cmd); + if (ret) + return ret; + + devlog_meminfo = ntohl(devlog_cmd.memtype_devlog_memaddr16_devlog); + dparams->memtype = FW_DEVLOG_CMD_MEMTYPE_DEVLOG_G(devlog_meminfo); + dparams->start = FW_DEVLOG_CMD_MEMADDR16_DEVLOG_G(devlog_meminfo) << 4; + dparams->size = ntohl(devlog_cmd.memsize_devlog); + + return 0; +} + +/** * t4_init_sge_params - initialize adap->params.sge * @adapter: the adapter * diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h index 231a725..326674b 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h @@ -63,6 +63,8 @@ #define MC_BIST_STATUS_REG(reg_addr, idx) ((reg_addr) + (idx) * 4) #define EDC_BIST_STATUS_REG(reg_addr, idx) ((reg_addr) + (idx) * 4) +#define PCIE_FW_REG(reg_addr, idx) ((reg_addr) + (idx) * 4) + #define SGE_PF_KDOORBELL_A 0x0 #define QID_S 15 @@ -707,6 +709,7 @@ #define PFNUM_V(x) ((x) << PFNUM_S) #define PCIE_FW_A 0x30b8 +#define PCIE_FW_PF_A 0x30bc #define PCIE_CORE_UTL_SYSTEM_BUS_AGENT_STATUS_A 0x5908 -- cgit v0.10.2 From 43cb6954f8c8a68fdc354226fa045ff43c7e4d39 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 1 Apr 2015 04:15:16 +0000 Subject: ASoC: rsnd: add Synchronous SRC mode Renesas R-Car sound SRC (= Sampling Rate Converter) has Asynchronous/Synchronous SRC mode. Asynchronous mode is already supported via DPCM. This patch adds Synchronous mode on it. The condition of enabling Synchronous mode are - SoC is clock master - Sound uses SRC - Sound doesn't use DVC - Sound card uses DPCM (= rsrc-card card) amixer set "SRC Out Rate" on aplay xxx.wav & amixer set "SRC Out Rate" 48000 Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index a0a2bda..3beb32e 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -22,13 +22,15 @@ struct rsnd_src { struct rsnd_src_platform_info *info; /* rcar_snd.h */ struct rsnd_mod mod; + struct rsnd_kctrl_cfg_s sen; /* sync convert enable */ + struct rsnd_kctrl_cfg_s sync; /* sync convert */ u32 convert_rate; /* sampling rate convert */ int err; }; #define RSND_SRC_NAME_SIZE 16 -#define rsnd_src_convert_rate(s) ((s)->convert_rate) +#define rsnd_enable_sync_convert(src) ((src)->sen.val) #define rsnd_src_of_node(priv) \ of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,src") @@ -233,6 +235,30 @@ int rsnd_src_ssi_irq_disable(struct rsnd_mod *ssi_mod) return 0; } +static u32 rsnd_src_convert_rate(struct rsnd_src *src) +{ + struct rsnd_mod *mod = &src->mod; + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + u32 convert_rate; + + if (!runtime) + return 0; + + if (!rsnd_enable_sync_convert(src)) + return src->convert_rate; + + convert_rate = src->sync.val; + + if (!convert_rate) + convert_rate = src->convert_rate; + + if (!convert_rate) + convert_rate = runtime->rate; + + return convert_rate; +} + unsigned int rsnd_src_get_ssi_rate(struct rsnd_priv *priv, struct rsnd_dai_stream *io, struct snd_pcm_runtime *runtime) @@ -333,6 +359,9 @@ static int rsnd_src_init(struct rsnd_mod *mod, src->err = 0; + /* reset sync convert_rate */ + src->sync.val = 0; + /* * Initialize the operation of the SRC internal circuits * see rsnd_src_start() @@ -356,6 +385,9 @@ static int rsnd_src_quit(struct rsnd_mod *mod, src->convert_rate = 0; + /* reset sync convert_rate */ + src->sync.val = 0; + return 0; } @@ -672,6 +704,7 @@ static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod) struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); struct rsnd_src *src = rsnd_mod_to_src(mod); u32 convert_rate = rsnd_src_convert_rate(src); + u32 cr, route; uint ratio; int ret; @@ -692,13 +725,21 @@ static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod) if (ret < 0) return ret; - rsnd_mod_write(mod, SRC_SRCCR, 0x00011110); - + cr = 0x00011110; + route = 0x0; if (convert_rate) { - /* Gen1/Gen2 are not compatible */ - rsnd_mod_write(mod, SRC_ROUTE_MODE0, 1); + route = 0x1; + + if (rsnd_enable_sync_convert(src)) { + cr |= 0x1; + route |= rsnd_io_is_play(io) ? + (0x1 << 24) : (0x1 << 25); + } } + rsnd_mod_write(mod, SRC_SRCCR, cr); + rsnd_mod_write(mod, SRC_ROUTE_MODE0, route); + switch (rsnd_mod_id(mod)) { case 5: case 6: @@ -811,6 +852,80 @@ static int rsnd_src_stop_gen2(struct rsnd_mod *mod, return ret; } +static void rsnd_src_reconvert_update(struct rsnd_mod *mod) +{ + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_src *src = rsnd_mod_to_src(mod); + u32 convert_rate = rsnd_src_convert_rate(src); + u32 fsrate; + + if (!runtime) + return; + + if (!convert_rate) + convert_rate = runtime->rate; + + fsrate = 0x0400000 / convert_rate * runtime->rate; + + /* update IFS */ + rsnd_mod_write(mod, SRC_IFSVR, fsrate); +} + +static int rsnd_src_pcm_new(struct rsnd_mod *mod, + struct snd_soc_pcm_runtime *rtd) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + struct rsnd_src *src = rsnd_mod_to_src(mod); + int ret; + + /* + * enable SRC sync convert if possible + */ + + /* + * Gen1 is not supported + */ + if (rsnd_is_gen1(priv)) + return 0; + + /* + * SRC sync convert needs clock master + */ + if (!rsnd_rdai_is_clk_master(rdai)) + return 0; + + /* + * We can't use SRC sync convert + * if it has DVC + */ + if (rsnd_io_to_mod_dvc(io)) + return 0; + + /* + * enable sync convert + */ + ret = rsnd_kctrl_new_s(mod, rtd, + rsnd_io_is_play(io) ? + "SRC Out Rate Switch" : + "SRC In Rate Switch", + rsnd_src_reconvert_update, + &src->sen, 1); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_s(mod, rtd, + rsnd_io_is_play(io) ? + "SRC Out Rate" : + "SRC In Rate", + rsnd_src_reconvert_update, + &src->sync, 192000); + + return ret; +} + static struct rsnd_mod_ops rsnd_src_gen2_ops = { .name = SRC_NAME, .dma_req = rsnd_src_dma_req, @@ -821,6 +936,7 @@ static struct rsnd_mod_ops rsnd_src_gen2_ops = { .start = rsnd_src_start_gen2, .stop = rsnd_src_stop_gen2, .hw_params = rsnd_src_hw_params, + .pcm_new = rsnd_src_pcm_new, }; struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id) -- cgit v0.10.2 From 488cb533911b96fe41af68a0206878aa46b799bc Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 30 Mar 2015 19:54:54 +0200 Subject: ASoC: atmel-pcm-pdc: merge atmel-pcm back in atmel-pcm.c was split into two files to create a generic framework for both PDC and DMA. atmel-pcm-dma.c is using the generic dmaengine framework since 95e0e07e710e (ASoC: atmel-pcm: use generic dmaengine framework). Merge atmel-pcm.c in atmel-pcm-pdc.c as this is now the only user. Signed-off-by: Alexandre Belloni Acked-by: Nicolas Ferre Signed-off-by: Mark Brown diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 466a821..b327e5c 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -1,10 +1,8 @@ # AT91 Platform Support -snd-soc-atmel-pcm-objs := atmel-pcm.o snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o -obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c index a366b35..da861b4 100644 --- a/sound/soc/atmel/atmel-pcm-pdc.c +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -47,6 +47,85 @@ #include "atmel-pcm.h" +static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = ATMEL_SSC_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n", + (void *)buf->area, (void *)(long)buf->addr, size); + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static int atmel_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n"); + ret = atmel_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n"); + ret = atmel_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +static void atmel_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + /*--------------------------------------------------------------------------*\ * Hardware definition \*--------------------------------------------------------------------------*/ diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c deleted file mode 100644 index 8ae3fa5..0000000 --- a/sound/soc/atmel/atmel-pcm.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC. - * - * Copyright (C) 2005 SAN People - * Copyright (C) 2008 Atmel - * - * Authors: Sedji Gaouaou - * - * Based on at91-pcm. by: - * Frank Mandarino - * Copyright 2006 Endrelia Technologies Inc. - * - * Based on pxa2xx-pcm.c by: - * - * Author: Nicolas Pitre - * Created: Nov 30, 2004 - * Copyright: (C) 2004 MontaVista Software, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include -#include -#include -#include "atmel-pcm.h" - -static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, - int stream) -{ - struct snd_pcm_substream *substream = pcm->streams[stream].substream; - struct snd_dma_buffer *buf = &substream->dma_buffer; - size_t size = ATMEL_SSC_DMABUF_SIZE; - - buf->dev.type = SNDRV_DMA_TYPE_DEV; - buf->dev.dev = pcm->card->dev; - buf->private_data = NULL; - buf->area = dma_alloc_coherent(pcm->card->dev, size, - &buf->addr, GFP_KERNEL); - pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n", - (void *)buf->area, (void *)(long)buf->addr, size); - - if (!buf->area) - return -ENOMEM; - - buf->bytes = size; - return 0; -} - -int atmel_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - return remap_pfn_range(vma, vma->vm_start, - substream->dma_buffer.addr >> PAGE_SHIFT, - vma->vm_end - vma->vm_start, vma->vm_page_prot); -} -EXPORT_SYMBOL_GPL(atmel_pcm_mmap); - -int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_card *card = rtd->card->snd_card; - struct snd_pcm *pcm = rtd->pcm; - int ret; - - ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); - if (ret) - return ret; - - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { - pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n"); - ret = atmel_pcm_preallocate_dma_buffer(pcm, - SNDRV_PCM_STREAM_PLAYBACK); - if (ret) - goto out; - } - - if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { - pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n"); - ret = atmel_pcm_preallocate_dma_buffer(pcm, - SNDRV_PCM_STREAM_CAPTURE); - if (ret) - goto out; - } - out: - return ret; -} -EXPORT_SYMBOL_GPL(atmel_pcm_new); - -void atmel_pcm_free(struct snd_pcm *pcm) -{ - struct snd_pcm_substream *substream; - struct snd_dma_buffer *buf; - int stream; - - for (stream = 0; stream < 2; stream++) { - substream = pcm->streams[stream].substream; - if (!substream) - continue; - - buf = &substream->dma_buffer; - if (!buf->area) - continue; - dma_free_coherent(pcm->card->dev, buf->bytes, - buf->area, buf->addr); - buf->area = NULL; - } -} -EXPORT_SYMBOL_GPL(atmel_pcm_free); - diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h index 12ae814..6eaf081 100644 --- a/sound/soc/atmel/atmel-pcm.h +++ b/sound/soc/atmel/atmel-pcm.h @@ -83,11 +83,6 @@ struct atmel_pcm_dma_params { #define ssc_readx(base, reg) (__raw_readl((base) + (reg))) #define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg)) -int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd); -void atmel_pcm_free(struct snd_pcm *pcm); -int atmel_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma); - #if defined(CONFIG_SND_ATMEL_SOC_PDC) || \ defined(CONFIG_SND_ATMEL_SOC_PDC_MODULE) int atmel_pcm_pdc_platform_register(struct device *dev); -- cgit v0.10.2 From ab87ce1d9bb0501fccfc00d5e5ce7c16cd2bcc3e Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:45 +0200 Subject: ASoC: wm8971: Use system_power_efficient_wq instead of custom workqueue The delayed work used by the wm8971 driver to manage the caps charging doesn't have any special requirements that would justify using a custom workqueue, just use the generic system_power_efficient_wq instead. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 39ddb9b..44baacd 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -31,8 +31,6 @@ #define WM8971_REG_COUNT 43 -static struct workqueue_struct *wm8971_workq = NULL; - /* codec private data */ struct wm8971_priv { unsigned int sysclk; @@ -636,7 +634,8 @@ static int wm8971_resume(struct snd_soc_codec *codec) reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0); codec->dapm.bias_level = SND_SOC_BIAS_ON; - queue_delayed_work(wm8971_workq, &codec->dapm.delayed_work, + queue_delayed_work(system_power_efficient_wq, + &codec->dapm.delayed_work, msecs_to_jiffies(1000)); } @@ -649,9 +648,6 @@ static int wm8971_probe(struct snd_soc_codec *codec) u16 reg; INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8971_work); - wm8971_workq = create_workqueue("wm8971"); - if (wm8971_workq == NULL) - return -ENOMEM; wm8971_reset(codec); @@ -659,7 +655,8 @@ static int wm8971_probe(struct snd_soc_codec *codec) reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0); codec->dapm.bias_level = SND_SOC_BIAS_STANDBY; - queue_delayed_work(wm8971_workq, &codec->dapm.delayed_work, + queue_delayed_work(system_power_efficient_wq, + &codec->dapm.delayed_work, msecs_to_jiffies(1000)); /* set the update bits */ @@ -681,8 +678,6 @@ static int wm8971_remove(struct snd_soc_codec *codec) { wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); - if (wm8971_workq) - destroy_workqueue(wm8971_workq); return 0; } -- cgit v0.10.2 From 643518403c3fdecd40d9edfd50f4bafcebeda79b Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:46 +0200 Subject: ASoC: wm8971: Integrate capacitor charging into the DAPM sequence When being powered on, either initially on probe or when resuming from suspend, the wm8971 configures the device for quick output capacitor charging. Since the charging can take a rather long time (up to multiple seconds) it is done asynchronously without blocking. A delayed work item is run once the charging is finished and the device is switched to the target bias level. This all done asynchronously to the regular DAPM sequence accessing the same data structures and registers without any looking, which can lead to race conditions. Furthermore this potentially delays the start of stream on the CODEC while the rest of the system is already up and running, meaning the first bytes of audio are lost. It also does no comply with the assumption of the DAPM core that if set_bias_level() returned successfully the device will be at the requested bias level. This patch slightly refactors things and makes sure that the caps charging is properly integrated into the DAPM sequence. When transitioning from SND_SOC_BIAS_OFF to SND_SOC_BIAS_STANDBY the part will be put into fast charging mode and a work item will be scheduled that puts it back into standby charging once the charging period has elapsed. If a playback or capture stream is started while charging is in progress the driver will now wait in SND_SOC_BIAS_PREPARE until the charging is done. This makes sure that charging is done asynchronously in the background when the chip is idle, but at the same time makes sure that playback/capture is not started before the charging is done. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 44baacd..4ab034d 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -34,6 +34,8 @@ /* codec private data */ struct wm8971_priv { unsigned int sysclk; + struct delayed_work charge_work; + struct regmap *regmap; }; /* @@ -550,9 +552,19 @@ static int wm8971_mute(struct snd_soc_dai *dai, int mute) return 0; } +static void wm8971_charge_work(struct work_struct *work) +{ + struct wm8971_priv *wm8971 = + container_of(work, struct wm8971_priv, charge_work.work); + + /* Set to 500k */ + regmap_update_bits(wm8971->regmap, WM8971_PWR1, 0x0180, 0x0100); +} + static int wm8971_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; switch (level) { @@ -561,15 +573,24 @@ static int wm8971_set_bias_level(struct snd_soc_codec *codec, snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); break; case SND_SOC_BIAS_PREPARE: + /* Wait until fully charged */ + flush_delayed_work(&wm8971->charge_work); break; case SND_SOC_BIAS_STANDBY: - if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { snd_soc_cache_sync(codec); + /* charge output caps - set vmid to 5k for quick power up */ + snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x01c0); + queue_delayed_work(system_power_efficient_wq, + &wm8971->charge_work, msecs_to_jiffies(1000)); + } else { + /* mute dac and set vmid to 500k, enable VREF */ + snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140); + } - /* mute dac and set vmid to 500k, enable VREF */ - snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140); break; case SND_SOC_BIAS_OFF: + cancel_delayed_work_sync(&wm8971->charge_work); snd_soc_write(codec, WM8971_PWR1, 0x0001); break; } @@ -608,15 +629,6 @@ static struct snd_soc_dai_driver wm8971_dai = { .ops = &wm8971_dai_ops, }; -static void wm8971_work(struct work_struct *work) -{ - struct snd_soc_dapm_context *dapm = - container_of(work, struct snd_soc_dapm_context, - delayed_work.work); - struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); - wm8971_set_bias_level(codec, codec->dapm.bias_level); -} - static int wm8971_suspend(struct snd_soc_codec *codec) { wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -625,39 +637,19 @@ static int wm8971_suspend(struct snd_soc_codec *codec) static int wm8971_resume(struct snd_soc_codec *codec) { - u16 reg; - wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - /* charge wm8971 caps */ - if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) { - reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; - snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0); - codec->dapm.bias_level = SND_SOC_BIAS_ON; - queue_delayed_work(system_power_efficient_wq, - &codec->dapm.delayed_work, - msecs_to_jiffies(1000)); - } - return 0; } static int wm8971_probe(struct snd_soc_codec *codec) { - int ret = 0; - u16 reg; + struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); - INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8971_work); + INIT_DELAYED_WORK(&wm8971->charge_work, wm8971_charge_work); wm8971_reset(codec); - /* charge output caps - set vmid to 5k for quick power up */ - reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; - snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0); - codec->dapm.bias_level = SND_SOC_BIAS_STANDBY; - queue_delayed_work(system_power_efficient_wq, - &codec->dapm.delayed_work, - msecs_to_jiffies(1000)); + wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set the update bits */ snd_soc_update_bits(codec, WM8971_LDAC, 0x0100, 0x0100); @@ -669,7 +661,7 @@ static int wm8971_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8971_LINVOL, 0x0100, 0x0100); snd_soc_update_bits(codec, WM8971_RINVOL, 0x0100, 0x0100); - return ret; + return 0; } @@ -710,7 +702,6 @@ static int wm8971_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct wm8971_priv *wm8971; - struct regmap *regmap; int ret; wm8971 = devm_kzalloc(&i2c->dev, sizeof(struct wm8971_priv), @@ -718,9 +709,9 @@ static int wm8971_i2c_probe(struct i2c_client *i2c, if (wm8971 == NULL) return -ENOMEM; - regmap = devm_regmap_init_i2c(i2c, &wm8971_regmap); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); + wm8971->regmap = devm_regmap_init_i2c(i2c, &wm8971_regmap); + if (IS_ERR(wm8971->regmap)) + return PTR_ERR(wm8971->regmap); i2c_set_clientdata(i2c, wm8971); -- cgit v0.10.2 From c59e6abba9dd7bc273c3dd389ae9927d1da88f35 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:47 +0200 Subject: ASoC: wm8971: Cleanup manual bias level transitions Set the CODEC driver's suspend_bias_off flag rather than manually going to SND_SOC_BIAS_OFF in suspend and SND_SOC_BIAS_STANDBY in resume. This makes the code a bit shorter and cleaner. Since the ASoC core now takes care of setting the bias level to SND_SOC_BIAS_OFF when removing the CODEC there is no need to do it manually anymore either. The manual transition to SND_SOC_BIAS_STANDBY at the end of CODEC probe() can also be removed as the core will automatically do this after the CODEC has been probed. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 4ab034d..f9cbabd 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -629,18 +629,6 @@ static struct snd_soc_dai_driver wm8971_dai = { .ops = &wm8971_dai_ops, }; -static int wm8971_suspend(struct snd_soc_codec *codec) -{ - wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8971_resume(struct snd_soc_codec *codec) -{ - wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int wm8971_probe(struct snd_soc_codec *codec) { struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); @@ -649,8 +637,6 @@ static int wm8971_probe(struct snd_soc_codec *codec) wm8971_reset(codec); - wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* set the update bits */ snd_soc_update_bits(codec, WM8971_LDAC, 0x0100, 0x0100); snd_soc_update_bits(codec, WM8971_RDAC, 0x0100, 0x0100); @@ -664,21 +650,10 @@ static int wm8971_probe(struct snd_soc_codec *codec) return 0; } - -/* power down chip */ -static int wm8971_remove(struct snd_soc_codec *codec) -{ - wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8971 = { .probe = wm8971_probe, - .remove = wm8971_remove, - .suspend = wm8971_suspend, - .resume = wm8971_resume, .set_bias_level = wm8971_set_bias_level, + .suspend_bias_off = true, .controls = wm8971_snd_controls, .num_controls = ARRAY_SIZE(wm8971_snd_controls), -- cgit v0.10.2 From 35afd9221b301d1959eadab2d45a2cb94dcb7d30 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:48 +0200 Subject: ASoC: wm8753: Integrate capacitor charging into the DAPM sequence When being powered on, either initially on probe or when resuming from suspend, the wm8971 configures the device for quick output capacitor charging. Since the charging can take a rather long time (up to multiple seconds) it is done asynchronously without blocking. A delayed work item is run once the charging is finished and the device is switched to the target bias level. This all done asynchronously to the regular DAPM sequence accessing the same data structures and registers without any looking, which can lead to race conditions. Furthermore this potentially delays the start of stream on the CODEC while the rest of the system is already up and running, meaning the first bytes of audio are lost. It also does no comply with the assumption of the DAPM core that if set_bias_level() returned successfully the device will be at the requested bias level. This patch slightly refactors things and makes sure that the caps charging is properly integrated into the DAPM sequence. When transitioning from SND_SOC_BIAS_OFF to SND_SOC_BIAS_STANDBY the part will be put into fast charging mode and a work item will be scheduled that puts it back into standby charging once the charging period has elapsed. If a playback or capture stream is started while charging is in progress the driver will now wait in SND_SOC_BIAS_PREPARE until the charging is done. This makes sure that charging is done asynchronously in the background when the chip is idle, but at the same time makes sure that playback/capture is not started before the charging is done. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 21ca3a9..176fcb1 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -153,6 +153,7 @@ struct wm8753_priv { unsigned int hifi_fmt; int dai_func; + struct delayed_work charge_work; }; #define wm8753_reset(c) snd_soc_write(c, WM8753_RESET, 0) @@ -1326,9 +1327,19 @@ static int wm8753_mute(struct snd_soc_dai *dai, int mute) return 0; } +static void wm8753_charge_work(struct work_struct *work) +{ + struct wm8753_priv *wm8753 = + container_of(work, struct wm8753_priv, charge_work.work); + + /* Set to 500k */ + regmap_update_bits(wm8753->regmap, WM8753_PWR1, 0x0180, 0x0100); +} + static int wm8753_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 pwr_reg = snd_soc_read(codec, WM8753_PWR1) & 0xfe3e; switch (level) { @@ -1337,14 +1348,22 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec, snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); break; case SND_SOC_BIAS_PREPARE: - /* set vmid to 5k for quick power up */ - snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); + /* Wait until fully charged */ + flush_delayed_work(&wm8753->charge_work); break; case SND_SOC_BIAS_STANDBY: - /* mute dac and set vmid to 500k, enable VREF */ - snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x0141); + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + /* set vmid to 5k for quick power up */ + snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); + schedule_delayed_work(&wm8753->charge_work, + msecs_to_jiffies(caps_charge)); + } else { + /* mute dac and set vmid to 500k, enable VREF */ + snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x0141); + } break; case SND_SOC_BIAS_OFF: + cancel_delayed_work_sync(&wm8753->charge_work); snd_soc_write(codec, WM8753_PWR1, 0x0001); break; } @@ -1428,15 +1447,6 @@ static struct snd_soc_dai_driver wm8753_dai[] = { }, }; -static void wm8753_work(struct work_struct *work) -{ - struct snd_soc_dapm_context *dapm = - container_of(work, struct snd_soc_dapm_context, - delayed_work.work); - struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); - wm8753_set_bias_level(codec, dapm->bias_level); -} - static int wm8753_suspend(struct snd_soc_codec *codec) { wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -1450,16 +1460,6 @@ static int wm8753_resume(struct snd_soc_codec *codec) regcache_sync(wm8753->regmap); wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - /* charge wm8753 caps */ - if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) { - wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - codec->dapm.bias_level = SND_SOC_BIAS_ON; - queue_delayed_work(system_power_efficient_wq, - &codec->dapm.delayed_work, - msecs_to_jiffies(caps_charge)); - } - return 0; } @@ -1468,7 +1468,7 @@ static int wm8753_probe(struct snd_soc_codec *codec) struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); int ret; - INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8753_work); + INIT_DELAYED_WORK(&wm8753->charge_work, wm8753_charge_work); ret = wm8753_reset(codec); if (ret < 0) { @@ -1479,11 +1479,6 @@ static int wm8753_probe(struct snd_soc_codec *codec) wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm8753->dai_func = 0; - /* charge output caps */ - wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - schedule_delayed_work(&codec->dapm.delayed_work, - msecs_to_jiffies(caps_charge)); - /* set the update bits */ snd_soc_update_bits(codec, WM8753_LDAC, 0x0100, 0x0100); snd_soc_update_bits(codec, WM8753_RDAC, 0x0100, 0x0100); @@ -1502,7 +1497,6 @@ static int wm8753_probe(struct snd_soc_codec *codec) /* power down chip */ static int wm8753_remove(struct snd_soc_codec *codec) { - flush_delayed_work(&codec->dapm.delayed_work); wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; -- cgit v0.10.2 From a1f0b9674936bf55d5d49813de01547de2667690 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:49 +0200 Subject: ASoC: wm8753: Cleanup manual bias level transitions Set the CODEC driver's suspend_bias_off flag rather than manually going to SND_SOC_BIAS_OFF in suspend and SND_SOC_BIAS_STANDBY in resume. This makes the code a bit shorter and cleaner. Since the ASoC core now takes care of setting the bias level to SND_SOC_BIAS_OFF when removing the CODEC there is no need to do it manually anymore either. The manual transition to SND_SOC_BIAS_STANDBY at the end of CODEC probe() can also be removed as the core will automatically do this after the CODEC has been probed. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 176fcb1..c50a595 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1447,19 +1447,12 @@ static struct snd_soc_dai_driver wm8753_dai[] = { }, }; -static int wm8753_suspend(struct snd_soc_codec *codec) -{ - wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static int wm8753_resume(struct snd_soc_codec *codec) { struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); regcache_sync(wm8753->regmap); - wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } @@ -1476,7 +1469,6 @@ static int wm8753_probe(struct snd_soc_codec *codec) return ret; } - wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm8753->dai_func = 0; /* set the update bits */ @@ -1494,20 +1486,11 @@ static int wm8753_probe(struct snd_soc_codec *codec) return 0; } -/* power down chip */ -static int wm8753_remove(struct snd_soc_codec *codec) -{ - wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8753 = { .probe = wm8753_probe, - .remove = wm8753_remove, - .suspend = wm8753_suspend, .resume = wm8753_resume, .set_bias_level = wm8753_set_bias_level, + .suspend_bias_off = true, .controls = wm8753_snd_controls, .num_controls = ARRAY_SIZE(wm8753_snd_controls), -- cgit v0.10.2 From 37660b6daf6d28bb2206c95ec75c8063f2db1606 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:50 +0200 Subject: ASoC: Remove suspend_bias_level from DAPM context struct The only two users of the suspend_bias_level field were two rather old drivers which weren't exactly doing things by the book. Those drivers have been updated and field is now unused and can be removed. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 8d7416e..9e35203 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -586,7 +586,6 @@ struct snd_soc_dapm_update { /* DAPM context */ struct snd_soc_dapm_context { enum snd_soc_bias_level bias_level; - enum snd_soc_bias_level suspend_bias_level; struct delayed_work delayed_work; unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */ /* Go to BIAS_OFF in suspend if the DAPM context is idle */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 30579ca..72601a8 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -583,15 +583,9 @@ int snd_soc_suspend(struct device *dev) cpu_dai->driver->suspend(cpu_dai); } - /* close any waiting streams and save state */ - for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai **codec_dais = card->rtd[i].codec_dais; + /* close any waiting streams */ + for (i = 0; i < card->num_rtd; i++) flush_delayed_work(&card->rtd[i].delayed_work); - for (j = 0; j < card->rtd[i].num_codecs; j++) { - codec_dais[j]->codec->dapm.suspend_bias_level = - codec_dais[j]->codec->dapm.bias_level; - } - } for (i = 0; i < card->num_rtd; i++) { -- cgit v0.10.2 From cd5d822688f3b32af286a76f7078dfe49c716282 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:51 +0200 Subject: ASoC: wm8350: Move delayed work struct from DAPM context to driver state The wm8350 driver is the last driver that still uses the delayed_work field from the snd_soc_dapm_context struct. Moving this over to the driver's private data struct will allow us to remove the field from the DAPM context, which will drastically reduce its size. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index c81a9ea..c65e5a7 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -69,14 +69,14 @@ struct wm8350_data { struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; int fll_freq_out; int fll_freq_in; + struct delayed_work pga_work; }; /* * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown. */ -static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) +static inline int wm8350_out1_ramp_step(struct wm8350_data *wm8350_data) { - struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1; struct wm8350 *wm8350 = wm8350_data->wm8350; int left_complete = 0, right_complete = 0; @@ -140,9 +140,8 @@ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) /* * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown. */ -static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) +static inline int wm8350_out2_ramp_step(struct wm8350_data *wm8350_data) { - struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out2 = &wm8350_data->out2; struct wm8350 *wm8350 = wm8350_data->wm8350; int left_complete = 0, right_complete = 0; @@ -210,10 +209,8 @@ static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) */ static void wm8350_pga_work(struct work_struct *work) { - struct snd_soc_dapm_context *dapm = - container_of(work, struct snd_soc_dapm_context, delayed_work.work); - struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); - struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); + struct wm8350_data *wm8350_data = + container_of(work, struct wm8350_data, pga_work.work); struct wm8350_output *out1 = &wm8350_data->out1, *out2 = &wm8350_data->out2; int i, out1_complete, out2_complete; @@ -226,9 +223,9 @@ static void wm8350_pga_work(struct work_struct *work) for (i = 0; i <= 63; i++) { out1_complete = 1, out2_complete = 1; if (out1->ramp != WM8350_RAMP_NONE) - out1_complete = wm8350_out1_ramp_step(codec); + out1_complete = wm8350_out1_ramp_step(wm8350_data); if (out2->ramp != WM8350_RAMP_NONE) - out2_complete = wm8350_out2_ramp_step(codec); + out2_complete = wm8350_out2_ramp_step(wm8350_data); /* ramp finished ? */ if (out1_complete && out2_complete) @@ -283,7 +280,7 @@ static int pga_event(struct snd_soc_dapm_widget *w, out->ramp = WM8350_RAMP_UP; out->active = 1; - schedule_delayed_work(&codec->dapm.delayed_work, + schedule_delayed_work(&wm8350_data->pga_work, msecs_to_jiffies(1)); break; @@ -291,7 +288,7 @@ static int pga_event(struct snd_soc_dapm_widget *w, out->ramp = WM8350_RAMP_DOWN; out->active = 0; - schedule_delayed_work(&codec->dapm.delayed_work, + schedule_delayed_work(&wm8350_data->pga_work, msecs_to_jiffies(1)); break; } @@ -1492,7 +1489,7 @@ static int wm8350_codec_probe(struct snd_soc_codec *codec) /* Put the codec into reset if it wasn't already */ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); - INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8350_pga_work); + INIT_DELAYED_WORK(&priv->pga_work, wm8350_pga_work); INIT_DELAYED_WORK(&priv->hpl.work, wm8350_hpl_work); INIT_DELAYED_WORK(&priv->hpr.work, wm8350_hpr_work); @@ -1578,7 +1575,7 @@ static int wm8350_codec_remove(struct snd_soc_codec *codec) /* if there was any work waiting then we run it now and * wait for its completion */ - flush_delayed_work(&codec->dapm.delayed_work); + flush_delayed_work(&priv->pga_work); wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); -- cgit v0.10.2 From 7c0e3facf39a68f30c2eae69e969918eca1ff9d6 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 30 Mar 2015 21:04:52 +0200 Subject: ASoC: dapm: Remove delayed_work from dapm context struct The delayed_work field in the snd_soc_dapm_context struct is now unused and can be removed. Removing it reduces the size of the snd_soc_dapm_context struct by ~50% from 100 bytes to 48 bytes. Signed-off-by: Lars-Peter Clausen Acked-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 9e35203..485fc9d 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -586,7 +586,6 @@ struct snd_soc_dapm_update { /* DAPM context */ struct snd_soc_dapm_context { enum snd_soc_bias_level bias_level; - struct delayed_work delayed_work; unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */ /* Go to BIAS_OFF in suspend if the DAPM context is idle */ unsigned int suspend_bias_off:1; -- cgit v0.10.2 From 7b425f264fc2572bea8b15fe97948659507c6a45 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Wed, 1 Apr 2015 20:35:57 +0300 Subject: ASoC: omap-hdmi-audio: No not use IEC958_AES1_PRO_MODE_NOTID No IEC958_AES?_PRO_* macros should be used in HDMI consumer audio mode and IEC958_AES1_PRO_MODE_NOTID should be applied to byte 1 when applicable. However IEC958_AES1_PRO_MODE_NOTID is defined as 0 so this fix does not affect the functionality in any way. Reported-by: Russell King Signed-off-by: Jyri Sarha Signed-off-by: Mark Brown diff --git a/sound/soc/omap/omap-hdmi-audio.c b/sound/soc/omap/omap-hdmi-audio.c index ccfb41c..1822578 100644 --- a/sound/soc/omap/omap-hdmi-audio.c +++ b/sound/soc/omap/omap-hdmi-audio.c @@ -142,8 +142,6 @@ static int hdmi_dai_hw_params(struct snd_pcm_substream *substream, iec->status[0] |= IEC958_AES0_CON_EMPHASIS_NONE; - iec->status[0] |= IEC958_AES1_PRO_MODE_NOTID; - iec->status[1] = IEC958_AES1_CON_GENERAL; iec->status[2] |= IEC958_AES2_CON_SOURCE_UNSPEC; -- cgit v0.10.2 From 7b3d165a282145e605247148d3dec034814e0a78 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Fri, 27 Mar 2015 11:47:51 +0200 Subject: ASoC: davinci-mcasp: Index ruledata in drvdata with substream->stream The serializer direction definitions runs from 1 to 2, which does not suite the purpose. The substream->stream is perfect for the purpose and should have been used from the beginning. Signed-off-by: Jyri Sarha Signed-off-by: Mark Brown diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 76156d18..0b6b1b2 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -1149,7 +1149,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, if (mcasp->serial_dir[i] == dir) max_channels++; } - mcasp->ruledata[dir].serializers = max_channels; + mcasp->ruledata[substream->stream].serializers = max_channels; max_channels *= mcasp->tdm_slots; /* * If the already active stream has less channels than the calculated @@ -1172,7 +1172,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { int ret; - mcasp->ruledata[dir].mcasp = mcasp; + mcasp->ruledata[substream->stream].mcasp = mcasp; ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, -- cgit v0.10.2 From 840a1cf0cd533f30da792527ca5ff6a023d4a727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 27 Mar 2015 19:59:40 +0200 Subject: drm/i915: Reject the colorkey ioctls for primary and cursor planes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legcy colorkey ioctls are only implemented for sprite planes, so reject the ioctl for primary/cursor planes. If we want to support colorkeying with these planes (assuming we have hw support of course) we should just move ahead with the colorkey property conversion. Testcase: kms_legacy_colorkey Cc: Tommi Rantala Cc: stable@vger.kernel.org Reference: http://mid.gmane.org/CA+ydwtr+bCo7LJ44JFmUkVRx144UDFgOS+aJTfK6KHtvBDVuAw@mail.gmail.com Reported-and-tested-by: Tommi Rantala Signed-off-by: Ville Syrjälä Reviewed-by: Daniel Vetter Signed-off-by: Jani Nikula diff --git a/drivers/gpu/drm/i915/intel_sprite.c b/drivers/gpu/drm/i915/intel_sprite.c index 0a52c44..9c5451c 100644 --- a/drivers/gpu/drm/i915/intel_sprite.c +++ b/drivers/gpu/drm/i915/intel_sprite.c @@ -1322,7 +1322,7 @@ int intel_sprite_set_colorkey(struct drm_device *dev, void *data, drm_modeset_lock_all(dev); plane = drm_plane_find(dev, set->plane_id); - if (!plane) { + if (!plane || plane->type != DRM_PLANE_TYPE_OVERLAY) { ret = -ENOENT; goto out_unlock; } @@ -1349,7 +1349,7 @@ int intel_sprite_get_colorkey(struct drm_device *dev, void *data, drm_modeset_lock_all(dev); plane = drm_plane_find(dev, get->plane_id); - if (!plane) { + if (!plane || plane->type != DRM_PLANE_TYPE_OVERLAY) { ret = -ENOENT; goto out_unlock; } -- cgit v0.10.2 From c14e2591bf54c45c9f80cf728fb90976c4e10384 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 30 Mar 2015 21:40:37 +0200 Subject: ASoC: atmel-pcm-dma: increase buffer_bytes_max atmel-pcm-dma is not limited to a buffer size of 64kB like atmel-pcm-pdc. Increase buffer_bytes_max to 512kB to allow for higher bit rates (i.e. 32bps at 192kHz) to work correctly. By default, keep the prealloc at 64kB. Signed-off-by: Alexandre Belloni Signed-off-by: Mark Brown diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c index b8e7bad..b6625c8 100644 --- a/sound/soc/atmel/atmel-pcm-dma.c +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -54,7 +54,7 @@ static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { .period_bytes_max = 2 * 0xffff, /* if 2 bytes format */ .periods_min = 8, .periods_max = 1024, /* no limit */ - .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, + .buffer_bytes_max = 512 * 1024, }; /** @@ -119,7 +119,7 @@ static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream, static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = { .prepare_slave_config = atmel_pcm_configure_dma, .pcm_hardware = &atmel_pcm_dma_hardware, - .prealloc_buffer_size = ATMEL_SSC_DMABUF_SIZE, + .prealloc_buffer_size = 64 * 1024, }; int atmel_pcm_dma_platform_register(struct device *dev) -- cgit v0.10.2 From 7ea24169097d3d3a3eab2dcc5773bc43fd5593e7 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 1 Apr 2015 14:26:34 -0700 Subject: x86/asm/entry/64: Disable opportunistic SYSRET if regs->flags has TF set When I wrote the opportunistic SYSRET code, I missed an important difference between SYSRET and IRET. Both instructions are capable of setting EFLAGS.TF, but they behave differently when doing so: - IRET will not issue a #DB trap after execution when it sets TF. This is critical -- otherwise you'd never be able to make forward progress when returning to userspace. - SYSRET, on the other hand, will trap with #DB immediately after returning to CPL3, and the next instruction will never execute. This breaks anything that opportunistically SYSRETs to a user context with TF set. For example, running this code with TF set and a SIGTRAP handler loaded never gets past 'post_nop': extern unsigned char post_nop[]; asm volatile ("pushfq\n\t" "popq %%r11\n\t" "nop\n\t" "post_nop:" : : "c" (post_nop) : "r11"); In my defense, I can't find this documented in the AMD or Intel manual. Fix it by using IRET to restore TF. Signed-off-by: Andy Lutomirski Cc: Borislav Petkov Cc: Borislav Petkov Cc: Brian Gerst Cc: Denys Vlasenko Cc: H. Peter Anvin Cc: Linus Torvalds Cc: Thomas Gleixner Fixes: 2a23c6b8a9c4 ("x86_64, entry: Use sysret to return to userspace when possible") Link: http://lkml.kernel.org/r/9472f1ca4c19a38ecda45bba9c91b7168135fcfa.1427923514.git.luto@kernel.org Signed-off-by: Ingo Molnar diff --git a/arch/x86/kernel/entry_64.S b/arch/x86/kernel/entry_64.S index 2babb39..f0095a7 100644 --- a/arch/x86/kernel/entry_64.S +++ b/arch/x86/kernel/entry_64.S @@ -799,7 +799,21 @@ retint_swapgs: /* return to user-space */ cmpq %r11,(EFLAGS-ARGOFFSET)(%rsp) /* R11 == RFLAGS */ jne opportunistic_sysret_failed - testq $X86_EFLAGS_RF,%r11 /* sysret can't restore RF */ + /* + * SYSRET can't restore RF. SYSRET can restore TF, but unlike IRET, + * restoring TF results in a trap from userspace immediately after + * SYSRET. This would cause an infinite loop whenever #DB happens + * with register state that satisfies the opportunistic SYSRET + * conditions. For example, single-stepping this user code: + * + * movq $stuck_here,%rcx + * pushfq + * popq %r11 + * stuck_here: + * + * would never get past 'stuck_here'. + */ + testq $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11 jnz opportunistic_sysret_failed /* nothing to check for RSP */ -- cgit v0.10.2 From f59df35fc28167886a0caf9f15db2f4a1f5932da Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Thu, 2 Apr 2015 11:01:59 +0200 Subject: kgdb/x86: Fix reporting of 'si' in kgdb on x86_64 This patch fixes an error in kgdb for x86_64 which would report the value of dx when asked to give the value of si. Signed-off-by: Steffen Liebergeld Cc: Jason Wessel Cc: linux-kernel@vger.kernel.org Signed-off-by: Ingo Molnar diff --git a/arch/x86/kernel/kgdb.c b/arch/x86/kernel/kgdb.c index 7ec1d5f..25ecd56 100644 --- a/arch/x86/kernel/kgdb.c +++ b/arch/x86/kernel/kgdb.c @@ -72,7 +72,7 @@ struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { "bx", 8, offsetof(struct pt_regs, bx) }, { "cx", 8, offsetof(struct pt_regs, cx) }, { "dx", 8, offsetof(struct pt_regs, dx) }, - { "si", 8, offsetof(struct pt_regs, dx) }, + { "si", 8, offsetof(struct pt_regs, si) }, { "di", 8, offsetof(struct pt_regs, di) }, { "bp", 8, offsetof(struct pt_regs, bp) }, { "sp", 8, offsetof(struct pt_regs, sp) }, -- cgit v0.10.2 From adacb228d72b9529f8de0769cc33414c3012054d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 17 Mar 2015 13:24:58 +0000 Subject: drm: Exynos: Respect framebuffer pitch for FIMD/Mixer When performing a modeset, use the framebuffer pitch value to set FIMD IMG_SIZE and Mixer SPAN registers. These are both defined as pitch - the distance between contiguous lines (bytes for FIMD, pixels for mixer). Fixes display on Snow (1366x768). Signed-off-by: Daniel Stone Tested-by: Javier Martinez Canillas Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index c300e22..33a10ce 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -147,6 +147,7 @@ struct fimd_win_data { unsigned int ovl_height; unsigned int fb_width; unsigned int fb_height; + unsigned int fb_pitch; unsigned int bpp; unsigned int pixel_format; dma_addr_t dma_addr; @@ -532,13 +533,14 @@ static void fimd_win_mode_set(struct exynos_drm_crtc *crtc, win_data->offset_y = plane->crtc_y; win_data->ovl_width = plane->crtc_width; win_data->ovl_height = plane->crtc_height; + win_data->fb_pitch = plane->pitch; win_data->fb_width = plane->fb_width; win_data->fb_height = plane->fb_height; win_data->dma_addr = plane->dma_addr[0] + offset; win_data->bpp = plane->bpp; win_data->pixel_format = plane->pixel_format; - win_data->buf_offsize = (plane->fb_width - plane->crtc_width) * - (plane->bpp >> 3); + win_data->buf_offsize = + plane->pitch - (plane->crtc_width * (plane->bpp >> 3)); win_data->line_size = plane->crtc_width * (plane->bpp >> 3); DRM_DEBUG_KMS("offset_x = %d, offset_y = %d\n", @@ -704,7 +706,7 @@ static void fimd_win_commit(struct exynos_drm_crtc *crtc, int zpos) writel(val, ctx->regs + VIDWx_BUF_START(win, 0)); /* buffer end address */ - size = win_data->fb_width * win_data->ovl_height * (win_data->bpp >> 3); + size = win_data->fb_pitch * win_data->ovl_height * (win_data->bpp >> 3); val = (unsigned long)(win_data->dma_addr + size); writel(val, ctx->regs + VIDWx_BUF_END(win, 0)); diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index 3518bc4..2e3bc57 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -55,6 +55,7 @@ struct hdmi_win_data { unsigned int fb_x; unsigned int fb_y; unsigned int fb_width; + unsigned int fb_pitch; unsigned int fb_height; unsigned int src_width; unsigned int src_height; @@ -438,7 +439,7 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) } else { luma_addr[0] = win_data->dma_addr; chroma_addr[0] = win_data->dma_addr - + (win_data->fb_width * win_data->fb_height); + + (win_data->fb_pitch * win_data->fb_height); } if (win_data->scan_flags & DRM_MODE_FLAG_INTERLACE) { @@ -447,8 +448,8 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) luma_addr[1] = luma_addr[0] + 0x40; chroma_addr[1] = chroma_addr[0] + 0x40; } else { - luma_addr[1] = luma_addr[0] + win_data->fb_width; - chroma_addr[1] = chroma_addr[0] + win_data->fb_width; + luma_addr[1] = luma_addr[0] + win_data->fb_pitch; + chroma_addr[1] = chroma_addr[0] + win_data->fb_pitch; } } else { ctx->interlace = false; @@ -469,10 +470,10 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) vp_reg_writemask(res, VP_MODE, val, VP_MODE_FMT_MASK); /* setting size of input image */ - vp_reg_write(res, VP_IMG_SIZE_Y, VP_IMG_HSIZE(win_data->fb_width) | + vp_reg_write(res, VP_IMG_SIZE_Y, VP_IMG_HSIZE(win_data->fb_pitch) | VP_IMG_VSIZE(win_data->fb_height)); /* chroma height has to reduced by 2 to avoid chroma distorions */ - vp_reg_write(res, VP_IMG_SIZE_C, VP_IMG_HSIZE(win_data->fb_width) | + vp_reg_write(res, VP_IMG_SIZE_C, VP_IMG_HSIZE(win_data->fb_pitch) | VP_IMG_VSIZE(win_data->fb_height / 2)); vp_reg_write(res, VP_SRC_WIDTH, win_data->src_width); @@ -559,7 +560,7 @@ static void mixer_graph_buffer(struct mixer_context *ctx, int win) /* converting dma address base and source offset */ dma_addr = win_data->dma_addr + (win_data->fb_x * win_data->bpp >> 3) - + (win_data->fb_y * win_data->fb_width * win_data->bpp >> 3); + + (win_data->fb_y * win_data->fb_pitch); src_x_offset = 0; src_y_offset = 0; @@ -576,7 +577,8 @@ static void mixer_graph_buffer(struct mixer_context *ctx, int win) MXR_GRP_CFG_FORMAT_VAL(fmt), MXR_GRP_CFG_FORMAT_MASK); /* setup geometry */ - mixer_reg_write(res, MXR_GRAPHIC_SPAN(win), win_data->fb_width); + mixer_reg_write(res, MXR_GRAPHIC_SPAN(win), + win_data->fb_pitch / (win_data->bpp >> 3)); /* setup display size */ if (ctx->mxr_ver == MXR_VER_128_0_0_184 && @@ -961,6 +963,7 @@ static void mixer_win_mode_set(struct exynos_drm_crtc *crtc, win_data->fb_y = plane->fb_y; win_data->fb_width = plane->fb_width; win_data->fb_height = plane->fb_height; + win_data->fb_pitch = plane->pitch; win_data->src_width = plane->src_width; win_data->src_height = plane->src_height; -- cgit v0.10.2 From 863653fed0f449fb738295255cc834b271cfa088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Tue, 31 Mar 2015 17:36:57 +0200 Subject: drm/radeon: add extra check in radeon_ttm_tt_unpin_userptr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We somehow try to free the SG table twice. Bugs: https://bugs.freedesktop.org/show_bug.cgi?id=89734 Signed-off-by: Christian König Cc: Signed-off-by: Alex Deucher diff --git a/drivers/gpu/drm/radeon/radeon_ttm.c b/drivers/gpu/drm/radeon/radeon_ttm.c index d02aa1d..b292aca 100644 --- a/drivers/gpu/drm/radeon/radeon_ttm.c +++ b/drivers/gpu/drm/radeon/radeon_ttm.c @@ -598,6 +598,10 @@ static void radeon_ttm_tt_unpin_userptr(struct ttm_tt *ttm) enum dma_data_direction direction = write ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE; + /* double check that we don't free the table twice */ + if (!ttm->sg->sgl) + return; + /* free the sg table and pages again */ dma_unmap_sg(rdev->dev, ttm->sg->sgl, ttm->sg->nents, direction); -- cgit v0.10.2 From 22e2e86560c5fca6f9b9d078f221fcdab9947a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Tue, 31 Mar 2015 17:36:58 +0200 Subject: drm/radeon: fix wait in radeon_mn_invalidate_range_start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to wait for all fences, not just the exclusive one. Signed-off-by: Christian König Cc: Signed-off-by: Alex Deucher diff --git a/drivers/gpu/drm/radeon/radeon_mn.c b/drivers/gpu/drm/radeon/radeon_mn.c index a69bd44..572b4db 100644 --- a/drivers/gpu/drm/radeon/radeon_mn.c +++ b/drivers/gpu/drm/radeon/radeon_mn.c @@ -122,7 +122,6 @@ static void radeon_mn_invalidate_range_start(struct mmu_notifier *mn, it = interval_tree_iter_first(&rmn->objects, start, end); while (it) { struct radeon_bo *bo; - struct fence *fence; int r; bo = container_of(it, struct radeon_bo, mn_it); @@ -134,12 +133,10 @@ static void radeon_mn_invalidate_range_start(struct mmu_notifier *mn, continue; } - fence = reservation_object_get_excl(bo->tbo.resv); - if (fence) { - r = radeon_fence_wait((struct radeon_fence *)fence, false); - if (r) - DRM_ERROR("(%d) failed to wait for user bo\n", r); - } + r = reservation_object_wait_timeout_rcu(bo->tbo.resv, true, + false, MAX_SCHEDULE_TIMEOUT); + if (r) + DRM_ERROR("(%d) failed to wait for user bo\n", r); radeon_ttm_placement_from_domain(bo, RADEON_GEM_DOMAIN_CPU); r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false); -- cgit v0.10.2 From 687805e4a60fe83a11556c041840161f8016a367 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Fri, 27 Mar 2015 10:38:25 -0400 Subject: perf/x86/intel: Filter branches for PEBS event For supporting Intel LBR branches filtering, Intel LBR sharing logic mechanism is introduced from commit b36817e88630 ("perf/x86: Add Intel LBR sharing logic"). It modifies __intel_shared_reg_get_constraints() to config lbr_sel, which is finally used to set LBR_SELECT. However, the intel_shared_regs_constraints() function is called after intel_pebs_constraints(). The PEBS event will return immediately after intel_pebs_constraints(). So it's impossible to filter branches for PEBS events. This patch moves intel_shared_regs_constraints() ahead of intel_pebs_constraints(). We can safely do that because the intel_shared_regs_constraints() function only returns empty constraint if its rejecting the event, otherwise it returns NULL such that we continue calling intel_pebs_constraints() and x86_get_event_constraint(). Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Cc: eranian@google.com Link: http://lkml.kernel.org/r/1427467105-9260-1-git-send-email-kan.liang@intel.com Signed-off-by: Ingo Molnar diff --git a/arch/x86/kernel/cpu/perf_event_intel.c b/arch/x86/kernel/cpu/perf_event_intel.c index 498b6d9..40898ab 100644 --- a/arch/x86/kernel/cpu/perf_event_intel.c +++ b/arch/x86/kernel/cpu/perf_event_intel.c @@ -1649,11 +1649,11 @@ intel_get_event_constraints(struct cpu_hw_events *cpuc, struct perf_event *event if (c) return c; - c = intel_pebs_constraints(event); + c = intel_shared_regs_constraints(cpuc, event); if (c) return c; - c = intel_shared_regs_constraints(cpuc, event); + c = intel_pebs_constraints(event); if (c) return c; -- cgit v0.10.2 From c420f19b9cdc59662dbb56677417487efc1729ec Mon Sep 17 00:00:00 2001 From: Andi Kleen Date: Mon, 9 Mar 2015 11:20:22 -0700 Subject: perf/x86/intel: Fix Haswell CYCLE_ACTIVITY.* counter constraints Some of the CYCLE_ACTIVITY.* events can only be scheduled on counter 2. Due to a typo Haswell matched those with INTEL_EVENT_CONSTRAINT, which lead to the events never matching as the comparison does not expect anything in the umask too. Fix the typo. Signed-off-by: Andi Kleen Signed-off-by: Peter Zijlstra (Intel) Link: http://lkml.kernel.org/r/1425925222-32361-1-git-send-email-andi@firstfloor.org Signed-off-by: Ingo Molnar diff --git a/arch/x86/kernel/cpu/perf_event_intel.c b/arch/x86/kernel/cpu/perf_event_intel.c index 40898ab..2589906 100644 --- a/arch/x86/kernel/cpu/perf_event_intel.c +++ b/arch/x86/kernel/cpu/perf_event_intel.c @@ -212,11 +212,11 @@ static struct event_constraint intel_hsw_event_constraints[] = { INTEL_UEVENT_CONSTRAINT(0x01c0, 0x2), /* INST_RETIRED.PREC_DIST */ INTEL_EVENT_CONSTRAINT(0xcd, 0x8), /* MEM_TRANS_RETIRED.LOAD_LATENCY */ /* CYCLE_ACTIVITY.CYCLES_L1D_PENDING */ - INTEL_EVENT_CONSTRAINT(0x08a3, 0x4), + INTEL_UEVENT_CONSTRAINT(0x08a3, 0x4), /* CYCLE_ACTIVITY.STALLS_L1D_PENDING */ - INTEL_EVENT_CONSTRAINT(0x0ca3, 0x4), + INTEL_UEVENT_CONSTRAINT(0x0ca3, 0x4), /* CYCLE_ACTIVITY.CYCLES_NO_EXECUTE */ - INTEL_EVENT_CONSTRAINT(0x04a3, 0xf), + INTEL_UEVENT_CONSTRAINT(0x04a3, 0xf), EVENT_CONSTRAINT_END }; -- cgit v0.10.2 From 8494057ab5e40df590ef6ef7d66324d3ae33356b Mon Sep 17 00:00:00 2001 From: Shachar Raindel Date: Wed, 18 Mar 2015 17:39:08 +0000 Subject: IB/uverbs: Prevent integer overflow in ib_umem_get address arithmetic Properly verify that the resulting page aligned end address is larger than both the start address and the length of the memory area requested. Both the start and length arguments for ib_umem_get are controlled by the user. A misbehaving user can provide values which will cause an integer overflow when calculating the page aligned end address. This overflow can cause also miscalculation of the number of pages mapped, and additional logic issues. Addresses: CVE-2014-8159 Cc: Signed-off-by: Shachar Raindel Signed-off-by: Jack Morgenstein Signed-off-by: Or Gerlitz Signed-off-by: Roland Dreier diff --git a/drivers/infiniband/core/umem.c b/drivers/infiniband/core/umem.c index aec7a6a..8c014b5 100644 --- a/drivers/infiniband/core/umem.c +++ b/drivers/infiniband/core/umem.c @@ -99,6 +99,14 @@ struct ib_umem *ib_umem_get(struct ib_ucontext *context, unsigned long addr, if (dmasync) dma_set_attr(DMA_ATTR_WRITE_BARRIER, &attrs); + /* + * If the combination of the addr and size requested for this memory + * region causes an integer overflow, return error. + */ + if ((PAGE_ALIGN(addr + size) <= size) || + (PAGE_ALIGN(addr + size) <= addr)) + return ERR_PTR(-EINVAL); + if (!can_do_mlock()) return ERR_PTR(-EPERM); -- cgit v0.10.2 From 74ff960222d90999508b4ba0d3449f796695b6d5 Mon Sep 17 00:00:00 2001 From: Pascal Huerst Date: Thu, 2 Apr 2015 10:17:40 +0200 Subject: ASoC: cs4271: Increase delay time after reset The delay time after a reset in the codec probe callback was too short, and did not work on certain hw because the codec needs more time to power on. This increases the delay time from 1us to 1ms. Signed-off-by: Pascal Huerst Acked-by: Brian Austin Signed-off-by: Mark Brown Cc: stable@vger.kernel.org diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index 79a4efc..55ca6ec 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -561,10 +561,10 @@ static int cs4271_codec_probe(struct snd_soc_codec *codec) if (gpio_is_valid(cs4271->gpio_nreset)) { /* Reset codec */ gpio_direction_output(cs4271->gpio_nreset, 0); - udelay(1); + mdelay(1); gpio_set_value(cs4271->gpio_nreset, 1); /* Give the codec time to wake up */ - udelay(1); + mdelay(1); } ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, -- cgit v0.10.2 From 0c36820e2ab7d943ab1188230fdf2149826d33c0 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 31 Mar 2015 11:05:15 +0100 Subject: xen-netfront: transmit fully GSO-sized packets xen-netfront limits transmitted skbs to be at most 44 segments in size. However, GSO permits up to 65536 bytes, which means a maximum of 45 segments of 1448 bytes each. This slight reduction in the size of packets means a slight loss in efficiency. Since c/s 9ecd1a75d, xen-netfront sets gso_max_size to XEN_NETIF_MAX_TX_SIZE - MAX_TCP_HEADER, where XEN_NETIF_MAX_TX_SIZE is 65535 bytes. The calculation used by tcp_tso_autosize (and also tcp_xmit_size_goal since c/s 6c09fa09d) in determining when to split an skb into two is sk->sk_gso_max_size - 1 - MAX_TCP_HEADER. So the maximum permitted size of an skb is calculated to be (XEN_NETIF_MAX_TX_SIZE - MAX_TCP_HEADER) - 1 - MAX_TCP_HEADER. Intuitively, this looks like the wrong formula -- we don't need two TCP headers. Instead, there is no need to deviate from the default gso_max_size of 65536 as this already accommodates the size of the header. Currently, the largest skb transmitted by netfront is 63712 bytes (44 segments of 1448 bytes each), as observed via tcpdump. This patch makes netfront send skbs of up to 65160 bytes (45 segments of 1448 bytes each). Similarly, the maximum allowable mtu does not need to subtract MAX_TCP_HEADER as it relates to the size of the whole packet, including the header. Fixes: 9ecd1a75d977 ("xen-netfront: reduce gso_max_size to account for max TCP header") Signed-off-by: Jonathan Davies Signed-off-by: David S. Miller diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index e9b960f..720aaf6 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1008,8 +1008,7 @@ err: static int xennet_change_mtu(struct net_device *dev, int mtu) { - int max = xennet_can_sg(dev) ? - XEN_NETIF_MAX_TX_SIZE - MAX_TCP_HEADER : ETH_DATA_LEN; + int max = xennet_can_sg(dev) ? XEN_NETIF_MAX_TX_SIZE : ETH_DATA_LEN; if (mtu > max) return -EINVAL; @@ -1279,8 +1278,6 @@ static struct net_device *xennet_create_dev(struct xenbus_device *dev) netdev->ethtool_ops = &xennet_ethtool_ops; SET_NETDEV_DEV(netdev, &dev->dev); - netif_set_gso_max_size(netdev, XEN_NETIF_MAX_TX_SIZE - MAX_TCP_HEADER); - np->netdev = netdev; netif_carrier_off(netdev); -- cgit v0.10.2 From 666b805150efd62f05810ff0db08f44a2370c937 Mon Sep 17 00:00:00 2001 From: Neal Cardwell Date: Wed, 1 Apr 2015 20:26:46 -0400 Subject: tcp: fix FRTO undo on cumulative ACK of SACKed range On processing cumulative ACKs, the FRTO code was not checking the SACKed bit, meaning that there could be a spurious FRTO undo on a cumulative ACK of a previously SACKed skb. The FRTO code should only consider a cumulative ACK to indicate that an original/unretransmitted skb is newly ACKed if the skb was not yet SACKed. The effect of the spurious FRTO undo would typically be to make the connection think that all previously-sent packets were in flight when they really weren't, leading to a stall and an RTO. Signed-off-by: Neal Cardwell Signed-off-by: Yuchung Cheng Fixes: e33099f96d99c ("tcp: implement RFC5682 F-RTO") Signed-off-by: David S. Miller diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index fb4cf8b..f501ac04 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -3105,10 +3105,11 @@ static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets, if (!first_ackt.v64) first_ackt = last_ackt; - if (!(sacked & TCPCB_SACKED_ACKED)) + if (!(sacked & TCPCB_SACKED_ACKED)) { reord = min(pkts_acked, reord); - if (!after(scb->end_seq, tp->high_seq)) - flag |= FLAG_ORIG_SACK_ACKED; + if (!after(scb->end_seq, tp->high_seq)) + flag |= FLAG_ORIG_SACK_ACKED; + } } if (sacked & TCPCB_SACKED_ACKED) -- cgit v0.10.2 From ed785309c94445dd90e242370e1f7bb034e008fd Mon Sep 17 00:00:00 2001 From: WANG Cong Date: Tue, 31 Mar 2015 11:01:45 -0700 Subject: ipv4: take rtnl_lock and mark mrt table as freed on namespace cleanup This is the IPv4 part for commit 905a6f96a1b1 (ipv6: take rtnl_lock and mark mrt6 table as freed on namespace cleanup). Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: Cong Wang Signed-off-by: David S. Miller diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 9282544..bc40115 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -278,10 +278,12 @@ static void __net_exit ipmr_rules_exit(struct net *net) { struct mr_table *mrt, *next; + rtnl_lock(); list_for_each_entry_safe(mrt, next, &net->ipv4.mr_tables, list) { list_del(&mrt->list); ipmr_free_table(mrt); } + rtnl_unlock(); fib_rules_unregister(net->ipv4.mr_rules_ops); } #else @@ -308,7 +310,10 @@ static int __net_init ipmr_rules_init(struct net *net) static void __net_exit ipmr_rules_exit(struct net *net) { + rtnl_lock(); ipmr_free_table(net->ipv4.mrt); + net->ipv4.mrt = NULL; + rtnl_unlock(); } #endif -- cgit v0.10.2 From 419df12fb5fa558451319276838c1842f2b11f8f Mon Sep 17 00:00:00 2001 From: WANG Cong Date: Tue, 31 Mar 2015 11:01:46 -0700 Subject: net: move fib_rules_unregister() under rtnl lock We have to hold rtnl lock for fib_rules_unregister() otherwise the following race could happen: fib_rules_unregister(): fib_nl_delrule(): ... ... ... ops = lookup_rules_ops(); list_del_rcu(&ops->list); list_for_each_entry(ops->rules) { fib_rules_cleanup_ops(ops); ... list_del_rcu(); list_del_rcu(); } Note, net->rules_mod_lock is actually not needed at all, either upper layer netns code or rtnl lock guarantees we are safe. Cc: Alexander Duyck Cc: Thomas Graf Signed-off-by: Cong Wang Signed-off-by: David S. Miller diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 44706e8..e4fdc9d 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -175,9 +175,9 @@ void fib_rules_unregister(struct fib_rules_ops *ops) spin_lock(&net->rules_mod_lock); list_del_rcu(&ops->list); - fib_rules_cleanup_ops(ops); spin_unlock(&net->rules_mod_lock); + fib_rules_cleanup_ops(ops); call_rcu(&ops->rcu, fib_rules_put_rcu); } EXPORT_SYMBOL_GPL(fib_rules_unregister); diff --git a/net/decnet/dn_rules.c b/net/decnet/dn_rules.c index faf7cc3..9d66a0f 100644 --- a/net/decnet/dn_rules.c +++ b/net/decnet/dn_rules.c @@ -248,7 +248,9 @@ void __init dn_fib_rules_init(void) void __exit dn_fib_rules_cleanup(void) { + rtnl_lock(); fib_rules_unregister(dn_fib_rules_ops); + rtnl_unlock(); rcu_barrier(); } diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 57be71d..23b9b3e 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -1111,11 +1111,10 @@ static void ip_fib_net_exit(struct net *net) { unsigned int i; + rtnl_lock(); #ifdef CONFIG_IP_MULTIPLE_TABLES fib4_rules_exit(net); #endif - - rtnl_lock(); for (i = 0; i < FIB_TABLE_HASHSZ; i++) { struct fib_table *tb; struct hlist_head *head; diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index bc40115..fe54eba 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -283,8 +283,8 @@ static void __net_exit ipmr_rules_exit(struct net *net) list_del(&mrt->list); ipmr_free_table(mrt); } - rtnl_unlock(); fib_rules_unregister(net->ipv4.mr_rules_ops); + rtnl_unlock(); } #else #define ipmr_for_each_table(mrt, net) \ diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index 27ca796..70bc6ab 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -322,7 +322,9 @@ out_fib6_rules_ops: static void __net_exit fib6_rules_net_exit(struct net *net) { + rtnl_lock(); fib_rules_unregister(net->ipv6.fib6_rules_ops); + rtnl_unlock(); } static struct pernet_operations fib6_rules_net_ops = { diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 52028f4..2f1fd9f 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -267,8 +267,8 @@ static void __net_exit ip6mr_rules_exit(struct net *net) list_del(&mrt->list); ip6mr_free_table(mrt); } - rtnl_unlock(); fib_rules_unregister(net->ipv6.mr6_rules_ops); + rtnl_unlock(); } #else #define ip6mr_for_each_table(mrt, net) \ -- cgit v0.10.2 From 7ba0c47c34a1ea5bc7a24ca67309996cce0569b5 Mon Sep 17 00:00:00 2001 From: WANG Cong Date: Tue, 31 Mar 2015 11:01:47 -0700 Subject: ip6mr: call del_timer_sync() in ip6mr_free_table() We need to wait for the flying timers, since we are going to free the mrtable right after it. Cc: Hannes Frederic Sowa Signed-off-by: Cong Wang Signed-off-by: David S. Miller diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 2f1fd9f..312e0ff 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -336,7 +336,7 @@ static struct mr6_table *ip6mr_new_table(struct net *net, u32 id) static void ip6mr_free_table(struct mr6_table *mrt) { - del_timer(&mrt->ipmr_expire_timer); + del_timer_sync(&mrt->ipmr_expire_timer); mroute_clean_tables(mrt); kfree(mrt); } -- cgit v0.10.2 From 6d458f5b4ece8542a5c2281e40008823fec91814 Mon Sep 17 00:00:00 2001 From: Nicolas Dichtel Date: Fri, 3 Apr 2015 12:02:36 +0200 Subject: Revert "netns: don't clear nsid too early on removal" This reverts commit 4217291e592d ("netns: don't clear nsid too early on removal"). This is not the right fix, it introduces races. Signed-off-by: Nicolas Dichtel Signed-off-by: David S. Miller diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 5221f97..cb5290b 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -349,7 +349,7 @@ static LIST_HEAD(cleanup_list); /* Must hold cleanup_list_lock to touch */ static void cleanup_net(struct work_struct *work) { const struct pernet_operations *ops; - struct net *net, *tmp, *peer; + struct net *net, *tmp; struct list_head net_kill_list; LIST_HEAD(net_exit_list); @@ -365,6 +365,14 @@ static void cleanup_net(struct work_struct *work) list_for_each_entry(net, &net_kill_list, cleanup_list) { list_del_rcu(&net->list); list_add_tail(&net->exit_list, &net_exit_list); + for_each_net(tmp) { + int id = __peernet2id(tmp, net, false); + + if (id >= 0) + idr_remove(&tmp->netns_ids, id); + } + idr_destroy(&net->netns_ids); + } rtnl_unlock(); @@ -390,26 +398,12 @@ static void cleanup_net(struct work_struct *work) */ rcu_barrier(); - rtnl_lock(); /* Finally it is safe to free my network namespace structure */ list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) { - /* Unreference net from all peers (no need to loop over - * net_exit_list because idr_destroy() will be called for each - * element of this list. - */ - for_each_net(peer) { - int id = __peernet2id(peer, net, false); - - if (id >= 0) - idr_remove(&peer->netns_ids, id); - } - idr_destroy(&net->netns_ids); - list_del_init(&net->exit_list); put_user_ns(net->user_ns); net_drop_ns(net); } - rtnl_unlock(); } static DECLARE_WORK(net_cleanup_work, cleanup_net); -- cgit v0.10.2 From 576b7cd2f6ff1e90b3fc0a000d2fe74f8a50a4bb Mon Sep 17 00:00:00 2001 From: Nicolas Dichtel Date: Fri, 3 Apr 2015 12:02:37 +0200 Subject: netns: don't allocate an id for dead netns First, let's explain the problem. Suppose you have an ipip interface that stands in the netns foo and its link part in the netns bar (so the netns bar has an nsid into the netns foo). Now, you remove the netns bar: - the bar nsid into the netns foo is removed - the netns exit method of ipip is called, thus our ipip iface is removed: => a netlink message is built in the netns foo to advertise this deletion => this netlink message requests an nsid for bar, thus a new nsid is allocated for bar and never removed. This patch adds a check in peernet2id() so that an id cannot be allocated for a netns which is currently destroyed. Signed-off-by: Nicolas Dichtel Signed-off-by: David S. Miller diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index cb5290b..70d3450 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -198,8 +198,10 @@ static int __peernet2id(struct net *net, struct net *peer, bool alloc) */ int peernet2id(struct net *net, struct net *peer) { - int id = __peernet2id(net, peer, true); + bool alloc = atomic_read(&peer->count) == 0 ? false : true; + int id; + id = __peernet2id(net, peer, alloc); return id >= 0 ? id : NETNSA_NSID_NOT_ASSIGNED; } EXPORT_SYMBOL(peernet2id); -- cgit v0.10.2 From 59c30afbd37c26168597e737297a1de68848c332 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 3 Apr 2015 17:14:40 -0700 Subject: Input: alps - report interleaved bare PS/2 packets via dev3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bare packets should be reported via the same evdev device independent on whether they are detected on the beginning of a packet or in the middle of a packet. This has been tested on a Dell Latitude E6400, where the DualPoint Stick reports bare packets, which get reported via dev3 when the touchpad is idle, and via dev2 when the touchpad and stick are used simultaneously. This commit fixes this inconsistency by always reporting bare packets via dev3. Note that since the come from a DualPoint Stick they really should be reported via dev2, this gets fixed in a later commit. Signed-off-by: Hans de Goede Reviewed-by: Pali Rohár Signed-off-by: Dmitry Torokhov diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index 33198b9..e32625c 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -1154,10 +1154,23 @@ out: mutex_unlock(&alps_mutex); } -static void alps_report_bare_ps2_packet(struct input_dev *dev, +static void alps_report_bare_ps2_packet(struct psmouse *psmouse, unsigned char packet[], bool report_buttons) { + struct alps_data *priv = psmouse->private; + struct input_dev *dev; + + if (unlikely(IS_ERR_OR_NULL(priv->dev3))) { + /* Register dev3 mouse if we received PS/2 packet first time */ + if (!IS_ERR(priv->dev3)) + psmouse_queue_work(psmouse, &priv->dev3_register_work, + 0); + return; + } else { + dev = priv->dev3; + } + if (report_buttons) alps_report_buttons(dev, NULL, packet[0] & 1, packet[0] & 2, packet[0] & 4); @@ -1232,8 +1245,8 @@ static psmouse_ret_t alps_handle_interleaved_ps2(struct psmouse *psmouse) * de-synchronization. */ - alps_report_bare_ps2_packet(priv->dev2, - &psmouse->packet[3], false); + alps_report_bare_ps2_packet(psmouse, &psmouse->packet[3], + false); /* * Continue with the standard ALPS protocol handling, @@ -1289,18 +1302,9 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse) * properly we only do this if the device is fully synchronized. */ if (!psmouse->out_of_sync_cnt && (psmouse->packet[0] & 0xc8) == 0x08) { - - /* Register dev3 mouse if we received PS/2 packet first time */ - if (unlikely(!priv->dev3)) - psmouse_queue_work(psmouse, - &priv->dev3_register_work, 0); - if (psmouse->pktcnt == 3) { - /* Once dev3 mouse device is registered report data */ - if (likely(!IS_ERR_OR_NULL(priv->dev3))) - alps_report_bare_ps2_packet(priv->dev3, - psmouse->packet, - true); + alps_report_bare_ps2_packet(psmouse, psmouse->packet, + true); return PSMOUSE_FULL_PACKET; } return PSMOUSE_GOOD_DATA; -- cgit v0.10.2 From e3a79212eae6eb64ed68c78409778f8d1a84c2a1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 3 Apr 2015 17:20:05 -0700 Subject: Input: alps - report V2 Dualpoint Stick events via the right evdev node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On V2 devices the DualPoint Stick reports bare packets, these should be reported via the "AlpsPS/2 ALPS DualPoint Stick" dev2 evdev node, which also has the INPUT_PROP_POINTING_STICK propbit set. Note that since there is no way to distinguish these packets from an external PS/2 mouse (insofar as these laptops have an external PS/2 port) this means that we will be reporting PS/2 mouse events via this evdev node too, as we've been doing in kernel 3.19 and older. This has been tested on a Dell Latitude D620 and a Dell Latitude E6400, which both have a V2 touchpad + a DualPoint Stick which reports bare packets. Signed-off-by: Hans de Goede Reviewed-by: Pali Rohár Signed-off-by: Dmitry Torokhov diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index e32625c..27bcdbc 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -1161,7 +1161,12 @@ static void alps_report_bare_ps2_packet(struct psmouse *psmouse, struct alps_data *priv = psmouse->private; struct input_dev *dev; - if (unlikely(IS_ERR_OR_NULL(priv->dev3))) { + /* Figure out which device to use to report the bare packet */ + if (priv->proto_version == ALPS_PROTO_V2 && + (priv->flags & ALPS_DUALPOINT)) { + /* On V2 devices the DualPoint Stick reports bare packets */ + dev = priv->dev2; + } else if (unlikely(IS_ERR_OR_NULL(priv->dev3))) { /* Register dev3 mouse if we received PS/2 packet first time */ if (!IS_ERR(priv->dev3)) psmouse_queue_work(psmouse, &priv->dev3_register_work, -- cgit v0.10.2 From 58d8a3be36cf0fe2eb2609e205bb07a141c29a7c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 3 Apr 2015 17:30:31 -0700 Subject: Input: alps - document stick behavior for protocol V2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document that protocol V2 uses standard (bare) PS/2 mouse packets for the DualPoint stick. Signed-off-by: Hans de Goede Acked-By: Pali Rohár Signed-off-by: Dmitry Torokhov diff --git a/Documentation/input/alps.txt b/Documentation/input/alps.txt index a63e5e0..92ae734 100644 --- a/Documentation/input/alps.txt +++ b/Documentation/input/alps.txt @@ -114,6 +114,9 @@ ALPS Absolute Mode - Protocol Version 2 byte 4: 0 y6 y5 y4 y3 y2 y1 y0 byte 5: 0 z6 z5 z4 z3 z2 z1 z0 +Protocol Version 2 DualPoint devices send standard PS/2 mouse packets for +the DualPoint Stick. + Dualpoint device -- interleaved packet format --------------------------------------------- @@ -127,6 +130,11 @@ Dualpoint device -- interleaved packet format byte 7: 0 y6 y5 y4 y3 y2 y1 y0 byte 8: 0 z6 z5 z4 z3 z2 z1 z0 +Devices which use the interleaving format normally send standard PS/2 mouse +packets for the DualPoint Stick + ALPS Absolute Mode packets for the +touchpad, switching to the interleaved packet format when both the stick and +the touchpad are used at the same time. + ALPS Absolute Mode - Protocol Version 3 --------------------------------------- -- cgit v0.10.2 From 158bf4ed7f686f92ea6c046c5d9d04afad92eb17 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 3 Apr 2015 18:28:32 +0100 Subject: ASoC: wm5102: Remove set of volume update bits for output 3R The earpiece on wm5102 is mono, thus there is no output 3R. Don't toggle the volume update bits for this output, although worth noting that doing so had no negative effects it is just redundant. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 6d0fe0a..0c6d1bc 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -1861,7 +1861,6 @@ static unsigned int wm5102_digital_vu[] = { ARIZONA_DAC_DIGITAL_VOLUME_2L, ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_DAC_DIGITAL_VOLUME_3L, - ARIZONA_DAC_DIGITAL_VOLUME_3R, ARIZONA_DAC_DIGITAL_VOLUME_4L, ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_DAC_DIGITAL_VOLUME_5L, -- cgit v0.10.2 From 2106241a680397f6f49da796a4ce11eb5cf2698e Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Thu, 2 Apr 2015 15:37:00 +0800 Subject: ASoC: Intel: create common folder and move common files in Restructure the sound/soc/intel/ directory: create common folder, and move sst common files here. Signed-off-by: Jie Yang Acked-by: Jarkko Nikula Tested-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index a8e53c4..28de8cd 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -1,6 +1,5 @@ # Core support -snd-soc-sst-dsp-objs := sst-dsp.o sst-firmware.o -snd-soc-sst-acpi-objs := sst-acpi.o +obj-$(CONFIG_SND_SOC_INTEL_SST) += common/ snd-soc-sst-mfld-platform-objs := sst-mfld-platform-pcm.o \ sst-mfld-platform-compress.o sst-atom-controls.o @@ -9,9 +8,6 @@ snd-soc-mfld-machine-objs := mfld_machine.o obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o -obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o -obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o - # Platform Support snd-soc-sst-haswell-pcm-objs := \ sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile new file mode 100644 index 0000000..3df0e1c --- /dev/null +++ b/sound/soc/intel/common/Makefile @@ -0,0 +1,6 @@ +snd-soc-sst-dsp-objs := sst-dsp.o sst-firmware.o +snd-soc-sst-acpi-objs := sst-acpi.o + +obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o +obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o + diff --git a/sound/soc/intel/common/sst-acpi.c b/sound/soc/intel/common/sst-acpi.c new file mode 100644 index 0000000..b3d8456 --- /dev/null +++ b/sound/soc/intel/common/sst-acpi.c @@ -0,0 +1,285 @@ +/* + * Intel SST loader on ACPI systems + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include + +#include "sst-dsp.h" + +#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000 +#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000 +#define SST_LPT_DSP_DMA_SIZE (1024 - 1) + +/* Descriptor for SST ASoC machine driver */ +struct sst_acpi_mach { + /* ACPI ID for the matching machine driver. Audio codec for instance */ + const u8 id[ACPI_ID_LEN]; + /* machine driver name */ + const char *drv_name; + /* firmware file name */ + const char *fw_filename; +}; + +/* Descriptor for setting up SST platform data */ +struct sst_acpi_desc { + const char *drv_name; + struct sst_acpi_mach *machines; + /* Platform resource indexes. Must set to -1 if not used */ + int resindex_lpe_base; + int resindex_pcicfg_base; + int resindex_fw_base; + int irqindex_host_ipc; + int resindex_dma_base; + /* Unique number identifying the SST core on platform */ + int sst_id; + /* DMA only valid when resindex_dma_base != -1*/ + int dma_engine; + int dma_size; +}; + +struct sst_acpi_priv { + struct platform_device *pdev_mach; + struct platform_device *pdev_pcm; + struct sst_pdata sst_pdata; + struct sst_acpi_desc *desc; + struct sst_acpi_mach *mach; +}; + +static void sst_acpi_fw_cb(const struct firmware *fw, void *context) +{ + struct platform_device *pdev = context; + struct device *dev = &pdev->dev; + struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); + struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; + struct sst_acpi_desc *desc = sst_acpi->desc; + struct sst_acpi_mach *mach = sst_acpi->mach; + + sst_pdata->fw = fw; + if (!fw) { + dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename); + return; + } + + /* register PCM and DAI driver */ + sst_acpi->pdev_pcm = + platform_device_register_data(dev, desc->drv_name, -1, + sst_pdata, sizeof(*sst_pdata)); + if (IS_ERR(sst_acpi->pdev_pcm)) { + dev_err(dev, "Cannot register device %s. Error %d\n", + desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm)); + } + + return; +} + +static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, + void *context, void **ret) +{ + *(bool *)context = true; + return AE_OK; +} + +static struct sst_acpi_mach *sst_acpi_find_machine( + struct sst_acpi_mach *machines) +{ + struct sst_acpi_mach *mach; + bool found = false; + + for (mach = machines; mach->id[0]; mach++) + if (ACPI_SUCCESS(acpi_get_devices(mach->id, + sst_acpi_mach_match, + &found, NULL)) && found) + return mach; + + return NULL; +} + +static int sst_acpi_probe(struct platform_device *pdev) +{ + const struct acpi_device_id *id; + struct device *dev = &pdev->dev; + struct sst_acpi_priv *sst_acpi; + struct sst_pdata *sst_pdata; + struct sst_acpi_mach *mach; + struct sst_acpi_desc *desc; + struct resource *mmio; + int ret = 0; + + sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL); + if (sst_acpi == NULL) + return -ENOMEM; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + + desc = (struct sst_acpi_desc *)id->driver_data; + mach = sst_acpi_find_machine(desc->machines); + if (mach == NULL) { + dev_err(dev, "No matching ASoC machine driver found\n"); + return -ENODEV; + } + + sst_pdata = &sst_acpi->sst_pdata; + sst_pdata->id = desc->sst_id; + sst_pdata->dma_dev = dev; + sst_acpi->desc = desc; + sst_acpi->mach = mach; + + if (desc->resindex_dma_base >= 0) { + sst_pdata->dma_engine = desc->dma_engine; + sst_pdata->dma_base = desc->resindex_dma_base; + sst_pdata->dma_size = desc->dma_size; + } + + if (desc->irqindex_host_ipc >= 0) + sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc); + + if (desc->resindex_lpe_base >= 0) { + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_lpe_base); + if (mmio) { + sst_pdata->lpe_base = mmio->start; + sst_pdata->lpe_size = resource_size(mmio); + } + } + + if (desc->resindex_pcicfg_base >= 0) { + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_pcicfg_base); + if (mmio) { + sst_pdata->pcicfg_base = mmio->start; + sst_pdata->pcicfg_size = resource_size(mmio); + } + } + + if (desc->resindex_fw_base >= 0) { + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_fw_base); + if (mmio) { + sst_pdata->fw_base = mmio->start; + sst_pdata->fw_size = resource_size(mmio); + } + } + + platform_set_drvdata(pdev, sst_acpi); + + /* register machine driver */ + sst_acpi->pdev_mach = + platform_device_register_data(dev, mach->drv_name, -1, + sst_pdata, sizeof(*sst_pdata)); + if (IS_ERR(sst_acpi->pdev_mach)) + return PTR_ERR(sst_acpi->pdev_mach); + + /* continue SST probing after firmware is loaded */ + ret = request_firmware_nowait(THIS_MODULE, true, mach->fw_filename, + dev, GFP_KERNEL, pdev, sst_acpi_fw_cb); + if (ret) + platform_device_unregister(sst_acpi->pdev_mach); + + return ret; +} + +static int sst_acpi_remove(struct platform_device *pdev) +{ + struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); + struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; + + platform_device_unregister(sst_acpi->pdev_mach); + if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm)) + platform_device_unregister(sst_acpi->pdev_pcm); + release_firmware(sst_pdata->fw); + + return 0; +} + +static struct sst_acpi_mach haswell_machines[] = { + { "INT33CA", "haswell-audio", "intel/IntcSST1.bin" }, + {} +}; + +static struct sst_acpi_desc sst_acpi_haswell_desc = { + .drv_name = "haswell-pcm-audio", + .machines = haswell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_fw_base = -1, + .irqindex_host_ipc = 0, + .sst_id = SST_DEV_ID_LYNX_POINT, + .dma_engine = SST_DMA_TYPE_DW, + .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET, + .dma_size = SST_LPT_DSP_DMA_SIZE, +}; + +static struct sst_acpi_mach broadwell_machines[] = { + { "INT343A", "broadwell-audio", "intel/IntcSST2.bin" }, + {} +}; + +static struct sst_acpi_desc sst_acpi_broadwell_desc = { + .drv_name = "haswell-pcm-audio", + .machines = broadwell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_fw_base = -1, + .irqindex_host_ipc = 0, + .sst_id = SST_DEV_ID_WILDCAT_POINT, + .dma_engine = SST_DMA_TYPE_DW, + .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET, + .dma_size = SST_LPT_DSP_DMA_SIZE, +}; + +static struct sst_acpi_mach baytrail_machines[] = { + { "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-48kHz_i2s_master" }, + { "193C9890", "byt-max98090", "intel/fw_sst_0f28.bin-48kHz_i2s_master" }, + {} +}; + +static struct sst_acpi_desc sst_acpi_baytrail_desc = { + .drv_name = "baytrail-pcm-audio", + .machines = baytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_fw_base = 2, + .irqindex_host_ipc = 5, + .sst_id = SST_DEV_ID_BYT, + .resindex_dma_base = -1, +}; + +static struct acpi_device_id sst_acpi_match[] = { + { "INT33C8", (unsigned long)&sst_acpi_haswell_desc }, + { "INT3438", (unsigned long)&sst_acpi_broadwell_desc }, + { "80860F28", (unsigned long)&sst_acpi_baytrail_desc }, + { } +}; +MODULE_DEVICE_TABLE(acpi, sst_acpi_match); + +static struct platform_driver sst_acpi_driver = { + .probe = sst_acpi_probe, + .remove = sst_acpi_remove, + .driver = { + .name = "sst-acpi", + .acpi_match_table = ACPI_PTR(sst_acpi_match), + }, +}; +module_platform_driver(sst_acpi_driver); + +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_DESCRIPTION("Intel SST loader on ACPI systems"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-dsp-priv.h b/sound/soc/intel/common/sst-dsp-priv.h new file mode 100644 index 0000000..396d545 --- /dev/null +++ b/sound/soc/intel/common/sst-dsp-priv.h @@ -0,0 +1,373 @@ +/* + * Intel Smart Sound Technology + * + * Copyright (C) 2013, Intel Corporation. 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 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 __SOUND_SOC_SST_DSP_PRIV_H +#define __SOUND_SOC_SST_DSP_PRIV_H + +#include +#include +#include +#include + +struct sst_mem_block; +struct sst_module; +struct sst_fw; + +/* do we need to remove or keep */ +#define DSP_DRAM_ADDR_OFFSET 0x400000 + +/* + * DSP Operations exported by platform Audio DSP driver. + */ +struct sst_ops { + /* DSP core boot / reset */ + void (*boot)(struct sst_dsp *); + void (*reset)(struct sst_dsp *); + int (*wake)(struct sst_dsp *); + void (*sleep)(struct sst_dsp *); + void (*stall)(struct sst_dsp *); + + /* Shim IO */ + void (*write)(void __iomem *addr, u32 offset, u32 value); + u32 (*read)(void __iomem *addr, u32 offset); + void (*write64)(void __iomem *addr, u32 offset, u64 value); + u64 (*read64)(void __iomem *addr, u32 offset); + + /* DSP I/DRAM IO */ + void (*ram_read)(struct sst_dsp *sst, void *dest, void __iomem *src, + size_t bytes); + void (*ram_write)(struct sst_dsp *sst, void __iomem *dest, void *src, + size_t bytes); + + void (*dump)(struct sst_dsp *); + + /* IRQ handlers */ + irqreturn_t (*irq_handler)(int irq, void *context); + + /* SST init and free */ + int (*init)(struct sst_dsp *sst, struct sst_pdata *pdata); + void (*free)(struct sst_dsp *sst); + + /* FW module parser/loader */ + int (*parse_fw)(struct sst_fw *sst_fw); +}; + +/* + * Audio DSP memory offsets and addresses. + */ +struct sst_addr { + u32 lpe_base; + u32 shim_offset; + u32 iram_offset; + u32 dram_offset; + u32 dsp_iram_offset; + u32 dsp_dram_offset; + void __iomem *lpe; + void __iomem *shim; + void __iomem *pci_cfg; + void __iomem *fw_ext; +}; + +/* + * Audio DSP Mailbox configuration. + */ +struct sst_mailbox { + void __iomem *in_base; + void __iomem *out_base; + size_t in_size; + size_t out_size; +}; + +/* + * Audio DSP memory block types. + */ +enum sst_mem_type { + SST_MEM_IRAM = 0, + SST_MEM_DRAM = 1, + SST_MEM_ANY = 2, + SST_MEM_CACHE= 3, +}; + +/* + * Audio DSP Generic Firmware File. + * + * SST Firmware files can consist of 1..N modules. This generic structure is + * used to manage each firmware file and it's modules regardless of SST firmware + * type. A SST driver may load multiple FW files. + */ +struct sst_fw { + struct sst_dsp *dsp; + + /* base addresses of FW file data */ + dma_addr_t dmable_fw_paddr; /* physical address of fw data */ + void *dma_buf; /* virtual address of fw data */ + u32 size; /* size of fw data */ + + /* lists */ + struct list_head list; /* DSP list of FW */ + struct list_head module_list; /* FW list of modules */ + + void *private; /* core doesn't touch this */ +}; + +/* + * Audio DSP Generic Module Template. + * + * Used to define and register a new FW module. This data is extracted from + * FW module header information. + */ +struct sst_module_template { + u32 id; + u32 entry; /* entry point */ + u32 scratch_size; + u32 persistent_size; +}; + +/* + * Block Allocator - Used to allocate blocks of DSP memory. + */ +struct sst_block_allocator { + u32 id; + u32 offset; + int size; + enum sst_mem_type type; +}; + +/* + * Runtime Module Instance - A module object can be instanciated multiple + * times within the DSP FW. + */ +struct sst_module_runtime { + struct sst_dsp *dsp; + int id; + struct sst_module *module; /* parent module we belong too */ + + u32 persistent_offset; /* private memory offset */ + void *private; + + struct list_head list; + struct list_head block_list; /* list of blocks used */ +}; + +/* + * Runtime Module Context - The runtime context must be manually stored by the + * driver prior to enter S3 and restored after leaving S3. This should really be + * part of the memory context saved by the enter D3 message IPC ??? + */ +struct sst_module_runtime_context { + dma_addr_t dma_buffer; + u32 *buffer; +}; + +/* + * Audio DSP Module State + */ +enum sst_module_state { + SST_MODULE_STATE_UNLOADED = 0, /* default state */ + SST_MODULE_STATE_LOADED, + SST_MODULE_STATE_INITIALIZED, /* and inactive */ + SST_MODULE_STATE_ACTIVE, +}; + +/* + * Audio DSP Generic Module. + * + * Each Firmware file can consist of 1..N modules. A module can span multiple + * ADSP memory blocks. The simplest FW will be a file with 1 module. A module + * can be instanciated multiple times in the DSP. + */ +struct sst_module { + struct sst_dsp *dsp; + struct sst_fw *sst_fw; /* parent FW we belong too */ + + /* module configuration */ + u32 id; + u32 entry; /* module entry point */ + s32 offset; /* module offset in firmware file */ + u32 size; /* module size */ + u32 scratch_size; /* global scratch memory required */ + u32 persistent_size; /* private memory required */ + enum sst_mem_type type; /* destination memory type */ + u32 data_offset; /* offset in ADSP memory space */ + void *data; /* module data */ + + /* runtime */ + u32 usage_count; /* can be unloaded if count == 0 */ + void *private; /* core doesn't touch this */ + + /* lists */ + struct list_head block_list; /* Module list of blocks in use */ + struct list_head list; /* DSP list of modules */ + struct list_head list_fw; /* FW list of modules */ + struct list_head runtime_list; /* list of runtime module objects*/ + + /* state */ + enum sst_module_state state; +}; + +/* + * SST Memory Block operations. + */ +struct sst_block_ops { + int (*enable)(struct sst_mem_block *block); + int (*disable)(struct sst_mem_block *block); +}; + +/* + * SST Generic Memory Block. + * + * SST ADP memory has multiple IRAM and DRAM blocks. Some ADSP blocks can be + * power gated. + */ +struct sst_mem_block { + struct sst_dsp *dsp; + struct sst_module *module; /* module that uses this block */ + + /* block config */ + u32 offset; /* offset from base */ + u32 size; /* block size */ + u32 index; /* block index 0..N */ + enum sst_mem_type type; /* block memory type IRAM/DRAM */ + struct sst_block_ops *ops; /* block operations, if any */ + + /* block status */ + u32 bytes_used; /* bytes in use by modules */ + void *private; /* generic core does not touch this */ + int users; /* number of modules using this block */ + + /* block lists */ + struct list_head module_list; /* Module list of blocks */ + struct list_head list; /* Map list of free/used blocks */ +}; + +/* + * Generic SST Shim Interface. + */ +struct sst_dsp { + + /* runtime */ + struct sst_dsp_device *sst_dev; + spinlock_t spinlock; /* IPC locking */ + struct mutex mutex; /* DSP FW lock */ + struct device *dev; + struct device *dma_dev; + void *thread_context; + int irq; + u32 id; + + /* list of free and used ADSP memory blocks */ + struct list_head used_block_list; + struct list_head free_block_list; + + /* operations */ + struct sst_ops *ops; + + /* debug FS */ + struct dentry *debugfs_root; + + /* base addresses */ + struct sst_addr addr; + + /* mailbox */ + struct sst_mailbox mailbox; + + /* SST FW files loaded and their modules */ + struct list_head module_list; + struct list_head fw_list; + + /* scratch buffer */ + struct list_head scratch_block_list; + u32 scratch_offset; + u32 scratch_size; + + /* platform data */ + struct sst_pdata *pdata; + + /* DMA FW loading */ + struct sst_dma *dma; + bool fw_use_dma; +}; + +/* Size optimised DRAM/IRAM memcpy */ +static inline void sst_dsp_write(struct sst_dsp *sst, void *src, + u32 dest_offset, size_t bytes) +{ + sst->ops->ram_write(sst, sst->addr.lpe + dest_offset, src, bytes); +} + +static inline void sst_dsp_read(struct sst_dsp *sst, void *dest, + u32 src_offset, size_t bytes) +{ + sst->ops->ram_read(sst, dest, sst->addr.lpe + src_offset, bytes); +} + +static inline void *sst_dsp_get_thread_context(struct sst_dsp *sst) +{ + return sst->thread_context; +} + +/* Create/Free FW files - can contain multiple modules */ +struct sst_fw *sst_fw_new(struct sst_dsp *dsp, + const struct firmware *fw, void *private); +void sst_fw_free(struct sst_fw *sst_fw); +void sst_fw_free_all(struct sst_dsp *dsp); +int sst_fw_reload(struct sst_fw *sst_fw); +void sst_fw_unload(struct sst_fw *sst_fw); + +/* Create/Free firmware modules */ +struct sst_module *sst_module_new(struct sst_fw *sst_fw, + struct sst_module_template *template, void *private); +void sst_module_free(struct sst_module *module); +struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); +int sst_module_alloc_blocks(struct sst_module *module); +int sst_module_free_blocks(struct sst_module *module); + +/* Create/Free firmware module runtime instances */ +struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, + int id, void *private); +void sst_module_runtime_free(struct sst_module_runtime *runtime); +struct sst_module_runtime *sst_module_runtime_get_from_id( + struct sst_module *module, u32 id); +int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, + int offset); +int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime); +int sst_module_runtime_save(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context); +int sst_module_runtime_restore(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context); + +/* generic block allocation */ +int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list); +int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list); + +/* scratch allocation */ +int sst_block_alloc_scratch(struct sst_dsp *dsp); +void sst_block_free_scratch(struct sst_dsp *dsp); + +/* Register the DSPs memory blocks - would be nice to read from ACPI */ +struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, + u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index, + void *private); +void sst_mem_block_unregister_all(struct sst_dsp *dsp); + +/* Create/Free DMA resources */ +int sst_dma_new(struct sst_dsp *sst); +void sst_dma_free(struct sst_dma *dma); + +u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, + enum sst_mem_type type); +#endif diff --git a/sound/soc/intel/common/sst-dsp.c b/sound/soc/intel/common/sst-dsp.c new file mode 100644 index 0000000..64e9421 --- /dev/null +++ b/sound/soc/intel/common/sst-dsp.c @@ -0,0 +1,420 @@ +/* + * Intel Smart Sound Technology (SST) DSP Core Driver + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" + +#define CREATE_TRACE_POINTS +#include + +/* Internal generic low-level SST IO functions - can be overidden */ +void sst_shim32_write(void __iomem *addr, u32 offset, u32 value) +{ + writel(value, addr + offset); +} +EXPORT_SYMBOL_GPL(sst_shim32_write); + +u32 sst_shim32_read(void __iomem *addr, u32 offset) +{ + return readl(addr + offset); +} +EXPORT_SYMBOL_GPL(sst_shim32_read); + +void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value) +{ + memcpy_toio(addr + offset, &value, sizeof(value)); +} +EXPORT_SYMBOL_GPL(sst_shim32_write64); + +u64 sst_shim32_read64(void __iomem *addr, u32 offset) +{ + u64 val; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + return val; +} +EXPORT_SYMBOL_GPL(sst_shim32_read64); + +static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest, + u32 *src, size_t bytes) +{ + int i, words = bytes >> 2; + + for (i = 0; i < words; i++) + writel(src[i], dest + i); +} + +static inline void _sst_memcpy_fromio_32(u32 *dest, + const volatile __iomem u32 *src, size_t bytes) +{ + int i, words = bytes >> 2; + + for (i = 0; i < words; i++) + dest[i] = readl(src + i); +} + +void sst_memcpy_toio_32(struct sst_dsp *sst, + void __iomem *dest, void *src, size_t bytes) +{ + _sst_memcpy_toio_32(dest, src, bytes); +} +EXPORT_SYMBOL_GPL(sst_memcpy_toio_32); + +void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest, + void __iomem *src, size_t bytes) +{ + _sst_memcpy_fromio_32(dest, src, bytes); +} +EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32); + +/* Public API */ +void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + sst->ops->write(sst->addr.shim, offset, value); + spin_unlock_irqrestore(&sst->spinlock, flags); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write); + +u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&sst->spinlock, flags); + val = sst->ops->read(sst->addr.shim, offset); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return val; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read); + +void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + sst->ops->write64(sst->addr.shim, offset, value); + spin_unlock_irqrestore(&sst->spinlock, flags); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write64); + +u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset) +{ + unsigned long flags; + u64 val; + + spin_lock_irqsave(&sst->spinlock, flags); + val = sst->ops->read64(sst->addr.shim, offset); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return val; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read64); + +void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value) +{ + sst->ops->write(sst->addr.shim, offset, value); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write_unlocked); + +u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset) +{ + return sst->ops->read(sst->addr.shim, offset); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read_unlocked); + +void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value) +{ + sst->ops->write64(sst->addr.shim, offset, value); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write64_unlocked); + +u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset) +{ + return sst->ops->read64(sst->addr.shim, offset); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read64_unlocked); + +int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value) +{ + bool change; + unsigned int old, new; + u32 ret; + + ret = sst_dsp_shim_read_unlocked(sst, offset); + + old = ret; + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) + sst_dsp_shim_write_unlocked(sst, offset, new); + + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_unlocked); + +int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value) +{ + bool change; + u64 old, new; + + old = sst_dsp_shim_read64_unlocked(sst, offset); + + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) + sst_dsp_shim_write64_unlocked(sst, offset, new); + + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64_unlocked); + +int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sst->spinlock, flags); + change = sst_dsp_shim_update_bits_unlocked(sst, offset, mask, value); + spin_unlock_irqrestore(&sst->spinlock, flags); + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits); + +int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sst->spinlock, flags); + change = sst_dsp_shim_update_bits64_unlocked(sst, offset, mask, value); + spin_unlock_irqrestore(&sst->spinlock, flags); + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64); + +void sst_dsp_dump(struct sst_dsp *sst) +{ + if (sst->ops->dump) + sst->ops->dump(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_dump); + +void sst_dsp_reset(struct sst_dsp *sst) +{ + if (sst->ops->reset) + sst->ops->reset(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_reset); + +int sst_dsp_boot(struct sst_dsp *sst) +{ + if (sst->ops->boot) + sst->ops->boot(sst); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_boot); + +int sst_dsp_wake(struct sst_dsp *sst) +{ + if (sst->ops->wake) + return sst->ops->wake(sst); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_wake); + +void sst_dsp_sleep(struct sst_dsp *sst) +{ + if (sst->ops->sleep) + sst->ops->sleep(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_sleep); + +void sst_dsp_stall(struct sst_dsp *sst) +{ + if (sst->ops->stall) + sst->ops->stall(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_stall); + +void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) +{ + sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); + trace_sst_ipc_msg_tx(msg); +} +EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_tx); + +u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp) +{ + u32 msg; + + msg = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); + trace_sst_ipc_msg_rx(msg); + + return msg; +} +EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_rx); + +int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size, + u32 outbox_offset, size_t outbox_size) +{ + sst->mailbox.in_base = sst->addr.lpe + inbox_offset; + sst->mailbox.out_base = sst->addr.lpe + outbox_offset; + sst->mailbox.in_size = inbox_size; + sst->mailbox.out_size = outbox_size; + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_mailbox_init); + +void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_outbox_write(bytes); + + memcpy_toio(sst->mailbox.out_base, message, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_outbox_wdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_outbox_write); + +void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_outbox_read(bytes); + + memcpy_fromio(message, sst->mailbox.out_base, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_outbox_rdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_outbox_read); + +void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_inbox_write(bytes); + + memcpy_toio(sst->mailbox.in_base, message, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_inbox_wdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_inbox_write); + +void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_inbox_read(bytes); + + memcpy_fromio(message, sst->mailbox.in_base, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_inbox_rdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); + +struct sst_dsp *sst_dsp_new(struct device *dev, + struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) +{ + struct sst_dsp *sst; + int err; + + dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); + + sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); + if (sst == NULL) + return NULL; + + spin_lock_init(&sst->spinlock); + mutex_init(&sst->mutex); + sst->dev = dev; + sst->dma_dev = pdata->dma_dev; + sst->thread_context = sst_dev->thread_context; + sst->sst_dev = sst_dev; + sst->id = pdata->id; + sst->irq = pdata->irq; + sst->ops = sst_dev->ops; + sst->pdata = pdata; + INIT_LIST_HEAD(&sst->used_block_list); + INIT_LIST_HEAD(&sst->free_block_list); + INIT_LIST_HEAD(&sst->module_list); + INIT_LIST_HEAD(&sst->fw_list); + INIT_LIST_HEAD(&sst->scratch_block_list); + + /* Initialise SST Audio DSP */ + if (sst->ops->init) { + err = sst->ops->init(sst, pdata); + if (err < 0) + return NULL; + } + + /* Register the ISR */ + err = request_threaded_irq(sst->irq, sst->ops->irq_handler, + sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); + if (err) + goto irq_err; + + err = sst_dma_new(sst); + if (err) + dev_warn(dev, "sst_dma_new failed %d\n", err); + + return sst; + +irq_err: + if (sst->ops->free) + sst->ops->free(sst); + + return NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_new); + +void sst_dsp_free(struct sst_dsp *sst) +{ + free_irq(sst->irq, sst); + if (sst->ops->free) + sst->ops->free(sst); + + sst_dma_free(sst->dma); +} +EXPORT_SYMBOL_GPL(sst_dsp_free); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Intel SST Core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-dsp.h b/sound/soc/intel/common/sst-dsp.h new file mode 100644 index 0000000..3412474 --- /dev/null +++ b/sound/soc/intel/common/sst-dsp.h @@ -0,0 +1,284 @@ +/* + * Intel Smart Sound Technology (SST) Core + * + * Copyright (C) 2013, Intel Corporation. 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 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 __SOUND_SOC_SST_DSP_H +#define __SOUND_SOC_SST_DSP_H + +#include +#include +#include + +/* SST Device IDs */ +#define SST_DEV_ID_LYNX_POINT 0x33C8 +#define SST_DEV_ID_WILDCAT_POINT 0x3438 +#define SST_DEV_ID_BYT 0x0F28 + +/* Supported SST DMA Devices */ +#define SST_DMA_TYPE_DW 1 + +/* autosuspend delay 5s*/ +#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000) + +/* SST Shim register map + * The register naming can differ between products. Some products also + * contain extra functionality. + */ +#define SST_CSR 0x00 +#define SST_PISR 0x08 +#define SST_PIMR 0x10 +#define SST_ISRX 0x18 +#define SST_ISRD 0x20 +#define SST_IMRX 0x28 +#define SST_IMRD 0x30 +#define SST_IPCX 0x38 /* IPC IA -> SST */ +#define SST_IPCD 0x40 /* IPC SST -> IA */ +#define SST_ISRSC 0x48 +#define SST_ISRLPESC 0x50 +#define SST_IMRSC 0x58 +#define SST_IMRLPESC 0x60 +#define SST_IPCSC 0x68 +#define SST_IPCLPESC 0x70 +#define SST_CLKCTL 0x78 +#define SST_CSR2 0x80 +#define SST_LTRC 0xE0 +#define SST_HMDC 0xE8 + +#define SST_SHIM_BEGIN SST_CSR +#define SST_SHIM_END SST_HDMC + +#define SST_DBGO 0xF0 + +#define SST_SHIM_SIZE 0x100 +#define SST_PWMCTRL 0x1000 + +/* SST Shim Register bits + * The register bit naming can differ between products. Some products also + * contain extra functionality. + */ + +/* CSR / CS */ +#define SST_CSR_RST (0x1 << 1) +#define SST_CSR_SBCS0 (0x1 << 2) +#define SST_CSR_SBCS1 (0x1 << 3) +#define SST_CSR_DCS(x) (x << 4) +#define SST_CSR_DCS_MASK (0x7 << 4) +#define SST_CSR_STALL (0x1 << 10) +#define SST_CSR_S0IOCS (0x1 << 21) +#define SST_CSR_S1IOCS (0x1 << 23) +#define SST_CSR_LPCS (0x1 << 31) +#define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS) +#define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1) +#define SST_BYT_CSR_RST (0x1 << 0) +#define SST_BYT_CSR_VECTOR_SEL (0x1 << 1) +#define SST_BYT_CSR_STALL (0x1 << 2) +#define SST_BYT_CSR_PWAITMODE (0x1 << 3) + +/* ISRX / ISC */ +#define SST_ISRX_BUSY (0x1 << 1) +#define SST_ISRX_DONE (0x1 << 0) +#define SST_BYT_ISRX_REQUEST (0x1 << 1) + +/* ISRD / ISD */ +#define SST_ISRD_BUSY (0x1 << 1) +#define SST_ISRD_DONE (0x1 << 0) + +/* IMRX / IMC */ +#define SST_IMRX_BUSY (0x1 << 1) +#define SST_IMRX_DONE (0x1 << 0) +#define SST_BYT_IMRX_REQUEST (0x1 << 1) + +/* IMRD / IMD */ +#define SST_IMRD_DONE (0x1 << 0) +#define SST_IMRD_BUSY (0x1 << 1) +#define SST_IMRD_SSP0 (0x1 << 16) +#define SST_IMRD_DMAC0 (0x1 << 21) +#define SST_IMRD_DMAC1 (0x1 << 22) +#define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1) + +/* IPCX / IPCC */ +#define SST_IPCX_DONE (0x1 << 30) +#define SST_IPCX_BUSY (0x1 << 31) +#define SST_BYT_IPCX_DONE ((u64)0x1 << 62) +#define SST_BYT_IPCX_BUSY ((u64)0x1 << 63) + +/* IPCD */ +#define SST_IPCD_DONE (0x1 << 30) +#define SST_IPCD_BUSY (0x1 << 31) +#define SST_BYT_IPCD_DONE ((u64)0x1 << 62) +#define SST_BYT_IPCD_BUSY ((u64)0x1 << 63) + +/* CLKCTL */ +#define SST_CLKCTL_SMOS(x) (x << 24) +#define SST_CLKCTL_MASK (3 << 24) +#define SST_CLKCTL_DCPLCG (1 << 18) +#define SST_CLKCTL_SCOE1 (1 << 17) +#define SST_CLKCTL_SCOE0 (1 << 16) + +/* CSR2 / CS2 */ +#define SST_CSR2_SDFD_SSP0 (1 << 1) +#define SST_CSR2_SDFD_SSP1 (1 << 2) + +/* LTRC */ +#define SST_LTRC_VAL(x) (x << 0) + +/* HMDC */ +#define SST_HMDC_HDDA0(x) (x << 0) +#define SST_HMDC_HDDA1(x) (x << 7) +#define SST_HMDC_HDDA_E0_CH0 1 +#define SST_HMDC_HDDA_E0_CH1 2 +#define SST_HMDC_HDDA_E0_CH2 4 +#define SST_HMDC_HDDA_E0_CH3 8 +#define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0) +#define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1) +#define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2) +#define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3) +#define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \ + SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3) +#define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \ + SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3) + + +/* SST Vendor Defined Registers and bits */ +#define SST_VDRTCTL0 0xa0 +#define SST_VDRTCTL1 0xa4 +#define SST_VDRTCTL2 0xa8 +#define SST_VDRTCTL3 0xaC + +/* VDRTCTL0 */ +#define SST_VDRTCL0_D3PGD (1 << 0) +#define SST_VDRTCL0_D3SRAMPGD (1 << 1) +#define SST_VDRTCL0_DSRAMPGE_SHIFT 12 +#define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT) +#define SST_VDRTCL0_ISRAMPGE_SHIFT 2 +#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) + +/* VDRTCTL2 */ +#define SST_VDRTCL2_DCLCGE (1 << 1) +#define SST_VDRTCL2_DTCGE (1 << 10) +#define SST_VDRTCL2_APLLSE_MASK (1 << 31) + +/* PMCS */ +#define SST_PMCS 0x84 +#define SST_PMCS_PS_MASK 0x3 + +struct sst_dsp; + +/* + * SST Device. + * + * This structure is populated by the SST core driver. + */ +struct sst_dsp_device { + /* Mandatory fields */ + struct sst_ops *ops; + irqreturn_t (*thread)(int irq, void *context); + void *thread_context; +}; + +/* + * SST Platform Data. + */ +struct sst_pdata { + /* ACPI data */ + u32 lpe_base; + u32 lpe_size; + u32 pcicfg_base; + u32 pcicfg_size; + u32 fw_base; + u32 fw_size; + int irq; + + /* Firmware */ + const struct firmware *fw; + + /* DMA */ + u32 dma_base; + u32 dma_size; + int dma_engine; + struct device *dma_dev; + + /* DSP */ + u32 id; + void *dsp; +}; + +/* Initialization */ +struct sst_dsp *sst_dsp_new(struct device *dev, + struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); +void sst_dsp_free(struct sst_dsp *sst); + +/* SHIM Read / Write */ +void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value); +u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value); +void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value); +u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value); + +/* SHIM Read / Write Unlocked for callers already holding sst lock */ +void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value); +u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value); +void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value); +u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value); + +/* Internal generic low-level SST IO functions - can be overidden */ +void sst_shim32_write(void __iomem *addr, u32 offset, u32 value); +u32 sst_shim32_read(void __iomem *addr, u32 offset); +void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value); +u64 sst_shim32_read64(void __iomem *addr, u32 offset); +void sst_memcpy_toio_32(struct sst_dsp *sst, + void __iomem *dest, void *src, size_t bytes); +void sst_memcpy_fromio_32(struct sst_dsp *sst, + void *dest, void __iomem *src, size_t bytes); + +/* DSP reset & boot */ +void sst_dsp_reset(struct sst_dsp *sst); +int sst_dsp_boot(struct sst_dsp *sst); +int sst_dsp_wake(struct sst_dsp *sst); +void sst_dsp_sleep(struct sst_dsp *sst); +void sst_dsp_stall(struct sst_dsp *sst); + +/* DMA */ +int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id); +void sst_dsp_dma_put_channel(struct sst_dsp *dsp); +int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size); +int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size); + +/* Msg IO */ +void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); +u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp); + +/* Mailbox management */ +int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset, + size_t inbox_size, u32 outbox_offset, size_t outbox_size); +void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes); + +/* Debug */ +void sst_dsp_dump(struct sst_dsp *sst); + +#endif diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c new file mode 100644 index 0000000..b5659ec --- /dev/null +++ b/sound/soc/intel/common/sst-firmware.c @@ -0,0 +1,1201 @@ +/* + * Intel SST Firmware Loader + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* supported DMA engine drivers */ +#include +#include + +#include +#include + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" + +#define SST_DMA_RESOURCES 2 +#define SST_DSP_DMA_MAX_BURST 0x3 +#define SST_HSW_BLOCK_ANY 0xffffffff + +#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000 + +struct sst_dma { + struct sst_dsp *sst; + + struct dw_dma_chip *chip; + + struct dma_async_tx_descriptor *desc; + struct dma_chan *ch; +}; + +static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) +{ + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy((void *)dest, src, bytes/4); +} + +static void sst_dma_transfer_complete(void *arg) +{ + struct sst_dsp *sst = (struct sst_dsp *)arg; + + dev_dbg(sst->dev, "DMA: callback\n"); +} + +static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + struct dma_async_tx_descriptor *desc; + struct sst_dma *dma = sst->dma; + + if (dma->ch == NULL) { + dev_err(sst->dev, "error: no DMA channel\n"); + return -ENODEV; + } + + dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n", + (unsigned long)src_addr, (unsigned long)dest_addr, size); + + desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr, + src_addr, size, DMA_CTRL_ACK); + if (!desc){ + dev_err(sst->dev, "error: dma prep memcpy failed\n"); + return -EINVAL; + } + + desc->callback = sst_dma_transfer_complete; + desc->callback_param = sst; + + desc->tx_submit(desc); + dma_wait_for_async_tx(desc); + + return 0; +} + +/* copy to DSP */ +int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP, + src_addr, size); +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto); + +/* copy from DSP */ +int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + return sst_dsp_dma_copy(sst, dest_addr, + src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size); +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom); + +/* remove module from memory - callers hold locks */ +static void block_list_remove(struct sst_dsp *dsp, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + int err; + + /* disable each block */ + list_for_each_entry(block, block_list, module_list) { + + if (block->ops && block->ops->disable) { + err = block->ops->disable(block); + if (err < 0) + dev_err(dsp->dev, + "error: cant disable block %d:%d\n", + block->type, block->index); + } + } + + /* mark each block as free */ + list_for_each_entry_safe(block, tmp, block_list, module_list) { + list_del(&block->module_list); + list_move(&block->list, &dsp->free_block_list); + dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + } +} + +/* prepare the memory block to receive data from host - callers hold locks */ +static int block_list_prepare(struct sst_dsp *dsp, + struct list_head *block_list) +{ + struct sst_mem_block *block; + int ret = 0; + + /* enable each block so that's it'e ready for data */ + list_for_each_entry(block, block_list, module_list) { + + if (block->ops && block->ops->enable && !block->users) { + ret = block->ops->enable(block); + if (ret < 0) { + dev_err(dsp->dev, + "error: cant disable block %d:%d\n", + block->type, block->index); + goto err; + } + } + } + return ret; + +err: + list_for_each_entry(block, block_list, module_list) { + if (block->ops && block->ops->disable) + block->ops->disable(block); + } + return ret; +} + +static struct dw_dma_platform_data dw_pdata = { + .is_private = 1, + .chan_allocation_order = CHAN_ALLOCATION_ASCENDING, + .chan_priority = CHAN_PRIORITY_ASCENDING, +}; + +static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem, + int irq) +{ + struct dw_dma_chip *chip; + int err; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return ERR_PTR(-ENOMEM); + + chip->irq = irq; + chip->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(chip->regs)) + return ERR_CAST(chip->regs); + + err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); + if (err) + return ERR_PTR(err); + + chip->dev = dev; + err = dw_dma_probe(chip, &dw_pdata); + if (err) + return ERR_PTR(err); + + return chip; +} + +static void dw_remove(struct dw_dma_chip *chip) +{ + dw_dma_remove(chip); +} + +static bool dma_chan_filter(struct dma_chan *chan, void *param) +{ + struct sst_dsp *dsp = (struct sst_dsp *)param; + + return chan->device->dev == dsp->dma_dev; +} + +int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) +{ + struct sst_dma *dma = dsp->dma; + struct dma_slave_config slave; + dma_cap_mask_t mask; + int ret; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_MEMCPY, mask); + + dma->ch = dma_request_channel(mask, dma_chan_filter, dsp); + if (dma->ch == NULL) { + dev_err(dsp->dev, "error: DMA request channel failed\n"); + return -EIO; + } + + memset(&slave, 0, sizeof(slave)); + slave.direction = DMA_MEM_TO_DEV; + slave.src_addr_width = + slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST; + + ret = dmaengine_slave_config(dma->ch, &slave); + if (ret) { + dev_err(dsp->dev, "error: unable to set DMA slave config %d\n", + ret); + dma_release_channel(dma->ch); + dma->ch = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel); + +void sst_dsp_dma_put_channel(struct sst_dsp *dsp) +{ + struct sst_dma *dma = dsp->dma; + + if (!dma->ch) + return; + + dma_release_channel(dma->ch); + dma->ch = NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel); + +int sst_dma_new(struct sst_dsp *sst) +{ + struct sst_pdata *sst_pdata = sst->pdata; + struct sst_dma *dma; + struct resource mem; + const char *dma_dev_name; + int ret = 0; + + /* configure the correct platform data for whatever DMA engine + * is attached to the ADSP IP. */ + switch (sst->pdata->dma_engine) { + case SST_DMA_TYPE_DW: + dma_dev_name = "dw_dmac"; + break; + default: + dev_err(sst->dev, "error: invalid DMA engine %d\n", + sst->pdata->dma_engine); + return -EINVAL; + } + + dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + dma->sst = sst; + + memset(&mem, 0, sizeof(mem)); + + mem.start = sst->addr.lpe_base + sst_pdata->dma_base; + mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1; + mem.flags = IORESOURCE_MEM; + + /* now register DMA engine device */ + dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq); + if (IS_ERR(dma->chip)) { + dev_err(sst->dev, "error: DMA device register failed\n"); + ret = PTR_ERR(dma->chip); + goto err_dma_dev; + } + + sst->dma = dma; + sst->fw_use_dma = true; + return 0; + +err_dma_dev: + devm_kfree(sst->dev, dma); + return ret; +} +EXPORT_SYMBOL(sst_dma_new); + +void sst_dma_free(struct sst_dma *dma) +{ + + if (dma == NULL) + return; + + if (dma->ch) + dma_release_channel(dma->ch); + + if (dma->chip) + dw_remove(dma->chip); + +} +EXPORT_SYMBOL(sst_dma_free); + +/* create new generic firmware object */ +struct sst_fw *sst_fw_new(struct sst_dsp *dsp, + const struct firmware *fw, void *private) +{ + struct sst_fw *sst_fw; + int err; + + if (!dsp->ops->parse_fw) + return NULL; + + sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL); + if (sst_fw == NULL) + return NULL; + + sst_fw->dsp = dsp; + sst_fw->private = private; + sst_fw->size = fw->size; + + /* allocate DMA buffer to store FW data */ + sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size, + &sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL); + if (!sst_fw->dma_buf) { + dev_err(dsp->dev, "error: DMA alloc failed\n"); + kfree(sst_fw); + return NULL; + } + + /* copy FW data to DMA-able memory */ + memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); + + if (dsp->fw_use_dma) { + err = sst_dsp_dma_get_channel(dsp, 0); + if (err < 0) + goto chan_err; + } + + /* call core specific FW paser to load FW data into DSP */ + err = dsp->ops->parse_fw(sst_fw); + if (err < 0) { + dev_err(dsp->dev, "error: parse fw failed %d\n", err); + goto parse_err; + } + + if (dsp->fw_use_dma) + sst_dsp_dma_put_channel(dsp); + + mutex_lock(&dsp->mutex); + list_add(&sst_fw->list, &dsp->fw_list); + mutex_unlock(&dsp->mutex); + + return sst_fw; + +parse_err: + if (dsp->fw_use_dma) + sst_dsp_dma_put_channel(dsp); +chan_err: + dma_free_coherent(dsp->dma_dev, sst_fw->size, + sst_fw->dma_buf, + sst_fw->dmable_fw_paddr); + sst_fw->dma_buf = NULL; + kfree(sst_fw); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_fw_new); + +int sst_fw_reload(struct sst_fw *sst_fw) +{ + struct sst_dsp *dsp = sst_fw->dsp; + int ret; + + dev_dbg(dsp->dev, "reloading firmware\n"); + + /* call core specific FW paser to load FW data into DSP */ + ret = dsp->ops->parse_fw(sst_fw); + if (ret < 0) + dev_err(dsp->dev, "error: parse fw failed %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(sst_fw_reload); + +void sst_fw_unload(struct sst_fw *sst_fw) +{ + struct sst_dsp *dsp = sst_fw->dsp; + struct sst_module *module, *mtmp; + struct sst_module_runtime *runtime, *rtmp; + + dev_dbg(dsp->dev, "unloading firmware\n"); + + mutex_lock(&dsp->mutex); + + /* check module by module */ + list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) { + if (module->sst_fw == sst_fw) { + + /* remove runtime modules */ + list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) { + + block_list_remove(dsp, &runtime->block_list); + list_del(&runtime->list); + kfree(runtime); + } + + /* now remove the module */ + block_list_remove(dsp, &module->block_list); + list_del(&module->list); + kfree(module); + } + } + + /* remove all scratch blocks */ + block_list_remove(dsp, &dsp->scratch_block_list); + + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_fw_unload); + +/* free single firmware object */ +void sst_fw_free(struct sst_fw *sst_fw) +{ + struct sst_dsp *dsp = sst_fw->dsp; + + mutex_lock(&dsp->mutex); + list_del(&sst_fw->list); + mutex_unlock(&dsp->mutex); + + if (sst_fw->dma_buf) + dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, + sst_fw->dmable_fw_paddr); + kfree(sst_fw); +} +EXPORT_SYMBOL_GPL(sst_fw_free); + +/* free all firmware objects */ +void sst_fw_free_all(struct sst_dsp *dsp) +{ + struct sst_fw *sst_fw, *t; + + mutex_lock(&dsp->mutex); + list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { + + list_del(&sst_fw->list); + dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, + sst_fw->dmable_fw_paddr); + kfree(sst_fw); + } + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_fw_free_all); + +/* create a new SST generic module from FW template */ +struct sst_module *sst_module_new(struct sst_fw *sst_fw, + struct sst_module_template *template, void *private) +{ + struct sst_dsp *dsp = sst_fw->dsp; + struct sst_module *sst_module; + + sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL); + if (sst_module == NULL) + return NULL; + + sst_module->id = template->id; + sst_module->dsp = dsp; + sst_module->sst_fw = sst_fw; + sst_module->scratch_size = template->scratch_size; + sst_module->persistent_size = template->persistent_size; + sst_module->entry = template->entry; + sst_module->state = SST_MODULE_STATE_UNLOADED; + + INIT_LIST_HEAD(&sst_module->block_list); + INIT_LIST_HEAD(&sst_module->runtime_list); + + mutex_lock(&dsp->mutex); + list_add(&sst_module->list, &dsp->module_list); + mutex_unlock(&dsp->mutex); + + return sst_module; +} +EXPORT_SYMBOL_GPL(sst_module_new); + +/* free firmware module and remove from available list */ +void sst_module_free(struct sst_module *sst_module) +{ + struct sst_dsp *dsp = sst_module->dsp; + + mutex_lock(&dsp->mutex); + list_del(&sst_module->list); + mutex_unlock(&dsp->mutex); + + kfree(sst_module); +} +EXPORT_SYMBOL_GPL(sst_module_free); + +struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, + int id, void *private) +{ + struct sst_dsp *dsp = module->dsp; + struct sst_module_runtime *runtime; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (runtime == NULL) + return NULL; + + runtime->id = id; + runtime->dsp = dsp; + runtime->module = module; + INIT_LIST_HEAD(&runtime->block_list); + + mutex_lock(&dsp->mutex); + list_add(&runtime->list, &module->runtime_list); + mutex_unlock(&dsp->mutex); + + return runtime; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_new); + +void sst_module_runtime_free(struct sst_module_runtime *runtime) +{ + struct sst_dsp *dsp = runtime->dsp; + + mutex_lock(&dsp->mutex); + list_del(&runtime->list); + mutex_unlock(&dsp->mutex); + + kfree(runtime); +} +EXPORT_SYMBOL_GPL(sst_module_runtime_free); + +static struct sst_mem_block *find_block(struct sst_dsp *dsp, + struct sst_block_allocator *ba) +{ + struct sst_mem_block *block; + + list_for_each_entry(block, &dsp->free_block_list, list) { + if (block->type == ba->type && block->offset == ba->offset) + return block; + } + + return NULL; +} + +/* Block allocator must be on block boundary */ +static int block_alloc_contiguous(struct sst_dsp *dsp, + struct sst_block_allocator *ba, struct list_head *block_list) +{ + struct list_head tmp = LIST_HEAD_INIT(tmp); + struct sst_mem_block *block; + u32 block_start = SST_HSW_BLOCK_ANY; + int size = ba->size, offset = ba->offset; + + while (ba->size > 0) { + + block = find_block(dsp, ba); + if (!block) { + list_splice(&tmp, &dsp->free_block_list); + + ba->size = size; + ba->offset = offset; + return -ENOMEM; + } + + list_move_tail(&block->list, &tmp); + ba->offset += block->size; + ba->size -= block->size; + } + ba->size = size; + ba->offset = offset; + + list_for_each_entry(block, &tmp, list) { + + if (block->offset < block_start) + block_start = block->offset; + + list_add(&block->module_list, block_list); + + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + } + + list_splice(&tmp, &dsp->used_block_list); + return 0; +} + +/* allocate first free DSP blocks for data - callers hold locks */ +static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + int ret = 0; + + if (ba->size == 0) + return 0; + + /* find first free whole blocks that can hold module */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + if (ba->size > block->size) + continue; + + ba->offset = block->offset; + block->bytes_used = ba->size % block->size; + list_add(&block->module_list, block_list); + list_move(&block->list, &dsp->used_block_list); + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + return 0; + } + + /* then find free multiple blocks that can hold module */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + /* do we span > 1 blocks */ + if (ba->size > block->size) { + + /* align ba to block boundary */ + ba->offset = block->offset; + + ret = block_alloc_contiguous(dsp, ba, block_list); + if (ret == 0) + return ret; + + } + } + + /* not enough free block space */ + return -ENOMEM; +} + +int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) +{ + int ret; + + dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", + ba->size, ba->offset, ba->type); + + mutex_lock(&dsp->mutex); + + ret = block_alloc(dsp, ba, block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret); + goto out; + } + + /* prepare DSP blocks for module usage */ + ret = block_list_prepare(dsp, block_list); + if (ret < 0) + dev_err(dsp->dev, "error: prepare failed\n"); + +out: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_alloc_blocks); + +int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list) +{ + mutex_lock(&dsp->mutex); + block_list_remove(dsp, block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_free_blocks); + +/* allocate memory blocks for static module addresses - callers hold locks */ +static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + struct sst_block_allocator ba_tmp = *ba; + u32 end = ba->offset + ba->size, block_end; + int err; + + /* only IRAM/DRAM blocks are managed */ + if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM) + return 0; + + /* are blocks already attached to this module */ + list_for_each_entry_safe(block, tmp, block_list, module_list) { + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + block_end = block->offset + block->size; + + /* find block that holds section */ + if (ba->offset >= block->offset && end <= block_end) + return 0; + + /* does block span more than 1 section */ + if (ba->offset >= block->offset && ba->offset < block_end) { + + /* align ba to block boundary */ + ba_tmp.size -= block_end - ba->offset; + ba_tmp.offset = block_end; + err = block_alloc_contiguous(dsp, &ba_tmp, block_list); + if (err < 0) + return -ENOMEM; + + /* module already owns blocks */ + return 0; + } + } + + /* find first free blocks that can hold section in free list */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + block_end = block->offset + block->size; + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + /* find block that holds section */ + if (ba->offset >= block->offset && end <= block_end) { + + /* add block */ + list_move(&block->list, &dsp->used_block_list); + list_add(&block->module_list, block_list); + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + return 0; + } + + /* does block span more than 1 section */ + if (ba->offset >= block->offset && ba->offset < block_end) { + + /* add block */ + list_move(&block->list, &dsp->used_block_list); + list_add(&block->module_list, block_list); + /* align ba to block boundary */ + ba_tmp.size -= block_end - ba->offset; + ba_tmp.offset = block_end; + + err = block_alloc_contiguous(dsp, &ba_tmp, block_list); + if (err < 0) + return -ENOMEM; + + return 0; + } + } + + return -ENOMEM; +} + +/* Load fixed module data into DSP memory blocks */ +int sst_module_alloc_blocks(struct sst_module *module) +{ + struct sst_dsp *dsp = module->dsp; + struct sst_fw *sst_fw = module->sst_fw; + struct sst_block_allocator ba; + int ret; + + memset(&ba, 0, sizeof(ba)); + ba.size = module->size; + ba.type = module->type; + ba.offset = module->offset; + + dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", + ba.size, ba.offset, ba.type); + + mutex_lock(&dsp->mutex); + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &module->block_list); + if (ret < 0) { + dev_err(dsp->dev, + "error: no free blocks for section at offset 0x%x size 0x%x\n", + module->offset, module->size); + mutex_unlock(&dsp->mutex); + return -ENOMEM; + } + + /* prepare DSP blocks for module copy */ + ret = block_list_prepare(dsp, &module->block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: fw module prepare failed\n"); + goto err; + } + + /* copy partial module data to blocks */ + if (dsp->fw_use_dma) { + ret = sst_dsp_dma_copyto(dsp, + dsp->addr.lpe_base + module->offset, + sst_fw->dmable_fw_paddr + module->data_offset, + module->size); + if (ret < 0) { + dev_err(dsp->dev, "error: module copy failed\n"); + goto err; + } + } else + sst_memcpy32(dsp->addr.lpe + module->offset, module->data, + module->size); + + mutex_unlock(&dsp->mutex); + return ret; + +err: + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_alloc_blocks); + +/* Unload entire module from DSP memory */ +int sst_module_free_blocks(struct sst_module *module) +{ + struct sst_dsp *dsp = module->dsp; + + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_module_free_blocks); + +int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, + int offset) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + struct sst_block_allocator ba; + int ret; + + if (module->persistent_size == 0) + return 0; + + memset(&ba, 0, sizeof(ba)); + ba.size = module->persistent_size; + ba.type = SST_MEM_DRAM; + + mutex_lock(&dsp->mutex); + + /* do we need to allocate at a fixed address ? */ + if (offset != 0) { + + ba.offset = offset; + + dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n", + ba.size, ba.type, ba.offset); + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &runtime->block_list); + + } else { + dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n", + ba.size, ba.type); + + /* alloc blocks that includes this section */ + ret = block_alloc(dsp, &ba, &runtime->block_list); + } + if (ret < 0) { + dev_err(dsp->dev, + "error: no free blocks for runtime module size 0x%x\n", + module->persistent_size); + mutex_unlock(&dsp->mutex); + return -ENOMEM; + } + runtime->persistent_offset = ba.offset; + + /* prepare DSP blocks for module copy */ + ret = block_list_prepare(dsp, &runtime->block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: runtime block prepare failed\n"); + goto err; + } + + mutex_unlock(&dsp->mutex); + return ret; + +err: + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks); + +int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime) +{ + struct sst_dsp *dsp = runtime->dsp; + + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &runtime->block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks); + +int sst_module_runtime_save(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + int ret = 0; + + dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n", + runtime->id, runtime->persistent_offset, + module->persistent_size); + + context->buffer = dma_alloc_coherent(dsp->dma_dev, + module->persistent_size, + &context->dma_buffer, GFP_DMA | GFP_KERNEL); + if (!context->buffer) { + dev_err(dsp->dev, "error: DMA context alloc failed\n"); + return -ENOMEM; + } + + mutex_lock(&dsp->mutex); + + if (dsp->fw_use_dma) { + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) + goto err; + + ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer, + dsp->addr.lpe_base + runtime->persistent_offset, + module->persistent_size); + sst_dsp_dma_put_channel(dsp); + if (ret < 0) { + dev_err(dsp->dev, "error: context copy failed\n"); + goto err; + } + } else + sst_memcpy32(context->buffer, dsp->addr.lpe + + runtime->persistent_offset, + module->persistent_size); + +err: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_save); + +int sst_module_runtime_restore(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + int ret = 0; + + dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n", + runtime->id, runtime->persistent_offset, + module->persistent_size); + + mutex_lock(&dsp->mutex); + + if (!context->buffer) { + dev_info(dsp->dev, "no context buffer need to restore!\n"); + goto err; + } + + if (dsp->fw_use_dma) { + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) + goto err; + + ret = sst_dsp_dma_copyto(dsp, + dsp->addr.lpe_base + runtime->persistent_offset, + context->dma_buffer, module->persistent_size); + sst_dsp_dma_put_channel(dsp); + if (ret < 0) { + dev_err(dsp->dev, "error: module copy failed\n"); + goto err; + } + } else + sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset, + context->buffer, module->persistent_size); + + dma_free_coherent(dsp->dma_dev, module->persistent_size, + context->buffer, context->dma_buffer); + context->buffer = NULL; + +err: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_restore); + +/* register a DSP memory block for use with FW based modules */ +struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, + u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index, + void *private) +{ + struct sst_mem_block *block; + + block = kzalloc(sizeof(*block), GFP_KERNEL); + if (block == NULL) + return NULL; + + block->offset = offset; + block->size = size; + block->index = index; + block->type = type; + block->dsp = dsp; + block->private = private; + block->ops = ops; + + mutex_lock(&dsp->mutex); + list_add(&block->list, &dsp->free_block_list); + mutex_unlock(&dsp->mutex); + + return block; +} +EXPORT_SYMBOL_GPL(sst_mem_block_register); + +/* unregister all DSP memory blocks */ +void sst_mem_block_unregister_all(struct sst_dsp *dsp) +{ + struct sst_mem_block *block, *tmp; + + mutex_lock(&dsp->mutex); + + /* unregister used blocks */ + list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) { + list_del(&block->list); + kfree(block); + } + + /* unregister free blocks */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + list_del(&block->list); + kfree(block); + } + + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); + +/* allocate scratch buffer blocks */ +int sst_block_alloc_scratch(struct sst_dsp *dsp) +{ + struct sst_module *module; + struct sst_block_allocator ba; + int ret; + + mutex_lock(&dsp->mutex); + + /* calculate required scratch size */ + dsp->scratch_size = 0; + list_for_each_entry(module, &dsp->module_list, list) { + dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n", + module->id, module->scratch_size); + if (dsp->scratch_size < module->scratch_size) + dsp->scratch_size = module->scratch_size; + } + + dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n", + dsp->scratch_size); + + if (dsp->scratch_size == 0) { + dev_info(dsp->dev, "no modules need scratch buffer\n"); + mutex_unlock(&dsp->mutex); + return 0; + } + + /* allocate blocks for module scratch buffers */ + dev_dbg(dsp->dev, "allocating scratch blocks\n"); + + ba.size = dsp->scratch_size; + ba.type = SST_MEM_DRAM; + + /* do we need to allocate at fixed offset */ + if (dsp->scratch_offset != 0) { + + dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n", + ba.size, ba.type, ba.offset); + + ba.offset = dsp->scratch_offset; + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list); + + } else { + dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n", + ba.size, ba.type); + + ba.offset = 0; + ret = block_alloc(dsp, &ba, &dsp->scratch_block_list); + } + if (ret < 0) { + dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); + mutex_unlock(&dsp->mutex); + return ret; + } + + ret = block_list_prepare(dsp, &dsp->scratch_block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: scratch block prepare failed\n"); + mutex_unlock(&dsp->mutex); + return ret; + } + + /* assign the same offset of scratch to each module */ + dsp->scratch_offset = ba.offset; + mutex_unlock(&dsp->mutex); + return dsp->scratch_size; +} +EXPORT_SYMBOL_GPL(sst_block_alloc_scratch); + +/* free all scratch blocks */ +void sst_block_free_scratch(struct sst_dsp *dsp) +{ + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &dsp->scratch_block_list); + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_block_free_scratch); + +/* get a module from it's unique ID */ +struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) +{ + struct sst_module *module; + + mutex_lock(&dsp->mutex); + + list_for_each_entry(module, &dsp->module_list, list) { + if (module->id == id) { + mutex_unlock(&dsp->mutex); + return module; + } + } + + mutex_unlock(&dsp->mutex); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_module_get_from_id); + +struct sst_module_runtime *sst_module_runtime_get_from_id( + struct sst_module *module, u32 id) +{ + struct sst_module_runtime *runtime; + struct sst_dsp *dsp = module->dsp; + + mutex_lock(&dsp->mutex); + + list_for_each_entry(runtime, &module->runtime_list, list) { + if (runtime->id == id) { + mutex_unlock(&dsp->mutex); + return runtime; + } + } + + mutex_unlock(&dsp->mutex); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id); + +/* returns block address in DSP address space */ +u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, + enum sst_mem_type type) +{ + switch (type) { + case SST_MEM_IRAM: + return offset - dsp->addr.iram_offset + + dsp->addr.dsp_iram_offset; + case SST_MEM_DRAM: + return offset - dsp->addr.dram_offset + + dsp->addr.dsp_dram_offset; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(sst_dsp_get_offset); diff --git a/sound/soc/intel/sst-acpi.c b/sound/soc/intel/sst-acpi.c deleted file mode 100644 index b3d8456..0000000 --- a/sound/soc/intel/sst-acpi.c +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Intel SST loader on ACPI systems - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include - -#include "sst-dsp.h" - -#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000 -#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000 -#define SST_LPT_DSP_DMA_SIZE (1024 - 1) - -/* Descriptor for SST ASoC machine driver */ -struct sst_acpi_mach { - /* ACPI ID for the matching machine driver. Audio codec for instance */ - const u8 id[ACPI_ID_LEN]; - /* machine driver name */ - const char *drv_name; - /* firmware file name */ - const char *fw_filename; -}; - -/* Descriptor for setting up SST platform data */ -struct sst_acpi_desc { - const char *drv_name; - struct sst_acpi_mach *machines; - /* Platform resource indexes. Must set to -1 if not used */ - int resindex_lpe_base; - int resindex_pcicfg_base; - int resindex_fw_base; - int irqindex_host_ipc; - int resindex_dma_base; - /* Unique number identifying the SST core on platform */ - int sst_id; - /* DMA only valid when resindex_dma_base != -1*/ - int dma_engine; - int dma_size; -}; - -struct sst_acpi_priv { - struct platform_device *pdev_mach; - struct platform_device *pdev_pcm; - struct sst_pdata sst_pdata; - struct sst_acpi_desc *desc; - struct sst_acpi_mach *mach; -}; - -static void sst_acpi_fw_cb(const struct firmware *fw, void *context) -{ - struct platform_device *pdev = context; - struct device *dev = &pdev->dev; - struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); - struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; - struct sst_acpi_desc *desc = sst_acpi->desc; - struct sst_acpi_mach *mach = sst_acpi->mach; - - sst_pdata->fw = fw; - if (!fw) { - dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename); - return; - } - - /* register PCM and DAI driver */ - sst_acpi->pdev_pcm = - platform_device_register_data(dev, desc->drv_name, -1, - sst_pdata, sizeof(*sst_pdata)); - if (IS_ERR(sst_acpi->pdev_pcm)) { - dev_err(dev, "Cannot register device %s. Error %d\n", - desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm)); - } - - return; -} - -static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, - void *context, void **ret) -{ - *(bool *)context = true; - return AE_OK; -} - -static struct sst_acpi_mach *sst_acpi_find_machine( - struct sst_acpi_mach *machines) -{ - struct sst_acpi_mach *mach; - bool found = false; - - for (mach = machines; mach->id[0]; mach++) - if (ACPI_SUCCESS(acpi_get_devices(mach->id, - sst_acpi_mach_match, - &found, NULL)) && found) - return mach; - - return NULL; -} - -static int sst_acpi_probe(struct platform_device *pdev) -{ - const struct acpi_device_id *id; - struct device *dev = &pdev->dev; - struct sst_acpi_priv *sst_acpi; - struct sst_pdata *sst_pdata; - struct sst_acpi_mach *mach; - struct sst_acpi_desc *desc; - struct resource *mmio; - int ret = 0; - - sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL); - if (sst_acpi == NULL) - return -ENOMEM; - - id = acpi_match_device(dev->driver->acpi_match_table, dev); - if (!id) - return -ENODEV; - - desc = (struct sst_acpi_desc *)id->driver_data; - mach = sst_acpi_find_machine(desc->machines); - if (mach == NULL) { - dev_err(dev, "No matching ASoC machine driver found\n"); - return -ENODEV; - } - - sst_pdata = &sst_acpi->sst_pdata; - sst_pdata->id = desc->sst_id; - sst_pdata->dma_dev = dev; - sst_acpi->desc = desc; - sst_acpi->mach = mach; - - if (desc->resindex_dma_base >= 0) { - sst_pdata->dma_engine = desc->dma_engine; - sst_pdata->dma_base = desc->resindex_dma_base; - sst_pdata->dma_size = desc->dma_size; - } - - if (desc->irqindex_host_ipc >= 0) - sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc); - - if (desc->resindex_lpe_base >= 0) { - mmio = platform_get_resource(pdev, IORESOURCE_MEM, - desc->resindex_lpe_base); - if (mmio) { - sst_pdata->lpe_base = mmio->start; - sst_pdata->lpe_size = resource_size(mmio); - } - } - - if (desc->resindex_pcicfg_base >= 0) { - mmio = platform_get_resource(pdev, IORESOURCE_MEM, - desc->resindex_pcicfg_base); - if (mmio) { - sst_pdata->pcicfg_base = mmio->start; - sst_pdata->pcicfg_size = resource_size(mmio); - } - } - - if (desc->resindex_fw_base >= 0) { - mmio = platform_get_resource(pdev, IORESOURCE_MEM, - desc->resindex_fw_base); - if (mmio) { - sst_pdata->fw_base = mmio->start; - sst_pdata->fw_size = resource_size(mmio); - } - } - - platform_set_drvdata(pdev, sst_acpi); - - /* register machine driver */ - sst_acpi->pdev_mach = - platform_device_register_data(dev, mach->drv_name, -1, - sst_pdata, sizeof(*sst_pdata)); - if (IS_ERR(sst_acpi->pdev_mach)) - return PTR_ERR(sst_acpi->pdev_mach); - - /* continue SST probing after firmware is loaded */ - ret = request_firmware_nowait(THIS_MODULE, true, mach->fw_filename, - dev, GFP_KERNEL, pdev, sst_acpi_fw_cb); - if (ret) - platform_device_unregister(sst_acpi->pdev_mach); - - return ret; -} - -static int sst_acpi_remove(struct platform_device *pdev) -{ - struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); - struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; - - platform_device_unregister(sst_acpi->pdev_mach); - if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm)) - platform_device_unregister(sst_acpi->pdev_pcm); - release_firmware(sst_pdata->fw); - - return 0; -} - -static struct sst_acpi_mach haswell_machines[] = { - { "INT33CA", "haswell-audio", "intel/IntcSST1.bin" }, - {} -}; - -static struct sst_acpi_desc sst_acpi_haswell_desc = { - .drv_name = "haswell-pcm-audio", - .machines = haswell_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_fw_base = -1, - .irqindex_host_ipc = 0, - .sst_id = SST_DEV_ID_LYNX_POINT, - .dma_engine = SST_DMA_TYPE_DW, - .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET, - .dma_size = SST_LPT_DSP_DMA_SIZE, -}; - -static struct sst_acpi_mach broadwell_machines[] = { - { "INT343A", "broadwell-audio", "intel/IntcSST2.bin" }, - {} -}; - -static struct sst_acpi_desc sst_acpi_broadwell_desc = { - .drv_name = "haswell-pcm-audio", - .machines = broadwell_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_fw_base = -1, - .irqindex_host_ipc = 0, - .sst_id = SST_DEV_ID_WILDCAT_POINT, - .dma_engine = SST_DMA_TYPE_DW, - .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET, - .dma_size = SST_LPT_DSP_DMA_SIZE, -}; - -static struct sst_acpi_mach baytrail_machines[] = { - { "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-48kHz_i2s_master" }, - { "193C9890", "byt-max98090", "intel/fw_sst_0f28.bin-48kHz_i2s_master" }, - {} -}; - -static struct sst_acpi_desc sst_acpi_baytrail_desc = { - .drv_name = "baytrail-pcm-audio", - .machines = baytrail_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_fw_base = 2, - .irqindex_host_ipc = 5, - .sst_id = SST_DEV_ID_BYT, - .resindex_dma_base = -1, -}; - -static struct acpi_device_id sst_acpi_match[] = { - { "INT33C8", (unsigned long)&sst_acpi_haswell_desc }, - { "INT3438", (unsigned long)&sst_acpi_broadwell_desc }, - { "80860F28", (unsigned long)&sst_acpi_baytrail_desc }, - { } -}; -MODULE_DEVICE_TABLE(acpi, sst_acpi_match); - -static struct platform_driver sst_acpi_driver = { - .probe = sst_acpi_probe, - .remove = sst_acpi_remove, - .driver = { - .name = "sst-acpi", - .acpi_match_table = ACPI_PTR(sst_acpi_match), - }, -}; -module_platform_driver(sst_acpi_driver); - -MODULE_AUTHOR("Jarkko Nikula "); -MODULE_DESCRIPTION("Intel SST loader on ACPI systems"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/sst-dsp-priv.h b/sound/soc/intel/sst-dsp-priv.h deleted file mode 100644 index 396d545..0000000 --- a/sound/soc/intel/sst-dsp-priv.h +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Intel Smart Sound Technology - * - * Copyright (C) 2013, Intel Corporation. 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 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 __SOUND_SOC_SST_DSP_PRIV_H -#define __SOUND_SOC_SST_DSP_PRIV_H - -#include -#include -#include -#include - -struct sst_mem_block; -struct sst_module; -struct sst_fw; - -/* do we need to remove or keep */ -#define DSP_DRAM_ADDR_OFFSET 0x400000 - -/* - * DSP Operations exported by platform Audio DSP driver. - */ -struct sst_ops { - /* DSP core boot / reset */ - void (*boot)(struct sst_dsp *); - void (*reset)(struct sst_dsp *); - int (*wake)(struct sst_dsp *); - void (*sleep)(struct sst_dsp *); - void (*stall)(struct sst_dsp *); - - /* Shim IO */ - void (*write)(void __iomem *addr, u32 offset, u32 value); - u32 (*read)(void __iomem *addr, u32 offset); - void (*write64)(void __iomem *addr, u32 offset, u64 value); - u64 (*read64)(void __iomem *addr, u32 offset); - - /* DSP I/DRAM IO */ - void (*ram_read)(struct sst_dsp *sst, void *dest, void __iomem *src, - size_t bytes); - void (*ram_write)(struct sst_dsp *sst, void __iomem *dest, void *src, - size_t bytes); - - void (*dump)(struct sst_dsp *); - - /* IRQ handlers */ - irqreturn_t (*irq_handler)(int irq, void *context); - - /* SST init and free */ - int (*init)(struct sst_dsp *sst, struct sst_pdata *pdata); - void (*free)(struct sst_dsp *sst); - - /* FW module parser/loader */ - int (*parse_fw)(struct sst_fw *sst_fw); -}; - -/* - * Audio DSP memory offsets and addresses. - */ -struct sst_addr { - u32 lpe_base; - u32 shim_offset; - u32 iram_offset; - u32 dram_offset; - u32 dsp_iram_offset; - u32 dsp_dram_offset; - void __iomem *lpe; - void __iomem *shim; - void __iomem *pci_cfg; - void __iomem *fw_ext; -}; - -/* - * Audio DSP Mailbox configuration. - */ -struct sst_mailbox { - void __iomem *in_base; - void __iomem *out_base; - size_t in_size; - size_t out_size; -}; - -/* - * Audio DSP memory block types. - */ -enum sst_mem_type { - SST_MEM_IRAM = 0, - SST_MEM_DRAM = 1, - SST_MEM_ANY = 2, - SST_MEM_CACHE= 3, -}; - -/* - * Audio DSP Generic Firmware File. - * - * SST Firmware files can consist of 1..N modules. This generic structure is - * used to manage each firmware file and it's modules regardless of SST firmware - * type. A SST driver may load multiple FW files. - */ -struct sst_fw { - struct sst_dsp *dsp; - - /* base addresses of FW file data */ - dma_addr_t dmable_fw_paddr; /* physical address of fw data */ - void *dma_buf; /* virtual address of fw data */ - u32 size; /* size of fw data */ - - /* lists */ - struct list_head list; /* DSP list of FW */ - struct list_head module_list; /* FW list of modules */ - - void *private; /* core doesn't touch this */ -}; - -/* - * Audio DSP Generic Module Template. - * - * Used to define and register a new FW module. This data is extracted from - * FW module header information. - */ -struct sst_module_template { - u32 id; - u32 entry; /* entry point */ - u32 scratch_size; - u32 persistent_size; -}; - -/* - * Block Allocator - Used to allocate blocks of DSP memory. - */ -struct sst_block_allocator { - u32 id; - u32 offset; - int size; - enum sst_mem_type type; -}; - -/* - * Runtime Module Instance - A module object can be instanciated multiple - * times within the DSP FW. - */ -struct sst_module_runtime { - struct sst_dsp *dsp; - int id; - struct sst_module *module; /* parent module we belong too */ - - u32 persistent_offset; /* private memory offset */ - void *private; - - struct list_head list; - struct list_head block_list; /* list of blocks used */ -}; - -/* - * Runtime Module Context - The runtime context must be manually stored by the - * driver prior to enter S3 and restored after leaving S3. This should really be - * part of the memory context saved by the enter D3 message IPC ??? - */ -struct sst_module_runtime_context { - dma_addr_t dma_buffer; - u32 *buffer; -}; - -/* - * Audio DSP Module State - */ -enum sst_module_state { - SST_MODULE_STATE_UNLOADED = 0, /* default state */ - SST_MODULE_STATE_LOADED, - SST_MODULE_STATE_INITIALIZED, /* and inactive */ - SST_MODULE_STATE_ACTIVE, -}; - -/* - * Audio DSP Generic Module. - * - * Each Firmware file can consist of 1..N modules. A module can span multiple - * ADSP memory blocks. The simplest FW will be a file with 1 module. A module - * can be instanciated multiple times in the DSP. - */ -struct sst_module { - struct sst_dsp *dsp; - struct sst_fw *sst_fw; /* parent FW we belong too */ - - /* module configuration */ - u32 id; - u32 entry; /* module entry point */ - s32 offset; /* module offset in firmware file */ - u32 size; /* module size */ - u32 scratch_size; /* global scratch memory required */ - u32 persistent_size; /* private memory required */ - enum sst_mem_type type; /* destination memory type */ - u32 data_offset; /* offset in ADSP memory space */ - void *data; /* module data */ - - /* runtime */ - u32 usage_count; /* can be unloaded if count == 0 */ - void *private; /* core doesn't touch this */ - - /* lists */ - struct list_head block_list; /* Module list of blocks in use */ - struct list_head list; /* DSP list of modules */ - struct list_head list_fw; /* FW list of modules */ - struct list_head runtime_list; /* list of runtime module objects*/ - - /* state */ - enum sst_module_state state; -}; - -/* - * SST Memory Block operations. - */ -struct sst_block_ops { - int (*enable)(struct sst_mem_block *block); - int (*disable)(struct sst_mem_block *block); -}; - -/* - * SST Generic Memory Block. - * - * SST ADP memory has multiple IRAM and DRAM blocks. Some ADSP blocks can be - * power gated. - */ -struct sst_mem_block { - struct sst_dsp *dsp; - struct sst_module *module; /* module that uses this block */ - - /* block config */ - u32 offset; /* offset from base */ - u32 size; /* block size */ - u32 index; /* block index 0..N */ - enum sst_mem_type type; /* block memory type IRAM/DRAM */ - struct sst_block_ops *ops; /* block operations, if any */ - - /* block status */ - u32 bytes_used; /* bytes in use by modules */ - void *private; /* generic core does not touch this */ - int users; /* number of modules using this block */ - - /* block lists */ - struct list_head module_list; /* Module list of blocks */ - struct list_head list; /* Map list of free/used blocks */ -}; - -/* - * Generic SST Shim Interface. - */ -struct sst_dsp { - - /* runtime */ - struct sst_dsp_device *sst_dev; - spinlock_t spinlock; /* IPC locking */ - struct mutex mutex; /* DSP FW lock */ - struct device *dev; - struct device *dma_dev; - void *thread_context; - int irq; - u32 id; - - /* list of free and used ADSP memory blocks */ - struct list_head used_block_list; - struct list_head free_block_list; - - /* operations */ - struct sst_ops *ops; - - /* debug FS */ - struct dentry *debugfs_root; - - /* base addresses */ - struct sst_addr addr; - - /* mailbox */ - struct sst_mailbox mailbox; - - /* SST FW files loaded and their modules */ - struct list_head module_list; - struct list_head fw_list; - - /* scratch buffer */ - struct list_head scratch_block_list; - u32 scratch_offset; - u32 scratch_size; - - /* platform data */ - struct sst_pdata *pdata; - - /* DMA FW loading */ - struct sst_dma *dma; - bool fw_use_dma; -}; - -/* Size optimised DRAM/IRAM memcpy */ -static inline void sst_dsp_write(struct sst_dsp *sst, void *src, - u32 dest_offset, size_t bytes) -{ - sst->ops->ram_write(sst, sst->addr.lpe + dest_offset, src, bytes); -} - -static inline void sst_dsp_read(struct sst_dsp *sst, void *dest, - u32 src_offset, size_t bytes) -{ - sst->ops->ram_read(sst, dest, sst->addr.lpe + src_offset, bytes); -} - -static inline void *sst_dsp_get_thread_context(struct sst_dsp *sst) -{ - return sst->thread_context; -} - -/* Create/Free FW files - can contain multiple modules */ -struct sst_fw *sst_fw_new(struct sst_dsp *dsp, - const struct firmware *fw, void *private); -void sst_fw_free(struct sst_fw *sst_fw); -void sst_fw_free_all(struct sst_dsp *dsp); -int sst_fw_reload(struct sst_fw *sst_fw); -void sst_fw_unload(struct sst_fw *sst_fw); - -/* Create/Free firmware modules */ -struct sst_module *sst_module_new(struct sst_fw *sst_fw, - struct sst_module_template *template, void *private); -void sst_module_free(struct sst_module *module); -struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); -int sst_module_alloc_blocks(struct sst_module *module); -int sst_module_free_blocks(struct sst_module *module); - -/* Create/Free firmware module runtime instances */ -struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, - int id, void *private); -void sst_module_runtime_free(struct sst_module_runtime *runtime); -struct sst_module_runtime *sst_module_runtime_get_from_id( - struct sst_module *module, u32 id); -int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, - int offset); -int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime); -int sst_module_runtime_save(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context); -int sst_module_runtime_restore(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context); - -/* generic block allocation */ -int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list); -int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list); - -/* scratch allocation */ -int sst_block_alloc_scratch(struct sst_dsp *dsp); -void sst_block_free_scratch(struct sst_dsp *dsp); - -/* Register the DSPs memory blocks - would be nice to read from ACPI */ -struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, - u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index, - void *private); -void sst_mem_block_unregister_all(struct sst_dsp *dsp); - -/* Create/Free DMA resources */ -int sst_dma_new(struct sst_dsp *sst); -void sst_dma_free(struct sst_dma *dma); - -u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, - enum sst_mem_type type); -#endif diff --git a/sound/soc/intel/sst-dsp.c b/sound/soc/intel/sst-dsp.c deleted file mode 100644 index 64e9421..0000000 --- a/sound/soc/intel/sst-dsp.c +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Intel Smart Sound Technology (SST) DSP Core Driver - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include - -#include "sst-dsp.h" -#include "sst-dsp-priv.h" - -#define CREATE_TRACE_POINTS -#include - -/* Internal generic low-level SST IO functions - can be overidden */ -void sst_shim32_write(void __iomem *addr, u32 offset, u32 value) -{ - writel(value, addr + offset); -} -EXPORT_SYMBOL_GPL(sst_shim32_write); - -u32 sst_shim32_read(void __iomem *addr, u32 offset) -{ - return readl(addr + offset); -} -EXPORT_SYMBOL_GPL(sst_shim32_read); - -void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value) -{ - memcpy_toio(addr + offset, &value, sizeof(value)); -} -EXPORT_SYMBOL_GPL(sst_shim32_write64); - -u64 sst_shim32_read64(void __iomem *addr, u32 offset) -{ - u64 val; - - memcpy_fromio(&val, addr + offset, sizeof(val)); - return val; -} -EXPORT_SYMBOL_GPL(sst_shim32_read64); - -static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest, - u32 *src, size_t bytes) -{ - int i, words = bytes >> 2; - - for (i = 0; i < words; i++) - writel(src[i], dest + i); -} - -static inline void _sst_memcpy_fromio_32(u32 *dest, - const volatile __iomem u32 *src, size_t bytes) -{ - int i, words = bytes >> 2; - - for (i = 0; i < words; i++) - dest[i] = readl(src + i); -} - -void sst_memcpy_toio_32(struct sst_dsp *sst, - void __iomem *dest, void *src, size_t bytes) -{ - _sst_memcpy_toio_32(dest, src, bytes); -} -EXPORT_SYMBOL_GPL(sst_memcpy_toio_32); - -void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest, - void __iomem *src, size_t bytes) -{ - _sst_memcpy_fromio_32(dest, src, bytes); -} -EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32); - -/* Public API */ -void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value) -{ - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - sst->ops->write(sst->addr.shim, offset, value); - spin_unlock_irqrestore(&sst->spinlock, flags); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_write); - -u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset) -{ - unsigned long flags; - u32 val; - - spin_lock_irqsave(&sst->spinlock, flags); - val = sst->ops->read(sst->addr.shim, offset); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return val; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_read); - -void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value) -{ - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - sst->ops->write64(sst->addr.shim, offset, value); - spin_unlock_irqrestore(&sst->spinlock, flags); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_write64); - -u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset) -{ - unsigned long flags; - u64 val; - - spin_lock_irqsave(&sst->spinlock, flags); - val = sst->ops->read64(sst->addr.shim, offset); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return val; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_read64); - -void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value) -{ - sst->ops->write(sst->addr.shim, offset, value); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_write_unlocked); - -u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset) -{ - return sst->ops->read(sst->addr.shim, offset); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_read_unlocked); - -void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value) -{ - sst->ops->write64(sst->addr.shim, offset, value); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_write64_unlocked); - -u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset) -{ - return sst->ops->read64(sst->addr.shim, offset); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_read64_unlocked); - -int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, - u32 mask, u32 value) -{ - bool change; - unsigned int old, new; - u32 ret; - - ret = sst_dsp_shim_read_unlocked(sst, offset); - - old = ret; - new = (old & (~mask)) | (value & mask); - - change = (old != new); - if (change) - sst_dsp_shim_write_unlocked(sst, offset, new); - - return change; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_unlocked); - -int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value) -{ - bool change; - u64 old, new; - - old = sst_dsp_shim_read64_unlocked(sst, offset); - - new = (old & (~mask)) | (value & mask); - - change = (old != new); - if (change) - sst_dsp_shim_write64_unlocked(sst, offset, new); - - return change; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64_unlocked); - -int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, - u32 mask, u32 value) -{ - unsigned long flags; - bool change; - - spin_lock_irqsave(&sst->spinlock, flags); - change = sst_dsp_shim_update_bits_unlocked(sst, offset, mask, value); - spin_unlock_irqrestore(&sst->spinlock, flags); - return change; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits); - -int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value) -{ - unsigned long flags; - bool change; - - spin_lock_irqsave(&sst->spinlock, flags); - change = sst_dsp_shim_update_bits64_unlocked(sst, offset, mask, value); - spin_unlock_irqrestore(&sst->spinlock, flags); - return change; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64); - -void sst_dsp_dump(struct sst_dsp *sst) -{ - if (sst->ops->dump) - sst->ops->dump(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_dump); - -void sst_dsp_reset(struct sst_dsp *sst) -{ - if (sst->ops->reset) - sst->ops->reset(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_reset); - -int sst_dsp_boot(struct sst_dsp *sst) -{ - if (sst->ops->boot) - sst->ops->boot(sst); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_dsp_boot); - -int sst_dsp_wake(struct sst_dsp *sst) -{ - if (sst->ops->wake) - return sst->ops->wake(sst); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_dsp_wake); - -void sst_dsp_sleep(struct sst_dsp *sst) -{ - if (sst->ops->sleep) - sst->ops->sleep(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_sleep); - -void sst_dsp_stall(struct sst_dsp *sst) -{ - if (sst->ops->stall) - sst->ops->stall(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_stall); - -void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) -{ - sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); - trace_sst_ipc_msg_tx(msg); -} -EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_tx); - -u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp) -{ - u32 msg; - - msg = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); - trace_sst_ipc_msg_rx(msg); - - return msg; -} -EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_rx); - -int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size, - u32 outbox_offset, size_t outbox_size) -{ - sst->mailbox.in_base = sst->addr.lpe + inbox_offset; - sst->mailbox.out_base = sst->addr.lpe + outbox_offset; - sst->mailbox.in_size = inbox_size; - sst->mailbox.out_size = outbox_size; - return 0; -} -EXPORT_SYMBOL_GPL(sst_dsp_mailbox_init); - -void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes) -{ - u32 i; - - trace_sst_ipc_outbox_write(bytes); - - memcpy_toio(sst->mailbox.out_base, message, bytes); - - for (i = 0; i < bytes; i += 4) - trace_sst_ipc_outbox_wdata(i, *(u32 *)(message + i)); -} -EXPORT_SYMBOL_GPL(sst_dsp_outbox_write); - -void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes) -{ - u32 i; - - trace_sst_ipc_outbox_read(bytes); - - memcpy_fromio(message, sst->mailbox.out_base, bytes); - - for (i = 0; i < bytes; i += 4) - trace_sst_ipc_outbox_rdata(i, *(u32 *)(message + i)); -} -EXPORT_SYMBOL_GPL(sst_dsp_outbox_read); - -void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes) -{ - u32 i; - - trace_sst_ipc_inbox_write(bytes); - - memcpy_toio(sst->mailbox.in_base, message, bytes); - - for (i = 0; i < bytes; i += 4) - trace_sst_ipc_inbox_wdata(i, *(u32 *)(message + i)); -} -EXPORT_SYMBOL_GPL(sst_dsp_inbox_write); - -void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) -{ - u32 i; - - trace_sst_ipc_inbox_read(bytes); - - memcpy_fromio(message, sst->mailbox.in_base, bytes); - - for (i = 0; i < bytes; i += 4) - trace_sst_ipc_inbox_rdata(i, *(u32 *)(message + i)); -} -EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); - -struct sst_dsp *sst_dsp_new(struct device *dev, - struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) -{ - struct sst_dsp *sst; - int err; - - dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); - - sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); - if (sst == NULL) - return NULL; - - spin_lock_init(&sst->spinlock); - mutex_init(&sst->mutex); - sst->dev = dev; - sst->dma_dev = pdata->dma_dev; - sst->thread_context = sst_dev->thread_context; - sst->sst_dev = sst_dev; - sst->id = pdata->id; - sst->irq = pdata->irq; - sst->ops = sst_dev->ops; - sst->pdata = pdata; - INIT_LIST_HEAD(&sst->used_block_list); - INIT_LIST_HEAD(&sst->free_block_list); - INIT_LIST_HEAD(&sst->module_list); - INIT_LIST_HEAD(&sst->fw_list); - INIT_LIST_HEAD(&sst->scratch_block_list); - - /* Initialise SST Audio DSP */ - if (sst->ops->init) { - err = sst->ops->init(sst, pdata); - if (err < 0) - return NULL; - } - - /* Register the ISR */ - err = request_threaded_irq(sst->irq, sst->ops->irq_handler, - sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); - if (err) - goto irq_err; - - err = sst_dma_new(sst); - if (err) - dev_warn(dev, "sst_dma_new failed %d\n", err); - - return sst; - -irq_err: - if (sst->ops->free) - sst->ops->free(sst); - - return NULL; -} -EXPORT_SYMBOL_GPL(sst_dsp_new); - -void sst_dsp_free(struct sst_dsp *sst) -{ - free_irq(sst->irq, sst); - if (sst->ops->free) - sst->ops->free(sst); - - sst_dma_free(sst->dma); -} -EXPORT_SYMBOL_GPL(sst_dsp_free); - -/* Module information */ -MODULE_AUTHOR("Liam Girdwood"); -MODULE_DESCRIPTION("Intel SST Core"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h deleted file mode 100644 index 3412474..0000000 --- a/sound/soc/intel/sst-dsp.h +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Intel Smart Sound Technology (SST) Core - * - * Copyright (C) 2013, Intel Corporation. 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 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 __SOUND_SOC_SST_DSP_H -#define __SOUND_SOC_SST_DSP_H - -#include -#include -#include - -/* SST Device IDs */ -#define SST_DEV_ID_LYNX_POINT 0x33C8 -#define SST_DEV_ID_WILDCAT_POINT 0x3438 -#define SST_DEV_ID_BYT 0x0F28 - -/* Supported SST DMA Devices */ -#define SST_DMA_TYPE_DW 1 - -/* autosuspend delay 5s*/ -#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000) - -/* SST Shim register map - * The register naming can differ between products. Some products also - * contain extra functionality. - */ -#define SST_CSR 0x00 -#define SST_PISR 0x08 -#define SST_PIMR 0x10 -#define SST_ISRX 0x18 -#define SST_ISRD 0x20 -#define SST_IMRX 0x28 -#define SST_IMRD 0x30 -#define SST_IPCX 0x38 /* IPC IA -> SST */ -#define SST_IPCD 0x40 /* IPC SST -> IA */ -#define SST_ISRSC 0x48 -#define SST_ISRLPESC 0x50 -#define SST_IMRSC 0x58 -#define SST_IMRLPESC 0x60 -#define SST_IPCSC 0x68 -#define SST_IPCLPESC 0x70 -#define SST_CLKCTL 0x78 -#define SST_CSR2 0x80 -#define SST_LTRC 0xE0 -#define SST_HMDC 0xE8 - -#define SST_SHIM_BEGIN SST_CSR -#define SST_SHIM_END SST_HDMC - -#define SST_DBGO 0xF0 - -#define SST_SHIM_SIZE 0x100 -#define SST_PWMCTRL 0x1000 - -/* SST Shim Register bits - * The register bit naming can differ between products. Some products also - * contain extra functionality. - */ - -/* CSR / CS */ -#define SST_CSR_RST (0x1 << 1) -#define SST_CSR_SBCS0 (0x1 << 2) -#define SST_CSR_SBCS1 (0x1 << 3) -#define SST_CSR_DCS(x) (x << 4) -#define SST_CSR_DCS_MASK (0x7 << 4) -#define SST_CSR_STALL (0x1 << 10) -#define SST_CSR_S0IOCS (0x1 << 21) -#define SST_CSR_S1IOCS (0x1 << 23) -#define SST_CSR_LPCS (0x1 << 31) -#define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS) -#define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1) -#define SST_BYT_CSR_RST (0x1 << 0) -#define SST_BYT_CSR_VECTOR_SEL (0x1 << 1) -#define SST_BYT_CSR_STALL (0x1 << 2) -#define SST_BYT_CSR_PWAITMODE (0x1 << 3) - -/* ISRX / ISC */ -#define SST_ISRX_BUSY (0x1 << 1) -#define SST_ISRX_DONE (0x1 << 0) -#define SST_BYT_ISRX_REQUEST (0x1 << 1) - -/* ISRD / ISD */ -#define SST_ISRD_BUSY (0x1 << 1) -#define SST_ISRD_DONE (0x1 << 0) - -/* IMRX / IMC */ -#define SST_IMRX_BUSY (0x1 << 1) -#define SST_IMRX_DONE (0x1 << 0) -#define SST_BYT_IMRX_REQUEST (0x1 << 1) - -/* IMRD / IMD */ -#define SST_IMRD_DONE (0x1 << 0) -#define SST_IMRD_BUSY (0x1 << 1) -#define SST_IMRD_SSP0 (0x1 << 16) -#define SST_IMRD_DMAC0 (0x1 << 21) -#define SST_IMRD_DMAC1 (0x1 << 22) -#define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1) - -/* IPCX / IPCC */ -#define SST_IPCX_DONE (0x1 << 30) -#define SST_IPCX_BUSY (0x1 << 31) -#define SST_BYT_IPCX_DONE ((u64)0x1 << 62) -#define SST_BYT_IPCX_BUSY ((u64)0x1 << 63) - -/* IPCD */ -#define SST_IPCD_DONE (0x1 << 30) -#define SST_IPCD_BUSY (0x1 << 31) -#define SST_BYT_IPCD_DONE ((u64)0x1 << 62) -#define SST_BYT_IPCD_BUSY ((u64)0x1 << 63) - -/* CLKCTL */ -#define SST_CLKCTL_SMOS(x) (x << 24) -#define SST_CLKCTL_MASK (3 << 24) -#define SST_CLKCTL_DCPLCG (1 << 18) -#define SST_CLKCTL_SCOE1 (1 << 17) -#define SST_CLKCTL_SCOE0 (1 << 16) - -/* CSR2 / CS2 */ -#define SST_CSR2_SDFD_SSP0 (1 << 1) -#define SST_CSR2_SDFD_SSP1 (1 << 2) - -/* LTRC */ -#define SST_LTRC_VAL(x) (x << 0) - -/* HMDC */ -#define SST_HMDC_HDDA0(x) (x << 0) -#define SST_HMDC_HDDA1(x) (x << 7) -#define SST_HMDC_HDDA_E0_CH0 1 -#define SST_HMDC_HDDA_E0_CH1 2 -#define SST_HMDC_HDDA_E0_CH2 4 -#define SST_HMDC_HDDA_E0_CH3 8 -#define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0) -#define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1) -#define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2) -#define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3) -#define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \ - SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3) -#define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \ - SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3) - - -/* SST Vendor Defined Registers and bits */ -#define SST_VDRTCTL0 0xa0 -#define SST_VDRTCTL1 0xa4 -#define SST_VDRTCTL2 0xa8 -#define SST_VDRTCTL3 0xaC - -/* VDRTCTL0 */ -#define SST_VDRTCL0_D3PGD (1 << 0) -#define SST_VDRTCL0_D3SRAMPGD (1 << 1) -#define SST_VDRTCL0_DSRAMPGE_SHIFT 12 -#define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT) -#define SST_VDRTCL0_ISRAMPGE_SHIFT 2 -#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) - -/* VDRTCTL2 */ -#define SST_VDRTCL2_DCLCGE (1 << 1) -#define SST_VDRTCL2_DTCGE (1 << 10) -#define SST_VDRTCL2_APLLSE_MASK (1 << 31) - -/* PMCS */ -#define SST_PMCS 0x84 -#define SST_PMCS_PS_MASK 0x3 - -struct sst_dsp; - -/* - * SST Device. - * - * This structure is populated by the SST core driver. - */ -struct sst_dsp_device { - /* Mandatory fields */ - struct sst_ops *ops; - irqreturn_t (*thread)(int irq, void *context); - void *thread_context; -}; - -/* - * SST Platform Data. - */ -struct sst_pdata { - /* ACPI data */ - u32 lpe_base; - u32 lpe_size; - u32 pcicfg_base; - u32 pcicfg_size; - u32 fw_base; - u32 fw_size; - int irq; - - /* Firmware */ - const struct firmware *fw; - - /* DMA */ - u32 dma_base; - u32 dma_size; - int dma_engine; - struct device *dma_dev; - - /* DSP */ - u32 id; - void *dsp; -}; - -/* Initialization */ -struct sst_dsp *sst_dsp_new(struct device *dev, - struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); -void sst_dsp_free(struct sst_dsp *sst); - -/* SHIM Read / Write */ -void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value); -u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset); -int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, - u32 mask, u32 value); -void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value); -u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset); -int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value); - -/* SHIM Read / Write Unlocked for callers already holding sst lock */ -void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value); -u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset); -int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, - u32 mask, u32 value); -void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value); -u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset); -int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value); - -/* Internal generic low-level SST IO functions - can be overidden */ -void sst_shim32_write(void __iomem *addr, u32 offset, u32 value); -u32 sst_shim32_read(void __iomem *addr, u32 offset); -void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value); -u64 sst_shim32_read64(void __iomem *addr, u32 offset); -void sst_memcpy_toio_32(struct sst_dsp *sst, - void __iomem *dest, void *src, size_t bytes); -void sst_memcpy_fromio_32(struct sst_dsp *sst, - void *dest, void __iomem *src, size_t bytes); - -/* DSP reset & boot */ -void sst_dsp_reset(struct sst_dsp *sst); -int sst_dsp_boot(struct sst_dsp *sst); -int sst_dsp_wake(struct sst_dsp *sst); -void sst_dsp_sleep(struct sst_dsp *sst); -void sst_dsp_stall(struct sst_dsp *sst); - -/* DMA */ -int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id); -void sst_dsp_dma_put_channel(struct sst_dsp *dsp); -int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size); -int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size); - -/* Msg IO */ -void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); -u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp); - -/* Mailbox management */ -int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset, - size_t inbox_size, u32 outbox_offset, size_t outbox_size); -void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes); - -/* Debug */ -void sst_dsp_dump(struct sst_dsp *sst); - -#endif diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c deleted file mode 100644 index b5659ec..0000000 --- a/sound/soc/intel/sst-firmware.c +++ /dev/null @@ -1,1201 +0,0 @@ -/* - * Intel SST Firmware Loader - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* supported DMA engine drivers */ -#include -#include - -#include -#include - -#include "sst-dsp.h" -#include "sst-dsp-priv.h" - -#define SST_DMA_RESOURCES 2 -#define SST_DSP_DMA_MAX_BURST 0x3 -#define SST_HSW_BLOCK_ANY 0xffffffff - -#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000 - -struct sst_dma { - struct sst_dsp *sst; - - struct dw_dma_chip *chip; - - struct dma_async_tx_descriptor *desc; - struct dma_chan *ch; -}; - -static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) -{ - /* __iowrite32_copy use 32bit size values so divide by 4 */ - __iowrite32_copy((void *)dest, src, bytes/4); -} - -static void sst_dma_transfer_complete(void *arg) -{ - struct sst_dsp *sst = (struct sst_dsp *)arg; - - dev_dbg(sst->dev, "DMA: callback\n"); -} - -static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size) -{ - struct dma_async_tx_descriptor *desc; - struct sst_dma *dma = sst->dma; - - if (dma->ch == NULL) { - dev_err(sst->dev, "error: no DMA channel\n"); - return -ENODEV; - } - - dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n", - (unsigned long)src_addr, (unsigned long)dest_addr, size); - - desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr, - src_addr, size, DMA_CTRL_ACK); - if (!desc){ - dev_err(sst->dev, "error: dma prep memcpy failed\n"); - return -EINVAL; - } - - desc->callback = sst_dma_transfer_complete; - desc->callback_param = sst; - - desc->tx_submit(desc); - dma_wait_for_async_tx(desc); - - return 0; -} - -/* copy to DSP */ -int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size) -{ - return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP, - src_addr, size); -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto); - -/* copy from DSP */ -int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size) -{ - return sst_dsp_dma_copy(sst, dest_addr, - src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size); -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom); - -/* remove module from memory - callers hold locks */ -static void block_list_remove(struct sst_dsp *dsp, - struct list_head *block_list) -{ - struct sst_mem_block *block, *tmp; - int err; - - /* disable each block */ - list_for_each_entry(block, block_list, module_list) { - - if (block->ops && block->ops->disable) { - err = block->ops->disable(block); - if (err < 0) - dev_err(dsp->dev, - "error: cant disable block %d:%d\n", - block->type, block->index); - } - } - - /* mark each block as free */ - list_for_each_entry_safe(block, tmp, block_list, module_list) { - list_del(&block->module_list); - list_move(&block->list, &dsp->free_block_list); - dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - } -} - -/* prepare the memory block to receive data from host - callers hold locks */ -static int block_list_prepare(struct sst_dsp *dsp, - struct list_head *block_list) -{ - struct sst_mem_block *block; - int ret = 0; - - /* enable each block so that's it'e ready for data */ - list_for_each_entry(block, block_list, module_list) { - - if (block->ops && block->ops->enable && !block->users) { - ret = block->ops->enable(block); - if (ret < 0) { - dev_err(dsp->dev, - "error: cant disable block %d:%d\n", - block->type, block->index); - goto err; - } - } - } - return ret; - -err: - list_for_each_entry(block, block_list, module_list) { - if (block->ops && block->ops->disable) - block->ops->disable(block); - } - return ret; -} - -static struct dw_dma_platform_data dw_pdata = { - .is_private = 1, - .chan_allocation_order = CHAN_ALLOCATION_ASCENDING, - .chan_priority = CHAN_PRIORITY_ASCENDING, -}; - -static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem, - int irq) -{ - struct dw_dma_chip *chip; - int err; - - chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return ERR_PTR(-ENOMEM); - - chip->irq = irq; - chip->regs = devm_ioremap_resource(dev, mem); - if (IS_ERR(chip->regs)) - return ERR_CAST(chip->regs); - - err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); - if (err) - return ERR_PTR(err); - - chip->dev = dev; - err = dw_dma_probe(chip, &dw_pdata); - if (err) - return ERR_PTR(err); - - return chip; -} - -static void dw_remove(struct dw_dma_chip *chip) -{ - dw_dma_remove(chip); -} - -static bool dma_chan_filter(struct dma_chan *chan, void *param) -{ - struct sst_dsp *dsp = (struct sst_dsp *)param; - - return chan->device->dev == dsp->dma_dev; -} - -int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) -{ - struct sst_dma *dma = dsp->dma; - struct dma_slave_config slave; - dma_cap_mask_t mask; - int ret; - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - dma_cap_set(DMA_MEMCPY, mask); - - dma->ch = dma_request_channel(mask, dma_chan_filter, dsp); - if (dma->ch == NULL) { - dev_err(dsp->dev, "error: DMA request channel failed\n"); - return -EIO; - } - - memset(&slave, 0, sizeof(slave)); - slave.direction = DMA_MEM_TO_DEV; - slave.src_addr_width = - slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST; - - ret = dmaengine_slave_config(dma->ch, &slave); - if (ret) { - dev_err(dsp->dev, "error: unable to set DMA slave config %d\n", - ret); - dma_release_channel(dma->ch); - dma->ch = NULL; - } - - return ret; -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel); - -void sst_dsp_dma_put_channel(struct sst_dsp *dsp) -{ - struct sst_dma *dma = dsp->dma; - - if (!dma->ch) - return; - - dma_release_channel(dma->ch); - dma->ch = NULL; -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel); - -int sst_dma_new(struct sst_dsp *sst) -{ - struct sst_pdata *sst_pdata = sst->pdata; - struct sst_dma *dma; - struct resource mem; - const char *dma_dev_name; - int ret = 0; - - /* configure the correct platform data for whatever DMA engine - * is attached to the ADSP IP. */ - switch (sst->pdata->dma_engine) { - case SST_DMA_TYPE_DW: - dma_dev_name = "dw_dmac"; - break; - default: - dev_err(sst->dev, "error: invalid DMA engine %d\n", - sst->pdata->dma_engine); - return -EINVAL; - } - - dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL); - if (!dma) - return -ENOMEM; - - dma->sst = sst; - - memset(&mem, 0, sizeof(mem)); - - mem.start = sst->addr.lpe_base + sst_pdata->dma_base; - mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1; - mem.flags = IORESOURCE_MEM; - - /* now register DMA engine device */ - dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq); - if (IS_ERR(dma->chip)) { - dev_err(sst->dev, "error: DMA device register failed\n"); - ret = PTR_ERR(dma->chip); - goto err_dma_dev; - } - - sst->dma = dma; - sst->fw_use_dma = true; - return 0; - -err_dma_dev: - devm_kfree(sst->dev, dma); - return ret; -} -EXPORT_SYMBOL(sst_dma_new); - -void sst_dma_free(struct sst_dma *dma) -{ - - if (dma == NULL) - return; - - if (dma->ch) - dma_release_channel(dma->ch); - - if (dma->chip) - dw_remove(dma->chip); - -} -EXPORT_SYMBOL(sst_dma_free); - -/* create new generic firmware object */ -struct sst_fw *sst_fw_new(struct sst_dsp *dsp, - const struct firmware *fw, void *private) -{ - struct sst_fw *sst_fw; - int err; - - if (!dsp->ops->parse_fw) - return NULL; - - sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL); - if (sst_fw == NULL) - return NULL; - - sst_fw->dsp = dsp; - sst_fw->private = private; - sst_fw->size = fw->size; - - /* allocate DMA buffer to store FW data */ - sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size, - &sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL); - if (!sst_fw->dma_buf) { - dev_err(dsp->dev, "error: DMA alloc failed\n"); - kfree(sst_fw); - return NULL; - } - - /* copy FW data to DMA-able memory */ - memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); - - if (dsp->fw_use_dma) { - err = sst_dsp_dma_get_channel(dsp, 0); - if (err < 0) - goto chan_err; - } - - /* call core specific FW paser to load FW data into DSP */ - err = dsp->ops->parse_fw(sst_fw); - if (err < 0) { - dev_err(dsp->dev, "error: parse fw failed %d\n", err); - goto parse_err; - } - - if (dsp->fw_use_dma) - sst_dsp_dma_put_channel(dsp); - - mutex_lock(&dsp->mutex); - list_add(&sst_fw->list, &dsp->fw_list); - mutex_unlock(&dsp->mutex); - - return sst_fw; - -parse_err: - if (dsp->fw_use_dma) - sst_dsp_dma_put_channel(dsp); -chan_err: - dma_free_coherent(dsp->dma_dev, sst_fw->size, - sst_fw->dma_buf, - sst_fw->dmable_fw_paddr); - sst_fw->dma_buf = NULL; - kfree(sst_fw); - return NULL; -} -EXPORT_SYMBOL_GPL(sst_fw_new); - -int sst_fw_reload(struct sst_fw *sst_fw) -{ - struct sst_dsp *dsp = sst_fw->dsp; - int ret; - - dev_dbg(dsp->dev, "reloading firmware\n"); - - /* call core specific FW paser to load FW data into DSP */ - ret = dsp->ops->parse_fw(sst_fw); - if (ret < 0) - dev_err(dsp->dev, "error: parse fw failed %d\n", ret); - - return ret; -} -EXPORT_SYMBOL_GPL(sst_fw_reload); - -void sst_fw_unload(struct sst_fw *sst_fw) -{ - struct sst_dsp *dsp = sst_fw->dsp; - struct sst_module *module, *mtmp; - struct sst_module_runtime *runtime, *rtmp; - - dev_dbg(dsp->dev, "unloading firmware\n"); - - mutex_lock(&dsp->mutex); - - /* check module by module */ - list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) { - if (module->sst_fw == sst_fw) { - - /* remove runtime modules */ - list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) { - - block_list_remove(dsp, &runtime->block_list); - list_del(&runtime->list); - kfree(runtime); - } - - /* now remove the module */ - block_list_remove(dsp, &module->block_list); - list_del(&module->list); - kfree(module); - } - } - - /* remove all scratch blocks */ - block_list_remove(dsp, &dsp->scratch_block_list); - - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_fw_unload); - -/* free single firmware object */ -void sst_fw_free(struct sst_fw *sst_fw) -{ - struct sst_dsp *dsp = sst_fw->dsp; - - mutex_lock(&dsp->mutex); - list_del(&sst_fw->list); - mutex_unlock(&dsp->mutex); - - if (sst_fw->dma_buf) - dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, - sst_fw->dmable_fw_paddr); - kfree(sst_fw); -} -EXPORT_SYMBOL_GPL(sst_fw_free); - -/* free all firmware objects */ -void sst_fw_free_all(struct sst_dsp *dsp) -{ - struct sst_fw *sst_fw, *t; - - mutex_lock(&dsp->mutex); - list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { - - list_del(&sst_fw->list); - dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, - sst_fw->dmable_fw_paddr); - kfree(sst_fw); - } - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_fw_free_all); - -/* create a new SST generic module from FW template */ -struct sst_module *sst_module_new(struct sst_fw *sst_fw, - struct sst_module_template *template, void *private) -{ - struct sst_dsp *dsp = sst_fw->dsp; - struct sst_module *sst_module; - - sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL); - if (sst_module == NULL) - return NULL; - - sst_module->id = template->id; - sst_module->dsp = dsp; - sst_module->sst_fw = sst_fw; - sst_module->scratch_size = template->scratch_size; - sst_module->persistent_size = template->persistent_size; - sst_module->entry = template->entry; - sst_module->state = SST_MODULE_STATE_UNLOADED; - - INIT_LIST_HEAD(&sst_module->block_list); - INIT_LIST_HEAD(&sst_module->runtime_list); - - mutex_lock(&dsp->mutex); - list_add(&sst_module->list, &dsp->module_list); - mutex_unlock(&dsp->mutex); - - return sst_module; -} -EXPORT_SYMBOL_GPL(sst_module_new); - -/* free firmware module and remove from available list */ -void sst_module_free(struct sst_module *sst_module) -{ - struct sst_dsp *dsp = sst_module->dsp; - - mutex_lock(&dsp->mutex); - list_del(&sst_module->list); - mutex_unlock(&dsp->mutex); - - kfree(sst_module); -} -EXPORT_SYMBOL_GPL(sst_module_free); - -struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, - int id, void *private) -{ - struct sst_dsp *dsp = module->dsp; - struct sst_module_runtime *runtime; - - runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); - if (runtime == NULL) - return NULL; - - runtime->id = id; - runtime->dsp = dsp; - runtime->module = module; - INIT_LIST_HEAD(&runtime->block_list); - - mutex_lock(&dsp->mutex); - list_add(&runtime->list, &module->runtime_list); - mutex_unlock(&dsp->mutex); - - return runtime; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_new); - -void sst_module_runtime_free(struct sst_module_runtime *runtime) -{ - struct sst_dsp *dsp = runtime->dsp; - - mutex_lock(&dsp->mutex); - list_del(&runtime->list); - mutex_unlock(&dsp->mutex); - - kfree(runtime); -} -EXPORT_SYMBOL_GPL(sst_module_runtime_free); - -static struct sst_mem_block *find_block(struct sst_dsp *dsp, - struct sst_block_allocator *ba) -{ - struct sst_mem_block *block; - - list_for_each_entry(block, &dsp->free_block_list, list) { - if (block->type == ba->type && block->offset == ba->offset) - return block; - } - - return NULL; -} - -/* Block allocator must be on block boundary */ -static int block_alloc_contiguous(struct sst_dsp *dsp, - struct sst_block_allocator *ba, struct list_head *block_list) -{ - struct list_head tmp = LIST_HEAD_INIT(tmp); - struct sst_mem_block *block; - u32 block_start = SST_HSW_BLOCK_ANY; - int size = ba->size, offset = ba->offset; - - while (ba->size > 0) { - - block = find_block(dsp, ba); - if (!block) { - list_splice(&tmp, &dsp->free_block_list); - - ba->size = size; - ba->offset = offset; - return -ENOMEM; - } - - list_move_tail(&block->list, &tmp); - ba->offset += block->size; - ba->size -= block->size; - } - ba->size = size; - ba->offset = offset; - - list_for_each_entry(block, &tmp, list) { - - if (block->offset < block_start) - block_start = block->offset; - - list_add(&block->module_list, block_list); - - dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - } - - list_splice(&tmp, &dsp->used_block_list); - return 0; -} - -/* allocate first free DSP blocks for data - callers hold locks */ -static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list) -{ - struct sst_mem_block *block, *tmp; - int ret = 0; - - if (ba->size == 0) - return 0; - - /* find first free whole blocks that can hold module */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - if (ba->size > block->size) - continue; - - ba->offset = block->offset; - block->bytes_used = ba->size % block->size; - list_add(&block->module_list, block_list); - list_move(&block->list, &dsp->used_block_list); - dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - return 0; - } - - /* then find free multiple blocks that can hold module */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - /* do we span > 1 blocks */ - if (ba->size > block->size) { - - /* align ba to block boundary */ - ba->offset = block->offset; - - ret = block_alloc_contiguous(dsp, ba, block_list); - if (ret == 0) - return ret; - - } - } - - /* not enough free block space */ - return -ENOMEM; -} - -int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list) -{ - int ret; - - dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", - ba->size, ba->offset, ba->type); - - mutex_lock(&dsp->mutex); - - ret = block_alloc(dsp, ba, block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret); - goto out; - } - - /* prepare DSP blocks for module usage */ - ret = block_list_prepare(dsp, block_list); - if (ret < 0) - dev_err(dsp->dev, "error: prepare failed\n"); - -out: - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_alloc_blocks); - -int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list) -{ - mutex_lock(&dsp->mutex); - block_list_remove(dsp, block_list); - mutex_unlock(&dsp->mutex); - return 0; -} -EXPORT_SYMBOL_GPL(sst_free_blocks); - -/* allocate memory blocks for static module addresses - callers hold locks */ -static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list) -{ - struct sst_mem_block *block, *tmp; - struct sst_block_allocator ba_tmp = *ba; - u32 end = ba->offset + ba->size, block_end; - int err; - - /* only IRAM/DRAM blocks are managed */ - if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM) - return 0; - - /* are blocks already attached to this module */ - list_for_each_entry_safe(block, tmp, block_list, module_list) { - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - block_end = block->offset + block->size; - - /* find block that holds section */ - if (ba->offset >= block->offset && end <= block_end) - return 0; - - /* does block span more than 1 section */ - if (ba->offset >= block->offset && ba->offset < block_end) { - - /* align ba to block boundary */ - ba_tmp.size -= block_end - ba->offset; - ba_tmp.offset = block_end; - err = block_alloc_contiguous(dsp, &ba_tmp, block_list); - if (err < 0) - return -ENOMEM; - - /* module already owns blocks */ - return 0; - } - } - - /* find first free blocks that can hold section in free list */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - block_end = block->offset + block->size; - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - /* find block that holds section */ - if (ba->offset >= block->offset && end <= block_end) { - - /* add block */ - list_move(&block->list, &dsp->used_block_list); - list_add(&block->module_list, block_list); - dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - return 0; - } - - /* does block span more than 1 section */ - if (ba->offset >= block->offset && ba->offset < block_end) { - - /* add block */ - list_move(&block->list, &dsp->used_block_list); - list_add(&block->module_list, block_list); - /* align ba to block boundary */ - ba_tmp.size -= block_end - ba->offset; - ba_tmp.offset = block_end; - - err = block_alloc_contiguous(dsp, &ba_tmp, block_list); - if (err < 0) - return -ENOMEM; - - return 0; - } - } - - return -ENOMEM; -} - -/* Load fixed module data into DSP memory blocks */ -int sst_module_alloc_blocks(struct sst_module *module) -{ - struct sst_dsp *dsp = module->dsp; - struct sst_fw *sst_fw = module->sst_fw; - struct sst_block_allocator ba; - int ret; - - memset(&ba, 0, sizeof(ba)); - ba.size = module->size; - ba.type = module->type; - ba.offset = module->offset; - - dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", - ba.size, ba.offset, ba.type); - - mutex_lock(&dsp->mutex); - - /* alloc blocks that includes this section */ - ret = block_alloc_fixed(dsp, &ba, &module->block_list); - if (ret < 0) { - dev_err(dsp->dev, - "error: no free blocks for section at offset 0x%x size 0x%x\n", - module->offset, module->size); - mutex_unlock(&dsp->mutex); - return -ENOMEM; - } - - /* prepare DSP blocks for module copy */ - ret = block_list_prepare(dsp, &module->block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: fw module prepare failed\n"); - goto err; - } - - /* copy partial module data to blocks */ - if (dsp->fw_use_dma) { - ret = sst_dsp_dma_copyto(dsp, - dsp->addr.lpe_base + module->offset, - sst_fw->dmable_fw_paddr + module->data_offset, - module->size); - if (ret < 0) { - dev_err(dsp->dev, "error: module copy failed\n"); - goto err; - } - } else - sst_memcpy32(dsp->addr.lpe + module->offset, module->data, - module->size); - - mutex_unlock(&dsp->mutex); - return ret; - -err: - block_list_remove(dsp, &module->block_list); - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_alloc_blocks); - -/* Unload entire module from DSP memory */ -int sst_module_free_blocks(struct sst_module *module) -{ - struct sst_dsp *dsp = module->dsp; - - mutex_lock(&dsp->mutex); - block_list_remove(dsp, &module->block_list); - mutex_unlock(&dsp->mutex); - return 0; -} -EXPORT_SYMBOL_GPL(sst_module_free_blocks); - -int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, - int offset) -{ - struct sst_dsp *dsp = runtime->dsp; - struct sst_module *module = runtime->module; - struct sst_block_allocator ba; - int ret; - - if (module->persistent_size == 0) - return 0; - - memset(&ba, 0, sizeof(ba)); - ba.size = module->persistent_size; - ba.type = SST_MEM_DRAM; - - mutex_lock(&dsp->mutex); - - /* do we need to allocate at a fixed address ? */ - if (offset != 0) { - - ba.offset = offset; - - dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n", - ba.size, ba.type, ba.offset); - - /* alloc blocks that includes this section */ - ret = block_alloc_fixed(dsp, &ba, &runtime->block_list); - - } else { - dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n", - ba.size, ba.type); - - /* alloc blocks that includes this section */ - ret = block_alloc(dsp, &ba, &runtime->block_list); - } - if (ret < 0) { - dev_err(dsp->dev, - "error: no free blocks for runtime module size 0x%x\n", - module->persistent_size); - mutex_unlock(&dsp->mutex); - return -ENOMEM; - } - runtime->persistent_offset = ba.offset; - - /* prepare DSP blocks for module copy */ - ret = block_list_prepare(dsp, &runtime->block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: runtime block prepare failed\n"); - goto err; - } - - mutex_unlock(&dsp->mutex); - return ret; - -err: - block_list_remove(dsp, &module->block_list); - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks); - -int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime) -{ - struct sst_dsp *dsp = runtime->dsp; - - mutex_lock(&dsp->mutex); - block_list_remove(dsp, &runtime->block_list); - mutex_unlock(&dsp->mutex); - return 0; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks); - -int sst_module_runtime_save(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context) -{ - struct sst_dsp *dsp = runtime->dsp; - struct sst_module *module = runtime->module; - int ret = 0; - - dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n", - runtime->id, runtime->persistent_offset, - module->persistent_size); - - context->buffer = dma_alloc_coherent(dsp->dma_dev, - module->persistent_size, - &context->dma_buffer, GFP_DMA | GFP_KERNEL); - if (!context->buffer) { - dev_err(dsp->dev, "error: DMA context alloc failed\n"); - return -ENOMEM; - } - - mutex_lock(&dsp->mutex); - - if (dsp->fw_use_dma) { - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) - goto err; - - ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer, - dsp->addr.lpe_base + runtime->persistent_offset, - module->persistent_size); - sst_dsp_dma_put_channel(dsp); - if (ret < 0) { - dev_err(dsp->dev, "error: context copy failed\n"); - goto err; - } - } else - sst_memcpy32(context->buffer, dsp->addr.lpe + - runtime->persistent_offset, - module->persistent_size); - -err: - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_save); - -int sst_module_runtime_restore(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context) -{ - struct sst_dsp *dsp = runtime->dsp; - struct sst_module *module = runtime->module; - int ret = 0; - - dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n", - runtime->id, runtime->persistent_offset, - module->persistent_size); - - mutex_lock(&dsp->mutex); - - if (!context->buffer) { - dev_info(dsp->dev, "no context buffer need to restore!\n"); - goto err; - } - - if (dsp->fw_use_dma) { - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) - goto err; - - ret = sst_dsp_dma_copyto(dsp, - dsp->addr.lpe_base + runtime->persistent_offset, - context->dma_buffer, module->persistent_size); - sst_dsp_dma_put_channel(dsp); - if (ret < 0) { - dev_err(dsp->dev, "error: module copy failed\n"); - goto err; - } - } else - sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset, - context->buffer, module->persistent_size); - - dma_free_coherent(dsp->dma_dev, module->persistent_size, - context->buffer, context->dma_buffer); - context->buffer = NULL; - -err: - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_restore); - -/* register a DSP memory block for use with FW based modules */ -struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, - u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index, - void *private) -{ - struct sst_mem_block *block; - - block = kzalloc(sizeof(*block), GFP_KERNEL); - if (block == NULL) - return NULL; - - block->offset = offset; - block->size = size; - block->index = index; - block->type = type; - block->dsp = dsp; - block->private = private; - block->ops = ops; - - mutex_lock(&dsp->mutex); - list_add(&block->list, &dsp->free_block_list); - mutex_unlock(&dsp->mutex); - - return block; -} -EXPORT_SYMBOL_GPL(sst_mem_block_register); - -/* unregister all DSP memory blocks */ -void sst_mem_block_unregister_all(struct sst_dsp *dsp) -{ - struct sst_mem_block *block, *tmp; - - mutex_lock(&dsp->mutex); - - /* unregister used blocks */ - list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) { - list_del(&block->list); - kfree(block); - } - - /* unregister free blocks */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - list_del(&block->list); - kfree(block); - } - - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); - -/* allocate scratch buffer blocks */ -int sst_block_alloc_scratch(struct sst_dsp *dsp) -{ - struct sst_module *module; - struct sst_block_allocator ba; - int ret; - - mutex_lock(&dsp->mutex); - - /* calculate required scratch size */ - dsp->scratch_size = 0; - list_for_each_entry(module, &dsp->module_list, list) { - dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n", - module->id, module->scratch_size); - if (dsp->scratch_size < module->scratch_size) - dsp->scratch_size = module->scratch_size; - } - - dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n", - dsp->scratch_size); - - if (dsp->scratch_size == 0) { - dev_info(dsp->dev, "no modules need scratch buffer\n"); - mutex_unlock(&dsp->mutex); - return 0; - } - - /* allocate blocks for module scratch buffers */ - dev_dbg(dsp->dev, "allocating scratch blocks\n"); - - ba.size = dsp->scratch_size; - ba.type = SST_MEM_DRAM; - - /* do we need to allocate at fixed offset */ - if (dsp->scratch_offset != 0) { - - dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n", - ba.size, ba.type, ba.offset); - - ba.offset = dsp->scratch_offset; - - /* alloc blocks that includes this section */ - ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list); - - } else { - dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n", - ba.size, ba.type); - - ba.offset = 0; - ret = block_alloc(dsp, &ba, &dsp->scratch_block_list); - } - if (ret < 0) { - dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); - mutex_unlock(&dsp->mutex); - return ret; - } - - ret = block_list_prepare(dsp, &dsp->scratch_block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: scratch block prepare failed\n"); - mutex_unlock(&dsp->mutex); - return ret; - } - - /* assign the same offset of scratch to each module */ - dsp->scratch_offset = ba.offset; - mutex_unlock(&dsp->mutex); - return dsp->scratch_size; -} -EXPORT_SYMBOL_GPL(sst_block_alloc_scratch); - -/* free all scratch blocks */ -void sst_block_free_scratch(struct sst_dsp *dsp) -{ - mutex_lock(&dsp->mutex); - block_list_remove(dsp, &dsp->scratch_block_list); - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_block_free_scratch); - -/* get a module from it's unique ID */ -struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) -{ - struct sst_module *module; - - mutex_lock(&dsp->mutex); - - list_for_each_entry(module, &dsp->module_list, list) { - if (module->id == id) { - mutex_unlock(&dsp->mutex); - return module; - } - } - - mutex_unlock(&dsp->mutex); - return NULL; -} -EXPORT_SYMBOL_GPL(sst_module_get_from_id); - -struct sst_module_runtime *sst_module_runtime_get_from_id( - struct sst_module *module, u32 id) -{ - struct sst_module_runtime *runtime; - struct sst_dsp *dsp = module->dsp; - - mutex_lock(&dsp->mutex); - - list_for_each_entry(runtime, &module->runtime_list, list) { - if (runtime->id == id) { - mutex_unlock(&dsp->mutex); - return runtime; - } - } - - mutex_unlock(&dsp->mutex); - return NULL; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id); - -/* returns block address in DSP address space */ -u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, - enum sst_mem_type type) -{ - switch (type) { - case SST_MEM_IRAM: - return offset - dsp->addr.iram_offset + - dsp->addr.dsp_iram_offset; - case SST_MEM_DRAM: - return offset - dsp->addr.dram_offset + - dsp->addr.dsp_dram_offset; - default: - return 0; - } -} -EXPORT_SYMBOL_GPL(sst_dsp_get_offset); diff --git a/sound/soc/intel/sst/sst.c b/sound/soc/intel/sst/sst.c index 1a7eeec..26b1e31c 100644 --- a/sound/soc/intel/sst/sst.c +++ b/sound/soc/intel/sst/sst.c @@ -32,7 +32,7 @@ #include #include "../sst-mfld-platform.h" #include "sst.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" MODULE_AUTHOR("Vinod Koul "); MODULE_AUTHOR("Harsha Priya "); diff --git a/sound/soc/intel/sst/sst_acpi.c b/sound/soc/intel/sst/sst_acpi.c index b782dfd..2a19cbc 100644 --- a/sound/soc/intel/sst/sst_acpi.c +++ b/sound/soc/intel/sst/sst_acpi.c @@ -39,7 +39,7 @@ #include #include #include "../sst-mfld-platform.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" #include "sst.h" struct sst_machines { diff --git a/sound/soc/intel/sst/sst_drv_interface.c b/sound/soc/intel/sst/sst_drv_interface.c index f0e4b99b..36d68b8 100644 --- a/sound/soc/intel/sst/sst_drv_interface.c +++ b/sound/soc/intel/sst/sst_drv_interface.c @@ -32,7 +32,7 @@ #include #include "../sst-mfld-platform.h" #include "sst.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" diff --git a/sound/soc/intel/sst/sst_ipc.c b/sound/soc/intel/sst/sst_ipc.c index 484e609..3943ae8 100644 --- a/sound/soc/intel/sst/sst_ipc.c +++ b/sound/soc/intel/sst/sst_ipc.c @@ -32,7 +32,7 @@ #include #include "../sst-mfld-platform.h" #include "sst.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" struct sst_block *sst_create_block(struct intel_sst_drv *ctx, u32 msg_id, u32 drv_id) diff --git a/sound/soc/intel/sst/sst_loader.c b/sound/soc/intel/sst/sst_loader.c index e88907a..6622e66 100644 --- a/sound/soc/intel/sst/sst_loader.c +++ b/sound/soc/intel/sst/sst_loader.c @@ -37,7 +37,7 @@ #include #include "../sst-mfld-platform.h" #include "sst.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" void memcpy32_toio(void __iomem *dst, const void *src, int count) { diff --git a/sound/soc/intel/sst/sst_pvt.c b/sound/soc/intel/sst/sst_pvt.c index 4b77208..2bb0e9e 100644 --- a/sound/soc/intel/sst/sst_pvt.c +++ b/sound/soc/intel/sst/sst_pvt.c @@ -34,7 +34,7 @@ #include #include "../sst-mfld-platform.h" #include "sst.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" int sst_shim_write(void __iomem *addr, int offset, int value) { diff --git a/sound/soc/intel/sst/sst_stream.c b/sound/soc/intel/sst/sst_stream.c index dae2a41..7638fca 100644 --- a/sound/soc/intel/sst/sst_stream.c +++ b/sound/soc/intel/sst/sst_stream.c @@ -31,7 +31,7 @@ #include #include "../sst-mfld-platform.h" #include "sst.h" -#include "../sst-dsp.h" +#include "../common/sst-dsp.h" int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) { -- cgit v0.10.2 From ba57f68235cf6e9105bf649b01cf9eafc321ea7b Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Thu, 2 Apr 2015 15:37:01 +0800 Subject: ASoC: Intel: create haswell folder and move haswell platform files in Restructure the sound/soc/intel/ directory: create haswell folder, and move haswell platform files here. Signed-off-by: Jie Yang Reviewed-by: Liam Girdwood Signed-off-by: Mark Brown diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index 28de8cd..eb3efce 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -9,12 +9,10 @@ obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o # Platform Support -snd-soc-sst-haswell-pcm-objs := \ - sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o +obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/ snd-soc-sst-baytrail-pcm-objs := \ sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o -obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o # Machine support diff --git a/sound/soc/intel/haswell/Makefile b/sound/soc/intel/haswell/Makefile new file mode 100644 index 0000000..9c17231 --- /dev/null +++ b/sound/soc/intel/haswell/Makefile @@ -0,0 +1,4 @@ +snd-soc-sst-haswell-pcm-objs := \ + sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o + +obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o diff --git a/sound/soc/intel/haswell/sst-haswell-dsp.c b/sound/soc/intel/haswell/sst-haswell-dsp.c new file mode 100644 index 0000000..7f94920 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-dsp.c @@ -0,0 +1,709 @@ +/* + * Intel Haswell SST DSP driver + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "../haswell/sst-haswell-ipc.h" + +#include + +#define SST_HSW_FW_SIGNATURE_SIZE 4 +#define SST_HSW_FW_SIGN "$SST" +#define SST_HSW_FW_LIB_SIGN "$LIB" + +#define SST_WPT_SHIM_OFFSET 0xFB000 +#define SST_LP_SHIM_OFFSET 0xE7000 +#define SST_WPT_IRAM_OFFSET 0xA0000 +#define SST_LP_IRAM_OFFSET 0x80000 +#define SST_WPT_DSP_DRAM_OFFSET 0x400000 +#define SST_WPT_DSP_IRAM_OFFSET 0x00000 +#define SST_LPT_DSP_DRAM_OFFSET 0x400000 +#define SST_LPT_DSP_IRAM_OFFSET 0x00000 + +#define SST_SHIM_PM_REG 0x84 + +#define SST_HSW_IRAM 1 +#define SST_HSW_DRAM 2 +#define SST_HSW_REGS 3 + +struct dma_block_info { + __le32 type; /* IRAM/DRAM */ + __le32 size; /* Bytes */ + __le32 ram_offset; /* Offset in I/DRAM */ + __le32 rsvd; /* Reserved field */ +} __attribute__((packed)); + +struct fw_module_info { + __le32 persistent_size; + __le32 scratch_size; +} __attribute__((packed)); + +struct fw_header { + unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */ + __le32 file_size; /* size of fw minus this header */ + __le32 modules; /* # of modules */ + __le32 file_format; /* version of header format */ + __le32 reserved[4]; +} __attribute__((packed)); + +struct fw_module_header { + unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */ + __le32 mod_size; /* size of module */ + __le32 blocks; /* # of blocks */ + __le16 padding; + __le16 type; /* codec type, pp lib */ + __le32 entry_point; + struct fw_module_info info; +} __attribute__((packed)); + +static void hsw_free(struct sst_dsp *sst); + +static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, + struct fw_module_header *module) +{ + struct dma_block_info *block; + struct sst_module *mod; + struct sst_module_template template; + int count, ret; + void __iomem *ram; + + /* TODO: allowed module types need to be configurable */ + if (module->type != SST_HSW_MODULE_BASE_FW + && module->type != SST_HSW_MODULE_PCM_SYSTEM + && module->type != SST_HSW_MODULE_PCM + && module->type != SST_HSW_MODULE_PCM_REFERENCE + && module->type != SST_HSW_MODULE_PCM_CAPTURE + && module->type != SST_HSW_MODULE_WAVES + && module->type != SST_HSW_MODULE_LPAL) + return 0; + + dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n", + module->signature, module->mod_size, + module->blocks, module->type); + dev_dbg(dsp->dev, " entrypoint 0x%x\n", module->entry_point); + dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n", + module->info.persistent_size, module->info.scratch_size); + + memset(&template, 0, sizeof(template)); + template.id = module->type; + template.entry = module->entry_point - 4; + template.persistent_size = module->info.persistent_size; + template.scratch_size = module->info.scratch_size; + + mod = sst_module_new(fw, &template, NULL); + if (mod == NULL) + return -ENOMEM; + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + + if (block->size <= 0) { + dev_err(dsp->dev, + "error: block %d size invalid\n", count); + sst_module_free(mod); + return -EINVAL; + } + + switch (block->type) { + case SST_HSW_IRAM: + ram = dsp->addr.lpe; + mod->offset = + block->ram_offset + dsp->addr.iram_offset; + mod->type = SST_MEM_IRAM; + break; + case SST_HSW_DRAM: + case SST_HSW_REGS: + ram = dsp->addr.lpe; + mod->offset = block->ram_offset; + mod->type = SST_MEM_DRAM; + break; + default: + dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", + block->type, count); + sst_module_free(mod); + return -EINVAL; + } + + mod->size = block->size; + mod->data = (void *)block + sizeof(*block); + mod->data_offset = mod->data - fw->dma_buf; + + dev_dbg(dsp->dev, "module block %d type 0x%x " + "size 0x%x ==> ram %p offset 0x%x\n", + count, mod->type, block->size, ram, + block->ram_offset); + + ret = sst_module_alloc_blocks(mod); + if (ret < 0) { + dev_err(dsp->dev, "error: could not allocate blocks for module %d\n", + count); + sst_module_free(mod); + return ret; + } + + block = (void *)block + sizeof(*block) + block->size; + } + mod->state = SST_MODULE_STATE_LOADED; + + return 0; +} + +static int hsw_parse_fw_image(struct sst_fw *sst_fw) +{ + struct fw_header *header; + struct fw_module_header *module; + struct sst_dsp *dsp = sst_fw->dsp; + int ret, count; + + /* Read the header information from the data pointer */ + header = (struct fw_header *)sst_fw->dma_buf; + + /* verify FW */ + if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) || + (sst_fw->size != header->file_size + sizeof(*header))) { + dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n"); + return -EINVAL; + } + + dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n", + header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* parse each module */ + module = (void *)sst_fw->dma_buf + sizeof(*header); + for (count = 0; count < header->modules; count++) { + + /* module */ + ret = hsw_parse_module(dsp, sst_fw, module); + if (ret < 0) { + dev_err(dsp->dev, "error: invalid module %d\n", count); + return ret; + } + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +static irqreturn_t hsw_irq(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + u32 isr; + int ret = IRQ_NONE; + + spin_lock(&sst->spinlock); + + /* Interrupt arrived, check src */ + isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); + if (isr & SST_ISRX_DONE) { + trace_sst_irq_done(isr, + sst_dsp_shim_read_unlocked(sst, SST_IMRX)); + + /* Mask Done interrupt before return */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, SST_IMRX_DONE); + ret = IRQ_WAKE_THREAD; + } + + if (isr & SST_ISRX_BUSY) { + trace_sst_irq_busy(isr, + sst_dsp_shim_read_unlocked(sst, SST_IMRX)); + + /* Mask Busy interrupt before return */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, SST_IMRX_BUSY); + ret = IRQ_WAKE_THREAD; + } + + spin_unlock(&sst->spinlock); + return ret; +} + +static void hsw_set_dsp_D3(struct sst_dsp *sst) +{ + u32 val; + u32 reg; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* enable power gating and switch off DRAM & IRAM blocks */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + val |= SST_VDRTCL0_DSRAMPGE_MASK | + SST_VDRTCL0_ISRAMPGE_MASK; + val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD); + writel(val, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* switch off audio PLL */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_APLLSE_MASK; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* disable MCLK(clkctl.smos = 0) */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, + SST_CLKCTL_MASK, 0); + + /* Set D3 state, delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_PMCS); + val |= SST_PMCS_PS_MASK; + writel(val, sst->addr.pci_cfg + SST_PMCS); + udelay(50); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + +} + +static void hsw_reset(struct sst_dsp *sst) +{ + /* put DSP into reset and stall */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_RST | SST_CSR_STALL, + SST_CSR_RST | SST_CSR_STALL); + + /* keep in reset for 10ms */ + mdelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); +} + +static int hsw_set_dsp_D0(struct sst_dsp *sst) +{ + int tries = 10; + u32 reg, fw_dump_bit; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + reg |= SST_VDRTCL0_D3PGD; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* Set D0 state */ + reg = readl(sst->addr.pci_cfg + SST_PMCS); + reg &= ~SST_PMCS_PS_MASK; + writel(reg, sst->addr.pci_cfg + SST_PMCS); + + /* check that ADSP shim is enabled */ + while (tries--) { + reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK; + if (reg == 0) + goto finish; + + msleep(1); + } + + return -ENODEV; + +finish: + /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); + + /* stall DSP core, set clk to 192/96Mhz */ + sst_dsp_shim_update_bits_unlocked(sst, + SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK, + SST_CSR_STALL | SST_CSR_DCS(4)); + + /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, + SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, + SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); + + /* Stall and reset core, set CSR */ + hsw_reset(sst); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + /* switch on audio PLL */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~SST_VDRTCL2_APLLSE_MASK; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* set default power gating control, enable power gating control for all blocks. that is, + can't be accessed, please enable each block before accessing. */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK; + /* for D0, always enable the block(DSRAM[0]) used for FW dump */ + fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; + writel(reg & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + + /* disable DMA finish function for SSP0 & SSP1 */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, + SST_CSR2_SDFD_SSP1); + + /* set on-demond mode on engine 0,1 for all channels */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); + + /* Enable Interrupt from both sides */ + sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE), + 0x0); + sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY | + SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0); + + /* clear IPC registers */ + sst_dsp_shim_write(sst, SST_IPCX, 0x0); + sst_dsp_shim_write(sst, SST_IPCD, 0x0); + sst_dsp_shim_write(sst, 0x80, 0x6); + sst_dsp_shim_write(sst, 0xe0, 0x300a); + + return 0; +} + +static void hsw_boot(struct sst_dsp *sst) +{ + /* set oportunistic mode on engine 0,1 for all channels */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0); + + /* set DSP to RUN */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); +} + +static void hsw_stall(struct sst_dsp *sst) +{ + /* stall DSP */ + sst_dsp_shim_update_bits(sst, SST_CSR, + SST_CSR_24MHZ_LPCS | SST_CSR_STALL, + SST_CSR_STALL | SST_CSR_24MHZ_LPCS); +} + +static void hsw_sleep(struct sst_dsp *sst) +{ + dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n"); + + /* put DSP into reset and stall */ + sst_dsp_shim_update_bits(sst, SST_CSR, + SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL, + SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS); + + hsw_set_dsp_D3(sst); + dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n"); +} + +static int hsw_wake(struct sst_dsp *sst) +{ + int ret; + + dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n"); + + ret = hsw_set_dsp_D0(sst); + if (ret < 0) + return ret; + + dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n"); + + return 0; +} + +struct sst_adsp_memregion { + u32 start; + u32 end; + int blocks; + enum sst_mem_type type; +}; + +/* lynx point ADSP mem regions */ +static const struct sst_adsp_memregion lp_region[] = { + {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ + {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ + {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */ +}; + +/* wild cat point ADSP mem regions */ +static const struct sst_adsp_memregion wpt_region[] = { + {0x00000, 0xA0000, 20, SST_MEM_DRAM}, /* D-SRAM0,D-SRAM1,D-SRAM2 - 20 * 32kB */ + {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */ +}; + +static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + /* ADSP DRAM & IRAM */ + sst->addr.lpe_base = pdata->lpe_base; + sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); + if (!sst->addr.lpe) + return -ENODEV; + + /* ADSP PCI MMIO config space */ + sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); + if (!sst->addr.pci_cfg) { + iounmap(sst->addr.lpe); + return -ENODEV; + } + + /* SST Shim */ + sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; + return 0; +} + +struct sst_sram_shift { + u32 dev_id; /* SST Device IDs */ + u32 iram_shift; + u32 dram_shift; +}; + +static const struct sst_sram_shift sram_shift[] = { + {SST_DEV_ID_LYNX_POINT, 6, 16}, /* lp */ + {SST_DEV_ID_WILDCAT_POINT, 2, 12}, /* wpt */ +}; + +static u32 hsw_block_get_bit(struct sst_mem_block *block) +{ + u32 bit = 0, shift = 0, index; + struct sst_dsp *sst = block->dsp; + + for (index = 0; index < ARRAY_SIZE(sram_shift); index++) { + if (sram_shift[index].dev_id == sst->id) + break; + } + + if (index < ARRAY_SIZE(sram_shift)) { + switch (block->type) { + case SST_MEM_DRAM: + shift = sram_shift[index].dram_shift; + break; + case SST_MEM_IRAM: + shift = sram_shift[index].iram_shift; + break; + default: + shift = 0; + } + } else + shift = 0; + + bit = 1 << (block->index + shift); + + return bit; +} + +/*dummy read a SRAM block.*/ +static void sst_mem_block_dummy_read(struct sst_mem_block *block) +{ + u32 size; + u8 tmp_buf[4]; + struct sst_dsp *sst = block->dsp; + + size = block->size > 4 ? 4 : block->size; + memcpy_fromio(tmp_buf, sst->addr.lpe + block->offset, size); +} + +/* enable 32kB memory block - locks held by caller */ +static int hsw_block_enable(struct sst_mem_block *block) +{ + struct sst_dsp *sst = block->dsp; + u32 bit, val; + + if (block->users++ > 0) + return 0; + + dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val &= ~SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + bit = hsw_block_get_bit(block); + writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* wait 18 DSP clock ticks */ + udelay(10); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ + sst_mem_block_dummy_read(block); + return 0; +} + +/* disable 32kB memory block - locks held by caller */ +static int hsw_block_disable(struct sst_mem_block *block) +{ + struct sst_dsp *sst = block->dsp; + u32 bit, val; + + if (--block->users > 0) + return 0; + + dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val &= ~SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + bit = hsw_block_get_bit(block); + /* don't disable DSRAM[0], keep it always enable for FW dump*/ + if (bit != (1 << SST_VDRTCL0_DSRAMPGE_SHIFT)) + writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* wait 18 DSP clock ticks */ + udelay(10); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + return 0; +} + +static struct sst_block_ops sst_hsw_ops = { + .enable = hsw_block_enable, + .disable = hsw_block_disable, +}; + +static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + const struct sst_adsp_memregion *region; + struct device *dev; + int ret = -ENODEV, i, j, region_count; + u32 offset, size, fw_dump_bit; + + dev = sst->dma_dev; + + switch (sst->id) { + case SST_DEV_ID_LYNX_POINT: + region = lp_region; + region_count = ARRAY_SIZE(lp_region); + sst->addr.iram_offset = SST_LP_IRAM_OFFSET; + sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET; + sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET; + sst->addr.shim_offset = SST_LP_SHIM_OFFSET; + break; + case SST_DEV_ID_WILDCAT_POINT: + region = wpt_region; + region_count = ARRAY_SIZE(wpt_region); + sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; + sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET; + sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET; + sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; + break; + default: + dev_err(dev, "error: failed to get mem resources\n"); + return ret; + } + + ret = hsw_acpi_resource_map(sst, pdata); + if (ret < 0) { + dev_err(dev, "error: failed to map resources\n"); + return ret; + } + + /* enable the DSP SHIM */ + ret = hsw_set_dsp_D0(sst); + if (ret < 0) { + dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); + return ret; + } + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); + if (ret) + return ret; + + + /* register DSP memory blocks - ideally we should get this from ACPI */ + for (i = 0; i < region_count; i++) { + offset = region[i].start; + size = (region[i].end - region[i].start) / region[i].blocks; + + /* register individual memory blocks */ + for (j = 0; j < region[i].blocks; j++) { + sst_mem_block_register(sst, offset, size, + region[i].type, &sst_hsw_ops, j, sst); + offset += size; + } + } + + /* always enable the block(DSRAM[0]) used for FW dump */ + fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; + /* set default power gating control, enable power gating control for all blocks. that is, + can't be accessed, please enable each block before accessing. */ + writel(0xffffffff & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + return 0; +} + +static void hsw_free(struct sst_dsp *sst) +{ + sst_mem_block_unregister_all(sst); + iounmap(sst->addr.lpe); + iounmap(sst->addr.pci_cfg); +} + +struct sst_ops haswell_ops = { + .reset = hsw_reset, + .boot = hsw_boot, + .stall = hsw_stall, + .wake = hsw_wake, + .sleep = hsw_sleep, + .write = sst_shim32_write, + .read = sst_shim32_read, + .write64 = sst_shim32_write64, + .read64 = sst_shim32_read64, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .irq_handler = hsw_irq, + .init = hsw_init, + .free = hsw_free, + .parse_fw = hsw_parse_fw_image, +}; diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c new file mode 100644 index 0000000..28667d8 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -0,0 +1,2428 @@ +/* + * Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sst-haswell-ipc.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" + +/* Global Message - Generic */ +#define IPC_GLB_TYPE_SHIFT 24 +#define IPC_GLB_TYPE_MASK (0x1f << IPC_GLB_TYPE_SHIFT) +#define IPC_GLB_TYPE(x) (x << IPC_GLB_TYPE_SHIFT) + +/* Global Message - Reply */ +#define IPC_GLB_REPLY_SHIFT 0 +#define IPC_GLB_REPLY_MASK (0x1f << IPC_GLB_REPLY_SHIFT) +#define IPC_GLB_REPLY_TYPE(x) (x << IPC_GLB_REPLY_TYPE_SHIFT) + +/* Stream Message - Generic */ +#define IPC_STR_TYPE_SHIFT 20 +#define IPC_STR_TYPE_MASK (0xf << IPC_STR_TYPE_SHIFT) +#define IPC_STR_TYPE(x) (x << IPC_STR_TYPE_SHIFT) +#define IPC_STR_ID_SHIFT 16 +#define IPC_STR_ID_MASK (0xf << IPC_STR_ID_SHIFT) +#define IPC_STR_ID(x) (x << IPC_STR_ID_SHIFT) + +/* Stream Message - Reply */ +#define IPC_STR_REPLY_SHIFT 0 +#define IPC_STR_REPLY_MASK (0x1f << IPC_STR_REPLY_SHIFT) + +/* Stream Stage Message - Generic */ +#define IPC_STG_TYPE_SHIFT 12 +#define IPC_STG_TYPE_MASK (0xf << IPC_STG_TYPE_SHIFT) +#define IPC_STG_TYPE(x) (x << IPC_STG_TYPE_SHIFT) +#define IPC_STG_ID_SHIFT 10 +#define IPC_STG_ID_MASK (0x3 << IPC_STG_ID_SHIFT) +#define IPC_STG_ID(x) (x << IPC_STG_ID_SHIFT) + +/* Stream Stage Message - Reply */ +#define IPC_STG_REPLY_SHIFT 0 +#define IPC_STG_REPLY_MASK (0x1f << IPC_STG_REPLY_SHIFT) + +/* Debug Log Message - Generic */ +#define IPC_LOG_OP_SHIFT 20 +#define IPC_LOG_OP_MASK (0xf << IPC_LOG_OP_SHIFT) +#define IPC_LOG_OP_TYPE(x) (x << IPC_LOG_OP_SHIFT) +#define IPC_LOG_ID_SHIFT 16 +#define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT) +#define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT) + +/* Module Message */ +#define IPC_MODULE_OPERATION_SHIFT 20 +#define IPC_MODULE_OPERATION_MASK (0xf << IPC_MODULE_OPERATION_SHIFT) +#define IPC_MODULE_OPERATION(x) (x << IPC_MODULE_OPERATION_SHIFT) + +#define IPC_MODULE_ID_SHIFT 16 +#define IPC_MODULE_ID_MASK (0xf << IPC_MODULE_ID_SHIFT) +#define IPC_MODULE_ID(x) (x << IPC_MODULE_ID_SHIFT) + +/* IPC message timeout (msecs) */ +#define IPC_TIMEOUT_MSECS 300 +#define IPC_BOOT_MSECS 200 +#define IPC_MSG_WAIT 0 +#define IPC_MSG_NOWAIT 1 + +/* Firmware Ready Message */ +#define IPC_FW_READY (0x1 << 29) +#define IPC_STATUS_MASK (0x3 << 30) + +#define IPC_EMPTY_LIST_SIZE 8 +#define IPC_MAX_STREAMS 4 + +/* Mailbox */ +#define IPC_MAX_MAILBOX_BYTES 256 + +#define INVALID_STREAM_HW_ID 0xffffffff + +/* Global Message - Types and Replies */ +enum ipc_glb_type { + IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */ + IPC_GLB_PERFORMANCE_MONITOR = 1, /* Performance monitoring actions */ + IPC_GLB_ALLOCATE_STREAM = 3, /* Request to allocate new stream */ + IPC_GLB_FREE_STREAM = 4, /* Request to free stream */ + IPC_GLB_GET_FW_CAPABILITIES = 5, /* Retrieves firmware capabilities */ + IPC_GLB_STREAM_MESSAGE = 6, /* Message directed to stream or its stages */ + /* Request to store firmware context during D0->D3 transition */ + IPC_GLB_REQUEST_DUMP = 7, + /* Request to restore firmware context during D3->D0 transition */ + IPC_GLB_RESTORE_CONTEXT = 8, + IPC_GLB_GET_DEVICE_FORMATS = 9, /* Set device format */ + IPC_GLB_SET_DEVICE_FORMATS = 10, /* Get device format */ + IPC_GLB_SHORT_REPLY = 11, + IPC_GLB_ENTER_DX_STATE = 12, + IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */ + IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */ + IPC_GLB_MODULE_OPERATION = 15, /* Message to loadable fw module */ + IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */ + IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */ +}; + +enum ipc_glb_reply { + IPC_GLB_REPLY_SUCCESS = 0, /* The operation was successful. */ + IPC_GLB_REPLY_ERROR_INVALID_PARAM = 1, /* Invalid parameter was passed. */ + IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 2, /* Uknown message type was resceived. */ + IPC_GLB_REPLY_OUT_OF_RESOURCES = 3, /* No resources to satisfy the request. */ + IPC_GLB_REPLY_BUSY = 4, /* The system or resource is busy. */ + IPC_GLB_REPLY_PENDING = 5, /* The action was scheduled for processing. */ + IPC_GLB_REPLY_FAILURE = 6, /* Critical error happened. */ + IPC_GLB_REPLY_INVALID_REQUEST = 7, /* Request can not be completed. */ + IPC_GLB_REPLY_STAGE_UNINITIALIZED = 8, /* Processing stage was uninitialized. */ + IPC_GLB_REPLY_NOT_FOUND = 9, /* Required resource can not be found. */ + IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */ +}; + +enum ipc_module_operation { + IPC_MODULE_NOTIFICATION = 0, + IPC_MODULE_ENABLE = 1, + IPC_MODULE_DISABLE = 2, + IPC_MODULE_GET_PARAMETER = 3, + IPC_MODULE_SET_PARAMETER = 4, + IPC_MODULE_GET_INFO = 5, + IPC_MODULE_MAX_MESSAGE +}; + +/* Stream Message - Types */ +enum ipc_str_operation { + IPC_STR_RESET = 0, + IPC_STR_PAUSE = 1, + IPC_STR_RESUME = 2, + IPC_STR_STAGE_MESSAGE = 3, + IPC_STR_NOTIFICATION = 4, + IPC_STR_MAX_MESSAGE +}; + +/* Stream Stage Message Types */ +enum ipc_stg_operation { + IPC_STG_GET_VOLUME = 0, + IPC_STG_SET_VOLUME, + IPC_STG_SET_WRITE_POSITION, + IPC_STG_SET_FX_ENABLE, + IPC_STG_SET_FX_DISABLE, + IPC_STG_SET_FX_GET_PARAM, + IPC_STG_SET_FX_SET_PARAM, + IPC_STG_SET_FX_GET_INFO, + IPC_STG_MUTE_LOOPBACK, + IPC_STG_MAX_MESSAGE +}; + +/* Stream Stage Message Types For Notification*/ +enum ipc_stg_operation_notify { + IPC_POSITION_CHANGED = 0, + IPC_STG_GLITCH, + IPC_STG_MAX_NOTIFY +}; + +enum ipc_glitch_type { + IPC_GLITCH_UNDERRUN = 1, + IPC_GLITCH_DECODER_ERROR, + IPC_GLITCH_DOUBLED_WRITE_POS, + IPC_GLITCH_MAX +}; + +/* Debug Control */ +enum ipc_debug_operation { + IPC_DEBUG_ENABLE_LOG = 0, + IPC_DEBUG_DISABLE_LOG = 1, + IPC_DEBUG_REQUEST_LOG_DUMP = 2, + IPC_DEBUG_NOTIFY_LOG_DUMP = 3, + IPC_DEBUG_MAX_DEBUG_LOG +}; + +/* Firmware Ready */ +struct sst_hsw_ipc_fw_ready { + u32 inbox_offset; + u32 outbox_offset; + u32 inbox_size; + u32 outbox_size; + u32 fw_info_size; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; +} __attribute__((packed)); + +struct ipc_message { + struct list_head list; + u32 header; + + /* direction wrt host CPU */ + char tx_data[IPC_MAX_MAILBOX_BYTES]; + size_t tx_size; + char rx_data[IPC_MAX_MAILBOX_BYTES]; + size_t rx_size; + + wait_queue_head_t waitq; + bool pending; + bool complete; + bool wait; + int errno; +}; + +struct sst_hsw_stream; +struct sst_hsw; + +/* Stream infomation */ +struct sst_hsw_stream { + /* configuration */ + struct sst_hsw_ipc_stream_alloc_req request; + struct sst_hsw_ipc_stream_alloc_reply reply; + struct sst_hsw_ipc_stream_free_req free_req; + + /* Mixer info */ + u32 mute_volume[SST_HSW_NO_CHANNELS]; + u32 mute[SST_HSW_NO_CHANNELS]; + + /* runtime info */ + struct sst_hsw *hsw; + int host_id; + bool commited; + bool running; + + /* Notification work */ + struct work_struct notify_work; + u32 header; + + /* Position info from DSP */ + struct sst_hsw_ipc_stream_set_position wpos; + struct sst_hsw_ipc_stream_get_position rpos; + struct sst_hsw_ipc_stream_glitch_position glitch; + + /* Volume info */ + struct sst_hsw_ipc_volume_req vol_req; + + /* driver callback */ + u32 (*notify_position)(struct sst_hsw_stream *stream, void *data); + void *pdata; + + /* record the fw read position when playback */ + snd_pcm_uframes_t old_position; + bool play_silence; + struct list_head node; +}; + +/* FW log ring information */ +struct sst_hsw_log_stream { + dma_addr_t dma_addr; + unsigned char *dma_area; + unsigned char *ring_descr; + int pages; + int size; + + /* Notification work */ + struct work_struct notify_work; + wait_queue_head_t readers_wait_q; + struct mutex rw_mutex; + + u32 last_pos; + u32 curr_pos; + u32 reader_pos; + + /* fw log config */ + u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; + + struct sst_hsw *hsw; +}; + +/* SST Haswell IPC data */ +struct sst_hsw { + struct device *dev; + struct sst_dsp *dsp; + struct platform_device *pdev_pcm; + + /* FW config */ + struct sst_hsw_ipc_fw_ready fw_ready; + struct sst_hsw_ipc_fw_version version; + bool fw_done; + struct sst_fw *sst_fw; + + /* stream */ + struct list_head stream_list; + + /* global mixer */ + struct sst_hsw_ipc_stream_info_reply mixer_info; + enum sst_hsw_volume_curve curve_type; + u32 curve_duration; + u32 mute[SST_HSW_NO_CHANNELS]; + u32 mute_volume[SST_HSW_NO_CHANNELS]; + + /* DX */ + struct sst_hsw_ipc_dx_reply dx; + void *dx_context; + dma_addr_t dx_context_paddr; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + bool shutdown; + + /* IPC messaging */ + struct list_head tx_list; + struct list_head rx_list; + struct list_head empty_list; + wait_queue_head_t wait_txq; + struct task_struct *tx_thread; + struct kthread_worker kworker; + struct kthread_work kwork; + bool pending; + struct ipc_message *msg; + + /* FW log stream */ + struct sst_hsw_log_stream log_stream; + + /* flags bit field to track module state when resume from RTD3, + * each bit represent state (enabled/disabled) of single module */ + u32 enabled_modules_rtd3; + + /* buffer to store parameter lines */ + u32 param_idx_w; /* write index */ + u32 param_idx_r; /* read index */ + u8 param_buf[WAVES_PARAM_LINES][WAVES_PARAM_COUNT]; +}; + +#define CREATE_TRACE_POINTS +#include + +static inline u32 msg_get_global_type(u32 msg) +{ + return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT; +} + +static inline u32 msg_get_global_reply(u32 msg) +{ + return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT; +} + +static inline u32 msg_get_stream_type(u32 msg) +{ + return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; +} + +static inline u32 msg_get_stage_type(u32 msg) +{ + return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_get_stream_id(u32 msg) +{ + return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; +} + +static inline u32 msg_get_notify_reason(u32 msg) +{ + return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_get_module_operation(u32 msg) +{ + return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT; +} + +static inline u32 msg_get_module_id(u32 msg) +{ + return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT; +} + +u32 create_channel_map(enum sst_hsw_channel_config config) +{ + switch (config) { + case SST_HSW_CHANNEL_CONFIG_MONO: + return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER); + case SST_HSW_CHANNEL_CONFIG_STEREO: + return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4)); + case SST_HSW_CHANNEL_CONFIG_2_POINT_1: + return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4) + | (SST_HSW_CHANNEL_LFE << 8 )); + case SST_HSW_CHANNEL_CONFIG_3_POINT_0: + return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8)); + case SST_HSW_CHANNEL_CONFIG_3_POINT_1: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LFE << 12)); + case SST_HSW_CHANNEL_CONFIG_QUATRO: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 8) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12)); + case SST_HSW_CHANNEL_CONFIG_4_POINT_0: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_CENTER_SURROUND << 12)); + case SST_HSW_CHANNEL_CONFIG_5_POINT_0: + return (0xFFF00000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)); + case SST_HSW_CHANNEL_CONFIG_5_POINT_1: + return (0xFF000000 | SST_HSW_CHANNEL_CENTER + | (SST_HSW_CHANNEL_LEFT << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16) + | (SST_HSW_CHANNEL_LFE << 20)); + case SST_HSW_CHANNEL_CONFIG_DUAL_MONO: + return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_LEFT << 4)); + default: + return 0xFFFFFFFF; + } +} + +static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, + int stream_id) +{ + struct sst_hsw_stream *stream; + + list_for_each_entry(stream, &hsw->stream_list, node) { + if (stream->reply.stream_hw_id == stream_id) + return stream; + } + + return NULL; +} + +static void ipc_shim_dbg(struct sst_hsw *hsw, const char *text) +{ + struct sst_dsp *sst = hsw->dsp; + u32 isr, ipcd, imrx, ipcx; + + ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); + isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); + + dev_err(hsw->dev, "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", + text, ipcx, isr, ipcd, imrx); +} + +/* locks held by caller */ +static struct ipc_message *msg_get_empty(struct sst_hsw *hsw) +{ + struct ipc_message *msg = NULL; + + if (!list_empty(&hsw->empty_list)) { + msg = list_first_entry(&hsw->empty_list, struct ipc_message, + list); + list_del(&msg->list); + } + + return msg; +} + +static void ipc_tx_msgs(struct kthread_work *work) +{ + struct sst_hsw *hsw = + container_of(work, struct sst_hsw, kwork); + struct ipc_message *msg; + unsigned long flags; + u32 ipcx; + + spin_lock_irqsave(&hsw->dsp->spinlock, flags); + + if (list_empty(&hsw->tx_list) || hsw->pending) { + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + return; + } + + /* if the DSP is busy, we will TX messages after IRQ. + * also postpone if we are in the middle of procesing completion irq*/ + ipcx = sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX); + if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) { + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + return; + } + + msg = list_first_entry(&hsw->tx_list, struct ipc_message, list); + + list_move(&msg->list, &hsw->rx_list); + + /* send the message */ + sst_dsp_outbox_write(hsw->dsp, msg->tx_data, msg->tx_size); + sst_dsp_ipc_msg_tx(hsw->dsp, msg->header | SST_IPCX_BUSY); + + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); +} + +/* locks held by caller */ +static void tx_msg_reply_complete(struct sst_hsw *hsw, struct ipc_message *msg) +{ + msg->complete = true; + trace_ipc_reply("completed", msg->header); + + if (!msg->wait) + list_add_tail(&msg->list, &hsw->empty_list); + else + wake_up(&msg->waitq); +} + +static int tx_wait_done(struct sst_hsw *hsw, struct ipc_message *msg, + void *rx_data) +{ + unsigned long flags; + int ret; + + /* wait for DSP completion (in all cases atm inc pending) */ + ret = wait_event_timeout(msg->waitq, msg->complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + + spin_lock_irqsave(&hsw->dsp->spinlock, flags); + if (ret == 0) { + ipc_shim_dbg(hsw, "message timeout"); + + trace_ipc_error("error message timeout for", msg->header); + list_del(&msg->list); + ret = -ETIMEDOUT; + } else { + + /* copy the data returned from DSP */ + if (msg->rx_size) + memcpy(rx_data, msg->rx_data, msg->rx_size); + ret = msg->errno; + } + + list_add_tail(&msg->list, &hsw->empty_list); + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + return ret; +} + +static int ipc_tx_message(struct sst_hsw *hsw, u32 header, void *tx_data, + size_t tx_bytes, void *rx_data, size_t rx_bytes, int wait) +{ + struct ipc_message *msg; + unsigned long flags; + + spin_lock_irqsave(&hsw->dsp->spinlock, flags); + + msg = msg_get_empty(hsw); + if (msg == NULL) { + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + return -EBUSY; + } + + if (tx_bytes) + memcpy(msg->tx_data, tx_data, tx_bytes); + + msg->header = header; + msg->tx_size = tx_bytes; + msg->rx_size = rx_bytes; + msg->wait = wait; + msg->errno = 0; + msg->pending = false; + msg->complete = false; + + list_add_tail(&msg->list, &hsw->tx_list); + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + + queue_kthread_work(&hsw->kworker, &hsw->kwork); + + if (wait) + return tx_wait_done(hsw, msg, rx_data); + else + return 0; +} + +static inline int ipc_tx_message_wait(struct sst_hsw *hsw, u32 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) +{ + return ipc_tx_message(hsw, header, tx_data, tx_bytes, rx_data, + rx_bytes, 1); +} + +static inline int ipc_tx_message_nowait(struct sst_hsw *hsw, u32 header, + void *tx_data, size_t tx_bytes) +{ + return ipc_tx_message(hsw, header, tx_data, tx_bytes, NULL, 0, 0); +} + +static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) +{ + struct sst_hsw_ipc_fw_ready fw_ready; + u32 offset; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; + char *tmp[5], *pinfo; + int i = 0; + + offset = (header & 0x1FFFFFFF) << 3; + + dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", + header, offset); + + /* copy data from the DSP FW ready offset */ + sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready)); + + sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset, + fw_ready.inbox_size, fw_ready.outbox_offset, + fw_ready.outbox_size); + + hsw->boot_complete = true; + wake_up(&hsw->boot_wait); + + dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n", + fw_ready.inbox_offset, fw_ready.inbox_size); + dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", + fw_ready.outbox_offset, fw_ready.outbox_size); + if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) { + fw_ready.fw_info[fw_ready.fw_info_size] = 0; + dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info); + + /* log the FW version info got from the mailbox here. */ + memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); + pinfo = &fw_info[0]; + for (i = 0; i < sizeof(tmp) / sizeof(char *); i++) + tmp[i] = strsep(&pinfo, " "); + dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " + "version: %s.%s, build %s, source commit id: %s\n", + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]); + } +} + +static void hsw_notification_work(struct work_struct *work) +{ + struct sst_hsw_stream *stream = container_of(work, + struct sst_hsw_stream, notify_work); + struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch; + struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos; + struct sst_hsw *hsw = stream->hsw; + u32 reason; + + reason = msg_get_notify_reason(stream->header); + + switch (reason) { + case IPC_STG_GLITCH: + trace_ipc_notification("DSP stream under/overrun", + stream->reply.stream_hw_id); + sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch)); + + dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n", + glitch->glitch_type, glitch->present_pos, + glitch->write_pos); + break; + + case IPC_POSITION_CHANGED: + trace_ipc_notification("DSP stream position changed for", + stream->reply.stream_hw_id); + sst_dsp_inbox_read(hsw->dsp, pos, sizeof(*pos)); + + if (stream->notify_position) + stream->notify_position(stream, stream->pdata); + + break; + default: + dev_err(hsw->dev, "error: unknown notification 0x%x\n", + stream->header); + break; + } + + /* tell DSP that notification has been handled */ + sst_dsp_shim_update_bits(hsw->dsp, SST_IPCD, + SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); +} + +static struct ipc_message *reply_find_msg(struct sst_hsw *hsw, u32 header) +{ + struct ipc_message *msg; + + /* clear reply bits & status bits */ + header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + + if (list_empty(&hsw->rx_list)) { + dev_err(hsw->dev, "error: rx list empty but received 0x%x\n", + header); + return NULL; + } + + list_for_each_entry(msg, &hsw->rx_list, list) { + if (msg->header == header) + return msg; + } + + return NULL; +} + +static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) +{ + struct sst_hsw_stream *stream; + u32 header = msg->header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + u32 stream_id = msg_get_stream_id(header); + u32 stream_msg = msg_get_stream_type(header); + + stream = get_stream_by_id(hsw, stream_id); + if (stream == NULL) + return; + + switch (stream_msg) { + case IPC_STR_STAGE_MESSAGE: + case IPC_STR_NOTIFICATION: + break; + case IPC_STR_RESET: + trace_ipc_notification("stream reset", stream->reply.stream_hw_id); + break; + case IPC_STR_PAUSE: + stream->running = false; + trace_ipc_notification("stream paused", + stream->reply.stream_hw_id); + break; + case IPC_STR_RESUME: + stream->running = true; + trace_ipc_notification("stream running", + stream->reply.stream_hw_id); + break; + } +} + +static int hsw_process_reply(struct sst_hsw *hsw, u32 header) +{ + struct ipc_message *msg; + u32 reply = msg_get_global_reply(header); + + trace_ipc_reply("processing -->", header); + + msg = reply_find_msg(hsw, header); + if (msg == NULL) { + trace_ipc_error("error: can't find message header", header); + return -EIO; + } + + /* first process the header */ + switch (reply) { + case IPC_GLB_REPLY_PENDING: + trace_ipc_pending_reply("received", header); + msg->pending = true; + hsw->pending = true; + return 1; + case IPC_GLB_REPLY_SUCCESS: + if (msg->pending) { + trace_ipc_pending_reply("completed", header); + sst_dsp_inbox_read(hsw->dsp, msg->rx_data, + msg->rx_size); + hsw->pending = false; + } else { + /* copy data from the DSP */ + sst_dsp_outbox_read(hsw->dsp, msg->rx_data, + msg->rx_size); + } + break; + /* these will be rare - but useful for debug */ + case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE: + trace_ipc_error("error: unknown message type", header); + msg->errno = -EBADMSG; + break; + case IPC_GLB_REPLY_OUT_OF_RESOURCES: + trace_ipc_error("error: out of resources", header); + msg->errno = -ENOMEM; + break; + case IPC_GLB_REPLY_BUSY: + trace_ipc_error("error: reply busy", header); + msg->errno = -EBUSY; + break; + case IPC_GLB_REPLY_FAILURE: + trace_ipc_error("error: reply failure", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_STAGE_UNINITIALIZED: + trace_ipc_error("error: stage uninitialized", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_NOT_FOUND: + trace_ipc_error("error: reply not found", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_SOURCE_NOT_STARTED: + trace_ipc_error("error: source not started", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_INVALID_REQUEST: + trace_ipc_error("error: invalid request", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_ERROR_INVALID_PARAM: + trace_ipc_error("error: invalid parameter", header); + msg->errno = -EINVAL; + break; + default: + trace_ipc_error("error: unknown reply", header); + msg->errno = -EINVAL; + break; + } + + /* update any stream states */ + if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE) + hsw_stream_update(hsw, msg); + + /* wake up and return the error if we have waiters on this message ? */ + list_del(&msg->list); + tx_msg_reply_complete(hsw, msg); + + return 1; +} + +static int hsw_module_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation, module_id; + int handled = 0; + + operation = msg_get_module_operation(header); + module_id = msg_get_module_id(header); + dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n", + header); + dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n", + operation, module_id); + + switch (operation) { + case IPC_MODULE_NOTIFICATION: + dev_dbg(hsw->dev, "module notification received"); + handled = 1; + break; + default: + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + +static int hsw_stream_message(struct sst_hsw *hsw, u32 header) +{ + u32 stream_msg, stream_id, stage_type; + struct sst_hsw_stream *stream; + int handled = 0; + + stream_msg = msg_get_stream_type(header); + stream_id = msg_get_stream_id(header); + stage_type = msg_get_stage_type(header); + + stream = get_stream_by_id(hsw, stream_id); + if (stream == NULL) + return handled; + + stream->header = header; + + switch (stream_msg) { + case IPC_STR_STAGE_MESSAGE: + dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n", + header); + break; + case IPC_STR_NOTIFICATION: + schedule_work(&stream->notify_work); + break; + default: + /* handle pending message complete request */ + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + +static int hsw_log_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT; + struct sst_hsw_log_stream *stream = &hsw->log_stream; + int ret = 1; + + if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) { + dev_err(hsw->dev, + "error: log msg not implemented 0x%8.8x\n", header); + return 0; + } + + mutex_lock(&stream->rw_mutex); + stream->last_pos = stream->curr_pos; + sst_dsp_inbox_read( + hsw->dsp, &stream->curr_pos, sizeof(stream->curr_pos)); + mutex_unlock(&stream->rw_mutex); + + schedule_work(&stream->notify_work); + + return ret; +} + +static int hsw_process_notification(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 type, header; + int handled = 1; + + header = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + type = msg_get_global_type(header); + + trace_ipc_request("processing -->", header); + + /* FW Ready is a special case */ + if (!hsw->boot_complete && header & IPC_FW_READY) { + hsw_fw_ready(hsw, header); + return handled; + } + + switch (type) { + case IPC_GLB_GET_FW_VERSION: + case IPC_GLB_ALLOCATE_STREAM: + case IPC_GLB_FREE_STREAM: + case IPC_GLB_GET_FW_CAPABILITIES: + case IPC_GLB_REQUEST_DUMP: + case IPC_GLB_GET_DEVICE_FORMATS: + case IPC_GLB_SET_DEVICE_FORMATS: + case IPC_GLB_ENTER_DX_STATE: + case IPC_GLB_GET_MIXER_STREAM_INFO: + case IPC_GLB_MAX_IPC_MESSAGE_TYPE: + case IPC_GLB_RESTORE_CONTEXT: + case IPC_GLB_SHORT_REPLY: + dev_err(hsw->dev, "error: message type %d header 0x%x\n", + type, header); + break; + case IPC_GLB_STREAM_MESSAGE: + handled = hsw_stream_message(hsw, header); + break; + case IPC_GLB_DEBUG_LOG_MESSAGE: + handled = hsw_log_message(hsw, header); + break; + case IPC_GLB_MODULE_OPERATION: + handled = hsw_module_message(hsw, header); + break; + default: + dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n", + type, header); + break; + } + + return handled; +} + +static irqreturn_t hsw_irq_thread(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); + u32 ipcx, ipcd; + int handled; + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + + ipcx = sst_dsp_ipc_msg_rx(hsw->dsp); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + + /* reply message from DSP */ + if (ipcx & SST_IPCX_DONE) { + + /* Handle Immediate reply from DSP Core */ + handled = hsw_process_reply(hsw, ipcx); + + if (handled > 0) { + /* clear DONE bit - tell DSP we have completed */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX, + SST_IPCX_DONE, 0); + + /* unmask Done interrupt */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, 0); + } + } + + /* new message from DSP */ + if (ipcd & SST_IPCD_BUSY) { + + /* Handle Notification and Delayed reply from DSP Core */ + handled = hsw_process_notification(hsw); + + /* clear BUSY bit and set DONE bit - accept new messages */ + if (handled > 0) { + sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD, + SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, 0); + } + } + + spin_unlock_irqrestore(&sst->spinlock, flags); + + /* continue to send any remaining messages... */ + queue_kthread_work(&hsw->kworker, &hsw->kwork); + + return IRQ_HANDLED; +} + +int sst_hsw_fw_get_version(struct sst_hsw *hsw, + struct sst_hsw_ipc_fw_version *version) +{ + int ret; + + ret = ipc_tx_message_wait(hsw, IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), + NULL, 0, version, sizeof(*version)); + if (ret < 0) + dev_err(hsw->dev, "error: get version failed\n"); + + return ret; +} + +/* Mixer Controls */ +int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 stage_id, u32 channel, u32 *volume) +{ + if (channel > 1) + return -EINVAL; + + sst_dsp_read(hsw->dsp, volume, + stream->reply.volume_register_address[channel], + sizeof(*volume)); + + return 0; +} + +/* stream volume */ +int sst_hsw_stream_set_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume) +{ + struct sst_hsw_ipc_volume_req *req; + u32 header; + int ret; + + trace_ipc_request("set stream volume", stream->reply.stream_hw_id); + + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) + return -EINVAL; + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | + IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); + header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); + header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); + header |= (stage_id << IPC_STG_ID_SHIFT); + + req = &stream->vol_req; + req->target_volume = volume; + + /* set both at same time ? */ + if (channel == SST_HSW_CHANNELS_ALL) { + if (hsw->mute[0] && hsw->mute[1]) { + hsw->mute_volume[0] = hsw->mute_volume[1] = volume; + return 0; + } else if (hsw->mute[0]) + req->channel = 1; + else if (hsw->mute[1]) + req->channel = 0; + else + req->channel = SST_HSW_CHANNELS_ALL; + } else { + /* set only 1 channel */ + if (hsw->mute[channel]) { + hsw->mute_volume[channel] = volume; + return 0; + } + req->channel = channel; + } + + ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: set stream volume failed\n"); + return ret; + } + + return 0; +} + +int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 *volume) +{ + if (channel > 1) + return -EINVAL; + + sst_dsp_read(hsw->dsp, volume, + hsw->mixer_info.volume_register_address[channel], + sizeof(*volume)); + + return 0; +} + +/* global mixer volume */ +int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 volume) +{ + struct sst_hsw_ipc_volume_req req; + u32 header; + int ret; + + trace_ipc_request("set mixer volume", volume); + + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) + return -EINVAL; + + /* set both at same time ? */ + if (channel == SST_HSW_CHANNELS_ALL) { + if (hsw->mute[0] && hsw->mute[1]) { + hsw->mute_volume[0] = hsw->mute_volume[1] = volume; + return 0; + } else if (hsw->mute[0]) + req.channel = 1; + else if (hsw->mute[1]) + req.channel = 0; + else + req.channel = SST_HSW_CHANNELS_ALL; + } else { + /* set only 1 channel */ + if (hsw->mute[channel]) { + hsw->mute_volume[channel] = volume; + return 0; + } + req.channel = channel; + } + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | + IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); + header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT); + header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); + header |= (stage_id << IPC_STG_ID_SHIFT); + + req.curve_duration = hsw->curve_duration; + req.curve_type = hsw->curve_type; + req.target_volume = volume; + + ret = ipc_tx_message_wait(hsw, header, &req, sizeof(req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: set mixer volume failed\n"); + return ret; + } + + return 0; +} + +/* Stream API */ +struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, + u32 (*notify_position)(struct sst_hsw_stream *stream, void *data), + void *data) +{ + struct sst_hsw_stream *stream; + struct sst_dsp *sst = hsw->dsp; + unsigned long flags; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (stream == NULL) + return NULL; + + spin_lock_irqsave(&sst->spinlock, flags); + stream->reply.stream_hw_id = INVALID_STREAM_HW_ID; + list_add(&stream->node, &hsw->stream_list); + stream->notify_position = notify_position; + stream->pdata = data; + stream->hsw = hsw; + stream->host_id = id; + + /* work to process notification messages */ + INIT_WORK(&stream->notify_work, hsw_notification_work); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return stream; +} + +int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ + u32 header; + int ret = 0; + struct sst_dsp *sst = hsw->dsp; + unsigned long flags; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to free, ignore it.\n"); + return 0; + } + + /* dont free DSP streams that are not commited */ + if (!stream->commited) + goto out; + + trace_ipc_request("stream free", stream->host_id); + + stream->free_req.stream_id = stream->reply.stream_hw_id; + header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); + + ret = ipc_tx_message_wait(hsw, header, &stream->free_req, + sizeof(stream->free_req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: free stream %d failed\n", + stream->free_req.stream_id); + return -EAGAIN; + } + + trace_hsw_stream_free_req(stream, &stream->free_req); + +out: + cancel_work_sync(&stream->notify_work); + spin_lock_irqsave(&sst->spinlock, flags); + list_del(&stream->node); + kfree(stream); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return ret; +} + +int sst_hsw_stream_set_bits(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, enum sst_hsw_bitdepth bits) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set bits\n"); + return -EINVAL; + } + + stream->request.format.bitdepth = bits; + return 0; +} + +int sst_hsw_stream_set_channels(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, int channels) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set channels\n"); + return -EINVAL; + } + + stream->request.format.ch_num = channels; + return 0; +} + +int sst_hsw_stream_set_rate(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, int rate) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set rate\n"); + return -EINVAL; + } + + stream->request.format.frequency = rate; + return 0; +} + +int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 map, + enum sst_hsw_channel_config config) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set map\n"); + return -EINVAL; + } + + stream->request.format.map = map; + stream->request.format.config = config; + return 0; +} + +int sst_hsw_stream_set_style(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, enum sst_hsw_interleaving style) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set style\n"); + return -EINVAL; + } + + stream->request.format.style = style; + return 0; +} + +int sst_hsw_stream_set_valid(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 bits) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set valid bits\n"); + return -EINVAL; + } + + stream->request.format.valid_bit = bits; + return 0; +} + +/* Stream Configuration */ +int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_stream_path_id path_id, + enum sst_hsw_stream_type stream_type, + enum sst_hsw_stream_format format_id) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set format\n"); + return -EINVAL; + } + + stream->request.path_id = path_id; + stream->request.stream_type = stream_type; + stream->request.format_id = format_id; + + trace_hsw_stream_alloc_request(stream, &stream->request); + + return 0; +} + +int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 ring_pt_address, u32 num_pages, + u32 ring_size, u32 ring_offset, u32 ring_first_pfn) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for buffer\n"); + return -EINVAL; + } + + stream->request.ringinfo.ring_pt_address = ring_pt_address; + stream->request.ringinfo.num_pages = num_pages; + stream->request.ringinfo.ring_size = ring_size; + stream->request.ringinfo.ring_offset = ring_offset; + stream->request.ringinfo.ring_first_pfn = ring_first_pfn; + + trace_hsw_stream_buffer(stream); + + return 0; +} + +int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, struct sst_module_runtime *runtime) +{ + struct sst_hsw_module_map *map = &stream->request.map; + struct sst_dsp *dsp = sst_hsw_get_dsp(hsw); + struct sst_module *module = runtime->module; + + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set module\n"); + return -EINVAL; + } + + /* only support initial module atm */ + map->module_entries_count = 1; + map->module_entries[0].module_id = module->id; + map->module_entries[0].entry_point = module->entry; + + stream->request.persistent_mem.offset = + sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM); + stream->request.persistent_mem.size = module->persistent_size; + + stream->request.scratch_mem.offset = + sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM); + stream->request.scratch_mem.size = dsp->scratch_size; + + dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id, + runtime->id); + dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n", + stream->request.persistent_mem.offset, + stream->request.persistent_mem.size); + dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n", + stream->request.scratch_mem.offset, + stream->request.scratch_mem.size); + + return 0; +} + +int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ + struct sst_hsw_ipc_stream_alloc_req *str_req = &stream->request; + struct sst_hsw_ipc_stream_alloc_reply *reply = &stream->reply; + u32 header; + int ret; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to commit, ignore it.\n"); + return 0; + } + + if (stream->commited) { + dev_warn(hsw->dev, "warning: stream is already committed, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream alloc", stream->host_id); + + header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); + + ret = ipc_tx_message_wait(hsw, header, str_req, sizeof(*str_req), + reply, sizeof(*reply)); + if (ret < 0) { + dev_err(hsw->dev, "error: stream commit failed\n"); + return ret; + } + + stream->commited = 1; + trace_hsw_stream_alloc_reply(stream); + + return 0; +} + +snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + return stream->old_position; +} + +void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, snd_pcm_uframes_t val) +{ + stream->old_position = val; +} + +bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + return stream->play_silence; +} + +void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, bool val) +{ + stream->play_silence = val; +} + +/* Stream Information - these calls could be inline but we want the IPC + ABI to be opaque to client PCM drivers to cope with any future ABI changes */ +int sst_hsw_mixer_get_info(struct sst_hsw *hsw) +{ + struct sst_hsw_ipc_stream_info_reply *reply; + u32 header; + int ret; + + reply = &hsw->mixer_info; + header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO); + + trace_ipc_request("get global mixer info", 0); + + ret = ipc_tx_message_wait(hsw, header, NULL, 0, reply, sizeof(*reply)); + if (ret < 0) { + dev_err(hsw->dev, "error: get stream info failed\n"); + return ret; + } + + trace_hsw_mixer_info_reply(reply); + + return 0; +} + +/* Send stream command */ +static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, + int stream_id, int wait) +{ + u32 header; + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type); + header |= (stream_id << IPC_STR_ID_SHIFT); + + if (wait) + return ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); + else + return ipc_tx_message_nowait(hsw, header, NULL, 0); +} + +/* Stream ALSA trigger operations */ +int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait) +{ + int ret; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to pause, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream pause", stream->reply.stream_hw_id); + + ret = sst_hsw_stream_operations(hsw, IPC_STR_PAUSE, + stream->reply.stream_hw_id, wait); + if (ret < 0) + dev_err(hsw->dev, "error: failed to pause stream %d\n", + stream->reply.stream_hw_id); + + return ret; +} + +int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait) +{ + int ret; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to resume, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream resume", stream->reply.stream_hw_id); + + ret = sst_hsw_stream_operations(hsw, IPC_STR_RESUME, + stream->reply.stream_hw_id, wait); + if (ret < 0) + dev_err(hsw->dev, "error: failed to resume stream %d\n", + stream->reply.stream_hw_id); + + return ret; +} + +int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ + int ret, tries = 10; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to reset, ignore it.\n"); + return 0; + } + + /* dont reset streams that are not commited */ + if (!stream->commited) + return 0; + + /* wait for pause to complete before we reset the stream */ + while (stream->running && tries--) + msleep(1); + if (!tries) { + dev_err(hsw->dev, "error: reset stream %d still running\n", + stream->reply.stream_hw_id); + return -EINVAL; + } + + trace_ipc_request("stream reset", stream->reply.stream_hw_id); + + ret = sst_hsw_stream_operations(hsw, IPC_STR_RESET, + stream->reply.stream_hw_id, 1); + if (ret < 0) + dev_err(hsw->dev, "error: failed to reset stream %d\n", + stream->reply.stream_hw_id); + return ret; +} + +/* Stream pointer positions */ +u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + u32 rpos; + + sst_dsp_read(hsw->dsp, &rpos, + stream->reply.read_position_register_address, sizeof(rpos)); + + return rpos; +} + +/* Stream presentation (monotonic) positions */ +u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + u64 ppos; + + sst_dsp_read(hsw->dsp, &ppos, + stream->reply.presentation_position_register_address, + sizeof(ppos)); + + return ppos; +} + +/* physical BE config */ +int sst_hsw_device_set_config(struct sst_hsw *hsw, + enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, + enum sst_hsw_device_mode mode, u32 clock_divider) +{ + struct sst_hsw_ipc_device_config_req config; + u32 header; + int ret; + + trace_ipc_request("set device config", dev); + + config.ssp_interface = dev; + config.clock_frequency = mclk; + config.mode = mode; + config.clock_divider = clock_divider; + if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER) + config.channels = 4; + else + config.channels = 2; + + trace_hsw_device_config_req(&config); + + header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); + + ret = ipc_tx_message_wait(hsw, header, &config, sizeof(config), + NULL, 0); + if (ret < 0) + dev_err(hsw->dev, "error: set device formats failed\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_device_set_config); + +/* DX Config */ +int sst_hsw_dx_set_state(struct sst_hsw *hsw, + enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) +{ + u32 header, state_; + int ret, item; + + header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); + state_ = state; + + trace_ipc_request("PM enter Dx state", state); + + ret = ipc_tx_message_wait(hsw, header, &state_, sizeof(state_), + dx, sizeof(*dx)); + if (ret < 0) { + dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); + return ret; + } + + for (item = 0; item < dx->entries_no; item++) { + dev_dbg(hsw->dev, + "Item[%d] offset[%x] - size[%x] - source[%x]\n", + item, dx->mem_info[item].offset, + dx->mem_info[item].size, + dx->mem_info[item].source); + } + dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", + dx->entries_no, state); + + return ret; +} + +struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, + int mod_id, int offset) +{ + struct sst_dsp *dsp = hsw->dsp; + struct sst_module *module; + struct sst_module_runtime *runtime; + int err; + + module = sst_module_get_from_id(dsp, mod_id); + if (module == NULL) { + dev_err(dsp->dev, "error: failed to get module %d for pcm\n", + mod_id); + return NULL; + } + + runtime = sst_module_runtime_new(module, mod_id, NULL); + if (runtime == NULL) { + dev_err(dsp->dev, "error: failed to create module %d runtime\n", + mod_id); + return NULL; + } + + err = sst_module_runtime_alloc_blocks(runtime, offset); + if (err < 0) { + dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n", + mod_id); + sst_module_runtime_free(runtime); + return NULL; + } + + dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id, + mod_id); + return runtime; +} + +void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime) +{ + sst_module_runtime_free_blocks(runtime); + sst_module_runtime_free(runtime); +} + +#ifdef CONFIG_PM +static int sst_hsw_dx_state_dump(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 item, offset, size; + int ret = 0; + + trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS); + + if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) { + dev_err(hsw->dev, + "error: number of FW context regions greater than %d\n", + SST_HSW_MAX_DX_REGIONS); + memset(&hsw->dx, 0, sizeof(hsw->dx)); + return -EINVAL; + } + + ret = sst_dsp_dma_get_channel(sst, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + /* set on-demond mode on engine 0 channel 3 */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); + + for (item = 0; item < hsw->dx.entries_no; item++) { + if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP + && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET + && hsw->dx.mem_info[item].offset < + DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { + + offset = hsw->dx.mem_info[item].offset + - DSP_DRAM_ADDR_OFFSET; + size = (hsw->dx.mem_info[item].size + 3) & (~3); + + ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset, + sst->addr.lpe_base + offset, size); + if (ret < 0) { + dev_err(hsw->dev, + "error: FW context dump failed\n"); + memset(&hsw->dx, 0, sizeof(hsw->dx)); + goto out; + } + } + } + +out: + sst_dsp_dma_put_channel(sst); + return ret; +} + +static int sst_hsw_dx_state_restore(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 item, offset, size; + int ret; + + for (item = 0; item < hsw->dx.entries_no; item++) { + if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP + && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET + && hsw->dx.mem_info[item].offset < + DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { + + offset = hsw->dx.mem_info[item].offset + - DSP_DRAM_ADDR_OFFSET; + size = (hsw->dx.mem_info[item].size + 3) & (~3); + + ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset, + hsw->dx_context_paddr + offset, size); + if (ret < 0) { + dev_err(hsw->dev, + "error: FW context restore failed\n"); + return ret; + } + } + } + + return 0; +} + +static void sst_hsw_drop_all(struct sst_hsw *hsw) +{ + struct ipc_message *msg, *tmp; + unsigned long flags; + int tx_drop_cnt = 0, rx_drop_cnt = 0; + + /* drop all TX and Rx messages before we stall + reset DSP */ + spin_lock_irqsave(&hsw->dsp->spinlock, flags); + + list_for_each_entry_safe(msg, tmp, &hsw->tx_list, list) { + list_move(&msg->list, &hsw->empty_list); + tx_drop_cnt++; + } + + list_for_each_entry_safe(msg, tmp, &hsw->rx_list, list) { + list_move(&msg->list, &hsw->empty_list); + rx_drop_cnt++; + } + + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + + if (tx_drop_cnt || rx_drop_cnt) + dev_err(hsw->dev, "dropped IPC msg RX=%d, TX=%d\n", + tx_drop_cnt, rx_drop_cnt); +} + +int sst_hsw_dsp_load(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + struct sst_fw *sst_fw, *t; + int ret; + + dev_dbg(hsw->dev, "loading audio DSP...."); + + ret = sst_dsp_wake(dsp); + if (ret < 0) { + dev_err(hsw->dev, "error: failed to wake audio DSP\n"); + return -ENODEV; + } + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) { + ret = sst_fw_reload(sst_fw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW reload failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + } + ret = sst_block_alloc_scratch(hsw->dsp); + if (ret < 0) + return -EINVAL; + + sst_dsp_dma_put_channel(dsp); + return 0; +} + +static int sst_hsw_dsp_restore(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + int ret; + + dev_dbg(hsw->dev, "restoring audio DSP...."); + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + ret = sst_hsw_dx_state_restore(hsw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW context restore failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + sst_dsp_dma_put_channel(dsp); + + /* wait for DSP boot completion */ + sst_dsp_boot(dsp); + + return ret; +} + +int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw) +{ + int ret; + + dev_dbg(hsw->dev, "audio dsp runtime suspend\n"); + + ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx); + if (ret < 0) + return ret; + + sst_dsp_stall(hsw->dsp); + + ret = sst_hsw_dx_state_dump(hsw); + if (ret < 0) + return ret; + + sst_hsw_drop_all(hsw); + + return 0; +} + +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw) +{ + struct sst_fw *sst_fw, *t; + struct sst_dsp *dsp = hsw->dsp; + + list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { + sst_fw_unload(sst_fw); + } + sst_block_free_scratch(dsp); + + hsw->boot_complete = false; + + sst_dsp_sleep(dsp); + + return 0; +} + +int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) +{ + struct device *dev = hsw->dev; + int ret; + + dev_dbg(dev, "audio dsp runtime resume\n"); + + if (hsw->boot_complete) + return 1; /* tell caller no action is required */ + + ret = sst_hsw_dsp_restore(hsw); + if (ret < 0) + dev_err(dev, "error: audio DSP boot failure\n"); + + sst_hsw_init_module_state(hsw); + + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); + return -EIO; + } + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(hsw, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) + dev_err(dev, "error: SSP re-initialization failed\n"); + + return ret; +} +#endif + +static int msg_empty_list_init(struct sst_hsw *hsw) +{ + int i; + + hsw->msg = kzalloc(sizeof(struct ipc_message) * + IPC_EMPTY_LIST_SIZE, GFP_KERNEL); + if (hsw->msg == NULL) + return -ENOMEM; + + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + init_waitqueue_head(&hsw->msg[i].waitq); + list_add(&hsw->msg[i].list, &hsw->empty_list); + } + + return 0; +} + +struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) +{ + return hsw->dsp; +} + +void sst_hsw_init_module_state(struct sst_hsw *hsw) +{ + struct sst_module *module; + enum sst_hsw_module_id id; + + /* the base fw contains several modules */ + for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) { + module = sst_module_get_from_id(hsw->dsp, id); + if (module) { + /* module waves is active only after being enabled */ + if (id == SST_HSW_MODULE_WAVES) + module->state = SST_MODULE_STATE_INITIALIZED; + else + module->state = SST_MODULE_STATE_ACTIVE; + } + } +} + +bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id) +{ + struct sst_module *module; + + module = sst_module_get_from_id(hsw->dsp, module_id); + if (module == NULL || module->state == SST_MODULE_STATE_UNLOADED) + return false; + else + return true; +} + +bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id) +{ + struct sst_module *module; + + module = sst_module_get_from_id(hsw->dsp, module_id); + if (module != NULL && module->state == SST_MODULE_STATE_ACTIVE) + return true; + else + return false; +} + +void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + hsw->enabled_modules_rtd3 |= (1 << module_id); +} + +void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + hsw->enabled_modules_rtd3 &= ~(1 << module_id); +} + +bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + return hsw->enabled_modules_rtd3 & (1 << module_id); +} + +void sst_hsw_reset_param_buf(struct sst_hsw *hsw) +{ + hsw->param_idx_w = 0; + hsw->param_idx_r = 0; + memset((void *)hsw->param_buf, 0, sizeof(hsw->param_buf)); +} + +int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf) +{ + /* save line to the first available position of param buffer */ + if (hsw->param_idx_w > WAVES_PARAM_LINES - 1) { + dev_warn(hsw->dev, "warning: param buffer overflow!\n"); + return -EPERM; + } + memcpy(hsw->param_buf[hsw->param_idx_w], buf, WAVES_PARAM_COUNT); + hsw->param_idx_w++; + return 0; +} + +int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf) +{ + u8 id = 0; + + /* read the first matching line from param buffer */ + while (hsw->param_idx_r < WAVES_PARAM_LINES) { + id = hsw->param_buf[hsw->param_idx_r][0]; + hsw->param_idx_r++; + if (buf[0] == id) { + memcpy(buf, hsw->param_buf[hsw->param_idx_r], + WAVES_PARAM_COUNT); + break; + } + } + if (hsw->param_idx_r > WAVES_PARAM_LINES - 1) { + dev_dbg(hsw->dev, "end of buffer, roll to the beginning\n"); + hsw->param_idx_r = 0; + return 0; + } + return 0; +} + +int sst_hsw_launch_param_buf(struct sst_hsw *hsw) +{ + int ret, idx; + + if (!sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { + dev_dbg(hsw->dev, "module waves is not active\n"); + return 0; + } + + /* put all param lines to DSP through ipc */ + for (idx = 0; idx < hsw->param_idx_w; idx++) { + ret = sst_hsw_module_set_param(hsw, + SST_HSW_MODULE_WAVES, 0, hsw->param_buf[idx][0], + WAVES_PARAM_COUNT, hsw->param_buf[idx]); + if (ret < 0) + return ret; + } + return 0; +} + +int sst_hsw_module_load(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, char *name) +{ + int ret = 0; + const struct firmware *fw = NULL; + struct sst_fw *hsw_sst_fw; + struct sst_module *module; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + dev_dbg(dev, "sst_hsw_module_load id=%d, name='%s'", module_id, name); + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + /* loading for the first time */ + if (module_id == SST_HSW_MODULE_BASE_FW) { + /* for base module: use fw requested in acpi probe */ + fw = dsp->pdata->fw; + if (!fw) { + dev_err(dev, "request Base fw failed\n"); + return -ENODEV; + } + } else { + /* try and load any other optional modules if they are + * available. Use dev_info instead of dev_err in case + * request firmware failed */ + ret = request_firmware(&fw, name, dev); + if (ret) { + dev_info(dev, "fw image %s not available(%d)\n", + name, ret); + return ret; + } + } + hsw_sst_fw = sst_fw_new(dsp, fw, hsw); + if (hsw_sst_fw == NULL) { + dev_err(dev, "error: failed to load firmware\n"); + ret = -ENOMEM; + goto out; + } + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "error: no module %d in firmware %s\n", + module_id, name); + } + } else + dev_info(dev, "module %d (%s) already loaded\n", + module_id, name); +out: + /* release fw, but base fw should be released by acpi driver */ + if (fw && module_id != SST_HSW_MODULE_BASE_FW) + release_firmware(fw); + + return ret; +} + +int sst_hsw_module_enable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id) +{ + int ret; + u32 header = 0; + struct sst_hsw_ipc_module_config config; + struct sst_module *module; + struct sst_module_runtime *runtime; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + if (!sst_hsw_is_module_loaded(hsw, module_id)) { + dev_dbg(dev, "module %d not loaded\n", module_id); + return 0; + } + + if (sst_hsw_is_module_active(hsw, module_id)) { + dev_info(dev, "module %d already enabled\n", module_id); + return 0; + } + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "module %d not valid\n", module_id); + return -ENXIO; + } + + runtime = sst_module_runtime_get_from_id(module, module_id); + if (runtime == NULL) { + dev_err(dev, "runtime %d not valid", module_id); + return -ENXIO; + } + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_ENABLE) | + IPC_MODULE_ID(module_id); + dev_dbg(dev, "module enable header: %x\n", header); + + config.map.module_entries_count = 1; + config.map.module_entries[0].module_id = module->id; + config.map.module_entries[0].entry_point = module->entry; + + config.persistent_mem.offset = + sst_dsp_get_offset(dsp, + runtime->persistent_offset, SST_MEM_DRAM); + config.persistent_mem.size = module->persistent_size; + + config.scratch_mem.offset = + sst_dsp_get_offset(dsp, + dsp->scratch_offset, SST_MEM_DRAM); + config.scratch_mem.size = module->scratch_size; + dev_dbg(dev, "mod %d enable p:%d @ %x, s:%d @ %x, ep: %x", + config.map.module_entries[0].module_id, + config.persistent_mem.size, + config.persistent_mem.offset, + config.scratch_mem.size, config.scratch_mem.offset, + config.map.module_entries[0].entry_point); + + ret = ipc_tx_message_wait(hsw, header, + &config, sizeof(config), NULL, 0); + if (ret < 0) + dev_err(dev, "ipc: module enable failed - %d\n", ret); + else + module->state = SST_MODULE_STATE_ACTIVE; + + return ret; +} + +int sst_hsw_module_disable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id) +{ + int ret; + u32 header; + struct sst_module *module; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + if (!sst_hsw_is_module_loaded(hsw, module_id)) { + dev_dbg(dev, "module %d not loaded\n", module_id); + return 0; + } + + if (!sst_hsw_is_module_active(hsw, module_id)) { + dev_info(dev, "module %d already disabled\n", module_id); + return 0; + } + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "module %d not valid\n", module_id); + return -ENXIO; + } + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) | + IPC_MODULE_ID(module_id); + + ret = ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); + if (ret < 0) + dev_err(dev, "module disable failed - %d\n", ret); + else + module->state = SST_MODULE_STATE_INITIALIZED; + + return ret; +} + +int sst_hsw_module_set_param(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, u32 parameter_id, + u32 param_size, char *param) +{ + int ret; + unsigned char *data = NULL; + u32 header = 0; + u32 payload_size = 0, transfer_parameter_size = 0; + dma_addr_t dma_addr = 0; + struct sst_hsw_transfer_parameter *parameter; + struct device *dev = hsw->dev; + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_SET_PARAMETER) | + IPC_MODULE_ID(module_id); + dev_dbg(dev, "sst_hsw_module_set_param header=%x\n", header); + + payload_size = param_size + + sizeof(struct sst_hsw_transfer_parameter) - + sizeof(struct sst_hsw_transfer_list); + dev_dbg(dev, "parameter size : %d\n", param_size); + dev_dbg(dev, "payload size : %d\n", payload_size); + + if (payload_size <= SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE) { + /* short parameter, mailbox can contain data */ + dev_dbg(dev, "transfer parameter size : %d\n", + transfer_parameter_size); + + transfer_parameter_size = ALIGN(payload_size, 4); + dev_dbg(dev, "transfer parameter aligned size : %d\n", + transfer_parameter_size); + + parameter = kzalloc(transfer_parameter_size, GFP_KERNEL); + if (parameter == NULL) + return -ENOMEM; + + memcpy(parameter->data, param, param_size); + } else { + dev_warn(dev, "transfer parameter size too large!"); + return 0; + } + + parameter->parameter_id = parameter_id; + parameter->data_size = param_size; + + ret = ipc_tx_message_wait(hsw, header, + parameter, transfer_parameter_size , NULL, 0); + if (ret < 0) + dev_err(dev, "ipc: module set parameter failed - %d\n", ret); + + kfree(parameter); + + if (data) + dma_free_coherent(hsw->dsp->dma_dev, + param_size, (void *)data, dma_addr); + + return ret; +} + +static struct sst_dsp_device hsw_dev = { + .thread = hsw_irq_thread, + .ops = &haswell_ops, +}; + +int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_hsw_ipc_fw_version version; + struct sst_hsw *hsw; + int ret; + + dev_dbg(dev, "initialising Audio DSP IPC\n"); + + hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL); + if (hsw == NULL) + return -ENOMEM; + + hsw->dev = dev; + INIT_LIST_HEAD(&hsw->stream_list); + INIT_LIST_HEAD(&hsw->tx_list); + INIT_LIST_HEAD(&hsw->rx_list); + INIT_LIST_HEAD(&hsw->empty_list); + init_waitqueue_head(&hsw->boot_wait); + init_waitqueue_head(&hsw->wait_txq); + + ret = msg_empty_list_init(hsw); + if (ret < 0) + return -ENOMEM; + + /* start the IPC message thread */ + init_kthread_worker(&hsw->kworker); + hsw->tx_thread = kthread_run(kthread_worker_fn, + &hsw->kworker, "%s", + dev_name(hsw->dev)); + if (IS_ERR(hsw->tx_thread)) { + ret = PTR_ERR(hsw->tx_thread); + dev_err(hsw->dev, "error: failed to create message TX task\n"); + goto err_free_msg; + } + init_kthread_work(&hsw->kwork, ipc_tx_msgs); + + hsw_dev.thread_context = hsw; + + /* init SST shim */ + hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata); + if (hsw->dsp == NULL) { + ret = -ENODEV; + goto dsp_err; + } + + /* allocate DMA buffer for context storage */ + hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev, + SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL); + if (hsw->dx_context == NULL) { + ret = -ENOMEM; + goto dma_err; + } + + /* keep the DSP in reset state for base FW loading */ + sst_dsp_reset(hsw->dsp); + + /* load base module and other modules in base firmware image */ + ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base"); + if (ret < 0) + goto fw_err; + + /* try to load module waves */ + sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "intel/IntcPP01.bin"); + + /* allocate scratch mem regions */ + ret = sst_block_alloc_scratch(hsw->dsp); + if (ret < 0) + goto boot_err; + + /* init param buffer */ + sst_hsw_reset_param_buf(hsw); + + /* wait for DSP boot completion */ + sst_dsp_boot(hsw->dsp); + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (ret == 0) { + ret = -EIO; + dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); + goto boot_err; + } + + /* init module state after boot */ + sst_hsw_init_module_state(hsw); + + /* get the FW version */ + sst_hsw_fw_get_version(hsw, &version); + + /* get the globalmixer */ + ret = sst_hsw_mixer_get_info(hsw); + if (ret < 0) { + dev_err(hsw->dev, "error: failed to get stream info\n"); + goto boot_err; + } + + pdata->dsp = hsw; + return 0; + +boot_err: + sst_dsp_reset(hsw->dsp); + sst_fw_free_all(hsw->dsp); +fw_err: + dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, + hsw->dx_context, hsw->dx_context_paddr); +dma_err: + sst_dsp_free(hsw->dsp); +dsp_err: + kthread_stop(hsw->tx_thread); +err_free_msg: + kfree(hsw->msg); + + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); + +void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_hsw *hsw = pdata->dsp; + + sst_dsp_reset(hsw->dsp); + sst_fw_free_all(hsw->dsp); + dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, + hsw->dx_context, hsw->dx_context_paddr); + sst_dsp_free(hsw->dsp); + kthread_stop(hsw->tx_thread); + kfree(hsw->msg); +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.h b/sound/soc/intel/haswell/sst-haswell-ipc.h new file mode 100644 index 0000000..06d71ae --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-ipc.h @@ -0,0 +1,534 @@ +/* + * Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. 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 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 __SST_HASWELL_IPC_H +#define __SST_HASWELL_IPC_H + +#include +#include +#include +#include + +#define SST_HSW_NO_CHANNELS 4 +#define SST_HSW_MAX_DX_REGIONS 14 +#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024) +#define SST_HSW_CHANNELS_ALL 0xffffffff + +#define SST_HSW_FW_LOG_CONFIG_DWORDS 12 +#define SST_HSW_GLOBAL_LOG 15 + +/** + * Upfront defined maximum message size that is + * expected by the in/out communication pipes in FW. + */ +#define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400 +#define SST_HSW_MAX_INFO_SIZE 64 +#define SST_HSW_BUILD_HASH_LENGTH 40 +#define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500 +#define WAVES_PARAM_COUNT 128 +#define WAVES_PARAM_LINES 160 + +struct sst_hsw; +struct sst_hsw_stream; +struct sst_hsw_log_stream; +struct sst_pdata; +struct sst_module; +struct sst_module_runtime; +extern struct sst_ops haswell_ops; + +/* Stream Allocate Path ID */ +enum sst_hsw_stream_path_id { + SST_HSW_STREAM_PATH_SSP0_OUT = 0, + SST_HSW_STREAM_PATH_SSP0_IN = 1, + SST_HSW_STREAM_PATH_MAX_PATH_ID = 2, +}; + +/* Stream Allocate Stream Type */ +enum sst_hsw_stream_type { + SST_HSW_STREAM_TYPE_RENDER = 0, + SST_HSW_STREAM_TYPE_SYSTEM = 1, + SST_HSW_STREAM_TYPE_CAPTURE = 2, + SST_HSW_STREAM_TYPE_LOOPBACK = 3, + SST_HSW_STREAM_TYPE_MAX_STREAM_TYPE = 4, +}; + +/* Stream Allocate Stream Format */ +enum sst_hsw_stream_format { + SST_HSW_STREAM_FORMAT_PCM_FORMAT = 0, + SST_HSW_STREAM_FORMAT_MP3_FORMAT = 1, + SST_HSW_STREAM_FORMAT_AAC_FORMAT = 2, + SST_HSW_STREAM_FORMAT_MAX_FORMAT_ID = 3, +}; + +/* Device ID */ +enum sst_hsw_device_id { + SST_HSW_DEVICE_SSP_0 = 0, + SST_HSW_DEVICE_SSP_1 = 1, +}; + +/* Device Master Clock Frequency */ +enum sst_hsw_device_mclk { + SST_HSW_DEVICE_MCLK_OFF = 0, + SST_HSW_DEVICE_MCLK_FREQ_6_MHZ = 1, + SST_HSW_DEVICE_MCLK_FREQ_12_MHZ = 2, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ = 3, +}; + +/* Device Clock Master */ +enum sst_hsw_device_mode { + SST_HSW_DEVICE_CLOCK_SLAVE = 0, + SST_HSW_DEVICE_CLOCK_MASTER = 1, + SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2, +}; + +/* DX Power State */ +enum sst_hsw_dx_state { + SST_HSW_DX_STATE_D0 = 0, + SST_HSW_DX_STATE_D1 = 1, + SST_HSW_DX_STATE_D3 = 3, + SST_HSW_DX_STATE_MAX = 3, +}; + +/* Audio stream stage IDs */ +enum sst_hsw_fx_stage_id { + SST_HSW_STAGE_ID_WAVES = 0, + SST_HSW_STAGE_ID_DTS = 1, + SST_HSW_STAGE_ID_DOLBY = 2, + SST_HSW_STAGE_ID_BOOST = 3, + SST_HSW_STAGE_ID_MAX_FX_ID +}; + +/* DX State Type */ +enum sst_hsw_dx_type { + SST_HSW_DX_TYPE_FW_IMAGE = 0, + SST_HSW_DX_TYPE_MEMORY_DUMP = 1 +}; + +/* Volume Curve Type*/ +enum sst_hsw_volume_curve { + SST_HSW_VOLUME_CURVE_NONE = 0, + SST_HSW_VOLUME_CURVE_FADE = 1 +}; + +/* Sample ordering */ +enum sst_hsw_interleaving { + SST_HSW_INTERLEAVING_PER_CHANNEL = 0, + SST_HSW_INTERLEAVING_PER_SAMPLE = 1, +}; + +/* Channel indices */ +enum sst_hsw_channel_index { + SST_HSW_CHANNEL_LEFT = 0, + SST_HSW_CHANNEL_CENTER = 1, + SST_HSW_CHANNEL_RIGHT = 2, + SST_HSW_CHANNEL_LEFT_SURROUND = 3, + SST_HSW_CHANNEL_CENTER_SURROUND = 3, + SST_HSW_CHANNEL_RIGHT_SURROUND = 4, + SST_HSW_CHANNEL_LFE = 7, + SST_HSW_CHANNEL_INVALID = 0xF, +}; + +/* List of supported channel maps. */ +enum sst_hsw_channel_config { + SST_HSW_CHANNEL_CONFIG_MONO = 0, /* mono only. */ + SST_HSW_CHANNEL_CONFIG_STEREO = 1, /* L & R. */ + SST_HSW_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only. */ + SST_HSW_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only. */ + SST_HSW_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only. */ + SST_HSW_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only. */ + SST_HSW_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only. */ + SST_HSW_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs. */ + SST_HSW_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE. */ + SST_HSW_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two. */ + SST_HSW_CHANNEL_CONFIG_INVALID, +}; + +/* List of supported bit depths. */ +enum sst_hsw_bitdepth { + SST_HSW_DEPTH_8BIT = 8, + SST_HSW_DEPTH_16BIT = 16, + SST_HSW_DEPTH_24BIT = 24, /* Default. */ + SST_HSW_DEPTH_32BIT = 32, + SST_HSW_DEPTH_INVALID = 33, +}; + +enum sst_hsw_module_id { + SST_HSW_MODULE_BASE_FW = 0x0, + SST_HSW_MODULE_MP3 = 0x1, + SST_HSW_MODULE_AAC_5_1 = 0x2, + SST_HSW_MODULE_AAC_2_0 = 0x3, + SST_HSW_MODULE_SRC = 0x4, + SST_HSW_MODULE_WAVES = 0x5, + SST_HSW_MODULE_DOLBY = 0x6, + SST_HSW_MODULE_BOOST = 0x7, + SST_HSW_MODULE_LPAL = 0x8, + SST_HSW_MODULE_DTS = 0x9, + SST_HSW_MODULE_PCM_CAPTURE = 0xA, + SST_HSW_MODULE_PCM_SYSTEM = 0xB, + SST_HSW_MODULE_PCM_REFERENCE = 0xC, + SST_HSW_MODULE_PCM = 0xD, + SST_HSW_MODULE_BLUETOOTH_RENDER_MODULE = 0xE, + SST_HSW_MODULE_BLUETOOTH_CAPTURE_MODULE = 0xF, + SST_HSW_MAX_MODULE_ID, +}; + +enum sst_hsw_performance_action { + SST_HSW_PERF_START = 0, + SST_HSW_PERF_STOP = 1, +}; + +struct sst_hsw_transfer_info { + uint32_t destination; /* destination address */ + uint32_t reverse:1; /* if 1 data flows from destination */ + uint32_t size:31; /* transfer size in bytes.*/ + uint16_t first_page_offset; /* offset to data in the first page. */ + uint8_t packed_pages; /* page addresses. Each occupies 20 bits */ +} __attribute__((packed)); + +struct sst_hsw_transfer_list { + uint32_t transfers_count; + struct sst_hsw_transfer_info transfers; +} __attribute__((packed)); + +struct sst_hsw_transfer_parameter { + uint32_t parameter_id; + uint32_t data_size; + union { + uint8_t data[1]; + struct sst_hsw_transfer_list transfer_list; + }; +} __attribute__((packed)); + +/* SST firmware module info */ +struct sst_hsw_module_info { + u8 name[SST_HSW_MAX_INFO_SIZE]; + u8 version[SST_HSW_MAX_INFO_SIZE]; +} __attribute__((packed)); + +/* Module entry point */ +struct sst_hsw_module_entry { + enum sst_hsw_module_id module_id; + u32 entry_point; +} __attribute__((packed)); + +/* Module map - alignement matches DSP */ +struct sst_hsw_module_map { + u8 module_entries_count; + struct sst_hsw_module_entry module_entries[1]; +} __attribute__((packed)); + +struct sst_hsw_memory_info { + u32 offset; + u32 size; +} __attribute__((packed)); + +struct sst_hsw_fx_enable { + struct sst_hsw_module_map module_map; + struct sst_hsw_memory_info persistent_mem; +} __attribute__((packed)); + +struct sst_hsw_ipc_module_config { + struct sst_hsw_module_map map; + struct sst_hsw_memory_info persistent_mem; + struct sst_hsw_memory_info scratch_mem; +} __attribute__((packed)); + +struct sst_hsw_get_fx_param { + u32 parameter_id; + u32 param_size; +} __attribute__((packed)); + +struct sst_hsw_perf_action { + u32 action; +} __attribute__((packed)); + +struct sst_hsw_perf_data { + u64 timestamp; + u64 cycles; + u64 datatime; +} __attribute__((packed)); + +/* FW version */ +struct sst_hsw_ipc_fw_version { + u8 build; + u8 minor; + u8 major; + u8 type; + u8 fw_build_hash[SST_HSW_BUILD_HASH_LENGTH]; + u32 fw_log_providers_hash; +} __attribute__((packed)); + +/* Stream ring info */ +struct sst_hsw_ipc_stream_ring { + u32 ring_pt_address; + u32 num_pages; + u32 ring_size; + u32 ring_offset; + u32 ring_first_pfn; +} __attribute__((packed)); + +/* Debug Dump Log Enable Request */ +struct sst_hsw_ipc_debug_log_enable_req { + struct sst_hsw_ipc_stream_ring ringinfo; + u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; +} __attribute__((packed)); + +/* Debug Dump Log Reply */ +struct sst_hsw_ipc_debug_log_reply { + u32 log_buffer_begining; + u32 log_buffer_size; +} __attribute__((packed)); + +/* Stream glitch position */ +struct sst_hsw_ipc_stream_glitch_position { + u32 glitch_type; + u32 present_pos; + u32 write_pos; +} __attribute__((packed)); + +/* Stream get position */ +struct sst_hsw_ipc_stream_get_position { + u32 position; + u32 fw_cycle_count; +} __attribute__((packed)); + +/* Stream set position */ +struct sst_hsw_ipc_stream_set_position { + u32 position; + u32 end_of_buffer; +} __attribute__((packed)); + +/* Stream Free Request */ +struct sst_hsw_ipc_stream_free_req { + u8 stream_id; + u8 reserved[3]; +} __attribute__((packed)); + +/* Set Volume Request */ +struct sst_hsw_ipc_volume_req { + u32 channel; + u32 target_volume; + u64 curve_duration; + u32 curve_type; +} __attribute__((packed)); + +/* Device Configuration Request */ +struct sst_hsw_ipc_device_config_req { + u32 ssp_interface; + u32 clock_frequency; + u32 mode; + u16 clock_divider; + u8 channels; + u8 reserved; +} __attribute__((packed)); + +/* Audio Data formats */ +struct sst_hsw_audio_data_format_ipc { + u32 frequency; + u32 bitdepth; + u32 map; + u32 config; + u32 style; + u8 ch_num; + u8 valid_bit; + u8 reserved[2]; +} __attribute__((packed)); + +/* Stream Allocate Request */ +struct sst_hsw_ipc_stream_alloc_req { + u8 path_id; + u8 stream_type; + u8 format_id; + u8 reserved; + struct sst_hsw_audio_data_format_ipc format; + struct sst_hsw_ipc_stream_ring ringinfo; + struct sst_hsw_module_map map; + struct sst_hsw_memory_info persistent_mem; + struct sst_hsw_memory_info scratch_mem; + u32 number_of_notifications; +} __attribute__((packed)); + +/* Stream Allocate Reply */ +struct sst_hsw_ipc_stream_alloc_reply { + u32 stream_hw_id; + u32 mixer_hw_id; // returns rate ???? + u32 read_position_register_address; + u32 presentation_position_register_address; + u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; + u32 volume_register_address[SST_HSW_NO_CHANNELS]; +} __attribute__((packed)); + +/* Get Mixer Stream Info */ +struct sst_hsw_ipc_stream_info_reply { + u32 mixer_hw_id; + u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; + u32 volume_register_address[SST_HSW_NO_CHANNELS]; +} __attribute__((packed)); + +/* DX State Request */ +struct sst_hsw_ipc_dx_req { + u8 state; + u8 reserved[3]; +} __attribute__((packed)); + +/* DX State Reply Memory Info Item */ +struct sst_hsw_ipc_dx_memory_item { + u32 offset; + u32 size; + u32 source; +} __attribute__((packed)); + +/* DX State Reply */ +struct sst_hsw_ipc_dx_reply { + u32 entries_no; + struct sst_hsw_ipc_dx_memory_item mem_info[SST_HSW_MAX_DX_REGIONS]; +} __attribute__((packed)); + +struct sst_hsw_ipc_fw_version; + +/* SST Init & Free */ +struct sst_hsw *sst_hsw_new(struct device *dev, const u8 *fw, size_t fw_length, + u32 fw_offset); +void sst_hsw_free(struct sst_hsw *hsw); +int sst_hsw_fw_get_version(struct sst_hsw *hsw, + struct sst_hsw_ipc_fw_version *version); +u32 create_channel_map(enum sst_hsw_channel_config config); + +/* Stream Mixer Controls - */ +int sst_hsw_stream_set_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume); +int sst_hsw_stream_get_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 *volume); + +/* Global Mixer Controls - */ +int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 volume); +int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 *volume); + +/* Stream API */ +struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, + u32 (*get_write_position)(struct sst_hsw_stream *stream, void *data), + void *data); + +int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream); + +/* Stream Configuration */ +int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_stream_path_id path_id, + enum sst_hsw_stream_type stream_type, + enum sst_hsw_stream_format format_id); + +int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 ring_pt_address, u32 num_pages, + u32 ring_size, u32 ring_offset, u32 ring_first_pfn); + +int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream); + +int sst_hsw_stream_set_valid(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 bits); +int sst_hsw_stream_set_rate(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int rate); +int sst_hsw_stream_set_bits(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_bitdepth bits); +int sst_hsw_stream_set_channels(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, int channels); +int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 map, + enum sst_hsw_channel_config config); +int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_interleaving style); +int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, struct sst_module_runtime *runtime); +int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 offset, u32 size); +int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 offset, u32 size); +snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); +void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, snd_pcm_uframes_t val); +bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); +void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, bool val); +int sst_hsw_mixer_get_info(struct sst_hsw *hsw); + +/* Stream ALSA trigger operations */ +int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait); +int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait); +int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream); + +/* Stream pointer positions */ +int sst_hsw_stream_get_read_pos(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 *position); +int sst_hsw_stream_get_write_pos(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 *position); +u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); +u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); + +/* HW port config */ +int sst_hsw_device_set_config(struct sst_hsw *hsw, + enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, + enum sst_hsw_device_mode mode, u32 clock_divider); + +/* DX Config */ +int sst_hsw_dx_set_state(struct sst_hsw *hsw, + enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx); + +/* init */ +int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); +void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); +struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); + +/* fw module function */ +void sst_hsw_init_module_state(struct sst_hsw *hsw); +bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); +bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id); +bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_reset_param_buf(struct sst_hsw *hsw); +int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf); +int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf); +int sst_hsw_launch_param_buf(struct sst_hsw *hsw); + +int sst_hsw_module_load(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, char *name); +int sst_hsw_module_enable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id); +int sst_hsw_module_disable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id); +int sst_hsw_module_set_param(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, u32 parameter_id, + u32 param_size, char *param); + +/* runtime module management */ +struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, + int mod_id, int offset); +void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime); + +/* PM */ +int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw); +int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw); +int sst_hsw_dsp_load(struct sst_hsw *hsw); +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw); + +#endif diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c new file mode 100644 index 0000000..157b3a6 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-pcm.c @@ -0,0 +1,1405 @@ +/* + * Intel SST Haswell/Broadwell PCM Support + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../haswell/sst-haswell-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-dsp.h" + +#define HSW_PCM_COUNT 6 +#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */ + +#define SST_OLD_POSITION(d, r, o) ((d) + \ + frames_to_bytes(r, o)) +#define SST_SAMPLES(r, x) (bytes_to_samples(r, \ + frames_to_bytes(r, (x)))) + +/* simple volume table */ +static const u32 volume_map[] = { + HSW_VOLUME_MAX >> 30, + HSW_VOLUME_MAX >> 29, + HSW_VOLUME_MAX >> 28, + HSW_VOLUME_MAX >> 27, + HSW_VOLUME_MAX >> 26, + HSW_VOLUME_MAX >> 25, + HSW_VOLUME_MAX >> 24, + HSW_VOLUME_MAX >> 23, + HSW_VOLUME_MAX >> 22, + HSW_VOLUME_MAX >> 21, + HSW_VOLUME_MAX >> 20, + HSW_VOLUME_MAX >> 19, + HSW_VOLUME_MAX >> 18, + HSW_VOLUME_MAX >> 17, + HSW_VOLUME_MAX >> 16, + HSW_VOLUME_MAX >> 15, + HSW_VOLUME_MAX >> 14, + HSW_VOLUME_MAX >> 13, + HSW_VOLUME_MAX >> 12, + HSW_VOLUME_MAX >> 11, + HSW_VOLUME_MAX >> 10, + HSW_VOLUME_MAX >> 9, + HSW_VOLUME_MAX >> 8, + HSW_VOLUME_MAX >> 7, + HSW_VOLUME_MAX >> 6, + HSW_VOLUME_MAX >> 5, + HSW_VOLUME_MAX >> 4, + HSW_VOLUME_MAX >> 3, + HSW_VOLUME_MAX >> 2, + HSW_VOLUME_MAX >> 1, + HSW_VOLUME_MAX >> 0, +}; + +#define HSW_PCM_PERIODS_MAX 64 +#define HSW_PCM_PERIODS_MIN 2 + +#define HSW_PCM_DAI_ID_SYSTEM 0 +#define HSW_PCM_DAI_ID_OFFLOAD0 1 +#define HSW_PCM_DAI_ID_OFFLOAD1 2 +#define HSW_PCM_DAI_ID_LOOPBACK 3 + + +static const struct snd_pcm_hardware hsw_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_DRAIN_TRIGGER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, + .periods_min = HSW_PCM_PERIODS_MIN, + .periods_max = HSW_PCM_PERIODS_MAX, + .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, +}; + +struct hsw_pcm_module_map { + int dai_id; + int stream; + enum sst_hsw_module_id mod_id; +}; + +/* private data for each PCM DSP stream */ +struct hsw_pcm_data { + int dai_id; + struct sst_hsw_stream *stream; + struct sst_module_runtime *runtime; + struct sst_module_runtime_context context; + struct snd_pcm *hsw_pcm; + u32 volume[2]; + struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; + unsigned int wpos; + struct mutex mutex; + bool allocated; + int persistent_offset; +}; + +enum hsw_pm_state { + HSW_PM_STATE_D0 = 0, + HSW_PM_STATE_RTD3 = 1, + HSW_PM_STATE_D3 = 2, +}; + +/* private data for the driver */ +struct hsw_priv_data { + /* runtime DSP */ + struct sst_hsw *hsw; + struct device *dev; + enum hsw_pm_state pm_state; + struct snd_soc_card *soc_card; + struct sst_module_runtime *runtime_waves; /* sound effect module */ + + /* page tables */ + struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; + + /* DAI data */ + struct hsw_pcm_data pcm[HSW_PCM_COUNT][2]; +}; + + +/* static mappings between PCMs and modules - may be dynamic in future */ +static struct hsw_pcm_module_map mod_map[] = { + {HSW_PCM_DAI_ID_SYSTEM, 0, SST_HSW_MODULE_PCM_SYSTEM}, + {HSW_PCM_DAI_ID_OFFLOAD0, 0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_OFFLOAD1, 0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_LOOPBACK, 1, SST_HSW_MODULE_PCM_REFERENCE}, + {HSW_PCM_DAI_ID_SYSTEM, 1, SST_HSW_MODULE_PCM_CAPTURE}, +}; + +static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data); + +static inline u32 hsw_mixer_to_ipc(unsigned int value) +{ + if (value >= ARRAY_SIZE(volume_map)) + return volume_map[0]; + else + return volume_map[value]; +} + +static inline unsigned int hsw_ipc_to_mixer(u32 value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(volume_map); i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + int dai, stream; + + dai = mod_map[mc->reg].dai_id; + stream = mod_map[mc->reg].stream; + pcm_data = &pdata->pcm[dai][stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + if (!pcm_data->stream) { + pcm_data->volume[0] = + hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + pcm_data->volume[1] = + hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; + } + + if (ucontrol->value.integer.value[0] == + ucontrol->value.integer.value[1]) { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + /* apply volume value to all channels */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume); + } else { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); + } + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + int dai, stream; + + dai = mod_map[mc->reg].dai_id; + stream = mod_map[mc->reg].stream; + pcm_data = &pdata->pcm[dai][stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + if (!pcm_data->stream) { + ucontrol->value.integer.value[0] = + hsw_ipc_to_mixer(pcm_data->volume[0]); + ucontrol->value.integer.value[1] = + hsw_ipc_to_mixer(pcm_data->volume[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; + } + + sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume); + ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); + sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); + ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + + return 0; +} + +static int hsw_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + + pm_runtime_get_sync(pdata->dev); + + if (ucontrol->value.integer.value[0] == + ucontrol->value.integer.value[1]) { + + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume); + + } else { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_mixer_set_volume(hsw, 0, 0, volume); + + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + sst_hsw_mixer_set_volume(hsw, 0, 1, volume); + } + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + return 0; +} + +static int hsw_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + unsigned int volume = 0; + + pm_runtime_get_sync(pdata->dev); + sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); + ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); + + sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); + ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + return 0; +} + +static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + + ucontrol->value.integer.value[0] = + (sst_hsw_is_module_active(hsw, id) || + sst_hsw_is_module_enabled_rtd3(hsw, id)); + return 0; +} + +static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + int ret = 0; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + bool switch_on = (bool)ucontrol->value.integer.value[0]; + + /* if module is in RAM on the DSP, apply user settings to module through + * ipc. If module is not in RAM on the DSP, store user setting for + * track */ + if (sst_hsw_is_module_loaded(hsw, id)) { + if (switch_on == sst_hsw_is_module_active(hsw, id)) + return 0; + + if (switch_on) + ret = sst_hsw_module_enable(hsw, id, 0); + else + ret = sst_hsw_module_disable(hsw, id, 0); + } else { + if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id)) + return 0; + + if (switch_on) + sst_hsw_set_module_enabled_rtd3(hsw, id); + else + sst_hsw_set_module_disabled_rtd3(hsw, id); + } + + return ret; +} + +static int hsw_waves_param_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + + /* return a matching line from param buffer */ + return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data); +} + +static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + int ret; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + int param_id = ucontrol->value.bytes.data[0]; + int param_size = WAVES_PARAM_COUNT; + + /* clear param buffer and reset buffer index */ + if (param_id == 0xFF) { + sst_hsw_reset_param_buf(hsw); + return 0; + } + + /* store params into buffer */ + ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data); + if (ret < 0) + return ret; + + if (sst_hsw_is_module_active(hsw, id)) + ret = sst_hsw_module_set_param(hsw, id, 0, param_id, + param_size, ucontrol->value.bytes.data); + return ret; +} + +/* TLV used by both global and stream volumes */ +static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); + +/* System Pin has no volume control */ +static const struct snd_kcontrol_new hsw_volume_controls[] = { + /* Global DSP volume */ + SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_volume_get, hsw_volume_put, hsw_vol_tlv), + /* Offload 0 volume */ + SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* Offload 1 volume */ + SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* Mic Capture volume */ + SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* enable/disable module waves */ + SOC_SINGLE_BOOL_EXT("Waves Switch", 0, + hsw_waves_switch_get, hsw_waves_switch_put), + /* set parameters to module waves */ + SND_SOC_BYTES_EXT("Waves Set Param", WAVES_PARAM_COUNT, + hsw_waves_param_get, hsw_waves_param_put), +}; + +/* Create DMA buffer page table for DSP */ +static int create_adsp_page_table(struct snd_pcm_substream *substream, + struct hsw_priv_data *pdata, struct snd_soc_pcm_runtime *rtd, + unsigned char *dma_area, size_t size, int pcm) +{ + struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); + int i, pages, stream = substream->stream; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(rtd->dev, "generating page table for %p size 0x%zu pages %d\n", + dma_area, size, pages); + + for (i = 0; i < pages; i++) { + u32 idx = (((i << 2) + i)) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u32 *pg_table; + + dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u32 *)(pdata->dmab[pcm][stream].area + idx); + + if (i & 1) + *pg_table |= (pfn << 4); + else + *pg_table |= pfn; + } + + return 0; +} + +/* this may get called several times by oss emulation */ +static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + struct sst_module *module_data; + struct sst_dsp *dsp; + struct snd_dma_buffer *dmab; + enum sst_hsw_stream_type stream_type; + enum sst_hsw_stream_path_id path_id; + u32 rate, bits, map, pages, module_id; + u8 channels; + int ret, dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + /* check if we are being called a subsequent time */ + if (pcm_data->allocated) { + ret = sst_hsw_stream_reset(hsw, pcm_data->stream); + if (ret < 0) + dev_dbg(rtd->dev, "error: reset stream failed %d\n", + ret); + + ret = sst_hsw_stream_free(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: free stream failed %d\n", + ret); + return ret; + } + pcm_data->allocated = false; + + pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, + hsw_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "error: failed to create stream\n"); + return -EINVAL; + } + } + + /* stream direction */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path_id = SST_HSW_STREAM_PATH_SSP0_OUT; + else + path_id = SST_HSW_STREAM_PATH_SSP0_IN; + + /* DSP stream type depends on DAI ID */ + switch (rtd->cpu_dai->id) { + case 0: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_type = SST_HSW_STREAM_TYPE_SYSTEM; + module_id = SST_HSW_MODULE_PCM_SYSTEM; + } + else { + stream_type = SST_HSW_STREAM_TYPE_CAPTURE; + module_id = SST_HSW_MODULE_PCM_CAPTURE; + } + break; + case 1: + case 2: + stream_type = SST_HSW_STREAM_TYPE_RENDER; + module_id = SST_HSW_MODULE_PCM; + break; + case 3: + /* path ID needs to be OUT for loopback */ + stream_type = SST_HSW_STREAM_TYPE_LOOPBACK; + path_id = SST_HSW_STREAM_PATH_SSP0_OUT; + module_id = SST_HSW_MODULE_PCM_REFERENCE; + break; + default: + dev_err(rtd->dev, "error: invalid DAI ID %d\n", + rtd->cpu_dai->id); + return -EINVAL; + }; + + ret = sst_hsw_stream_format(hsw, pcm_data->stream, + path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set format %d\n", ret); + return ret; + } + + rate = params_rate(params); + ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set rate %d\n", rate); + return ret; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bits = SST_HSW_DEPTH_16BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); + break; + case SNDRV_PCM_FORMAT_S24_LE: + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24); + break; + case SNDRV_PCM_FORMAT_S8: + bits = SST_HSW_DEPTH_8BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8); + break; + case SNDRV_PCM_FORMAT_S32_LE: + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); + break; + default: + dev_err(rtd->dev, "error: invalid format %d\n", + params_format(params)); + return -EINVAL; + } + + ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set bits %d\n", bits); + return ret; + } + + channels = params_channels(params); + map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); + sst_hsw_stream_set_map_config(hsw, pcm_data->stream, + map, SST_HSW_CHANNEL_CONFIG_STEREO); + + ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set channels %d\n", + channels); + return ret; + } + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n", + params_buffer_bytes(params), ret); + return ret; + } + + dmab = snd_pcm_get_dma_buf(substream); + + ret = create_adsp_page_table(substream, pdata, rtd, runtime->dma_area, + runtime->dma_bytes, rtd->cpu_dai->id); + if (ret < 0) + return ret; + + sst_hsw_stream_set_style(hsw, pcm_data->stream, + SST_HSW_INTERLEAVING_PER_CHANNEL); + + if (runtime->dma_bytes % PAGE_SIZE) + pages = (runtime->dma_bytes / PAGE_SIZE) + 1; + else + pages = runtime->dma_bytes / PAGE_SIZE; + + ret = sst_hsw_stream_buffer(hsw, pcm_data->stream, + pdata->dmab[rtd->cpu_dai->id][substream->stream].addr, + pages, runtime->dma_bytes, 0, + snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret); + return ret; + } + + dsp = sst_hsw_get_dsp(hsw); + + module_data = sst_module_get_from_id(dsp, module_id); + if (module_data == NULL) { + dev_err(rtd->dev, "error: failed to get module config\n"); + return -EINVAL; + } + + sst_hsw_stream_set_module_info(hsw, pcm_data->stream, + pcm_data->runtime); + + ret = sst_hsw_stream_commit(hsw, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); + return ret; + } + + if (!pcm_data->allocated) { + /* Set previous saved volume */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 0, pcm_data->volume[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 1, pcm_data->volume[1]); + pcm_data->allocated = true; + } + + ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); + if (ret < 0) + dev_err(rtd->dev, "error: failed to pause %d\n", ret); + + return 0; +} + +static int hsw_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw_stream *sst_stream; + struct sst_hsw *hsw = pdata->hsw; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t pos; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + sst_stream = pcm_data->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sst_hsw_stream_set_silence_start(hsw, sst_stream, false); + sst_hsw_stream_resume(hsw, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sst_hsw_stream_set_silence_start(hsw, sst_stream, false); + sst_hsw_stream_pause(hsw, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_DRAIN: + pos = runtime->control->appl_ptr % runtime->buffer_size; + sst_hsw_stream_set_old_position(hsw, pcm_data->stream, pos); + sst_hsw_stream_set_silence_start(hsw, sst_stream, true); + break; + default: + break; + } + + return 0; +} + +static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) +{ + struct hsw_pcm_data *pcm_data = data; + struct snd_pcm_substream *substream = pcm_data->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_hsw *hsw = pdata->hsw; + u32 pos; + snd_pcm_uframes_t position = bytes_to_frames(runtime, + sst_hsw_get_dsp_position(hsw, pcm_data->stream)); + unsigned char *dma_area = runtime->dma_area; + snd_pcm_uframes_t dma_frames = + bytes_to_frames(runtime, runtime->dma_bytes); + snd_pcm_uframes_t old_position; + ssize_t samples; + + pos = frames_to_bytes(runtime, + (runtime->control->appl_ptr % runtime->buffer_size)); + + dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); + + /* SST fw don't know where to stop dma + * So, SST driver need to clean the data which has been consumed + */ + if (dma_area == NULL || dma_frames <= 0 + || (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !sst_hsw_stream_get_silence_start(hsw, stream)) { + snd_pcm_period_elapsed(substream); + return pos; + } + + old_position = sst_hsw_stream_get_old_position(hsw, stream); + if (position > old_position) { + if (position < dma_frames) { + samples = SST_SAMPLES(runtime, position - old_position); + snd_pcm_format_set_silence(runtime->format, + SST_OLD_POSITION(dma_area, + runtime, old_position), + samples); + } else + dev_err(rtd->dev, "PCM: position is wrong\n"); + } else { + if (old_position < dma_frames) { + samples = SST_SAMPLES(runtime, + dma_frames - old_position); + snd_pcm_format_set_silence(runtime->format, + SST_OLD_POSITION(dma_area, + runtime, old_position), + samples); + } else + dev_err(rtd->dev, "PCM: dma_bytes is wrong\n"); + if (position < dma_frames) { + samples = SST_SAMPLES(runtime, position); + snd_pcm_format_set_silence(runtime->format, + dma_area, samples); + } else + dev_err(rtd->dev, "PCM: position is wrong\n"); + } + sst_hsw_stream_set_old_position(hsw, stream, position); + + /* let alsa know we have play a period */ + snd_pcm_period_elapsed(substream); + return pos; +} + +static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + snd_pcm_uframes_t offset; + uint64_t ppos; + u32 position; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + position = sst_hsw_get_dsp_position(hsw, pcm_data->stream); + + offset = bytes_to_frames(runtime, position); + ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); + + dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", + position, ppos); + return offset; +} + +static int hsw_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + snd_soc_pcm_set_drvdata(rtd, pcm_data); + pcm_data->substream = substream; + + snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); + + pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, + hsw_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "error: failed to create stream\n"); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return -EINVAL; + } + + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int hsw_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + int ret, dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + mutex_lock(&pcm_data->mutex); + ret = sst_hsw_stream_reset(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret); + goto out; + } + + ret = sst_hsw_stream_free(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: free stream failed %d\n", ret); + goto out; + } + pcm_data->allocated = 0; + pcm_data->stream = NULL; + +out: + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return ret; +} + +static struct snd_pcm_ops hsw_pcm_ops = { + .open = hsw_pcm_open, + .close = hsw_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hsw_pcm_hw_params, + .hw_free = hsw_pcm_hw_free, + .trigger = hsw_pcm_trigger, + .pointer = hsw_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + /* create new runtime module, use same offset if recreated */ + pcm_data->runtime = sst_hsw_runtime_module_create(hsw, + mod_map[i].mod_id, pcm_data->persistent_offset); + if (pcm_data->runtime == NULL) + goto err; + pcm_data->persistent_offset = + pcm_data->runtime->persistent_offset; + } + + /* create runtime blocks for module waves */ + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { + pdata->runtime_waves = sst_hsw_runtime_module_create(hsw, + SST_HSW_MODULE_WAVES, 0); + if (pdata->runtime_waves == NULL) + goto err; + } + + return 0; + +err: + for (--i; i >= 0; i--) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + sst_hsw_runtime_module_free(pcm_data->runtime); + } + + return -ENODEV; +} + +static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + sst_hsw_runtime_module_free(pcm_data->runtime); + } + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { + sst_hsw_runtime_module_free(pdata->runtime_waves); + } +} + +static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_platform *platform = rtd->platform; + struct sst_pdata *pdata = dev_get_platdata(platform->dev); + struct hsw_priv_data *priv_data = dev_get_drvdata(platform->dev); + struct device *dev = pdata->dma_dev; + int ret = 0; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + dev, + hsw_pcm_hardware.buffer_bytes_max, + hsw_pcm_hardware.buffer_bytes_max); + if (ret) { + dev_err(rtd->dev, "dma buffer allocation failed %d\n", + ret); + return ret; + } + } + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_PLAYBACK].hsw_pcm = pcm; + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_CAPTURE].hsw_pcm = pcm; + + return ret; +} + +#define HSW_FORMATS \ + (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_driver hsw_dais[] = { + { + .name = "System Pin", + .id = HSW_PCM_DAI_ID_SYSTEM, + .playback = { + .stream_name = "System Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + /* PCM */ + .name = "Offload0 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD0, + .playback = { + .stream_name = "Offload0 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = HSW_FORMATS, + }, + }, + { + /* PCM */ + .name = "Offload1 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD1, + .playback = { + .stream_name = "Offload1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = HSW_FORMATS, + }, + }, + { + .name = "Loopback Pin", + .id = HSW_PCM_DAI_ID_LOOPBACK, + .capture = { + .stream_name = "Loopback Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static const struct snd_soc_dapm_widget widgets[] = { + + /* Backend DAIs */ + SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Global Playback Mixer */ + SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route graph[] = { + + /* Playback Mixer */ + {"Playback VMixer", NULL, "System Playback"}, + {"Playback VMixer", NULL, "Offload0 Playback"}, + {"Playback VMixer", NULL, "Offload1 Playback"}, + + {"SSP0 CODEC OUT", NULL, "Playback VMixer"}, + + {"Analog Capture", NULL, "SSP0 CODEC IN"}, +}; + +static int hsw_pcm_probe(struct snd_soc_platform *platform) +{ + struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform); + struct sst_pdata *pdata = dev_get_platdata(platform->dev); + struct device *dma_dev, *dev; + int i, ret = 0; + + if (!pdata) + return -ENODEV; + + dev = platform->dev; + dma_dev = pdata->dma_dev; + + priv_data->hsw = pdata->dsp; + priv_data->dev = platform->dev; + priv_data->pm_state = HSW_PM_STATE_D0; + priv_data->soc_card = platform->component.card; + + /* allocate DSP buffer page tables */ + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + + /* playback */ + if (hsw_dais[i].playback.channels_min) { + mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_PLAYBACK].mutex); + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, + PAGE_SIZE, &priv_data->dmab[i][0]); + if (ret < 0) + goto err; + } + + /* capture */ + if (hsw_dais[i].capture.channels_min) { + mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_CAPTURE].mutex); + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, + PAGE_SIZE, &priv_data->dmab[i][1]); + if (ret < 0) + goto err; + } + } + + /* allocate runtime modules */ + ret = hsw_pcm_create_modules(priv_data); + if (ret < 0) + goto err; + + /* enable runtime PM with auto suspend */ + pm_runtime_set_autosuspend_delay(platform->dev, + SST_RUNTIME_SUSPEND_DELAY); + pm_runtime_use_autosuspend(platform->dev); + pm_runtime_enable(platform->dev); + pm_runtime_idle(platform->dev); + + return 0; + +err: + for (;i >= 0; i--) { + if (hsw_dais[i].playback.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][0]); + if (hsw_dais[i].capture.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][1]); + } + return ret; +} + +static int hsw_pcm_remove(struct snd_soc_platform *platform) +{ + struct hsw_priv_data *priv_data = + snd_soc_platform_get_drvdata(platform); + int i; + + pm_runtime_disable(platform->dev); + hsw_pcm_free_modules(priv_data); + + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + if (hsw_dais[i].playback.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][0]); + if (hsw_dais[i].capture.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][1]); + } + + return 0; +} + +static struct snd_soc_platform_driver hsw_soc_platform = { + .probe = hsw_pcm_probe, + .remove = hsw_pcm_remove, + .ops = &hsw_pcm_ops, + .pcm_new = hsw_pcm_new, +}; + +static const struct snd_soc_component_driver hsw_dai_component = { + .name = "haswell-dai", + .controls = hsw_volume_controls, + .num_controls = ARRAY_SIZE(hsw_volume_controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = graph, + .num_dapm_routes = ARRAY_SIZE(graph), +}; + +static int hsw_pcm_dev_probe(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + struct hsw_priv_data *priv_data; + int ret; + + if (!sst_pdata) + return -EINVAL; + + priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); + if (ret < 0) + return -ENODEV; + + priv_data->hsw = sst_pdata->dsp; + platform_set_drvdata(pdev, priv_data); + + ret = snd_soc_register_platform(&pdev->dev, &hsw_soc_platform); + if (ret < 0) + goto err_plat; + + ret = snd_soc_register_component(&pdev->dev, &hsw_dai_component, + hsw_dais, ARRAY_SIZE(hsw_dais)); + if (ret < 0) + goto err_comp; + + return 0; + +err_comp: + snd_soc_unregister_platform(&pdev->dev); +err_plat: + sst_hsw_dsp_free(&pdev->dev, sst_pdata); + return 0; +} + +static int hsw_pcm_dev_remove(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + + snd_soc_unregister_platform(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + sst_hsw_dsp_free(&pdev->dev, sst_pdata); + + return 0; +} + +#ifdef CONFIG_PM + +static int hsw_pcm_runtime_idle(struct device *dev) +{ + return 0; +} + +static int hsw_pcm_runtime_suspend(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state >= HSW_PM_STATE_RTD3) + return 0; + + /* fw modules will be unloaded on RTD3, set flag to track */ + if (sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_disable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + sst_hsw_set_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } + sst_hsw_dsp_runtime_suspend(hsw); + sst_hsw_dsp_runtime_sleep(hsw); + pdata->pm_state = HSW_PM_STATE_RTD3; + + return 0; +} + +static int hsw_pcm_runtime_resume(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state != HSW_PM_STATE_RTD3) + return 0; + + ret = sst_hsw_dsp_load(hsw); + if (ret < 0) { + dev_err(dev, "failed to reload %d\n", ret); + return ret; + } + + ret = hsw_pcm_create_modules(pdata); + if (ret < 0) { + dev_err(dev, "failed to create modules %d\n", ret); + return ret; + } + + ret = sst_hsw_dsp_runtime_resume(hsw); + if (ret < 0) + return ret; + else if (ret == 1) /* no action required */ + return 0; + + /* check flag when resume */ + if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + /* put parameters from buffer to dsp */ + ret = sst_hsw_launch_param_buf(hsw); + if (ret < 0) + return ret; + /* unset flag */ + sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } + + pdata->pm_state = HSW_PM_STATE_D0; + return ret; +} + +#else +#define hsw_pcm_runtime_idle NULL +#define hsw_pcm_runtime_suspend NULL +#define hsw_pcm_runtime_resume NULL +#endif + +#ifdef CONFIG_PM + +static void hsw_pcm_complete(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state != HSW_PM_STATE_D3) + return; + + err = sst_hsw_dsp_load(hsw); + if (err < 0) { + dev_err(dev, "failed to reload %d\n", err); + return; + } + + err = hsw_pcm_create_modules(pdata); + if (err < 0) { + dev_err(dev, "failed to create modules %d\n", err); + return; + } + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + + err = sst_module_runtime_restore(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to restore context for PCM %d\n", i); + } + + snd_soc_resume(pdata->soc_card->dev); + + err = sst_hsw_dsp_runtime_resume(hsw); + if (err < 0) + return; + else if (err == 1) /* no action required */ + return; + + pdata->pm_state = HSW_PM_STATE_D0; + return; +} + +static int hsw_pcm_prepare(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state == HSW_PM_STATE_D3) + return 0; + else if (pdata->pm_state == HSW_PM_STATE_D0) { + /* suspend all active streams */ + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + dev_dbg(dev, "suspending pcm %d\n", i); + snd_pcm_suspend_all(pcm_data->hsw_pcm); + + /* We need to wait until the DSP FW stops the streams */ + msleep(2); + } + + /* preserve persistent memory */ + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + + dev_dbg(dev, "saving context pcm %d\n", i); + err = sst_module_runtime_save(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to save context for PCM %d\n", i); + } + /* enter D3 state and stall */ + sst_hsw_dsp_runtime_suspend(hsw); + /* put the DSP to sleep */ + sst_hsw_dsp_runtime_sleep(hsw); + } + + snd_soc_suspend(pdata->soc_card->dev); + snd_soc_poweroff(pdata->soc_card->dev); + + pdata->pm_state = HSW_PM_STATE_D3; + + return 0; +} + +#else +#define hsw_pcm_prepare NULL +#define hsw_pcm_complete NULL +#endif + +static const struct dev_pm_ops hsw_pcm_pm = { + .runtime_idle = hsw_pcm_runtime_idle, + .runtime_suspend = hsw_pcm_runtime_suspend, + .runtime_resume = hsw_pcm_runtime_resume, + .prepare = hsw_pcm_prepare, + .complete = hsw_pcm_complete, +}; + +static struct platform_driver hsw_pcm_driver = { + .driver = { + .name = "haswell-pcm-audio", + .pm = &hsw_pcm_pm, + }, + + .probe = hsw_pcm_dev_probe, + .remove = hsw_pcm_dev_remove, +}; +module_platform_driver(hsw_pcm_driver); + +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:haswell-pcm-audio"); diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c deleted file mode 100644 index b3e957d..0000000 --- a/sound/soc/intel/sst-haswell-dsp.c +++ /dev/null @@ -1,709 +0,0 @@ -/* - * Intel Haswell SST DSP driver - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sst-dsp.h" -#include "sst-dsp-priv.h" -#include "sst-haswell-ipc.h" - -#include - -#define SST_HSW_FW_SIGNATURE_SIZE 4 -#define SST_HSW_FW_SIGN "$SST" -#define SST_HSW_FW_LIB_SIGN "$LIB" - -#define SST_WPT_SHIM_OFFSET 0xFB000 -#define SST_LP_SHIM_OFFSET 0xE7000 -#define SST_WPT_IRAM_OFFSET 0xA0000 -#define SST_LP_IRAM_OFFSET 0x80000 -#define SST_WPT_DSP_DRAM_OFFSET 0x400000 -#define SST_WPT_DSP_IRAM_OFFSET 0x00000 -#define SST_LPT_DSP_DRAM_OFFSET 0x400000 -#define SST_LPT_DSP_IRAM_OFFSET 0x00000 - -#define SST_SHIM_PM_REG 0x84 - -#define SST_HSW_IRAM 1 -#define SST_HSW_DRAM 2 -#define SST_HSW_REGS 3 - -struct dma_block_info { - __le32 type; /* IRAM/DRAM */ - __le32 size; /* Bytes */ - __le32 ram_offset; /* Offset in I/DRAM */ - __le32 rsvd; /* Reserved field */ -} __attribute__((packed)); - -struct fw_module_info { - __le32 persistent_size; - __le32 scratch_size; -} __attribute__((packed)); - -struct fw_header { - unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */ - __le32 file_size; /* size of fw minus this header */ - __le32 modules; /* # of modules */ - __le32 file_format; /* version of header format */ - __le32 reserved[4]; -} __attribute__((packed)); - -struct fw_module_header { - unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */ - __le32 mod_size; /* size of module */ - __le32 blocks; /* # of blocks */ - __le16 padding; - __le16 type; /* codec type, pp lib */ - __le32 entry_point; - struct fw_module_info info; -} __attribute__((packed)); - -static void hsw_free(struct sst_dsp *sst); - -static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, - struct fw_module_header *module) -{ - struct dma_block_info *block; - struct sst_module *mod; - struct sst_module_template template; - int count, ret; - void __iomem *ram; - - /* TODO: allowed module types need to be configurable */ - if (module->type != SST_HSW_MODULE_BASE_FW - && module->type != SST_HSW_MODULE_PCM_SYSTEM - && module->type != SST_HSW_MODULE_PCM - && module->type != SST_HSW_MODULE_PCM_REFERENCE - && module->type != SST_HSW_MODULE_PCM_CAPTURE - && module->type != SST_HSW_MODULE_WAVES - && module->type != SST_HSW_MODULE_LPAL) - return 0; - - dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n", - module->signature, module->mod_size, - module->blocks, module->type); - dev_dbg(dsp->dev, " entrypoint 0x%x\n", module->entry_point); - dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n", - module->info.persistent_size, module->info.scratch_size); - - memset(&template, 0, sizeof(template)); - template.id = module->type; - template.entry = module->entry_point - 4; - template.persistent_size = module->info.persistent_size; - template.scratch_size = module->info.scratch_size; - - mod = sst_module_new(fw, &template, NULL); - if (mod == NULL) - return -ENOMEM; - - block = (void *)module + sizeof(*module); - - for (count = 0; count < module->blocks; count++) { - - if (block->size <= 0) { - dev_err(dsp->dev, - "error: block %d size invalid\n", count); - sst_module_free(mod); - return -EINVAL; - } - - switch (block->type) { - case SST_HSW_IRAM: - ram = dsp->addr.lpe; - mod->offset = - block->ram_offset + dsp->addr.iram_offset; - mod->type = SST_MEM_IRAM; - break; - case SST_HSW_DRAM: - case SST_HSW_REGS: - ram = dsp->addr.lpe; - mod->offset = block->ram_offset; - mod->type = SST_MEM_DRAM; - break; - default: - dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", - block->type, count); - sst_module_free(mod); - return -EINVAL; - } - - mod->size = block->size; - mod->data = (void *)block + sizeof(*block); - mod->data_offset = mod->data - fw->dma_buf; - - dev_dbg(dsp->dev, "module block %d type 0x%x " - "size 0x%x ==> ram %p offset 0x%x\n", - count, mod->type, block->size, ram, - block->ram_offset); - - ret = sst_module_alloc_blocks(mod); - if (ret < 0) { - dev_err(dsp->dev, "error: could not allocate blocks for module %d\n", - count); - sst_module_free(mod); - return ret; - } - - block = (void *)block + sizeof(*block) + block->size; - } - mod->state = SST_MODULE_STATE_LOADED; - - return 0; -} - -static int hsw_parse_fw_image(struct sst_fw *sst_fw) -{ - struct fw_header *header; - struct fw_module_header *module; - struct sst_dsp *dsp = sst_fw->dsp; - int ret, count; - - /* Read the header information from the data pointer */ - header = (struct fw_header *)sst_fw->dma_buf; - - /* verify FW */ - if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) || - (sst_fw->size != header->file_size + sizeof(*header))) { - dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n"); - return -EINVAL; - } - - dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n", - header->file_size, header->modules, - header->file_format, sizeof(*header)); - - /* parse each module */ - module = (void *)sst_fw->dma_buf + sizeof(*header); - for (count = 0; count < header->modules; count++) { - - /* module */ - ret = hsw_parse_module(dsp, sst_fw, module); - if (ret < 0) { - dev_err(dsp->dev, "error: invalid module %d\n", count); - return ret; - } - module = (void *)module + sizeof(*module) + module->mod_size; - } - - return 0; -} - -static irqreturn_t hsw_irq(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - u32 isr; - int ret = IRQ_NONE; - - spin_lock(&sst->spinlock); - - /* Interrupt arrived, check src */ - isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); - if (isr & SST_ISRX_DONE) { - trace_sst_irq_done(isr, - sst_dsp_shim_read_unlocked(sst, SST_IMRX)); - - /* Mask Done interrupt before return */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_DONE, SST_IMRX_DONE); - ret = IRQ_WAKE_THREAD; - } - - if (isr & SST_ISRX_BUSY) { - trace_sst_irq_busy(isr, - sst_dsp_shim_read_unlocked(sst, SST_IMRX)); - - /* Mask Busy interrupt before return */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_BUSY, SST_IMRX_BUSY); - ret = IRQ_WAKE_THREAD; - } - - spin_unlock(&sst->spinlock); - return ret; -} - -static void hsw_set_dsp_D3(struct sst_dsp *sst) -{ - u32 val; - u32 reg; - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* enable power gating and switch off DRAM & IRAM blocks */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - val |= SST_VDRTCL0_DSRAMPGE_MASK | - SST_VDRTCL0_ISRAMPGE_MASK; - val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD); - writel(val, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* switch off audio PLL */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val |= SST_VDRTCL2_APLLSE_MASK; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* disable MCLK(clkctl.smos = 0) */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, - SST_CLKCTL_MASK, 0); - - /* Set D3 state, delay 50 us */ - val = readl(sst->addr.pci_cfg + SST_PMCS); - val |= SST_PMCS_PS_MASK; - writel(val, sst->addr.pci_cfg + SST_PMCS); - udelay(50); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - -} - -static void hsw_reset(struct sst_dsp *sst) -{ - /* put DSP into reset and stall */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_RST | SST_CSR_STALL, - SST_CSR_RST | SST_CSR_STALL); - - /* keep in reset for 10ms */ - mdelay(10); - - /* take DSP out of reset and keep stalled for FW loading */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); -} - -static int hsw_set_dsp_D0(struct sst_dsp *sst) -{ - int tries = 10; - u32 reg, fw_dump_bit; - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - reg |= SST_VDRTCL0_D3PGD; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* Set D0 state */ - reg = readl(sst->addr.pci_cfg + SST_PMCS); - reg &= ~SST_PMCS_PS_MASK; - writel(reg, sst->addr.pci_cfg + SST_PMCS); - - /* check that ADSP shim is enabled */ - while (tries--) { - reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK; - if (reg == 0) - goto finish; - - msleep(1); - } - - return -ENODEV; - -finish: - /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); - - /* stall DSP core, set clk to 192/96Mhz */ - sst_dsp_shim_update_bits_unlocked(sst, - SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK, - SST_CSR_STALL | SST_CSR_DCS(4)); - - /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, - SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, - SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); - - /* Stall and reset core, set CSR */ - hsw_reset(sst); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - - /* switch on audio PLL */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg &= ~SST_VDRTCL2_APLLSE_MASK; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* set default power gating control, enable power gating control for all blocks. that is, - can't be accessed, please enable each block before accessing. */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK; - /* for D0, always enable the block(DSRAM[0]) used for FW dump */ - fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; - writel(reg & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - - /* disable DMA finish function for SSP0 & SSP1 */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, - SST_CSR2_SDFD_SSP1); - - /* set on-demond mode on engine 0,1 for all channels */ - sst_dsp_shim_update_bits(sst, SST_HMDC, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); - - /* Enable Interrupt from both sides */ - sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE), - 0x0); - sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY | - SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0); - - /* clear IPC registers */ - sst_dsp_shim_write(sst, SST_IPCX, 0x0); - sst_dsp_shim_write(sst, SST_IPCD, 0x0); - sst_dsp_shim_write(sst, 0x80, 0x6); - sst_dsp_shim_write(sst, 0xe0, 0x300a); - - return 0; -} - -static void hsw_boot(struct sst_dsp *sst) -{ - /* set oportunistic mode on engine 0,1 for all channels */ - sst_dsp_shim_update_bits(sst, SST_HMDC, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0); - - /* set DSP to RUN */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); -} - -static void hsw_stall(struct sst_dsp *sst) -{ - /* stall DSP */ - sst_dsp_shim_update_bits(sst, SST_CSR, - SST_CSR_24MHZ_LPCS | SST_CSR_STALL, - SST_CSR_STALL | SST_CSR_24MHZ_LPCS); -} - -static void hsw_sleep(struct sst_dsp *sst) -{ - dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n"); - - /* put DSP into reset and stall */ - sst_dsp_shim_update_bits(sst, SST_CSR, - SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL, - SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS); - - hsw_set_dsp_D3(sst); - dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n"); -} - -static int hsw_wake(struct sst_dsp *sst) -{ - int ret; - - dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n"); - - ret = hsw_set_dsp_D0(sst); - if (ret < 0) - return ret; - - dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n"); - - return 0; -} - -struct sst_adsp_memregion { - u32 start; - u32 end; - int blocks; - enum sst_mem_type type; -}; - -/* lynx point ADSP mem regions */ -static const struct sst_adsp_memregion lp_region[] = { - {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ - {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ - {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */ -}; - -/* wild cat point ADSP mem regions */ -static const struct sst_adsp_memregion wpt_region[] = { - {0x00000, 0xA0000, 20, SST_MEM_DRAM}, /* D-SRAM0,D-SRAM1,D-SRAM2 - 20 * 32kB */ - {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */ -}; - -static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - /* ADSP DRAM & IRAM */ - sst->addr.lpe_base = pdata->lpe_base; - sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); - if (!sst->addr.lpe) - return -ENODEV; - - /* ADSP PCI MMIO config space */ - sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); - if (!sst->addr.pci_cfg) { - iounmap(sst->addr.lpe); - return -ENODEV; - } - - /* SST Shim */ - sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; - return 0; -} - -struct sst_sram_shift { - u32 dev_id; /* SST Device IDs */ - u32 iram_shift; - u32 dram_shift; -}; - -static const struct sst_sram_shift sram_shift[] = { - {SST_DEV_ID_LYNX_POINT, 6, 16}, /* lp */ - {SST_DEV_ID_WILDCAT_POINT, 2, 12}, /* wpt */ -}; - -static u32 hsw_block_get_bit(struct sst_mem_block *block) -{ - u32 bit = 0, shift = 0, index; - struct sst_dsp *sst = block->dsp; - - for (index = 0; index < ARRAY_SIZE(sram_shift); index++) { - if (sram_shift[index].dev_id == sst->id) - break; - } - - if (index < ARRAY_SIZE(sram_shift)) { - switch (block->type) { - case SST_MEM_DRAM: - shift = sram_shift[index].dram_shift; - break; - case SST_MEM_IRAM: - shift = sram_shift[index].iram_shift; - break; - default: - shift = 0; - } - } else - shift = 0; - - bit = 1 << (block->index + shift); - - return bit; -} - -/*dummy read a SRAM block.*/ -static void sst_mem_block_dummy_read(struct sst_mem_block *block) -{ - u32 size; - u8 tmp_buf[4]; - struct sst_dsp *sst = block->dsp; - - size = block->size > 4 ? 4 : block->size; - memcpy_fromio(tmp_buf, sst->addr.lpe + block->offset, size); -} - -/* enable 32kB memory block - locks held by caller */ -static int hsw_block_enable(struct sst_mem_block *block) -{ - struct sst_dsp *sst = block->dsp; - u32 bit, val; - - if (block->users++ > 0) - return 0; - - dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val &= ~SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - bit = hsw_block_get_bit(block); - writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* wait 18 DSP clock ticks */ - udelay(10); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val |= SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - - /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ - sst_mem_block_dummy_read(block); - return 0; -} - -/* disable 32kB memory block - locks held by caller */ -static int hsw_block_disable(struct sst_mem_block *block) -{ - struct sst_dsp *sst = block->dsp; - u32 bit, val; - - if (--block->users > 0) - return 0; - - dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val &= ~SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - - val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - bit = hsw_block_get_bit(block); - /* don't disable DSRAM[0], keep it always enable for FW dump*/ - if (bit != (1 << SST_VDRTCL0_DSRAMPGE_SHIFT)) - writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* wait 18 DSP clock ticks */ - udelay(10); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val |= SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - - return 0; -} - -static struct sst_block_ops sst_hsw_ops = { - .enable = hsw_block_enable, - .disable = hsw_block_disable, -}; - -static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - const struct sst_adsp_memregion *region; - struct device *dev; - int ret = -ENODEV, i, j, region_count; - u32 offset, size, fw_dump_bit; - - dev = sst->dma_dev; - - switch (sst->id) { - case SST_DEV_ID_LYNX_POINT: - region = lp_region; - region_count = ARRAY_SIZE(lp_region); - sst->addr.iram_offset = SST_LP_IRAM_OFFSET; - sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET; - sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET; - sst->addr.shim_offset = SST_LP_SHIM_OFFSET; - break; - case SST_DEV_ID_WILDCAT_POINT: - region = wpt_region; - region_count = ARRAY_SIZE(wpt_region); - sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; - sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET; - sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET; - sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; - break; - default: - dev_err(dev, "error: failed to get mem resources\n"); - return ret; - } - - ret = hsw_acpi_resource_map(sst, pdata); - if (ret < 0) { - dev_err(dev, "error: failed to map resources\n"); - return ret; - } - - /* enable the DSP SHIM */ - ret = hsw_set_dsp_D0(sst); - if (ret < 0) { - dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); - return ret; - } - - ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); - if (ret) - return ret; - - - /* register DSP memory blocks - ideally we should get this from ACPI */ - for (i = 0; i < region_count; i++) { - offset = region[i].start; - size = (region[i].end - region[i].start) / region[i].blocks; - - /* register individual memory blocks */ - for (j = 0; j < region[i].blocks; j++) { - sst_mem_block_register(sst, offset, size, - region[i].type, &sst_hsw_ops, j, sst); - offset += size; - } - } - - /* always enable the block(DSRAM[0]) used for FW dump */ - fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; - /* set default power gating control, enable power gating control for all blocks. that is, - can't be accessed, please enable each block before accessing. */ - writel(0xffffffff & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - return 0; -} - -static void hsw_free(struct sst_dsp *sst) -{ - sst_mem_block_unregister_all(sst); - iounmap(sst->addr.lpe); - iounmap(sst->addr.pci_cfg); -} - -struct sst_ops haswell_ops = { - .reset = hsw_reset, - .boot = hsw_boot, - .stall = hsw_stall, - .wake = hsw_wake, - .sleep = hsw_sleep, - .write = sst_shim32_write, - .read = sst_shim32_read, - .write64 = sst_shim32_write64, - .read64 = sst_shim32_read64, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, - .irq_handler = hsw_irq, - .init = hsw_init, - .free = hsw_free, - .parse_fw = hsw_parse_fw_image, -}; diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c deleted file mode 100644 index 20b629a..0000000 --- a/sound/soc/intel/sst-haswell-ipc.c +++ /dev/null @@ -1,2428 +0,0 @@ -/* - * Intel SST Haswell/Broadwell IPC Support - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sst-haswell-ipc.h" -#include "sst-dsp.h" -#include "sst-dsp-priv.h" - -/* Global Message - Generic */ -#define IPC_GLB_TYPE_SHIFT 24 -#define IPC_GLB_TYPE_MASK (0x1f << IPC_GLB_TYPE_SHIFT) -#define IPC_GLB_TYPE(x) (x << IPC_GLB_TYPE_SHIFT) - -/* Global Message - Reply */ -#define IPC_GLB_REPLY_SHIFT 0 -#define IPC_GLB_REPLY_MASK (0x1f << IPC_GLB_REPLY_SHIFT) -#define IPC_GLB_REPLY_TYPE(x) (x << IPC_GLB_REPLY_TYPE_SHIFT) - -/* Stream Message - Generic */ -#define IPC_STR_TYPE_SHIFT 20 -#define IPC_STR_TYPE_MASK (0xf << IPC_STR_TYPE_SHIFT) -#define IPC_STR_TYPE(x) (x << IPC_STR_TYPE_SHIFT) -#define IPC_STR_ID_SHIFT 16 -#define IPC_STR_ID_MASK (0xf << IPC_STR_ID_SHIFT) -#define IPC_STR_ID(x) (x << IPC_STR_ID_SHIFT) - -/* Stream Message - Reply */ -#define IPC_STR_REPLY_SHIFT 0 -#define IPC_STR_REPLY_MASK (0x1f << IPC_STR_REPLY_SHIFT) - -/* Stream Stage Message - Generic */ -#define IPC_STG_TYPE_SHIFT 12 -#define IPC_STG_TYPE_MASK (0xf << IPC_STG_TYPE_SHIFT) -#define IPC_STG_TYPE(x) (x << IPC_STG_TYPE_SHIFT) -#define IPC_STG_ID_SHIFT 10 -#define IPC_STG_ID_MASK (0x3 << IPC_STG_ID_SHIFT) -#define IPC_STG_ID(x) (x << IPC_STG_ID_SHIFT) - -/* Stream Stage Message - Reply */ -#define IPC_STG_REPLY_SHIFT 0 -#define IPC_STG_REPLY_MASK (0x1f << IPC_STG_REPLY_SHIFT) - -/* Debug Log Message - Generic */ -#define IPC_LOG_OP_SHIFT 20 -#define IPC_LOG_OP_MASK (0xf << IPC_LOG_OP_SHIFT) -#define IPC_LOG_OP_TYPE(x) (x << IPC_LOG_OP_SHIFT) -#define IPC_LOG_ID_SHIFT 16 -#define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT) -#define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT) - -/* Module Message */ -#define IPC_MODULE_OPERATION_SHIFT 20 -#define IPC_MODULE_OPERATION_MASK (0xf << IPC_MODULE_OPERATION_SHIFT) -#define IPC_MODULE_OPERATION(x) (x << IPC_MODULE_OPERATION_SHIFT) - -#define IPC_MODULE_ID_SHIFT 16 -#define IPC_MODULE_ID_MASK (0xf << IPC_MODULE_ID_SHIFT) -#define IPC_MODULE_ID(x) (x << IPC_MODULE_ID_SHIFT) - -/* IPC message timeout (msecs) */ -#define IPC_TIMEOUT_MSECS 300 -#define IPC_BOOT_MSECS 200 -#define IPC_MSG_WAIT 0 -#define IPC_MSG_NOWAIT 1 - -/* Firmware Ready Message */ -#define IPC_FW_READY (0x1 << 29) -#define IPC_STATUS_MASK (0x3 << 30) - -#define IPC_EMPTY_LIST_SIZE 8 -#define IPC_MAX_STREAMS 4 - -/* Mailbox */ -#define IPC_MAX_MAILBOX_BYTES 256 - -#define INVALID_STREAM_HW_ID 0xffffffff - -/* Global Message - Types and Replies */ -enum ipc_glb_type { - IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */ - IPC_GLB_PERFORMANCE_MONITOR = 1, /* Performance monitoring actions */ - IPC_GLB_ALLOCATE_STREAM = 3, /* Request to allocate new stream */ - IPC_GLB_FREE_STREAM = 4, /* Request to free stream */ - IPC_GLB_GET_FW_CAPABILITIES = 5, /* Retrieves firmware capabilities */ - IPC_GLB_STREAM_MESSAGE = 6, /* Message directed to stream or its stages */ - /* Request to store firmware context during D0->D3 transition */ - IPC_GLB_REQUEST_DUMP = 7, - /* Request to restore firmware context during D3->D0 transition */ - IPC_GLB_RESTORE_CONTEXT = 8, - IPC_GLB_GET_DEVICE_FORMATS = 9, /* Set device format */ - IPC_GLB_SET_DEVICE_FORMATS = 10, /* Get device format */ - IPC_GLB_SHORT_REPLY = 11, - IPC_GLB_ENTER_DX_STATE = 12, - IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */ - IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */ - IPC_GLB_MODULE_OPERATION = 15, /* Message to loadable fw module */ - IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */ - IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */ -}; - -enum ipc_glb_reply { - IPC_GLB_REPLY_SUCCESS = 0, /* The operation was successful. */ - IPC_GLB_REPLY_ERROR_INVALID_PARAM = 1, /* Invalid parameter was passed. */ - IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 2, /* Uknown message type was resceived. */ - IPC_GLB_REPLY_OUT_OF_RESOURCES = 3, /* No resources to satisfy the request. */ - IPC_GLB_REPLY_BUSY = 4, /* The system or resource is busy. */ - IPC_GLB_REPLY_PENDING = 5, /* The action was scheduled for processing. */ - IPC_GLB_REPLY_FAILURE = 6, /* Critical error happened. */ - IPC_GLB_REPLY_INVALID_REQUEST = 7, /* Request can not be completed. */ - IPC_GLB_REPLY_STAGE_UNINITIALIZED = 8, /* Processing stage was uninitialized. */ - IPC_GLB_REPLY_NOT_FOUND = 9, /* Required resource can not be found. */ - IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */ -}; - -enum ipc_module_operation { - IPC_MODULE_NOTIFICATION = 0, - IPC_MODULE_ENABLE = 1, - IPC_MODULE_DISABLE = 2, - IPC_MODULE_GET_PARAMETER = 3, - IPC_MODULE_SET_PARAMETER = 4, - IPC_MODULE_GET_INFO = 5, - IPC_MODULE_MAX_MESSAGE -}; - -/* Stream Message - Types */ -enum ipc_str_operation { - IPC_STR_RESET = 0, - IPC_STR_PAUSE = 1, - IPC_STR_RESUME = 2, - IPC_STR_STAGE_MESSAGE = 3, - IPC_STR_NOTIFICATION = 4, - IPC_STR_MAX_MESSAGE -}; - -/* Stream Stage Message Types */ -enum ipc_stg_operation { - IPC_STG_GET_VOLUME = 0, - IPC_STG_SET_VOLUME, - IPC_STG_SET_WRITE_POSITION, - IPC_STG_SET_FX_ENABLE, - IPC_STG_SET_FX_DISABLE, - IPC_STG_SET_FX_GET_PARAM, - IPC_STG_SET_FX_SET_PARAM, - IPC_STG_SET_FX_GET_INFO, - IPC_STG_MUTE_LOOPBACK, - IPC_STG_MAX_MESSAGE -}; - -/* Stream Stage Message Types For Notification*/ -enum ipc_stg_operation_notify { - IPC_POSITION_CHANGED = 0, - IPC_STG_GLITCH, - IPC_STG_MAX_NOTIFY -}; - -enum ipc_glitch_type { - IPC_GLITCH_UNDERRUN = 1, - IPC_GLITCH_DECODER_ERROR, - IPC_GLITCH_DOUBLED_WRITE_POS, - IPC_GLITCH_MAX -}; - -/* Debug Control */ -enum ipc_debug_operation { - IPC_DEBUG_ENABLE_LOG = 0, - IPC_DEBUG_DISABLE_LOG = 1, - IPC_DEBUG_REQUEST_LOG_DUMP = 2, - IPC_DEBUG_NOTIFY_LOG_DUMP = 3, - IPC_DEBUG_MAX_DEBUG_LOG -}; - -/* Firmware Ready */ -struct sst_hsw_ipc_fw_ready { - u32 inbox_offset; - u32 outbox_offset; - u32 inbox_size; - u32 outbox_size; - u32 fw_info_size; - u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; -} __attribute__((packed)); - -struct ipc_message { - struct list_head list; - u32 header; - - /* direction wrt host CPU */ - char tx_data[IPC_MAX_MAILBOX_BYTES]; - size_t tx_size; - char rx_data[IPC_MAX_MAILBOX_BYTES]; - size_t rx_size; - - wait_queue_head_t waitq; - bool pending; - bool complete; - bool wait; - int errno; -}; - -struct sst_hsw_stream; -struct sst_hsw; - -/* Stream infomation */ -struct sst_hsw_stream { - /* configuration */ - struct sst_hsw_ipc_stream_alloc_req request; - struct sst_hsw_ipc_stream_alloc_reply reply; - struct sst_hsw_ipc_stream_free_req free_req; - - /* Mixer info */ - u32 mute_volume[SST_HSW_NO_CHANNELS]; - u32 mute[SST_HSW_NO_CHANNELS]; - - /* runtime info */ - struct sst_hsw *hsw; - int host_id; - bool commited; - bool running; - - /* Notification work */ - struct work_struct notify_work; - u32 header; - - /* Position info from DSP */ - struct sst_hsw_ipc_stream_set_position wpos; - struct sst_hsw_ipc_stream_get_position rpos; - struct sst_hsw_ipc_stream_glitch_position glitch; - - /* Volume info */ - struct sst_hsw_ipc_volume_req vol_req; - - /* driver callback */ - u32 (*notify_position)(struct sst_hsw_stream *stream, void *data); - void *pdata; - - /* record the fw read position when playback */ - snd_pcm_uframes_t old_position; - bool play_silence; - struct list_head node; -}; - -/* FW log ring information */ -struct sst_hsw_log_stream { - dma_addr_t dma_addr; - unsigned char *dma_area; - unsigned char *ring_descr; - int pages; - int size; - - /* Notification work */ - struct work_struct notify_work; - wait_queue_head_t readers_wait_q; - struct mutex rw_mutex; - - u32 last_pos; - u32 curr_pos; - u32 reader_pos; - - /* fw log config */ - u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; - - struct sst_hsw *hsw; -}; - -/* SST Haswell IPC data */ -struct sst_hsw { - struct device *dev; - struct sst_dsp *dsp; - struct platform_device *pdev_pcm; - - /* FW config */ - struct sst_hsw_ipc_fw_ready fw_ready; - struct sst_hsw_ipc_fw_version version; - bool fw_done; - struct sst_fw *sst_fw; - - /* stream */ - struct list_head stream_list; - - /* global mixer */ - struct sst_hsw_ipc_stream_info_reply mixer_info; - enum sst_hsw_volume_curve curve_type; - u32 curve_duration; - u32 mute[SST_HSW_NO_CHANNELS]; - u32 mute_volume[SST_HSW_NO_CHANNELS]; - - /* DX */ - struct sst_hsw_ipc_dx_reply dx; - void *dx_context; - dma_addr_t dx_context_paddr; - - /* boot */ - wait_queue_head_t boot_wait; - bool boot_complete; - bool shutdown; - - /* IPC messaging */ - struct list_head tx_list; - struct list_head rx_list; - struct list_head empty_list; - wait_queue_head_t wait_txq; - struct task_struct *tx_thread; - struct kthread_worker kworker; - struct kthread_work kwork; - bool pending; - struct ipc_message *msg; - - /* FW log stream */ - struct sst_hsw_log_stream log_stream; - - /* flags bit field to track module state when resume from RTD3, - * each bit represent state (enabled/disabled) of single module */ - u32 enabled_modules_rtd3; - - /* buffer to store parameter lines */ - u32 param_idx_w; /* write index */ - u32 param_idx_r; /* read index */ - u8 param_buf[WAVES_PARAM_LINES][WAVES_PARAM_COUNT]; -}; - -#define CREATE_TRACE_POINTS -#include - -static inline u32 msg_get_global_type(u32 msg) -{ - return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT; -} - -static inline u32 msg_get_global_reply(u32 msg) -{ - return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT; -} - -static inline u32 msg_get_stream_type(u32 msg) -{ - return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; -} - -static inline u32 msg_get_stage_type(u32 msg) -{ - return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; -} - -static inline u32 msg_get_stream_id(u32 msg) -{ - return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; -} - -static inline u32 msg_get_notify_reason(u32 msg) -{ - return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; -} - -static inline u32 msg_get_module_operation(u32 msg) -{ - return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT; -} - -static inline u32 msg_get_module_id(u32 msg) -{ - return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT; -} - -u32 create_channel_map(enum sst_hsw_channel_config config) -{ - switch (config) { - case SST_HSW_CHANNEL_CONFIG_MONO: - return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER); - case SST_HSW_CHANNEL_CONFIG_STEREO: - return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_RIGHT << 4)); - case SST_HSW_CHANNEL_CONFIG_2_POINT_1: - return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_RIGHT << 4) - | (SST_HSW_CHANNEL_LFE << 8 )); - case SST_HSW_CHANNEL_CONFIG_3_POINT_0: - return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8)); - case SST_HSW_CHANNEL_CONFIG_3_POINT_1: - return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_LFE << 12)); - case SST_HSW_CHANNEL_CONFIG_QUATRO: - return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_RIGHT << 4) - | (SST_HSW_CHANNEL_LEFT_SURROUND << 8) - | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12)); - case SST_HSW_CHANNEL_CONFIG_4_POINT_0: - return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_CENTER_SURROUND << 12)); - case SST_HSW_CHANNEL_CONFIG_5_POINT_0: - return (0xFFF00000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) - | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)); - case SST_HSW_CHANNEL_CONFIG_5_POINT_1: - return (0xFF000000 | SST_HSW_CHANNEL_CENTER - | (SST_HSW_CHANNEL_LEFT << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) - | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16) - | (SST_HSW_CHANNEL_LFE << 20)); - case SST_HSW_CHANNEL_CONFIG_DUAL_MONO: - return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_LEFT << 4)); - default: - return 0xFFFFFFFF; - } -} - -static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, - int stream_id) -{ - struct sst_hsw_stream *stream; - - list_for_each_entry(stream, &hsw->stream_list, node) { - if (stream->reply.stream_hw_id == stream_id) - return stream; - } - - return NULL; -} - -static void ipc_shim_dbg(struct sst_hsw *hsw, const char *text) -{ - struct sst_dsp *sst = hsw->dsp; - u32 isr, ipcd, imrx, ipcx; - - ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); - isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); - ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); - - dev_err(hsw->dev, "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", - text, ipcx, isr, ipcd, imrx); -} - -/* locks held by caller */ -static struct ipc_message *msg_get_empty(struct sst_hsw *hsw) -{ - struct ipc_message *msg = NULL; - - if (!list_empty(&hsw->empty_list)) { - msg = list_first_entry(&hsw->empty_list, struct ipc_message, - list); - list_del(&msg->list); - } - - return msg; -} - -static void ipc_tx_msgs(struct kthread_work *work) -{ - struct sst_hsw *hsw = - container_of(work, struct sst_hsw, kwork); - struct ipc_message *msg; - unsigned long flags; - u32 ipcx; - - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - - if (list_empty(&hsw->tx_list) || hsw->pending) { - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return; - } - - /* if the DSP is busy, we will TX messages after IRQ. - * also postpone if we are in the middle of procesing completion irq*/ - ipcx = sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX); - if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) { - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return; - } - - msg = list_first_entry(&hsw->tx_list, struct ipc_message, list); - - list_move(&msg->list, &hsw->rx_list); - - /* send the message */ - sst_dsp_outbox_write(hsw->dsp, msg->tx_data, msg->tx_size); - sst_dsp_ipc_msg_tx(hsw->dsp, msg->header | SST_IPCX_BUSY); - - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); -} - -/* locks held by caller */ -static void tx_msg_reply_complete(struct sst_hsw *hsw, struct ipc_message *msg) -{ - msg->complete = true; - trace_ipc_reply("completed", msg->header); - - if (!msg->wait) - list_add_tail(&msg->list, &hsw->empty_list); - else - wake_up(&msg->waitq); -} - -static int tx_wait_done(struct sst_hsw *hsw, struct ipc_message *msg, - void *rx_data) -{ - unsigned long flags; - int ret; - - /* wait for DSP completion (in all cases atm inc pending) */ - ret = wait_event_timeout(msg->waitq, msg->complete, - msecs_to_jiffies(IPC_TIMEOUT_MSECS)); - - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - if (ret == 0) { - ipc_shim_dbg(hsw, "message timeout"); - - trace_ipc_error("error message timeout for", msg->header); - list_del(&msg->list); - ret = -ETIMEDOUT; - } else { - - /* copy the data returned from DSP */ - if (msg->rx_size) - memcpy(rx_data, msg->rx_data, msg->rx_size); - ret = msg->errno; - } - - list_add_tail(&msg->list, &hsw->empty_list); - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return ret; -} - -static int ipc_tx_message(struct sst_hsw *hsw, u32 header, void *tx_data, - size_t tx_bytes, void *rx_data, size_t rx_bytes, int wait) -{ - struct ipc_message *msg; - unsigned long flags; - - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - - msg = msg_get_empty(hsw); - if (msg == NULL) { - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return -EBUSY; - } - - if (tx_bytes) - memcpy(msg->tx_data, tx_data, tx_bytes); - - msg->header = header; - msg->tx_size = tx_bytes; - msg->rx_size = rx_bytes; - msg->wait = wait; - msg->errno = 0; - msg->pending = false; - msg->complete = false; - - list_add_tail(&msg->list, &hsw->tx_list); - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - - queue_kthread_work(&hsw->kworker, &hsw->kwork); - - if (wait) - return tx_wait_done(hsw, msg, rx_data); - else - return 0; -} - -static inline int ipc_tx_message_wait(struct sst_hsw *hsw, u32 header, - void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) -{ - return ipc_tx_message(hsw, header, tx_data, tx_bytes, rx_data, - rx_bytes, 1); -} - -static inline int ipc_tx_message_nowait(struct sst_hsw *hsw, u32 header, - void *tx_data, size_t tx_bytes) -{ - return ipc_tx_message(hsw, header, tx_data, tx_bytes, NULL, 0, 0); -} - -static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) -{ - struct sst_hsw_ipc_fw_ready fw_ready; - u32 offset; - u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; - char *tmp[5], *pinfo; - int i = 0; - - offset = (header & 0x1FFFFFFF) << 3; - - dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", - header, offset); - - /* copy data from the DSP FW ready offset */ - sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready)); - - sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset, - fw_ready.inbox_size, fw_ready.outbox_offset, - fw_ready.outbox_size); - - hsw->boot_complete = true; - wake_up(&hsw->boot_wait); - - dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n", - fw_ready.inbox_offset, fw_ready.inbox_size); - dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", - fw_ready.outbox_offset, fw_ready.outbox_size); - if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) { - fw_ready.fw_info[fw_ready.fw_info_size] = 0; - dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info); - - /* log the FW version info got from the mailbox here. */ - memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); - pinfo = &fw_info[0]; - for (i = 0; i < sizeof(tmp) / sizeof(char *); i++) - tmp[i] = strsep(&pinfo, " "); - dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " - "version: %s.%s, build %s, source commit id: %s\n", - tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]); - } -} - -static void hsw_notification_work(struct work_struct *work) -{ - struct sst_hsw_stream *stream = container_of(work, - struct sst_hsw_stream, notify_work); - struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch; - struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos; - struct sst_hsw *hsw = stream->hsw; - u32 reason; - - reason = msg_get_notify_reason(stream->header); - - switch (reason) { - case IPC_STG_GLITCH: - trace_ipc_notification("DSP stream under/overrun", - stream->reply.stream_hw_id); - sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch)); - - dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n", - glitch->glitch_type, glitch->present_pos, - glitch->write_pos); - break; - - case IPC_POSITION_CHANGED: - trace_ipc_notification("DSP stream position changed for", - stream->reply.stream_hw_id); - sst_dsp_inbox_read(hsw->dsp, pos, sizeof(*pos)); - - if (stream->notify_position) - stream->notify_position(stream, stream->pdata); - - break; - default: - dev_err(hsw->dev, "error: unknown notification 0x%x\n", - stream->header); - break; - } - - /* tell DSP that notification has been handled */ - sst_dsp_shim_update_bits(hsw->dsp, SST_IPCD, - SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); - - /* unmask busy interrupt */ - sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); -} - -static struct ipc_message *reply_find_msg(struct sst_hsw *hsw, u32 header) -{ - struct ipc_message *msg; - - /* clear reply bits & status bits */ - header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); - - if (list_empty(&hsw->rx_list)) { - dev_err(hsw->dev, "error: rx list empty but received 0x%x\n", - header); - return NULL; - } - - list_for_each_entry(msg, &hsw->rx_list, list) { - if (msg->header == header) - return msg; - } - - return NULL; -} - -static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) -{ - struct sst_hsw_stream *stream; - u32 header = msg->header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); - u32 stream_id = msg_get_stream_id(header); - u32 stream_msg = msg_get_stream_type(header); - - stream = get_stream_by_id(hsw, stream_id); - if (stream == NULL) - return; - - switch (stream_msg) { - case IPC_STR_STAGE_MESSAGE: - case IPC_STR_NOTIFICATION: - break; - case IPC_STR_RESET: - trace_ipc_notification("stream reset", stream->reply.stream_hw_id); - break; - case IPC_STR_PAUSE: - stream->running = false; - trace_ipc_notification("stream paused", - stream->reply.stream_hw_id); - break; - case IPC_STR_RESUME: - stream->running = true; - trace_ipc_notification("stream running", - stream->reply.stream_hw_id); - break; - } -} - -static int hsw_process_reply(struct sst_hsw *hsw, u32 header) -{ - struct ipc_message *msg; - u32 reply = msg_get_global_reply(header); - - trace_ipc_reply("processing -->", header); - - msg = reply_find_msg(hsw, header); - if (msg == NULL) { - trace_ipc_error("error: can't find message header", header); - return -EIO; - } - - /* first process the header */ - switch (reply) { - case IPC_GLB_REPLY_PENDING: - trace_ipc_pending_reply("received", header); - msg->pending = true; - hsw->pending = true; - return 1; - case IPC_GLB_REPLY_SUCCESS: - if (msg->pending) { - trace_ipc_pending_reply("completed", header); - sst_dsp_inbox_read(hsw->dsp, msg->rx_data, - msg->rx_size); - hsw->pending = false; - } else { - /* copy data from the DSP */ - sst_dsp_outbox_read(hsw->dsp, msg->rx_data, - msg->rx_size); - } - break; - /* these will be rare - but useful for debug */ - case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE: - trace_ipc_error("error: unknown message type", header); - msg->errno = -EBADMSG; - break; - case IPC_GLB_REPLY_OUT_OF_RESOURCES: - trace_ipc_error("error: out of resources", header); - msg->errno = -ENOMEM; - break; - case IPC_GLB_REPLY_BUSY: - trace_ipc_error("error: reply busy", header); - msg->errno = -EBUSY; - break; - case IPC_GLB_REPLY_FAILURE: - trace_ipc_error("error: reply failure", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_STAGE_UNINITIALIZED: - trace_ipc_error("error: stage uninitialized", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_NOT_FOUND: - trace_ipc_error("error: reply not found", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_SOURCE_NOT_STARTED: - trace_ipc_error("error: source not started", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_INVALID_REQUEST: - trace_ipc_error("error: invalid request", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_ERROR_INVALID_PARAM: - trace_ipc_error("error: invalid parameter", header); - msg->errno = -EINVAL; - break; - default: - trace_ipc_error("error: unknown reply", header); - msg->errno = -EINVAL; - break; - } - - /* update any stream states */ - if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE) - hsw_stream_update(hsw, msg); - - /* wake up and return the error if we have waiters on this message ? */ - list_del(&msg->list); - tx_msg_reply_complete(hsw, msg); - - return 1; -} - -static int hsw_module_message(struct sst_hsw *hsw, u32 header) -{ - u32 operation, module_id; - int handled = 0; - - operation = msg_get_module_operation(header); - module_id = msg_get_module_id(header); - dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n", - header); - dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n", - operation, module_id); - - switch (operation) { - case IPC_MODULE_NOTIFICATION: - dev_dbg(hsw->dev, "module notification received"); - handled = 1; - break; - default: - handled = hsw_process_reply(hsw, header); - break; - } - - return handled; -} - -static int hsw_stream_message(struct sst_hsw *hsw, u32 header) -{ - u32 stream_msg, stream_id, stage_type; - struct sst_hsw_stream *stream; - int handled = 0; - - stream_msg = msg_get_stream_type(header); - stream_id = msg_get_stream_id(header); - stage_type = msg_get_stage_type(header); - - stream = get_stream_by_id(hsw, stream_id); - if (stream == NULL) - return handled; - - stream->header = header; - - switch (stream_msg) { - case IPC_STR_STAGE_MESSAGE: - dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n", - header); - break; - case IPC_STR_NOTIFICATION: - schedule_work(&stream->notify_work); - break; - default: - /* handle pending message complete request */ - handled = hsw_process_reply(hsw, header); - break; - } - - return handled; -} - -static int hsw_log_message(struct sst_hsw *hsw, u32 header) -{ - u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT; - struct sst_hsw_log_stream *stream = &hsw->log_stream; - int ret = 1; - - if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) { - dev_err(hsw->dev, - "error: log msg not implemented 0x%8.8x\n", header); - return 0; - } - - mutex_lock(&stream->rw_mutex); - stream->last_pos = stream->curr_pos; - sst_dsp_inbox_read( - hsw->dsp, &stream->curr_pos, sizeof(stream->curr_pos)); - mutex_unlock(&stream->rw_mutex); - - schedule_work(&stream->notify_work); - - return ret; -} - -static int hsw_process_notification(struct sst_hsw *hsw) -{ - struct sst_dsp *sst = hsw->dsp; - u32 type, header; - int handled = 1; - - header = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - type = msg_get_global_type(header); - - trace_ipc_request("processing -->", header); - - /* FW Ready is a special case */ - if (!hsw->boot_complete && header & IPC_FW_READY) { - hsw_fw_ready(hsw, header); - return handled; - } - - switch (type) { - case IPC_GLB_GET_FW_VERSION: - case IPC_GLB_ALLOCATE_STREAM: - case IPC_GLB_FREE_STREAM: - case IPC_GLB_GET_FW_CAPABILITIES: - case IPC_GLB_REQUEST_DUMP: - case IPC_GLB_GET_DEVICE_FORMATS: - case IPC_GLB_SET_DEVICE_FORMATS: - case IPC_GLB_ENTER_DX_STATE: - case IPC_GLB_GET_MIXER_STREAM_INFO: - case IPC_GLB_MAX_IPC_MESSAGE_TYPE: - case IPC_GLB_RESTORE_CONTEXT: - case IPC_GLB_SHORT_REPLY: - dev_err(hsw->dev, "error: message type %d header 0x%x\n", - type, header); - break; - case IPC_GLB_STREAM_MESSAGE: - handled = hsw_stream_message(hsw, header); - break; - case IPC_GLB_DEBUG_LOG_MESSAGE: - handled = hsw_log_message(hsw, header); - break; - case IPC_GLB_MODULE_OPERATION: - handled = hsw_module_message(hsw, header); - break; - default: - dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n", - type, header); - break; - } - - return handled; -} - -static irqreturn_t hsw_irq_thread(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); - u32 ipcx, ipcd; - int handled; - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - - ipcx = sst_dsp_ipc_msg_rx(hsw->dsp); - ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - - /* reply message from DSP */ - if (ipcx & SST_IPCX_DONE) { - - /* Handle Immediate reply from DSP Core */ - handled = hsw_process_reply(hsw, ipcx); - - if (handled > 0) { - /* clear DONE bit - tell DSP we have completed */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX, - SST_IPCX_DONE, 0); - - /* unmask Done interrupt */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_DONE, 0); - } - } - - /* new message from DSP */ - if (ipcd & SST_IPCD_BUSY) { - - /* Handle Notification and Delayed reply from DSP Core */ - handled = hsw_process_notification(hsw); - - /* clear BUSY bit and set DONE bit - accept new messages */ - if (handled > 0) { - sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD, - SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); - - /* unmask busy interrupt */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_BUSY, 0); - } - } - - spin_unlock_irqrestore(&sst->spinlock, flags); - - /* continue to send any remaining messages... */ - queue_kthread_work(&hsw->kworker, &hsw->kwork); - - return IRQ_HANDLED; -} - -int sst_hsw_fw_get_version(struct sst_hsw *hsw, - struct sst_hsw_ipc_fw_version *version) -{ - int ret; - - ret = ipc_tx_message_wait(hsw, IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), - NULL, 0, version, sizeof(*version)); - if (ret < 0) - dev_err(hsw->dev, "error: get version failed\n"); - - return ret; -} - -/* Mixer Controls */ -int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 stage_id, u32 channel, u32 *volume) -{ - if (channel > 1) - return -EINVAL; - - sst_dsp_read(hsw->dsp, volume, - stream->reply.volume_register_address[channel], - sizeof(*volume)); - - return 0; -} - -/* stream volume */ -int sst_hsw_stream_set_volume(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume) -{ - struct sst_hsw_ipc_volume_req *req; - u32 header; - int ret; - - trace_ipc_request("set stream volume", stream->reply.stream_hw_id); - - if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) - return -EINVAL; - - header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | - IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); - header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); - header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); - header |= (stage_id << IPC_STG_ID_SHIFT); - - req = &stream->vol_req; - req->target_volume = volume; - - /* set both at same time ? */ - if (channel == SST_HSW_CHANNELS_ALL) { - if (hsw->mute[0] && hsw->mute[1]) { - hsw->mute_volume[0] = hsw->mute_volume[1] = volume; - return 0; - } else if (hsw->mute[0]) - req->channel = 1; - else if (hsw->mute[1]) - req->channel = 0; - else - req->channel = SST_HSW_CHANNELS_ALL; - } else { - /* set only 1 channel */ - if (hsw->mute[channel]) { - hsw->mute_volume[channel] = volume; - return 0; - } - req->channel = channel; - } - - ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: set stream volume failed\n"); - return ret; - } - - return 0; -} - -int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 *volume) -{ - if (channel > 1) - return -EINVAL; - - sst_dsp_read(hsw->dsp, volume, - hsw->mixer_info.volume_register_address[channel], - sizeof(*volume)); - - return 0; -} - -/* global mixer volume */ -int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 volume) -{ - struct sst_hsw_ipc_volume_req req; - u32 header; - int ret; - - trace_ipc_request("set mixer volume", volume); - - if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) - return -EINVAL; - - /* set both at same time ? */ - if (channel == SST_HSW_CHANNELS_ALL) { - if (hsw->mute[0] && hsw->mute[1]) { - hsw->mute_volume[0] = hsw->mute_volume[1] = volume; - return 0; - } else if (hsw->mute[0]) - req.channel = 1; - else if (hsw->mute[1]) - req.channel = 0; - else - req.channel = SST_HSW_CHANNELS_ALL; - } else { - /* set only 1 channel */ - if (hsw->mute[channel]) { - hsw->mute_volume[channel] = volume; - return 0; - } - req.channel = channel; - } - - header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | - IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); - header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT); - header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); - header |= (stage_id << IPC_STG_ID_SHIFT); - - req.curve_duration = hsw->curve_duration; - req.curve_type = hsw->curve_type; - req.target_volume = volume; - - ret = ipc_tx_message_wait(hsw, header, &req, sizeof(req), NULL, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: set mixer volume failed\n"); - return ret; - } - - return 0; -} - -/* Stream API */ -struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, - u32 (*notify_position)(struct sst_hsw_stream *stream, void *data), - void *data) -{ - struct sst_hsw_stream *stream; - struct sst_dsp *sst = hsw->dsp; - unsigned long flags; - - stream = kzalloc(sizeof(*stream), GFP_KERNEL); - if (stream == NULL) - return NULL; - - spin_lock_irqsave(&sst->spinlock, flags); - stream->reply.stream_hw_id = INVALID_STREAM_HW_ID; - list_add(&stream->node, &hsw->stream_list); - stream->notify_position = notify_position; - stream->pdata = data; - stream->hsw = hsw; - stream->host_id = id; - - /* work to process notification messages */ - INIT_WORK(&stream->notify_work, hsw_notification_work); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return stream; -} - -int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream) -{ - u32 header; - int ret = 0; - struct sst_dsp *sst = hsw->dsp; - unsigned long flags; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to free, ignore it.\n"); - return 0; - } - - /* dont free DSP streams that are not commited */ - if (!stream->commited) - goto out; - - trace_ipc_request("stream free", stream->host_id); - - stream->free_req.stream_id = stream->reply.stream_hw_id; - header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); - - ret = ipc_tx_message_wait(hsw, header, &stream->free_req, - sizeof(stream->free_req), NULL, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: free stream %d failed\n", - stream->free_req.stream_id); - return -EAGAIN; - } - - trace_hsw_stream_free_req(stream, &stream->free_req); - -out: - cancel_work_sync(&stream->notify_work); - spin_lock_irqsave(&sst->spinlock, flags); - list_del(&stream->node); - kfree(stream); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return ret; -} - -int sst_hsw_stream_set_bits(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, enum sst_hsw_bitdepth bits) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set bits\n"); - return -EINVAL; - } - - stream->request.format.bitdepth = bits; - return 0; -} - -int sst_hsw_stream_set_channels(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, int channels) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set channels\n"); - return -EINVAL; - } - - stream->request.format.ch_num = channels; - return 0; -} - -int sst_hsw_stream_set_rate(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, int rate) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set rate\n"); - return -EINVAL; - } - - stream->request.format.frequency = rate; - return 0; -} - -int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 map, - enum sst_hsw_channel_config config) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set map\n"); - return -EINVAL; - } - - stream->request.format.map = map; - stream->request.format.config = config; - return 0; -} - -int sst_hsw_stream_set_style(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, enum sst_hsw_interleaving style) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set style\n"); - return -EINVAL; - } - - stream->request.format.style = style; - return 0; -} - -int sst_hsw_stream_set_valid(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 bits) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set valid bits\n"); - return -EINVAL; - } - - stream->request.format.valid_bit = bits; - return 0; -} - -/* Stream Configuration */ -int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_stream_path_id path_id, - enum sst_hsw_stream_type stream_type, - enum sst_hsw_stream_format format_id) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set format\n"); - return -EINVAL; - } - - stream->request.path_id = path_id; - stream->request.stream_type = stream_type; - stream->request.format_id = format_id; - - trace_hsw_stream_alloc_request(stream, &stream->request); - - return 0; -} - -int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 ring_pt_address, u32 num_pages, - u32 ring_size, u32 ring_offset, u32 ring_first_pfn) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for buffer\n"); - return -EINVAL; - } - - stream->request.ringinfo.ring_pt_address = ring_pt_address; - stream->request.ringinfo.num_pages = num_pages; - stream->request.ringinfo.ring_size = ring_size; - stream->request.ringinfo.ring_offset = ring_offset; - stream->request.ringinfo.ring_first_pfn = ring_first_pfn; - - trace_hsw_stream_buffer(stream); - - return 0; -} - -int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, struct sst_module_runtime *runtime) -{ - struct sst_hsw_module_map *map = &stream->request.map; - struct sst_dsp *dsp = sst_hsw_get_dsp(hsw); - struct sst_module *module = runtime->module; - - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set module\n"); - return -EINVAL; - } - - /* only support initial module atm */ - map->module_entries_count = 1; - map->module_entries[0].module_id = module->id; - map->module_entries[0].entry_point = module->entry; - - stream->request.persistent_mem.offset = - sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM); - stream->request.persistent_mem.size = module->persistent_size; - - stream->request.scratch_mem.offset = - sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM); - stream->request.scratch_mem.size = dsp->scratch_size; - - dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id, - runtime->id); - dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n", - stream->request.persistent_mem.offset, - stream->request.persistent_mem.size); - dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n", - stream->request.scratch_mem.offset, - stream->request.scratch_mem.size); - - return 0; -} - -int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) -{ - struct sst_hsw_ipc_stream_alloc_req *str_req = &stream->request; - struct sst_hsw_ipc_stream_alloc_reply *reply = &stream->reply; - u32 header; - int ret; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to commit, ignore it.\n"); - return 0; - } - - if (stream->commited) { - dev_warn(hsw->dev, "warning: stream is already committed, ignore it.\n"); - return 0; - } - - trace_ipc_request("stream alloc", stream->host_id); - - header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); - - ret = ipc_tx_message_wait(hsw, header, str_req, sizeof(*str_req), - reply, sizeof(*reply)); - if (ret < 0) { - dev_err(hsw->dev, "error: stream commit failed\n"); - return ret; - } - - stream->commited = 1; - trace_hsw_stream_alloc_reply(stream); - - return 0; -} - -snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - return stream->old_position; -} - -void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, snd_pcm_uframes_t val) -{ - stream->old_position = val; -} - -bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - return stream->play_silence; -} - -void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, bool val) -{ - stream->play_silence = val; -} - -/* Stream Information - these calls could be inline but we want the IPC - ABI to be opaque to client PCM drivers to cope with any future ABI changes */ -int sst_hsw_mixer_get_info(struct sst_hsw *hsw) -{ - struct sst_hsw_ipc_stream_info_reply *reply; - u32 header; - int ret; - - reply = &hsw->mixer_info; - header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO); - - trace_ipc_request("get global mixer info", 0); - - ret = ipc_tx_message_wait(hsw, header, NULL, 0, reply, sizeof(*reply)); - if (ret < 0) { - dev_err(hsw->dev, "error: get stream info failed\n"); - return ret; - } - - trace_hsw_mixer_info_reply(reply); - - return 0; -} - -/* Send stream command */ -static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, - int stream_id, int wait) -{ - u32 header; - - header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type); - header |= (stream_id << IPC_STR_ID_SHIFT); - - if (wait) - return ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); - else - return ipc_tx_message_nowait(hsw, header, NULL, 0); -} - -/* Stream ALSA trigger operations */ -int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait) -{ - int ret; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to pause, ignore it.\n"); - return 0; - } - - trace_ipc_request("stream pause", stream->reply.stream_hw_id); - - ret = sst_hsw_stream_operations(hsw, IPC_STR_PAUSE, - stream->reply.stream_hw_id, wait); - if (ret < 0) - dev_err(hsw->dev, "error: failed to pause stream %d\n", - stream->reply.stream_hw_id); - - return ret; -} - -int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait) -{ - int ret; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to resume, ignore it.\n"); - return 0; - } - - trace_ipc_request("stream resume", stream->reply.stream_hw_id); - - ret = sst_hsw_stream_operations(hsw, IPC_STR_RESUME, - stream->reply.stream_hw_id, wait); - if (ret < 0) - dev_err(hsw->dev, "error: failed to resume stream %d\n", - stream->reply.stream_hw_id); - - return ret; -} - -int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream) -{ - int ret, tries = 10; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to reset, ignore it.\n"); - return 0; - } - - /* dont reset streams that are not commited */ - if (!stream->commited) - return 0; - - /* wait for pause to complete before we reset the stream */ - while (stream->running && tries--) - msleep(1); - if (!tries) { - dev_err(hsw->dev, "error: reset stream %d still running\n", - stream->reply.stream_hw_id); - return -EINVAL; - } - - trace_ipc_request("stream reset", stream->reply.stream_hw_id); - - ret = sst_hsw_stream_operations(hsw, IPC_STR_RESET, - stream->reply.stream_hw_id, 1); - if (ret < 0) - dev_err(hsw->dev, "error: failed to reset stream %d\n", - stream->reply.stream_hw_id); - return ret; -} - -/* Stream pointer positions */ -u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - u32 rpos; - - sst_dsp_read(hsw->dsp, &rpos, - stream->reply.read_position_register_address, sizeof(rpos)); - - return rpos; -} - -/* Stream presentation (monotonic) positions */ -u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - u64 ppos; - - sst_dsp_read(hsw->dsp, &ppos, - stream->reply.presentation_position_register_address, - sizeof(ppos)); - - return ppos; -} - -/* physical BE config */ -int sst_hsw_device_set_config(struct sst_hsw *hsw, - enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, - enum sst_hsw_device_mode mode, u32 clock_divider) -{ - struct sst_hsw_ipc_device_config_req config; - u32 header; - int ret; - - trace_ipc_request("set device config", dev); - - config.ssp_interface = dev; - config.clock_frequency = mclk; - config.mode = mode; - config.clock_divider = clock_divider; - if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER) - config.channels = 4; - else - config.channels = 2; - - trace_hsw_device_config_req(&config); - - header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); - - ret = ipc_tx_message_wait(hsw, header, &config, sizeof(config), - NULL, 0); - if (ret < 0) - dev_err(hsw->dev, "error: set device formats failed\n"); - - return ret; -} -EXPORT_SYMBOL_GPL(sst_hsw_device_set_config); - -/* DX Config */ -int sst_hsw_dx_set_state(struct sst_hsw *hsw, - enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) -{ - u32 header, state_; - int ret, item; - - header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); - state_ = state; - - trace_ipc_request("PM enter Dx state", state); - - ret = ipc_tx_message_wait(hsw, header, &state_, sizeof(state_), - dx, sizeof(*dx)); - if (ret < 0) { - dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); - return ret; - } - - for (item = 0; item < dx->entries_no; item++) { - dev_dbg(hsw->dev, - "Item[%d] offset[%x] - size[%x] - source[%x]\n", - item, dx->mem_info[item].offset, - dx->mem_info[item].size, - dx->mem_info[item].source); - } - dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", - dx->entries_no, state); - - return ret; -} - -struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, - int mod_id, int offset) -{ - struct sst_dsp *dsp = hsw->dsp; - struct sst_module *module; - struct sst_module_runtime *runtime; - int err; - - module = sst_module_get_from_id(dsp, mod_id); - if (module == NULL) { - dev_err(dsp->dev, "error: failed to get module %d for pcm\n", - mod_id); - return NULL; - } - - runtime = sst_module_runtime_new(module, mod_id, NULL); - if (runtime == NULL) { - dev_err(dsp->dev, "error: failed to create module %d runtime\n", - mod_id); - return NULL; - } - - err = sst_module_runtime_alloc_blocks(runtime, offset); - if (err < 0) { - dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n", - mod_id); - sst_module_runtime_free(runtime); - return NULL; - } - - dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id, - mod_id); - return runtime; -} - -void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime) -{ - sst_module_runtime_free_blocks(runtime); - sst_module_runtime_free(runtime); -} - -#ifdef CONFIG_PM -static int sst_hsw_dx_state_dump(struct sst_hsw *hsw) -{ - struct sst_dsp *sst = hsw->dsp; - u32 item, offset, size; - int ret = 0; - - trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS); - - if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) { - dev_err(hsw->dev, - "error: number of FW context regions greater than %d\n", - SST_HSW_MAX_DX_REGIONS); - memset(&hsw->dx, 0, sizeof(hsw->dx)); - return -EINVAL; - } - - ret = sst_dsp_dma_get_channel(sst, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); - return ret; - } - - /* set on-demond mode on engine 0 channel 3 */ - sst_dsp_shim_update_bits(sst, SST_HMDC, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); - - for (item = 0; item < hsw->dx.entries_no; item++) { - if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP - && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET - && hsw->dx.mem_info[item].offset < - DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { - - offset = hsw->dx.mem_info[item].offset - - DSP_DRAM_ADDR_OFFSET; - size = (hsw->dx.mem_info[item].size + 3) & (~3); - - ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset, - sst->addr.lpe_base + offset, size); - if (ret < 0) { - dev_err(hsw->dev, - "error: FW context dump failed\n"); - memset(&hsw->dx, 0, sizeof(hsw->dx)); - goto out; - } - } - } - -out: - sst_dsp_dma_put_channel(sst); - return ret; -} - -static int sst_hsw_dx_state_restore(struct sst_hsw *hsw) -{ - struct sst_dsp *sst = hsw->dsp; - u32 item, offset, size; - int ret; - - for (item = 0; item < hsw->dx.entries_no; item++) { - if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP - && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET - && hsw->dx.mem_info[item].offset < - DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { - - offset = hsw->dx.mem_info[item].offset - - DSP_DRAM_ADDR_OFFSET; - size = (hsw->dx.mem_info[item].size + 3) & (~3); - - ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset, - hsw->dx_context_paddr + offset, size); - if (ret < 0) { - dev_err(hsw->dev, - "error: FW context restore failed\n"); - return ret; - } - } - } - - return 0; -} - -static void sst_hsw_drop_all(struct sst_hsw *hsw) -{ - struct ipc_message *msg, *tmp; - unsigned long flags; - int tx_drop_cnt = 0, rx_drop_cnt = 0; - - /* drop all TX and Rx messages before we stall + reset DSP */ - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - - list_for_each_entry_safe(msg, tmp, &hsw->tx_list, list) { - list_move(&msg->list, &hsw->empty_list); - tx_drop_cnt++; - } - - list_for_each_entry_safe(msg, tmp, &hsw->rx_list, list) { - list_move(&msg->list, &hsw->empty_list); - rx_drop_cnt++; - } - - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - - if (tx_drop_cnt || rx_drop_cnt) - dev_err(hsw->dev, "dropped IPC msg RX=%d, TX=%d\n", - tx_drop_cnt, rx_drop_cnt); -} - -int sst_hsw_dsp_load(struct sst_hsw *hsw) -{ - struct sst_dsp *dsp = hsw->dsp; - struct sst_fw *sst_fw, *t; - int ret; - - dev_dbg(hsw->dev, "loading audio DSP...."); - - ret = sst_dsp_wake(dsp); - if (ret < 0) { - dev_err(hsw->dev, "error: failed to wake audio DSP\n"); - return -ENODEV; - } - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); - return ret; - } - - list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) { - ret = sst_fw_reload(sst_fw); - if (ret < 0) { - dev_err(hsw->dev, "error: SST FW reload failed\n"); - sst_dsp_dma_put_channel(dsp); - return -ENOMEM; - } - } - ret = sst_block_alloc_scratch(hsw->dsp); - if (ret < 0) - return -EINVAL; - - sst_dsp_dma_put_channel(dsp); - return 0; -} - -static int sst_hsw_dsp_restore(struct sst_hsw *hsw) -{ - struct sst_dsp *dsp = hsw->dsp; - int ret; - - dev_dbg(hsw->dev, "restoring audio DSP...."); - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); - return ret; - } - - ret = sst_hsw_dx_state_restore(hsw); - if (ret < 0) { - dev_err(hsw->dev, "error: SST FW context restore failed\n"); - sst_dsp_dma_put_channel(dsp); - return -ENOMEM; - } - sst_dsp_dma_put_channel(dsp); - - /* wait for DSP boot completion */ - sst_dsp_boot(dsp); - - return ret; -} - -int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw) -{ - int ret; - - dev_dbg(hsw->dev, "audio dsp runtime suspend\n"); - - ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx); - if (ret < 0) - return ret; - - sst_dsp_stall(hsw->dsp); - - ret = sst_hsw_dx_state_dump(hsw); - if (ret < 0) - return ret; - - sst_hsw_drop_all(hsw); - - return 0; -} - -int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw) -{ - struct sst_fw *sst_fw, *t; - struct sst_dsp *dsp = hsw->dsp; - - list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { - sst_fw_unload(sst_fw); - } - sst_block_free_scratch(dsp); - - hsw->boot_complete = false; - - sst_dsp_sleep(dsp); - - return 0; -} - -int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) -{ - struct device *dev = hsw->dev; - int ret; - - dev_dbg(dev, "audio dsp runtime resume\n"); - - if (hsw->boot_complete) - return 1; /* tell caller no action is required */ - - ret = sst_hsw_dsp_restore(hsw); - if (ret < 0) - dev_err(dev, "error: audio DSP boot failure\n"); - - sst_hsw_init_module_state(hsw); - - ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (ret == 0) { - dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); - return -EIO; - } - - /* Set ADSP SSP port settings */ - ret = sst_hsw_device_set_config(hsw, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_CLOCK_MASTER, 9); - if (ret < 0) - dev_err(dev, "error: SSP re-initialization failed\n"); - - return ret; -} -#endif - -static int msg_empty_list_init(struct sst_hsw *hsw) -{ - int i; - - hsw->msg = kzalloc(sizeof(struct ipc_message) * - IPC_EMPTY_LIST_SIZE, GFP_KERNEL); - if (hsw->msg == NULL) - return -ENOMEM; - - for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { - init_waitqueue_head(&hsw->msg[i].waitq); - list_add(&hsw->msg[i].list, &hsw->empty_list); - } - - return 0; -} - -struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) -{ - return hsw->dsp; -} - -void sst_hsw_init_module_state(struct sst_hsw *hsw) -{ - struct sst_module *module; - enum sst_hsw_module_id id; - - /* the base fw contains several modules */ - for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) { - module = sst_module_get_from_id(hsw->dsp, id); - if (module) { - /* module waves is active only after being enabled */ - if (id == SST_HSW_MODULE_WAVES) - module->state = SST_MODULE_STATE_INITIALIZED; - else - module->state = SST_MODULE_STATE_ACTIVE; - } - } -} - -bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id) -{ - struct sst_module *module; - - module = sst_module_get_from_id(hsw->dsp, module_id); - if (module == NULL || module->state == SST_MODULE_STATE_UNLOADED) - return false; - else - return true; -} - -bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id) -{ - struct sst_module *module; - - module = sst_module_get_from_id(hsw->dsp, module_id); - if (module != NULL && module->state == SST_MODULE_STATE_ACTIVE) - return true; - else - return false; -} - -void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) -{ - hsw->enabled_modules_rtd3 |= (1 << module_id); -} - -void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id) -{ - hsw->enabled_modules_rtd3 &= ~(1 << module_id); -} - -bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) -{ - return hsw->enabled_modules_rtd3 & (1 << module_id); -} - -void sst_hsw_reset_param_buf(struct sst_hsw *hsw) -{ - hsw->param_idx_w = 0; - hsw->param_idx_r = 0; - memset((void *)hsw->param_buf, 0, sizeof(hsw->param_buf)); -} - -int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf) -{ - /* save line to the first available position of param buffer */ - if (hsw->param_idx_w > WAVES_PARAM_LINES - 1) { - dev_warn(hsw->dev, "warning: param buffer overflow!\n"); - return -EPERM; - } - memcpy(hsw->param_buf[hsw->param_idx_w], buf, WAVES_PARAM_COUNT); - hsw->param_idx_w++; - return 0; -} - -int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf) -{ - u8 id = 0; - - /* read the first matching line from param buffer */ - while (hsw->param_idx_r < WAVES_PARAM_LINES) { - id = hsw->param_buf[hsw->param_idx_r][0]; - hsw->param_idx_r++; - if (buf[0] == id) { - memcpy(buf, hsw->param_buf[hsw->param_idx_r], - WAVES_PARAM_COUNT); - break; - } - } - if (hsw->param_idx_r > WAVES_PARAM_LINES - 1) { - dev_dbg(hsw->dev, "end of buffer, roll to the beginning\n"); - hsw->param_idx_r = 0; - return 0; - } - return 0; -} - -int sst_hsw_launch_param_buf(struct sst_hsw *hsw) -{ - int ret, idx; - - if (!sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { - dev_dbg(hsw->dev, "module waves is not active\n"); - return 0; - } - - /* put all param lines to DSP through ipc */ - for (idx = 0; idx < hsw->param_idx_w; idx++) { - ret = sst_hsw_module_set_param(hsw, - SST_HSW_MODULE_WAVES, 0, hsw->param_buf[idx][0], - WAVES_PARAM_COUNT, hsw->param_buf[idx]); - if (ret < 0) - return ret; - } - return 0; -} - -int sst_hsw_module_load(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, char *name) -{ - int ret = 0; - const struct firmware *fw = NULL; - struct sst_fw *hsw_sst_fw; - struct sst_module *module; - struct device *dev = hsw->dev; - struct sst_dsp *dsp = hsw->dsp; - - dev_dbg(dev, "sst_hsw_module_load id=%d, name='%s'", module_id, name); - - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - /* loading for the first time */ - if (module_id == SST_HSW_MODULE_BASE_FW) { - /* for base module: use fw requested in acpi probe */ - fw = dsp->pdata->fw; - if (!fw) { - dev_err(dev, "request Base fw failed\n"); - return -ENODEV; - } - } else { - /* try and load any other optional modules if they are - * available. Use dev_info instead of dev_err in case - * request firmware failed */ - ret = request_firmware(&fw, name, dev); - if (ret) { - dev_info(dev, "fw image %s not available(%d)\n", - name, ret); - return ret; - } - } - hsw_sst_fw = sst_fw_new(dsp, fw, hsw); - if (hsw_sst_fw == NULL) { - dev_err(dev, "error: failed to load firmware\n"); - ret = -ENOMEM; - goto out; - } - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - dev_err(dev, "error: no module %d in firmware %s\n", - module_id, name); - } - } else - dev_info(dev, "module %d (%s) already loaded\n", - module_id, name); -out: - /* release fw, but base fw should be released by acpi driver */ - if (fw && module_id != SST_HSW_MODULE_BASE_FW) - release_firmware(fw); - - return ret; -} - -int sst_hsw_module_enable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id) -{ - int ret; - u32 header = 0; - struct sst_hsw_ipc_module_config config; - struct sst_module *module; - struct sst_module_runtime *runtime; - struct device *dev = hsw->dev; - struct sst_dsp *dsp = hsw->dsp; - - if (!sst_hsw_is_module_loaded(hsw, module_id)) { - dev_dbg(dev, "module %d not loaded\n", module_id); - return 0; - } - - if (sst_hsw_is_module_active(hsw, module_id)) { - dev_info(dev, "module %d already enabled\n", module_id); - return 0; - } - - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - dev_err(dev, "module %d not valid\n", module_id); - return -ENXIO; - } - - runtime = sst_module_runtime_get_from_id(module, module_id); - if (runtime == NULL) { - dev_err(dev, "runtime %d not valid", module_id); - return -ENXIO; - } - - header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | - IPC_MODULE_OPERATION(IPC_MODULE_ENABLE) | - IPC_MODULE_ID(module_id); - dev_dbg(dev, "module enable header: %x\n", header); - - config.map.module_entries_count = 1; - config.map.module_entries[0].module_id = module->id; - config.map.module_entries[0].entry_point = module->entry; - - config.persistent_mem.offset = - sst_dsp_get_offset(dsp, - runtime->persistent_offset, SST_MEM_DRAM); - config.persistent_mem.size = module->persistent_size; - - config.scratch_mem.offset = - sst_dsp_get_offset(dsp, - dsp->scratch_offset, SST_MEM_DRAM); - config.scratch_mem.size = module->scratch_size; - dev_dbg(dev, "mod %d enable p:%d @ %x, s:%d @ %x, ep: %x", - config.map.module_entries[0].module_id, - config.persistent_mem.size, - config.persistent_mem.offset, - config.scratch_mem.size, config.scratch_mem.offset, - config.map.module_entries[0].entry_point); - - ret = ipc_tx_message_wait(hsw, header, - &config, sizeof(config), NULL, 0); - if (ret < 0) - dev_err(dev, "ipc: module enable failed - %d\n", ret); - else - module->state = SST_MODULE_STATE_ACTIVE; - - return ret; -} - -int sst_hsw_module_disable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id) -{ - int ret; - u32 header; - struct sst_module *module; - struct device *dev = hsw->dev; - struct sst_dsp *dsp = hsw->dsp; - - if (!sst_hsw_is_module_loaded(hsw, module_id)) { - dev_dbg(dev, "module %d not loaded\n", module_id); - return 0; - } - - if (!sst_hsw_is_module_active(hsw, module_id)) { - dev_info(dev, "module %d already disabled\n", module_id); - return 0; - } - - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - dev_err(dev, "module %d not valid\n", module_id); - return -ENXIO; - } - - header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | - IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) | - IPC_MODULE_ID(module_id); - - ret = ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); - if (ret < 0) - dev_err(dev, "module disable failed - %d\n", ret); - else - module->state = SST_MODULE_STATE_INITIALIZED; - - return ret; -} - -int sst_hsw_module_set_param(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, u32 parameter_id, - u32 param_size, char *param) -{ - int ret; - unsigned char *data = NULL; - u32 header = 0; - u32 payload_size = 0, transfer_parameter_size = 0; - dma_addr_t dma_addr = 0; - struct sst_hsw_transfer_parameter *parameter; - struct device *dev = hsw->dev; - - header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | - IPC_MODULE_OPERATION(IPC_MODULE_SET_PARAMETER) | - IPC_MODULE_ID(module_id); - dev_dbg(dev, "sst_hsw_module_set_param header=%x\n", header); - - payload_size = param_size + - sizeof(struct sst_hsw_transfer_parameter) - - sizeof(struct sst_hsw_transfer_list); - dev_dbg(dev, "parameter size : %d\n", param_size); - dev_dbg(dev, "payload size : %d\n", payload_size); - - if (payload_size <= SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE) { - /* short parameter, mailbox can contain data */ - dev_dbg(dev, "transfer parameter size : %d\n", - transfer_parameter_size); - - transfer_parameter_size = ALIGN(payload_size, 4); - dev_dbg(dev, "transfer parameter aligned size : %d\n", - transfer_parameter_size); - - parameter = kzalloc(transfer_parameter_size, GFP_KERNEL); - if (parameter == NULL) - return -ENOMEM; - - memcpy(parameter->data, param, param_size); - } else { - dev_warn(dev, "transfer parameter size too large!"); - return 0; - } - - parameter->parameter_id = parameter_id; - parameter->data_size = param_size; - - ret = ipc_tx_message_wait(hsw, header, - parameter, transfer_parameter_size , NULL, 0); - if (ret < 0) - dev_err(dev, "ipc: module set parameter failed - %d\n", ret); - - kfree(parameter); - - if (data) - dma_free_coherent(hsw->dsp->dma_dev, - param_size, (void *)data, dma_addr); - - return ret; -} - -static struct sst_dsp_device hsw_dev = { - .thread = hsw_irq_thread, - .ops = &haswell_ops, -}; - -int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_hsw_ipc_fw_version version; - struct sst_hsw *hsw; - int ret; - - dev_dbg(dev, "initialising Audio DSP IPC\n"); - - hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL); - if (hsw == NULL) - return -ENOMEM; - - hsw->dev = dev; - INIT_LIST_HEAD(&hsw->stream_list); - INIT_LIST_HEAD(&hsw->tx_list); - INIT_LIST_HEAD(&hsw->rx_list); - INIT_LIST_HEAD(&hsw->empty_list); - init_waitqueue_head(&hsw->boot_wait); - init_waitqueue_head(&hsw->wait_txq); - - ret = msg_empty_list_init(hsw); - if (ret < 0) - return -ENOMEM; - - /* start the IPC message thread */ - init_kthread_worker(&hsw->kworker); - hsw->tx_thread = kthread_run(kthread_worker_fn, - &hsw->kworker, "%s", - dev_name(hsw->dev)); - if (IS_ERR(hsw->tx_thread)) { - ret = PTR_ERR(hsw->tx_thread); - dev_err(hsw->dev, "error: failed to create message TX task\n"); - goto err_free_msg; - } - init_kthread_work(&hsw->kwork, ipc_tx_msgs); - - hsw_dev.thread_context = hsw; - - /* init SST shim */ - hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata); - if (hsw->dsp == NULL) { - ret = -ENODEV; - goto dsp_err; - } - - /* allocate DMA buffer for context storage */ - hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev, - SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL); - if (hsw->dx_context == NULL) { - ret = -ENOMEM; - goto dma_err; - } - - /* keep the DSP in reset state for base FW loading */ - sst_dsp_reset(hsw->dsp); - - /* load base module and other modules in base firmware image */ - ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base"); - if (ret < 0) - goto fw_err; - - /* try to load module waves */ - sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "intel/IntcPP01.bin"); - - /* allocate scratch mem regions */ - ret = sst_block_alloc_scratch(hsw->dsp); - if (ret < 0) - goto boot_err; - - /* init param buffer */ - sst_hsw_reset_param_buf(hsw); - - /* wait for DSP boot completion */ - sst_dsp_boot(hsw->dsp); - ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (ret == 0) { - ret = -EIO; - dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); - goto boot_err; - } - - /* init module state after boot */ - sst_hsw_init_module_state(hsw); - - /* get the FW version */ - sst_hsw_fw_get_version(hsw, &version); - - /* get the globalmixer */ - ret = sst_hsw_mixer_get_info(hsw); - if (ret < 0) { - dev_err(hsw->dev, "error: failed to get stream info\n"); - goto boot_err; - } - - pdata->dsp = hsw; - return 0; - -boot_err: - sst_dsp_reset(hsw->dsp); - sst_fw_free_all(hsw->dsp); -fw_err: - dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, - hsw->dx_context, hsw->dx_context_paddr); -dma_err: - sst_dsp_free(hsw->dsp); -dsp_err: - kthread_stop(hsw->tx_thread); -err_free_msg: - kfree(hsw->msg); - - return ret; -} -EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); - -void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_hsw *hsw = pdata->dsp; - - sst_dsp_reset(hsw->dsp); - sst_fw_free_all(hsw->dsp); - dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, - hsw->dx_context, hsw->dx_context_paddr); - sst_dsp_free(hsw->dsp); - kthread_stop(hsw->tx_thread); - kfree(hsw->msg); -} -EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h deleted file mode 100644 index 06d71ae..0000000 --- a/sound/soc/intel/sst-haswell-ipc.h +++ /dev/null @@ -1,534 +0,0 @@ -/* - * Intel SST Haswell/Broadwell IPC Support - * - * Copyright (C) 2013, Intel Corporation. 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 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 __SST_HASWELL_IPC_H -#define __SST_HASWELL_IPC_H - -#include -#include -#include -#include - -#define SST_HSW_NO_CHANNELS 4 -#define SST_HSW_MAX_DX_REGIONS 14 -#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024) -#define SST_HSW_CHANNELS_ALL 0xffffffff - -#define SST_HSW_FW_LOG_CONFIG_DWORDS 12 -#define SST_HSW_GLOBAL_LOG 15 - -/** - * Upfront defined maximum message size that is - * expected by the in/out communication pipes in FW. - */ -#define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400 -#define SST_HSW_MAX_INFO_SIZE 64 -#define SST_HSW_BUILD_HASH_LENGTH 40 -#define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500 -#define WAVES_PARAM_COUNT 128 -#define WAVES_PARAM_LINES 160 - -struct sst_hsw; -struct sst_hsw_stream; -struct sst_hsw_log_stream; -struct sst_pdata; -struct sst_module; -struct sst_module_runtime; -extern struct sst_ops haswell_ops; - -/* Stream Allocate Path ID */ -enum sst_hsw_stream_path_id { - SST_HSW_STREAM_PATH_SSP0_OUT = 0, - SST_HSW_STREAM_PATH_SSP0_IN = 1, - SST_HSW_STREAM_PATH_MAX_PATH_ID = 2, -}; - -/* Stream Allocate Stream Type */ -enum sst_hsw_stream_type { - SST_HSW_STREAM_TYPE_RENDER = 0, - SST_HSW_STREAM_TYPE_SYSTEM = 1, - SST_HSW_STREAM_TYPE_CAPTURE = 2, - SST_HSW_STREAM_TYPE_LOOPBACK = 3, - SST_HSW_STREAM_TYPE_MAX_STREAM_TYPE = 4, -}; - -/* Stream Allocate Stream Format */ -enum sst_hsw_stream_format { - SST_HSW_STREAM_FORMAT_PCM_FORMAT = 0, - SST_HSW_STREAM_FORMAT_MP3_FORMAT = 1, - SST_HSW_STREAM_FORMAT_AAC_FORMAT = 2, - SST_HSW_STREAM_FORMAT_MAX_FORMAT_ID = 3, -}; - -/* Device ID */ -enum sst_hsw_device_id { - SST_HSW_DEVICE_SSP_0 = 0, - SST_HSW_DEVICE_SSP_1 = 1, -}; - -/* Device Master Clock Frequency */ -enum sst_hsw_device_mclk { - SST_HSW_DEVICE_MCLK_OFF = 0, - SST_HSW_DEVICE_MCLK_FREQ_6_MHZ = 1, - SST_HSW_DEVICE_MCLK_FREQ_12_MHZ = 2, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ = 3, -}; - -/* Device Clock Master */ -enum sst_hsw_device_mode { - SST_HSW_DEVICE_CLOCK_SLAVE = 0, - SST_HSW_DEVICE_CLOCK_MASTER = 1, - SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2, -}; - -/* DX Power State */ -enum sst_hsw_dx_state { - SST_HSW_DX_STATE_D0 = 0, - SST_HSW_DX_STATE_D1 = 1, - SST_HSW_DX_STATE_D3 = 3, - SST_HSW_DX_STATE_MAX = 3, -}; - -/* Audio stream stage IDs */ -enum sst_hsw_fx_stage_id { - SST_HSW_STAGE_ID_WAVES = 0, - SST_HSW_STAGE_ID_DTS = 1, - SST_HSW_STAGE_ID_DOLBY = 2, - SST_HSW_STAGE_ID_BOOST = 3, - SST_HSW_STAGE_ID_MAX_FX_ID -}; - -/* DX State Type */ -enum sst_hsw_dx_type { - SST_HSW_DX_TYPE_FW_IMAGE = 0, - SST_HSW_DX_TYPE_MEMORY_DUMP = 1 -}; - -/* Volume Curve Type*/ -enum sst_hsw_volume_curve { - SST_HSW_VOLUME_CURVE_NONE = 0, - SST_HSW_VOLUME_CURVE_FADE = 1 -}; - -/* Sample ordering */ -enum sst_hsw_interleaving { - SST_HSW_INTERLEAVING_PER_CHANNEL = 0, - SST_HSW_INTERLEAVING_PER_SAMPLE = 1, -}; - -/* Channel indices */ -enum sst_hsw_channel_index { - SST_HSW_CHANNEL_LEFT = 0, - SST_HSW_CHANNEL_CENTER = 1, - SST_HSW_CHANNEL_RIGHT = 2, - SST_HSW_CHANNEL_LEFT_SURROUND = 3, - SST_HSW_CHANNEL_CENTER_SURROUND = 3, - SST_HSW_CHANNEL_RIGHT_SURROUND = 4, - SST_HSW_CHANNEL_LFE = 7, - SST_HSW_CHANNEL_INVALID = 0xF, -}; - -/* List of supported channel maps. */ -enum sst_hsw_channel_config { - SST_HSW_CHANNEL_CONFIG_MONO = 0, /* mono only. */ - SST_HSW_CHANNEL_CONFIG_STEREO = 1, /* L & R. */ - SST_HSW_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only. */ - SST_HSW_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only. */ - SST_HSW_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only. */ - SST_HSW_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only. */ - SST_HSW_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only. */ - SST_HSW_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs. */ - SST_HSW_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE. */ - SST_HSW_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two. */ - SST_HSW_CHANNEL_CONFIG_INVALID, -}; - -/* List of supported bit depths. */ -enum sst_hsw_bitdepth { - SST_HSW_DEPTH_8BIT = 8, - SST_HSW_DEPTH_16BIT = 16, - SST_HSW_DEPTH_24BIT = 24, /* Default. */ - SST_HSW_DEPTH_32BIT = 32, - SST_HSW_DEPTH_INVALID = 33, -}; - -enum sst_hsw_module_id { - SST_HSW_MODULE_BASE_FW = 0x0, - SST_HSW_MODULE_MP3 = 0x1, - SST_HSW_MODULE_AAC_5_1 = 0x2, - SST_HSW_MODULE_AAC_2_0 = 0x3, - SST_HSW_MODULE_SRC = 0x4, - SST_HSW_MODULE_WAVES = 0x5, - SST_HSW_MODULE_DOLBY = 0x6, - SST_HSW_MODULE_BOOST = 0x7, - SST_HSW_MODULE_LPAL = 0x8, - SST_HSW_MODULE_DTS = 0x9, - SST_HSW_MODULE_PCM_CAPTURE = 0xA, - SST_HSW_MODULE_PCM_SYSTEM = 0xB, - SST_HSW_MODULE_PCM_REFERENCE = 0xC, - SST_HSW_MODULE_PCM = 0xD, - SST_HSW_MODULE_BLUETOOTH_RENDER_MODULE = 0xE, - SST_HSW_MODULE_BLUETOOTH_CAPTURE_MODULE = 0xF, - SST_HSW_MAX_MODULE_ID, -}; - -enum sst_hsw_performance_action { - SST_HSW_PERF_START = 0, - SST_HSW_PERF_STOP = 1, -}; - -struct sst_hsw_transfer_info { - uint32_t destination; /* destination address */ - uint32_t reverse:1; /* if 1 data flows from destination */ - uint32_t size:31; /* transfer size in bytes.*/ - uint16_t first_page_offset; /* offset to data in the first page. */ - uint8_t packed_pages; /* page addresses. Each occupies 20 bits */ -} __attribute__((packed)); - -struct sst_hsw_transfer_list { - uint32_t transfers_count; - struct sst_hsw_transfer_info transfers; -} __attribute__((packed)); - -struct sst_hsw_transfer_parameter { - uint32_t parameter_id; - uint32_t data_size; - union { - uint8_t data[1]; - struct sst_hsw_transfer_list transfer_list; - }; -} __attribute__((packed)); - -/* SST firmware module info */ -struct sst_hsw_module_info { - u8 name[SST_HSW_MAX_INFO_SIZE]; - u8 version[SST_HSW_MAX_INFO_SIZE]; -} __attribute__((packed)); - -/* Module entry point */ -struct sst_hsw_module_entry { - enum sst_hsw_module_id module_id; - u32 entry_point; -} __attribute__((packed)); - -/* Module map - alignement matches DSP */ -struct sst_hsw_module_map { - u8 module_entries_count; - struct sst_hsw_module_entry module_entries[1]; -} __attribute__((packed)); - -struct sst_hsw_memory_info { - u32 offset; - u32 size; -} __attribute__((packed)); - -struct sst_hsw_fx_enable { - struct sst_hsw_module_map module_map; - struct sst_hsw_memory_info persistent_mem; -} __attribute__((packed)); - -struct sst_hsw_ipc_module_config { - struct sst_hsw_module_map map; - struct sst_hsw_memory_info persistent_mem; - struct sst_hsw_memory_info scratch_mem; -} __attribute__((packed)); - -struct sst_hsw_get_fx_param { - u32 parameter_id; - u32 param_size; -} __attribute__((packed)); - -struct sst_hsw_perf_action { - u32 action; -} __attribute__((packed)); - -struct sst_hsw_perf_data { - u64 timestamp; - u64 cycles; - u64 datatime; -} __attribute__((packed)); - -/* FW version */ -struct sst_hsw_ipc_fw_version { - u8 build; - u8 minor; - u8 major; - u8 type; - u8 fw_build_hash[SST_HSW_BUILD_HASH_LENGTH]; - u32 fw_log_providers_hash; -} __attribute__((packed)); - -/* Stream ring info */ -struct sst_hsw_ipc_stream_ring { - u32 ring_pt_address; - u32 num_pages; - u32 ring_size; - u32 ring_offset; - u32 ring_first_pfn; -} __attribute__((packed)); - -/* Debug Dump Log Enable Request */ -struct sst_hsw_ipc_debug_log_enable_req { - struct sst_hsw_ipc_stream_ring ringinfo; - u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; -} __attribute__((packed)); - -/* Debug Dump Log Reply */ -struct sst_hsw_ipc_debug_log_reply { - u32 log_buffer_begining; - u32 log_buffer_size; -} __attribute__((packed)); - -/* Stream glitch position */ -struct sst_hsw_ipc_stream_glitch_position { - u32 glitch_type; - u32 present_pos; - u32 write_pos; -} __attribute__((packed)); - -/* Stream get position */ -struct sst_hsw_ipc_stream_get_position { - u32 position; - u32 fw_cycle_count; -} __attribute__((packed)); - -/* Stream set position */ -struct sst_hsw_ipc_stream_set_position { - u32 position; - u32 end_of_buffer; -} __attribute__((packed)); - -/* Stream Free Request */ -struct sst_hsw_ipc_stream_free_req { - u8 stream_id; - u8 reserved[3]; -} __attribute__((packed)); - -/* Set Volume Request */ -struct sst_hsw_ipc_volume_req { - u32 channel; - u32 target_volume; - u64 curve_duration; - u32 curve_type; -} __attribute__((packed)); - -/* Device Configuration Request */ -struct sst_hsw_ipc_device_config_req { - u32 ssp_interface; - u32 clock_frequency; - u32 mode; - u16 clock_divider; - u8 channels; - u8 reserved; -} __attribute__((packed)); - -/* Audio Data formats */ -struct sst_hsw_audio_data_format_ipc { - u32 frequency; - u32 bitdepth; - u32 map; - u32 config; - u32 style; - u8 ch_num; - u8 valid_bit; - u8 reserved[2]; -} __attribute__((packed)); - -/* Stream Allocate Request */ -struct sst_hsw_ipc_stream_alloc_req { - u8 path_id; - u8 stream_type; - u8 format_id; - u8 reserved; - struct sst_hsw_audio_data_format_ipc format; - struct sst_hsw_ipc_stream_ring ringinfo; - struct sst_hsw_module_map map; - struct sst_hsw_memory_info persistent_mem; - struct sst_hsw_memory_info scratch_mem; - u32 number_of_notifications; -} __attribute__((packed)); - -/* Stream Allocate Reply */ -struct sst_hsw_ipc_stream_alloc_reply { - u32 stream_hw_id; - u32 mixer_hw_id; // returns rate ???? - u32 read_position_register_address; - u32 presentation_position_register_address; - u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; - u32 volume_register_address[SST_HSW_NO_CHANNELS]; -} __attribute__((packed)); - -/* Get Mixer Stream Info */ -struct sst_hsw_ipc_stream_info_reply { - u32 mixer_hw_id; - u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; - u32 volume_register_address[SST_HSW_NO_CHANNELS]; -} __attribute__((packed)); - -/* DX State Request */ -struct sst_hsw_ipc_dx_req { - u8 state; - u8 reserved[3]; -} __attribute__((packed)); - -/* DX State Reply Memory Info Item */ -struct sst_hsw_ipc_dx_memory_item { - u32 offset; - u32 size; - u32 source; -} __attribute__((packed)); - -/* DX State Reply */ -struct sst_hsw_ipc_dx_reply { - u32 entries_no; - struct sst_hsw_ipc_dx_memory_item mem_info[SST_HSW_MAX_DX_REGIONS]; -} __attribute__((packed)); - -struct sst_hsw_ipc_fw_version; - -/* SST Init & Free */ -struct sst_hsw *sst_hsw_new(struct device *dev, const u8 *fw, size_t fw_length, - u32 fw_offset); -void sst_hsw_free(struct sst_hsw *hsw); -int sst_hsw_fw_get_version(struct sst_hsw *hsw, - struct sst_hsw_ipc_fw_version *version); -u32 create_channel_map(enum sst_hsw_channel_config config); - -/* Stream Mixer Controls - */ -int sst_hsw_stream_set_volume(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume); -int sst_hsw_stream_get_volume(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 *volume); - -/* Global Mixer Controls - */ -int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 volume); -int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 *volume); - -/* Stream API */ -struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, - u32 (*get_write_position)(struct sst_hsw_stream *stream, void *data), - void *data); - -int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream); - -/* Stream Configuration */ -int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_stream_path_id path_id, - enum sst_hsw_stream_type stream_type, - enum sst_hsw_stream_format format_id); - -int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 ring_pt_address, u32 num_pages, - u32 ring_size, u32 ring_offset, u32 ring_first_pfn); - -int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream); - -int sst_hsw_stream_set_valid(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 bits); -int sst_hsw_stream_set_rate(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int rate); -int sst_hsw_stream_set_bits(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_bitdepth bits); -int sst_hsw_stream_set_channels(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, int channels); -int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 map, - enum sst_hsw_channel_config config); -int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_interleaving style); -int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, struct sst_module_runtime *runtime); -int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 offset, u32 size); -int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 offset, u32 size); -snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); -void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, snd_pcm_uframes_t val); -bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); -void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, bool val); -int sst_hsw_mixer_get_info(struct sst_hsw *hsw); - -/* Stream ALSA trigger operations */ -int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait); -int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait); -int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream); - -/* Stream pointer positions */ -int sst_hsw_stream_get_read_pos(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 *position); -int sst_hsw_stream_get_write_pos(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 *position); -u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); -u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); - -/* HW port config */ -int sst_hsw_device_set_config(struct sst_hsw *hsw, - enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, - enum sst_hsw_device_mode mode, u32 clock_divider); - -/* DX Config */ -int sst_hsw_dx_set_state(struct sst_hsw *hsw, - enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx); - -/* init */ -int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); -void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); -struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); - -/* fw module function */ -void sst_hsw_init_module_state(struct sst_hsw *hsw); -bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); -bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); -void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); -void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id); -bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); -void sst_hsw_reset_param_buf(struct sst_hsw *hsw); -int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf); -int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf); -int sst_hsw_launch_param_buf(struct sst_hsw *hsw); - -int sst_hsw_module_load(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, char *name); -int sst_hsw_module_enable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id); -int sst_hsw_module_disable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id); -int sst_hsw_module_set_param(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, u32 parameter_id, - u32 param_size, char *param); - -/* runtime module management */ -struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, - int mod_id, int offset); -void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime); - -/* PM */ -int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw); -int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw); -int sst_hsw_dsp_load(struct sst_hsw *hsw); -int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw); - -#endif diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c deleted file mode 100644 index 31ffc0f..0000000 --- a/sound/soc/intel/sst-haswell-pcm.c +++ /dev/null @@ -1,1405 +0,0 @@ -/* - * Intel SST Haswell/Broadwell PCM Support - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sst-haswell-ipc.h" -#include "sst-dsp-priv.h" -#include "sst-dsp.h" - -#define HSW_PCM_COUNT 6 -#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */ - -#define SST_OLD_POSITION(d, r, o) ((d) + \ - frames_to_bytes(r, o)) -#define SST_SAMPLES(r, x) (bytes_to_samples(r, \ - frames_to_bytes(r, (x)))) - -/* simple volume table */ -static const u32 volume_map[] = { - HSW_VOLUME_MAX >> 30, - HSW_VOLUME_MAX >> 29, - HSW_VOLUME_MAX >> 28, - HSW_VOLUME_MAX >> 27, - HSW_VOLUME_MAX >> 26, - HSW_VOLUME_MAX >> 25, - HSW_VOLUME_MAX >> 24, - HSW_VOLUME_MAX >> 23, - HSW_VOLUME_MAX >> 22, - HSW_VOLUME_MAX >> 21, - HSW_VOLUME_MAX >> 20, - HSW_VOLUME_MAX >> 19, - HSW_VOLUME_MAX >> 18, - HSW_VOLUME_MAX >> 17, - HSW_VOLUME_MAX >> 16, - HSW_VOLUME_MAX >> 15, - HSW_VOLUME_MAX >> 14, - HSW_VOLUME_MAX >> 13, - HSW_VOLUME_MAX >> 12, - HSW_VOLUME_MAX >> 11, - HSW_VOLUME_MAX >> 10, - HSW_VOLUME_MAX >> 9, - HSW_VOLUME_MAX >> 8, - HSW_VOLUME_MAX >> 7, - HSW_VOLUME_MAX >> 6, - HSW_VOLUME_MAX >> 5, - HSW_VOLUME_MAX >> 4, - HSW_VOLUME_MAX >> 3, - HSW_VOLUME_MAX >> 2, - HSW_VOLUME_MAX >> 1, - HSW_VOLUME_MAX >> 0, -}; - -#define HSW_PCM_PERIODS_MAX 64 -#define HSW_PCM_PERIODS_MIN 2 - -#define HSW_PCM_DAI_ID_SYSTEM 0 -#define HSW_PCM_DAI_ID_OFFLOAD0 1 -#define HSW_PCM_DAI_ID_OFFLOAD1 2 -#define HSW_PCM_DAI_ID_LOOPBACK 3 - - -static const struct snd_pcm_hardware hsw_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | - SNDRV_PCM_INFO_DRAIN_TRIGGER, - .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | - SNDRV_PCM_FMTBIT_S32_LE, - .period_bytes_min = PAGE_SIZE, - .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, - .periods_min = HSW_PCM_PERIODS_MIN, - .periods_max = HSW_PCM_PERIODS_MAX, - .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, -}; - -struct hsw_pcm_module_map { - int dai_id; - int stream; - enum sst_hsw_module_id mod_id; -}; - -/* private data for each PCM DSP stream */ -struct hsw_pcm_data { - int dai_id; - struct sst_hsw_stream *stream; - struct sst_module_runtime *runtime; - struct sst_module_runtime_context context; - struct snd_pcm *hsw_pcm; - u32 volume[2]; - struct snd_pcm_substream *substream; - struct snd_compr_stream *cstream; - unsigned int wpos; - struct mutex mutex; - bool allocated; - int persistent_offset; -}; - -enum hsw_pm_state { - HSW_PM_STATE_D0 = 0, - HSW_PM_STATE_RTD3 = 1, - HSW_PM_STATE_D3 = 2, -}; - -/* private data for the driver */ -struct hsw_priv_data { - /* runtime DSP */ - struct sst_hsw *hsw; - struct device *dev; - enum hsw_pm_state pm_state; - struct snd_soc_card *soc_card; - struct sst_module_runtime *runtime_waves; /* sound effect module */ - - /* page tables */ - struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; - - /* DAI data */ - struct hsw_pcm_data pcm[HSW_PCM_COUNT][2]; -}; - - -/* static mappings between PCMs and modules - may be dynamic in future */ -static struct hsw_pcm_module_map mod_map[] = { - {HSW_PCM_DAI_ID_SYSTEM, 0, SST_HSW_MODULE_PCM_SYSTEM}, - {HSW_PCM_DAI_ID_OFFLOAD0, 0, SST_HSW_MODULE_PCM}, - {HSW_PCM_DAI_ID_OFFLOAD1, 0, SST_HSW_MODULE_PCM}, - {HSW_PCM_DAI_ID_LOOPBACK, 1, SST_HSW_MODULE_PCM_REFERENCE}, - {HSW_PCM_DAI_ID_SYSTEM, 1, SST_HSW_MODULE_PCM_CAPTURE}, -}; - -static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data); - -static inline u32 hsw_mixer_to_ipc(unsigned int value) -{ - if (value >= ARRAY_SIZE(volume_map)) - return volume_map[0]; - else - return volume_map[value]; -} - -static inline unsigned int hsw_ipc_to_mixer(u32 value) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(volume_map); i++) { - if (volume_map[i] >= value) - return i; - } - - return i - 1; -} - -static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - u32 volume; - int dai, stream; - - dai = mod_map[mc->reg].dai_id; - stream = mod_map[mc->reg].stream; - pcm_data = &pdata->pcm[dai][stream]; - - mutex_lock(&pcm_data->mutex); - pm_runtime_get_sync(pdata->dev); - - if (!pcm_data->stream) { - pcm_data->volume[0] = - hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - pcm_data->volume[1] = - hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return 0; - } - - if (ucontrol->value.integer.value[0] == - ucontrol->value.integer.value[1]) { - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - /* apply volume value to all channels */ - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume); - } else { - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); - } - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return 0; -} - -static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - u32 volume; - int dai, stream; - - dai = mod_map[mc->reg].dai_id; - stream = mod_map[mc->reg].stream; - pcm_data = &pdata->pcm[dai][stream]; - - mutex_lock(&pcm_data->mutex); - pm_runtime_get_sync(pdata->dev); - - if (!pcm_data->stream) { - ucontrol->value.integer.value[0] = - hsw_ipc_to_mixer(pcm_data->volume[0]); - ucontrol->value.integer.value[1] = - hsw_ipc_to_mixer(pcm_data->volume[1]); - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return 0; - } - - sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume); - ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); - sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); - ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - - return 0; -} - -static int hsw_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); - struct sst_hsw *hsw = pdata->hsw; - u32 volume; - - pm_runtime_get_sync(pdata->dev); - - if (ucontrol->value.integer.value[0] == - ucontrol->value.integer.value[1]) { - - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume); - - } else { - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_mixer_set_volume(hsw, 0, 0, volume); - - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); - sst_hsw_mixer_set_volume(hsw, 0, 1, volume); - } - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - return 0; -} - -static int hsw_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); - struct sst_hsw *hsw = pdata->hsw; - unsigned int volume = 0; - - pm_runtime_get_sync(pdata->dev); - sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); - ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); - - sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); - ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - return 0; -} - -static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); - struct sst_hsw *hsw = pdata->hsw; - enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; - - ucontrol->value.integer.value[0] = - (sst_hsw_is_module_active(hsw, id) || - sst_hsw_is_module_enabled_rtd3(hsw, id)); - return 0; -} - -static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); - struct sst_hsw *hsw = pdata->hsw; - int ret = 0; - enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; - bool switch_on = (bool)ucontrol->value.integer.value[0]; - - /* if module is in RAM on the DSP, apply user settings to module through - * ipc. If module is not in RAM on the DSP, store user setting for - * track */ - if (sst_hsw_is_module_loaded(hsw, id)) { - if (switch_on == sst_hsw_is_module_active(hsw, id)) - return 0; - - if (switch_on) - ret = sst_hsw_module_enable(hsw, id, 0); - else - ret = sst_hsw_module_disable(hsw, id, 0); - } else { - if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id)) - return 0; - - if (switch_on) - sst_hsw_set_module_enabled_rtd3(hsw, id); - else - sst_hsw_set_module_disabled_rtd3(hsw, id); - } - - return ret; -} - -static int hsw_waves_param_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); - struct sst_hsw *hsw = pdata->hsw; - - /* return a matching line from param buffer */ - return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data); -} - -static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); - struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); - struct sst_hsw *hsw = pdata->hsw; - int ret; - enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; - int param_id = ucontrol->value.bytes.data[0]; - int param_size = WAVES_PARAM_COUNT; - - /* clear param buffer and reset buffer index */ - if (param_id == 0xFF) { - sst_hsw_reset_param_buf(hsw); - return 0; - } - - /* store params into buffer */ - ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data); - if (ret < 0) - return ret; - - if (sst_hsw_is_module_active(hsw, id)) - ret = sst_hsw_module_set_param(hsw, id, 0, param_id, - param_size, ucontrol->value.bytes.data); - return ret; -} - -/* TLV used by both global and stream volumes */ -static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); - -/* System Pin has no volume control */ -static const struct snd_kcontrol_new hsw_volume_controls[] = { - /* Global DSP volume */ - SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_volume_get, hsw_volume_put, hsw_vol_tlv), - /* Offload 0 volume */ - SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* Offload 1 volume */ - SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* Mic Capture volume */ - SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* enable/disable module waves */ - SOC_SINGLE_BOOL_EXT("Waves Switch", 0, - hsw_waves_switch_get, hsw_waves_switch_put), - /* set parameters to module waves */ - SND_SOC_BYTES_EXT("Waves Set Param", WAVES_PARAM_COUNT, - hsw_waves_param_get, hsw_waves_param_put), -}; - -/* Create DMA buffer page table for DSP */ -static int create_adsp_page_table(struct snd_pcm_substream *substream, - struct hsw_priv_data *pdata, struct snd_soc_pcm_runtime *rtd, - unsigned char *dma_area, size_t size, int pcm) -{ - struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); - int i, pages, stream = substream->stream; - - pages = snd_sgbuf_aligned_pages(size); - - dev_dbg(rtd->dev, "generating page table for %p size 0x%zu pages %d\n", - dma_area, size, pages); - - for (i = 0; i < pages; i++) { - u32 idx = (((i << 2) + i)) >> 1; - u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; - u32 *pg_table; - - dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); - - pg_table = (u32 *)(pdata->dmab[pcm][stream].area + idx); - - if (i & 1) - *pg_table |= (pfn << 4); - else - *pg_table |= pfn; - } - - return 0; -} - -/* this may get called several times by oss emulation */ -static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - struct sst_module *module_data; - struct sst_dsp *dsp; - struct snd_dma_buffer *dmab; - enum sst_hsw_stream_type stream_type; - enum sst_hsw_stream_path_id path_id; - u32 rate, bits, map, pages, module_id; - u8 channels; - int ret, dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - - /* check if we are being called a subsequent time */ - if (pcm_data->allocated) { - ret = sst_hsw_stream_reset(hsw, pcm_data->stream); - if (ret < 0) - dev_dbg(rtd->dev, "error: reset stream failed %d\n", - ret); - - ret = sst_hsw_stream_free(hsw, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "error: free stream failed %d\n", - ret); - return ret; - } - pcm_data->allocated = false; - - pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, - hsw_notify_pointer, pcm_data); - if (pcm_data->stream == NULL) { - dev_err(rtd->dev, "error: failed to create stream\n"); - return -EINVAL; - } - } - - /* stream direction */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - path_id = SST_HSW_STREAM_PATH_SSP0_OUT; - else - path_id = SST_HSW_STREAM_PATH_SSP0_IN; - - /* DSP stream type depends on DAI ID */ - switch (rtd->cpu_dai->id) { - case 0: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - stream_type = SST_HSW_STREAM_TYPE_SYSTEM; - module_id = SST_HSW_MODULE_PCM_SYSTEM; - } - else { - stream_type = SST_HSW_STREAM_TYPE_CAPTURE; - module_id = SST_HSW_MODULE_PCM_CAPTURE; - } - break; - case 1: - case 2: - stream_type = SST_HSW_STREAM_TYPE_RENDER; - module_id = SST_HSW_MODULE_PCM; - break; - case 3: - /* path ID needs to be OUT for loopback */ - stream_type = SST_HSW_STREAM_TYPE_LOOPBACK; - path_id = SST_HSW_STREAM_PATH_SSP0_OUT; - module_id = SST_HSW_MODULE_PCM_REFERENCE; - break; - default: - dev_err(rtd->dev, "error: invalid DAI ID %d\n", - rtd->cpu_dai->id); - return -EINVAL; - }; - - ret = sst_hsw_stream_format(hsw, pcm_data->stream, - path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set format %d\n", ret); - return ret; - } - - rate = params_rate(params); - ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate); - if (ret < 0) { - dev_err(rtd->dev, "error: could not set rate %d\n", rate); - return ret; - } - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - bits = SST_HSW_DEPTH_16BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); - break; - case SNDRV_PCM_FORMAT_S24_LE: - bits = SST_HSW_DEPTH_32BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24); - break; - case SNDRV_PCM_FORMAT_S8: - bits = SST_HSW_DEPTH_8BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8); - break; - case SNDRV_PCM_FORMAT_S32_LE: - bits = SST_HSW_DEPTH_32BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); - break; - default: - dev_err(rtd->dev, "error: invalid format %d\n", - params_format(params)); - return -EINVAL; - } - - ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits); - if (ret < 0) { - dev_err(rtd->dev, "error: could not set bits %d\n", bits); - return ret; - } - - channels = params_channels(params); - map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); - sst_hsw_stream_set_map_config(hsw, pcm_data->stream, - map, SST_HSW_CHANNEL_CONFIG_STEREO); - - ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels); - if (ret < 0) { - dev_err(rtd->dev, "error: could not set channels %d\n", - channels); - return ret; - } - - ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - if (ret < 0) { - dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n", - params_buffer_bytes(params), ret); - return ret; - } - - dmab = snd_pcm_get_dma_buf(substream); - - ret = create_adsp_page_table(substream, pdata, rtd, runtime->dma_area, - runtime->dma_bytes, rtd->cpu_dai->id); - if (ret < 0) - return ret; - - sst_hsw_stream_set_style(hsw, pcm_data->stream, - SST_HSW_INTERLEAVING_PER_CHANNEL); - - if (runtime->dma_bytes % PAGE_SIZE) - pages = (runtime->dma_bytes / PAGE_SIZE) + 1; - else - pages = runtime->dma_bytes / PAGE_SIZE; - - ret = sst_hsw_stream_buffer(hsw, pcm_data->stream, - pdata->dmab[rtd->cpu_dai->id][substream->stream].addr, - pages, runtime->dma_bytes, 0, - snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret); - return ret; - } - - dsp = sst_hsw_get_dsp(hsw); - - module_data = sst_module_get_from_id(dsp, module_id); - if (module_data == NULL) { - dev_err(rtd->dev, "error: failed to get module config\n"); - return -EINVAL; - } - - sst_hsw_stream_set_module_info(hsw, pcm_data->stream, - pcm_data->runtime); - - ret = sst_hsw_stream_commit(hsw, pcm_data->stream); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); - return ret; - } - - if (!pcm_data->allocated) { - /* Set previous saved volume */ - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, - 0, pcm_data->volume[0]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, - 1, pcm_data->volume[1]); - pcm_data->allocated = true; - } - - ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); - if (ret < 0) - dev_err(rtd->dev, "error: failed to pause %d\n", ret); - - return 0; -} - -static int hsw_pcm_hw_free(struct snd_pcm_substream *substream) -{ - snd_pcm_lib_free_pages(substream); - return 0; -} - -static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw_stream *sst_stream; - struct sst_hsw *hsw = pdata->hsw; - struct snd_pcm_runtime *runtime = substream->runtime; - snd_pcm_uframes_t pos; - int dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - sst_stream = pcm_data->stream; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - sst_hsw_stream_set_silence_start(hsw, sst_stream, false); - sst_hsw_stream_resume(hsw, pcm_data->stream, 0); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - sst_hsw_stream_set_silence_start(hsw, sst_stream, false); - sst_hsw_stream_pause(hsw, pcm_data->stream, 0); - break; - case SNDRV_PCM_TRIGGER_DRAIN: - pos = runtime->control->appl_ptr % runtime->buffer_size; - sst_hsw_stream_set_old_position(hsw, pcm_data->stream, pos); - sst_hsw_stream_set_silence_start(hsw, sst_stream, true); - break; - default: - break; - } - - return 0; -} - -static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) -{ - struct hsw_pcm_data *pcm_data = data; - struct snd_pcm_substream *substream = pcm_data->substream; - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_hsw *hsw = pdata->hsw; - u32 pos; - snd_pcm_uframes_t position = bytes_to_frames(runtime, - sst_hsw_get_dsp_position(hsw, pcm_data->stream)); - unsigned char *dma_area = runtime->dma_area; - snd_pcm_uframes_t dma_frames = - bytes_to_frames(runtime, runtime->dma_bytes); - snd_pcm_uframes_t old_position; - ssize_t samples; - - pos = frames_to_bytes(runtime, - (runtime->control->appl_ptr % runtime->buffer_size)); - - dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); - - /* SST fw don't know where to stop dma - * So, SST driver need to clean the data which has been consumed - */ - if (dma_area == NULL || dma_frames <= 0 - || (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - || !sst_hsw_stream_get_silence_start(hsw, stream)) { - snd_pcm_period_elapsed(substream); - return pos; - } - - old_position = sst_hsw_stream_get_old_position(hsw, stream); - if (position > old_position) { - if (position < dma_frames) { - samples = SST_SAMPLES(runtime, position - old_position); - snd_pcm_format_set_silence(runtime->format, - SST_OLD_POSITION(dma_area, - runtime, old_position), - samples); - } else - dev_err(rtd->dev, "PCM: position is wrong\n"); - } else { - if (old_position < dma_frames) { - samples = SST_SAMPLES(runtime, - dma_frames - old_position); - snd_pcm_format_set_silence(runtime->format, - SST_OLD_POSITION(dma_area, - runtime, old_position), - samples); - } else - dev_err(rtd->dev, "PCM: dma_bytes is wrong\n"); - if (position < dma_frames) { - samples = SST_SAMPLES(runtime, position); - snd_pcm_format_set_silence(runtime->format, - dma_area, samples); - } else - dev_err(rtd->dev, "PCM: position is wrong\n"); - } - sst_hsw_stream_set_old_position(hsw, stream, position); - - /* let alsa know we have play a period */ - snd_pcm_period_elapsed(substream); - return pos; -} - -static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - snd_pcm_uframes_t offset; - uint64_t ppos; - u32 position; - int dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - position = sst_hsw_get_dsp_position(hsw, pcm_data->stream); - - offset = bytes_to_frames(runtime, position); - ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); - - dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", - position, ppos); - return offset; -} - -static int hsw_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - int dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - - mutex_lock(&pcm_data->mutex); - pm_runtime_get_sync(pdata->dev); - - snd_soc_pcm_set_drvdata(rtd, pcm_data); - pcm_data->substream = substream; - - snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); - - pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, - hsw_notify_pointer, pcm_data); - if (pcm_data->stream == NULL) { - dev_err(rtd->dev, "error: failed to create stream\n"); - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return -EINVAL; - } - - mutex_unlock(&pcm_data->mutex); - return 0; -} - -static int hsw_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - int ret, dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - - mutex_lock(&pcm_data->mutex); - ret = sst_hsw_stream_reset(hsw, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret); - goto out; - } - - ret = sst_hsw_stream_free(hsw, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "error: free stream failed %d\n", ret); - goto out; - } - pcm_data->allocated = 0; - pcm_data->stream = NULL; - -out: - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return ret; -} - -static struct snd_pcm_ops hsw_pcm_ops = { - .open = hsw_pcm_open, - .close = hsw_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = hsw_pcm_hw_params, - .hw_free = hsw_pcm_hw_free, - .trigger = hsw_pcm_trigger, - .pointer = hsw_pcm_pointer, - .page = snd_pcm_sgbuf_ops_page, -}; - -static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) -{ - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i; - - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - /* create new runtime module, use same offset if recreated */ - pcm_data->runtime = sst_hsw_runtime_module_create(hsw, - mod_map[i].mod_id, pcm_data->persistent_offset); - if (pcm_data->runtime == NULL) - goto err; - pcm_data->persistent_offset = - pcm_data->runtime->persistent_offset; - } - - /* create runtime blocks for module waves */ - if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { - pdata->runtime_waves = sst_hsw_runtime_module_create(hsw, - SST_HSW_MODULE_WAVES, 0); - if (pdata->runtime_waves == NULL) - goto err; - } - - return 0; - -err: - for (--i; i >= 0; i--) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - sst_hsw_runtime_module_free(pcm_data->runtime); - } - - return -ENODEV; -} - -static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) -{ - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i; - - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - sst_hsw_runtime_module_free(pcm_data->runtime); - } - if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { - sst_hsw_runtime_module_free(pdata->runtime_waves); - } -} - -static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_pcm *pcm = rtd->pcm; - struct snd_soc_platform *platform = rtd->platform; - struct sst_pdata *pdata = dev_get_platdata(platform->dev); - struct hsw_priv_data *priv_data = dev_get_drvdata(platform->dev); - struct device *dev = pdata->dma_dev; - int ret = 0; - - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || - pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { - ret = snd_pcm_lib_preallocate_pages_for_all(pcm, - SNDRV_DMA_TYPE_DEV_SG, - dev, - hsw_pcm_hardware.buffer_bytes_max, - hsw_pcm_hardware.buffer_bytes_max); - if (ret) { - dev_err(rtd->dev, "dma buffer allocation failed %d\n", - ret); - return ret; - } - } - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) - priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_PLAYBACK].hsw_pcm = pcm; - if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) - priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_CAPTURE].hsw_pcm = pcm; - - return ret; -} - -#define HSW_FORMATS \ - (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) - -static struct snd_soc_dai_driver hsw_dais[] = { - { - .name = "System Pin", - .id = HSW_PCM_DAI_ID_SYSTEM, - .playback = { - .stream_name = "System Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "Analog Capture", - .channels_min = 2, - .channels_max = 4, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - }, - { - /* PCM */ - .name = "Offload0 Pin", - .id = HSW_PCM_DAI_ID_OFFLOAD0, - .playback = { - .stream_name = "Offload0 Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = HSW_FORMATS, - }, - }, - { - /* PCM */ - .name = "Offload1 Pin", - .id = HSW_PCM_DAI_ID_OFFLOAD1, - .playback = { - .stream_name = "Offload1 Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = HSW_FORMATS, - }, - }, - { - .name = "Loopback Pin", - .id = HSW_PCM_DAI_ID_LOOPBACK, - .capture = { - .stream_name = "Loopback Capture", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - }, -}; - -static const struct snd_soc_dapm_widget widgets[] = { - - /* Backend DAIs */ - SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0), - - /* Global Playback Mixer */ - SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), -}; - -static const struct snd_soc_dapm_route graph[] = { - - /* Playback Mixer */ - {"Playback VMixer", NULL, "System Playback"}, - {"Playback VMixer", NULL, "Offload0 Playback"}, - {"Playback VMixer", NULL, "Offload1 Playback"}, - - {"SSP0 CODEC OUT", NULL, "Playback VMixer"}, - - {"Analog Capture", NULL, "SSP0 CODEC IN"}, -}; - -static int hsw_pcm_probe(struct snd_soc_platform *platform) -{ - struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform); - struct sst_pdata *pdata = dev_get_platdata(platform->dev); - struct device *dma_dev, *dev; - int i, ret = 0; - - if (!pdata) - return -ENODEV; - - dev = platform->dev; - dma_dev = pdata->dma_dev; - - priv_data->hsw = pdata->dsp; - priv_data->dev = platform->dev; - priv_data->pm_state = HSW_PM_STATE_D0; - priv_data->soc_card = platform->component.card; - - /* allocate DSP buffer page tables */ - for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { - - /* playback */ - if (hsw_dais[i].playback.channels_min) { - mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_PLAYBACK].mutex); - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, - PAGE_SIZE, &priv_data->dmab[i][0]); - if (ret < 0) - goto err; - } - - /* capture */ - if (hsw_dais[i].capture.channels_min) { - mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_CAPTURE].mutex); - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, - PAGE_SIZE, &priv_data->dmab[i][1]); - if (ret < 0) - goto err; - } - } - - /* allocate runtime modules */ - ret = hsw_pcm_create_modules(priv_data); - if (ret < 0) - goto err; - - /* enable runtime PM with auto suspend */ - pm_runtime_set_autosuspend_delay(platform->dev, - SST_RUNTIME_SUSPEND_DELAY); - pm_runtime_use_autosuspend(platform->dev); - pm_runtime_enable(platform->dev); - pm_runtime_idle(platform->dev); - - return 0; - -err: - for (;i >= 0; i--) { - if (hsw_dais[i].playback.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][0]); - if (hsw_dais[i].capture.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][1]); - } - return ret; -} - -static int hsw_pcm_remove(struct snd_soc_platform *platform) -{ - struct hsw_priv_data *priv_data = - snd_soc_platform_get_drvdata(platform); - int i; - - pm_runtime_disable(platform->dev); - hsw_pcm_free_modules(priv_data); - - for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { - if (hsw_dais[i].playback.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][0]); - if (hsw_dais[i].capture.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][1]); - } - - return 0; -} - -static struct snd_soc_platform_driver hsw_soc_platform = { - .probe = hsw_pcm_probe, - .remove = hsw_pcm_remove, - .ops = &hsw_pcm_ops, - .pcm_new = hsw_pcm_new, -}; - -static const struct snd_soc_component_driver hsw_dai_component = { - .name = "haswell-dai", - .controls = hsw_volume_controls, - .num_controls = ARRAY_SIZE(hsw_volume_controls), - .dapm_widgets = widgets, - .num_dapm_widgets = ARRAY_SIZE(widgets), - .dapm_routes = graph, - .num_dapm_routes = ARRAY_SIZE(graph), -}; - -static int hsw_pcm_dev_probe(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - struct hsw_priv_data *priv_data; - int ret; - - if (!sst_pdata) - return -EINVAL; - - priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL); - if (!priv_data) - return -ENOMEM; - - ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); - if (ret < 0) - return -ENODEV; - - priv_data->hsw = sst_pdata->dsp; - platform_set_drvdata(pdev, priv_data); - - ret = snd_soc_register_platform(&pdev->dev, &hsw_soc_platform); - if (ret < 0) - goto err_plat; - - ret = snd_soc_register_component(&pdev->dev, &hsw_dai_component, - hsw_dais, ARRAY_SIZE(hsw_dais)); - if (ret < 0) - goto err_comp; - - return 0; - -err_comp: - snd_soc_unregister_platform(&pdev->dev); -err_plat: - sst_hsw_dsp_free(&pdev->dev, sst_pdata); - return 0; -} - -static int hsw_pcm_dev_remove(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - - snd_soc_unregister_platform(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - sst_hsw_dsp_free(&pdev->dev, sst_pdata); - - return 0; -} - -#ifdef CONFIG_PM - -static int hsw_pcm_runtime_idle(struct device *dev) -{ - return 0; -} - -static int hsw_pcm_runtime_suspend(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - int ret; - - if (pdata->pm_state >= HSW_PM_STATE_RTD3) - return 0; - - /* fw modules will be unloaded on RTD3, set flag to track */ - if (sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { - ret = sst_hsw_module_disable(hsw, SST_HSW_MODULE_WAVES, 0); - if (ret < 0) - return ret; - sst_hsw_set_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES); - } - sst_hsw_dsp_runtime_suspend(hsw); - sst_hsw_dsp_runtime_sleep(hsw); - pdata->pm_state = HSW_PM_STATE_RTD3; - - return 0; -} - -static int hsw_pcm_runtime_resume(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - int ret; - - if (pdata->pm_state != HSW_PM_STATE_RTD3) - return 0; - - ret = sst_hsw_dsp_load(hsw); - if (ret < 0) { - dev_err(dev, "failed to reload %d\n", ret); - return ret; - } - - ret = hsw_pcm_create_modules(pdata); - if (ret < 0) { - dev_err(dev, "failed to create modules %d\n", ret); - return ret; - } - - ret = sst_hsw_dsp_runtime_resume(hsw); - if (ret < 0) - return ret; - else if (ret == 1) /* no action required */ - return 0; - - /* check flag when resume */ - if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) { - ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); - if (ret < 0) - return ret; - /* put parameters from buffer to dsp */ - ret = sst_hsw_launch_param_buf(hsw); - if (ret < 0) - return ret; - /* unset flag */ - sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); - } - - pdata->pm_state = HSW_PM_STATE_D0; - return ret; -} - -#else -#define hsw_pcm_runtime_idle NULL -#define hsw_pcm_runtime_suspend NULL -#define hsw_pcm_runtime_resume NULL -#endif - -#ifdef CONFIG_PM - -static void hsw_pcm_complete(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i, err; - - if (pdata->pm_state != HSW_PM_STATE_D3) - return; - - err = sst_hsw_dsp_load(hsw); - if (err < 0) { - dev_err(dev, "failed to reload %d\n", err); - return; - } - - err = hsw_pcm_create_modules(pdata); - if (err < 0) { - dev_err(dev, "failed to create modules %d\n", err); - return; - } - - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - if (!pcm_data->substream) - continue; - - err = sst_module_runtime_restore(pcm_data->runtime, - &pcm_data->context); - if (err < 0) - dev_err(dev, "failed to restore context for PCM %d\n", i); - } - - snd_soc_resume(pdata->soc_card->dev); - - err = sst_hsw_dsp_runtime_resume(hsw); - if (err < 0) - return; - else if (err == 1) /* no action required */ - return; - - pdata->pm_state = HSW_PM_STATE_D0; - return; -} - -static int hsw_pcm_prepare(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i, err; - - if (pdata->pm_state == HSW_PM_STATE_D3) - return 0; - else if (pdata->pm_state == HSW_PM_STATE_D0) { - /* suspend all active streams */ - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - if (!pcm_data->substream) - continue; - dev_dbg(dev, "suspending pcm %d\n", i); - snd_pcm_suspend_all(pcm_data->hsw_pcm); - - /* We need to wait until the DSP FW stops the streams */ - msleep(2); - } - - /* preserve persistent memory */ - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - if (!pcm_data->substream) - continue; - - dev_dbg(dev, "saving context pcm %d\n", i); - err = sst_module_runtime_save(pcm_data->runtime, - &pcm_data->context); - if (err < 0) - dev_err(dev, "failed to save context for PCM %d\n", i); - } - /* enter D3 state and stall */ - sst_hsw_dsp_runtime_suspend(hsw); - /* put the DSP to sleep */ - sst_hsw_dsp_runtime_sleep(hsw); - } - - snd_soc_suspend(pdata->soc_card->dev); - snd_soc_poweroff(pdata->soc_card->dev); - - pdata->pm_state = HSW_PM_STATE_D3; - - return 0; -} - -#else -#define hsw_pcm_prepare NULL -#define hsw_pcm_complete NULL -#endif - -static const struct dev_pm_ops hsw_pcm_pm = { - .runtime_idle = hsw_pcm_runtime_idle, - .runtime_suspend = hsw_pcm_runtime_suspend, - .runtime_resume = hsw_pcm_runtime_resume, - .prepare = hsw_pcm_prepare, - .complete = hsw_pcm_complete, -}; - -static struct platform_driver hsw_pcm_driver = { - .driver = { - .name = "haswell-pcm-audio", - .pm = &hsw_pcm_pm, - }, - - .probe = hsw_pcm_dev_probe, - .remove = hsw_pcm_dev_remove, -}; -module_platform_driver(hsw_pcm_driver); - -MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); -MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:haswell-pcm-audio"); -- cgit v0.10.2 From e56c72d5f201044b14191c5b83a25e17f2d68ccf Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Thu, 2 Apr 2015 15:37:02 +0800 Subject: ASoC: Intel: create boards folder and move sst boards files in Restructure the sound/soc/intel/ directory: create boards folder, and move sst boards files here. Signed-off-by: Jie Yang Acked-by: Vinod Koul Acked-by: Jarkko Nikula Tested-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index eb3efce..ac0248f 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -16,21 +16,7 @@ snd-soc-sst-baytrail-pcm-objs := \ obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o # Machine support -snd-soc-sst-haswell-objs := haswell.o -snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o -snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o -snd-soc-sst-broadwell-objs := broadwell.o -snd-soc-sst-bytcr-dpcm-rt5640-objs := bytcr_dpcm_rt5640.o -snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o -snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o - -obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o -obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o -obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o -obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o -obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-dpcm-rt5640.o -obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o -obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o +obj-$(CONFIG_SND_SOC_INTEL_SST) += boards/ # DSP driver obj-$(CONFIG_SND_SST_IPC) += sst/ diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile new file mode 100644 index 0000000..f8237f0 --- /dev/null +++ b/sound/soc/intel/boards/Makefile @@ -0,0 +1,15 @@ +snd-soc-sst-haswell-objs := haswell.o +snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o +snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o +snd-soc-sst-broadwell-objs := broadwell.o +snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o +snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o +snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o + +obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o +obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c new file mode 100644 index 0000000..8bafaf6 --- /dev/null +++ b/sound/soc/intel/boards/broadwell.c @@ -0,0 +1,292 @@ +/* + * Intel Broadwell Wildcatpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../haswell/sst-haswell-ipc.h" + +#include "../../codecs/rt286.h" + +static struct snd_soc_jack broadwell_headset; +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin broadwell_headset_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static const struct snd_kcontrol_new broadwell_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + +static const struct snd_soc_dapm_widget broadwell_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("DMIC1", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_LINE("Line Jack", NULL), +}; + +static const struct snd_soc_dapm_route broadwell_rt286_map[] = { + + /* speaker */ + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + /* HP jack connectors - unknown if we have jack deteck */ + {"Headphone Jack", NULL, "HPO Pin"}, + + /* other jacks */ + {"MIC1", NULL, "Mic Jack"}, + {"LINE1", NULL, "Line Jack"}, + + /* digital mics */ + {"DMIC1 Pin", NULL, "DMIC1"}, + {"DMIC2 Pin", NULL, "DMIC2"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret = 0; + ret = snd_soc_card_jack_new(rtd->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset, + broadwell_headset_pins, ARRAY_SIZE(broadwell_headset_pins)); + if (ret) + return ret; + + rt286_mic_detect(codec, &broadwell_headset); + return 0; +} + + +static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int broadwell_rt286_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, + SND_SOC_CLOCK_IN); + + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + return ret; +} + +static struct snd_soc_ops broadwell_rt286_ops = { + .hw_params = broadwell_rt286_hw_params, +}; + +static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev); + struct sst_hsw *broadwell = pdata->dsp; + int ret; + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set device config\n"); + return ret; + } + + return 0; +} + +/* broadwell digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broadwell_rt286_dais[] = { + /* Front End DAI links */ + { + .name = "System PCM", + .stream_name = "System Playback/Capture", + .cpu_dai_name = "System Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = broadwell_rtd_init, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .cpu_dai_name = "Offload0 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .cpu_dai_name = "Offload1 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Loopback PCM", + .stream_name = "Loopback", + .cpu_dai_name = "Loopback Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 0, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .be_id = 0, + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_name = "i2c-INT343A:00", + .codec_dai_name = "rt286-aif1", + .init = broadwell_rt286_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broadwell_ssp0_fixup, + .ops = &broadwell_rt286_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static int broadwell_suspend(struct snd_soc_card *card){ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-INT343A:00")) { + dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n"); + rt286_mic_detect(codec, NULL); + break; + } + } + return 0; +} + +static int broadwell_resume(struct snd_soc_card *card){ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-INT343A:00")) { + dev_dbg(codec->dev, "enabling jack detect for resume.\n"); + rt286_mic_detect(codec, &broadwell_headset); + break; + } + } + return 0; +} + +/* broadwell audio machine driver for WPT + RT286S */ +static struct snd_soc_card broadwell_rt286 = { + .name = "broadwell-rt286", + .owner = THIS_MODULE, + .dai_link = broadwell_rt286_dais, + .num_links = ARRAY_SIZE(broadwell_rt286_dais), + .controls = broadwell_controls, + .num_controls = ARRAY_SIZE(broadwell_controls), + .dapm_widgets = broadwell_widgets, + .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), + .dapm_routes = broadwell_rt286_map, + .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map), + .fully_routed = true, + .suspend_pre = broadwell_suspend, + .resume_post = broadwell_resume, +}; + +static int broadwell_audio_probe(struct platform_device *pdev) +{ + broadwell_rt286.dev = &pdev->dev; + + return snd_soc_register_card(&broadwell_rt286); +} + +static int broadwell_audio_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&broadwell_rt286); + return 0; +} + +static struct platform_driver broadwell_audio = { + .probe = broadwell_audio_probe, + .remove = broadwell_audio_remove, + .driver = { + .name = "broadwell-audio", + }, +}; + +module_platform_driver(broadwell_audio) + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:broadwell-audio"); diff --git a/sound/soc/intel/boards/byt-max98090.c b/sound/soc/intel/boards/byt-max98090.c new file mode 100644 index 0000000..7ab8cc9 --- /dev/null +++ b/sound/soc/intel/boards/byt-max98090.c @@ -0,0 +1,187 @@ +/* + * Intel Baytrail SST MAX98090 machine driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/max98090.h" + +struct byt_max98090_private { + struct snd_soc_jack jack; +}; + +static const struct snd_soc_dapm_widget byt_max98090_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route byt_max98090_audio_map[] = { + {"IN34", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS"}, + {"DMICL", NULL, "Int Mic"}, + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + {"Ext Spk", NULL, "SPKL"}, + {"Ext Spk", NULL, "SPKR"}, +}; + +static const struct snd_kcontrol_new byt_max98090_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .name = "hp-gpio", + .idx = 0, + .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, + .debounce_time = 200, + }, + { + .name = "mic-gpio", + .idx = 1, + .invert = 1, + .report = SND_JACK_MICROPHONE, + .debounce_time = 200, + }, +}; + +static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_card *card = runtime->card; + struct byt_max98090_private *drv = snd_soc_card_get_drvdata(card); + struct snd_soc_jack *jack = &drv->jack; + + card->dapm.idle_bias_off = true; + + ret = snd_soc_dai_set_sysclk(runtime->codec_dai, + M98090_REG_SYSTEM_CLOCK, + 25000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + /* Enable jack detection */ + ret = snd_soc_card_jack_new(runtime->card, "Headset", + SND_JACK_LINEOUT | SND_JACK_HEADSET, jack, + hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); + if (ret) + return ret; + + return snd_soc_jack_add_gpiods(card->dev->parent, jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); +} + +static struct snd_soc_dai_link byt_max98090_dais[] = { + { + .name = "Baytrail Audio", + .stream_name = "Audio", + .cpu_dai_name = "baytrail-pcm-audio", + .codec_dai_name = "HiFi", + .codec_name = "i2c-193C9890:00", + .platform_name = "baytrail-pcm-audio", + .init = byt_max98090_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, +}; + +static struct snd_soc_card byt_max98090_card = { + .name = "byt-max98090", + .dai_link = byt_max98090_dais, + .num_links = ARRAY_SIZE(byt_max98090_dais), + .dapm_widgets = byt_max98090_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_max98090_widgets), + .dapm_routes = byt_max98090_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_max98090_audio_map), + .controls = byt_max98090_controls, + .num_controls = ARRAY_SIZE(byt_max98090_controls), + .fully_routed = true, +}; + +static int byt_max98090_probe(struct platform_device *pdev) +{ + int ret_val = 0; + struct byt_max98090_private *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); + if (!priv) { + dev_err(&pdev->dev, "allocation failed\n"); + return -ENOMEM; + } + + byt_max98090_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&byt_max98090_card, priv); + ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_max98090_card); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + + return ret_val; +} + +static int byt_max98090_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct byt_max98090_private *priv = snd_soc_card_get_drvdata(card); + + snd_soc_jack_free_gpios(&priv->jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + + return 0; +} + +static struct platform_driver byt_max98090_driver = { + .probe = byt_max98090_probe, + .remove = byt_max98090_remove, + .driver = { + .name = "byt-max98090", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(byt_max98090_driver) + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); +MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:byt-max98090"); diff --git a/sound/soc/intel/boards/byt-rt5640.c b/sound/soc/intel/boards/byt-rt5640.c new file mode 100644 index 0000000..ae89b9b9 --- /dev/null +++ b/sound/soc/intel/boards/byt-rt5640.c @@ -0,0 +1,229 @@ +/* + * Intel Baytrail SST RT5640 machine driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5640.h" + +#include "../common/sst-dsp.h" + +static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { + {"Headset Mic", NULL, "MICBIAS1"}, + {"IN2P", NULL, "Headset Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Speaker", NULL, "SPOLP"}, + {"Speaker", NULL, "SPOLN"}, + {"Speaker", NULL, "SPORP"}, + {"Speaker", NULL, "SPORN"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = { + {"DMIC1", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = { + {"DMIC2", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = { + {"Internal Mic", NULL, "MICBIAS1"}, + {"IN1P", NULL, "Internal Mic"}, +}; + +enum { + BYT_RT5640_DMIC1_MAP, + BYT_RT5640_DMIC2_MAP, + BYT_RT5640_IN1_MAP, +}; + +#define BYT_RT5640_MAP(quirk) ((quirk) & 0xff) +#define BYT_RT5640_DMIC_EN BIT(16) + +static unsigned long byt_rt5640_quirk = BYT_RT5640_DMIC1_MAP | + BYT_RT5640_DMIC_EN; + +static const struct snd_kcontrol_new byt_rt5640_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int byt_rt5640_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, + params_rate(params) * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set codec clock %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, + params_rate(params) * 64, + params_rate(params) * 256); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret); + return ret; + } + return 0; +} + +static int byt_rt5640_quirk_cb(const struct dmi_system_id *id) +{ + byt_rt5640_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id byt_rt5640_quirk_table[] = { + { + .callback = byt_rt5640_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), + }, + .driver_data = (unsigned long *)BYT_RT5640_IN1_MAP, + }, + { + .callback = byt_rt5640_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "DellInc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"), + }, + .driver_data = (unsigned long *)(BYT_RT5640_DMIC2_MAP | + BYT_RT5640_DMIC_EN), + }, + {} +}; + +static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_codec *codec = runtime->codec; + struct snd_soc_card *card = runtime->card; + const struct snd_soc_dapm_route *custom_map; + int num_routes; + + card->dapm.idle_bias_off = true; + + ret = snd_soc_add_card_controls(card, byt_rt5640_controls, + ARRAY_SIZE(byt_rt5640_controls)); + if (ret) { + dev_err(card->dev, "unable to add card controls\n"); + return ret; + } + + dmi_check_system(byt_rt5640_quirk_table); + switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { + case BYT_RT5640_IN1_MAP: + custom_map = byt_rt5640_intmic_in1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map); + break; + case BYT_RT5640_DMIC2_MAP: + custom_map = byt_rt5640_intmic_dmic2_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map); + break; + default: + custom_map = byt_rt5640_intmic_dmic1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); + } + + ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); + if (ret) + return ret; + + if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) { + ret = rt5640_dmic_enable(codec, 0, 0); + if (ret) + return ret; + } + + snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); + + return ret; +} + +static struct snd_soc_ops byt_rt5640_ops = { + .hw_params = byt_rt5640_hw_params, +}; + +static struct snd_soc_dai_link byt_rt5640_dais[] = { + { + .name = "Baytrail Audio", + .stream_name = "Audio", + .cpu_dai_name = "baytrail-pcm-audio", + .codec_dai_name = "rt5640-aif1", + .codec_name = "i2c-10EC5640:00", + .platform_name = "baytrail-pcm-audio", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = byt_rt5640_init, + .ops = &byt_rt5640_ops, + }, +}; + +static struct snd_soc_card byt_rt5640_card = { + .name = "byt-rt5640", + .dai_link = byt_rt5640_dais, + .num_links = ARRAY_SIZE(byt_rt5640_dais), + .dapm_widgets = byt_rt5640_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets), + .dapm_routes = byt_rt5640_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map), + .fully_routed = true, +}; + +static int byt_rt5640_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &byt_rt5640_card; + + card->dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static struct platform_driver byt_rt5640_audio = { + .probe = byt_rt5640_probe, + .driver = { + .name = "byt-rt5640", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(byt_rt5640_audio) + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); +MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:byt-rt5640"); diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c new file mode 100644 index 0000000..5c2d8fa --- /dev/null +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -0,0 +1,227 @@ +/* + * byt_cr_dpcm_rt5640.c - ASoc Machine driver for Intel Byt CR platform + * + * Copyright (C) 2014 Intel Corp + * Author: Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5640.h" +#include "../sst-atom-controls.h" + +static const struct snd_soc_dapm_widget byt_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route byt_audio_map[] = { + {"IN2P", NULL, "Headset Mic"}, + {"IN2N", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS1"}, + {"IN1P", NULL, "MICBIAS1"}, + {"LDO2", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOLP"}, + {"Ext Spk", NULL, "SPOLN"}, + {"Ext Spk", NULL, "SPORP"}, + {"Ext Spk", NULL, "SPORN"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_kcontrol_new byt_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int byt_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + snd_soc_dai_set_bclk_ratio(codec_dai, 50); + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec clock %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, + params_rate(params) * 50, + params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_pcm_stream byt_dai_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static int byt_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int byt_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops byt_aif1_ops = { + .startup = byt_aif1_startup, +}; + +static struct snd_soc_ops byt_be_ssp2_ops = { + .hw_params = byt_aif1_hw_params, +}; + +static struct snd_soc_dai_link byt_dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Baytrail Audio Port", + .stream_name = "Baytrail Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Baytrail Compressed Port", + .stream_name = "Baytrail Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* back ends */ + { + .name = "SSP2-Codec", + .be_id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5640-aif1", + .codec_name = "i2c-10EC5640:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = byt_codec_fixup, + .ignore_suspend = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_byt = { + .name = "baytrailcraudio", + .dai_link = byt_dailink, + .num_links = ARRAY_SIZE(byt_dailink), + .dapm_widgets = byt_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_dapm_widgets), + .dapm_routes = byt_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_audio_map), + .controls = byt_mc_controls, + .num_controls = ARRAY_SIZE(byt_mc_controls), +}; + +static int snd_byt_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + snd_soc_card_byt.dev = &pdev->dev; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_byt); + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_byt); + return ret_val; +} + +static struct platform_driver snd_byt_mc_driver = { + .driver = { + .name = "bytt100_rt5640", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_byt_mc_probe, +}; + +module_platform_driver(snd_byt_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Subhransu S. Prusty "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytt100_rt5640"); diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c new file mode 100644 index 0000000..93bb671 --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -0,0 +1,324 @@ +/* + * cht-bsw-rt5645.c - ASoc Machine driver for Intel Cherryview-based platforms + * Cherrytrail and Braswell, with RT5645 codec. + * + * Copyright (C) 2015 Intel Corp + * Author: Fang, Yang A + * N,Harshapriya + * This file is modified from cht_bsw_rt5672.c + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5645.h" +#include "../sst-atom-controls.h" + +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5645-aif1" + +struct cht_mc_private { + struct snd_soc_jack hp_jack; + struct snd_soc_jack mic_jack; +}; + +static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) +{ + int i; + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_pcm_runtime *rtd; + + rtd = card->rtd + i; + if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, + strlen(CHT_CODEC_DAI))) + return rtd->codec_dai; + } + return NULL; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = cht_get_codec_dai(card); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (!SND_SOC_DAPM_EVENT_OFF(event)) + return 0; + + /* Set codec sysclk source to its internal clock because codec PLL will + * be off when idle and MCLK will also be off by ACPI when codec is + * runtime suspended. Codec needs clock for jack detection and button + * press. + */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_audio_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOL"}, + {"Ext Spk", NULL, "SPOR"}, + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, + params_rate(params) * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_codec *codec = runtime->codec; + struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); + + /* Select clk_i2s1_asrc as ASRC clock source */ + rt5645_sel_asrc_clk_src(codec, + RT5645_DA_STEREO_FILTER | + RT5645_DA_MONO_L_FILTER | + RT5645_DA_MONO_R_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new(runtime->card, "Headphone Jack", + SND_JACK_HEADPHONE, &ctx->hp_jack, + NULL, 0); + if (ret) { + dev_err(runtime->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new(runtime->card, "Mic Jack", + SND_JACK_MICROPHONE, &ctx->mic_jack, + NULL, 0); + if (ret) { + dev_err(runtime->dev, "Mic jack creation failed %d\n", ret); + return ret; + } + + rt5645_set_jack_detect(codec, &ctx->hp_jack, &ctx->mic_jack); + + return ret; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_dai_link cht_dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-Codec", + .be_id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5645-aif1", + .codec_name = "i2c-10EC5645:00", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .ignore_suspend = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_cht = { + .name = "chtrt5645", + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), +}; + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + struct cht_mc_private *drv; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); + if (!drv) + return -ENOMEM; + + snd_soc_card_cht.dev = &pdev->dev; + snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_cht); + return ret_val; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .name = "cht-bsw-rt5645", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_cht_mc_probe, +}; + +module_platform_driver(snd_cht_mc_driver) + +MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver"); +MODULE_AUTHOR("Fang, Yang A,N,Harshapriya"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-rt5645"); diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c new file mode 100644 index 0000000..2cea002 --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -0,0 +1,366 @@ +/* + * cht_bsw_rt5672.c - ASoc Machine driver for Intel Cherryview-based platforms + * Cherrytrail and Braswell, with RT5672 codec. + * + * Copyright (C) 2014 Intel Corp + * Author: Subhransu S. Prusty + * Mengdong Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5670.h" +#include "../sst-atom-controls.h" + +/* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */ +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5670-aif1" + +static struct snd_soc_jack cht_bsw_headset; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin cht_bsw_headset_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) +{ + int i; + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_pcm_runtime *rtd; + + rtd = card->rtd + i; + if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, + strlen(CHT_CODEC_DAI))) + return rtd->codec_dai; + } + return NULL; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = cht_get_codec_dai(card); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, 48000 * 512); + if (ret < 0) { + dev_err(card->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + /* set codec sysclk source to PLL */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + } else { + /* Set codec sysclk source to its internal clock because codec + * PLL will be off when idle and MCLK will also be off by ACPI + * when codec is runtime suspended. Codec needs clock for jack + * detection and button press. + */ + snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + } + return 0; +} + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_audio_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOLP"}, + {"Ext Spk", NULL, "SPOLN"}, + {"Ext Spk", NULL, "SPORP"}, + {"Ext Spk", NULL, "SPORN"}, + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + /* set codec sysclk source to PLL */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + return 0; +} + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + + /* Select codec ASRC clock source to track I2S1 clock, because codec + * is in slave mode and 100fs I2S format (BCLK = 100 * LRCLK) cannot + * be supported by RT5672. Otherwise, ASRC will be disabled and cause + * noise. + */ + rt5670_sel_asrc_clk_src(codec, + RT5670_DA_STEREO_FILTER + | RT5670_DA_MONO_L_FILTER + | RT5670_DA_MONO_R_FILTER + | RT5670_AD_STEREO_FILTER + | RT5670_AD_MONO_L_FILTER + | RT5670_AD_MONO_R_FILTER, + RT5670_CLK_SEL_I2S1_ASRC); + + ret = snd_soc_card_jack_new(runtime->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, &cht_bsw_headset, + cht_bsw_headset_pins, ARRAY_SIZE(cht_bsw_headset_pins)); + if (ret) + return ret; + + rt5670_set_jack_detect(codec, &cht_bsw_headset); + return 0; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_dai_link cht_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .be_id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .nonatomic = true, + .codec_dai_name = "rt5670-aif1", + .codec_name = "i2c-10EC5670:00", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +static int cht_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-10EC5670:00")) { + dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n"); + rt5670_jack_suspend(codec); + break; + } + } + return 0; +} + +static int cht_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->component.name, "i2c-10EC5670:00")) { + dev_dbg(codec->dev, "enabling jack detect for resume.\n"); + rt5670_jack_resume(codec); + break; + } + } + + return 0; +} + +/* SoC card */ +static struct snd_soc_card snd_soc_card_cht = { + .name = "cherrytrailcraudio", + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), + .suspend_pre = cht_suspend_pre, + .resume_post = cht_resume_post, +}; + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + snd_soc_card_cht.dev = &pdev->dev; + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_cht); + return ret_val; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .name = "cht-bsw-rt5672", + }, + .probe = snd_cht_mc_probe, +}; + +module_platform_driver(snd_cht_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Subhransu S. Prusty, Mengdong Lin"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-rt5672"); diff --git a/sound/soc/intel/boards/haswell.c b/sound/soc/intel/boards/haswell.c new file mode 100644 index 0000000..2255857 --- /dev/null +++ b/sound/soc/intel/boards/haswell.c @@ -0,0 +1,209 @@ +/* + * Intel Haswell Lynxpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. 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 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 +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../haswell/sst-haswell-ipc.h" + +#include "../../codecs/rt5640.h" + +/* Haswell ULT platforms have a Headphone and Mic jack */ +static const struct snd_soc_dapm_widget haswell_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route haswell_rt5640_map[] = { + + {"Headphones", NULL, "HPOR"}, + {"Headphones", NULL, "HPOL"}, + {"IN2P", NULL, "Mic"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int haswell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int haswell_rt5640_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, + SND_SOC_CLOCK_IN); + + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + /* set correct codec filter for DAI format and clock config */ + snd_soc_update_bits(rtd->codec, 0x83, 0xffff, 0x8000); + + return ret; +} + +static struct snd_soc_ops haswell_rt5640_ops = { + .hw_params = haswell_rt5640_hw_params, +}; + +static int haswell_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev); + struct sst_hsw *haswell = pdata->dsp; + int ret; + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(haswell, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) { + dev_err(rtd->dev, "failed to set device config\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_dai_link haswell_rt5640_dais[] = { + /* Front End DAI links */ + { + .name = "System", + .stream_name = "System Playback/Capture", + .cpu_dai_name = "System Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = haswell_rtd_init, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .cpu_dai_name = "Offload0 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .cpu_dai_name = "Offload1 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Loopback", + .stream_name = "Loopback", + .cpu_dai_name = "Loopback Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 0, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .be_id = 0, + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_name = "i2c-INT33CA:00", + .codec_dai_name = "rt5640-aif1", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = haswell_ssp0_fixup, + .ops = &haswell_rt5640_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +/* audio machine driver for Haswell Lynxpoint DSP + RT5640 */ +static struct snd_soc_card haswell_rt5640 = { + .name = "haswell-rt5640", + .owner = THIS_MODULE, + .dai_link = haswell_rt5640_dais, + .num_links = ARRAY_SIZE(haswell_rt5640_dais), + .dapm_widgets = haswell_widgets, + .num_dapm_widgets = ARRAY_SIZE(haswell_widgets), + .dapm_routes = haswell_rt5640_map, + .num_dapm_routes = ARRAY_SIZE(haswell_rt5640_map), + .fully_routed = true, +}; + +static int haswell_audio_probe(struct platform_device *pdev) +{ + haswell_rt5640.dev = &pdev->dev; + + return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640); +} + +static struct platform_driver haswell_audio = { + .probe = haswell_audio_probe, + .driver = { + .name = "haswell-audio", + }, +}; + +module_platform_driver(haswell_audio) + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Intel SST Audio for Haswell Lynxpoint"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:haswell-audio"); diff --git a/sound/soc/intel/boards/mfld_machine.c b/sound/soc/intel/boards/mfld_machine.c new file mode 100644 index 0000000..49c09a0 --- /dev/null +++ b/sound/soc/intel/boards/mfld_machine.c @@ -0,0 +1,430 @@ +/* + * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul + * Author: Harsha Priya + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/sn95031.h" + +#define MID_MONO 1 +#define MID_STEREO 2 +#define MID_MAX_CAP 5 +#define MFLD_JACK_INSERT 0x04 + +enum soc_mic_bias_zones { + MFLD_MV_START = 0, + /* mic bias volutage range for Headphones*/ + MFLD_MV_HP = 400, + /* mic bias volutage range for American Headset*/ + MFLD_MV_AM_HS = 650, + /* mic bias volutage range for Headset*/ + MFLD_MV_HS = 2000, + MFLD_MV_UNDEFINED, +}; + +static unsigned int hs_switch; +static unsigned int lo_dac; +static struct snd_soc_codec *mfld_codec; + +struct mfld_mc_private { + void __iomem *int_base; + u8 interrupt_status; +}; + +struct snd_soc_jack mfld_jack; + +/*Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin mfld_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "AMIC1", + .mask = SND_JACK_MICROPHONE, + }, +}; + +/* jack detection voltage zones */ +static struct snd_soc_jack_zone mfld_zones[] = { + {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE}, + {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET}, +}; + +/* sound card controls */ +static const char *headset_switch_text[] = {"Earpiece", "Headset"}; + +static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"}; + +static const struct soc_enum headset_enum = + SOC_ENUM_SINGLE_EXT(2, headset_switch_text); + +static const struct soc_enum lo_enum = + SOC_ENUM_SINGLE_EXT(4, lo_text); + +static int headset_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = hs_switch; + return 0; +} + +static int headset_set_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = &card->dapm; + + if (ucontrol->value.integer.value[0] == hs_switch) + return 0; + + snd_soc_dapm_mutex_lock(dapm); + + if (ucontrol->value.integer.value[0]) { + pr_debug("hs_set HS path\n"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones"); + snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); + } else { + pr_debug("hs_set EP path\n"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); + snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT"); + } + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + + hs_switch = ucontrol->value.integer.value[0]; + + return 0; +} + +static void lo_enable_out_pins(struct snd_soc_dapm_context *dapm) +{ + snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTL"); + snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTR"); + snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTL"); + snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTR"); + snd_soc_dapm_enable_pin_unlocked(dapm, "VIB1OUT"); + snd_soc_dapm_enable_pin_unlocked(dapm, "VIB2OUT"); + if (hs_switch) { + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones"); + snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); + } else { + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); + snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT"); + } +} + +static int lo_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lo_dac; + return 0; +} + +static int lo_set_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = &card->dapm; + + if (ucontrol->value.integer.value[0] == lo_dac) + return 0; + + snd_soc_dapm_mutex_lock(dapm); + + /* we dont want to work with last state of lineout so just enable all + * pins and then disable pins not required + */ + lo_enable_out_pins(dapm); + + switch (ucontrol->value.integer.value[0]) { + case 0: + pr_debug("set vibra path\n"); + snd_soc_dapm_disable_pin_unlocked(dapm, "VIB1OUT"); + snd_soc_dapm_disable_pin_unlocked(dapm, "VIB2OUT"); + snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0); + break; + + case 1: + pr_debug("set hs path\n"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); + snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); + snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x22); + break; + + case 2: + pr_debug("set spkr path\n"); + snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTL"); + snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTR"); + snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x44); + break; + + case 3: + pr_debug("set null path\n"); + snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTL"); + snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTR"); + snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x66); + break; + } + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + + lo_dac = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_kcontrol_new mfld_snd_controls[] = { + SOC_ENUM_EXT("Playback Switch", headset_enum, + headset_get_switch, headset_set_switch), + SOC_ENUM_EXT("Lineout Mux", lo_enum, + lo_get_switch, lo_set_switch), +}; + +static const struct snd_soc_dapm_widget mfld_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route mfld_map[] = { + {"Headphones", NULL, "HPOUTR"}, + {"Headphones", NULL, "HPOUTL"}, + {"Mic", NULL, "AMIC1"}, +}; + +static void mfld_jack_check(unsigned int intr_status) +{ + struct mfld_jack_data jack_data; + + if (!mfld_codec) + return; + + jack_data.mfld_jack = &mfld_jack; + jack_data.intr_id = intr_status; + + sn95031_jack_detection(mfld_codec, &jack_data); + /* TODO: add american headset detection post gpiolib support */ +} + +static int mfld_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dapm_context *dapm = &runtime->card->dapm; + int ret_val; + + /* default is earpiece pin, userspace sets it explcitly */ + snd_soc_dapm_disable_pin(dapm, "Headphones"); + /* default is lineout NC, userspace sets it explcitly */ + snd_soc_dapm_disable_pin(dapm, "LINEOUTL"); + snd_soc_dapm_disable_pin(dapm, "LINEOUTR"); + lo_dac = 3; + hs_switch = 0; + /* we dont use linein in this so set to NC */ + snd_soc_dapm_disable_pin(dapm, "LINEINL"); + snd_soc_dapm_disable_pin(dapm, "LINEINR"); + + /* Headset and button jack detection */ + ret_val = snd_soc_card_jack_new(runtime->card, + "Intel(R) MID Audio Jack", SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1, &mfld_jack, + mfld_jack_pins, ARRAY_SIZE(mfld_jack_pins)); + if (ret_val) { + pr_err("jack creation failed\n"); + return ret_val; + } + + ret_val = snd_soc_jack_add_zones(&mfld_jack, + ARRAY_SIZE(mfld_zones), mfld_zones); + if (ret_val) { + pr_err("adding jack zones failed\n"); + return ret_val; + } + + mfld_codec = runtime->codec; + + /* we want to check if anything is inserted at boot, + * so send a fake event to codec and it will read adc + * to find if anything is there or not */ + mfld_jack_check(MFLD_JACK_INSERT); + return ret_val; +} + +static struct snd_soc_dai_link mfld_msic_dailink[] = { + { + .name = "Medfield Headset", + .stream_name = "Headset", + .cpu_dai_name = "Headset-cpu-dai", + .codec_dai_name = "SN95031 Headset", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = mfld_init, + }, + { + .name = "Medfield Speaker", + .stream_name = "Speaker", + .cpu_dai_name = "Speaker-cpu-dai", + .codec_dai_name = "SN95031 Speaker", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Vibra", + .stream_name = "Vibra1", + .cpu_dai_name = "Vibra1-cpu-dai", + .codec_dai_name = "SN95031 Vibra1", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Haptics", + .stream_name = "Vibra2", + .cpu_dai_name = "Vibra2-cpu-dai", + .codec_dai_name = "SN95031 Vibra2", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Compress", + .stream_name = "Speaker", + .cpu_dai_name = "Compress-cpu-dai", + .codec_dai_name = "SN95031 Speaker", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_mfld = { + .name = "medfield_audio", + .owner = THIS_MODULE, + .dai_link = mfld_msic_dailink, + .num_links = ARRAY_SIZE(mfld_msic_dailink), + + .controls = mfld_snd_controls, + .num_controls = ARRAY_SIZE(mfld_snd_controls), + .dapm_widgets = mfld_widgets, + .num_dapm_widgets = ARRAY_SIZE(mfld_widgets), + .dapm_routes = mfld_map, + .num_dapm_routes = ARRAY_SIZE(mfld_map), +}; + +static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev) +{ + struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev; + + memcpy_fromio(&mc_private->interrupt_status, + ((void *)(mc_private->int_base)), + sizeof(u8)); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t snd_mfld_jack_detection(int irq, void *data) +{ + struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data; + + mfld_jack_check(mc_drv_ctx->interrupt_status); + + return IRQ_HANDLED; +} + +static int snd_mfld_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0, irq; + struct mfld_mc_private *mc_drv_ctx; + struct resource *irq_mem; + + pr_debug("snd_mfld_mc_probe called\n"); + + /* retrive the irq number */ + irq = platform_get_irq(pdev, 0); + + /* audio interrupt base of SRAM location where + * interrupts are stored by System FW */ + mc_drv_ctx = devm_kzalloc(&pdev->dev, sizeof(*mc_drv_ctx), GFP_ATOMIC); + if (!mc_drv_ctx) { + pr_err("allocation failed\n"); + return -ENOMEM; + } + + irq_mem = platform_get_resource_byname( + pdev, IORESOURCE_MEM, "IRQ_BASE"); + if (!irq_mem) { + pr_err("no mem resource given\n"); + return -ENODEV; + } + mc_drv_ctx->int_base = devm_ioremap_nocache(&pdev->dev, irq_mem->start, + resource_size(irq_mem)); + if (!mc_drv_ctx->int_base) { + pr_err("Mapping of cache failed\n"); + return -ENOMEM; + } + /* register for interrupt */ + ret_val = devm_request_threaded_irq(&pdev->dev, irq, + snd_mfld_jack_intr_handler, + snd_mfld_jack_detection, + IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx); + if (ret_val) { + pr_err("cannot register IRQ\n"); + return ret_val; + } + /* register the soc card */ + snd_soc_card_mfld.dev = &pdev->dev; + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_mfld); + if (ret_val) { + pr_debug("snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, mc_drv_ctx); + pr_debug("successfully exited probe\n"); + return 0; +} + +static struct platform_driver snd_mfld_mc_driver = { + .driver = { + .name = "msic_audio", + }, + .probe = snd_mfld_mc_probe, +}; + +module_platform_driver(snd_mfld_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver"); +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msic-audio"); diff --git a/sound/soc/intel/broadwell.c b/sound/soc/intel/broadwell.c deleted file mode 100644 index 6c75b6b..0000000 --- a/sound/soc/intel/broadwell.c +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Intel Broadwell Wildcatpoint SST Audio - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include -#include - -#include "sst-dsp.h" -#include "sst-haswell-ipc.h" - -#include "../codecs/rt286.h" - -static struct snd_soc_jack broadwell_headset; -/* Headset jack detection DAPM pins */ -static struct snd_soc_jack_pin broadwell_headset_pins[] = { - { - .pin = "Mic Jack", - .mask = SND_JACK_MICROPHONE, - }, - { - .pin = "Headphone Jack", - .mask = SND_JACK_HEADPHONE, - }, -}; - -static const struct snd_kcontrol_new broadwell_controls[] = { - SOC_DAPM_PIN_SWITCH("Speaker"), - SOC_DAPM_PIN_SWITCH("Headphone Jack"), -}; - -static const struct snd_soc_dapm_widget broadwell_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_SPK("Speaker", NULL), - SND_SOC_DAPM_MIC("Mic Jack", NULL), - SND_SOC_DAPM_MIC("DMIC1", NULL), - SND_SOC_DAPM_MIC("DMIC2", NULL), - SND_SOC_DAPM_LINE("Line Jack", NULL), -}; - -static const struct snd_soc_dapm_route broadwell_rt286_map[] = { - - /* speaker */ - {"Speaker", NULL, "SPOR"}, - {"Speaker", NULL, "SPOL"}, - - /* HP jack connectors - unknown if we have jack deteck */ - {"Headphone Jack", NULL, "HPO Pin"}, - - /* other jacks */ - {"MIC1", NULL, "Mic Jack"}, - {"LINE1", NULL, "Line Jack"}, - - /* digital mics */ - {"DMIC1 Pin", NULL, "DMIC1"}, - {"DMIC2 Pin", NULL, "DMIC2"}, - - /* CODEC BE connections */ - {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, - {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, -}; - -static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_codec *codec = rtd->codec; - int ret = 0; - ret = snd_soc_card_jack_new(rtd->card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset, - broadwell_headset_pins, ARRAY_SIZE(broadwell_headset_pins)); - if (ret) - return ret; - - rt286_mic_detect(codec, &broadwell_headset); - return 0; -} - - -static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The ADSP will covert the FE rate to 48k, stereo */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP0 to 16 bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); - return 0; -} - -static int broadwell_rt286_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, - SND_SOC_CLOCK_IN); - - if (ret < 0) { - dev_err(rtd->dev, "can't set codec sysclk configuration\n"); - return ret; - } - - return ret; -} - -static struct snd_soc_ops broadwell_rt286_ops = { - .hw_params = broadwell_rt286_hw_params, -}; - -static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) -{ - struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev); - struct sst_hsw *broadwell = pdata->dsp; - int ret; - - /* Set ADSP SSP port settings */ - ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_CLOCK_MASTER, 9); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set device config\n"); - return ret; - } - - return 0; -} - -/* broadwell digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link broadwell_rt286_dais[] = { - /* Front End DAI links */ - { - .name = "System PCM", - .stream_name = "System Playback/Capture", - .cpu_dai_name = "System Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .init = broadwell_rtd_init, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - .dpcm_capture = 1, - }, - { - .name = "Offload0", - .stream_name = "Offload0 Playback", - .cpu_dai_name = "Offload0 Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - }, - { - .name = "Offload1", - .stream_name = "Offload1 Playback", - .cpu_dai_name = "Offload1 Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - }, - { - .name = "Loopback PCM", - .stream_name = "Loopback", - .cpu_dai_name = "Loopback Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 0, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - }, - /* Back End DAI links */ - { - /* SSP0 - Codec */ - .name = "Codec", - .be_id = 0, - .cpu_dai_name = "snd-soc-dummy-dai", - .platform_name = "snd-soc-dummy", - .no_pcm = 1, - .codec_name = "i2c-INT343A:00", - .codec_dai_name = "rt286-aif1", - .init = broadwell_rt286_codec_init, - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .ignore_suspend = 1, - .ignore_pmdown_time = 1, - .be_hw_params_fixup = broadwell_ssp0_fixup, - .ops = &broadwell_rt286_ops, - .dpcm_playback = 1, - .dpcm_capture = 1, - }, -}; - -static int broadwell_suspend(struct snd_soc_card *card){ - struct snd_soc_codec *codec; - - list_for_each_entry(codec, &card->codec_dev_list, card_list) { - if (!strcmp(codec->component.name, "i2c-INT343A:00")) { - dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n"); - rt286_mic_detect(codec, NULL); - break; - } - } - return 0; -} - -static int broadwell_resume(struct snd_soc_card *card){ - struct snd_soc_codec *codec; - - list_for_each_entry(codec, &card->codec_dev_list, card_list) { - if (!strcmp(codec->component.name, "i2c-INT343A:00")) { - dev_dbg(codec->dev, "enabling jack detect for resume.\n"); - rt286_mic_detect(codec, &broadwell_headset); - break; - } - } - return 0; -} - -/* broadwell audio machine driver for WPT + RT286S */ -static struct snd_soc_card broadwell_rt286 = { - .name = "broadwell-rt286", - .owner = THIS_MODULE, - .dai_link = broadwell_rt286_dais, - .num_links = ARRAY_SIZE(broadwell_rt286_dais), - .controls = broadwell_controls, - .num_controls = ARRAY_SIZE(broadwell_controls), - .dapm_widgets = broadwell_widgets, - .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), - .dapm_routes = broadwell_rt286_map, - .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map), - .fully_routed = true, - .suspend_pre = broadwell_suspend, - .resume_post = broadwell_resume, -}; - -static int broadwell_audio_probe(struct platform_device *pdev) -{ - broadwell_rt286.dev = &pdev->dev; - - return snd_soc_register_card(&broadwell_rt286); -} - -static int broadwell_audio_remove(struct platform_device *pdev) -{ - snd_soc_unregister_card(&broadwell_rt286); - return 0; -} - -static struct platform_driver broadwell_audio = { - .probe = broadwell_audio_probe, - .remove = broadwell_audio_remove, - .driver = { - .name = "broadwell-audio", - }, -}; - -module_platform_driver(broadwell_audio) - -/* Module information */ -MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); -MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:broadwell-audio"); diff --git a/sound/soc/intel/byt-max98090.c b/sound/soc/intel/byt-max98090.c deleted file mode 100644 index d8b1f03..0000000 --- a/sound/soc/intel/byt-max98090.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Intel Baytrail SST MAX98090 machine driver - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../codecs/max98090.h" - -struct byt_max98090_private { - struct snd_soc_jack jack; -}; - -static const struct snd_soc_dapm_widget byt_max98090_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), -}; - -static const struct snd_soc_dapm_route byt_max98090_audio_map[] = { - {"IN34", NULL, "Headset Mic"}, - {"Headset Mic", NULL, "MICBIAS"}, - {"DMICL", NULL, "Int Mic"}, - {"Headphone", NULL, "HPL"}, - {"Headphone", NULL, "HPR"}, - {"Ext Spk", NULL, "SPKL"}, - {"Ext Spk", NULL, "SPKR"}, -}; - -static const struct snd_kcontrol_new byt_max98090_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Int Mic"), - SOC_DAPM_PIN_SWITCH("Ext Spk"), -}; - -static struct snd_soc_jack_pin hs_jack_pins[] = { - { - .pin = "Headphone", - .mask = SND_JACK_HEADPHONE, - }, - { - .pin = "Headset Mic", - .mask = SND_JACK_MICROPHONE, - }, -}; - -static struct snd_soc_jack_gpio hs_jack_gpios[] = { - { - .name = "hp-gpio", - .idx = 0, - .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, - .debounce_time = 200, - }, - { - .name = "mic-gpio", - .idx = 1, - .invert = 1, - .report = SND_JACK_MICROPHONE, - .debounce_time = 200, - }, -}; - -static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) -{ - int ret; - struct snd_soc_card *card = runtime->card; - struct byt_max98090_private *drv = snd_soc_card_get_drvdata(card); - struct snd_soc_jack *jack = &drv->jack; - - card->dapm.idle_bias_off = true; - - ret = snd_soc_dai_set_sysclk(runtime->codec_dai, - M98090_REG_SYSTEM_CLOCK, - 25000000, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(card->dev, "Can't set codec clock %d\n", ret); - return ret; - } - - /* Enable jack detection */ - ret = snd_soc_card_jack_new(runtime->card, "Headset", - SND_JACK_LINEOUT | SND_JACK_HEADSET, jack, - hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); - if (ret) - return ret; - - return snd_soc_jack_add_gpiods(card->dev->parent, jack, - ARRAY_SIZE(hs_jack_gpios), - hs_jack_gpios); -} - -static struct snd_soc_dai_link byt_max98090_dais[] = { - { - .name = "Baytrail Audio", - .stream_name = "Audio", - .cpu_dai_name = "baytrail-pcm-audio", - .codec_dai_name = "HiFi", - .codec_name = "i2c-193C9890:00", - .platform_name = "baytrail-pcm-audio", - .init = byt_max98090_init, - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - }, -}; - -static struct snd_soc_card byt_max98090_card = { - .name = "byt-max98090", - .dai_link = byt_max98090_dais, - .num_links = ARRAY_SIZE(byt_max98090_dais), - .dapm_widgets = byt_max98090_widgets, - .num_dapm_widgets = ARRAY_SIZE(byt_max98090_widgets), - .dapm_routes = byt_max98090_audio_map, - .num_dapm_routes = ARRAY_SIZE(byt_max98090_audio_map), - .controls = byt_max98090_controls, - .num_controls = ARRAY_SIZE(byt_max98090_controls), - .fully_routed = true, -}; - -static int byt_max98090_probe(struct platform_device *pdev) -{ - int ret_val = 0; - struct byt_max98090_private *priv; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); - if (!priv) { - dev_err(&pdev->dev, "allocation failed\n"); - return -ENOMEM; - } - - byt_max98090_card.dev = &pdev->dev; - snd_soc_card_set_drvdata(&byt_max98090_card, priv); - ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_max98090_card); - if (ret_val) { - dev_err(&pdev->dev, - "snd_soc_register_card failed %d\n", ret_val); - return ret_val; - } - - return ret_val; -} - -static int byt_max98090_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct byt_max98090_private *priv = snd_soc_card_get_drvdata(card); - - snd_soc_jack_free_gpios(&priv->jack, ARRAY_SIZE(hs_jack_gpios), - hs_jack_gpios); - - return 0; -} - -static struct platform_driver byt_max98090_driver = { - .probe = byt_max98090_probe, - .remove = byt_max98090_remove, - .driver = { - .name = "byt-max98090", - .pm = &snd_soc_pm_ops, - }, -}; -module_platform_driver(byt_max98090_driver) - -MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); -MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:byt-max98090"); diff --git a/sound/soc/intel/byt-rt5640.c b/sound/soc/intel/byt-rt5640.c deleted file mode 100644 index 354eaad..0000000 --- a/sound/soc/intel/byt-rt5640.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Intel Baytrail SST RT5640 machine driver - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../codecs/rt5640.h" - -#include "sst-dsp.h" - -static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Internal Mic", NULL), - SND_SOC_DAPM_SPK("Speaker", NULL), -}; - -static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { - {"Headset Mic", NULL, "MICBIAS1"}, - {"IN2P", NULL, "Headset Mic"}, - {"Headphone", NULL, "HPOL"}, - {"Headphone", NULL, "HPOR"}, - {"Speaker", NULL, "SPOLP"}, - {"Speaker", NULL, "SPOLN"}, - {"Speaker", NULL, "SPORP"}, - {"Speaker", NULL, "SPORN"}, -}; - -static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = { - {"DMIC1", NULL, "Internal Mic"}, -}; - -static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = { - {"DMIC2", NULL, "Internal Mic"}, -}; - -static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = { - {"Internal Mic", NULL, "MICBIAS1"}, - {"IN1P", NULL, "Internal Mic"}, -}; - -enum { - BYT_RT5640_DMIC1_MAP, - BYT_RT5640_DMIC2_MAP, - BYT_RT5640_IN1_MAP, -}; - -#define BYT_RT5640_MAP(quirk) ((quirk) & 0xff) -#define BYT_RT5640_DMIC_EN BIT(16) - -static unsigned long byt_rt5640_quirk = BYT_RT5640_DMIC1_MAP | - BYT_RT5640_DMIC_EN; - -static const struct snd_kcontrol_new byt_rt5640_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Internal Mic"), - SOC_DAPM_PIN_SWITCH("Speaker"), -}; - -static int byt_rt5640_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, - params_rate(params) * 256, - SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(codec_dai->dev, "can't set codec clock %d\n", ret); - return ret; - } - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, - params_rate(params) * 64, - params_rate(params) * 256); - if (ret < 0) { - dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret); - return ret; - } - return 0; -} - -static int byt_rt5640_quirk_cb(const struct dmi_system_id *id) -{ - byt_rt5640_quirk = (unsigned long)id->driver_data; - return 1; -} - -static const struct dmi_system_id byt_rt5640_quirk_table[] = { - { - .callback = byt_rt5640_quirk_cb, - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), - }, - .driver_data = (unsigned long *)BYT_RT5640_IN1_MAP, - }, - { - .callback = byt_rt5640_quirk_cb, - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "DellInc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"), - }, - .driver_data = (unsigned long *)(BYT_RT5640_DMIC2_MAP | - BYT_RT5640_DMIC_EN), - }, - {} -}; - -static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) -{ - int ret; - struct snd_soc_codec *codec = runtime->codec; - struct snd_soc_card *card = runtime->card; - const struct snd_soc_dapm_route *custom_map; - int num_routes; - - card->dapm.idle_bias_off = true; - - ret = snd_soc_add_card_controls(card, byt_rt5640_controls, - ARRAY_SIZE(byt_rt5640_controls)); - if (ret) { - dev_err(card->dev, "unable to add card controls\n"); - return ret; - } - - dmi_check_system(byt_rt5640_quirk_table); - switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { - case BYT_RT5640_IN1_MAP: - custom_map = byt_rt5640_intmic_in1_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map); - break; - case BYT_RT5640_DMIC2_MAP: - custom_map = byt_rt5640_intmic_dmic2_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map); - break; - default: - custom_map = byt_rt5640_intmic_dmic1_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); - } - - ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); - if (ret) - return ret; - - if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) { - ret = rt5640_dmic_enable(codec, 0, 0); - if (ret) - return ret; - } - - snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); - snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); - - return ret; -} - -static struct snd_soc_ops byt_rt5640_ops = { - .hw_params = byt_rt5640_hw_params, -}; - -static struct snd_soc_dai_link byt_rt5640_dais[] = { - { - .name = "Baytrail Audio", - .stream_name = "Audio", - .cpu_dai_name = "baytrail-pcm-audio", - .codec_dai_name = "rt5640-aif1", - .codec_name = "i2c-10EC5640:00", - .platform_name = "baytrail-pcm-audio", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .init = byt_rt5640_init, - .ops = &byt_rt5640_ops, - }, -}; - -static struct snd_soc_card byt_rt5640_card = { - .name = "byt-rt5640", - .dai_link = byt_rt5640_dais, - .num_links = ARRAY_SIZE(byt_rt5640_dais), - .dapm_widgets = byt_rt5640_widgets, - .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets), - .dapm_routes = byt_rt5640_audio_map, - .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map), - .fully_routed = true, -}; - -static int byt_rt5640_probe(struct platform_device *pdev) -{ - struct snd_soc_card *card = &byt_rt5640_card; - - card->dev = &pdev->dev; - return devm_snd_soc_register_card(&pdev->dev, card); -} - -static struct platform_driver byt_rt5640_audio = { - .probe = byt_rt5640_probe, - .driver = { - .name = "byt-rt5640", - .pm = &snd_soc_pm_ops, - }, -}; -module_platform_driver(byt_rt5640_audio) - -MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); -MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:byt-rt5640"); diff --git a/sound/soc/intel/bytcr_dpcm_rt5640.c b/sound/soc/intel/bytcr_dpcm_rt5640.c deleted file mode 100644 index 3b262d0..0000000 --- a/sound/soc/intel/bytcr_dpcm_rt5640.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * byt_cr_dpcm_rt5640.c - ASoc Machine driver for Intel Byt CR platform - * - * Copyright (C) 2014 Intel Corp - * Author: Subhransu S. Prusty - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include "../codecs/rt5640.h" -#include "sst-atom-controls.h" - -static const struct snd_soc_dapm_widget byt_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), -}; - -static const struct snd_soc_dapm_route byt_audio_map[] = { - {"IN2P", NULL, "Headset Mic"}, - {"IN2N", NULL, "Headset Mic"}, - {"Headset Mic", NULL, "MICBIAS1"}, - {"IN1P", NULL, "MICBIAS1"}, - {"LDO2", NULL, "Int Mic"}, - {"Headphone", NULL, "HPOL"}, - {"Headphone", NULL, "HPOR"}, - {"Ext Spk", NULL, "SPOLP"}, - {"Ext Spk", NULL, "SPOLN"}, - {"Ext Spk", NULL, "SPORP"}, - {"Ext Spk", NULL, "SPORN"}, - - {"AIF1 Playback", NULL, "ssp2 Tx"}, - {"ssp2 Tx", NULL, "codec_out0"}, - {"ssp2 Tx", NULL, "codec_out1"}, - {"codec_in0", NULL, "ssp2 Rx"}, - {"codec_in1", NULL, "ssp2 Rx"}, - {"ssp2 Rx", NULL, "AIF1 Capture"}, -}; - -static const struct snd_kcontrol_new byt_mc_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Int Mic"), - SOC_DAPM_PIN_SWITCH("Ext Spk"), -}; - -static int byt_aif1_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - snd_soc_dai_set_bclk_ratio(codec_dai, 50); - - ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, - params_rate(params) * 512, - SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(rtd->dev, "can't set codec clock %d\n", ret); - return ret; - } - - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, - params_rate(params) * 50, - params_rate(params) * 512); - if (ret < 0) { - dev_err(rtd->dev, "can't set codec pll: %d\n", ret); - return ret; - } - - return 0; -} - -static const struct snd_soc_pcm_stream byt_dai_params = { - .formats = SNDRV_PCM_FMTBIT_S24_LE, - .rate_min = 48000, - .rate_max = 48000, - .channels_min = 2, - .channels_max = 2, -}; - -static int byt_codec_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The DSP will covert the FE rate to 48k, stereo, 24bits */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP2 to 24-bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); - return 0; -} - -static unsigned int rates_48000[] = { - 48000, -}; - -static struct snd_pcm_hw_constraint_list constraints_48000 = { - .count = ARRAY_SIZE(rates_48000), - .list = rates_48000, -}; - -static int byt_aif1_startup(struct snd_pcm_substream *substream) -{ - return snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, - &constraints_48000); -} - -static struct snd_soc_ops byt_aif1_ops = { - .startup = byt_aif1_startup, -}; - -static struct snd_soc_ops byt_be_ssp2_ops = { - .hw_params = byt_aif1_hw_params, -}; - -static struct snd_soc_dai_link byt_dailink[] = { - [MERR_DPCM_AUDIO] = { - .name = "Baytrail Audio Port", - .stream_name = "Baytrail Audio", - .cpu_dai_name = "media-cpu-dai", - .codec_dai_name = "snd-soc-dummy-dai", - .codec_name = "snd-soc-dummy", - .platform_name = "sst-mfld-platform", - .ignore_suspend = 1, - .dynamic = 1, - .dpcm_playback = 1, - .dpcm_capture = 1, - .ops = &byt_aif1_ops, - }, - [MERR_DPCM_COMPR] = { - .name = "Baytrail Compressed Port", - .stream_name = "Baytrail Compress", - .cpu_dai_name = "compress-cpu-dai", - .codec_dai_name = "snd-soc-dummy-dai", - .codec_name = "snd-soc-dummy", - .platform_name = "sst-mfld-platform", - }, - /* back ends */ - { - .name = "SSP2-Codec", - .be_id = 1, - .cpu_dai_name = "ssp2-port", - .platform_name = "sst-mfld-platform", - .no_pcm = 1, - .codec_dai_name = "rt5640-aif1", - .codec_name = "i2c-10EC5640:00", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, - .be_hw_params_fixup = byt_codec_fixup, - .ignore_suspend = 1, - .dpcm_playback = 1, - .dpcm_capture = 1, - .ops = &byt_be_ssp2_ops, - }, -}; - -/* SoC card */ -static struct snd_soc_card snd_soc_card_byt = { - .name = "baytrailcraudio", - .dai_link = byt_dailink, - .num_links = ARRAY_SIZE(byt_dailink), - .dapm_widgets = byt_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(byt_dapm_widgets), - .dapm_routes = byt_audio_map, - .num_dapm_routes = ARRAY_SIZE(byt_audio_map), - .controls = byt_mc_controls, - .num_controls = ARRAY_SIZE(byt_mc_controls), -}; - -static int snd_byt_mc_probe(struct platform_device *pdev) -{ - int ret_val = 0; - - /* register the soc card */ - snd_soc_card_byt.dev = &pdev->dev; - - ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_byt); - if (ret_val) { - dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ret_val); - return ret_val; - } - platform_set_drvdata(pdev, &snd_soc_card_byt); - return ret_val; -} - -static struct platform_driver snd_byt_mc_driver = { - .driver = { - .name = "bytt100_rt5640", - .pm = &snd_soc_pm_ops, - }, - .probe = snd_byt_mc_probe, -}; - -module_platform_driver(snd_byt_mc_driver); - -MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); -MODULE_AUTHOR("Subhransu S. Prusty "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:bytt100_rt5640"); diff --git a/sound/soc/intel/cht_bsw_rt5645.c b/sound/soc/intel/cht_bsw_rt5645.c deleted file mode 100644 index 0122279..0000000 --- a/sound/soc/intel/cht_bsw_rt5645.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * cht-bsw-rt5645.c - ASoc Machine driver for Intel Cherryview-based platforms - * Cherrytrail and Braswell, with RT5645 codec. - * - * Copyright (C) 2015 Intel Corp - * Author: Fang, Yang A - * N,Harshapriya - * This file is modified from cht_bsw_rt5672.c - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include "../codecs/rt5645.h" -#include "sst-atom-controls.h" - -#define CHT_PLAT_CLK_3_HZ 19200000 -#define CHT_CODEC_DAI "rt5645-aif1" - -struct cht_mc_private { - struct snd_soc_jack hp_jack; - struct snd_soc_jack mic_jack; -}; - -static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) -{ - int i; - - for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_pcm_runtime *rtd; - - rtd = card->rtd + i; - if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, - strlen(CHT_CODEC_DAI))) - return rtd->codec_dai; - } - return NULL; -} - -static int platform_clock_control(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_soc_dapm_context *dapm = w->dapm; - struct snd_soc_card *card = dapm->card; - struct snd_soc_dai *codec_dai; - int ret; - - codec_dai = cht_get_codec_dai(card); - if (!codec_dai) { - dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); - return -EIO; - } - - if (!SND_SOC_DAPM_EVENT_OFF(event)) - return 0; - - /* Set codec sysclk source to its internal clock because codec PLL will - * be off when idle and MCLK will also be off by ACPI when codec is - * runtime suspended. Codec needs clock for jack detection and button - * press. - */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK, - 0, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(card->dev, "can't set codec sysclk: %d\n", ret); - return ret; - } - - return 0; -} - -static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), - SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, - platform_clock_control, SND_SOC_DAPM_POST_PMD), -}; - -static const struct snd_soc_dapm_route cht_audio_map[] = { - {"IN1P", NULL, "Headset Mic"}, - {"IN1N", NULL, "Headset Mic"}, - {"DMIC L1", NULL, "Int Mic"}, - {"DMIC R1", NULL, "Int Mic"}, - {"Headphone", NULL, "HPOL"}, - {"Headphone", NULL, "HPOR"}, - {"Ext Spk", NULL, "SPOL"}, - {"Ext Spk", NULL, "SPOR"}, - {"AIF1 Playback", NULL, "ssp2 Tx"}, - {"ssp2 Tx", NULL, "codec_out0"}, - {"ssp2 Tx", NULL, "codec_out1"}, - {"codec_in0", NULL, "ssp2 Rx" }, - {"codec_in1", NULL, "ssp2 Rx" }, - {"ssp2 Rx", NULL, "AIF1 Capture"}, - {"Headphone", NULL, "Platform Clock"}, - {"Headset Mic", NULL, "Platform Clock"}, - {"Int Mic", NULL, "Platform Clock"}, - {"Ext Spk", NULL, "Platform Clock"}, -}; - -static const struct snd_kcontrol_new cht_mc_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Int Mic"), - SOC_DAPM_PIN_SWITCH("Ext Spk"), -}; - -static int cht_aif1_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK, - CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); - if (ret < 0) { - dev_err(rtd->dev, "can't set codec pll: %d\n", ret); - return ret; - } - - ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, - params_rate(params) * 512, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); - return ret; - } - - return 0; -} - -static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) -{ - int ret; - struct snd_soc_codec *codec = runtime->codec; - struct snd_soc_dai *codec_dai = runtime->codec_dai; - struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); - - /* Select clk_i2s1_asrc as ASRC clock source */ - rt5645_sel_asrc_clk_src(codec, - RT5645_DA_STEREO_FILTER | - RT5645_DA_MONO_L_FILTER | - RT5645_DA_MONO_R_FILTER | - RT5645_AD_STEREO_FILTER, - RT5645_CLK_SEL_I2S1_ASRC); - - /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); - if (ret < 0) { - dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); - return ret; - } - - ret = snd_soc_card_jack_new(runtime->card, "Headphone Jack", - SND_JACK_HEADPHONE, &ctx->hp_jack, - NULL, 0); - if (ret) { - dev_err(runtime->dev, "HP jack creation failed %d\n", ret); - return ret; - } - - ret = snd_soc_card_jack_new(runtime->card, "Mic Jack", - SND_JACK_MICROPHONE, &ctx->mic_jack, - NULL, 0); - if (ret) { - dev_err(runtime->dev, "Mic jack creation failed %d\n", ret); - return ret; - } - - rt5645_set_jack_detect(codec, &ctx->hp_jack, &ctx->mic_jack); - - return ret; -} - -static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The DSP will covert the FE rate to 48k, stereo, 24bits */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP2 to 24-bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); - return 0; -} - -static unsigned int rates_48000[] = { - 48000, -}; - -static struct snd_pcm_hw_constraint_list constraints_48000 = { - .count = ARRAY_SIZE(rates_48000), - .list = rates_48000, -}; - -static int cht_aif1_startup(struct snd_pcm_substream *substream) -{ - return snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, - &constraints_48000); -} - -static struct snd_soc_ops cht_aif1_ops = { - .startup = cht_aif1_startup, -}; - -static struct snd_soc_ops cht_be_ssp2_ops = { - .hw_params = cht_aif1_hw_params, -}; - -static struct snd_soc_dai_link cht_dailink[] = { - [MERR_DPCM_AUDIO] = { - .name = "Audio Port", - .stream_name = "Audio", - .cpu_dai_name = "media-cpu-dai", - .codec_dai_name = "snd-soc-dummy-dai", - .codec_name = "snd-soc-dummy", - .platform_name = "sst-mfld-platform", - .ignore_suspend = 1, - .dynamic = 1, - .dpcm_playback = 1, - .dpcm_capture = 1, - .ops = &cht_aif1_ops, - }, - [MERR_DPCM_COMPR] = { - .name = "Compressed Port", - .stream_name = "Compress", - .cpu_dai_name = "compress-cpu-dai", - .codec_dai_name = "snd-soc-dummy-dai", - .codec_name = "snd-soc-dummy", - .platform_name = "sst-mfld-platform", - }, - /* CODEC<->CODEC link */ - /* back ends */ - { - .name = "SSP2-Codec", - .be_id = 1, - .cpu_dai_name = "ssp2-port", - .platform_name = "sst-mfld-platform", - .no_pcm = 1, - .codec_dai_name = "rt5645-aif1", - .codec_name = "i2c-10EC5645:00", - .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF - | SND_SOC_DAIFMT_CBS_CFS, - .init = cht_codec_init, - .be_hw_params_fixup = cht_codec_fixup, - .ignore_suspend = 1, - .dpcm_playback = 1, - .dpcm_capture = 1, - .ops = &cht_be_ssp2_ops, - }, -}; - -/* SoC card */ -static struct snd_soc_card snd_soc_card_cht = { - .name = "chtrt5645", - .dai_link = cht_dailink, - .num_links = ARRAY_SIZE(cht_dailink), - .dapm_widgets = cht_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), - .dapm_routes = cht_audio_map, - .num_dapm_routes = ARRAY_SIZE(cht_audio_map), - .controls = cht_mc_controls, - .num_controls = ARRAY_SIZE(cht_mc_controls), -}; - -static int snd_cht_mc_probe(struct platform_device *pdev) -{ - int ret_val = 0; - struct cht_mc_private *drv; - - drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); - if (!drv) - return -ENOMEM; - - snd_soc_card_cht.dev = &pdev->dev; - snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); - ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); - if (ret_val) { - dev_err(&pdev->dev, - "snd_soc_register_card failed %d\n", ret_val); - return ret_val; - } - platform_set_drvdata(pdev, &snd_soc_card_cht); - return ret_val; -} - -static struct platform_driver snd_cht_mc_driver = { - .driver = { - .name = "cht-bsw-rt5645", - .pm = &snd_soc_pm_ops, - }, - .probe = snd_cht_mc_probe, -}; - -module_platform_driver(snd_cht_mc_driver) - -MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver"); -MODULE_AUTHOR("Fang, Yang A,N,Harshapriya"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:cht-bsw-rt5645"); diff --git a/sound/soc/intel/cht_bsw_rt5672.c b/sound/soc/intel/cht_bsw_rt5672.c deleted file mode 100644 index 4204fc4..0000000 --- a/sound/soc/intel/cht_bsw_rt5672.c +++ /dev/null @@ -1,366 +0,0 @@ -/* - * cht_bsw_rt5672.c - ASoc Machine driver for Intel Cherryview-based platforms - * Cherrytrail and Braswell, with RT5672 codec. - * - * Copyright (C) 2014 Intel Corp - * Author: Subhransu S. Prusty - * Mengdong Lin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include "../codecs/rt5670.h" -#include "sst-atom-controls.h" - -/* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */ -#define CHT_PLAT_CLK_3_HZ 19200000 -#define CHT_CODEC_DAI "rt5670-aif1" - -static struct snd_soc_jack cht_bsw_headset; - -/* Headset jack detection DAPM pins */ -static struct snd_soc_jack_pin cht_bsw_headset_pins[] = { - { - .pin = "Headset Mic", - .mask = SND_JACK_MICROPHONE, - }, - { - .pin = "Headphone", - .mask = SND_JACK_HEADPHONE, - }, -}; - -static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) -{ - int i; - - for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_pcm_runtime *rtd; - - rtd = card->rtd + i; - if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, - strlen(CHT_CODEC_DAI))) - return rtd->codec_dai; - } - return NULL; -} - -static int platform_clock_control(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_soc_dapm_context *dapm = w->dapm; - struct snd_soc_card *card = dapm->card; - struct snd_soc_dai *codec_dai; - int ret; - - codec_dai = cht_get_codec_dai(card); - if (!codec_dai) { - dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); - return -EIO; - } - - if (SND_SOC_DAPM_EVENT_ON(event)) { - /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, - CHT_PLAT_CLK_3_HZ, 48000 * 512); - if (ret < 0) { - dev_err(card->dev, "can't set codec pll: %d\n", ret); - return ret; - } - - /* set codec sysclk source to PLL */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, - 48000 * 512, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(card->dev, "can't set codec sysclk: %d\n", ret); - return ret; - } - } else { - /* Set codec sysclk source to its internal clock because codec - * PLL will be off when idle and MCLK will also be off by ACPI - * when codec is runtime suspended. Codec needs clock for jack - * detection and button press. - */ - snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK, - 48000 * 512, SND_SOC_CLOCK_IN); - } - return 0; -} - -static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), - SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, - platform_clock_control, SND_SOC_DAPM_PRE_PMU | - SND_SOC_DAPM_POST_PMD), -}; - -static const struct snd_soc_dapm_route cht_audio_map[] = { - {"IN1P", NULL, "Headset Mic"}, - {"IN1N", NULL, "Headset Mic"}, - {"DMIC L1", NULL, "Int Mic"}, - {"DMIC R1", NULL, "Int Mic"}, - {"Headphone", NULL, "HPOL"}, - {"Headphone", NULL, "HPOR"}, - {"Ext Spk", NULL, "SPOLP"}, - {"Ext Spk", NULL, "SPOLN"}, - {"Ext Spk", NULL, "SPORP"}, - {"Ext Spk", NULL, "SPORN"}, - {"AIF1 Playback", NULL, "ssp2 Tx"}, - {"ssp2 Tx", NULL, "codec_out0"}, - {"ssp2 Tx", NULL, "codec_out1"}, - {"codec_in0", NULL, "ssp2 Rx"}, - {"codec_in1", NULL, "ssp2 Rx"}, - {"ssp2 Rx", NULL, "AIF1 Capture"}, - {"Headphone", NULL, "Platform Clock"}, - {"Headset Mic", NULL, "Platform Clock"}, - {"Int Mic", NULL, "Platform Clock"}, - {"Ext Spk", NULL, "Platform Clock"}, -}; - -static const struct snd_kcontrol_new cht_mc_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Int Mic"), - SOC_DAPM_PIN_SWITCH("Ext Spk"), -}; - -static int cht_aif1_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, - CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); - if (ret < 0) { - dev_err(rtd->dev, "can't set codec pll: %d\n", ret); - return ret; - } - - /* set codec sysclk source to PLL */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, - params_rate(params) * 512, - SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); - return ret; - } - return 0; -} - -static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) -{ - int ret; - struct snd_soc_dai *codec_dai = runtime->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - - /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); - if (ret < 0) { - dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); - return ret; - } - - /* Select codec ASRC clock source to track I2S1 clock, because codec - * is in slave mode and 100fs I2S format (BCLK = 100 * LRCLK) cannot - * be supported by RT5672. Otherwise, ASRC will be disabled and cause - * noise. - */ - rt5670_sel_asrc_clk_src(codec, - RT5670_DA_STEREO_FILTER - | RT5670_DA_MONO_L_FILTER - | RT5670_DA_MONO_R_FILTER - | RT5670_AD_STEREO_FILTER - | RT5670_AD_MONO_L_FILTER - | RT5670_AD_MONO_R_FILTER, - RT5670_CLK_SEL_I2S1_ASRC); - - ret = snd_soc_card_jack_new(runtime->card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0 | - SND_JACK_BTN_1 | SND_JACK_BTN_2, &cht_bsw_headset, - cht_bsw_headset_pins, ARRAY_SIZE(cht_bsw_headset_pins)); - if (ret) - return ret; - - rt5670_set_jack_detect(codec, &cht_bsw_headset); - return 0; -} - -static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The DSP will covert the FE rate to 48k, stereo, 24bits */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP2 to 24-bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); - return 0; -} - -static unsigned int rates_48000[] = { - 48000, -}; - -static struct snd_pcm_hw_constraint_list constraints_48000 = { - .count = ARRAY_SIZE(rates_48000), - .list = rates_48000, -}; - -static int cht_aif1_startup(struct snd_pcm_substream *substream) -{ - return snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, - &constraints_48000); -} - -static struct snd_soc_ops cht_aif1_ops = { - .startup = cht_aif1_startup, -}; - -static struct snd_soc_ops cht_be_ssp2_ops = { - .hw_params = cht_aif1_hw_params, -}; - -static struct snd_soc_dai_link cht_dailink[] = { - /* Front End DAI links */ - [MERR_DPCM_AUDIO] = { - .name = "Audio Port", - .stream_name = "Audio", - .cpu_dai_name = "media-cpu-dai", - .codec_dai_name = "snd-soc-dummy-dai", - .codec_name = "snd-soc-dummy", - .platform_name = "sst-mfld-platform", - .nonatomic = true, - .dynamic = 1, - .dpcm_playback = 1, - .dpcm_capture = 1, - .ops = &cht_aif1_ops, - }, - [MERR_DPCM_COMPR] = { - .name = "Compressed Port", - .stream_name = "Compress", - .cpu_dai_name = "compress-cpu-dai", - .codec_dai_name = "snd-soc-dummy-dai", - .codec_name = "snd-soc-dummy", - .platform_name = "sst-mfld-platform", - }, - - /* Back End DAI links */ - { - /* SSP2 - Codec */ - .name = "SSP2-Codec", - .be_id = 1, - .cpu_dai_name = "ssp2-port", - .platform_name = "sst-mfld-platform", - .no_pcm = 1, - .nonatomic = true, - .codec_dai_name = "rt5670-aif1", - .codec_name = "i2c-10EC5670:00", - .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF - | SND_SOC_DAIFMT_CBS_CFS, - .init = cht_codec_init, - .be_hw_params_fixup = cht_codec_fixup, - .dpcm_playback = 1, - .dpcm_capture = 1, - .ops = &cht_be_ssp2_ops, - }, -}; - -static int cht_suspend_pre(struct snd_soc_card *card) -{ - struct snd_soc_codec *codec; - - list_for_each_entry(codec, &card->codec_dev_list, card_list) { - if (!strcmp(codec->component.name, "i2c-10EC5670:00")) { - dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n"); - rt5670_jack_suspend(codec); - break; - } - } - return 0; -} - -static int cht_resume_post(struct snd_soc_card *card) -{ - struct snd_soc_codec *codec; - - list_for_each_entry(codec, &card->codec_dev_list, card_list) { - if (!strcmp(codec->component.name, "i2c-10EC5670:00")) { - dev_dbg(codec->dev, "enabling jack detect for resume.\n"); - rt5670_jack_resume(codec); - break; - } - } - - return 0; -} - -/* SoC card */ -static struct snd_soc_card snd_soc_card_cht = { - .name = "cherrytrailcraudio", - .dai_link = cht_dailink, - .num_links = ARRAY_SIZE(cht_dailink), - .dapm_widgets = cht_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), - .dapm_routes = cht_audio_map, - .num_dapm_routes = ARRAY_SIZE(cht_audio_map), - .controls = cht_mc_controls, - .num_controls = ARRAY_SIZE(cht_mc_controls), - .suspend_pre = cht_suspend_pre, - .resume_post = cht_resume_post, -}; - -static int snd_cht_mc_probe(struct platform_device *pdev) -{ - int ret_val = 0; - - /* register the soc card */ - snd_soc_card_cht.dev = &pdev->dev; - ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); - if (ret_val) { - dev_err(&pdev->dev, - "snd_soc_register_card failed %d\n", ret_val); - return ret_val; - } - platform_set_drvdata(pdev, &snd_soc_card_cht); - return ret_val; -} - -static struct platform_driver snd_cht_mc_driver = { - .driver = { - .name = "cht-bsw-rt5672", - }, - .probe = snd_cht_mc_probe, -}; - -module_platform_driver(snd_cht_mc_driver); - -MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); -MODULE_AUTHOR("Subhransu S. Prusty, Mengdong Lin"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:cht-bsw-rt5672"); diff --git a/sound/soc/intel/haswell.c b/sound/soc/intel/haswell.c deleted file mode 100644 index 00fddd3..0000000 --- a/sound/soc/intel/haswell.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Intel Haswell Lynxpoint SST Audio - * - * Copyright (C) 2013, Intel Corporation. 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 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 -#include -#include -#include -#include -#include - -#include "sst-dsp.h" -#include "sst-haswell-ipc.h" - -#include "../codecs/rt5640.h" - -/* Haswell ULT platforms have a Headphone and Mic jack */ -static const struct snd_soc_dapm_widget haswell_widgets[] = { - SND_SOC_DAPM_HP("Headphones", NULL), - SND_SOC_DAPM_MIC("Mic", NULL), -}; - -static const struct snd_soc_dapm_route haswell_rt5640_map[] = { - - {"Headphones", NULL, "HPOR"}, - {"Headphones", NULL, "HPOL"}, - {"IN2P", NULL, "Mic"}, - - /* CODEC BE connections */ - {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, - {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, -}; - -static int haswell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The ADSP will covert the FE rate to 48k, stereo */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP0 to 16 bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); - return 0; -} - -static int haswell_rt5640_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, - SND_SOC_CLOCK_IN); - - if (ret < 0) { - dev_err(rtd->dev, "can't set codec sysclk configuration\n"); - return ret; - } - - /* set correct codec filter for DAI format and clock config */ - snd_soc_update_bits(rtd->codec, 0x83, 0xffff, 0x8000); - - return ret; -} - -static struct snd_soc_ops haswell_rt5640_ops = { - .hw_params = haswell_rt5640_hw_params, -}; - -static int haswell_rtd_init(struct snd_soc_pcm_runtime *rtd) -{ - struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev); - struct sst_hsw *haswell = pdata->dsp; - int ret; - - /* Set ADSP SSP port settings */ - ret = sst_hsw_device_set_config(haswell, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_CLOCK_MASTER, 9); - if (ret < 0) { - dev_err(rtd->dev, "failed to set device config\n"); - return ret; - } - - return 0; -} - -static struct snd_soc_dai_link haswell_rt5640_dais[] = { - /* Front End DAI links */ - { - .name = "System", - .stream_name = "System Playback/Capture", - .cpu_dai_name = "System Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .init = haswell_rtd_init, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - .dpcm_capture = 1, - }, - { - .name = "Offload0", - .stream_name = "Offload0 Playback", - .cpu_dai_name = "Offload0 Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - }, - { - .name = "Offload1", - .stream_name = "Offload1 Playback", - .cpu_dai_name = "Offload1 Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - }, - { - .name = "Loopback", - .stream_name = "Loopback", - .cpu_dai_name = "Loopback Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 0, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - }, - - /* Back End DAI links */ - { - /* SSP0 - Codec */ - .name = "Codec", - .be_id = 0, - .cpu_dai_name = "snd-soc-dummy-dai", - .platform_name = "snd-soc-dummy", - .no_pcm = 1, - .codec_name = "i2c-INT33CA:00", - .codec_dai_name = "rt5640-aif1", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .ignore_suspend = 1, - .ignore_pmdown_time = 1, - .be_hw_params_fixup = haswell_ssp0_fixup, - .ops = &haswell_rt5640_ops, - .dpcm_playback = 1, - .dpcm_capture = 1, - }, -}; - -/* audio machine driver for Haswell Lynxpoint DSP + RT5640 */ -static struct snd_soc_card haswell_rt5640 = { - .name = "haswell-rt5640", - .owner = THIS_MODULE, - .dai_link = haswell_rt5640_dais, - .num_links = ARRAY_SIZE(haswell_rt5640_dais), - .dapm_widgets = haswell_widgets, - .num_dapm_widgets = ARRAY_SIZE(haswell_widgets), - .dapm_routes = haswell_rt5640_map, - .num_dapm_routes = ARRAY_SIZE(haswell_rt5640_map), - .fully_routed = true, -}; - -static int haswell_audio_probe(struct platform_device *pdev) -{ - haswell_rt5640.dev = &pdev->dev; - - return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640); -} - -static struct platform_driver haswell_audio = { - .probe = haswell_audio_probe, - .driver = { - .name = "haswell-audio", - }, -}; - -module_platform_driver(haswell_audio) - -/* Module information */ -MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); -MODULE_DESCRIPTION("Intel SST Audio for Haswell Lynxpoint"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:haswell-audio"); diff --git a/sound/soc/intel/mfld_machine.c b/sound/soc/intel/mfld_machine.c deleted file mode 100644 index 49c09a0..0000000 --- a/sound/soc/intel/mfld_machine.c +++ /dev/null @@ -1,430 +0,0 @@ -/* - * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform - * - * Copyright (C) 2010 Intel Corp - * Author: Vinod Koul - * Author: Harsha Priya - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../codecs/sn95031.h" - -#define MID_MONO 1 -#define MID_STEREO 2 -#define MID_MAX_CAP 5 -#define MFLD_JACK_INSERT 0x04 - -enum soc_mic_bias_zones { - MFLD_MV_START = 0, - /* mic bias volutage range for Headphones*/ - MFLD_MV_HP = 400, - /* mic bias volutage range for American Headset*/ - MFLD_MV_AM_HS = 650, - /* mic bias volutage range for Headset*/ - MFLD_MV_HS = 2000, - MFLD_MV_UNDEFINED, -}; - -static unsigned int hs_switch; -static unsigned int lo_dac; -static struct snd_soc_codec *mfld_codec; - -struct mfld_mc_private { - void __iomem *int_base; - u8 interrupt_status; -}; - -struct snd_soc_jack mfld_jack; - -/*Headset jack detection DAPM pins */ -static struct snd_soc_jack_pin mfld_jack_pins[] = { - { - .pin = "Headphones", - .mask = SND_JACK_HEADPHONE, - }, - { - .pin = "AMIC1", - .mask = SND_JACK_MICROPHONE, - }, -}; - -/* jack detection voltage zones */ -static struct snd_soc_jack_zone mfld_zones[] = { - {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE}, - {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET}, -}; - -/* sound card controls */ -static const char *headset_switch_text[] = {"Earpiece", "Headset"}; - -static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"}; - -static const struct soc_enum headset_enum = - SOC_ENUM_SINGLE_EXT(2, headset_switch_text); - -static const struct soc_enum lo_enum = - SOC_ENUM_SINGLE_EXT(4, lo_text); - -static int headset_get_switch(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = hs_switch; - return 0; -} - -static int headset_set_switch(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct snd_soc_dapm_context *dapm = &card->dapm; - - if (ucontrol->value.integer.value[0] == hs_switch) - return 0; - - snd_soc_dapm_mutex_lock(dapm); - - if (ucontrol->value.integer.value[0]) { - pr_debug("hs_set HS path\n"); - snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones"); - snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); - } else { - pr_debug("hs_set EP path\n"); - snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); - snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT"); - } - - snd_soc_dapm_sync_unlocked(dapm); - - snd_soc_dapm_mutex_unlock(dapm); - - hs_switch = ucontrol->value.integer.value[0]; - - return 0; -} - -static void lo_enable_out_pins(struct snd_soc_dapm_context *dapm) -{ - snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTL"); - snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTR"); - snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTL"); - snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTR"); - snd_soc_dapm_enable_pin_unlocked(dapm, "VIB1OUT"); - snd_soc_dapm_enable_pin_unlocked(dapm, "VIB2OUT"); - if (hs_switch) { - snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones"); - snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); - } else { - snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); - snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT"); - } -} - -static int lo_get_switch(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = lo_dac; - return 0; -} - -static int lo_set_switch(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct snd_soc_dapm_context *dapm = &card->dapm; - - if (ucontrol->value.integer.value[0] == lo_dac) - return 0; - - snd_soc_dapm_mutex_lock(dapm); - - /* we dont want to work with last state of lineout so just enable all - * pins and then disable pins not required - */ - lo_enable_out_pins(dapm); - - switch (ucontrol->value.integer.value[0]) { - case 0: - pr_debug("set vibra path\n"); - snd_soc_dapm_disable_pin_unlocked(dapm, "VIB1OUT"); - snd_soc_dapm_disable_pin_unlocked(dapm, "VIB2OUT"); - snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0); - break; - - case 1: - pr_debug("set hs path\n"); - snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); - snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); - snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x22); - break; - - case 2: - pr_debug("set spkr path\n"); - snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTL"); - snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTR"); - snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x44); - break; - - case 3: - pr_debug("set null path\n"); - snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTL"); - snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTR"); - snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x66); - break; - } - - snd_soc_dapm_sync_unlocked(dapm); - - snd_soc_dapm_mutex_unlock(dapm); - - lo_dac = ucontrol->value.integer.value[0]; - return 0; -} - -static const struct snd_kcontrol_new mfld_snd_controls[] = { - SOC_ENUM_EXT("Playback Switch", headset_enum, - headset_get_switch, headset_set_switch), - SOC_ENUM_EXT("Lineout Mux", lo_enum, - lo_get_switch, lo_set_switch), -}; - -static const struct snd_soc_dapm_widget mfld_widgets[] = { - SND_SOC_DAPM_HP("Headphones", NULL), - SND_SOC_DAPM_MIC("Mic", NULL), -}; - -static const struct snd_soc_dapm_route mfld_map[] = { - {"Headphones", NULL, "HPOUTR"}, - {"Headphones", NULL, "HPOUTL"}, - {"Mic", NULL, "AMIC1"}, -}; - -static void mfld_jack_check(unsigned int intr_status) -{ - struct mfld_jack_data jack_data; - - if (!mfld_codec) - return; - - jack_data.mfld_jack = &mfld_jack; - jack_data.intr_id = intr_status; - - sn95031_jack_detection(mfld_codec, &jack_data); - /* TODO: add american headset detection post gpiolib support */ -} - -static int mfld_init(struct snd_soc_pcm_runtime *runtime) -{ - struct snd_soc_dapm_context *dapm = &runtime->card->dapm; - int ret_val; - - /* default is earpiece pin, userspace sets it explcitly */ - snd_soc_dapm_disable_pin(dapm, "Headphones"); - /* default is lineout NC, userspace sets it explcitly */ - snd_soc_dapm_disable_pin(dapm, "LINEOUTL"); - snd_soc_dapm_disable_pin(dapm, "LINEOUTR"); - lo_dac = 3; - hs_switch = 0; - /* we dont use linein in this so set to NC */ - snd_soc_dapm_disable_pin(dapm, "LINEINL"); - snd_soc_dapm_disable_pin(dapm, "LINEINR"); - - /* Headset and button jack detection */ - ret_val = snd_soc_card_jack_new(runtime->card, - "Intel(R) MID Audio Jack", SND_JACK_HEADSET | - SND_JACK_BTN_0 | SND_JACK_BTN_1, &mfld_jack, - mfld_jack_pins, ARRAY_SIZE(mfld_jack_pins)); - if (ret_val) { - pr_err("jack creation failed\n"); - return ret_val; - } - - ret_val = snd_soc_jack_add_zones(&mfld_jack, - ARRAY_SIZE(mfld_zones), mfld_zones); - if (ret_val) { - pr_err("adding jack zones failed\n"); - return ret_val; - } - - mfld_codec = runtime->codec; - - /* we want to check if anything is inserted at boot, - * so send a fake event to codec and it will read adc - * to find if anything is there or not */ - mfld_jack_check(MFLD_JACK_INSERT); - return ret_val; -} - -static struct snd_soc_dai_link mfld_msic_dailink[] = { - { - .name = "Medfield Headset", - .stream_name = "Headset", - .cpu_dai_name = "Headset-cpu-dai", - .codec_dai_name = "SN95031 Headset", - .codec_name = "sn95031", - .platform_name = "sst-platform", - .init = mfld_init, - }, - { - .name = "Medfield Speaker", - .stream_name = "Speaker", - .cpu_dai_name = "Speaker-cpu-dai", - .codec_dai_name = "SN95031 Speaker", - .codec_name = "sn95031", - .platform_name = "sst-platform", - .init = NULL, - }, - { - .name = "Medfield Vibra", - .stream_name = "Vibra1", - .cpu_dai_name = "Vibra1-cpu-dai", - .codec_dai_name = "SN95031 Vibra1", - .codec_name = "sn95031", - .platform_name = "sst-platform", - .init = NULL, - }, - { - .name = "Medfield Haptics", - .stream_name = "Vibra2", - .cpu_dai_name = "Vibra2-cpu-dai", - .codec_dai_name = "SN95031 Vibra2", - .codec_name = "sn95031", - .platform_name = "sst-platform", - .init = NULL, - }, - { - .name = "Medfield Compress", - .stream_name = "Speaker", - .cpu_dai_name = "Compress-cpu-dai", - .codec_dai_name = "SN95031 Speaker", - .codec_name = "sn95031", - .platform_name = "sst-platform", - .init = NULL, - }, -}; - -/* SoC card */ -static struct snd_soc_card snd_soc_card_mfld = { - .name = "medfield_audio", - .owner = THIS_MODULE, - .dai_link = mfld_msic_dailink, - .num_links = ARRAY_SIZE(mfld_msic_dailink), - - .controls = mfld_snd_controls, - .num_controls = ARRAY_SIZE(mfld_snd_controls), - .dapm_widgets = mfld_widgets, - .num_dapm_widgets = ARRAY_SIZE(mfld_widgets), - .dapm_routes = mfld_map, - .num_dapm_routes = ARRAY_SIZE(mfld_map), -}; - -static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev) -{ - struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev; - - memcpy_fromio(&mc_private->interrupt_status, - ((void *)(mc_private->int_base)), - sizeof(u8)); - return IRQ_WAKE_THREAD; -} - -static irqreturn_t snd_mfld_jack_detection(int irq, void *data) -{ - struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data; - - mfld_jack_check(mc_drv_ctx->interrupt_status); - - return IRQ_HANDLED; -} - -static int snd_mfld_mc_probe(struct platform_device *pdev) -{ - int ret_val = 0, irq; - struct mfld_mc_private *mc_drv_ctx; - struct resource *irq_mem; - - pr_debug("snd_mfld_mc_probe called\n"); - - /* retrive the irq number */ - irq = platform_get_irq(pdev, 0); - - /* audio interrupt base of SRAM location where - * interrupts are stored by System FW */ - mc_drv_ctx = devm_kzalloc(&pdev->dev, sizeof(*mc_drv_ctx), GFP_ATOMIC); - if (!mc_drv_ctx) { - pr_err("allocation failed\n"); - return -ENOMEM; - } - - irq_mem = platform_get_resource_byname( - pdev, IORESOURCE_MEM, "IRQ_BASE"); - if (!irq_mem) { - pr_err("no mem resource given\n"); - return -ENODEV; - } - mc_drv_ctx->int_base = devm_ioremap_nocache(&pdev->dev, irq_mem->start, - resource_size(irq_mem)); - if (!mc_drv_ctx->int_base) { - pr_err("Mapping of cache failed\n"); - return -ENOMEM; - } - /* register for interrupt */ - ret_val = devm_request_threaded_irq(&pdev->dev, irq, - snd_mfld_jack_intr_handler, - snd_mfld_jack_detection, - IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx); - if (ret_val) { - pr_err("cannot register IRQ\n"); - return ret_val; - } - /* register the soc card */ - snd_soc_card_mfld.dev = &pdev->dev; - ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_mfld); - if (ret_val) { - pr_debug("snd_soc_register_card failed %d\n", ret_val); - return ret_val; - } - platform_set_drvdata(pdev, mc_drv_ctx); - pr_debug("successfully exited probe\n"); - return 0; -} - -static struct platform_driver snd_mfld_mc_driver = { - .driver = { - .name = "msic_audio", - }, - .probe = snd_mfld_mc_probe, -}; - -module_platform_driver(snd_mfld_mc_driver); - -MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver"); -MODULE_AUTHOR("Vinod Koul "); -MODULE_AUTHOR("Harsha Priya "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:msic-audio"); -- cgit v0.10.2 From 66a6fd9846f0aecdbab9324b792b319fd8e95e77 Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Thu, 2 Apr 2015 15:37:03 +0800 Subject: ASoC: Intel: create baytrail folder and move baytrail platform files in Restructure the sound/soc/intel/ directory: create baytrail folder, and move sst baytrail platform files here. Signed-off-by: Jie Yang Acked-by: Jarkko Nikula Tested-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index ac0248f..62de82a 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -10,10 +10,7 @@ obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o # Platform Support obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/ -snd-soc-sst-baytrail-pcm-objs := \ - sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o - -obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o +obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += baytrail/ # Machine support obj-$(CONFIG_SND_SOC_INTEL_SST) += boards/ diff --git a/sound/soc/intel/baytrail/Makefile b/sound/soc/intel/baytrail/Makefile new file mode 100644 index 0000000..488408c --- /dev/null +++ b/sound/soc/intel/baytrail/Makefile @@ -0,0 +1,4 @@ +snd-soc-sst-baytrail-pcm-objs := \ + sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o + +obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o diff --git a/sound/soc/intel/baytrail/sst-baytrail-dsp.c b/sound/soc/intel/baytrail/sst-baytrail-dsp.c new file mode 100644 index 0000000..01d023c --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-dsp.c @@ -0,0 +1,366 @@ +/* + * Intel Baytrail SST DSP driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "sst-baytrail-ipc.h" + +#define SST_BYT_FW_SIGNATURE_SIZE 4 +#define SST_BYT_FW_SIGN "$SST" + +#define SST_BYT_IRAM_OFFSET 0xC0000 +#define SST_BYT_DRAM_OFFSET 0x100000 +#define SST_BYT_SHIM_OFFSET 0x140000 + +enum sst_ram_type { + SST_BYT_IRAM = 1, + SST_BYT_DRAM = 2, + SST_BYT_CACHE = 3, +}; + +struct dma_block_info { + enum sst_ram_type type; /* IRAM/DRAM */ + u32 size; /* Bytes */ + u32 ram_offset; /* Offset in I/DRAM */ + u32 rsvd; /* Reserved field */ +}; + +struct fw_header { + unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; + u32 file_size; /* size of fw minus this header */ + u32 modules; /* # of modules */ + u32 file_format; /* version of header format */ + u32 reserved[4]; +}; + +struct sst_byt_fw_module_header { + unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; + u32 mod_size; /* size of module */ + u32 blocks; /* # of blocks */ + u32 type; /* codec type, pp lib */ + u32 entry_point; +}; + +static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, + struct sst_byt_fw_module_header *module) +{ + struct dma_block_info *block; + struct sst_module *mod; + struct sst_module_template template; + int count; + + memset(&template, 0, sizeof(template)); + template.id = module->type; + template.entry = module->entry_point; + + mod = sst_module_new(fw, &template, NULL); + if (mod == NULL) + return -ENOMEM; + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + + if (block->size <= 0) { + dev_err(dsp->dev, "block %d size invalid\n", count); + return -EINVAL; + } + + switch (block->type) { + case SST_BYT_IRAM: + mod->offset = block->ram_offset + + dsp->addr.iram_offset; + mod->type = SST_MEM_IRAM; + break; + case SST_BYT_DRAM: + mod->offset = block->ram_offset + + dsp->addr.dram_offset; + mod->type = SST_MEM_DRAM; + break; + case SST_BYT_CACHE: + mod->offset = block->ram_offset + + (dsp->addr.fw_ext - dsp->addr.lpe); + mod->type = SST_MEM_CACHE; + break; + default: + dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", + block->type, count); + return -EINVAL; + } + + mod->size = block->size; + mod->data = (void *)block + sizeof(*block); + + sst_module_alloc_blocks(mod); + + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +static int sst_byt_parse_fw_image(struct sst_fw *sst_fw) +{ + struct fw_header *header; + struct sst_byt_fw_module_header *module; + struct sst_dsp *dsp = sst_fw->dsp; + int ret, count; + + /* Read the header information from the data pointer */ + header = (struct fw_header *)sst_fw->dma_buf; + + /* verify FW */ + if ((strncmp(header->signature, SST_BYT_FW_SIGN, 4) != 0) || + (sst_fw->size != header->file_size + sizeof(*header))) { + /* Invalid FW signature */ + dev_err(dsp->dev, "Invalid FW sign/filesize mismatch\n"); + return -EINVAL; + } + + dev_dbg(dsp->dev, + "header sign=%4s size=0x%x modules=0x%x fmt=0x%x size=%zu\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + + module = (void *)sst_fw->dma_buf + sizeof(*header); + for (count = 0; count < header->modules; count++) { + /* module */ + ret = sst_byt_parse_module(dsp, sst_fw, module); + if (ret < 0) { + dev_err(dsp->dev, "invalid module %d\n", count); + return ret; + } + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +static void sst_byt_dump_shim(struct sst_dsp *sst) +{ + int i; + u64 reg; + + for (i = 0; i <= 0xF0; i += 8) { + reg = sst_dsp_shim_read64_unlocked(sst, i); + if (reg) + dev_dbg(sst->dev, "shim 0x%2.2x value 0x%16.16llx\n", + i, reg); + } + + for (i = 0x00; i <= 0xff; i += 4) { + reg = readl(sst->addr.pci_cfg + i); + if (reg) + dev_dbg(sst->dev, "pci 0x%2.2x value 0x%8.8x\n", + i, (u32)reg); + } +} + +static irqreturn_t sst_byt_irq(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + u64 isrx; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&sst->spinlock); + + isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + if (isrx & SST_ISRX_DONE) { + /* ADSP has processed the message request from IA */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX, + SST_BYT_IPCX_DONE, 0); + ret = IRQ_WAKE_THREAD; + } + if (isrx & SST_BYT_ISRX_REQUEST) { + /* mask message request from ADSP and do processing later */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_BYT_IMRX_REQUEST, + SST_BYT_IMRX_REQUEST); + ret = IRQ_WAKE_THREAD; + } + + spin_unlock(&sst->spinlock); + + return ret; +} + +static void sst_byt_boot(struct sst_dsp *sst) +{ + int tries = 10; + + /* + * save the physical address of extended firmware block in the first + * 4 bytes of the mailbox + */ + memcpy_toio(sst->addr.lpe + SST_BYT_MAILBOX_OFFSET, + &sst->pdata->fw_base, sizeof(u32)); + + /* release stall and wait to unstall */ + sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_STALL, 0x0); + while (tries--) { + if (!(sst_dsp_shim_read64(sst, SST_CSR) & + SST_BYT_CSR_PWAITMODE)) + break; + msleep(100); + } + if (tries < 0) { + dev_err(sst->dev, "unable to start DSP\n"); + sst_byt_dump_shim(sst); + } +} + +static void sst_byt_reset(struct sst_dsp *sst) +{ + /* put DSP into reset, set reset vector and stall */ + sst_dsp_shim_update_bits64(sst, SST_CSR, + SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL, + SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL); + + udelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_RST, 0); +} + +struct sst_adsp_memregion { + u32 start; + u32 end; + int blocks; + enum sst_mem_type type; +}; + +/* BYT test stuff */ +static const struct sst_adsp_memregion byt_region[] = { + {0xC0000, 0x100000, 8, SST_MEM_IRAM}, /* I-SRAM - 8 * 32kB */ + {0x100000, 0x140000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ +}; + +static int sst_byt_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + sst->addr.lpe_base = pdata->lpe_base; + sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); + if (!sst->addr.lpe) + return -ENODEV; + + /* ADSP PCI MMIO config space */ + sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); + if (!sst->addr.pci_cfg) { + iounmap(sst->addr.lpe); + return -ENODEV; + } + + /* SST Extended FW allocation */ + sst->addr.fw_ext = ioremap(pdata->fw_base, pdata->fw_size); + if (!sst->addr.fw_ext) { + iounmap(sst->addr.pci_cfg); + iounmap(sst->addr.lpe); + return -ENODEV; + } + + /* SST Shim */ + sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; + + sst_dsp_mailbox_init(sst, SST_BYT_MAILBOX_OFFSET + 0x204, + SST_BYT_IPC_MAX_PAYLOAD_SIZE, + SST_BYT_MAILBOX_OFFSET, + SST_BYT_IPC_MAX_PAYLOAD_SIZE); + + sst->irq = pdata->irq; + + return 0; +} + +static int sst_byt_init(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + const struct sst_adsp_memregion *region; + struct device *dev; + int ret = -ENODEV, i, j, region_count; + u32 offset, size; + + dev = sst->dev; + + switch (sst->id) { + case SST_DEV_ID_BYT: + region = byt_region; + region_count = ARRAY_SIZE(byt_region); + sst->addr.iram_offset = SST_BYT_IRAM_OFFSET; + sst->addr.dram_offset = SST_BYT_DRAM_OFFSET; + sst->addr.shim_offset = SST_BYT_SHIM_OFFSET; + break; + default: + dev_err(dev, "failed to get mem resources\n"); + return ret; + } + + ret = sst_byt_resource_map(sst, pdata); + if (ret < 0) { + dev_err(dev, "failed to map resources\n"); + return ret; + } + + ret = dma_coerce_mask_and_coherent(sst->dma_dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /* enable Interrupt from both sides */ + sst_dsp_shim_update_bits64(sst, SST_IMRX, 0x3, 0x0); + sst_dsp_shim_update_bits64(sst, SST_IMRD, 0x3, 0x0); + + /* register DSP memory blocks - ideally we should get this from ACPI */ + for (i = 0; i < region_count; i++) { + offset = region[i].start; + size = (region[i].end - region[i].start) / region[i].blocks; + + /* register individual memory blocks */ + for (j = 0; j < region[i].blocks; j++) { + sst_mem_block_register(sst, offset, size, + region[i].type, NULL, j, sst); + offset += size; + } + } + + return 0; +} + +static void sst_byt_free(struct sst_dsp *sst) +{ + sst_mem_block_unregister_all(sst); + iounmap(sst->addr.lpe); + iounmap(sst->addr.pci_cfg); + iounmap(sst->addr.fw_ext); +} + +struct sst_ops sst_byt_ops = { + .reset = sst_byt_reset, + .boot = sst_byt_boot, + .write = sst_shim32_write, + .read = sst_shim32_read, + .write64 = sst_shim32_write64, + .read64 = sst_shim32_read64, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .irq_handler = sst_byt_irq, + .init = sst_byt_init, + .free = sst_byt_free, + .parse_fw = sst_byt_parse_fw_image, +}; diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.c b/sound/soc/intel/baytrail/sst-baytrail-ipc.c new file mode 100644 index 0000000..aabb9b0 --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.c @@ -0,0 +1,983 @@ +/* + * Intel Baytrail SST IPC Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sst-baytrail-ipc.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" + +/* IPC message timeout */ +#define IPC_TIMEOUT_MSECS 300 +#define IPC_BOOT_MSECS 200 + +#define IPC_EMPTY_LIST_SIZE 8 + +/* IPC header bits */ +#define IPC_HEADER_MSG_ID_MASK 0xff +#define IPC_HEADER_MSG_ID(x) ((x) & IPC_HEADER_MSG_ID_MASK) +#define IPC_HEADER_STR_ID_SHIFT 8 +#define IPC_HEADER_STR_ID_MASK 0x1f +#define IPC_HEADER_STR_ID(x) (((x) & 0x1f) << IPC_HEADER_STR_ID_SHIFT) +#define IPC_HEADER_LARGE_SHIFT 13 +#define IPC_HEADER_LARGE(x) (((x) & 0x1) << IPC_HEADER_LARGE_SHIFT) +#define IPC_HEADER_DATA_SHIFT 16 +#define IPC_HEADER_DATA_MASK 0x3fff +#define IPC_HEADER_DATA(x) (((x) & 0x3fff) << IPC_HEADER_DATA_SHIFT) + +/* mask for differentiating between notification and reply message */ +#define IPC_NOTIFICATION (0x1 << 7) + +/* I2L Stream config/control msgs */ +#define IPC_IA_ALLOC_STREAM 0x20 +#define IPC_IA_FREE_STREAM 0x21 +#define IPC_IA_PAUSE_STREAM 0x24 +#define IPC_IA_RESUME_STREAM 0x25 +#define IPC_IA_DROP_STREAM 0x26 +#define IPC_IA_START_STREAM 0x30 + +/* notification messages */ +#define IPC_IA_FW_INIT_CMPLT 0x81 +#define IPC_SST_PERIOD_ELAPSED 0x97 + +/* IPC messages between host and ADSP */ +struct sst_byt_address_info { + u32 addr; + u32 size; +} __packed; + +struct sst_byt_str_type { + u8 codec_type; + u8 str_type; + u8 operation; + u8 protected_str; + u8 time_slots; + u8 reserved; + u16 result; +} __packed; + +struct sst_byt_pcm_params { + u8 num_chan; + u8 pcm_wd_sz; + u8 use_offload_path; + u8 reserved; + u32 sfreq; + u8 channel_map[8]; +} __packed; + +struct sst_byt_frames_info { + u16 num_entries; + u16 rsrvd; + u32 frag_size; + struct sst_byt_address_info ring_buf_info[8]; +} __packed; + +struct sst_byt_alloc_params { + struct sst_byt_str_type str_type; + struct sst_byt_pcm_params pcm_params; + struct sst_byt_frames_info frame_info; +} __packed; + +struct sst_byt_alloc_response { + struct sst_byt_str_type str_type; + u8 reserved[88]; +} __packed; + +struct sst_byt_start_stream_params { + u32 byte_offset; +} __packed; + +struct sst_byt_tstamp { + u64 ring_buffer_counter; + u64 hardware_counter; + u64 frames_decoded; + u64 bytes_decoded; + u64 bytes_copied; + u32 sampling_frequency; + u32 channel_peak[8]; +} __packed; + +struct sst_byt_fw_version { + u8 build; + u8 minor; + u8 major; + u8 type; +} __packed; + +struct sst_byt_fw_build_info { + u8 date[16]; + u8 time[16]; +} __packed; + +struct sst_byt_fw_init { + struct sst_byt_fw_version fw_version; + struct sst_byt_fw_build_info build_info; + u16 result; + u8 module_id; + u8 debug_info; +} __packed; + +/* driver internal IPC message structure */ +struct ipc_message { + struct list_head list; + u64 header; + + /* direction wrt host CPU */ + char tx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; + size_t tx_size; + char rx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; + size_t rx_size; + + wait_queue_head_t waitq; + bool complete; + bool wait; + int errno; +}; + +struct sst_byt_stream; +struct sst_byt; + +/* stream infomation */ +struct sst_byt_stream { + struct list_head node; + + /* configuration */ + struct sst_byt_alloc_params request; + struct sst_byt_alloc_response reply; + + /* runtime info */ + struct sst_byt *byt; + int str_id; + bool commited; + bool running; + + /* driver callback */ + u32 (*notify_position)(struct sst_byt_stream *stream, void *data); + void *pdata; +}; + +/* SST Baytrail IPC data */ +struct sst_byt { + struct device *dev; + struct sst_dsp *dsp; + + /* stream */ + struct list_head stream_list; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + struct sst_fw *fw; + + /* IPC messaging */ + struct list_head tx_list; + struct list_head rx_list; + struct list_head empty_list; + wait_queue_head_t wait_txq; + struct task_struct *tx_thread; + struct kthread_worker kworker; + struct kthread_work kwork; + struct ipc_message *msg; +}; + +static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) +{ + u64 header; + + header = IPC_HEADER_MSG_ID(msg_id) | + IPC_HEADER_STR_ID(str_id) | + IPC_HEADER_LARGE(large) | + IPC_HEADER_DATA(data) | + SST_BYT_IPCX_BUSY; + + return header; +} + +static inline u16 sst_byt_header_msg_id(u64 header) +{ + return header & IPC_HEADER_MSG_ID_MASK; +} + +static inline u8 sst_byt_header_str_id(u64 header) +{ + return (header >> IPC_HEADER_STR_ID_SHIFT) & IPC_HEADER_STR_ID_MASK; +} + +static inline u16 sst_byt_header_data(u64 header) +{ + return (header >> IPC_HEADER_DATA_SHIFT) & IPC_HEADER_DATA_MASK; +} + +static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt, + int stream_id) +{ + struct sst_byt_stream *stream; + + list_for_each_entry(stream, &byt->stream_list, node) { + if (stream->str_id == stream_id) + return stream; + } + + return NULL; +} + +static void sst_byt_ipc_shim_dbg(struct sst_byt *byt, const char *text) +{ + struct sst_dsp *sst = byt->dsp; + u64 isr, ipcd, imrx, ipcx; + + ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); + isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); + + dev_err(byt->dev, + "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", + text, ipcx, isr, ipcd, imrx); +} + +/* locks held by caller */ +static struct ipc_message *sst_byt_msg_get_empty(struct sst_byt *byt) +{ + struct ipc_message *msg = NULL; + + if (!list_empty(&byt->empty_list)) { + msg = list_first_entry(&byt->empty_list, + struct ipc_message, list); + list_del(&msg->list); + } + + return msg; +} + +static void sst_byt_ipc_tx_msgs(struct kthread_work *work) +{ + struct sst_byt *byt = + container_of(work, struct sst_byt, kwork); + struct ipc_message *msg; + u64 ipcx; + unsigned long flags; + + spin_lock_irqsave(&byt->dsp->spinlock, flags); + if (list_empty(&byt->tx_list)) { + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); + return; + } + + /* if the DSP is busy we will TX messages after IRQ */ + ipcx = sst_dsp_shim_read64_unlocked(byt->dsp, SST_IPCX); + if (ipcx & SST_BYT_IPCX_BUSY) { + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); + return; + } + + msg = list_first_entry(&byt->tx_list, struct ipc_message, list); + + list_move(&msg->list, &byt->rx_list); + + /* send the message */ + if (msg->header & IPC_HEADER_LARGE(true)) + sst_dsp_outbox_write(byt->dsp, msg->tx_data, msg->tx_size); + sst_dsp_shim_write64_unlocked(byt->dsp, SST_IPCX, msg->header); + + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +} + +static inline void sst_byt_tx_msg_reply_complete(struct sst_byt *byt, + struct ipc_message *msg) +{ + msg->complete = true; + + if (!msg->wait) + list_add_tail(&msg->list, &byt->empty_list); + else + wake_up(&msg->waitq); +} + +static void sst_byt_drop_all(struct sst_byt *byt) +{ + struct ipc_message *msg, *tmp; + unsigned long flags; + + /* drop all TX and Rx messages before we stall + reset DSP */ + spin_lock_irqsave(&byt->dsp->spinlock, flags); + list_for_each_entry_safe(msg, tmp, &byt->tx_list, list) { + list_move(&msg->list, &byt->empty_list); + } + + list_for_each_entry_safe(msg, tmp, &byt->rx_list, list) { + list_move(&msg->list, &byt->empty_list); + } + + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +} + +static int sst_byt_tx_wait_done(struct sst_byt *byt, struct ipc_message *msg, + void *rx_data) +{ + unsigned long flags; + int ret; + + /* wait for DSP completion */ + ret = wait_event_timeout(msg->waitq, msg->complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + + spin_lock_irqsave(&byt->dsp->spinlock, flags); + if (ret == 0) { + list_del(&msg->list); + sst_byt_ipc_shim_dbg(byt, "message timeout"); + + ret = -ETIMEDOUT; + } else { + + /* copy the data returned from DSP */ + if (msg->rx_size) + memcpy(rx_data, msg->rx_data, msg->rx_size); + ret = msg->errno; + } + + list_add_tail(&msg->list, &byt->empty_list); + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); + return ret; +} + +static int sst_byt_ipc_tx_message(struct sst_byt *byt, u64 header, + void *tx_data, size_t tx_bytes, + void *rx_data, size_t rx_bytes, int wait) +{ + unsigned long flags; + struct ipc_message *msg; + + spin_lock_irqsave(&byt->dsp->spinlock, flags); + + msg = sst_byt_msg_get_empty(byt); + if (msg == NULL) { + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); + return -EBUSY; + } + + msg->header = header; + msg->tx_size = tx_bytes; + msg->rx_size = rx_bytes; + msg->wait = wait; + msg->errno = 0; + msg->complete = false; + + if (tx_bytes) { + /* msg content = lower 32-bit of the header + data */ + *(u32 *)msg->tx_data = (u32)(header & (u32)-1); + memcpy(msg->tx_data + sizeof(u32), tx_data, tx_bytes); + msg->tx_size += sizeof(u32); + } + + list_add_tail(&msg->list, &byt->tx_list); + spin_unlock_irqrestore(&byt->dsp->spinlock, flags); + + queue_kthread_work(&byt->kworker, &byt->kwork); + + if (wait) + return sst_byt_tx_wait_done(byt, msg, rx_data); + else + return 0; +} + +static inline int sst_byt_ipc_tx_msg_wait(struct sst_byt *byt, u64 header, + void *tx_data, size_t tx_bytes, + void *rx_data, size_t rx_bytes) +{ + return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, + rx_data, rx_bytes, 1); +} + +static inline int sst_byt_ipc_tx_msg_nowait(struct sst_byt *byt, u64 header, + void *tx_data, size_t tx_bytes) +{ + return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, + NULL, 0, 0); +} + +static struct ipc_message *sst_byt_reply_find_msg(struct sst_byt *byt, + u64 header) +{ + struct ipc_message *msg = NULL, *_msg; + u64 mask; + + /* match reply to message sent based on msg and stream IDs */ + mask = IPC_HEADER_MSG_ID_MASK | + IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; + header &= mask; + + if (list_empty(&byt->rx_list)) { + dev_err(byt->dev, + "ipc: rx list is empty but received 0x%llx\n", header); + goto out; + } + + list_for_each_entry(_msg, &byt->rx_list, list) { + if ((_msg->header & mask) == header) { + msg = _msg; + break; + } + } + +out: + return msg; +} + +static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) +{ + struct sst_byt_stream *stream; + u64 header = msg->header; + u8 stream_id = sst_byt_header_str_id(header); + u8 stream_msg = sst_byt_header_msg_id(header); + + stream = sst_byt_get_stream(byt, stream_id); + if (stream == NULL) + return; + + switch (stream_msg) { + case IPC_IA_DROP_STREAM: + case IPC_IA_PAUSE_STREAM: + case IPC_IA_FREE_STREAM: + stream->running = false; + break; + case IPC_IA_START_STREAM: + case IPC_IA_RESUME_STREAM: + stream->running = true; + break; + } +} + +static int sst_byt_process_reply(struct sst_byt *byt, u64 header) +{ + struct ipc_message *msg; + + msg = sst_byt_reply_find_msg(byt, header); + if (msg == NULL) + return 1; + + if (header & IPC_HEADER_LARGE(true)) { + msg->rx_size = sst_byt_header_data(header); + sst_dsp_inbox_read(byt->dsp, msg->rx_data, msg->rx_size); + } + + /* update any stream states */ + sst_byt_stream_update(byt, msg); + + list_del(&msg->list); + /* wake up */ + sst_byt_tx_msg_reply_complete(byt, msg); + + return 1; +} + +static void sst_byt_fw_ready(struct sst_byt *byt, u64 header) +{ + dev_dbg(byt->dev, "ipc: DSP is ready 0x%llX\n", header); + + byt->boot_complete = true; + wake_up(&byt->boot_wait); +} + +static int sst_byt_process_notification(struct sst_byt *byt, + unsigned long *flags) +{ + struct sst_dsp *sst = byt->dsp; + struct sst_byt_stream *stream; + u64 header; + u8 msg_id, stream_id; + int handled = 1; + + header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + msg_id = sst_byt_header_msg_id(header); + + switch (msg_id) { + case IPC_SST_PERIOD_ELAPSED: + stream_id = sst_byt_header_str_id(header); + stream = sst_byt_get_stream(byt, stream_id); + if (stream && stream->running && stream->notify_position) { + spin_unlock_irqrestore(&sst->spinlock, *flags); + stream->notify_position(stream, stream->pdata); + spin_lock_irqsave(&sst->spinlock, *flags); + } + break; + case IPC_IA_FW_INIT_CMPLT: + sst_byt_fw_ready(byt, header); + break; + } + + return handled; +} + +static irqreturn_t sst_byt_irq_thread(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + struct sst_byt *byt = sst_dsp_get_thread_context(sst); + u64 header; + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + + header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + if (header & SST_BYT_IPCD_BUSY) { + if (header & IPC_NOTIFICATION) { + /* message from ADSP */ + sst_byt_process_notification(byt, &flags); + } else { + /* reply from ADSP */ + sst_byt_process_reply(byt, header); + } + /* + * clear IPCD BUSY bit and set DONE bit. Tell DSP we have + * processed the message and can accept new. Clear data part + * of the header + */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD, + SST_BYT_IPCD_DONE | SST_BYT_IPCD_BUSY | + IPC_HEADER_DATA(IPC_HEADER_DATA_MASK), + SST_BYT_IPCD_DONE); + /* unmask message request interrupts */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_BYT_IMRX_REQUEST, 0); + } + + spin_unlock_irqrestore(&sst->spinlock, flags); + + /* continue to send any remaining messages... */ + queue_kthread_work(&byt->kworker, &byt->kwork); + + return IRQ_HANDLED; +} + +/* stream API */ +struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, + u32 (*notify_position)(struct sst_byt_stream *stream, void *data), + void *data) +{ + struct sst_byt_stream *stream; + struct sst_dsp *sst = byt->dsp; + unsigned long flags; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (stream == NULL) + return NULL; + + spin_lock_irqsave(&sst->spinlock, flags); + list_add(&stream->node, &byt->stream_list); + stream->notify_position = notify_position; + stream->pdata = data; + stream->byt = byt; + stream->str_id = id; + spin_unlock_irqrestore(&sst->spinlock, flags); + + return stream; +} + +int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, + int bits) +{ + stream->request.pcm_params.pcm_wd_sz = bits; + return 0; +} + +int sst_byt_stream_set_channels(struct sst_byt *byt, + struct sst_byt_stream *stream, u8 channels) +{ + stream->request.pcm_params.num_chan = channels; + return 0; +} + +int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, + unsigned int rate) +{ + stream->request.pcm_params.sfreq = rate; + return 0; +} + +/* stream sonfiguration */ +int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, + int codec_type, int stream_type, int operation) +{ + stream->request.str_type.codec_type = codec_type; + stream->request.str_type.str_type = stream_type; + stream->request.str_type.operation = operation; + stream->request.str_type.time_slots = 0xc; + + return 0; +} + +int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, + uint32_t buffer_addr, uint32_t buffer_size) +{ + stream->request.frame_info.num_entries = 1; + stream->request.frame_info.ring_buf_info[0].addr = buffer_addr; + stream->request.frame_info.ring_buf_info[0].size = buffer_size; + /* calculate bytes per 4 ms fragment */ + stream->request.frame_info.frag_size = + stream->request.pcm_params.sfreq * + stream->request.pcm_params.num_chan * + stream->request.pcm_params.pcm_wd_sz / 8 * + 4 / 1000; + return 0; +} + +int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + struct sst_byt_alloc_params *str_req = &stream->request; + struct sst_byt_alloc_response *reply = &stream->reply; + u64 header; + int ret; + + header = sst_byt_header(IPC_IA_ALLOC_STREAM, + sizeof(*str_req) + sizeof(u32), + true, stream->str_id); + ret = sst_byt_ipc_tx_msg_wait(byt, header, str_req, sizeof(*str_req), + reply, sizeof(*reply)); + if (ret < 0) { + dev_err(byt->dev, "ipc: error stream commit failed\n"); + return ret; + } + + stream->commited = true; + + return 0; +} + +int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + u64 header; + int ret = 0; + struct sst_dsp *sst = byt->dsp; + unsigned long flags; + + if (!stream->commited) + goto out; + + header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id); + ret = sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(byt->dev, "ipc: free stream %d failed\n", + stream->str_id); + return -EAGAIN; + } + + stream->commited = false; +out: + spin_lock_irqsave(&sst->spinlock, flags); + list_del(&stream->node); + kfree(stream); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return ret; +} + +static int sst_byt_stream_operations(struct sst_byt *byt, int type, + int stream_id, int wait) +{ + u64 header; + + header = sst_byt_header(type, 0, false, stream_id); + if (wait) + return sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); + else + return sst_byt_ipc_tx_msg_nowait(byt, header, NULL, 0); +} + +/* stream ALSA trigger operations */ +int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, + u32 start_offset) +{ + struct sst_byt_start_stream_params start_stream; + void *tx_msg; + size_t size; + u64 header; + int ret; + + start_stream.byte_offset = start_offset; + header = sst_byt_header(IPC_IA_START_STREAM, + sizeof(start_stream) + sizeof(u32), + true, stream->str_id); + tx_msg = &start_stream; + size = sizeof(start_stream); + + ret = sst_byt_ipc_tx_msg_nowait(byt, header, tx_msg, size); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to start stream %d\n", + stream->str_id); + + return ret; +} + +int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + int ret; + + /* don't stop streams that are not commited */ + if (!stream->commited) + return 0; + + ret = sst_byt_stream_operations(byt, IPC_IA_DROP_STREAM, + stream->str_id, 0); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to stop stream %d\n", + stream->str_id); + return ret; +} + +int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + int ret; + + ret = sst_byt_stream_operations(byt, IPC_IA_PAUSE_STREAM, + stream->str_id, 0); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to pause stream %d\n", + stream->str_id); + + return ret; +} + +int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + int ret; + + ret = sst_byt_stream_operations(byt, IPC_IA_RESUME_STREAM, + stream->str_id, 0); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to resume stream %d\n", + stream->str_id); + + return ret; +} + +int sst_byt_get_dsp_position(struct sst_byt *byt, + struct sst_byt_stream *stream, int buffer_size) +{ + struct sst_dsp *sst = byt->dsp; + struct sst_byt_tstamp fw_tstamp; + u8 str_id = stream->str_id; + u32 tstamp_offset; + + tstamp_offset = SST_BYT_TIMESTAMP_OFFSET + str_id * sizeof(fw_tstamp); + memcpy_fromio(&fw_tstamp, + sst->addr.lpe + tstamp_offset, sizeof(fw_tstamp)); + + return do_div(fw_tstamp.ring_buffer_counter, buffer_size); +} + +static int msg_empty_list_init(struct sst_byt *byt) +{ + struct ipc_message *msg; + int i; + + byt->msg = kzalloc(sizeof(*msg) * IPC_EMPTY_LIST_SIZE, GFP_KERNEL); + if (byt->msg == NULL) + return -ENOMEM; + + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + init_waitqueue_head(&byt->msg[i].waitq); + list_add(&byt->msg[i].list, &byt->empty_list); + } + + return 0; +} + +struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt) +{ + return byt->dsp; +} + +static struct sst_dsp_device byt_dev = { + .thread = sst_byt_irq_thread, + .ops = &sst_byt_ops, +}; + +int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + + dev_dbg(byt->dev, "dsp reset\n"); + sst_dsp_reset(byt->dsp); + sst_byt_drop_all(byt); + dev_dbg(byt->dev, "dsp in reset\n"); + + dev_dbg(byt->dev, "free all blocks and unload fw\n"); + sst_fw_unload(byt->fw); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_suspend_late); + +int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + int ret; + + dev_dbg(byt->dev, "reload dsp fw\n"); + + sst_dsp_reset(byt->dsp); + + ret = sst_fw_reload(byt->fw); + if (ret < 0) { + dev_err(dev, "error: failed to reload firmware\n"); + return ret; + } + + /* wait for DSP boot completion */ + byt->boot_complete = false; + sst_dsp_boot(byt->dsp); + dev_dbg(byt->dev, "dsp booting...\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_boot); + +int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + int err; + + dev_dbg(byt->dev, "wait for dsp reboot\n"); + + err = wait_event_timeout(byt->boot_wait, byt->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (err == 0) { + dev_err(byt->dev, "ipc: error DSP boot timeout\n"); + return -EIO; + } + + dev_dbg(byt->dev, "dsp rebooted\n"); + return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_wait_for_ready); + +int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt; + struct sst_fw *byt_sst_fw; + struct sst_byt_fw_init init; + int err; + + dev_dbg(dev, "initialising Byt DSP IPC\n"); + + byt = devm_kzalloc(dev, sizeof(*byt), GFP_KERNEL); + if (byt == NULL) + return -ENOMEM; + + byt->dev = dev; + INIT_LIST_HEAD(&byt->stream_list); + INIT_LIST_HEAD(&byt->tx_list); + INIT_LIST_HEAD(&byt->rx_list); + INIT_LIST_HEAD(&byt->empty_list); + init_waitqueue_head(&byt->boot_wait); + init_waitqueue_head(&byt->wait_txq); + + err = msg_empty_list_init(byt); + if (err < 0) + return -ENOMEM; + + /* start the IPC message thread */ + init_kthread_worker(&byt->kworker); + byt->tx_thread = kthread_run(kthread_worker_fn, + &byt->kworker, "%s", + dev_name(byt->dev)); + if (IS_ERR(byt->tx_thread)) { + err = PTR_ERR(byt->tx_thread); + dev_err(byt->dev, "error failed to create message TX task\n"); + goto err_free_msg; + } + init_kthread_work(&byt->kwork, sst_byt_ipc_tx_msgs); + + byt_dev.thread_context = byt; + + /* init SST shim */ + byt->dsp = sst_dsp_new(dev, &byt_dev, pdata); + if (byt->dsp == NULL) { + err = -ENODEV; + goto dsp_err; + } + + /* keep the DSP in reset state for base FW loading */ + sst_dsp_reset(byt->dsp); + + byt_sst_fw = sst_fw_new(byt->dsp, pdata->fw, byt); + if (byt_sst_fw == NULL) { + err = -ENODEV; + dev_err(dev, "error: failed to load firmware\n"); + goto fw_err; + } + + /* wait for DSP boot completion */ + sst_dsp_boot(byt->dsp); + err = wait_event_timeout(byt->boot_wait, byt->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (err == 0) { + err = -EIO; + dev_err(byt->dev, "ipc: error DSP boot timeout\n"); + goto boot_err; + } + + /* show firmware information */ + sst_dsp_inbox_read(byt->dsp, &init, sizeof(init)); + dev_info(byt->dev, "FW version: %02x.%02x.%02x.%02x\n", + init.fw_version.major, init.fw_version.minor, + init.fw_version.build, init.fw_version.type); + dev_info(byt->dev, "Build type: %x\n", init.fw_version.type); + dev_info(byt->dev, "Build date: %s %s\n", + init.build_info.date, init.build_info.time); + + pdata->dsp = byt; + byt->fw = byt_sst_fw; + + return 0; + +boot_err: + sst_dsp_reset(byt->dsp); + sst_fw_free(byt_sst_fw); +fw_err: + sst_dsp_free(byt->dsp); +dsp_err: + kthread_stop(byt->tx_thread); +err_free_msg: + kfree(byt->msg); + + return err; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_init); + +void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + + sst_dsp_reset(byt->dsp); + sst_fw_free_all(byt->dsp); + sst_dsp_free(byt->dsp); + kthread_stop(byt->tx_thread); + kfree(byt->msg); +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_free); diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.h b/sound/soc/intel/baytrail/sst-baytrail-ipc.h new file mode 100644 index 0000000..8faff6d --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.h @@ -0,0 +1,73 @@ +/* + * Intel Baytrail SST IPC Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __SST_BYT_IPC_H +#define __SST_BYT_IPC_H + +#include + +struct sst_byt; +struct sst_byt_stream; +struct sst_pdata; +extern struct sst_ops sst_byt_ops; + + +#define SST_BYT_MAILBOX_OFFSET 0x144000 +#define SST_BYT_TIMESTAMP_OFFSET (SST_BYT_MAILBOX_OFFSET + 0x800) + +/** + * Upfront defined maximum message size that is + * expected by the in/out communication pipes in FW. + */ +#define SST_BYT_IPC_MAX_PAYLOAD_SIZE 200 + +/* stream API */ +struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, + uint32_t (*get_write_position)(struct sst_byt_stream *stream, + void *data), + void *data); + +/* stream configuration */ +int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, + int bits); +int sst_byt_stream_set_channels(struct sst_byt *byt, + struct sst_byt_stream *stream, u8 channels); +int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, + unsigned int rate); +int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, + int codec_type, int stream_type, int operation); +int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, + uint32_t buffer_addr, uint32_t buffer_size); +int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream); +int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream); + +/* stream ALSA trigger operations */ +int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, + u32 start_offset); +int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream); +int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream); +int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream); + +int sst_byt_get_dsp_position(struct sst_byt *byt, + struct sst_byt_stream *stream, int buffer_size); + +/* init */ +int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata); +void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata); +struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt); +int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata); +int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata); +int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata); + +#endif diff --git a/sound/soc/intel/baytrail/sst-baytrail-pcm.c b/sound/soc/intel/baytrail/sst-baytrail-pcm.c new file mode 100644 index 0000000..79547be --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-pcm.c @@ -0,0 +1,505 @@ +/* + * Intel Baytrail SST PCM Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include "sst-baytrail-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-dsp.h" + +#define BYT_PCM_COUNT 2 + +static const struct snd_pcm_hardware sst_byt_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_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .period_bytes_min = 384, + .period_bytes_max = 48000, + .periods_min = 2, + .periods_max = 250, + .buffer_bytes_max = 96000, +}; + +/* private data for each PCM DSP stream */ +struct sst_byt_pcm_data { + struct sst_byt_stream *stream; + struct snd_pcm_substream *substream; + struct mutex mutex; + + /* latest DSP DMA hw pointer */ + u32 hw_ptr; + + struct work_struct work; +}; + +/* private data for the driver */ +struct sst_byt_priv_data { + /* runtime DSP */ + struct sst_byt *byt; + + /* DAI data */ + struct sst_byt_pcm_data pcm[BYT_PCM_COUNT]; + + /* flag indicating is stream context restore needed after suspend */ + bool restore_stream; +}; + +/* this may get called several times by oss emulation */ +static int sst_byt_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + u32 rate, bits; + u8 channels; + int ret, playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(rtd->dev, "PCM: hw_params, pcm_data %p\n", pcm_data); + + ret = sst_byt_stream_type(byt, pcm_data->stream, + 1, 1, !playback); + if (ret < 0) { + dev_err(rtd->dev, "failed to set stream format %d\n", ret); + return ret; + } + + rate = params_rate(params); + ret = sst_byt_stream_set_rate(byt, pcm_data->stream, rate); + if (ret < 0) { + dev_err(rtd->dev, "could not set rate %d\n", rate); + return ret; + } + + bits = snd_pcm_format_width(params_format(params)); + ret = sst_byt_stream_set_bits(byt, pcm_data->stream, bits); + if (ret < 0) { + dev_err(rtd->dev, "could not set formats %d\n", + params_rate(params)); + return ret; + } + + channels = (u8)(params_channels(params) & 0xF); + ret = sst_byt_stream_set_channels(byt, pcm_data->stream, channels); + if (ret < 0) { + dev_err(rtd->dev, "could not set channels %d\n", + params_rate(params)); + return ret; + } + + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + + ret = sst_byt_stream_buffer(byt, pcm_data->stream, + substream->dma_buffer.addr, + params_buffer_bytes(params)); + if (ret < 0) { + dev_err(rtd->dev, "PCM: failed to set DMA buffer %d\n", ret); + return ret; + } + + ret = sst_byt_stream_commit(byt, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); + return ret; + } + + return 0; +} + +static int sst_byt_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->dev, "PCM: hw_free\n"); + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int sst_byt_pcm_restore_stream_context(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + int ret; + + /* commit stream using existing stream params */ + ret = sst_byt_stream_commit(byt, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); + return ret; + } + + sst_byt_stream_start(byt, pcm_data->stream, pcm_data->hw_ptr); + + dev_dbg(rtd->dev, "stream context restored at offset %d\n", + pcm_data->hw_ptr); + + return 0; +} + +static void sst_byt_pcm_work(struct work_struct *work) +{ + struct sst_byt_pcm_data *pcm_data = + container_of(work, struct sst_byt_pcm_data, work); + + if (snd_pcm_running(pcm_data->substream)) + sst_byt_pcm_restore_stream_context(pcm_data->substream); +} + +static int sst_byt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + + dev_dbg(rtd->dev, "PCM: trigger %d\n", cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm_data->hw_ptr = 0; + sst_byt_stream_start(byt, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (pdata->restore_stream == true) + schedule_work(&pcm_data->work); + else + sst_byt_stream_resume(byt, pcm_data->stream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sst_byt_stream_resume(byt, pcm_data->stream); + break; + case SNDRV_PCM_TRIGGER_STOP: + sst_byt_stream_stop(byt, pcm_data->stream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + pdata->restore_stream = false; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sst_byt_stream_pause(byt, pcm_data->stream); + break; + default: + break; + } + + return 0; +} + +static u32 byt_notify_pointer(struct sst_byt_stream *stream, void *data) +{ + struct sst_byt_pcm_data *pcm_data = data; + struct snd_pcm_substream *substream = pcm_data->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt *byt = pdata->byt; + u32 pos, hw_pos; + + hw_pos = sst_byt_get_dsp_position(byt, pcm_data->stream, + snd_pcm_lib_buffer_bytes(substream)); + pcm_data->hw_ptr = hw_pos; + pos = frames_to_bytes(runtime, + (runtime->control->appl_ptr % + runtime->buffer_size)); + + dev_dbg(rtd->dev, "PCM: App/DMA pointer %u/%u bytes\n", pos, hw_pos); + + snd_pcm_period_elapsed(substream); + return pos; +} + +static snd_pcm_uframes_t sst_byt_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + + dev_dbg(rtd->dev, "PCM: DMA pointer %u bytes\n", pcm_data->hw_ptr); + + return bytes_to_frames(runtime, pcm_data->hw_ptr); +} + +static int sst_byt_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + + dev_dbg(rtd->dev, "PCM: open\n"); + + mutex_lock(&pcm_data->mutex); + + pcm_data->substream = substream; + + snd_soc_set_runtime_hwparams(substream, &sst_byt_pcm_hardware); + + pcm_data->stream = sst_byt_stream_new(byt, substream->stream + 1, + byt_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "failed to create stream\n"); + mutex_unlock(&pcm_data->mutex); + return -EINVAL; + } + + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int sst_byt_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sst_byt_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + int ret; + + dev_dbg(rtd->dev, "PCM: close\n"); + + cancel_work_sync(&pcm_data->work); + mutex_lock(&pcm_data->mutex); + ret = sst_byt_stream_free(byt, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "Free stream fail\n"); + goto out; + } + pcm_data->stream = NULL; + +out: + mutex_unlock(&pcm_data->mutex); + return ret; +} + +static int sst_byt_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->dev, "PCM: mmap\n"); + return snd_pcm_lib_default_mmap(substream, vma); +} + +static struct snd_pcm_ops sst_byt_pcm_ops = { + .open = sst_byt_pcm_open, + .close = sst_byt_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sst_byt_pcm_hw_params, + .hw_free = sst_byt_pcm_hw_free, + .trigger = sst_byt_pcm_trigger, + .pointer = sst_byt_pcm_pointer, + .mmap = sst_byt_pcm_mmap, +}; + +static int sst_byt_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + size_t size; + struct snd_soc_platform *platform = rtd->platform; + struct sst_pdata *pdata = dev_get_platdata(platform->dev); + int ret = 0; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + size = sst_byt_pcm_hardware.buffer_bytes_max; + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, + pdata->dma_dev, + size, size); + if (ret) { + dev_err(rtd->dev, "dma buffer allocation failed %d\n", + ret); + return ret; + } + } + + return ret; +} + +static struct snd_soc_dai_driver byt_dais[] = { + { + .name = "Baytrail PCM", + .playback = { + .stream_name = "System Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static int sst_byt_pcm_probe(struct snd_soc_platform *platform) +{ + struct sst_pdata *plat_data = dev_get_platdata(platform->dev); + struct sst_byt_priv_data *priv_data; + int i; + + if (!plat_data) + return -ENODEV; + + priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), + GFP_KERNEL); + priv_data->byt = plat_data->dsp; + snd_soc_platform_set_drvdata(platform, priv_data); + + for (i = 0; i < BYT_PCM_COUNT; i++) { + mutex_init(&priv_data->pcm[i].mutex); + INIT_WORK(&priv_data->pcm[i].work, sst_byt_pcm_work); + } + + return 0; +} + +static int sst_byt_pcm_remove(struct snd_soc_platform *platform) +{ + return 0; +} + +static struct snd_soc_platform_driver byt_soc_platform = { + .probe = sst_byt_pcm_probe, + .remove = sst_byt_pcm_remove, + .ops = &sst_byt_pcm_ops, + .pcm_new = sst_byt_pcm_new, +}; + +static const struct snd_soc_component_driver byt_dai_component = { + .name = "byt-dai", +}; + +#ifdef CONFIG_PM +static int sst_byt_pcm_dev_suspend_late(struct device *dev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(dev); + struct sst_byt_priv_data *priv_data = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "suspending late\n"); + + ret = sst_byt_dsp_suspend_late(dev, sst_pdata); + if (ret < 0) { + dev_err(dev, "failed to suspend %d\n", ret); + return ret; + } + + priv_data->restore_stream = true; + + return ret; +} + +static int sst_byt_pcm_dev_resume_early(struct device *dev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(dev); + int ret; + + dev_dbg(dev, "resume early\n"); + + /* load fw and boot DSP */ + ret = sst_byt_dsp_boot(dev, sst_pdata); + if (ret) + return ret; + + /* wait for FW to finish booting */ + return sst_byt_dsp_wait_for_ready(dev, sst_pdata); +} + +static const struct dev_pm_ops sst_byt_pm_ops = { + .suspend_late = sst_byt_pcm_dev_suspend_late, + .resume_early = sst_byt_pcm_dev_resume_early, +}; + +#define SST_BYT_PM_OPS (&sst_byt_pm_ops) +#else +#define SST_BYT_PM_OPS NULL +#endif + +static int sst_byt_pcm_dev_probe(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + int ret; + + ret = sst_byt_dsp_init(&pdev->dev, sst_pdata); + if (ret < 0) + return -ENODEV; + + ret = snd_soc_register_platform(&pdev->dev, &byt_soc_platform); + if (ret < 0) + goto err_plat; + + ret = snd_soc_register_component(&pdev->dev, &byt_dai_component, + byt_dais, ARRAY_SIZE(byt_dais)); + if (ret < 0) + goto err_comp; + + return 0; + +err_comp: + snd_soc_unregister_platform(&pdev->dev); +err_plat: + sst_byt_dsp_free(&pdev->dev, sst_pdata); + return ret; +} + +static int sst_byt_pcm_dev_remove(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + + snd_soc_unregister_platform(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + sst_byt_dsp_free(&pdev->dev, sst_pdata); + + return 0; +} + +static struct platform_driver sst_byt_pcm_driver = { + .driver = { + .name = "baytrail-pcm-audio", + .pm = SST_BYT_PM_OPS, + }, + + .probe = sst_byt_pcm_dev_probe, + .remove = sst_byt_pcm_dev_remove, +}; +module_platform_driver(sst_byt_pcm_driver); + +MODULE_AUTHOR("Jarkko Nikula"); +MODULE_DESCRIPTION("Baytrail PCM"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:baytrail-pcm-audio"); diff --git a/sound/soc/intel/sst-baytrail-dsp.c b/sound/soc/intel/sst-baytrail-dsp.c deleted file mode 100644 index 5a9e567..0000000 --- a/sound/soc/intel/sst-baytrail-dsp.c +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Intel Baytrail SST DSP driver - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sst-dsp.h" -#include "sst-dsp-priv.h" -#include "sst-baytrail-ipc.h" - -#define SST_BYT_FW_SIGNATURE_SIZE 4 -#define SST_BYT_FW_SIGN "$SST" - -#define SST_BYT_IRAM_OFFSET 0xC0000 -#define SST_BYT_DRAM_OFFSET 0x100000 -#define SST_BYT_SHIM_OFFSET 0x140000 - -enum sst_ram_type { - SST_BYT_IRAM = 1, - SST_BYT_DRAM = 2, - SST_BYT_CACHE = 3, -}; - -struct dma_block_info { - enum sst_ram_type type; /* IRAM/DRAM */ - u32 size; /* Bytes */ - u32 ram_offset; /* Offset in I/DRAM */ - u32 rsvd; /* Reserved field */ -}; - -struct fw_header { - unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; - u32 file_size; /* size of fw minus this header */ - u32 modules; /* # of modules */ - u32 file_format; /* version of header format */ - u32 reserved[4]; -}; - -struct sst_byt_fw_module_header { - unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; - u32 mod_size; /* size of module */ - u32 blocks; /* # of blocks */ - u32 type; /* codec type, pp lib */ - u32 entry_point; -}; - -static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, - struct sst_byt_fw_module_header *module) -{ - struct dma_block_info *block; - struct sst_module *mod; - struct sst_module_template template; - int count; - - memset(&template, 0, sizeof(template)); - template.id = module->type; - template.entry = module->entry_point; - - mod = sst_module_new(fw, &template, NULL); - if (mod == NULL) - return -ENOMEM; - - block = (void *)module + sizeof(*module); - - for (count = 0; count < module->blocks; count++) { - - if (block->size <= 0) { - dev_err(dsp->dev, "block %d size invalid\n", count); - return -EINVAL; - } - - switch (block->type) { - case SST_BYT_IRAM: - mod->offset = block->ram_offset + - dsp->addr.iram_offset; - mod->type = SST_MEM_IRAM; - break; - case SST_BYT_DRAM: - mod->offset = block->ram_offset + - dsp->addr.dram_offset; - mod->type = SST_MEM_DRAM; - break; - case SST_BYT_CACHE: - mod->offset = block->ram_offset + - (dsp->addr.fw_ext - dsp->addr.lpe); - mod->type = SST_MEM_CACHE; - break; - default: - dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", - block->type, count); - return -EINVAL; - } - - mod->size = block->size; - mod->data = (void *)block + sizeof(*block); - - sst_module_alloc_blocks(mod); - - block = (void *)block + sizeof(*block) + block->size; - } - return 0; -} - -static int sst_byt_parse_fw_image(struct sst_fw *sst_fw) -{ - struct fw_header *header; - struct sst_byt_fw_module_header *module; - struct sst_dsp *dsp = sst_fw->dsp; - int ret, count; - - /* Read the header information from the data pointer */ - header = (struct fw_header *)sst_fw->dma_buf; - - /* verify FW */ - if ((strncmp(header->signature, SST_BYT_FW_SIGN, 4) != 0) || - (sst_fw->size != header->file_size + sizeof(*header))) { - /* Invalid FW signature */ - dev_err(dsp->dev, "Invalid FW sign/filesize mismatch\n"); - return -EINVAL; - } - - dev_dbg(dsp->dev, - "header sign=%4s size=0x%x modules=0x%x fmt=0x%x size=%zu\n", - header->signature, header->file_size, header->modules, - header->file_format, sizeof(*header)); - - module = (void *)sst_fw->dma_buf + sizeof(*header); - for (count = 0; count < header->modules; count++) { - /* module */ - ret = sst_byt_parse_module(dsp, sst_fw, module); - if (ret < 0) { - dev_err(dsp->dev, "invalid module %d\n", count); - return ret; - } - module = (void *)module + sizeof(*module) + module->mod_size; - } - - return 0; -} - -static void sst_byt_dump_shim(struct sst_dsp *sst) -{ - int i; - u64 reg; - - for (i = 0; i <= 0xF0; i += 8) { - reg = sst_dsp_shim_read64_unlocked(sst, i); - if (reg) - dev_dbg(sst->dev, "shim 0x%2.2x value 0x%16.16llx\n", - i, reg); - } - - for (i = 0x00; i <= 0xff; i += 4) { - reg = readl(sst->addr.pci_cfg + i); - if (reg) - dev_dbg(sst->dev, "pci 0x%2.2x value 0x%8.8x\n", - i, (u32)reg); - } -} - -static irqreturn_t sst_byt_irq(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - u64 isrx; - irqreturn_t ret = IRQ_NONE; - - spin_lock(&sst->spinlock); - - isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); - if (isrx & SST_ISRX_DONE) { - /* ADSP has processed the message request from IA */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX, - SST_BYT_IPCX_DONE, 0); - ret = IRQ_WAKE_THREAD; - } - if (isrx & SST_BYT_ISRX_REQUEST) { - /* mask message request from ADSP and do processing later */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, - SST_BYT_IMRX_REQUEST, - SST_BYT_IMRX_REQUEST); - ret = IRQ_WAKE_THREAD; - } - - spin_unlock(&sst->spinlock); - - return ret; -} - -static void sst_byt_boot(struct sst_dsp *sst) -{ - int tries = 10; - - /* - * save the physical address of extended firmware block in the first - * 4 bytes of the mailbox - */ - memcpy_toio(sst->addr.lpe + SST_BYT_MAILBOX_OFFSET, - &sst->pdata->fw_base, sizeof(u32)); - - /* release stall and wait to unstall */ - sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_STALL, 0x0); - while (tries--) { - if (!(sst_dsp_shim_read64(sst, SST_CSR) & - SST_BYT_CSR_PWAITMODE)) - break; - msleep(100); - } - if (tries < 0) { - dev_err(sst->dev, "unable to start DSP\n"); - sst_byt_dump_shim(sst); - } -} - -static void sst_byt_reset(struct sst_dsp *sst) -{ - /* put DSP into reset, set reset vector and stall */ - sst_dsp_shim_update_bits64(sst, SST_CSR, - SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL, - SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL); - - udelay(10); - - /* take DSP out of reset and keep stalled for FW loading */ - sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_RST, 0); -} - -struct sst_adsp_memregion { - u32 start; - u32 end; - int blocks; - enum sst_mem_type type; -}; - -/* BYT test stuff */ -static const struct sst_adsp_memregion byt_region[] = { - {0xC0000, 0x100000, 8, SST_MEM_IRAM}, /* I-SRAM - 8 * 32kB */ - {0x100000, 0x140000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ -}; - -static int sst_byt_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - sst->addr.lpe_base = pdata->lpe_base; - sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); - if (!sst->addr.lpe) - return -ENODEV; - - /* ADSP PCI MMIO config space */ - sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); - if (!sst->addr.pci_cfg) { - iounmap(sst->addr.lpe); - return -ENODEV; - } - - /* SST Extended FW allocation */ - sst->addr.fw_ext = ioremap(pdata->fw_base, pdata->fw_size); - if (!sst->addr.fw_ext) { - iounmap(sst->addr.pci_cfg); - iounmap(sst->addr.lpe); - return -ENODEV; - } - - /* SST Shim */ - sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; - - sst_dsp_mailbox_init(sst, SST_BYT_MAILBOX_OFFSET + 0x204, - SST_BYT_IPC_MAX_PAYLOAD_SIZE, - SST_BYT_MAILBOX_OFFSET, - SST_BYT_IPC_MAX_PAYLOAD_SIZE); - - sst->irq = pdata->irq; - - return 0; -} - -static int sst_byt_init(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - const struct sst_adsp_memregion *region; - struct device *dev; - int ret = -ENODEV, i, j, region_count; - u32 offset, size; - - dev = sst->dev; - - switch (sst->id) { - case SST_DEV_ID_BYT: - region = byt_region; - region_count = ARRAY_SIZE(byt_region); - sst->addr.iram_offset = SST_BYT_IRAM_OFFSET; - sst->addr.dram_offset = SST_BYT_DRAM_OFFSET; - sst->addr.shim_offset = SST_BYT_SHIM_OFFSET; - break; - default: - dev_err(dev, "failed to get mem resources\n"); - return ret; - } - - ret = sst_byt_resource_map(sst, pdata); - if (ret < 0) { - dev_err(dev, "failed to map resources\n"); - return ret; - } - - ret = dma_coerce_mask_and_coherent(sst->dma_dev, DMA_BIT_MASK(32)); - if (ret) - return ret; - - /* enable Interrupt from both sides */ - sst_dsp_shim_update_bits64(sst, SST_IMRX, 0x3, 0x0); - sst_dsp_shim_update_bits64(sst, SST_IMRD, 0x3, 0x0); - - /* register DSP memory blocks - ideally we should get this from ACPI */ - for (i = 0; i < region_count; i++) { - offset = region[i].start; - size = (region[i].end - region[i].start) / region[i].blocks; - - /* register individual memory blocks */ - for (j = 0; j < region[i].blocks; j++) { - sst_mem_block_register(sst, offset, size, - region[i].type, NULL, j, sst); - offset += size; - } - } - - return 0; -} - -static void sst_byt_free(struct sst_dsp *sst) -{ - sst_mem_block_unregister_all(sst); - iounmap(sst->addr.lpe); - iounmap(sst->addr.pci_cfg); - iounmap(sst->addr.fw_ext); -} - -struct sst_ops sst_byt_ops = { - .reset = sst_byt_reset, - .boot = sst_byt_boot, - .write = sst_shim32_write, - .read = sst_shim32_read, - .write64 = sst_shim32_write64, - .read64 = sst_shim32_read64, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, - .irq_handler = sst_byt_irq, - .init = sst_byt_init, - .free = sst_byt_free, - .parse_fw = sst_byt_parse_fw_image, -}; diff --git a/sound/soc/intel/sst-baytrail-ipc.c b/sound/soc/intel/sst-baytrail-ipc.c deleted file mode 100644 index b4ad98c..0000000 --- a/sound/soc/intel/sst-baytrail-ipc.c +++ /dev/null @@ -1,983 +0,0 @@ -/* - * Intel Baytrail SST IPC Support - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sst-baytrail-ipc.h" -#include "sst-dsp.h" -#include "sst-dsp-priv.h" - -/* IPC message timeout */ -#define IPC_TIMEOUT_MSECS 300 -#define IPC_BOOT_MSECS 200 - -#define IPC_EMPTY_LIST_SIZE 8 - -/* IPC header bits */ -#define IPC_HEADER_MSG_ID_MASK 0xff -#define IPC_HEADER_MSG_ID(x) ((x) & IPC_HEADER_MSG_ID_MASK) -#define IPC_HEADER_STR_ID_SHIFT 8 -#define IPC_HEADER_STR_ID_MASK 0x1f -#define IPC_HEADER_STR_ID(x) (((x) & 0x1f) << IPC_HEADER_STR_ID_SHIFT) -#define IPC_HEADER_LARGE_SHIFT 13 -#define IPC_HEADER_LARGE(x) (((x) & 0x1) << IPC_HEADER_LARGE_SHIFT) -#define IPC_HEADER_DATA_SHIFT 16 -#define IPC_HEADER_DATA_MASK 0x3fff -#define IPC_HEADER_DATA(x) (((x) & 0x3fff) << IPC_HEADER_DATA_SHIFT) - -/* mask for differentiating between notification and reply message */ -#define IPC_NOTIFICATION (0x1 << 7) - -/* I2L Stream config/control msgs */ -#define IPC_IA_ALLOC_STREAM 0x20 -#define IPC_IA_FREE_STREAM 0x21 -#define IPC_IA_PAUSE_STREAM 0x24 -#define IPC_IA_RESUME_STREAM 0x25 -#define IPC_IA_DROP_STREAM 0x26 -#define IPC_IA_START_STREAM 0x30 - -/* notification messages */ -#define IPC_IA_FW_INIT_CMPLT 0x81 -#define IPC_SST_PERIOD_ELAPSED 0x97 - -/* IPC messages between host and ADSP */ -struct sst_byt_address_info { - u32 addr; - u32 size; -} __packed; - -struct sst_byt_str_type { - u8 codec_type; - u8 str_type; - u8 operation; - u8 protected_str; - u8 time_slots; - u8 reserved; - u16 result; -} __packed; - -struct sst_byt_pcm_params { - u8 num_chan; - u8 pcm_wd_sz; - u8 use_offload_path; - u8 reserved; - u32 sfreq; - u8 channel_map[8]; -} __packed; - -struct sst_byt_frames_info { - u16 num_entries; - u16 rsrvd; - u32 frag_size; - struct sst_byt_address_info ring_buf_info[8]; -} __packed; - -struct sst_byt_alloc_params { - struct sst_byt_str_type str_type; - struct sst_byt_pcm_params pcm_params; - struct sst_byt_frames_info frame_info; -} __packed; - -struct sst_byt_alloc_response { - struct sst_byt_str_type str_type; - u8 reserved[88]; -} __packed; - -struct sst_byt_start_stream_params { - u32 byte_offset; -} __packed; - -struct sst_byt_tstamp { - u64 ring_buffer_counter; - u64 hardware_counter; - u64 frames_decoded; - u64 bytes_decoded; - u64 bytes_copied; - u32 sampling_frequency; - u32 channel_peak[8]; -} __packed; - -struct sst_byt_fw_version { - u8 build; - u8 minor; - u8 major; - u8 type; -} __packed; - -struct sst_byt_fw_build_info { - u8 date[16]; - u8 time[16]; -} __packed; - -struct sst_byt_fw_init { - struct sst_byt_fw_version fw_version; - struct sst_byt_fw_build_info build_info; - u16 result; - u8 module_id; - u8 debug_info; -} __packed; - -/* driver internal IPC message structure */ -struct ipc_message { - struct list_head list; - u64 header; - - /* direction wrt host CPU */ - char tx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; - size_t tx_size; - char rx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; - size_t rx_size; - - wait_queue_head_t waitq; - bool complete; - bool wait; - int errno; -}; - -struct sst_byt_stream; -struct sst_byt; - -/* stream infomation */ -struct sst_byt_stream { - struct list_head node; - - /* configuration */ - struct sst_byt_alloc_params request; - struct sst_byt_alloc_response reply; - - /* runtime info */ - struct sst_byt *byt; - int str_id; - bool commited; - bool running; - - /* driver callback */ - u32 (*notify_position)(struct sst_byt_stream *stream, void *data); - void *pdata; -}; - -/* SST Baytrail IPC data */ -struct sst_byt { - struct device *dev; - struct sst_dsp *dsp; - - /* stream */ - struct list_head stream_list; - - /* boot */ - wait_queue_head_t boot_wait; - bool boot_complete; - struct sst_fw *fw; - - /* IPC messaging */ - struct list_head tx_list; - struct list_head rx_list; - struct list_head empty_list; - wait_queue_head_t wait_txq; - struct task_struct *tx_thread; - struct kthread_worker kworker; - struct kthread_work kwork; - struct ipc_message *msg; -}; - -static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) -{ - u64 header; - - header = IPC_HEADER_MSG_ID(msg_id) | - IPC_HEADER_STR_ID(str_id) | - IPC_HEADER_LARGE(large) | - IPC_HEADER_DATA(data) | - SST_BYT_IPCX_BUSY; - - return header; -} - -static inline u16 sst_byt_header_msg_id(u64 header) -{ - return header & IPC_HEADER_MSG_ID_MASK; -} - -static inline u8 sst_byt_header_str_id(u64 header) -{ - return (header >> IPC_HEADER_STR_ID_SHIFT) & IPC_HEADER_STR_ID_MASK; -} - -static inline u16 sst_byt_header_data(u64 header) -{ - return (header >> IPC_HEADER_DATA_SHIFT) & IPC_HEADER_DATA_MASK; -} - -static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt, - int stream_id) -{ - struct sst_byt_stream *stream; - - list_for_each_entry(stream, &byt->stream_list, node) { - if (stream->str_id == stream_id) - return stream; - } - - return NULL; -} - -static void sst_byt_ipc_shim_dbg(struct sst_byt *byt, const char *text) -{ - struct sst_dsp *sst = byt->dsp; - u64 isr, ipcd, imrx, ipcx; - - ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); - isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); - ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); - - dev_err(byt->dev, - "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", - text, ipcx, isr, ipcd, imrx); -} - -/* locks held by caller */ -static struct ipc_message *sst_byt_msg_get_empty(struct sst_byt *byt) -{ - struct ipc_message *msg = NULL; - - if (!list_empty(&byt->empty_list)) { - msg = list_first_entry(&byt->empty_list, - struct ipc_message, list); - list_del(&msg->list); - } - - return msg; -} - -static void sst_byt_ipc_tx_msgs(struct kthread_work *work) -{ - struct sst_byt *byt = - container_of(work, struct sst_byt, kwork); - struct ipc_message *msg; - u64 ipcx; - unsigned long flags; - - spin_lock_irqsave(&byt->dsp->spinlock, flags); - if (list_empty(&byt->tx_list)) { - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return; - } - - /* if the DSP is busy we will TX messages after IRQ */ - ipcx = sst_dsp_shim_read64_unlocked(byt->dsp, SST_IPCX); - if (ipcx & SST_BYT_IPCX_BUSY) { - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return; - } - - msg = list_first_entry(&byt->tx_list, struct ipc_message, list); - - list_move(&msg->list, &byt->rx_list); - - /* send the message */ - if (msg->header & IPC_HEADER_LARGE(true)) - sst_dsp_outbox_write(byt->dsp, msg->tx_data, msg->tx_size); - sst_dsp_shim_write64_unlocked(byt->dsp, SST_IPCX, msg->header); - - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); -} - -static inline void sst_byt_tx_msg_reply_complete(struct sst_byt *byt, - struct ipc_message *msg) -{ - msg->complete = true; - - if (!msg->wait) - list_add_tail(&msg->list, &byt->empty_list); - else - wake_up(&msg->waitq); -} - -static void sst_byt_drop_all(struct sst_byt *byt) -{ - struct ipc_message *msg, *tmp; - unsigned long flags; - - /* drop all TX and Rx messages before we stall + reset DSP */ - spin_lock_irqsave(&byt->dsp->spinlock, flags); - list_for_each_entry_safe(msg, tmp, &byt->tx_list, list) { - list_move(&msg->list, &byt->empty_list); - } - - list_for_each_entry_safe(msg, tmp, &byt->rx_list, list) { - list_move(&msg->list, &byt->empty_list); - } - - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); -} - -static int sst_byt_tx_wait_done(struct sst_byt *byt, struct ipc_message *msg, - void *rx_data) -{ - unsigned long flags; - int ret; - - /* wait for DSP completion */ - ret = wait_event_timeout(msg->waitq, msg->complete, - msecs_to_jiffies(IPC_TIMEOUT_MSECS)); - - spin_lock_irqsave(&byt->dsp->spinlock, flags); - if (ret == 0) { - list_del(&msg->list); - sst_byt_ipc_shim_dbg(byt, "message timeout"); - - ret = -ETIMEDOUT; - } else { - - /* copy the data returned from DSP */ - if (msg->rx_size) - memcpy(rx_data, msg->rx_data, msg->rx_size); - ret = msg->errno; - } - - list_add_tail(&msg->list, &byt->empty_list); - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return ret; -} - -static int sst_byt_ipc_tx_message(struct sst_byt *byt, u64 header, - void *tx_data, size_t tx_bytes, - void *rx_data, size_t rx_bytes, int wait) -{ - unsigned long flags; - struct ipc_message *msg; - - spin_lock_irqsave(&byt->dsp->spinlock, flags); - - msg = sst_byt_msg_get_empty(byt); - if (msg == NULL) { - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return -EBUSY; - } - - msg->header = header; - msg->tx_size = tx_bytes; - msg->rx_size = rx_bytes; - msg->wait = wait; - msg->errno = 0; - msg->complete = false; - - if (tx_bytes) { - /* msg content = lower 32-bit of the header + data */ - *(u32 *)msg->tx_data = (u32)(header & (u32)-1); - memcpy(msg->tx_data + sizeof(u32), tx_data, tx_bytes); - msg->tx_size += sizeof(u32); - } - - list_add_tail(&msg->list, &byt->tx_list); - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - - queue_kthread_work(&byt->kworker, &byt->kwork); - - if (wait) - return sst_byt_tx_wait_done(byt, msg, rx_data); - else - return 0; -} - -static inline int sst_byt_ipc_tx_msg_wait(struct sst_byt *byt, u64 header, - void *tx_data, size_t tx_bytes, - void *rx_data, size_t rx_bytes) -{ - return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, - rx_data, rx_bytes, 1); -} - -static inline int sst_byt_ipc_tx_msg_nowait(struct sst_byt *byt, u64 header, - void *tx_data, size_t tx_bytes) -{ - return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, - NULL, 0, 0); -} - -static struct ipc_message *sst_byt_reply_find_msg(struct sst_byt *byt, - u64 header) -{ - struct ipc_message *msg = NULL, *_msg; - u64 mask; - - /* match reply to message sent based on msg and stream IDs */ - mask = IPC_HEADER_MSG_ID_MASK | - IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; - header &= mask; - - if (list_empty(&byt->rx_list)) { - dev_err(byt->dev, - "ipc: rx list is empty but received 0x%llx\n", header); - goto out; - } - - list_for_each_entry(_msg, &byt->rx_list, list) { - if ((_msg->header & mask) == header) { - msg = _msg; - break; - } - } - -out: - return msg; -} - -static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) -{ - struct sst_byt_stream *stream; - u64 header = msg->header; - u8 stream_id = sst_byt_header_str_id(header); - u8 stream_msg = sst_byt_header_msg_id(header); - - stream = sst_byt_get_stream(byt, stream_id); - if (stream == NULL) - return; - - switch (stream_msg) { - case IPC_IA_DROP_STREAM: - case IPC_IA_PAUSE_STREAM: - case IPC_IA_FREE_STREAM: - stream->running = false; - break; - case IPC_IA_START_STREAM: - case IPC_IA_RESUME_STREAM: - stream->running = true; - break; - } -} - -static int sst_byt_process_reply(struct sst_byt *byt, u64 header) -{ - struct ipc_message *msg; - - msg = sst_byt_reply_find_msg(byt, header); - if (msg == NULL) - return 1; - - if (header & IPC_HEADER_LARGE(true)) { - msg->rx_size = sst_byt_header_data(header); - sst_dsp_inbox_read(byt->dsp, msg->rx_data, msg->rx_size); - } - - /* update any stream states */ - sst_byt_stream_update(byt, msg); - - list_del(&msg->list); - /* wake up */ - sst_byt_tx_msg_reply_complete(byt, msg); - - return 1; -} - -static void sst_byt_fw_ready(struct sst_byt *byt, u64 header) -{ - dev_dbg(byt->dev, "ipc: DSP is ready 0x%llX\n", header); - - byt->boot_complete = true; - wake_up(&byt->boot_wait); -} - -static int sst_byt_process_notification(struct sst_byt *byt, - unsigned long *flags) -{ - struct sst_dsp *sst = byt->dsp; - struct sst_byt_stream *stream; - u64 header; - u8 msg_id, stream_id; - int handled = 1; - - header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - msg_id = sst_byt_header_msg_id(header); - - switch (msg_id) { - case IPC_SST_PERIOD_ELAPSED: - stream_id = sst_byt_header_str_id(header); - stream = sst_byt_get_stream(byt, stream_id); - if (stream && stream->running && stream->notify_position) { - spin_unlock_irqrestore(&sst->spinlock, *flags); - stream->notify_position(stream, stream->pdata); - spin_lock_irqsave(&sst->spinlock, *flags); - } - break; - case IPC_IA_FW_INIT_CMPLT: - sst_byt_fw_ready(byt, header); - break; - } - - return handled; -} - -static irqreturn_t sst_byt_irq_thread(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - struct sst_byt *byt = sst_dsp_get_thread_context(sst); - u64 header; - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - - header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - if (header & SST_BYT_IPCD_BUSY) { - if (header & IPC_NOTIFICATION) { - /* message from ADSP */ - sst_byt_process_notification(byt, &flags); - } else { - /* reply from ADSP */ - sst_byt_process_reply(byt, header); - } - /* - * clear IPCD BUSY bit and set DONE bit. Tell DSP we have - * processed the message and can accept new. Clear data part - * of the header - */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD, - SST_BYT_IPCD_DONE | SST_BYT_IPCD_BUSY | - IPC_HEADER_DATA(IPC_HEADER_DATA_MASK), - SST_BYT_IPCD_DONE); - /* unmask message request interrupts */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, - SST_BYT_IMRX_REQUEST, 0); - } - - spin_unlock_irqrestore(&sst->spinlock, flags); - - /* continue to send any remaining messages... */ - queue_kthread_work(&byt->kworker, &byt->kwork); - - return IRQ_HANDLED; -} - -/* stream API */ -struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, - u32 (*notify_position)(struct sst_byt_stream *stream, void *data), - void *data) -{ - struct sst_byt_stream *stream; - struct sst_dsp *sst = byt->dsp; - unsigned long flags; - - stream = kzalloc(sizeof(*stream), GFP_KERNEL); - if (stream == NULL) - return NULL; - - spin_lock_irqsave(&sst->spinlock, flags); - list_add(&stream->node, &byt->stream_list); - stream->notify_position = notify_position; - stream->pdata = data; - stream->byt = byt; - stream->str_id = id; - spin_unlock_irqrestore(&sst->spinlock, flags); - - return stream; -} - -int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, - int bits) -{ - stream->request.pcm_params.pcm_wd_sz = bits; - return 0; -} - -int sst_byt_stream_set_channels(struct sst_byt *byt, - struct sst_byt_stream *stream, u8 channels) -{ - stream->request.pcm_params.num_chan = channels; - return 0; -} - -int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, - unsigned int rate) -{ - stream->request.pcm_params.sfreq = rate; - return 0; -} - -/* stream sonfiguration */ -int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, - int codec_type, int stream_type, int operation) -{ - stream->request.str_type.codec_type = codec_type; - stream->request.str_type.str_type = stream_type; - stream->request.str_type.operation = operation; - stream->request.str_type.time_slots = 0xc; - - return 0; -} - -int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, - uint32_t buffer_addr, uint32_t buffer_size) -{ - stream->request.frame_info.num_entries = 1; - stream->request.frame_info.ring_buf_info[0].addr = buffer_addr; - stream->request.frame_info.ring_buf_info[0].size = buffer_size; - /* calculate bytes per 4 ms fragment */ - stream->request.frame_info.frag_size = - stream->request.pcm_params.sfreq * - stream->request.pcm_params.num_chan * - stream->request.pcm_params.pcm_wd_sz / 8 * - 4 / 1000; - return 0; -} - -int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - struct sst_byt_alloc_params *str_req = &stream->request; - struct sst_byt_alloc_response *reply = &stream->reply; - u64 header; - int ret; - - header = sst_byt_header(IPC_IA_ALLOC_STREAM, - sizeof(*str_req) + sizeof(u32), - true, stream->str_id); - ret = sst_byt_ipc_tx_msg_wait(byt, header, str_req, sizeof(*str_req), - reply, sizeof(*reply)); - if (ret < 0) { - dev_err(byt->dev, "ipc: error stream commit failed\n"); - return ret; - } - - stream->commited = true; - - return 0; -} - -int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - u64 header; - int ret = 0; - struct sst_dsp *sst = byt->dsp; - unsigned long flags; - - if (!stream->commited) - goto out; - - header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id); - ret = sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); - if (ret < 0) { - dev_err(byt->dev, "ipc: free stream %d failed\n", - stream->str_id); - return -EAGAIN; - } - - stream->commited = false; -out: - spin_lock_irqsave(&sst->spinlock, flags); - list_del(&stream->node); - kfree(stream); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return ret; -} - -static int sst_byt_stream_operations(struct sst_byt *byt, int type, - int stream_id, int wait) -{ - u64 header; - - header = sst_byt_header(type, 0, false, stream_id); - if (wait) - return sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); - else - return sst_byt_ipc_tx_msg_nowait(byt, header, NULL, 0); -} - -/* stream ALSA trigger operations */ -int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, - u32 start_offset) -{ - struct sst_byt_start_stream_params start_stream; - void *tx_msg; - size_t size; - u64 header; - int ret; - - start_stream.byte_offset = start_offset; - header = sst_byt_header(IPC_IA_START_STREAM, - sizeof(start_stream) + sizeof(u32), - true, stream->str_id); - tx_msg = &start_stream; - size = sizeof(start_stream); - - ret = sst_byt_ipc_tx_msg_nowait(byt, header, tx_msg, size); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to start stream %d\n", - stream->str_id); - - return ret; -} - -int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - int ret; - - /* don't stop streams that are not commited */ - if (!stream->commited) - return 0; - - ret = sst_byt_stream_operations(byt, IPC_IA_DROP_STREAM, - stream->str_id, 0); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to stop stream %d\n", - stream->str_id); - return ret; -} - -int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - int ret; - - ret = sst_byt_stream_operations(byt, IPC_IA_PAUSE_STREAM, - stream->str_id, 0); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to pause stream %d\n", - stream->str_id); - - return ret; -} - -int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - int ret; - - ret = sst_byt_stream_operations(byt, IPC_IA_RESUME_STREAM, - stream->str_id, 0); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to resume stream %d\n", - stream->str_id); - - return ret; -} - -int sst_byt_get_dsp_position(struct sst_byt *byt, - struct sst_byt_stream *stream, int buffer_size) -{ - struct sst_dsp *sst = byt->dsp; - struct sst_byt_tstamp fw_tstamp; - u8 str_id = stream->str_id; - u32 tstamp_offset; - - tstamp_offset = SST_BYT_TIMESTAMP_OFFSET + str_id * sizeof(fw_tstamp); - memcpy_fromio(&fw_tstamp, - sst->addr.lpe + tstamp_offset, sizeof(fw_tstamp)); - - return do_div(fw_tstamp.ring_buffer_counter, buffer_size); -} - -static int msg_empty_list_init(struct sst_byt *byt) -{ - struct ipc_message *msg; - int i; - - byt->msg = kzalloc(sizeof(*msg) * IPC_EMPTY_LIST_SIZE, GFP_KERNEL); - if (byt->msg == NULL) - return -ENOMEM; - - for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { - init_waitqueue_head(&byt->msg[i].waitq); - list_add(&byt->msg[i].list, &byt->empty_list); - } - - return 0; -} - -struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt) -{ - return byt->dsp; -} - -static struct sst_dsp_device byt_dev = { - .thread = sst_byt_irq_thread, - .ops = &sst_byt_ops, -}; - -int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - - dev_dbg(byt->dev, "dsp reset\n"); - sst_dsp_reset(byt->dsp); - sst_byt_drop_all(byt); - dev_dbg(byt->dev, "dsp in reset\n"); - - dev_dbg(byt->dev, "free all blocks and unload fw\n"); - sst_fw_unload(byt->fw); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_suspend_late); - -int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - int ret; - - dev_dbg(byt->dev, "reload dsp fw\n"); - - sst_dsp_reset(byt->dsp); - - ret = sst_fw_reload(byt->fw); - if (ret < 0) { - dev_err(dev, "error: failed to reload firmware\n"); - return ret; - } - - /* wait for DSP boot completion */ - byt->boot_complete = false; - sst_dsp_boot(byt->dsp); - dev_dbg(byt->dev, "dsp booting...\n"); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_boot); - -int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - int err; - - dev_dbg(byt->dev, "wait for dsp reboot\n"); - - err = wait_event_timeout(byt->boot_wait, byt->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (err == 0) { - dev_err(byt->dev, "ipc: error DSP boot timeout\n"); - return -EIO; - } - - dev_dbg(byt->dev, "dsp rebooted\n"); - return 0; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_wait_for_ready); - -int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt; - struct sst_fw *byt_sst_fw; - struct sst_byt_fw_init init; - int err; - - dev_dbg(dev, "initialising Byt DSP IPC\n"); - - byt = devm_kzalloc(dev, sizeof(*byt), GFP_KERNEL); - if (byt == NULL) - return -ENOMEM; - - byt->dev = dev; - INIT_LIST_HEAD(&byt->stream_list); - INIT_LIST_HEAD(&byt->tx_list); - INIT_LIST_HEAD(&byt->rx_list); - INIT_LIST_HEAD(&byt->empty_list); - init_waitqueue_head(&byt->boot_wait); - init_waitqueue_head(&byt->wait_txq); - - err = msg_empty_list_init(byt); - if (err < 0) - return -ENOMEM; - - /* start the IPC message thread */ - init_kthread_worker(&byt->kworker); - byt->tx_thread = kthread_run(kthread_worker_fn, - &byt->kworker, "%s", - dev_name(byt->dev)); - if (IS_ERR(byt->tx_thread)) { - err = PTR_ERR(byt->tx_thread); - dev_err(byt->dev, "error failed to create message TX task\n"); - goto err_free_msg; - } - init_kthread_work(&byt->kwork, sst_byt_ipc_tx_msgs); - - byt_dev.thread_context = byt; - - /* init SST shim */ - byt->dsp = sst_dsp_new(dev, &byt_dev, pdata); - if (byt->dsp == NULL) { - err = -ENODEV; - goto dsp_err; - } - - /* keep the DSP in reset state for base FW loading */ - sst_dsp_reset(byt->dsp); - - byt_sst_fw = sst_fw_new(byt->dsp, pdata->fw, byt); - if (byt_sst_fw == NULL) { - err = -ENODEV; - dev_err(dev, "error: failed to load firmware\n"); - goto fw_err; - } - - /* wait for DSP boot completion */ - sst_dsp_boot(byt->dsp); - err = wait_event_timeout(byt->boot_wait, byt->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (err == 0) { - err = -EIO; - dev_err(byt->dev, "ipc: error DSP boot timeout\n"); - goto boot_err; - } - - /* show firmware information */ - sst_dsp_inbox_read(byt->dsp, &init, sizeof(init)); - dev_info(byt->dev, "FW version: %02x.%02x.%02x.%02x\n", - init.fw_version.major, init.fw_version.minor, - init.fw_version.build, init.fw_version.type); - dev_info(byt->dev, "Build type: %x\n", init.fw_version.type); - dev_info(byt->dev, "Build date: %s %s\n", - init.build_info.date, init.build_info.time); - - pdata->dsp = byt; - byt->fw = byt_sst_fw; - - return 0; - -boot_err: - sst_dsp_reset(byt->dsp); - sst_fw_free(byt_sst_fw); -fw_err: - sst_dsp_free(byt->dsp); -dsp_err: - kthread_stop(byt->tx_thread); -err_free_msg: - kfree(byt->msg); - - return err; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_init); - -void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - - sst_dsp_reset(byt->dsp); - sst_fw_free_all(byt->dsp); - sst_dsp_free(byt->dsp); - kthread_stop(byt->tx_thread); - kfree(byt->msg); -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_free); diff --git a/sound/soc/intel/sst-baytrail-ipc.h b/sound/soc/intel/sst-baytrail-ipc.h deleted file mode 100644 index 8faff6d..0000000 --- a/sound/soc/intel/sst-baytrail-ipc.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Intel Baytrail SST IPC Support - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 __SST_BYT_IPC_H -#define __SST_BYT_IPC_H - -#include - -struct sst_byt; -struct sst_byt_stream; -struct sst_pdata; -extern struct sst_ops sst_byt_ops; - - -#define SST_BYT_MAILBOX_OFFSET 0x144000 -#define SST_BYT_TIMESTAMP_OFFSET (SST_BYT_MAILBOX_OFFSET + 0x800) - -/** - * Upfront defined maximum message size that is - * expected by the in/out communication pipes in FW. - */ -#define SST_BYT_IPC_MAX_PAYLOAD_SIZE 200 - -/* stream API */ -struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, - uint32_t (*get_write_position)(struct sst_byt_stream *stream, - void *data), - void *data); - -/* stream configuration */ -int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, - int bits); -int sst_byt_stream_set_channels(struct sst_byt *byt, - struct sst_byt_stream *stream, u8 channels); -int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, - unsigned int rate); -int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, - int codec_type, int stream_type, int operation); -int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, - uint32_t buffer_addr, uint32_t buffer_size); -int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream); -int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream); - -/* stream ALSA trigger operations */ -int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, - u32 start_offset); -int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream); -int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream); -int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream); - -int sst_byt_get_dsp_position(struct sst_byt *byt, - struct sst_byt_stream *stream, int buffer_size); - -/* init */ -int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata); -void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata); -struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt); -int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata); -int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata); -int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata); - -#endif diff --git a/sound/soc/intel/sst-baytrail-pcm.c b/sound/soc/intel/sst-baytrail-pcm.c deleted file mode 100644 index 224c49c..0000000 --- a/sound/soc/intel/sst-baytrail-pcm.c +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Intel Baytrail SST PCM Support - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include "sst-baytrail-ipc.h" -#include "sst-dsp-priv.h" -#include "sst-dsp.h" - -#define BYT_PCM_COUNT 2 - -static const struct snd_pcm_hardware sst_byt_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_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .period_bytes_min = 384, - .period_bytes_max = 48000, - .periods_min = 2, - .periods_max = 250, - .buffer_bytes_max = 96000, -}; - -/* private data for each PCM DSP stream */ -struct sst_byt_pcm_data { - struct sst_byt_stream *stream; - struct snd_pcm_substream *substream; - struct mutex mutex; - - /* latest DSP DMA hw pointer */ - u32 hw_ptr; - - struct work_struct work; -}; - -/* private data for the driver */ -struct sst_byt_priv_data { - /* runtime DSP */ - struct sst_byt *byt; - - /* DAI data */ - struct sst_byt_pcm_data pcm[BYT_PCM_COUNT]; - - /* flag indicating is stream context restore needed after suspend */ - bool restore_stream; -}; - -/* this may get called several times by oss emulation */ -static int sst_byt_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - u32 rate, bits; - u8 channels; - int ret, playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - - dev_dbg(rtd->dev, "PCM: hw_params, pcm_data %p\n", pcm_data); - - ret = sst_byt_stream_type(byt, pcm_data->stream, - 1, 1, !playback); - if (ret < 0) { - dev_err(rtd->dev, "failed to set stream format %d\n", ret); - return ret; - } - - rate = params_rate(params); - ret = sst_byt_stream_set_rate(byt, pcm_data->stream, rate); - if (ret < 0) { - dev_err(rtd->dev, "could not set rate %d\n", rate); - return ret; - } - - bits = snd_pcm_format_width(params_format(params)); - ret = sst_byt_stream_set_bits(byt, pcm_data->stream, bits); - if (ret < 0) { - dev_err(rtd->dev, "could not set formats %d\n", - params_rate(params)); - return ret; - } - - channels = (u8)(params_channels(params) & 0xF); - ret = sst_byt_stream_set_channels(byt, pcm_data->stream, channels); - if (ret < 0) { - dev_err(rtd->dev, "could not set channels %d\n", - params_rate(params)); - return ret; - } - - snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - - ret = sst_byt_stream_buffer(byt, pcm_data->stream, - substream->dma_buffer.addr, - params_buffer_bytes(params)); - if (ret < 0) { - dev_err(rtd->dev, "PCM: failed to set DMA buffer %d\n", ret); - return ret; - } - - ret = sst_byt_stream_commit(byt, pcm_data->stream); - if (ret < 0) { - dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); - return ret; - } - - return 0; -} - -static int sst_byt_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - dev_dbg(rtd->dev, "PCM: hw_free\n"); - snd_pcm_lib_free_pages(substream); - - return 0; -} - -static int sst_byt_pcm_restore_stream_context(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - int ret; - - /* commit stream using existing stream params */ - ret = sst_byt_stream_commit(byt, pcm_data->stream); - if (ret < 0) { - dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); - return ret; - } - - sst_byt_stream_start(byt, pcm_data->stream, pcm_data->hw_ptr); - - dev_dbg(rtd->dev, "stream context restored at offset %d\n", - pcm_data->hw_ptr); - - return 0; -} - -static void sst_byt_pcm_work(struct work_struct *work) -{ - struct sst_byt_pcm_data *pcm_data = - container_of(work, struct sst_byt_pcm_data, work); - - if (snd_pcm_running(pcm_data->substream)) - sst_byt_pcm_restore_stream_context(pcm_data->substream); -} - -static int sst_byt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - - dev_dbg(rtd->dev, "PCM: trigger %d\n", cmd); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - pcm_data->hw_ptr = 0; - sst_byt_stream_start(byt, pcm_data->stream, 0); - break; - case SNDRV_PCM_TRIGGER_RESUME: - if (pdata->restore_stream == true) - schedule_work(&pcm_data->work); - else - sst_byt_stream_resume(byt, pcm_data->stream); - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - sst_byt_stream_resume(byt, pcm_data->stream); - break; - case SNDRV_PCM_TRIGGER_STOP: - sst_byt_stream_stop(byt, pcm_data->stream); - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - pdata->restore_stream = false; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - sst_byt_stream_pause(byt, pcm_data->stream); - break; - default: - break; - } - - return 0; -} - -static u32 byt_notify_pointer(struct sst_byt_stream *stream, void *data) -{ - struct sst_byt_pcm_data *pcm_data = data; - struct snd_pcm_substream *substream = pcm_data->substream; - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt *byt = pdata->byt; - u32 pos, hw_pos; - - hw_pos = sst_byt_get_dsp_position(byt, pcm_data->stream, - snd_pcm_lib_buffer_bytes(substream)); - pcm_data->hw_ptr = hw_pos; - pos = frames_to_bytes(runtime, - (runtime->control->appl_ptr % - runtime->buffer_size)); - - dev_dbg(rtd->dev, "PCM: App/DMA pointer %u/%u bytes\n", pos, hw_pos); - - snd_pcm_period_elapsed(substream); - return pos; -} - -static snd_pcm_uframes_t sst_byt_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - - dev_dbg(rtd->dev, "PCM: DMA pointer %u bytes\n", pcm_data->hw_ptr); - - return bytes_to_frames(runtime, pcm_data->hw_ptr); -} - -static int sst_byt_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - - dev_dbg(rtd->dev, "PCM: open\n"); - - mutex_lock(&pcm_data->mutex); - - pcm_data->substream = substream; - - snd_soc_set_runtime_hwparams(substream, &sst_byt_pcm_hardware); - - pcm_data->stream = sst_byt_stream_new(byt, substream->stream + 1, - byt_notify_pointer, pcm_data); - if (pcm_data->stream == NULL) { - dev_err(rtd->dev, "failed to create stream\n"); - mutex_unlock(&pcm_data->mutex); - return -EINVAL; - } - - mutex_unlock(&pcm_data->mutex); - return 0; -} - -static int sst_byt_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = - snd_soc_platform_get_drvdata(rtd->platform); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - int ret; - - dev_dbg(rtd->dev, "PCM: close\n"); - - cancel_work_sync(&pcm_data->work); - mutex_lock(&pcm_data->mutex); - ret = sst_byt_stream_free(byt, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "Free stream fail\n"); - goto out; - } - pcm_data->stream = NULL; - -out: - mutex_unlock(&pcm_data->mutex); - return ret; -} - -static int sst_byt_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - dev_dbg(rtd->dev, "PCM: mmap\n"); - return snd_pcm_lib_default_mmap(substream, vma); -} - -static struct snd_pcm_ops sst_byt_pcm_ops = { - .open = sst_byt_pcm_open, - .close = sst_byt_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = sst_byt_pcm_hw_params, - .hw_free = sst_byt_pcm_hw_free, - .trigger = sst_byt_pcm_trigger, - .pointer = sst_byt_pcm_pointer, - .mmap = sst_byt_pcm_mmap, -}; - -static int sst_byt_pcm_new(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_pcm *pcm = rtd->pcm; - size_t size; - struct snd_soc_platform *platform = rtd->platform; - struct sst_pdata *pdata = dev_get_platdata(platform->dev); - int ret = 0; - - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || - pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { - size = sst_byt_pcm_hardware.buffer_bytes_max; - ret = snd_pcm_lib_preallocate_pages_for_all(pcm, - SNDRV_DMA_TYPE_DEV, - pdata->dma_dev, - size, size); - if (ret) { - dev_err(rtd->dev, "dma buffer allocation failed %d\n", - ret); - return ret; - } - } - - return ret; -} - -static struct snd_soc_dai_driver byt_dais[] = { - { - .name = "Baytrail PCM", - .playback = { - .stream_name = "System Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_3LE | - SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "Analog Capture", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - }, -}; - -static int sst_byt_pcm_probe(struct snd_soc_platform *platform) -{ - struct sst_pdata *plat_data = dev_get_platdata(platform->dev); - struct sst_byt_priv_data *priv_data; - int i; - - if (!plat_data) - return -ENODEV; - - priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), - GFP_KERNEL); - priv_data->byt = plat_data->dsp; - snd_soc_platform_set_drvdata(platform, priv_data); - - for (i = 0; i < BYT_PCM_COUNT; i++) { - mutex_init(&priv_data->pcm[i].mutex); - INIT_WORK(&priv_data->pcm[i].work, sst_byt_pcm_work); - } - - return 0; -} - -static int sst_byt_pcm_remove(struct snd_soc_platform *platform) -{ - return 0; -} - -static struct snd_soc_platform_driver byt_soc_platform = { - .probe = sst_byt_pcm_probe, - .remove = sst_byt_pcm_remove, - .ops = &sst_byt_pcm_ops, - .pcm_new = sst_byt_pcm_new, -}; - -static const struct snd_soc_component_driver byt_dai_component = { - .name = "byt-dai", -}; - -#ifdef CONFIG_PM -static int sst_byt_pcm_dev_suspend_late(struct device *dev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(dev); - struct sst_byt_priv_data *priv_data = dev_get_drvdata(dev); - int ret; - - dev_dbg(dev, "suspending late\n"); - - ret = sst_byt_dsp_suspend_late(dev, sst_pdata); - if (ret < 0) { - dev_err(dev, "failed to suspend %d\n", ret); - return ret; - } - - priv_data->restore_stream = true; - - return ret; -} - -static int sst_byt_pcm_dev_resume_early(struct device *dev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(dev); - int ret; - - dev_dbg(dev, "resume early\n"); - - /* load fw and boot DSP */ - ret = sst_byt_dsp_boot(dev, sst_pdata); - if (ret) - return ret; - - /* wait for FW to finish booting */ - return sst_byt_dsp_wait_for_ready(dev, sst_pdata); -} - -static const struct dev_pm_ops sst_byt_pm_ops = { - .suspend_late = sst_byt_pcm_dev_suspend_late, - .resume_early = sst_byt_pcm_dev_resume_early, -}; - -#define SST_BYT_PM_OPS (&sst_byt_pm_ops) -#else -#define SST_BYT_PM_OPS NULL -#endif - -static int sst_byt_pcm_dev_probe(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - int ret; - - ret = sst_byt_dsp_init(&pdev->dev, sst_pdata); - if (ret < 0) - return -ENODEV; - - ret = snd_soc_register_platform(&pdev->dev, &byt_soc_platform); - if (ret < 0) - goto err_plat; - - ret = snd_soc_register_component(&pdev->dev, &byt_dai_component, - byt_dais, ARRAY_SIZE(byt_dais)); - if (ret < 0) - goto err_comp; - - return 0; - -err_comp: - snd_soc_unregister_platform(&pdev->dev); -err_plat: - sst_byt_dsp_free(&pdev->dev, sst_pdata); - return ret; -} - -static int sst_byt_pcm_dev_remove(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - - snd_soc_unregister_platform(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - sst_byt_dsp_free(&pdev->dev, sst_pdata); - - return 0; -} - -static struct platform_driver sst_byt_pcm_driver = { - .driver = { - .name = "baytrail-pcm-audio", - .pm = SST_BYT_PM_OPS, - }, - - .probe = sst_byt_pcm_dev_probe, - .remove = sst_byt_pcm_dev_remove, -}; -module_platform_driver(sst_byt_pcm_driver); - -MODULE_AUTHOR("Jarkko Nikula"); -MODULE_DESCRIPTION("Baytrail PCM"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:baytrail-pcm-audio"); -- cgit v0.10.2 From b97169da06992ef04081e66ed22bbdb23dbf6610 Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Thu, 2 Apr 2015 15:37:04 +0800 Subject: ASoC: Intel: create atom folder and move atom platform files in Restructure the sound/soc/intel/ directory: create atom folder, and move sst atom platform files here. Signed-off-by: Jie Yang Acked-by: Vinod Koul Signed-off-by: Mark Brown diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index 62de82a..cd9aee9 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -1,19 +1,10 @@ # Core support obj-$(CONFIG_SND_SOC_INTEL_SST) += common/ -snd-soc-sst-mfld-platform-objs := sst-mfld-platform-pcm.o \ - sst-mfld-platform-compress.o sst-atom-controls.o -snd-soc-mfld-machine-objs := mfld_machine.o - -obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o -obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o - # Platform Support obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/ obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += baytrail/ +obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += atom/ # Machine support obj-$(CONFIG_SND_SOC_INTEL_SST) += boards/ - -# DSP driver -obj-$(CONFIG_SND_SST_IPC) += sst/ diff --git a/sound/soc/intel/atom/Makefile b/sound/soc/intel/atom/Makefile new file mode 100644 index 0000000..ce8074f --- /dev/null +++ b/sound/soc/intel/atom/Makefile @@ -0,0 +1,7 @@ +snd-soc-sst-mfld-platform-objs := sst-mfld-platform-pcm.o \ + sst-mfld-platform-compress.o sst-atom-controls.o + +obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o + +# DSP driver +obj-$(CONFIG_SND_SST_IPC) += sst/ diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c new file mode 100644 index 0000000..90aa5c0 --- /dev/null +++ b/sound/soc/intel/atom/sst-atom-controls.c @@ -0,0 +1,1422 @@ +/* + * sst-atom-controls.c - Intel MID Platform driver DPCM ALSA controls for Mrfld + * + * Copyright (C) 2013-14 Intel Corp + * Author: Omair Mohammed Abdullah + * Vinod Koul + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active + * we forward the settings and parameters, rest we keep the values in + * driver and forward when DAPM enables them + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include "sst-mfld-platform.h" +#include "sst-atom-controls.h" + +static int sst_fill_byte_control(struct sst_data *drv, + u8 ipc_msg, u8 block, + u8 task_id, u8 pipe_id, + u16 len, void *cmd_data) +{ + struct snd_sst_bytes_v2 *byte_data = drv->byte_stream; + + byte_data->type = SST_CMD_BYTES_SET; + byte_data->ipc_msg = ipc_msg; + byte_data->block = block; + byte_data->task_id = task_id; + byte_data->pipe_id = pipe_id; + + if (len > SST_MAX_BIN_BYTES - sizeof(*byte_data)) { + dev_err(&drv->pdev->dev, "command length too big (%u)", len); + return -EINVAL; + } + byte_data->len = len; + memcpy(byte_data->bytes, cmd_data, len); + print_hex_dump_bytes("writing to lpe: ", DUMP_PREFIX_OFFSET, + byte_data, len + sizeof(*byte_data)); + return 0; +} + +static int sst_fill_and_send_cmd_unlocked(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) +{ + int ret = 0; + + ret = sst_fill_byte_control(drv, ipc_msg, + block, task_id, pipe_id, len, cmd_data); + if (ret < 0) + return ret; + return sst->ops->send_byte_stream(sst->dev, drv->byte_stream); +} + +/** + * sst_fill_and_send_cmd - generate the IPC message and send it to the FW + * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS) + * @cmd_data: the IPC payload + */ +static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) +{ + int ret; + + mutex_lock(&drv->lock); + ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, + task_id, pipe_id, cmd_data, len); + mutex_unlock(&drv->lock); + + return ret; +} + +/** + * tx map value is a bitfield where each bit represents a FW channel + * + * 3 2 1 0 # 0 = codec0, 1 = codec1 + * RLRLRLRL # 3, 4 = reserved + * + * e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R + */ +static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = { + 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */ +}; + +/** + * rx map value is a bitfield where each bit represents a slot + * + * 76543210 # 0 = slot 0, 1 = slot 1 + * + * e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2 + */ +static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = { + 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */ +}; + +/** + * NOTE: this is invoked with lock held + */ +static int sst_send_slot_map(struct sst_data *drv) +{ + struct sst_param_sba_ssp_slot_map cmd; + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.command_id = SBA_SET_SSP_SLOT_MAP; + cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map) + - sizeof(struct sst_dsp_header); + + cmd.param_id = SBA_SET_SSP_SLOT_MAP; + cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map) + + sizeof(cmd.ssp_index); + cmd.ssp_index = SSP_CODEC; + + memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map)); + memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map)); + + return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +int sst_slot_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_enum *e = (struct sst_enum *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = e->max; + + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; + strcpy(uinfo->value.enumerated.name, + e->texts[uinfo->value.enumerated.item]); + + return 0; +} + +/** + * sst_slot_get - get the status of the interleaver/deinterleaver control + * + * Searches the map where the control status is stored, and gets the + * channel/slot which is currently set for this enumerated control. Since it is + * an enumerated control, there is only one possible value. + */ +static int sst_slot_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sst_enum *e = (void *)kcontrol->private_value; + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + unsigned int ctl_no = e->reg; + unsigned int is_tx = e->tx; + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + + mutex_lock(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) + if (map[mux - 1] & val) + break; + + ucontrol->value.enumerated.item[0] = mux; + mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", + e->texts[mux], mux ? map[mux - 1] : -1); + return 0; +} + +/* sst_check_and_send_slot_map - helper for checking power state and sending + * slot map cmd + * + * called with lock held + */ +static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol) +{ + struct sst_enum *e = (void *)kcontrol->private_value; + int ret = 0; + + if (e->w && e->w->power) + ret = sst_send_slot_map(drv); + else + dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n", + kcontrol->id.name); + return ret; +} + +/** + * sst_slot_put - set the status of interleaver/deinterleaver control + * + * (de)interleaver controls are defined in opposite sense to be user-friendly + * + * Instead of the enum value being the value written to the register, it is the + * register address; and the kcontrol number (register num) is the value written + * to the register. This is so that there can be only one value for each + * slot/channel since there is only one control for each slot/channel. + * + * This means that whenever an enum is set, we need to clear the bit + * for that kcontrol_no for all the interleaver OR deinterleaver registers + */ +static int sst_slot_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_enum *e = (void *)kcontrol->private_value; + int i, ret = 0; + unsigned int ctl_no = e->reg; + unsigned int is_tx = e->tx; + unsigned int slot_channel_no; + unsigned int val, mux; + u8 *map; + + map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + + val = 1 << ctl_no; + mux = ucontrol->value.enumerated.item[0]; + if (mux > e->max - 1) + return -EINVAL; + + mutex_lock(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; + + if (mux == 0) { + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + + mutex_unlock(&drv->lock); + return ret; + } + + /* offset by one to take "None" into account */ + slot_channel_no = mux - 1; + map[slot_channel_no] |= val; + + dev_dbg(c->dev, "%s %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", + e->texts[mux], map[slot_channel_no]); + + ret = sst_check_and_send_slot_map(drv, kcontrol); + + mutex_unlock(&drv->lock); + return ret; +} + +static int sst_send_algo_cmd(struct sst_data *drv, + struct sst_algo_control *bc) +{ + int len, ret = 0; + struct sst_cmd_set_params *cmd; + + /*bc->max includes sizeof algos + length field*/ + len = sizeof(cmd->dst) + sizeof(cmd->command_id) + bc->max; + + cmd = kzalloc(len, GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + SST_FILL_DESTINATION(2, cmd->dst, bc->pipe_id, bc->module_id); + cmd->command_id = bc->cmd_id; + memcpy(cmd->params, bc->params, bc->max); + + ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, bc->task_id, 0, cmd, len); + kfree(cmd); + return ret; +} + +/** + * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe + * + * The algos which are in each pipeline are sent to the firmware one by one + * + * Called with lock held + */ +static int sst_find_and_send_pipe_algo(struct sst_data *drv, + const char *pipe, struct sst_ids *ids) +{ + int ret = 0; + struct sst_algo_control *bc; + struct sst_module *algo = NULL; + + dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe); + + list_for_each_entry(algo, &ids->algo_list, node) { + bc = (void *)algo->kctl->private_value; + + dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n", + algo->kctl->id.name, pipe); + ret = sst_send_algo_cmd(drv, bc); + if (ret) + return ret; + } + return ret; +} + +static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = bc->max; + + return 0; +} + +static int sst_algo_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sst_algo_control *bc = (void *)kcontrol->private_value; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(ucontrol->value.bytes.data, bc->params, bc->max); + break; + default: + dev_err(component->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; + + } + return 0; +} + +static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); + mutex_lock(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: + mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; + } + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); + mutex_unlock(&drv->lock); + + return ret; +} + +static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = mc->stereo ? 2 : 1; + uinfo->value.integer.min = mc->min; + uinfo->value.integer.max = mc->max; + + return 0; +} + +/** + * sst_send_gain_cmd - send the gain algorithm IPC to the FW + * @gv: the stored value of gain (also contains rampduration) + * @mute: flag that indicates whether this was called from the + * digital_mute callback or directly. If called from the + * digital_mute callback, module will be muted/unmuted based on this + * flag. The flag is always 0 if called directly. + * + * Called with sst_data.lock held + * + * The user-set gain value is sent only if the user-controllable 'mute' control + * is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is + * sent. + */ +static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv, + u16 task_id, u16 loc_id, u16 module_id, int mute) +{ + struct sst_cmd_set_gain_dual cmd; + + dev_dbg(&drv->pdev->dev, "Enter\n"); + + cmd.header.command_id = MMX_SET_GAIN; + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.gain_cell_num = 1; + + if (mute || gv->mute) { + cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE; + cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE; + } else { + cmd.cell_gains[0].cell_gain_left = gv->l_gain; + cmd.cell_gains[0].cell_gain_right = gv->r_gain; + } + + SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest, + loc_id, module_id); + cmd.cell_gains[0].gain_time_constant = gv->ramp_duration; + + cmd.header.length = sizeof(struct sst_cmd_set_gain_dual) + - sizeof(struct sst_dsp_header); + + /* we are with lock held, so call the unlocked api to send */ + return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +static int sst_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + + switch (mc->type) { + case SST_GAIN_TLV: + ucontrol->value.integer.value[0] = gv->l_gain; + ucontrol->value.integer.value[1] = gv->r_gain; + break; + + case SST_GAIN_MUTE: + ucontrol->value.integer.value[0] = gv->mute ? 1 : 0; + break; + + case SST_GAIN_RAMP_DURATION: + ucontrol->value.integer.value[0] = gv->ramp_duration; + break; + + default: + dev_err(component->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; + } + + return 0; +} + +static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + + mutex_lock(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: + gv->l_gain = ucontrol->value.integer.value[0]; + gv->r_gain = ucontrol->value.integer.value[1]; + dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n", + mc->pname, gv->l_gain, gv->r_gain); + break; + + case SST_GAIN_MUTE: + gv->mute = !!ucontrol->value.integer.value[0]; + dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute); + break; + + case SST_GAIN_RAMP_DURATION: + gv->ramp_duration = ucontrol->value.integer.value[0]; + dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n", + mc->pname, gv->ramp_duration); + break; + + default: + mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; + } + + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); + mutex_unlock(&drv->lock); + + return ret; +} + +static int sst_set_pipe_gain(struct sst_ids *ids, + struct sst_data *drv, int mute); + +static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + mutex_lock(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); + mutex_unlock(&drv->lock); + + return 0; +} + +static int sst_generic_modules_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + return sst_send_pipe_module_params(w, k); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0); + +/* Look up table to convert MIXER SW bit regs to SWM inputs */ +static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = { + [SST_IP_CODEC0] = SST_SWM_IN_CODEC0, + [SST_IP_CODEC1] = SST_SWM_IN_CODEC1, + [SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP, + [SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1, + [SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2, + [SST_IP_PCM0] = SST_SWM_IN_PCM0, + [SST_IP_PCM1] = SST_SWM_IN_PCM1, + [SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0, + [SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1, + [SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2, + [SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3, +}; + +/** + * fill_swm_input - fill in the SWM input ids given the register + * + * The register value is a bit-field inicated which mixer inputs are ON. Use the + * lookup table to get the input-id and fill it in the structure. + */ +static int fill_swm_input(struct snd_soc_component *cmpnt, + struct swm_input_ids *swm_input, unsigned int reg) +{ + uint i, is_set, nb_inputs = 0; + u16 input_loc_id; + + dev_dbg(cmpnt->dev, "reg: %#x\n", reg); + for (i = 0; i < SST_SWM_INPUT_COUNT; i++) { + is_set = reg & BIT(i); + if (!is_set) + continue; + + input_loc_id = swm_mixer_input_ids[i]; + SST_FILL_DESTINATION(2, swm_input->input_id, + input_loc_id, SST_DEFAULT_MODULE_ID); + nb_inputs++; + swm_input++; + dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n", + input_loc_id, nb_inputs); + + if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) { + dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached"); + break; + } + } + return nb_inputs; +} + + +/** + * called with lock held + */ +static int sst_set_pipe_gain(struct sst_ids *ids, + struct sst_data *drv, int mute) +{ + int ret = 0; + struct sst_gain_mixer_control *mc; + struct sst_gain_value *gv; + struct sst_module *gain = NULL; + + list_for_each_entry(gain, &ids->gain_list, node) { + struct snd_kcontrol *kctl = gain->kctl; + + dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name); + mc = (void *)kctl->private_value; + gv = mc->gain_val; + + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, mute); + if (ret) + return ret; + } + return ret; +} + +static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct sst_cmd_set_swm cmd; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_ids *ids = w->priv; + bool set_mixer = false; + struct soc_mixer_control *mc; + int val = 0; + int i = 0; + + dev_dbg(cmpnt->dev, "widget = %s\n", w->name); + /* + * Identify which mixer input is on and send the bitmap of the + * inputs as an IPC to the DSP. + */ + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value; + val |= 1 << mc->shift; + } + } + dev_dbg(cmpnt->dev, "val = %#x\n", val); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + set_mixer = true; + break; + case SND_SOC_DAPM_POST_REG: + if (w->power) + set_mixer = true; + break; + default: + set_mixer = false; + } + + if (set_mixer == false) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event) || + event == SND_SOC_DAPM_POST_REG) + cmd.switch_state = SST_SWM_ON; + else + cmd.switch_state = SST_SWM_OFF; + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + /* MMX_SET_SWM == SBA_SET_SWM */ + cmd.header.command_id = SBA_SET_SWM; + + SST_FILL_DESTINATION(2, cmd.output_id, + ids->location_id, SST_DEFAULT_MODULE_ID); + cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val); + cmd.header.length = offsetof(struct sst_cmd_set_swm, input) + - sizeof(struct sst_dsp_header) + + (cmd.nb_inputs * sizeof(cmd.input[0])); + + return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + ids->task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +/* SBA mixers - 16 inputs */ +#define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \ + static const struct snd_kcontrol_new kctl_name[] = { \ + SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \ + SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \ + SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \ + SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \ + SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \ + SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \ + SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \ + } + +#define SST_SBA_MIXER_GRAPH_MAP(mix_name) \ + { mix_name, "codec_in0 Switch", "codec_in0" }, \ + { mix_name, "codec_in1 Switch", "codec_in1" }, \ + { mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \ + { mix_name, "media_loop1_in Switch", "media_loop1_in" }, \ + { mix_name, "media_loop2_in Switch", "media_loop2_in" }, \ + { mix_name, "pcm0_in Switch", "pcm0_in" }, \ + { mix_name, "pcm1_in Switch", "pcm1_in" } + +#define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \ + static const struct snd_kcontrol_new kctl_name[] = { \ + SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \ + SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \ + SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \ + SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \ + } + +SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls); +SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls); + +/* 18 SBA mixers */ +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls); + +/* + * sst_handle_vb_timer - Start/Stop the DSP scheduler + * + * The DSP expects first cmd to be SBA_VB_START, so at first startup send + * that. + * DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that. + * + * Do refcount internally so that we send command only at first start + * and last end. Since SST driver does its own ref count, invoke sst's + * power ops always! + */ +int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) +{ + int ret = 0; + struct sst_cmd_generic cmd; + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + static int timer_usage; + + if (enable) + cmd.header.command_id = SBA_VB_START; + else + cmd.header.command_id = SBA_IDLE; + dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage); + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.length = 0; + + if (enable) { + ret = sst->ops->power(sst->dev, true); + if (ret < 0) + return ret; + } + + mutex_lock(&drv->lock); + if (enable) + timer_usage++; + else + timer_usage--; + + /* + * Send the command only if this call is the first enable or last + * disable + */ + if ((enable && (timer_usage == 1)) || + (!enable && (timer_usage == 0))) { + ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, + SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret && enable) { + timer_usage--; + enable = false; + } + } + mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); + return ret; +} + +/** + * sst_ssp_config - contains SSP configuration for media UC + */ +static const struct sst_ssp_config sst_ssp_configs = { + .ssp_id = SSP_CODEC, + .bits_per_slot = 24, + .slots = 4, + .ssp_mode = SSP_MODE_MASTER, + .pcm_mode = SSP_PCM_MODE_NETWORK, + .duplex = SSP_DUPLEX, + .ssp_protocol = SSP_MODE_PCM, + .fs_width = 1, + .fs_frequency = SSP_FS_48_KHZ, + .active_slot_map = 0xF, + .start_delay = 0, +}; + +int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable) +{ + struct sst_cmd_sba_hw_set_ssp cmd; + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + const struct sst_ssp_config *config; + + dev_info(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id); + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.command_id = SBA_HW_SET_SSP; + cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp) + - sizeof(struct sst_dsp_header); + + config = &sst_ssp_configs; + dev_dbg(dai->dev, "ssp_id: %u\n", config->ssp_id); + + if (enable) + cmd.switch_state = SST_SWITCH_ON; + else + cmd.switch_state = SST_SWITCH_OFF; + + cmd.selection = config->ssp_id; + cmd.nb_bits_per_slots = config->bits_per_slot; + cmd.nb_slots = config->slots; + cmd.mode = config->ssp_mode | (config->pcm_mode << 1); + cmd.duplex = config->duplex; + cmd.active_tx_slot_map = config->active_slot_map; + cmd.active_rx_slot_map = config->active_slot_map; + cmd.frame_sync_frequency = config->fs_frequency; + cmd.frame_sync_polarity = SSP_FS_ACTIVE_HIGH; + cmd.data_polarity = 1; + cmd.frame_sync_width = config->fs_width; + cmd.ssp_protocol = config->ssp_protocol; + cmd.start_delay = config->start_delay; + cmd.reserved1 = cmd.reserved2 = 0xFF; + + return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +static int sst_set_be_modules(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + + dev_dbg(c->dev, "Enter: widget=%s\n", w->name); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = sst_send_slot_map(drv); + if (ret) + return ret; + ret = sst_send_pipe_module_params(w, k); + } + return ret; +} + +static int sst_set_media_path(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct sst_cmd_set_media_path cmd; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + dev_dbg(c->dev, "widget=%s\n", w->name); + dev_dbg(c->dev, "task=%u, location=%#x\n", + ids->task_id, ids->location_id); + + if (SND_SOC_DAPM_EVENT_ON(event)) + cmd.switch_state = SST_PATH_ON; + else + cmd.switch_state = SST_PATH_OFF; + + SST_FILL_DESTINATION(2, cmd.header.dst, + ids->location_id, SST_DEFAULT_MODULE_ID); + + /* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */ + cmd.header.command_id = MMX_SET_MEDIA_PATH; + cmd.header.length = sizeof(struct sst_cmd_set_media_path) + - sizeof(struct sst_dsp_header); + + ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + ids->task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret) + return ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = sst_send_pipe_module_params(w, k); + return ret; +} + +static int sst_set_media_loop(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct sst_cmd_sba_set_media_loop_map cmd; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + dev_dbg(c->dev, "Enter:widget=%s\n", w->name); + if (SND_SOC_DAPM_EVENT_ON(event)) + cmd.switch_state = SST_SWITCH_ON; + else + cmd.switch_state = SST_SWITCH_OFF; + + SST_FILL_DESTINATION(2, cmd.header.dst, + ids->location_id, SST_DEFAULT_MODULE_ID); + + cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP; + cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map) + - sizeof(struct sst_dsp_header); + cmd.param.part.cfg.rate = 2; /* 48khz */ + + cmd.param.part.cfg.format = ids->format; /* stereo/Mono */ + cmd.param.part.cfg.s_length = 1; /* 24bit left justified */ + cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */ + + ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret) + return ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = sst_send_pipe_module_params(w, k); + return ret; +} + +static const struct snd_soc_dapm_widget sst_dapm_widgets[] = { + SST_AIF_IN("codec_in0", sst_set_be_modules), + SST_AIF_IN("codec_in1", sst_set_be_modules), + SST_AIF_OUT("codec_out0", sst_set_be_modules), + SST_AIF_OUT("codec_out1", sst_set_be_modules), + + /* Media Paths */ + /* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */ + SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event), + SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL), + SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path), + SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL), + SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path), + SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path), + + /* SBA PCM Paths */ + SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path), + SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path), + SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path), + SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path), + SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path), + + /* SBA Loops */ + SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL), + SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL), + SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL), + SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_MONO, sst_set_media_loop), + SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_MONO, sst_set_media_loop), + SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop), + + /* Media Mixers */ + SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0, + sst_mix_media0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1, + sst_mix_media1_controls, sst_swm_mixer_event), + + /* SBA PCM mixers */ + SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0, + sst_mix_pcm0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1, + sst_mix_pcm1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2, + sst_mix_pcm2_controls, sst_swm_mixer_event), + + /* SBA Loop mixers */ + SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, + sst_mix_sprot_l0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, + sst_mix_media_l1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, + sst_mix_media_l2_controls, sst_swm_mixer_event), + + /* SBA Backend mixers */ + SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0, + sst_mix_codec0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1, + sst_mix_codec1_controls, sst_swm_mixer_event), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"media0_in", NULL, "Compress Playback"}, + {"media1_in", NULL, "Headset Playback"}, + {"media2_in", NULL, "pcm0_out"}, + + {"media0_out mix 0", "media0_in Switch", "media0_in"}, + {"media0_out mix 0", "media1_in Switch", "media1_in"}, + {"media0_out mix 0", "media2_in Switch", "media2_in"}, + {"media0_out mix 0", "media3_in Switch", "media3_in"}, + {"media1_out mix 0", "media0_in Switch", "media0_in"}, + {"media1_out mix 0", "media1_in Switch", "media1_in"}, + {"media1_out mix 0", "media2_in Switch", "media2_in"}, + {"media1_out mix 0", "media3_in Switch", "media3_in"}, + + {"media0_out", NULL, "media0_out mix 0"}, + {"media1_out", NULL, "media1_out mix 0"}, + {"pcm0_in", NULL, "media0_out"}, + {"pcm1_in", NULL, "media1_out"}, + + {"Headset Capture", NULL, "pcm1_out"}, + {"Headset Capture", NULL, "pcm2_out"}, + {"pcm0_out", NULL, "pcm0_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"), + {"pcm1_out", NULL, "pcm1_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"), + {"pcm2_out", NULL, "pcm2_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"), + + {"media_loop1_in", NULL, "media_loop1_out"}, + {"media_loop1_out", NULL, "media_loop1_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"), + {"media_loop2_in", NULL, "media_loop2_out"}, + {"media_loop2_out", NULL, "media_loop2_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"), + {"sprot_loop_in", NULL, "sprot_loop_out"}, + {"sprot_loop_out", NULL, "sprot_loop_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"), + + {"codec_out0", NULL, "codec_out0 mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"), + {"codec_out1", NULL, "codec_out1 mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"), + +}; +static const char * const slot_names[] = { + "none", + "slot 0", "slot 1", "slot 2", "slot 3", + "slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */ +}; + +static const char * const channel_names[] = { + "none", + "codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1", + "codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */ +}; + +#define SST_INTERLEAVER(xpname, slot_name, slotno) \ + SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \ + channel_names, sst_slot_get, sst_slot_put) + +#define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \ + SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \ + slot_names, sst_slot_get, sst_slot_put) + +static const struct snd_kcontrol_new sst_slot_controls[] = { + SST_INTERLEAVER("codec_out", "slot 0", 0), + SST_INTERLEAVER("codec_out", "slot 1", 1), + SST_INTERLEAVER("codec_out", "slot 2", 2), + SST_INTERLEAVER("codec_out", "slot 3", 3), + SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0), + SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1), + SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2), + SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3), +}; + +/* Gain helper with min/max set */ +#define SST_GAIN(name, path_id, task_id, instance, gain_var) \ + SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ + SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ + sst_gain_get, sst_gain_put, \ + SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \ + sst_gain_tlv_common, gain_var) + +#define SST_VOLUME(name, path_id, task_id, instance, gain_var) \ + SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ + SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ + sst_gain_get, sst_gain_put, \ + SST_MODULE_ID_VOLUME, path_id, instance, task_id, \ + sst_gain_tlv_common, gain_var) + +static struct sst_gain_value sst_gains[]; + +static const struct snd_kcontrol_new sst_gain_controls[] = { + SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]), + SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]), + SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]), + SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]), + + SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]), + SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]), + SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]), + SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]), + + SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]), + SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]), + SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]), + SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]), + SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]), + SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]), + SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]), + SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]), +}; + +#define SST_GAIN_NUM_CONTROLS 3 +/* the SST_GAIN macro above will create three alsa controls for each + * instance invoked, gain, mute and ramp duration, which use the same gain + * cell sst_gain to keep track of data + * To calculate number of gain cell instances we need to device by 3 in + * below caulcation for gain cell memory. + * This gets rid of static number and issues while adding new controls + */ +static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS]; + +static const struct snd_kcontrol_new sst_algo_controls[] = { + SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, + SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), + SST_ALGO_KCONTROL_BYTES("media_loop1_out", "iir", 300, SST_MODULE_ID_IIR_24, + SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + SST_ALGO_KCONTROL_BYTES("media_loop1_out", "mdrp", 286, SST_MODULE_ID_MDRP, + SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP), + SST_ALGO_KCONTROL_BYTES("media_loop2_out", "fir", 272, SST_MODULE_ID_FIR_24, + SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), + SST_ALGO_KCONTROL_BYTES("media_loop2_out", "iir", 300, SST_MODULE_ID_IIR_24, + SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + SST_ALGO_KCONTROL_BYTES("media_loop2_out", "mdrp", 286, SST_MODULE_ID_MDRP, + SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP), + SST_ALGO_KCONTROL_BYTES("sprot_loop_out", "lpro", 192, SST_MODULE_ID_SPROT, + SST_PATH_INDEX_SPROT_LOOP_OUT, 0, SST_TASK_SBA, SBA_VB_LPRO), + SST_ALGO_KCONTROL_BYTES("codec_in0", "dcr", 52, SST_MODULE_ID_FILT_DCR, + SST_PATH_INDEX_CODEC_IN0, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + SST_ALGO_KCONTROL_BYTES("codec_in1", "dcr", 52, SST_MODULE_ID_FILT_DCR, + SST_PATH_INDEX_CODEC_IN1, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + +}; + +static int sst_algo_control_init(struct device *dev) +{ + int i = 0; + struct sst_algo_control *bc; + /*allocate space to cache the algo parameters in the driver*/ + for (i = 0; i < ARRAY_SIZE(sst_algo_controls); i++) { + bc = (struct sst_algo_control *)sst_algo_controls[i].private_value; + bc->params = devm_kzalloc(dev, bc->max, GFP_KERNEL); + if (bc->params == NULL) + return -ENOMEM; + } + return 0; +} + +static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w) +{ + switch (w->id) { + case snd_soc_dapm_pga: + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + case snd_soc_dapm_input: + case snd_soc_dapm_output: + case snd_soc_dapm_mixer: + return true; + default: + return false; + } +} + +/** + * sst_send_pipe_gains - send gains for the front-end DAIs + * + * The gains in the pipes connected to the front-ends are muted/unmuted + * automatically via the digital_mute() DAPM callback. This function sends the + * gains for the front-end pipes. + */ +int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) +{ + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_path *p = NULL; + + dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_dbg(dai->dev, "Stream name=%s\n", + dai->playback_widget->name); + w = dai->playback_widget; + list_for_each_entry(p, &w->sinks, list_source) { + if (p->connected && !p->connected(w, p->sink)) + continue; + + if (p->connect && p->sink->power && + is_sst_dapm_widget(p->sink)) { + struct sst_ids *ids = p->sink->priv; + + dev_dbg(dai->dev, "send gains for widget=%s\n", + p->sink->name); + mutex_lock(&drv->lock); + sst_set_pipe_gain(ids, drv, mute); + mutex_unlock(&drv->lock); + } + } + } else { + dev_dbg(dai->dev, "Stream name=%s\n", + dai->capture_widget->name); + w = dai->capture_widget; + list_for_each_entry(p, &w->sources, list_sink) { + if (p->connected && !p->connected(w, p->sink)) + continue; + + if (p->connect && p->source->power && + is_sst_dapm_widget(p->source)) { + struct sst_ids *ids = p->source->priv; + + dev_dbg(dai->dev, "send gain for widget=%s\n", + p->source->name); + mutex_lock(&drv->lock); + sst_set_pipe_gain(ids, drv, mute); + mutex_unlock(&drv->lock); + } + } + } + return 0; +} + +/** + * sst_fill_module_list - populate the list of modules/gains for a pipe + * + * + * Fills the widget pointer in the kcontrol private data, and also fills the + * kcontrol pointer in the widget private data. + * + * Widget pointer is used to send the algo/gain in the .put() handler if the + * widget is powerd on. + * + * Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF + * event handler. Each widget (pipe) has multiple algos stored in the algo_list. + */ +static int sst_fill_module_list(struct snd_kcontrol *kctl, + struct snd_soc_dapm_widget *w, int type) +{ + struct sst_module *module = NULL; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_ids *ids = w->priv; + int ret = 0; + + module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL); + if (!module) + return -ENOMEM; + + if (type == SST_MODULE_GAIN) { + struct sst_gain_mixer_control *mc = (void *)kctl->private_value; + + mc->w = w; + module->kctl = kctl; + list_add_tail(&module->node, &ids->gain_list); + } else if (type == SST_MODULE_ALGO) { + struct sst_algo_control *bc = (void *)kctl->private_value; + + bc->w = w; + module->kctl = kctl; + list_add_tail(&module->node, &ids->algo_list); + } else { + dev_err(c->dev, "invoked for unknown type %d module %s", + type, kctl->id.name); + ret = -EINVAL; + } + + return ret; +} + +/** + * sst_fill_widget_module_info - fill list of gains/algos for the pipe + * @widget: pipe modelled as a DAPM widget + * + * Fill the list of gains/algos for the widget by looking at all the card + * controls and comparing the name of the widget with the first part of control + * name. First part of control name contains the pipe name (widget name). + */ +static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w, + struct snd_soc_platform *platform) +{ + struct snd_kcontrol *kctl; + int index, ret = 0; + struct snd_card *card = platform->component.card->snd_card; + char *idx; + + down_read(&card->controls_rwsem); + + list_for_each_entry(kctl, &card->controls, list) { + idx = strstr(kctl->id.name, " "); + if (idx == NULL) + continue; + index = strlen(kctl->id.name) - strlen(idx); + + if (strstr(kctl->id.name, "Volume") && + !strncmp(kctl->id.name, w->name, index)) + ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN); + + else if (strstr(kctl->id.name, "params") && + !strncmp(kctl->id.name, w->name, index)) + ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO); + + else if (strstr(kctl->id.name, "Switch") && + !strncmp(kctl->id.name, w->name, index) && + strstr(kctl->id.name, "Gain")) { + struct sst_gain_mixer_control *mc = + (void *)kctl->private_value; + + mc->w = w; + + } else if (strstr(kctl->id.name, "interleaver") && + !strncmp(kctl->id.name, w->name, index)) { + struct sst_enum *e = (void *)kctl->private_value; + + e->w = w; + + } else if (strstr(kctl->id.name, "deinterleaver") && + !strncmp(kctl->id.name, w->name, index)) { + + struct sst_enum *e = (void *)kctl->private_value; + + e->w = w; + } + + if (ret < 0) { + up_read(&card->controls_rwsem); + return ret; + } + } + + up_read(&card->controls_rwsem); + return 0; +} + +/** + * sst_fill_linked_widgets - fill the parent pointer for the linked widget + */ +static void sst_fill_linked_widgets(struct snd_soc_platform *platform, + struct sst_ids *ids) +{ + struct snd_soc_dapm_widget *w; + unsigned int len = strlen(ids->parent_wname); + + list_for_each_entry(w, &platform->component.card->widgets, list) { + if (!strncmp(ids->parent_wname, w->name, len)) { + ids->parent_w = w; + break; + } + } +} + +/** + * sst_map_modules_to_pipe - fill algo/gains list for all pipes + */ +static int sst_map_modules_to_pipe(struct snd_soc_platform *platform) +{ + struct snd_soc_dapm_widget *w; + int ret = 0; + + list_for_each_entry(w, &platform->component.card->widgets, list) { + if (is_sst_dapm_widget(w) && (w->priv)) { + struct sst_ids *ids = w->priv; + + dev_dbg(platform->dev, "widget type=%d name=%s\n", + w->id, w->name); + INIT_LIST_HEAD(&ids->algo_list); + INIT_LIST_HEAD(&ids->gain_list); + ret = sst_fill_widget_module_info(w, platform); + + if (ret < 0) + return ret; + + /* fill linked widgets */ + if (ids->parent_wname != NULL) + sst_fill_linked_widgets(platform, ids); + } + } + return 0; +} + +int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) +{ + int i, ret = 0; + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(&platform->component); + struct sst_data *drv = snd_soc_platform_get_drvdata(platform); + unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3; + + drv->byte_stream = devm_kzalloc(platform->dev, + SST_MAX_BIN_BYTES, GFP_KERNEL); + if (!drv->byte_stream) + return -ENOMEM; + + snd_soc_dapm_new_controls(dapm, sst_dapm_widgets, + ARRAY_SIZE(sst_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon, + ARRAY_SIZE(intercon)); + snd_soc_dapm_new_widgets(dapm->card); + + for (i = 0; i < gains; i++) { + sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT; + sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT; + sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT; + sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT; + } + + ret = snd_soc_add_platform_controls(platform, sst_gain_controls, + ARRAY_SIZE(sst_gain_controls)); + if (ret) + return ret; + + /* Initialize algo control params */ + ret = sst_algo_control_init(platform->dev); + if (ret) + return ret; + ret = snd_soc_add_platform_controls(platform, sst_algo_controls, + ARRAY_SIZE(sst_algo_controls)); + if (ret) + return ret; + + ret = snd_soc_add_platform_controls(platform, sst_slot_controls, + ARRAY_SIZE(sst_slot_controls)); + if (ret) + return ret; + + ret = sst_map_modules_to_pipe(platform); + + return ret; +} diff --git a/sound/soc/intel/atom/sst-atom-controls.h b/sound/soc/intel/atom/sst-atom-controls.h new file mode 100644 index 0000000..daecc58 --- /dev/null +++ b/sound/soc/intel/atom/sst-atom-controls.h @@ -0,0 +1,870 @@ +/* + * sst-atom-controls.h - Intel MID Platform driver header file + * + * Copyright (C) 2013-14 Intel Corp + * Author: Ramesh Babu + * Omair M Abdullah + * Samreen Nilofer + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 __SST_ATOM_CONTROLS_H__ +#define __SST_ATOM_CONTROLS_H__ + +#include +#include + +enum { + MERR_DPCM_AUDIO = 0, + MERR_DPCM_COMPR, +}; + +/* define a bit for each mixer input */ +#define SST_MIX_IP(x) (x) + +#define SST_IP_CODEC0 SST_MIX_IP(2) +#define SST_IP_CODEC1 SST_MIX_IP(3) +#define SST_IP_LOOP0 SST_MIX_IP(4) +#define SST_IP_LOOP1 SST_MIX_IP(5) +#define SST_IP_LOOP2 SST_MIX_IP(6) +#define SST_IP_PROBE SST_MIX_IP(7) +#define SST_IP_VOIP SST_MIX_IP(12) +#define SST_IP_PCM0 SST_MIX_IP(13) +#define SST_IP_PCM1 SST_MIX_IP(14) +#define SST_IP_MEDIA0 SST_MIX_IP(17) +#define SST_IP_MEDIA1 SST_MIX_IP(18) +#define SST_IP_MEDIA2 SST_MIX_IP(19) +#define SST_IP_MEDIA3 SST_MIX_IP(20) + +#define SST_IP_LAST SST_IP_MEDIA3 + +#define SST_SWM_INPUT_COUNT (SST_IP_LAST + 1) +#define SST_CMD_SWM_MAX_INPUTS 6 + +#define SST_PATH_ID_SHIFT 8 +#define SST_DEFAULT_LOCATION_ID 0xFFFF +#define SST_DEFAULT_CELL_NBR 0xFF +#define SST_DEFAULT_MODULE_ID 0xFFFF + +/* + * Audio DSP Path Ids. Specified by the audio DSP FW + */ +enum sst_path_index { + SST_PATH_INDEX_CODEC_OUT0 = (0x02 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_CODEC_OUT1 = (0x03 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_SPROT_LOOP_OUT = (0x04 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP1_OUT = (0x05 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP2_OUT = (0x06 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_VOIP_OUT = (0x0C << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM0_OUT = (0x0D << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM1_OUT = (0x0E << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM2_OUT = (0x0F << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_MEDIA0_OUT = (0x12 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA1_OUT = (0x13 << SST_PATH_ID_SHIFT), + + + /* Start of input paths */ + SST_PATH_INDEX_CODEC_IN0 = (0x82 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_CODEC_IN1 = (0x83 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_SPROT_LOOP_IN = (0x84 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP1_IN = (0x85 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP2_IN = (0x86 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_VOIP_IN = (0x8C << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_PCM0_IN = (0x8D << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM1_IN = (0x8E << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_MEDIA0_IN = (0x8F << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA1_IN = (0x90 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA2_IN = (0x91 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_MEDIA3_IN = (0x9C << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_RESERVED = (0xFF << SST_PATH_ID_SHIFT), +}; + +/* + * path IDs + */ +enum sst_swm_inputs { + SST_SWM_IN_CODEC0 = (SST_PATH_INDEX_CODEC_IN0 | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_CODEC1 = (SST_PATH_INDEX_CODEC_IN1 | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_VOIP = (SST_PATH_INDEX_VOIP_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_PCM0 = (SST_PATH_INDEX_PCM0_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_PCM1 = (SST_PATH_INDEX_PCM1_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_MEDIA0 = (SST_PATH_INDEX_MEDIA0_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_MEDIA1 = (SST_PATH_INDEX_MEDIA1_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_MEDIA2 = (SST_PATH_INDEX_MEDIA2_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_MEDIA3 = (SST_PATH_INDEX_MEDIA3_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR) +}; + +/* + * path IDs + */ +enum sst_swm_outputs { + SST_SWM_OUT_CODEC0 = (SST_PATH_INDEX_CODEC_OUT0 | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_CODEC1 = (SST_PATH_INDEX_CODEC_OUT1 | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_VOIP = (SST_PATH_INDEX_VOIP_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_PCM0 = (SST_PATH_INDEX_PCM0_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_PCM1 = (SST_PATH_INDEX_PCM1_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_PCM2 = (SST_PATH_INDEX_PCM2_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_MEDIA0 = (SST_PATH_INDEX_MEDIA0_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_OUT_MEDIA1 = (SST_PATH_INDEX_MEDIA1_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_OUT_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR), +}; + +enum sst_ipc_msg { + SST_IPC_IA_CMD = 1, + SST_IPC_IA_SET_PARAMS, + SST_IPC_IA_GET_PARAMS, +}; + +enum sst_cmd_type { + SST_CMD_BYTES_SET = 1, + SST_CMD_BYTES_GET = 2, +}; + +enum sst_task { + SST_TASK_SBA = 1, + SST_TASK_MMX = 3, +}; + +enum sst_type { + SST_TYPE_CMD = 1, + SST_TYPE_PARAMS, +}; + +enum sst_flag { + SST_FLAG_BLOCKED = 1, + SST_FLAG_NONBLOCK, +}; + +/* + * Enumeration for indexing the gain cells in VB_SET_GAIN DSP command + */ +enum sst_gain_index { + /* GAIN IDs for SB task start here */ + SST_GAIN_INDEX_CODEC_OUT0, + SST_GAIN_INDEX_CODEC_OUT1, + SST_GAIN_INDEX_CODEC_IN0, + SST_GAIN_INDEX_CODEC_IN1, + + SST_GAIN_INDEX_SPROT_LOOP_OUT, + SST_GAIN_INDEX_MEDIA_LOOP1_OUT, + SST_GAIN_INDEX_MEDIA_LOOP2_OUT, + + SST_GAIN_INDEX_PCM0_IN_LEFT, + SST_GAIN_INDEX_PCM0_IN_RIGHT, + + SST_GAIN_INDEX_PCM1_OUT_LEFT, + SST_GAIN_INDEX_PCM1_OUT_RIGHT, + SST_GAIN_INDEX_PCM1_IN_LEFT, + SST_GAIN_INDEX_PCM1_IN_RIGHT, + SST_GAIN_INDEX_PCM2_OUT_LEFT, + + SST_GAIN_INDEX_PCM2_OUT_RIGHT, + SST_GAIN_INDEX_VOIP_OUT, + SST_GAIN_INDEX_VOIP_IN, + + /* Gain IDs for MMX task start here */ + SST_GAIN_INDEX_MEDIA0_IN_LEFT, + SST_GAIN_INDEX_MEDIA0_IN_RIGHT, + SST_GAIN_INDEX_MEDIA1_IN_LEFT, + SST_GAIN_INDEX_MEDIA1_IN_RIGHT, + + SST_GAIN_INDEX_MEDIA2_IN_LEFT, + SST_GAIN_INDEX_MEDIA2_IN_RIGHT, + + SST_GAIN_INDEX_GAIN_END +}; + +/* + * Audio DSP module IDs specified by FW spec + * TODO: Update with all modules + */ +enum sst_module_id { + SST_MODULE_ID_PCM = 0x0001, + SST_MODULE_ID_MP3 = 0x0002, + SST_MODULE_ID_MP24 = 0x0003, + SST_MODULE_ID_AAC = 0x0004, + SST_MODULE_ID_AACP = 0x0005, + SST_MODULE_ID_EAACP = 0x0006, + SST_MODULE_ID_WMA9 = 0x0007, + SST_MODULE_ID_WMA10 = 0x0008, + SST_MODULE_ID_WMA10P = 0x0009, + SST_MODULE_ID_RA = 0x000A, + SST_MODULE_ID_DDAC3 = 0x000B, + SST_MODULE_ID_TRUE_HD = 0x000C, + SST_MODULE_ID_HD_PLUS = 0x000D, + + SST_MODULE_ID_SRC = 0x0064, + SST_MODULE_ID_DOWNMIX = 0x0066, + SST_MODULE_ID_GAIN_CELL = 0x0067, + SST_MODULE_ID_SPROT = 0x006D, + SST_MODULE_ID_BASS_BOOST = 0x006E, + SST_MODULE_ID_STEREO_WDNG = 0x006F, + SST_MODULE_ID_AV_REMOVAL = 0x0070, + SST_MODULE_ID_MIC_EQ = 0x0071, + SST_MODULE_ID_SPL = 0x0072, + SST_MODULE_ID_ALGO_VTSV = 0x0073, + SST_MODULE_ID_NR = 0x0076, + SST_MODULE_ID_BWX = 0x0077, + SST_MODULE_ID_DRP = 0x0078, + SST_MODULE_ID_MDRP = 0x0079, + + SST_MODULE_ID_ANA = 0x007A, + SST_MODULE_ID_AEC = 0x007B, + SST_MODULE_ID_NR_SNS = 0x007C, + SST_MODULE_ID_SER = 0x007D, + SST_MODULE_ID_AGC = 0x007E, + + SST_MODULE_ID_CNI = 0x007F, + SST_MODULE_ID_CONTEXT_ALGO_AWARE = 0x0080, + SST_MODULE_ID_FIR_24 = 0x0081, + SST_MODULE_ID_IIR_24 = 0x0082, + + SST_MODULE_ID_ASRC = 0x0083, + SST_MODULE_ID_TONE_GEN = 0x0084, + SST_MODULE_ID_BMF = 0x0086, + SST_MODULE_ID_EDL = 0x0087, + SST_MODULE_ID_GLC = 0x0088, + + SST_MODULE_ID_FIR_16 = 0x0089, + SST_MODULE_ID_IIR_16 = 0x008A, + SST_MODULE_ID_DNR = 0x008B, + + SST_MODULE_ID_VIRTUALIZER = 0x008C, + SST_MODULE_ID_VISUALIZATION = 0x008D, + SST_MODULE_ID_LOUDNESS_OPTIMIZER = 0x008E, + SST_MODULE_ID_REVERBERATION = 0x008F, + + SST_MODULE_ID_CNI_TX = 0x0090, + SST_MODULE_ID_REF_LINE = 0x0091, + SST_MODULE_ID_VOLUME = 0x0092, + SST_MODULE_ID_FILT_DCR = 0x0094, + SST_MODULE_ID_SLV = 0x009A, + SST_MODULE_ID_NLF = 0x009B, + SST_MODULE_ID_TNR = 0x009C, + SST_MODULE_ID_WNR = 0x009D, + + SST_MODULE_ID_LOG = 0xFF00, + + SST_MODULE_ID_TASK = 0xFFFF, +}; + +enum sst_cmd { + SBA_IDLE = 14, + SBA_VB_SET_SPEECH_PATH = 26, + MMX_SET_GAIN = 33, + SBA_VB_SET_GAIN = 33, + FBA_VB_RX_CNI = 35, + MMX_SET_GAIN_TIMECONST = 36, + SBA_VB_SET_TIMECONST = 36, + SBA_VB_START = 85, + SBA_SET_SWM = 114, + SBA_SET_MDRP = 116, + SBA_HW_SET_SSP = 117, + SBA_SET_MEDIA_LOOP_MAP = 118, + SBA_SET_MEDIA_PATH = 119, + MMX_SET_MEDIA_PATH = 119, + SBA_VB_LPRO = 126, + SBA_VB_SET_FIR = 128, + SBA_VB_SET_IIR = 129, + SBA_SET_SSP_SLOT_MAP = 130, +}; + +enum sst_dsp_switch { + SST_SWITCH_OFF = 0, + SST_SWITCH_ON = 3, +}; + +enum sst_path_switch { + SST_PATH_OFF = 0, + SST_PATH_ON = 1, +}; + +enum sst_swm_state { + SST_SWM_OFF = 0, + SST_SWM_ON = 3, +}; + +#define SST_FILL_LOCATION_IDS(dst, cell_idx, pipe_id) do { \ + dst.location_id.p.cell_nbr_idx = (cell_idx); \ + dst.location_id.p.path_id = (pipe_id); \ + } while (0) +#define SST_FILL_LOCATION_ID(dst, loc_id) (\ + dst.location_id.f = (loc_id)) +#define SST_FILL_MODULE_ID(dst, mod_id) (\ + dst.module_id = (mod_id)) + +#define SST_FILL_DESTINATION1(dst, id) do { \ + SST_FILL_LOCATION_ID(dst, (id) & 0xFFFF); \ + SST_FILL_MODULE_ID(dst, ((id) & 0xFFFF0000) >> 16); \ + } while (0) +#define SST_FILL_DESTINATION2(dst, loc_id, mod_id) do { \ + SST_FILL_LOCATION_ID(dst, loc_id); \ + SST_FILL_MODULE_ID(dst, mod_id); \ + } while (0) +#define SST_FILL_DESTINATION3(dst, cell_idx, path_id, mod_id) do { \ + SST_FILL_LOCATION_IDS(dst, cell_idx, path_id); \ + SST_FILL_MODULE_ID(dst, mod_id); \ + } while (0) + +#define SST_FILL_DESTINATION(level, dst, ...) \ + SST_FILL_DESTINATION##level(dst, __VA_ARGS__) +#define SST_FILL_DEFAULT_DESTINATION(dst) \ + SST_FILL_DESTINATION(2, dst, SST_DEFAULT_LOCATION_ID, SST_DEFAULT_MODULE_ID) + +struct sst_destination_id { + union sst_location_id { + struct { + u8 cell_nbr_idx; /* module index */ + u8 path_id; /* pipe_id */ + } __packed p; /* part */ + u16 f; /* full */ + } __packed location_id; + u16 module_id; +} __packed; +struct sst_dsp_header { + struct sst_destination_id dst; + u16 command_id; + u16 length; +} __packed; + +/* + * + * Common Commands + * + */ +struct sst_cmd_generic { + struct sst_dsp_header header; +} __packed; + +struct swm_input_ids { + struct sst_destination_id input_id; +} __packed; + +struct sst_cmd_set_swm { + struct sst_dsp_header header; + struct sst_destination_id output_id; + u16 switch_state; + u16 nb_inputs; + struct swm_input_ids input[SST_CMD_SWM_MAX_INPUTS]; +} __packed; + +struct sst_cmd_set_media_path { + struct sst_dsp_header header; + u16 switch_state; +} __packed; + +struct pcm_cfg { + u8 s_length:2; + u8 rate:3; + u8 format:3; +} __packed; + +struct sst_cmd_set_speech_path { + struct sst_dsp_header header; + u16 switch_state; + struct { + u16 rsvd:8; + struct pcm_cfg cfg; + } config; +} __packed; + +struct gain_cell { + struct sst_destination_id dest; + s16 cell_gain_left; + s16 cell_gain_right; + u16 gain_time_constant; +} __packed; + +#define NUM_GAIN_CELLS 1 +struct sst_cmd_set_gain_dual { + struct sst_dsp_header header; + u16 gain_cell_num; + struct gain_cell cell_gains[NUM_GAIN_CELLS]; +} __packed; +struct sst_cmd_set_params { + struct sst_destination_id dst; + u16 command_id; + char params[0]; +} __packed; + + +struct sst_cmd_sba_vb_start { + struct sst_dsp_header header; +} __packed; + +union sba_media_loop_params { + struct { + u16 rsvd:8; + struct pcm_cfg cfg; + } part; + u16 full; +} __packed; + +struct sst_cmd_sba_set_media_loop_map { + struct sst_dsp_header header; + u16 switch_state; + union sba_media_loop_params param; + u16 map; +} __packed; + +struct sst_cmd_tone_stop { + struct sst_dsp_header header; + u16 switch_state; +} __packed; + +enum sst_ssp_mode { + SSP_MODE_MASTER = 0, + SSP_MODE_SLAVE = 1, +}; + +enum sst_ssp_pcm_mode { + SSP_PCM_MODE_NORMAL = 0, + SSP_PCM_MODE_NETWORK = 1, +}; + +enum sst_ssp_duplex { + SSP_DUPLEX = 0, + SSP_RX = 1, + SSP_TX = 2, +}; + +enum sst_ssp_fs_frequency { + SSP_FS_8_KHZ = 0, + SSP_FS_16_KHZ = 1, + SSP_FS_44_1_KHZ = 2, + SSP_FS_48_KHZ = 3, +}; + +enum sst_ssp_fs_polarity { + SSP_FS_ACTIVE_LOW = 0, + SSP_FS_ACTIVE_HIGH = 1, +}; + +enum sst_ssp_protocol { + SSP_MODE_PCM = 0, + SSP_MODE_I2S = 1, +}; + +enum sst_ssp_port_id { + SSP_MODEM = 0, + SSP_BT = 1, + SSP_FM = 2, + SSP_CODEC = 3, +}; + +struct sst_cmd_sba_hw_set_ssp { + struct sst_dsp_header header; + u16 selection; /* 0:SSP0(def), 1:SSP1, 2:SSP2 */ + + u16 switch_state; + + u16 nb_bits_per_slots:6; /* 0-32 bits, 24 (def) */ + u16 nb_slots:4; /* 0-8: slots per frame */ + u16 mode:3; /* 0:Master, 1: Slave */ + u16 duplex:3; + + u16 active_tx_slot_map:8; /* Bit map, 0:off, 1:on */ + u16 reserved1:8; + + u16 active_rx_slot_map:8; /* Bit map 0: Off, 1:On */ + u16 reserved2:8; + + u16 frame_sync_frequency; + + u16 frame_sync_polarity:8; + u16 data_polarity:8; + + u16 frame_sync_width; /* 1 to N clocks */ + u16 ssp_protocol:8; + u16 start_delay:8; /* Start delay in terms of clock ticks */ +} __packed; + +#define SST_MAX_TDM_SLOTS 8 + +struct sst_param_sba_ssp_slot_map { + struct sst_dsp_header header; + + u16 param_id; + u16 param_len; + u16 ssp_index; + + u8 rx_slot_map[SST_MAX_TDM_SLOTS]; + u8 tx_slot_map[SST_MAX_TDM_SLOTS]; +} __packed; + +enum { + SST_PROBE_EXTRACTOR = 0, + SST_PROBE_INJECTOR = 1, +}; + +/**** widget defines *****/ + +#define SST_MODULE_GAIN 1 +#define SST_MODULE_ALGO 2 + +#define SST_FMT_MONO 0 +#define SST_FMT_STEREO 3 + +/* physical SSP numbers */ +enum { + SST_SSP0 = 0, + SST_SSP1, + SST_SSP2, + SST_SSP_LAST = SST_SSP2, +}; + +#define SST_NUM_SSPS (SST_SSP_LAST + 1) /* physical SSPs */ +#define SST_MAX_SSP_MUX 2 /* single SSP muxed between pipes */ +#define SST_MAX_SSP_DOMAINS 2 /* domains present in each pipe */ + +struct sst_module { + struct snd_kcontrol *kctl; + struct list_head node; +}; + +struct sst_ssp_config { + u8 ssp_id; + u8 bits_per_slot; + u8 slots; + u8 ssp_mode; + u8 pcm_mode; + u8 duplex; + u8 ssp_protocol; + u8 fs_frequency; + u8 active_slot_map; + u8 start_delay; + u16 fs_width; +}; + +struct sst_ssp_cfg { + const u8 ssp_number; + const int *mux_shift; + const int (*domain_shift)[SST_MAX_SSP_MUX]; + const struct sst_ssp_config (*ssp_config)[SST_MAX_SSP_MUX][SST_MAX_SSP_DOMAINS]; +}; + +struct sst_ids { + u16 location_id; + u16 module_id; + u8 task_id; + u8 format; + u8 reg; + const char *parent_wname; + struct snd_soc_dapm_widget *parent_w; + struct list_head algo_list; + struct list_head gain_list; + const struct sst_pcm_format *pcm_fmt; +}; + + +#define SST_AIF_IN(wname, wevent) \ +{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_AIF_OUT(wname, wevent) \ +{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_INPUT(wname, wevent) \ +{ .id = snd_soc_dapm_input, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_OUTPUT(wname, wevent) \ +{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_DAPM_OUTPUT(wname, wloc_id, wtask_id, wformat, wevent) \ +{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .location_id = wloc_id, .task_id = wtask_id,\ + .pcm_fmt = wformat, } \ +} + +#define SST_PATH(wname, wtask, wloc_id, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, } \ +} + +#define SST_LINKED_PATH(wname, wtask, wloc_id, linked_wname, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .parent_wname = linked_wname} \ +} + +#define SST_PATH_MEDIA_LOOP(wname, wtask, wloc_id, wformat, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .format = wformat,} \ +} + +/* output is triggered before input */ +#define SST_PATH_INPUT(name, task_id, loc_id, event) \ + SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define SST_PATH_LINKED_INPUT(name, task_id, loc_id, linked_wname, event) \ + SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define SST_PATH_OUTPUT(name, task_id, loc_id, event) \ + SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#define SST_PATH_LINKED_OUTPUT(name, task_id, loc_id, linked_wname, event) \ + SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#define SST_PATH_MEDIA_LOOP_OUTPUT(name, task_id, loc_id, format, event) \ + SST_PATH_MEDIA_LOOP(name, task_id, loc_id, format, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + + +#define SST_SWM_MIXER(wname, wreg, wtask, wloc_id, wcontrols, wevent) \ +{ .id = snd_soc_dapm_mixer, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols),\ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | \ + SND_SOC_DAPM_POST_REG, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .reg = wreg } \ +} + +enum sst_gain_kcontrol_type { + SST_GAIN_TLV, + SST_GAIN_MUTE, + SST_GAIN_RAMP_DURATION, +}; + +struct sst_gain_mixer_control { + bool stereo; + enum sst_gain_kcontrol_type type; + struct sst_gain_value *gain_val; + int max; + int min; + u16 instance_id; + u16 module_id; + u16 pipe_id; + u16 task_id; + char pname[44]; + struct snd_soc_dapm_widget *w; +}; + +struct sst_gain_value { + u16 ramp_duration; + s16 l_gain; + s16 r_gain; + bool mute; +}; +#define SST_GAIN_VOLUME_DEFAULT (-1440) +#define SST_GAIN_RAMP_DURATION_DEFAULT 5 /* timeconstant */ +#define SST_GAIN_MUTE_DEFAULT true + +#define SST_GAIN_KCONTROL_TLV(xname, xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, tlv_array, xgain_val, \ + xmin, xmax, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = sst_gain_ctl_info,\ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = true, .max = xmax, .min = xmin, .type = SST_GAIN_TLV, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} + +#define SST_GAIN_KCONTROL_INT(xname, xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, xtype, xgain_val, \ + xmin, xmax, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sst_gain_ctl_info, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = false, .max = xmax, .min = xmin, .type = xtype, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} + +#define SST_GAIN_KCONTROL_BOOL(xname, xhandler_get, xhandler_put,\ + xmod, xpipe, xinstance, xtask, xgain_val, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_bool_ext, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = false, .type = SST_GAIN_MUTE, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} +#define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \ + xpname " " xmname " " #xinstance " " xtype + +#define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \ + xpname " " xmname " " #xinstance " " xtype " " xsubmodule + +/* + * 3 Controls for each Gain module + * e.g. - pcm0_in Gain 0 Volume + * - pcm0_in Gain 0 Ramp Delay + * - pcm0_in Gain 0 Switch + */ +#define SST_GAIN_KCONTROLS(xpname, xmname, xmin_gain, xmax_gain, xmin_tc, xmax_tc, \ + xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, tlv_array, xgain_val) \ + { SST_GAIN_KCONTROL_INT(SST_CONTROL_NAME(xpname, xmname, xinstance, "Ramp Delay"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, SST_GAIN_RAMP_DURATION, \ + xgain_val, xmin_tc, xmax_tc, xpname) }, \ + { SST_GAIN_KCONTROL_BOOL(SST_CONTROL_NAME(xpname, xmname, xinstance, "Switch"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, \ + xgain_val, xpname) } ,\ + { SST_GAIN_KCONTROL_TLV(SST_CONTROL_NAME(xpname, xmname, xinstance, "Volume"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, tlv_array, \ + xgain_val, xmin_gain, xmax_gain, xpname) } + +#define SST_GAIN_TC_MIN 5 +#define SST_GAIN_TC_MAX 5000 +#define SST_GAIN_MIN_VALUE -1440 /* in 0.1 DB units */ +#define SST_GAIN_MAX_VALUE 360 + +enum sst_algo_kcontrol_type { + SST_ALGO_PARAMS, + SST_ALGO_BYPASS, +}; + +struct sst_algo_control { + enum sst_algo_kcontrol_type type; + int max; + u16 module_id; + u16 pipe_id; + u16 task_id; + u16 cmd_id; + bool bypass; + unsigned char *params; + struct snd_soc_dapm_widget *w; +}; + +/* size of the control = size of params + size of length field */ +#define SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, xmod, xtask, xcmd) \ + (struct sst_algo_control){ \ + .max = xcount + sizeof(u16), .type = xtype, .module_id = xmod, \ + .pipe_id = xpipe, .task_id = xtask, .cmd_id = xcmd, \ + } + +#define SST_ALGO_KCONTROL(xname, xcount, xmod, xpipe, \ + xtask, xcmd, xtype, xinfo, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = xinfo, .get = xget, .put = xput, \ + .private_value = (unsigned long)& \ + SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, \ + xmod, xtask, xcmd), \ +} + +#define SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, \ + xpipe, xinstance, xtask, xcmd) \ + SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "params"), \ + xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \ + sst_algo_bytes_ctl_info, \ + sst_algo_control_get, sst_algo_control_set) + +#define SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask) \ + SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "bypass"), \ + 0, xmod, xpipe, xtask, 0, SST_ALGO_BYPASS, \ + snd_soc_info_bool_ext, \ + sst_algo_control_get, sst_algo_control_set) + +#define SST_ALGO_BYPASS_PARAMS(xpname, xmname, xcount, xmod, xpipe, \ + xinstance, xtask, xcmd) \ + SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask), \ + SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, xpipe, xinstance, xtask, xcmd) + +#define SST_COMBO_ALGO_KCONTROL_BYTES(xpname, xmname, xsubmod, xcount, xmod, \ + xpipe, xinstance, xtask, xcmd) \ + SST_ALGO_KCONTROL(SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, "params", \ + xsubmod), \ + xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \ + sst_algo_bytes_ctl_info, \ + sst_algo_control_get, sst_algo_control_set) + + +struct sst_enum { + bool tx; + unsigned short reg; + unsigned int max; + const char * const *texts; + struct snd_soc_dapm_widget *w; +}; + +/* only 4 slots/channels supported atm */ +#define SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts) \ + (struct sst_enum){ .reg = s_ch_no, .tx = is_tx, .max = 4+1, .texts = xtexts, } + +#define SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name) \ + xpname " " xmname " " s_ch_name + +#define SST_SSP_SLOT_CTL(xpname, xmname, s_ch_name, s_ch_no, is_tx, xtexts, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name), \ + .info = sst_slot_enum_info, \ + .get = xget, .put = xput, \ + .private_value = (unsigned long)&SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts), \ +} + +#define SST_MUX_CTL_NAME(xpname, xinstance) \ + xpname " " #xinstance + +#define SST_SSP_MUX_ENUM(xreg, xshift, xtexts) \ + (struct soc_enum) SOC_ENUM_DOUBLE(xreg, xshift, xshift, ARRAY_SIZE(xtexts), xtexts) + +#define SST_SSP_MUX_CTL(xpname, xinstance, xreg, xshift, xtexts) \ + SOC_DAPM_ENUM(SST_MUX_CTL_NAME(xpname, xinstance), \ + SST_SSP_MUX_ENUM(xreg, xshift, xtexts)) + +#endif diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h new file mode 100644 index 0000000..4257263 --- /dev/null +++ b/sound/soc/intel/atom/sst-mfld-dsp.h @@ -0,0 +1,533 @@ +#ifndef __SST_MFLD_DSP_H__ +#define __SST_MFLD_DSP_H__ +/* + * sst_mfld_dsp.h - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define SST_MAX_BIN_BYTES 1024 + +#define MAX_DBG_RW_BYTES 80 +#define MAX_NUM_SCATTER_BUFFERS 8 +#define MAX_LOOP_BACK_DWORDS 8 +/* IPC base address and mailbox, timestamp offsets */ +#define SST_MAILBOX_SIZE 0x0400 +#define SST_MAILBOX_SEND 0x0000 +#define SST_TIME_STAMP 0x1800 +#define SST_TIME_STAMP_MRFLD 0x800 +#define SST_RESERVED_OFFSET 0x1A00 +#define SST_SCU_LPE_MAILBOX 0x1000 +#define SST_LPE_SCU_MAILBOX 0x1400 +#define SST_SCU_LPE_LOG_BUF (SST_SCU_LPE_MAILBOX+16) +#define PROCESS_MSG 0x80 + +/* Message ID's for IPC messages */ +/* Bits B7: SST or IA/SC ; B6-B4: Msg Category; B3-B0: Msg Type */ + +/* I2L Firmware/Codec Download msgs */ +#define IPC_IA_PREP_LIB_DNLD 0x01 +#define IPC_IA_LIB_DNLD_CMPLT 0x02 +#define IPC_IA_GET_FW_VERSION 0x04 +#define IPC_IA_GET_FW_BUILD_INF 0x05 +#define IPC_IA_GET_FW_INFO 0x06 +#define IPC_IA_GET_FW_CTXT 0x07 +#define IPC_IA_SET_FW_CTXT 0x08 +#define IPC_IA_PREPARE_SHUTDOWN 0x31 +/* I2L Codec Config/control msgs */ +#define IPC_PREP_D3 0x10 +#define IPC_IA_SET_CODEC_PARAMS 0x10 +#define IPC_IA_GET_CODEC_PARAMS 0x11 +#define IPC_IA_SET_PPP_PARAMS 0x12 +#define IPC_IA_GET_PPP_PARAMS 0x13 +#define IPC_SST_PERIOD_ELAPSED_MRFLD 0xA +#define IPC_IA_ALG_PARAMS 0x1A +#define IPC_IA_TUNING_PARAMS 0x1B +#define IPC_IA_SET_RUNTIME_PARAMS 0x1C +#define IPC_IA_SET_PARAMS 0x1 +#define IPC_IA_GET_PARAMS 0x2 + +#define IPC_EFFECTS_CREATE 0xE +#define IPC_EFFECTS_DESTROY 0xF + +/* I2L Stream config/control msgs */ +#define IPC_IA_ALLOC_STREAM_MRFLD 0x2 +#define IPC_IA_ALLOC_STREAM 0x20 /* Allocate a stream ID */ +#define IPC_IA_FREE_STREAM_MRFLD 0x03 +#define IPC_IA_FREE_STREAM 0x21 /* Free the stream ID */ +#define IPC_IA_SET_STREAM_PARAMS 0x22 +#define IPC_IA_SET_STREAM_PARAMS_MRFLD 0x12 +#define IPC_IA_GET_STREAM_PARAMS 0x23 +#define IPC_IA_PAUSE_STREAM 0x24 +#define IPC_IA_PAUSE_STREAM_MRFLD 0x4 +#define IPC_IA_RESUME_STREAM 0x25 +#define IPC_IA_RESUME_STREAM_MRFLD 0x5 +#define IPC_IA_DROP_STREAM 0x26 +#define IPC_IA_DROP_STREAM_MRFLD 0x07 +#define IPC_IA_DRAIN_STREAM 0x27 /* Short msg with str_id */ +#define IPC_IA_DRAIN_STREAM_MRFLD 0x8 +#define IPC_IA_CONTROL_ROUTING 0x29 +#define IPC_IA_VTSV_UPDATE_MODULES 0x20 +#define IPC_IA_VTSV_DETECTED 0x21 + +#define IPC_IA_START_STREAM_MRFLD 0X06 +#define IPC_IA_START_STREAM 0x30 /* Short msg with str_id */ + +#define IPC_IA_SET_GAIN_MRFLD 0x21 +/* Debug msgs */ +#define IPC_IA_DBG_MEM_READ 0x40 +#define IPC_IA_DBG_MEM_WRITE 0x41 +#define IPC_IA_DBG_LOOP_BACK 0x42 +#define IPC_IA_DBG_LOG_ENABLE 0x45 +#define IPC_IA_DBG_SET_PROBE_PARAMS 0x47 + +/* L2I Firmware/Codec Download msgs */ +#define IPC_IA_FW_INIT_CMPLT 0x81 +#define IPC_IA_FW_INIT_CMPLT_MRFLD 0x01 +#define IPC_IA_FW_ASYNC_ERR_MRFLD 0x11 + +/* L2I Codec Config/control msgs */ +#define IPC_SST_FRAGMENT_ELPASED 0x90 /* Request IA more data */ + +#define IPC_SST_BUF_UNDER_RUN 0x92 /* PB Under run and stopped */ +#define IPC_SST_BUF_OVER_RUN 0x93 /* CAP Under run and stopped */ +#define IPC_SST_DRAIN_END 0x94 /* PB Drain complete and stopped */ +#define IPC_SST_CHNGE_SSP_PARAMS 0x95 /* PB SSP parameters changed */ +#define IPC_SST_STREAM_PROCESS_FATAL_ERR 0x96/* error in processing a stream */ +#define IPC_SST_PERIOD_ELAPSED 0x97 /* period elapsed */ + +#define IPC_SST_ERROR_EVENT 0x99 /* Buffer over run occurred */ +/* L2S messages */ +#define IPC_SC_DDR_LINK_UP 0xC0 +#define IPC_SC_DDR_LINK_DOWN 0xC1 +#define IPC_SC_SET_LPECLK_REQ 0xC2 +#define IPC_SC_SSP_BIT_BANG 0xC3 + +/* L2I Error reporting msgs */ +#define IPC_IA_MEM_ALLOC_FAIL 0xE0 +#define IPC_IA_PROC_ERR 0xE1 /* error in processing a + stream can be used by playback and + capture modules */ + +/* L2I Debug msgs */ +#define IPC_IA_PRINT_STRING 0xF0 + +/* Buffer under-run */ +#define IPC_IA_BUF_UNDER_RUN_MRFLD 0x0B + +/* Mrfld specific defines: + * For asynchronous messages(INIT_CMPLT, PERIOD_ELAPSED, ASYNC_ERROR) + * received from FW, the format is: + * - IPC High: pvt_id is set to zero. Always short message. + * - msg_id is in lower 16-bits of IPC low payload. + * - pipe_id is in higher 16-bits of IPC low payload for period_elapsed. + * - error id is in higher 16-bits of IPC low payload for async errors. + */ +#define SST_ASYNC_DRV_ID 0 + +/* Command Response or Acknowledge message to any IPC message will have + * same message ID and stream ID information which is sent. + * There is no specific Ack message ID. The data field is used as response + * meaning. + */ +enum ackData { + IPC_ACK_SUCCESS = 0, + IPC_ACK_FAILURE, +}; + +enum ipc_ia_msg_id { + IPC_CMD = 1, /*!< Task Control message ID */ + IPC_SET_PARAMS = 2,/*!< Task Set param message ID */ + IPC_GET_PARAMS = 3, /*!< Task Get param message ID */ + IPC_INVALID = 0xFF, /*! + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sst-mfld-platform.h" + +/* compress stream operations */ +static void sst_compr_fragment_elapsed(void *arg) +{ + struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; + + pr_debug("fragment elapsed by driver\n"); + if (cstream) + snd_compr_fragment_elapsed(cstream); +} + +static void sst_drain_notify(void *arg) +{ + struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; + + pr_debug("drain notify by driver\n"); + if (cstream) + snd_compr_drain_notify(cstream); +} + +static int sst_platform_compr_open(struct snd_compr_stream *cstream) +{ + + int ret_val = 0; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sst_runtime_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ + if (!sst || !try_module_get(sst->dev->driver->owner)) { + pr_err("no device available to run\n"); + ret_val = -ENODEV; + goto out_ops; + } + stream->compr_ops = sst->compr_ops; + stream->id = 0; + + /* Turn on LPE */ + sst->compr_ops->power(sst->dev, true); + + sst_set_stream_status(stream, SST_PLATFORM_INIT); + runtime->private_data = stream; + return 0; +out_ops: + kfree(stream); + return ret_val; +} + +static int sst_platform_compr_free(struct snd_compr_stream *cstream) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + stream = cstream->runtime->private_data; + /* Turn off LPE */ + sst->compr_ops->power(sst->dev, false); + + /*need to check*/ + str_id = stream->id; + if (str_id) + ret_val = stream->compr_ops->close(sst->dev, str_id); + module_put(sst->dev->driver->owner); + kfree(stream); + pr_debug("%s: %d\n", __func__, ret_val); + return 0; +} + +static int sst_platform_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct sst_runtime_stream *stream; + int retval; + struct snd_sst_params str_params; + struct sst_compress_cb cb; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct sst_data *ctx = snd_soc_platform_get_drvdata(platform); + + stream = cstream->runtime->private_data; + /* construct fw structure for this*/ + memset(&str_params, 0, sizeof(str_params)); + + /* fill the device type and stream id to pass to SST driver */ + retval = sst_fill_stream_params(cstream, ctx, &str_params, true); + pr_debug("compr_set_params: fill stream params ret_val = 0x%x\n", retval); + if (retval < 0) + return retval; + + switch (params->codec.id) { + case SND_AUDIOCODEC_MP3: { + str_params.codec = SST_CODEC_TYPE_MP3; + str_params.sparams.uc.mp3_params.num_chan = params->codec.ch_in; + str_params.sparams.uc.mp3_params.pcm_wd_sz = 16; + break; + } + + case SND_AUDIOCODEC_AAC: { + str_params.codec = SST_CODEC_TYPE_AAC; + str_params.sparams.uc.aac_params.num_chan = params->codec.ch_in; + str_params.sparams.uc.aac_params.pcm_wd_sz = 16; + if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS) + str_params.sparams.uc.aac_params.bs_format = + AAC_BIT_STREAM_ADTS; + else if (params->codec.format == SND_AUDIOSTREAMFORMAT_RAW) + str_params.sparams.uc.aac_params.bs_format = + AAC_BIT_STREAM_RAW; + else { + pr_err("Undefined format%d\n", params->codec.format); + return -EINVAL; + } + str_params.sparams.uc.aac_params.externalsr = + params->codec.sample_rate; + break; + } + + default: + pr_err("codec not supported, id =%d\n", params->codec.id); + return -EINVAL; + } + + str_params.aparams.ring_buf_info[0].addr = + virt_to_phys(cstream->runtime->buffer); + str_params.aparams.ring_buf_info[0].size = + cstream->runtime->buffer_size; + str_params.aparams.sg_count = 1; + str_params.aparams.frag_size = cstream->runtime->fragment_size; + + cb.param = cstream; + cb.compr_cb = sst_compr_fragment_elapsed; + cb.drain_cb_param = cstream; + cb.drain_notify = sst_drain_notify; + + retval = stream->compr_ops->open(sst->dev, &str_params, &cb); + if (retval < 0) { + pr_err("stream allocation failed %d\n", retval); + return retval; + } + + stream->id = retval; + return 0; +} + +static int sst_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) +{ + struct sst_runtime_stream *stream = cstream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (stream->compr_ops->stream_start) + return stream->compr_ops->stream_start(sst->dev, stream->id); + case SNDRV_PCM_TRIGGER_STOP: + if (stream->compr_ops->stream_drop) + return stream->compr_ops->stream_drop(sst->dev, stream->id); + case SND_COMPR_TRIGGER_DRAIN: + if (stream->compr_ops->stream_drain) + return stream->compr_ops->stream_drain(sst->dev, stream->id); + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + if (stream->compr_ops->stream_partial_drain) + return stream->compr_ops->stream_partial_drain(sst->dev, stream->id); + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (stream->compr_ops->stream_pause) + return stream->compr_ops->stream_pause(sst->dev, stream->id); + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (stream->compr_ops->stream_pause_release) + return stream->compr_ops->stream_pause_release(sst->dev, stream->id); + default: + return -EINVAL; + } +} + +static int sst_platform_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct sst_runtime_stream *stream; + + stream = cstream->runtime->private_data; + stream->compr_ops->tstamp(sst->dev, stream->id, tstamp); + tstamp->byte_offset = tstamp->copied_total % + (u32)cstream->runtime->buffer_size; + pr_debug("calc bytes offset/copied bytes as %d\n", tstamp->byte_offset); + return 0; +} + +static int sst_platform_compr_ack(struct snd_compr_stream *cstream, + size_t bytes) +{ + struct sst_runtime_stream *stream; + + stream = cstream->runtime->private_data; + stream->compr_ops->ack(sst->dev, stream->id, (unsigned long)bytes); + stream->bytes_written += bytes; + + return 0; +} + +static int sst_platform_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + struct sst_runtime_stream *stream = + cstream->runtime->private_data; + + return stream->compr_ops->get_caps(caps); +} + +static int sst_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + struct sst_runtime_stream *stream = + cstream->runtime->private_data; + + return stream->compr_ops->get_codec_caps(codec); +} + +static int sst_platform_compr_set_metadata(struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + struct sst_runtime_stream *stream = + cstream->runtime->private_data; + + return stream->compr_ops->set_metadata(sst->dev, stream->id, metadata); +} + +struct snd_compr_ops sst_platform_compr_ops = { + + .open = sst_platform_compr_open, + .free = sst_platform_compr_free, + .set_params = sst_platform_compr_set_params, + .set_metadata = sst_platform_compr_set_metadata, + .trigger = sst_platform_compr_trigger, + .pointer = sst_platform_compr_pointer, + .ack = sst_platform_compr_ack, + .get_caps = sst_platform_compr_get_caps, + .get_codec_caps = sst_platform_compr_get_codec_caps, +}; diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c new file mode 100644 index 0000000..2fbaf2c --- /dev/null +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -0,0 +1,804 @@ +/* + * sst_mfld_platform.c - Intel MID Platform driver + * + * Copyright (C) 2010-2014 Intel Corp + * Author: Vinod Koul + * Author: Harsha Priya + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sst-mfld-platform.h" +#include "sst-atom-controls.h" + +struct sst_device *sst; +static DEFINE_MUTEX(sst_lock); +extern struct snd_compr_ops sst_platform_compr_ops; + +int sst_register_dsp(struct sst_device *dev) +{ + if (WARN_ON(!dev)) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; + mutex_lock(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); + mutex_unlock(&sst_lock); + return -EEXIST; + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; + mutex_unlock(&sst_lock); + return 0; +} +EXPORT_SYMBOL_GPL(sst_register_dsp); + +int sst_unregister_dsp(struct sst_device *dev) +{ + if (WARN_ON(!dev)) + return -EINVAL; + if (dev != sst) + return -EINVAL; + + mutex_lock(&sst_lock); + + if (!sst) { + mutex_unlock(&sst_lock); + return -EIO; + } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; + mutex_unlock(&sst_lock); + return 0; +} +EXPORT_SYMBOL_GPL(sst_unregister_dsp); + +static struct snd_pcm_hardware sst_platform_pcm_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP| + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_SYNC_START), + .buffer_bytes_max = SST_MAX_BUFFER, + .period_bytes_min = SST_MIN_PERIOD_BYTES, + .period_bytes_max = SST_MAX_PERIOD_BYTES, + .periods_min = SST_MIN_PERIODS, + .periods_max = SST_MAX_PERIODS, + .fifo_size = SST_FIFO_SIZE, +}; + +static struct sst_dev_stream_map dpcm_strm_map[] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, /* Reserved, not in use */ + {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA1_IN, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_COMPR, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA0_IN, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0}, +}; + +static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + + return sst_send_pipe_gains(dai, stream, mute); +} + +/* helper functions */ +void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) +{ + unsigned long flags; + spin_lock_irqsave(&stream->status_lock, flags); + stream->stream_status = state; + spin_unlock_irqrestore(&stream->status_lock, flags); +} + +static inline int sst_get_stream_status(struct sst_runtime_stream *stream) +{ + int state; + unsigned long flags; + + spin_lock_irqsave(&stream->status_lock, flags); + state = stream->stream_status; + spin_unlock_irqrestore(&stream->status_lock, flags); + return state; +} + +static void sst_fill_alloc_params(struct snd_pcm_substream *substream, + struct snd_sst_alloc_params_ext *alloc_param) +{ + unsigned int channels; + snd_pcm_uframes_t period_size; + ssize_t periodbytes; + ssize_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + u32 buffer_addr = virt_to_phys(substream->dma_buffer.area); + + channels = substream->runtime->channels; + period_size = substream->runtime->period_size; + periodbytes = samples_to_bytes(substream->runtime, period_size); + alloc_param->ring_buf_info[0].addr = buffer_addr; + alloc_param->ring_buf_info[0].size = buffer_bytes; + alloc_param->sg_count = 1; + alloc_param->reserved = 0; + alloc_param->frag_size = periodbytes * channels; + +} +static void sst_fill_pcm_params(struct snd_pcm_substream *substream, + struct snd_sst_stream_params *param) +{ + param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; + param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; + param->uc.pcm_params.sfreq = substream->runtime->rate; + + /* PCM stream via ALSA interface */ + param->uc.pcm_params.use_offload_path = 0; + param->uc.pcm_params.reserved2 = 0; + memset(param->uc.pcm_params.channel_map, 0, sizeof(u8)); + +} + +static int sst_get_stream_mapping(int dev, int sdev, int dir, + struct sst_dev_stream_map *map, int size) +{ + int i; + + if (map == NULL) + return -EINVAL; + + + /* index 0 is not used in stream map */ + for (i = 1; i < size; i++) { + if ((map[i].dev_num == dev) && (map[i].direction == dir)) + return i; + } + return 0; +} + +int sst_fill_stream_params(void *substream, + const struct sst_data *ctx, struct snd_sst_params *str_params, bool is_compress) +{ + int map_size; + int index; + struct sst_dev_stream_map *map; + struct snd_pcm_substream *pstream = NULL; + struct snd_compr_stream *cstream = NULL; + + map = ctx->pdata->pdev_strm_map; + map_size = ctx->pdata->strm_map_size; + + if (is_compress == true) + cstream = (struct snd_compr_stream *)substream; + else + pstream = (struct snd_pcm_substream *)substream; + + str_params->stream_type = SST_STREAM_TYPE_MUSIC; + + /* For pcm streams */ + if (pstream) { + index = sst_get_stream_mapping(pstream->pcm->device, + pstream->number, pstream->stream, + map, map_size); + if (index <= 0) + return -EINVAL; + + str_params->stream_id = index; + str_params->device_type = map[index].device_id; + str_params->task = map[index].task_id; + + str_params->ops = (u8)pstream->stream; + } + + if (cstream) { + index = sst_get_stream_mapping(cstream->device->device, + 0, cstream->direction, + map, map_size); + if (index <= 0) + return -EINVAL; + str_params->stream_id = index; + str_params->device_type = map[index].device_id; + str_params->task = map[index].task_id; + + str_params->ops = (u8)cstream->direction; + } + return 0; +} + +static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + struct snd_sst_stream_params param = {{{0,},},}; + struct snd_sst_params str_params = {0}; + struct snd_sst_alloc_params_ext alloc_params = {0}; + int ret_val = 0; + struct sst_data *ctx = snd_soc_dai_get_drvdata(dai); + + /* set codec params and inform SST driver the same */ + sst_fill_pcm_params(substream, ¶m); + sst_fill_alloc_params(substream, &alloc_params); + substream->runtime->dma_area = substream->dma_buffer.area; + str_params.sparams = param; + str_params.aparams = alloc_params; + str_params.codec = SST_CODEC_TYPE_PCM; + + /* fill the device type and stream id to pass to SST driver */ + ret_val = sst_fill_stream_params(substream, ctx, &str_params, false); + if (ret_val < 0) + return ret_val; + + stream->stream_info.str_id = str_params.stream_id; + + ret_val = stream->ops->open(sst->dev, &str_params); + if (ret_val <= 0) + return ret_val; + + + return ret_val; +} + +static void sst_period_elapsed(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct sst_runtime_stream *stream; + int status; + + if (!substream || !substream->runtime) + return; + stream = substream->runtime->private_data; + if (!stream) + return; + status = sst_get_stream_status(stream); + if (status != SST_PLATFORM_RUNNING) + return; + snd_pcm_period_elapsed(substream); +} + +static int sst_platform_init_stream(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret_val; + + dev_dbg(rtd->dev, "setting buffer ptr param\n"); + sst_set_stream_status(stream, SST_PLATFORM_INIT); + stream->stream_info.period_elapsed = sst_period_elapsed; + stream->stream_info.arg = substream; + stream->stream_info.buffer_ptr = 0; + stream->stream_info.sfreq = substream->runtime->rate; + ret_val = stream->ops->stream_init(sst->dev, &stream->stream_info); + if (ret_val) + dev_err(rtd->dev, "control_set ret error %d\n", ret_val); + return ret_val; + +} + +static int power_up_sst(struct sst_runtime_stream *stream) +{ + return stream->ops->power(sst->dev, true); +} + +static void power_down_sst(struct sst_runtime_stream *stream) +{ + stream->ops->power(sst->dev, false); +} + +static int sst_media_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sst_runtime_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ + mutex_lock(&sst_lock); + if (!sst || + !try_module_get(sst->dev->driver->owner)) { + dev_err(dai->dev, "no device available to run\n"); + ret_val = -ENODEV; + goto out_ops; + } + stream->ops = sst->ops; + mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + + stream->stream_info.arg = substream; + /* allocate memory for SST API set */ + runtime->private_data = stream; + + ret_val = power_up_sst(stream); + if (ret_val < 0) + return ret_val; + + /* Make sure, that the period size is always even */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +out_ops: + kfree(stream); + mutex_unlock(&sst_lock); + return ret_val; +} + +static void sst_media_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + stream = substream->runtime->private_data; + power_down_sst(stream); + + str_id = stream->stream_info.str_id; + if (str_id) + ret_val = stream->ops->close(sst->dev, str_id); + module_put(sst->dev->driver->owner); + kfree(stream); +} + +static inline unsigned int get_current_pipe_id(struct snd_soc_dai *dai, + struct snd_pcm_substream *substream) +{ + struct sst_data *sst = snd_soc_dai_get_drvdata(dai); + struct sst_dev_stream_map *map = sst->pdata->pdev_strm_map; + struct sst_runtime_stream *stream = + substream->runtime->private_data; + u32 str_id = stream->stream_info.str_id; + unsigned int pipe_id; + + pipe_id = map[str_id].device_id; + + dev_dbg(dai->dev, "got pipe_id = %#x for str_id = %d\n", + pipe_id, str_id); + return pipe_id; +} + +static int sst_media_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + if (stream->stream_info.str_id) { + ret_val = stream->ops->stream_drop(sst->dev, str_id); + return ret_val; + } + + ret_val = sst_platform_alloc_stream(substream, dai); + if (ret_val <= 0) + return ret_val; + snprintf(substream->pcm->id, sizeof(substream->pcm->id), + "%d", stream->stream_info.str_id); + + ret_val = sst_platform_init_stream(substream); + if (ret_val) + return ret_val; + substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; + return ret_val; +} + +static int sst_media_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + return 0; +} + +static int sst_media_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int sst_enable_ssp(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + + if (!dai->active) { + ret = sst_handle_vb_timer(dai, true); + if (ret) + return ret; + ret = send_ssp_cmd(dai, dai->name, 1); + } + return ret; +} + +static void sst_disable_ssp(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!dai->active) { + send_ssp_cmd(dai, dai->name, 0); + sst_handle_vb_timer(dai, false); + } +} + +static struct snd_soc_dai_ops sst_media_dai_ops = { + .startup = sst_media_open, + .shutdown = sst_media_close, + .prepare = sst_media_prepare, + .hw_params = sst_media_hw_params, + .hw_free = sst_media_hw_free, + .mute_stream = sst_media_digital_mute, +}; + +static struct snd_soc_dai_ops sst_compr_dai_ops = { + .mute_stream = sst_media_digital_mute, +}; + +static struct snd_soc_dai_ops sst_be_dai_ops = { + .startup = sst_enable_ssp, + .shutdown = sst_disable_ssp, +}; + +static struct snd_soc_dai_driver sst_platform_dai[] = { +{ + .name = "media-cpu-dai", + .ops = &sst_media_dai_ops, + .playback = { + .stream_name = "Headset Playback", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Headset Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "compress-cpu-dai", + .compress_dai = 1, + .ops = &sst_compr_dai_ops, + .playback = { + .stream_name = "Compress Playback", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +/* BE CPU Dais */ +{ + .name = "ssp0-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp0 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp0 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "ssp1-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp1 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp1 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "ssp2-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp2 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp2 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static int sst_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (substream->pcm->internal) + return 0; + + runtime = substream->runtime; + runtime->hw = sst_platform_pcm_hw; + return 0; +} + +static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + int ret_val = 0, str_id; + struct sst_runtime_stream *stream; + int status; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->dev, "sst_platform_pcm_trigger called\n"); + if (substream->pcm->internal) + return 0; + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(rtd->dev, "sst: Trigger Start\n"); + status = SST_PLATFORM_RUNNING; + stream->stream_info.arg = substream; + ret_val = stream->ops->stream_start(sst->dev, str_id); + break; + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(rtd->dev, "sst: in stop\n"); + status = SST_PLATFORM_DROPPED; + ret_val = stream->ops->stream_drop(sst->dev, str_id); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + dev_dbg(rtd->dev, "sst: in pause\n"); + status = SST_PLATFORM_PAUSED; + ret_val = stream->ops->stream_pause(sst->dev, str_id); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + dev_dbg(rtd->dev, "sst: in pause release\n"); + status = SST_PLATFORM_RUNNING; + ret_val = stream->ops->stream_pause_release(sst->dev, str_id); + break; + default: + return -EINVAL; + } + + if (!ret_val) + sst_set_stream_status(stream, status); + + return ret_val; +} + + +static snd_pcm_uframes_t sst_platform_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val, status; + struct pcm_stream_info *str_info; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + stream = substream->runtime->private_data; + status = sst_get_stream_status(stream); + if (status == SST_PLATFORM_INIT) + return 0; + str_info = &stream->stream_info; + ret_val = stream->ops->stream_read_tstamp(sst->dev, str_info); + if (ret_val) { + dev_err(rtd->dev, "sst: error code = %d\n", ret_val); + return ret_val; + } + substream->runtime->delay = str_info->pcm_delay; + return str_info->buffer_ptr; +} + +static struct snd_pcm_ops sst_platform_ops = { + .open = sst_platform_open, + .ioctl = snd_pcm_lib_ioctl, + .trigger = sst_platform_pcm_trigger, + .pointer = sst_platform_pcm_pointer, +}; + +static int sst_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_pcm *pcm = rtd->pcm; + int retval = 0; + + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_DMA), + SST_MIN_BUFFER, SST_MAX_BUFFER); + if (retval) { + dev_err(rtd->dev, "dma buffer allocationf fail\n"); + return retval; + } + } + return retval; +} + +static int sst_soc_probe(struct snd_soc_platform *platform) +{ + struct sst_data *drv = dev_get_drvdata(platform->dev); + + drv->soc_card = platform->component.card; + return sst_dsp_init_v2_dpcm(platform); +} + +static struct snd_soc_platform_driver sst_soc_platform_drv = { + .probe = sst_soc_probe, + .ops = &sst_platform_ops, + .compr_ops = &sst_platform_compr_ops, + .pcm_new = sst_pcm_new, +}; + +static const struct snd_soc_component_driver sst_component = { + .name = "sst", +}; + + +static int sst_platform_probe(struct platform_device *pdev) +{ + struct sst_data *drv; + int ret; + struct sst_platform_data *pdata; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (drv == NULL) { + return -ENOMEM; + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) { + return -ENOMEM; + } + + pdata->pdev_strm_map = dpcm_strm_map; + pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); + drv->pdata = pdata; + drv->pdev = pdev; + mutex_init(&drv->lock); + dev_set_drvdata(&pdev->dev, drv); + + ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); + if (ret) { + dev_err(&pdev->dev, "registering soc platform failed\n"); + return ret; + } + + ret = snd_soc_register_component(&pdev->dev, &sst_component, + sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); + if (ret) { + dev_err(&pdev->dev, "registering cpu dais failed\n"); + snd_soc_unregister_platform(&pdev->dev); + } + return ret; +} + +static int sst_platform_remove(struct platform_device *pdev) +{ + + snd_soc_unregister_component(&pdev->dev); + snd_soc_unregister_platform(&pdev->dev); + dev_dbg(&pdev->dev, "sst_platform_remove success\n"); + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int sst_soc_prepare(struct device *dev) +{ + struct sst_data *drv = dev_get_drvdata(dev); + int i; + + /* suspend all pcms first */ + snd_soc_suspend(drv->soc_card->dev); + snd_soc_poweroff(drv->soc_card->dev); + + /* set the SSPs to idle */ + for (i = 0; i < drv->soc_card->num_rtd; i++) { + struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai; + + if (dai->active) { + send_ssp_cmd(dai, dai->name, 0); + sst_handle_vb_timer(dai, false); + } + } + + return 0; +} + +static void sst_soc_complete(struct device *dev) +{ + struct sst_data *drv = dev_get_drvdata(dev); + int i; + + /* restart SSPs */ + for (i = 0; i < drv->soc_card->num_rtd; i++) { + struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai; + + if (dai->active) { + sst_handle_vb_timer(dai, true); + send_ssp_cmd(dai, dai->name, 1); + } + } + snd_soc_resume(drv->soc_card->dev); +} + +#else + +#define sst_soc_prepare NULL +#define sst_soc_complete NULL + +#endif + + +static const struct dev_pm_ops sst_platform_pm = { + .prepare = sst_soc_prepare, + .complete = sst_soc_complete, +}; + +static struct platform_driver sst_platform_driver = { + .driver = { + .name = "sst-mfld-platform", + .pm = &sst_platform_pm, + }, + .probe = sst_platform_probe, + .remove = sst_platform_remove, +}; + +module_platform_driver(sst_platform_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sst-mfld-platform"); diff --git a/sound/soc/intel/atom/sst-mfld-platform.h b/sound/soc/intel/atom/sst-mfld-platform.h new file mode 100644 index 0000000..9094314 --- /dev/null +++ b/sound/soc/intel/atom/sst-mfld-platform.h @@ -0,0 +1,181 @@ +/* + * sst_mfld_platform.h - Intel MID Platform driver header file + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul + * Author: Harsha Priya + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 __SST_PLATFORMDRV_H__ +#define __SST_PLATFORMDRV_H__ + +#include "sst-mfld-dsp.h" + +extern struct sst_device *sst; + +#define SST_MONO 1 +#define SST_STEREO 2 +#define SST_MAX_CAP 5 + +#define SST_MAX_BUFFER (800*1024) +#define SST_MIN_BUFFER (800*1024) +#define SST_MIN_PERIOD_BYTES 32 +#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER +#define SST_MIN_PERIODS 2 +#define SST_MAX_PERIODS (1024*2) +#define SST_FIFO_SIZE 0 + +struct pcm_stream_info { + int str_id; + void *arg; + void (*period_elapsed) (void *arg); + unsigned long long buffer_ptr; + unsigned long long pcm_delay; + int sfreq; +}; + +enum sst_drv_status { + SST_PLATFORM_INIT = 1, + SST_PLATFORM_STARTED, + SST_PLATFORM_RUNNING, + SST_PLATFORM_PAUSED, + SST_PLATFORM_DROPPED, +}; + +enum sst_stream_ops { + STREAM_OPS_PLAYBACK = 0, + STREAM_OPS_CAPTURE, +}; + +enum sst_audio_device_type { + SND_SST_DEVICE_HEADSET = 1, + SND_SST_DEVICE_IHF, + SND_SST_DEVICE_VIBRA, + SND_SST_DEVICE_HAPTIC, + SND_SST_DEVICE_CAPTURE, + SND_SST_DEVICE_COMPRESS, +}; + +/* PCM Parameters */ +struct sst_pcm_params { + u16 codec; /* codec type */ + u8 num_chan; /* 1=Mono, 2=Stereo */ + u8 pcm_wd_sz; /* 16/24 - bit*/ + u32 reserved; /* Bitrate in bits per second */ + u32 sfreq; /* Sampling rate in Hz */ + u32 ring_buffer_size; + u32 period_count; /* period elapsed in samples*/ + u32 ring_buffer_addr; +}; + +struct sst_stream_params { + u32 result; + u32 stream_id; + u8 codec; + u8 ops; + u8 stream_type; + u8 device_type; + struct sst_pcm_params sparams; +}; + +struct sst_compress_cb { + void *param; + void (*compr_cb)(void *param); + void *drain_cb_param; + void (*drain_notify)(void *param); +}; + +struct compress_sst_ops { + const char *name; + int (*open)(struct device *dev, + struct snd_sst_params *str_params, struct sst_compress_cb *cb); + int (*stream_start)(struct device *dev, unsigned int str_id); + int (*stream_drop)(struct device *dev, unsigned int str_id); + int (*stream_drain)(struct device *dev, unsigned int str_id); + int (*stream_partial_drain)(struct device *dev, unsigned int str_id); + int (*stream_pause)(struct device *dev, unsigned int str_id); + int (*stream_pause_release)(struct device *dev, unsigned int str_id); + + int (*tstamp)(struct device *dev, unsigned int str_id, + struct snd_compr_tstamp *tstamp); + int (*ack)(struct device *dev, unsigned int str_id, + unsigned long bytes); + int (*close)(struct device *dev, unsigned int str_id); + int (*get_caps)(struct snd_compr_caps *caps); + int (*get_codec_caps)(struct snd_compr_codec_caps *codec); + int (*set_metadata)(struct device *dev, unsigned int str_id, + struct snd_compr_metadata *mdata); + int (*power)(struct device *dev, bool state); +}; + +struct sst_ops { + int (*open)(struct device *dev, struct snd_sst_params *str_param); + int (*stream_init)(struct device *dev, struct pcm_stream_info *str_info); + int (*stream_start)(struct device *dev, int str_id); + int (*stream_drop)(struct device *dev, int str_id); + int (*stream_pause)(struct device *dev, int str_id); + int (*stream_pause_release)(struct device *dev, int str_id); + int (*stream_read_tstamp)(struct device *dev, struct pcm_stream_info *str_info); + int (*send_byte_stream)(struct device *dev, struct snd_sst_bytes_v2 *bytes); + int (*close)(struct device *dev, unsigned int str_id); + int (*power)(struct device *dev, bool state); +}; + +struct sst_runtime_stream { + int stream_status; + unsigned int id; + size_t bytes_written; + struct pcm_stream_info stream_info; + struct sst_ops *ops; + struct compress_sst_ops *compr_ops; + spinlock_t status_lock; +}; + +struct sst_device { + char *name; + struct device *dev; + struct sst_ops *ops; + struct platform_device *pdev; + struct compress_sst_ops *compr_ops; +}; + +struct sst_data; + +int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform); +int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute); +int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable); +int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable); + +void sst_set_stream_status(struct sst_runtime_stream *stream, int state); +int sst_fill_stream_params(void *substream, const struct sst_data *ctx, + struct snd_sst_params *str_params, bool is_compress); + +struct sst_algo_int_control_v2 { + struct soc_mixer_control mc; + u16 module_id; /* module identifieer */ + u16 pipe_id; /* location info: pipe_id + instance_id */ + u16 instance_id; + unsigned int value; /* Value received is stored here */ +}; +struct sst_data { + struct platform_device *pdev; + struct sst_platform_data *pdata; + struct snd_sst_bytes_v2 *byte_stream; + struct mutex lock; + struct snd_soc_card *soc_card; +}; +int sst_register_dsp(struct sst_device *sst); +int sst_unregister_dsp(struct sst_device *sst); +#endif diff --git a/sound/soc/intel/atom/sst/Makefile b/sound/soc/intel/atom/sst/Makefile new file mode 100644 index 0000000..fd21726 --- /dev/null +++ b/sound/soc/intel/atom/sst/Makefile @@ -0,0 +1,7 @@ +snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o +snd-intel-sst-pci-objs += sst_pci.o +snd-intel-sst-acpi-objs += sst_acpi.o + +obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst-core.o +obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o +obj-$(CONFIG_SND_SST_IPC_ACPI) += snd-intel-sst-acpi.o diff --git a/sound/soc/intel/atom/sst/sst.c b/sound/soc/intel/atom/sst/sst.c new file mode 100644 index 0000000..96c2e42 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst.c @@ -0,0 +1,557 @@ +/* + * sst.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); +MODULE_LICENSE("GPL v2"); + +static inline bool sst_is_process_reply(u32 msg_id) +{ + return ((msg_id & PROCESS_MSG) ? true : false); +} + +static inline bool sst_validate_mailbox_size(unsigned int size) +{ + return ((size <= SST_MAILBOX_SIZE) ? true : false); +} + +static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) +{ + union interrupt_reg_mrfld isr; + union ipc_header_mrfld header; + union sst_imr_reg_mrfld imr; + struct ipc_post *msg = NULL; + unsigned int size = 0; + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + irqreturn_t retval = IRQ_HANDLED; + + /* Interrupt arrived, check src */ + isr.full = sst_shim_read64(drv->shim, SST_ISRX); + + if (isr.part.done_interrupt) { + /* Clear done bit */ + spin_lock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, + drv->ipc_reg.ipcx); + header.p.header_high.part.done = 0; + sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); + + /* write 1 to clear status register */; + isr.part.done_interrupt = 1; + sst_shim_write64(drv->shim, SST_ISRX, isr.full); + spin_unlock(&drv->ipc_spin_lock); + + /* we can send more messages to DSP so trigger work */ + queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq); + retval = IRQ_HANDLED; + } + + if (isr.part.busy_interrupt) { + /* message from dsp so copy that */ + spin_lock(&drv->ipc_spin_lock); + imr.full = sst_shim_read64(drv->shim, SST_IMRX); + imr.part.busy_interrupt = 1; + sst_shim_write64(drv->shim, SST_IMRX, imr.full); + spin_unlock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); + + if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { + drv->ops->clear_interrupt(drv); + return IRQ_HANDLED; + } + + if (header.p.header_high.part.large) { + size = header.p.header_low_payload; + if (sst_validate_mailbox_size(size)) { + memcpy_fromio(msg->mailbox_data, + drv->mailbox + drv->mailbox_recv_offset, size); + } else { + dev_err(drv->dev, + "Mailbox not copied, payload size is: %u\n", size); + header.p.header_low_payload = 0; + } + } + + msg->mrfld_header = header; + msg->is_process_reply = + sst_is_process_reply(header.p.header_high.part.msg_id); + spin_lock(&drv->rx_msg_lock); + list_add_tail(&msg->node, &drv->rx_list); + spin_unlock(&drv->rx_msg_lock); + drv->ops->clear_interrupt(drv); + retval = IRQ_WAKE_THREAD; + } + return retval; +} + +static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) +{ + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + struct ipc_post *__msg, *msg = NULL; + unsigned long irq_flags; + + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + if (list_empty(&drv->rx_list)) { + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; + } + + list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { + list_del(&msg->node); + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + if (msg->is_process_reply) + drv->ops->process_message(msg); + else + drv->ops->process_reply(drv, msg); + + if (msg->is_large) + kfree(msg->mailbox_data); + kfree(msg); + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + } + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; +} + +static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) +{ + int ret = 0; + + ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD, + IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL, + true, true, false, true); + + if (ret < 0) { + dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret); + return -EIO; + } + + return 0; +} + + +static struct intel_sst_ops mrfld_ops = { + .interrupt = intel_sst_interrupt_mrfld, + .irq_thread = intel_sst_irq_thread_mrfld, + .clear_interrupt = intel_sst_clear_intr_mrfld, + .start = sst_start_mrfld, + .reset = intel_sst_reset_dsp_mrfld, + .post_message = sst_post_message_mrfld, + .process_reply = sst_process_reply_mrfld, + .save_dsp_context = sst_save_dsp_context_v2, + .alloc_stream = sst_alloc_stream_mrfld, + .post_download = sst_post_download_mrfld, +}; + +int sst_driver_ops(struct intel_sst_drv *sst) +{ + + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + case SST_CHV_ACPI_ID: + sst->tstamp = SST_TIME_STAMP_MRFLD; + sst->ops = &mrfld_ops; + return 0; + + default: + dev_err(sst->dev, + "SST Driver capablities missing for dev_id: %x", sst->dev_id); + return -EINVAL; + }; +} + +void sst_process_pending_msg(struct work_struct *work) +{ + struct intel_sst_drv *ctx = container_of(work, + struct intel_sst_drv, ipc_post_msg_wq); + + ctx->ops->post_message(ctx, NULL, false); +} + +static int sst_workqueue_init(struct intel_sst_drv *ctx) +{ + INIT_LIST_HEAD(&ctx->memcpy_list); + INIT_LIST_HEAD(&ctx->rx_list); + INIT_LIST_HEAD(&ctx->ipc_dispatch_list); + INIT_LIST_HEAD(&ctx->block_list); + INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg); + init_waitqueue_head(&ctx->wait_queue); + + ctx->post_msg_wq = + create_singlethread_workqueue("sst_post_msg_wq"); + if (!ctx->post_msg_wq) + return -EBUSY; + return 0; +} + +static void sst_init_locks(struct intel_sst_drv *ctx) +{ + mutex_init(&ctx->sst_lock); + spin_lock_init(&ctx->rx_msg_lock); + spin_lock_init(&ctx->ipc_spin_lock); + spin_lock_init(&ctx->block_lock); +} + +int sst_alloc_drv_context(struct intel_sst_drv **ctx, + struct device *dev, unsigned int dev_id) +{ + *ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL); + if (!(*ctx)) + return -ENOMEM; + + (*ctx)->dev = dev; + (*ctx)->dev_id = dev_id; + + return 0; +} +EXPORT_SYMBOL_GPL(sst_alloc_drv_context); + +int sst_context_init(struct intel_sst_drv *ctx) +{ + int ret = 0, i; + + if (!ctx->pdata) + return -EINVAL; + + if (!ctx->pdata->probe_data) + return -EINVAL; + + memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info)); + + ret = sst_driver_ops(ctx); + if (ret != 0) + return -EINVAL; + + sst_init_locks(ctx); + sst_set_fw_state_locked(ctx, SST_RESET); + + /* pvt_id 0 reserved for async messages */ + ctx->pvt_id = 1; + ctx->stream_cnt = 0; + ctx->fw_in_mem = NULL; + /* we use memcpy, so set to 0 */ + ctx->use_dma = 0; + ctx->use_lli = 0; + + if (sst_workqueue_init(ctx)) + return -EINVAL; + + ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off; + ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset; + ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset; + + dev_info(ctx->dev, "Got drv data max stream %d\n", + ctx->info.max_streams); + + for (i = 1; i <= ctx->info.max_streams; i++) { + struct stream_info *stream = &ctx->streams[i]; + + memset(stream, 0, sizeof(*stream)); + stream->pipe_id = PIPE_RSVD; + mutex_init(&stream->lock); + } + + /* Register the ISR */ + ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt, + ctx->ops->irq_thread, 0, SST_DRV_NAME, + ctx); + if (ret) + goto do_free_mem; + + dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num); + + /* default intr are unmasked so set this as masked */ + sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038); + + ctx->qos = devm_kzalloc(ctx->dev, + sizeof(struct pm_qos_request), GFP_KERNEL); + if (!ctx->qos) { + ret = -ENOMEM; + goto do_free_mem; + } + pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name); + ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name, + ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb); + if (ret) { + dev_err(ctx->dev, "Firmware download failed:%d\n", ret); + goto do_free_mem; + } + sst_register(ctx->dev); + return 0; + +do_free_mem: + destroy_workqueue(ctx->post_msg_wq); + return ret; +} +EXPORT_SYMBOL_GPL(sst_context_init); + +void sst_context_cleanup(struct intel_sst_drv *ctx) +{ + pm_runtime_get_noresume(ctx->dev); + pm_runtime_disable(ctx->dev); + sst_unregister(ctx->dev); + sst_set_fw_state_locked(ctx, SST_SHUTDOWN); + flush_scheduled_work(); + destroy_workqueue(ctx->post_msg_wq); + pm_qos_remove_request(ctx->qos); + kfree(ctx->fw_sg_list.src); + kfree(ctx->fw_sg_list.dst); + ctx->fw_sg_list.list_len = 0; + kfree(ctx->fw_in_mem); + ctx->fw_in_mem = NULL; + sst_memcpy_free_resources(ctx); + ctx = NULL; +} +EXPORT_SYMBOL_GPL(sst_context_cleanup); + +static inline void sst_save_shim64(struct intel_sst_drv *ctx, + void __iomem *shim, + struct sst_shim_regs64 *shim_regs) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); + + shim_regs->imrx = sst_shim_read64(shim, SST_IMRX); + shim_regs->csr = sst_shim_read64(shim, SST_CSR); + + + spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); +} + +static inline void sst_restore_shim64(struct intel_sst_drv *ctx, + void __iomem *shim, + struct sst_shim_regs64 *shim_regs) +{ + unsigned long irq_flags; + + /* + * we only need to restore IMRX for this case, rest will be + * initialize by FW or driver when firmware is loaded + */ + spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); + sst_shim_write64(shim, SST_IMRX, shim_regs->imrx), + sst_shim_write64(shim, SST_CSR, shim_regs->csr), + spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); +} + +void sst_configure_runtime_pm(struct intel_sst_drv *ctx) +{ + pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY); + pm_runtime_use_autosuspend(ctx->dev); + /* + * For acpi devices, the actual physical device state is + * initially active. So change the state to active before + * enabling the pm + */ + + if (!acpi_disabled) + pm_runtime_set_active(ctx->dev); + + pm_runtime_enable(ctx->dev); + + if (acpi_disabled) + pm_runtime_set_active(ctx->dev); + else + pm_runtime_put_noidle(ctx->dev); + + sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); +} +EXPORT_SYMBOL_GPL(sst_configure_runtime_pm); + +static int intel_sst_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state == SST_RESET) { + dev_dbg(dev, "LPE is already in RESET state, No action\n"); + return 0; + } + /* save fw context */ + if (ctx->ops->save_dsp_context(ctx)) + return -EBUSY; + + /* Move the SST state to Reset */ + sst_set_fw_state_locked(ctx, SST_RESET); + + synchronize_irq(ctx->irq_num); + flush_workqueue(ctx->post_msg_wq); + + ctx->ops->reset(ctx); + /* save the shim registers because PMC doesn't save state */ + sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); + + return ret; +} + +static int intel_sst_suspend(struct device *dev) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + struct sst_fw_save *fw_save; + int i, ret = 0; + + /* check first if we are already in SW reset */ + if (ctx->sst_state == SST_RESET) + return 0; + + /* + * check if any stream is active and running + * they should already by suspend by soc_suspend + */ + for (i = 1; i <= ctx->info.max_streams; i++) { + struct stream_info *stream = &ctx->streams[i]; + + if (stream->status == STREAM_RUNNING) { + dev_err(dev, "stream %d is running, cant susupend, abort\n", i); + return -EBUSY; + } + } + synchronize_irq(ctx->irq_num); + flush_workqueue(ctx->post_msg_wq); + + /* Move the SST state to Reset */ + sst_set_fw_state_locked(ctx, SST_RESET); + + /* tell DSP we are suspending */ + if (ctx->ops->save_dsp_context(ctx)) + return -EBUSY; + + /* save the memories */ + fw_save = kzalloc(sizeof(*fw_save), GFP_KERNEL); + if (!fw_save) + return -ENOMEM; + fw_save->iram = kzalloc(ctx->iram_end - ctx->iram_base, GFP_KERNEL); + if (!fw_save->iram) { + ret = -ENOMEM; + goto iram; + } + fw_save->dram = kzalloc(ctx->dram_end - ctx->dram_base, GFP_KERNEL); + if (!fw_save->dram) { + ret = -ENOMEM; + goto dram; + } + fw_save->sram = kzalloc(SST_MAILBOX_SIZE, GFP_KERNEL); + if (!fw_save->sram) { + ret = -ENOMEM; + goto sram; + } + + fw_save->ddr = kzalloc(ctx->ddr_end - ctx->ddr_base, GFP_KERNEL); + if (!fw_save->ddr) { + ret = -ENOMEM; + goto ddr; + } + + memcpy32_fromio(fw_save->iram, ctx->iram, ctx->iram_end - ctx->iram_base); + memcpy32_fromio(fw_save->dram, ctx->dram, ctx->dram_end - ctx->dram_base); + memcpy32_fromio(fw_save->sram, ctx->mailbox, SST_MAILBOX_SIZE); + memcpy32_fromio(fw_save->ddr, ctx->ddr, ctx->ddr_end - ctx->ddr_base); + + ctx->fw_save = fw_save; + ctx->ops->reset(ctx); + return 0; +ddr: + kfree(fw_save->sram); +sram: + kfree(fw_save->dram); +dram: + kfree(fw_save->iram); +iram: + kfree(fw_save); + return ret; +} + +static int intel_sst_resume(struct device *dev) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + struct sst_fw_save *fw_save = ctx->fw_save; + int ret = 0; + struct sst_block *block; + + if (!fw_save) + return 0; + + sst_set_fw_state_locked(ctx, SST_FW_LOADING); + + /* we have to restore the memory saved */ + ctx->ops->reset(ctx); + + ctx->fw_save = NULL; + + memcpy32_toio(ctx->iram, fw_save->iram, ctx->iram_end - ctx->iram_base); + memcpy32_toio(ctx->dram, fw_save->dram, ctx->dram_end - ctx->dram_base); + memcpy32_toio(ctx->mailbox, fw_save->sram, SST_MAILBOX_SIZE); + memcpy32_toio(ctx->ddr, fw_save->ddr, ctx->ddr_end - ctx->ddr_base); + + kfree(fw_save->sram); + kfree(fw_save->dram); + kfree(fw_save->iram); + kfree(fw_save->ddr); + kfree(fw_save); + + block = sst_create_block(ctx, 0, FW_DWNL_ID); + if (block == NULL) + return -ENOMEM; + + + /* start and wait for ack */ + ctx->ops->start(ctx); + ret = sst_wait_timeout(ctx, block); + if (ret) { + dev_err(ctx->dev, "fw download failed %d\n", ret); + /* FW download failed due to timeout */ + ret = -EBUSY; + + } else { + sst_set_fw_state_locked(ctx, SST_FW_RUNNING); + } + + sst_free_block(ctx, block); + return ret; +} + +const struct dev_pm_ops intel_sst_pm = { + .suspend = intel_sst_suspend, + .resume = intel_sst_resume, + .runtime_suspend = intel_sst_runtime_suspend, +}; +EXPORT_SYMBOL_GPL(intel_sst_pm); diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h new file mode 100644 index 0000000..3f49386 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst.h @@ -0,0 +1,559 @@ +/* + * sst.h - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Common private declarations for SST + */ +#ifndef __SST_H__ +#define __SST_H__ + +#include + +/* driver names */ +#define SST_DRV_NAME "intel_sst_driver" +#define SST_MRFLD_PCI_ID 0x119A +#define SST_BYT_ACPI_ID 0x80860F28 +#define SST_CHV_ACPI_ID 0x808622A8 + +#define SST_SUSPEND_DELAY 2000 +#define FW_CONTEXT_MEM (64*1024) +#define SST_ICCM_BOUNDARY 4 +#define SST_CONFIG_SSP_SIGN 0x7ffe8001 + +#define MRFLD_FW_VIRTUAL_BASE 0xC0000000 +#define MRFLD_FW_DDR_BASE_OFFSET 0x0 +#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4 +#define MRFLD_FW_BSS_RESET_BIT 0 + +extern const struct dev_pm_ops intel_sst_pm; +enum sst_states { + SST_FW_LOADING = 1, + SST_FW_RUNNING, + SST_RESET, + SST_SHUTDOWN, +}; + +enum sst_algo_ops { + SST_SET_ALGO = 0, + SST_GET_ALGO = 1, +}; + +#define SST_BLOCK_TIMEOUT 1000 + +#define FW_SIGNATURE_SIZE 4 +#define FW_NAME_SIZE 32 + +/* stream states */ +enum sst_stream_states { + STREAM_UN_INIT = 0, /* Freed/Not used stream */ + STREAM_RUNNING = 1, /* Running */ + STREAM_PAUSED = 2, /* Paused stream */ + STREAM_DECODE = 3, /* stream is in decoding only state */ + STREAM_INIT = 4, /* stream init, waiting for data */ + STREAM_RESET = 5, /* force reset on recovery */ +}; + +enum sst_ram_type { + SST_IRAM = 1, + SST_DRAM = 2, + SST_DDR = 5, + SST_CUSTOM_INFO = 7, /* consists of FW binary information */ +}; + +/* SST shim registers to structure mapping */ +union interrupt_reg { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_pisr_reg { + struct { + u32 pssp0:1; + u32 pssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:26; + } part; + u32 full; +}; + +union sst_pimr_reg { + struct { + u32 ssp0:1; + u32 ssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:10; + u32 ssp0_sc:1; + u32 ssp1_sc:1; + u32 rsvd2:3; + u32 dmac_sc:1; + u32 rsvd3:10; + } part; + u32 full; +}; + +union config_status_reg_mrfld { + struct { + u64 lpe_reset:1; + u64 lpe_reset_vector:1; + u64 runstall:1; + u64 pwaitmode:1; + u64 clk_sel:3; + u64 rsvd2:1; + u64 sst_clk:3; + u64 xt_snoop:1; + u64 rsvd3:4; + u64 clk_sel1:6; + u64 clk_enable:3; + u64 rsvd4:6; + u64 slim0baseclk:1; + u64 rsvd:32; + } part; + u64 full; +}; + +union interrupt_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_imr_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +/** + * struct sst_block - This structure is used to block a user/fw data call to another + * fw/user call + * + * @condition: condition for blocking check + * @ret_code: ret code when block is released + * @data: data ptr + * @size: size of data + * @on: block condition + * @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL + * @drv_id: str_id in mfld/ctp, = drv_id in mrfld + * @node: list head node + */ +struct sst_block { + bool condition; + int ret_code; + void *data; + u32 size; + bool on; + u32 msg_id; + u32 drv_id; + struct list_head node; +}; + +/** + * struct stream_info - structure that holds the stream information + * + * @status : stream current state + * @prev : stream prev state + * @ops : stream operation pb/cp/drm... + * @bufs: stream buffer list + * @lock : stream mutex for protecting state + * @pcm_substream : PCM substream + * @period_elapsed : PCM period elapsed callback + * @sfreq : stream sampling freq + * @str_type : stream type + * @cumm_bytes : cummulative bytes decoded + * @str_type : stream type + * @src : stream source + */ +struct stream_info { + unsigned int status; + unsigned int prev; + unsigned int ops; + struct mutex lock; + + void *pcm_substream; + void (*period_elapsed)(void *pcm_substream); + + unsigned int sfreq; + u32 cumm_bytes; + + void *compr_cb_param; + void (*compr_cb)(void *compr_cb_param); + + void *drain_cb_param; + void (*drain_notify)(void *drain_cb_param); + + unsigned int num_ch; + unsigned int pipe_id; + unsigned int str_id; + unsigned int task_id; +}; + +#define SST_FW_SIGN "$SST" +#define SST_FW_LIB_SIGN "$LIB" + +/** + * struct sst_fw_header - FW file headers + * + * @signature : FW signature + * @file_size: size of fw image + * @modules : # of modules + * @file_format : version of header format + * @reserved : reserved fields + */ +struct sst_fw_header { + unsigned char signature[FW_SIGNATURE_SIZE]; + u32 file_size; + u32 modules; + u32 file_format; + u32 reserved[4]; +}; + +/** + * struct fw_module_header - module header in FW + * + * @signature: module signature + * @mod_size: size of module + * @blocks: block count + * @type: block type + * @entry_point: module netry point + */ +struct fw_module_header { + unsigned char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; + u32 type; + u32 entry_point; +}; + +/** + * struct fw_block_info - block header for FW + * + * @type: block ram type I/D + * @size: size of block + * @ram_offset: offset in ram + */ +struct fw_block_info { + enum sst_ram_type type; + u32 size; + u32 ram_offset; + u32 rsvd; +}; + +struct sst_runtime_param { + struct snd_sst_runtime_params param; +}; + +struct sst_sg_list { + struct scatterlist *src; + struct scatterlist *dst; + int list_len; + unsigned int sg_idx; +}; + +struct sst_memcpy_list { + struct list_head memcpylist; + void *dstn; + const void *src; + u32 size; + bool is_io; +}; + +/*Firmware Module Information*/ +enum sst_lib_dwnld_status { + SST_LIB_NOT_FOUND = 0, + SST_LIB_FOUND, + SST_LIB_DOWNLOADED, +}; + +struct sst_module_info { + const char *name; /*Library name*/ + u32 id; /*Module ID*/ + u32 entry_pt; /*Module entry point*/ + u8 status; /*module status*/ + u8 rsvd1; + u16 rsvd2; +}; + +/* + * Structure for managing the Library Region(1.5MB) + * in DDR in Merrifield + */ +struct sst_mem_mgr { + phys_addr_t current_base; + int avail; + unsigned int count; +}; + +struct sst_ipc_reg { + int ipcx; + int ipcd; +}; + +struct sst_shim_regs64 { + u64 csr; + u64 pisr; + u64 pimr; + u64 isrx; + u64 isrd; + u64 imrx; + u64 imrd; + u64 ipcx; + u64 ipcd; + u64 isrsc; + u64 isrlpesc; + u64 imrsc; + u64 imrlpesc; + u64 ipcsc; + u64 ipclpesc; + u64 clkctl; + u64 csr2; +}; + +struct sst_fw_save { + void *iram; + void *dram; + void *sram; + void *ddr; +}; + +/** + * struct intel_sst_drv - driver ops + * + * @sst_state : current sst device state + * @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi + * devices + * @shim : SST shim pointer + * @mailbox : SST mailbox pointer + * @iram : SST IRAM pointer + * @dram : SST DRAM pointer + * @pdata : SST info passed as a part of pci platform data + * @shim_phy_add : SST shim phy addr + * @shim_regs64: Struct to save shim registers + * @ipc_dispatch_list : ipc messages dispatched + * @rx_list : to copy the process_reply/process_msg from DSP + * @ipc_post_msg_wq : wq to post IPC messages context + * @mad_ops : MAD driver operations registered + * @mad_wq : MAD driver wq + * @post_msg_wq : wq to post IPC messages + * @streams : sst stream contexts + * @list_lock : sst driver list lock (deprecated) + * @ipc_spin_lock : spin lock to handle audio shim access and ipc queue + * @block_lock : spin lock to add block to block_list and assign pvt_id + * @rx_msg_lock : spin lock to handle the rx messages from the DSP + * @scard_ops : sst card ops + * @pci : sst pci device struture + * @dev : pointer to current device struct + * @sst_lock : sst device lock + * @pvt_id : sst private id + * @stream_cnt : total sst active stream count + * @pb_streams : total active pb streams + * @cp_streams : total active cp streams + * @audio_start : audio status + * @qos : PM Qos struct + * firmware_name : Firmware / Library name + */ +struct intel_sst_drv { + int sst_state; + int irq_num; + unsigned int dev_id; + void __iomem *ddr; + void __iomem *shim; + void __iomem *mailbox; + void __iomem *iram; + void __iomem *dram; + unsigned int mailbox_add; + unsigned int iram_base; + unsigned int dram_base; + unsigned int shim_phy_add; + unsigned int iram_end; + unsigned int dram_end; + unsigned int ddr_end; + unsigned int ddr_base; + unsigned int mailbox_recv_offset; + struct sst_shim_regs64 *shim_regs64; + struct list_head block_list; + struct list_head ipc_dispatch_list; + struct sst_platform_info *pdata; + struct list_head rx_list; + struct work_struct ipc_post_msg_wq; + wait_queue_head_t wait_queue; + struct workqueue_struct *post_msg_wq; + unsigned int tstamp; + /* str_id 0 is not used */ + struct stream_info streams[MAX_NUM_STREAMS+1]; + spinlock_t ipc_spin_lock; + spinlock_t block_lock; + spinlock_t rx_msg_lock; + struct pci_dev *pci; + struct device *dev; + volatile long unsigned pvt_id; + struct mutex sst_lock; + unsigned int stream_cnt; + unsigned int csr_value; + void *fw_in_mem; + struct sst_sg_list fw_sg_list, library_list; + struct intel_sst_ops *ops; + struct sst_info info; + struct pm_qos_request *qos; + unsigned int use_dma; + unsigned int use_lli; + atomic_t fw_clear_context; + bool lib_dwnld_reqd; + struct list_head memcpy_list; + struct sst_ipc_reg ipc_reg; + struct sst_mem_mgr lib_mem_mgr; + /* + * Holder for firmware name. Due to async call it needs to be + * persistent till worker thread gets called + */ + char firmware_name[FW_NAME_SIZE]; + + struct sst_fw_save *fw_save; +}; + +/* misc definitions */ +#define FW_DWNL_ID 0x01 + +struct intel_sst_ops { + irqreturn_t (*interrupt)(int, void *); + irqreturn_t (*irq_thread)(int, void *); + void (*clear_interrupt)(struct intel_sst_drv *ctx); + int (*start)(struct intel_sst_drv *ctx); + int (*reset)(struct intel_sst_drv *ctx); + void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg); + int (*post_message)(struct intel_sst_drv *ctx, + struct ipc_post *msg, bool sync); + void (*process_message)(struct ipc_post *msg); + void (*set_bypass)(bool set); + int (*save_dsp_context)(struct intel_sst_drv *sst); + void (*restore_dsp_context)(void); + int (*alloc_stream)(struct intel_sst_drv *ctx, void *params); + void (*post_download)(struct intel_sst_drv *sst); +}; + +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_send_byte_stream_mrfld(struct intel_sst_drv *ctx, + struct snd_sst_bytes_v2 *sbytes); +int sst_set_stream_param(int str_id, struct snd_sst_params *str_param); +int sst_set_metadata(int str_id, char *params); +int sst_get_stream(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_params *str_param); +int sst_get_stream_allocated(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld); +int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, + int str_id, bool partial_drain); +int sst_post_message_mrfld(struct intel_sst_drv *ctx, + struct ipc_post *msg, bool sync); +void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg); +int sst_start_mrfld(struct intel_sst_drv *ctx); +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx); +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx); + +int sst_load_fw(struct intel_sst_drv *ctx); +int sst_load_library(struct snd_sst_lib_download *lib, u8 ops); +void sst_post_download_mrfld(struct intel_sst_drv *ctx); +int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx); +void sst_memcpy_free_resources(struct intel_sst_drv *ctx); + +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_create_ipc_msg(struct ipc_post **arg, bool large); +int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id); +void sst_clean_stream(struct stream_info *stream); +int intel_sst_register_compress(struct intel_sst_drv *sst); +int intel_sst_remove_compress(struct intel_sst_drv *sst); +void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id); +int sst_send_sync_msg(int ipc, int str_id); +int sst_get_num_channel(struct snd_sst_params *str_param); +int sst_get_sfreq(struct snd_sst_params *str_param); +int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params); +void sst_restore_fw_context(void); +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id); +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id); +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed); +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size); +int sst_request_firmware_async(struct intel_sst_drv *ctx); +int sst_driver_ops(struct intel_sst_drv *sst); +struct sst_platform_info *sst_get_acpi_driver_data(const char *hid); +void sst_firmware_load_cb(const struct firmware *fw, void *context); +int sst_prepare_and_post_msg(struct intel_sst_drv *sst, + int task_id, int ipc_msg, int cmd_id, int pipe_id, + size_t mbox_data_len, const void *mbox_data, void **data, + bool large, bool fill_dsp, bool sync, bool response); + +void sst_process_pending_msg(struct work_struct *work); +int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx); +void sst_init_stream(struct stream_info *stream, + int codec, int sst_id, int ops, u8 slot); +int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id); +struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx, + int str_id); +int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, + u32 pipe_id); +u32 relocate_imr_addr_mrfld(u32 base_addr); +void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg); +int sst_pm_runtime_put(struct intel_sst_drv *sst_drv); +int sst_shim_write(void __iomem *addr, int offset, int value); +u32 sst_shim_read(void __iomem *addr, int offset); +u64 sst_reg_read64(void __iomem *addr, int offset); +int sst_shim_write64(void __iomem *addr, int offset, u64 value); +u64 sst_shim_read64(void __iomem *addr, int offset); +void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state); +void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id); +void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len); + +int sst_register(struct device *); +int sst_unregister(struct device *); + +int sst_alloc_drv_context(struct intel_sst_drv **ctx, + struct device *dev, unsigned int dev_id); +int sst_context_init(struct intel_sst_drv *ctx); +void sst_context_cleanup(struct intel_sst_drv *ctx); +void sst_configure_runtime_pm(struct intel_sst_drv *ctx); +void memcpy32_toio(void __iomem *dst, const void *src, int count); +void memcpy32_fromio(void *dst, const void __iomem *src, int count); + +#endif diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c new file mode 100644 index 0000000..678f36e --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -0,0 +1,384 @@ +/* + * sst_acpi.c - SST (LPE) driver init file for ACPI enumeration. + * + * Copyright (c) 2013, Intel Corporation. + * + * Authors: Ramesh Babu K V + * Authors: Omair Mohammed Abdullah + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "../../common/sst-dsp.h" +#include "sst.h" + +struct sst_machines { + char *codec_id; + char board[32]; + char machine[32]; + void (*machine_quirk)(void); + char firmware[FW_NAME_SIZE]; + struct sst_platform_info *pdata; + +}; + +/* LPE viewpoint addresses */ +#define SST_BYT_IRAM_PHY_START 0xff2c0000 +#define SST_BYT_IRAM_PHY_END 0xff2d4000 +#define SST_BYT_DRAM_PHY_START 0xff300000 +#define SST_BYT_DRAM_PHY_END 0xff320000 +#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */ +#define SST_BYT_IMR_VIRT_END 0xc01fffff +#define SST_BYT_SHIM_PHY_ADDR 0xff340000 +#define SST_BYT_MBOX_PHY_ADDR 0xff344000 +#define SST_BYT_DMA0_PHY_ADDR 0xff298000 +#define SST_BYT_DMA1_PHY_ADDR 0xff29c000 +#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000 +#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000 + +#define BYT_FW_MOD_TABLE_OFFSET 0x80000 +#define BYT_FW_MOD_TABLE_SIZE 0x100 +#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE) + +static const struct sst_info byt_fwparse_info = { + .use_elf = false, + .max_streams = 25, + .iram_start = SST_BYT_IRAM_PHY_START, + .iram_end = SST_BYT_IRAM_PHY_END, + .iram_use = true, + .dram_start = SST_BYT_DRAM_PHY_START, + .dram_end = SST_BYT_DRAM_PHY_END, + .dram_use = true, + .imr_start = SST_BYT_IMR_VIRT_START, + .imr_end = SST_BYT_IMR_VIRT_END, + .imr_use = true, + .mailbox_start = SST_BYT_MBOX_PHY_ADDR, + .num_probes = 0, + .lpe_viewpt_rqd = true, +}; + +static const struct sst_ipc_info byt_ipc_info = { + .ipc_offset = 0, + .mbox_recv_off = 0x400, +}; + +static const struct sst_lib_dnld_info byt_lib_dnld_info = { + .mod_base = SST_BYT_IMR_VIRT_START, + .mod_end = SST_BYT_IMR_VIRT_END, + .mod_table_offset = BYT_FW_MOD_TABLE_OFFSET, + .mod_table_size = BYT_FW_MOD_TABLE_SIZE, + .mod_ddr_dnld = false, +}; + +static const struct sst_res_info byt_rvp_res_info = { + .shim_offset = 0x140000, + .shim_size = 0x000100, + .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR, + .ssp0_offset = 0xa0000, + .ssp0_size = 0x1000, + .dma0_offset = 0x98000, + .dma0_size = 0x4000, + .dma1_offset = 0x9c000, + .dma1_size = 0x4000, + .iram_offset = 0x0c0000, + .iram_size = 0x14000, + .dram_offset = 0x100000, + .dram_size = 0x28000, + .mbox_offset = 0x144000, + .mbox_size = 0x1000, + .acpi_lpe_res_index = 0, + .acpi_ddr_index = 2, + .acpi_ipc_irq_index = 5, +}; + +static struct sst_platform_info byt_rvp_platform_data = { + .probe_data = &byt_fwparse_info, + .ipc_info = &byt_ipc_info, + .lib_info = &byt_lib_dnld_info, + .res_info = &byt_rvp_res_info, + .platform = "sst-mfld-platform", +}; + +/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail, + * so pdata is same as Baytrail. + */ +static struct sst_platform_info chv_platform_data = { + .probe_data = &byt_fwparse_info, + .ipc_info = &byt_ipc_info, + .lib_info = &byt_lib_dnld_info, + .res_info = &byt_rvp_res_info, + .platform = "sst-mfld-platform", +}; + +static int sst_platform_get_resources(struct intel_sst_drv *ctx) +{ + struct resource *rsrc; + struct platform_device *pdev = to_platform_device(ctx->dev); + + /* All ACPI resource request here */ + /* Get Shim addr */ + rsrc = platform_get_resource(pdev, IORESOURCE_MEM, + ctx->pdata->res_info->acpi_lpe_res_index); + if (!rsrc) { + dev_err(ctx->dev, "Invalid SHIM base from IFWI"); + return -EIO; + } + dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start, + (unsigned int)resource_size(rsrc)); + + ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset; + ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1; + dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base); + ctx->iram = devm_ioremap_nocache(ctx->dev, ctx->iram_base, + ctx->pdata->res_info->iram_size); + if (!ctx->iram) { + dev_err(ctx->dev, "unable to map IRAM"); + return -EIO; + } + + ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset; + ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1; + dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base); + ctx->dram = devm_ioremap_nocache(ctx->dev, ctx->dram_base, + ctx->pdata->res_info->dram_size); + if (!ctx->dram) { + dev_err(ctx->dev, "unable to map DRAM"); + return -EIO; + } + + ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset; + dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add); + ctx->shim = devm_ioremap_nocache(ctx->dev, ctx->shim_phy_add, + ctx->pdata->res_info->shim_size); + if (!ctx->shim) { + dev_err(ctx->dev, "unable to map SHIM"); + return -EIO; + } + + /* reassign physical address to LPE viewpoint address */ + ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr; + + /* Get mailbox addr */ + ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset; + dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add); + ctx->mailbox = devm_ioremap_nocache(ctx->dev, ctx->mailbox_add, + ctx->pdata->res_info->mbox_size); + if (!ctx->mailbox) { + dev_err(ctx->dev, "unable to map mailbox"); + return -EIO; + } + + /* reassign physical address to LPE viewpoint address */ + ctx->mailbox_add = ctx->info.mailbox_start; + + rsrc = platform_get_resource(pdev, IORESOURCE_MEM, + ctx->pdata->res_info->acpi_ddr_index); + if (!rsrc) { + dev_err(ctx->dev, "Invalid DDR base from IFWI"); + return -EIO; + } + ctx->ddr_base = rsrc->start; + ctx->ddr_end = rsrc->end; + dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base); + ctx->ddr = devm_ioremap_nocache(ctx->dev, ctx->ddr_base, + resource_size(rsrc)); + if (!ctx->ddr) { + dev_err(ctx->dev, "unable to map DDR"); + return -EIO; + } + + /* Find the IRQ */ + ctx->irq_num = platform_get_irq(pdev, + ctx->pdata->res_info->acpi_ipc_irq_index); + return 0; +} + +static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, + void *context, void **ret) +{ + *(bool *)context = true; + return AE_OK; +} + +static struct sst_machines *sst_acpi_find_machine( + struct sst_machines *machines) +{ + struct sst_machines *mach; + bool found = false; + + for (mach = machines; mach->codec_id; mach++) + if (ACPI_SUCCESS(acpi_get_devices(mach->codec_id, + sst_acpi_mach_match, + &found, NULL)) && found) + return mach; + + return NULL; +} + +static int sst_acpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0; + struct intel_sst_drv *ctx; + const struct acpi_device_id *id; + struct sst_machines *mach; + struct platform_device *mdev; + struct platform_device *plat_dev; + unsigned int dev_id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + dev_dbg(dev, "for %s", id->id); + + mach = (struct sst_machines *)id->driver_data; + mach = sst_acpi_find_machine(mach); + if (mach == NULL) { + dev_err(dev, "No matching machine driver found\n"); + return -ENODEV; + } + + ret = kstrtouint(id->id, 16, &dev_id); + if (ret < 0) { + dev_err(dev, "Unique device id conversion error: %d\n", ret); + return ret; + } + + dev_dbg(dev, "ACPI device id: %x\n", dev_id); + + plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0); + if (IS_ERR(plat_dev)) { + dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform); + return PTR_ERR(plat_dev); + } + + /* Create platform device for sst machine driver */ + mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0); + if (IS_ERR(mdev)) { + dev_err(dev, "Failed to create machine device: %s\n", mach->machine); + return PTR_ERR(mdev); + } + + ret = sst_alloc_drv_context(&ctx, dev, dev_id); + if (ret < 0) + return ret; + + /* Fill sst platform data */ + ctx->pdata = mach->pdata; + strcpy(ctx->firmware_name, mach->firmware); + + ret = sst_platform_get_resources(ctx); + if (ret) + return ret; + + ret = sst_context_init(ctx); + if (ret < 0) + return ret; + + /* need to save shim registers in BYT */ + ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64), + GFP_KERNEL); + if (!ctx->shim_regs64) { + return -ENOMEM; + goto do_sst_cleanup; + } + + sst_configure_runtime_pm(ctx); + platform_set_drvdata(pdev, ctx); + return ret; + +do_sst_cleanup: + sst_context_cleanup(ctx); + platform_set_drvdata(pdev, NULL); + dev_err(ctx->dev, "failed with %d\n", ret); + return ret; +} + +/** +* intel_sst_remove - remove function +* +* @pdev: platform device structure +* +* This function is called by OS when a device is unloaded +* This frees the interrupt etc +*/ +static int sst_acpi_remove(struct platform_device *pdev) +{ + struct intel_sst_drv *ctx; + + ctx = platform_get_drvdata(pdev); + sst_context_cleanup(ctx); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct sst_machines sst_acpi_bytcr[] = { + {"10EC5640", "T100", "bytt100_rt5640", NULL, "intel/fw_sst_0f28.bin", + &byt_rvp_platform_data }, + {}, +}; + +/* Cherryview-based platforms: CherryTrail and Braswell */ +static struct sst_machines sst_acpi_chv[] = { + {"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "intel/fw_sst_22a8.bin", + &chv_platform_data }, + {"10EC5645", "cht-bsw", "cht-bsw-rt5645", NULL, "intel/fw_sst_22a8.bin", + &chv_platform_data }, + {}, +}; + +static const struct acpi_device_id sst_acpi_ids[] = { + { "80860F28", (unsigned long)&sst_acpi_bytcr}, + { "808622A8", (unsigned long) &sst_acpi_chv}, + { }, +}; + +MODULE_DEVICE_TABLE(acpi, sst_acpi_ids); + +static struct platform_driver sst_acpi_driver = { + .driver = { + .name = "intel_sst_acpi", + .acpi_match_table = ACPI_PTR(sst_acpi_ids), + .pm = &intel_sst_pm, + }, + .probe = sst_acpi_probe, + .remove = sst_acpi_remove, +}; + +module_platform_driver(sst_acpi_driver); + +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver"); +MODULE_AUTHOR("Ramesh Babu K V"); +MODULE_AUTHOR("Omair Mohammed Abdullah"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/atom/sst/sst_drv_interface.c b/sound/soc/intel/atom/sst/sst_drv_interface.c new file mode 100644 index 0000000..718838b --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_drv_interface.c @@ -0,0 +1,741 @@ +/* + * sst_drv_interface.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + + + +#define NUM_CODEC 2 +#define MIN_FRAGMENT 2 +#define MAX_FRAGMENT 4 +#define MIN_FRAGMENT_SIZE (50 * 1024) +#define MAX_FRAGMENT_SIZE (1024 * 1024) +#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1) + +int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id) +{ + struct stream_info *stream; + int ret = 0; + + stream = get_stream_info(ctx, str_id); + if (stream) { + /* str_id is valid, so stream is alloacted */ + ret = sst_free_stream(ctx, str_id); + if (ret) + sst_clean_stream(&ctx->streams[str_id]); + return ret; + } else { + dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id); + } + return ret; +} + +int sst_get_stream_allocated(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld) +{ + int retval; + + retval = ctx->ops->alloc_stream(ctx, str_param); + if (retval > 0) + dev_dbg(ctx->dev, "Stream allocated %d\n", retval); + return retval; + +} + +/* + * sst_get_sfreq - this function returns the frequency of the stream + * + * @str_param : stream params + */ +int sst_get_sfreq(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.sfreq; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.externalsr; + case SST_CODEC_TYPE_MP3: + return 0; + default: + return -EINVAL; + } +} + +/* + * sst_get_num_channel - get number of channels for the stream + * + * @str_param : stream params + */ +int sst_get_num_channel(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.num_chan; + case SST_CODEC_TYPE_MP3: + return str_param->sparams.uc.mp3_params.num_chan; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.num_chan; + default: + return -EINVAL; + } +} + +/* + * sst_get_stream - this function prepares for stream allocation + * + * @str_param : stream param + */ +int sst_get_stream(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param) +{ + int retval; + struct stream_info *str_info; + + /* stream is not allocated, we are allocating */ + retval = ctx->ops->alloc_stream(ctx, str_param); + if (retval <= 0) { + return -EIO; + } + /* store sampling freq */ + str_info = &ctx->streams[retval]; + str_info->sfreq = sst_get_sfreq(str_param); + + return retval; +} + +static int sst_power_control(struct device *dev, bool state) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + int ret = 0; + int usage_count = 0; + +#ifdef CONFIG_PM + usage_count = atomic_read(&dev->power.usage_count); +#else + usage_count = 1; +#endif + + if (state == true) { + ret = pm_runtime_get_sync(dev); + + dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count); + if (ret < 0) { + dev_err(ctx->dev, "Runtime get failed with err: %d\n", ret); + return ret; + } + if ((ctx->sst_state == SST_RESET) && (usage_count == 1)) { + ret = sst_load_fw(ctx); + if (ret) { + dev_err(dev, "FW download fail %d\n", ret); + sst_set_fw_state_locked(ctx, SST_RESET); + ret = sst_pm_runtime_put(ctx); + } + } + } else { + dev_dbg(ctx->dev, "Disable: pm usage count: %d\n", usage_count); + return sst_pm_runtime_put(ctx); + } + return ret; +} + +/* + * sst_open_pcm_stream - Open PCM interface + * + * @str_param: parameters of pcm stream + * + * This function is called by MID sound card driver to open + * a new pcm interface + */ +static int sst_open_pcm_stream(struct device *dev, + struct snd_sst_params *str_param) +{ + int retval; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (!str_param) + return -EINVAL; + + retval = sst_get_stream(ctx, str_param); + if (retval > 0) + ctx->stream_cnt++; + else + dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval); + + return retval; +} + +static int sst_cdev_open(struct device *dev, + struct snd_sst_params *str_params, struct sst_compress_cb *cb) +{ + int str_id, retval; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + retval = pm_runtime_get_sync(ctx->dev); + if (retval < 0) + return retval; + + str_id = sst_get_stream(ctx, str_params); + if (str_id > 0) { + dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id); + stream = &ctx->streams[str_id]; + stream->compr_cb = cb->compr_cb; + stream->compr_cb_param = cb->param; + stream->drain_notify = cb->drain_notify; + stream->drain_cb_param = cb->drain_cb_param; + } else { + dev_err(dev, "stream encountered error during alloc %d\n", str_id); + str_id = -EINVAL; + sst_pm_runtime_put(ctx); + } + return str_id; +} + +static int sst_cdev_close(struct device *dev, unsigned int str_id) +{ + int retval; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) { + dev_err(dev, "stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + if (stream->status == STREAM_RESET) { + dev_dbg(dev, "stream in reset state...\n"); + stream->status = STREAM_UN_INIT; + + retval = 0; + goto put; + } + + retval = sst_free_stream(ctx, str_id); +put: + stream->compr_cb_param = NULL; + stream->compr_cb = NULL; + + if (retval) + dev_err(dev, "free stream returned err %d\n", retval); + + dev_dbg(dev, "End\n"); + return retval; + +} + +static int sst_cdev_ack(struct device *dev, unsigned int str_id, + unsigned long bytes) +{ + struct stream_info *stream; + struct snd_sst_tstamp fw_tstamp = {0,}; + int offset; + void __iomem *addr; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + /* update bytes sent */ + stream->cumm_bytes += bytes; + dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes); + + memcpy_fromio(&fw_tstamp, + ((void *)(ctx->mailbox + ctx->tstamp) + +(str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + + fw_tstamp.bytes_copied = stream->cumm_bytes; + dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n", + fw_tstamp.bytes_copied, bytes); + + addr = ((void *)(ctx->mailbox + ctx->tstamp)) + + (str_id * sizeof(fw_tstamp)); + offset = offsetof(struct snd_sst_tstamp, bytes_copied); + sst_shim_write(addr, offset, fw_tstamp.bytes_copied); + return 0; +} + +static int sst_cdev_set_metadata(struct device *dev, + unsigned int str_id, struct snd_compr_metadata *metadata) +{ + int retval = 0; + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + dev_dbg(dev, "set metadata for stream %d\n", str_id); + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + + dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id); + retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD, + IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id, + sizeof(*metadata), metadata, NULL, + true, true, true, false); + + return retval; +} + +static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_pause_stream(ctx, str_id); +} + +static int sst_cdev_stream_pause_release(struct device *dev, + unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_resume_stream(ctx, str_id); +} + +static int sst_cdev_stream_start(struct device *dev, unsigned int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + return sst_start_stream(ctx, str_id); +} + +static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drop_stream(ctx, str_id); +} + +static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drain_stream(ctx, str_id, false); +} + +static int sst_cdev_stream_partial_drain(struct device *dev, + unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drain_stream(ctx, str_id, true); +} + +static int sst_cdev_tstamp(struct device *dev, unsigned int str_id, + struct snd_compr_tstamp *tstamp) +{ + struct snd_sst_tstamp fw_tstamp = {0,}; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + memcpy_fromio(&fw_tstamp, + ((void *)(ctx->mailbox + ctx->tstamp) + +(str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter); + + tstamp->copied_total = fw_tstamp.ring_buffer_counter; + tstamp->pcm_frames = fw_tstamp.frames_decoded; + tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter, + (u64)((stream->num_ch) * SST_GET_BYTES_PER_SAMPLE(24))); + tstamp->sampling_rate = fw_tstamp.sampling_frequency; + + dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames); + dev_dbg(dev, "Ptr Query on strid = %d copied_total %d, decodec %d\n", + str_id, tstamp->copied_total, tstamp->pcm_frames); + dev_dbg(dev, "rendered %d\n", tstamp->pcm_io_frames); + + return 0; +} + +static int sst_cdev_caps(struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + return 0; +} + +static struct snd_compr_codec_caps caps_mp3 = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static struct snd_compr_codec_caps caps_aac = { + .num_descriptors = 2, + .descriptor[1].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[1].bit_rate[0] = 320, + .descriptor[1].bit_rate[1] = 192, + .descriptor[1].num_bitrates = 2, + .descriptor[1].profiles = 0, + .descriptor[1].modes = 0, + .descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | + SND_AUDIOSTREAMFORMAT_RAW), +}; + +static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_MP3) + *codec = caps_mp3; + else if (codec->codec == SND_AUDIOCODEC_AAC) + *codec = caps_aac; + else + return -EINVAL; + + return 0; +} + +void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id) +{ + struct stream_info *stream; + + dev_dbg(ctx->dev, "fragment elapsed from firmware for str_id %d\n", + str_id); + stream = &ctx->streams[str_id]; + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); +} + +/* + * sst_close_pcm_stream - Close PCM interface + * + * @str_id: stream id to be closed + * + * This function is called by MID sound card driver to close + * an existing pcm interface + */ +static int sst_close_pcm_stream(struct device *dev, unsigned int str_id) +{ + struct stream_info *stream; + int retval = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) { + dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + if (stream->status == STREAM_RESET) { + /* silently fail here as we have cleaned the stream earlier */ + dev_dbg(ctx->dev, "stream in reset state...\n"); + + retval = 0; + goto put; + } + + retval = free_stream_context(ctx, str_id); +put: + stream->pcm_substream = NULL; + stream->status = STREAM_UN_INIT; + stream->period_elapsed = NULL; + ctx->stream_cnt--; + + if (retval) + dev_err(ctx->dev, "free stream returned err %d\n", retval); + + dev_dbg(ctx->dev, "Exit\n"); + return 0; +} + +static inline int sst_calc_tstamp(struct intel_sst_drv *ctx, + struct pcm_stream_info *info, + struct snd_pcm_substream *substream, + struct snd_sst_tstamp *fw_tstamp) +{ + size_t delay_bytes, delay_frames; + size_t buffer_sz; + u32 pointer_bytes, pointer_samples; + + dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n", + fw_tstamp->ring_buffer_counter); + dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n", + fw_tstamp->hardware_counter); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter - + fw_tstamp->hardware_counter); + else + delay_bytes = (size_t) (fw_tstamp->hardware_counter - + fw_tstamp->ring_buffer_counter); + delay_frames = bytes_to_frames(substream->runtime, delay_bytes); + buffer_sz = snd_pcm_lib_buffer_bytes(substream); + div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes); + pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes); + + dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes); + + info->buffer_ptr = pointer_samples / substream->runtime->channels; + + info->pcm_delay = delay_frames / substream->runtime->channels; + dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n", + info->buffer_ptr, info->pcm_delay); + return 0; +} + +static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info) +{ + struct stream_info *stream; + struct snd_pcm_substream *substream; + struct snd_sst_tstamp fw_tstamp; + unsigned int str_id; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_id = info->str_id; + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + if (!stream->pcm_substream) + return -EINVAL; + substream = stream->pcm_substream; + + memcpy_fromio(&fw_tstamp, + ((void *)(ctx->mailbox + ctx->tstamp) + + (str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + return sst_calc_tstamp(ctx, info, substream, &fw_tstamp); +} + +static int sst_stream_start(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + sst_start_stream(ctx, str_id); + + return 0; +} + +static int sst_stream_drop(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + return sst_drop_stream(ctx, str_id); +} + +static int sst_stream_pause(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + + return sst_pause_stream(ctx, str_id); +} + +static int sst_stream_resume(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + return sst_resume_stream(ctx, str_id); +} + +static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info) +{ + int str_id = 0; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_id = str_info->str_id; + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + dev_dbg(ctx->dev, "setting the period ptrs\n"); + stream->pcm_substream = str_info->arg; + stream->period_elapsed = str_info->period_elapsed; + stream->sfreq = str_info->sfreq; + stream->prev = stream->status; + stream->status = STREAM_INIT; + dev_dbg(ctx->dev, + "pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n", + stream->pcm_substream, stream->period_elapsed, + stream->sfreq, stream->status); + + return 0; +} + +/* + * sst_set_byte_stream - Set generic params + * + * @cmd: control cmd to be set + * @arg: command argument + * + * This function is called by MID sound card driver to configure + * SST runtime params. + */ +static int sst_send_byte_stream(struct device *dev, + struct snd_sst_bytes_v2 *bytes) +{ + int ret_val = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (NULL == bytes) + return -EINVAL; + ret_val = pm_runtime_get_sync(ctx->dev); + if (ret_val < 0) + return ret_val; + + ret_val = sst_send_byte_stream_mrfld(ctx, bytes); + sst_pm_runtime_put(ctx); + + return ret_val; +} + +static struct sst_ops pcm_ops = { + .open = sst_open_pcm_stream, + .stream_init = sst_stream_init, + .stream_start = sst_stream_start, + .stream_drop = sst_stream_drop, + .stream_pause = sst_stream_pause, + .stream_pause_release = sst_stream_resume, + .stream_read_tstamp = sst_read_timestamp, + .send_byte_stream = sst_send_byte_stream, + .close = sst_close_pcm_stream, + .power = sst_power_control, +}; + +static struct compress_sst_ops compr_ops = { + .open = sst_cdev_open, + .close = sst_cdev_close, + .stream_pause = sst_cdev_stream_pause, + .stream_pause_release = sst_cdev_stream_pause_release, + .stream_start = sst_cdev_stream_start, + .stream_drop = sst_cdev_stream_drop, + .stream_drain = sst_cdev_stream_drain, + .stream_partial_drain = sst_cdev_stream_partial_drain, + .tstamp = sst_cdev_tstamp, + .ack = sst_cdev_ack, + .get_caps = sst_cdev_caps, + .get_codec_caps = sst_cdev_codec_caps, + .set_metadata = sst_cdev_set_metadata, + .power = sst_power_control, +}; + +static struct sst_device sst_dsp_device = { + .name = "Intel(R) SST LPE", + .dev = NULL, + .ops = &pcm_ops, + .compr_ops = &compr_ops, +}; + +/* + * sst_register - function to register DSP + * + * This functions registers DSP with the platform driver + */ +int sst_register(struct device *dev) +{ + int ret_val; + + sst_dsp_device.dev = dev; + ret_val = sst_register_dsp(&sst_dsp_device); + if (ret_val) + dev_err(dev, "Unable to register DSP with platform driver\n"); + + return ret_val; +} + +int sst_unregister(struct device *dev) +{ + return sst_unregister_dsp(&sst_dsp_device); +} diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c new file mode 100644 index 0000000..5a27861 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_ipc.c @@ -0,0 +1,373 @@ +/* + * sst_ipc.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id) +{ + struct sst_block *msg = NULL; + + dev_dbg(ctx->dev, "Enter\n"); + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return NULL; + msg->condition = false; + msg->on = true; + msg->msg_id = msg_id; + msg->drv_id = drv_id; + spin_lock_bh(&ctx->block_lock); + list_add_tail(&msg->node, &ctx->block_list); + spin_unlock_bh(&ctx->block_lock); + + return msg; +} + +/* + * while handling the interrupts, we need to check for message status and + * then if we are blocking for a message + * + * here we are unblocking the blocked ones, this is based on id we have + * passed and search that for block threads. + * We will not find block in two cases + * a) when its small message and block in not there, so silently ignore + * them + * b) when we are actually not able to find the block (bug perhaps) + * + * Since we have bit of small messages we can spam kernel log with err + * print on above so need to keep as debug prints which should be enabled + * via dynamic debug while debugging IPC issues + */ +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size) +{ + struct sst_block *block = NULL; + + dev_dbg(ctx->dev, "Enter\n"); + + spin_lock_bh(&ctx->block_lock); + list_for_each_entry(block, &ctx->block_list, node) { + dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id, + block->drv_id); + if (block->msg_id == ipc && block->drv_id == drv_id) { + dev_dbg(ctx->dev, "free up the block\n"); + block->ret_code = result; + block->data = data; + block->size = size; + block->condition = true; + spin_unlock_bh(&ctx->block_lock); + wake_up(&ctx->wait_queue); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + dev_dbg(ctx->dev, + "Block not found or a response received for a short msg for ipc %d, drv_id %d\n", + ipc, drv_id); + return -EINVAL; +} + +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) +{ + struct sst_block *block = NULL, *__block; + + dev_dbg(ctx->dev, "Enter\n"); + spin_lock_bh(&ctx->block_lock); + list_for_each_entry_safe(block, __block, &ctx->block_list, node) { + if (block == freed) { + pr_debug("pvt_id freed --> %d\n", freed->drv_id); + /* toggle the index position of pvt_id */ + list_del(&freed->node); + spin_unlock_bh(&ctx->block_lock); + kfree(freed->data); + freed->data = NULL; + kfree(freed); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + dev_err(ctx->dev, "block is already freed!!!\n"); + return -EINVAL; +} + +int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *ipc_msg, bool sync) +{ + struct ipc_post *msg = ipc_msg; + union ipc_header_mrfld header; + unsigned int loop_count = 0; + int retval = 0; + unsigned long irq_flags; + + dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync); + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + if (sync) { + while (header.p.header_high.part.busy) { + if (loop_count > 25) { + dev_err(sst_drv_ctx->dev, + "sst: Busy wait failed, cant send this msg\n"); + retval = -EBUSY; + goto out; + } + cpu_relax(); + loop_count++; + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + } + } else { + if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { + /* queue is empty, nothing to send */ + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + dev_dbg(sst_drv_ctx->dev, + "Empty msg queue... NO Action\n"); + return 0; + } + + if (header.p.header_high.part.busy) { + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n"); + return 0; + } + + /* copy msg from list */ + msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, + struct ipc_post, node); + list_del(&msg->node); + } + dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n", + msg->mrfld_header.p.header_high.full); + dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n", + msg->mrfld_header.p.header_low_payload); + + if (msg->mrfld_header.p.header_high.part.large) + memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, + msg->mailbox_data, + msg->mrfld_header.p.header_low_payload); + + sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); + +out: + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + kfree(msg->mailbox_data); + kfree(msg); + return retval; +} + +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; + unsigned long irq_flags; + + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + + /* write 1 to clear*/ + isr.part.busy_interrupt = 1; + sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); + + /* Set IA done bit */ + clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); + + clear_ipc.p.header_high.part.busy = 0; + clear_ipc.p.header_high.part.done = 1; + clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; + sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); +} + + +/* + * process_fw_init - process the FW init msg + * + * @msg: IPC message mailbox data from FW + * + * This function processes the FW init msg from FW + * marks FW state and prints debug info of loaded FW + */ +static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, + void *msg) +{ + struct ipc_header_fw_init *init = + (struct ipc_header_fw_init *)msg; + int retval = 0; + + dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n"); + if (init->result) { + sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); + dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n", + init->result); + retval = init->result; + goto ret; + } + +ret: + sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); +} + +static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *msg) +{ + u32 msg_id; + int str_id; + u32 data_size, i; + void *data_offset; + struct stream_info *stream; + union ipc_header_high msg_high; + u32 msg_low, pipe_id; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; + data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); + data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); + + switch (msg_id) { + case IPC_SST_PERIOD_ELAPSED_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) { + dev_dbg(sst_drv_ctx->dev, + "Period elapsed rcvd for pipe id 0x%x\n", + pipe_id); + stream = &sst_drv_ctx->streams[str_id]; + if (stream->period_elapsed) + stream->period_elapsed(stream->pcm_substream); + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); + } + break; + + case IPC_IA_DRAIN_STREAM_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) { + stream = &sst_drv_ctx->streams[str_id]; + if (stream->drain_notify) + stream->drain_notify(stream->drain_cb_param); + } + break; + + case IPC_IA_FW_ASYNC_ERR_MRFLD: + dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n"); + for (i = 0; i < (data_size/4); i++) + print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, + 16, 4, data_offset, data_size, false); + break; + + case IPC_IA_FW_INIT_CMPLT_MRFLD: + process_fw_init(sst_drv_ctx, data_offset); + break; + + case IPC_IA_BUF_UNDER_RUN_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) + dev_err(sst_drv_ctx->dev, + "Buffer under-run for pipe:%#x str_id:%d\n", + pipe_id, str_id); + break; + + default: + dev_err(sst_drv_ctx->dev, + "Unrecognized async msg from FW msg_id %#x\n", msg_id); + } +} + +void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *msg) +{ + unsigned int drv_id; + void *data; + union ipc_header_high msg_high; + u32 msg_low; + struct ipc_dsp_hdr *dsp_hdr; + unsigned int cmd_id; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + + dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n", + msg->mrfld_header.p.header_high.full, + msg->mrfld_header.p.header_low_payload); + + drv_id = msg_high.part.drv_id; + + /* Check for async messages first */ + if (drv_id == SST_ASYNC_DRV_ID) { + /*FW sent async large message*/ + process_fw_async_msg(sst_drv_ctx, msg); + return; + } + + /* FW sent short error response for an IPC */ + if (msg_high.part.result && drv_id && !msg_high.part.large) { + /* 32-bit FW error code in msg_low */ + dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + return; + } + + /* + * Process all valid responses + * if it is a large message, the payload contains the size to + * copy from mailbox + **/ + if (msg_high.part.large) { + data = kzalloc(msg_low, GFP_KERNEL); + if (!data) + return; + memcpy(data, (void *) msg->mailbox_data, msg_low); + /* Copy command id so that we can use to put sst to reset */ + dsp_hdr = (struct ipc_dsp_hdr *)data; + cmd_id = dsp_hdr->cmd_id; + dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id); + if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, data, msg_low)) + kfree(data); + } else { + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + } + +} diff --git a/sound/soc/intel/atom/sst/sst_loader.c b/sound/soc/intel/atom/sst/sst_loader.c new file mode 100644 index 0000000..3391714 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_loader.c @@ -0,0 +1,463 @@ +/* + * sst_dsp.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file contains all dsp controlling functions like firmware download, + * setting/resetting dsp cores, etc + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +void memcpy32_toio(void __iomem *dst, const void *src, int count) +{ + /* __iowrite32_copy uses 32-bit count values so divide by 4 for + * right count in words + */ + __iowrite32_copy(dst, src, count/4); +} + +void memcpy32_fromio(void *dst, const void __iomem *src, int count) +{ + /* __iowrite32_copy uses 32-bit count values so divide by 4 for + * right count in words + */ + __iowrite32_copy(dst, src, count/4); +} + +/** + * intel_sst_reset_dsp_mrfld - Resetting SST DSP + * + * This resets DSP in case of MRFLD platfroms + */ +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union config_status_reg_mrfld csr; + + dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full &= ~(0x1); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + return 0; +} + +/** + * sst_start_merrifield - Start the SST DSP processor + * + * This starts the DSP in MERRIFIELD platfroms + */ +int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union config_status_reg_mrfld csr; + + dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.part.xt_snoop = 1; + csr.full &= ~(0x5); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n", + csr.full); + return 0; +} + +static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size, + struct fw_module_header **module, u32 *num_modules) +{ + struct sst_fw_header *header; + const void *sst_fw_in_mem = ctx->fw_in_mem; + + dev_dbg(ctx->dev, "Enter\n"); + + /* Read the header information from the data pointer */ + header = (struct sst_fw_header *)sst_fw_in_mem; + dev_dbg(ctx->dev, + "header sign=%s size=%x modules=%x fmt=%x size=%zx\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* verify FW */ + if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) || + (size != header->file_size + sizeof(*header))) { + /* Invalid FW signature */ + dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n"); + return -EINVAL; + } + *num_modules = header->modules; + *module = (void *)sst_fw_in_mem + sizeof(*header); + + return 0; +} + +/* + * sst_fill_memcpy_list - Fill the memcpy list + * + * @memcpy_list: List to be filled + * @destn: Destination addr to be filled in the list + * @src: Source addr to be filled in the list + * @size: Size to be filled in the list + * + * Adds the node to the list after required fields + * are populated in the node + */ +static int sst_fill_memcpy_list(struct list_head *memcpy_list, + void *destn, const void *src, u32 size, bool is_io) +{ + struct sst_memcpy_list *listnode; + + listnode = kzalloc(sizeof(*listnode), GFP_KERNEL); + if (listnode == NULL) + return -ENOMEM; + listnode->dstn = destn; + listnode->src = src; + listnode->size = size; + listnode->is_io = is_io; + list_add_tail(&listnode->memcpylist, memcpy_list); + + return 0; +} + +/** + * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list + * + * @sst_drv_ctx : driver context + * @module : FW module header + * @memcpy_list : Pointer to the list to be populated + * Create the memcpy list as the number of block to be copied + * returns error or 0 if module sizes are proper + */ +static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx, + struct fw_module_header *module, struct list_head *memcpy_list) +{ + struct fw_block_info *block; + u32 count; + int ret_val = 0; + void __iomem *ram_iomem; + + dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n", + module->signature, module->mod_size, + module->blocks, module->type); + dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + if (block->size <= 0) { + dev_err(sst_drv_ctx->dev, "block size invalid\n"); + return -EINVAL; + } + switch (block->type) { + case SST_IRAM: + ram_iomem = sst_drv_ctx->iram; + break; + case SST_DRAM: + ram_iomem = sst_drv_ctx->dram; + break; + case SST_DDR: + ram_iomem = sst_drv_ctx->ddr; + break; + case SST_CUSTOM_INFO: + block = (void *)block + sizeof(*block) + block->size; + continue; + default: + dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n", + block->type, count); + return -EINVAL; + } + + ret_val = sst_fill_memcpy_list(memcpy_list, + ram_iomem + block->ram_offset, + (void *)block + sizeof(*block), block->size, 1); + if (ret_val) + return ret_val; + + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +/** + * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy + * + * @ctx : pointer to drv context + * @size : size of the firmware + * @fw_list : pointer to list_head to be populated + * This function parses the FW image and saves the parsed image in the list + * for memcpy + */ +static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size, + struct list_head *fw_list) +{ + struct fw_module_header *module; + u32 count, num_modules; + int ret_val; + + ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules); + if (ret_val) + return ret_val; + + for (count = 0; count < num_modules; count++) { + ret_val = sst_parse_module_memcpy(ctx, module, fw_list); + if (ret_val) + return ret_val; + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +/** + * sst_do_memcpy - function initiates the memcpy + * + * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated + * + * Triggers the memcpy + */ +static void sst_do_memcpy(struct list_head *memcpy_list) +{ + struct sst_memcpy_list *listnode; + + list_for_each_entry(listnode, memcpy_list, memcpylist) { + if (listnode->is_io == true) + memcpy32_toio((void __iomem *)listnode->dstn, + listnode->src, listnode->size); + else + memcpy(listnode->dstn, listnode->src, listnode->size); + } +} + +void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx) +{ + struct sst_memcpy_list *listnode, *tmplistnode; + + /* Free the list */ + if (!list_empty(&sst_drv_ctx->memcpy_list)) { + list_for_each_entry_safe(listnode, tmplistnode, + &sst_drv_ctx->memcpy_list, memcpylist) { + list_del(&listnode->memcpylist); + kfree(listnode); + } + } +} + +static int sst_cache_and_parse_fw(struct intel_sst_drv *sst, + const struct firmware *fw) +{ + int retval = 0; + + sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); + if (!sst->fw_in_mem) { + retval = -ENOMEM; + goto end_release; + } + dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem); + dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem)); + memcpy(sst->fw_in_mem, fw->data, fw->size); + retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list); + if (retval) { + dev_err(sst->dev, "Failed to parse fw\n"); + kfree(sst->fw_in_mem); + sst->fw_in_mem = NULL; + } + +end_release: + release_firmware(fw); + return retval; + +} + +void sst_firmware_load_cb(const struct firmware *fw, void *context) +{ + struct intel_sst_drv *ctx = context; + + dev_dbg(ctx->dev, "Enter\n"); + + if (fw == NULL) { + dev_err(ctx->dev, "request fw failed\n"); + return; + } + + mutex_lock(&ctx->sst_lock); + + if (ctx->sst_state != SST_RESET || + ctx->fw_in_mem != NULL) { + release_firmware(fw); + mutex_unlock(&ctx->sst_lock); + return; + } + + dev_dbg(ctx->dev, "Request Fw completed\n"); + sst_cache_and_parse_fw(ctx, fw); + mutex_unlock(&ctx->sst_lock); +} + +/* + * sst_request_fw - requests audio fw from kernel and saves a copy + * + * This function requests the SST FW from the kernel, parses it and + * saves a copy in the driver context + */ +static int sst_request_fw(struct intel_sst_drv *sst) +{ + int retval = 0; + const struct firmware *fw; + + retval = request_firmware(&fw, sst->firmware_name, sst->dev); + if (fw == NULL) { + dev_err(sst->dev, "fw is returning as null\n"); + return -EINVAL; + } + if (retval) { + dev_err(sst->dev, "request fw failed %d\n", retval); + return retval; + } + mutex_lock(&sst->sst_lock); + retval = sst_cache_and_parse_fw(sst, fw); + mutex_unlock(&sst->sst_lock); + + return retval; +} + +/* + * Writing the DDR physical base to DCCM offset + * so that FW can use it to setup TLB + */ +static void sst_dccm_config_write(void __iomem *dram_base, + unsigned int ddr_base) +{ + void __iomem *addr; + u32 bss_reset = 0; + + addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET); + memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32)); + bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT); + addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET); + memcpy32_toio(addr, &bss_reset, sizeof(u32)); + +} + +void sst_post_download_mrfld(struct intel_sst_drv *ctx) +{ + sst_dccm_config_write(ctx->dram, ctx->ddr_base); + dev_dbg(ctx->dev, "config written to DCCM\n"); +} + +/** + * sst_load_fw - function to load FW into DSP + * Transfers the FW to DSP using dma/memcpy + */ +int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) +{ + int ret_val = 0; + struct sst_block *block; + + dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n"); + + if (sst_drv_ctx->sst_state != SST_RESET || + sst_drv_ctx->sst_state == SST_SHUTDOWN) + return -EAGAIN; + + if (!sst_drv_ctx->fw_in_mem) { + dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n"); + ret_val = sst_request_fw(sst_drv_ctx); + if (ret_val) + return ret_val; + } + + BUG_ON(!sst_drv_ctx->fw_in_mem); + block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID); + if (block == NULL) + return -ENOMEM; + + /* Prevent C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, 0); + + sst_drv_ctx->sst_state = SST_FW_LOADING; + + ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx); + if (ret_val) + goto restore; + + sst_do_memcpy(&sst_drv_ctx->memcpy_list); + + /* Write the DRAM/DCCM config before enabling FW */ + if (sst_drv_ctx->ops->post_download) + sst_drv_ctx->ops->post_download(sst_drv_ctx); + + /* bring sst out of reset */ + ret_val = sst_drv_ctx->ops->start(sst_drv_ctx); + if (ret_val) + goto restore; + + ret_val = sst_wait_timeout(sst_drv_ctx, block); + if (ret_val) { + dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val); + /* FW download failed due to timeout */ + ret_val = -EBUSY; + + } + + +restore: + /* Re-enable Deeper C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); + sst_free_block(sst_drv_ctx, block); + dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n"); + + if (sst_drv_ctx->ops->restore_dsp_context) + sst_drv_ctx->ops->restore_dsp_context(); + sst_drv_ctx->sst_state = SST_FW_RUNNING; + return ret_val; +} + diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c new file mode 100644 index 0000000..3a0b3bf --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_pci.c @@ -0,0 +1,209 @@ +/* + * sst_pci.c - SST (LPE) driver init file for pci enumeration. + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" + +static int sst_platform_get_resources(struct intel_sst_drv *ctx) +{ + int ddr_base, ret = 0; + struct pci_dev *pci = ctx->pci; + + ret = pci_request_regions(pci, SST_DRV_NAME); + if (ret) + return ret; + + /* map registers */ + /* DDR base */ + if (ctx->dev_id == SST_MRFLD_PCI_ID) { + ctx->ddr_base = pci_resource_start(pci, 0); + /* check that the relocated IMR base matches with FW Binary */ + ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base); + if (!ctx->pdata->lib_info) { + dev_err(ctx->dev, "lib_info pointer NULL\n"); + ret = -EINVAL; + goto do_release_regions; + } + if (ddr_base != ctx->pdata->lib_info->mod_base) { + dev_err(ctx->dev, + "FW LSP DDR BASE does not match with IFWI\n"); + ret = -EINVAL; + goto do_release_regions; + } + ctx->ddr_end = pci_resource_end(pci, 0); + + ctx->ddr = pcim_iomap(pci, 0, + pci_resource_len(pci, 0)); + if (!ctx->ddr) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr); + } else { + ctx->ddr = NULL; + } + /* SHIM */ + ctx->shim_phy_add = pci_resource_start(pci, 1); + ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1)); + if (!ctx->shim) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim); + + /* Shared SRAM */ + ctx->mailbox_add = pci_resource_start(pci, 2); + ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2)); + if (!ctx->mailbox) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox); + + /* IRAM */ + ctx->iram_end = pci_resource_end(pci, 3); + ctx->iram_base = pci_resource_start(pci, 3); + ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3)); + if (!ctx->iram) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram); + + /* DRAM */ + ctx->dram_end = pci_resource_end(pci, 4); + ctx->dram_base = pci_resource_start(pci, 4); + ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4)); + if (!ctx->dram) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram); +do_release_regions: + pci_release_regions(pci); + return 0; +} + +/* + * intel_sst_probe - PCI probe function + * + * @pci: PCI device structure + * @pci_id: PCI device ID structure + * + */ +static int intel_sst_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + int ret = 0; + struct intel_sst_drv *sst_drv_ctx; + struct sst_platform_info *sst_pdata = pci->dev.platform_data; + + dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device); + ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device); + if (ret < 0) + return ret; + + sst_drv_ctx->pdata = sst_pdata; + sst_drv_ctx->irq_num = pci->irq; + snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name), + "%s%04x%s", "fw_sst_", + sst_drv_ctx->dev_id, ".bin"); + + ret = sst_context_init(sst_drv_ctx); + if (ret < 0) + return ret; + + /* Init the device */ + ret = pcim_enable_device(pci); + if (ret) { + dev_err(sst_drv_ctx->dev, + "device can't be enabled. Returned err: %d\n", ret); + goto do_free_drv_ctx; + } + sst_drv_ctx->pci = pci_dev_get(pci); + ret = sst_platform_get_resources(sst_drv_ctx); + if (ret < 0) + goto do_free_drv_ctx; + + pci_set_drvdata(pci, sst_drv_ctx); + sst_configure_runtime_pm(sst_drv_ctx); + + return ret; + +do_free_drv_ctx: + sst_context_cleanup(sst_drv_ctx); + dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret); + return ret; +} + +/** + * intel_sst_remove - PCI remove function + * + * @pci: PCI device structure + * + * This function is called by OS when a device is unloaded + * This frees the interrupt etc + */ +static void intel_sst_remove(struct pci_dev *pci) +{ + struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci); + + sst_context_cleanup(sst_drv_ctx); + pci_dev_put(sst_drv_ctx->pci); + pci_release_regions(pci); + pci_set_drvdata(pci, NULL); +} + +/* PCI Routines */ +static struct pci_device_id intel_sst_ids[] = { + { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, + { 0, } +}; + +static struct pci_driver sst_driver = { + .name = SST_DRV_NAME, + .id_table = intel_sst_ids, + .probe = intel_sst_probe, + .remove = intel_sst_remove, +#ifdef CONFIG_PM + .driver = { + .pm = &intel_sst_pm, + }, +#endif +}; + +module_pci_driver(sst_driver); + +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver"); +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_AUTHOR("Dharageswari R "); +MODULE_AUTHOR("KP Jeeja "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c new file mode 100644 index 0000000..3c17844 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_pvt.c @@ -0,0 +1,449 @@ +/* + * sst_pvt.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +int sst_shim_write(void __iomem *addr, int offset, int value) +{ + writel(value, addr + offset); + return 0; +} + +u32 sst_shim_read(void __iomem *addr, int offset) +{ + return readl(addr + offset); +} + +u64 sst_reg_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + + return val; +} + +int sst_shim_write64(void __iomem *addr, int offset, u64 value) +{ + memcpy_toio(addr + offset, &value, sizeof(value)); + return 0; +} + +u64 sst_shim_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + return val; +} + +void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) +{ + mutex_lock(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; + mutex_unlock(&sst_drv_ctx->sst_lock); +} + +/* + * sst_wait_interruptible - wait on event + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits without a timeout (and is interruptable) for a + * given block event + */ +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block) +{ + int retval = 0; + + if (!wait_event_interruptible(sst_drv_ctx->wait_queue, + block->condition)) { + /* event wake */ + if (block->ret_code < 0) { + dev_err(sst_drv_ctx->dev, + "stream failed %d\n", block->ret_code); + retval = -EBUSY; + } else { + dev_dbg(sst_drv_ctx->dev, "event up\n"); + retval = 0; + } + } else { + dev_err(sst_drv_ctx->dev, "signal interrupted\n"); + retval = -EINTR; + } + return retval; + +} + +unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) +{ + unsigned long long val = 0; + + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + val = sst_shim_read64(sst->shim, addr); + break; + } + return val; +} + +void write_shim_data(struct intel_sst_drv *sst, int addr, + unsigned long long data) +{ + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + sst_shim_write64(sst->shim, addr, (u64) data); + break; + } +} + +/* + * sst_wait_timeout - wait on event for timeout + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits with a timeout value (and is not interruptible) on a + * given block event + */ +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block) +{ + int retval = 0; + + /* + * NOTE: + * Observed that FW processes the alloc msg and replies even + * before the alloc thread has finished execution + */ + dev_dbg(sst_drv_ctx->dev, + "waiting for condition %x ipc %d drv_id %d\n", + block->condition, block->msg_id, block->drv_id); + if (wait_event_timeout(sst_drv_ctx->wait_queue, + block->condition, + msecs_to_jiffies(SST_BLOCK_TIMEOUT))) { + /* event wake */ + dev_dbg(sst_drv_ctx->dev, "Event wake %x\n", + block->condition); + dev_dbg(sst_drv_ctx->dev, "message ret: %d\n", + block->ret_code); + retval = -block->ret_code; + } else { + block->on = false; + dev_err(sst_drv_ctx->dev, + "Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n", + block->condition, block->msg_id, sst_drv_ctx->sst_state); + sst_drv_ctx->sst_state = SST_RESET; + + retval = -EBUSY; + } + return retval; +} + +/* + * sst_create_ipc_msg - create a IPC message + * + * @arg: ipc message + * @large: large or short message + * + * this function allocates structures to send a large or short + * message to the firmware + */ +int sst_create_ipc_msg(struct ipc_post **arg, bool large) +{ + struct ipc_post *msg; + + msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + if (large) { + msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC); + if (!msg->mailbox_data) { + kfree(msg); + return -ENOMEM; + } + } else { + msg->mailbox_data = NULL; + } + msg->is_large = large; + *arg = msg; + return 0; +} + +/* + * sst_create_block_and_ipc_msg - Creates IPC message and sst block + * @arg: passed to sst_create_ipc_message API + * @large: large or short message + * @sst_drv_ctx: sst driver context + * @block: return block allocated + * @msg_id: IPC + * @drv_id: stream id or private id + */ +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id) +{ + int retval = 0; + + retval = sst_create_ipc_msg(arg, large); + if (retval) + return retval; + *block = sst_create_block(sst_drv_ctx, msg_id, drv_id); + if (*block == NULL) { + kfree(*arg); + return -ENOMEM; + } + return retval; +} + +/* + * sst_clean_stream - clean the stream context + * + * @stream: stream structure + * + * this function resets the stream contexts + * should be called in free + */ +void sst_clean_stream(struct stream_info *stream) +{ + stream->status = STREAM_UN_INIT; + stream->prev = STREAM_UN_INIT; + mutex_lock(&stream->lock); + stream->cumm_bytes = 0; + mutex_unlock(&stream->lock); +} + +int sst_prepare_and_post_msg(struct intel_sst_drv *sst, + int task_id, int ipc_msg, int cmd_id, int pipe_id, + size_t mbox_data_len, const void *mbox_data, void **data, + bool large, bool fill_dsp, bool sync, bool response) +{ + struct ipc_post *msg = NULL; + struct ipc_dsp_hdr dsp_hdr; + struct sst_block *block; + int ret = 0, pvt_id; + + pvt_id = sst_assign_pvt_id(sst); + if (pvt_id < 0) + return pvt_id; + + if (response) + ret = sst_create_block_and_ipc_msg( + &msg, large, sst, &block, ipc_msg, pvt_id); + else + ret = sst_create_ipc_msg(&msg, large); + + if (ret < 0) { + test_and_clear_bit(pvt_id, &sst->pvt_id); + return -ENOMEM; + } + + dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n", + pvt_id, pipe_id, task_id, ipc_msg); + sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg, + task_id, large, pvt_id); + msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len; + msg->mrfld_header.p.header_high.part.res_rqd = !sync; + dev_dbg(sst->dev, "header:%x\n", + msg->mrfld_header.p.header_high.full); + dev_dbg(sst->dev, "response rqd: %x", + msg->mrfld_header.p.header_high.part.res_rqd); + dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d", + msg->mrfld_header.p.header_low_payload); + if (fill_dsp) { + sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + if (mbox_data_len) { + memcpy(msg->mailbox_data + sizeof(dsp_hdr), + mbox_data, mbox_data_len); + } + } + + if (sync) + sst->ops->post_message(sst, msg, true); + else + sst_add_to_dispatch_list_and_post(sst, msg); + + if (response) { + ret = sst_wait_timeout(sst, block); + if (ret < 0) { + goto out; + } else if(block->data) { + if (!data) + goto out; + *data = kzalloc(block->size, GFP_KERNEL); + if (!(*data)) { + ret = -ENOMEM; + goto out; + } else + memcpy(data, (void *) block->data, block->size); + } + } +out: + if (response) + sst_free_block(sst, block); + test_and_clear_bit(pvt_id, &sst->pvt_id); + return ret; +} + +int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{ + int ret; + + pm_runtime_mark_last_busy(sst_drv->dev); + ret = pm_runtime_put_autosuspend(sst_drv->dev); + if (ret < 0) + return ret; + return 0; +} + +void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id) +{ + header->full = 0; + header->p.header_high.part.msg_id = msg; + header->p.header_high.part.task_id = task_id; + header->p.header_high.part.large = large; + header->p.header_high.part.drv_id = drv_id; + header->p.header_high.part.done = 0; + header->p.header_high.part.busy = 1; + header->p.header_high.part.res_rqd = 1; +} + +void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len) +{ + dsp->cmd_id = msg; + dsp->mod_index_id = 0xff; + dsp->pipe_id = pipe_id; + dsp->length = len; + dsp->mod_id = 0; +} + +#define SST_MAX_BLOCKS 15 +/* + * sst_assign_pvt_id - assign a pvt id for stream + * + * @sst_drv_ctx : driver context + * + * this function assigns a private id for calls that dont have stream + * context yet, should be called with lock held + * uses bits for the id, and finds first free bits and assigns that + */ +int sst_assign_pvt_id(struct intel_sst_drv *drv) +{ + int local; + + spin_lock(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ + spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); + spin_unlock(&drv->block_lock); + return local; +} + +void sst_init_stream(struct stream_info *stream, + int codec, int sst_id, int ops, u8 slot) +{ + stream->status = STREAM_INIT; + stream->prev = STREAM_UN_INIT; + stream->ops = ops; +} + +int sst_validate_strid( + struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) { + dev_err(sst_drv_ctx->dev, + "SST ERR: invalid stream id : %d, max %d\n", + str_id, sst_drv_ctx->info.max_streams); + return -EINVAL; + } + + return 0; +} + +struct stream_info *get_stream_info( + struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + if (sst_validate_strid(sst_drv_ctx, str_id)) + return NULL; + return &sst_drv_ctx->streams[str_id]; +} + +int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, + u32 pipe_id) +{ + int i; + + for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) + if (pipe_id == sst_drv_ctx->streams[i].pipe_id) + return i; + + dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id); + return -1; +} + +u32 relocate_imr_addr_mrfld(u32 base_addr) +{ + /* Get the difference from 512MB aligned base addr */ + /* relocate the base */ + base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024)); + return base_addr; +} +EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld); + +void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags); + list_add_tail(&msg->node, &sst->ipc_dispatch_list); + spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags); + sst->ops->post_message(sst, NULL, false); +} diff --git a/sound/soc/intel/atom/sst/sst_stream.c b/sound/soc/intel/atom/sst/sst_stream.c new file mode 100644 index 0000000..a74c64c --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_stream.c @@ -0,0 +1,437 @@ +/* + * sst_stream.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) +{ + struct snd_sst_alloc_mrfld alloc_param; + struct snd_sst_params *str_params; + struct snd_sst_tstamp fw_tstamp; + struct stream_info *str_info; + struct snd_sst_alloc_response *response; + unsigned int str_id, pipe_id, task_id; + int i, num_ch, ret = 0; + void *data = NULL; + + dev_dbg(sst_drv_ctx->dev, "Enter\n"); + BUG_ON(!params); + + str_params = (struct snd_sst_params *)params; + memset(&alloc_param, 0, sizeof(alloc_param)); + alloc_param.operation = str_params->ops; + alloc_param.codec_type = str_params->codec; + alloc_param.sg_count = str_params->aparams.sg_count; + alloc_param.ring_buf_info[0].addr = + str_params->aparams.ring_buf_info[0].addr; + alloc_param.ring_buf_info[0].size = + str_params->aparams.ring_buf_info[0].size; + alloc_param.frag_size = str_params->aparams.frag_size; + + memcpy(&alloc_param.codec_params, &str_params->sparams, + sizeof(struct snd_sst_stream_params)); + + /* + * fill channel map params for multichannel support. + * Ideally channel map should be received from upper layers + * for multichannel support. + * Currently hardcoding as per FW reqm. + */ + num_ch = sst_get_num_channel(str_params); + for (i = 0; i < 8; i++) { + if (i < num_ch) + alloc_param.codec_params.uc.pcm_params.channel_map[i] = i; + else + alloc_param.codec_params.uc.pcm_params.channel_map[i] = 0xFF; + } + + str_id = str_params->stream_id; + str_info = get_stream_info(sst_drv_ctx, str_id); + if (str_info == NULL) { + dev_err(sst_drv_ctx->dev, "get stream info returned null\n"); + return -EINVAL; + } + + pipe_id = str_params->device_type; + task_id = str_params->task; + sst_drv_ctx->streams[str_id].pipe_id = pipe_id; + sst_drv_ctx->streams[str_id].task_id = task_id; + sst_drv_ctx->streams[str_id].num_ch = num_ch; + + if (sst_drv_ctx->info.lpe_viewpt_rqd) + alloc_param.ts = sst_drv_ctx->info.mailbox_start + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + else + alloc_param.ts = sst_drv_ctx->mailbox_add + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + + dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n", + alloc_param.ts); + dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n", + pipe_id, task_id); + + /* allocate device type context */ + sst_init_stream(&sst_drv_ctx->streams[str_id], alloc_param.codec_type, + str_id, alloc_param.operation, 0); + + dev_info(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n", + str_id, pipe_id); + ret = sst_prepare_and_post_msg(sst_drv_ctx, task_id, IPC_CMD, + IPC_IA_ALLOC_STREAM_MRFLD, pipe_id, sizeof(alloc_param), + &alloc_param, data, true, true, false, true); + + if (ret < 0) { + dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); + /* alloc failed, so reset the state to uninit */ + str_info->status = STREAM_UN_INIT; + str_id = ret; + } else if (data) { + response = (struct snd_sst_alloc_response *)data; + ret = response->str_type.result; + if (!ret) + goto out; + dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); + if (ret == SST_ERR_STREAM_IN_USE) { + dev_err(sst_drv_ctx->dev, + "FW not in clean state, send free for:%d\n", str_id); + sst_free_stream(sst_drv_ctx, str_id); + } + str_id = -ret; + } +out: + kfree(data); + return str_id; +} + +/** +* sst_start_stream - Send msg for a starting stream +* @str_id: stream ID +* +* This function is called by any function which wants to start +* a stream. +*/ +int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + u16 data = 0; + + dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING) + return -EBADRQC; + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id, + sizeof(u16), &data, NULL, true, true, true, false); + + return retval; +} + +int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_bytes_v2 *bytes) +{ struct ipc_post *msg = NULL; + u32 length; + int pvt_id, ret = 0; + struct sst_block *block = NULL; + + dev_dbg(sst_drv_ctx->dev, + "type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n", + bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id, + bytes->pipe_id, bytes->len); + + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg, + bytes->task_id, 1, pvt_id); + msg->mrfld_header.p.header_high.part.res_rqd = bytes->block; + length = bytes->len; + msg->mrfld_header.p.header_low_payload = length; + dev_dbg(sst_drv_ctx->dev, "length is %d\n", length); + memcpy(msg->mailbox_data, &bytes->bytes, bytes->len); + if (bytes->block) { + block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id); + if (block == NULL) { + kfree(msg); + ret = -ENOMEM; + goto out; + } + } + + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d", + msg->mrfld_header.p.header_low_payload); + + if (bytes->block) { + ret = sst_wait_timeout(sst_drv_ctx, block); + if (ret) { + dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret); + sst_free_block(sst_drv_ctx, block); + goto out; + } + } + if (bytes->type == SND_SST_BYTES_GET) { + /* + * copy the reply and send back + * we need to update only sz and payload + */ + if (bytes->block) { + unsigned char *r = block->data; + + dev_dbg(sst_drv_ctx->dev, "read back %d bytes", + bytes->len); + memcpy(bytes->bytes, r, bytes->len); + } + } + if (bytes->block) + sst_free_block(sst_drv_ctx, block); +out: + test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id); + return 0; +} + +/* + * sst_pause_stream - Send msg for a pausing stream + * @str_id: stream ID + * + * This function is called by any function which wants to pause + * an already running stream. + */ +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status == STREAM_PAUSED) + return 0; + if (str_info->status == STREAM_RUNNING || + str_info->status == STREAM_INIT) { + if (str_info->prev == STREAM_UN_INIT) + return -EBADRQC; + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id, + 0, NULL, NULL, true, true, false, true); + + if (retval == 0) { + str_info->prev = str_info->status; + str_info->status = STREAM_PAUSED; + } else if (retval == SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n "); + } + + return retval; +} + +/** + * sst_resume_stream - Send msg for resuming stream + * @str_id: stream ID + * + * This function is called by any function which wants to resume + * an already paused stream. + */ +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status == STREAM_RUNNING) + return 0; + if (str_info->status == STREAM_PAUSED) { + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD, + str_info->pipe_id, 0, NULL, NULL, + true, true, false, true); + + if (!retval) { + if (str_info->prev == STREAM_RUNNING) + str_info->status = STREAM_RUNNING; + else + str_info->status = STREAM_INIT; + str_info->prev = STREAM_PAUSED; + } else if (retval == -SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n"); + } + + return retval; +} + + +/** + * sst_drop_stream - Send msg for stopping stream + * @str_id: stream ID + * + * This function is called by any function which wants to stop + * a stream. + */ +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + str_info->cumm_bytes = 0; + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_DROP_STREAM_MRFLD, + str_info->pipe_id, 0, NULL, NULL, + true, true, true, false); + } else { + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n", + str_info->status); + } + return retval; +} + +/** +* sst_drain_stream - Send msg for draining stream +* @str_id: stream ID +* +* This function is called by any function which wants to drain +* a stream. +*/ +int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, + int str_id, bool partial_drain) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING && + str_info->status != STREAM_INIT && + str_info->status != STREAM_PAUSED) { + dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n", + str_info->status); + return -EBADRQC; + } + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id, + sizeof(u8), &partial_drain, NULL, true, true, false, false); + /* + * with new non blocked drain implementation in core we dont need to + * wait for respsonse, and need to only invoke callback for drain + * complete + */ + + return retval; +} + +/** + * sst_free_stream - Frees a stream + * @str_id: stream ID + * + * This function is called by any function which wants to free + * a stream. + */ +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + struct intel_sst_ops *ops; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id); + + mutex_lock(&sst_drv_ctx->sst_lock); + if (sst_drv_ctx->sst_state == SST_RESET) { + mutex_unlock(&sst_drv_ctx->sst_lock); + return -ENODEV; + } + mutex_unlock(&sst_drv_ctx->sst_lock); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + ops = sst_drv_ctx->ops; + + mutex_lock(&str_info->lock); + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = str_info->status; + str_info->status = STREAM_UN_INIT; + mutex_unlock(&str_info->lock); + + dev_info(sst_drv_ctx->dev, "Free for str %d pipe %#x\n", + str_id, str_info->pipe_id); + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0, + NULL, NULL, true, true, false, true); + + dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n", + retval); + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n"); + } else { + mutex_unlock(&str_info->lock); + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n"); + } + + return retval; +} diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 5c2d8fa..7f55d59 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -27,7 +27,7 @@ #include #include #include "../../codecs/rt5640.h" -#include "../sst-atom-controls.h" +#include "../atom/sst-atom-controls.h" static const struct snd_soc_dapm_widget byt_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index 93bb671..20a28b2 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -28,7 +28,7 @@ #include #include #include "../../codecs/rt5645.h" -#include "../sst-atom-controls.h" +#include "../atom/sst-atom-controls.h" #define CHT_PLAT_CLK_3_HZ 19200000 #define CHT_CODEC_DAI "rt5645-aif1" diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c index 2cea002..2c9cc5b 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5672.c +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -24,7 +24,7 @@ #include #include #include "../../codecs/rt5670.h" -#include "../sst-atom-controls.h" +#include "../atom/sst-atom-controls.h" /* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */ #define CHT_PLAT_CLK_3_HZ 19200000 diff --git a/sound/soc/intel/sst-atom-controls.c b/sound/soc/intel/sst-atom-controls.c deleted file mode 100644 index 90aa5c0..0000000 --- a/sound/soc/intel/sst-atom-controls.c +++ /dev/null @@ -1,1422 +0,0 @@ -/* - * sst-atom-controls.c - Intel MID Platform driver DPCM ALSA controls for Mrfld - * - * Copyright (C) 2013-14 Intel Corp - * Author: Omair Mohammed Abdullah - * Vinod Koul - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active - * we forward the settings and parameters, rest we keep the values in - * driver and forward when DAPM enables them - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include "sst-mfld-platform.h" -#include "sst-atom-controls.h" - -static int sst_fill_byte_control(struct sst_data *drv, - u8 ipc_msg, u8 block, - u8 task_id, u8 pipe_id, - u16 len, void *cmd_data) -{ - struct snd_sst_bytes_v2 *byte_data = drv->byte_stream; - - byte_data->type = SST_CMD_BYTES_SET; - byte_data->ipc_msg = ipc_msg; - byte_data->block = block; - byte_data->task_id = task_id; - byte_data->pipe_id = pipe_id; - - if (len > SST_MAX_BIN_BYTES - sizeof(*byte_data)) { - dev_err(&drv->pdev->dev, "command length too big (%u)", len); - return -EINVAL; - } - byte_data->len = len; - memcpy(byte_data->bytes, cmd_data, len); - print_hex_dump_bytes("writing to lpe: ", DUMP_PREFIX_OFFSET, - byte_data, len + sizeof(*byte_data)); - return 0; -} - -static int sst_fill_and_send_cmd_unlocked(struct sst_data *drv, - u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, - void *cmd_data, u16 len) -{ - int ret = 0; - - ret = sst_fill_byte_control(drv, ipc_msg, - block, task_id, pipe_id, len, cmd_data); - if (ret < 0) - return ret; - return sst->ops->send_byte_stream(sst->dev, drv->byte_stream); -} - -/** - * sst_fill_and_send_cmd - generate the IPC message and send it to the FW - * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS) - * @cmd_data: the IPC payload - */ -static int sst_fill_and_send_cmd(struct sst_data *drv, - u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, - void *cmd_data, u16 len) -{ - int ret; - - mutex_lock(&drv->lock); - ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, - task_id, pipe_id, cmd_data, len); - mutex_unlock(&drv->lock); - - return ret; -} - -/** - * tx map value is a bitfield where each bit represents a FW channel - * - * 3 2 1 0 # 0 = codec0, 1 = codec1 - * RLRLRLRL # 3, 4 = reserved - * - * e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R - */ -static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = { - 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */ -}; - -/** - * rx map value is a bitfield where each bit represents a slot - * - * 76543210 # 0 = slot 0, 1 = slot 1 - * - * e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2 - */ -static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = { - 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */ -}; - -/** - * NOTE: this is invoked with lock held - */ -static int sst_send_slot_map(struct sst_data *drv) -{ - struct sst_param_sba_ssp_slot_map cmd; - - SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); - cmd.header.command_id = SBA_SET_SSP_SLOT_MAP; - cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map) - - sizeof(struct sst_dsp_header); - - cmd.param_id = SBA_SET_SSP_SLOT_MAP; - cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map) - + sizeof(cmd.ssp_index); - cmd.ssp_index = SSP_CODEC; - - memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map)); - memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map)); - - return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, - SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); -} - -int sst_slot_enum_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct sst_enum *e = (struct sst_enum *)kcontrol->private_value; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = e->max; - - if (uinfo->value.enumerated.item > e->max - 1) - uinfo->value.enumerated.item = e->max - 1; - strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); - - return 0; -} - -/** - * sst_slot_get - get the status of the interleaver/deinterleaver control - * - * Searches the map where the control status is stored, and gets the - * channel/slot which is currently set for this enumerated control. Since it is - * an enumerated control, there is only one possible value. - */ -static int sst_slot_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct sst_enum *e = (void *)kcontrol->private_value; - struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); - struct sst_data *drv = snd_soc_component_get_drvdata(c); - unsigned int ctl_no = e->reg; - unsigned int is_tx = e->tx; - unsigned int val, mux; - u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; - - mutex_lock(&drv->lock); - val = 1 << ctl_no; - /* search which slot/channel has this bit set - there should be only one */ - for (mux = e->max; mux > 0; mux--) - if (map[mux - 1] & val) - break; - - ucontrol->value.enumerated.item[0] = mux; - mutex_unlock(&drv->lock); - - dev_dbg(c->dev, "%s - %s map = %#x\n", - is_tx ? "tx channel" : "rx slot", - e->texts[mux], mux ? map[mux - 1] : -1); - return 0; -} - -/* sst_check_and_send_slot_map - helper for checking power state and sending - * slot map cmd - * - * called with lock held - */ -static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol) -{ - struct sst_enum *e = (void *)kcontrol->private_value; - int ret = 0; - - if (e->w && e->w->power) - ret = sst_send_slot_map(drv); - else - dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n", - kcontrol->id.name); - return ret; -} - -/** - * sst_slot_put - set the status of interleaver/deinterleaver control - * - * (de)interleaver controls are defined in opposite sense to be user-friendly - * - * Instead of the enum value being the value written to the register, it is the - * register address; and the kcontrol number (register num) is the value written - * to the register. This is so that there can be only one value for each - * slot/channel since there is only one control for each slot/channel. - * - * This means that whenever an enum is set, we need to clear the bit - * for that kcontrol_no for all the interleaver OR deinterleaver registers - */ -static int sst_slot_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); - struct sst_data *drv = snd_soc_component_get_drvdata(c); - struct sst_enum *e = (void *)kcontrol->private_value; - int i, ret = 0; - unsigned int ctl_no = e->reg; - unsigned int is_tx = e->tx; - unsigned int slot_channel_no; - unsigned int val, mux; - u8 *map; - - map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; - - val = 1 << ctl_no; - mux = ucontrol->value.enumerated.item[0]; - if (mux > e->max - 1) - return -EINVAL; - - mutex_lock(&drv->lock); - /* first clear all registers of this bit */ - for (i = 0; i < e->max; i++) - map[i] &= ~val; - - if (mux == 0) { - /* kctl set to 'none' and we reset the bits so send IPC */ - ret = sst_check_and_send_slot_map(drv, kcontrol); - - mutex_unlock(&drv->lock); - return ret; - } - - /* offset by one to take "None" into account */ - slot_channel_no = mux - 1; - map[slot_channel_no] |= val; - - dev_dbg(c->dev, "%s %s map = %#x\n", - is_tx ? "tx channel" : "rx slot", - e->texts[mux], map[slot_channel_no]); - - ret = sst_check_and_send_slot_map(drv, kcontrol); - - mutex_unlock(&drv->lock); - return ret; -} - -static int sst_send_algo_cmd(struct sst_data *drv, - struct sst_algo_control *bc) -{ - int len, ret = 0; - struct sst_cmd_set_params *cmd; - - /*bc->max includes sizeof algos + length field*/ - len = sizeof(cmd->dst) + sizeof(cmd->command_id) + bc->max; - - cmd = kzalloc(len, GFP_KERNEL); - if (cmd == NULL) - return -ENOMEM; - - SST_FILL_DESTINATION(2, cmd->dst, bc->pipe_id, bc->module_id); - cmd->command_id = bc->cmd_id; - memcpy(cmd->params, bc->params, bc->max); - - ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, - SST_FLAG_BLOCKED, bc->task_id, 0, cmd, len); - kfree(cmd); - return ret; -} - -/** - * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe - * - * The algos which are in each pipeline are sent to the firmware one by one - * - * Called with lock held - */ -static int sst_find_and_send_pipe_algo(struct sst_data *drv, - const char *pipe, struct sst_ids *ids) -{ - int ret = 0; - struct sst_algo_control *bc; - struct sst_module *algo = NULL; - - dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe); - - list_for_each_entry(algo, &ids->algo_list, node) { - bc = (void *)algo->kctl->private_value; - - dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n", - algo->kctl->id.name, pipe); - ret = sst_send_algo_cmd(drv, bc); - if (ret) - return ret; - } - return ret; -} - -static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct sst_algo_control *bc = (void *)kcontrol->private_value; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; - uinfo->count = bc->max; - - return 0; -} - -static int sst_algo_control_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct sst_algo_control *bc = (void *)kcontrol->private_value; - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - - switch (bc->type) { - case SST_ALGO_PARAMS: - memcpy(ucontrol->value.bytes.data, bc->params, bc->max); - break; - default: - dev_err(component->dev, "Invalid Input- algo type:%d\n", - bc->type); - return -EINVAL; - - } - return 0; -} - -static int sst_algo_control_set(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int ret = 0; - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); - struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); - struct sst_algo_control *bc = (void *)kcontrol->private_value; - - dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); - mutex_lock(&drv->lock); - switch (bc->type) { - case SST_ALGO_PARAMS: - memcpy(bc->params, ucontrol->value.bytes.data, bc->max); - break; - default: - mutex_unlock(&drv->lock); - dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", - bc->type); - return -EINVAL; - } - /*if pipe is enabled, need to send the algo params from here*/ - if (bc->w && bc->w->power) - ret = sst_send_algo_cmd(drv, bc); - mutex_unlock(&drv->lock); - - return ret; -} - -static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = mc->stereo ? 2 : 1; - uinfo->value.integer.min = mc->min; - uinfo->value.integer.max = mc->max; - - return 0; -} - -/** - * sst_send_gain_cmd - send the gain algorithm IPC to the FW - * @gv: the stored value of gain (also contains rampduration) - * @mute: flag that indicates whether this was called from the - * digital_mute callback or directly. If called from the - * digital_mute callback, module will be muted/unmuted based on this - * flag. The flag is always 0 if called directly. - * - * Called with sst_data.lock held - * - * The user-set gain value is sent only if the user-controllable 'mute' control - * is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is - * sent. - */ -static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv, - u16 task_id, u16 loc_id, u16 module_id, int mute) -{ - struct sst_cmd_set_gain_dual cmd; - - dev_dbg(&drv->pdev->dev, "Enter\n"); - - cmd.header.command_id = MMX_SET_GAIN; - SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); - cmd.gain_cell_num = 1; - - if (mute || gv->mute) { - cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE; - cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE; - } else { - cmd.cell_gains[0].cell_gain_left = gv->l_gain; - cmd.cell_gains[0].cell_gain_right = gv->r_gain; - } - - SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest, - loc_id, module_id); - cmd.cell_gains[0].gain_time_constant = gv->ramp_duration; - - cmd.header.length = sizeof(struct sst_cmd_set_gain_dual) - - sizeof(struct sst_dsp_header); - - /* we are with lock held, so call the unlocked api to send */ - return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, - SST_FLAG_BLOCKED, task_id, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); -} - -static int sst_gain_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; - struct sst_gain_value *gv = mc->gain_val; - - switch (mc->type) { - case SST_GAIN_TLV: - ucontrol->value.integer.value[0] = gv->l_gain; - ucontrol->value.integer.value[1] = gv->r_gain; - break; - - case SST_GAIN_MUTE: - ucontrol->value.integer.value[0] = gv->mute ? 1 : 0; - break; - - case SST_GAIN_RAMP_DURATION: - ucontrol->value.integer.value[0] = gv->ramp_duration; - break; - - default: - dev_err(component->dev, "Invalid Input- gain type:%d\n", - mc->type); - return -EINVAL; - } - - return 0; -} - -static int sst_gain_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int ret = 0; - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); - struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); - struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; - struct sst_gain_value *gv = mc->gain_val; - - mutex_lock(&drv->lock); - - switch (mc->type) { - case SST_GAIN_TLV: - gv->l_gain = ucontrol->value.integer.value[0]; - gv->r_gain = ucontrol->value.integer.value[1]; - dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n", - mc->pname, gv->l_gain, gv->r_gain); - break; - - case SST_GAIN_MUTE: - gv->mute = !!ucontrol->value.integer.value[0]; - dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute); - break; - - case SST_GAIN_RAMP_DURATION: - gv->ramp_duration = ucontrol->value.integer.value[0]; - dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n", - mc->pname, gv->ramp_duration); - break; - - default: - mutex_unlock(&drv->lock); - dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", - mc->type); - return -EINVAL; - } - - if (mc->w && mc->w->power) - ret = sst_send_gain_cmd(drv, gv, mc->task_id, - mc->pipe_id | mc->instance_id, mc->module_id, 0); - mutex_unlock(&drv->lock); - - return ret; -} - -static int sst_set_pipe_gain(struct sst_ids *ids, - struct sst_data *drv, int mute); - -static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol) -{ - struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); - struct sst_data *drv = snd_soc_component_get_drvdata(c); - struct sst_ids *ids = w->priv; - - mutex_lock(&drv->lock); - sst_find_and_send_pipe_algo(drv, w->name, ids); - sst_set_pipe_gain(ids, drv, 0); - mutex_unlock(&drv->lock); - - return 0; -} - -static int sst_generic_modules_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - if (SND_SOC_DAPM_EVENT_ON(event)) - return sst_send_pipe_module_params(w, k); - return 0; -} - -static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0); - -/* Look up table to convert MIXER SW bit regs to SWM inputs */ -static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = { - [SST_IP_CODEC0] = SST_SWM_IN_CODEC0, - [SST_IP_CODEC1] = SST_SWM_IN_CODEC1, - [SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP, - [SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1, - [SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2, - [SST_IP_PCM0] = SST_SWM_IN_PCM0, - [SST_IP_PCM1] = SST_SWM_IN_PCM1, - [SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0, - [SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1, - [SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2, - [SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3, -}; - -/** - * fill_swm_input - fill in the SWM input ids given the register - * - * The register value is a bit-field inicated which mixer inputs are ON. Use the - * lookup table to get the input-id and fill it in the structure. - */ -static int fill_swm_input(struct snd_soc_component *cmpnt, - struct swm_input_ids *swm_input, unsigned int reg) -{ - uint i, is_set, nb_inputs = 0; - u16 input_loc_id; - - dev_dbg(cmpnt->dev, "reg: %#x\n", reg); - for (i = 0; i < SST_SWM_INPUT_COUNT; i++) { - is_set = reg & BIT(i); - if (!is_set) - continue; - - input_loc_id = swm_mixer_input_ids[i]; - SST_FILL_DESTINATION(2, swm_input->input_id, - input_loc_id, SST_DEFAULT_MODULE_ID); - nb_inputs++; - swm_input++; - dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n", - input_loc_id, nb_inputs); - - if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) { - dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached"); - break; - } - } - return nb_inputs; -} - - -/** - * called with lock held - */ -static int sst_set_pipe_gain(struct sst_ids *ids, - struct sst_data *drv, int mute) -{ - int ret = 0; - struct sst_gain_mixer_control *mc; - struct sst_gain_value *gv; - struct sst_module *gain = NULL; - - list_for_each_entry(gain, &ids->gain_list, node) { - struct snd_kcontrol *kctl = gain->kctl; - - dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name); - mc = (void *)kctl->private_value; - gv = mc->gain_val; - - ret = sst_send_gain_cmd(drv, gv, mc->task_id, - mc->pipe_id | mc->instance_id, mc->module_id, mute); - if (ret) - return ret; - } - return ret; -} - -static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct sst_cmd_set_swm cmd; - struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); - struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); - struct sst_ids *ids = w->priv; - bool set_mixer = false; - struct soc_mixer_control *mc; - int val = 0; - int i = 0; - - dev_dbg(cmpnt->dev, "widget = %s\n", w->name); - /* - * Identify which mixer input is on and send the bitmap of the - * inputs as an IPC to the DSP. - */ - for (i = 0; i < w->num_kcontrols; i++) { - if (dapm_kcontrol_get_value(w->kcontrols[i])) { - mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value; - val |= 1 << mc->shift; - } - } - dev_dbg(cmpnt->dev, "val = %#x\n", val); - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - case SND_SOC_DAPM_POST_PMD: - set_mixer = true; - break; - case SND_SOC_DAPM_POST_REG: - if (w->power) - set_mixer = true; - break; - default: - set_mixer = false; - } - - if (set_mixer == false) - return 0; - - if (SND_SOC_DAPM_EVENT_ON(event) || - event == SND_SOC_DAPM_POST_REG) - cmd.switch_state = SST_SWM_ON; - else - cmd.switch_state = SST_SWM_OFF; - - SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); - /* MMX_SET_SWM == SBA_SET_SWM */ - cmd.header.command_id = SBA_SET_SWM; - - SST_FILL_DESTINATION(2, cmd.output_id, - ids->location_id, SST_DEFAULT_MODULE_ID); - cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val); - cmd.header.length = offsetof(struct sst_cmd_set_swm, input) - - sizeof(struct sst_dsp_header) - + (cmd.nb_inputs * sizeof(cmd.input[0])); - - return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, - ids->task_id, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); -} - -/* SBA mixers - 16 inputs */ -#define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \ - static const struct snd_kcontrol_new kctl_name[] = { \ - SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \ - SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \ - SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \ - SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \ - SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \ - SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \ - SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \ - } - -#define SST_SBA_MIXER_GRAPH_MAP(mix_name) \ - { mix_name, "codec_in0 Switch", "codec_in0" }, \ - { mix_name, "codec_in1 Switch", "codec_in1" }, \ - { mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \ - { mix_name, "media_loop1_in Switch", "media_loop1_in" }, \ - { mix_name, "media_loop2_in Switch", "media_loop2_in" }, \ - { mix_name, "pcm0_in Switch", "pcm0_in" }, \ - { mix_name, "pcm1_in Switch", "pcm1_in" } - -#define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \ - static const struct snd_kcontrol_new kctl_name[] = { \ - SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \ - SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \ - SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \ - SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \ - } - -SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls); -SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls); - -/* 18 SBA mixers */ -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls); - -/* - * sst_handle_vb_timer - Start/Stop the DSP scheduler - * - * The DSP expects first cmd to be SBA_VB_START, so at first startup send - * that. - * DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that. - * - * Do refcount internally so that we send command only at first start - * and last end. Since SST driver does its own ref count, invoke sst's - * power ops always! - */ -int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) -{ - int ret = 0; - struct sst_cmd_generic cmd; - struct sst_data *drv = snd_soc_dai_get_drvdata(dai); - static int timer_usage; - - if (enable) - cmd.header.command_id = SBA_VB_START; - else - cmd.header.command_id = SBA_IDLE; - dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage); - - SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); - cmd.header.length = 0; - - if (enable) { - ret = sst->ops->power(sst->dev, true); - if (ret < 0) - return ret; - } - - mutex_lock(&drv->lock); - if (enable) - timer_usage++; - else - timer_usage--; - - /* - * Send the command only if this call is the first enable or last - * disable - */ - if ((enable && (timer_usage == 1)) || - (!enable && (timer_usage == 0))) { - ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, - SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); - if (ret && enable) { - timer_usage--; - enable = false; - } - } - mutex_unlock(&drv->lock); - - if (!enable) - sst->ops->power(sst->dev, false); - return ret; -} - -/** - * sst_ssp_config - contains SSP configuration for media UC - */ -static const struct sst_ssp_config sst_ssp_configs = { - .ssp_id = SSP_CODEC, - .bits_per_slot = 24, - .slots = 4, - .ssp_mode = SSP_MODE_MASTER, - .pcm_mode = SSP_PCM_MODE_NETWORK, - .duplex = SSP_DUPLEX, - .ssp_protocol = SSP_MODE_PCM, - .fs_width = 1, - .fs_frequency = SSP_FS_48_KHZ, - .active_slot_map = 0xF, - .start_delay = 0, -}; - -int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable) -{ - struct sst_cmd_sba_hw_set_ssp cmd; - struct sst_data *drv = snd_soc_dai_get_drvdata(dai); - const struct sst_ssp_config *config; - - dev_info(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id); - - SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); - cmd.header.command_id = SBA_HW_SET_SSP; - cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp) - - sizeof(struct sst_dsp_header); - - config = &sst_ssp_configs; - dev_dbg(dai->dev, "ssp_id: %u\n", config->ssp_id); - - if (enable) - cmd.switch_state = SST_SWITCH_ON; - else - cmd.switch_state = SST_SWITCH_OFF; - - cmd.selection = config->ssp_id; - cmd.nb_bits_per_slots = config->bits_per_slot; - cmd.nb_slots = config->slots; - cmd.mode = config->ssp_mode | (config->pcm_mode << 1); - cmd.duplex = config->duplex; - cmd.active_tx_slot_map = config->active_slot_map; - cmd.active_rx_slot_map = config->active_slot_map; - cmd.frame_sync_frequency = config->fs_frequency; - cmd.frame_sync_polarity = SSP_FS_ACTIVE_HIGH; - cmd.data_polarity = 1; - cmd.frame_sync_width = config->fs_width; - cmd.ssp_protocol = config->ssp_protocol; - cmd.start_delay = config->start_delay; - cmd.reserved1 = cmd.reserved2 = 0xFF; - - return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, - SST_TASK_SBA, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); -} - -static int sst_set_be_modules(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - int ret = 0; - struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); - struct sst_data *drv = snd_soc_component_get_drvdata(c); - - dev_dbg(c->dev, "Enter: widget=%s\n", w->name); - - if (SND_SOC_DAPM_EVENT_ON(event)) { - ret = sst_send_slot_map(drv); - if (ret) - return ret; - ret = sst_send_pipe_module_params(w, k); - } - return ret; -} - -static int sst_set_media_path(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - int ret = 0; - struct sst_cmd_set_media_path cmd; - struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); - struct sst_data *drv = snd_soc_component_get_drvdata(c); - struct sst_ids *ids = w->priv; - - dev_dbg(c->dev, "widget=%s\n", w->name); - dev_dbg(c->dev, "task=%u, location=%#x\n", - ids->task_id, ids->location_id); - - if (SND_SOC_DAPM_EVENT_ON(event)) - cmd.switch_state = SST_PATH_ON; - else - cmd.switch_state = SST_PATH_OFF; - - SST_FILL_DESTINATION(2, cmd.header.dst, - ids->location_id, SST_DEFAULT_MODULE_ID); - - /* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */ - cmd.header.command_id = MMX_SET_MEDIA_PATH; - cmd.header.length = sizeof(struct sst_cmd_set_media_path) - - sizeof(struct sst_dsp_header); - - ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, - ids->task_id, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); - if (ret) - return ret; - - if (SND_SOC_DAPM_EVENT_ON(event)) - ret = sst_send_pipe_module_params(w, k); - return ret; -} - -static int sst_set_media_loop(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - int ret = 0; - struct sst_cmd_sba_set_media_loop_map cmd; - struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); - struct sst_data *drv = snd_soc_component_get_drvdata(c); - struct sst_ids *ids = w->priv; - - dev_dbg(c->dev, "Enter:widget=%s\n", w->name); - if (SND_SOC_DAPM_EVENT_ON(event)) - cmd.switch_state = SST_SWITCH_ON; - else - cmd.switch_state = SST_SWITCH_OFF; - - SST_FILL_DESTINATION(2, cmd.header.dst, - ids->location_id, SST_DEFAULT_MODULE_ID); - - cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP; - cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map) - - sizeof(struct sst_dsp_header); - cmd.param.part.cfg.rate = 2; /* 48khz */ - - cmd.param.part.cfg.format = ids->format; /* stereo/Mono */ - cmd.param.part.cfg.s_length = 1; /* 24bit left justified */ - cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */ - - ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, - SST_TASK_SBA, 0, &cmd, - sizeof(cmd.header) + cmd.header.length); - if (ret) - return ret; - - if (SND_SOC_DAPM_EVENT_ON(event)) - ret = sst_send_pipe_module_params(w, k); - return ret; -} - -static const struct snd_soc_dapm_widget sst_dapm_widgets[] = { - SST_AIF_IN("codec_in0", sst_set_be_modules), - SST_AIF_IN("codec_in1", sst_set_be_modules), - SST_AIF_OUT("codec_out0", sst_set_be_modules), - SST_AIF_OUT("codec_out1", sst_set_be_modules), - - /* Media Paths */ - /* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */ - SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event), - SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL), - SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path), - SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL), - SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path), - SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path), - - /* SBA PCM Paths */ - SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path), - SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path), - SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path), - SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path), - SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path), - - /* SBA Loops */ - SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL), - SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL), - SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL), - SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_MONO, sst_set_media_loop), - SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_MONO, sst_set_media_loop), - SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop), - - /* Media Mixers */ - SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0, - sst_mix_media0_controls, sst_swm_mixer_event), - SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1, - sst_mix_media1_controls, sst_swm_mixer_event), - - /* SBA PCM mixers */ - SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0, - sst_mix_pcm0_controls, sst_swm_mixer_event), - SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1, - sst_mix_pcm1_controls, sst_swm_mixer_event), - SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2, - sst_mix_pcm2_controls, sst_swm_mixer_event), - - /* SBA Loop mixers */ - SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, - sst_mix_sprot_l0_controls, sst_swm_mixer_event), - SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, - sst_mix_media_l1_controls, sst_swm_mixer_event), - SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, - sst_mix_media_l2_controls, sst_swm_mixer_event), - - /* SBA Backend mixers */ - SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0, - sst_mix_codec0_controls, sst_swm_mixer_event), - SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1, - sst_mix_codec1_controls, sst_swm_mixer_event), -}; - -static const struct snd_soc_dapm_route intercon[] = { - {"media0_in", NULL, "Compress Playback"}, - {"media1_in", NULL, "Headset Playback"}, - {"media2_in", NULL, "pcm0_out"}, - - {"media0_out mix 0", "media0_in Switch", "media0_in"}, - {"media0_out mix 0", "media1_in Switch", "media1_in"}, - {"media0_out mix 0", "media2_in Switch", "media2_in"}, - {"media0_out mix 0", "media3_in Switch", "media3_in"}, - {"media1_out mix 0", "media0_in Switch", "media0_in"}, - {"media1_out mix 0", "media1_in Switch", "media1_in"}, - {"media1_out mix 0", "media2_in Switch", "media2_in"}, - {"media1_out mix 0", "media3_in Switch", "media3_in"}, - - {"media0_out", NULL, "media0_out mix 0"}, - {"media1_out", NULL, "media1_out mix 0"}, - {"pcm0_in", NULL, "media0_out"}, - {"pcm1_in", NULL, "media1_out"}, - - {"Headset Capture", NULL, "pcm1_out"}, - {"Headset Capture", NULL, "pcm2_out"}, - {"pcm0_out", NULL, "pcm0_out mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"), - {"pcm1_out", NULL, "pcm1_out mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"), - {"pcm2_out", NULL, "pcm2_out mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"), - - {"media_loop1_in", NULL, "media_loop1_out"}, - {"media_loop1_out", NULL, "media_loop1_out mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"), - {"media_loop2_in", NULL, "media_loop2_out"}, - {"media_loop2_out", NULL, "media_loop2_out mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"), - {"sprot_loop_in", NULL, "sprot_loop_out"}, - {"sprot_loop_out", NULL, "sprot_loop_out mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"), - - {"codec_out0", NULL, "codec_out0 mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"), - {"codec_out1", NULL, "codec_out1 mix 0"}, - SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"), - -}; -static const char * const slot_names[] = { - "none", - "slot 0", "slot 1", "slot 2", "slot 3", - "slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */ -}; - -static const char * const channel_names[] = { - "none", - "codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1", - "codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */ -}; - -#define SST_INTERLEAVER(xpname, slot_name, slotno) \ - SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \ - channel_names, sst_slot_get, sst_slot_put) - -#define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \ - SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \ - slot_names, sst_slot_get, sst_slot_put) - -static const struct snd_kcontrol_new sst_slot_controls[] = { - SST_INTERLEAVER("codec_out", "slot 0", 0), - SST_INTERLEAVER("codec_out", "slot 1", 1), - SST_INTERLEAVER("codec_out", "slot 2", 2), - SST_INTERLEAVER("codec_out", "slot 3", 3), - SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0), - SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1), - SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2), - SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3), -}; - -/* Gain helper with min/max set */ -#define SST_GAIN(name, path_id, task_id, instance, gain_var) \ - SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ - SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ - sst_gain_get, sst_gain_put, \ - SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \ - sst_gain_tlv_common, gain_var) - -#define SST_VOLUME(name, path_id, task_id, instance, gain_var) \ - SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ - SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ - sst_gain_get, sst_gain_put, \ - SST_MODULE_ID_VOLUME, path_id, instance, task_id, \ - sst_gain_tlv_common, gain_var) - -static struct sst_gain_value sst_gains[]; - -static const struct snd_kcontrol_new sst_gain_controls[] = { - SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]), - SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]), - SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]), - SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]), - - SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]), - SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]), - SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]), - SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]), - - SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]), - SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]), - SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]), - SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]), - SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]), - SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]), - SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]), - SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]), -}; - -#define SST_GAIN_NUM_CONTROLS 3 -/* the SST_GAIN macro above will create three alsa controls for each - * instance invoked, gain, mute and ramp duration, which use the same gain - * cell sst_gain to keep track of data - * To calculate number of gain cell instances we need to device by 3 in - * below caulcation for gain cell memory. - * This gets rid of static number and issues while adding new controls - */ -static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS]; - -static const struct snd_kcontrol_new sst_algo_controls[] = { - SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, - SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), - SST_ALGO_KCONTROL_BYTES("media_loop1_out", "iir", 300, SST_MODULE_ID_IIR_24, - SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR), - SST_ALGO_KCONTROL_BYTES("media_loop1_out", "mdrp", 286, SST_MODULE_ID_MDRP, - SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP), - SST_ALGO_KCONTROL_BYTES("media_loop2_out", "fir", 272, SST_MODULE_ID_FIR_24, - SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), - SST_ALGO_KCONTROL_BYTES("media_loop2_out", "iir", 300, SST_MODULE_ID_IIR_24, - SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR), - SST_ALGO_KCONTROL_BYTES("media_loop2_out", "mdrp", 286, SST_MODULE_ID_MDRP, - SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP), - SST_ALGO_KCONTROL_BYTES("sprot_loop_out", "lpro", 192, SST_MODULE_ID_SPROT, - SST_PATH_INDEX_SPROT_LOOP_OUT, 0, SST_TASK_SBA, SBA_VB_LPRO), - SST_ALGO_KCONTROL_BYTES("codec_in0", "dcr", 52, SST_MODULE_ID_FILT_DCR, - SST_PATH_INDEX_CODEC_IN0, 0, SST_TASK_SBA, SBA_VB_SET_IIR), - SST_ALGO_KCONTROL_BYTES("codec_in1", "dcr", 52, SST_MODULE_ID_FILT_DCR, - SST_PATH_INDEX_CODEC_IN1, 0, SST_TASK_SBA, SBA_VB_SET_IIR), - -}; - -static int sst_algo_control_init(struct device *dev) -{ - int i = 0; - struct sst_algo_control *bc; - /*allocate space to cache the algo parameters in the driver*/ - for (i = 0; i < ARRAY_SIZE(sst_algo_controls); i++) { - bc = (struct sst_algo_control *)sst_algo_controls[i].private_value; - bc->params = devm_kzalloc(dev, bc->max, GFP_KERNEL); - if (bc->params == NULL) - return -ENOMEM; - } - return 0; -} - -static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w) -{ - switch (w->id) { - case snd_soc_dapm_pga: - case snd_soc_dapm_aif_in: - case snd_soc_dapm_aif_out: - case snd_soc_dapm_input: - case snd_soc_dapm_output: - case snd_soc_dapm_mixer: - return true; - default: - return false; - } -} - -/** - * sst_send_pipe_gains - send gains for the front-end DAIs - * - * The gains in the pipes connected to the front-ends are muted/unmuted - * automatically via the digital_mute() DAPM callback. This function sends the - * gains for the front-end pipes. - */ -int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) -{ - struct sst_data *drv = snd_soc_dai_get_drvdata(dai); - struct snd_soc_dapm_widget *w; - struct snd_soc_dapm_path *p = NULL; - - dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream); - - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - dev_dbg(dai->dev, "Stream name=%s\n", - dai->playback_widget->name); - w = dai->playback_widget; - list_for_each_entry(p, &w->sinks, list_source) { - if (p->connected && !p->connected(w, p->sink)) - continue; - - if (p->connect && p->sink->power && - is_sst_dapm_widget(p->sink)) { - struct sst_ids *ids = p->sink->priv; - - dev_dbg(dai->dev, "send gains for widget=%s\n", - p->sink->name); - mutex_lock(&drv->lock); - sst_set_pipe_gain(ids, drv, mute); - mutex_unlock(&drv->lock); - } - } - } else { - dev_dbg(dai->dev, "Stream name=%s\n", - dai->capture_widget->name); - w = dai->capture_widget; - list_for_each_entry(p, &w->sources, list_sink) { - if (p->connected && !p->connected(w, p->sink)) - continue; - - if (p->connect && p->source->power && - is_sst_dapm_widget(p->source)) { - struct sst_ids *ids = p->source->priv; - - dev_dbg(dai->dev, "send gain for widget=%s\n", - p->source->name); - mutex_lock(&drv->lock); - sst_set_pipe_gain(ids, drv, mute); - mutex_unlock(&drv->lock); - } - } - } - return 0; -} - -/** - * sst_fill_module_list - populate the list of modules/gains for a pipe - * - * - * Fills the widget pointer in the kcontrol private data, and also fills the - * kcontrol pointer in the widget private data. - * - * Widget pointer is used to send the algo/gain in the .put() handler if the - * widget is powerd on. - * - * Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF - * event handler. Each widget (pipe) has multiple algos stored in the algo_list. - */ -static int sst_fill_module_list(struct snd_kcontrol *kctl, - struct snd_soc_dapm_widget *w, int type) -{ - struct sst_module *module = NULL; - struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); - struct sst_ids *ids = w->priv; - int ret = 0; - - module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL); - if (!module) - return -ENOMEM; - - if (type == SST_MODULE_GAIN) { - struct sst_gain_mixer_control *mc = (void *)kctl->private_value; - - mc->w = w; - module->kctl = kctl; - list_add_tail(&module->node, &ids->gain_list); - } else if (type == SST_MODULE_ALGO) { - struct sst_algo_control *bc = (void *)kctl->private_value; - - bc->w = w; - module->kctl = kctl; - list_add_tail(&module->node, &ids->algo_list); - } else { - dev_err(c->dev, "invoked for unknown type %d module %s", - type, kctl->id.name); - ret = -EINVAL; - } - - return ret; -} - -/** - * sst_fill_widget_module_info - fill list of gains/algos for the pipe - * @widget: pipe modelled as a DAPM widget - * - * Fill the list of gains/algos for the widget by looking at all the card - * controls and comparing the name of the widget with the first part of control - * name. First part of control name contains the pipe name (widget name). - */ -static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w, - struct snd_soc_platform *platform) -{ - struct snd_kcontrol *kctl; - int index, ret = 0; - struct snd_card *card = platform->component.card->snd_card; - char *idx; - - down_read(&card->controls_rwsem); - - list_for_each_entry(kctl, &card->controls, list) { - idx = strstr(kctl->id.name, " "); - if (idx == NULL) - continue; - index = strlen(kctl->id.name) - strlen(idx); - - if (strstr(kctl->id.name, "Volume") && - !strncmp(kctl->id.name, w->name, index)) - ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN); - - else if (strstr(kctl->id.name, "params") && - !strncmp(kctl->id.name, w->name, index)) - ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO); - - else if (strstr(kctl->id.name, "Switch") && - !strncmp(kctl->id.name, w->name, index) && - strstr(kctl->id.name, "Gain")) { - struct sst_gain_mixer_control *mc = - (void *)kctl->private_value; - - mc->w = w; - - } else if (strstr(kctl->id.name, "interleaver") && - !strncmp(kctl->id.name, w->name, index)) { - struct sst_enum *e = (void *)kctl->private_value; - - e->w = w; - - } else if (strstr(kctl->id.name, "deinterleaver") && - !strncmp(kctl->id.name, w->name, index)) { - - struct sst_enum *e = (void *)kctl->private_value; - - e->w = w; - } - - if (ret < 0) { - up_read(&card->controls_rwsem); - return ret; - } - } - - up_read(&card->controls_rwsem); - return 0; -} - -/** - * sst_fill_linked_widgets - fill the parent pointer for the linked widget - */ -static void sst_fill_linked_widgets(struct snd_soc_platform *platform, - struct sst_ids *ids) -{ - struct snd_soc_dapm_widget *w; - unsigned int len = strlen(ids->parent_wname); - - list_for_each_entry(w, &platform->component.card->widgets, list) { - if (!strncmp(ids->parent_wname, w->name, len)) { - ids->parent_w = w; - break; - } - } -} - -/** - * sst_map_modules_to_pipe - fill algo/gains list for all pipes - */ -static int sst_map_modules_to_pipe(struct snd_soc_platform *platform) -{ - struct snd_soc_dapm_widget *w; - int ret = 0; - - list_for_each_entry(w, &platform->component.card->widgets, list) { - if (is_sst_dapm_widget(w) && (w->priv)) { - struct sst_ids *ids = w->priv; - - dev_dbg(platform->dev, "widget type=%d name=%s\n", - w->id, w->name); - INIT_LIST_HEAD(&ids->algo_list); - INIT_LIST_HEAD(&ids->gain_list); - ret = sst_fill_widget_module_info(w, platform); - - if (ret < 0) - return ret; - - /* fill linked widgets */ - if (ids->parent_wname != NULL) - sst_fill_linked_widgets(platform, ids); - } - } - return 0; -} - -int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) -{ - int i, ret = 0; - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(&platform->component); - struct sst_data *drv = snd_soc_platform_get_drvdata(platform); - unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3; - - drv->byte_stream = devm_kzalloc(platform->dev, - SST_MAX_BIN_BYTES, GFP_KERNEL); - if (!drv->byte_stream) - return -ENOMEM; - - snd_soc_dapm_new_controls(dapm, sst_dapm_widgets, - ARRAY_SIZE(sst_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, intercon, - ARRAY_SIZE(intercon)); - snd_soc_dapm_new_widgets(dapm->card); - - for (i = 0; i < gains; i++) { - sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT; - sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT; - sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT; - sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT; - } - - ret = snd_soc_add_platform_controls(platform, sst_gain_controls, - ARRAY_SIZE(sst_gain_controls)); - if (ret) - return ret; - - /* Initialize algo control params */ - ret = sst_algo_control_init(platform->dev); - if (ret) - return ret; - ret = snd_soc_add_platform_controls(platform, sst_algo_controls, - ARRAY_SIZE(sst_algo_controls)); - if (ret) - return ret; - - ret = snd_soc_add_platform_controls(platform, sst_slot_controls, - ARRAY_SIZE(sst_slot_controls)); - if (ret) - return ret; - - ret = sst_map_modules_to_pipe(platform); - - return ret; -} diff --git a/sound/soc/intel/sst-atom-controls.h b/sound/soc/intel/sst-atom-controls.h deleted file mode 100644 index daecc58..0000000 --- a/sound/soc/intel/sst-atom-controls.h +++ /dev/null @@ -1,870 +0,0 @@ -/* - * sst-atom-controls.h - Intel MID Platform driver header file - * - * Copyright (C) 2013-14 Intel Corp - * Author: Ramesh Babu - * Omair M Abdullah - * Samreen Nilofer - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 __SST_ATOM_CONTROLS_H__ -#define __SST_ATOM_CONTROLS_H__ - -#include -#include - -enum { - MERR_DPCM_AUDIO = 0, - MERR_DPCM_COMPR, -}; - -/* define a bit for each mixer input */ -#define SST_MIX_IP(x) (x) - -#define SST_IP_CODEC0 SST_MIX_IP(2) -#define SST_IP_CODEC1 SST_MIX_IP(3) -#define SST_IP_LOOP0 SST_MIX_IP(4) -#define SST_IP_LOOP1 SST_MIX_IP(5) -#define SST_IP_LOOP2 SST_MIX_IP(6) -#define SST_IP_PROBE SST_MIX_IP(7) -#define SST_IP_VOIP SST_MIX_IP(12) -#define SST_IP_PCM0 SST_MIX_IP(13) -#define SST_IP_PCM1 SST_MIX_IP(14) -#define SST_IP_MEDIA0 SST_MIX_IP(17) -#define SST_IP_MEDIA1 SST_MIX_IP(18) -#define SST_IP_MEDIA2 SST_MIX_IP(19) -#define SST_IP_MEDIA3 SST_MIX_IP(20) - -#define SST_IP_LAST SST_IP_MEDIA3 - -#define SST_SWM_INPUT_COUNT (SST_IP_LAST + 1) -#define SST_CMD_SWM_MAX_INPUTS 6 - -#define SST_PATH_ID_SHIFT 8 -#define SST_DEFAULT_LOCATION_ID 0xFFFF -#define SST_DEFAULT_CELL_NBR 0xFF -#define SST_DEFAULT_MODULE_ID 0xFFFF - -/* - * Audio DSP Path Ids. Specified by the audio DSP FW - */ -enum sst_path_index { - SST_PATH_INDEX_CODEC_OUT0 = (0x02 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_CODEC_OUT1 = (0x03 << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_SPROT_LOOP_OUT = (0x04 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA_LOOP1_OUT = (0x05 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA_LOOP2_OUT = (0x06 << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_VOIP_OUT = (0x0C << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_PCM0_OUT = (0x0D << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_PCM1_OUT = (0x0E << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_PCM2_OUT = (0x0F << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_MEDIA0_OUT = (0x12 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA1_OUT = (0x13 << SST_PATH_ID_SHIFT), - - - /* Start of input paths */ - SST_PATH_INDEX_CODEC_IN0 = (0x82 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_CODEC_IN1 = (0x83 << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_SPROT_LOOP_IN = (0x84 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA_LOOP1_IN = (0x85 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA_LOOP2_IN = (0x86 << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_VOIP_IN = (0x8C << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_PCM0_IN = (0x8D << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_PCM1_IN = (0x8E << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_MEDIA0_IN = (0x8F << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA1_IN = (0x90 << SST_PATH_ID_SHIFT), - SST_PATH_INDEX_MEDIA2_IN = (0x91 << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_MEDIA3_IN = (0x9C << SST_PATH_ID_SHIFT), - - SST_PATH_INDEX_RESERVED = (0xFF << SST_PATH_ID_SHIFT), -}; - -/* - * path IDs - */ -enum sst_swm_inputs { - SST_SWM_IN_CODEC0 = (SST_PATH_INDEX_CODEC_IN0 | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_CODEC1 = (SST_PATH_INDEX_CODEC_IN1 | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_IN | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_IN | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_IN | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_VOIP = (SST_PATH_INDEX_VOIP_IN | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_PCM0 = (SST_PATH_INDEX_PCM0_IN | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_PCM1 = (SST_PATH_INDEX_PCM1_IN | SST_DEFAULT_CELL_NBR), - SST_SWM_IN_MEDIA0 = (SST_PATH_INDEX_MEDIA0_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ - SST_SWM_IN_MEDIA1 = (SST_PATH_INDEX_MEDIA1_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ - SST_SWM_IN_MEDIA2 = (SST_PATH_INDEX_MEDIA2_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ - SST_SWM_IN_MEDIA3 = (SST_PATH_INDEX_MEDIA3_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ - SST_SWM_IN_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR) -}; - -/* - * path IDs - */ -enum sst_swm_outputs { - SST_SWM_OUT_CODEC0 = (SST_PATH_INDEX_CODEC_OUT0 | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_CODEC1 = (SST_PATH_INDEX_CODEC_OUT1 | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_VOIP = (SST_PATH_INDEX_VOIP_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_PCM0 = (SST_PATH_INDEX_PCM0_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_PCM1 = (SST_PATH_INDEX_PCM1_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_PCM2 = (SST_PATH_INDEX_PCM2_OUT | SST_DEFAULT_CELL_NBR), - SST_SWM_OUT_MEDIA0 = (SST_PATH_INDEX_MEDIA0_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ - SST_SWM_OUT_MEDIA1 = (SST_PATH_INDEX_MEDIA1_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ - SST_SWM_OUT_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR), -}; - -enum sst_ipc_msg { - SST_IPC_IA_CMD = 1, - SST_IPC_IA_SET_PARAMS, - SST_IPC_IA_GET_PARAMS, -}; - -enum sst_cmd_type { - SST_CMD_BYTES_SET = 1, - SST_CMD_BYTES_GET = 2, -}; - -enum sst_task { - SST_TASK_SBA = 1, - SST_TASK_MMX = 3, -}; - -enum sst_type { - SST_TYPE_CMD = 1, - SST_TYPE_PARAMS, -}; - -enum sst_flag { - SST_FLAG_BLOCKED = 1, - SST_FLAG_NONBLOCK, -}; - -/* - * Enumeration for indexing the gain cells in VB_SET_GAIN DSP command - */ -enum sst_gain_index { - /* GAIN IDs for SB task start here */ - SST_GAIN_INDEX_CODEC_OUT0, - SST_GAIN_INDEX_CODEC_OUT1, - SST_GAIN_INDEX_CODEC_IN0, - SST_GAIN_INDEX_CODEC_IN1, - - SST_GAIN_INDEX_SPROT_LOOP_OUT, - SST_GAIN_INDEX_MEDIA_LOOP1_OUT, - SST_GAIN_INDEX_MEDIA_LOOP2_OUT, - - SST_GAIN_INDEX_PCM0_IN_LEFT, - SST_GAIN_INDEX_PCM0_IN_RIGHT, - - SST_GAIN_INDEX_PCM1_OUT_LEFT, - SST_GAIN_INDEX_PCM1_OUT_RIGHT, - SST_GAIN_INDEX_PCM1_IN_LEFT, - SST_GAIN_INDEX_PCM1_IN_RIGHT, - SST_GAIN_INDEX_PCM2_OUT_LEFT, - - SST_GAIN_INDEX_PCM2_OUT_RIGHT, - SST_GAIN_INDEX_VOIP_OUT, - SST_GAIN_INDEX_VOIP_IN, - - /* Gain IDs for MMX task start here */ - SST_GAIN_INDEX_MEDIA0_IN_LEFT, - SST_GAIN_INDEX_MEDIA0_IN_RIGHT, - SST_GAIN_INDEX_MEDIA1_IN_LEFT, - SST_GAIN_INDEX_MEDIA1_IN_RIGHT, - - SST_GAIN_INDEX_MEDIA2_IN_LEFT, - SST_GAIN_INDEX_MEDIA2_IN_RIGHT, - - SST_GAIN_INDEX_GAIN_END -}; - -/* - * Audio DSP module IDs specified by FW spec - * TODO: Update with all modules - */ -enum sst_module_id { - SST_MODULE_ID_PCM = 0x0001, - SST_MODULE_ID_MP3 = 0x0002, - SST_MODULE_ID_MP24 = 0x0003, - SST_MODULE_ID_AAC = 0x0004, - SST_MODULE_ID_AACP = 0x0005, - SST_MODULE_ID_EAACP = 0x0006, - SST_MODULE_ID_WMA9 = 0x0007, - SST_MODULE_ID_WMA10 = 0x0008, - SST_MODULE_ID_WMA10P = 0x0009, - SST_MODULE_ID_RA = 0x000A, - SST_MODULE_ID_DDAC3 = 0x000B, - SST_MODULE_ID_TRUE_HD = 0x000C, - SST_MODULE_ID_HD_PLUS = 0x000D, - - SST_MODULE_ID_SRC = 0x0064, - SST_MODULE_ID_DOWNMIX = 0x0066, - SST_MODULE_ID_GAIN_CELL = 0x0067, - SST_MODULE_ID_SPROT = 0x006D, - SST_MODULE_ID_BASS_BOOST = 0x006E, - SST_MODULE_ID_STEREO_WDNG = 0x006F, - SST_MODULE_ID_AV_REMOVAL = 0x0070, - SST_MODULE_ID_MIC_EQ = 0x0071, - SST_MODULE_ID_SPL = 0x0072, - SST_MODULE_ID_ALGO_VTSV = 0x0073, - SST_MODULE_ID_NR = 0x0076, - SST_MODULE_ID_BWX = 0x0077, - SST_MODULE_ID_DRP = 0x0078, - SST_MODULE_ID_MDRP = 0x0079, - - SST_MODULE_ID_ANA = 0x007A, - SST_MODULE_ID_AEC = 0x007B, - SST_MODULE_ID_NR_SNS = 0x007C, - SST_MODULE_ID_SER = 0x007D, - SST_MODULE_ID_AGC = 0x007E, - - SST_MODULE_ID_CNI = 0x007F, - SST_MODULE_ID_CONTEXT_ALGO_AWARE = 0x0080, - SST_MODULE_ID_FIR_24 = 0x0081, - SST_MODULE_ID_IIR_24 = 0x0082, - - SST_MODULE_ID_ASRC = 0x0083, - SST_MODULE_ID_TONE_GEN = 0x0084, - SST_MODULE_ID_BMF = 0x0086, - SST_MODULE_ID_EDL = 0x0087, - SST_MODULE_ID_GLC = 0x0088, - - SST_MODULE_ID_FIR_16 = 0x0089, - SST_MODULE_ID_IIR_16 = 0x008A, - SST_MODULE_ID_DNR = 0x008B, - - SST_MODULE_ID_VIRTUALIZER = 0x008C, - SST_MODULE_ID_VISUALIZATION = 0x008D, - SST_MODULE_ID_LOUDNESS_OPTIMIZER = 0x008E, - SST_MODULE_ID_REVERBERATION = 0x008F, - - SST_MODULE_ID_CNI_TX = 0x0090, - SST_MODULE_ID_REF_LINE = 0x0091, - SST_MODULE_ID_VOLUME = 0x0092, - SST_MODULE_ID_FILT_DCR = 0x0094, - SST_MODULE_ID_SLV = 0x009A, - SST_MODULE_ID_NLF = 0x009B, - SST_MODULE_ID_TNR = 0x009C, - SST_MODULE_ID_WNR = 0x009D, - - SST_MODULE_ID_LOG = 0xFF00, - - SST_MODULE_ID_TASK = 0xFFFF, -}; - -enum sst_cmd { - SBA_IDLE = 14, - SBA_VB_SET_SPEECH_PATH = 26, - MMX_SET_GAIN = 33, - SBA_VB_SET_GAIN = 33, - FBA_VB_RX_CNI = 35, - MMX_SET_GAIN_TIMECONST = 36, - SBA_VB_SET_TIMECONST = 36, - SBA_VB_START = 85, - SBA_SET_SWM = 114, - SBA_SET_MDRP = 116, - SBA_HW_SET_SSP = 117, - SBA_SET_MEDIA_LOOP_MAP = 118, - SBA_SET_MEDIA_PATH = 119, - MMX_SET_MEDIA_PATH = 119, - SBA_VB_LPRO = 126, - SBA_VB_SET_FIR = 128, - SBA_VB_SET_IIR = 129, - SBA_SET_SSP_SLOT_MAP = 130, -}; - -enum sst_dsp_switch { - SST_SWITCH_OFF = 0, - SST_SWITCH_ON = 3, -}; - -enum sst_path_switch { - SST_PATH_OFF = 0, - SST_PATH_ON = 1, -}; - -enum sst_swm_state { - SST_SWM_OFF = 0, - SST_SWM_ON = 3, -}; - -#define SST_FILL_LOCATION_IDS(dst, cell_idx, pipe_id) do { \ - dst.location_id.p.cell_nbr_idx = (cell_idx); \ - dst.location_id.p.path_id = (pipe_id); \ - } while (0) -#define SST_FILL_LOCATION_ID(dst, loc_id) (\ - dst.location_id.f = (loc_id)) -#define SST_FILL_MODULE_ID(dst, mod_id) (\ - dst.module_id = (mod_id)) - -#define SST_FILL_DESTINATION1(dst, id) do { \ - SST_FILL_LOCATION_ID(dst, (id) & 0xFFFF); \ - SST_FILL_MODULE_ID(dst, ((id) & 0xFFFF0000) >> 16); \ - } while (0) -#define SST_FILL_DESTINATION2(dst, loc_id, mod_id) do { \ - SST_FILL_LOCATION_ID(dst, loc_id); \ - SST_FILL_MODULE_ID(dst, mod_id); \ - } while (0) -#define SST_FILL_DESTINATION3(dst, cell_idx, path_id, mod_id) do { \ - SST_FILL_LOCATION_IDS(dst, cell_idx, path_id); \ - SST_FILL_MODULE_ID(dst, mod_id); \ - } while (0) - -#define SST_FILL_DESTINATION(level, dst, ...) \ - SST_FILL_DESTINATION##level(dst, __VA_ARGS__) -#define SST_FILL_DEFAULT_DESTINATION(dst) \ - SST_FILL_DESTINATION(2, dst, SST_DEFAULT_LOCATION_ID, SST_DEFAULT_MODULE_ID) - -struct sst_destination_id { - union sst_location_id { - struct { - u8 cell_nbr_idx; /* module index */ - u8 path_id; /* pipe_id */ - } __packed p; /* part */ - u16 f; /* full */ - } __packed location_id; - u16 module_id; -} __packed; -struct sst_dsp_header { - struct sst_destination_id dst; - u16 command_id; - u16 length; -} __packed; - -/* - * - * Common Commands - * - */ -struct sst_cmd_generic { - struct sst_dsp_header header; -} __packed; - -struct swm_input_ids { - struct sst_destination_id input_id; -} __packed; - -struct sst_cmd_set_swm { - struct sst_dsp_header header; - struct sst_destination_id output_id; - u16 switch_state; - u16 nb_inputs; - struct swm_input_ids input[SST_CMD_SWM_MAX_INPUTS]; -} __packed; - -struct sst_cmd_set_media_path { - struct sst_dsp_header header; - u16 switch_state; -} __packed; - -struct pcm_cfg { - u8 s_length:2; - u8 rate:3; - u8 format:3; -} __packed; - -struct sst_cmd_set_speech_path { - struct sst_dsp_header header; - u16 switch_state; - struct { - u16 rsvd:8; - struct pcm_cfg cfg; - } config; -} __packed; - -struct gain_cell { - struct sst_destination_id dest; - s16 cell_gain_left; - s16 cell_gain_right; - u16 gain_time_constant; -} __packed; - -#define NUM_GAIN_CELLS 1 -struct sst_cmd_set_gain_dual { - struct sst_dsp_header header; - u16 gain_cell_num; - struct gain_cell cell_gains[NUM_GAIN_CELLS]; -} __packed; -struct sst_cmd_set_params { - struct sst_destination_id dst; - u16 command_id; - char params[0]; -} __packed; - - -struct sst_cmd_sba_vb_start { - struct sst_dsp_header header; -} __packed; - -union sba_media_loop_params { - struct { - u16 rsvd:8; - struct pcm_cfg cfg; - } part; - u16 full; -} __packed; - -struct sst_cmd_sba_set_media_loop_map { - struct sst_dsp_header header; - u16 switch_state; - union sba_media_loop_params param; - u16 map; -} __packed; - -struct sst_cmd_tone_stop { - struct sst_dsp_header header; - u16 switch_state; -} __packed; - -enum sst_ssp_mode { - SSP_MODE_MASTER = 0, - SSP_MODE_SLAVE = 1, -}; - -enum sst_ssp_pcm_mode { - SSP_PCM_MODE_NORMAL = 0, - SSP_PCM_MODE_NETWORK = 1, -}; - -enum sst_ssp_duplex { - SSP_DUPLEX = 0, - SSP_RX = 1, - SSP_TX = 2, -}; - -enum sst_ssp_fs_frequency { - SSP_FS_8_KHZ = 0, - SSP_FS_16_KHZ = 1, - SSP_FS_44_1_KHZ = 2, - SSP_FS_48_KHZ = 3, -}; - -enum sst_ssp_fs_polarity { - SSP_FS_ACTIVE_LOW = 0, - SSP_FS_ACTIVE_HIGH = 1, -}; - -enum sst_ssp_protocol { - SSP_MODE_PCM = 0, - SSP_MODE_I2S = 1, -}; - -enum sst_ssp_port_id { - SSP_MODEM = 0, - SSP_BT = 1, - SSP_FM = 2, - SSP_CODEC = 3, -}; - -struct sst_cmd_sba_hw_set_ssp { - struct sst_dsp_header header; - u16 selection; /* 0:SSP0(def), 1:SSP1, 2:SSP2 */ - - u16 switch_state; - - u16 nb_bits_per_slots:6; /* 0-32 bits, 24 (def) */ - u16 nb_slots:4; /* 0-8: slots per frame */ - u16 mode:3; /* 0:Master, 1: Slave */ - u16 duplex:3; - - u16 active_tx_slot_map:8; /* Bit map, 0:off, 1:on */ - u16 reserved1:8; - - u16 active_rx_slot_map:8; /* Bit map 0: Off, 1:On */ - u16 reserved2:8; - - u16 frame_sync_frequency; - - u16 frame_sync_polarity:8; - u16 data_polarity:8; - - u16 frame_sync_width; /* 1 to N clocks */ - u16 ssp_protocol:8; - u16 start_delay:8; /* Start delay in terms of clock ticks */ -} __packed; - -#define SST_MAX_TDM_SLOTS 8 - -struct sst_param_sba_ssp_slot_map { - struct sst_dsp_header header; - - u16 param_id; - u16 param_len; - u16 ssp_index; - - u8 rx_slot_map[SST_MAX_TDM_SLOTS]; - u8 tx_slot_map[SST_MAX_TDM_SLOTS]; -} __packed; - -enum { - SST_PROBE_EXTRACTOR = 0, - SST_PROBE_INJECTOR = 1, -}; - -/**** widget defines *****/ - -#define SST_MODULE_GAIN 1 -#define SST_MODULE_ALGO 2 - -#define SST_FMT_MONO 0 -#define SST_FMT_STEREO 3 - -/* physical SSP numbers */ -enum { - SST_SSP0 = 0, - SST_SSP1, - SST_SSP2, - SST_SSP_LAST = SST_SSP2, -}; - -#define SST_NUM_SSPS (SST_SSP_LAST + 1) /* physical SSPs */ -#define SST_MAX_SSP_MUX 2 /* single SSP muxed between pipes */ -#define SST_MAX_SSP_DOMAINS 2 /* domains present in each pipe */ - -struct sst_module { - struct snd_kcontrol *kctl; - struct list_head node; -}; - -struct sst_ssp_config { - u8 ssp_id; - u8 bits_per_slot; - u8 slots; - u8 ssp_mode; - u8 pcm_mode; - u8 duplex; - u8 ssp_protocol; - u8 fs_frequency; - u8 active_slot_map; - u8 start_delay; - u16 fs_width; -}; - -struct sst_ssp_cfg { - const u8 ssp_number; - const int *mux_shift; - const int (*domain_shift)[SST_MAX_SSP_MUX]; - const struct sst_ssp_config (*ssp_config)[SST_MAX_SSP_MUX][SST_MAX_SSP_DOMAINS]; -}; - -struct sst_ids { - u16 location_id; - u16 module_id; - u8 task_id; - u8 format; - u8 reg; - const char *parent_wname; - struct snd_soc_dapm_widget *parent_w; - struct list_head algo_list; - struct list_head gain_list; - const struct sst_pcm_format *pcm_fmt; -}; - - -#define SST_AIF_IN(wname, wevent) \ -{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = NULL, \ - .reg = SND_SOC_NOPM, .shift = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ - .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ -} - -#define SST_AIF_OUT(wname, wevent) \ -{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = NULL, \ - .reg = SND_SOC_NOPM, .shift = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ - .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ -} - -#define SST_INPUT(wname, wevent) \ -{ .id = snd_soc_dapm_input, .name = wname, .sname = NULL, \ - .reg = SND_SOC_NOPM, .shift = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ - .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ -} - -#define SST_OUTPUT(wname, wevent) \ -{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ - .reg = SND_SOC_NOPM, .shift = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ - .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ -} - -#define SST_DAPM_OUTPUT(wname, wloc_id, wtask_id, wformat, wevent) \ -{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ - .reg = SND_SOC_NOPM, .shift = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ - .priv = (void *)&(struct sst_ids) { .location_id = wloc_id, .task_id = wtask_id,\ - .pcm_fmt = wformat, } \ -} - -#define SST_PATH(wname, wtask, wloc_id, wevent, wflags) \ -{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ - .kcontrol_news = NULL, .num_kcontrols = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = wflags, \ - .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, } \ -} - -#define SST_LINKED_PATH(wname, wtask, wloc_id, linked_wname, wevent, wflags) \ -{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ - .kcontrol_news = NULL, .num_kcontrols = 0, \ - .on_val = 1, .off_val = 0, \ - .event = wevent, .event_flags = wflags, \ - .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ - .parent_wname = linked_wname} \ -} - -#define SST_PATH_MEDIA_LOOP(wname, wtask, wloc_id, wformat, wevent, wflags) \ -{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ - .kcontrol_news = NULL, .num_kcontrols = 0, \ - .event = wevent, .event_flags = wflags, \ - .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ - .format = wformat,} \ -} - -/* output is triggered before input */ -#define SST_PATH_INPUT(name, task_id, loc_id, event) \ - SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) - -#define SST_PATH_LINKED_INPUT(name, task_id, loc_id, linked_wname, event) \ - SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) - -#define SST_PATH_OUTPUT(name, task_id, loc_id, event) \ - SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) - -#define SST_PATH_LINKED_OUTPUT(name, task_id, loc_id, linked_wname, event) \ - SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) - -#define SST_PATH_MEDIA_LOOP_OUTPUT(name, task_id, loc_id, format, event) \ - SST_PATH_MEDIA_LOOP(name, task_id, loc_id, format, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) - - -#define SST_SWM_MIXER(wname, wreg, wtask, wloc_id, wcontrols, wevent) \ -{ .id = snd_soc_dapm_mixer, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ - .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols),\ - .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | \ - SND_SOC_DAPM_POST_REG, \ - .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ - .reg = wreg } \ -} - -enum sst_gain_kcontrol_type { - SST_GAIN_TLV, - SST_GAIN_MUTE, - SST_GAIN_RAMP_DURATION, -}; - -struct sst_gain_mixer_control { - bool stereo; - enum sst_gain_kcontrol_type type; - struct sst_gain_value *gain_val; - int max; - int min; - u16 instance_id; - u16 module_id; - u16 pipe_id; - u16 task_id; - char pname[44]; - struct snd_soc_dapm_widget *w; -}; - -struct sst_gain_value { - u16 ramp_duration; - s16 l_gain; - s16 r_gain; - bool mute; -}; -#define SST_GAIN_VOLUME_DEFAULT (-1440) -#define SST_GAIN_RAMP_DURATION_DEFAULT 5 /* timeconstant */ -#define SST_GAIN_MUTE_DEFAULT true - -#define SST_GAIN_KCONTROL_TLV(xname, xhandler_get, xhandler_put, \ - xmod, xpipe, xinstance, xtask, tlv_array, xgain_val, \ - xmin, xmax, xpname) \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ - SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .tlv.p = (tlv_array), \ - .info = sst_gain_ctl_info,\ - .get = xhandler_get, .put = xhandler_put, \ - .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ - { .stereo = true, .max = xmax, .min = xmin, .type = SST_GAIN_TLV, \ - .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ - .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} - -#define SST_GAIN_KCONTROL_INT(xname, xhandler_get, xhandler_put, \ - xmod, xpipe, xinstance, xtask, xtype, xgain_val, \ - xmin, xmax, xpname) \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = sst_gain_ctl_info, \ - .get = xhandler_get, .put = xhandler_put, \ - .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ - { .stereo = false, .max = xmax, .min = xmin, .type = xtype, \ - .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ - .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} - -#define SST_GAIN_KCONTROL_BOOL(xname, xhandler_get, xhandler_put,\ - xmod, xpipe, xinstance, xtask, xgain_val, xpname) \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_bool_ext, \ - .get = xhandler_get, .put = xhandler_put, \ - .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ - { .stereo = false, .type = SST_GAIN_MUTE, \ - .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ - .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} -#define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \ - xpname " " xmname " " #xinstance " " xtype - -#define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \ - xpname " " xmname " " #xinstance " " xtype " " xsubmodule - -/* - * 3 Controls for each Gain module - * e.g. - pcm0_in Gain 0 Volume - * - pcm0_in Gain 0 Ramp Delay - * - pcm0_in Gain 0 Switch - */ -#define SST_GAIN_KCONTROLS(xpname, xmname, xmin_gain, xmax_gain, xmin_tc, xmax_tc, \ - xhandler_get, xhandler_put, \ - xmod, xpipe, xinstance, xtask, tlv_array, xgain_val) \ - { SST_GAIN_KCONTROL_INT(SST_CONTROL_NAME(xpname, xmname, xinstance, "Ramp Delay"), \ - xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, SST_GAIN_RAMP_DURATION, \ - xgain_val, xmin_tc, xmax_tc, xpname) }, \ - { SST_GAIN_KCONTROL_BOOL(SST_CONTROL_NAME(xpname, xmname, xinstance, "Switch"), \ - xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, \ - xgain_val, xpname) } ,\ - { SST_GAIN_KCONTROL_TLV(SST_CONTROL_NAME(xpname, xmname, xinstance, "Volume"), \ - xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, tlv_array, \ - xgain_val, xmin_gain, xmax_gain, xpname) } - -#define SST_GAIN_TC_MIN 5 -#define SST_GAIN_TC_MAX 5000 -#define SST_GAIN_MIN_VALUE -1440 /* in 0.1 DB units */ -#define SST_GAIN_MAX_VALUE 360 - -enum sst_algo_kcontrol_type { - SST_ALGO_PARAMS, - SST_ALGO_BYPASS, -}; - -struct sst_algo_control { - enum sst_algo_kcontrol_type type; - int max; - u16 module_id; - u16 pipe_id; - u16 task_id; - u16 cmd_id; - bool bypass; - unsigned char *params; - struct snd_soc_dapm_widget *w; -}; - -/* size of the control = size of params + size of length field */ -#define SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, xmod, xtask, xcmd) \ - (struct sst_algo_control){ \ - .max = xcount + sizeof(u16), .type = xtype, .module_id = xmod, \ - .pipe_id = xpipe, .task_id = xtask, .cmd_id = xcmd, \ - } - -#define SST_ALGO_KCONTROL(xname, xcount, xmod, xpipe, \ - xtask, xcmd, xtype, xinfo, xget, xput) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ - .name = xname, \ - .info = xinfo, .get = xget, .put = xput, \ - .private_value = (unsigned long)& \ - SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, \ - xmod, xtask, xcmd), \ -} - -#define SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, \ - xpipe, xinstance, xtask, xcmd) \ - SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "params"), \ - xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \ - sst_algo_bytes_ctl_info, \ - sst_algo_control_get, sst_algo_control_set) - -#define SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask) \ - SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "bypass"), \ - 0, xmod, xpipe, xtask, 0, SST_ALGO_BYPASS, \ - snd_soc_info_bool_ext, \ - sst_algo_control_get, sst_algo_control_set) - -#define SST_ALGO_BYPASS_PARAMS(xpname, xmname, xcount, xmod, xpipe, \ - xinstance, xtask, xcmd) \ - SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask), \ - SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, xpipe, xinstance, xtask, xcmd) - -#define SST_COMBO_ALGO_KCONTROL_BYTES(xpname, xmname, xsubmod, xcount, xmod, \ - xpipe, xinstance, xtask, xcmd) \ - SST_ALGO_KCONTROL(SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, "params", \ - xsubmod), \ - xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \ - sst_algo_bytes_ctl_info, \ - sst_algo_control_get, sst_algo_control_set) - - -struct sst_enum { - bool tx; - unsigned short reg; - unsigned int max; - const char * const *texts; - struct snd_soc_dapm_widget *w; -}; - -/* only 4 slots/channels supported atm */ -#define SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts) \ - (struct sst_enum){ .reg = s_ch_no, .tx = is_tx, .max = 4+1, .texts = xtexts, } - -#define SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name) \ - xpname " " xmname " " s_ch_name - -#define SST_SSP_SLOT_CTL(xpname, xmname, s_ch_name, s_ch_no, is_tx, xtexts, xget, xput) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ - .name = SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name), \ - .info = sst_slot_enum_info, \ - .get = xget, .put = xput, \ - .private_value = (unsigned long)&SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts), \ -} - -#define SST_MUX_CTL_NAME(xpname, xinstance) \ - xpname " " #xinstance - -#define SST_SSP_MUX_ENUM(xreg, xshift, xtexts) \ - (struct soc_enum) SOC_ENUM_DOUBLE(xreg, xshift, xshift, ARRAY_SIZE(xtexts), xtexts) - -#define SST_SSP_MUX_CTL(xpname, xinstance, xreg, xshift, xtexts) \ - SOC_DAPM_ENUM(SST_MUX_CTL_NAME(xpname, xinstance), \ - SST_SSP_MUX_ENUM(xreg, xshift, xtexts)) - -#endif diff --git a/sound/soc/intel/sst-mfld-dsp.h b/sound/soc/intel/sst-mfld-dsp.h deleted file mode 100644 index 4257263..0000000 --- a/sound/soc/intel/sst-mfld-dsp.h +++ /dev/null @@ -1,533 +0,0 @@ -#ifndef __SST_MFLD_DSP_H__ -#define __SST_MFLD_DSP_H__ -/* - * sst_mfld_dsp.h - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corporation - * Authors: Vinod Koul - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -#define SST_MAX_BIN_BYTES 1024 - -#define MAX_DBG_RW_BYTES 80 -#define MAX_NUM_SCATTER_BUFFERS 8 -#define MAX_LOOP_BACK_DWORDS 8 -/* IPC base address and mailbox, timestamp offsets */ -#define SST_MAILBOX_SIZE 0x0400 -#define SST_MAILBOX_SEND 0x0000 -#define SST_TIME_STAMP 0x1800 -#define SST_TIME_STAMP_MRFLD 0x800 -#define SST_RESERVED_OFFSET 0x1A00 -#define SST_SCU_LPE_MAILBOX 0x1000 -#define SST_LPE_SCU_MAILBOX 0x1400 -#define SST_SCU_LPE_LOG_BUF (SST_SCU_LPE_MAILBOX+16) -#define PROCESS_MSG 0x80 - -/* Message ID's for IPC messages */ -/* Bits B7: SST or IA/SC ; B6-B4: Msg Category; B3-B0: Msg Type */ - -/* I2L Firmware/Codec Download msgs */ -#define IPC_IA_PREP_LIB_DNLD 0x01 -#define IPC_IA_LIB_DNLD_CMPLT 0x02 -#define IPC_IA_GET_FW_VERSION 0x04 -#define IPC_IA_GET_FW_BUILD_INF 0x05 -#define IPC_IA_GET_FW_INFO 0x06 -#define IPC_IA_GET_FW_CTXT 0x07 -#define IPC_IA_SET_FW_CTXT 0x08 -#define IPC_IA_PREPARE_SHUTDOWN 0x31 -/* I2L Codec Config/control msgs */ -#define IPC_PREP_D3 0x10 -#define IPC_IA_SET_CODEC_PARAMS 0x10 -#define IPC_IA_GET_CODEC_PARAMS 0x11 -#define IPC_IA_SET_PPP_PARAMS 0x12 -#define IPC_IA_GET_PPP_PARAMS 0x13 -#define IPC_SST_PERIOD_ELAPSED_MRFLD 0xA -#define IPC_IA_ALG_PARAMS 0x1A -#define IPC_IA_TUNING_PARAMS 0x1B -#define IPC_IA_SET_RUNTIME_PARAMS 0x1C -#define IPC_IA_SET_PARAMS 0x1 -#define IPC_IA_GET_PARAMS 0x2 - -#define IPC_EFFECTS_CREATE 0xE -#define IPC_EFFECTS_DESTROY 0xF - -/* I2L Stream config/control msgs */ -#define IPC_IA_ALLOC_STREAM_MRFLD 0x2 -#define IPC_IA_ALLOC_STREAM 0x20 /* Allocate a stream ID */ -#define IPC_IA_FREE_STREAM_MRFLD 0x03 -#define IPC_IA_FREE_STREAM 0x21 /* Free the stream ID */ -#define IPC_IA_SET_STREAM_PARAMS 0x22 -#define IPC_IA_SET_STREAM_PARAMS_MRFLD 0x12 -#define IPC_IA_GET_STREAM_PARAMS 0x23 -#define IPC_IA_PAUSE_STREAM 0x24 -#define IPC_IA_PAUSE_STREAM_MRFLD 0x4 -#define IPC_IA_RESUME_STREAM 0x25 -#define IPC_IA_RESUME_STREAM_MRFLD 0x5 -#define IPC_IA_DROP_STREAM 0x26 -#define IPC_IA_DROP_STREAM_MRFLD 0x07 -#define IPC_IA_DRAIN_STREAM 0x27 /* Short msg with str_id */ -#define IPC_IA_DRAIN_STREAM_MRFLD 0x8 -#define IPC_IA_CONTROL_ROUTING 0x29 -#define IPC_IA_VTSV_UPDATE_MODULES 0x20 -#define IPC_IA_VTSV_DETECTED 0x21 - -#define IPC_IA_START_STREAM_MRFLD 0X06 -#define IPC_IA_START_STREAM 0x30 /* Short msg with str_id */ - -#define IPC_IA_SET_GAIN_MRFLD 0x21 -/* Debug msgs */ -#define IPC_IA_DBG_MEM_READ 0x40 -#define IPC_IA_DBG_MEM_WRITE 0x41 -#define IPC_IA_DBG_LOOP_BACK 0x42 -#define IPC_IA_DBG_LOG_ENABLE 0x45 -#define IPC_IA_DBG_SET_PROBE_PARAMS 0x47 - -/* L2I Firmware/Codec Download msgs */ -#define IPC_IA_FW_INIT_CMPLT 0x81 -#define IPC_IA_FW_INIT_CMPLT_MRFLD 0x01 -#define IPC_IA_FW_ASYNC_ERR_MRFLD 0x11 - -/* L2I Codec Config/control msgs */ -#define IPC_SST_FRAGMENT_ELPASED 0x90 /* Request IA more data */ - -#define IPC_SST_BUF_UNDER_RUN 0x92 /* PB Under run and stopped */ -#define IPC_SST_BUF_OVER_RUN 0x93 /* CAP Under run and stopped */ -#define IPC_SST_DRAIN_END 0x94 /* PB Drain complete and stopped */ -#define IPC_SST_CHNGE_SSP_PARAMS 0x95 /* PB SSP parameters changed */ -#define IPC_SST_STREAM_PROCESS_FATAL_ERR 0x96/* error in processing a stream */ -#define IPC_SST_PERIOD_ELAPSED 0x97 /* period elapsed */ - -#define IPC_SST_ERROR_EVENT 0x99 /* Buffer over run occurred */ -/* L2S messages */ -#define IPC_SC_DDR_LINK_UP 0xC0 -#define IPC_SC_DDR_LINK_DOWN 0xC1 -#define IPC_SC_SET_LPECLK_REQ 0xC2 -#define IPC_SC_SSP_BIT_BANG 0xC3 - -/* L2I Error reporting msgs */ -#define IPC_IA_MEM_ALLOC_FAIL 0xE0 -#define IPC_IA_PROC_ERR 0xE1 /* error in processing a - stream can be used by playback and - capture modules */ - -/* L2I Debug msgs */ -#define IPC_IA_PRINT_STRING 0xF0 - -/* Buffer under-run */ -#define IPC_IA_BUF_UNDER_RUN_MRFLD 0x0B - -/* Mrfld specific defines: - * For asynchronous messages(INIT_CMPLT, PERIOD_ELAPSED, ASYNC_ERROR) - * received from FW, the format is: - * - IPC High: pvt_id is set to zero. Always short message. - * - msg_id is in lower 16-bits of IPC low payload. - * - pipe_id is in higher 16-bits of IPC low payload for period_elapsed. - * - error id is in higher 16-bits of IPC low payload for async errors. - */ -#define SST_ASYNC_DRV_ID 0 - -/* Command Response or Acknowledge message to any IPC message will have - * same message ID and stream ID information which is sent. - * There is no specific Ack message ID. The data field is used as response - * meaning. - */ -enum ackData { - IPC_ACK_SUCCESS = 0, - IPC_ACK_FAILURE, -}; - -enum ipc_ia_msg_id { - IPC_CMD = 1, /*!< Task Control message ID */ - IPC_SET_PARAMS = 2,/*!< Task Set param message ID */ - IPC_GET_PARAMS = 3, /*!< Task Get param message ID */ - IPC_INVALID = 0xFF, /*! - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include "sst-mfld-platform.h" - -/* compress stream operations */ -static void sst_compr_fragment_elapsed(void *arg) -{ - struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; - - pr_debug("fragment elapsed by driver\n"); - if (cstream) - snd_compr_fragment_elapsed(cstream); -} - -static void sst_drain_notify(void *arg) -{ - struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; - - pr_debug("drain notify by driver\n"); - if (cstream) - snd_compr_drain_notify(cstream); -} - -static int sst_platform_compr_open(struct snd_compr_stream *cstream) -{ - - int ret_val = 0; - struct snd_compr_runtime *runtime = cstream->runtime; - struct sst_runtime_stream *stream; - - stream = kzalloc(sizeof(*stream), GFP_KERNEL); - if (!stream) - return -ENOMEM; - - spin_lock_init(&stream->status_lock); - - /* get the sst ops */ - if (!sst || !try_module_get(sst->dev->driver->owner)) { - pr_err("no device available to run\n"); - ret_val = -ENODEV; - goto out_ops; - } - stream->compr_ops = sst->compr_ops; - stream->id = 0; - - /* Turn on LPE */ - sst->compr_ops->power(sst->dev, true); - - sst_set_stream_status(stream, SST_PLATFORM_INIT); - runtime->private_data = stream; - return 0; -out_ops: - kfree(stream); - return ret_val; -} - -static int sst_platform_compr_free(struct snd_compr_stream *cstream) -{ - struct sst_runtime_stream *stream; - int ret_val = 0, str_id; - - stream = cstream->runtime->private_data; - /* Turn off LPE */ - sst->compr_ops->power(sst->dev, false); - - /*need to check*/ - str_id = stream->id; - if (str_id) - ret_val = stream->compr_ops->close(sst->dev, str_id); - module_put(sst->dev->driver->owner); - kfree(stream); - pr_debug("%s: %d\n", __func__, ret_val); - return 0; -} - -static int sst_platform_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params) -{ - struct sst_runtime_stream *stream; - int retval; - struct snd_sst_params str_params; - struct sst_compress_cb cb; - struct snd_soc_pcm_runtime *rtd = cstream->private_data; - struct snd_soc_platform *platform = rtd->platform; - struct sst_data *ctx = snd_soc_platform_get_drvdata(platform); - - stream = cstream->runtime->private_data; - /* construct fw structure for this*/ - memset(&str_params, 0, sizeof(str_params)); - - /* fill the device type and stream id to pass to SST driver */ - retval = sst_fill_stream_params(cstream, ctx, &str_params, true); - pr_debug("compr_set_params: fill stream params ret_val = 0x%x\n", retval); - if (retval < 0) - return retval; - - switch (params->codec.id) { - case SND_AUDIOCODEC_MP3: { - str_params.codec = SST_CODEC_TYPE_MP3; - str_params.sparams.uc.mp3_params.num_chan = params->codec.ch_in; - str_params.sparams.uc.mp3_params.pcm_wd_sz = 16; - break; - } - - case SND_AUDIOCODEC_AAC: { - str_params.codec = SST_CODEC_TYPE_AAC; - str_params.sparams.uc.aac_params.num_chan = params->codec.ch_in; - str_params.sparams.uc.aac_params.pcm_wd_sz = 16; - if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS) - str_params.sparams.uc.aac_params.bs_format = - AAC_BIT_STREAM_ADTS; - else if (params->codec.format == SND_AUDIOSTREAMFORMAT_RAW) - str_params.sparams.uc.aac_params.bs_format = - AAC_BIT_STREAM_RAW; - else { - pr_err("Undefined format%d\n", params->codec.format); - return -EINVAL; - } - str_params.sparams.uc.aac_params.externalsr = - params->codec.sample_rate; - break; - } - - default: - pr_err("codec not supported, id =%d\n", params->codec.id); - return -EINVAL; - } - - str_params.aparams.ring_buf_info[0].addr = - virt_to_phys(cstream->runtime->buffer); - str_params.aparams.ring_buf_info[0].size = - cstream->runtime->buffer_size; - str_params.aparams.sg_count = 1; - str_params.aparams.frag_size = cstream->runtime->fragment_size; - - cb.param = cstream; - cb.compr_cb = sst_compr_fragment_elapsed; - cb.drain_cb_param = cstream; - cb.drain_notify = sst_drain_notify; - - retval = stream->compr_ops->open(sst->dev, &str_params, &cb); - if (retval < 0) { - pr_err("stream allocation failed %d\n", retval); - return retval; - } - - stream->id = retval; - return 0; -} - -static int sst_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) -{ - struct sst_runtime_stream *stream = cstream->runtime->private_data; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - if (stream->compr_ops->stream_start) - return stream->compr_ops->stream_start(sst->dev, stream->id); - case SNDRV_PCM_TRIGGER_STOP: - if (stream->compr_ops->stream_drop) - return stream->compr_ops->stream_drop(sst->dev, stream->id); - case SND_COMPR_TRIGGER_DRAIN: - if (stream->compr_ops->stream_drain) - return stream->compr_ops->stream_drain(sst->dev, stream->id); - case SND_COMPR_TRIGGER_PARTIAL_DRAIN: - if (stream->compr_ops->stream_partial_drain) - return stream->compr_ops->stream_partial_drain(sst->dev, stream->id); - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (stream->compr_ops->stream_pause) - return stream->compr_ops->stream_pause(sst->dev, stream->id); - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (stream->compr_ops->stream_pause_release) - return stream->compr_ops->stream_pause_release(sst->dev, stream->id); - default: - return -EINVAL; - } -} - -static int sst_platform_compr_pointer(struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp) -{ - struct sst_runtime_stream *stream; - - stream = cstream->runtime->private_data; - stream->compr_ops->tstamp(sst->dev, stream->id, tstamp); - tstamp->byte_offset = tstamp->copied_total % - (u32)cstream->runtime->buffer_size; - pr_debug("calc bytes offset/copied bytes as %d\n", tstamp->byte_offset); - return 0; -} - -static int sst_platform_compr_ack(struct snd_compr_stream *cstream, - size_t bytes) -{ - struct sst_runtime_stream *stream; - - stream = cstream->runtime->private_data; - stream->compr_ops->ack(sst->dev, stream->id, (unsigned long)bytes); - stream->bytes_written += bytes; - - return 0; -} - -static int sst_platform_compr_get_caps(struct snd_compr_stream *cstream, - struct snd_compr_caps *caps) -{ - struct sst_runtime_stream *stream = - cstream->runtime->private_data; - - return stream->compr_ops->get_caps(caps); -} - -static int sst_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, - struct snd_compr_codec_caps *codec) -{ - struct sst_runtime_stream *stream = - cstream->runtime->private_data; - - return stream->compr_ops->get_codec_caps(codec); -} - -static int sst_platform_compr_set_metadata(struct snd_compr_stream *cstream, - struct snd_compr_metadata *metadata) -{ - struct sst_runtime_stream *stream = - cstream->runtime->private_data; - - return stream->compr_ops->set_metadata(sst->dev, stream->id, metadata); -} - -struct snd_compr_ops sst_platform_compr_ops = { - - .open = sst_platform_compr_open, - .free = sst_platform_compr_free, - .set_params = sst_platform_compr_set_params, - .set_metadata = sst_platform_compr_set_metadata, - .trigger = sst_platform_compr_trigger, - .pointer = sst_platform_compr_pointer, - .ack = sst_platform_compr_ack, - .get_caps = sst_platform_compr_get_caps, - .get_codec_caps = sst_platform_compr_get_codec_caps, -}; diff --git a/sound/soc/intel/sst-mfld-platform-pcm.c b/sound/soc/intel/sst-mfld-platform-pcm.c deleted file mode 100644 index 2fbaf2c..0000000 --- a/sound/soc/intel/sst-mfld-platform-pcm.c +++ /dev/null @@ -1,804 +0,0 @@ -/* - * sst_mfld_platform.c - Intel MID Platform driver - * - * Copyright (C) 2010-2014 Intel Corp - * Author: Vinod Koul - * Author: Harsha Priya - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "sst-mfld-platform.h" -#include "sst-atom-controls.h" - -struct sst_device *sst; -static DEFINE_MUTEX(sst_lock); -extern struct snd_compr_ops sst_platform_compr_ops; - -int sst_register_dsp(struct sst_device *dev) -{ - if (WARN_ON(!dev)) - return -EINVAL; - if (!try_module_get(dev->dev->driver->owner)) - return -ENODEV; - mutex_lock(&sst_lock); - if (sst) { - dev_err(dev->dev, "we already have a device %s\n", sst->name); - module_put(dev->dev->driver->owner); - mutex_unlock(&sst_lock); - return -EEXIST; - } - dev_dbg(dev->dev, "registering device %s\n", dev->name); - sst = dev; - mutex_unlock(&sst_lock); - return 0; -} -EXPORT_SYMBOL_GPL(sst_register_dsp); - -int sst_unregister_dsp(struct sst_device *dev) -{ - if (WARN_ON(!dev)) - return -EINVAL; - if (dev != sst) - return -EINVAL; - - mutex_lock(&sst_lock); - - if (!sst) { - mutex_unlock(&sst_lock); - return -EIO; - } - - module_put(sst->dev->driver->owner); - dev_dbg(dev->dev, "unreg %s\n", sst->name); - sst = NULL; - mutex_unlock(&sst_lock); - return 0; -} -EXPORT_SYMBOL_GPL(sst_unregister_dsp); - -static struct snd_pcm_hardware sst_platform_pcm_hw = { - .info = (SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_DOUBLE | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME | - SNDRV_PCM_INFO_MMAP| - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_SYNC_START), - .buffer_bytes_max = SST_MAX_BUFFER, - .period_bytes_min = SST_MIN_PERIOD_BYTES, - .period_bytes_max = SST_MAX_PERIOD_BYTES, - .periods_min = SST_MIN_PERIODS, - .periods_max = SST_MAX_PERIODS, - .fifo_size = SST_FIFO_SIZE, -}; - -static struct sst_dev_stream_map dpcm_strm_map[] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, /* Reserved, not in use */ - {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA1_IN, SST_TASK_ID_MEDIA, 0}, - {MERR_DPCM_COMPR, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA0_IN, SST_TASK_ID_MEDIA, 0}, - {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0}, -}; - -static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) -{ - - return sst_send_pipe_gains(dai, stream, mute); -} - -/* helper functions */ -void sst_set_stream_status(struct sst_runtime_stream *stream, - int state) -{ - unsigned long flags; - spin_lock_irqsave(&stream->status_lock, flags); - stream->stream_status = state; - spin_unlock_irqrestore(&stream->status_lock, flags); -} - -static inline int sst_get_stream_status(struct sst_runtime_stream *stream) -{ - int state; - unsigned long flags; - - spin_lock_irqsave(&stream->status_lock, flags); - state = stream->stream_status; - spin_unlock_irqrestore(&stream->status_lock, flags); - return state; -} - -static void sst_fill_alloc_params(struct snd_pcm_substream *substream, - struct snd_sst_alloc_params_ext *alloc_param) -{ - unsigned int channels; - snd_pcm_uframes_t period_size; - ssize_t periodbytes; - ssize_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream); - u32 buffer_addr = virt_to_phys(substream->dma_buffer.area); - - channels = substream->runtime->channels; - period_size = substream->runtime->period_size; - periodbytes = samples_to_bytes(substream->runtime, period_size); - alloc_param->ring_buf_info[0].addr = buffer_addr; - alloc_param->ring_buf_info[0].size = buffer_bytes; - alloc_param->sg_count = 1; - alloc_param->reserved = 0; - alloc_param->frag_size = periodbytes * channels; - -} -static void sst_fill_pcm_params(struct snd_pcm_substream *substream, - struct snd_sst_stream_params *param) -{ - param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; - param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; - param->uc.pcm_params.sfreq = substream->runtime->rate; - - /* PCM stream via ALSA interface */ - param->uc.pcm_params.use_offload_path = 0; - param->uc.pcm_params.reserved2 = 0; - memset(param->uc.pcm_params.channel_map, 0, sizeof(u8)); - -} - -static int sst_get_stream_mapping(int dev, int sdev, int dir, - struct sst_dev_stream_map *map, int size) -{ - int i; - - if (map == NULL) - return -EINVAL; - - - /* index 0 is not used in stream map */ - for (i = 1; i < size; i++) { - if ((map[i].dev_num == dev) && (map[i].direction == dir)) - return i; - } - return 0; -} - -int sst_fill_stream_params(void *substream, - const struct sst_data *ctx, struct snd_sst_params *str_params, bool is_compress) -{ - int map_size; - int index; - struct sst_dev_stream_map *map; - struct snd_pcm_substream *pstream = NULL; - struct snd_compr_stream *cstream = NULL; - - map = ctx->pdata->pdev_strm_map; - map_size = ctx->pdata->strm_map_size; - - if (is_compress == true) - cstream = (struct snd_compr_stream *)substream; - else - pstream = (struct snd_pcm_substream *)substream; - - str_params->stream_type = SST_STREAM_TYPE_MUSIC; - - /* For pcm streams */ - if (pstream) { - index = sst_get_stream_mapping(pstream->pcm->device, - pstream->number, pstream->stream, - map, map_size); - if (index <= 0) - return -EINVAL; - - str_params->stream_id = index; - str_params->device_type = map[index].device_id; - str_params->task = map[index].task_id; - - str_params->ops = (u8)pstream->stream; - } - - if (cstream) { - index = sst_get_stream_mapping(cstream->device->device, - 0, cstream->direction, - map, map_size); - if (index <= 0) - return -EINVAL; - str_params->stream_id = index; - str_params->device_type = map[index].device_id; - str_params->task = map[index].task_id; - - str_params->ops = (u8)cstream->direction; - } - return 0; -} - -static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct sst_runtime_stream *stream = - substream->runtime->private_data; - struct snd_sst_stream_params param = {{{0,},},}; - struct snd_sst_params str_params = {0}; - struct snd_sst_alloc_params_ext alloc_params = {0}; - int ret_val = 0; - struct sst_data *ctx = snd_soc_dai_get_drvdata(dai); - - /* set codec params and inform SST driver the same */ - sst_fill_pcm_params(substream, ¶m); - sst_fill_alloc_params(substream, &alloc_params); - substream->runtime->dma_area = substream->dma_buffer.area; - str_params.sparams = param; - str_params.aparams = alloc_params; - str_params.codec = SST_CODEC_TYPE_PCM; - - /* fill the device type and stream id to pass to SST driver */ - ret_val = sst_fill_stream_params(substream, ctx, &str_params, false); - if (ret_val < 0) - return ret_val; - - stream->stream_info.str_id = str_params.stream_id; - - ret_val = stream->ops->open(sst->dev, &str_params); - if (ret_val <= 0) - return ret_val; - - - return ret_val; -} - -static void sst_period_elapsed(void *arg) -{ - struct snd_pcm_substream *substream = arg; - struct sst_runtime_stream *stream; - int status; - - if (!substream || !substream->runtime) - return; - stream = substream->runtime->private_data; - if (!stream) - return; - status = sst_get_stream_status(stream); - if (status != SST_PLATFORM_RUNNING) - return; - snd_pcm_period_elapsed(substream); -} - -static int sst_platform_init_stream(struct snd_pcm_substream *substream) -{ - struct sst_runtime_stream *stream = - substream->runtime->private_data; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - int ret_val; - - dev_dbg(rtd->dev, "setting buffer ptr param\n"); - sst_set_stream_status(stream, SST_PLATFORM_INIT); - stream->stream_info.period_elapsed = sst_period_elapsed; - stream->stream_info.arg = substream; - stream->stream_info.buffer_ptr = 0; - stream->stream_info.sfreq = substream->runtime->rate; - ret_val = stream->ops->stream_init(sst->dev, &stream->stream_info); - if (ret_val) - dev_err(rtd->dev, "control_set ret error %d\n", ret_val); - return ret_val; - -} - -static int power_up_sst(struct sst_runtime_stream *stream) -{ - return stream->ops->power(sst->dev, true); -} - -static void power_down_sst(struct sst_runtime_stream *stream) -{ - stream->ops->power(sst->dev, false); -} - -static int sst_media_open(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - int ret_val = 0; - struct snd_pcm_runtime *runtime = substream->runtime; - struct sst_runtime_stream *stream; - - stream = kzalloc(sizeof(*stream), GFP_KERNEL); - if (!stream) - return -ENOMEM; - spin_lock_init(&stream->status_lock); - - /* get the sst ops */ - mutex_lock(&sst_lock); - if (!sst || - !try_module_get(sst->dev->driver->owner)) { - dev_err(dai->dev, "no device available to run\n"); - ret_val = -ENODEV; - goto out_ops; - } - stream->ops = sst->ops; - mutex_unlock(&sst_lock); - - stream->stream_info.str_id = 0; - - stream->stream_info.arg = substream; - /* allocate memory for SST API set */ - runtime->private_data = stream; - - ret_val = power_up_sst(stream); - if (ret_val < 0) - return ret_val; - - /* Make sure, that the period size is always even */ - snd_pcm_hw_constraint_step(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_PERIODS, 2); - - return snd_pcm_hw_constraint_integer(runtime, - SNDRV_PCM_HW_PARAM_PERIODS); -out_ops: - kfree(stream); - mutex_unlock(&sst_lock); - return ret_val; -} - -static void sst_media_close(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct sst_runtime_stream *stream; - int ret_val = 0, str_id; - - stream = substream->runtime->private_data; - power_down_sst(stream); - - str_id = stream->stream_info.str_id; - if (str_id) - ret_val = stream->ops->close(sst->dev, str_id); - module_put(sst->dev->driver->owner); - kfree(stream); -} - -static inline unsigned int get_current_pipe_id(struct snd_soc_dai *dai, - struct snd_pcm_substream *substream) -{ - struct sst_data *sst = snd_soc_dai_get_drvdata(dai); - struct sst_dev_stream_map *map = sst->pdata->pdev_strm_map; - struct sst_runtime_stream *stream = - substream->runtime->private_data; - u32 str_id = stream->stream_info.str_id; - unsigned int pipe_id; - - pipe_id = map[str_id].device_id; - - dev_dbg(dai->dev, "got pipe_id = %#x for str_id = %d\n", - pipe_id, str_id); - return pipe_id; -} - -static int sst_media_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct sst_runtime_stream *stream; - int ret_val = 0, str_id; - - stream = substream->runtime->private_data; - str_id = stream->stream_info.str_id; - if (stream->stream_info.str_id) { - ret_val = stream->ops->stream_drop(sst->dev, str_id); - return ret_val; - } - - ret_val = sst_platform_alloc_stream(substream, dai); - if (ret_val <= 0) - return ret_val; - snprintf(substream->pcm->id, sizeof(substream->pcm->id), - "%d", stream->stream_info.str_id); - - ret_val = sst_platform_init_stream(substream); - if (ret_val) - return ret_val; - substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; - return ret_val; -} - -static int sst_media_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); - return 0; -} - -static int sst_media_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - return snd_pcm_lib_free_pages(substream); -} - -static int sst_enable_ssp(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - int ret = 0; - - if (!dai->active) { - ret = sst_handle_vb_timer(dai, true); - if (ret) - return ret; - ret = send_ssp_cmd(dai, dai->name, 1); - } - return ret; -} - -static void sst_disable_ssp(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - if (!dai->active) { - send_ssp_cmd(dai, dai->name, 0); - sst_handle_vb_timer(dai, false); - } -} - -static struct snd_soc_dai_ops sst_media_dai_ops = { - .startup = sst_media_open, - .shutdown = sst_media_close, - .prepare = sst_media_prepare, - .hw_params = sst_media_hw_params, - .hw_free = sst_media_hw_free, - .mute_stream = sst_media_digital_mute, -}; - -static struct snd_soc_dai_ops sst_compr_dai_ops = { - .mute_stream = sst_media_digital_mute, -}; - -static struct snd_soc_dai_ops sst_be_dai_ops = { - .startup = sst_enable_ssp, - .shutdown = sst_disable_ssp, -}; - -static struct snd_soc_dai_driver sst_platform_dai[] = { -{ - .name = "media-cpu-dai", - .ops = &sst_media_dai_ops, - .playback = { - .stream_name = "Headset Playback", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "Headset Capture", - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, -}, -{ - .name = "compress-cpu-dai", - .compress_dai = 1, - .ops = &sst_compr_dai_ops, - .playback = { - .stream_name = "Compress Playback", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, -}, -/* BE CPU Dais */ -{ - .name = "ssp0-port", - .ops = &sst_be_dai_ops, - .playback = { - .stream_name = "ssp0 Tx", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "ssp0 Rx", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, -}, -{ - .name = "ssp1-port", - .ops = &sst_be_dai_ops, - .playback = { - .stream_name = "ssp1 Tx", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "ssp1 Rx", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, -}, -{ - .name = "ssp2-port", - .ops = &sst_be_dai_ops, - .playback = { - .stream_name = "ssp2 Tx", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "ssp2 Rx", - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, -}, -}; - -static int sst_platform_open(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime; - - if (substream->pcm->internal) - return 0; - - runtime = substream->runtime; - runtime->hw = sst_platform_pcm_hw; - return 0; -} - -static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, - int cmd) -{ - int ret_val = 0, str_id; - struct sst_runtime_stream *stream; - int status; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - dev_dbg(rtd->dev, "sst_platform_pcm_trigger called\n"); - if (substream->pcm->internal) - return 0; - stream = substream->runtime->private_data; - str_id = stream->stream_info.str_id; - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - dev_dbg(rtd->dev, "sst: Trigger Start\n"); - status = SST_PLATFORM_RUNNING; - stream->stream_info.arg = substream; - ret_val = stream->ops->stream_start(sst->dev, str_id); - break; - case SNDRV_PCM_TRIGGER_STOP: - dev_dbg(rtd->dev, "sst: in stop\n"); - status = SST_PLATFORM_DROPPED; - ret_val = stream->ops->stream_drop(sst->dev, str_id); - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_SUSPEND: - dev_dbg(rtd->dev, "sst: in pause\n"); - status = SST_PLATFORM_PAUSED; - ret_val = stream->ops->stream_pause(sst->dev, str_id); - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: - dev_dbg(rtd->dev, "sst: in pause release\n"); - status = SST_PLATFORM_RUNNING; - ret_val = stream->ops->stream_pause_release(sst->dev, str_id); - break; - default: - return -EINVAL; - } - - if (!ret_val) - sst_set_stream_status(stream, status); - - return ret_val; -} - - -static snd_pcm_uframes_t sst_platform_pcm_pointer - (struct snd_pcm_substream *substream) -{ - struct sst_runtime_stream *stream; - int ret_val, status; - struct pcm_stream_info *str_info; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - stream = substream->runtime->private_data; - status = sst_get_stream_status(stream); - if (status == SST_PLATFORM_INIT) - return 0; - str_info = &stream->stream_info; - ret_val = stream->ops->stream_read_tstamp(sst->dev, str_info); - if (ret_val) { - dev_err(rtd->dev, "sst: error code = %d\n", ret_val); - return ret_val; - } - substream->runtime->delay = str_info->pcm_delay; - return str_info->buffer_ptr; -} - -static struct snd_pcm_ops sst_platform_ops = { - .open = sst_platform_open, - .ioctl = snd_pcm_lib_ioctl, - .trigger = sst_platform_pcm_trigger, - .pointer = sst_platform_pcm_pointer, -}; - -static int sst_pcm_new(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dai *dai = rtd->cpu_dai; - struct snd_pcm *pcm = rtd->pcm; - int retval = 0; - - if (dai->driver->playback.channels_min || - dai->driver->capture.channels_min) { - retval = snd_pcm_lib_preallocate_pages_for_all(pcm, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_DMA), - SST_MIN_BUFFER, SST_MAX_BUFFER); - if (retval) { - dev_err(rtd->dev, "dma buffer allocationf fail\n"); - return retval; - } - } - return retval; -} - -static int sst_soc_probe(struct snd_soc_platform *platform) -{ - struct sst_data *drv = dev_get_drvdata(platform->dev); - - drv->soc_card = platform->component.card; - return sst_dsp_init_v2_dpcm(platform); -} - -static struct snd_soc_platform_driver sst_soc_platform_drv = { - .probe = sst_soc_probe, - .ops = &sst_platform_ops, - .compr_ops = &sst_platform_compr_ops, - .pcm_new = sst_pcm_new, -}; - -static const struct snd_soc_component_driver sst_component = { - .name = "sst", -}; - - -static int sst_platform_probe(struct platform_device *pdev) -{ - struct sst_data *drv; - int ret; - struct sst_platform_data *pdata; - - drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); - if (drv == NULL) { - return -ENOMEM; - } - - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (pdata == NULL) { - return -ENOMEM; - } - - pdata->pdev_strm_map = dpcm_strm_map; - pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); - drv->pdata = pdata; - drv->pdev = pdev; - mutex_init(&drv->lock); - dev_set_drvdata(&pdev->dev, drv); - - ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); - if (ret) { - dev_err(&pdev->dev, "registering soc platform failed\n"); - return ret; - } - - ret = snd_soc_register_component(&pdev->dev, &sst_component, - sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); - if (ret) { - dev_err(&pdev->dev, "registering cpu dais failed\n"); - snd_soc_unregister_platform(&pdev->dev); - } - return ret; -} - -static int sst_platform_remove(struct platform_device *pdev) -{ - - snd_soc_unregister_component(&pdev->dev); - snd_soc_unregister_platform(&pdev->dev); - dev_dbg(&pdev->dev, "sst_platform_remove success\n"); - return 0; -} - -#ifdef CONFIG_PM_SLEEP - -static int sst_soc_prepare(struct device *dev) -{ - struct sst_data *drv = dev_get_drvdata(dev); - int i; - - /* suspend all pcms first */ - snd_soc_suspend(drv->soc_card->dev); - snd_soc_poweroff(drv->soc_card->dev); - - /* set the SSPs to idle */ - for (i = 0; i < drv->soc_card->num_rtd; i++) { - struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai; - - if (dai->active) { - send_ssp_cmd(dai, dai->name, 0); - sst_handle_vb_timer(dai, false); - } - } - - return 0; -} - -static void sst_soc_complete(struct device *dev) -{ - struct sst_data *drv = dev_get_drvdata(dev); - int i; - - /* restart SSPs */ - for (i = 0; i < drv->soc_card->num_rtd; i++) { - struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai; - - if (dai->active) { - sst_handle_vb_timer(dai, true); - send_ssp_cmd(dai, dai->name, 1); - } - } - snd_soc_resume(drv->soc_card->dev); -} - -#else - -#define sst_soc_prepare NULL -#define sst_soc_complete NULL - -#endif - - -static const struct dev_pm_ops sst_platform_pm = { - .prepare = sst_soc_prepare, - .complete = sst_soc_complete, -}; - -static struct platform_driver sst_platform_driver = { - .driver = { - .name = "sst-mfld-platform", - .pm = &sst_platform_pm, - }, - .probe = sst_platform_probe, - .remove = sst_platform_remove, -}; - -module_platform_driver(sst_platform_driver); - -MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); -MODULE_AUTHOR("Vinod Koul "); -MODULE_AUTHOR("Harsha Priya "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:sst-mfld-platform"); diff --git a/sound/soc/intel/sst-mfld-platform.h b/sound/soc/intel/sst-mfld-platform.h deleted file mode 100644 index 9094314..0000000 --- a/sound/soc/intel/sst-mfld-platform.h +++ /dev/null @@ -1,181 +0,0 @@ -/* - * sst_mfld_platform.h - Intel MID Platform driver header file - * - * Copyright (C) 2010 Intel Corp - * Author: Vinod Koul - * Author: Harsha Priya - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 __SST_PLATFORMDRV_H__ -#define __SST_PLATFORMDRV_H__ - -#include "sst-mfld-dsp.h" - -extern struct sst_device *sst; - -#define SST_MONO 1 -#define SST_STEREO 2 -#define SST_MAX_CAP 5 - -#define SST_MAX_BUFFER (800*1024) -#define SST_MIN_BUFFER (800*1024) -#define SST_MIN_PERIOD_BYTES 32 -#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER -#define SST_MIN_PERIODS 2 -#define SST_MAX_PERIODS (1024*2) -#define SST_FIFO_SIZE 0 - -struct pcm_stream_info { - int str_id; - void *arg; - void (*period_elapsed) (void *arg); - unsigned long long buffer_ptr; - unsigned long long pcm_delay; - int sfreq; -}; - -enum sst_drv_status { - SST_PLATFORM_INIT = 1, - SST_PLATFORM_STARTED, - SST_PLATFORM_RUNNING, - SST_PLATFORM_PAUSED, - SST_PLATFORM_DROPPED, -}; - -enum sst_stream_ops { - STREAM_OPS_PLAYBACK = 0, - STREAM_OPS_CAPTURE, -}; - -enum sst_audio_device_type { - SND_SST_DEVICE_HEADSET = 1, - SND_SST_DEVICE_IHF, - SND_SST_DEVICE_VIBRA, - SND_SST_DEVICE_HAPTIC, - SND_SST_DEVICE_CAPTURE, - SND_SST_DEVICE_COMPRESS, -}; - -/* PCM Parameters */ -struct sst_pcm_params { - u16 codec; /* codec type */ - u8 num_chan; /* 1=Mono, 2=Stereo */ - u8 pcm_wd_sz; /* 16/24 - bit*/ - u32 reserved; /* Bitrate in bits per second */ - u32 sfreq; /* Sampling rate in Hz */ - u32 ring_buffer_size; - u32 period_count; /* period elapsed in samples*/ - u32 ring_buffer_addr; -}; - -struct sst_stream_params { - u32 result; - u32 stream_id; - u8 codec; - u8 ops; - u8 stream_type; - u8 device_type; - struct sst_pcm_params sparams; -}; - -struct sst_compress_cb { - void *param; - void (*compr_cb)(void *param); - void *drain_cb_param; - void (*drain_notify)(void *param); -}; - -struct compress_sst_ops { - const char *name; - int (*open)(struct device *dev, - struct snd_sst_params *str_params, struct sst_compress_cb *cb); - int (*stream_start)(struct device *dev, unsigned int str_id); - int (*stream_drop)(struct device *dev, unsigned int str_id); - int (*stream_drain)(struct device *dev, unsigned int str_id); - int (*stream_partial_drain)(struct device *dev, unsigned int str_id); - int (*stream_pause)(struct device *dev, unsigned int str_id); - int (*stream_pause_release)(struct device *dev, unsigned int str_id); - - int (*tstamp)(struct device *dev, unsigned int str_id, - struct snd_compr_tstamp *tstamp); - int (*ack)(struct device *dev, unsigned int str_id, - unsigned long bytes); - int (*close)(struct device *dev, unsigned int str_id); - int (*get_caps)(struct snd_compr_caps *caps); - int (*get_codec_caps)(struct snd_compr_codec_caps *codec); - int (*set_metadata)(struct device *dev, unsigned int str_id, - struct snd_compr_metadata *mdata); - int (*power)(struct device *dev, bool state); -}; - -struct sst_ops { - int (*open)(struct device *dev, struct snd_sst_params *str_param); - int (*stream_init)(struct device *dev, struct pcm_stream_info *str_info); - int (*stream_start)(struct device *dev, int str_id); - int (*stream_drop)(struct device *dev, int str_id); - int (*stream_pause)(struct device *dev, int str_id); - int (*stream_pause_release)(struct device *dev, int str_id); - int (*stream_read_tstamp)(struct device *dev, struct pcm_stream_info *str_info); - int (*send_byte_stream)(struct device *dev, struct snd_sst_bytes_v2 *bytes); - int (*close)(struct device *dev, unsigned int str_id); - int (*power)(struct device *dev, bool state); -}; - -struct sst_runtime_stream { - int stream_status; - unsigned int id; - size_t bytes_written; - struct pcm_stream_info stream_info; - struct sst_ops *ops; - struct compress_sst_ops *compr_ops; - spinlock_t status_lock; -}; - -struct sst_device { - char *name; - struct device *dev; - struct sst_ops *ops; - struct platform_device *pdev; - struct compress_sst_ops *compr_ops; -}; - -struct sst_data; - -int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform); -int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute); -int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable); -int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable); - -void sst_set_stream_status(struct sst_runtime_stream *stream, int state); -int sst_fill_stream_params(void *substream, const struct sst_data *ctx, - struct snd_sst_params *str_params, bool is_compress); - -struct sst_algo_int_control_v2 { - struct soc_mixer_control mc; - u16 module_id; /* module identifieer */ - u16 pipe_id; /* location info: pipe_id + instance_id */ - u16 instance_id; - unsigned int value; /* Value received is stored here */ -}; -struct sst_data { - struct platform_device *pdev; - struct sst_platform_data *pdata; - struct snd_sst_bytes_v2 *byte_stream; - struct mutex lock; - struct snd_soc_card *soc_card; -}; -int sst_register_dsp(struct sst_device *sst); -int sst_unregister_dsp(struct sst_device *sst); -#endif diff --git a/sound/soc/intel/sst/Makefile b/sound/soc/intel/sst/Makefile deleted file mode 100644 index fd21726..0000000 --- a/sound/soc/intel/sst/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o -snd-intel-sst-pci-objs += sst_pci.o -snd-intel-sst-acpi-objs += sst_acpi.o - -obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst-core.o -obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o -obj-$(CONFIG_SND_SST_IPC_ACPI) += snd-intel-sst-acpi.o diff --git a/sound/soc/intel/sst/sst.c b/sound/soc/intel/sst/sst.c deleted file mode 100644 index 26b1e31c..0000000 --- a/sound/soc/intel/sst/sst.c +++ /dev/null @@ -1,557 +0,0 @@ -/* - * sst.c - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corp - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" -#include "../common/sst-dsp.h" - -MODULE_AUTHOR("Vinod Koul "); -MODULE_AUTHOR("Harsha Priya "); -MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); -MODULE_LICENSE("GPL v2"); - -static inline bool sst_is_process_reply(u32 msg_id) -{ - return ((msg_id & PROCESS_MSG) ? true : false); -} - -static inline bool sst_validate_mailbox_size(unsigned int size) -{ - return ((size <= SST_MAILBOX_SIZE) ? true : false); -} - -static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) -{ - union interrupt_reg_mrfld isr; - union ipc_header_mrfld header; - union sst_imr_reg_mrfld imr; - struct ipc_post *msg = NULL; - unsigned int size = 0; - struct intel_sst_drv *drv = (struct intel_sst_drv *) context; - irqreturn_t retval = IRQ_HANDLED; - - /* Interrupt arrived, check src */ - isr.full = sst_shim_read64(drv->shim, SST_ISRX); - - if (isr.part.done_interrupt) { - /* Clear done bit */ - spin_lock(&drv->ipc_spin_lock); - header.full = sst_shim_read64(drv->shim, - drv->ipc_reg.ipcx); - header.p.header_high.part.done = 0; - sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); - - /* write 1 to clear status register */; - isr.part.done_interrupt = 1; - sst_shim_write64(drv->shim, SST_ISRX, isr.full); - spin_unlock(&drv->ipc_spin_lock); - - /* we can send more messages to DSP so trigger work */ - queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq); - retval = IRQ_HANDLED; - } - - if (isr.part.busy_interrupt) { - /* message from dsp so copy that */ - spin_lock(&drv->ipc_spin_lock); - imr.full = sst_shim_read64(drv->shim, SST_IMRX); - imr.part.busy_interrupt = 1; - sst_shim_write64(drv->shim, SST_IMRX, imr.full); - spin_unlock(&drv->ipc_spin_lock); - header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); - - if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { - drv->ops->clear_interrupt(drv); - return IRQ_HANDLED; - } - - if (header.p.header_high.part.large) { - size = header.p.header_low_payload; - if (sst_validate_mailbox_size(size)) { - memcpy_fromio(msg->mailbox_data, - drv->mailbox + drv->mailbox_recv_offset, size); - } else { - dev_err(drv->dev, - "Mailbox not copied, payload size is: %u\n", size); - header.p.header_low_payload = 0; - } - } - - msg->mrfld_header = header; - msg->is_process_reply = - sst_is_process_reply(header.p.header_high.part.msg_id); - spin_lock(&drv->rx_msg_lock); - list_add_tail(&msg->node, &drv->rx_list); - spin_unlock(&drv->rx_msg_lock); - drv->ops->clear_interrupt(drv); - retval = IRQ_WAKE_THREAD; - } - return retval; -} - -static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) -{ - struct intel_sst_drv *drv = (struct intel_sst_drv *) context; - struct ipc_post *__msg, *msg = NULL; - unsigned long irq_flags; - - spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); - if (list_empty(&drv->rx_list)) { - spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); - return IRQ_HANDLED; - } - - list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { - list_del(&msg->node); - spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); - if (msg->is_process_reply) - drv->ops->process_message(msg); - else - drv->ops->process_reply(drv, msg); - - if (msg->is_large) - kfree(msg->mailbox_data); - kfree(msg); - spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); - } - spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); - return IRQ_HANDLED; -} - -static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) -{ - int ret = 0; - - ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD, - IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL, - true, true, false, true); - - if (ret < 0) { - dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret); - return -EIO; - } - - return 0; -} - - -static struct intel_sst_ops mrfld_ops = { - .interrupt = intel_sst_interrupt_mrfld, - .irq_thread = intel_sst_irq_thread_mrfld, - .clear_interrupt = intel_sst_clear_intr_mrfld, - .start = sst_start_mrfld, - .reset = intel_sst_reset_dsp_mrfld, - .post_message = sst_post_message_mrfld, - .process_reply = sst_process_reply_mrfld, - .save_dsp_context = sst_save_dsp_context_v2, - .alloc_stream = sst_alloc_stream_mrfld, - .post_download = sst_post_download_mrfld, -}; - -int sst_driver_ops(struct intel_sst_drv *sst) -{ - - switch (sst->dev_id) { - case SST_MRFLD_PCI_ID: - case SST_BYT_ACPI_ID: - case SST_CHV_ACPI_ID: - sst->tstamp = SST_TIME_STAMP_MRFLD; - sst->ops = &mrfld_ops; - return 0; - - default: - dev_err(sst->dev, - "SST Driver capablities missing for dev_id: %x", sst->dev_id); - return -EINVAL; - }; -} - -void sst_process_pending_msg(struct work_struct *work) -{ - struct intel_sst_drv *ctx = container_of(work, - struct intel_sst_drv, ipc_post_msg_wq); - - ctx->ops->post_message(ctx, NULL, false); -} - -static int sst_workqueue_init(struct intel_sst_drv *ctx) -{ - INIT_LIST_HEAD(&ctx->memcpy_list); - INIT_LIST_HEAD(&ctx->rx_list); - INIT_LIST_HEAD(&ctx->ipc_dispatch_list); - INIT_LIST_HEAD(&ctx->block_list); - INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg); - init_waitqueue_head(&ctx->wait_queue); - - ctx->post_msg_wq = - create_singlethread_workqueue("sst_post_msg_wq"); - if (!ctx->post_msg_wq) - return -EBUSY; - return 0; -} - -static void sst_init_locks(struct intel_sst_drv *ctx) -{ - mutex_init(&ctx->sst_lock); - spin_lock_init(&ctx->rx_msg_lock); - spin_lock_init(&ctx->ipc_spin_lock); - spin_lock_init(&ctx->block_lock); -} - -int sst_alloc_drv_context(struct intel_sst_drv **ctx, - struct device *dev, unsigned int dev_id) -{ - *ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL); - if (!(*ctx)) - return -ENOMEM; - - (*ctx)->dev = dev; - (*ctx)->dev_id = dev_id; - - return 0; -} -EXPORT_SYMBOL_GPL(sst_alloc_drv_context); - -int sst_context_init(struct intel_sst_drv *ctx) -{ - int ret = 0, i; - - if (!ctx->pdata) - return -EINVAL; - - if (!ctx->pdata->probe_data) - return -EINVAL; - - memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info)); - - ret = sst_driver_ops(ctx); - if (ret != 0) - return -EINVAL; - - sst_init_locks(ctx); - sst_set_fw_state_locked(ctx, SST_RESET); - - /* pvt_id 0 reserved for async messages */ - ctx->pvt_id = 1; - ctx->stream_cnt = 0; - ctx->fw_in_mem = NULL; - /* we use memcpy, so set to 0 */ - ctx->use_dma = 0; - ctx->use_lli = 0; - - if (sst_workqueue_init(ctx)) - return -EINVAL; - - ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off; - ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset; - ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset; - - dev_info(ctx->dev, "Got drv data max stream %d\n", - ctx->info.max_streams); - - for (i = 1; i <= ctx->info.max_streams; i++) { - struct stream_info *stream = &ctx->streams[i]; - - memset(stream, 0, sizeof(*stream)); - stream->pipe_id = PIPE_RSVD; - mutex_init(&stream->lock); - } - - /* Register the ISR */ - ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt, - ctx->ops->irq_thread, 0, SST_DRV_NAME, - ctx); - if (ret) - goto do_free_mem; - - dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num); - - /* default intr are unmasked so set this as masked */ - sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038); - - ctx->qos = devm_kzalloc(ctx->dev, - sizeof(struct pm_qos_request), GFP_KERNEL); - if (!ctx->qos) { - ret = -ENOMEM; - goto do_free_mem; - } - pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY, - PM_QOS_DEFAULT_VALUE); - - dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name); - ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name, - ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb); - if (ret) { - dev_err(ctx->dev, "Firmware download failed:%d\n", ret); - goto do_free_mem; - } - sst_register(ctx->dev); - return 0; - -do_free_mem: - destroy_workqueue(ctx->post_msg_wq); - return ret; -} -EXPORT_SYMBOL_GPL(sst_context_init); - -void sst_context_cleanup(struct intel_sst_drv *ctx) -{ - pm_runtime_get_noresume(ctx->dev); - pm_runtime_disable(ctx->dev); - sst_unregister(ctx->dev); - sst_set_fw_state_locked(ctx, SST_SHUTDOWN); - flush_scheduled_work(); - destroy_workqueue(ctx->post_msg_wq); - pm_qos_remove_request(ctx->qos); - kfree(ctx->fw_sg_list.src); - kfree(ctx->fw_sg_list.dst); - ctx->fw_sg_list.list_len = 0; - kfree(ctx->fw_in_mem); - ctx->fw_in_mem = NULL; - sst_memcpy_free_resources(ctx); - ctx = NULL; -} -EXPORT_SYMBOL_GPL(sst_context_cleanup); - -static inline void sst_save_shim64(struct intel_sst_drv *ctx, - void __iomem *shim, - struct sst_shim_regs64 *shim_regs) -{ - unsigned long irq_flags; - - spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); - - shim_regs->imrx = sst_shim_read64(shim, SST_IMRX); - shim_regs->csr = sst_shim_read64(shim, SST_CSR); - - - spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); -} - -static inline void sst_restore_shim64(struct intel_sst_drv *ctx, - void __iomem *shim, - struct sst_shim_regs64 *shim_regs) -{ - unsigned long irq_flags; - - /* - * we only need to restore IMRX for this case, rest will be - * initialize by FW or driver when firmware is loaded - */ - spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); - sst_shim_write64(shim, SST_IMRX, shim_regs->imrx), - sst_shim_write64(shim, SST_CSR, shim_regs->csr), - spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); -} - -void sst_configure_runtime_pm(struct intel_sst_drv *ctx) -{ - pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY); - pm_runtime_use_autosuspend(ctx->dev); - /* - * For acpi devices, the actual physical device state is - * initially active. So change the state to active before - * enabling the pm - */ - - if (!acpi_disabled) - pm_runtime_set_active(ctx->dev); - - pm_runtime_enable(ctx->dev); - - if (acpi_disabled) - pm_runtime_set_active(ctx->dev); - else - pm_runtime_put_noidle(ctx->dev); - - sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); -} -EXPORT_SYMBOL_GPL(sst_configure_runtime_pm); - -static int intel_sst_runtime_suspend(struct device *dev) -{ - int ret = 0; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (ctx->sst_state == SST_RESET) { - dev_dbg(dev, "LPE is already in RESET state, No action\n"); - return 0; - } - /* save fw context */ - if (ctx->ops->save_dsp_context(ctx)) - return -EBUSY; - - /* Move the SST state to Reset */ - sst_set_fw_state_locked(ctx, SST_RESET); - - synchronize_irq(ctx->irq_num); - flush_workqueue(ctx->post_msg_wq); - - ctx->ops->reset(ctx); - /* save the shim registers because PMC doesn't save state */ - sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); - - return ret; -} - -static int intel_sst_suspend(struct device *dev) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - struct sst_fw_save *fw_save; - int i, ret = 0; - - /* check first if we are already in SW reset */ - if (ctx->sst_state == SST_RESET) - return 0; - - /* - * check if any stream is active and running - * they should already by suspend by soc_suspend - */ - for (i = 1; i <= ctx->info.max_streams; i++) { - struct stream_info *stream = &ctx->streams[i]; - - if (stream->status == STREAM_RUNNING) { - dev_err(dev, "stream %d is running, cant susupend, abort\n", i); - return -EBUSY; - } - } - synchronize_irq(ctx->irq_num); - flush_workqueue(ctx->post_msg_wq); - - /* Move the SST state to Reset */ - sst_set_fw_state_locked(ctx, SST_RESET); - - /* tell DSP we are suspending */ - if (ctx->ops->save_dsp_context(ctx)) - return -EBUSY; - - /* save the memories */ - fw_save = kzalloc(sizeof(*fw_save), GFP_KERNEL); - if (!fw_save) - return -ENOMEM; - fw_save->iram = kzalloc(ctx->iram_end - ctx->iram_base, GFP_KERNEL); - if (!fw_save->iram) { - ret = -ENOMEM; - goto iram; - } - fw_save->dram = kzalloc(ctx->dram_end - ctx->dram_base, GFP_KERNEL); - if (!fw_save->dram) { - ret = -ENOMEM; - goto dram; - } - fw_save->sram = kzalloc(SST_MAILBOX_SIZE, GFP_KERNEL); - if (!fw_save->sram) { - ret = -ENOMEM; - goto sram; - } - - fw_save->ddr = kzalloc(ctx->ddr_end - ctx->ddr_base, GFP_KERNEL); - if (!fw_save->ddr) { - ret = -ENOMEM; - goto ddr; - } - - memcpy32_fromio(fw_save->iram, ctx->iram, ctx->iram_end - ctx->iram_base); - memcpy32_fromio(fw_save->dram, ctx->dram, ctx->dram_end - ctx->dram_base); - memcpy32_fromio(fw_save->sram, ctx->mailbox, SST_MAILBOX_SIZE); - memcpy32_fromio(fw_save->ddr, ctx->ddr, ctx->ddr_end - ctx->ddr_base); - - ctx->fw_save = fw_save; - ctx->ops->reset(ctx); - return 0; -ddr: - kfree(fw_save->sram); -sram: - kfree(fw_save->dram); -dram: - kfree(fw_save->iram); -iram: - kfree(fw_save); - return ret; -} - -static int intel_sst_resume(struct device *dev) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - struct sst_fw_save *fw_save = ctx->fw_save; - int ret = 0; - struct sst_block *block; - - if (!fw_save) - return 0; - - sst_set_fw_state_locked(ctx, SST_FW_LOADING); - - /* we have to restore the memory saved */ - ctx->ops->reset(ctx); - - ctx->fw_save = NULL; - - memcpy32_toio(ctx->iram, fw_save->iram, ctx->iram_end - ctx->iram_base); - memcpy32_toio(ctx->dram, fw_save->dram, ctx->dram_end - ctx->dram_base); - memcpy32_toio(ctx->mailbox, fw_save->sram, SST_MAILBOX_SIZE); - memcpy32_toio(ctx->ddr, fw_save->ddr, ctx->ddr_end - ctx->ddr_base); - - kfree(fw_save->sram); - kfree(fw_save->dram); - kfree(fw_save->iram); - kfree(fw_save->ddr); - kfree(fw_save); - - block = sst_create_block(ctx, 0, FW_DWNL_ID); - if (block == NULL) - return -ENOMEM; - - - /* start and wait for ack */ - ctx->ops->start(ctx); - ret = sst_wait_timeout(ctx, block); - if (ret) { - dev_err(ctx->dev, "fw download failed %d\n", ret); - /* FW download failed due to timeout */ - ret = -EBUSY; - - } else { - sst_set_fw_state_locked(ctx, SST_FW_RUNNING); - } - - sst_free_block(ctx, block); - return ret; -} - -const struct dev_pm_ops intel_sst_pm = { - .suspend = intel_sst_suspend, - .resume = intel_sst_resume, - .runtime_suspend = intel_sst_runtime_suspend, -}; -EXPORT_SYMBOL_GPL(intel_sst_pm); diff --git a/sound/soc/intel/sst/sst.h b/sound/soc/intel/sst/sst.h deleted file mode 100644 index 3f49386..0000000 --- a/sound/soc/intel/sst/sst.h +++ /dev/null @@ -1,559 +0,0 @@ -/* - * sst.h - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corporation - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Common private declarations for SST - */ -#ifndef __SST_H__ -#define __SST_H__ - -#include - -/* driver names */ -#define SST_DRV_NAME "intel_sst_driver" -#define SST_MRFLD_PCI_ID 0x119A -#define SST_BYT_ACPI_ID 0x80860F28 -#define SST_CHV_ACPI_ID 0x808622A8 - -#define SST_SUSPEND_DELAY 2000 -#define FW_CONTEXT_MEM (64*1024) -#define SST_ICCM_BOUNDARY 4 -#define SST_CONFIG_SSP_SIGN 0x7ffe8001 - -#define MRFLD_FW_VIRTUAL_BASE 0xC0000000 -#define MRFLD_FW_DDR_BASE_OFFSET 0x0 -#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4 -#define MRFLD_FW_BSS_RESET_BIT 0 - -extern const struct dev_pm_ops intel_sst_pm; -enum sst_states { - SST_FW_LOADING = 1, - SST_FW_RUNNING, - SST_RESET, - SST_SHUTDOWN, -}; - -enum sst_algo_ops { - SST_SET_ALGO = 0, - SST_GET_ALGO = 1, -}; - -#define SST_BLOCK_TIMEOUT 1000 - -#define FW_SIGNATURE_SIZE 4 -#define FW_NAME_SIZE 32 - -/* stream states */ -enum sst_stream_states { - STREAM_UN_INIT = 0, /* Freed/Not used stream */ - STREAM_RUNNING = 1, /* Running */ - STREAM_PAUSED = 2, /* Paused stream */ - STREAM_DECODE = 3, /* stream is in decoding only state */ - STREAM_INIT = 4, /* stream init, waiting for data */ - STREAM_RESET = 5, /* force reset on recovery */ -}; - -enum sst_ram_type { - SST_IRAM = 1, - SST_DRAM = 2, - SST_DDR = 5, - SST_CUSTOM_INFO = 7, /* consists of FW binary information */ -}; - -/* SST shim registers to structure mapping */ -union interrupt_reg { - struct { - u64 done_interrupt:1; - u64 busy_interrupt:1; - u64 rsvd:62; - } part; - u64 full; -}; - -union sst_pisr_reg { - struct { - u32 pssp0:1; - u32 pssp1:1; - u32 rsvd0:3; - u32 dmac:1; - u32 rsvd1:26; - } part; - u32 full; -}; - -union sst_pimr_reg { - struct { - u32 ssp0:1; - u32 ssp1:1; - u32 rsvd0:3; - u32 dmac:1; - u32 rsvd1:10; - u32 ssp0_sc:1; - u32 ssp1_sc:1; - u32 rsvd2:3; - u32 dmac_sc:1; - u32 rsvd3:10; - } part; - u32 full; -}; - -union config_status_reg_mrfld { - struct { - u64 lpe_reset:1; - u64 lpe_reset_vector:1; - u64 runstall:1; - u64 pwaitmode:1; - u64 clk_sel:3; - u64 rsvd2:1; - u64 sst_clk:3; - u64 xt_snoop:1; - u64 rsvd3:4; - u64 clk_sel1:6; - u64 clk_enable:3; - u64 rsvd4:6; - u64 slim0baseclk:1; - u64 rsvd:32; - } part; - u64 full; -}; - -union interrupt_reg_mrfld { - struct { - u64 done_interrupt:1; - u64 busy_interrupt:1; - u64 rsvd:62; - } part; - u64 full; -}; - -union sst_imr_reg_mrfld { - struct { - u64 done_interrupt:1; - u64 busy_interrupt:1; - u64 rsvd:62; - } part; - u64 full; -}; - -/** - * struct sst_block - This structure is used to block a user/fw data call to another - * fw/user call - * - * @condition: condition for blocking check - * @ret_code: ret code when block is released - * @data: data ptr - * @size: size of data - * @on: block condition - * @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL - * @drv_id: str_id in mfld/ctp, = drv_id in mrfld - * @node: list head node - */ -struct sst_block { - bool condition; - int ret_code; - void *data; - u32 size; - bool on; - u32 msg_id; - u32 drv_id; - struct list_head node; -}; - -/** - * struct stream_info - structure that holds the stream information - * - * @status : stream current state - * @prev : stream prev state - * @ops : stream operation pb/cp/drm... - * @bufs: stream buffer list - * @lock : stream mutex for protecting state - * @pcm_substream : PCM substream - * @period_elapsed : PCM period elapsed callback - * @sfreq : stream sampling freq - * @str_type : stream type - * @cumm_bytes : cummulative bytes decoded - * @str_type : stream type - * @src : stream source - */ -struct stream_info { - unsigned int status; - unsigned int prev; - unsigned int ops; - struct mutex lock; - - void *pcm_substream; - void (*period_elapsed)(void *pcm_substream); - - unsigned int sfreq; - u32 cumm_bytes; - - void *compr_cb_param; - void (*compr_cb)(void *compr_cb_param); - - void *drain_cb_param; - void (*drain_notify)(void *drain_cb_param); - - unsigned int num_ch; - unsigned int pipe_id; - unsigned int str_id; - unsigned int task_id; -}; - -#define SST_FW_SIGN "$SST" -#define SST_FW_LIB_SIGN "$LIB" - -/** - * struct sst_fw_header - FW file headers - * - * @signature : FW signature - * @file_size: size of fw image - * @modules : # of modules - * @file_format : version of header format - * @reserved : reserved fields - */ -struct sst_fw_header { - unsigned char signature[FW_SIGNATURE_SIZE]; - u32 file_size; - u32 modules; - u32 file_format; - u32 reserved[4]; -}; - -/** - * struct fw_module_header - module header in FW - * - * @signature: module signature - * @mod_size: size of module - * @blocks: block count - * @type: block type - * @entry_point: module netry point - */ -struct fw_module_header { - unsigned char signature[FW_SIGNATURE_SIZE]; - u32 mod_size; - u32 blocks; - u32 type; - u32 entry_point; -}; - -/** - * struct fw_block_info - block header for FW - * - * @type: block ram type I/D - * @size: size of block - * @ram_offset: offset in ram - */ -struct fw_block_info { - enum sst_ram_type type; - u32 size; - u32 ram_offset; - u32 rsvd; -}; - -struct sst_runtime_param { - struct snd_sst_runtime_params param; -}; - -struct sst_sg_list { - struct scatterlist *src; - struct scatterlist *dst; - int list_len; - unsigned int sg_idx; -}; - -struct sst_memcpy_list { - struct list_head memcpylist; - void *dstn; - const void *src; - u32 size; - bool is_io; -}; - -/*Firmware Module Information*/ -enum sst_lib_dwnld_status { - SST_LIB_NOT_FOUND = 0, - SST_LIB_FOUND, - SST_LIB_DOWNLOADED, -}; - -struct sst_module_info { - const char *name; /*Library name*/ - u32 id; /*Module ID*/ - u32 entry_pt; /*Module entry point*/ - u8 status; /*module status*/ - u8 rsvd1; - u16 rsvd2; -}; - -/* - * Structure for managing the Library Region(1.5MB) - * in DDR in Merrifield - */ -struct sst_mem_mgr { - phys_addr_t current_base; - int avail; - unsigned int count; -}; - -struct sst_ipc_reg { - int ipcx; - int ipcd; -}; - -struct sst_shim_regs64 { - u64 csr; - u64 pisr; - u64 pimr; - u64 isrx; - u64 isrd; - u64 imrx; - u64 imrd; - u64 ipcx; - u64 ipcd; - u64 isrsc; - u64 isrlpesc; - u64 imrsc; - u64 imrlpesc; - u64 ipcsc; - u64 ipclpesc; - u64 clkctl; - u64 csr2; -}; - -struct sst_fw_save { - void *iram; - void *dram; - void *sram; - void *ddr; -}; - -/** - * struct intel_sst_drv - driver ops - * - * @sst_state : current sst device state - * @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi - * devices - * @shim : SST shim pointer - * @mailbox : SST mailbox pointer - * @iram : SST IRAM pointer - * @dram : SST DRAM pointer - * @pdata : SST info passed as a part of pci platform data - * @shim_phy_add : SST shim phy addr - * @shim_regs64: Struct to save shim registers - * @ipc_dispatch_list : ipc messages dispatched - * @rx_list : to copy the process_reply/process_msg from DSP - * @ipc_post_msg_wq : wq to post IPC messages context - * @mad_ops : MAD driver operations registered - * @mad_wq : MAD driver wq - * @post_msg_wq : wq to post IPC messages - * @streams : sst stream contexts - * @list_lock : sst driver list lock (deprecated) - * @ipc_spin_lock : spin lock to handle audio shim access and ipc queue - * @block_lock : spin lock to add block to block_list and assign pvt_id - * @rx_msg_lock : spin lock to handle the rx messages from the DSP - * @scard_ops : sst card ops - * @pci : sst pci device struture - * @dev : pointer to current device struct - * @sst_lock : sst device lock - * @pvt_id : sst private id - * @stream_cnt : total sst active stream count - * @pb_streams : total active pb streams - * @cp_streams : total active cp streams - * @audio_start : audio status - * @qos : PM Qos struct - * firmware_name : Firmware / Library name - */ -struct intel_sst_drv { - int sst_state; - int irq_num; - unsigned int dev_id; - void __iomem *ddr; - void __iomem *shim; - void __iomem *mailbox; - void __iomem *iram; - void __iomem *dram; - unsigned int mailbox_add; - unsigned int iram_base; - unsigned int dram_base; - unsigned int shim_phy_add; - unsigned int iram_end; - unsigned int dram_end; - unsigned int ddr_end; - unsigned int ddr_base; - unsigned int mailbox_recv_offset; - struct sst_shim_regs64 *shim_regs64; - struct list_head block_list; - struct list_head ipc_dispatch_list; - struct sst_platform_info *pdata; - struct list_head rx_list; - struct work_struct ipc_post_msg_wq; - wait_queue_head_t wait_queue; - struct workqueue_struct *post_msg_wq; - unsigned int tstamp; - /* str_id 0 is not used */ - struct stream_info streams[MAX_NUM_STREAMS+1]; - spinlock_t ipc_spin_lock; - spinlock_t block_lock; - spinlock_t rx_msg_lock; - struct pci_dev *pci; - struct device *dev; - volatile long unsigned pvt_id; - struct mutex sst_lock; - unsigned int stream_cnt; - unsigned int csr_value; - void *fw_in_mem; - struct sst_sg_list fw_sg_list, library_list; - struct intel_sst_ops *ops; - struct sst_info info; - struct pm_qos_request *qos; - unsigned int use_dma; - unsigned int use_lli; - atomic_t fw_clear_context; - bool lib_dwnld_reqd; - struct list_head memcpy_list; - struct sst_ipc_reg ipc_reg; - struct sst_mem_mgr lib_mem_mgr; - /* - * Holder for firmware name. Due to async call it needs to be - * persistent till worker thread gets called - */ - char firmware_name[FW_NAME_SIZE]; - - struct sst_fw_save *fw_save; -}; - -/* misc definitions */ -#define FW_DWNL_ID 0x01 - -struct intel_sst_ops { - irqreturn_t (*interrupt)(int, void *); - irqreturn_t (*irq_thread)(int, void *); - void (*clear_interrupt)(struct intel_sst_drv *ctx); - int (*start)(struct intel_sst_drv *ctx); - int (*reset)(struct intel_sst_drv *ctx); - void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg); - int (*post_message)(struct intel_sst_drv *ctx, - struct ipc_post *msg, bool sync); - void (*process_message)(struct ipc_post *msg); - void (*set_bypass)(bool set); - int (*save_dsp_context)(struct intel_sst_drv *sst); - void (*restore_dsp_context)(void); - int (*alloc_stream)(struct intel_sst_drv *ctx, void *params); - void (*post_download)(struct intel_sst_drv *sst); -}; - -int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); -int sst_send_byte_stream_mrfld(struct intel_sst_drv *ctx, - struct snd_sst_bytes_v2 *sbytes); -int sst_set_stream_param(int str_id, struct snd_sst_params *str_param); -int sst_set_metadata(int str_id, char *params); -int sst_get_stream(struct intel_sst_drv *sst_drv_ctx, - struct snd_sst_params *str_param); -int sst_get_stream_allocated(struct intel_sst_drv *ctx, - struct snd_sst_params *str_param, - struct snd_sst_lib_download **lib_dnld); -int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, - int str_id, bool partial_drain); -int sst_post_message_mrfld(struct intel_sst_drv *ctx, - struct ipc_post *msg, bool sync); -void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg); -int sst_start_mrfld(struct intel_sst_drv *ctx); -int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx); -void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx); - -int sst_load_fw(struct intel_sst_drv *ctx); -int sst_load_library(struct snd_sst_lib_download *lib, u8 ops); -void sst_post_download_mrfld(struct intel_sst_drv *ctx); -int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx); -void sst_memcpy_free_resources(struct intel_sst_drv *ctx); - -int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, - struct sst_block *block); -int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, - struct sst_block *block); -int sst_create_ipc_msg(struct ipc_post **arg, bool large); -int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id); -void sst_clean_stream(struct stream_info *stream); -int intel_sst_register_compress(struct intel_sst_drv *sst); -int intel_sst_remove_compress(struct intel_sst_drv *sst); -void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id); -int sst_send_sync_msg(int ipc, int str_id); -int sst_get_num_channel(struct snd_sst_params *str_param); -int sst_get_sfreq(struct snd_sst_params *str_param); -int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params); -void sst_restore_fw_context(void); -struct sst_block *sst_create_block(struct intel_sst_drv *ctx, - u32 msg_id, u32 drv_id); -int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, - struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, - u32 msg_id, u32 drv_id); -int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed); -int sst_wake_up_block(struct intel_sst_drv *ctx, int result, - u32 drv_id, u32 ipc, void *data, u32 size); -int sst_request_firmware_async(struct intel_sst_drv *ctx); -int sst_driver_ops(struct intel_sst_drv *sst); -struct sst_platform_info *sst_get_acpi_driver_data(const char *hid); -void sst_firmware_load_cb(const struct firmware *fw, void *context); -int sst_prepare_and_post_msg(struct intel_sst_drv *sst, - int task_id, int ipc_msg, int cmd_id, int pipe_id, - size_t mbox_data_len, const void *mbox_data, void **data, - bool large, bool fill_dsp, bool sync, bool response); - -void sst_process_pending_msg(struct work_struct *work); -int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx); -void sst_init_stream(struct stream_info *stream, - int codec, int sst_id, int ops, u8 slot); -int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id); -struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx, - int str_id); -int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, - u32 pipe_id); -u32 relocate_imr_addr_mrfld(u32 base_addr); -void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, - struct ipc_post *msg); -int sst_pm_runtime_put(struct intel_sst_drv *sst_drv); -int sst_shim_write(void __iomem *addr, int offset, int value); -u32 sst_shim_read(void __iomem *addr, int offset); -u64 sst_reg_read64(void __iomem *addr, int offset); -int sst_shim_write64(void __iomem *addr, int offset, u64 value); -u64 sst_shim_read64(void __iomem *addr, int offset); -void sst_set_fw_state_locked( - struct intel_sst_drv *sst_drv_ctx, int sst_state); -void sst_fill_header_mrfld(union ipc_header_mrfld *header, - int msg, int task_id, int large, int drv_id); -void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, - int pipe_id, int len); - -int sst_register(struct device *); -int sst_unregister(struct device *); - -int sst_alloc_drv_context(struct intel_sst_drv **ctx, - struct device *dev, unsigned int dev_id); -int sst_context_init(struct intel_sst_drv *ctx); -void sst_context_cleanup(struct intel_sst_drv *ctx); -void sst_configure_runtime_pm(struct intel_sst_drv *ctx); -void memcpy32_toio(void __iomem *dst, const void *src, int count); -void memcpy32_fromio(void *dst, const void __iomem *src, int count); - -#endif diff --git a/sound/soc/intel/sst/sst_acpi.c b/sound/soc/intel/sst/sst_acpi.c deleted file mode 100644 index 2a19cbc..0000000 --- a/sound/soc/intel/sst/sst_acpi.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * sst_acpi.c - SST (LPE) driver init file for ACPI enumeration. - * - * Copyright (c) 2013, Intel Corporation. - * - * Authors: Ramesh Babu K V - * Authors: Omair Mohammed Abdullah - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "../common/sst-dsp.h" -#include "sst.h" - -struct sst_machines { - char *codec_id; - char board[32]; - char machine[32]; - void (*machine_quirk)(void); - char firmware[FW_NAME_SIZE]; - struct sst_platform_info *pdata; - -}; - -/* LPE viewpoint addresses */ -#define SST_BYT_IRAM_PHY_START 0xff2c0000 -#define SST_BYT_IRAM_PHY_END 0xff2d4000 -#define SST_BYT_DRAM_PHY_START 0xff300000 -#define SST_BYT_DRAM_PHY_END 0xff320000 -#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */ -#define SST_BYT_IMR_VIRT_END 0xc01fffff -#define SST_BYT_SHIM_PHY_ADDR 0xff340000 -#define SST_BYT_MBOX_PHY_ADDR 0xff344000 -#define SST_BYT_DMA0_PHY_ADDR 0xff298000 -#define SST_BYT_DMA1_PHY_ADDR 0xff29c000 -#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000 -#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000 - -#define BYT_FW_MOD_TABLE_OFFSET 0x80000 -#define BYT_FW_MOD_TABLE_SIZE 0x100 -#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE) - -static const struct sst_info byt_fwparse_info = { - .use_elf = false, - .max_streams = 25, - .iram_start = SST_BYT_IRAM_PHY_START, - .iram_end = SST_BYT_IRAM_PHY_END, - .iram_use = true, - .dram_start = SST_BYT_DRAM_PHY_START, - .dram_end = SST_BYT_DRAM_PHY_END, - .dram_use = true, - .imr_start = SST_BYT_IMR_VIRT_START, - .imr_end = SST_BYT_IMR_VIRT_END, - .imr_use = true, - .mailbox_start = SST_BYT_MBOX_PHY_ADDR, - .num_probes = 0, - .lpe_viewpt_rqd = true, -}; - -static const struct sst_ipc_info byt_ipc_info = { - .ipc_offset = 0, - .mbox_recv_off = 0x400, -}; - -static const struct sst_lib_dnld_info byt_lib_dnld_info = { - .mod_base = SST_BYT_IMR_VIRT_START, - .mod_end = SST_BYT_IMR_VIRT_END, - .mod_table_offset = BYT_FW_MOD_TABLE_OFFSET, - .mod_table_size = BYT_FW_MOD_TABLE_SIZE, - .mod_ddr_dnld = false, -}; - -static const struct sst_res_info byt_rvp_res_info = { - .shim_offset = 0x140000, - .shim_size = 0x000100, - .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR, - .ssp0_offset = 0xa0000, - .ssp0_size = 0x1000, - .dma0_offset = 0x98000, - .dma0_size = 0x4000, - .dma1_offset = 0x9c000, - .dma1_size = 0x4000, - .iram_offset = 0x0c0000, - .iram_size = 0x14000, - .dram_offset = 0x100000, - .dram_size = 0x28000, - .mbox_offset = 0x144000, - .mbox_size = 0x1000, - .acpi_lpe_res_index = 0, - .acpi_ddr_index = 2, - .acpi_ipc_irq_index = 5, -}; - -static struct sst_platform_info byt_rvp_platform_data = { - .probe_data = &byt_fwparse_info, - .ipc_info = &byt_ipc_info, - .lib_info = &byt_lib_dnld_info, - .res_info = &byt_rvp_res_info, - .platform = "sst-mfld-platform", -}; - -/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail, - * so pdata is same as Baytrail. - */ -static struct sst_platform_info chv_platform_data = { - .probe_data = &byt_fwparse_info, - .ipc_info = &byt_ipc_info, - .lib_info = &byt_lib_dnld_info, - .res_info = &byt_rvp_res_info, - .platform = "sst-mfld-platform", -}; - -static int sst_platform_get_resources(struct intel_sst_drv *ctx) -{ - struct resource *rsrc; - struct platform_device *pdev = to_platform_device(ctx->dev); - - /* All ACPI resource request here */ - /* Get Shim addr */ - rsrc = platform_get_resource(pdev, IORESOURCE_MEM, - ctx->pdata->res_info->acpi_lpe_res_index); - if (!rsrc) { - dev_err(ctx->dev, "Invalid SHIM base from IFWI"); - return -EIO; - } - dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start, - (unsigned int)resource_size(rsrc)); - - ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset; - ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1; - dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base); - ctx->iram = devm_ioremap_nocache(ctx->dev, ctx->iram_base, - ctx->pdata->res_info->iram_size); - if (!ctx->iram) { - dev_err(ctx->dev, "unable to map IRAM"); - return -EIO; - } - - ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset; - ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1; - dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base); - ctx->dram = devm_ioremap_nocache(ctx->dev, ctx->dram_base, - ctx->pdata->res_info->dram_size); - if (!ctx->dram) { - dev_err(ctx->dev, "unable to map DRAM"); - return -EIO; - } - - ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset; - dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add); - ctx->shim = devm_ioremap_nocache(ctx->dev, ctx->shim_phy_add, - ctx->pdata->res_info->shim_size); - if (!ctx->shim) { - dev_err(ctx->dev, "unable to map SHIM"); - return -EIO; - } - - /* reassign physical address to LPE viewpoint address */ - ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr; - - /* Get mailbox addr */ - ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset; - dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add); - ctx->mailbox = devm_ioremap_nocache(ctx->dev, ctx->mailbox_add, - ctx->pdata->res_info->mbox_size); - if (!ctx->mailbox) { - dev_err(ctx->dev, "unable to map mailbox"); - return -EIO; - } - - /* reassign physical address to LPE viewpoint address */ - ctx->mailbox_add = ctx->info.mailbox_start; - - rsrc = platform_get_resource(pdev, IORESOURCE_MEM, - ctx->pdata->res_info->acpi_ddr_index); - if (!rsrc) { - dev_err(ctx->dev, "Invalid DDR base from IFWI"); - return -EIO; - } - ctx->ddr_base = rsrc->start; - ctx->ddr_end = rsrc->end; - dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base); - ctx->ddr = devm_ioremap_nocache(ctx->dev, ctx->ddr_base, - resource_size(rsrc)); - if (!ctx->ddr) { - dev_err(ctx->dev, "unable to map DDR"); - return -EIO; - } - - /* Find the IRQ */ - ctx->irq_num = platform_get_irq(pdev, - ctx->pdata->res_info->acpi_ipc_irq_index); - return 0; -} - -static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, - void *context, void **ret) -{ - *(bool *)context = true; - return AE_OK; -} - -static struct sst_machines *sst_acpi_find_machine( - struct sst_machines *machines) -{ - struct sst_machines *mach; - bool found = false; - - for (mach = machines; mach->codec_id; mach++) - if (ACPI_SUCCESS(acpi_get_devices(mach->codec_id, - sst_acpi_mach_match, - &found, NULL)) && found) - return mach; - - return NULL; -} - -static int sst_acpi_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - int ret = 0; - struct intel_sst_drv *ctx; - const struct acpi_device_id *id; - struct sst_machines *mach; - struct platform_device *mdev; - struct platform_device *plat_dev; - unsigned int dev_id; - - id = acpi_match_device(dev->driver->acpi_match_table, dev); - if (!id) - return -ENODEV; - dev_dbg(dev, "for %s", id->id); - - mach = (struct sst_machines *)id->driver_data; - mach = sst_acpi_find_machine(mach); - if (mach == NULL) { - dev_err(dev, "No matching machine driver found\n"); - return -ENODEV; - } - - ret = kstrtouint(id->id, 16, &dev_id); - if (ret < 0) { - dev_err(dev, "Unique device id conversion error: %d\n", ret); - return ret; - } - - dev_dbg(dev, "ACPI device id: %x\n", dev_id); - - plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0); - if (IS_ERR(plat_dev)) { - dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform); - return PTR_ERR(plat_dev); - } - - /* Create platform device for sst machine driver */ - mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0); - if (IS_ERR(mdev)) { - dev_err(dev, "Failed to create machine device: %s\n", mach->machine); - return PTR_ERR(mdev); - } - - ret = sst_alloc_drv_context(&ctx, dev, dev_id); - if (ret < 0) - return ret; - - /* Fill sst platform data */ - ctx->pdata = mach->pdata; - strcpy(ctx->firmware_name, mach->firmware); - - ret = sst_platform_get_resources(ctx); - if (ret) - return ret; - - ret = sst_context_init(ctx); - if (ret < 0) - return ret; - - /* need to save shim registers in BYT */ - ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64), - GFP_KERNEL); - if (!ctx->shim_regs64) { - return -ENOMEM; - goto do_sst_cleanup; - } - - sst_configure_runtime_pm(ctx); - platform_set_drvdata(pdev, ctx); - return ret; - -do_sst_cleanup: - sst_context_cleanup(ctx); - platform_set_drvdata(pdev, NULL); - dev_err(ctx->dev, "failed with %d\n", ret); - return ret; -} - -/** -* intel_sst_remove - remove function -* -* @pdev: platform device structure -* -* This function is called by OS when a device is unloaded -* This frees the interrupt etc -*/ -static int sst_acpi_remove(struct platform_device *pdev) -{ - struct intel_sst_drv *ctx; - - ctx = platform_get_drvdata(pdev); - sst_context_cleanup(ctx); - platform_set_drvdata(pdev, NULL); - return 0; -} - -static struct sst_machines sst_acpi_bytcr[] = { - {"10EC5640", "T100", "bytt100_rt5640", NULL, "intel/fw_sst_0f28.bin", - &byt_rvp_platform_data }, - {}, -}; - -/* Cherryview-based platforms: CherryTrail and Braswell */ -static struct sst_machines sst_acpi_chv[] = { - {"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "intel/fw_sst_22a8.bin", - &chv_platform_data }, - {"10EC5645", "cht-bsw", "cht-bsw-rt5645", NULL, "intel/fw_sst_22a8.bin", - &chv_platform_data }, - {}, -}; - -static const struct acpi_device_id sst_acpi_ids[] = { - { "80860F28", (unsigned long)&sst_acpi_bytcr}, - { "808622A8", (unsigned long) &sst_acpi_chv}, - { }, -}; - -MODULE_DEVICE_TABLE(acpi, sst_acpi_ids); - -static struct platform_driver sst_acpi_driver = { - .driver = { - .name = "intel_sst_acpi", - .acpi_match_table = ACPI_PTR(sst_acpi_ids), - .pm = &intel_sst_pm, - }, - .probe = sst_acpi_probe, - .remove = sst_acpi_remove, -}; - -module_platform_driver(sst_acpi_driver); - -MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver"); -MODULE_AUTHOR("Ramesh Babu K V"); -MODULE_AUTHOR("Omair Mohammed Abdullah"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/sst/sst_drv_interface.c b/sound/soc/intel/sst/sst_drv_interface.c deleted file mode 100644 index 36d68b8..0000000 --- a/sound/soc/intel/sst/sst_drv_interface.c +++ /dev/null @@ -1,741 +0,0 @@ -/* - * sst_drv_interface.c - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corp - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" -#include "../common/sst-dsp.h" - - - -#define NUM_CODEC 2 -#define MIN_FRAGMENT 2 -#define MAX_FRAGMENT 4 -#define MIN_FRAGMENT_SIZE (50 * 1024) -#define MAX_FRAGMENT_SIZE (1024 * 1024) -#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1) - -int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id) -{ - struct stream_info *stream; - int ret = 0; - - stream = get_stream_info(ctx, str_id); - if (stream) { - /* str_id is valid, so stream is alloacted */ - ret = sst_free_stream(ctx, str_id); - if (ret) - sst_clean_stream(&ctx->streams[str_id]); - return ret; - } else { - dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id); - } - return ret; -} - -int sst_get_stream_allocated(struct intel_sst_drv *ctx, - struct snd_sst_params *str_param, - struct snd_sst_lib_download **lib_dnld) -{ - int retval; - - retval = ctx->ops->alloc_stream(ctx, str_param); - if (retval > 0) - dev_dbg(ctx->dev, "Stream allocated %d\n", retval); - return retval; - -} - -/* - * sst_get_sfreq - this function returns the frequency of the stream - * - * @str_param : stream params - */ -int sst_get_sfreq(struct snd_sst_params *str_param) -{ - switch (str_param->codec) { - case SST_CODEC_TYPE_PCM: - return str_param->sparams.uc.pcm_params.sfreq; - case SST_CODEC_TYPE_AAC: - return str_param->sparams.uc.aac_params.externalsr; - case SST_CODEC_TYPE_MP3: - return 0; - default: - return -EINVAL; - } -} - -/* - * sst_get_num_channel - get number of channels for the stream - * - * @str_param : stream params - */ -int sst_get_num_channel(struct snd_sst_params *str_param) -{ - switch (str_param->codec) { - case SST_CODEC_TYPE_PCM: - return str_param->sparams.uc.pcm_params.num_chan; - case SST_CODEC_TYPE_MP3: - return str_param->sparams.uc.mp3_params.num_chan; - case SST_CODEC_TYPE_AAC: - return str_param->sparams.uc.aac_params.num_chan; - default: - return -EINVAL; - } -} - -/* - * sst_get_stream - this function prepares for stream allocation - * - * @str_param : stream param - */ -int sst_get_stream(struct intel_sst_drv *ctx, - struct snd_sst_params *str_param) -{ - int retval; - struct stream_info *str_info; - - /* stream is not allocated, we are allocating */ - retval = ctx->ops->alloc_stream(ctx, str_param); - if (retval <= 0) { - return -EIO; - } - /* store sampling freq */ - str_info = &ctx->streams[retval]; - str_info->sfreq = sst_get_sfreq(str_param); - - return retval; -} - -static int sst_power_control(struct device *dev, bool state) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - int ret = 0; - int usage_count = 0; - -#ifdef CONFIG_PM - usage_count = atomic_read(&dev->power.usage_count); -#else - usage_count = 1; -#endif - - if (state == true) { - ret = pm_runtime_get_sync(dev); - - dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count); - if (ret < 0) { - dev_err(ctx->dev, "Runtime get failed with err: %d\n", ret); - return ret; - } - if ((ctx->sst_state == SST_RESET) && (usage_count == 1)) { - ret = sst_load_fw(ctx); - if (ret) { - dev_err(dev, "FW download fail %d\n", ret); - sst_set_fw_state_locked(ctx, SST_RESET); - ret = sst_pm_runtime_put(ctx); - } - } - } else { - dev_dbg(ctx->dev, "Disable: pm usage count: %d\n", usage_count); - return sst_pm_runtime_put(ctx); - } - return ret; -} - -/* - * sst_open_pcm_stream - Open PCM interface - * - * @str_param: parameters of pcm stream - * - * This function is called by MID sound card driver to open - * a new pcm interface - */ -static int sst_open_pcm_stream(struct device *dev, - struct snd_sst_params *str_param) -{ - int retval; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (!str_param) - return -EINVAL; - - retval = sst_get_stream(ctx, str_param); - if (retval > 0) - ctx->stream_cnt++; - else - dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval); - - return retval; -} - -static int sst_cdev_open(struct device *dev, - struct snd_sst_params *str_params, struct sst_compress_cb *cb) -{ - int str_id, retval; - struct stream_info *stream; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - retval = pm_runtime_get_sync(ctx->dev); - if (retval < 0) - return retval; - - str_id = sst_get_stream(ctx, str_params); - if (str_id > 0) { - dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id); - stream = &ctx->streams[str_id]; - stream->compr_cb = cb->compr_cb; - stream->compr_cb_param = cb->param; - stream->drain_notify = cb->drain_notify; - stream->drain_cb_param = cb->drain_cb_param; - } else { - dev_err(dev, "stream encountered error during alloc %d\n", str_id); - str_id = -EINVAL; - sst_pm_runtime_put(ctx); - } - return str_id; -} - -static int sst_cdev_close(struct device *dev, unsigned int str_id) -{ - int retval; - struct stream_info *stream; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - stream = get_stream_info(ctx, str_id); - if (!stream) { - dev_err(dev, "stream info is NULL for str %d!!!\n", str_id); - return -EINVAL; - } - - if (stream->status == STREAM_RESET) { - dev_dbg(dev, "stream in reset state...\n"); - stream->status = STREAM_UN_INIT; - - retval = 0; - goto put; - } - - retval = sst_free_stream(ctx, str_id); -put: - stream->compr_cb_param = NULL; - stream->compr_cb = NULL; - - if (retval) - dev_err(dev, "free stream returned err %d\n", retval); - - dev_dbg(dev, "End\n"); - return retval; - -} - -static int sst_cdev_ack(struct device *dev, unsigned int str_id, - unsigned long bytes) -{ - struct stream_info *stream; - struct snd_sst_tstamp fw_tstamp = {0,}; - int offset; - void __iomem *addr; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - stream = get_stream_info(ctx, str_id); - if (!stream) - return -EINVAL; - - /* update bytes sent */ - stream->cumm_bytes += bytes; - dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes); - - memcpy_fromio(&fw_tstamp, - ((void *)(ctx->mailbox + ctx->tstamp) - +(str_id * sizeof(fw_tstamp))), - sizeof(fw_tstamp)); - - fw_tstamp.bytes_copied = stream->cumm_bytes; - dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n", - fw_tstamp.bytes_copied, bytes); - - addr = ((void *)(ctx->mailbox + ctx->tstamp)) + - (str_id * sizeof(fw_tstamp)); - offset = offsetof(struct snd_sst_tstamp, bytes_copied); - sst_shim_write(addr, offset, fw_tstamp.bytes_copied); - return 0; -} - -static int sst_cdev_set_metadata(struct device *dev, - unsigned int str_id, struct snd_compr_metadata *metadata) -{ - int retval = 0; - struct stream_info *str_info; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - dev_dbg(dev, "set metadata for stream %d\n", str_id); - - str_info = get_stream_info(ctx, str_id); - if (!str_info) - return -EINVAL; - - dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id); - retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD, - IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id, - sizeof(*metadata), metadata, NULL, - true, true, true, false); - - return retval; -} - -static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - return sst_pause_stream(ctx, str_id); -} - -static int sst_cdev_stream_pause_release(struct device *dev, - unsigned int str_id) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - return sst_resume_stream(ctx, str_id); -} - -static int sst_cdev_stream_start(struct device *dev, unsigned int str_id) -{ - struct stream_info *str_info; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - str_info = get_stream_info(ctx, str_id); - if (!str_info) - return -EINVAL; - str_info->prev = str_info->status; - str_info->status = STREAM_RUNNING; - return sst_start_stream(ctx, str_id); -} - -static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - return sst_drop_stream(ctx, str_id); -} - -static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - return sst_drain_stream(ctx, str_id, false); -} - -static int sst_cdev_stream_partial_drain(struct device *dev, - unsigned int str_id) -{ - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - return sst_drain_stream(ctx, str_id, true); -} - -static int sst_cdev_tstamp(struct device *dev, unsigned int str_id, - struct snd_compr_tstamp *tstamp) -{ - struct snd_sst_tstamp fw_tstamp = {0,}; - struct stream_info *stream; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - memcpy_fromio(&fw_tstamp, - ((void *)(ctx->mailbox + ctx->tstamp) - +(str_id * sizeof(fw_tstamp))), - sizeof(fw_tstamp)); - - stream = get_stream_info(ctx, str_id); - if (!stream) - return -EINVAL; - dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter); - - tstamp->copied_total = fw_tstamp.ring_buffer_counter; - tstamp->pcm_frames = fw_tstamp.frames_decoded; - tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter, - (u64)((stream->num_ch) * SST_GET_BYTES_PER_SAMPLE(24))); - tstamp->sampling_rate = fw_tstamp.sampling_frequency; - - dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames); - dev_dbg(dev, "Ptr Query on strid = %d copied_total %d, decodec %d\n", - str_id, tstamp->copied_total, tstamp->pcm_frames); - dev_dbg(dev, "rendered %d\n", tstamp->pcm_io_frames); - - return 0; -} - -static int sst_cdev_caps(struct snd_compr_caps *caps) -{ - caps->num_codecs = NUM_CODEC; - caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ - caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ - caps->min_fragments = MIN_FRAGMENT; - caps->max_fragments = MAX_FRAGMENT; - caps->codecs[0] = SND_AUDIOCODEC_MP3; - caps->codecs[1] = SND_AUDIOCODEC_AAC; - return 0; -} - -static struct snd_compr_codec_caps caps_mp3 = { - .num_descriptors = 1, - .descriptor[0].max_ch = 2, - .descriptor[0].sample_rates[0] = 48000, - .descriptor[0].sample_rates[1] = 44100, - .descriptor[0].sample_rates[2] = 32000, - .descriptor[0].sample_rates[3] = 16000, - .descriptor[0].sample_rates[4] = 8000, - .descriptor[0].num_sample_rates = 5, - .descriptor[0].bit_rate[0] = 320, - .descriptor[0].bit_rate[1] = 192, - .descriptor[0].num_bitrates = 2, - .descriptor[0].profiles = 0, - .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, - .descriptor[0].formats = 0, -}; - -static struct snd_compr_codec_caps caps_aac = { - .num_descriptors = 2, - .descriptor[1].max_ch = 2, - .descriptor[0].sample_rates[0] = 48000, - .descriptor[0].sample_rates[1] = 44100, - .descriptor[0].sample_rates[2] = 32000, - .descriptor[0].sample_rates[3] = 16000, - .descriptor[0].sample_rates[4] = 8000, - .descriptor[0].num_sample_rates = 5, - .descriptor[1].bit_rate[0] = 320, - .descriptor[1].bit_rate[1] = 192, - .descriptor[1].num_bitrates = 2, - .descriptor[1].profiles = 0, - .descriptor[1].modes = 0, - .descriptor[1].formats = - (SND_AUDIOSTREAMFORMAT_MP4ADTS | - SND_AUDIOSTREAMFORMAT_RAW), -}; - -static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec) -{ - if (codec->codec == SND_AUDIOCODEC_MP3) - *codec = caps_mp3; - else if (codec->codec == SND_AUDIOCODEC_AAC) - *codec = caps_aac; - else - return -EINVAL; - - return 0; -} - -void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id) -{ - struct stream_info *stream; - - dev_dbg(ctx->dev, "fragment elapsed from firmware for str_id %d\n", - str_id); - stream = &ctx->streams[str_id]; - if (stream->compr_cb) - stream->compr_cb(stream->compr_cb_param); -} - -/* - * sst_close_pcm_stream - Close PCM interface - * - * @str_id: stream id to be closed - * - * This function is called by MID sound card driver to close - * an existing pcm interface - */ -static int sst_close_pcm_stream(struct device *dev, unsigned int str_id) -{ - struct stream_info *stream; - int retval = 0; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - stream = get_stream_info(ctx, str_id); - if (!stream) { - dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id); - return -EINVAL; - } - - if (stream->status == STREAM_RESET) { - /* silently fail here as we have cleaned the stream earlier */ - dev_dbg(ctx->dev, "stream in reset state...\n"); - - retval = 0; - goto put; - } - - retval = free_stream_context(ctx, str_id); -put: - stream->pcm_substream = NULL; - stream->status = STREAM_UN_INIT; - stream->period_elapsed = NULL; - ctx->stream_cnt--; - - if (retval) - dev_err(ctx->dev, "free stream returned err %d\n", retval); - - dev_dbg(ctx->dev, "Exit\n"); - return 0; -} - -static inline int sst_calc_tstamp(struct intel_sst_drv *ctx, - struct pcm_stream_info *info, - struct snd_pcm_substream *substream, - struct snd_sst_tstamp *fw_tstamp) -{ - size_t delay_bytes, delay_frames; - size_t buffer_sz; - u32 pointer_bytes, pointer_samples; - - dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n", - fw_tstamp->ring_buffer_counter); - dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n", - fw_tstamp->hardware_counter); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter - - fw_tstamp->hardware_counter); - else - delay_bytes = (size_t) (fw_tstamp->hardware_counter - - fw_tstamp->ring_buffer_counter); - delay_frames = bytes_to_frames(substream->runtime, delay_bytes); - buffer_sz = snd_pcm_lib_buffer_bytes(substream); - div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes); - pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes); - - dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes); - - info->buffer_ptr = pointer_samples / substream->runtime->channels; - - info->pcm_delay = delay_frames / substream->runtime->channels; - dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n", - info->buffer_ptr, info->pcm_delay); - return 0; -} - -static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info) -{ - struct stream_info *stream; - struct snd_pcm_substream *substream; - struct snd_sst_tstamp fw_tstamp; - unsigned int str_id; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - str_id = info->str_id; - stream = get_stream_info(ctx, str_id); - if (!stream) - return -EINVAL; - - if (!stream->pcm_substream) - return -EINVAL; - substream = stream->pcm_substream; - - memcpy_fromio(&fw_tstamp, - ((void *)(ctx->mailbox + ctx->tstamp) - + (str_id * sizeof(fw_tstamp))), - sizeof(fw_tstamp)); - return sst_calc_tstamp(ctx, info, substream, &fw_tstamp); -} - -static int sst_stream_start(struct device *dev, int str_id) -{ - struct stream_info *str_info; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (ctx->sst_state != SST_FW_RUNNING) - return 0; - str_info = get_stream_info(ctx, str_id); - if (!str_info) - return -EINVAL; - str_info->prev = str_info->status; - str_info->status = STREAM_RUNNING; - sst_start_stream(ctx, str_id); - - return 0; -} - -static int sst_stream_drop(struct device *dev, int str_id) -{ - struct stream_info *str_info; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (ctx->sst_state != SST_FW_RUNNING) - return 0; - - str_info = get_stream_info(ctx, str_id); - if (!str_info) - return -EINVAL; - str_info->prev = STREAM_UN_INIT; - str_info->status = STREAM_INIT; - return sst_drop_stream(ctx, str_id); -} - -static int sst_stream_pause(struct device *dev, int str_id) -{ - struct stream_info *str_info; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (ctx->sst_state != SST_FW_RUNNING) - return 0; - - str_info = get_stream_info(ctx, str_id); - if (!str_info) - return -EINVAL; - - return sst_pause_stream(ctx, str_id); -} - -static int sst_stream_resume(struct device *dev, int str_id) -{ - struct stream_info *str_info; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (ctx->sst_state != SST_FW_RUNNING) - return 0; - - str_info = get_stream_info(ctx, str_id); - if (!str_info) - return -EINVAL; - return sst_resume_stream(ctx, str_id); -} - -static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info) -{ - int str_id = 0; - struct stream_info *stream; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - str_id = str_info->str_id; - - if (ctx->sst_state != SST_FW_RUNNING) - return 0; - - stream = get_stream_info(ctx, str_id); - if (!stream) - return -EINVAL; - - dev_dbg(ctx->dev, "setting the period ptrs\n"); - stream->pcm_substream = str_info->arg; - stream->period_elapsed = str_info->period_elapsed; - stream->sfreq = str_info->sfreq; - stream->prev = stream->status; - stream->status = STREAM_INIT; - dev_dbg(ctx->dev, - "pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n", - stream->pcm_substream, stream->period_elapsed, - stream->sfreq, stream->status); - - return 0; -} - -/* - * sst_set_byte_stream - Set generic params - * - * @cmd: control cmd to be set - * @arg: command argument - * - * This function is called by MID sound card driver to configure - * SST runtime params. - */ -static int sst_send_byte_stream(struct device *dev, - struct snd_sst_bytes_v2 *bytes) -{ - int ret_val = 0; - struct intel_sst_drv *ctx = dev_get_drvdata(dev); - - if (NULL == bytes) - return -EINVAL; - ret_val = pm_runtime_get_sync(ctx->dev); - if (ret_val < 0) - return ret_val; - - ret_val = sst_send_byte_stream_mrfld(ctx, bytes); - sst_pm_runtime_put(ctx); - - return ret_val; -} - -static struct sst_ops pcm_ops = { - .open = sst_open_pcm_stream, - .stream_init = sst_stream_init, - .stream_start = sst_stream_start, - .stream_drop = sst_stream_drop, - .stream_pause = sst_stream_pause, - .stream_pause_release = sst_stream_resume, - .stream_read_tstamp = sst_read_timestamp, - .send_byte_stream = sst_send_byte_stream, - .close = sst_close_pcm_stream, - .power = sst_power_control, -}; - -static struct compress_sst_ops compr_ops = { - .open = sst_cdev_open, - .close = sst_cdev_close, - .stream_pause = sst_cdev_stream_pause, - .stream_pause_release = sst_cdev_stream_pause_release, - .stream_start = sst_cdev_stream_start, - .stream_drop = sst_cdev_stream_drop, - .stream_drain = sst_cdev_stream_drain, - .stream_partial_drain = sst_cdev_stream_partial_drain, - .tstamp = sst_cdev_tstamp, - .ack = sst_cdev_ack, - .get_caps = sst_cdev_caps, - .get_codec_caps = sst_cdev_codec_caps, - .set_metadata = sst_cdev_set_metadata, - .power = sst_power_control, -}; - -static struct sst_device sst_dsp_device = { - .name = "Intel(R) SST LPE", - .dev = NULL, - .ops = &pcm_ops, - .compr_ops = &compr_ops, -}; - -/* - * sst_register - function to register DSP - * - * This functions registers DSP with the platform driver - */ -int sst_register(struct device *dev) -{ - int ret_val; - - sst_dsp_device.dev = dev; - ret_val = sst_register_dsp(&sst_dsp_device); - if (ret_val) - dev_err(dev, "Unable to register DSP with platform driver\n"); - - return ret_val; -} - -int sst_unregister(struct device *dev) -{ - return sst_unregister_dsp(&sst_dsp_device); -} diff --git a/sound/soc/intel/sst/sst_ipc.c b/sound/soc/intel/sst/sst_ipc.c deleted file mode 100644 index 3943ae8..0000000 --- a/sound/soc/intel/sst/sst_ipc.c +++ /dev/null @@ -1,373 +0,0 @@ -/* - * sst_ipc.c - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corporation - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" -#include "../common/sst-dsp.h" - -struct sst_block *sst_create_block(struct intel_sst_drv *ctx, - u32 msg_id, u32 drv_id) -{ - struct sst_block *msg = NULL; - - dev_dbg(ctx->dev, "Enter\n"); - msg = kzalloc(sizeof(*msg), GFP_KERNEL); - if (!msg) - return NULL; - msg->condition = false; - msg->on = true; - msg->msg_id = msg_id; - msg->drv_id = drv_id; - spin_lock_bh(&ctx->block_lock); - list_add_tail(&msg->node, &ctx->block_list); - spin_unlock_bh(&ctx->block_lock); - - return msg; -} - -/* - * while handling the interrupts, we need to check for message status and - * then if we are blocking for a message - * - * here we are unblocking the blocked ones, this is based on id we have - * passed and search that for block threads. - * We will not find block in two cases - * a) when its small message and block in not there, so silently ignore - * them - * b) when we are actually not able to find the block (bug perhaps) - * - * Since we have bit of small messages we can spam kernel log with err - * print on above so need to keep as debug prints which should be enabled - * via dynamic debug while debugging IPC issues - */ -int sst_wake_up_block(struct intel_sst_drv *ctx, int result, - u32 drv_id, u32 ipc, void *data, u32 size) -{ - struct sst_block *block = NULL; - - dev_dbg(ctx->dev, "Enter\n"); - - spin_lock_bh(&ctx->block_lock); - list_for_each_entry(block, &ctx->block_list, node) { - dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id, - block->drv_id); - if (block->msg_id == ipc && block->drv_id == drv_id) { - dev_dbg(ctx->dev, "free up the block\n"); - block->ret_code = result; - block->data = data; - block->size = size; - block->condition = true; - spin_unlock_bh(&ctx->block_lock); - wake_up(&ctx->wait_queue); - return 0; - } - } - spin_unlock_bh(&ctx->block_lock); - dev_dbg(ctx->dev, - "Block not found or a response received for a short msg for ipc %d, drv_id %d\n", - ipc, drv_id); - return -EINVAL; -} - -int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) -{ - struct sst_block *block = NULL, *__block; - - dev_dbg(ctx->dev, "Enter\n"); - spin_lock_bh(&ctx->block_lock); - list_for_each_entry_safe(block, __block, &ctx->block_list, node) { - if (block == freed) { - pr_debug("pvt_id freed --> %d\n", freed->drv_id); - /* toggle the index position of pvt_id */ - list_del(&freed->node); - spin_unlock_bh(&ctx->block_lock); - kfree(freed->data); - freed->data = NULL; - kfree(freed); - return 0; - } - } - spin_unlock_bh(&ctx->block_lock); - dev_err(ctx->dev, "block is already freed!!!\n"); - return -EINVAL; -} - -int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, - struct ipc_post *ipc_msg, bool sync) -{ - struct ipc_post *msg = ipc_msg; - union ipc_header_mrfld header; - unsigned int loop_count = 0; - int retval = 0; - unsigned long irq_flags; - - dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync); - spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); - header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); - if (sync) { - while (header.p.header_high.part.busy) { - if (loop_count > 25) { - dev_err(sst_drv_ctx->dev, - "sst: Busy wait failed, cant send this msg\n"); - retval = -EBUSY; - goto out; - } - cpu_relax(); - loop_count++; - header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); - } - } else { - if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { - /* queue is empty, nothing to send */ - spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); - dev_dbg(sst_drv_ctx->dev, - "Empty msg queue... NO Action\n"); - return 0; - } - - if (header.p.header_high.part.busy) { - spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); - dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n"); - return 0; - } - - /* copy msg from list */ - msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, - struct ipc_post, node); - list_del(&msg->node); - } - dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n", - msg->mrfld_header.p.header_high.full); - dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n", - msg->mrfld_header.p.header_low_payload); - - if (msg->mrfld_header.p.header_high.part.large) - memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, - msg->mailbox_data, - msg->mrfld_header.p.header_low_payload); - - sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); - -out: - spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); - kfree(msg->mailbox_data); - kfree(msg); - return retval; -} - -void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) -{ - union interrupt_reg_mrfld isr; - union interrupt_reg_mrfld imr; - union ipc_header_mrfld clear_ipc; - unsigned long irq_flags; - - spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); - imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); - isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); - - /* write 1 to clear*/ - isr.part.busy_interrupt = 1; - sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); - - /* Set IA done bit */ - clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); - - clear_ipc.p.header_high.part.busy = 0; - clear_ipc.p.header_high.part.done = 1; - clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; - sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); - /* un mask busy interrupt */ - imr.part.busy_interrupt = 0; - sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); - spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); -} - - -/* - * process_fw_init - process the FW init msg - * - * @msg: IPC message mailbox data from FW - * - * This function processes the FW init msg from FW - * marks FW state and prints debug info of loaded FW - */ -static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, - void *msg) -{ - struct ipc_header_fw_init *init = - (struct ipc_header_fw_init *)msg; - int retval = 0; - - dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n"); - if (init->result) { - sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); - dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n", - init->result); - retval = init->result; - goto ret; - } - -ret: - sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); -} - -static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx, - struct ipc_post *msg) -{ - u32 msg_id; - int str_id; - u32 data_size, i; - void *data_offset; - struct stream_info *stream; - union ipc_header_high msg_high; - u32 msg_low, pipe_id; - - msg_high = msg->mrfld_header.p.header_high; - msg_low = msg->mrfld_header.p.header_low_payload; - msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; - data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); - data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); - - switch (msg_id) { - case IPC_SST_PERIOD_ELAPSED_MRFLD: - pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; - str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); - if (str_id > 0) { - dev_dbg(sst_drv_ctx->dev, - "Period elapsed rcvd for pipe id 0x%x\n", - pipe_id); - stream = &sst_drv_ctx->streams[str_id]; - if (stream->period_elapsed) - stream->period_elapsed(stream->pcm_substream); - if (stream->compr_cb) - stream->compr_cb(stream->compr_cb_param); - } - break; - - case IPC_IA_DRAIN_STREAM_MRFLD: - pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; - str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); - if (str_id > 0) { - stream = &sst_drv_ctx->streams[str_id]; - if (stream->drain_notify) - stream->drain_notify(stream->drain_cb_param); - } - break; - - case IPC_IA_FW_ASYNC_ERR_MRFLD: - dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n"); - for (i = 0; i < (data_size/4); i++) - print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, - 16, 4, data_offset, data_size, false); - break; - - case IPC_IA_FW_INIT_CMPLT_MRFLD: - process_fw_init(sst_drv_ctx, data_offset); - break; - - case IPC_IA_BUF_UNDER_RUN_MRFLD: - pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; - str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); - if (str_id > 0) - dev_err(sst_drv_ctx->dev, - "Buffer under-run for pipe:%#x str_id:%d\n", - pipe_id, str_id); - break; - - default: - dev_err(sst_drv_ctx->dev, - "Unrecognized async msg from FW msg_id %#x\n", msg_id); - } -} - -void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, - struct ipc_post *msg) -{ - unsigned int drv_id; - void *data; - union ipc_header_high msg_high; - u32 msg_low; - struct ipc_dsp_hdr *dsp_hdr; - unsigned int cmd_id; - - msg_high = msg->mrfld_header.p.header_high; - msg_low = msg->mrfld_header.p.header_low_payload; - - dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n", - msg->mrfld_header.p.header_high.full, - msg->mrfld_header.p.header_low_payload); - - drv_id = msg_high.part.drv_id; - - /* Check for async messages first */ - if (drv_id == SST_ASYNC_DRV_ID) { - /*FW sent async large message*/ - process_fw_async_msg(sst_drv_ctx, msg); - return; - } - - /* FW sent short error response for an IPC */ - if (msg_high.part.result && drv_id && !msg_high.part.large) { - /* 32-bit FW error code in msg_low */ - dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); - sst_wake_up_block(sst_drv_ctx, msg_high.part.result, - msg_high.part.drv_id, - msg_high.part.msg_id, NULL, 0); - return; - } - - /* - * Process all valid responses - * if it is a large message, the payload contains the size to - * copy from mailbox - **/ - if (msg_high.part.large) { - data = kzalloc(msg_low, GFP_KERNEL); - if (!data) - return; - memcpy(data, (void *) msg->mailbox_data, msg_low); - /* Copy command id so that we can use to put sst to reset */ - dsp_hdr = (struct ipc_dsp_hdr *)data; - cmd_id = dsp_hdr->cmd_id; - dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id); - if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, - msg_high.part.drv_id, - msg_high.part.msg_id, data, msg_low)) - kfree(data); - } else { - sst_wake_up_block(sst_drv_ctx, msg_high.part.result, - msg_high.part.drv_id, - msg_high.part.msg_id, NULL, 0); - } - -} diff --git a/sound/soc/intel/sst/sst_loader.c b/sound/soc/intel/sst/sst_loader.c deleted file mode 100644 index 6622e66..0000000 --- a/sound/soc/intel/sst/sst_loader.c +++ /dev/null @@ -1,463 +0,0 @@ -/* - * sst_dsp.c - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corp - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This file contains all dsp controlling functions like firmware download, - * setting/resetting dsp cores, etc - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" -#include "../common/sst-dsp.h" - -void memcpy32_toio(void __iomem *dst, const void *src, int count) -{ - /* __iowrite32_copy uses 32-bit count values so divide by 4 for - * right count in words - */ - __iowrite32_copy(dst, src, count/4); -} - -void memcpy32_fromio(void *dst, const void __iomem *src, int count) -{ - /* __iowrite32_copy uses 32-bit count values so divide by 4 for - * right count in words - */ - __iowrite32_copy(dst, src, count/4); -} - -/** - * intel_sst_reset_dsp_mrfld - Resetting SST DSP - * - * This resets DSP in case of MRFLD platfroms - */ -int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx) -{ - union config_status_reg_mrfld csr; - - dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n"); - csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); - - dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); - - csr.full |= 0x7; - sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); - csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); - - dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); - - csr.full &= ~(0x1); - sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); - - csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); - dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); - return 0; -} - -/** - * sst_start_merrifield - Start the SST DSP processor - * - * This starts the DSP in MERRIFIELD platfroms - */ -int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx) -{ - union config_status_reg_mrfld csr; - - dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n"); - csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); - dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); - - csr.full |= 0x7; - sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); - - csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); - dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); - - csr.part.xt_snoop = 1; - csr.full &= ~(0x5); - sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); - - csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); - dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n", - csr.full); - return 0; -} - -static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size, - struct fw_module_header **module, u32 *num_modules) -{ - struct sst_fw_header *header; - const void *sst_fw_in_mem = ctx->fw_in_mem; - - dev_dbg(ctx->dev, "Enter\n"); - - /* Read the header information from the data pointer */ - header = (struct sst_fw_header *)sst_fw_in_mem; - dev_dbg(ctx->dev, - "header sign=%s size=%x modules=%x fmt=%x size=%zx\n", - header->signature, header->file_size, header->modules, - header->file_format, sizeof(*header)); - - /* verify FW */ - if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) || - (size != header->file_size + sizeof(*header))) { - /* Invalid FW signature */ - dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n"); - return -EINVAL; - } - *num_modules = header->modules; - *module = (void *)sst_fw_in_mem + sizeof(*header); - - return 0; -} - -/* - * sst_fill_memcpy_list - Fill the memcpy list - * - * @memcpy_list: List to be filled - * @destn: Destination addr to be filled in the list - * @src: Source addr to be filled in the list - * @size: Size to be filled in the list - * - * Adds the node to the list after required fields - * are populated in the node - */ -static int sst_fill_memcpy_list(struct list_head *memcpy_list, - void *destn, const void *src, u32 size, bool is_io) -{ - struct sst_memcpy_list *listnode; - - listnode = kzalloc(sizeof(*listnode), GFP_KERNEL); - if (listnode == NULL) - return -ENOMEM; - listnode->dstn = destn; - listnode->src = src; - listnode->size = size; - listnode->is_io = is_io; - list_add_tail(&listnode->memcpylist, memcpy_list); - - return 0; -} - -/** - * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list - * - * @sst_drv_ctx : driver context - * @module : FW module header - * @memcpy_list : Pointer to the list to be populated - * Create the memcpy list as the number of block to be copied - * returns error or 0 if module sizes are proper - */ -static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx, - struct fw_module_header *module, struct list_head *memcpy_list) -{ - struct fw_block_info *block; - u32 count; - int ret_val = 0; - void __iomem *ram_iomem; - - dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n", - module->signature, module->mod_size, - module->blocks, module->type); - dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point); - - block = (void *)module + sizeof(*module); - - for (count = 0; count < module->blocks; count++) { - if (block->size <= 0) { - dev_err(sst_drv_ctx->dev, "block size invalid\n"); - return -EINVAL; - } - switch (block->type) { - case SST_IRAM: - ram_iomem = sst_drv_ctx->iram; - break; - case SST_DRAM: - ram_iomem = sst_drv_ctx->dram; - break; - case SST_DDR: - ram_iomem = sst_drv_ctx->ddr; - break; - case SST_CUSTOM_INFO: - block = (void *)block + sizeof(*block) + block->size; - continue; - default: - dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n", - block->type, count); - return -EINVAL; - } - - ret_val = sst_fill_memcpy_list(memcpy_list, - ram_iomem + block->ram_offset, - (void *)block + sizeof(*block), block->size, 1); - if (ret_val) - return ret_val; - - block = (void *)block + sizeof(*block) + block->size; - } - return 0; -} - -/** - * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy - * - * @ctx : pointer to drv context - * @size : size of the firmware - * @fw_list : pointer to list_head to be populated - * This function parses the FW image and saves the parsed image in the list - * for memcpy - */ -static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size, - struct list_head *fw_list) -{ - struct fw_module_header *module; - u32 count, num_modules; - int ret_val; - - ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules); - if (ret_val) - return ret_val; - - for (count = 0; count < num_modules; count++) { - ret_val = sst_parse_module_memcpy(ctx, module, fw_list); - if (ret_val) - return ret_val; - module = (void *)module + sizeof(*module) + module->mod_size; - } - - return 0; -} - -/** - * sst_do_memcpy - function initiates the memcpy - * - * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated - * - * Triggers the memcpy - */ -static void sst_do_memcpy(struct list_head *memcpy_list) -{ - struct sst_memcpy_list *listnode; - - list_for_each_entry(listnode, memcpy_list, memcpylist) { - if (listnode->is_io == true) - memcpy32_toio((void __iomem *)listnode->dstn, - listnode->src, listnode->size); - else - memcpy(listnode->dstn, listnode->src, listnode->size); - } -} - -void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx) -{ - struct sst_memcpy_list *listnode, *tmplistnode; - - /* Free the list */ - if (!list_empty(&sst_drv_ctx->memcpy_list)) { - list_for_each_entry_safe(listnode, tmplistnode, - &sst_drv_ctx->memcpy_list, memcpylist) { - list_del(&listnode->memcpylist); - kfree(listnode); - } - } -} - -static int sst_cache_and_parse_fw(struct intel_sst_drv *sst, - const struct firmware *fw) -{ - int retval = 0; - - sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); - if (!sst->fw_in_mem) { - retval = -ENOMEM; - goto end_release; - } - dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem); - dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem)); - memcpy(sst->fw_in_mem, fw->data, fw->size); - retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list); - if (retval) { - dev_err(sst->dev, "Failed to parse fw\n"); - kfree(sst->fw_in_mem); - sst->fw_in_mem = NULL; - } - -end_release: - release_firmware(fw); - return retval; - -} - -void sst_firmware_load_cb(const struct firmware *fw, void *context) -{ - struct intel_sst_drv *ctx = context; - - dev_dbg(ctx->dev, "Enter\n"); - - if (fw == NULL) { - dev_err(ctx->dev, "request fw failed\n"); - return; - } - - mutex_lock(&ctx->sst_lock); - - if (ctx->sst_state != SST_RESET || - ctx->fw_in_mem != NULL) { - release_firmware(fw); - mutex_unlock(&ctx->sst_lock); - return; - } - - dev_dbg(ctx->dev, "Request Fw completed\n"); - sst_cache_and_parse_fw(ctx, fw); - mutex_unlock(&ctx->sst_lock); -} - -/* - * sst_request_fw - requests audio fw from kernel and saves a copy - * - * This function requests the SST FW from the kernel, parses it and - * saves a copy in the driver context - */ -static int sst_request_fw(struct intel_sst_drv *sst) -{ - int retval = 0; - const struct firmware *fw; - - retval = request_firmware(&fw, sst->firmware_name, sst->dev); - if (fw == NULL) { - dev_err(sst->dev, "fw is returning as null\n"); - return -EINVAL; - } - if (retval) { - dev_err(sst->dev, "request fw failed %d\n", retval); - return retval; - } - mutex_lock(&sst->sst_lock); - retval = sst_cache_and_parse_fw(sst, fw); - mutex_unlock(&sst->sst_lock); - - return retval; -} - -/* - * Writing the DDR physical base to DCCM offset - * so that FW can use it to setup TLB - */ -static void sst_dccm_config_write(void __iomem *dram_base, - unsigned int ddr_base) -{ - void __iomem *addr; - u32 bss_reset = 0; - - addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET); - memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32)); - bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT); - addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET); - memcpy32_toio(addr, &bss_reset, sizeof(u32)); - -} - -void sst_post_download_mrfld(struct intel_sst_drv *ctx) -{ - sst_dccm_config_write(ctx->dram, ctx->ddr_base); - dev_dbg(ctx->dev, "config written to DCCM\n"); -} - -/** - * sst_load_fw - function to load FW into DSP - * Transfers the FW to DSP using dma/memcpy - */ -int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) -{ - int ret_val = 0; - struct sst_block *block; - - dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n"); - - if (sst_drv_ctx->sst_state != SST_RESET || - sst_drv_ctx->sst_state == SST_SHUTDOWN) - return -EAGAIN; - - if (!sst_drv_ctx->fw_in_mem) { - dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n"); - ret_val = sst_request_fw(sst_drv_ctx); - if (ret_val) - return ret_val; - } - - BUG_ON(!sst_drv_ctx->fw_in_mem); - block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID); - if (block == NULL) - return -ENOMEM; - - /* Prevent C-states beyond C6 */ - pm_qos_update_request(sst_drv_ctx->qos, 0); - - sst_drv_ctx->sst_state = SST_FW_LOADING; - - ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx); - if (ret_val) - goto restore; - - sst_do_memcpy(&sst_drv_ctx->memcpy_list); - - /* Write the DRAM/DCCM config before enabling FW */ - if (sst_drv_ctx->ops->post_download) - sst_drv_ctx->ops->post_download(sst_drv_ctx); - - /* bring sst out of reset */ - ret_val = sst_drv_ctx->ops->start(sst_drv_ctx); - if (ret_val) - goto restore; - - ret_val = sst_wait_timeout(sst_drv_ctx, block); - if (ret_val) { - dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val); - /* FW download failed due to timeout */ - ret_val = -EBUSY; - - } - - -restore: - /* Re-enable Deeper C-states beyond C6 */ - pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); - sst_free_block(sst_drv_ctx, block); - dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n"); - - if (sst_drv_ctx->ops->restore_dsp_context) - sst_drv_ctx->ops->restore_dsp_context(); - sst_drv_ctx->sst_state = SST_FW_RUNNING; - return ret_val; -} - diff --git a/sound/soc/intel/sst/sst_pci.c b/sound/soc/intel/sst/sst_pci.c deleted file mode 100644 index 3a0b3bf..0000000 --- a/sound/soc/intel/sst/sst_pci.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * sst_pci.c - SST (LPE) driver init file for pci enumeration. - * - * Copyright (C) 2008-14 Intel Corp - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" - -static int sst_platform_get_resources(struct intel_sst_drv *ctx) -{ - int ddr_base, ret = 0; - struct pci_dev *pci = ctx->pci; - - ret = pci_request_regions(pci, SST_DRV_NAME); - if (ret) - return ret; - - /* map registers */ - /* DDR base */ - if (ctx->dev_id == SST_MRFLD_PCI_ID) { - ctx->ddr_base = pci_resource_start(pci, 0); - /* check that the relocated IMR base matches with FW Binary */ - ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base); - if (!ctx->pdata->lib_info) { - dev_err(ctx->dev, "lib_info pointer NULL\n"); - ret = -EINVAL; - goto do_release_regions; - } - if (ddr_base != ctx->pdata->lib_info->mod_base) { - dev_err(ctx->dev, - "FW LSP DDR BASE does not match with IFWI\n"); - ret = -EINVAL; - goto do_release_regions; - } - ctx->ddr_end = pci_resource_end(pci, 0); - - ctx->ddr = pcim_iomap(pci, 0, - pci_resource_len(pci, 0)); - if (!ctx->ddr) { - ret = -EINVAL; - goto do_release_regions; - } - dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr); - } else { - ctx->ddr = NULL; - } - /* SHIM */ - ctx->shim_phy_add = pci_resource_start(pci, 1); - ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1)); - if (!ctx->shim) { - ret = -EINVAL; - goto do_release_regions; - } - dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim); - - /* Shared SRAM */ - ctx->mailbox_add = pci_resource_start(pci, 2); - ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2)); - if (!ctx->mailbox) { - ret = -EINVAL; - goto do_release_regions; - } - dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox); - - /* IRAM */ - ctx->iram_end = pci_resource_end(pci, 3); - ctx->iram_base = pci_resource_start(pci, 3); - ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3)); - if (!ctx->iram) { - ret = -EINVAL; - goto do_release_regions; - } - dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram); - - /* DRAM */ - ctx->dram_end = pci_resource_end(pci, 4); - ctx->dram_base = pci_resource_start(pci, 4); - ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4)); - if (!ctx->dram) { - ret = -EINVAL; - goto do_release_regions; - } - dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram); -do_release_regions: - pci_release_regions(pci); - return 0; -} - -/* - * intel_sst_probe - PCI probe function - * - * @pci: PCI device structure - * @pci_id: PCI device ID structure - * - */ -static int intel_sst_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - int ret = 0; - struct intel_sst_drv *sst_drv_ctx; - struct sst_platform_info *sst_pdata = pci->dev.platform_data; - - dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device); - ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device); - if (ret < 0) - return ret; - - sst_drv_ctx->pdata = sst_pdata; - sst_drv_ctx->irq_num = pci->irq; - snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name), - "%s%04x%s", "fw_sst_", - sst_drv_ctx->dev_id, ".bin"); - - ret = sst_context_init(sst_drv_ctx); - if (ret < 0) - return ret; - - /* Init the device */ - ret = pcim_enable_device(pci); - if (ret) { - dev_err(sst_drv_ctx->dev, - "device can't be enabled. Returned err: %d\n", ret); - goto do_free_drv_ctx; - } - sst_drv_ctx->pci = pci_dev_get(pci); - ret = sst_platform_get_resources(sst_drv_ctx); - if (ret < 0) - goto do_free_drv_ctx; - - pci_set_drvdata(pci, sst_drv_ctx); - sst_configure_runtime_pm(sst_drv_ctx); - - return ret; - -do_free_drv_ctx: - sst_context_cleanup(sst_drv_ctx); - dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret); - return ret; -} - -/** - * intel_sst_remove - PCI remove function - * - * @pci: PCI device structure - * - * This function is called by OS when a device is unloaded - * This frees the interrupt etc - */ -static void intel_sst_remove(struct pci_dev *pci) -{ - struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci); - - sst_context_cleanup(sst_drv_ctx); - pci_dev_put(sst_drv_ctx->pci); - pci_release_regions(pci); - pci_set_drvdata(pci, NULL); -} - -/* PCI Routines */ -static struct pci_device_id intel_sst_ids[] = { - { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, - { 0, } -}; - -static struct pci_driver sst_driver = { - .name = SST_DRV_NAME, - .id_table = intel_sst_ids, - .probe = intel_sst_probe, - .remove = intel_sst_remove, -#ifdef CONFIG_PM - .driver = { - .pm = &intel_sst_pm, - }, -#endif -}; - -module_pci_driver(sst_driver); - -MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver"); -MODULE_AUTHOR("Vinod Koul "); -MODULE_AUTHOR("Harsha Priya "); -MODULE_AUTHOR("Dharageswari R "); -MODULE_AUTHOR("KP Jeeja "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/sst/sst_pvt.c b/sound/soc/intel/sst/sst_pvt.c deleted file mode 100644 index 2bb0e9e..0000000 --- a/sound/soc/intel/sst/sst_pvt.c +++ /dev/null @@ -1,449 +0,0 @@ -/* - * sst_pvt.c - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corp - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" -#include "../common/sst-dsp.h" - -int sst_shim_write(void __iomem *addr, int offset, int value) -{ - writel(value, addr + offset); - return 0; -} - -u32 sst_shim_read(void __iomem *addr, int offset) -{ - return readl(addr + offset); -} - -u64 sst_reg_read64(void __iomem *addr, int offset) -{ - u64 val = 0; - - memcpy_fromio(&val, addr + offset, sizeof(val)); - - return val; -} - -int sst_shim_write64(void __iomem *addr, int offset, u64 value) -{ - memcpy_toio(addr + offset, &value, sizeof(value)); - return 0; -} - -u64 sst_shim_read64(void __iomem *addr, int offset) -{ - u64 val = 0; - - memcpy_fromio(&val, addr + offset, sizeof(val)); - return val; -} - -void sst_set_fw_state_locked( - struct intel_sst_drv *sst_drv_ctx, int sst_state) -{ - mutex_lock(&sst_drv_ctx->sst_lock); - sst_drv_ctx->sst_state = sst_state; - mutex_unlock(&sst_drv_ctx->sst_lock); -} - -/* - * sst_wait_interruptible - wait on event - * - * @sst_drv_ctx: Driver context - * @block: Driver block to wait on - * - * This function waits without a timeout (and is interruptable) for a - * given block event - */ -int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, - struct sst_block *block) -{ - int retval = 0; - - if (!wait_event_interruptible(sst_drv_ctx->wait_queue, - block->condition)) { - /* event wake */ - if (block->ret_code < 0) { - dev_err(sst_drv_ctx->dev, - "stream failed %d\n", block->ret_code); - retval = -EBUSY; - } else { - dev_dbg(sst_drv_ctx->dev, "event up\n"); - retval = 0; - } - } else { - dev_err(sst_drv_ctx->dev, "signal interrupted\n"); - retval = -EINTR; - } - return retval; - -} - -unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) -{ - unsigned long long val = 0; - - switch (sst->dev_id) { - case SST_MRFLD_PCI_ID: - case SST_BYT_ACPI_ID: - val = sst_shim_read64(sst->shim, addr); - break; - } - return val; -} - -void write_shim_data(struct intel_sst_drv *sst, int addr, - unsigned long long data) -{ - switch (sst->dev_id) { - case SST_MRFLD_PCI_ID: - case SST_BYT_ACPI_ID: - sst_shim_write64(sst->shim, addr, (u64) data); - break; - } -} - -/* - * sst_wait_timeout - wait on event for timeout - * - * @sst_drv_ctx: Driver context - * @block: Driver block to wait on - * - * This function waits with a timeout value (and is not interruptible) on a - * given block event - */ -int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block) -{ - int retval = 0; - - /* - * NOTE: - * Observed that FW processes the alloc msg and replies even - * before the alloc thread has finished execution - */ - dev_dbg(sst_drv_ctx->dev, - "waiting for condition %x ipc %d drv_id %d\n", - block->condition, block->msg_id, block->drv_id); - if (wait_event_timeout(sst_drv_ctx->wait_queue, - block->condition, - msecs_to_jiffies(SST_BLOCK_TIMEOUT))) { - /* event wake */ - dev_dbg(sst_drv_ctx->dev, "Event wake %x\n", - block->condition); - dev_dbg(sst_drv_ctx->dev, "message ret: %d\n", - block->ret_code); - retval = -block->ret_code; - } else { - block->on = false; - dev_err(sst_drv_ctx->dev, - "Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n", - block->condition, block->msg_id, sst_drv_ctx->sst_state); - sst_drv_ctx->sst_state = SST_RESET; - - retval = -EBUSY; - } - return retval; -} - -/* - * sst_create_ipc_msg - create a IPC message - * - * @arg: ipc message - * @large: large or short message - * - * this function allocates structures to send a large or short - * message to the firmware - */ -int sst_create_ipc_msg(struct ipc_post **arg, bool large) -{ - struct ipc_post *msg; - - msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC); - if (!msg) - return -ENOMEM; - if (large) { - msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC); - if (!msg->mailbox_data) { - kfree(msg); - return -ENOMEM; - } - } else { - msg->mailbox_data = NULL; - } - msg->is_large = large; - *arg = msg; - return 0; -} - -/* - * sst_create_block_and_ipc_msg - Creates IPC message and sst block - * @arg: passed to sst_create_ipc_message API - * @large: large or short message - * @sst_drv_ctx: sst driver context - * @block: return block allocated - * @msg_id: IPC - * @drv_id: stream id or private id - */ -int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, - struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, - u32 msg_id, u32 drv_id) -{ - int retval = 0; - - retval = sst_create_ipc_msg(arg, large); - if (retval) - return retval; - *block = sst_create_block(sst_drv_ctx, msg_id, drv_id); - if (*block == NULL) { - kfree(*arg); - return -ENOMEM; - } - return retval; -} - -/* - * sst_clean_stream - clean the stream context - * - * @stream: stream structure - * - * this function resets the stream contexts - * should be called in free - */ -void sst_clean_stream(struct stream_info *stream) -{ - stream->status = STREAM_UN_INIT; - stream->prev = STREAM_UN_INIT; - mutex_lock(&stream->lock); - stream->cumm_bytes = 0; - mutex_unlock(&stream->lock); -} - -int sst_prepare_and_post_msg(struct intel_sst_drv *sst, - int task_id, int ipc_msg, int cmd_id, int pipe_id, - size_t mbox_data_len, const void *mbox_data, void **data, - bool large, bool fill_dsp, bool sync, bool response) -{ - struct ipc_post *msg = NULL; - struct ipc_dsp_hdr dsp_hdr; - struct sst_block *block; - int ret = 0, pvt_id; - - pvt_id = sst_assign_pvt_id(sst); - if (pvt_id < 0) - return pvt_id; - - if (response) - ret = sst_create_block_and_ipc_msg( - &msg, large, sst, &block, ipc_msg, pvt_id); - else - ret = sst_create_ipc_msg(&msg, large); - - if (ret < 0) { - test_and_clear_bit(pvt_id, &sst->pvt_id); - return -ENOMEM; - } - - dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n", - pvt_id, pipe_id, task_id, ipc_msg); - sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg, - task_id, large, pvt_id); - msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len; - msg->mrfld_header.p.header_high.part.res_rqd = !sync; - dev_dbg(sst->dev, "header:%x\n", - msg->mrfld_header.p.header_high.full); - dev_dbg(sst->dev, "response rqd: %x", - msg->mrfld_header.p.header_high.part.res_rqd); - dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d", - msg->mrfld_header.p.header_low_payload); - if (fill_dsp) { - sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len); - memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); - if (mbox_data_len) { - memcpy(msg->mailbox_data + sizeof(dsp_hdr), - mbox_data, mbox_data_len); - } - } - - if (sync) - sst->ops->post_message(sst, msg, true); - else - sst_add_to_dispatch_list_and_post(sst, msg); - - if (response) { - ret = sst_wait_timeout(sst, block); - if (ret < 0) { - goto out; - } else if(block->data) { - if (!data) - goto out; - *data = kzalloc(block->size, GFP_KERNEL); - if (!(*data)) { - ret = -ENOMEM; - goto out; - } else - memcpy(data, (void *) block->data, block->size); - } - } -out: - if (response) - sst_free_block(sst, block); - test_and_clear_bit(pvt_id, &sst->pvt_id); - return ret; -} - -int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) -{ - int ret; - - pm_runtime_mark_last_busy(sst_drv->dev); - ret = pm_runtime_put_autosuspend(sst_drv->dev); - if (ret < 0) - return ret; - return 0; -} - -void sst_fill_header_mrfld(union ipc_header_mrfld *header, - int msg, int task_id, int large, int drv_id) -{ - header->full = 0; - header->p.header_high.part.msg_id = msg; - header->p.header_high.part.task_id = task_id; - header->p.header_high.part.large = large; - header->p.header_high.part.drv_id = drv_id; - header->p.header_high.part.done = 0; - header->p.header_high.part.busy = 1; - header->p.header_high.part.res_rqd = 1; -} - -void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, - int pipe_id, int len) -{ - dsp->cmd_id = msg; - dsp->mod_index_id = 0xff; - dsp->pipe_id = pipe_id; - dsp->length = len; - dsp->mod_id = 0; -} - -#define SST_MAX_BLOCKS 15 -/* - * sst_assign_pvt_id - assign a pvt id for stream - * - * @sst_drv_ctx : driver context - * - * this function assigns a private id for calls that dont have stream - * context yet, should be called with lock held - * uses bits for the id, and finds first free bits and assigns that - */ -int sst_assign_pvt_id(struct intel_sst_drv *drv) -{ - int local; - - spin_lock(&drv->block_lock); - /* find first zero index from lsb */ - local = ffz(drv->pvt_id); - dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); - if (local >= SST_MAX_BLOCKS){ - spin_unlock(&drv->block_lock); - dev_err(drv->dev, "PVT _ID error: no free id blocks "); - return -EINVAL; - } - /* toggle the index */ - change_bit(local, &drv->pvt_id); - spin_unlock(&drv->block_lock); - return local; -} - -void sst_init_stream(struct stream_info *stream, - int codec, int sst_id, int ops, u8 slot) -{ - stream->status = STREAM_INIT; - stream->prev = STREAM_UN_INIT; - stream->ops = ops; -} - -int sst_validate_strid( - struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) { - dev_err(sst_drv_ctx->dev, - "SST ERR: invalid stream id : %d, max %d\n", - str_id, sst_drv_ctx->info.max_streams); - return -EINVAL; - } - - return 0; -} - -struct stream_info *get_stream_info( - struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - if (sst_validate_strid(sst_drv_ctx, str_id)) - return NULL; - return &sst_drv_ctx->streams[str_id]; -} - -int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, - u32 pipe_id) -{ - int i; - - for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) - if (pipe_id == sst_drv_ctx->streams[i].pipe_id) - return i; - - dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id); - return -1; -} - -u32 relocate_imr_addr_mrfld(u32 base_addr) -{ - /* Get the difference from 512MB aligned base addr */ - /* relocate the base */ - base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024)); - return base_addr; -} -EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld); - -void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, - struct ipc_post *msg) -{ - unsigned long irq_flags; - - spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags); - list_add_tail(&msg->node, &sst->ipc_dispatch_list); - spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags); - sst->ops->post_message(sst, NULL, false); -} diff --git a/sound/soc/intel/sst/sst_stream.c b/sound/soc/intel/sst/sst_stream.c deleted file mode 100644 index 7638fca..0000000 --- a/sound/soc/intel/sst/sst_stream.c +++ /dev/null @@ -1,437 +0,0 @@ -/* - * sst_stream.c - Intel SST Driver for audio engine - * - * Copyright (C) 2008-14 Intel Corp - * Authors: Vinod Koul - * Harsha Priya - * Dharageswari R - * KP Jeeja - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../sst-mfld-platform.h" -#include "sst.h" -#include "../common/sst-dsp.h" - -int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) -{ - struct snd_sst_alloc_mrfld alloc_param; - struct snd_sst_params *str_params; - struct snd_sst_tstamp fw_tstamp; - struct stream_info *str_info; - struct snd_sst_alloc_response *response; - unsigned int str_id, pipe_id, task_id; - int i, num_ch, ret = 0; - void *data = NULL; - - dev_dbg(sst_drv_ctx->dev, "Enter\n"); - BUG_ON(!params); - - str_params = (struct snd_sst_params *)params; - memset(&alloc_param, 0, sizeof(alloc_param)); - alloc_param.operation = str_params->ops; - alloc_param.codec_type = str_params->codec; - alloc_param.sg_count = str_params->aparams.sg_count; - alloc_param.ring_buf_info[0].addr = - str_params->aparams.ring_buf_info[0].addr; - alloc_param.ring_buf_info[0].size = - str_params->aparams.ring_buf_info[0].size; - alloc_param.frag_size = str_params->aparams.frag_size; - - memcpy(&alloc_param.codec_params, &str_params->sparams, - sizeof(struct snd_sst_stream_params)); - - /* - * fill channel map params for multichannel support. - * Ideally channel map should be received from upper layers - * for multichannel support. - * Currently hardcoding as per FW reqm. - */ - num_ch = sst_get_num_channel(str_params); - for (i = 0; i < 8; i++) { - if (i < num_ch) - alloc_param.codec_params.uc.pcm_params.channel_map[i] = i; - else - alloc_param.codec_params.uc.pcm_params.channel_map[i] = 0xFF; - } - - str_id = str_params->stream_id; - str_info = get_stream_info(sst_drv_ctx, str_id); - if (str_info == NULL) { - dev_err(sst_drv_ctx->dev, "get stream info returned null\n"); - return -EINVAL; - } - - pipe_id = str_params->device_type; - task_id = str_params->task; - sst_drv_ctx->streams[str_id].pipe_id = pipe_id; - sst_drv_ctx->streams[str_id].task_id = task_id; - sst_drv_ctx->streams[str_id].num_ch = num_ch; - - if (sst_drv_ctx->info.lpe_viewpt_rqd) - alloc_param.ts = sst_drv_ctx->info.mailbox_start + - sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); - else - alloc_param.ts = sst_drv_ctx->mailbox_add + - sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); - - dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n", - alloc_param.ts); - dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n", - pipe_id, task_id); - - /* allocate device type context */ - sst_init_stream(&sst_drv_ctx->streams[str_id], alloc_param.codec_type, - str_id, alloc_param.operation, 0); - - dev_info(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n", - str_id, pipe_id); - ret = sst_prepare_and_post_msg(sst_drv_ctx, task_id, IPC_CMD, - IPC_IA_ALLOC_STREAM_MRFLD, pipe_id, sizeof(alloc_param), - &alloc_param, data, true, true, false, true); - - if (ret < 0) { - dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); - /* alloc failed, so reset the state to uninit */ - str_info->status = STREAM_UN_INIT; - str_id = ret; - } else if (data) { - response = (struct snd_sst_alloc_response *)data; - ret = response->str_type.result; - if (!ret) - goto out; - dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); - if (ret == SST_ERR_STREAM_IN_USE) { - dev_err(sst_drv_ctx->dev, - "FW not in clean state, send free for:%d\n", str_id); - sst_free_stream(sst_drv_ctx, str_id); - } - str_id = -ret; - } -out: - kfree(data); - return str_id; -} - -/** -* sst_start_stream - Send msg for a starting stream -* @str_id: stream ID -* -* This function is called by any function which wants to start -* a stream. -*/ -int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - int retval = 0; - struct stream_info *str_info; - u16 data = 0; - - dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id); - str_info = get_stream_info(sst_drv_ctx, str_id); - if (!str_info) - return -EINVAL; - if (str_info->status != STREAM_RUNNING) - return -EBADRQC; - - retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, - IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id, - sizeof(u16), &data, NULL, true, true, true, false); - - return retval; -} - -int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, - struct snd_sst_bytes_v2 *bytes) -{ struct ipc_post *msg = NULL; - u32 length; - int pvt_id, ret = 0; - struct sst_block *block = NULL; - - dev_dbg(sst_drv_ctx->dev, - "type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n", - bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id, - bytes->pipe_id, bytes->len); - - if (sst_create_ipc_msg(&msg, true)) - return -ENOMEM; - - pvt_id = sst_assign_pvt_id(sst_drv_ctx); - sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg, - bytes->task_id, 1, pvt_id); - msg->mrfld_header.p.header_high.part.res_rqd = bytes->block; - length = bytes->len; - msg->mrfld_header.p.header_low_payload = length; - dev_dbg(sst_drv_ctx->dev, "length is %d\n", length); - memcpy(msg->mailbox_data, &bytes->bytes, bytes->len); - if (bytes->block) { - block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id); - if (block == NULL) { - kfree(msg); - ret = -ENOMEM; - goto out; - } - } - - sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); - dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d", - msg->mrfld_header.p.header_low_payload); - - if (bytes->block) { - ret = sst_wait_timeout(sst_drv_ctx, block); - if (ret) { - dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret); - sst_free_block(sst_drv_ctx, block); - goto out; - } - } - if (bytes->type == SND_SST_BYTES_GET) { - /* - * copy the reply and send back - * we need to update only sz and payload - */ - if (bytes->block) { - unsigned char *r = block->data; - - dev_dbg(sst_drv_ctx->dev, "read back %d bytes", - bytes->len); - memcpy(bytes->bytes, r, bytes->len); - } - } - if (bytes->block) - sst_free_block(sst_drv_ctx, block); -out: - test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id); - return 0; -} - -/* - * sst_pause_stream - Send msg for a pausing stream - * @str_id: stream ID - * - * This function is called by any function which wants to pause - * an already running stream. - */ -int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - int retval = 0; - struct stream_info *str_info; - - dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id); - str_info = get_stream_info(sst_drv_ctx, str_id); - if (!str_info) - return -EINVAL; - if (str_info->status == STREAM_PAUSED) - return 0; - if (str_info->status == STREAM_RUNNING || - str_info->status == STREAM_INIT) { - if (str_info->prev == STREAM_UN_INIT) - return -EBADRQC; - - retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, - IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id, - 0, NULL, NULL, true, true, false, true); - - if (retval == 0) { - str_info->prev = str_info->status; - str_info->status = STREAM_PAUSED; - } else if (retval == SST_ERR_INVALID_STREAM_ID) { - retval = -EINVAL; - mutex_lock(&sst_drv_ctx->sst_lock); - sst_clean_stream(str_info); - mutex_unlock(&sst_drv_ctx->sst_lock); - } - } else { - retval = -EBADRQC; - dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n "); - } - - return retval; -} - -/** - * sst_resume_stream - Send msg for resuming stream - * @str_id: stream ID - * - * This function is called by any function which wants to resume - * an already paused stream. - */ -int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - int retval = 0; - struct stream_info *str_info; - - dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id); - str_info = get_stream_info(sst_drv_ctx, str_id); - if (!str_info) - return -EINVAL; - if (str_info->status == STREAM_RUNNING) - return 0; - if (str_info->status == STREAM_PAUSED) { - retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, - IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD, - str_info->pipe_id, 0, NULL, NULL, - true, true, false, true); - - if (!retval) { - if (str_info->prev == STREAM_RUNNING) - str_info->status = STREAM_RUNNING; - else - str_info->status = STREAM_INIT; - str_info->prev = STREAM_PAUSED; - } else if (retval == -SST_ERR_INVALID_STREAM_ID) { - retval = -EINVAL; - mutex_lock(&sst_drv_ctx->sst_lock); - sst_clean_stream(str_info); - mutex_unlock(&sst_drv_ctx->sst_lock); - } - } else { - retval = -EBADRQC; - dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n"); - } - - return retval; -} - - -/** - * sst_drop_stream - Send msg for stopping stream - * @str_id: stream ID - * - * This function is called by any function which wants to stop - * a stream. - */ -int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - int retval = 0; - struct stream_info *str_info; - - dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id); - str_info = get_stream_info(sst_drv_ctx, str_id); - if (!str_info) - return -EINVAL; - - if (str_info->status != STREAM_UN_INIT) { - str_info->prev = STREAM_UN_INIT; - str_info->status = STREAM_INIT; - str_info->cumm_bytes = 0; - retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, - IPC_CMD, IPC_IA_DROP_STREAM_MRFLD, - str_info->pipe_id, 0, NULL, NULL, - true, true, true, false); - } else { - retval = -EBADRQC; - dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n", - str_info->status); - } - return retval; -} - -/** -* sst_drain_stream - Send msg for draining stream -* @str_id: stream ID -* -* This function is called by any function which wants to drain -* a stream. -*/ -int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, - int str_id, bool partial_drain) -{ - int retval = 0; - struct stream_info *str_info; - - dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id); - str_info = get_stream_info(sst_drv_ctx, str_id); - if (!str_info) - return -EINVAL; - if (str_info->status != STREAM_RUNNING && - str_info->status != STREAM_INIT && - str_info->status != STREAM_PAUSED) { - dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n", - str_info->status); - return -EBADRQC; - } - - retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, - IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id, - sizeof(u8), &partial_drain, NULL, true, true, false, false); - /* - * with new non blocked drain implementation in core we dont need to - * wait for respsonse, and need to only invoke callback for drain - * complete - */ - - return retval; -} - -/** - * sst_free_stream - Frees a stream - * @str_id: stream ID - * - * This function is called by any function which wants to free - * a stream. - */ -int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) -{ - int retval = 0; - struct stream_info *str_info; - struct intel_sst_ops *ops; - - dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id); - - mutex_lock(&sst_drv_ctx->sst_lock); - if (sst_drv_ctx->sst_state == SST_RESET) { - mutex_unlock(&sst_drv_ctx->sst_lock); - return -ENODEV; - } - mutex_unlock(&sst_drv_ctx->sst_lock); - str_info = get_stream_info(sst_drv_ctx, str_id); - if (!str_info) - return -EINVAL; - ops = sst_drv_ctx->ops; - - mutex_lock(&str_info->lock); - if (str_info->status != STREAM_UN_INIT) { - str_info->prev = str_info->status; - str_info->status = STREAM_UN_INIT; - mutex_unlock(&str_info->lock); - - dev_info(sst_drv_ctx->dev, "Free for str %d pipe %#x\n", - str_id, str_info->pipe_id); - retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, - IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0, - NULL, NULL, true, true, false, true); - - dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n", - retval); - mutex_lock(&sst_drv_ctx->sst_lock); - sst_clean_stream(str_info); - mutex_unlock(&sst_drv_ctx->sst_lock); - dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n"); - } else { - mutex_unlock(&str_info->lock); - retval = -EBADRQC; - dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n"); - } - - return retval; -} -- cgit v0.10.2 From f34c4bc7e599bb895f77381c4d91ccc77635d68f Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Tue, 7 Apr 2015 03:06:06 +0800 Subject: ASoC: Intel: read_shim_data() can be static Signed-off-by: Fengguang Wu Signed-off-by: Mark Brown diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c index 3c17844..2d74249 100644 --- a/sound/soc/intel/atom/sst/sst_pvt.c +++ b/sound/soc/intel/atom/sst/sst_pvt.c @@ -111,7 +111,7 @@ int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, } -unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) +static unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) { unsigned long long val = 0; @@ -124,7 +124,7 @@ unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) return val; } -void write_shim_data(struct intel_sst_drv *sst, int addr, +static void write_shim_data(struct intel_sst_drv *sst, int addr, unsigned long long data) { switch (sst->dev_id) { -- cgit v0.10.2 From f60e5990d9c1424af9dbca60a23ba2a1c7c1ce90 Mon Sep 17 00:00:00 2001 From: "hannes@stressinduktion.org" Date: Wed, 1 Apr 2015 17:07:44 +0200 Subject: ipv6: protect skb->sk accesses from recursive dereference inside the stack We should not consult skb->sk for output decisions in xmit recursion levels > 0 in the stack. Otherwise local socket settings could influence the result of e.g. tunnel encapsulation process. ipv6 does not conform with this in three places: 1) ip6_fragment: we do consult ipv6_npinfo for frag_size 2) sk_mc_loop in ipv6 uses skb->sk and checks if we should loop the packet back to the local socket 3) ip6_skb_dst_mtu could query the settings from the user socket and force a wrong MTU Furthermore: In sk_mc_loop we could potentially land in WARN_ON(1) if we use a PF_PACKET socket ontop of an IPv6-backed vxlan device. Reuse xmit_recursion as we are currently only interested in protecting tunnel devices. Cc: Jiri Pirko Signed-off-by: Hannes Frederic Sowa Signed-off-by: David S. Miller diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index dcf6ec2..2787388 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2185,6 +2185,12 @@ void netdev_freemem(struct net_device *dev); void synchronize_net(void); int init_dummy_netdev(struct net_device *dev); +DECLARE_PER_CPU(int, xmit_recursion); +static inline int dev_recursion_level(void) +{ + return this_cpu_read(xmit_recursion); +} + struct net_device *dev_get_by_index(struct net *net, int ifindex); struct net_device *__dev_get_by_index(struct net *net, int ifindex); struct net_device *dev_get_by_index_rcu(struct net *net, int ifindex); diff --git a/include/net/ip.h b/include/net/ip.h index 025c61c..6cc1eaf 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -453,22 +453,6 @@ static __inline__ void inet_reset_saddr(struct sock *sk) #endif -static inline int sk_mc_loop(struct sock *sk) -{ - if (!sk) - return 1; - switch (sk->sk_family) { - case AF_INET: - return inet_sk(sk)->mc_loop; -#if IS_ENABLED(CONFIG_IPV6) - case AF_INET6: - return inet6_sk(sk)->mc_loop; -#endif - } - WARN_ON(1); - return 1; -} - bool ip_call_ra_chain(struct sk_buff *skb); /* diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index 1d09b46..eda131d 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -174,7 +174,8 @@ int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *)); static inline int ip6_skb_dst_mtu(struct sk_buff *skb) { - struct ipv6_pinfo *np = skb->sk ? inet6_sk(skb->sk) : NULL; + struct ipv6_pinfo *np = skb->sk && !dev_recursion_level() ? + inet6_sk(skb->sk) : NULL; return (np && np->pmtudisc >= IPV6_PMTUDISC_PROBE) ? skb_dst(skb)->dev->mtu : dst_mtu(skb_dst(skb)); diff --git a/include/net/sock.h b/include/net/sock.h index ab186b1..e4079c2 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -1762,6 +1762,8 @@ struct dst_entry *__sk_dst_check(struct sock *sk, u32 cookie); struct dst_entry *sk_dst_check(struct sock *sk, u32 cookie); +bool sk_mc_loop(struct sock *sk); + static inline bool sk_can_gso(const struct sock *sk) { return net_gso_ok(sk->sk_route_caps, sk->sk_gso_type); diff --git a/net/core/dev.c b/net/core/dev.c index 962ee9d..45109b7 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2848,7 +2848,9 @@ static void skb_update_prio(struct sk_buff *skb) #define skb_update_prio(skb) #endif -static DEFINE_PER_CPU(int, xmit_recursion); +DEFINE_PER_CPU(int, xmit_recursion); +EXPORT_SYMBOL(xmit_recursion); + #define RECURSION_LIMIT 10 /** diff --git a/net/core/sock.c b/net/core/sock.c index 78e89eb..71e3e5f 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -653,6 +653,25 @@ static inline void sock_valbool_flag(struct sock *sk, int bit, int valbool) sock_reset_flag(sk, bit); } +bool sk_mc_loop(struct sock *sk) +{ + if (dev_recursion_level()) + return false; + if (!sk) + return true; + switch (sk->sk_family) { + case AF_INET: + return inet_sk(sk)->mc_loop; +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + return inet6_sk(sk)->mc_loop; +#endif + } + WARN_ON(1); + return true; +} +EXPORT_SYMBOL(sk_mc_loop); + /* * This is meant for all protocols to use and covers goings on * at the socket level. Everything here is generic. diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 7e80b61..36cf0ab 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -542,7 +542,8 @@ int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *)) { struct sk_buff *frag; struct rt6_info *rt = (struct rt6_info *)skb_dst(skb); - struct ipv6_pinfo *np = skb->sk ? inet6_sk(skb->sk) : NULL; + struct ipv6_pinfo *np = skb->sk && !dev_recursion_level() ? + inet6_sk(skb->sk) : NULL; struct ipv6hdr *tmp_hdr; struct frag_hdr *fh; unsigned int mtu, hlen, left, len; -- cgit v0.10.2 From ecf7b361a6bc1fd5441e4d6a3d7433abbe577064 Mon Sep 17 00:00:00 2001 From: Stas Sergeev Date: Wed, 1 Apr 2015 19:23:29 +0300 Subject: mvneta: dont call mvneta_adjust_link() manually mvneta_adjust_link() is a callback for of_phy_connect() and should not be called directly. The result of calling it directly is as below: Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 96208f1..2db6532 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -2658,16 +2658,11 @@ static int mvneta_stop(struct net_device *dev) static int mvneta_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct mvneta_port *pp = netdev_priv(dev); - int ret; if (!pp->phy_dev) return -ENOTSUPP; - ret = phy_mii_ioctl(pp->phy_dev, ifr, cmd); - if (!ret) - mvneta_adjust_link(dev); - - return ret; + return phy_mii_ioctl(pp->phy_dev, ifr, cmd); } /* Ethtool methods */ -- cgit v0.10.2 From 67e04c29ec0daad9ba29341b4dab4b89526994cf Mon Sep 17 00:00:00 2001 From: WANG Cong Date: Fri, 3 Apr 2015 13:46:09 -0700 Subject: l2tp: unregister l2tp_net_ops on failure path Signed-off-by: Cong Wang Signed-off-by: David S. Miller diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 895348e..a29a504 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1871,6 +1871,7 @@ static int __init l2tp_init(void) l2tp_wq = alloc_workqueue("l2tp", WQ_UNBOUND, 0); if (!l2tp_wq) { pr_err("alloc_workqueue failed\n"); + unregister_pernet_device(&l2tp_net_ops); rc = -ENOMEM; goto out; } -- cgit v0.10.2 From 303038135afbd0520d1e241c02592be6e4ea7204 Mon Sep 17 00:00:00 2001 From: Pavel Nakonechny Date: Sun, 5 Apr 2015 00:46:21 +0300 Subject: net: dsa: fix filling routing table from OF description According to description in 'include/net/dsa.h', in cascade switches configurations where there are more than one interconnected devices, 'rtable' array in 'dsa_chip_data' structure is used to indicate which port on this switch should be used to send packets to that are destined for corresponding switch. However, dsa_of_setup_routing_table() fills 'rtable' with port numbers of the _target_ switch, but not current one. This commit removes redundant devicetree parsing and adds needed port number as a function argument. So dsa_of_setup_routing_table() now just looks for target switch number by parsing parent of 'link' device node. To remove possible misunderstandings with the way of determining target switch number, a corresponding comment was added to the source code and to the DSA device tree bindings documentation file. This was tested on a custom board with two Marvell 88E6095 switches with following corresponding routing tables: { -1, 10 } and { 8, -1 }. Signed-off-by: Pavel Nakonechny Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli Signed-off-by: David S. Miller diff --git a/Documentation/devicetree/bindings/net/dsa/dsa.txt b/Documentation/devicetree/bindings/net/dsa/dsa.txt index e124847..f0b4cd7 100644 --- a/Documentation/devicetree/bindings/net/dsa/dsa.txt +++ b/Documentation/devicetree/bindings/net/dsa/dsa.txt @@ -19,7 +19,9 @@ the parent DSA node. The maximum number of allowed child nodes is 4 (DSA_MAX_SWITCHES). Each of these switch child nodes should have the following required properties: -- reg : Describes the switch address on the MII bus +- reg : Contains two fields. The first one describes the + address on the MII bus. The second is the switch + number that must be unique in cascaded configurations - #address-cells : Must be 1 - #size-cells : Must be 0 diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 2173402..4dea2e0 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -501,12 +501,10 @@ static struct net_device *dev_to_net_device(struct device *dev) #ifdef CONFIG_OF static int dsa_of_setup_routing_table(struct dsa_platform_data *pd, struct dsa_chip_data *cd, - int chip_index, + int chip_index, int port_index, struct device_node *link) { - int ret; const __be32 *reg; - int link_port_addr; int link_sw_addr; struct device_node *parent_sw; int len; @@ -519,6 +517,10 @@ static int dsa_of_setup_routing_table(struct dsa_platform_data *pd, if (!reg || (len != sizeof(*reg) * 2)) return -EINVAL; + /* + * Get the destination switch number from the second field of its 'reg' + * property, i.e. for "reg = <0x19 1>" sw_addr is '1'. + */ link_sw_addr = be32_to_cpup(reg + 1); if (link_sw_addr >= pd->nr_chips) @@ -535,20 +537,9 @@ static int dsa_of_setup_routing_table(struct dsa_platform_data *pd, memset(cd->rtable, -1, pd->nr_chips * sizeof(s8)); } - reg = of_get_property(link, "reg", NULL); - if (!reg) { - ret = -EINVAL; - goto out; - } - - link_port_addr = be32_to_cpup(reg); - - cd->rtable[link_sw_addr] = link_port_addr; + cd->rtable[link_sw_addr] = port_index; return 0; -out: - kfree(cd->rtable); - return ret; } static void dsa_of_free_platform_data(struct dsa_platform_data *pd) @@ -658,7 +649,7 @@ static int dsa_of_probe(struct platform_device *pdev) if (!strcmp(port_name, "dsa") && link && pd->nr_chips > 1) { ret = dsa_of_setup_routing_table(pd, cd, - chip_index, link); + chip_index, port_index, link); if (ret) goto out_free_chip; } -- cgit v0.10.2 From fde913e25496761a4e2a4c81230c913aba6289a2 Mon Sep 17 00:00:00 2001 From: Jack Morgenstein Date: Sun, 5 Apr 2015 17:50:48 +0300 Subject: net/mlx4_core: Fix error message deprecation for ConnectX-2 cards Commit 1daa4303b4ca ("net/mlx4_core: Deprecate error message at ConnectX-2 cards startup to debug") did the deprecation only for port 1 of the card. Need to deprecate for port 2 as well. Fixes: 1daa4303b4ca ("net/mlx4_core: Deprecate error message at ConnectX-2 cards startup to debug") Signed-off-by: Jack Morgenstein Signed-off-by: Amir Vadai Signed-off-by: David S. Miller diff --git a/drivers/net/ethernet/mellanox/mlx4/cmd.c b/drivers/net/ethernet/mellanox/mlx4/cmd.c index 3350721..546ca42 100644 --- a/drivers/net/ethernet/mellanox/mlx4/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx4/cmd.c @@ -724,7 +724,8 @@ static int mlx4_cmd_wait(struct mlx4_dev *dev, u64 in_param, u64 *out_param, * on the host, we deprecate the error message for this * specific command/input_mod/opcode_mod/fw-status to be debug. */ - if (op == MLX4_CMD_SET_PORT && in_modifier == 1 && + if (op == MLX4_CMD_SET_PORT && + (in_modifier == 1 || in_modifier == 2) && op_modifier == 0 && context->fw_status == CMD_STAT_BAD_SIZE) mlx4_dbg(dev, "command 0x%x failed: fw status = 0x%x\n", op, context->fw_status); -- cgit v0.10.2 From f22e6e847115abc3a0e2ad7bb18d243d42275af1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 6 Apr 2015 15:39:45 -0700 Subject: Linux 4.0-rc7 diff --git a/Makefile b/Makefile index da36a3b..54430f9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ VERSION = 4 PATCHLEVEL = 0 SUBLEVEL = 0 -EXTRAVERSION = -rc6 +EXTRAVERSION = -rc7 NAME = Hurr durr I'ma sheep # *DOCUMENTATION* -- cgit v0.10.2 From 4cd9db08598454e4d051b818c5f1d61ac7539a47 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 7 Apr 2015 14:03:53 +0300 Subject: ASoC: davinci-mcasp: Fix ruledata setup in davinci_mcasp_startup Passing &mcasp->ruledata[dir] to snd_pcm_hw_rule_add() is not correct since commit: 7b3d165a2821 ASoC: davinci-mcasp: Index ruledata in drvdata with substream->stream now sets up the struct based on the substream->stream (0 or 1) while we pass a pointer which we take with dir (1 or 2). This will lead kernel crash. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 0b6b1b2..bb4b78e 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -1128,6 +1128,8 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + struct davinci_mcasp_ruledata *ruledata = + &mcasp->ruledata[substream->stream]; u32 max_channels = 0; int i, dir; @@ -1149,7 +1151,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, if (mcasp->serial_dir[i] == dir) max_channels++; } - mcasp->ruledata[substream->stream].serializers = max_channels; + ruledata->serializers = max_channels; max_channels *= mcasp->tdm_slots; /* * If the already active stream has less channels than the calculated @@ -1172,12 +1174,12 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { int ret; - mcasp->ruledata[substream->stream].mcasp = mcasp; + ruledata->mcasp = mcasp; ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, davinci_mcasp_hw_rule_rate, - &mcasp->ruledata[dir], + ruledata, SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS, -1); if (ret) @@ -1185,7 +1187,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, davinci_mcasp_hw_rule_format, - &mcasp->ruledata[dir], + ruledata, SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_CHANNELS, -1); if (ret) @@ -1193,7 +1195,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, davinci_mcasp_hw_rule_channels, - &mcasp->ruledata[dir], + ruledata, SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT, -1); if (ret) -- cgit v0.10.2 From 7e5ee1c33e9ce4e4be0a6b8955c760e9a41a9e84 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 7 Apr 2015 11:34:50 +0100 Subject: ASoC: wm8804: Add support for hardware reset line It is best to use the physical reset if it is available. This patch adds support for a GPIO controlled physical reset for the chip. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index f44da83..cc168c4 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,8 @@ struct wm8804_priv { struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES]; struct notifier_block disable_nb[WM8804_NUM_SUPPLIES]; int mclk_div; + + struct gpio_desc *reset; }; static int txsrc_get(struct snd_kcontrol *kcontrol, @@ -182,7 +185,7 @@ static bool wm8804_volatile(struct device *dev, unsigned int reg) } } -static int wm8804_reset(struct wm8804_priv *wm8804) +static int wm8804_soft_reset(struct wm8804_priv *wm8804) { return regmap_write(wm8804->regmap, WM8804_RST_DEVID1, 0x0); } @@ -586,6 +589,14 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) wm8804->regmap = regmap; + wm8804->reset = devm_gpiod_get_optional(dev, "wlf,reset", + GPIOD_OUT_LOW); + if (IS_ERR(wm8804->reset)) { + ret = PTR_ERR(wm8804->reset); + dev_err(dev, "Failed to get reset line: %d\n", ret); + return ret; + } + for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) wm8804->supplies[i].supply = wm8804_supply_names[i]; @@ -620,6 +631,9 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) return ret; } + if (wm8804->reset) + gpiod_set_value_cansleep(wm8804->reset, 1); + ret = regmap_read(regmap, WM8804_RST_DEVID1, &id1); if (ret < 0) { dev_err(dev, "Failed to read device ID: %d\n", ret); @@ -648,10 +662,12 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) } dev_info(dev, "revision %c\n", id1 + 'A'); - ret = wm8804_reset(wm8804); - if (ret < 0) { - dev_err(dev, "Failed to issue reset: %d\n", ret); - goto err_reg_enable; + if (!wm8804->reset) { + ret = wm8804_soft_reset(wm8804); + if (ret < 0) { + dev_err(dev, "Failed to issue reset: %d\n", ret); + goto err_reg_enable; + } } ret = snd_soc_register_codec(dev, &soc_codec_dev_wm8804, -- cgit v0.10.2 From 9b5c352e5425622b4371387d4718060da5d3ae32 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 7 Apr 2015 11:34:51 +0100 Subject: ASoC: wm8804: Update binding documentation to include reset GPIO Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/Documentation/devicetree/bindings/sound/wm8804.txt b/Documentation/devicetree/bindings/sound/wm8804.txt index 10ef076..6fd124b 100644 --- a/Documentation/devicetree/bindings/sound/wm8804.txt +++ b/Documentation/devicetree/bindings/sound/wm8804.txt @@ -13,6 +13,10 @@ Required properties: - PVDD-supply, DVDD-supply : Power supplies for the device, as covered in Documentation/devicetree/bindings/regulator/regulator.txt +Optional properties: + + - wlf,reset-gpio: A GPIO specifier for the GPIO controlling the reset pin + Example: codec: wm8804@1a { -- cgit v0.10.2 From 8e64aedf80ae14b852abc0d7ca262530b69e9a18 Mon Sep 17 00:00:00 2001 From: Jie Yang Date: Tue, 7 Apr 2015 20:14:59 +0800 Subject: ASoC: Intel: Fix a buffer overflow issue 0day robot reported a buffer overflow issue: ... sound/soc/intel/haswell/sst-haswell-pcm.c:1107 hsw_pcm_probe() error: buffer\ overflow 'hsw_dais' 4 <= 4 sound/soc/intel/haswell/sst-haswell-pcm.c:1109 hsw_pcm_probe() error: buffer\ overflow 'hsw_dais' 4 <= 4 ... Fix it by initializing the index(i) to correct value. Signed-off-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c index 157b3a6..23ae040 100644 --- a/sound/soc/intel/haswell/sst-haswell-pcm.c +++ b/sound/soc/intel/haswell/sst-haswell-pcm.c @@ -1103,7 +1103,7 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform) return 0; err: - for (;i >= 0; i--) { + for (--i; i >= 0; i--) { if (hsw_dais[i].playback.channels_min) snd_dma_free_pages(&priv_data->dmab[i][0]); if (hsw_dais[i].capture.channels_min) -- cgit v0.10.2 From 1f544fd8ff377127a512e20358045cc9b92c245c Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Wed, 8 Apr 2015 18:34:25 +0530 Subject: ASoC: Intel: remove unused functions these functions were never called by anyone. Signed-off-by: Sudip Mukherjee Signed-off-by: Mark Brown diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c index 2d74249..adb32fe 100644 --- a/sound/soc/intel/atom/sst/sst_pvt.c +++ b/sound/soc/intel/atom/sst/sst_pvt.c @@ -111,30 +111,6 @@ int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, } -static unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) -{ - unsigned long long val = 0; - - switch (sst->dev_id) { - case SST_MRFLD_PCI_ID: - case SST_BYT_ACPI_ID: - val = sst_shim_read64(sst->shim, addr); - break; - } - return val; -} - -static void write_shim_data(struct intel_sst_drv *sst, int addr, - unsigned long long data) -{ - switch (sst->dev_id) { - case SST_MRFLD_PCI_ID: - case SST_BYT_ACPI_ID: - sst_shim_write64(sst->shim, addr, (u64) data); - break; - } -} - /* * sst_wait_timeout - wait on event for timeout * -- cgit v0.10.2 From c6b424fee7511407107403d3a7c50e0756ae282e Mon Sep 17 00:00:00 2001 From: Caesar Wang Date: Wed, 8 Apr 2015 19:05:56 +0800 Subject: ASoC: max98090: add shutdown callback for max98090 To fix pop noise when shutdown,the pop noise during shutdown is the pmic cutoff power of codec without any notice. Signed-off-by: jay.xu Signed-off-by: zhengxing Signed-off-by: Caesar Wang Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index b112b1c..3e33ef2 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -2605,8 +2605,24 @@ err_enable: return ret; } +static void max98090_i2c_shutdown(struct i2c_client *i2c) +{ + struct max98090_priv *max98090 = dev_get_drvdata(&i2c->dev); + + /* + * Enable volume smoothing, disable zero cross. This will cause + * a quick 40ms ramp to mute on shutdown. + */ + regmap_write(max98090->regmap, + M98090_REG_LEVEL_CONTROL, M98090_VSENN_MASK); + regmap_write(max98090->regmap, + M98090_REG_DEVICE_SHUTDOWN, 0x00); + msleep(40); +} + static int max98090_i2c_remove(struct i2c_client *client) { + max98090_i2c_shutdown(client); snd_soc_unregister_codec(&client->dev); return 0; } @@ -2696,6 +2712,7 @@ static struct i2c_driver max98090_i2c_driver = { .acpi_match_table = ACPI_PTR(max98090_acpi_match), }, .probe = max98090_i2c_probe, + .shutdown = max98090_i2c_shutdown, .remove = max98090_i2c_remove, .id_table = max98090_i2c_id, }; -- cgit v0.10.2 From 5631f18763f1b0989cec7cbf8f3badcb74dfe469 Mon Sep 17 00:00:00 2001 From: Sapthagiri Baratam Date: Tue, 7 Apr 2015 12:55:09 +0100 Subject: ASoC: wm8804: Add DAPM widgets for SPDIF/AIF This change converts the driver to use DAPM to control the power for the various blocks on the chip. As part of this change the existing controls "TX Playback Switch" (controlled power for the SPDIF TX block) and "AIF Playback Switch" (controlled power for the AIF block) are both removed, as they are now redundant since the power state of those blocks is controlled automatically by DAPM. There are several benefits of this change, the most important of which is this change adds support for powering down the SPDIF RX block. The RX block will automatically assume control of the PLL on the chip when it is receiving a signal, so leaving this enabled all the time as was currently done in the driver can be problematic. An incoming SPDIF signal that is not being used can completely destroy the clocking for an in use TX signal. But this change ensures that the RX block will only be powered when the user intends to be receiving data, thus avoiding this issue. Additional benefits include the chip being simpler to operate as the power no longer needs to be manually controlled between use-cases and a small power saving (although it is acknowledged that this is likely unimportant in the typical use-cases for this chip). Signed-off-by: Sapthagiri Baratam Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index cc168c4..cff34be 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "wm8804.h" @@ -64,14 +65,16 @@ struct wm8804_priv { int mclk_div; struct gpio_desc *reset; -}; -static int txsrc_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); + int aif_pwr; +}; static int txsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +static int wm8804_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + /* * We can't use the same notifier block for more than one supply and * there's no way I can see to get from a callback to the caller @@ -93,26 +96,62 @@ WM8804_REGULATOR_EVENT(0) WM8804_REGULATOR_EVENT(1) static const char *txsrc_text[] = { "S/PDIF RX", "AIF" }; -static SOC_ENUM_SINGLE_EXT_DECL(txsrc, txsrc_text); +static const SOC_ENUM_SINGLE_DECL(txsrc, WM8804_SPDTX4, 6, txsrc_text); -static const struct snd_kcontrol_new wm8804_snd_controls[] = { - SOC_ENUM_EXT("Input Source", txsrc, txsrc_get, txsrc_put), - SOC_SINGLE("TX Playback Switch", WM8804_PWRDN, 2, 1, 1), - SOC_SINGLE("AIF Playback Switch", WM8804_PWRDN, 4, 1, 1) +static const struct snd_kcontrol_new wm8804_tx_source_mux[] = { + SOC_DAPM_ENUM_EXT("Input Source", txsrc, + snd_soc_dapm_get_enum_double, txsrc_put), }; -static int txsrc_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec; - unsigned int src; +static const struct snd_soc_dapm_widget wm8804_dapm_widgets[] = { +SND_SOC_DAPM_OUTPUT("SPDIF Out"), +SND_SOC_DAPM_INPUT("SPDIF In"), + +SND_SOC_DAPM_PGA("SPDIFTX", WM8804_PWRDN, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("SPDIFRX", WM8804_PWRDN, 1, 1, NULL, 0), + +SND_SOC_DAPM_MUX("Tx Source", SND_SOC_NOPM, 6, 0, wm8804_tx_source_mux), + +SND_SOC_DAPM_AIF_OUT_E("AIFTX", NULL, 0, SND_SOC_NOPM, 0, 0, wm8804_aif_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_AIF_IN_E("AIFRX", NULL, 0, SND_SOC_NOPM, 0, 0, wm8804_aif_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route wm8804_dapm_routes[] = { + { "AIFRX", NULL, "Playback" }, + { "Tx Source", "AIF", "AIFRX" }, + + { "SPDIFRX", NULL, "SPDIF In" }, + { "Tx Source", "S/PDIF RX", "SPDIFRX" }, + + { "SPDIFTX", NULL, "Tx Source" }, + { "SPDIF Out", NULL, "SPDIFTX" }, - codec = snd_soc_kcontrol_codec(kcontrol); - src = snd_soc_read(codec, WM8804_SPDTX4); - if (src & 0x40) - ucontrol->value.integer.value[0] = 1; - else - ucontrol->value.integer.value[0] = 0; + { "AIFTX", NULL, "SPDIFRX" }, + { "Capture", NULL, "AIFTX" }, +}; + +static int wm8804_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct wm8804_priv *wm8804 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* power up the aif */ + if (!wm8804->aif_pwr) + snd_soc_update_bits(codec, WM8804_PWRDN, 0x10, 0x0); + wm8804->aif_pwr++; + break; + case SND_SOC_DAPM_POST_PMD: + /* power down only both paths are disabled */ + wm8804->aif_pwr--; + if (!wm8804->aif_pwr) + snd_soc_update_bits(codec, WM8804_PWRDN, 0x10, 0x10); + break; + } return 0; } @@ -120,48 +159,33 @@ static int txsrc_get(struct snd_kcontrol *kcontrol, static int txsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec; - unsigned int src, txpwr; + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val = ucontrol->value.enumerated.item[0] << e->shift_l; + unsigned int mask = 1 << e->shift_l; + unsigned int txpwr; + + if (val != 0 && val != mask) + return -EINVAL; - codec = snd_soc_kcontrol_codec(kcontrol); + snd_soc_dapm_mutex_lock(dapm); - if (ucontrol->value.integer.value[0] != 0 - && ucontrol->value.integer.value[0] != 1) - return -EINVAL; + if (snd_soc_test_bits(codec, e->reg, mask, val)) { + /* save the current power state of the transmitter */ + txpwr = snd_soc_read(codec, WM8804_PWRDN) & 0x4; - src = snd_soc_read(codec, WM8804_SPDTX4); - switch ((src & 0x40) >> 6) { - case 0: - if (!ucontrol->value.integer.value[0]) - return 0; - break; - case 1: - if (ucontrol->value.integer.value[1]) - return 0; - break; - } + /* power down the transmitter */ + snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, 0x4); - /* save the current power state of the transmitter */ - txpwr = snd_soc_read(codec, WM8804_PWRDN) & 0x4; - /* power down the transmitter */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, 0x4); - /* set the tx source */ - snd_soc_update_bits(codec, WM8804_SPDTX4, 0x40, - ucontrol->value.integer.value[0] << 6); - - if (ucontrol->value.integer.value[0]) { - /* power down the receiver */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0x2); - /* power up the AIF */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x10, 0); - } else { - /* don't power down the AIF -- may be used as an output */ - /* power up the receiver */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0); + /* set the tx source */ + snd_soc_update_bits(codec, e->reg, mask, val); + + /* restore the transmitter's configuration */ + snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, txpwr); } - /* restore the transmitter's configuration */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, txpwr); + snd_soc_dapm_mutex_unlock(dapm); return 0; } @@ -558,8 +582,10 @@ static const struct snd_soc_codec_driver soc_codec_dev_wm8804 = { .set_bias_level = wm8804_set_bias_level, .idle_bias_off = true, - .controls = wm8804_snd_controls, - .num_controls = ARRAY_SIZE(wm8804_snd_controls), + .dapm_widgets = wm8804_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8804_dapm_widgets), + .dapm_routes = wm8804_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8804_dapm_routes), }; const struct regmap_config wm8804_regmap_config = { -- cgit v0.10.2 From 1a60667fc81fdab3733a1d41480da3a5d0ccecea Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 7 Apr 2015 12:55:10 +0100 Subject: ASoC: wm8804: Enable runtime PM Currently both the oscillator and the PLL are powered up in set_bias_level. This can be problematic when using output clocks from the wm8804 for other devices. The snd_soc_codec_set_pll API defines that a clock should be available once the call returns, however, with all the clocking controlled in set_bias_level this is not currently the case. This patch enables pm_runtime for the wm8804, enabling both the regulators and the oscillator when the chip resumes, and enabling the PLL in the snd_soc_codec_set_pll call. Naturally the enabling the PLL will also cause the chip to resume. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/wm8804-i2c.c b/sound/soc/codecs/wm8804-i2c.c index 5bd4af2..6596f5f 100644 --- a/sound/soc/codecs/wm8804-i2c.c +++ b/sound/soc/codecs/wm8804-i2c.c @@ -50,6 +50,7 @@ static struct i2c_driver wm8804_i2c_driver = { .driver = { .name = "wm8804", .owner = THIS_MODULE, + .pm = &wm8804_pm, .of_match_table = wm8804_of_match, }, .probe = wm8804_i2c_probe, diff --git a/sound/soc/codecs/wm8804-spi.c b/sound/soc/codecs/wm8804-spi.c index 287e11e..407a3cf 100644 --- a/sound/soc/codecs/wm8804-spi.c +++ b/sound/soc/codecs/wm8804-spi.c @@ -43,6 +43,7 @@ static struct spi_driver wm8804_spi_driver = { .driver = { .name = "wm8804", .owner = THIS_MODULE, + .pm = &wm8804_pm, .of_match_table = wm8804_of_match, }, .probe = wm8804_spi_probe, diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index cff34be..1e403f6 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ static const struct reg_default wm8804_reg_defaults[] = { }; struct wm8804_priv { + struct device *dev; struct regmap *regmap; struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES]; struct notifier_block disable_nb[WM8804_NUM_SUPPLIES]; @@ -403,19 +405,19 @@ static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out) { - struct snd_soc_codec *codec; + struct snd_soc_codec *codec = dai->codec; + struct wm8804_priv *wm8804 = snd_soc_codec_get_drvdata(codec); + bool change; - codec = dai->codec; if (!freq_in || !freq_out) { /* disable the PLL */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1); - return 0; + regmap_update_bits_check(wm8804->regmap, WM8804_PWRDN, + 0x1, 0x1, &change); + if (change) + pm_runtime_put(wm8804->dev); } else { int ret; struct pll_div pll_div; - struct wm8804_priv *wm8804; - - wm8804 = snd_soc_codec_get_drvdata(codec); ret = pll_factors(&pll_div, freq_out, freq_in, wm8804->mclk_div); @@ -423,7 +425,10 @@ static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id, return ret; /* power down the PLL before reprogramming it */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1); + regmap_update_bits_check(wm8804->regmap, WM8804_PWRDN, + 0x1, 0x1, &change); + if (!change) + pm_runtime_get_sync(wm8804->dev); /* set PLLN and PRESCALE */ snd_soc_update_bits(codec, WM8804_PLL4, 0xf | 0x10, @@ -501,47 +506,6 @@ static int wm8804_set_clkdiv(struct snd_soc_dai *dai, return 0; } -static int wm8804_set_bias_level(struct snd_soc_codec *codec, - enum snd_soc_bias_level level) -{ - int ret; - struct wm8804_priv *wm8804; - - wm8804 = snd_soc_codec_get_drvdata(codec); - switch (level) { - case SND_SOC_BIAS_ON: - break; - case SND_SOC_BIAS_PREPARE: - /* power up the OSC and the PLL */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0); - break; - case SND_SOC_BIAS_STANDBY: - if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { - ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), - wm8804->supplies); - if (ret) { - dev_err(codec->dev, - "Failed to enable supplies: %d\n", - ret); - return ret; - } - regcache_sync(wm8804->regmap); - } - /* power down the OSC and the PLL */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9); - break; - case SND_SOC_BIAS_OFF: - /* power down the OSC and the PLL */ - snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9); - regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), - wm8804->supplies); - break; - } - - codec->dapm.bias_level = level; - return 0; -} - static const struct snd_soc_dai_ops wm8804_dai_ops = { .hw_params = wm8804_hw_params, .set_fmt = wm8804_set_fmt, @@ -579,7 +543,6 @@ static struct snd_soc_dai_driver wm8804_dai = { }; static const struct snd_soc_codec_driver soc_codec_dev_wm8804 = { - .set_bias_level = wm8804_set_bias_level, .idle_bias_off = true, .dapm_widgets = wm8804_dapm_widgets, @@ -613,6 +576,7 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) dev_set_drvdata(dev, wm8804); + wm8804->dev = dev; wm8804->regmap = regmap; wm8804->reset = devm_gpiod_get_optional(dev, "wlf,reset", @@ -703,6 +667,10 @@ int wm8804_probe(struct device *dev, struct regmap *regmap) goto err_reg_enable; } + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + return 0; err_reg_enable: @@ -713,10 +681,51 @@ EXPORT_SYMBOL_GPL(wm8804_probe); void wm8804_remove(struct device *dev) { + pm_runtime_disable(dev); snd_soc_unregister_codec(dev); } EXPORT_SYMBOL_GPL(wm8804_remove); +#if IS_ENABLED(CONFIG_PM) +static int wm8804_runtime_resume(struct device *dev) +{ + struct wm8804_priv *wm8804 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), + wm8804->supplies); + if (ret) { + dev_err(wm8804->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_sync(wm8804->regmap); + + /* Power up OSCCLK */ + regmap_update_bits(wm8804->regmap, WM8804_PWRDN, 0x8, 0x0); + + return 0; +} + +static int wm8804_runtime_suspend(struct device *dev) +{ + struct wm8804_priv *wm8804 = dev_get_drvdata(dev); + + /* Power down OSCCLK */ + regmap_update_bits(wm8804->regmap, WM8804_PWRDN, 0x8, 0x8); + + regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), + wm8804->supplies); + + return 0; +} +#endif + +const struct dev_pm_ops wm8804_pm = { + SET_RUNTIME_PM_OPS(wm8804_runtime_suspend, wm8804_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(wm8804_pm); + MODULE_DESCRIPTION("ASoC WM8804 driver"); MODULE_AUTHOR("Dimitris Papastamos "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8804.h b/sound/soc/codecs/wm8804.h index a39a256..aa72fa6 100644 --- a/sound/soc/codecs/wm8804.h +++ b/sound/soc/codecs/wm8804.h @@ -65,6 +65,7 @@ #define WM8804_MCLKDIV_128FS 1 extern const struct regmap_config wm8804_regmap_config; +extern const struct dev_pm_ops wm8804_pm; int wm8804_probe(struct device *dev, struct regmap *regmap); void wm8804_remove(struct device *dev); -- cgit v0.10.2 From 884c0f5b2ad542037b6f04de84a3fd3f82b15828 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 3 Apr 2015 13:04:05 +0200 Subject: ASoC: tegra_alc5632: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c index 6dcd06a..ba272e2 100644 --- a/sound/soc/tegra/tegra_alc5632.c +++ b/sound/soc/tegra/tegra_alc5632.c @@ -101,9 +101,6 @@ static const struct snd_kcontrol_new tegra_alc5632_controls[] = { static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(rtd->card); snd_soc_card_jack_new(rtd->card, "Headset Jack", SND_JACK_HEADSET, @@ -118,7 +115,7 @@ static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) &tegra_alc5632_hp_jack_gpio); } - snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); return 0; } -- cgit v0.10.2 From c67a443b11d96f35f8514283334c50302253f2b4 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 3 Apr 2015 13:04:06 +0200 Subject: ASoC: tegra_rt5677: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/tegra/tegra_rt5677.c b/sound/soc/tegra/tegra_rt5677.c index d082882..1470873 100644 --- a/sound/soc/tegra/tegra_rt5677.c +++ b/sound/soc/tegra/tegra_rt5677.c @@ -141,9 +141,6 @@ static const struct snd_kcontrol_new tegra_rt5677_controls[] = { static int tegra_rt5677_asoc_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(rtd->card); snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, @@ -167,7 +164,7 @@ static int tegra_rt5677_asoc_init(struct snd_soc_pcm_runtime *rtd) &tegra_rt5677_mic_jack_gpio); } - snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); return 0; } -- cgit v0.10.2 From 14e954f5ddc56c0dc9c32fadf188f44f5855e0fa Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 3 Apr 2015 13:04:07 +0200 Subject: ASoC: tegra_wm8903: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index 4a95b70..2160400 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -171,7 +171,6 @@ static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; struct snd_soc_card *card = rtd->card; struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); @@ -193,7 +192,7 @@ static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) wm8903_mic_detect(codec, &tegra_wm8903_mic_jack, SND_JACK_MICROPHONE, 0); - snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS"); return 0; } -- cgit v0.10.2 From 1b13fe718b6ebca2b12cfa3d6cd32b8b68a03e38 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 3 Apr 2015 13:04:08 +0200 Subject: ASoC: tegra_wm9712: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c index 2868b48..6492f81 100644 --- a/sound/soc/tegra/tegra_wm9712.c +++ b/sound/soc/tegra/tegra_wm9712.c @@ -46,11 +46,7 @@ static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = { static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; - - return snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); + return snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); } static struct snd_soc_dai_link tegra_wm9712_dai = { -- cgit v0.10.2 From 75afbd052b3675e9b812f9327e19be63f3e7b5de Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 9 Apr 2015 12:02:39 +0300 Subject: ASoC: Intel: do cast earlier in sst_cdev_tstamp() My static checker complains about these because it looks like the multiply can overflow and then we cast to a larger data type. I don't think this is a problem, but it's also harmless to do the cast earlier so let's silence the static checker warning. Signed-off-by: Dan Carpenter Signed-off-by: Mark Brown diff --git a/sound/soc/intel/atom/sst/sst_drv_interface.c b/sound/soc/intel/atom/sst/sst_drv_interface.c index 718838b..7b50a9d 100644 --- a/sound/soc/intel/atom/sst/sst_drv_interface.c +++ b/sound/soc/intel/atom/sst/sst_drv_interface.c @@ -381,7 +381,7 @@ static int sst_cdev_tstamp(struct device *dev, unsigned int str_id, tstamp->copied_total = fw_tstamp.ring_buffer_counter; tstamp->pcm_frames = fw_tstamp.frames_decoded; tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter, - (u64)((stream->num_ch) * SST_GET_BYTES_PER_SAMPLE(24))); + (u64)stream->num_ch * SST_GET_BYTES_PER_SAMPLE(24)); tstamp->sampling_rate = fw_tstamp.sampling_frequency; dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames); -- cgit v0.10.2 From 0757d834eb7482e5763fb9ee014abb50789f906a Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 9 Apr 2015 10:52:36 +0200 Subject: ASoC: Create card debugfs directory earlier Create the card debugfs directory at the begining of the initilization rather then the end as various steps in the initilization sequence will try to register files and sub-directories in the card directory. Fixes: 4e2576bd36a1 ("ASoC: soc-core: initialize debugfs in snd_soc_instantiate_card()") Reported-by: Fabio Estevam Reported-by: Nicolin Chen Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 71585d0..3f18fa7 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1559,6 +1559,8 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) goto base_error; } + soc_init_card_debugfs(card); + card->dapm.bias_level = SND_SOC_BIAS_OFF; card->dapm.dev = card->dev; card->dapm.card = card; @@ -1680,8 +1682,6 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) mutex_unlock(&card->mutex); mutex_unlock(&client_mutex); - soc_init_card_debugfs(card); - return 0; probe_aux_dev_err: @@ -1695,6 +1695,7 @@ card_probe_error: if (card->remove) card->remove(card); + soc_cleanup_card_debugfs(card); snd_card_free(card->snd_card); base_error: -- cgit v0.10.2 From d53d59ecad74f3e90c6eefedd2186abbadd64d7c Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 9 Apr 2015 11:20:32 +0800 Subject: ASoC: rt286: Restore default in probe RT286 can't do register reset. If the hardware power is still existing in power off, rt286 will keep the register settings. So, we need to restore the default register value in probe to make sure the cache value is the same as the real register value. Signed-off-by: Bard Liao Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 842cfb9..87af81b 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -1250,6 +1250,14 @@ static int rt286_i2c_probe(struct i2c_client *i2c, rt286->i2c = i2c; i2c_set_clientdata(i2c, rt286); + /* restore codec default */ + for (i = 0; i < INDEX_CACHE_SIZE; i++) + regmap_write(rt286->regmap, rt286->index_cache[i].reg, + rt286->index_cache[i].def); + for (i = 0; i < ARRAY_SIZE(rt286_reg); i++) + regmap_write(rt286->regmap, rt286_reg[i].reg, + rt286_reg[i].def); + if (pdata) rt286->pdata = *pdata; -- cgit v0.10.2 From 2e55b90a5e234524f8195c657006ec4f71103dff Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 9 Apr 2015 10:52:37 +0200 Subject: ASoC: Make soc_dpcm_debugfs_add() non-fatal Failing to register the debugfs entries is not fatal and will not affect normal operation of the sound card. Don't abort the card registration if soc_dpcm_debugfs_add() fails. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index 98f2ade..8060590 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -135,7 +135,7 @@ void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, int stream, /* internal use only */ int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute); -int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd); +void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd); int soc_dpcm_runtime_update(struct snd_soc_card *); int dpcm_path_get(struct snd_soc_pcm_runtime *fe, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 3f18fa7..d29c68a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1328,15 +1328,8 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) #ifdef CONFIG_DEBUG_FS /* add DPCM sysfs entries */ - if (dai_link->dynamic) { - ret = soc_dpcm_debugfs_add(rtd); - if (ret < 0) { - dev_err(rtd->dev, - "ASoC: failed to add dpcm sysfs entries: %d\n", - ret); - return ret; - } - } + if (dai_link->dynamic) + soc_dpcm_debugfs_add(rtd); #endif if (cpu_dai->driver->compress_dai) { diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 6b0136e..9c514fd 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2802,10 +2802,10 @@ static const struct file_operations dpcm_state_fops = { .llseek = default_llseek, }; -int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) +void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) { if (!rtd->dai_link) - return 0; + return; rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, rtd->card->debugfs_card_root); @@ -2813,13 +2813,11 @@ int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) dev_dbg(rtd->dev, "ASoC: Failed to create dpcm debugfs directory %s\n", rtd->dai_link->name); - return -EINVAL; + return; } rtd->debugfs_dpcm_state = debugfs_create_file("state", 0444, rtd->debugfs_dpcm_root, rtd, &dpcm_state_fops); - - return 0; } #endif -- cgit v0.10.2 From 6553bf06a369683408895b87e5172aa99a9266bd Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 9 Apr 2015 10:52:38 +0200 Subject: ASoC: Don't try to register debugfs entries if the parent does not exist If the registration of a debugfs directory fails this is treated as a non-fatal error in ASoC and operation continues as normal. This means we need to be careful and check if the parent debugfs directory exists if we try to register a debugfs file or sub-directory. Otherwise we might end up passing NULL for the parent and the file or directory will be registered in the top-level debugfs directory. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index d29c68a..9dfa2e2 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -292,6 +292,9 @@ static const struct file_operations codec_reg_fops = { static void soc_init_component_debugfs(struct snd_soc_component *component) { + if (!component->card->debugfs_card_root) + return; + if (component->debugfs_prefix) { char *name; @@ -455,6 +458,9 @@ static const struct file_operations platform_list_fops = { static void soc_init_card_debugfs(struct snd_soc_card *card) { + if (!snd_soc_debugfs_root) + return; + card->debugfs_card_root = debugfs_create_dir(card->name, snd_soc_debugfs_root); if (!card->debugfs_card_root) { @@ -476,6 +482,34 @@ static void soc_cleanup_card_debugfs(struct snd_soc_card *card) debugfs_remove_recursive(card->debugfs_card_root); } + +static void snd_soc_debugfs_init(void) +{ + snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); + if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) { + pr_warn("ASoC: Failed to create debugfs directory\n"); + snd_soc_debugfs_root = NULL; + return; + } + + if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL, + &codec_list_fops)) + pr_warn("ASoC: Failed to create CODEC list debugfs file\n"); + + if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, + &dai_list_fops)) + pr_warn("ASoC: Failed to create DAI list debugfs file\n"); + + if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL, + &platform_list_fops)) + pr_warn("ASoC: Failed to create platform list debugfs file\n"); +} + +static void snd_soc_debugfs_exit(void) +{ + debugfs_remove_recursive(snd_soc_debugfs_root); +} + #else #define soc_init_codec_debugfs NULL @@ -497,6 +531,15 @@ static inline void soc_init_card_debugfs(struct snd_soc_card *card) static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) { } + +static inline void snd_soc_debugfs_init(void) +{ +} + +static inline void snd_soc_debugfs_exit(void) +{ +} + #endif struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, @@ -3580,26 +3623,7 @@ EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_codecs); static int __init snd_soc_init(void) { -#ifdef CONFIG_DEBUG_FS - snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); - if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) { - pr_warn("ASoC: Failed to create debugfs directory\n"); - snd_soc_debugfs_root = NULL; - } - - if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL, - &codec_list_fops)) - pr_warn("ASoC: Failed to create CODEC list debugfs file\n"); - - if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, - &dai_list_fops)) - pr_warn("ASoC: Failed to create DAI list debugfs file\n"); - - if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL, - &platform_list_fops)) - pr_warn("ASoC: Failed to create platform list debugfs file\n"); -#endif - + snd_soc_debugfs_init(); snd_soc_util_init(); return platform_driver_register(&soc_driver); @@ -3609,9 +3633,9 @@ module_init(snd_soc_init); static void __exit snd_soc_exit(void) { snd_soc_util_exit(); + snd_soc_debugfs_exit(); #ifdef CONFIG_DEBUG_FS - debugfs_remove_recursive(snd_soc_debugfs_root); #endif platform_driver_unregister(&soc_driver); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b6f8820..1fd2d45 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1898,6 +1898,9 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, { struct dentry *d; + if (!parent) + return; + dapm->debugfs_dapm = debugfs_create_dir("dapm", parent); if (!dapm->debugfs_dapm) { diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 9c514fd..b0d61e6 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1097,8 +1097,9 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, stream ? "<-" : "->", be->dai_link->name); #ifdef CONFIG_DEBUG_FS - dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, - fe->debugfs_dpcm_root, &dpcm->state); + if (fe->debugfs_dpcm_root) + dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, + fe->debugfs_dpcm_root, &dpcm->state); #endif return 1; } @@ -2807,6 +2808,9 @@ void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) if (!rtd->dai_link) return; + if (!rtd->card->debugfs_card_root) + return; + rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, rtd->card->debugfs_card_root); if (!rtd->debugfs_dpcm_root) { -- cgit v0.10.2 From c01673e0b7236140eb5bbe1c7b30aa262f142d7e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 10 Apr 2015 08:46:22 +0000 Subject: ASoC: ak4642: fixup channels_min ak4642 doesn't have Mono record, ak4643 have it, but not supported. This patch fixes channel mismatch Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index dde8b49..fba80f3 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -468,13 +468,13 @@ static struct snd_soc_dai_driver ak4642_dai = { .name = "ak4642-hifi", .playback = { .stream_name = "Playback", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE }, .capture = { .stream_name = "Capture", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE }, -- cgit v0.10.2 From 299e7e97cc33d2d8894250ae2a3101bfb5670141 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 9 Apr 2015 14:56:41 -0300 Subject: ASoC: fsl_ssi: Use devm_snd_soc_register_component() Using devm_snd_soc_register_component() can make the code shorter and cleaner. Signed-off-by: Fabio Estevam Acked-by: Nicolin Chen Signed-off-by: Mark Brown diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 2595611..4201bfe 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -1390,8 +1390,8 @@ static int fsl_ssi_probe(struct platform_device *pdev) return ret; } - ret = snd_soc_register_component(&pdev->dev, &fsl_ssi_component, - &ssi_private->cpu_dai_drv, 1); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_ssi_component, + &ssi_private->cpu_dai_drv, 1); if (ret) { dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); goto error_asoc_register; @@ -1404,13 +1404,13 @@ static int fsl_ssi_probe(struct platform_device *pdev) if (ret < 0) { dev_err(&pdev->dev, "could not claim irq %u\n", ssi_private->irq); - goto error_irq; + goto error_asoc_register; } } ret = fsl_ssi_debugfs_create(&ssi_private->dbg_stats, &pdev->dev); if (ret) - goto error_irq; + goto error_asoc_register; /* * If codec-handle property is missing from SSI node, we assume @@ -1451,9 +1451,6 @@ done: error_sound_card: fsl_ssi_debugfs_remove(&ssi_private->dbg_stats); -error_irq: - snd_soc_unregister_component(&pdev->dev); - error_asoc_register: if (ssi_private->soc->imx) fsl_ssi_imx_clean(pdev, ssi_private); @@ -1469,7 +1466,6 @@ static int fsl_ssi_remove(struct platform_device *pdev) if (ssi_private->pdev) platform_device_unregister(ssi_private->pdev); - snd_soc_unregister_component(&pdev->dev); if (ssi_private->soc->imx) fsl_ssi_imx_clean(pdev, ssi_private); -- cgit v0.10.2 From ca2641891d8f0503f166502d168690c1e7d38e49 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 10 Apr 2015 07:12:29 -0300 Subject: ASoC: fsl_ssi: Use devm_ioremap_resource() Using platform_get_resource() and devm_ioremap_resource() can make the code a bit simpler. Signed-off-by: Fabio Estevam Signed-off-by: Mark Brown diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 4201bfe..4f643c4 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -1285,7 +1285,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) const struct of_device_id *of_id; const char *p, *sprop; const uint32_t *iprop; - struct resource res; + struct resource *res; void __iomem *iomem; char name[64]; @@ -1332,19 +1332,11 @@ static int fsl_ssi_probe(struct platform_device *pdev) } ssi_private->cpu_dai_drv.name = dev_name(&pdev->dev); - /* Get the addresses and IRQ */ - ret = of_address_to_resource(np, 0, &res); - if (ret) { - dev_err(&pdev->dev, "could not determine device resources\n"); - return ret; - } - ssi_private->ssi_phys = res.start; - - iomem = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); - if (!iomem) { - dev_err(&pdev->dev, "could not map device resources\n"); - return -ENOMEM; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iomem = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + ssi_private->ssi_phys = res->start; ret = of_property_match_string(np, "clock-names", "ipg"); if (ret < 0) { -- cgit v0.10.2 From a5053a8e200e865ab786384df3f985a3cbb346fe Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 10 Apr 2015 09:47:00 +0000 Subject: ASoC: core: call snd_soc_runtime_set_dai_fmt() before soc_new_pcm() Current snd_soc_runtime_set_dai_fmt() is called after soc_probe_link_dais(). this means snd_soc_dai_set_fmt() will be called after soc_new_pcm(). Before appling 1efb53a220b78fdfdbb97b726a2156713e75bdab (ASoC: simple-card: Remove support for setting differing DAI formats) simple-card user had (1) snd_soc_dai_set_fmt() -> soc_new_pcm(), but, after that it is (2) soc_new_pcm() -> snd_soc_dai_set_fmt(). At least rsnd driver is assuming (1) pattern. This patch move snd_soc_dai_set_fmt() into soc_probe_link_dais() after the dai_link->init section to solve this issue. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 25fcd80..2fb3bf7 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1359,6 +1359,9 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) } } + if (dai_link->dai_fmt) + snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); + ret = soc_post_component_init(rtd, dai_link->name); if (ret) return ret; @@ -1672,12 +1675,6 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, card->num_of_dapm_routes); - for (i = 0; i < card->num_links; i++) { - if (card->dai_link[i].dai_fmt) - snd_soc_runtime_set_dai_fmt(&card->rtd[i], - card->dai_link[i].dai_fmt); - } - snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), "%s", card->name); snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), -- cgit v0.10.2 From 1169006b054ed98f6056b67fd7f18231b65794a0 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 9 Apr 2015 15:32:09 -0300 Subject: ASoC: fsl: Add the audio interface acronyms in Kconfig text To keep consistency with the other Kconfig entries, use the audio interface acronyms (SSI and SPDIF) in the Kconfig menu text. Signed-off-by: Fabio Estevam Acked-by: Nicolin Chen Signed-off-by: Mark Brown diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 081e406..19c302b 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -24,7 +24,7 @@ config SND_SOC_FSL_SAI in-tree drivers select it automatically. config SND_SOC_FSL_SSI - tristate "Synchronous Serial Interface module support" + tristate "Synchronous Serial Interface module (SSI) support" select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) select REGMAP_MMIO @@ -35,7 +35,7 @@ config SND_SOC_FSL_SSI in-tree drivers select it automatically. config SND_SOC_FSL_SPDIF - tristate "Sony/Philips Digital Interface module support" + tristate "Sony/Philips Digital Interface (S/PDIF) module support" select REGMAP_MMIO select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) -- cgit v0.10.2 From a33c1ec5cf82efb76f0e7339b13f11cfb53a2a2f Mon Sep 17 00:00:00 2001 From: Jin Yao Date: Tue, 7 Apr 2015 09:33:30 +0800 Subject: ASoC: Intel: Refactor common IPC/mailbox code into generic APIs Currently in Intel SST driver, some similar IPC/mailbox processing code are used in different platforms (e.g. in baytrail/broadwell). This patch extracts the common code and creates new files (sst-ipc.c/sst-ipc.h) to contain the common code and provide the generic APIs for IPC/mailbox processing. Signed-off-by: Jin Yao Acked-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile index 3df0e1c..f24154c 100644 --- a/sound/soc/intel/common/Makefile +++ b/sound/soc/intel/common/Makefile @@ -1,6 +1,7 @@ snd-soc-sst-dsp-objs := sst-dsp.o sst-firmware.o snd-soc-sst-acpi-objs := sst-acpi.o +snd-soc-sst-ipc-objs := sst-ipc.o -obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o +obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o diff --git a/sound/soc/intel/common/sst-ipc.c b/sound/soc/intel/common/sst-ipc.c new file mode 100644 index 0000000..4b62a55 --- /dev/null +++ b/sound/soc/intel/common/sst-ipc.c @@ -0,0 +1,294 @@ +/* + * Intel SST generic IPC Support + * + * Copyright (C) 2015, Intel Corporation. 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" +#include "sst-ipc.h" + +/* IPC message timeout (msecs) */ +#define IPC_TIMEOUT_MSECS 300 + +#define IPC_EMPTY_LIST_SIZE 8 + +/* locks held by caller */ +static struct ipc_message *msg_get_empty(struct sst_generic_ipc *ipc) +{ + struct ipc_message *msg = NULL; + + if (!list_empty(&ipc->empty_list)) { + msg = list_first_entry(&ipc->empty_list, struct ipc_message, + list); + list_del(&msg->list); + } + + return msg; +} + +static int tx_wait_done(struct sst_generic_ipc *ipc, + struct ipc_message *msg, void *rx_data) +{ + unsigned long flags; + int ret; + + /* wait for DSP completion (in all cases atm inc pending) */ + ret = wait_event_timeout(msg->waitq, msg->complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + if (ret == 0) { + if (ipc->ops.shim_dbg != NULL) + ipc->ops.shim_dbg(ipc, "message timeout"); + + list_del(&msg->list); + ret = -ETIMEDOUT; + } else { + + /* copy the data returned from DSP */ + if (msg->rx_size) + memcpy(rx_data, msg->rx_data, msg->rx_size); + ret = msg->errno; + } + + list_add_tail(&msg->list, &ipc->empty_list); + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + return ret; +} + +static int ipc_tx_message(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, + size_t rx_bytes, int wait) +{ + struct ipc_message *msg; + unsigned long flags; + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + + msg = msg_get_empty(ipc); + if (msg == NULL) { + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + return -EBUSY; + } + + msg->header = header; + msg->tx_size = tx_bytes; + msg->rx_size = rx_bytes; + msg->wait = wait; + msg->errno = 0; + msg->pending = false; + msg->complete = false; + + if ((tx_bytes) && (ipc->ops.tx_data_copy != NULL)) + ipc->ops.tx_data_copy(msg, tx_data, tx_bytes); + + list_add_tail(&msg->list, &ipc->tx_list); + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + + queue_kthread_work(&ipc->kworker, &ipc->kwork); + + if (wait) + return tx_wait_done(ipc, msg, rx_data); + else + return 0; +} + +static int msg_empty_list_init(struct sst_generic_ipc *ipc) +{ + int i; + + ipc->msg = kzalloc(sizeof(struct ipc_message) * + IPC_EMPTY_LIST_SIZE, GFP_KERNEL); + if (ipc->msg == NULL) + return -ENOMEM; + + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + init_waitqueue_head(&ipc->msg[i].waitq); + list_add(&ipc->msg[i].list, &ipc->empty_list); + } + + return 0; +} + +static void ipc_tx_msgs(struct kthread_work *work) +{ + struct sst_generic_ipc *ipc = + container_of(work, struct sst_generic_ipc, kwork); + struct ipc_message *msg; + unsigned long flags; + u64 ipcx; + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + + if (list_empty(&ipc->tx_list) || ipc->pending) { + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + return; + } + + /* if the DSP is busy, we will TX messages after IRQ. + * also postpone if we are in the middle of procesing completion irq*/ + ipcx = sst_dsp_shim_read_unlocked(ipc->dsp, SST_IPCX); + if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) { + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + return; + } + + msg = list_first_entry(&ipc->tx_list, struct ipc_message, list); + list_move(&msg->list, &ipc->rx_list); + + if (ipc->ops.tx_msg != NULL) + ipc->ops.tx_msg(ipc, msg); + + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); +} + +int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) +{ + return ipc_tx_message(ipc, header, tx_data, tx_bytes, + rx_data, rx_bytes, 1); +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_message_wait); + +int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes) +{ + return ipc_tx_message(ipc, header, tx_data, tx_bytes, + NULL, 0, 0); +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nowait); + +struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc, + u64 header) +{ + struct ipc_message *msg; + u64 mask; + + if (ipc->ops.reply_msg_match != NULL) + header = ipc->ops.reply_msg_match(header, &mask); + + if (list_empty(&ipc->rx_list)) { + dev_err(ipc->dev, "error: rx list empty but received 0x%llx\n", + header); + return NULL; + } + + list_for_each_entry(msg, &ipc->rx_list, list) { + if ((msg->header & mask) == header) + return msg; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(sst_ipc_reply_find_msg); + +/* locks held by caller */ +void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, + struct ipc_message *msg) +{ + msg->complete = true; + + if (!msg->wait) + list_add_tail(&msg->list, &ipc->empty_list); + else + wake_up(&msg->waitq); +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_msg_reply_complete); + +void sst_ipc_drop_all(struct sst_generic_ipc *ipc) +{ + struct ipc_message *msg, *tmp; + unsigned long flags; + int tx_drop_cnt = 0, rx_drop_cnt = 0; + + /* drop all TX and Rx messages before we stall + reset DSP */ + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + + list_for_each_entry_safe(msg, tmp, &ipc->tx_list, list) { + list_move(&msg->list, &ipc->empty_list); + tx_drop_cnt++; + } + + list_for_each_entry_safe(msg, tmp, &ipc->rx_list, list) { + list_move(&msg->list, &ipc->empty_list); + rx_drop_cnt++; + } + + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + + if (tx_drop_cnt || rx_drop_cnt) + dev_err(ipc->dev, "dropped IPC msg RX=%d, TX=%d\n", + tx_drop_cnt, rx_drop_cnt); +} +EXPORT_SYMBOL_GPL(sst_ipc_drop_all); + +int sst_ipc_init(struct sst_generic_ipc *ipc) +{ + int ret; + + INIT_LIST_HEAD(&ipc->tx_list); + INIT_LIST_HEAD(&ipc->rx_list); + INIT_LIST_HEAD(&ipc->empty_list); + init_waitqueue_head(&ipc->wait_txq); + + ret = msg_empty_list_init(ipc); + if (ret < 0) + return -ENOMEM; + + /* start the IPC message thread */ + init_kthread_worker(&ipc->kworker); + ipc->tx_thread = kthread_run(kthread_worker_fn, + &ipc->kworker, "%s", + dev_name(ipc->dev)); + if (IS_ERR(ipc->tx_thread)) { + dev_err(ipc->dev, "error: failed to create message TX task\n"); + ret = PTR_ERR(ipc->tx_thread); + kfree(ipc->msg); + return ret; + } + + init_kthread_work(&ipc->kwork, ipc_tx_msgs); + return 0; +} +EXPORT_SYMBOL_GPL(sst_ipc_init); + +void sst_ipc_fini(struct sst_generic_ipc *ipc) +{ + if (ipc->tx_thread) + kthread_stop(ipc->tx_thread); + + if (ipc->msg) + kfree(ipc->msg); +} +EXPORT_SYMBOL_GPL(sst_ipc_fini); + +/* Module information */ +MODULE_AUTHOR("Jin Yao"); +MODULE_DESCRIPTION("Intel SST IPC generic"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-ipc.h b/sound/soc/intel/common/sst-ipc.h new file mode 100644 index 0000000..125ea45 --- /dev/null +++ b/sound/soc/intel/common/sst-ipc.h @@ -0,0 +1,91 @@ +/* + * Intel SST generic IPC Support + * + * Copyright (C) 2015, Intel Corporation. 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 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 __SST_GENERIC_IPC_H +#define __SST_GENERIC_IPC_H + +#include +#include +#include +#include +#include +#include +#include + +#define IPC_MAX_MAILBOX_BYTES 256 + +struct ipc_message { + struct list_head list; + u64 header; + + /* direction wrt host CPU */ + char tx_data[IPC_MAX_MAILBOX_BYTES]; + size_t tx_size; + char rx_data[IPC_MAX_MAILBOX_BYTES]; + size_t rx_size; + + wait_queue_head_t waitq; + bool pending; + bool complete; + bool wait; + int errno; +}; + +struct sst_generic_ipc; + +struct sst_plat_ipc_ops { + void (*tx_msg)(struct sst_generic_ipc *, struct ipc_message *); + void (*shim_dbg)(struct sst_generic_ipc *, const char *); + void (*tx_data_copy)(struct ipc_message *, char *, size_t); + u64 (*reply_msg_match)(u64 header, u64 *mask); +}; + +/* SST generic IPC data */ +struct sst_generic_ipc { + struct device *dev; + struct sst_dsp *dsp; + + /* IPC messaging */ + struct list_head tx_list; + struct list_head rx_list; + struct list_head empty_list; + wait_queue_head_t wait_txq; + struct task_struct *tx_thread; + struct kthread_worker kworker; + struct kthread_work kwork; + bool pending; + struct ipc_message *msg; + + struct sst_plat_ipc_ops ops; +}; + +int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes); + +int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes); + +struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc, + u64 header); + +void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, + struct ipc_message *msg); + +void sst_ipc_drop_all(struct sst_generic_ipc *ipc); +int sst_ipc_init(struct sst_generic_ipc *ipc); +void sst_ipc_fini(struct sst_generic_ipc *ipc); + +#endif -- cgit v0.10.2 From 48cec59b6f383c63b2b828b93656ee2030abecc0 Mon Sep 17 00:00:00 2001 From: Jin Yao Date: Tue, 7 Apr 2015 09:33:31 +0800 Subject: ASoC: Intel: Use the generic IPC/mailbox APIs in Baytrail Use the generic IPC/mailbox APIs to replace the original processing code for Baytrail platform. Signed-off-by: Jin Yao Acked-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.c b/sound/soc/intel/baytrail/sst-baytrail-ipc.c index aabb9b0..1efb33b 100644 --- a/sound/soc/intel/baytrail/sst-baytrail-ipc.c +++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.c @@ -31,6 +31,7 @@ #include "sst-baytrail-ipc.h" #include "../common/sst-dsp.h" #include "../common/sst-dsp-priv.h" +#include "../common/sst-ipc.h" /* IPC message timeout */ #define IPC_TIMEOUT_MSECS 300 @@ -142,23 +143,6 @@ struct sst_byt_fw_init { u8 debug_info; } __packed; -/* driver internal IPC message structure */ -struct ipc_message { - struct list_head list; - u64 header; - - /* direction wrt host CPU */ - char tx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; - size_t tx_size; - char rx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; - size_t rx_size; - - wait_queue_head_t waitq; - bool complete; - bool wait; - int errno; -}; - struct sst_byt_stream; struct sst_byt; @@ -195,14 +179,7 @@ struct sst_byt { struct sst_fw *fw; /* IPC messaging */ - struct list_head tx_list; - struct list_head rx_list; - struct list_head empty_list; - wait_queue_head_t wait_txq; - struct task_struct *tx_thread; - struct kthread_worker kworker; - struct kthread_work kwork; - struct ipc_message *msg; + struct sst_generic_ipc ipc; }; static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) @@ -246,209 +223,6 @@ static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt, return NULL; } -static void sst_byt_ipc_shim_dbg(struct sst_byt *byt, const char *text) -{ - struct sst_dsp *sst = byt->dsp; - u64 isr, ipcd, imrx, ipcx; - - ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); - isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); - ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); - - dev_err(byt->dev, - "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", - text, ipcx, isr, ipcd, imrx); -} - -/* locks held by caller */ -static struct ipc_message *sst_byt_msg_get_empty(struct sst_byt *byt) -{ - struct ipc_message *msg = NULL; - - if (!list_empty(&byt->empty_list)) { - msg = list_first_entry(&byt->empty_list, - struct ipc_message, list); - list_del(&msg->list); - } - - return msg; -} - -static void sst_byt_ipc_tx_msgs(struct kthread_work *work) -{ - struct sst_byt *byt = - container_of(work, struct sst_byt, kwork); - struct ipc_message *msg; - u64 ipcx; - unsigned long flags; - - spin_lock_irqsave(&byt->dsp->spinlock, flags); - if (list_empty(&byt->tx_list)) { - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return; - } - - /* if the DSP is busy we will TX messages after IRQ */ - ipcx = sst_dsp_shim_read64_unlocked(byt->dsp, SST_IPCX); - if (ipcx & SST_BYT_IPCX_BUSY) { - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return; - } - - msg = list_first_entry(&byt->tx_list, struct ipc_message, list); - - list_move(&msg->list, &byt->rx_list); - - /* send the message */ - if (msg->header & IPC_HEADER_LARGE(true)) - sst_dsp_outbox_write(byt->dsp, msg->tx_data, msg->tx_size); - sst_dsp_shim_write64_unlocked(byt->dsp, SST_IPCX, msg->header); - - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); -} - -static inline void sst_byt_tx_msg_reply_complete(struct sst_byt *byt, - struct ipc_message *msg) -{ - msg->complete = true; - - if (!msg->wait) - list_add_tail(&msg->list, &byt->empty_list); - else - wake_up(&msg->waitq); -} - -static void sst_byt_drop_all(struct sst_byt *byt) -{ - struct ipc_message *msg, *tmp; - unsigned long flags; - - /* drop all TX and Rx messages before we stall + reset DSP */ - spin_lock_irqsave(&byt->dsp->spinlock, flags); - list_for_each_entry_safe(msg, tmp, &byt->tx_list, list) { - list_move(&msg->list, &byt->empty_list); - } - - list_for_each_entry_safe(msg, tmp, &byt->rx_list, list) { - list_move(&msg->list, &byt->empty_list); - } - - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); -} - -static int sst_byt_tx_wait_done(struct sst_byt *byt, struct ipc_message *msg, - void *rx_data) -{ - unsigned long flags; - int ret; - - /* wait for DSP completion */ - ret = wait_event_timeout(msg->waitq, msg->complete, - msecs_to_jiffies(IPC_TIMEOUT_MSECS)); - - spin_lock_irqsave(&byt->dsp->spinlock, flags); - if (ret == 0) { - list_del(&msg->list); - sst_byt_ipc_shim_dbg(byt, "message timeout"); - - ret = -ETIMEDOUT; - } else { - - /* copy the data returned from DSP */ - if (msg->rx_size) - memcpy(rx_data, msg->rx_data, msg->rx_size); - ret = msg->errno; - } - - list_add_tail(&msg->list, &byt->empty_list); - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return ret; -} - -static int sst_byt_ipc_tx_message(struct sst_byt *byt, u64 header, - void *tx_data, size_t tx_bytes, - void *rx_data, size_t rx_bytes, int wait) -{ - unsigned long flags; - struct ipc_message *msg; - - spin_lock_irqsave(&byt->dsp->spinlock, flags); - - msg = sst_byt_msg_get_empty(byt); - if (msg == NULL) { - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - return -EBUSY; - } - - msg->header = header; - msg->tx_size = tx_bytes; - msg->rx_size = rx_bytes; - msg->wait = wait; - msg->errno = 0; - msg->complete = false; - - if (tx_bytes) { - /* msg content = lower 32-bit of the header + data */ - *(u32 *)msg->tx_data = (u32)(header & (u32)-1); - memcpy(msg->tx_data + sizeof(u32), tx_data, tx_bytes); - msg->tx_size += sizeof(u32); - } - - list_add_tail(&msg->list, &byt->tx_list); - spin_unlock_irqrestore(&byt->dsp->spinlock, flags); - - queue_kthread_work(&byt->kworker, &byt->kwork); - - if (wait) - return sst_byt_tx_wait_done(byt, msg, rx_data); - else - return 0; -} - -static inline int sst_byt_ipc_tx_msg_wait(struct sst_byt *byt, u64 header, - void *tx_data, size_t tx_bytes, - void *rx_data, size_t rx_bytes) -{ - return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, - rx_data, rx_bytes, 1); -} - -static inline int sst_byt_ipc_tx_msg_nowait(struct sst_byt *byt, u64 header, - void *tx_data, size_t tx_bytes) -{ - return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, - NULL, 0, 0); -} - -static struct ipc_message *sst_byt_reply_find_msg(struct sst_byt *byt, - u64 header) -{ - struct ipc_message *msg = NULL, *_msg; - u64 mask; - - /* match reply to message sent based on msg and stream IDs */ - mask = IPC_HEADER_MSG_ID_MASK | - IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; - header &= mask; - - if (list_empty(&byt->rx_list)) { - dev_err(byt->dev, - "ipc: rx list is empty but received 0x%llx\n", header); - goto out; - } - - list_for_each_entry(_msg, &byt->rx_list, list) { - if ((_msg->header & mask) == header) { - msg = _msg; - break; - } - } - -out: - return msg; -} - static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) { struct sst_byt_stream *stream; @@ -477,7 +251,7 @@ static int sst_byt_process_reply(struct sst_byt *byt, u64 header) { struct ipc_message *msg; - msg = sst_byt_reply_find_msg(byt, header); + msg = sst_ipc_reply_find_msg(&byt->ipc, header); if (msg == NULL) return 1; @@ -491,7 +265,7 @@ static int sst_byt_process_reply(struct sst_byt *byt, u64 header) list_del(&msg->list); /* wake up */ - sst_byt_tx_msg_reply_complete(byt, msg); + sst_ipc_tx_msg_reply_complete(&byt->ipc, msg); return 1; } @@ -538,6 +312,7 @@ static irqreturn_t sst_byt_irq_thread(int irq, void *context) { struct sst_dsp *sst = (struct sst_dsp *) context; struct sst_byt *byt = sst_dsp_get_thread_context(sst); + struct sst_generic_ipc *ipc = &byt->ipc; u64 header; unsigned long flags; @@ -569,7 +344,7 @@ static irqreturn_t sst_byt_irq_thread(int irq, void *context) spin_unlock_irqrestore(&sst->spinlock, flags); /* continue to send any remaining messages... */ - queue_kthread_work(&byt->kworker, &byt->kwork); + queue_kthread_work(&ipc->kworker, &ipc->kwork); return IRQ_HANDLED; } @@ -656,7 +431,8 @@ int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream) header = sst_byt_header(IPC_IA_ALLOC_STREAM, sizeof(*str_req) + sizeof(u32), true, stream->str_id); - ret = sst_byt_ipc_tx_msg_wait(byt, header, str_req, sizeof(*str_req), + ret = sst_ipc_tx_message_wait(&byt->ipc, header, str_req, + sizeof(*str_req), reply, sizeof(*reply)); if (ret < 0) { dev_err(byt->dev, "ipc: error stream commit failed\n"); @@ -679,7 +455,7 @@ int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream) goto out; header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id); - ret = sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); + ret = sst_ipc_tx_message_wait(&byt->ipc, header, NULL, 0, NULL, 0); if (ret < 0) { dev_err(byt->dev, "ipc: free stream %d failed\n", stream->str_id); @@ -703,9 +479,11 @@ static int sst_byt_stream_operations(struct sst_byt *byt, int type, header = sst_byt_header(type, 0, false, stream_id); if (wait) - return sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); + return sst_ipc_tx_message_wait(&byt->ipc, header, NULL, + 0, NULL, 0); else - return sst_byt_ipc_tx_msg_nowait(byt, header, NULL, 0); + return sst_ipc_tx_message_nowait(&byt->ipc, header, + NULL, 0); } /* stream ALSA trigger operations */ @@ -725,7 +503,7 @@ int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, tx_msg = &start_stream; size = sizeof(start_stream); - ret = sst_byt_ipc_tx_msg_nowait(byt, header, tx_msg, size); + ret = sst_ipc_tx_message_nowait(&byt->ipc, header, tx_msg, size); if (ret < 0) dev_err(byt->dev, "ipc: error failed to start stream %d\n", stream->str_id); @@ -790,23 +568,6 @@ int sst_byt_get_dsp_position(struct sst_byt *byt, return do_div(fw_tstamp.ring_buffer_counter, buffer_size); } -static int msg_empty_list_init(struct sst_byt *byt) -{ - struct ipc_message *msg; - int i; - - byt->msg = kzalloc(sizeof(*msg) * IPC_EMPTY_LIST_SIZE, GFP_KERNEL); - if (byt->msg == NULL) - return -ENOMEM; - - for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { - init_waitqueue_head(&byt->msg[i].waitq); - list_add(&byt->msg[i].list, &byt->empty_list); - } - - return 0; -} - struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt) { return byt->dsp; @@ -823,7 +584,7 @@ int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata) dev_dbg(byt->dev, "dsp reset\n"); sst_dsp_reset(byt->dsp); - sst_byt_drop_all(byt); + sst_ipc_drop_all(&byt->ipc); dev_dbg(byt->dev, "dsp in reset\n"); dev_dbg(byt->dev, "free all blocks and unload fw\n"); @@ -876,9 +637,52 @@ int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata) } EXPORT_SYMBOL_GPL(sst_byt_dsp_wait_for_ready); +static void byt_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + if (msg->header & IPC_HEADER_LARGE(true)) + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + + sst_dsp_shim_write64_unlocked(ipc->dsp, SST_IPCX, msg->header); +} + +static void byt_shim_dbg(struct sst_generic_ipc *ipc, const char *text) +{ + struct sst_dsp *sst = ipc->dsp; + u64 isr, ipcd, imrx, ipcx; + + ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); + isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); + + dev_err(ipc->dev, + "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", + text, ipcx, isr, ipcd, imrx); +} + +static void byt_tx_data_copy(struct ipc_message *msg, char *tx_data, + size_t tx_size) +{ + /* msg content = lower 32-bit of the header + data */ + *(u32 *)msg->tx_data = (u32)(msg->header & (u32)-1); + memcpy(msg->tx_data + sizeof(u32), tx_data, tx_size); + msg->tx_size += sizeof(u32); +} + +static u64 byt_reply_msg_match(u64 header, u64 *mask) +{ + /* match reply to message sent based on msg and stream IDs */ + *mask = IPC_HEADER_MSG_ID_MASK | + IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; + header &= *mask; + + return header; +} + int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) { struct sst_byt *byt; + struct sst_generic_ipc *ipc; struct sst_fw *byt_sst_fw; struct sst_byt_fw_init init; int err; @@ -889,39 +693,30 @@ int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) if (byt == NULL) return -ENOMEM; - byt->dev = dev; - INIT_LIST_HEAD(&byt->stream_list); - INIT_LIST_HEAD(&byt->tx_list); - INIT_LIST_HEAD(&byt->rx_list); - INIT_LIST_HEAD(&byt->empty_list); - init_waitqueue_head(&byt->boot_wait); - init_waitqueue_head(&byt->wait_txq); + ipc = &byt->ipc; + ipc->dev = dev; + ipc->ops.tx_msg = byt_tx_msg; + ipc->ops.shim_dbg = byt_shim_dbg; + ipc->ops.tx_data_copy = byt_tx_data_copy; + ipc->ops.reply_msg_match = byt_reply_msg_match; - err = msg_empty_list_init(byt); - if (err < 0) - return -ENOMEM; - - /* start the IPC message thread */ - init_kthread_worker(&byt->kworker); - byt->tx_thread = kthread_run(kthread_worker_fn, - &byt->kworker, "%s", - dev_name(byt->dev)); - if (IS_ERR(byt->tx_thread)) { - err = PTR_ERR(byt->tx_thread); - dev_err(byt->dev, "error failed to create message TX task\n"); - goto err_free_msg; - } - init_kthread_work(&byt->kwork, sst_byt_ipc_tx_msgs); + err = sst_ipc_init(ipc); + if (err != 0) + goto ipc_init_err; + INIT_LIST_HEAD(&byt->stream_list); + init_waitqueue_head(&byt->boot_wait); byt_dev.thread_context = byt; /* init SST shim */ byt->dsp = sst_dsp_new(dev, &byt_dev, pdata); if (byt->dsp == NULL) { err = -ENODEV; - goto dsp_err; + goto dsp_new_err; } + ipc->dsp = byt->dsp; + /* keep the DSP in reset state for base FW loading */ sst_dsp_reset(byt->dsp); @@ -961,10 +756,10 @@ boot_err: sst_fw_free(byt_sst_fw); fw_err: sst_dsp_free(byt->dsp); -dsp_err: - kthread_stop(byt->tx_thread); -err_free_msg: - kfree(byt->msg); +dsp_new_err: + sst_ipc_fini(ipc); +ipc_init_err: + kfree(byt); return err; } @@ -977,7 +772,6 @@ void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata) sst_dsp_reset(byt->dsp); sst_fw_free_all(byt->dsp); sst_dsp_free(byt->dsp); - kthread_stop(byt->tx_thread); - kfree(byt->msg); + sst_ipc_fini(&byt->ipc); } EXPORT_SYMBOL_GPL(sst_byt_dsp_free); -- cgit v0.10.2 From 0e7921e9583b72be93d8fa82536a7594974b7eea Mon Sep 17 00:00:00 2001 From: Jin Yao Date: Tue, 7 Apr 2015 09:33:32 +0800 Subject: ASoC: Intel: Use the generic IPC/mailbox APIs in Broadwell Use the generic IPC/mailbox APIs to replace the original processing code for Broadwell platform. Signed-off-by: Jin Yao Acked-by: Jie Yang Signed-off-by: Mark Brown diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c index 28667d8..d75f09e 100644 --- a/sound/soc/intel/haswell/sst-haswell-ipc.c +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -36,6 +36,7 @@ #include "sst-haswell-ipc.h" #include "../common/sst-dsp.h" #include "../common/sst-dsp-priv.h" +#include "../common/sst-ipc.h" /* Global Message - Generic */ #define IPC_GLB_TYPE_SHIFT 24 @@ -210,23 +211,6 @@ struct sst_hsw_ipc_fw_ready { u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; } __attribute__((packed)); -struct ipc_message { - struct list_head list; - u32 header; - - /* direction wrt host CPU */ - char tx_data[IPC_MAX_MAILBOX_BYTES]; - size_t tx_size; - char rx_data[IPC_MAX_MAILBOX_BYTES]; - size_t rx_size; - - wait_queue_head_t waitq; - bool pending; - bool complete; - bool wait; - int errno; -}; - struct sst_hsw_stream; struct sst_hsw; @@ -325,15 +309,7 @@ struct sst_hsw { bool shutdown; /* IPC messaging */ - struct list_head tx_list; - struct list_head rx_list; - struct list_head empty_list; - wait_queue_head_t wait_txq; - struct task_struct *tx_thread; - struct kthread_worker kworker; - struct kthread_work kwork; - bool pending; - struct ipc_message *msg; + struct sst_generic_ipc ipc; /* FW log stream */ struct sst_hsw_log_stream log_stream; @@ -456,159 +432,6 @@ static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, return NULL; } -static void ipc_shim_dbg(struct sst_hsw *hsw, const char *text) -{ - struct sst_dsp *sst = hsw->dsp; - u32 isr, ipcd, imrx, ipcx; - - ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); - isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); - ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); - - dev_err(hsw->dev, "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", - text, ipcx, isr, ipcd, imrx); -} - -/* locks held by caller */ -static struct ipc_message *msg_get_empty(struct sst_hsw *hsw) -{ - struct ipc_message *msg = NULL; - - if (!list_empty(&hsw->empty_list)) { - msg = list_first_entry(&hsw->empty_list, struct ipc_message, - list); - list_del(&msg->list); - } - - return msg; -} - -static void ipc_tx_msgs(struct kthread_work *work) -{ - struct sst_hsw *hsw = - container_of(work, struct sst_hsw, kwork); - struct ipc_message *msg; - unsigned long flags; - u32 ipcx; - - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - - if (list_empty(&hsw->tx_list) || hsw->pending) { - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return; - } - - /* if the DSP is busy, we will TX messages after IRQ. - * also postpone if we are in the middle of procesing completion irq*/ - ipcx = sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX); - if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) { - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return; - } - - msg = list_first_entry(&hsw->tx_list, struct ipc_message, list); - - list_move(&msg->list, &hsw->rx_list); - - /* send the message */ - sst_dsp_outbox_write(hsw->dsp, msg->tx_data, msg->tx_size); - sst_dsp_ipc_msg_tx(hsw->dsp, msg->header | SST_IPCX_BUSY); - - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); -} - -/* locks held by caller */ -static void tx_msg_reply_complete(struct sst_hsw *hsw, struct ipc_message *msg) -{ - msg->complete = true; - trace_ipc_reply("completed", msg->header); - - if (!msg->wait) - list_add_tail(&msg->list, &hsw->empty_list); - else - wake_up(&msg->waitq); -} - -static int tx_wait_done(struct sst_hsw *hsw, struct ipc_message *msg, - void *rx_data) -{ - unsigned long flags; - int ret; - - /* wait for DSP completion (in all cases atm inc pending) */ - ret = wait_event_timeout(msg->waitq, msg->complete, - msecs_to_jiffies(IPC_TIMEOUT_MSECS)); - - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - if (ret == 0) { - ipc_shim_dbg(hsw, "message timeout"); - - trace_ipc_error("error message timeout for", msg->header); - list_del(&msg->list); - ret = -ETIMEDOUT; - } else { - - /* copy the data returned from DSP */ - if (msg->rx_size) - memcpy(rx_data, msg->rx_data, msg->rx_size); - ret = msg->errno; - } - - list_add_tail(&msg->list, &hsw->empty_list); - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return ret; -} - -static int ipc_tx_message(struct sst_hsw *hsw, u32 header, void *tx_data, - size_t tx_bytes, void *rx_data, size_t rx_bytes, int wait) -{ - struct ipc_message *msg; - unsigned long flags; - - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - - msg = msg_get_empty(hsw); - if (msg == NULL) { - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - return -EBUSY; - } - - if (tx_bytes) - memcpy(msg->tx_data, tx_data, tx_bytes); - - msg->header = header; - msg->tx_size = tx_bytes; - msg->rx_size = rx_bytes; - msg->wait = wait; - msg->errno = 0; - msg->pending = false; - msg->complete = false; - - list_add_tail(&msg->list, &hsw->tx_list); - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - - queue_kthread_work(&hsw->kworker, &hsw->kwork); - - if (wait) - return tx_wait_done(hsw, msg, rx_data); - else - return 0; -} - -static inline int ipc_tx_message_wait(struct sst_hsw *hsw, u32 header, - void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) -{ - return ipc_tx_message(hsw, header, tx_data, tx_bytes, rx_data, - rx_bytes, 1); -} - -static inline int ipc_tx_message_nowait(struct sst_hsw *hsw, u32 header, - void *tx_data, size_t tx_bytes) -{ - return ipc_tx_message(hsw, header, tx_data, tx_bytes, NULL, 0, 0); -} - static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) { struct sst_hsw_ipc_fw_ready fw_ready; @@ -696,27 +519,6 @@ static void hsw_notification_work(struct work_struct *work) sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); } -static struct ipc_message *reply_find_msg(struct sst_hsw *hsw, u32 header) -{ - struct ipc_message *msg; - - /* clear reply bits & status bits */ - header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); - - if (list_empty(&hsw->rx_list)) { - dev_err(hsw->dev, "error: rx list empty but received 0x%x\n", - header); - return NULL; - } - - list_for_each_entry(msg, &hsw->rx_list, list) { - if (msg->header == header) - return msg; - } - - return NULL; -} - static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) { struct sst_hsw_stream *stream; @@ -755,7 +557,7 @@ static int hsw_process_reply(struct sst_hsw *hsw, u32 header) trace_ipc_reply("processing -->", header); - msg = reply_find_msg(hsw, header); + msg = sst_ipc_reply_find_msg(&hsw->ipc, header); if (msg == NULL) { trace_ipc_error("error: can't find message header", header); return -EIO; @@ -766,14 +568,14 @@ static int hsw_process_reply(struct sst_hsw *hsw, u32 header) case IPC_GLB_REPLY_PENDING: trace_ipc_pending_reply("received", header); msg->pending = true; - hsw->pending = true; + hsw->ipc.pending = true; return 1; case IPC_GLB_REPLY_SUCCESS: if (msg->pending) { trace_ipc_pending_reply("completed", header); sst_dsp_inbox_read(hsw->dsp, msg->rx_data, msg->rx_size); - hsw->pending = false; + hsw->ipc.pending = false; } else { /* copy data from the DSP */ sst_dsp_outbox_read(hsw->dsp, msg->rx_data, @@ -829,7 +631,7 @@ static int hsw_process_reply(struct sst_hsw *hsw, u32 header) /* wake up and return the error if we have waiters on this message ? */ list_del(&msg->list); - tx_msg_reply_complete(hsw, msg); + sst_ipc_tx_msg_reply_complete(&hsw->ipc, msg); return 1; } @@ -970,6 +772,7 @@ static irqreturn_t hsw_irq_thread(int irq, void *context) { struct sst_dsp *sst = (struct sst_dsp *) context; struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); + struct sst_generic_ipc *ipc = &hsw->ipc; u32 ipcx, ipcd; int handled; unsigned long flags; @@ -1016,7 +819,7 @@ static irqreturn_t hsw_irq_thread(int irq, void *context) spin_unlock_irqrestore(&sst->spinlock, flags); /* continue to send any remaining messages... */ - queue_kthread_work(&hsw->kworker, &hsw->kwork); + queue_kthread_work(&ipc->kworker, &ipc->kwork); return IRQ_HANDLED; } @@ -1026,7 +829,8 @@ int sst_hsw_fw_get_version(struct sst_hsw *hsw, { int ret; - ret = ipc_tx_message_wait(hsw, IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), + ret = sst_ipc_tx_message_wait(&hsw->ipc, + IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), NULL, 0, version, sizeof(*version)); if (ret < 0) dev_err(hsw->dev, "error: get version failed\n"); @@ -1090,7 +894,8 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw, req->channel = channel; } - ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, req, + sizeof(*req), NULL, 0); if (ret < 0) { dev_err(hsw->dev, "error: set stream volume failed\n"); return ret; @@ -1155,7 +960,8 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, req.curve_type = hsw->curve_type; req.target_volume = volume; - ret = ipc_tx_message_wait(hsw, header, &req, sizeof(req), NULL, 0); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &req, + sizeof(req), NULL, 0); if (ret < 0) { dev_err(hsw->dev, "error: set mixer volume failed\n"); return ret; @@ -1213,7 +1019,7 @@ int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream) stream->free_req.stream_id = stream->reply.stream_hw_id; header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); - ret = ipc_tx_message_wait(hsw, header, &stream->free_req, + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &stream->free_req, sizeof(stream->free_req), NULL, 0); if (ret < 0) { dev_err(hsw->dev, "error: free stream %d failed\n", @@ -1405,8 +1211,8 @@ int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); - ret = ipc_tx_message_wait(hsw, header, str_req, sizeof(*str_req), - reply, sizeof(*reply)); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, str_req, + sizeof(*str_req), reply, sizeof(*reply)); if (ret < 0) { dev_err(hsw->dev, "error: stream commit failed\n"); return ret; @@ -1455,7 +1261,8 @@ int sst_hsw_mixer_get_info(struct sst_hsw *hsw) trace_ipc_request("get global mixer info", 0); - ret = ipc_tx_message_wait(hsw, header, NULL, 0, reply, sizeof(*reply)); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, NULL, 0, + reply, sizeof(*reply)); if (ret < 0) { dev_err(hsw->dev, "error: get stream info failed\n"); return ret; @@ -1476,9 +1283,10 @@ static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, header |= (stream_id << IPC_STR_ID_SHIFT); if (wait) - return ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); + return sst_ipc_tx_message_wait(&hsw->ipc, header, + NULL, 0, NULL, 0); else - return ipc_tx_message_nowait(hsw, header, NULL, 0); + return sst_ipc_tx_message_nowait(&hsw->ipc, header, NULL, 0); } /* Stream ALSA trigger operations */ @@ -1605,8 +1413,8 @@ int sst_hsw_device_set_config(struct sst_hsw *hsw, header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); - ret = ipc_tx_message_wait(hsw, header, &config, sizeof(config), - NULL, 0); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &config, + sizeof(config), NULL, 0); if (ret < 0) dev_err(hsw->dev, "error: set device formats failed\n"); @@ -1626,8 +1434,8 @@ int sst_hsw_dx_set_state(struct sst_hsw *hsw, trace_ipc_request("PM enter Dx state", state); - ret = ipc_tx_message_wait(hsw, header, &state_, sizeof(state_), - dx, sizeof(*dx)); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &state_, + sizeof(state_), dx, sizeof(*dx)); if (ret < 0) { dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); return ret; @@ -1770,32 +1578,6 @@ static int sst_hsw_dx_state_restore(struct sst_hsw *hsw) return 0; } -static void sst_hsw_drop_all(struct sst_hsw *hsw) -{ - struct ipc_message *msg, *tmp; - unsigned long flags; - int tx_drop_cnt = 0, rx_drop_cnt = 0; - - /* drop all TX and Rx messages before we stall + reset DSP */ - spin_lock_irqsave(&hsw->dsp->spinlock, flags); - - list_for_each_entry_safe(msg, tmp, &hsw->tx_list, list) { - list_move(&msg->list, &hsw->empty_list); - tx_drop_cnt++; - } - - list_for_each_entry_safe(msg, tmp, &hsw->rx_list, list) { - list_move(&msg->list, &hsw->empty_list); - rx_drop_cnt++; - } - - spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); - - if (tx_drop_cnt || rx_drop_cnt) - dev_err(hsw->dev, "dropped IPC msg RX=%d, TX=%d\n", - tx_drop_cnt, rx_drop_cnt); -} - int sst_hsw_dsp_load(struct sst_hsw *hsw) { struct sst_dsp *dsp = hsw->dsp; @@ -1875,7 +1657,7 @@ int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw) if (ret < 0) return ret; - sst_hsw_drop_all(hsw); + sst_ipc_drop_all(&hsw->ipc); return 0; } @@ -1933,23 +1715,6 @@ int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) } #endif -static int msg_empty_list_init(struct sst_hsw *hsw) -{ - int i; - - hsw->msg = kzalloc(sizeof(struct ipc_message) * - IPC_EMPTY_LIST_SIZE, GFP_KERNEL); - if (hsw->msg == NULL) - return -ENOMEM; - - for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { - init_waitqueue_head(&hsw->msg[i].waitq); - list_add(&hsw->msg[i].list, &hsw->empty_list); - } - - return 0; -} - struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) { return hsw->dsp; @@ -2184,7 +1949,7 @@ int sst_hsw_module_enable(struct sst_hsw *hsw, config.scratch_mem.size, config.scratch_mem.offset, config.map.module_entries[0].entry_point); - ret = ipc_tx_message_wait(hsw, header, + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &config, sizeof(config), NULL, 0); if (ret < 0) dev_err(dev, "ipc: module enable failed - %d\n", ret); @@ -2223,7 +1988,7 @@ int sst_hsw_module_disable(struct sst_hsw *hsw, IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) | IPC_MODULE_ID(module_id); - ret = ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, NULL, 0, NULL, 0); if (ret < 0) dev_err(dev, "module disable failed - %d\n", ret); else @@ -2277,7 +2042,7 @@ int sst_hsw_module_set_param(struct sst_hsw *hsw, parameter->parameter_id = parameter_id; parameter->data_size = param_size; - ret = ipc_tx_message_wait(hsw, header, + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, parameter, transfer_parameter_size , NULL, 0); if (ret < 0) dev_err(dev, "ipc: module set parameter failed - %d\n", ret); @@ -2296,10 +2061,48 @@ static struct sst_dsp_device hsw_dev = { .ops = &haswell_ops, }; +static void hsw_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + /* send the message */ + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + sst_dsp_ipc_msg_tx(ipc->dsp, msg->header); +} + +static void hsw_shim_dbg(struct sst_generic_ipc *ipc, const char *text) +{ + struct sst_dsp *sst = ipc->dsp; + u32 isr, ipcd, imrx, ipcx; + + ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); + isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); + + dev_err(ipc->dev, + "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", + text, ipcx, isr, ipcd, imrx); +} + +static void hsw_tx_data_copy(struct ipc_message *msg, char *tx_data, + size_t tx_size) +{ + memcpy(msg->tx_data, tx_data, tx_size); +} + +static u64 hsw_reply_msg_match(u64 header, u64 *mask) +{ + /* clear reply bits & status bits */ + header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + *mask = (u64)-1; + + return header; +} + int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) { struct sst_hsw_ipc_fw_version version; struct sst_hsw *hsw; + struct sst_generic_ipc *ipc; int ret; dev_dbg(dev, "initialising Audio DSP IPC\n"); @@ -2308,39 +2111,30 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) if (hsw == NULL) return -ENOMEM; - hsw->dev = dev; - INIT_LIST_HEAD(&hsw->stream_list); - INIT_LIST_HEAD(&hsw->tx_list); - INIT_LIST_HEAD(&hsw->rx_list); - INIT_LIST_HEAD(&hsw->empty_list); - init_waitqueue_head(&hsw->boot_wait); - init_waitqueue_head(&hsw->wait_txq); + ipc = &hsw->ipc; + ipc->dev = dev; + ipc->ops.tx_msg = hsw_tx_msg; + ipc->ops.shim_dbg = hsw_shim_dbg; + ipc->ops.tx_data_copy = hsw_tx_data_copy; + ipc->ops.reply_msg_match = hsw_reply_msg_match; - ret = msg_empty_list_init(hsw); - if (ret < 0) - return -ENOMEM; - - /* start the IPC message thread */ - init_kthread_worker(&hsw->kworker); - hsw->tx_thread = kthread_run(kthread_worker_fn, - &hsw->kworker, "%s", - dev_name(hsw->dev)); - if (IS_ERR(hsw->tx_thread)) { - ret = PTR_ERR(hsw->tx_thread); - dev_err(hsw->dev, "error: failed to create message TX task\n"); - goto err_free_msg; - } - init_kthread_work(&hsw->kwork, ipc_tx_msgs); + ret = sst_ipc_init(ipc); + if (ret != 0) + goto ipc_init_err; + INIT_LIST_HEAD(&hsw->stream_list); + init_waitqueue_head(&hsw->boot_wait); hsw_dev.thread_context = hsw; /* init SST shim */ hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata); if (hsw->dsp == NULL) { ret = -ENODEV; - goto dsp_err; + goto dsp_new_err; } + ipc->dsp = hsw->dsp; + /* allocate DMA buffer for context storage */ hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL); @@ -2404,11 +2198,10 @@ fw_err: hsw->dx_context, hsw->dx_context_paddr); dma_err: sst_dsp_free(hsw->dsp); -dsp_err: - kthread_stop(hsw->tx_thread); -err_free_msg: - kfree(hsw->msg); - +dsp_new_err: + sst_ipc_fini(ipc); +ipc_init_err: + kfree(hsw); return ret; } EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); @@ -2422,7 +2215,6 @@ void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, hsw->dx_context, hsw->dx_context_paddr); sst_dsp_free(hsw->dsp); - kthread_stop(hsw->tx_thread); - kfree(hsw->msg); + sst_ipc_fini(&hsw->ipc); } EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); -- cgit v0.10.2 From 14f0413ce3c400a74213a861bc8f47dca5e4d231 Mon Sep 17 00:00:00 2001 From: Howard Mitchell Date: Mon, 23 Mar 2015 21:17:01 +0000 Subject: ASoC: pcm512x: Remove hardcoding of pll-lock to GPIO4 Currently GPIO4 is hardcoded to output the pll-lock signal. Unfortunately this is after the pll-out GPIO is configured which is selectable in the device tree. Therefore it is not possible to use GPIO4 for pll-out. Therefore this patch removes the configuration of GPIO4. Signed-off-by: Howard Mitchell Signed-off-by: Mark Brown diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 9974f20..474cae8 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -1156,25 +1156,6 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream, ret, pcm512x->pll_out); return ret; } - - gpio = PCM512x_G1OE << (4 - 1); - ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_EN, - gpio, gpio); - if (ret != 0) { - dev_err(codec->dev, "Failed to enable gpio %d: %d\n", - 4, ret); - return ret; - } - - gpio = PCM512x_GPIO_OUTPUT_1 + 4 - 1; - ret = regmap_update_bits(pcm512x->regmap, gpio, - PCM512x_GxSL, PCM512x_GxSL_PLLLK); - if (ret != 0) { - dev_err(codec->dev, - "Failed to output pll lock on %d: %d\n", - ret, 4); - return ret; - } } ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE, -- cgit v0.10.2 From 8616774968f3baf0c49fc6bcca9550cac49034ee Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Sat, 11 Apr 2015 00:18:37 +0200 Subject: ASoC: rnsd: fix build regression without CONFIG_OF The r-car sound driver only works when CONFIG_OF is set, and after a recent change has a compile-time dependency as well: sound/built-in.o: In function `rsnd_dma_request_channel': :(.text+0x9fb84): undefined reference to `of_dma_request_slave_channel' This could be fixed either by adding a static inline wrapper for the function, or by adding a Kconfig dependency. This implements the second approach, which seems appropriate because the driver in fact has a hard dependency. Signed-off-by: Arnd Bergmann Fixes: 72adc61f4637aa3 ("ASoC: rsnd: 1st DMAC dma-names cares subnode") Signed-off-by: Mark Brown diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index 2b30304..07114b0 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -36,6 +36,7 @@ config SND_SOC_SH4_SIU config SND_SOC_RCAR tristate "R-Car series SRU/SCU/SSIU/SSI support" + depends on DMA_OF select SND_SIMPLE_CARD select REGMAP_MMIO help -- cgit v0.10.2 From 7ca92b8f5ad80e0d97f093dad81a4e4143c8edeb Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 11:16:18 +0200 Subject: ASoC: atmel: Add dependency to SND_SOC_I2C_AND_SPI where necessary The SND_AT91_SOC_SAM9G20_WM8731 and SND_AT91_SOC_SAM9X5_WM8731 machine driver symbols select SND_SOC_WM8731 which depends on SND_SOC_I2C_AND_SPI. So the machine driver symbols need to depend on SND_SOC_I2C_AND_SPI as well, otherwise we might end up with a invalid configuration, which will sooner or later upset the randconfig auto-builders. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 1579e99..2f185e5 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -25,7 +25,7 @@ config SND_ATMEL_SOC_SSC config SND_AT91_SOC_SAM9G20_WM8731 tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board" - depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC + depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && SND_SOC_I2C_AND_SPI select SND_ATMEL_SOC_PDC select SND_ATMEL_SOC_SSC select SND_SOC_WM8731 @@ -45,7 +45,7 @@ config SND_ATMEL_SOC_WM8904 config SND_AT91_SOC_SAM9X5_WM8731 tristate "SoC Audio support for WM8731-based at91sam9x5 board" - depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC + depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && SND_SOC_I2C_AND_SPI select SND_ATMEL_SOC_SSC select SND_ATMEL_SOC_DMA select SND_SOC_WM8731 -- cgit v0.10.2 From ada602b30e070e786caa6e14a2e17100c6bd6998 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 11:16:19 +0200 Subject: ASoC: atmel: Improve machine driver compile test coverage The Atmel ASoC machine drivers don't have any compile time arch dependencies anymore. Make it possible to select them when COMPILE_TEST is enabled to get better compile test coverage. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 2f185e5..e7d0880 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -25,7 +25,8 @@ config SND_ATMEL_SOC_SSC config SND_AT91_SOC_SAM9G20_WM8731 tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board" - depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && SND_SOC_I2C_AND_SPI + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_ATMEL_SOC && SND_SOC_I2C_AND_SPI select SND_ATMEL_SOC_PDC select SND_ATMEL_SOC_SSC select SND_SOC_WM8731 @@ -35,7 +36,8 @@ config SND_AT91_SOC_SAM9G20_WM8731 config SND_ATMEL_SOC_WM8904 tristate "Atmel ASoC driver for boards using WM8904 codec" - depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && I2C + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_ATMEL_SOC && I2C select SND_ATMEL_SOC_SSC select SND_ATMEL_SOC_DMA select SND_SOC_WM8904 @@ -45,7 +47,8 @@ config SND_ATMEL_SOC_WM8904 config SND_AT91_SOC_SAM9X5_WM8731 tristate "SoC Audio support for WM8731-based at91sam9x5 board" - depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && SND_SOC_I2C_AND_SPI + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_ATMEL_SOC && SND_SOC_I2C_AND_SPI select SND_ATMEL_SOC_SSC select SND_ATMEL_SOC_DMA select SND_SOC_WM8731 -- cgit v0.10.2 From 02f51640fedb61ab17fcddbd6fdb40239a4f46d9 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 12:52:01 +0200 Subject: ASoC: wm1133-ev1: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c index 0653aa8..b454972 100644 --- a/sound/soc/fsl/wm1133-ev1.c +++ b/sound/soc/fsl/wm1133-ev1.c @@ -202,7 +202,6 @@ static struct snd_soc_jack_pin mic_jack_pins[] = { static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; /* Headphone jack detection */ snd_soc_card_jack_new(rtd->card, "Headphone", SND_JACK_HEADPHONE, @@ -216,7 +215,7 @@ static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd) wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE, SND_JACK_BTN_0); - snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); return 0; } -- cgit v0.10.2 From 5cf57f0f6b25d046e6ea219d99681077edca5d7c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 13:01:02 +0200 Subject: ASoC: mop500_ab8500: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c index aa65370..b81a7a4c 100644 --- a/sound/soc/ux500/mop500_ab8500.c +++ b/sound/soc/ux500/mop500_ab8500.c @@ -362,7 +362,7 @@ struct snd_soc_ops mop500_ab8500_ops[] = { int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; struct device *dev = rtd->card->dev; struct mop500_ab8500_drvdata *drvdata; int ret; @@ -407,23 +407,23 @@ int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) return ret; } - ret = snd_soc_dapm_disable_pin(&codec->dapm, "Earpiece"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Left"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Right"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Left"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Right"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 1"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 2"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 1"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 2"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Left"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Right"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 1"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 2"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 3"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 4"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 5"); - ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 6"); + ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); + ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); return ret; } -- cgit v0.10.2 From b213b44a96ed1f868f68b094dbcf8fc9622984ef Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 13:11:28 +0200 Subject: ASoC: davinci-evm: Use card DAPM context to access widgets The dapm field of the snd_soc_codec struct will eventually be removed (replaced with the DAPM context from the component embedded inside the CODEC). Replace its usage with the card's DAPM context. The idea is that DAPM is hierarchical and with the card at the root it is possible to access widgets from other contexts through the card context. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index b6bb594..1f314a8 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -117,7 +117,6 @@ static const struct snd_soc_dapm_route audio_map[] = { static int evm_aic3x_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; - struct snd_soc_codec *codec = rtd->codec; struct device_node *np = card->dev->of_node; int ret; @@ -136,9 +135,9 @@ static int evm_aic3x_init(struct snd_soc_pcm_runtime *rtd) } /* not connected */ - snd_soc_dapm_nc_pin(&codec->dapm, "MONO_LOUT"); - snd_soc_dapm_nc_pin(&codec->dapm, "HPLCOM"); - snd_soc_dapm_nc_pin(&codec->dapm, "HPRCOM"); + snd_soc_dapm_nc_pin(&card->dapm, "MONO_LOUT"); + snd_soc_dapm_nc_pin(&card->dapm, "HPLCOM"); + snd_soc_dapm_nc_pin(&card->dapm, "HPRCOM"); return 0; } -- cgit v0.10.2 From d4bdaced1a81ca2953557f8ecae842b42879fda4 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 10:47:57 +0200 Subject: ASoC: n810: Consistently pass the card DAPM context to n810_ext_control() Some callers of n810_ext_control() pass the card DAPM context and some pass the CODEC DAPM context. Given that some of the widgets that are accessed in the function are in the card's context, always passing it is the obvious choice. Signed-off-by: Lars-Peter Clausen Acked-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 5d7f9ce..617eae3 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -98,12 +98,11 @@ static int n810_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); - n810_ext_control(&codec->dapm); + n810_ext_control(&rtd->card->dapm); return clk_prepare_enable(sys_clkout2); } -- cgit v0.10.2 From 59c41d15e63063545b1f91f7ad5411c25f6d046d Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Apr 2015 10:47:58 +0200 Subject: ASoC: n810: Automatically disconnect non-connected pins All CODEC input and output widgets are either in the DAPM routing table or manually marked as non-connected. This means the card is fully routed and we can let the core take care of disconnecting non-connected pins. Signed-off-by: Lars-Peter Clausen Acked-by: Jarkko Nikula Signed-off-by: Mark Brown diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 617eae3..dcb5336 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -254,24 +254,6 @@ static const struct snd_kcontrol_new aic33_n810_controls[] = { n810_get_input, n810_set_input), }; -static int n810_aic33_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_codec *codec = rtd->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; - - /* Not connected */ - snd_soc_dapm_nc_pin(dapm, "MONO_LOUT"); - snd_soc_dapm_nc_pin(dapm, "HPLCOM"); - snd_soc_dapm_nc_pin(dapm, "HPRCOM"); - snd_soc_dapm_nc_pin(dapm, "MIC3L"); - snd_soc_dapm_nc_pin(dapm, "MIC3R"); - snd_soc_dapm_nc_pin(dapm, "LINE1R"); - snd_soc_dapm_nc_pin(dapm, "LINE2L"); - snd_soc_dapm_nc_pin(dapm, "LINE2R"); - - return 0; -} - /* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link n810_dai = { .name = "TLV320AIC33", @@ -282,7 +264,6 @@ static struct snd_soc_dai_link n810_dai = { .codec_dai_name = "tlv320aic3x-hifi", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM, - .init = n810_aic33_init, .ops = &n810_ops, }; @@ -299,6 +280,7 @@ static struct snd_soc_card snd_soc_n810 = { .num_dapm_widgets = ARRAY_SIZE(aic33_dapm_widgets), .dapm_routes = audio_map, .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, }; static struct platform_device *n810_snd_device; -- cgit v0.10.2 From a5e5e12bd4ed5cd1123ace4300b5c07230fbf21e Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Mon, 13 Apr 2015 02:16:21 +0800 Subject: ASoC: Intel: fix array_size.cocci warnings sound/soc/intel/haswell/sst-haswell-ipc.c:646:28-29: WARNING: Use ARRAY_SIZE Use ARRAY_SIZE instead of dividing sizeof array with sizeof an element Semantic patch information: This makes an effort to find cases where ARRAY_SIZE can be used such as where there is a division of sizeof the array by the sizeof its first element or by any indexed element or the element type. It replaces the division of the two sizeofs by ARRAY_SIZE. Generated by: scripts/coccinelle/misc/array_size.cocci CC: Jie Yang Signed-off-by: Fengguang Wu Signed-off-by: Mark Brown diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c index d75f09e..344a1e9 100644 --- a/sound/soc/intel/haswell/sst-haswell-ipc.c +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -466,7 +466,7 @@ static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) /* log the FW version info got from the mailbox here. */ memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); pinfo = &fw_info[0]; - for (i = 0; i < sizeof(tmp) / sizeof(char *); i++) + for (i = 0; i < ARRAY_SIZE(tmp); i++) tmp[i] = strsep(&pinfo, " "); dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " "version: %s.%s, build %s, source commit id: %s\n", -- cgit v0.10.2