Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion sound/soc/codecs/wm8804-i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/acpi.h>

#include "wm8804.h"

Expand Down Expand Up @@ -40,17 +41,29 @@ static const struct i2c_device_id wm8804_i2c_id[] = {
};
MODULE_DEVICE_TABLE(i2c, wm8804_i2c_id);

#if defined(CONFIG_OF)
static const struct of_device_id wm8804_of_match[] = {
{ .compatible = "wlf,wm8804", },
{ }
};
MODULE_DEVICE_TABLE(of, wm8804_of_match);
#endif

#ifdef CONFIG_ACPI
static const struct acpi_device_id wm8804_acpi_match[] = {
{ "1AEC8804", 0 }, /* Wolfson PCI ID + part ID */
{ "10138804", 0 }, /* Cirrus Logic PCI ID + part ID */
{ },
};
MODULE_DEVICE_TABLE(acpi, wm8804_acpi_match);
#endif

static struct i2c_driver wm8804_i2c_driver = {
.driver = {
.name = "wm8804",
.pm = &wm8804_pm,
.of_match_table = wm8804_of_match,
.of_match_table = of_match_ptr(wm8804_of_match),
.acpi_match_table = ACPI_PTR(wm8804_acpi_match),
},
.probe = wm8804_i2c_probe,
.remove = wm8804_i2c_remove,
Expand Down
10 changes: 10 additions & 0 deletions sound/soc/intel/boards/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ config SND_SOC_INTEL_BXT_PCM512x_MACH
Say Y or m if you have such a device. This is a recommended option.
If unsure select "N".

config SND_SOC_INTEL_BXT_WM8804_MACH
tristate "Broxton with Wolfson/Cirrus WM8804 codec for Hifiberry Digi+"
depends on MFD_INTEL_LPSS && I2C && ACPI
select SND_SOC_WM8804_I2C
help
This adds support for ASoC machine driver for Broxton platforms
with Hifiberry Digiplus w/ Wolfson/Cirrus WM8804 I2S audio codec.
Say Y or m if you have such a device. This is a recommended option.
If unsure select "N".

config SND_SOC_INTEL_BXT_TDF8532_MACH
tristate "ASoC Audio driver for BXT with TDF8532 in I2S mode"
depends on X86 && ACPI && I2C
Expand Down
2 changes: 2 additions & 0 deletions sound/soc/intel/boards/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ snd-soc-sst-broadwell-objs := broadwell.o
snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o
snd-soc-sst-bxt-rt298-objs := bxt_rt298.o
snd-soc-sst-bxt-pcm512x-objs := bxt_pcm512x.o
snd-soc-sst-bxt-wm8804-objs := bxt_wm8804.o
snd-soc-sst_bxt_tdf8532-objs := bxt_tdf8532.o
snd-soc-sst-glk-rt5682_max98357a-objs := glk_rt5682_max98357a.o
snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o
Expand All @@ -32,6 +33,7 @@ obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o
obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH) += snd-soc-sst-bxt-da7219_max98357a.o
obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o
obj-$(CONFIG_SND_SOC_INTEL_BXT_PCM512x_MACH) += snd-soc-sst-bxt-pcm512x.o
obj-$(CONFIG_SND_SOC_INTEL_BXT_WM8804_MACH) += snd-soc-sst-bxt-wm8804.o
obj-$(CONFIG_SND_SOC_INTEL_BXT_TDF8532_MACH) += snd-soc-sst_bxt_tdf8532.o
obj-$(CONFIG_SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH) += snd-soc-sst-glk-rt5682_max98357a.o
obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o
Expand Down
263 changes: 263 additions & 0 deletions sound/soc/intel/boards/bxt_wm8804.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0
/*
* bxt-wm8804.c - ASoC machine driver for Up and Up2 board
* based on WM8804/Hifiberry Digi+
* Copyright (c) 2018, Intel Corporation.
*/

#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/platform_sst_audio.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-acpi.h>
#include "../../codecs/wm8804.h"
#include "../atom/sst-atom-controls.h"
#include <linux/gpio/consumer.h>

#include "../../codecs/wm8804.h"

static short int auto_shutdown_output;
module_param(auto_shutdown_output, short, 0660);
MODULE_PARM_DESC(auto_shutdown_output, "Shutdown SP/DIF output if playback is stopped");

#define CLK_44EN_RATE 22579200UL
#define CLK_48EN_RATE 24576000UL

static bool snd_rpi_hifiberry_is_digipro;
static struct gpio_desc *snd_rpi_hifiberry_clk44gpio;
static struct gpio_desc *snd_rpi_hifiberry_clk48gpio;

static int samplerate = 44100;

static uint32_t snd_rpi_hifiberry_digi_enable_clock(int sample_rate)
{
switch (sample_rate) {
case 11025:
case 22050:
case 44100:
case 88200:
case 176400:
gpiod_set_value_cansleep(snd_rpi_hifiberry_clk44gpio, 1);
gpiod_set_value_cansleep(snd_rpi_hifiberry_clk48gpio, 0);
return CLK_44EN_RATE;
default:
gpiod_set_value_cansleep(snd_rpi_hifiberry_clk48gpio, 1);
gpiod_set_value_cansleep(snd_rpi_hifiberry_clk44gpio, 0);
return CLK_48EN_RATE;
}
}

static int snd_rpi_hifiberry_digi_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_component *codec = rtd->codec_dai->component;

/* enable TX output */
snd_soc_component_update_bits(codec, WM8804_PWRDN, 0x4, 0x0);

/* Initialize Digi+ Pro hardware */
if (snd_rpi_hifiberry_is_digipro) {
struct snd_soc_dai_link *dai = rtd->dai_link;

dai->name = "HiFiBerry Digi+ Pro";
dai->stream_name = "HiFiBerry Digi+ Pro HiFi";
}

return 0;
}

static int snd_rpi_hifiberry_digi_startup(struct snd_pcm_substream *substream)
{
/* turn on digital output */
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_component *codec = rtd->codec_dai->component;

snd_soc_component_update_bits(codec, WM8804_PWRDN, 0x3c, 0x00);
return 0;
}

static void snd_rpi_hifiberry_digi_shutdown(struct snd_pcm_substream *substream)
{
/* turn off output */
if (auto_shutdown_output) {
/* turn off output */
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_component *codec = rtd->codec_dai->component;

snd_soc_component_update_bits(codec, WM8804_PWRDN, 0x3c, 0x3c);
}
}

static int snd_rpi_hifiberry_digi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_component *codec = codec_dai->component;

int sysclk = 27000000; /* This is fixed on this board */

long mclk_freq = 0;
int mclk_div = 1;
int sampling_freq = 1;

int ret;

samplerate = params_rate(params);

if (samplerate <= 96000) {
mclk_freq = samplerate * 256;
mclk_div = WM8804_MCLKDIV_256FS;
} else {
mclk_freq = samplerate * 128;
mclk_div = WM8804_MCLKDIV_128FS;
}

if (snd_rpi_hifiberry_is_digipro)
sysclk = snd_rpi_hifiberry_digi_enable_clock(samplerate);

switch (samplerate) {
case 32000:
sampling_freq = 0x03;
break;
case 44100:
sampling_freq = 0x00;
break;
case 48000:
sampling_freq = 0x02;
break;
case 88200:
sampling_freq = 0x08;
break;
case 96000:
sampling_freq = 0x0a;
break;
case 176400:
sampling_freq = 0x0c;
break;
case 192000:
sampling_freq = 0x0e;
break;
default:
dev_err(codec->dev,
"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
samplerate);
}

snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);

ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
sysclk, SND_SOC_CLOCK_OUT);

if (ret < 0) {
dev_err(codec->dev,
"Failed to set WM8804 SYSCLK: %d\n", ret);
return ret;
}

/* Enable TX output */
snd_soc_component_update_bits(codec, WM8804_PWRDN, 0x4, 0x0);

/* Power on */
snd_soc_component_update_bits(codec, WM8804_PWRDN, 0x9, 0);

/* set sampling frequency status bits */
snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f,
sampling_freq);

return 0;
}

/* machine stream operations */
static struct snd_soc_ops snd_rpi_hifiberry_digi_ops = {
.hw_params = snd_rpi_hifiberry_digi_hw_params,
.startup = snd_rpi_hifiberry_digi_startup,
.shutdown = snd_rpi_hifiberry_digi_shutdown,
};

static struct snd_soc_dai_link dailink[] = {
/* CODEC<->CODEC link */
/* back ends */
{
.name = "SSP5-Codec",
.id = 0,
.cpu_dai_name = "SSP5 Pin",
.platform_name = "sof-audio",
.no_pcm = 1,
.codec_dai_name = "wm8804-spdif",
.codec_name = "i2c-1AEC8804:00",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.nonatomic = true,
.dpcm_playback = 1,
.ops = &snd_rpi_hifiberry_digi_ops,
.init = snd_rpi_hifiberry_digi_init,
},
};

/* SoC card */
static struct snd_soc_card bxt_wm8804_card = {
.name = "bxt-wm8804",
.owner = THIS_MODULE,
.dai_link = dailink,
.num_links = ARRAY_SIZE(dailink),
};

/* i2c-<HID>:00 with HID being 8 chars */
static char codec_name[SND_ACPI_I2C_ID_LEN];

static int bxt_wm8804_probe(struct platform_device *pdev)
{
struct snd_soc_card *card;
struct snd_soc_acpi_mach *mach;
const char *i2c_name = NULL;
int dai_index = 0;
int ret_val = 0;
int i;

mach = (&pdev->dev)->platform_data;
card = &bxt_wm8804_card;
card->dev = &pdev->dev;

/* fix index of codec dai */
for (i = 0; i < ARRAY_SIZE(dailink); i++) {
if (!strcmp(dailink[i].codec_name, "i2c-1AEC8804:00")) {
dai_index = i;
break;
}
}

/* fixup codec name based on HID */
i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1);
if (i2c_name) {
snprintf(codec_name, sizeof(codec_name),
"%s%s", "i2c-", i2c_name);
dailink[dai_index].codec_name = codec_name;
}

ret_val = devm_snd_soc_register_card(&pdev->dev, card);
if (ret_val) {
dev_err(&pdev->dev,
"snd_soc_register_card failed %d\n", ret_val);
return ret_val;
}
platform_set_drvdata(pdev, card);
return ret_val;
}

static struct platform_driver bxt_wm8804_driver = {
.driver = {
.name = "bxt-wm8804",
},
.probe = bxt_wm8804_probe,
};
module_platform_driver(bxt_wm8804_driver);

MODULE_DESCRIPTION("ASoC Intel(R) Broxton + WM8804 Machine driver");
MODULE_AUTHOR("Pierre-Louis Bossart");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:bxt-wm8804");