18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Copyright 2009 Simtec Electronics 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci#include <linux/gpio.h> 68c2ecf20Sopenharmony_ci#include <linux/clk.h> 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <sound/soc.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/platform_data/asoc-s3c24xx_simtec.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include "s3c24xx-i2s.h" 148c2ecf20Sopenharmony_ci#include "s3c24xx_simtec.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistatic struct s3c24xx_audio_simtec_pdata *pdata; 178c2ecf20Sopenharmony_cistatic struct clk *xtal_clk; 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistatic int spk_gain; 208c2ecf20Sopenharmony_cistatic int spk_unmute; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/** 238c2ecf20Sopenharmony_ci * speaker_gain_get - read the speaker gain setting. 248c2ecf20Sopenharmony_ci * @kcontrol: The control for the speaker gain. 258c2ecf20Sopenharmony_ci * @ucontrol: The value that needs to be updated. 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * Read the value for the AMP gain control. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_cistatic int speaker_gain_get(struct snd_kcontrol *kcontrol, 308c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci ucontrol->value.integer.value[0] = spk_gain; 338c2ecf20Sopenharmony_ci return 0; 348c2ecf20Sopenharmony_ci} 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci/** 378c2ecf20Sopenharmony_ci * speaker_gain_set - set the value of the speaker amp gain 388c2ecf20Sopenharmony_ci * @value: The value to write. 398c2ecf20Sopenharmony_ci */ 408c2ecf20Sopenharmony_cistatic void speaker_gain_set(int value) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci gpio_set_value_cansleep(pdata->amp_gain[0], value & 1); 438c2ecf20Sopenharmony_ci gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1); 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/** 478c2ecf20Sopenharmony_ci * speaker_gain_put - set the speaker gain setting. 488c2ecf20Sopenharmony_ci * @kcontrol: The control for the speaker gain. 498c2ecf20Sopenharmony_ci * @ucontrol: The value that needs to be set. 508c2ecf20Sopenharmony_ci * 518c2ecf20Sopenharmony_ci * Set the value of the speaker gain from the specified 528c2ecf20Sopenharmony_ci * @ucontrol setting. 538c2ecf20Sopenharmony_ci * 548c2ecf20Sopenharmony_ci * Note, if the speaker amp is muted, then we do not set a gain value 558c2ecf20Sopenharmony_ci * as at-least one of the ICs that is fitted will try and power up even 568c2ecf20Sopenharmony_ci * if the main control is set to off. 578c2ecf20Sopenharmony_ci */ 588c2ecf20Sopenharmony_cistatic int speaker_gain_put(struct snd_kcontrol *kcontrol, 598c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci int value = ucontrol->value.integer.value[0]; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci spk_gain = value; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (!spk_unmute) 668c2ecf20Sopenharmony_ci speaker_gain_set(value); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return 0; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new amp_gain_controls[] = { 728c2ecf20Sopenharmony_ci SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0, 738c2ecf20Sopenharmony_ci speaker_gain_get, speaker_gain_put), 748c2ecf20Sopenharmony_ci}; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/** 778c2ecf20Sopenharmony_ci * spk_unmute_state - set the unmute state of the speaker 788c2ecf20Sopenharmony_ci * @to: zero to unmute, non-zero to ununmute. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_cistatic void spk_unmute_state(int to) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci pr_debug("%s: to=%d\n", __func__, to); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci spk_unmute = to; 858c2ecf20Sopenharmony_ci gpio_set_value(pdata->amp_gpio, to); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* if we're umuting, also re-set the gain */ 888c2ecf20Sopenharmony_ci if (to && pdata->amp_gain[0] > 0) 898c2ecf20Sopenharmony_ci speaker_gain_set(spk_gain); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci/** 938c2ecf20Sopenharmony_ci * speaker_unmute_get - read the speaker unmute setting. 948c2ecf20Sopenharmony_ci * @kcontrol: The control for the speaker gain. 958c2ecf20Sopenharmony_ci * @ucontrol: The value that needs to be updated. 968c2ecf20Sopenharmony_ci * 978c2ecf20Sopenharmony_ci * Read the value for the AMP gain control. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_cistatic int speaker_unmute_get(struct snd_kcontrol *kcontrol, 1008c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci ucontrol->value.integer.value[0] = spk_unmute; 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/** 1078c2ecf20Sopenharmony_ci * speaker_unmute_put - set the speaker unmute setting. 1088c2ecf20Sopenharmony_ci * @kcontrol: The control for the speaker gain. 1098c2ecf20Sopenharmony_ci * @ucontrol: The value that needs to be set. 1108c2ecf20Sopenharmony_ci * 1118c2ecf20Sopenharmony_ci * Set the value of the speaker gain from the specified 1128c2ecf20Sopenharmony_ci * @ucontrol setting. 1138c2ecf20Sopenharmony_ci */ 1148c2ecf20Sopenharmony_cistatic int speaker_unmute_put(struct snd_kcontrol *kcontrol, 1158c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci spk_unmute_state(ucontrol->value.integer.value[0]); 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci/* This is added as a manual control as the speaker amps create clicks 1228c2ecf20Sopenharmony_ci * when their power state is changed, which are far more noticeable than 1238c2ecf20Sopenharmony_ci * anything produced by the CODEC itself. 1248c2ecf20Sopenharmony_ci */ 1258c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new amp_unmute_controls[] = { 1268c2ecf20Sopenharmony_ci SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0, 1278c2ecf20Sopenharmony_ci speaker_unmute_get, speaker_unmute_put), 1288c2ecf20Sopenharmony_ci}; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_civoid simtec_audio_init(struct snd_soc_pcm_runtime *rtd) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci struct snd_soc_card *card = rtd->card; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci if (pdata->amp_gpio > 0) { 1358c2ecf20Sopenharmony_ci pr_debug("%s: adding amp routes\n", __func__); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci snd_soc_add_card_controls(card, amp_unmute_controls, 1388c2ecf20Sopenharmony_ci ARRAY_SIZE(amp_unmute_controls)); 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (pdata->amp_gain[0] > 0) { 1428c2ecf20Sopenharmony_ci pr_debug("%s: adding amp controls\n", __func__); 1438c2ecf20Sopenharmony_ci snd_soc_add_card_controls(card, amp_gain_controls, 1448c2ecf20Sopenharmony_ci ARRAY_SIZE(amp_gain_controls)); 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(simtec_audio_init); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci#define CODEC_CLOCK 12000000 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci/** 1528c2ecf20Sopenharmony_ci * simtec_hw_params - update hardware parameters 1538c2ecf20Sopenharmony_ci * @substream: The audio substream instance. 1548c2ecf20Sopenharmony_ci * @params: The parameters requested. 1558c2ecf20Sopenharmony_ci * 1568c2ecf20Sopenharmony_ci * Update the codec data routing and configuration settings 1578c2ecf20Sopenharmony_ci * from the supplied data. 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_cistatic int simtec_hw_params(struct snd_pcm_substream *substream, 1608c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 1638c2ecf20Sopenharmony_ci struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 1648c2ecf20Sopenharmony_ci struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); 1658c2ecf20Sopenharmony_ci int ret; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(codec_dai, 0, 1688c2ecf20Sopenharmony_ci CODEC_CLOCK, SND_SOC_CLOCK_IN); 1698c2ecf20Sopenharmony_ci if (ret) { 1708c2ecf20Sopenharmony_ci pr_err( "%s: failed setting codec sysclk\n", __func__); 1718c2ecf20Sopenharmony_ci return ret; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (pdata->use_mpllin) { 1758c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL, 1768c2ecf20Sopenharmony_ci 0, SND_SOC_CLOCK_OUT); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (ret) { 1798c2ecf20Sopenharmony_ci pr_err("%s: failed to set MPLLin as clksrc\n", 1808c2ecf20Sopenharmony_ci __func__); 1818c2ecf20Sopenharmony_ci return ret; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (pdata->output_cdclk) { 1868c2ecf20Sopenharmony_ci int cdclk_scale; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK; 1898c2ecf20Sopenharmony_ci cdclk_scale--; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, 1928c2ecf20Sopenharmony_ci cdclk_scale); 1938c2ecf20Sopenharmony_ci } 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci /* call any board supplied startup code, this currently only 2018c2ecf20Sopenharmony_ci * covers the bast/vr1000 which have a CPLD in the way of the 2028c2ecf20Sopenharmony_ci * LRCLK */ 2038c2ecf20Sopenharmony_ci if (pd->startup) 2048c2ecf20Sopenharmony_ci pd->startup(); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci return 0; 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic const struct snd_soc_ops simtec_snd_ops = { 2108c2ecf20Sopenharmony_ci .hw_params = simtec_hw_params, 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci/** 2148c2ecf20Sopenharmony_ci * attach_gpio_amp - get and configure the necessary gpios 2158c2ecf20Sopenharmony_ci * @dev: The device we're probing. 2168c2ecf20Sopenharmony_ci * @pd: The platform data supplied by the board. 2178c2ecf20Sopenharmony_ci * 2188c2ecf20Sopenharmony_ci * If there is a GPIO based amplifier attached to the board, claim 2198c2ecf20Sopenharmony_ci * the necessary GPIO lines for it, and set default values. 2208c2ecf20Sopenharmony_ci */ 2218c2ecf20Sopenharmony_cistatic int attach_gpio_amp(struct device *dev, 2228c2ecf20Sopenharmony_ci struct s3c24xx_audio_simtec_pdata *pd) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci int ret; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* attach gpio amp gain (if any) */ 2278c2ecf20Sopenharmony_ci if (pdata->amp_gain[0] > 0) { 2288c2ecf20Sopenharmony_ci ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0"); 2298c2ecf20Sopenharmony_ci if (ret) { 2308c2ecf20Sopenharmony_ci dev_err(dev, "cannot get amp gpio gain0\n"); 2318c2ecf20Sopenharmony_ci return ret; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1"); 2358c2ecf20Sopenharmony_ci if (ret) { 2368c2ecf20Sopenharmony_ci dev_err(dev, "cannot get amp gpio gain1\n"); 2378c2ecf20Sopenharmony_ci gpio_free(pdata->amp_gain[0]); 2388c2ecf20Sopenharmony_ci return ret; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci gpio_direction_output(pd->amp_gain[0], 0); 2428c2ecf20Sopenharmony_ci gpio_direction_output(pd->amp_gain[1], 0); 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci /* note, currently we assume GPA0 isn't valid amp */ 2468c2ecf20Sopenharmony_ci if (pdata->amp_gpio > 0) { 2478c2ecf20Sopenharmony_ci ret = gpio_request(pd->amp_gpio, "gpio-amp"); 2488c2ecf20Sopenharmony_ci if (ret) { 2498c2ecf20Sopenharmony_ci dev_err(dev, "cannot get amp gpio %d (%d)\n", 2508c2ecf20Sopenharmony_ci pd->amp_gpio, ret); 2518c2ecf20Sopenharmony_ci goto err_amp; 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* set the amp off at startup */ 2558c2ecf20Sopenharmony_ci spk_unmute_state(0); 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci return 0; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cierr_amp: 2618c2ecf20Sopenharmony_ci if (pd->amp_gain[0] > 0) { 2628c2ecf20Sopenharmony_ci gpio_free(pd->amp_gain[0]); 2638c2ecf20Sopenharmony_ci gpio_free(pd->amp_gain[1]); 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci return ret; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci if (pd->amp_gain[0] > 0) { 2728c2ecf20Sopenharmony_ci gpio_free(pd->amp_gain[0]); 2738c2ecf20Sopenharmony_ci gpio_free(pd->amp_gain[1]); 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci if (pd->amp_gpio > 0) 2778c2ecf20Sopenharmony_ci gpio_free(pd->amp_gpio); 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2818c2ecf20Sopenharmony_cistatic int simtec_audio_resume(struct device *dev) 2828c2ecf20Sopenharmony_ci{ 2838c2ecf20Sopenharmony_ci simtec_call_startup(pdata); 2848c2ecf20Sopenharmony_ci return 0; 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ciconst struct dev_pm_ops simtec_audio_pmops = { 2888c2ecf20Sopenharmony_ci .resume = simtec_audio_resume, 2898c2ecf20Sopenharmony_ci}; 2908c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(simtec_audio_pmops); 2918c2ecf20Sopenharmony_ci#endif 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ciint simtec_audio_core_probe(struct platform_device *pdev, 2948c2ecf20Sopenharmony_ci struct snd_soc_card *card) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci struct platform_device *snd_dev; 2978c2ecf20Sopenharmony_ci int ret; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci card->dai_link->ops = &simtec_snd_ops; 3008c2ecf20Sopenharmony_ci card->dai_link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 3018c2ecf20Sopenharmony_ci SND_SOC_DAIFMT_CBM_CFM; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci pdata = pdev->dev.platform_data; 3048c2ecf20Sopenharmony_ci if (!pdata) { 3058c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "no platform data supplied\n"); 3068c2ecf20Sopenharmony_ci return -EINVAL; 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci simtec_call_startup(pdata); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci xtal_clk = clk_get(&pdev->dev, "xtal"); 3128c2ecf20Sopenharmony_ci if (IS_ERR(xtal_clk)) { 3138c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "could not get clkout0\n"); 3148c2ecf20Sopenharmony_ci return -EINVAL; 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk)); 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci ret = attach_gpio_amp(&pdev->dev, pdata); 3208c2ecf20Sopenharmony_ci if (ret) 3218c2ecf20Sopenharmony_ci goto err_clk; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci snd_dev = platform_device_alloc("soc-audio", -1); 3248c2ecf20Sopenharmony_ci if (!snd_dev) { 3258c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n"); 3268c2ecf20Sopenharmony_ci ret = -ENOMEM; 3278c2ecf20Sopenharmony_ci goto err_gpio; 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci platform_set_drvdata(snd_dev, card); 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci ret = platform_device_add(snd_dev); 3338c2ecf20Sopenharmony_ci if (ret) { 3348c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to add soc-audio dev\n"); 3358c2ecf20Sopenharmony_ci goto err_pdev; 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, snd_dev); 3398c2ecf20Sopenharmony_ci return 0; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_cierr_pdev: 3428c2ecf20Sopenharmony_ci platform_device_put(snd_dev); 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_cierr_gpio: 3458c2ecf20Sopenharmony_ci detach_gpio_amp(pdata); 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_cierr_clk: 3488c2ecf20Sopenharmony_ci clk_put(xtal_clk); 3498c2ecf20Sopenharmony_ci return ret; 3508c2ecf20Sopenharmony_ci} 3518c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(simtec_audio_core_probe); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ciint simtec_audio_remove(struct platform_device *pdev) 3548c2ecf20Sopenharmony_ci{ 3558c2ecf20Sopenharmony_ci struct platform_device *snd_dev = platform_get_drvdata(pdev); 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci platform_device_unregister(snd_dev); 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci detach_gpio_amp(pdata); 3608c2ecf20Sopenharmony_ci clk_put(xtal_clk); 3618c2ecf20Sopenharmony_ci return 0; 3628c2ecf20Sopenharmony_ci} 3638c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(simtec_audio_remove); 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); 3668c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA SoC Simtec Audio common support"); 3678c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 368