1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * n810.c -- SoC audio for Nokia N810 4 * 5 * Copyright (C) 2008 Nokia Corporation 6 * 7 * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com> 8 */ 9 10 #include <linux/clk.h> 11 #include <linux/i2c.h> 12 #include <linux/platform_device.h> 13 #include <sound/core.h> 14 #include <sound/pcm.h> 15 #include <sound/soc.h> 16 17 #include <asm/mach-types.h> 18 #include <linux/gpio/consumer.h> 19 #include <linux/module.h> 20 #include <linux/platform_data/asoc-ti-mcbsp.h> 21 22 #include "omap-mcbsp.h" 23 24 static struct gpio_desc *n810_headset_amp; 25 static struct gpio_desc *n810_speaker_amp; 26 27 enum { 28 N810_JACK_DISABLED, 29 N810_JACK_HP, 30 N810_JACK_HS, 31 N810_JACK_MIC, 32 }; 33 34 static struct clk *sys_clkout2; 35 static struct clk *sys_clkout2_src; 36 static struct clk *func96m_clk; 37 38 static int n810_spk_func; 39 static int n810_jack_func; 40 static int n810_dmic_func; 41 42 static void n810_ext_control(struct snd_soc_dapm_context *dapm) 43 { 44 int hp = 0, line1l = 0; 45 46 switch (n810_jack_func) { 47 case N810_JACK_HS: 48 line1l = 1; 49 fallthrough; 50 case N810_JACK_HP: 51 hp = 1; 52 break; 53 case N810_JACK_MIC: 54 line1l = 1; 55 break; 56 } 57 58 snd_soc_dapm_mutex_lock(dapm); 59 60 if (n810_spk_func) 61 snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk"); 62 else 63 snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk"); 64 65 if (hp) 66 snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); 67 else 68 snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); 69 if (line1l) 70 snd_soc_dapm_enable_pin_unlocked(dapm, "HS Mic"); 71 else 72 snd_soc_dapm_disable_pin_unlocked(dapm, "HS Mic"); 73 74 if (n810_dmic_func) 75 snd_soc_dapm_enable_pin_unlocked(dapm, "DMic"); 76 else 77 snd_soc_dapm_disable_pin_unlocked(dapm, "DMic"); 78 79 snd_soc_dapm_sync_unlocked(dapm); 80 81 snd_soc_dapm_mutex_unlock(dapm); 82 } 83 84 static int n810_startup(struct snd_pcm_substream *substream) 85 { 86 struct snd_pcm_runtime *runtime = substream->runtime; 87 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 88 89 snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); 90 91 n810_ext_control(&rtd->card->dapm); 92 return clk_prepare_enable(sys_clkout2); 93 } 94 95 static void n810_shutdown(struct snd_pcm_substream *substream) 96 { 97 clk_disable_unprepare(sys_clkout2); 98 } 99 100 static int n810_hw_params(struct snd_pcm_substream *substream, 101 struct snd_pcm_hw_params *params) 102 { 103 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 104 struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); 105 int err; 106 107 /* Set the codec system clock for DAC and ADC */ 108 err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000, 109 SND_SOC_CLOCK_IN); 110 111 return err; 112 } 113 114 static const struct snd_soc_ops n810_ops = { 115 .startup = n810_startup, 116 .hw_params = n810_hw_params, 117 .shutdown = n810_shutdown, 118 }; 119 120 static int n810_get_spk(struct snd_kcontrol *kcontrol, 121 struct snd_ctl_elem_value *ucontrol) 122 { 123 ucontrol->value.enumerated.item[0] = n810_spk_func; 124 125 return 0; 126 } 127 128 static int n810_set_spk(struct snd_kcontrol *kcontrol, 129 struct snd_ctl_elem_value *ucontrol) 130 { 131 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 132 133 if (n810_spk_func == ucontrol->value.enumerated.item[0]) 134 return 0; 135 136 n810_spk_func = ucontrol->value.enumerated.item[0]; 137 n810_ext_control(&card->dapm); 138 139 return 1; 140 } 141 142 static int n810_get_jack(struct snd_kcontrol *kcontrol, 143 struct snd_ctl_elem_value *ucontrol) 144 { 145 ucontrol->value.enumerated.item[0] = n810_jack_func; 146 147 return 0; 148 } 149 150 static int n810_set_jack(struct snd_kcontrol *kcontrol, 151 struct snd_ctl_elem_value *ucontrol) 152 { 153 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 154 155 if (n810_jack_func == ucontrol->value.enumerated.item[0]) 156 return 0; 157 158 n810_jack_func = ucontrol->value.enumerated.item[0]; 159 n810_ext_control(&card->dapm); 160 161 return 1; 162 } 163 164 static int n810_get_input(struct snd_kcontrol *kcontrol, 165 struct snd_ctl_elem_value *ucontrol) 166 { 167 ucontrol->value.enumerated.item[0] = n810_dmic_func; 168 169 return 0; 170 } 171 172 static int n810_set_input(struct snd_kcontrol *kcontrol, 173 struct snd_ctl_elem_value *ucontrol) 174 { 175 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 176 177 if (n810_dmic_func == ucontrol->value.enumerated.item[0]) 178 return 0; 179 180 n810_dmic_func = ucontrol->value.enumerated.item[0]; 181 n810_ext_control(&card->dapm); 182 183 return 1; 184 } 185 186 static int n810_spk_event(struct snd_soc_dapm_widget *w, 187 struct snd_kcontrol *k, int event) 188 { 189 if (SND_SOC_DAPM_EVENT_ON(event)) 190 gpiod_set_value(n810_speaker_amp, 1); 191 else 192 gpiod_set_value(n810_speaker_amp, 0); 193 194 return 0; 195 } 196 197 static int n810_jack_event(struct snd_soc_dapm_widget *w, 198 struct snd_kcontrol *k, int event) 199 { 200 if (SND_SOC_DAPM_EVENT_ON(event)) 201 gpiod_set_value(n810_headset_amp, 1); 202 else 203 gpiod_set_value(n810_headset_amp, 0); 204 205 return 0; 206 } 207 208 static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = { 209 SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event), 210 SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event), 211 SND_SOC_DAPM_MIC("DMic", NULL), 212 SND_SOC_DAPM_MIC("HS Mic", NULL), 213 }; 214 215 static const struct snd_soc_dapm_route audio_map[] = { 216 {"Headphone Jack", NULL, "HPLOUT"}, 217 {"Headphone Jack", NULL, "HPROUT"}, 218 219 {"Ext Spk", NULL, "LLOUT"}, 220 {"Ext Spk", NULL, "RLOUT"}, 221 222 {"DMic Rate 64", NULL, "DMic"}, 223 {"DMic", NULL, "Mic Bias"}, 224 225 /* 226 * Note that the mic bias is coming from Retu/Vilma and we don't have 227 * control over it atm. The analog HS mic is not working. <- TODO 228 */ 229 {"LINE1L", NULL, "HS Mic"}, 230 }; 231 232 static const char *spk_function[] = {"Off", "On"}; 233 static const char *jack_function[] = {"Off", "Headphone", "Headset", "Mic"}; 234 static const char *input_function[] = {"ADC", "Digital Mic"}; 235 static const struct soc_enum n810_enum[] = { 236 SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), 237 SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), 238 SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), 239 }; 240 241 static const struct snd_kcontrol_new aic33_n810_controls[] = { 242 SOC_ENUM_EXT("Speaker Function", n810_enum[0], 243 n810_get_spk, n810_set_spk), 244 SOC_ENUM_EXT("Jack Function", n810_enum[1], 245 n810_get_jack, n810_set_jack), 246 SOC_ENUM_EXT("Input Select", n810_enum[2], 247 n810_get_input, n810_set_input), 248 }; 249 250 /* Digital audio interface glue - connects codec <--> CPU */ 251 SND_SOC_DAILINK_DEFS(aic33, 252 DAILINK_COMP_ARRAY(COMP_CPU("48076000.mcbsp")), 253 DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x-codec.1-0018", 254 "tlv320aic3x-hifi")), 255 DAILINK_COMP_ARRAY(COMP_PLATFORM("48076000.mcbsp"))); 256 257 static struct snd_soc_dai_link n810_dai = { 258 .name = "TLV320AIC33", 259 .stream_name = "AIC33", 260 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 261 SND_SOC_DAIFMT_CBM_CFM, 262 .ops = &n810_ops, 263 SND_SOC_DAILINK_REG(aic33), 264 }; 265 266 /* Audio machine driver */ 267 static struct snd_soc_card snd_soc_n810 = { 268 .name = "N810", 269 .owner = THIS_MODULE, 270 .dai_link = &n810_dai, 271 .num_links = 1, 272 273 .controls = aic33_n810_controls, 274 .num_controls = ARRAY_SIZE(aic33_n810_controls), 275 .dapm_widgets = aic33_dapm_widgets, 276 .num_dapm_widgets = ARRAY_SIZE(aic33_dapm_widgets), 277 .dapm_routes = audio_map, 278 .num_dapm_routes = ARRAY_SIZE(audio_map), 279 .fully_routed = true, 280 }; 281 282 static struct platform_device *n810_snd_device; 283 284 static int __init n810_soc_init(void) 285 { 286 int err; 287 struct device *dev; 288 289 if (!of_have_populated_dt() || 290 (!of_machine_is_compatible("nokia,n810") && 291 !of_machine_is_compatible("nokia,n810-wimax"))) 292 return -ENODEV; 293 294 n810_snd_device = platform_device_alloc("soc-audio", -1); 295 if (!n810_snd_device) 296 return -ENOMEM; 297 298 platform_set_drvdata(n810_snd_device, &snd_soc_n810); 299 err = platform_device_add(n810_snd_device); 300 if (err) 301 goto err1; 302 303 dev = &n810_snd_device->dev; 304 305 sys_clkout2_src = clk_get(dev, "sys_clkout2_src"); 306 if (IS_ERR(sys_clkout2_src)) { 307 dev_err(dev, "Could not get sys_clkout2_src clock\n"); 308 err = PTR_ERR(sys_clkout2_src); 309 goto err2; 310 } 311 sys_clkout2 = clk_get(dev, "sys_clkout2"); 312 if (IS_ERR(sys_clkout2)) { 313 dev_err(dev, "Could not get sys_clkout2\n"); 314 err = PTR_ERR(sys_clkout2); 315 goto err3; 316 } 317 /* 318 * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use 319 * 96 MHz as its parent in order to get 12 MHz 320 */ 321 func96m_clk = clk_get(dev, "func_96m_ck"); 322 if (IS_ERR(func96m_clk)) { 323 dev_err(dev, "Could not get func 96M clock\n"); 324 err = PTR_ERR(func96m_clk); 325 goto err4; 326 } 327 clk_set_parent(sys_clkout2_src, func96m_clk); 328 clk_set_rate(sys_clkout2, 12000000); 329 330 n810_headset_amp = devm_gpiod_get(&n810_snd_device->dev, 331 "headphone", GPIOD_OUT_LOW); 332 if (IS_ERR(n810_headset_amp)) { 333 err = PTR_ERR(n810_headset_amp); 334 goto err4; 335 } 336 337 n810_speaker_amp = devm_gpiod_get(&n810_snd_device->dev, 338 "speaker", GPIOD_OUT_LOW); 339 if (IS_ERR(n810_speaker_amp)) { 340 err = PTR_ERR(n810_speaker_amp); 341 goto err4; 342 } 343 344 return 0; 345 err4: 346 clk_put(sys_clkout2); 347 err3: 348 clk_put(sys_clkout2_src); 349 err2: 350 platform_device_del(n810_snd_device); 351 err1: 352 platform_device_put(n810_snd_device); 353 354 return err; 355 } 356 357 static void __exit n810_soc_exit(void) 358 { 359 clk_put(sys_clkout2_src); 360 clk_put(sys_clkout2); 361 clk_put(func96m_clk); 362 363 platform_device_unregister(n810_snd_device); 364 } 365 366 module_init(n810_soc_init); 367 module_exit(n810_soc_exit); 368 369 MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>"); 370 MODULE_DESCRIPTION("ALSA SoC Nokia N810"); 371 MODULE_LICENSE("GPL"); 372
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.