|  | /* | 
|  | * raumfeld_audio.c  --  SoC audio for Raumfeld audio devices | 
|  | * | 
|  | * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> | 
|  | * | 
|  | * based on code from: | 
|  | * | 
|  | *    Wolfson Microelectronics PLC. | 
|  | *    Openedhand Ltd. | 
|  | *    Liam Girdwood <lrg@slimlogic.co.uk> | 
|  | *    Richard Purdie <richard@openedhand.com> | 
|  | * | 
|  | * 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 <linux/module.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/gpio.h> | 
|  | #include <sound/pcm.h> | 
|  | #include <sound/soc.h> | 
|  |  | 
|  | #include <asm/mach-types.h> | 
|  |  | 
|  | #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 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; | 
|  | } | 
|  |  | 
|  | 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_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_analog_suspend(struct snd_soc_card *card) | 
|  | { | 
|  | raumfeld_enable_audio(false); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int raumfeld_analog_resume(struct snd_soc_card *card) | 
|  | { | 
|  | 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 *cpu_dai = rtd->cpu_dai; | 
|  | int 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; | 
|  | } | 
|  |  | 
|  | /* 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_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, | 
|  | }; | 
|  |  | 
|  | #define DAI_LINK_CS4270		\ | 
|  | {							\ | 
|  | .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.0-0048",	\ | 
|  | .dai_fmt	= SND_SOC_DAIFMT_I2S |		\ | 
|  | SND_SOC_DAIFMT_NB_NF |        \ | 
|  | SND_SOC_DAIFMT_CBS_CFS,       \ | 
|  | .ops		= &raumfeld_cs4270_ops,		\ | 
|  | } | 
|  |  | 
|  | #define DAI_LINK_AK4104		\ | 
|  | {							\ | 
|  | .name		= "ak4104",			\ | 
|  | .stream_name	= "Playback",			\ | 
|  | .cpu_dai_name	= "pxa-ssp-dai.1",		\ | 
|  | .codec_dai_name	= "ak4104-hifi",		\ | 
|  | .platform_name	= "pxa-pcm-audio",		\ | 
|  | .dai_fmt	= SND_SOC_DAIFMT_I2S |		\ | 
|  | SND_SOC_DAIFMT_NB_NF |	\ | 
|  | SND_SOC_DAIFMT_CBS_CFS,       \ | 
|  | .ops		= &raumfeld_ak4104_ops,		\ | 
|  | .codec_name	= "spi0.0",			\ | 
|  | } | 
|  |  | 
|  | static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = | 
|  | { | 
|  | DAI_LINK_CS4270, | 
|  | DAI_LINK_AK4104, | 
|  | }; | 
|  |  | 
|  | static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = | 
|  | { | 
|  | DAI_LINK_CS4270, | 
|  | }; | 
|  |  | 
|  | static struct snd_soc_card snd_soc_raumfeld_connector = { | 
|  | .name		= "Raumfeld Connector", | 
|  | .owner		= THIS_MODULE, | 
|  | .dai_link	= snd_soc_raumfeld_connector_dai, | 
|  | .num_links	= ARRAY_SIZE(snd_soc_raumfeld_connector_dai), | 
|  | .suspend_post	= raumfeld_analog_suspend, | 
|  | .resume_pre	= raumfeld_analog_resume, | 
|  | }; | 
|  |  | 
|  | static struct snd_soc_card snd_soc_raumfeld_speaker = { | 
|  | .name		= "Raumfeld Speaker", | 
|  | .owner		= THIS_MODULE, | 
|  | .dai_link	= snd_soc_raumfeld_speaker_dai, | 
|  | .num_links	= ARRAY_SIZE(snd_soc_raumfeld_speaker_dai), | 
|  | .suspend_post	= raumfeld_analog_suspend, | 
|  | .resume_pre	= raumfeld_analog_resume, | 
|  | }; | 
|  |  | 
|  | 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 analog device */ | 
|  | raumfeld_audio_device = platform_device_alloc("soc-audio", 0); | 
|  | if (!raumfeld_audio_device) | 
|  | return -ENOMEM; | 
|  |  | 
|  | if (machine_is_raumfeld_speaker()) | 
|  | platform_set_drvdata(raumfeld_audio_device, | 
|  | &snd_soc_raumfeld_speaker); | 
|  |  | 
|  | if (machine_is_raumfeld_connector()) | 
|  | platform_set_drvdata(raumfeld_audio_device, | 
|  | &snd_soc_raumfeld_connector); | 
|  |  | 
|  | ret = platform_device_add(raumfeld_audio_device); | 
|  | if (ret < 0) { | 
|  | platform_device_put(raumfeld_audio_device); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | raumfeld_enable_audio(true); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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 <daniel@caiaq.de>"); | 
|  | MODULE_DESCRIPTION("Raumfeld audio SoC"); | 
|  | MODULE_LICENSE("GPL"); |