1/* 2 * raumfeld_audio.c -- SoC audio for Raumfeld audio devices 3 * 4 * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> 5 * 6 * based on code from: 7 * 8 * Wolfson Microelectronics PLC. 9 * Openedhand Ltd. 10 * Liam Girdwood <lrg@slimlogic.co.uk> 11 * Richard Purdie <richard@openedhand.com> 12 * 13 * This program is free software; you can redistribute it and/or modify it 14 * under the terms of the GNU General Public License as published by the 15 * Free Software Foundation; either version 2 of the License, or (at your 16 * option) any later version. 17 */ 18 19#include <linux/module.h> 20#include <linux/i2c.h> 21#include <linux/delay.h> 22#include <linux/gpio.h> 23#include <sound/pcm.h> 24#include <sound/soc.h> 25 26#include <asm/mach-types.h> 27 28#include "pxa-ssp.h" 29 30#define GPIO_SPDIF_RESET (38) 31#define GPIO_MCLK_RESET (111) 32#define GPIO_CODEC_RESET (120) 33 34static struct i2c_client *max9486_client; 35static struct i2c_board_info max9486_hwmon_info = { 36 I2C_BOARD_INFO("max9485", 0x63), 37}; 38 39#define MAX9485_MCLK_FREQ_112896 0x22 40#define MAX9485_MCLK_FREQ_122880 0x23 41#define MAX9485_MCLK_FREQ_225792 0x32 42#define MAX9485_MCLK_FREQ_245760 0x33 43 44static void set_max9485_clk(char clk) 45{ 46 i2c_master_send(max9486_client, &clk, 1); 47} 48 49static void raumfeld_enable_audio(bool en) 50{ 51 if (en) { 52 gpio_set_value(GPIO_MCLK_RESET, 1); 53 54 /* wait some time to let the clocks become stable */ 55 msleep(100); 56 57 gpio_set_value(GPIO_SPDIF_RESET, 1); 58 gpio_set_value(GPIO_CODEC_RESET, 1); 59 } else { 60 gpio_set_value(GPIO_MCLK_RESET, 0); 61 gpio_set_value(GPIO_SPDIF_RESET, 0); 62 gpio_set_value(GPIO_CODEC_RESET, 0); 63 } 64} 65 66/* CS4270 */ 67static int raumfeld_cs4270_startup(struct snd_pcm_substream *substream) 68{ 69 struct snd_soc_pcm_runtime *rtd = substream->private_data; 70 struct snd_soc_dai *codec_dai = rtd->codec_dai; 71 72 /* set freq to 0 to enable all possible codec sample rates */ 73 return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); 74} 75 76static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream) 77{ 78 struct snd_soc_pcm_runtime *rtd = substream->private_data; 79 struct snd_soc_dai *codec_dai = rtd->codec_dai; 80 81 /* set freq to 0 to enable all possible codec sample rates */ 82 snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); 83} 84 85static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream, 86 struct snd_pcm_hw_params *params) 87{ 88 struct snd_soc_pcm_runtime *rtd = substream->private_data; 89 struct snd_soc_dai *codec_dai = rtd->codec_dai; 90 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 91 unsigned int clk = 0; 92 int ret = 0; 93 94 switch (params_rate(params)) { 95 case 44100: 96 set_max9485_clk(MAX9485_MCLK_FREQ_112896); 97 clk = 11289600; 98 break; 99 case 48000: 100 set_max9485_clk(MAX9485_MCLK_FREQ_122880); 101 clk = 12288000; 102 break; 103 case 88200: 104 set_max9485_clk(MAX9485_MCLK_FREQ_225792); 105 clk = 22579200; 106 break; 107 case 96000: 108 set_max9485_clk(MAX9485_MCLK_FREQ_245760); 109 clk = 24576000; 110 break; 111 default: 112 return -EINVAL; 113 } 114 115 ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, 0); 116 if (ret < 0) 117 return ret; 118 119 /* setup the CPU DAI */ 120 ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk); 121 if (ret < 0) 122 return ret; 123 124 ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4); 125 if (ret < 0) 126 return ret; 127 128 ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1); 129 if (ret < 0) 130 return ret; 131 132 return 0; 133} 134 135static struct snd_soc_ops raumfeld_cs4270_ops = { 136 .startup = raumfeld_cs4270_startup, 137 .shutdown = raumfeld_cs4270_shutdown, 138 .hw_params = raumfeld_cs4270_hw_params, 139}; 140 141static int raumfeld_analog_suspend(struct snd_soc_card *card) 142{ 143 raumfeld_enable_audio(false); 144 return 0; 145} 146 147static int raumfeld_analog_resume(struct snd_soc_card *card) 148{ 149 raumfeld_enable_audio(true); 150 return 0; 151} 152 153/* AK4104 */ 154 155static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream, 156 struct snd_pcm_hw_params *params) 157{ 158 struct snd_soc_pcm_runtime *rtd = substream->private_data; 159 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 160 int ret = 0, clk = 0; 161 162 switch (params_rate(params)) { 163 case 44100: 164 set_max9485_clk(MAX9485_MCLK_FREQ_112896); 165 clk = 11289600; 166 break; 167 case 48000: 168 set_max9485_clk(MAX9485_MCLK_FREQ_122880); 169 clk = 12288000; 170 break; 171 case 88200: 172 set_max9485_clk(MAX9485_MCLK_FREQ_225792); 173 clk = 22579200; 174 break; 175 case 96000: 176 set_max9485_clk(MAX9485_MCLK_FREQ_245760); 177 clk = 24576000; 178 break; 179 default: 180 return -EINVAL; 181 } 182 183 /* setup the CPU DAI */ 184 ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk); 185 if (ret < 0) 186 return ret; 187 188 ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4); 189 if (ret < 0) 190 return ret; 191 192 ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1); 193 if (ret < 0) 194 return ret; 195 196 return 0; 197} 198 199static struct snd_soc_ops raumfeld_ak4104_ops = { 200 .hw_params = raumfeld_ak4104_hw_params, 201}; 202 203#define DAI_LINK_CS4270 \ 204{ \ 205 .name = "CS4270", \ 206 .stream_name = "CS4270", \ 207 .cpu_dai_name = "pxa-ssp-dai.0", \ 208 .platform_name = "pxa-pcm-audio", \ 209 .codec_dai_name = "cs4270-hifi", \ 210 .codec_name = "cs4270.0-0048", \ 211 .dai_fmt = SND_SOC_DAIFMT_I2S | \ 212 SND_SOC_DAIFMT_NB_NF | \ 213 SND_SOC_DAIFMT_CBS_CFS, \ 214 .ops = &raumfeld_cs4270_ops, \ 215} 216 217#define DAI_LINK_AK4104 \ 218{ \ 219 .name = "ak4104", \ 220 .stream_name = "Playback", \ 221 .cpu_dai_name = "pxa-ssp-dai.1", \ 222 .codec_dai_name = "ak4104-hifi", \ 223 .platform_name = "pxa-pcm-audio", \ 224 .dai_fmt = SND_SOC_DAIFMT_I2S | \ 225 SND_SOC_DAIFMT_NB_NF | \ 226 SND_SOC_DAIFMT_CBS_CFS, \ 227 .ops = &raumfeld_ak4104_ops, \ 228 .codec_name = "spi0.0", \ 229} 230 231static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = 232{ 233 DAI_LINK_CS4270, 234 DAI_LINK_AK4104, 235}; 236 237static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = 238{ 239 DAI_LINK_CS4270, 240}; 241 242static struct snd_soc_card snd_soc_raumfeld_connector = { 243 .name = "Raumfeld Connector", 244 .owner = THIS_MODULE, 245 .dai_link = snd_soc_raumfeld_connector_dai, 246 .num_links = ARRAY_SIZE(snd_soc_raumfeld_connector_dai), 247 .suspend_post = raumfeld_analog_suspend, 248 .resume_pre = raumfeld_analog_resume, 249}; 250 251static struct snd_soc_card snd_soc_raumfeld_speaker = { 252 .name = "Raumfeld Speaker", 253 .owner = THIS_MODULE, 254 .dai_link = snd_soc_raumfeld_speaker_dai, 255 .num_links = ARRAY_SIZE(snd_soc_raumfeld_speaker_dai), 256 .suspend_post = raumfeld_analog_suspend, 257 .resume_pre = raumfeld_analog_resume, 258}; 259 260static struct platform_device *raumfeld_audio_device; 261 262static int __init raumfeld_audio_init(void) 263{ 264 int ret; 265 266 if (!machine_is_raumfeld_speaker() && 267 !machine_is_raumfeld_connector()) 268 return 0; 269 270 max9486_client = i2c_new_device(i2c_get_adapter(0), 271 &max9486_hwmon_info); 272 273 if (!max9486_client) 274 return -ENOMEM; 275 276 set_max9485_clk(MAX9485_MCLK_FREQ_122880); 277 278 /* Register analog device */ 279 raumfeld_audio_device = platform_device_alloc("soc-audio", 0); 280 if (!raumfeld_audio_device) 281 return -ENOMEM; 282 283 if (machine_is_raumfeld_speaker()) 284 platform_set_drvdata(raumfeld_audio_device, 285 &snd_soc_raumfeld_speaker); 286 287 if (machine_is_raumfeld_connector()) 288 platform_set_drvdata(raumfeld_audio_device, 289 &snd_soc_raumfeld_connector); 290 291 ret = platform_device_add(raumfeld_audio_device); 292 if (ret < 0) { 293 platform_device_put(raumfeld_audio_device); 294 return ret; 295 } 296 297 raumfeld_enable_audio(true); 298 return 0; 299} 300 301static void __exit raumfeld_audio_exit(void) 302{ 303 raumfeld_enable_audio(false); 304 305 platform_device_unregister(raumfeld_audio_device); 306 307 i2c_unregister_device(max9486_client); 308 309 gpio_free(GPIO_MCLK_RESET); 310 gpio_free(GPIO_CODEC_RESET); 311 gpio_free(GPIO_SPDIF_RESET); 312} 313 314module_init(raumfeld_audio_init); 315module_exit(raumfeld_audio_exit); 316 317/* Module information */ 318MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); 319MODULE_DESCRIPTION("Raumfeld audio SoC"); 320MODULE_LICENSE("GPL"); 321