summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2012-01-18 13:25:02 +0000
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>2024-01-08 10:29:16 +0000
commitd8ad9a8813b036185117b09e90f84e58e0647706 (patch)
tree27e429aad0a09dd48502d222b017e6170e502264
parent1817b8b280426a619c2dea342241823fe2d81637 (diff)
ALSA: ASoC: add SA-11x0 Assabet platform support
Add the support code for the UDA1341 on the Assabet platform, which uses the DMA engine and SA-11x0 SSP drivers. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r--sound/soc/sa11x0/Kconfig10
-rw-r--r--sound/soc/sa11x0/Makefile3
-rw-r--r--sound/soc/sa11x0/assabet.c522
3 files changed, 535 insertions, 0 deletions
diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
index 7a400f914c52..f18fe9faa76d 100644
--- a/sound/soc/sa11x0/Kconfig
+++ b/sound/soc/sa11x0/Kconfig
@@ -2,3 +2,13 @@ config SND_SOC_SA11X0_SSP
tristate
depends on DMA_SA11X0
select SND_SOC_DMAENGINE
+
+config SND_SA11X0_ASSABET
+ tristate "SoC Audio support for the Intel Assabet board"
+ depends on SA1100_ASSABET && DMA_SA11X0
+ select SND_SOC_L3
+ select SND_SOC_UDA134X
+ select SND_SOC_SA11X0_SSP
+ help
+ Enable support for the audio UDA1341 codec found on the Intel
+ Assabet SA-1110 development board.
diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile
index 770ca0647343..9a2ab2c346d9 100644
--- a/sound/soc/sa11x0/Makefile
+++ b/sound/soc/sa11x0/Makefile
@@ -1,2 +1,5 @@
snd-soc-sa11x0-ssp-objs := ssp.o
obj-$(CONFIG_SND_SOC_SA11X0_SSP) += snd-soc-sa11x0-ssp.o
+
+snd-soc-sa11x0-assabet-objs := assabet.o
+obj-$(CONFIG_SND_SA11X0_ASSABET) += snd-soc-sa11x0-assabet.o
diff --git a/sound/soc/sa11x0/assabet.c b/sound/soc/sa11x0/assabet.c
new file mode 100644
index 000000000000..0dc5362c82a8
--- /dev/null
+++ b/sound/soc/sa11x0/assabet.c
@@ -0,0 +1,522 @@
+/*
+ * SA11x0 Assabet ASoC driver
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * 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.
+ *
+ * The Assabet board uses an unused SDRAM clock output as the source
+ * clock for the UDA1341 audio subsystem. This clock is supplied to
+ * via a CPLD divider to the SA11x0 GPIO19 (alternately the SSP block
+ * external clock input) and to the UDA1341 as the bit clock.
+ *
+ * As the SSPs TXD,RXD,SFRM,SCLK outputs are not directly compatible
+ * with the UDA1341 input, the CPLD implements logic to provide the
+ * WS (LRCK) signal to the UDA1341, and buffer the RXD and clock signals.
+ *
+ * The UDA1341 is powered by the AUDIO3P3V supply, which can be turned
+ * off. This tristates the CPLD outputs, preventing power draining
+ * into the UDA1341. However, revision 4 Assabets do not tristate the
+ * WS signal, and so need to be worked-around to place this at logic 0.
+ *
+ * A side effect of using the SDRAM clock is that this scales with the
+ * core CPU frequency. Hence, the available sample rate depends on the
+ * CPU clock rate. At present, we have no logic here to restrict the
+ * requested sample rate; the UDA1341 driver will just fail the preparation.
+ * We rely on userspace (at present) to ask for the correct sample rate.
+ */
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/uda134x.h>
+
+#include <asm/mach-types.h>
+#include <mach/assabet.h>
+#include <mach/hardware.h>
+#include "ssp.h"
+
+struct assabet_card {
+ struct snd_soc_card card;
+};
+
+#define GPIO_TXD 10
+#define GPIO_RXD 11
+#define GPIO_SCLK 12
+#define GPIO_SFRM 13
+#define GPIO_L3_DAT 15
+#define GPIO_L3_MODE 17
+#define GPIO_L3_SCK 18
+#define GPIO_CLK 19
+
+/*
+ * The UDA1341 wants a WS (or LRCK), and the Assabet derives this from
+ * the SFRM pulses from the SSP. Unfortunately, some Assabets have a bug
+ * in the hardware where the WS output from the CPLD remains high even
+ * after the codec is powered down, causing power to drain through the
+ * CPLD to the UDA1341.
+ *
+ * Although the Assabet provides a reset signal to initialize WS to a
+ * known state, this presents two problems: (a) this forces the WS output
+ * high, (b) this signal also resets the UCB1300 device.
+ */
+static void assabet_asoc_uda1341_power(int on)
+{
+ static bool state;
+
+ if (state == !!on)
+ return;
+ state = !!on;
+
+ if (on) {
+ /*
+ * Enable the power for the UDA1341 before fixing WS.
+ * Also assert the mute signal.
+ */
+ ASSABET_BCR_set(ASSABET_BCR_AUDIO_ON | ASSABET_BCR_QMUTE);
+
+ /*
+ * Toggle SFRM with the codec reset signal held active.
+ * This will set LRCK high, which results in the left
+ * sample being transmitted first.
+ */
+ gpio_set_value(GPIO_SFRM, 1);
+ gpio_set_value(GPIO_SFRM, 0);
+
+ /*
+ * If the reset was being held, release it now. This
+ * ensures that the above SFRM fiddling has no effect
+ * should the reset be raised elsewhere.
+ */
+ assabet_uda1341_reset(0);
+ } else {
+ /*
+ * The SSP will have transmitted a whole number of 32-bit
+ * words, so we know that LRCK will be left high at this
+ * point. Toggle SFRM once to kick the LRCK low before
+ * powering down the codec.
+ */
+ gpio_set_value(GPIO_SFRM, 1);
+ gpio_set_value(GPIO_SFRM, 0);
+
+ /* Finally, disable the audio power */
+ ASSABET_BCR_clear(ASSABET_BCR_AUDIO_ON);
+
+ /* And lower the QMUTE signal to stop power draining */
+ ASSABET_BCR_clear(ASSABET_BCR_QMUTE);
+ }
+}
+
+static void assabet_asoc_init_clk(void)
+{
+ u32 mdrefr, mdrefr_old;
+
+ /*
+ * The assabet board uses the SDRAM clock as the source clock for
+ * audio. This is supplied to the SA11x0 from the CPLD on pin 19.
+ * At 206MHz we need to run the audio clock (SDRAM bank 2) at half
+ * speed. This clock will scale with core frequency so the audio
+ * sample rate will also scale. The CPLD on Assabet will need to
+ * be programmed to match the core frequency.
+ */
+ mdrefr_old = MDREFR;
+ mdrefr = mdrefr_old & ~(MDREFR_EAPD | MDREFR_KAPD);
+ mdrefr = mdrefr | MDREFR_K2DB2 | MDREFR_K2RUN;
+ if (mdrefr != mdrefr_old) {
+ MDREFR = mdrefr;
+ (void) MDREFR;
+ }
+}
+
+static int assabet_asoc_resume_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ bool active = false;
+
+ list_for_each_entry(rtd, &card->rtd_list, list)
+ if (rtd->cpu_dai->active)
+ active = true;
+
+ if (active) {
+ unsigned long flags;
+
+ local_irq_save(flags);
+ assabet_asoc_init_clk();
+ local_irq_restore(flags);
+ }
+ return 0;
+}
+
+static int
+assabet_asoc_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval range, *p = hw_param_interval(params, rule->var);
+ unsigned int sysclk = (cpufreq_get(0) / 18) * 1000;
+
+ snd_interval_any(&range);
+ range.min = UINT_MAX;
+ range.max = 0;
+
+ /* We do 512Fs and 256Fs only */
+ if (snd_interval_test(p, sysclk / 256)) {
+ range.min = min(range.min, sysclk / 256);
+ range.max = max(range.max, sysclk / 256);
+ }
+ if (snd_interval_test(p, sysclk / 512)) {
+ range.min = min(range.min, sysclk / 512);
+ range.max = max(range.max, sysclk / 512);
+ }
+ return snd_interval_refine(p, &range);
+}
+
+static int assabet_asoc_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (!cpu_dai->active) {
+ unsigned long flags;
+ unsigned int sysclk;
+ int ret;
+
+ /*
+ * Hand the SFRM signal over to the SSP block as it is
+ * now enabled. This keeps SFRM low until the interface
+ * starts outputting data.
+ */
+ local_irq_save(flags);
+ GAFR |= GPIO_SSP_SFRM;
+
+ assabet_asoc_init_clk();
+ local_irq_restore(flags);
+
+ /*
+ * Calculate the sysclk, which is the codec input clock.
+ * This is supplied from the SDRAM bank 2 clock, divided by
+ * 18 by the CPLD. This is then further divided by four by
+ * the CPLD, and supplied to the SA1110 GPIO19 to
+ * synchronously drive the SSP block.
+ *
+ * Ideally, we would lock cpufreq against any transitions
+ * which would upset the sample rate, or have userspace
+ * renegotiate the sample rate, but let's keep this simple.
+ *
+ * We intentionally lose some precision here to avoid the
+ * codec bombing out as it claims to only do standard rates.
+ */
+ sysclk = (cpufreq_get(0) / 18) * 1000;
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sysclk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SA11X0_SSP_CLK_EXT,
+ sysclk / 4, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Release the mute */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ASSABET_BCR_clear(ASSABET_BCR_QMUTE);
+
+ snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ assabet_asoc_rate_constraint, NULL,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+
+ return 0;
+}
+
+static void assabet_asoc_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ /* Apply the output mute */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ASSABET_BCR_set(ASSABET_BCR_QMUTE);
+
+ if (!cpu_dai->active) {
+ unsigned long flags;
+
+ /*
+ * Take the SFRM pin away from the SSP block before it
+ * shuts down. We must do this to keep SFRM low.
+ */
+ local_irq_save(flags);
+ GAFR &= ~GPIO_SSP_SFRM;
+ local_irq_restore(flags);
+ }
+}
+
+static struct snd_soc_ops assabet_asoc_ops = {
+ .startup = assabet_asoc_startup,
+ .shutdown = assabet_asoc_shutdown,
+};
+
+static struct snd_soc_dai_link assabet_asoc_dai = {
+ .name = "Assabet",
+ .stream_name = "assabet-ssp",
+ .codec_name = "uda134x-codec",
+ .platform_name = "soc-dmaengine",
+ .cpu_dai_name = "sa11x0-ssp",
+ .codec_dai_name = "uda134x-hifi",
+ .ops = &assabet_asoc_ops,
+ .dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+};
+
+static int
+assabet_pwramp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
+ int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ASSABET_BCR_clear(ASSABET_BCR_SPK_OFF);
+ else
+ ASSABET_BCR_set(ASSABET_BCR_SPK_OFF);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget assabet_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Power Amp", assabet_pwramp_event),
+};
+
+static const struct snd_soc_dapm_route assabet_dapm_routes[] = {
+ { "Power Amp", NULL, "VOUTL" },
+ { "Power Amp", NULL, "VOUTR" },
+};
+
+static struct snd_soc_card snd_soc_assabet = {
+ .name = "Assabet",
+ .resume_pre = assabet_asoc_resume_pre,
+ .dai_link = &assabet_asoc_dai,
+ .num_links = 1,
+ .dapm_widgets = assabet_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(assabet_dapm_widgets),
+ .dapm_routes = assabet_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(assabet_dapm_routes),
+};
+
+/*
+ * This is not-quite-right stuff: we have to _hope_ by a miracle that
+ * we don't stamp on the toes of I2C, which shares these pins.
+ */
+static void assabet_setdat(struct l3_pins *adap, int v)
+{
+ gpio_set_value(GPIO_L3_DAT, !!v);
+}
+
+static void assabet_setclk(struct l3_pins *adap, int v)
+{
+ gpio_set_value(GPIO_L3_SCK, !!v);
+}
+
+static void assabet_setmode(struct l3_pins *adap, int v)
+{
+ gpio_set_value(GPIO_L3_MODE, !!v);
+}
+
+static struct uda134x_platform_data uda1341_data = {
+ .l3 = {
+ .setdat = assabet_setdat,
+ .setclk = assabet_setclk,
+ .setmode = assabet_setmode,
+ .data_hold = 1,
+ .data_setup = 1,
+ .clock_high = 1,
+ .mode_hold = 1,
+ .mode = 1,
+ .mode_setup = 1,
+ },
+ .model = UDA134X_UDA1341,
+ .power = assabet_asoc_uda1341_power,
+};
+
+static struct gpio ssp_gpio[] = {
+ {
+ .gpio = GPIO_TXD,
+ .flags = GPIOF_OUT_INIT_LOW,
+ .label = "ssp_txd",
+ }, {
+ .gpio = GPIO_RXD,
+ .flags = GPIOF_IN,
+ .label = "ssp_rxd",
+ }, {
+ .gpio = GPIO_SCLK,
+ .flags = GPIOF_OUT_INIT_LOW,
+ .label = "ssp_sclk",
+ }, {
+ .gpio = GPIO_SFRM,
+ .flags = GPIOF_OUT_INIT_LOW,
+ .label = "ssp_sfrm",
+ }, {
+ .gpio = GPIO_CLK,
+ .flags = GPIOF_IN,
+ .label = "ssp_clk",
+ }, {
+ .gpio = GPIO_L3_MODE,
+ .flags = GPIOF_OUT_INIT_LOW,
+ .label = "l3_mode",
+ },
+};
+
+static int assabet_asoc_probe(struct platform_device *pdev)
+{
+ struct assabet_card *ac;
+
+ ac = devm_kzalloc(&pdev->dev, sizeof(*ac), GFP_KERNEL);
+ if (!ac)
+ return -ENOMEM;
+
+ memcpy(&ac->card, &snd_soc_assabet, sizeof(ac->card));
+ ac->card.dev = &pdev->dev;
+
+ snd_soc_card_set_drvdata(&ac->card, ac);
+
+ return snd_soc_register_card(&ac->card);
+}
+
+static int assabet_asoc_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 assabet_asoc_driver = {
+ .probe = assabet_asoc_probe,
+ .remove = assabet_asoc_remove,
+ .driver = {
+ .name = "assabet-asoc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *soc, *uda, *dma;
+
+static int assabet_init(void)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!machine_is_assabet())
+ return -ENODEV;
+
+ ret = gpio_request_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+ if (ret)
+ return ret;
+
+ /*
+ * Request these irrespective of whether they succeed. Wish we
+ * could have our shared I2C/L3 driver from v2.4 kernels, but
+ * alas this would require a buggeration of the ASoC code.
+ * Not only this, but we have no way to do any kind of locking
+ * between the UDA134x L3 driver and I2C.
+ */
+ gpio_request_one(GPIO_L3_DAT, GPIOF_OUT_INIT_LOW, "l3_dat");
+ gpio_request_one(GPIO_L3_SCK, GPIOF_OUT_INIT_LOW, "l3_sck");
+
+ /*
+ * Put the LRCK signal into a known good state: early Assabets
+ * do not mask the LRCK signal when the codec is powered down,
+ * and it defaults to logic 1. So, first release the reset,
+ * and then toggle the SFRM signal to set LRCK to zero.
+ */
+ ASSABET_BCR_set(ASSABET_BCR_SPK_OFF);
+ ASSABET_BCR_clear(ASSABET_BCR_STEREO_LB);
+ assabet_uda1341_reset(0);
+
+ gpio_set_value(GPIO_SFRM, 1);
+ gpio_set_value(GPIO_SFRM, 0);
+
+ local_irq_save(flags);
+ /*
+ * Ensure SSP pin reassignment is enabled, so that the SSP appears
+ * on the GPIO pins rather than the deddicated UART4 pins.
+ */
+ PPAR |= PPAR_SPR;
+
+ /* Configure the SSP input clock, and most SSP output signals */
+ GAFR |= GPIO_SSP_TXD | GPIO_SSP_RXD | GPIO_SSP_SCLK | GPIO_SSP_CLK;
+
+ local_irq_restore(flags);
+
+ soc = platform_device_alloc("assabet-asoc", -1);
+ uda = platform_device_alloc(assabet_asoc_dai.codec_name, -1);
+ dma = platform_device_alloc("soc-dmaengine", -1);
+ if (!soc || !uda || !dma) {
+ ret = -ENOMEM;
+ goto err_dev_alloc;
+ }
+
+ /* Set some platform data for the UDA1341 codec driver... */
+ uda->dev.platform_data = &uda1341_data;
+
+ /* Set some sensible parents */
+ uda->dev.parent = &soc->dev;
+ dma->dev.parent = &soc->dev;
+
+ ret = platform_device_add(soc);
+ if (ret)
+ goto err_dev_alloc;
+ ret = platform_device_add(uda);
+ if (ret)
+ goto err_dev_add_soc;
+ ret = platform_device_add(dma);
+ if (ret)
+ goto err_dev_add_uda;
+ ret = platform_driver_register(&assabet_asoc_driver);
+ if (ret)
+ goto err_dev_add_dma;
+ return 0;
+
+ err_dev_add_dma:
+ platform_device_del(dma);
+ err_dev_add_uda:
+ platform_device_del(uda);
+ err_dev_add_soc:
+ platform_device_del(soc);
+ err_dev_alloc:
+ if (dma)
+ platform_device_put(dma);
+ if (uda)
+ platform_device_put(uda);
+ if (soc)
+ platform_device_put(soc);
+
+ gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+
+ return ret;
+}
+module_init(assabet_init);
+
+static void assabet_exit(void)
+{
+ if (!machine_is_assabet())
+ return;
+
+ platform_driver_unregister(&assabet_asoc_driver);
+ platform_device_unregister(dma);
+ platform_device_unregister(uda);
+
+ /* Don't try to kfree this data */
+ soc->dev.platform_data = NULL;
+
+ platform_device_unregister(soc);
+ gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+}
+module_exit(assabet_exit);
+
+MODULE_LICENSE("GPL");