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.
205 lines
4.9 KiB
205 lines
4.9 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// |
|
// ALSA SoC driver for Migo-R |
|
// |
|
// Copyright (C) 2009-2010 Guennadi Liakhovetski <[email protected]> |
|
|
|
#include <linux/clkdev.h> |
|
#include <linux/device.h> |
|
#include <linux/firmware.h> |
|
#include <linux/module.h> |
|
|
|
#include <asm/clock.h> |
|
|
|
#include <cpu/sh7722.h> |
|
|
|
#include <sound/core.h> |
|
#include <sound/pcm.h> |
|
#include <sound/soc.h> |
|
|
|
#include "../codecs/wm8978.h" |
|
#include "siu.h" |
|
|
|
/* Default 8000Hz sampling frequency */ |
|
static unsigned long codec_freq = 8000 * 512; |
|
|
|
static unsigned int use_count; |
|
|
|
/* External clock, sourced from the codec at the SIUMCKB pin */ |
|
static unsigned long siumckb_recalc(struct clk *clk) |
|
{ |
|
return codec_freq; |
|
} |
|
|
|
static struct sh_clk_ops siumckb_clk_ops = { |
|
.recalc = siumckb_recalc, |
|
}; |
|
|
|
static struct clk siumckb_clk = { |
|
.ops = &siumckb_clk_ops, |
|
.rate = 0, /* initialised at run-time */ |
|
}; |
|
|
|
static struct clk_lookup *siumckb_lookup; |
|
|
|
static int migor_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); |
|
int ret; |
|
unsigned int rate = params_rate(params); |
|
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000, |
|
SND_SOC_CLOCK_IN); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512); |
|
if (ret < 0) |
|
return ret; |
|
|
|
codec_freq = rate * 512; |
|
/* |
|
* This propagates the parent frequency change to children and |
|
* recalculates the frequency table |
|
*/ |
|
clk_set_rate(&siumckb_clk, codec_freq); |
|
dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq); |
|
|
|
ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), SIU_CLKB_EXT, |
|
codec_freq / 2, SND_SOC_CLOCK_IN); |
|
|
|
if (!ret) |
|
use_count++; |
|
|
|
return ret; |
|
} |
|
|
|
static int migor_hw_free(struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
|
|
|
if (use_count) { |
|
use_count--; |
|
|
|
if (!use_count) |
|
snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0, |
|
SND_SOC_CLOCK_IN); |
|
} else { |
|
dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n"); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static const struct snd_soc_ops migor_dai_ops = { |
|
.hw_params = migor_hw_params, |
|
.hw_free = migor_hw_free, |
|
}; |
|
|
|
static const struct snd_soc_dapm_widget migor_dapm_widgets[] = { |
|
SND_SOC_DAPM_HP("Headphone", NULL), |
|
SND_SOC_DAPM_MIC("Onboard Microphone", NULL), |
|
SND_SOC_DAPM_MIC("External Microphone", NULL), |
|
}; |
|
|
|
static const struct snd_soc_dapm_route audio_map[] = { |
|
/* Headphone output connected to LHP/RHP, enable OUT4 for VMID */ |
|
{ "Headphone", NULL, "OUT4 VMID" }, |
|
{ "OUT4 VMID", NULL, "LHP" }, |
|
{ "OUT4 VMID", NULL, "RHP" }, |
|
|
|
/* On-board microphone */ |
|
{ "RMICN", NULL, "Mic Bias" }, |
|
{ "RMICP", NULL, "Mic Bias" }, |
|
{ "Mic Bias", NULL, "Onboard Microphone" }, |
|
|
|
/* External microphone */ |
|
{ "LMICN", NULL, "Mic Bias" }, |
|
{ "LMICP", NULL, "Mic Bias" }, |
|
{ "Mic Bias", NULL, "External Microphone" }, |
|
}; |
|
|
|
/* migor digital audio interface glue - connects codec <--> CPU */ |
|
SND_SOC_DAILINK_DEFS(wm8978, |
|
DAILINK_COMP_ARRAY(COMP_CPU("siu-pcm-audio")), |
|
DAILINK_COMP_ARRAY(COMP_CODEC("wm8978.0-001a", "wm8978-hifi")), |
|
DAILINK_COMP_ARRAY(COMP_PLATFORM("siu-pcm-audio"))); |
|
|
|
static struct snd_soc_dai_link migor_dai = { |
|
.name = "wm8978", |
|
.stream_name = "WM8978", |
|
.dai_fmt = SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_I2S | |
|
SND_SOC_DAIFMT_CBS_CFS, |
|
.ops = &migor_dai_ops, |
|
SND_SOC_DAILINK_REG(wm8978), |
|
}; |
|
|
|
/* migor audio machine driver */ |
|
static struct snd_soc_card snd_soc_migor = { |
|
.name = "Migo-R", |
|
.owner = THIS_MODULE, |
|
.dai_link = &migor_dai, |
|
.num_links = 1, |
|
|
|
.dapm_widgets = migor_dapm_widgets, |
|
.num_dapm_widgets = ARRAY_SIZE(migor_dapm_widgets), |
|
.dapm_routes = audio_map, |
|
.num_dapm_routes = ARRAY_SIZE(audio_map), |
|
}; |
|
|
|
static struct platform_device *migor_snd_device; |
|
|
|
static int __init migor_init(void) |
|
{ |
|
int ret; |
|
|
|
ret = clk_register(&siumckb_clk); |
|
if (ret < 0) |
|
return ret; |
|
|
|
siumckb_lookup = clkdev_create(&siumckb_clk, "siumckb_clk", NULL); |
|
if (!siumckb_lookup) { |
|
ret = -ENOMEM; |
|
goto eclkdevalloc; |
|
} |
|
|
|
/* Port number used on this machine: port B */ |
|
migor_snd_device = platform_device_alloc("soc-audio", 1); |
|
if (!migor_snd_device) { |
|
ret = -ENOMEM; |
|
goto epdevalloc; |
|
} |
|
|
|
platform_set_drvdata(migor_snd_device, &snd_soc_migor); |
|
|
|
ret = platform_device_add(migor_snd_device); |
|
if (ret) |
|
goto epdevadd; |
|
|
|
return 0; |
|
|
|
epdevadd: |
|
platform_device_put(migor_snd_device); |
|
epdevalloc: |
|
clkdev_drop(siumckb_lookup); |
|
eclkdevalloc: |
|
clk_unregister(&siumckb_clk); |
|
return ret; |
|
} |
|
|
|
static void __exit migor_exit(void) |
|
{ |
|
clkdev_drop(siumckb_lookup); |
|
clk_unregister(&siumckb_clk); |
|
platform_device_unregister(migor_snd_device); |
|
} |
|
|
|
module_init(migor_init); |
|
module_exit(migor_exit); |
|
|
|
MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>"); |
|
MODULE_DESCRIPTION("ALSA SoC Migor"); |
|
MODULE_LICENSE("GPL v2");
|
|
|