diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2012-01-18 13:25:02 +0000 |
---|---|---|
committer | Russell King (Oracle) <rmk+kernel@armlinux.org.uk> | 2024-03-26 12:08:26 +0000 |
commit | 6325a3de3e59eb59de78837b74cbfa3d3580b39c (patch) | |
tree | 116f54728af80dc6ce37facb4c1d969f7867a827 /sound | |
parent | b2e90045ec833705cea31e34ac472ec9da863960 (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>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/sa11x0/Kconfig | 10 | ||||
-rw-r--r-- | sound/soc/sa11x0/Makefile | 3 | ||||
-rw-r--r-- | sound/soc/sa11x0/assabet.c | 522 |
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"); |