summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2016-09-26 15:13:40 +0100
committerRussell King <rmk+kernel@armlinux.org.uk>2020-10-12 21:56:23 +0100
commit9d7abd3260933438cbdb03cb826edc96923ecae9 (patch)
tree44c9ca164455f9041c34e81d310864f60fe2a76f
parent7e0b76003607e6f7d3cebc3d98372d976f14adef (diff)
asoc: sa11x0/h3xxx: add audio support
Add support for the UDA1341 on the iPAQ H3600 and H3100 devices. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--sound/soc/sa11x0/Kconfig10
-rw-r--r--sound/soc/sa11x0/Makefile3
-rw-r--r--sound/soc/sa11x0/h3xxx.c317
3 files changed, 330 insertions, 0 deletions
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 <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h> /* HACK */
+#include <linux/gpio/consumer.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/hardware.h>
+#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");