| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 1 | /* | 
 | 2 |  * h1940-uda1380.c  --  ALSA Soc Audio Layer | 
 | 3 |  * | 
 | 4 |  * Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org> | 
 | 5 |  * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> | 
 | 6 |  * | 
 | 7 |  * Based on version from Arnaud Patard <arnaud.patard@rtp-net.org> | 
 | 8 |  * | 
 | 9 |  * This program is free software; you can redistribute  it and/or modify it | 
 | 10 |  * under  the terms of  the GNU General  Public License as published by the | 
 | 11 |  * Free Software Foundation;  either version 2 of the  License, or (at your | 
 | 12 |  * option) any later version. | 
 | 13 |  * | 
 | 14 |  */ | 
 | 15 |  | 
| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 16 | #include <linux/gpio.h> | 
 | 17 |  | 
 | 18 | #include <sound/soc.h> | 
| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 19 | #include <sound/jack.h> | 
 | 20 |  | 
 | 21 | #include <plat/regs-iis.h> | 
| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 22 | #include <mach/h1940-latch.h> | 
| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 23 | #include <asm/mach-types.h> | 
 | 24 |  | 
| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 25 | #include "s3c24xx-i2s.h" | 
| Vasily Khoruzhick | 1957668 | 2010-12-09 21:17:56 +0200 | [diff] [blame] | 26 |  | 
 | 27 | static unsigned int rates[] = { | 
 | 28 | 	11025, | 
 | 29 | 	22050, | 
 | 30 | 	44100, | 
 | 31 | }; | 
 | 32 |  | 
 | 33 | static struct snd_pcm_hw_constraint_list hw_rates = { | 
 | 34 | 	.count = ARRAY_SIZE(rates), | 
 | 35 | 	.list = rates, | 
 | 36 | 	.mask = 0, | 
 | 37 | }; | 
 | 38 |  | 
 | 39 | static struct snd_soc_jack hp_jack; | 
 | 40 |  | 
 | 41 | static struct snd_soc_jack_pin hp_jack_pins[] = { | 
 | 42 | 	{ | 
 | 43 | 		.pin	= "Headphone Jack", | 
 | 44 | 		.mask	= SND_JACK_HEADPHONE, | 
 | 45 | 	}, | 
 | 46 | 	{ | 
 | 47 | 		.pin	= "Speaker", | 
 | 48 | 		.mask	= SND_JACK_HEADPHONE, | 
 | 49 | 		.invert	= 1, | 
 | 50 | 	}, | 
 | 51 | }; | 
 | 52 |  | 
 | 53 | static struct snd_soc_jack_gpio hp_jack_gpios[] = { | 
 | 54 | 	{ | 
 | 55 | 		.gpio			= S3C2410_GPG(4), | 
 | 56 | 		.name			= "hp-gpio", | 
 | 57 | 		.report			= SND_JACK_HEADPHONE, | 
 | 58 | 		.invert			= 1, | 
 | 59 | 		.debounce_time		= 200, | 
 | 60 | 	}, | 
 | 61 | }; | 
 | 62 |  | 
 | 63 | static int h1940_startup(struct snd_pcm_substream *substream) | 
 | 64 | { | 
 | 65 | 	struct snd_pcm_runtime *runtime = substream->runtime; | 
 | 66 |  | 
 | 67 | 	runtime->hw.rate_min = hw_rates.list[0]; | 
 | 68 | 	runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1]; | 
 | 69 | 	runtime->hw.rates = SNDRV_PCM_RATE_KNOT; | 
 | 70 |  | 
 | 71 | 	return snd_pcm_hw_constraint_list(runtime, 0, | 
 | 72 | 					SNDRV_PCM_HW_PARAM_RATE, | 
 | 73 | 					&hw_rates); | 
 | 74 | } | 
 | 75 |  | 
 | 76 | static int h1940_hw_params(struct snd_pcm_substream *substream, | 
 | 77 | 				struct snd_pcm_hw_params *params) | 
 | 78 | { | 
 | 79 | 	struct snd_soc_pcm_runtime *rtd = substream->private_data; | 
 | 80 | 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | 
 | 81 | 	struct snd_soc_dai *codec_dai = rtd->codec_dai; | 
 | 82 | 	int div; | 
 | 83 | 	int ret; | 
 | 84 | 	unsigned int rate = params_rate(params); | 
 | 85 |  | 
 | 86 | 	switch (rate) { | 
 | 87 | 	case 11025: | 
 | 88 | 	case 22050: | 
 | 89 | 	case 44100: | 
 | 90 | 		div = s3c24xx_i2s_get_clockrate() / (384 * rate); | 
 | 91 | 		if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate)) | 
 | 92 | 			div++; | 
 | 93 | 		break; | 
 | 94 | 	default: | 
 | 95 | 		dev_err(&rtd->dev, "%s: rate %d is not supported\n", | 
 | 96 | 			__func__, rate); | 
 | 97 | 		return -EINVAL; | 
 | 98 | 	} | 
 | 99 |  | 
 | 100 | 	/* set codec DAI configuration */ | 
 | 101 | 	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | | 
 | 102 | 		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | 
 | 103 | 	if (ret < 0) | 
 | 104 | 		return ret; | 
 | 105 |  | 
 | 106 | 	/* set cpu DAI configuration */ | 
 | 107 | 	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | | 
 | 108 | 		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | 
 | 109 | 	if (ret < 0) | 
 | 110 | 		return ret; | 
 | 111 |  | 
 | 112 | 	/* select clock source */ | 
 | 113 | 	ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate, | 
 | 114 | 			SND_SOC_CLOCK_OUT); | 
 | 115 | 	if (ret < 0) | 
 | 116 | 		return ret; | 
 | 117 |  | 
 | 118 | 	/* set MCLK division for sample rate */ | 
 | 119 | 	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, | 
 | 120 | 		S3C2410_IISMOD_384FS); | 
 | 121 | 	if (ret < 0) | 
 | 122 | 		return ret; | 
 | 123 |  | 
 | 124 | 	/* set BCLK division for sample rate */ | 
 | 125 | 	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, | 
 | 126 | 		S3C2410_IISMOD_32FS); | 
 | 127 | 	if (ret < 0) | 
 | 128 | 		return ret; | 
 | 129 |  | 
 | 130 | 	/* set prescaler division for sample rate */ | 
 | 131 | 	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, | 
 | 132 | 		S3C24XX_PRESCALE(div, div)); | 
 | 133 | 	if (ret < 0) | 
 | 134 | 		return ret; | 
 | 135 |  | 
 | 136 | 	return 0; | 
 | 137 | } | 
 | 138 |  | 
 | 139 | static struct snd_soc_ops h1940_ops = { | 
 | 140 | 	.startup	= h1940_startup, | 
 | 141 | 	.hw_params	= h1940_hw_params, | 
 | 142 | }; | 
 | 143 |  | 
 | 144 | static int h1940_spk_power(struct snd_soc_dapm_widget *w, | 
 | 145 | 				struct snd_kcontrol *kcontrol, int event) | 
 | 146 | { | 
 | 147 | 	if (SND_SOC_DAPM_EVENT_ON(event)) | 
 | 148 | 		gpio_set_value(H1940_LATCH_AUDIO_POWER, 1); | 
 | 149 | 	else | 
 | 150 | 		gpio_set_value(H1940_LATCH_AUDIO_POWER, 0); | 
 | 151 |  | 
 | 152 | 	return 0; | 
 | 153 | } | 
 | 154 |  | 
 | 155 | /* h1940 machine dapm widgets */ | 
 | 156 | static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { | 
 | 157 | 	SND_SOC_DAPM_HP("Headphone Jack", NULL), | 
 | 158 | 	SND_SOC_DAPM_MIC("Mic Jack", NULL), | 
 | 159 | 	SND_SOC_DAPM_SPK("Speaker", h1940_spk_power), | 
 | 160 | }; | 
 | 161 |  | 
 | 162 | /* h1940 machine audio_map */ | 
 | 163 | static const struct snd_soc_dapm_route audio_map[] = { | 
 | 164 | 	/* headphone connected to VOUTLHP, VOUTRHP */ | 
 | 165 | 	{"Headphone Jack", NULL, "VOUTLHP"}, | 
 | 166 | 	{"Headphone Jack", NULL, "VOUTRHP"}, | 
 | 167 |  | 
 | 168 | 	/* ext speaker connected to VOUTL, VOUTR  */ | 
 | 169 | 	{"Speaker", NULL, "VOUTL"}, | 
 | 170 | 	{"Speaker", NULL, "VOUTR"}, | 
 | 171 |  | 
 | 172 | 	/* mic is connected to VINM */ | 
 | 173 | 	{"VINM", NULL, "Mic Jack"}, | 
 | 174 | }; | 
 | 175 |  | 
 | 176 | static struct platform_device *s3c24xx_snd_device; | 
 | 177 |  | 
 | 178 | static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd) | 
 | 179 | { | 
 | 180 | 	struct snd_soc_codec *codec = rtd->codec; | 
 | 181 | 	struct snd_soc_dapm_context *dapm = &codec->dapm; | 
 | 182 | 	int err; | 
 | 183 |  | 
 | 184 | 	/* Add h1940 specific widgets */ | 
 | 185 | 	err = snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets, | 
 | 186 | 				  ARRAY_SIZE(uda1380_dapm_widgets)); | 
 | 187 | 	if (err) | 
 | 188 | 		return err; | 
 | 189 |  | 
 | 190 | 	/* Set up h1940 specific audio path audio_mapnects */ | 
 | 191 | 	err = snd_soc_dapm_add_routes(dapm, audio_map, | 
 | 192 | 				      ARRAY_SIZE(audio_map)); | 
 | 193 | 	if (err) | 
 | 194 | 		return err; | 
 | 195 |  | 
 | 196 | 	snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); | 
 | 197 | 	snd_soc_dapm_enable_pin(dapm, "Speaker"); | 
 | 198 | 	snd_soc_dapm_enable_pin(dapm, "Mic Jack"); | 
 | 199 |  | 
 | 200 | 	snd_soc_dapm_sync(dapm); | 
 | 201 |  | 
 | 202 | 	snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, | 
 | 203 | 		&hp_jack); | 
 | 204 |  | 
 | 205 | 	snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), | 
 | 206 | 		hp_jack_pins); | 
 | 207 |  | 
 | 208 | 	snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), | 
 | 209 | 		hp_jack_gpios); | 
 | 210 |  | 
 | 211 | 	return 0; | 
 | 212 | } | 
 | 213 |  | 
 | 214 | /* s3c24xx digital audio interface glue - connects codec <--> CPU */ | 
 | 215 | static struct snd_soc_dai_link h1940_uda1380_dai[] = { | 
 | 216 | 	{ | 
 | 217 | 		.name		= "uda1380", | 
 | 218 | 		.stream_name	= "UDA1380 Duplex", | 
 | 219 | 		.cpu_dai_name	= "s3c24xx-iis", | 
 | 220 | 		.codec_dai_name	= "uda1380-hifi", | 
 | 221 | 		.init		= h1940_uda1380_init, | 
 | 222 | 		.platform_name	= "samsung-audio", | 
 | 223 | 		.codec_name	= "uda1380-codec.0-001a", | 
 | 224 | 		.ops		= &h1940_ops, | 
 | 225 | 	}, | 
 | 226 | }; | 
 | 227 |  | 
 | 228 | static struct snd_soc_card h1940_asoc = { | 
 | 229 | 	.name = "h1940", | 
 | 230 | 	.dai_link = h1940_uda1380_dai, | 
 | 231 | 	.num_links = ARRAY_SIZE(h1940_uda1380_dai), | 
 | 232 | }; | 
 | 233 |  | 
 | 234 | static int __init h1940_init(void) | 
 | 235 | { | 
 | 236 | 	int ret; | 
 | 237 |  | 
 | 238 | 	if (!machine_is_h1940()) | 
 | 239 | 		return -ENODEV; | 
 | 240 |  | 
 | 241 | 	/* configure some gpios */ | 
 | 242 | 	ret = gpio_request(H1940_LATCH_AUDIO_POWER, "speaker-power"); | 
 | 243 | 	if (ret) | 
 | 244 | 		goto err_out; | 
 | 245 |  | 
 | 246 | 	ret = gpio_direction_output(H1940_LATCH_AUDIO_POWER, 0); | 
 | 247 | 	if (ret) | 
 | 248 | 		goto err_gpio; | 
 | 249 |  | 
 | 250 | 	s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); | 
 | 251 | 	if (!s3c24xx_snd_device) { | 
 | 252 | 		ret = -ENOMEM; | 
 | 253 | 		goto err_gpio; | 
 | 254 | 	} | 
 | 255 |  | 
 | 256 | 	platform_set_drvdata(s3c24xx_snd_device, &h1940_asoc); | 
 | 257 | 	ret = platform_device_add(s3c24xx_snd_device); | 
 | 258 |  | 
 | 259 | 	if (ret) | 
 | 260 | 		goto err_plat; | 
 | 261 |  | 
 | 262 | 	return 0; | 
 | 263 |  | 
 | 264 | err_plat: | 
 | 265 | 	platform_device_put(s3c24xx_snd_device); | 
 | 266 | err_gpio: | 
 | 267 | 	gpio_free(H1940_LATCH_AUDIO_POWER); | 
 | 268 |  | 
 | 269 | err_out: | 
 | 270 | 	return ret; | 
 | 271 | } | 
 | 272 |  | 
 | 273 | static void __exit h1940_exit(void) | 
 | 274 | { | 
 | 275 | 	platform_device_unregister(s3c24xx_snd_device); | 
 | 276 | 	snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), | 
 | 277 | 		hp_jack_gpios); | 
 | 278 | 	gpio_free(H1940_LATCH_AUDIO_POWER); | 
 | 279 | } | 
 | 280 |  | 
 | 281 | module_init(h1940_init); | 
 | 282 | module_exit(h1940_exit); | 
 | 283 |  | 
 | 284 | /* Module information */ | 
 | 285 | MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick"); | 
 | 286 | MODULE_DESCRIPTION("ALSA SoC H1940"); | 
 | 287 | MODULE_LICENSE("GPL"); |