xref: /openbmc/linux/sound/soc/intel/boards/sof_wm8804.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
1  // SPDX-License-Identifier: GPL-2.0-only
2  // Copyright (c) 2018-2020, Intel Corporation
3  //
4  // sof-wm8804.c - ASoC machine driver for Up and Up2 board
5  // based on WM8804/Hifiberry Digi+
6  
7  
8  #include <linux/acpi.h>
9  #include <linux/dmi.h>
10  #include <linux/gpio/consumer.h>
11  #include <linux/gpio/machine.h>
12  #include <linux/module.h>
13  #include <linux/platform_device.h>
14  #include <linux/slab.h>
15  #include <sound/pcm.h>
16  #include <sound/pcm_params.h>
17  #include <sound/soc.h>
18  #include <sound/soc-acpi.h>
19  #include "../../codecs/wm8804.h"
20  
21  struct sof_card_private {
22  	struct gpio_desc *gpio_44;
23  	struct gpio_desc *gpio_48;
24  	int sample_rate;
25  };
26  
27  #define SOF_WM8804_UP2_QUIRK			BIT(0)
28  
29  static unsigned long sof_wm8804_quirk;
30  
sof_wm8804_quirk_cb(const struct dmi_system_id * id)31  static int sof_wm8804_quirk_cb(const struct dmi_system_id *id)
32  {
33  	sof_wm8804_quirk = (unsigned long)id->driver_data;
34  	return 1;
35  }
36  
37  static const struct dmi_system_id sof_wm8804_quirk_table[] = {
38  	{
39  		.callback = sof_wm8804_quirk_cb,
40  		.matches = {
41  			DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
42  			DMI_MATCH(DMI_PRODUCT_NAME, "UP-APL01"),
43  		},
44  		.driver_data = (void *)SOF_WM8804_UP2_QUIRK,
45  	},
46  	{}
47  };
48  
sof_wm8804_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)49  static int sof_wm8804_hw_params(struct snd_pcm_substream *substream,
50  				struct snd_pcm_hw_params *params)
51  {
52  	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
53  	struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
54  	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
55  	struct snd_soc_component *codec = codec_dai->component;
56  	const int sysclk = 27000000; /* This is fixed on this board */
57  	int samplerate;
58  	long mclk_freq;
59  	int mclk_div;
60  	int sampling_freq;
61  	bool clk_44;
62  	int ret;
63  
64  	samplerate = params_rate(params);
65  	if (samplerate == ctx->sample_rate)
66  		return 0;
67  
68  	ctx->sample_rate = 0;
69  
70  	if (samplerate <= 96000) {
71  		mclk_freq = samplerate * 256;
72  		mclk_div = WM8804_MCLKDIV_256FS;
73  	} else {
74  		mclk_freq = samplerate * 128;
75  		mclk_div = WM8804_MCLKDIV_128FS;
76  	}
77  
78  	switch (samplerate) {
79  	case 32000:
80  		sampling_freq = 0x03;
81  		break;
82  	case 44100:
83  		sampling_freq = 0x00;
84  		break;
85  	case 48000:
86  		sampling_freq = 0x02;
87  		break;
88  	case 88200:
89  		sampling_freq = 0x08;
90  		break;
91  	case 96000:
92  		sampling_freq = 0x0a;
93  		break;
94  	case 176400:
95  		sampling_freq = 0x0c;
96  		break;
97  	case 192000:
98  		sampling_freq = 0x0e;
99  		break;
100  	default:
101  		dev_err(rtd->card->dev,
102  			"unsupported samplerate %d\n", samplerate);
103  		return -EINVAL;
104  	}
105  
106  	if (samplerate % 16000)
107  		clk_44 = true; /* use 44.1 kHz root frequency */
108  	else
109  		clk_44 = false;
110  
111  	if (!(IS_ERR_OR_NULL(ctx->gpio_44) ||
112  	      IS_ERR_OR_NULL(ctx->gpio_48))) {
113  		/*
114  		 * ensure both GPIOs are LOW first, then drive the
115  		 * relevant one to HIGH
116  		 */
117  		if (clk_44) {
118  			gpiod_set_value_cansleep(ctx->gpio_48, !clk_44);
119  			gpiod_set_value_cansleep(ctx->gpio_44, clk_44);
120  		} else {
121  			gpiod_set_value_cansleep(ctx->gpio_44, clk_44);
122  			gpiod_set_value_cansleep(ctx->gpio_48, !clk_44);
123  		}
124  	}
125  
126  	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
127  	ret = snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
128  	if (ret < 0) {
129  		dev_err(rtd->card->dev, "Failed to set WM8804 PLL\n");
130  		return ret;
131  	}
132  
133  	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
134  				     sysclk, SND_SOC_CLOCK_OUT);
135  	if (ret < 0) {
136  		dev_err(rtd->card->dev,
137  			"Failed to set WM8804 SYSCLK: %d\n", ret);
138  		return ret;
139  	}
140  
141  	/* set sampling frequency status bits */
142  	snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f,
143  				      sampling_freq);
144  
145  	ctx->sample_rate = samplerate;
146  
147  	return 0;
148  }
149  
150  /* machine stream operations */
151  static struct snd_soc_ops sof_wm8804_ops = {
152  	.hw_params = sof_wm8804_hw_params,
153  };
154  
155  SND_SOC_DAILINK_DEF(ssp5_pin,
156  	DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin")));
157  
158  SND_SOC_DAILINK_DEF(ssp5_codec,
159  	DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif")));
160  
161  SND_SOC_DAILINK_DEF(platform,
162  	DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));
163  
164  static struct snd_soc_dai_link dailink[] = {
165  	/* back ends */
166  	{
167  		.name = "SSP5-Codec",
168  		.id = 0,
169  		.no_pcm = 1,
170  		.dpcm_playback = 1,
171  		.dpcm_capture = 1,
172  		.ops = &sof_wm8804_ops,
173  		SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform),
174  	},
175  };
176  
177  /* SoC card */
178  static struct snd_soc_card sof_wm8804_card = {
179  	.name = "wm8804", /* sof- prefix added automatically */
180  	.owner = THIS_MODULE,
181  	.dai_link = dailink,
182  	.num_links = ARRAY_SIZE(dailink),
183  };
184  
185   /* i2c-<HID>:00 with HID being 8 chars */
186  static char codec_name[SND_ACPI_I2C_ID_LEN];
187  
188  /*
189   * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to
190   * select the clock source. On the Up2 board, this means
191   * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404.
192   *
193   * Using the ACPI device name is not very nice, but since we only use
194   * the value for the Up2 board there is no risk of conflict with other
195   * platforms.
196   */
197  
198  static struct gpiod_lookup_table up2_gpios_table = {
199  	/* .dev_id is set during probe */
200  	.table = {
201  		GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH),
202  		GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH),
203  		{ },
204  	},
205  };
206  
sof_wm8804_probe(struct platform_device * pdev)207  static int sof_wm8804_probe(struct platform_device *pdev)
208  {
209  	struct snd_soc_card *card;
210  	struct snd_soc_acpi_mach *mach;
211  	struct sof_card_private *ctx;
212  	struct acpi_device *adev;
213  	int dai_index = 0;
214  	int ret;
215  	int i;
216  
217  	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
218  	if (!ctx)
219  		return -ENOMEM;
220  
221  	mach = pdev->dev.platform_data;
222  	card = &sof_wm8804_card;
223  	card->dev = &pdev->dev;
224  
225  	dmi_check_system(sof_wm8804_quirk_table);
226  
227  	if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) {
228  		up2_gpios_table.dev_id = dev_name(&pdev->dev);
229  		gpiod_add_lookup_table(&up2_gpios_table);
230  
231  		/*
232  		 * The gpios are required for specific boards with
233  		 * local oscillators, and optional in other cases.
234  		 * Since we can't identify when they are needed, use
235  		 * the GPIO as non-optional
236  		 */
237  
238  		ctx->gpio_44 = devm_gpiod_get(&pdev->dev, "BCM-GPIO5",
239  					      GPIOD_OUT_LOW);
240  		if (IS_ERR(ctx->gpio_44)) {
241  			ret = PTR_ERR(ctx->gpio_44);
242  			dev_err(&pdev->dev,
243  				"could not get BCM-GPIO5: %d\n",
244  				ret);
245  			return ret;
246  		}
247  
248  		ctx->gpio_48 = devm_gpiod_get(&pdev->dev, "BCM-GPIO6",
249  					      GPIOD_OUT_LOW);
250  		if (IS_ERR(ctx->gpio_48)) {
251  			ret = PTR_ERR(ctx->gpio_48);
252  			dev_err(&pdev->dev,
253  				"could not get BCM-GPIO6: %d\n",
254  				ret);
255  			return ret;
256  		}
257  	}
258  
259  	/* fix index of codec dai */
260  	for (i = 0; i < ARRAY_SIZE(dailink); i++) {
261  		if (!strcmp(dailink[i].codecs->name, "i2c-1AEC8804:00")) {
262  			dai_index = i;
263  			break;
264  		}
265  	}
266  
267  	/* fixup codec name based on HID */
268  	adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
269  	if (adev) {
270  		snprintf(codec_name, sizeof(codec_name),
271  			 "%s%s", "i2c-", acpi_dev_name(adev));
272  		dailink[dai_index].codecs->name = codec_name;
273  	}
274  	acpi_dev_put(adev);
275  
276  	snd_soc_card_set_drvdata(card, ctx);
277  
278  	return devm_snd_soc_register_card(&pdev->dev, card);
279  }
280  
sof_wm8804_remove(struct platform_device * pdev)281  static void sof_wm8804_remove(struct platform_device *pdev)
282  {
283  	if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK)
284  		gpiod_remove_lookup_table(&up2_gpios_table);
285  }
286  
287  static struct platform_driver sof_wm8804_driver = {
288  	.driver = {
289  		.name = "sof-wm8804",
290  		.pm = &snd_soc_pm_ops,
291  	},
292  	.probe = sof_wm8804_probe,
293  	.remove_new = sof_wm8804_remove,
294  };
295  module_platform_driver(sof_wm8804_driver);
296  
297  MODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver");
298  MODULE_AUTHOR("Pierre-Louis Bossart");
299  MODULE_LICENSE("GPL v2");
300  MODULE_ALIAS("platform:sof-wm8804");
301