From e740bd2cc3fcd632fcd6c8881b1fc671bcde5914 Mon Sep 17 00:00:00 2001 From: dev-3Dlab <45081440+dev-3Dlab@users.noreply.github.com> Date: Wed, 5 Dec 2018 10:59:11 +0100 Subject: [PATCH] ASoC: add driver for 3Dlab Nano soundcard (#2758) Signed-off-by: GT --- .../overlays/3dlab-nano-player-overlay.dts | 32 ++ arch/arm/boot/dts/overlays/Makefile | 1 + arch/arm/boot/dts/overlays/README | 6 + sound/soc/bcm/3dlab-nano-player.c | 370 ++++++++++++++++++ sound/soc/bcm/Kconfig | 6 + sound/soc/bcm/Makefile | 2 + 8 files changed, 419 insertions(+) create mode 100644 arch/arm/boot/dts/overlays/3dlab-nano-player-overlay.dts create mode 100644 sound/soc/bcm/3dlab-nano-player.c --- /dev/null +++ b/arch/arm/boot/dts/overlays/3dlab-nano-player-overlay.dts @@ -0,0 +1,32 @@ +// Definitions for 3Dlab Nano Player +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2708"; + + fragment@0 { + target = <&i2s>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + nano-player@41 { + compatible = "3dlab,nano-player"; + reg = <0x41>; + i2s-controller = <&i2s>; + status = "okay"; + }; + }; + }; +}; + +// EOF --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -1,6 +1,7 @@ # Overlays for the Raspberry Pi platform dtbo-$(CONFIG_ARCH_BCM2835) += \ + 3dlab-nano-player.dtbo \ adau1977-adc.dtbo \ adau7002-simple.dtbo \ ads1015.dtbo \ --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -199,6 +199,12 @@ Params: and the other i2c baudrate parameters. +Name: 3dlab-nano-player +Info: Configures the 3Dlab Nano Player +Load: dtoverlay=3dlab-nano-player +Params: + + Name: adau1977-adc Info: Overlay for activation of ADAU1977 ADC codec over I2C for control and I2S for data. --- /dev/null +++ b/sound/soc/bcm/3dlab-nano-player.c @@ -0,0 +1,370 @@ +/* + * 3Dlab Nano Player ALSA SoC Audio driver. + * + * Copyright (C) 2018 3Dlab. + * + * Author: GT + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define NANO_ID 0x00 +#define NANO_VER 0x01 +#define NANO_CFG 0x02 +#define NANO_STATUS 0x03 +#define NANO_SPI_ADDR 0x04 +#define NANO_SPI_DATA 0x05 + +#define NANO_ID_VAL 0x3D +#define NANO_CFG_OFF 0x00 +#define NANO_CFG_MULT1 0 +#define NANO_CFG_MULT2 1 +#define NANO_CFG_MULT4 2 +#define NANO_CFG_MULT8 3 +#define NANO_CFG_MULT16 4 +#define NANO_CFG_CLK22 0 +#define NANO_CFG_CLK24 BIT(3) +#define NANO_CFG_DSD BIT(4) +#define NANO_CFG_ENA BIT(5) +#define NANO_CFG_BLINK BIT(6) +#define NANO_STATUS_P1 BIT(0) +#define NANO_STATUS_P2 BIT(1) +#define NANO_STATUS_FLG BIT(2) +#define NANO_STATUS_CLK BIT(3) +#define NANO_SPI_READ 0 +#define NANO_SPI_WRITE BIT(5) + +#define NANO_DAC_CTRL1 0x00 +#define NANO_DAC_CTRL2 0x01 +#define NANO_DAC_CTRL3 0x02 +#define NANO_DAC_LATT 0x03 +#define NANO_DAC_RATT 0x04 + +#define NANO_CTRL2_VAL 0x22 + +static int nano_player_spi_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + /* indirect register access */ + regmap_write(map, NANO_SPI_DATA, val); + regmap_write(map, NANO_SPI_ADDR, reg | NANO_SPI_WRITE); + return 0; +} + +static int nano_player_ctrl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + /* describe control element */ + if (strstr(kcontrol->id.name, "Volume")) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + } else { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + } + + return 0; +} + +static int nano_player_ctrl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* program control value to hardware */ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct regmap *regmap = snd_soc_card_get_drvdata(card); + + if (strstr(kcontrol->id.name, "Volume")) { + unsigned int vol = ucontrol->value.integer.value[0]; + unsigned int att = 255 - (2 * (100 - vol)); + + nano_player_spi_write(regmap, NANO_DAC_LATT, att); + nano_player_spi_write(regmap, NANO_DAC_RATT, att); + kcontrol->private_value = vol; + } else { + unsigned int mute = ucontrol->value.integer.value[0]; + unsigned int reg = NANO_CTRL2_VAL | mute; + + nano_player_spi_write(regmap, NANO_DAC_CTRL2, reg); + kcontrol->private_value = mute; + } + return 0; +} + +static int nano_player_ctrl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* return last programmed value */ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +#define SOC_NANO_PLAYER_CTRL(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = nano_player_ctrl_info, \ + .put = nano_player_ctrl_put, \ + .get = nano_player_ctrl_get } + +static const struct snd_kcontrol_new nano_player_controls[] = { + SOC_NANO_PLAYER_CTRL("Master Playback Volume"), + SOC_NANO_PLAYER_CTRL("Master Playback Switch"), +}; + +static const unsigned int nano_player_rates[] = { + 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000, + 705600, 768000 /* only possible with fast clocks */ +}; + +static struct snd_pcm_hw_constraint_list nano_player_constraint_rates = { + .list = nano_player_rates, + .count = ARRAY_SIZE(nano_player_rates), +}; + +static int nano_player_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct regmap *regmap = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_stream *cpu = &rtd->cpu_dai->driver->playback; + struct snd_soc_pcm_stream *codec = &rtd->codec_dai->driver->playback; + unsigned int sample_bits = 32; + unsigned int val; + + /* configure cpu dai */ + cpu->formats |= SNDRV_PCM_FMTBIT_DSD_U32_LE; + cpu->rate_max = 768000; + + /* configure dummy codec dai */ + codec->rate_min = 44100; + codec->rates = SNDRV_PCM_RATE_KNOT; + codec->formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_DSD_U32_LE; + + /* configure max supported rate */ + regmap_read(regmap, NANO_STATUS, &val); + if (val & NANO_STATUS_CLK) { + dev_notice(card->dev, "Board with fast clocks installed\n"); + codec->rate_max = 768000; + } else { + dev_notice(card->dev, "Board with normal clocks installed\n"); + codec->rate_max = 384000; + } + + /* frame length enforced by hardware */ + return snd_soc_dai_set_bclk_ratio(rtd->cpu_dai, sample_bits * 2); +} + +static int nano_player_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &nano_player_constraint_rates); +} + +static int nano_player_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_card *card = rtd->card; + struct regmap *regmap = snd_soc_card_get_drvdata(card); + unsigned int config = NANO_CFG_ENA; + struct snd_mask *fmt; + + /* configure PCM or DSD */ + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_test(fmt, SNDRV_PCM_FORMAT_DSD_U32_LE)) { + /* embed DSD in PCM data */ + snd_mask_none(fmt); + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S32_LE); + /* enable DSD mode */ + config |= NANO_CFG_DSD; + } + + /* configure clocks */ + switch (params_rate(params)) { + case 44100: + config |= NANO_CFG_MULT1 | NANO_CFG_CLK22; + break; + case 88200: + config |= NANO_CFG_MULT2 | NANO_CFG_CLK22; + break; + case 176400: + config |= NANO_CFG_MULT4 | NANO_CFG_CLK22; + break; + case 352800: + config |= NANO_CFG_MULT8 | NANO_CFG_CLK22; + break; + case 705600: + config |= NANO_CFG_MULT16 | NANO_CFG_CLK22; + break; + case 48000: + config |= NANO_CFG_MULT1 | NANO_CFG_CLK24; + break; + case 96000: + config |= NANO_CFG_MULT2 | NANO_CFG_CLK24; + break; + case 192000: + config |= NANO_CFG_MULT4 | NANO_CFG_CLK24; + break; + case 384000: + config |= NANO_CFG_MULT8 | NANO_CFG_CLK24; + break; + case 768000: + config |= NANO_CFG_MULT16 | NANO_CFG_CLK24; + break; + default: + return -EINVAL; + } + + dev_dbg(card->dev, "Send CFG register 0x%02X\n", config); + return regmap_write(regmap, NANO_CFG, config); +} + +static struct snd_soc_ops nano_player_ops = { + .startup = nano_player_startup, + .hw_params = nano_player_hw_params, +}; + +static struct snd_soc_dai_link nano_player_link = { + .name = "3Dlab Nano Player", + .stream_name = "3Dlab Nano Player HiFi", + .platform_name = "bcm2708-i2s.0", + .cpu_dai_name = "bcm2708-i2s.0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CONT | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .init = nano_player_init, + .ops = &nano_player_ops, +}; + +static const struct regmap_config nano_player_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 128, + .cache_type = REGCACHE_RBTREE, +}; + +static int nano_player_card_probe(struct snd_soc_card *card) +{ + struct regmap *regmap = snd_soc_card_get_drvdata(card); + unsigned int val; + + /* check hardware integrity */ + regmap_read(regmap, NANO_ID, &val); + if (val != NANO_ID_VAL) { + dev_err(card->dev, "Invalid ID register 0x%02X\n", val); + return -ENODEV; + } + + /* report version to the user */ + regmap_read(regmap, NANO_VER, &val); + dev_notice(card->dev, "Started 3Dlab Nano Player driver (v%d)\n", val); + + /* enable internal audio bus and blink status LED */ + return regmap_write(regmap, NANO_CFG, NANO_CFG_ENA | NANO_CFG_BLINK); +} + +static int nano_player_card_remove(struct snd_soc_card *card) +{ + /* disable internal audio bus */ + struct regmap *regmap = snd_soc_card_get_drvdata(card); + + return regmap_write(regmap, NANO_CFG, NANO_CFG_OFF); +} + +static struct snd_soc_card nano_player_card = { + .name = "3Dlab_Nano_Player", + .owner = THIS_MODULE, + .dai_link = &nano_player_link, + .num_links = 1, + .controls = nano_player_controls, + .num_controls = ARRAY_SIZE(nano_player_controls), + .probe = nano_player_card_probe, + .remove = nano_player_card_remove, +}; + +static int nano_player_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &nano_player_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "Failed to init regmap %d\n", ret); + return ret; + } + + if (i2c->dev.of_node) { + struct snd_soc_dai_link *dai = &nano_player_link; + struct device_node *node; + + /* cpu handle configured by device tree */ + node = of_parse_phandle(i2c->dev.of_node, "i2s-controller", 0); + if (node) { + dai->platform_name = NULL; + dai->platform_of_node = node; + dai->cpu_dai_name = NULL; + dai->cpu_of_node = node; + } + } + + nano_player_card.dev = &i2c->dev; + snd_soc_card_set_drvdata(&nano_player_card, regmap); + ret = devm_snd_soc_register_card(&i2c->dev, &nano_player_card); + + if (ret && ret != -EPROBE_DEFER) + dev_err(&i2c->dev, "Failed to register card %d\n", ret); + + return ret; +} + +static const struct of_device_id nano_player_of_match[] = { + { .compatible = "3dlab,nano-player", }, + { } +}; +MODULE_DEVICE_TABLE(of, nano_player_of_match); + +static const struct i2c_device_id nano_player_i2c_id[] = { + { "nano-player", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nano_player_i2c_id); + +static struct i2c_driver nano_player_i2c_driver = { + .probe = nano_player_i2c_probe, + .id_table = nano_player_i2c_id, + .driver = { + .name = "nano-player", + .owner = THIS_MODULE, + .of_match_table = nano_player_of_match, + }, +}; + +module_i2c_driver(nano_player_i2c_driver); + +MODULE_DESCRIPTION("ASoC 3Dlab Nano Player driver"); +MODULE_AUTHOR("GT "); +MODULE_LICENSE("GPL v2"); + +/* EOF */ --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -17,6 +17,12 @@ config SND_SOC_CYGNUS If you don't know what to do here, say N. +config SND_BCM2708_SOC_3DLAB_NANO_PLAYER + tristate "Support for 3Dlab Nano Player" + depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S + help + Say Y or M if you want to add support for 3Dlab Nano Player. + config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD tristate "Support for Google voiceHAT soundcard" depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc- snd-soc-googlevoicehat-codec-objs := googlevoicehat-codec.o # BCM2708 Machine Support +snd-soc-3dlab-nano-player-objs := 3dlab-nano-player.o snd-soc-hifiberry-dacplus-objs := hifiberry_dacplus.o snd-soc-justboom-dac-objs := justboom-dac.o snd-soc-rpi-cirrus-objs := rpi-cirrus.o @@ -31,6 +32,7 @@ snd-soc-fe-pi-audio-objs := fe-pi-audio. snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o +obj-$(CONFIG_SND_BCM2708_SOC_3DLAB_NANO_PLAYER) += snd-soc-3dlab-nano-player.o obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD) += snd-soc-googlevoicehat-codec.o obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o