mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
8.7 KiB
372 lines
8.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// |
|
// Copyright 2009 Simtec Electronics |
|
|
|
#include <linux/gpio.h> |
|
#include <linux/clk.h> |
|
#include <linux/module.h> |
|
|
|
#include <sound/soc.h> |
|
|
|
#include <linux/platform_data/asoc-s3c24xx_simtec.h> |
|
|
|
#include "s3c24xx-i2s.h" |
|
#include "s3c24xx_simtec.h" |
|
|
|
static struct s3c24xx_audio_simtec_pdata *pdata; |
|
static struct clk *xtal_clk; |
|
|
|
static int spk_gain; |
|
static int spk_unmute; |
|
|
|
/** |
|
* speaker_gain_get - read the speaker gain setting. |
|
* @kcontrol: The control for the speaker gain. |
|
* @ucontrol: The value that needs to be updated. |
|
* |
|
* Read the value for the AMP gain control. |
|
*/ |
|
static int speaker_gain_get(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
ucontrol->value.integer.value[0] = spk_gain; |
|
return 0; |
|
} |
|
|
|
/** |
|
* speaker_gain_set - set the value of the speaker amp gain |
|
* @value: The value to write. |
|
*/ |
|
static void speaker_gain_set(int value) |
|
{ |
|
gpio_set_value_cansleep(pdata->amp_gain[0], value & 1); |
|
gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1); |
|
} |
|
|
|
/** |
|
* speaker_gain_put - set the speaker gain setting. |
|
* @kcontrol: The control for the speaker gain. |
|
* @ucontrol: The value that needs to be set. |
|
* |
|
* Set the value of the speaker gain from the specified |
|
* @ucontrol setting. |
|
* |
|
* Note, if the speaker amp is muted, then we do not set a gain value |
|
* as at-least one of the ICs that is fitted will try and power up even |
|
* if the main control is set to off. |
|
*/ |
|
static int speaker_gain_put(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
int value = ucontrol->value.integer.value[0]; |
|
|
|
spk_gain = value; |
|
|
|
if (!spk_unmute) |
|
speaker_gain_set(value); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct snd_kcontrol_new amp_gain_controls[] = { |
|
SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0, |
|
speaker_gain_get, speaker_gain_put), |
|
}; |
|
|
|
/** |
|
* spk_unmute_state - set the unmute state of the speaker |
|
* @to: zero to unmute, non-zero to ununmute. |
|
*/ |
|
static void spk_unmute_state(int to) |
|
{ |
|
pr_debug("%s: to=%d\n", __func__, to); |
|
|
|
spk_unmute = to; |
|
gpio_set_value(pdata->amp_gpio, to); |
|
|
|
/* if we're umuting, also re-set the gain */ |
|
if (to && pdata->amp_gain[0] > 0) |
|
speaker_gain_set(spk_gain); |
|
} |
|
|
|
/** |
|
* speaker_unmute_get - read the speaker unmute setting. |
|
* @kcontrol: The control for the speaker gain. |
|
* @ucontrol: The value that needs to be updated. |
|
* |
|
* Read the value for the AMP gain control. |
|
*/ |
|
static int speaker_unmute_get(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
ucontrol->value.integer.value[0] = spk_unmute; |
|
return 0; |
|
} |
|
|
|
/** |
|
* speaker_unmute_put - set the speaker unmute setting. |
|
* @kcontrol: The control for the speaker gain. |
|
* @ucontrol: The value that needs to be set. |
|
* |
|
* Set the value of the speaker gain from the specified |
|
* @ucontrol setting. |
|
*/ |
|
static int speaker_unmute_put(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
spk_unmute_state(ucontrol->value.integer.value[0]); |
|
return 0; |
|
} |
|
|
|
/* This is added as a manual control as the speaker amps create clicks |
|
* when their power state is changed, which are far more noticeable than |
|
* anything produced by the CODEC itself. |
|
*/ |
|
static const struct snd_kcontrol_new amp_unmute_controls[] = { |
|
SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0, |
|
speaker_unmute_get, speaker_unmute_put), |
|
}; |
|
|
|
void simtec_audio_init(struct snd_soc_pcm_runtime *rtd) |
|
{ |
|
struct snd_soc_card *card = rtd->card; |
|
|
|
if (pdata->amp_gpio > 0) { |
|
pr_debug("%s: adding amp routes\n", __func__); |
|
|
|
snd_soc_add_card_controls(card, amp_unmute_controls, |
|
ARRAY_SIZE(amp_unmute_controls)); |
|
} |
|
|
|
if (pdata->amp_gain[0] > 0) { |
|
pr_debug("%s: adding amp controls\n", __func__); |
|
snd_soc_add_card_controls(card, amp_gain_controls, |
|
ARRAY_SIZE(amp_gain_controls)); |
|
} |
|
} |
|
EXPORT_SYMBOL_GPL(simtec_audio_init); |
|
|
|
#define CODEC_CLOCK 12000000 |
|
|
|
/** |
|
* simtec_hw_params - update hardware parameters |
|
* @substream: The audio substream instance. |
|
* @params: The parameters requested. |
|
* |
|
* Update the codec data routing and configuration settings |
|
* from the supplied data. |
|
*/ |
|
static int simtec_hw_params(struct snd_pcm_substream *substream, |
|
struct snd_pcm_hw_params *params) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
|
int ret; |
|
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, |
|
CODEC_CLOCK, SND_SOC_CLOCK_IN); |
|
if (ret) { |
|
pr_err( "%s: failed setting codec sysclk\n", __func__); |
|
return ret; |
|
} |
|
|
|
if (pdata->use_mpllin) { |
|
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL, |
|
0, SND_SOC_CLOCK_OUT); |
|
|
|
if (ret) { |
|
pr_err("%s: failed to set MPLLin as clksrc\n", |
|
__func__); |
|
return ret; |
|
} |
|
} |
|
|
|
if (pdata->output_cdclk) { |
|
int cdclk_scale; |
|
|
|
cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK; |
|
cdclk_scale--; |
|
|
|
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, |
|
cdclk_scale); |
|
if (ret) { |
|
pr_err("%s: failed to set clock div\n", |
|
__func__); |
|
return ret; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd) |
|
{ |
|
/* call any board supplied startup code, this currently only |
|
* covers the bast/vr1000 which have a CPLD in the way of the |
|
* LRCLK */ |
|
if (pd->startup) |
|
pd->startup(); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct snd_soc_ops simtec_snd_ops = { |
|
.hw_params = simtec_hw_params, |
|
}; |
|
|
|
/** |
|
* attach_gpio_amp - get and configure the necessary gpios |
|
* @dev: The device we're probing. |
|
* @pd: The platform data supplied by the board. |
|
* |
|
* If there is a GPIO based amplifier attached to the board, claim |
|
* the necessary GPIO lines for it, and set default values. |
|
*/ |
|
static int attach_gpio_amp(struct device *dev, |
|
struct s3c24xx_audio_simtec_pdata *pd) |
|
{ |
|
int ret; |
|
|
|
/* attach gpio amp gain (if any) */ |
|
if (pdata->amp_gain[0] > 0) { |
|
ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0"); |
|
if (ret) { |
|
dev_err(dev, "cannot get amp gpio gain0\n"); |
|
return ret; |
|
} |
|
|
|
ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1"); |
|
if (ret) { |
|
dev_err(dev, "cannot get amp gpio gain1\n"); |
|
gpio_free(pdata->amp_gain[0]); |
|
return ret; |
|
} |
|
|
|
gpio_direction_output(pd->amp_gain[0], 0); |
|
gpio_direction_output(pd->amp_gain[1], 0); |
|
} |
|
|
|
/* note, currently we assume GPA0 isn't valid amp */ |
|
if (pdata->amp_gpio > 0) { |
|
ret = gpio_request(pd->amp_gpio, "gpio-amp"); |
|
if (ret) { |
|
dev_err(dev, "cannot get amp gpio %d (%d)\n", |
|
pd->amp_gpio, ret); |
|
goto err_amp; |
|
} |
|
|
|
/* set the amp off at startup */ |
|
spk_unmute_state(0); |
|
} |
|
|
|
return 0; |
|
|
|
err_amp: |
|
if (pd->amp_gain[0] > 0) { |
|
gpio_free(pd->amp_gain[0]); |
|
gpio_free(pd->amp_gain[1]); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd) |
|
{ |
|
if (pd->amp_gain[0] > 0) { |
|
gpio_free(pd->amp_gain[0]); |
|
gpio_free(pd->amp_gain[1]); |
|
} |
|
|
|
if (pd->amp_gpio > 0) |
|
gpio_free(pd->amp_gpio); |
|
} |
|
|
|
#ifdef CONFIG_PM |
|
static int simtec_audio_resume(struct device *dev) |
|
{ |
|
simtec_call_startup(pdata); |
|
return 0; |
|
} |
|
|
|
const struct dev_pm_ops simtec_audio_pmops = { |
|
.resume = simtec_audio_resume, |
|
}; |
|
EXPORT_SYMBOL_GPL(simtec_audio_pmops); |
|
#endif |
|
|
|
int simtec_audio_core_probe(struct platform_device *pdev, |
|
struct snd_soc_card *card) |
|
{ |
|
struct platform_device *snd_dev; |
|
int ret; |
|
|
|
card->dai_link->ops = &simtec_snd_ops; |
|
card->dai_link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
|
SND_SOC_DAIFMT_CBM_CFM; |
|
|
|
pdata = pdev->dev.platform_data; |
|
if (!pdata) { |
|
dev_err(&pdev->dev, "no platform data supplied\n"); |
|
return -EINVAL; |
|
} |
|
|
|
simtec_call_startup(pdata); |
|
|
|
xtal_clk = clk_get(&pdev->dev, "xtal"); |
|
if (IS_ERR(xtal_clk)) { |
|
dev_err(&pdev->dev, "could not get clkout0\n"); |
|
return -EINVAL; |
|
} |
|
|
|
dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk)); |
|
|
|
ret = attach_gpio_amp(&pdev->dev, pdata); |
|
if (ret) |
|
goto err_clk; |
|
|
|
snd_dev = platform_device_alloc("soc-audio", -1); |
|
if (!snd_dev) { |
|
dev_err(&pdev->dev, "failed to alloc soc-audio device\n"); |
|
ret = -ENOMEM; |
|
goto err_gpio; |
|
} |
|
|
|
platform_set_drvdata(snd_dev, card); |
|
|
|
ret = platform_device_add(snd_dev); |
|
if (ret) { |
|
dev_err(&pdev->dev, "failed to add soc-audio dev\n"); |
|
goto err_pdev; |
|
} |
|
|
|
platform_set_drvdata(pdev, snd_dev); |
|
return 0; |
|
|
|
err_pdev: |
|
platform_device_put(snd_dev); |
|
|
|
err_gpio: |
|
detach_gpio_amp(pdata); |
|
|
|
err_clk: |
|
clk_put(xtal_clk); |
|
return ret; |
|
} |
|
EXPORT_SYMBOL_GPL(simtec_audio_core_probe); |
|
|
|
int simtec_audio_remove(struct platform_device *pdev) |
|
{ |
|
struct platform_device *snd_dev = platform_get_drvdata(pdev); |
|
|
|
platform_device_unregister(snd_dev); |
|
|
|
detach_gpio_amp(pdata); |
|
clk_put(xtal_clk); |
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(simtec_audio_remove); |
|
|
|
MODULE_AUTHOR("Ben Dooks <[email protected]>"); |
|
MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support"); |
|
MODULE_LICENSE("GPL");
|
|
|