[alsa-devel] [PATCH v11 0/7] ASoC: fsl-ssi: ac97-slave support
Hi,
this series implements ac97-slave support for fsl-ssi and adds a simple generic DT sound card.
Regards,
Markus
Changes in v11: - Dropped phycard specific sound patches - Added a generic simple DT sound card with support for ac97 using ac97-codec - Some new bugfix patches
Markus Pargmann (5): ASoC: imx-pcm-dma: Add missing include ASoC: core: Generic ac97 link reset functions ASoC: generic: simple DT sound card ASoC: fsl: Kconfig, visible config items for simple card ASoC: fsl-ssi: ac97-slave support
Michael Grzeschik (2): ASoC: fsl-ssi: imx-pcm-fiq bugfix ASoC: fsl: disable ssi irq for imx
Steffen Trumtrar (1): ASoC: fsl-ssi: add SSIEN errata work around
.../devicetree/bindings/sound/fsl,ssi.txt | 4 + .../bindings/sound/generic,simple-dt-card.txt | 28 ++ .../devicetree/bindings/sound/soc-ac97link.txt | 28 ++ include/sound/soc.h | 2 + sound/soc/fsl/Kconfig | 23 +- sound/soc/fsl/fsl_ssi.c | 369 ++++++++++++++++----- sound/soc/fsl/imx-pcm-dma.c | 1 + sound/soc/generic/Kconfig | 1 + sound/soc/generic/Makefile | 3 +- sound/soc/generic/simple-dt-card.c | 186 +++++++++++ sound/soc/soc-core.c | 153 +++++++++ 11 files changed, 711 insertions(+), 87 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/generic,simple-dt-card.txt create mode 100644 Documentation/devicetree/bindings/sound/soc-ac97link.txt create mode 100644 sound/soc/generic/simple-dt-card.c
Signed-off-by: Markus Pargmann mpa@pengutronix.de --- sound/soc/fsl/imx-pcm-dma.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c index aa6de5c..4dc1296 100644 --- a/sound/soc/fsl/imx-pcm-dma.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -14,6 +14,7 @@ #include <linux/platform_device.h> #include <linux/dmaengine.h> #include <linux/types.h> +#include <linux/module.h>
#include <sound/core.h> #include <sound/pcm.h>
On Mon, Aug 19, 2013 at 05:05:54PM +0200, Markus Pargmann wrote:
Signed-off-by: Markus Pargmann mpa@pengutronix.de
This is there in the current code.
This patch adds generic ac97 reset functions using pincontrol and gpio parsed from devicetree.
Signed-off-by: Markus Pargmann mpa@pengutronix.de --- .../devicetree/bindings/sound/soc-ac97link.txt | 28 ++++ include/sound/soc.h | 2 + sound/soc/soc-core.c | 153 +++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/soc-ac97link.txt
diff --git a/Documentation/devicetree/bindings/sound/soc-ac97link.txt b/Documentation/devicetree/bindings/sound/soc-ac97link.txt new file mode 100644 index 0000000..80152a8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/soc-ac97link.txt @@ -0,0 +1,28 @@ +AC97 link bindings + +These bindings can be included within any other device node. + +Required properties: + - pinctrl-names: Has to contain following states to setup the correct + pinmuxing for the used gpios: + "ac97-running": AC97-link is active + "ac97-reset": AC97-link reset state + "ac97-warm-reset": AC97-link warm reset state + - ac97-gpios: List of gpio phandles with args in the order ac97-sync, + ac97-sdata, ac97-reset + + +Example: + +ssi { + ... + + pinctrl-names = "default", "ac97-running", "ac97-reset", "ac97-warm-reset"; + pinctrl-0 = <&ac97link_running>; + pinctrl-1 = <&ac97link_running>; + pinctrl-2 = <&ac97link_reset>; + pinctrl-3 = <&ac97link_warm_reset>; + ac97-gpios = <&gpio3 20 0 &gpio3 22 0 &gpio3 28 0>; + + ... +}; diff --git a/include/sound/soc.h b/include/sound/soc.h index 6eabee7..c0ac3bc 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -468,6 +468,8 @@ int snd_soc_new_ac97_codec(struct snd_soc_codec *codec, void snd_soc_free_ac97_codec(struct snd_soc_codec *codec);
int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops); +int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops, + struct platform_device *pdev);
/* *Controls diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 0ec070c..6010932 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -30,9 +30,12 @@ #include <linux/bitops.h> #include <linux/debugfs.h> #include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> #include <linux/ctype.h> #include <linux/slab.h> #include <linux/of.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> #include <sound/ac97_codec.h> #include <sound/core.h> #include <sound/jack.h> @@ -69,6 +72,16 @@ static int pmdown_time = 5000; module_param(pmdown_time, int, 0); MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+struct snd_ac97_reset_cfg { + struct pinctrl *pctl; + struct pinctrl_state *pstate_reset; + struct pinctrl_state *pstate_warm_reset; + struct pinctrl_state *pstate_run; + int gpio_sdata; + int gpio_sync; + int gpio_reset; +}; + /* returns the minimum number of bytes needed to represent * a particular given value */ static int min_bytes_needed(unsigned long val) @@ -2080,6 +2093,117 @@ int snd_soc_new_ac97_codec(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec);
+static struct snd_ac97_reset_cfg snd_ac97_rst_cfg; + +static void snd_soc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_warm_reset); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 1); + + udelay(10); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); + msleep(2); +} + +static void snd_soc_ac97_reset(struct snd_ac97 *ac97) +{ + struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_reset); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); + gpio_direction_output(snd_ac97_rst_cfg.gpio_sdata, 0); + gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 0); + + udelay(10); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 1); + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); + msleep(2); +} + +static int snd_soc_ac97_parse_pinctl(struct device *dev, + struct snd_ac97_reset_cfg *cfg) +{ + struct pinctrl *p; + struct pinctrl_state *state; + int gpio; + int ret; + + p = devm_pinctrl_get(dev); + if (IS_ERR(p)) { + dev_err(dev, "Failed to get pinctrl\n"); + return PTR_RET(p); + } + cfg->pctl = p; + + state = pinctrl_lookup_state(p, "ac97-reset"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-reset\n"); + return PTR_RET(state); + } + cfg->pstate_reset = state; + + state = pinctrl_lookup_state(p, "ac97-warm-reset"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-warm-reset\n"); + return PTR_RET(state); + } + cfg->pstate_warm_reset = state; + + state = pinctrl_lookup_state(p, "ac97-running"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-running\n"); + return PTR_RET(state); + } + cfg->pstate_run = state; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 0); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-sync gpio\n"); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link sync"); + if (ret) { + dev_err(dev, "Failed requesting ac97-sync gpio\n"); + return ret; + } + cfg->gpio_sync = gpio; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 1); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-sdata gpio %d\n", gpio); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link sdata"); + if (ret) { + dev_err(dev, "Failed requesting ac97-sdata gpio\n"); + return ret; + } + cfg->gpio_sdata = gpio; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 2); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-reset gpio\n"); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link reset"); + if (ret) { + dev_err(dev, "Failed requesting ac97-reset gpio\n"); + return ret; + } + cfg->gpio_reset = gpio; + + return 0; +} + struct snd_ac97_bus_ops *soc_ac97_ops; EXPORT_SYMBOL_GPL(soc_ac97_ops);
@@ -2098,6 +2222,35 @@ int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops);
/** + * snd_soc_set_ac97_ops_of_reset - Set ac97 ops with generic ac97 reset functions + * + * This function sets the reset and warm_reset properties of ops and parses + * the device node of pdev to get pinctrl states and gpio numbers to use. + */ +int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_ac97_reset_cfg cfg; + int ret; + + ret = snd_soc_ac97_parse_pinctl(dev, &cfg); + if (ret) + return ret; + + ret = snd_soc_set_ac97_ops(ops); + if (ret) + return ret; + + ops->warm_reset = snd_soc_ac97_warm_reset; + ops->reset = snd_soc_ac97_reset; + + snd_ac97_rst_cfg = cfg; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops_of_reset); + +/** * snd_soc_free_ac97_codec - free AC97 codec device * @codec: audio codec *
On Mon, Aug 19, 2013 at 05:05:55PM +0200, Markus Pargmann wrote:
This patch adds generic ac97 reset functions using pincontrol and gpio parsed from devicetree.
Applied, thanks.
+Required properties:
- pinctrl-names: Has to contain following states to setup the correct
- pinmuxing for the used gpios:
- "ac97-running": AC97-link is active
- "ac97-reset": AC97-link reset state
- "ac97-warm-reset": AC97-link warm reset state
- ac97-gpios: List of gpio phandles with args in the order ac97-sync,
- ac97-sdata, ac97-reset
This should really be reviewed on the DT list but never mind.
- gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0);
These ought to be _cansleep() since there's no need for them to run in atomic context but that can be fixed up later.
Simple sound card initialized using DT. When used with AC97, ac97-codec is used to automatically discover the used codec.
Signed-off-by: Markus Pargmann mpa@pengutronix.de --- .../bindings/sound/generic,simple-dt-card.txt | 28 ++++ sound/soc/generic/Kconfig | 1 + sound/soc/generic/Makefile | 3 +- sound/soc/generic/simple-dt-card.c | 186 +++++++++++++++++++++ 4 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/sound/generic,simple-dt-card.txt create mode 100644 sound/soc/generic/simple-dt-card.c
diff --git a/Documentation/devicetree/bindings/sound/generic,simple-dt-card.txt b/Documentation/devicetree/bindings/sound/generic,simple-dt-card.txt new file mode 100644 index 0000000..189d754 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/generic,simple-dt-card.txt @@ -0,0 +1,28 @@ +Simple DT card + +A simple generic driver that connects a CPU DAI with a CODEC. + +Required properties: + - compatible: "asoc-simple-dt-card" used for standard ssi, codec + combinations, or "asoc-simple-ac97-dt-card" to use ac97 to discover the + codec. + - cpu-dai: CPU DAI connected to the codec. + +Required properties for "asoc-simple-dt-card": + - audio-codec: Codec phandle. + - codec-dai-name: DAI name within the codec. + +Examples: + +sound { + compatible = "asoc-simple-dt-card"; + cpu-dai = <&ssi1>; + audio-codec = <&wm9712>; +}; + +For AC97: + +sound { + compatible = "asoc-simple-ac97-dt-card"; + cpu-dai = <&ssi1>; +}; diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index 610f612..8718ac6 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -1,4 +1,5 @@ config SND_SIMPLE_CARD tristate "ASoC Simple sound card support" + select SND_SOC_AC97_BUS help This option enables generic simple sound card support diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index 9c3b246..9ccf864 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -1,3 +1,4 @@ snd-soc-simple-card-objs := simple-card.o +snd-soc-simple-dt-card-objs := simple-dt-card.o
-obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o +obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o snd-soc-simple-dt-card.o diff --git a/sound/soc/generic/simple-dt-card.c b/sound/soc/generic/simple-dt-card.c new file mode 100644 index 0000000..532be0f --- /dev/null +++ b/sound/soc/generic/simple-dt-card.c @@ -0,0 +1,186 @@ +/* + * phycore-ac97-dt.c -- SoC audio for imx_phycore in AC97 mode + * + * Copyright 2013 Markus Pargmann, Pengutronix mpa@pengutronix.de + * + * 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. + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +struct simple_dt_card_data { + struct snd_soc_dai_link link; + struct snd_soc_card card; + struct device_node *cpu_np; + struct device_node *codec_np; + struct platform_device *ac97_codec; +}; + +static struct snd_soc_dai_link simple_ac97_dt_card_dai = { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "ac97-hifi", + .codec_name = "ac97-codec", +}; + +static const struct snd_soc_card simple_ac97_dt_card = { + .name = "ASoC simple AC97 DT card", + .owner = THIS_MODULE, +}; + +static struct snd_soc_dai_link simple_dt_card_dai = { + .name = "simple-link", + .stream_name = "simple-link", +}; + +static const struct snd_soc_card simple_dt_card = { + .name = "ASoC simple DT card", + .owner = THIS_MODULE, +}; + +enum simple_dt_card_type { + SIMPLE_CARD, + SIMPLE_CARD_AC97, +}; + +static const struct of_device_id simple_dt_card_of_dev_ids[] = { + { + .compatible = "asoc-simple-dt-card", + .data = (void *)SIMPLE_CARD, + }, { + .compatible = "asoc-simple-ac97-dt-card", + .data = (void *)SIMPLE_CARD_AC97, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, simple_dt_card_of_dev_id); + +static int simple_dt_card_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(simple_dt_card_of_dev_ids, &pdev->dev); + enum simple_dt_card_type type = (enum simple_dt_card_type)of_id->data; + int ret; + struct simple_dt_card_data *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + + if (!priv) + return -ENOMEM; + + priv->card.dev = &pdev->dev; + + priv->cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!priv->cpu_np) { + dev_err(&pdev->dev, "No valid CPU DAI phandle found\n"); + return -EINVAL; + } + + if (type == SIMPLE_CARD_AC97) { + struct platform_device *ac97_codec; + memcpy(&priv->card, &simple_ac97_dt_card, + sizeof(simple_ac97_dt_card)); + memcpy(&priv->link, &simple_ac97_dt_card_dai, + sizeof(simple_ac97_dt_card_dai)); + + ac97_codec = platform_device_register_simple("ac97-codec", -1, + NULL, 0); + if (IS_ERR(ac97_codec)) { + dev_err(&pdev->dev, "Failed to register ac97-codec device\n"); + ret = PTR_ERR(priv->ac97_codec); + goto err; + } + priv->ac97_codec = ac97_codec; + } else { + priv->codec_np = of_parse_phandle(pdev->dev.of_node, + "audio-codec", 0); + if (!priv->codec_np) { + dev_err(&pdev->dev, "No valid codec phandle found\n"); + ret = -EINVAL; + goto err; + } + + memcpy(&priv->link, &simple_dt_card_dai, + sizeof(simple_dt_card_dai)); + memcpy(&priv->card, &simple_dt_card, sizeof(simple_dt_card)); + priv->link.codec_of_node = priv->codec_np; + + ret = of_property_read_string(pdev->dev.of_node, + "codec-dai-name", &priv->link.stream_name); + if (ret) { + dev_err(&pdev->dev, "Failed to parse codec-dai-name string\n"); + ret = -EINVAL; + goto err; + } + } + + priv->link.cpu_of_node = priv->cpu_np; + priv->link.platform_of_node = priv->cpu_np; + + priv->card.dai_link = &priv->link; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + + ret = snd_soc_register_card(&priv->card); + if (ret) { + dev_err(&pdev->dev, "ASoC: soc card registration failed\n"); + goto err; + } + + dev_set_drvdata(&pdev->dev, priv); + + return 0; + +err: + if (priv->cpu_np) + of_node_put(priv->cpu_np); + if (priv->codec_np) + of_node_put(priv->codec_np); + if (priv->ac97_codec) + platform_device_unregister(priv->ac97_codec); + return ret; +} + +static int simple_dt_card_remove(struct platform_device *pdev) +{ + struct simple_dt_card_data *priv = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_card(&priv->card); + + if (priv->cpu_np) + of_node_put(priv->cpu_np); + if (priv->codec_np) + of_node_put(priv->codec_np); + if (priv->ac97_codec) + platform_device_unregister(priv->ac97_codec); + + return 0; +} + +static struct platform_driver simple_dt_card_driver = { + .probe = simple_dt_card_probe, + .remove = simple_dt_card_remove, + .driver = { + .name = "asoc-simple-dt-card", + .owner = THIS_MODULE, + .of_match_table = simple_dt_card_of_dev_ids, + }, +}; + +module_platform_driver(simple_dt_card_driver); + +MODULE_AUTHOR("Markus Pargmann mpa@pengutronix.de"); +MODULE_DESCRIPTION("ASoC Simple DT Sound Card"); +MODULE_LICENSE("GPL");
On Mon, Aug 19, 2013 at 05:05:56PM +0200, Markus Pargmann wrote:
Simple sound card initialized using DT. When used with AC97, ac97-codec is used to automatically discover the used codec.
I've held back a bit on this one. The reason is that it's adding a separate card to the generic card which doesn't seem like the obvious thing to do - obviously they have very similar goals, it's just that one is for DT usage and the other isn't. What's the reason for not adding DT support to the generic card?
Hi,
On Fri, Aug 23, 2013 at 10:58:16AM +0100, Mark Brown wrote:
On Mon, Aug 19, 2013 at 05:05:56PM +0200, Markus Pargmann wrote:
Simple sound card initialized using DT. When used with AC97, ac97-codec is used to automatically discover the used codec.
I've held back a bit on this one. The reason is that it's adding a separate card to the generic card which doesn't seem like the obvious thing to do - obviously they have very similar goals, it's just that one is for DT usage and the other isn't. What's the reason for not adding DT support to the generic card?
There was no code I could reuse from the existing simple card driver, so I decided to seperate the DT part.
I will move the DT driver code into simple card and resend the patch.
Thanks,
Markus
The simple sound card does not automatically select the used chipdrivers. This patch makes them user-selectable so simple sound cards can be used without a problem.
Signed-off-by: Markus Pargmann mpa@pengutronix.de --- sound/soc/fsl/Kconfig | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3a4808d..247e340 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,5 +1,11 @@ config SND_SOC_FSL_SSI - tristate + tristate "Freescale SSI unit" + help + Say Y here if you want tou use a Freescale SSI unit. This option is + automatically selected if your board has a seperated sound card + driver. If you are using one of the generic drivers, please select + this manually. +
config SND_SOC_FSL_UTILS tristate @@ -109,15 +115,24 @@ config SND_SOC_IMX_SSI tristate
config SND_SOC_IMX_PCM_FIQ - tristate + tristate "i.MX PCM FIQ" select FIQ + help + Say Y here if you are using a generic soundcard and you have a chip + that with this audio muxer. Otherwise select N.
config SND_SOC_IMX_PCM_DMA - tristate + tristate "i.MX PCM DMA" select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y here if you are using a generic soundcard and you have a DMA + capable SSI unit. Otherwise select N.
config SND_SOC_IMX_AUDMUX - tristate + tristate "i.MX Audio muxer" + help + Say Y here if you are using a generic soundcard and you have chip + with this audio muxer. Otherwise select N.
config SND_MXC_SOC_WM1133_EV1 tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted"
This patch adds ac97-slave support.
For ac97, the registers have to be setup earlier than for other ssi modes because there is some communication with the external device before streaming. So this patch introduces a fsl_ssi_setup function to setup the registers for different ssi operation modes seperately.
This patch was tested with imx27-pca100.
Signed-off-by: Markus Pargmann mpa@pengutronix.de Tested-by: Shawn Guo shawn.guo@linaro.org --- .../devicetree/bindings/sound/fsl,ssi.txt | 4 + sound/soc/fsl/fsl_ssi.c | 346 ++++++++++++++++----- 2 files changed, 280 insertions(+), 70 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/fsl,ssi.txt b/Documentation/devicetree/bindings/sound/fsl,ssi.txt index 088a2c0..4303b6a 100644 --- a/Documentation/devicetree/bindings/sound/fsl,ssi.txt +++ b/Documentation/devicetree/bindings/sound/fsl,ssi.txt @@ -43,6 +43,10 @@ Required properties: together. This would still allow different sample sizes, but not different sample rates.
+Required are also ac97 link bindings if ac97 is used. See +Documentation/devicetree/bindings/sound/soc-ac97link.txt for the necessary +bindings. + Optional properties: - codec-handle: Phandle to a 'codec' node that defines an audio codec connected to this SSI. This node is typically diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 0c072ff..827af75 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -141,6 +141,7 @@ struct fsl_ssi_private {
bool new_binding; bool ssi_on_imx; + bool imx_ac97; bool use_dma; struct clk *clk; struct snd_dmaengine_dai_dma_data dma_params_tx; @@ -320,6 +321,124 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) return ret; }
+static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private) +{ + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + u8 i2s_mode; + u8 wm; + int synchronous = ssi_private->cpu_dai_drv.symmetric_rates; + + if (ssi_private->imx_ac97) + i2s_mode = CCSR_SSI_SCR_I2S_MODE_NORMAL | CCSR_SSI_SCR_NET; + else + i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE; + + /* + * Section 16.5 of the MPC8610 reference manual says that the SSI needs + * to be disabled before updating the registers we set here. + */ + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0); + + /* + * Program the SSI into I2S Slave Non-Network Synchronous mode. Also + * enable the transmit and receive FIFO. + * + * FIXME: Little-endian samples require a different shift dir + */ + write_ssi_mask(&ssi->scr, + CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN, + CCSR_SSI_SCR_TFR_CLK_DIS | + i2s_mode | + (synchronous ? CCSR_SSI_SCR_SYN : 0)); + + write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | + CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS | + CCSR_SSI_STCR_TSCKP, &ssi->stcr); + + write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 | + CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS | + CCSR_SSI_SRCR_RSCKP, &ssi->srcr); + /* + * The DC and PM bits are only used if the SSI is the clock master. + */ + + /* + * Set the watermark for transmit FIFI 0 and receive FIFO 0. We don't + * use FIFO 1. We program the transmit water to signal a DMA transfer + * if there are only two (or fewer) elements left in the FIFO. Two + * elements equals one frame (left channel, right channel). This value, + * however, depends on the depth of the transmit buffer. + * + * We set the watermark on the same level as the DMA burstsize. For + * fiq it is probably better to use the biggest possible watermark + * size. + */ + if (ssi_private->use_dma) + wm = ssi_private->fifo_depth - 2; + else + wm = ssi_private->fifo_depth; + + write_ssi(CCSR_SSI_SFCSR_TFWM0(wm) | CCSR_SSI_SFCSR_RFWM0(wm) | + CCSR_SSI_SFCSR_TFWM1(wm) | CCSR_SSI_SFCSR_RFWM1(wm), + &ssi->sfcsr); + + /* + * For non-ac97 setups, we keep the SSI disabled because if we enable + * it, then the DMA controller will start. It's not supposed to start + * until the SCR.TE (or SCR.RE) bit is set, but it does anyway. The DMA + * controller will transfer one "BWC" of data (i.e. the amount of data + * that the MR.BWC bits are set to). The reason this is bad is because + * at this point, the PCM driver has not finished initializing the DMA + * controller. + */ + + + /* + * For ac97 interrupts are enabled with the startup of the substream + * because it is also running without an active substream. Normally SSI + * is only enabled when there is a substream. + */ + if (!ssi_private->imx_ac97) { + /* Enable the interrupts and DMA requests */ + if (ssi_private->use_dma) + write_ssi(SIER_FLAGS, &ssi->sier); + else + write_ssi(CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TFE0_EN | + CCSR_SSI_SIER_RIE | + CCSR_SSI_SIER_RFF0_EN, &ssi->sier); + } else { + /* + * Setup the clock control register + */ + write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13), + &ssi->stccr); + write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13), + &ssi->srccr); + + /* + * Enable AC97 mode and startup the SSI + */ + write_ssi(CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV, + &ssi->sacnt); + write_ssi(0xff, &ssi->saccdis); + write_ssi(0x300, &ssi->saccen); + + /* + * Enable SSI + */ + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN); + write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor); + + /* + * Enable Transmit and Receive + */ + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE); + } + + return 0; +} + + /** * fsl_ssi_startup: create a new substream * @@ -341,75 +460,14 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * and initialize the SSI registers. */ if (!ssi_private->first_stream) { - struct ccsr_ssi __iomem *ssi = ssi_private->ssi; - ssi_private->first_stream = substream;
/* - * Section 16.5 of the MPC8610 reference manual says that the - * SSI needs to be disabled before updating the registers we set - * here. - */ - write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0); - - /* - * Program the SSI into I2S Slave Non-Network Synchronous mode. - * Also enable the transmit and receive FIFO. - * - * FIXME: Little-endian samples require a different shift dir - */ - write_ssi_mask(&ssi->scr, - CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN, - CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE - | (synchronous ? CCSR_SSI_SCR_SYN : 0)); - - write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | - CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS | - CCSR_SSI_STCR_TSCKP, &ssi->stcr); - - write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 | - CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS | - CCSR_SSI_SRCR_RSCKP, &ssi->srcr); - - /* - * The DC and PM bits are only used if the SSI is the clock - * master. - */ - - /* Enable the interrupts and DMA requests */ - if (ssi_private->use_dma) - write_ssi(SIER_FLAGS, &ssi->sier); - else - write_ssi(CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TFE0_EN | - CCSR_SSI_SIER_RIE | - CCSR_SSI_SIER_RFF0_EN, &ssi->sier); - - /* - * Set the watermark for transmit FIFI 0 and receive FIFO 0. We - * don't use FIFO 1. We program the transmit water to signal a - * DMA transfer if there are only two (or fewer) elements left - * in the FIFO. Two elements equals one frame (left channel, - * right channel). This value, however, depends on the depth of - * the transmit buffer. - * - * We program the receive FIFO to notify us if at least two - * elements (one frame) have been written to the FIFO. We could - * make this value larger (and maybe we should), but this way - * data will be written to memory as soon as it's available. - */ - write_ssi(CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) | - CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2), - &ssi->sfcsr); - - /* - * We keep the SSI disabled because if we enable it, then the - * DMA controller will start. It's not supposed to start until - * the SCR.TE (or SCR.RE) bit is set, but it does anyway. The - * DMA controller will transfer one "BWC" of data (i.e. the - * amount of data that the MR.BWC bits are set to). The reason - * this is bad is because at this point, the PCM driver has not - * finished initializing the DMA controller. + * fsl_ssi_setup was already called by ac97_init earlier if + * the driver is in ac97 mode. */ + if (!ssi_private->imx_ac97) + fsl_ssi_setup(ssi_private); } else { if (synchronous) { struct snd_pcm_runtime *first_runtime = @@ -538,7 +596,8 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, else write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
- if ((read_ssi(&ssi->scr) & (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0) + if (!ssi_private->imx_ac97 && (read_ssi(&ssi->scr) & + (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0) write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0); break;
@@ -608,6 +667,133 @@ static const struct snd_soc_component_driver fsl_ssi_component = { .name = "fsl-ssi", };
+/** + * fsl_ssi_ac97_trigger: start and stop the AC97 receive/transmit. + * + * This function is called by ALSA to start, stop, pause, and resume the + * transfer of data. + */ +static int fsl_ssi_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata( + rtd->cpu_dai); + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + write_ssi_mask(&ssi->sier, 0, CCSR_SSI_SIER_TIE | + CCSR_SSI_SIER_TFE0_EN); + else + write_ssi_mask(&ssi->sier, 0, CCSR_SSI_SIER_RIE | + CCSR_SSI_SIER_RFF0_EN); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + write_ssi_mask(&ssi->sier, CCSR_SSI_SIER_TIE | + CCSR_SSI_SIER_TFE0_EN, 0); + else + write_ssi_mask(&ssi->sier, CCSR_SSI_SIER_RIE | + CCSR_SSI_SIER_RFF0_EN, 0); + break; + + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + write_ssi(CCSR_SSI_SOR_TX_CLR, &ssi->sor); + else + write_ssi(CCSR_SSI_SOR_RX_CLR, &ssi->sor); + + return 0; +} + +static const struct snd_soc_dai_ops fsl_ssi_ac97_dai_ops = { + .startup = fsl_ssi_startup, + .shutdown = fsl_ssi_shutdown, + .trigger = fsl_ssi_ac97_trigger, +}; + +static struct snd_soc_dai_driver fsl_ssi_ac97_dai = { + .ac97_control = 1, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &fsl_ssi_ac97_dai_ops, +}; + + +static struct fsl_ssi_private *fsl_ac97_data; + +static void fsl_ssi_ac97_init(void) +{ + fsl_ssi_setup(fsl_ac97_data); +} + +void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct ccsr_ssi *ssi = fsl_ac97_data->ssi; + unsigned int lreg; + unsigned int lval; + + if (reg > 0x7f) + return; + + + lreg = reg << 12; + write_ssi(lreg, &ssi->sacadd); + + lval = val << 4; + write_ssi(lval , &ssi->sacdat); + + write_ssi_mask(&ssi->sacnt, CCSR_SSI_SACNT_RDWR_MASK, + CCSR_SSI_SACNT_WR); + udelay(100); +} + +unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ccsr_ssi *ssi = fsl_ac97_data->ssi; + + unsigned short val = -1; + unsigned int lreg; + + lreg = (reg & 0x7f) << 12; + write_ssi(lreg, &ssi->sacadd); + write_ssi_mask(&ssi->sacnt, CCSR_SSI_SACNT_RDWR_MASK, + CCSR_SSI_SACNT_RD); + + udelay(100); + + val = (read_ssi(&ssi->sacdat) >> 4) & 0xffff; + + return val; +} + +static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = { + .read = fsl_ssi_ac97_read, + .write = fsl_ssi_ac97_write, +}; + /* Show the statistics of a flag only if its interrupt is enabled. The * compiler will optimze this code to a no-op if the interrupt is not * enabled. @@ -684,6 +870,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) struct resource res; char name[64]; bool shared; + bool ac97 = false;
/* SSIs that are not connected on the board should have a * status = "disabled" @@ -694,7 +881,13 @@ static int fsl_ssi_probe(struct platform_device *pdev)
/* We only support the SSI in "I2S Slave" mode */ sprop = of_get_property(np, "fsl,mode", NULL); - if (!sprop || strcmp(sprop, "i2s-slave")) { + if (!sprop) { + dev_err(&pdev->dev, "fsl,mode property is necessary\n"); + return -EINVAL; + } + if (!strcmp(sprop, "ac97-slave")) { + ac97 = true; + } else if (strcmp(sprop, "i2s-slave")) { dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop); return -ENODEV; } @@ -713,9 +906,19 @@ static int fsl_ssi_probe(struct platform_device *pdev) ssi_private->use_dma = !of_property_read_bool(np, "fsl,fiq-stream-filter");
- /* Initialize this copy of the CPU DAI driver structure */ - memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template, - sizeof(fsl_ssi_dai_template)); + if (ac97) { + memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_ac97_dai, + sizeof(fsl_ssi_ac97_dai)); + + fsl_ac97_data = ssi_private; + ssi_private->imx_ac97 = true; + + snd_soc_set_ac97_ops_of_reset(&fsl_ssi_ac97_ops, pdev); + } else { + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template, + sizeof(fsl_ssi_dai_template)); + } ssi_private->cpu_dai_drv.name = ssi_private->name;
/* Get the addresses and IRQ */ @@ -901,6 +1104,9 @@ static int fsl_ssi_probe(struct platform_device *pdev) }
done: + if (ssi_private->imx_ac97) + fsl_ssi_ac97_init(); + return 0;
error_dai:
On Mon, Aug 19, 2013 at 05:05:58PM +0200, Markus Pargmann wrote:
This patch adds ac97-slave support.
For ac97, the registers have to be setup earlier than for other ssi modes because there is some communication with the external device before streaming. So this patch introduces a fsl_ssi_setup function to setup the registers for different ssi operation modes seperately.
Applied, thanks.
From: Steffen Trumtrar s.trumtrar@pengutronix.de
The chip errata for the i.MX35, Rev.2 has the following errata:
ENGcm06222: SSI:Transmission does not take place in bit length early frame sync configuration
The workaround states, that TX_EN and SSI_EN bits should be set in the same register write. As the next errata in the document (ENGcm06532) says to always write RX_EN and TX_EN in the same register write in network mode.
Therefore include the whole write to CCSR_SSI_SCR_TE and CCSR_SSI_SCR_RE into the write to CCSR_SSI_SCR_SSIEN
Signed-off-by: Steffen Trumtrar s.trumtrar@pengutronix.de --- sound/soc/fsl/fsl_ssi.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 827af75..3af15e9 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -424,15 +424,12 @@ static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private) write_ssi(0x300, &ssi->saccen);
/* - * Enable SSI + * Enable SSI, Transmit and Receive */ - write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN); - write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor); + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN | + CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE);
- /* - * Enable Transmit and Receive - */ - write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE); + write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor); }
return 0;
On Mon, Aug 19, 2013 at 05:05:59PM +0200, Markus Pargmann wrote:
From: Steffen Trumtrar s.trumtrar@pengutronix.de
The chip errata for the i.MX35, Rev.2 has the following errata:
Applied, thanks.
From: Michael Grzeschik m.grzeschik@pengutronix.de
imx-pcm-fiq is checking for TE RE bits, so enable them only if necessary.
Signed-off-by: Michael Grzeschik m.grzeschik@pengutronix.de --- sound/soc/fsl/fsl_ssi.c | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 3af15e9..8407d59 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -383,30 +383,11 @@ static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private) &ssi->sfcsr);
/* - * For non-ac97 setups, we keep the SSI disabled because if we enable - * it, then the DMA controller will start. It's not supposed to start - * until the SCR.TE (or SCR.RE) bit is set, but it does anyway. The DMA - * controller will transfer one "BWC" of data (i.e. the amount of data - * that the MR.BWC bits are set to). The reason this is bad is because - * at this point, the PCM driver has not finished initializing the DMA - * controller. - */ - - - /* * For ac97 interrupts are enabled with the startup of the substream * because it is also running without an active substream. Normally SSI * is only enabled when there is a substream. */ - if (!ssi_private->imx_ac97) { - /* Enable the interrupts and DMA requests */ - if (ssi_private->use_dma) - write_ssi(SIER_FLAGS, &ssi->sier); - else - write_ssi(CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TFE0_EN | - CCSR_SSI_SIER_RIE | - CCSR_SSI_SIER_RFF0_EN, &ssi->sier); - } else { + if (ssi_private->imx_ac97) { /* * Setup the clock control register */ @@ -574,6 +555,27 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai); struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + unsigned int sier_bits; + + /* + * Enable only the interrupts and DMA requests + * that are needed for the channel. As the fiq + * is polling for this bits, we have to ensure + * that this are aligned with the preallocated + * buffers + */ + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (ssi_private->use_dma) + sier_bits = SIER_FLAGS; + else + sier_bits = CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TFE0_EN; + } else { + if (ssi_private->use_dma) + sier_bits = SIER_FLAGS; + else + sier_bits = CCSR_SSI_SIER_RIE | CCSR_SSI_SIER_RFF0_EN; + }
switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -602,6 +604,8 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, return -EINVAL; }
+ write_ssi(sier_bits, &ssi->sier); + return 0; }
On Mon, Aug 19, 2013 at 05:06:00PM +0200, Markus Pargmann wrote:
From: Michael Grzeschik m.grzeschik@pengutronix.de
imx-pcm-fiq is checking for TE RE bits, so enable them only if necessary.
Applied, thanks.
From: Michael Grzeschik m.grzeschik@pengutronix.de
We have to disable the ssi irq, as it is not safe for all platforms to write back into the status register. It also runs into non-linefetch aborts.
Signed-off-by: Michael Grzeschik m.grzeschik@pengutronix.de --- sound/soc/fsl/fsl_ssi.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 8407d59..653ad6b 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -941,18 +941,6 @@ static int fsl_ssi_probe(struct platform_device *pdev) return -ENXIO; }
- if (ssi_private->use_dma) { - /* The 'name' should not have any slashes in it. */ - ret = devm_request_irq(&pdev->dev, ssi_private->irq, - fsl_ssi_isr, 0, ssi_private->name, - ssi_private); - if (ret < 0) { - dev_err(&pdev->dev, "could not claim irq %u\n", - ssi_private->irq); - goto error_irqmap; - } - } - /* Are the RX and the TX clocks locked? */ if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) ssi_private->cpu_dai_drv.symmetric_rates = 1; @@ -1020,6 +1008,16 @@ static int fsl_ssi_probe(struct platform_device *pdev) dma_events[0], shared ? IMX_DMATYPE_SSI_SP : IMX_DMATYPE_SSI); imx_pcm_dma_params_init_data(&ssi_private->filter_data_rx, dma_events[1], shared ? IMX_DMATYPE_SSI_SP : IMX_DMATYPE_SSI); + } else if (ssi_private->use_dma) { + /* The 'name' should not have any slashes in it. */ + ret = devm_request_irq(&pdev->dev, ssi_private->irq, + fsl_ssi_isr, 0, ssi_private->name, + ssi_private); + if (ret < 0) { + dev_err(&pdev->dev, "could not claim irq %u\n", + ssi_private->irq); + goto error_irqmap; + } }
/* Initialize the the device_attribute structure */
On Mon, Aug 19, 2013 at 05:06:01PM +0200, Markus Pargmann wrote:
From: Michael Grzeschik m.grzeschik@pengutronix.de
We have to disable the ssi irq, as it is not safe for all platforms to write back into the status register. It also runs into non-linefetch aborts.
Applied, thanks.
participants (2)
-
Mark Brown
-
Markus Pargmann