/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 = "sa11x0-ssp", .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");