From 9d7abd3260933438cbdb03cb826edc96923ecae9 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 26 Sep 2016 15:13:40 +0100 Subject: asoc: sa11x0/h3xxx: add audio support Add support for the UDA1341 on the iPAQ H3600 and H3100 devices. Signed-off-by: Russell King --- sound/soc/sa11x0/Kconfig | 10 ++ sound/soc/sa11x0/Makefile | 3 + sound/soc/sa11x0/h3xxx.c | 317 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 sound/soc/sa11x0/h3xxx.c diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig index ae7930842e07..81ba6f9bccc8 100644 --- a/sound/soc/sa11x0/Kconfig +++ b/sound/soc/sa11x0/Kconfig @@ -12,3 +12,13 @@ config SND_SA11X0_ASSABET help Enable support for the audio UDA1341 codec found on the Intel Assabet SA-1110 development board. + +config SND_SA11X0_H3XXX + tristate "SoC Audio support for H3xxx iPAQ" + depends on (SA1100_H3100 || SA1100_H3600) && 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 iPAQ + H3100 and H3600 platforms. diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile index 9a2ab2c346d9..dab7e2deff6e 100644 --- a/sound/soc/sa11x0/Makefile +++ b/sound/soc/sa11x0/Makefile @@ -3,3 +3,6 @@ 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 + +snd-soc-sa11x0-h3xxx-objs := h3xxx.o +obj-$(CONFIG_SND_SA11X0_H3XXX) += snd-soc-sa11x0-h3xxx.o diff --git a/sound/soc/sa11x0/h3xxx.c b/sound/soc/sa11x0/h3xxx.c new file mode 100644 index 000000000000..baed22e2dc2a --- /dev/null +++ b/sound/soc/sa11x0/h3xxx.c @@ -0,0 +1,317 @@ +#include +#include +#include /* HACK */ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "ssp.h" + +struct h3xxx_card { + struct snd_soc_card card; + struct clk *clk_ext; + struct gpio_desc *reset; + struct gpio_desc *aud_pwr; + struct gpio_desc *amp_pwr; +}; + +static int h3xxx_asoc_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card); + int ret = 0; + + if (!rtd->cpu_dai->active) + ret = clk_prepare_enable(hc->clk_ext); + + return ret; +} + +static int h3xxx_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card); + unsigned long rate = params_rate(params); + unsigned long sclk; + int ret; + + switch (rate) { + case 24000: + case 32000: + case 48000: + sclk = 12288000; + break; + case 22050: + case 29400: + case 44100: + sclk = 11289600; + break; + case 8000: + case 10666: + case 16000: + sclk = 4096000; + break; + case 10985: + case 14647: + case 21970: + sclk = 5624500; + break; + default: + pr_warn("invalid rate: %luHz\n", rate); + return -EINVAL; + } + + ret = clk_set_rate(hc->clk_ext, sclk); + if (ret) { + pr_warn("clk_set_rate: %d (%luHz)\n", ret, sclk); + return ret; + } + + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sclk, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_warn("snd_soc_dai_set_sysclk(codec): %d (%luHz)\n", ret, sclk); + return ret; + } + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SA11X0_SSP_CLK_EXT, + sclk, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_warn("snd_soc_dai_set_sysclk(cpu): %d (%luHz)\n", ret, sclk); + return ret; + } + + return ret; +} + +static void h3xxx_asoc_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card); + + if (!rtd->cpu_dai->active) + clk_disable_unprepare(hc->clk_ext); +} + +static const struct snd_soc_ops h3xxx_asoc_ops = { + .startup = h3xxx_asoc_startup, + .hw_params = h3xxx_asoc_hw_params, + .shutdown = h3xxx_asoc_shutdown, +}; + +static struct snd_soc_dai_link h3xxx_asoc_dais[1] = { + { + .name = "h3xxx", + .stream_name = "iPAQ Playback/Capture", + .cpu_dai_name = "sa11x0-ssp", + .codec_name = "uda134x-codec", + .codec_dai_name = "uda134x-hifi", + .platform_name = "sa11x0-ssp", + .dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &h3xxx_asoc_ops, + }, +}; + +static int h3xxx_asoc_pwramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct h3xxx_card *hc = snd_soc_card_get_drvdata(w->dapm->card); + + gpiod_set_value(hc->amp_pwr, SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget h3xxx_asoc_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Power Amp", h3xxx_asoc_pwramp_event), +}; + +static const struct snd_soc_dapm_route h3xxx_asoc_dapm_routes[] = { + { "Power Amp", NULL, "VOUTL" }, + { "Power Amp", NULL, "VOUTR" }, +}; + +static const struct snd_soc_card h3xxx_asoc_card_template = { + .name = "H3xxx", + .long_name = "iPAQ H3xxx HiFi Audio", + .owner = THIS_MODULE, + .dai_link = h3xxx_asoc_dais, + .num_links = ARRAY_SIZE(h3xxx_asoc_dais), + .dapm_widgets = h3xxx_asoc_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(h3xxx_asoc_dapm_widgets), + .dapm_routes = h3xxx_asoc_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(h3xxx_asoc_dapm_routes), +}; + +static struct h3xxx_card *card; /* HACK */ + +static int h3xxx_asoc_probe(struct platform_device *pdev) +{ + struct h3xxx_card *hc; + + GAFR |= GPIO_SSP_CLK; + GPDR &= ~GPIO_SSP_CLK; + + hc = devm_kzalloc(&pdev->dev, sizeof(*hc), GFP_KERNEL); + if (!hc) + return -ENOMEM; + + memcpy(&hc->card, &h3xxx_asoc_card_template, sizeof(hc->card)); + hc->card.dev = &pdev->dev; + + hc->reset = devm_gpiod_get(&pdev->dev, "audio-reset", GPIOD_OUT_LOW); + if (IS_ERR(hc->reset)) + return PTR_ERR(hc->reset); + + /* should be regulators? */ + hc->aud_pwr = devm_gpiod_get(&pdev->dev, "audio-power", GPIOD_OUT_LOW); + if (IS_ERR(hc->aud_pwr)) + return PTR_ERR(hc->aud_pwr); + + hc->amp_pwr = devm_gpiod_get(&pdev->dev, "amp-power", GPIOD_OUT_LOW); + if (IS_ERR(hc->amp_pwr)) + return PTR_ERR(hc->amp_pwr); + + hc->clk_ext = devm_clk_get(&pdev->dev, "ext"); + if (IS_ERR(hc->clk_ext)) + return PTR_ERR(hc->clk_ext); + + snd_soc_card_set_drvdata(&hc->card, hc); + + card = hc; + + return snd_soc_register_card(&hc->card); +} + +static int h3xxx_asoc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + card = NULL; + + return 0; +} + +static struct platform_driver h3xxx_asoc_driver = { + .probe = h3xxx_asoc_probe, + .remove = h3xxx_asoc_remove, + .driver = { + .name = "h3xxx-asoc", + }, +}; + +/* HACK */ +static void h3xxx_asoc_setdat(struct l3_pins *adap, int v) +{ + gpio_set_value(14, v); +} + +static void h3xxx_asoc_setclk(struct l3_pins *adap, int v) +{ + gpio_set_value(16, v); +} + +static void h3xxx_asoc_setmode(struct l3_pins *adap, int v) +{ + gpio_set_value(15, v); +} + +static void h3xxx_asoc_power(int on) +{ + struct h3xxx_card *hc = card; + + if (!hc) + return; + + if (on) { + gpiod_set_value_cansleep(hc->aud_pwr, 1); + gpiod_set_value_cansleep(hc->reset, 1); + mdelay(1); + } else { + gpiod_set_value_cansleep(hc->reset, 0); + gpiod_set_value_cansleep(hc->aud_pwr, 0); + } +} + +static struct uda134x_platform_data uda134x_data = { + .l3 = { + .setdat = h3xxx_asoc_setdat, + .setclk = h3xxx_asoc_setclk, + .setmode = h3xxx_asoc_setmode, + .data_hold = 1, + .data_setup = 1, + .clock_high = 1, + .mode_hold = 1, + .mode = 1, + .mode_setup = 1, + }, + .model = UDA134X_UDA1341, + .power = h3xxx_asoc_power, +}; +static struct platform_device *h3xxx_asoc, *h3xxx_uda; +static void h3xxx_hack_init(void) +{ + gpio_direction_output(14, 0); + gpio_direction_output(15, 0); + gpio_direction_output(16, 0); + + h3xxx_asoc = platform_device_register_simple("h3xxx-asoc", -1, + NULL, 0); + if (IS_ERR(h3xxx_asoc)) + h3xxx_asoc = NULL; + h3xxx_uda = platform_device_register_data(NULL, "uda134x-codec", -1, + &uda134x_data, + sizeof(uda134x_data)); + if (IS_ERR(h3xxx_uda)) + h3xxx_uda = NULL; +} + +static void h3xxx_hack_exit(void) +{ + if (h3xxx_asoc) { + platform_device_unregister(h3xxx_asoc); + h3xxx_asoc = NULL; + } + if (h3xxx_uda) { + platform_device_unregister(h3xxx_uda); + h3xxx_uda = NULL; + } +} + +static int h3xxx_asoc_init(void) +{ + int ret; + + if (machine_is_h3100() || machine_is_h3600()) + h3xxx_hack_init(); + + ret = platform_driver_register(&h3xxx_asoc_driver); + if (ret) + goto err_driver; + + return 0; + +err_driver: + h3xxx_hack_exit(); + return ret; +} +module_init(h3xxx_asoc_init); + +static void h3xxx_asoc_exit(void) +{ + platform_driver_unregister(&h3xxx_asoc_driver); + h3xxx_hack_exit(); +} +module_exit(h3xxx_asoc_exit) + +MODULE_LICENSE("GPL"); -- cgit