/* * raumfeld_audio.c -- SoC audio for Raumfeld audio devices * * Copyright (c) 2009 Daniel Mack * * based on code from: * * Wolfson Microelectronics PLC. * Openedhand Ltd. * Liam Girdwood * Richard Purdie * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ #include #include #include #include #include #include #include #include "pxa-ssp.h" #define GPIO_SPDIF_RESET (38) #define GPIO_MCLK_RESET (111) #define GPIO_CODEC_RESET (120) static struct i2c_client *max9486_client; static struct i2c_board_info max9486_hwmon_info = { I2C_BOARD_INFO("max9485", 0x63), }; #define MAX9485_MCLK_FREQ_112896 0x22 #define MAX9485_MCLK_FREQ_122880 0x23 #define MAX9485_MCLK_FREQ_225792 0x32 #define MAX9485_MCLK_FREQ_245760 0x33 static void set_max9485_clk(char clk) { i2c_master_send(max9486_client, &clk, 1); } static void raumfeld_enable_audio(bool en) { if (en) { gpio_set_value(GPIO_MCLK_RESET, 1); /* wait some time to let the clocks become stable */ msleep(100); gpio_set_value(GPIO_SPDIF_RESET, 1); gpio_set_value(GPIO_CODEC_RESET, 1); } else { gpio_set_value(GPIO_MCLK_RESET, 0); gpio_set_value(GPIO_SPDIF_RESET, 0); gpio_set_value(GPIO_CODEC_RESET, 0); } } /* CS4270 */ static int raumfeld_cs4270_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; /* set freq to 0 to enable all possible codec sample rates */ return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); } static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; /* set freq to 0 to enable all possible codec sample rates */ snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); } static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int fmt, clk = 0; int ret = 0; switch (params_rate(params)) { case 44100: set_max9485_clk(MAX9485_MCLK_FREQ_112896); clk = 11289600; break; case 48000: set_max9485_clk(MAX9485_MCLK_FREQ_122880); clk = 12288000; break; case 88200: set_max9485_clk(MAX9485_MCLK_FREQ_225792); clk = 22579200; break; case 96000: set_max9485_clk(MAX9485_MCLK_FREQ_245760); clk = 24576000; break; default: return -EINVAL; } fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; /* setup the CODEC DAI */ ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) return ret; ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, 0); if (ret < 0) return ret; /* setup the CPU DAI */ ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk); if (ret < 0) return ret; ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) return ret; ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4); if (ret < 0) return ret; ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1); if (ret < 0) return ret; return 0; } static struct snd_soc_ops raumfeld_cs4270_ops = { .startup = raumfeld_cs4270_startup, .shutdown = raumfeld_cs4270_shutdown, .hw_params = raumfeld_cs4270_hw_params, }; static int raumfeld_line_suspend(struct platform_device *pdev, pm_message_t state) { raumfeld_enable_audio(false); return 0; } static int raumfeld_line_resume(struct platform_device *pdev) { raumfeld_enable_audio(true); return 0; } /* AK4104 */ static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int fmt, ret = 0, clk = 0; switch (params_rate(params)) { case 44100: set_max9485_clk(MAX9485_MCLK_FREQ_112896); clk = 11289600; break; case 48000: set_max9485_clk(MAX9485_MCLK_FREQ_122880); clk = 12288000; break; case 88200: set_max9485_clk(MAX9485_MCLK_FREQ_225792); clk = 22579200; break; case 96000: set_max9485_clk(MAX9485_MCLK_FREQ_245760); clk = 24576000; break; default: return -EINVAL; } fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; /* setup the CODEC DAI */ ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* setup the CPU DAI */ ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk); if (ret < 0) return ret; ret = snd_soc_dai_set_fmt(cpu_dai, fmt | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4); if (ret < 0) return ret; ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1); if (ret < 0) return ret; return 0; } static struct snd_soc_ops raumfeld_ak4104_ops = { .hw_params = raumfeld_ak4104_hw_params, }; static struct snd_soc_dai_link raumfeld_dai[] = { { .name = "ak4104", .stream_name = "Playback", .cpu_dai_name = "pxa-ssp-dai.1", .codec_dai_name = "ak4104-hifi", .platform_name = "pxa-pcm-audio", .ops = &raumfeld_ak4104_ops, .codec_name = "ak4104-codec.0", }, { .name = "CS4270", .stream_name = "CS4270", .cpu_dai_name = "pxa-ssp-dai.0", .platform_name = "pxa-pcm-audio", .codec_dai_name = "cs4270-hifi", .codec_name = "cs4270-codec.0-0048", .ops = &raumfeld_cs4270_ops, },}; static struct snd_soc_card snd_soc_raumfeld = { .name = "Raumfeld", .dai_link = raumfeld_dai, .suspend_post = raumfeld_line_suspend, .resume_pre = raumfeld_line_resume, .num_links = ARRAY_SIZE(raumfeld_dai), }; static struct platform_device *raumfeld_audio_device; static int __init raumfeld_audio_init(void) { int ret; if (!machine_is_raumfeld_speaker() && !machine_is_raumfeld_connector()) return 0; max9486_client = i2c_new_device(i2c_get_adapter(0), &max9486_hwmon_info); if (!max9486_client) return -ENOMEM; set_max9485_clk(MAX9485_MCLK_FREQ_122880); /* Register LINE and SPDIF */ raumfeld_audio_device = platform_device_alloc("soc-audio", 0); if (!raumfeld_audio_device) return -ENOMEM; platform_set_drvdata(raumfeld_audio_device, &snd_soc_raumfeld); ret = platform_device_add(raumfeld_audio_device); /* no S/PDIF on Speakers */ if (machine_is_raumfeld_speaker()) return ret; raumfeld_enable_audio(true); return ret; } static void __exit raumfeld_audio_exit(void) { raumfeld_enable_audio(false); platform_device_unregister(raumfeld_audio_device); i2c_unregister_device(max9486_client); gpio_free(GPIO_MCLK_RESET); gpio_free(GPIO_CODEC_RESET); gpio_free(GPIO_SPDIF_RESET); } module_init(raumfeld_audio_init); module_exit(raumfeld_audio_exit); /* Module information */ MODULE_AUTHOR("Daniel Mack "); MODULE_DESCRIPTION("Raumfeld audio SoC"); MODULE_LICENSE("GPL");