305 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2// Copyright (c) 2018-2020, Intel Corporation3//4// sof-wm8804.c - ASoC machine driver for Up and Up2 board5// 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 21struct 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 29static unsigned long sof_wm8804_quirk;30 31static 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 37static 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 49static 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 = snd_soc_substream_to_rtd(substream);53 struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);54 struct snd_soc_dai *codec_dai = snd_soc_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 else109 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 the115 * relevant one to HIGH116 */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 */151static const struct snd_soc_ops sof_wm8804_ops = {152 .hw_params = sof_wm8804_hw_params,153};154 155SND_SOC_DAILINK_DEF(ssp5_pin,156 DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin")));157 158SND_SOC_DAILINK_DEF(ssp5_codec,159 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif")));160 161SND_SOC_DAILINK_DEF(platform,162 DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));163 164static 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 */178static 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 */186static char codec_name[SND_ACPI_I2C_ID_LEN];187 188/*189 * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to190 * select the clock source. On the Up2 board, this means191 * 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 use194 * the value for the Up2 board there is no risk of conflict with other195 * platforms.196 */197 198static 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 207static 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 with233 * local oscillators, and optional in other cases.234 * Since we can't identify when they are needed, use235 * the GPIO as non-optional236 */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 } else {274 dev_err(&pdev->dev, "Error cannot find '%s' dev\n", mach->id);275 return -ENOENT;276 }277 278 acpi_dev_put(adev);279 280 snd_soc_card_set_drvdata(card, ctx);281 282 return devm_snd_soc_register_card(&pdev->dev, card);283}284 285static void sof_wm8804_remove(struct platform_device *pdev)286{287 if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK)288 gpiod_remove_lookup_table(&up2_gpios_table);289}290 291static struct platform_driver sof_wm8804_driver = {292 .driver = {293 .name = "sof-wm8804",294 .pm = &snd_soc_pm_ops,295 },296 .probe = sof_wm8804_probe,297 .remove = sof_wm8804_remove,298};299module_platform_driver(sof_wm8804_driver);300 301MODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver");302MODULE_AUTHOR("Pierre-Louis Bossart");303MODULE_LICENSE("GPL v2");304MODULE_ALIAS("platform:sof-wm8804");305